From f9f7a365f8835917b8ff0a90cd7b4072fdded55b Mon Sep 17 00:00:00 2001 From: Le Tan Date: Mon, 3 Jul 2017 22:03:22 +0800 Subject: [PATCH] add toolbar for text editing Bold, italic, underline, strikethrough, inline code. Underline and strikethrough are not implemented yet. --- src/resources/icons/bold.svg | 7 + src/resources/icons/inline_code.svg | 7 + src/resources/icons/italic.svg | 7 + src/resources/icons/strikethrough.svg | 8 + src/resources/icons/underline.svg | 8 + src/utils/vvim.cpp | 4 +- src/vconstants.h | 7 + src/vedit.cpp | 7 + src/vedit.h | 3 + src/veditoperations.h | 3 + src/vedittab.h | 3 + src/vmainwindow.cpp | 94 ++++++++- src/vmainwindow.h | 6 + src/vmdeditoperations.cpp | 289 +++++++++++++------------- src/vmdeditoperations.h | 16 +- src/vmdtab.cpp | 7 + src/vmdtab.h | 3 + src/vnote.qrc | 5 + 18 files changed, 338 insertions(+), 146 deletions(-) create mode 100644 src/resources/icons/bold.svg create mode 100644 src/resources/icons/inline_code.svg create mode 100644 src/resources/icons/italic.svg create mode 100644 src/resources/icons/strikethrough.svg create mode 100644 src/resources/icons/underline.svg diff --git a/src/resources/icons/bold.svg b/src/resources/icons/bold.svg new file mode 100644 index 00000000..9dd41cae --- /dev/null +++ b/src/resources/icons/bold.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + B + + \ No newline at end of file diff --git a/src/resources/icons/inline_code.svg b/src/resources/icons/inline_code.svg new file mode 100644 index 00000000..b98f898e --- /dev/null +++ b/src/resources/icons/inline_code.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + O + + \ No newline at end of file diff --git a/src/resources/icons/italic.svg b/src/resources/icons/italic.svg new file mode 100644 index 00000000..da96923d --- /dev/null +++ b/src/resources/icons/italic.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + I + + \ No newline at end of file diff --git a/src/resources/icons/strikethrough.svg b/src/resources/icons/strikethrough.svg new file mode 100644 index 00000000..2ae29a98 --- /dev/null +++ b/src/resources/icons/strikethrough.svg @@ -0,0 +1,8 @@ + + + + Layer 1 + S + + + \ No newline at end of file diff --git a/src/resources/icons/underline.svg b/src/resources/icons/underline.svg new file mode 100644 index 00000000..2ef3453f --- /dev/null +++ b/src/resources/icons/underline.svg @@ -0,0 +1,8 @@ + + + + Layer 1 + U + + + \ No newline at end of file diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index 4aaa0d9b..80d090c8 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -4241,8 +4241,8 @@ int VVim::blockCountOfPageStep() const void VVim::selectionToVisualMode(bool p_hasText) { if (p_hasText && m_mode == VimMode::Normal) { - // Enter visual mode. - setMode(VimMode::Visual); + // Enter visual mode without clearing the selection. + setMode(VimMode::Visual, false); } } diff --git a/src/vconstants.h b/src/vconstants.h index 6317dceb..f3f58624 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -34,4 +34,11 @@ namespace DirConfig } static const QString c_emptyHeaderName = "[EMPTY]"; + +enum class TextDecoration { None, + Bold, + Italic, + Underline, + Strikethrough, + InlineCode }; #endif diff --git a/src/vedit.cpp b/src/vedit.cpp index f5e90227..460f9866 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -826,3 +826,10 @@ void VEdit::setInputMethodEnabled(bool p_enabled) im->update(Qt::ImEnabled); } } + +void VEdit::decorateText(TextDecoration p_decoration) +{ + if (m_editOps) { + m_editOps->decorateText(p_decoration); + } +} diff --git a/src/vedit.h b/src/vedit.h index d21f2cee..ffa85007 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -92,6 +92,9 @@ public: void setInputMethodEnabled(bool p_enabled); + // Insert decoration markers or decorate selected text. + void decorateText(TextDecoration p_decoration); + signals: // Request VEditTab to save and exit edit mode. void saveAndRead(); diff --git a/src/veditoperations.h b/src/veditoperations.h index f349e5f0..cc6b2371 100644 --- a/src/veditoperations.h +++ b/src/veditoperations.h @@ -30,6 +30,9 @@ public: // Request to propogate Vim status. void requestUpdateVimStatus(); + // Insert decoration markers or decorate selected text. + virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);}; + signals: // Want to display a template message in status bar. void statusMessage(const QString &p_msg); diff --git a/src/vedittab.h b/src/vedittab.h index 73d66021..116c814b 100644 --- a/src/vedittab.h +++ b/src/vedittab.h @@ -68,6 +68,9 @@ public: // Request current tab to propogate its status about Vim. virtual void requestUpdateVimStatus() = 0; + // Insert decoration markers or decorate selected text. + virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);}; + public slots: // Enter edit mode virtual void editFile() = 0; diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 7f5863e2..6e31648a 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -175,6 +175,7 @@ void VMainWindow::initToolBar() { initFileToolBar(); initViewToolBar(); + initEditToolBar(); } void VMainWindow::initViewToolBar() @@ -210,6 +211,90 @@ void VMainWindow::initViewToolBar() viewToolBar->addAction(expandViewAct); } +static void setActionsVisible(QWidget *p_widget, bool p_visible) +{ + Q_ASSERT(p_widget); + QList actions = p_widget->actions(); + for (auto const & act : actions) { + act->setVisible(p_visible); + } +} + +void VMainWindow::initEditToolBar() +{ + m_editToolBar = addToolBar(tr("Edit Toolbar")); + m_editToolBar->setObjectName("EditToolBar"); + m_editToolBar->setMovable(false); + + m_editToolBar->addSeparator(); + + QAction *boldAct = new QAction(QIcon(":/resources/icons/bold.svg"), + tr("Bold"), this); + boldAct->setStatusTip(tr("Insert bold text or change selected text to bold")); + connect(boldAct, &QAction::triggered, + this, [this](){ + if (m_curTab) { + m_curTab->decorateText(TextDecoration::Bold); + } + }); + + m_editToolBar->addAction(boldAct); + + QAction *italicAct = new QAction(QIcon(":/resources/icons/italic.svg"), + tr("Italic"), this); + italicAct->setStatusTip(tr("Insert italic text or change selected text to italic")); + connect(italicAct, &QAction::triggered, + this, [this](){ + if (m_curTab) { + m_curTab->decorateText(TextDecoration::Italic); + } + }); + + m_editToolBar->addAction(italicAct); + + QAction *underlineAct = new QAction(QIcon(":/resources/icons/underline.svg"), + tr("Underline"), this); + underlineAct->setStatusTip(tr("Insert underlined text or change selected text to underlined")); + connect(underlineAct, &QAction::triggered, + this, [this](){ + if (m_curTab) { + m_curTab->decorateText(TextDecoration::Underline); + } + }); + + m_editToolBar->addAction(underlineAct); + + QAction *strikethroughAct = new QAction(QIcon(":/resources/icons/strikethrough.svg"), + tr("Strikethrough"), this); + strikethroughAct->setStatusTip(tr("Insert strikethrough text or change selected text to strikethroughed")); + connect(strikethroughAct, &QAction::triggered, + this, [this](){ + if (m_curTab) { + m_curTab->decorateText(TextDecoration::Strikethrough); + } + }); + + m_editToolBar->addAction(strikethroughAct); + + QAction *inlineCodeAct = new QAction(QIcon(":/resources/icons/inline_code.svg"), + tr("Inline Code"), this); + inlineCodeAct->setStatusTip(tr("Insert inline-code text or change selected text to inline-coded")); + connect(inlineCodeAct, &QAction::triggered, + this, [this](){ + if (m_curTab) { + m_curTab->decorateText(TextDecoration::InlineCode); + } + }); + + m_editToolBar->addAction(inlineCodeAct); + + QAction *toggleAct = m_editToolBar->toggleViewAction(); + toggleAct->setToolTip(tr("Toggle the edit toolbar")); + viewMenu->addAction(toggleAct); + + setActionsVisible(m_editToolBar, false); +} + void VMainWindow::initFileToolBar() { QToolBar *fileToolBar = addToolBar(tr("Note")); @@ -507,6 +592,7 @@ void VMainWindow::initMarkdownMenu() void VMainWindow::initViewMenu() { viewMenu = menuBar()->addMenu(tr("&View")); + viewMenu->setToolTipsVisible(true); } void VMainWindow::initFileMenu() @@ -795,7 +881,10 @@ void VMainWindow::initDockWindows() toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline")); toolDock->setWidget(toolBox); addDockWidget(Qt::RightDockWidgetArea, toolDock); - viewMenu->addAction(toolDock->toggleViewAction()); + + QAction *toggleAct = toolDock->toggleViewAction(); + toggleAct->setToolTip(tr("Toggle the tools dock widget")); + viewMenu->addAction(toggleAct); } void VMainWindow::initAvatar() @@ -1154,6 +1243,9 @@ void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file, noteInfoAct->setEnabled(p_file && p_file->getType() == FileType::Normal); m_insertImageAct->setEnabled(p_file && p_editMode); + + setActionsVisible(m_editToolBar, p_file && p_editMode); + // Find/Replace m_findReplaceAct->setEnabled(p_file); m_findNextAct->setEnabled(p_file); diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 7f63bb9f..02cb509f 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -108,6 +108,9 @@ private: void initFileToolBar(); void initViewToolBar(); + // Init the Edit toolbar. + void initEditToolBar(); + void initMenuBar(); void initFileMenu(); void initEditMenu(); @@ -196,6 +199,9 @@ private: // Menus QMenu *viewMenu; + // Edit Toolbar. + QToolBar *m_editToolBar; + QVector predefinedColorPixmaps; }; diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp index 239cc867..9a65e74b 100644 --- a/src/vmdeditoperations.cpp +++ b/src/vmdeditoperations.cpp @@ -241,10 +241,12 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event) case Qt::Key_B: { - if (handleKeyB(p_event)) { + if (modifiers == Qt::ControlModifier) { + decorateBold(); + p_event->accept(); ret = true; - goto exit; } + break; } @@ -259,19 +261,23 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event) case Qt::Key_I: { - if (handleKeyI(p_event)) { + if (modifiers == Qt::ControlModifier) { + decorateItalic(); + p_event->accept(); ret = true; - goto exit; } + break; } case Qt::Key_O: { - if (handleKeyO(p_event)) { + if (modifiers == Qt::ControlModifier) { + decorateInlineCode(); + p_event->accept(); ret = true; - goto exit; } + break; } @@ -435,51 +441,6 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event) return true; } -bool VMdEditOperations::handleKeyB(QKeyEvent *p_event) -{ - if (p_event->modifiers() == Qt::ControlModifier) { - // Ctrl+B, Bold. - QTextCursor cursor = m_editor->textCursor(); - if (cursor.hasSelection()) { - // Insert ** around the selected text. - int start = cursor.selectionStart(); - int end = cursor.selectionEnd(); - cursor.beginEditBlock(); - cursor.clearSelection(); - cursor.setPosition(start, QTextCursor::MoveAnchor); - cursor.insertText("**"); - cursor.setPosition(end + 2, QTextCursor::MoveAnchor); - cursor.insertText("**"); - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - } else { - // Insert **** and place cursor in the middle. - // Or if there are two * after current cursor, just skip them. - cursor.beginEditBlock(); - int pos = cursor.positionInBlock(); - bool hasStars = false; - QString text = cursor.block().text(); - if (pos <= text.size() - 2) { - if (text[pos] == '*' && text[pos + 1] == '*') { - hasStars = true; - } - } - if (hasStars) { - cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2); - } else { - cursor.insertText("****"); - cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2); - } - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - } - - p_event->accept(); - return true; - } - return false; -} - bool VMdEditOperations::handleKeyH(QKeyEvent *p_event) { if (p_event->modifiers() == Qt::ControlModifier) { @@ -493,95 +454,6 @@ bool VMdEditOperations::handleKeyH(QKeyEvent *p_event) return false; } -bool VMdEditOperations::handleKeyI(QKeyEvent *p_event) -{ - if (p_event->modifiers() == Qt::ControlModifier) { - // Ctrl+I, Italic. - QTextCursor cursor = m_editor->textCursor(); - if (cursor.hasSelection()) { - // Insert * around the selected text. - int start = cursor.selectionStart(); - int end = cursor.selectionEnd(); - cursor.beginEditBlock(); - cursor.clearSelection(); - cursor.setPosition(start, QTextCursor::MoveAnchor); - cursor.insertText("*"); - cursor.setPosition(end + 1, QTextCursor::MoveAnchor); - cursor.insertText("*"); - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - } else { - // Insert ** and place cursor in the middle. - // Or if there are one * after current cursor, just skip it. - cursor.beginEditBlock(); - int pos = cursor.positionInBlock(); - bool hasStar = false; - QString text = cursor.block().text(); - if (pos <= text.size() - 1) { - if (text[pos] == '*') { - hasStar = true; - } - } - if (hasStar) { - cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); - } else { - cursor.insertText("**"); - cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1); - } - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - } - - p_event->accept(); - return true; - } - return false; -} - -bool VMdEditOperations::handleKeyO(QKeyEvent *p_event) -{ - if (p_event->modifiers() == Qt::ControlModifier) { - // Ctrl+O, inline codeblock. - QTextCursor cursor = m_editor->textCursor(); - if (cursor.hasSelection()) { - // Insert ` around the selected text. - int start = cursor.selectionStart(); - int end = cursor.selectionEnd(); - cursor.beginEditBlock(); - cursor.clearSelection(); - cursor.setPosition(start, QTextCursor::MoveAnchor); - cursor.insertText("`"); - cursor.setPosition(end + 1, QTextCursor::MoveAnchor); - cursor.insertText("`"); - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - } else { - // Insert `` and place cursor in the middle. - // Or if there are one ` after current cursor, just skip it. - cursor.beginEditBlock(); - int pos = cursor.positionInBlock(); - bool hasBackquote = false; - QString text = cursor.block().text(); - if (pos <= text.size() - 1) { - if (text[pos] == '`') { - hasBackquote = true; - } - } - if (hasBackquote) { - cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); - } else { - cursor.insertText("``"); - cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1); - } - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - } - p_event->accept(); - return true; - } - return false; -} - bool VMdEditOperations::handleKeyU(QKeyEvent *p_event) { if (p_event->modifiers() == Qt::ControlModifier) { @@ -775,3 +647,140 @@ bool VMdEditOperations::insertTitle(int p_level) return true; } +void VMdEditOperations::decorateText(TextDecoration p_decoration) +{ + if (p_decoration == TextDecoration::None) { + return; + } + + m_vim->setMode(VimMode::Insert, false); + + switch (p_decoration) { + case TextDecoration::Bold: + decorateBold(); + break; + + case TextDecoration::Italic: + decorateItalic(); + break; + + case TextDecoration::InlineCode: + decorateInlineCode(); + break; + + default: + qDebug() << "decoration" << (int)p_decoration << "is not implemented yet"; + break; + } +} + +void VMdEditOperations::decorateBold() +{ + QTextCursor cursor = m_editor->textCursor(); + cursor.beginEditBlock(); + if (cursor.hasSelection()) { + // Insert ** around the selected text. + int start = cursor.selectionStart(); + int end = cursor.selectionEnd(); + cursor.clearSelection(); + cursor.setPosition(start, QTextCursor::MoveAnchor); + cursor.insertText("**"); + cursor.setPosition(end + 2, QTextCursor::MoveAnchor); + cursor.insertText("**"); + } else { + // Insert **** and place cursor in the middle. + // Or if there are two * after current cursor, just skip them. + int pos = cursor.positionInBlock(); + bool hasStars = false; + QString text = cursor.block().text(); + if (pos <= text.size() - 2) { + if (text[pos] == '*' && text[pos + 1] == '*') { + hasStars = true; + } + } + + if (hasStars) { + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2); + } else { + cursor.insertText("****"); + cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2); + } + } + + cursor.endEditBlock(); + m_editor->setTextCursor(cursor); +} + +void VMdEditOperations::decorateItalic() +{ + QTextCursor cursor = m_editor->textCursor(); + cursor.beginEditBlock(); + if (cursor.hasSelection()) { + // Insert * around the selected text. + int start = cursor.selectionStart(); + int end = cursor.selectionEnd(); + cursor.clearSelection(); + cursor.setPosition(start, QTextCursor::MoveAnchor); + cursor.insertText("*"); + cursor.setPosition(end + 1, QTextCursor::MoveAnchor); + cursor.insertText("*"); + } else { + // Insert ** and place cursor in the middle. + // Or if there are one * after current cursor, just skip it. + int pos = cursor.positionInBlock(); + bool hasStar = false; + QString text = cursor.block().text(); + if (pos <= text.size() - 1) { + if (text[pos] == '*') { + hasStar = true; + } + } + + if (hasStar) { + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); + } else { + cursor.insertText("**"); + cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1); + } + } + + cursor.endEditBlock(); + m_editor->setTextCursor(cursor); +} + +void VMdEditOperations::decorateInlineCode() +{ + QTextCursor cursor = m_editor->textCursor(); + cursor.beginEditBlock(); + if (cursor.hasSelection()) { + // Insert ` around the selected text. + int start = cursor.selectionStart(); + int end = cursor.selectionEnd(); + cursor.clearSelection(); + cursor.setPosition(start, QTextCursor::MoveAnchor); + cursor.insertText("`"); + cursor.setPosition(end + 1, QTextCursor::MoveAnchor); + cursor.insertText("`"); + } else { + // Insert `` and place cursor in the middle. + // Or if there are one ` after current cursor, just skip it. + int pos = cursor.positionInBlock(); + bool hasBackquote = false; + QString text = cursor.block().text(); + if (pos <= text.size() - 1) { + if (text[pos] == '`') { + hasBackquote = true; + } + } + + if (hasBackquote) { + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); + } else { + cursor.insertText("``"); + cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1); + } + } + + cursor.endEditBlock(); + m_editor->setTextCursor(cursor); +} diff --git a/src/vmdeditoperations.h b/src/vmdeditoperations.h index a5fa4143..c297c49b 100644 --- a/src/vmdeditoperations.h +++ b/src/vmdeditoperations.h @@ -21,6 +21,10 @@ public: bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE; + // Insert decoration markers or decorate selected text. + // If it is Vim Normal mode, change to Insert mode first. + void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE; + private: void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath); @@ -32,10 +36,7 @@ private: // Key press handlers. bool handleKeyTab(QKeyEvent *p_event); bool handleKeyBackTab(QKeyEvent *p_event); - bool handleKeyB(QKeyEvent *p_event); bool handleKeyH(QKeyEvent *p_event); - bool handleKeyI(QKeyEvent *p_event); - bool handleKeyO(QKeyEvent *p_event); bool handleKeyU(QKeyEvent *p_event); bool handleKeyW(QKeyEvent *p_event); bool handleKeyEsc(QKeyEvent *p_event); @@ -46,6 +47,15 @@ private: // Change the sequence number of a list block. void changeListBlockSeqNumber(QTextBlock &p_block, int p_seq); + // Insert bold marker or set selected text bold. + void decorateBold(); + + // Insert italic marker or set selected text italic. + void decorateItalic(); + + // Insert inline-code marker or set selected text inline-coded. + void decorateInlineCode(); + // The cursor position after auto indent or auto list. // It will be -1 if last key press do not trigger the auto indent or auto list. int m_autoIndentPos; diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp index 2dcc397d..321f3d6c 100644 --- a/src/vmdtab.cpp +++ b/src/vmdtab.cpp @@ -665,3 +665,10 @@ VEditTabInfo VMdTab::createEditTabInfo() return info; } + +void VMdTab::decorateText(TextDecoration p_decoration) +{ + if (m_editor) { + m_editor->decorateText(p_decoration); + } +} diff --git a/src/vmdtab.h b/src/vmdtab.h index 31c00f86..016363f3 100644 --- a/src/vmdtab.h +++ b/src/vmdtab.h @@ -57,6 +57,9 @@ public: void requestUpdateVimStatus() Q_DECL_OVERRIDE; + // Insert decoration markers or decorate selected text. + void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE; + public slots: // Enter edit mode. void editFile() Q_DECL_OVERRIDE; diff --git a/src/vnote.qrc b/src/vnote.qrc index 04f98be6..f9a8cc1e 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -111,5 +111,10 @@ resources/icons/vnote_update.svg utils/flowchart.js/flowchart.min.js utils/flowchart.js/raphael.min.js + resources/icons/bold.svg + resources/icons/italic.svg + resources/icons/underline.svg + resources/icons/strikethrough.svg + resources/icons/inline_code.svg