support word count

This commit is contained in:
Le Tan 2022-08-24 20:16:46 +08:00
parent 2a91577521
commit 791a5da245
22 changed files with 349 additions and 21 deletions

View File

@ -62,6 +62,8 @@ namespace vnotex
Debug, Debug,
Print, Print,
ClearHighlights, ClearHighlights,
WordCount,
Attachment,
MaxShortcut MaxShortcut
}; };
Q_ENUM(Shortcut) Q_ENUM(Shortcut)

View File

@ -43,6 +43,7 @@
<file>icons/attachment_editor.svg</file> <file>icons/attachment_editor.svg</file>
<file>icons/attachment_full_editor.svg</file> <file>icons/attachment_full_editor.svg</file>
<file>icons/tag_editor.svg</file> <file>icons/tag_editor.svg</file>
<file>icons/word_count_editor.svg</file>
<file>icons/split_menu.svg</file> <file>icons/split_menu.svg</file>
<file>icons/split_window_list.svg</file> <file>icons/split_window_list.svg</file>
<file>icons/type_heading_editor.svg</file> <file>icons/type_heading_editor.svg</file>

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="24" cy="24" r="20" fill="none" stroke="#000000" stroke-width="4"/><path d="M32 16H16" stroke="#000000" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M24 34V16" stroke="#000000" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@ -218,6 +218,8 @@
"FindPrevious" : "Shift+F3", "FindPrevious" : "Shift+F3",
"ApplySnippet" : "Ctrl+G, I", "ApplySnippet" : "Ctrl+G, I",
"Tag" : "Ctrl+G, B", "Tag" : "Ctrl+G, B",
"Attachment" : "",
"WordCount" : "",
"Debug" : "F12", "Debug" : "F12",
"Print" : "", "Print" : "",
"ClearHighlights" : "Ctrl+G, Space" "ClearHighlights" : "Ctrl+G, Space"

View File

@ -0,0 +1,34 @@
#include "buttonpopup.h"
#include <QVBoxLayout>
#include <utils/widgetutils.h>
using namespace vnotex;
ButtonPopup::ButtonPopup(QToolButton *p_btn, QWidget *p_parent)
: QMenu(p_parent),
m_button(p_btn)
{
setupUI();
#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
// Qt::Popup on macOS does not work well with input method.
setWindowFlags(Qt::Tool | Qt::NoDropShadowWindowHint);
setWindowModality(Qt::ApplicationModal);
#endif
}
void ButtonPopup::setupUI()
{
auto mainLayout = new QVBoxLayout(this);
WidgetUtils::setContentsMargins(mainLayout);
}
void ButtonPopup::setCentralWidget(QWidget *p_widget)
{
Q_ASSERT(p_widget);
auto mainLayout = layout();
Q_ASSERT(mainLayout->count() == 0);
mainLayout->addWidget(p_widget);
}

28
src/widgets/buttonpopup.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef BUTTONPOPUP_H
#define BUTTONPOPUP_H
#include <QMenu>
class QToolButton;
namespace vnotex
{
// Base class for the popup of a QToolButton.
class ButtonPopup : public QMenu
{
Q_OBJECT
public:
ButtonPopup(QToolButton *p_btn, QWidget *p_parent = nullptr);
protected:
void setCentralWidget(QWidget *p_widget);
private:
void setupUI();
// Button for this menu.
QToolButton *m_button = nullptr;
};
}
#endif // BUTTONPOPUP_H

View File

