diff --git a/src/core/editorconfig.h b/src/core/editorconfig.h
index 0e6b2426..0eb95fe6 100644
--- a/src/core/editorconfig.h
+++ b/src/core/editorconfig.h
@@ -62,6 +62,8 @@ namespace vnotex
Debug,
Print,
ClearHighlights,
+ WordCount,
+ Attachment,
MaxShortcut
};
Q_ENUM(Shortcut)
diff --git a/src/data/core/core.qrc b/src/data/core/core.qrc
index cf27d264..5d8aed59 100644
--- a/src/data/core/core.qrc
+++ b/src/data/core/core.qrc
@@ -43,6 +43,7 @@
icons/attachment_editor.svg
icons/attachment_full_editor.svg
icons/tag_editor.svg
+ icons/word_count_editor.svg
icons/split_menu.svg
icons/split_window_list.svg
icons/type_heading_editor.svg
diff --git a/src/data/core/icons/word_count_editor.svg b/src/data/core/icons/word_count_editor.svg
new file mode 100644
index 00000000..a1b4a510
--- /dev/null
+++ b/src/data/core/icons/word_count_editor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json
index 1926eefe..d664f229 100644
--- a/src/data/core/vnotex.json
+++ b/src/data/core/vnotex.json
@@ -218,6 +218,8 @@
"FindPrevious" : "Shift+F3",
"ApplySnippet" : "Ctrl+G, I",
"Tag" : "Ctrl+G, B",
+ "Attachment" : "",
+ "WordCount" : "",
"Debug" : "F12",
"Print" : "",
"ClearHighlights" : "Ctrl+G, Space"
diff --git a/src/widgets/buttonpopup.cpp b/src/widgets/buttonpopup.cpp
new file mode 100644
index 00000000..44de5f3f
--- /dev/null
+++ b/src/widgets/buttonpopup.cpp
@@ -0,0 +1,34 @@
+#include "buttonpopup.h"
+
+#include
+
+#include
+
+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);
+}
diff --git a/src/widgets/buttonpopup.h b/src/widgets/buttonpopup.h
new file mode 100644
index 00000000..ce29ae96
--- /dev/null
+++ b/src/widgets/buttonpopup.h
@@ -0,0 +1,28 @@
+#ifndef BUTTONPOPUP_H
+#define BUTTONPOPUP_H
+
+#include
+
+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
diff --git a/src/widgets/editors/markdownviewer.cpp b/src/widgets/editors/markdownviewer.cpp
index bd8ed0cb..61be2ce5 100644
--- a/src/widgets/editors/markdownviewer.cpp
+++ b/src/widgets/editors/markdownviewer.cpp
@@ -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);
}
+
+void MarkdownViewer::saveContent(const std::function &p_callback)
+{
+ page()->runJavaScript("document.getElementById('vx-content').textContent", [p_callback](const QVariant &v) {
+ p_callback(v.toString());
+ });
+}
diff --git a/src/widgets/editors/markdownviewer.h b/src/widgets/editors/markdownviewer.h
index ed7ca96f..9d0ffd04 100644
--- a/src/widgets/editors/markdownviewer.h
+++ b/src/widgets/editors/markdownviewer.h
@@ -31,6 +31,8 @@ namespace vnotex
void setPreviewHelper(PreviewHelper *p_previewHelper);
+ void saveContent(const std::function &p_callback);
+
signals:
void zoomFactorChanged(qreal p_factor);
diff --git a/src/widgets/markdownviewwindow.cpp b/src/widgets/markdownviewwindow.cpp
index 06052209..46663e93 100644
--- a/src/widgets/markdownviewwindow.cpp
+++ b/src/widgets/markdownviewwindow.cpp
@@ -273,6 +273,7 @@ void MarkdownViewWindow::setupToolBar()
addAction(toolBar, ViewWindowToolBarHelper::EditReadDiscard);
addAction(toolBar, ViewWindowToolBarHelper::Save);
addAction(toolBar, ViewWindowToolBarHelper::ViewMode);
+ addAction(toolBar, ViewWindowToolBarHelper::WordCount);
toolBar->addSeparator();
@@ -1465,3 +1466,39 @@ void MarkdownViewWindow::updateEditorFromConfig()
m_editor->setLeaderKeyToSkip(leaderKey.m_key, leaderKey.m_modifiers);
}
}
+
+void MarkdownViewWindow::fetchWordCountInfo(const std::function &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;
+ }
+}
diff --git a/src/widgets/markdownviewwindow.h b/src/widgets/markdownviewwindow.h
index 4bc84548..1e6f8087 100644
--- a/src/widgets/markdownviewwindow.h
+++ b/src/widgets/markdownviewwindow.h
@@ -56,6 +56,8 @@ namespace vnotex
void applySnippet() Q_DECL_OVERRIDE;
+ void fetchWordCountInfo(const std::function &p_callback) const Q_DECL_OVERRIDE;
+
public slots:
void handleEditorConfigChange() Q_DECL_OVERRIDE;
diff --git a/src/widgets/tagpopup.cpp b/src/widgets/tagpopup.cpp
index e20b3f0d..3c89f364 100644
--- a/src/widgets/tagpopup.cpp
+++ b/src/widgets/tagpopup.cpp
@@ -11,17 +11,10 @@
using namespace vnotex;
TagPopup::TagPopup(QToolButton *p_btn, QWidget *p_parent)
- : QMenu(p_parent),
- m_button(p_btn)
+ : ButtonPopup(p_btn, p_parent)
{
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,
this, [this]() {
m_tagViewer->setNode(m_buffer ? m_buffer->getNode() : nullptr);
@@ -36,11 +29,8 @@ TagPopup::TagPopup(QToolButton *p_btn, QWidget *p_parent)
void TagPopup::setupUI()
{
- auto mainLayout = new QVBoxLayout(this);
- WidgetUtils::setContentsMargins(mainLayout);
-
m_tagViewer = new TagViewer(true, this);
- mainLayout->addWidget(m_tagViewer);
+ setCentralWidget(m_tagViewer);
setMinimumSize(256, 320);
}
diff --git a/src/widgets/tagpopup.h b/src/widgets/tagpopup.h
index d549baea..edc358d4 100644
--- a/src/widgets/tagpopup.h
+++ b/src/widgets/tagpopup.h
@@ -1,7 +1,7 @@
#ifndef TAGPOPUP_H
#define TAGPOPUP_H
-#include
+#include "buttonpopup.h"
class QToolButton;
@@ -10,7 +10,7 @@ namespace vnotex
class Buffer;
class TagViewer;
- class TagPopup : public QMenu
+ class TagPopup : public ButtonPopup
{
Q_OBJECT
public:
@@ -23,9 +23,6 @@ namespace vnotex
Buffer *m_buffer = nullptr;
- // Button for this menu.
- QToolButton *m_button = nullptr;
-
// Managed by QObject.
TagViewer *m_tagViewer = nullptr;
};
diff --git a/src/widgets/textviewwindow.cpp b/src/widgets/textviewwindow.cpp
index 44ffda11..1b30562d 100644
--- a/src/widgets/textviewwindow.cpp
+++ b/src/widgets/textviewwindow.cpp
@@ -66,6 +66,7 @@ void TextViewWindow::setupToolBar()
addToolBar(toolBar);
addAction(toolBar, ViewWindowToolBarHelper::Save);
+ addAction(toolBar, ViewWindowToolBarHelper::WordCount);
toolBar->addSeparator();
@@ -322,3 +323,18 @@ void TextViewWindow::clearHighlights()
{
TextViewWindowHelper::clearSearchHighlights(this);
}
+
+void TextViewWindow::fetchWordCountInfo(const std::function &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);
+ }
+}
diff --git a/src/widgets/textviewwindow.h b/src/widgets/textviewwindow.h
index 52ebcfc4..11da33e2 100644
--- a/src/widgets/textviewwindow.h
+++ b/src/widgets/textviewwindow.h
@@ -37,6 +37,8 @@ namespace vnotex
void applySnippet() Q_DECL_OVERRIDE;
+ void fetchWordCountInfo(const std::function &p_callback) const Q_DECL_OVERRIDE;
+
public slots:
void handleEditorConfigChange() Q_DECL_OVERRIDE;
diff --git a/src/widgets/textviewwindowhelper.h b/src/widgets/textviewwindowhelper.h
index 1e3e51a3..3db9fabf 100644
--- a/src/widgets/textviewwindowhelper.h
+++ b/src/widgets/textviewwindowhelper.h
@@ -325,6 +325,47 @@ namespace vnotex
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);
}
+
+ 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;
+ }
};
}
diff --git a/src/widgets/viewwindow.cpp b/src/widgets/viewwindow.cpp
index e2c8506a..50b024d9 100644
--- a/src/widgets/viewwindow.cpp
+++ b/src/widgets/viewwindow.cpp
@@ -456,6 +456,12 @@ QAction *ViewWindow::addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Act
break;
}
+ case ViewWindowToolBarHelper::WordCount:
+ {
+ act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action);
+ break;
+ }
+
case ViewWindowToolBarHelper::Outline:
{
act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action);
diff --git a/src/widgets/viewwindow.h b/src/widgets/viewwindow.h
index da8e1f3d..853a9585 100644
--- a/src/widgets/viewwindow.h
+++ b/src/widgets/viewwindow.h
@@ -40,6 +40,14 @@ namespace vnotex
};
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);
virtual ~ViewWindow();
@@ -99,6 +107,8 @@ namespace vnotex
virtual QString selectedText() const;
+ virtual void fetchWordCountInfo(const std::function &p_callback) const = 0;
+
public slots:
virtual void handleEditorConfigChange() = 0;
diff --git a/src/widgets/viewwindowtoolbarhelper.cpp b/src/widgets/viewwindowtoolbarhelper.cpp
index c935d4f6..ab4701b9 100644
--- a/src/widgets/viewwindowtoolbarhelper.cpp
+++ b/src/widgets/viewwindowtoolbarhelper.cpp
@@ -19,6 +19,7 @@
#include "widgetsfactory.h"
#include "attachmentpopup.h"
#include "tagpopup.h"
+#include "wordcountpopup.h"
#include "propertydefs.h"
#include "outlinepopup.h"
#include "viewwindow.h"
@@ -84,7 +85,7 @@ void ViewWindowToolBarHelper::addButtonShortcut(QToolButton *p_btn,
QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
{
- auto viewWindow = static_cast(p_tb->parent());
+ auto viewWindow = static_cast(p_tb->parent());
const auto &editorConfig = ConfigMgr::getInst().getEditorConfig();
QAction *act = nullptr;
@@ -301,6 +302,8 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
+ addButtonShortcut(toolBtn, editorConfig.getShortcut(Shortcut::Attachment), viewWindow);
+
auto menu = new AttachmentPopup(toolBtn, p_tb);
toolBtn->setMenu(menu);
break;
@@ -419,6 +422,23 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
break;
}
+ case Action::WordCount:
+ {
+ act = p_tb->addAction(ToolBarHelper::generateIcon("word_count_editor.svg"),
+ ViewWindow::tr("Word Count"));
+
+ auto toolBtn = dynamic_cast(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:
Q_ASSERT(false);
break;
diff --git a/src/widgets/viewwindowtoolbarhelper.h b/src/widgets/viewwindowtoolbarhelper.h
index f260ec8e..79f96ac5 100644
--- a/src/widgets/viewwindowtoolbarhelper.h
+++ b/src/widgets/viewwindowtoolbarhelper.h
@@ -49,7 +49,8 @@ namespace vnotex
InplacePreview,
ImageHost,
Debug,
- Print
+ Print,
+ WordCount
};
static QAction *addAction(QToolBar *p_tb, Action p_action);
diff --git a/src/widgets/widgets.pri b/src/widgets/widgets.pri
index 561121c8..1240f494 100644
--- a/src/widgets/widgets.pri
+++ b/src/widgets/widgets.pri
@@ -2,6 +2,7 @@ SOURCES += \
$$PWD/attachmentdragdropareaindicator.cpp \
$$PWD/attachmentpopup.cpp \
$$PWD/biaction.cpp \
+ $$PWD/buttonpopup.cpp \
$$PWD/combobox.cpp \
$$PWD/consoleviewer.cpp \
$$PWD/dialogs/dialog.cpp \
@@ -126,12 +127,14 @@ SOURCES += \
$$PWD/titletoolbar.cpp \
$$PWD/viewarea.cpp \
$$PWD/windowspanel.cpp \
- $$PWD/windowsprovider.cpp
+ $$PWD/windowsprovider.cpp \
+ $$PWD/wordcountpopup.cpp
HEADERS += \
$$PWD/attachmentdragdropareaindicator.h \
$$PWD/attachmentpopup.h \
$$PWD/biaction.h \
+ $$PWD/buttonpopup.h \
$$PWD/combobox.h \
$$PWD/consoleviewer.h \
$$PWD/dialogs/dialog.h \
@@ -260,4 +263,5 @@ HEADERS += \
$$PWD/titletoolbar.h \
$$PWD/viewarea.h \
$$PWD/windowspanel.h \
- $$PWD/windowsprovider.h
+ $$PWD/windowsprovider.h \
+ $$PWD/wordcountpopup.h
diff --git a/src/widgets/wordcountpopup.cpp b/src/widgets/wordcountpopup.cpp
new file mode 100644
index 00000000..1e075daa
--- /dev/null
+++ b/src/widgets/wordcountpopup.cpp
@@ -0,0 +1,80 @@
+#include "wordcountpopup.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+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 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);
+}
diff --git a/src/widgets/wordcountpopup.h b/src/widgets/wordcountpopup.h
new file mode 100644
index 00000000..1cc515e3
--- /dev/null
+++ b/src/widgets/wordcountpopup.h
@@ -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