@ -435,3 +435,10 @@ void MarkdownViewer::crossCopy(const QString &p_target, const QString &p_baseUrl
{ {
emit m_adapter->crossCopyRequested(0, 0, p_target, p_baseUrl, p_html); emit m_adapter->crossCopyRequested(0, 0, p_target, p_baseUrl, p_html);
} }
void MarkdownViewer::saveContent(const std::function<void(const QString &p_content)> &p_callback)
{
page()->runJavaScript("document.getElementById('vx-content').textContent", [p_callback](const QVariant &v) {
p_callback(v.toString());
});
}

View File

@ -31,6 +31,8 @@ namespace vnotex
void setPreviewHelper(PreviewHelper *p_previewHelper); void setPreviewHelper(PreviewHelper *p_previewHelper);
void saveContent(const std::function<void(const QString &p_content)> &p_callback);
signals: signals:
void zoomFactorChanged(qreal p_factor); void zoomFactorChanged(qreal p_factor);

View File

@ -273,6 +273,7 @@ void MarkdownViewWindow::setupToolBar()
addAction(toolBar, ViewWindowToolBarHelper::EditReadDiscard); addAction(toolBar, ViewWindowToolBarHelper::EditReadDiscard);
addAction(toolBar, ViewWindowToolBarHelper::Save); addAction(toolBar, ViewWindowToolBarHelper::Save);
addAction(toolBar, ViewWindowToolBarHelper::ViewMode); addAction(toolBar, ViewWindowToolBarHelper::ViewMode);
addAction(toolBar, ViewWindowToolBarHelper::WordCount);
toolBar->addSeparator(); toolBar->addSeparator();
@ -1465,3 +1466,39 @@ void MarkdownViewWindow::updateEditorFromConfig()
m_editor->setLeaderKeyToSkip(leaderKey.m_key, leaderKey.m_modifiers); m_editor->setLeaderKeyToSkip(leaderKey.m_key, leaderKey.m_modifiers);
} }
} }
void MarkdownViewWindow::fetchWordCountInfo(const std::function<void(const WordCountInfo &)> &p_callback) const
{
auto text = selectedText();
if (!text.isEmpty()) {
auto info = TextViewWindowHelper::calculateWordCountInfo(text);
info.m_isSelection = true;
p_callback(info);
}
switch (m_mode) {
case ViewWindowMode::Read:
{
Q_ASSERT(m_viewer);
m_viewer->saveContent([p_callback](const QString &content) {
auto info = TextViewWindowHelper::calculateWordCountInfo(content);
info.m_isSelection = false;
p_callback(info);
});
break;
}
case ViewWindowMode::Edit:
{
Q_ASSERT(m_editor);
auto info = TextViewWindowHelper::calculateWordCountInfo(m_editor->getText());
info.m_isSelection = false;
p_callback(info);
break;
}
default:
p_callback(WordCountInfo());
break;
}
}

View File

@ -56,6 +56,8 @@ namespace vnotex
void applySnippet() Q_DECL_OVERRIDE; void applySnippet() Q_DECL_OVERRIDE;
void fetchWordCountInfo(const std::function<void(const WordCountInfo &)> &p_callback) const Q_DECL_OVERRIDE;
public slots: public slots:
void handleEditorConfigChange() Q_DECL_OVERRIDE; void handleEditorConfigChange() Q_DECL_OVERRIDE;

View File

@ -11,17 +11,10 @@
using namespace vnotex; using namespace vnotex;
TagPopup::TagPopup(QToolButton *p_btn, QWidget *p_parent) TagPopup::TagPopup(QToolButton *p_btn, QWidget *p_parent)
: QMenu(p_parent), : ButtonPopup(p_btn, p_parent)
m_button(p_btn)
{ {
setupUI(); setupUI();
#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
// Qt::Popup on macOS does not work well with input method.
setWindowFlags(Qt::Tool | Qt::NoDropShadowWindowHint);
setWindowModality(Qt::ApplicationModal);
#endif
connect(this, &QMenu::aboutToShow, connect(this, &QMenu::aboutToShow,
this, [this]() { this, [this]() {
m_tagViewer->setNode(m_buffer ? m_buffer->getNode() : nullptr); m_tagViewer->setNode(m_buffer ? m_buffer->getNode() : nullptr);
@ -36,11 +29,8 @@ TagPopup::TagPopup(QToolButton *p_btn, QWidget *p_parent)
void TagPopup::setupUI() void TagPopup::setupUI()
{ {
auto mainLayout = new QVBoxLayout(this);
WidgetUtils::setContentsMargins(mainLayout);
m_tagViewer = new TagViewer(true, this); m_tagViewer = new TagViewer(true, this);
mainLayout->addWidget(m_tagViewer); setCentralWidget(m_tagViewer);
setMinimumSize(256, 320); setMinimumSize(256, 320);
} }

View File

@ -1,7 +1,7 @@
#ifndef TAGPOPUP_H #ifndef TAGPOPUP_H
#define TAGPOPUP_H #define TAGPOPUP_H
#include <QMenu> #include "buttonpopup.h"
class QToolButton; class QToolButton;
@ -10,7 +10,7 @@ namespace vnotex
class Buffer; class Buffer;
class TagViewer; class TagViewer;
class TagPopup : public QMenu class TagPopup : public ButtonPopup
{ {
Q_OBJECT Q_OBJECT
public: public:
@ -23,9 +23,6 @@ namespace vnotex
Buffer *m_buffer = nullptr; Buffer *m_buffer = nullptr;
// Button for this menu.
QToolButton *m_button = nullptr;
// Managed by QObject. // Managed by QObject.
TagViewer *m_tagViewer = nullptr; TagViewer *m_tagViewer = nullptr;
}; };

View File

@ -66,6 +66,7 @@ void TextViewWindow::setupToolBar()
addToolBar(toolBar); addToolBar(toolBar);
addAction(toolBar, ViewWindowToolBarHelper::Save); addAction(toolBar, ViewWindowToolBarHelper::Save);
addAction(toolBar, ViewWindowToolBarHelper::WordCount);
toolBar->addSeparator(); toolBar->addSeparator();
@ -322,3 +323,18 @@ void TextViewWindow::clearHighlights()
{ {
TextViewWindowHelper::clearSearchHighlights(this); TextViewWindowHelper::clearSearchHighlights(this);
} }
void TextViewWindow::fetchWordCountInfo(const std::function<void(const WordCountInfo &)> &p_callback) const
{
auto text = selectedText();
if (text.isEmpty()) {
text = getLatestContent();
auto info = TextViewWindowHelper::calculateWordCountInfo(text);
info.m_isSelection = false;
p_callback(info);
} else {
auto info = TextViewWindowHelper::calculateWordCountInfo(text);
info.m_isSelection = true;
p_callback(info);
}
}

View File

@ -37,6 +37,8 @@ namespace vnotex
void applySnippet() Q_DECL_OVERRIDE; void applySnippet() Q_DECL_OVERRIDE;
void fetchWordCountInfo(const std::function<void(const WordCountInfo &)> &p_callback) const Q_DECL_OVERRIDE;
public slots: public slots:
void handleEditorConfigChange() Q_DECL_OVERRIDE; void handleEditorConfigChange() Q_DECL_OVERRIDE;

View File

@ -325,6 +325,47 @@ namespace vnotex
const auto result = p_win->m_editor->findText(patterns.first, toEditorFindFlags(patterns.second), 0, -1, p_currentMatchLine); const auto result = p_win->m_editor->findText(patterns.first, toEditorFindFlags(patterns.second), 0, -1, p_currentMatchLine);
p_win->showFindResult(patterns.first, result.m_totalMatches, result.m_currentMatchIndex); p_win->showFindResult(patterns.first, result.m_totalMatches, result.m_currentMatchIndex);
} }
static ViewWindow::WordCountInfo calculateWordCountInfo(const QString &p_text)
{
ViewWindow::WordCountInfo info;
// Char without spaces.
int cns = 0;
int wc = 0;
// Remove th ending new line.
int cc = p_text.size();
// 0 - not in word;
// 1 - in English word;
// 2 - in non-English word;
int state = 0;
for (int i = 0; i < cc; ++i) {
QChar ch = p_text[i];
if (ch.isSpace()) {
if (state) {
state = 0;
}
continue;
} else if (ch.unicode() < 128) {
if (state != 1) {
state = 1;
++wc;
}
} else {
state = 2;
++wc;
}
++cns;
}
info.m_wordCount = wc;
info.m_charWithoutSpaceCount = cns;
info.m_charWithSpaceCount = cc;
return info;
}
}; };
} }

View File

@ -456,6 +456,12 @@ QAction *ViewWindow::addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Act
break; break;
} }
case ViewWindowToolBarHelper::WordCount:
{
act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action);
break;
}
case ViewWindowToolBarHelper::Outline: case ViewWindowToolBarHelper::Outline:
{ {
act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action); act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action);

View File

@ -40,6 +40,14 @@ namespace vnotex
}; };
Q_DECLARE_FLAGS(WindowFlags, WindowFlag); Q_DECLARE_FLAGS(WindowFlags, WindowFlag);
struct WordCountInfo
{
bool m_isSelection = false;
int m_wordCount = 0;
int m_charWithoutSpaceCount = 0;
int m_charWithSpaceCount = 0;
};
explicit ViewWindow(QWidget *p_parent = nullptr); explicit ViewWindow(QWidget *p_parent = nullptr);
virtual ~ViewWindow(); virtual ~ViewWindow();
@ -99,6 +107,8 @@ namespace vnotex
virtual QString selectedText() const; virtual QString selectedText() const;
virtual void fetchWordCountInfo(const std::function<void(const WordCountInfo &)> &p_callback) const = 0;
public slots: public slots:
virtual void handleEditorConfigChange() = 0; virtual void handleEditorConfigChange() = 0;

View File

@ -19,6 +19,7 @@
#include "widgetsfactory.h" #include "widgetsfactory.h"
#include "attachmentpopup.h" #include "attachmentpopup.h"
#include "tagpopup.h" #include "tagpopup.h"
#include "wordcountpopup.h"
#include "propertydefs.h" #include "propertydefs.h"
#include "outlinepopup.h" #include "outlinepopup.h"
#include "viewwindow.h" #include "viewwindow.h"
@ -84,7 +85,7 @@ void ViewWindowToolBarHelper::addButtonShortcut(QToolButton *p_btn,
QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action) QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
{ {
auto viewWindow = static_cast<QWidget *>(p_tb->parent()); auto viewWindow = static_cast<ViewWindow *>(p_tb->parent());
const auto &editorConfig = ConfigMgr::getInst().getEditorConfig(); const auto &editorConfig = ConfigMgr::getInst().getEditorConfig();
QAction *act = nullptr; QAction *act = nullptr;
@ -301,6 +302,8 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
toolBtn->setPopupMode(QToolButton::InstantPopup); toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true); toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
addButtonShortcut(toolBtn, editorConfig.getShortcut(Shortcut::Attachment), viewWindow);
auto menu = new AttachmentPopup(toolBtn, p_tb); auto menu = new AttachmentPopup(toolBtn, p_tb);
toolBtn->setMenu(menu); toolBtn->setMenu(menu);
break; break;
@ -419,6 +422,23 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
break; break;
} }
case Action::WordCount:
{
act = p_tb->addAction(ToolBarHelper::generateIcon("word_count_editor.svg"),
ViewWindow::tr("Word Count"));
auto toolBtn = dynamic_cast<QToolButton *>(p_tb->widgetForAction(act));
Q_ASSERT(toolBtn);
toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
addButtonShortcut(toolBtn, editorConfig.getShortcut(Shortcut::WordCount), viewWindow);
auto menu = new WordCountPopup(toolBtn, viewWindow, p_tb);
toolBtn->setMenu(menu);
break;
}
default: default:
Q_ASSERT(false); Q_ASSERT(false);
break; break;

View File

@ -49,7 +49,8 @@ namespace vnotex
InplacePreview, InplacePreview,
ImageHost, ImageHost,
Debug, Debug,
Print Print,
WordCount
}; };
static QAction *addAction(QToolBar *p_tb, Action p_action); static QAction *addAction(QToolBar *p_tb, Action p_action);

View File

@ -2,6 +2,7 @@ SOURCES += \
$$PWD/attachmentdragdropareaindicator.cpp \ $$PWD/attachmentdragdropareaindicator.cpp \
$$PWD/attachmentpopup.cpp \ $$PWD/attachmentpopup.cpp \
$$PWD/biaction.cpp \ $$PWD/biaction.cpp \
$$PWD/buttonpopup.cpp \
$$PWD/combobox.cpp \ $$PWD/combobox.cpp \
$$PWD/consoleviewer.cpp \ $$PWD/consoleviewer.cpp \
$$PWD/dialogs/dialog.cpp \ $$PWD/dialogs/dialog.cpp \
@ -126,12 +127,14 @@ SOURCES += \
$$PWD/titletoolbar.cpp \ $$PWD/titletoolbar.cpp \
$$PWD/viewarea.cpp \ $$PWD/viewarea.cpp \
$$PWD/windowspanel.cpp \ $$PWD/windowspanel.cpp \
$$PWD/windowsprovider.cpp $$PWD/windowsprovider.cpp \
$$PWD/wordcountpopup.cpp
HEADERS += \ HEADERS += \
$$PWD/attachmentdragdropareaindicator.h \ $$PWD/attachmentdragdropareaindicator.h \
$$PWD/attachmentpopup.h \ $$PWD/attachmentpopup.h \
$$PWD/biaction.h \ $$PWD/biaction.h \
$$PWD/buttonpopup.h \
$$PWD/combobox.h \ $$PWD/combobox.h \
$$PWD/consoleviewer.h \ $$PWD/consoleviewer.h \
$$PWD/dialogs/dialog.h \ $$PWD/dialogs/dialog.h \
@ -260,4 +263,5 @@ HEADERS += \
$$PWD/titletoolbar.h \ $$PWD/titletoolbar.h \
$$PWD/viewarea.h \ $$PWD/viewarea.h \
$$PWD/windowspanel.h \ $$PWD/windowspanel.h \
$$PWD/windowsprovider.h $$PWD/windowsprovider.h \
$$PWD/wordcountpopup.h

View File

@ -0,0 +1,80 @@
#include "wordcountpopup.h"
#include <QFormLayout>
#include <QLabel>
#include <QGroupBox>
#include <QPointer>
#include <utils/widgetutils.h>
using namespace vnotex;
WordCountPanel::WordCountPanel(QWidget *p_parent)
: QWidget(p_parent)
{
auto mainLayout = new QFormLayout(this);
m_selectionLabel = new QLabel(tr("Selection Area"), this);
mainLayout->addRow(m_selectionLabel);
m_selectionLabel->hide();
const auto alignment = Qt::AlignRight | Qt::AlignVCenter;
m_wordLabel = new QLabel("0", this);
m_wordLabel->setAlignment(alignment);
mainLayout->addRow(tr("Words"), m_wordLabel);
m_charWithoutSpaceLabel = new QLabel("0", this);
m_charWithoutSpaceLabel->setAlignment(alignment);
mainLayout->addRow(tr("Characters (no spaces)"), m_charWithoutSpaceLabel);
m_charWithSpaceLabel = new QLabel("0", this);
m_charWithSpaceLabel->setAlignment(alignment);
mainLayout->addRow(tr("Characters (with spaces)"), m_charWithSpaceLabel);
}
void WordCountPanel::updateCount(bool p_isSelection,
int p_words,
int p_charsWithoutSpace,
int p_charsWithSpace)
{
m_selectionLabel->setVisible(p_isSelection);
m_wordLabel->setText(QString::number(p_words));
m_charWithoutSpaceLabel->setText(QString::number(p_charsWithoutSpace));
m_charWithSpaceLabel->setText(QString::number(p_charsWithSpace));
}
WordCountPopup::WordCountPopup(QToolButton *p_btn, const ViewWindow *p_viewWindow, QWidget *p_parent)
: ButtonPopup(p_btn, p_parent),
m_viewWindow(p_viewWindow)
{
setupUI();
connect(this, &QMenu::aboutToShow,
this, [this]() {
QPointer<WordCountPopup> popup(this);
m_viewWindow->fetchWordCountInfo([popup](const ViewWindow::WordCountInfo &info) {
if (popup) {
popup->updateCount(info);
}
});
});
}
void WordCountPopup::updateCount(const ViewWindow::WordCountInfo &p_info)
{
m_panel->updateCount(p_info.m_isSelection,
p_info.m_wordCount,
p_info.m_charWithoutSpaceCount,
p_info.m_charWithSpaceCount);
}
void WordCountPopup::setupUI()
{
QWidget *mainWidget = new QWidget(this);
setCentralWidget(mainWidget);
auto mainLayout = new QVBoxLayout(mainWidget);
m_panel = new WordCountPanel(mainWidget);
mainLayout->addWidget(m_panel);
}

View File

@ -0,0 +1,45 @@
#ifndef WORDCOUNTPOPUP_H
#define WORDCOUNTPOPUP_H
#include "buttonpopup.h"
#include "viewwindow.h"
class QToolButton;
class QLabel;
namespace vnotex
{
class WordCountPanel : public QWidget
{
Q_OBJECT
public:
explicit WordCountPanel(QWidget *p_parent = nullptr);
void updateCount(bool p_isSelection, int p_words, int p_charsWithoutSpace, int p_charsWithSpace);
private:
QLabel *m_selectionLabel = nullptr;
QLabel *m_wordLabel = nullptr;
QLabel *m_charWithoutSpaceLabel = nullptr;
QLabel *m_charWithSpaceLabel = nullptr;
};
class WordCountPopup : public ButtonPopup
{
Q_OBJECT
public:
WordCountPopup(QToolButton *p_btn, const ViewWindow *p_viewWindow, QWidget *p_parent = nullptr);
void updateCount(const ViewWindow::WordCountInfo &p_info);
private:
void setupUI();
WordCountPanel *m_panel = nullptr;
const ViewWindow *m_viewWindow = nullptr;
};
}
#endif // WORDCOUNTPOPUP_H