mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
replace VEdit and VMdEdit with VEditor and VMdEditor
This commit is contained in:
parent
5abcb1a8d9
commit
404b5329a1
12
src/src.pro
12
src/src.pro
@ -84,7 +84,11 @@ SOURCES += main.cpp\
|
||||
dialog/vinsertlinkdialog.cpp \
|
||||
vplaintextedit.cpp \
|
||||
vimageresourcemanager.cpp \
|
||||
vlinenumberarea.cpp
|
||||
vlinenumberarea.cpp \
|
||||
veditor.cpp \
|
||||
vmdeditor.cpp \
|
||||
veditconfig.cpp \
|
||||
vpreviewmanager.cpp
|
||||
|
||||
HEADERS += vmainwindow.h \
|
||||
vdirectorytree.h \
|
||||
@ -156,7 +160,11 @@ HEADERS += vmainwindow.h \
|
||||
dialog/vinsertlinkdialog.h \
|
||||
vplaintextedit.h \
|
||||
vimageresourcemanager.h \
|
||||
vlinenumberarea.h
|
||||
vlinenumberarea.h \
|
||||
veditor.h \
|
||||
vmdeditor.h \
|
||||
veditconfig.h \
|
||||
vpreviewmanager.h
|
||||
|
||||
RESOURCES += \
|
||||
vnote.qrc \
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <QTextDocument>
|
||||
#include <QDebug>
|
||||
#include <QTextEdit>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QScrollBar>
|
||||
|
||||
#include "vutils.h"
|
||||
@ -418,6 +419,92 @@ void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
|
||||
p_edit->ensureCursorVisible();
|
||||
}
|
||||
|
||||
void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
|
||||
int p_blockNum,
|
||||
int p_dest)
|
||||
{
|
||||
QTextDocument *doc = p_edit->document();
|
||||
QTextCursor cursor = p_edit->textCursor();
|
||||
if (p_blockNum >= doc->blockCount()) {
|
||||
p_blockNum = doc->blockCount() - 1;
|
||||
}
|
||||
|
||||
QTextBlock block = doc->findBlockByNumber(p_blockNum);
|
||||
|
||||
int pib = cursor.positionInBlock();
|
||||
if (cursor.block().blockNumber() != p_blockNum) {
|
||||
// Move the cursor to the block.
|
||||
if (pib >= block.length()) {
|
||||
pib = block.length() - 1;
|
||||
}
|
||||
|
||||
cursor.setPosition(block.position() + pib);
|
||||
p_edit->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
// Scroll to let current cursor locate in proper position.
|
||||
p_edit->ensureCursorVisible();
|
||||
QScrollBar *vsbar = p_edit->verticalScrollBar();
|
||||
|
||||
if (!vsbar || !vsbar->isVisible()) {
|
||||
// No vertical scrollbar. No need to scroll.
|
||||
return;
|
||||
}
|
||||
|
||||
QRect rect = p_edit->cursorRect();
|
||||
int height = p_edit->rect().height();
|
||||
QScrollBar *sbar = p_edit->horizontalScrollBar();
|
||||
if (sbar && sbar->isVisible()) {
|
||||
height -= sbar->height();
|
||||
}
|
||||
|
||||
switch (p_dest) {
|
||||
case 0:
|
||||
{
|
||||
// Top.
|
||||
while (rect.y() > 0 && vsbar->value() < vsbar->maximum()) {
|
||||
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
||||
rect = p_edit->cursorRect();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
// Center.
|
||||
height = qMax(height / 2, 1);
|
||||
if (rect.y() > height) {
|
||||
while (rect.y() > height && vsbar->value() < vsbar->maximum()) {
|
||||
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
||||
rect = p_edit->cursorRect();
|
||||
}
|
||||
} else if (rect.y() < height) {
|
||||
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
||||
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
||||
rect = p_edit->cursorRect();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 2:
|
||||
// Bottom.
|
||||
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
||||
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
||||
rect = p_edit->cursorRect();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
p_edit->ensureCursorVisible();
|
||||
}
|
||||
|
||||
bool VEditUtils::isListBlock(const QTextBlock &p_block, int *p_seq)
|
||||
{
|
||||
QString text = p_block.text();
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
class QTextDocument;
|
||||
class QTextEdit;
|
||||
class QPlainTextEdit;
|
||||
|
||||
// Utils for text edit.
|
||||
class VEditUtils
|
||||
@ -113,6 +114,14 @@ public:
|
||||
int p_blockNum,
|
||||
int p_dest);
|
||||
|
||||
// Scroll block @p_blockNum into the visual window.
|
||||
// @p_dest is the position of the window: 0 for top, 1 for center, 2 for bottom.
|
||||
// @p_blockNum is based on 0.
|
||||
// Will set the cursor to the block.
|
||||
static void scrollBlockInPage(QPlainTextEdit *p_edit,
|
||||
int p_blockNum,
|
||||
int p_dest);
|
||||
|
||||
// Check if @p_block is a auto list block.
|
||||
// @p_seq will be the seq number of the ordered list, or -1.
|
||||
// Returns true if it is an auto list block.
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include <QApplication>
|
||||
#include <QMimeData>
|
||||
#include "vconfigmanager.h"
|
||||
#include "vedit.h"
|
||||
#include "veditor.h"
|
||||
#include "utils/veditutils.h"
|
||||
#include "vconstants.h"
|
||||
|
||||
@ -106,8 +106,8 @@ static QString keyToString(int p_key, int p_modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
VVim::VVim(VEdit *p_editor)
|
||||
: QObject(p_editor), m_editor(p_editor),
|
||||
VVim::VVim(VEditor *p_editor)
|
||||
: QObject(p_editor->getEditor()), m_editor(p_editor),
|
||||
m_editConfig(&p_editor->getConfig()), m_mode(VimMode::Invalid),
|
||||
m_resetPositionInBlock(true), m_regName(c_unnamedRegister),
|
||||
m_leaderKey(Key(Qt::Key_Space)), m_replayLeaderSequence(false),
|
||||
@ -119,7 +119,7 @@ VVim::VVim(VEdit *p_editor)
|
||||
|
||||
initRegisters();
|
||||
|
||||
connect(m_editor, &VEdit::selectionChangedByMouse,
|
||||
connect(m_editor->object(), &VEditorObject::selectionChangedByMouse,
|
||||
this, &VVim::selectionToVisualMode);
|
||||
}
|
||||
|
||||
@ -472,13 +472,13 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
// See if we need to cancel auto indent.
|
||||
bool cancelAutoIndent = false;
|
||||
if (p_autoIndentPos && *p_autoIndentPos > -1) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cancelAutoIndent = VEditUtils::needToCancelAutoIndent(*p_autoIndentPos, cursor);
|
||||
|
||||
if (cancelAutoIndent) {
|
||||
autoIndentPos = -1;
|
||||
VEditUtils::deleteIndentAndListMark(cursor);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,7 +501,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
QChar reg = keyToRegisterName(keyInfo);
|
||||
if (!reg.isNull()) {
|
||||
// Insert register content.
|
||||
m_editor->insertPlainText(getRegister(reg).read());
|
||||
m_editor->insertPlainTextW(getRegister(reg).read());
|
||||
}
|
||||
|
||||
goto clear_accept;
|
||||
@ -565,7 +565,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
// Expecting a mark name to create a mark.
|
||||
if (keyInfo.isAlphabet() && modifiers == Qt::NoModifier) {
|
||||
m_keys.clear();
|
||||
m_marks.setMark(keyToChar(key, modifiers), m_editor->textCursor());
|
||||
m_marks.setMark(keyToChar(key, modifiers), m_editor->textCursorW());
|
||||
}
|
||||
|
||||
goto clear_accept;
|
||||
@ -804,7 +804,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
setMode(VimMode::Insert, false);
|
||||
}
|
||||
} else if (modifiers == Qt::ShiftModifier) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (m_mode == VimMode::Normal) {
|
||||
// Insert at the first non-space character.
|
||||
VEditUtils::moveCursorFirstNonSpaceCharacter(cursor, QTextCursor::MoveAnchor);
|
||||
@ -815,7 +815,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
1);
|
||||
}
|
||||
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
setMode(VimMode::Insert);
|
||||
} else if (isControlModifier(modifiers)) {
|
||||
// Ctrl+I, jump to next location.
|
||||
@ -856,29 +856,29 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
// Enter Insert mode.
|
||||
// Move cursor back one character.
|
||||
if (m_mode == VimMode::Normal) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
V_ASSERT(!cursor.hasSelection());
|
||||
|
||||
if (!cursor.atBlockEnd()) {
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
setMode(VimMode::Insert);
|
||||
}
|
||||
} else if (modifiers == Qt::ShiftModifier) {
|
||||
// Insert at the end of line.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (m_mode == VimMode::Normal) {
|
||||
cursor.movePosition(QTextCursor::EndOfBlock,
|
||||
QTextCursor::MoveAnchor,
|
||||
1);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
} else if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) {
|
||||
if (!cursor.atBlockEnd()) {
|
||||
cursor.clearSelection();
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -894,7 +894,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
// Insert a new block under/above current block and enter insert mode.
|
||||
bool insertAbove = modifiers == Qt::ShiftModifier;
|
||||
if (m_mode == VimMode::Normal) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
cursor.movePosition(insertAbove ? QTextCursor::StartOfBlock
|
||||
: QTextCursor::EndOfBlock,
|
||||
@ -919,7 +919,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
|
||||
if (textInserted) {
|
||||
autoIndentPos = cursor.position();
|
||||
@ -1104,7 +1104,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
} else if (m_keys.isEmpty() && !hasActionToken()) {
|
||||
if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) {
|
||||
// u/U for tolower and toupper selected text.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
// Different from Vim:
|
||||
// If there is no selection in Visual mode, we do nothing.
|
||||
@ -1116,7 +1116,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
|
||||
convertCaseOfSelectedText(cursor, toLower);
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
|
||||
setMode(VimMode::Normal);
|
||||
break;
|
||||
@ -1143,7 +1143,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
} else if (checkPendingKey(Key(Qt::Key_G))) {
|
||||
// gu/gU, ToLower/ToUpper action.
|
||||
if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
// Different from Vim:
|
||||
// If there is no selection in Visual mode, we do nothing.
|
||||
@ -1155,7 +1155,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
|
||||
convertCaseOfSelectedText(cursor, toLower);
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
setMode(VimMode::Normal);
|
||||
break;
|
||||
}
|
||||
@ -1338,7 +1338,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
// Clear selection and enter normal mode.
|
||||
bool ret = clearSelection();
|
||||
if (!ret && checkMode(VimMode::Normal)) {
|
||||
emit m_editor->requestCloseFindReplaceDialog();
|
||||
emit m_editor->object()->requestCloseFindReplaceDialog();
|
||||
}
|
||||
|
||||
setMode(VimMode::Normal);
|
||||
@ -1367,9 +1367,9 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
setMode(mode);
|
||||
|
||||
if (m_mode == VimMode::VisualLine) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
expandSelectionToWholeLines(cursor);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1681,8 +1681,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
} else {
|
||||
// The first >/<, an Action.
|
||||
if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
VEditUtils::indentSelectedBlocks(m_editor->document(),
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
VEditUtils::indentSelectedBlocks(m_editor->documentW(),
|
||||
cursor,
|
||||
m_editConfig->m_tabSpaces,
|
||||
!unindent);
|
||||
@ -1857,7 +1857,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
// xx%, jump to a certain line (percentage of the documents).
|
||||
// Change the repeat from percentage to line number.
|
||||
Token *token = getRepeatToken();
|
||||
int bn = percentageToBlockNumber(m_editor->document(), token->m_repeat);
|
||||
int bn = percentageToBlockNumber(m_editor->documentW(), token->m_repeat);
|
||||
if (bn == -1) {
|
||||
break;
|
||||
} else {
|
||||
@ -2148,7 +2148,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
|
||||
clear_accept:
|
||||
resetState();
|
||||
m_editor->makeBlockVisible(m_editor->textCursor().block());
|
||||
m_editor->makeBlockVisible(m_editor->textCursorW().block());
|
||||
|
||||
accept:
|
||||
ret = true;
|
||||
@ -2353,7 +2353,7 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (m_resetPositionInBlock) {
|
||||
positionInBlock = cursor.positionInBlock();
|
||||
}
|
||||
@ -2389,7 +2389,7 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
|
||||
expandSelectionToWholeLines(cursor);
|
||||
}
|
||||
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3383,8 +3383,8 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
bool hasMoved = false;
|
||||
QTextCursor::MoveMode moveMode = QTextCursor::KeepAnchor;
|
||||
|
||||
@ -3598,7 +3598,7 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
|
||||
|
||||
exit:
|
||||
if (hasMoved) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3616,8 +3616,8 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
int oriPos = cursor.position();
|
||||
bool changed = false;
|
||||
QTextCursor::MoveMode moveMode = QTextCursor::KeepAnchor;
|
||||
@ -3810,7 +3810,7 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
|
||||
|
||||
exit:
|
||||
if (changed) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3853,7 +3853,7 @@ void VVim::processPasteAction(QList<Token> &p_tokens, bool p_pasteBefore)
|
||||
bool changed = false;
|
||||
int nrBlock = 0;
|
||||
int restorePos = -1;
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
|
||||
// Different from Vim:
|
||||
@ -3942,7 +3942,7 @@ void VVim::processPasteAction(QList<Token> &p_tokens, bool p_pasteBefore)
|
||||
cursor.endEditBlock();
|
||||
|
||||
if (changed) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
qDebug() << "text pasted" << text;
|
||||
@ -3962,8 +3962,8 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
bool hasMoved = false;
|
||||
QTextCursor::MoveMode moveMode = QTextCursor::KeepAnchor;
|
||||
|
||||
@ -4226,7 +4226,7 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
|
||||
int pos = cursor.selectionStart();
|
||||
bool allDeleted = false;
|
||||
if (pos == 0) {
|
||||
QTextBlock block = m_editor->document()->lastBlock();
|
||||
QTextBlock block = m_editor->documentW()->lastBlock();
|
||||
if (block.position() + block.length() - 1 == cursor.selectionEnd()) {
|
||||
allDeleted = true;
|
||||
}
|
||||
@ -4243,7 +4243,7 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
|
||||
|
||||
exit:
|
||||
if (hasMoved) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
setMode(VimMode::Insert);
|
||||
@ -4263,8 +4263,8 @@ void VVim::processIndentAction(QList<Token> &p_tokens, bool p_isIndent)
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
|
||||
if (to.isRange()) {
|
||||
bool changed = selectRange(cursor, doc, to.m_range, repeat);
|
||||
@ -4402,8 +4402,8 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
bool changed = false;
|
||||
QTextCursor::MoveMode moveMode = QTextCursor::KeepAnchor;
|
||||
int oriPos = cursor.position();
|
||||
@ -4500,7 +4500,7 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
|
||||
|
||||
exit:
|
||||
if (changed) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4517,10 +4517,10 @@ void VVim::processUndoAction(QList<Token> &p_tokens)
|
||||
repeat = to.m_repeat;
|
||||
}
|
||||
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
int i = 0;
|
||||
for (i = 0; i < repeat && doc->isUndoAvailable(); ++i) {
|
||||
m_editor->undo();
|
||||
m_editor->undoW();
|
||||
}
|
||||
|
||||
message(tr("Undo %1 %2").arg(i).arg(i > 1 ? tr("changes") : tr("change")));
|
||||
@ -4539,10 +4539,10 @@ void VVim::processRedoAction(QList<Token> &p_tokens)
|
||||
repeat = to.m_repeat;
|
||||
}
|
||||
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
int i = 0;
|
||||
for (i = 0; i < repeat && doc->isRedoAvailable(); ++i) {
|
||||
m_editor->redo();
|
||||
m_editor->redoW();
|
||||
}
|
||||
|
||||
message(tr("Redo %1 %2").arg(i).arg(i > 1 ? tr("changes") : tr("change")));
|
||||
@ -4550,7 +4550,7 @@ void VVim::processRedoAction(QList<Token> &p_tokens)
|
||||
|
||||
void VVim::processRedrawLineAction(QList<Token> &p_tokens, int p_dest)
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
int repeat = cursor.block().blockNumber();
|
||||
if (!p_tokens.isEmpty()) {
|
||||
Token to = p_tokens.takeFirst();
|
||||
@ -4562,7 +4562,7 @@ void VVim::processRedrawLineAction(QList<Token> &p_tokens, int p_dest)
|
||||
repeat = to.m_repeat - 1;
|
||||
}
|
||||
|
||||
VEditUtils::scrollBlockInPage(m_editor, repeat, p_dest);
|
||||
m_editor->scrollBlockInPage(repeat, p_dest);
|
||||
}
|
||||
|
||||
void VVim::processJumpLocationAction(QList<Token> &p_tokens, bool p_next)
|
||||
@ -4578,7 +4578,7 @@ void VVim::processJumpLocationAction(QList<Token> &p_tokens, bool p_next)
|
||||
repeat = to.m_repeat;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
Location loc;
|
||||
if (p_next) {
|
||||
while (m_locations.hasNext() && repeat > 0) {
|
||||
@ -4593,7 +4593,7 @@ void VVim::processJumpLocationAction(QList<Token> &p_tokens, bool p_next)
|
||||
}
|
||||
|
||||
if (loc.isValid()) {
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
if (loc.m_blockNumber >= doc->blockCount()) {
|
||||
message(tr("Mark has invalid line number"));
|
||||
return;
|
||||
@ -4607,11 +4607,11 @@ void VVim::processJumpLocationAction(QList<Token> &p_tokens, bool p_next)
|
||||
|
||||
if (!m_editor->isBlockVisible(block)) {
|
||||
// Scroll the block to the center of screen.
|
||||
VEditUtils::scrollBlockInPage(m_editor, block.blockNumber(), 1);
|
||||
m_editor->scrollBlockInPage(block.blockNumber(), 1);
|
||||
}
|
||||
|
||||
cursor.setPosition(block.position() + pib);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4643,7 +4643,7 @@ void VVim::processReplaceAction(QList<Token> &p_tokens)
|
||||
// If repeat is greater than the number of left characters in current line,
|
||||
// do nothing.
|
||||
// In visual mode, repeat is ignored.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (checkMode(VimMode::Normal)) {
|
||||
// Select the characters to be replaced.
|
||||
@ -4659,7 +4659,7 @@ void VVim::processReplaceAction(QList<Token> &p_tokens)
|
||||
cursor.endEditBlock();
|
||||
|
||||
if (changed) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
setMode(VimMode::Normal);
|
||||
}
|
||||
}
|
||||
@ -4683,7 +4683,7 @@ void VVim::processReverseCaseAction(QList<Token> &p_tokens)
|
||||
// If repeat is greater than the number of left characters in current line,
|
||||
// just change the actual number of left characters.
|
||||
// In visual mode, repeat is ignored and reverse the selected text.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (checkMode(VimMode::Normal)) {
|
||||
// Select the characters to be replaced.
|
||||
@ -4699,7 +4699,7 @@ void VVim::processReverseCaseAction(QList<Token> &p_tokens)
|
||||
cursor.endEditBlock();
|
||||
|
||||
if (changed) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
setMode(VimMode::Normal);
|
||||
}
|
||||
}
|
||||
@ -4726,12 +4726,12 @@ void VVim::processJoinAction(QList<Token> &p_tokens, bool p_modifySpaces)
|
||||
// In visual mode, repeat is ignored and join the highlighted lines.
|
||||
int firstBlock = -1;
|
||||
int blockCount = repeat;
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (checkMode(VimMode::Normal)) {
|
||||
firstBlock = cursor.block().blockNumber();
|
||||
} else {
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
firstBlock = doc->findBlock(cursor.selectionStart()).blockNumber();
|
||||
int lastBlock = doc->findBlock(cursor.selectionEnd()).blockNumber();
|
||||
blockCount = lastBlock - firstBlock + 1;
|
||||
@ -4741,17 +4741,17 @@ void VVim::processJoinAction(QList<Token> &p_tokens, bool p_modifySpaces)
|
||||
cursor.endEditBlock();
|
||||
|
||||
if (changed) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
setMode(VimMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
bool VVim::clearSelection()
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.clearSelection();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -4760,8 +4760,8 @@ bool VVim::clearSelection()
|
||||
|
||||
int VVim::blockCountOfPageStep() const
|
||||
{
|
||||
int lineCount = m_editor->document()->blockCount();
|
||||
QScrollBar *bar = m_editor->verticalScrollBar();
|
||||
int lineCount = m_editor->documentW()->blockCount();
|
||||
QScrollBar *bar = m_editor->verticalScrollBarW();
|
||||
int steps = (bar->maximum() - bar->minimum() + bar->pageStep());
|
||||
int pageLineCount = lineCount * (bar->pageStep() * 1.0 / steps);
|
||||
return pageLineCount;
|
||||
@ -4781,7 +4781,7 @@ void VVim::selectionToVisualMode(bool p_hasText)
|
||||
|
||||
void VVim::expandSelectionToWholeLines(QTextCursor &p_cursor)
|
||||
{
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
int curPos = p_cursor.position();
|
||||
int anchorPos = p_cursor.anchor();
|
||||
QTextBlock curBlock = doc->findBlock(curPos);
|
||||
@ -5031,12 +5031,12 @@ void VVim::deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock)
|
||||
|
||||
void VVim::copySelectedText(bool p_addNewLine)
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.beginEditBlock();
|
||||
copySelectedText(cursor, p_addNewLine);
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5318,15 +5318,15 @@ bool VVim::executeCommand(const QString &p_cmd)
|
||||
}else if (p_cmd.size() == 1) {
|
||||
if (p_cmd == "w") {
|
||||
// :w, save current file.
|
||||
emit m_editor->saveNote();
|
||||
emit m_editor->object()->saveNote();
|
||||
msg = tr("Note has been saved");
|
||||
} else if (p_cmd == "q") {
|
||||
// :q, quit edit mode.
|
||||
emit m_editor->discardAndRead();
|
||||
emit m_editor->object()->discardAndRead();
|
||||
msg = tr("Quit");
|
||||
} else if (p_cmd == "x") {
|
||||
// :x, save if there is any change and quit edit mode.
|
||||
emit m_editor->saveAndRead();
|
||||
emit m_editor->object()->saveAndRead();
|
||||
msg = tr("Quit with note having been saved");
|
||||
} else {
|
||||
validCommand = false;
|
||||
@ -5335,11 +5335,11 @@ bool VVim::executeCommand(const QString &p_cmd)
|
||||
if (p_cmd == "wq") {
|
||||
// :wq, save change and quit edit mode.
|
||||
// We treat it same as :x.
|
||||
emit m_editor->saveAndRead();
|
||||
emit m_editor->object()->saveAndRead();
|
||||
msg = tr("Quit with note having been saved");
|
||||
} else if (p_cmd == "q!") {
|
||||
// :q!, discard change and quit edit mode.
|
||||
emit m_editor->discardAndRead();
|
||||
emit m_editor->object()->discardAndRead();
|
||||
msg = tr("Quit");
|
||||
} else {
|
||||
validCommand = false;
|
||||
@ -5437,7 +5437,7 @@ bool VVim::processLeaderSequence(const Key &p_key)
|
||||
clearSearchHighlight();
|
||||
} else if (p_key == Key(Qt::Key_W)) {
|
||||
// <leader>w, save note
|
||||
emit m_editor->saveNote();
|
||||
emit m_editor->object()->saveNote();
|
||||
message(tr("Note has been saved"));
|
||||
} else {
|
||||
validSequence = false;
|
||||
@ -5624,7 +5624,7 @@ void VVim::processTitleJump(const QList<Token> &p_tokens, bool p_forward, int p_
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (m_editor->jumpTitle(p_forward, p_relativeLevel, repeat)) {
|
||||
// Record current location.
|
||||
m_locations.addLocation(cursor);
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include <QDebug>
|
||||
#include "vutils.h"
|
||||
|
||||
class VEdit;
|
||||
class VEditor;
|
||||
class QKeyEvent;
|
||||
class VEditConfig;
|
||||
class QKeyEvent;
|
||||
@ -26,7 +26,7 @@ class VVim : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VVim(VEdit *p_editor);
|
||||
explicit VVim(VEditor *p_editor);
|
||||
|
||||
// Struct for a location.
|
||||
struct Location
|
||||
@ -801,7 +801,7 @@ private:
|
||||
Register &getRegister(QChar p_regName) const;
|
||||
void setRegister(QChar p_regName, const QString &p_val);
|
||||
|
||||
VEdit *m_editor;
|
||||
VEditor *m_editor;
|
||||
const VEditConfig *m_editConfig;
|
||||
VimMode m_mode;
|
||||
|
||||
|
@ -95,14 +95,6 @@ enum HighlightBlockState
|
||||
Comment
|
||||
};
|
||||
|
||||
enum class LineNumberType
|
||||
{
|
||||
None = 0,
|
||||
Absolute,
|
||||
Relative,
|
||||
CodeBlock
|
||||
};
|
||||
|
||||
// Pages to open on start up.
|
||||
enum class StartupPageType
|
||||
{
|
||||
|
@ -18,44 +18,6 @@ extern VNote *g_vnote;
|
||||
|
||||
extern VMetaWordManager *g_mwMgr;
|
||||
|
||||
void VEditConfig::init(const QFontMetrics &p_metric,
|
||||
bool p_enableHeadingSequence)
|
||||
{
|
||||
update(p_metric);
|
||||
|
||||
// Init configs that do not support update later.
|
||||
m_enableVimMode = g_config->getEnableVimMode();
|
||||
|
||||
if (g_config->getLineDistanceHeight() <= 0) {
|
||||
m_lineDistanceHeight = 0;
|
||||
} else {
|
||||
m_lineDistanceHeight = g_config->getLineDistanceHeight() * VUtils::calculateScaleFactor();
|
||||
}
|
||||
|
||||
m_highlightWholeBlock = m_enableVimMode;
|
||||
|
||||
m_enableHeadingSequence = p_enableHeadingSequence;
|
||||
}
|
||||
|
||||
void VEditConfig::update(const QFontMetrics &p_metric)
|
||||
{
|
||||
if (g_config->getTabStopWidth() > 0) {
|
||||
m_tabStopWidth = g_config->getTabStopWidth() * p_metric.width(' ');
|
||||
} else {
|
||||
m_tabStopWidth = 0;
|
||||
}
|
||||
|
||||
m_expandTab = g_config->getIsExpandTab();
|
||||
|
||||
if (m_expandTab && (g_config->getTabStopWidth() > 0)) {
|
||||
m_tabSpaces = QString(g_config->getTabStopWidth(), ' ');
|
||||
} else {
|
||||
m_tabSpaces = "\t";
|
||||
}
|
||||
|
||||
m_cursorLineBg = QColor(g_config->getEditorCurrentLineBg());
|
||||
}
|
||||
|
||||
VEdit::VEdit(VFile *p_file, QWidget *p_parent)
|
||||
: QTextEdit(p_parent), m_file(p_file),
|
||||
m_editOps(NULL), m_enableInputMethod(true)
|
||||
|
53
src/vedit.h
53
src/vedit.h
@ -11,6 +11,8 @@
|
||||
#include <QFontMetrics>
|
||||
#include "vconstants.h"
|
||||
#include "vnotefile.h"
|
||||
#include "veditconfig.h"
|
||||
#include "veditor.h"
|
||||
|
||||
class VEditOperations;
|
||||
class QLabel;
|
||||
@ -21,55 +23,6 @@ class QResizeEvent;
|
||||
class QSize;
|
||||
class QWidget;
|
||||
|
||||
enum class SelectionId {
|
||||
CurrentLine = 0,
|
||||
SelectedWord,
|
||||
SearchedKeyword,
|
||||
SearchedKeywordUnderCursor,
|
||||
IncrementalSearchedKeyword,
|
||||
TrailingSapce,
|
||||
MaxSelection
|
||||
};
|
||||
|
||||
class VEditConfig {
|
||||
public:
|
||||
VEditConfig() : m_tabStopWidth(0),
|
||||
m_tabSpaces("\t"),
|
||||
m_enableVimMode(false),
|
||||
m_highlightWholeBlock(false),
|
||||
m_lineDistanceHeight(0),
|
||||
m_enableHeadingSequence(false)
|
||||
{}
|
||||
|
||||
void init(const QFontMetrics &p_metric,
|
||||
bool p_enableHeadingSequence);
|
||||
|
||||
// Only update those configs which could be updated online.
|
||||
void update(const QFontMetrics &p_metric);
|
||||
|
||||
// Width in pixels.
|
||||
int m_tabStopWidth;
|
||||
|
||||
bool m_expandTab;
|
||||
|
||||
// The literal string for Tab. It is spaces if Tab is expanded.
|
||||
QString m_tabSpaces;
|
||||
|
||||
bool m_enableVimMode;
|
||||
|
||||
// The background color of cursor line.
|
||||
QColor m_cursorLineBg;
|
||||
|
||||
// Whether highlight a visual line or a whole block.
|
||||
bool m_highlightWholeBlock;
|
||||
|
||||
// Line distance height in pixels.
|
||||
int m_lineDistanceHeight;
|
||||
|
||||
// Whether enable auto heading sequence.
|
||||
bool m_enableHeadingSequence;
|
||||
};
|
||||
|
||||
class LineNumberArea;
|
||||
|
||||
class VEdit : public QTextEdit
|
||||
@ -98,7 +51,7 @@ public:
|
||||
// User has enter the content to search, but does not enter the "find" button yet.
|
||||
bool peekText(const QString &p_text, uint p_options, bool p_forward = true);
|
||||
|
||||
// If @p_cursor is not now, set the position of @p_cursor instead of current
|
||||
// If @p_cursor is not null, set the position of @p_cursor instead of current
|
||||
// cursor.
|
||||
bool findText(const QString &p_text, uint p_options, bool p_forward,
|
||||
QTextCursor *p_cursor = NULL,
|
||||
|
44
src/veditconfig.cpp
Normal file
44
src/veditconfig.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include "veditconfig.h"
|
||||
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
void VEditConfig::init(const QFontMetrics &p_metric,
|
||||
bool p_enableHeadingSequence)
|
||||
{
|
||||
update(p_metric);
|
||||
|
||||
// Init configs that do not support update later.
|
||||
m_enableVimMode = g_config->getEnableVimMode();
|
||||
|
||||
if (g_config->getLineDistanceHeight() <= 0) {
|
||||
m_lineDistanceHeight = 0;
|
||||
} else {
|
||||
m_lineDistanceHeight = g_config->getLineDistanceHeight() * VUtils::calculateScaleFactor();
|
||||
}
|
||||
|
||||
m_highlightWholeBlock = m_enableVimMode;
|
||||
|
||||
m_enableHeadingSequence = p_enableHeadingSequence;
|
||||
}
|
||||
|
||||
void VEditConfig::update(const QFontMetrics &p_metric)
|
||||
{
|
||||
if (g_config->getTabStopWidth() > 0) {
|
||||
m_tabStopWidth = g_config->getTabStopWidth() * p_metric.width(' ');
|
||||
} else {
|
||||
m_tabStopWidth = 0;
|
||||
}
|
||||
|
||||
m_expandTab = g_config->getIsExpandTab();
|
||||
|
||||
if (m_expandTab && (g_config->getTabStopWidth() > 0)) {
|
||||
m_tabSpaces = QString(g_config->getTabStopWidth(), ' ');
|
||||
} else {
|
||||
m_tabSpaces = "\t";
|
||||
}
|
||||
|
||||
m_cursorLineBg = QColor(g_config->getEditorCurrentLineBg());
|
||||
}
|
48
src/veditconfig.h
Normal file
48
src/veditconfig.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef VEDITCONFIG_H
|
||||
#define VEDITCONFIG_H
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
|
||||
|
||||
class VEditConfig {
|
||||
public:
|
||||
VEditConfig() : m_tabStopWidth(0),
|
||||
m_tabSpaces("\t"),
|
||||
m_enableVimMode(false),
|
||||
m_highlightWholeBlock(false),
|
||||
m_lineDistanceHeight(0),
|
||||
m_enableHeadingSequence(false)
|
||||
{}
|
||||
|
||||
void init(const QFontMetrics &p_metric,
|
||||
bool p_enableHeadingSequence);
|
||||
|
||||
// Only update those configs which could be updated online.
|
||||
void update(const QFontMetrics &p_metric);
|
||||
|
||||
// Width in pixels.
|
||||
int m_tabStopWidth;
|
||||
|
||||
bool m_expandTab;
|
||||
|
||||
// The literal string for Tab. It is spaces if Tab is expanded.
|
||||
QString m_tabSpaces;
|
||||
|
||||
bool m_enableVimMode;
|
||||
|
||||
// The background color of cursor line.
|
||||
QColor m_cursorLineBg;
|
||||
|
||||
// Whether highlight a visual line or a whole block.
|
||||
bool m_highlightWholeBlock;
|
||||
|
||||
// Line distance height in pixels.
|
||||
int m_lineDistanceHeight;
|
||||
|
||||
// Whether enable auto heading sequence.
|
||||
bool m_enableHeadingSequence;
|
||||
};
|
||||
|
||||
#endif // VEDITCONFIG_H
|
@ -1,18 +1,21 @@
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
#include <QFontMetrics>
|
||||
#include "vedit.h"
|
||||
#include "veditor.h"
|
||||
#include "veditoperations.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
|
||||
: QObject(p_editor), m_editor(p_editor), m_file(p_file),
|
||||
m_editConfig(&p_editor->getConfig()), m_vim(NULL)
|
||||
VEditOperations::VEditOperations(VEditor *p_editor, VFile *p_file)
|
||||
: QObject(p_editor->getEditor()),
|
||||
m_editor(p_editor),
|
||||
m_file(p_file),
|
||||
m_editConfig(&p_editor->getConfig()),
|
||||
m_vim(NULL)
|
||||
{
|
||||
connect(m_editor, &VEdit::configUpdated,
|
||||
connect(m_editor->object(), &VEditorObject::configUpdated,
|
||||
this, &VEditOperations::handleEditConfigUpdated);
|
||||
|
||||
if (m_editConfig->m_enableVimMode) {
|
||||
@ -29,7 +32,7 @@ VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
|
||||
|
||||
void VEditOperations::insertTextAtCurPos(const QString &p_text)
|
||||
{
|
||||
m_editor->insertPlainText(p_text);
|
||||
m_editor->insertPlainTextW(p_text);
|
||||
}
|
||||
|
||||
VEditOperations::~VEditOperations()
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "vfile.h"
|
||||
#include "utils/vvim.h"
|
||||
|
||||
class VEdit;
|
||||
class VEditor;
|
||||
class VEditConfig;
|
||||
class QMimeData;
|
||||
class QKeyEvent;
|
||||
@ -17,7 +17,7 @@ class VEditOperations: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VEditOperations(VEdit *p_editor, VFile *p_file);
|
||||
VEditOperations(VEditor *p_editor, VFile *p_file);
|
||||
|
||||
virtual ~VEditOperations();
|
||||
|
||||
@ -64,7 +64,7 @@ private:
|
||||
protected:
|
||||
void insertTextAtCurPos(const QString &p_text);
|
||||
|
||||
VEdit *m_editor;
|
||||
VEditor *m_editor;
|
||||
QPointer<VFile> m_file;
|
||||
VEditConfig *m_editConfig;
|
||||
VVim *m_vim;
|
||||
|
917
src/veditor.cpp
Normal file
917
src/veditor.cpp
Normal file
@ -0,0 +1,917 @@
|
||||
#include "veditor.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "utils/veditutils.h"
|
||||
#include "veditoperations.h"
|
||||
#include "dialog/vinsertlinkdialog.h"
|
||||
#include "utils/vmetawordmanager.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
extern VMetaWordManager *g_mwMgr;
|
||||
|
||||
VEditor::VEditor(VFile *p_file, QWidget *p_editor)
|
||||
: m_editor(p_editor),
|
||||
m_object(new VEditorObject(this, p_editor)),
|
||||
m_file(p_file),
|
||||
m_editOps(nullptr),
|
||||
m_document(nullptr),
|
||||
m_enableInputMethod(true)
|
||||
{
|
||||
}
|
||||
|
||||
VEditor::~VEditor()
|
||||
{
|
||||
if (m_file && m_document) {
|
||||
QObject::disconnect(m_document, &QTextDocument::modificationChanged,
|
||||
(VFile *)m_file, &VFile::setModified);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::init()
|
||||
{
|
||||
const int labelTimerInterval = 500;
|
||||
const int extraSelectionHighlightTimer = 500;
|
||||
const int labelSize = 64;
|
||||
|
||||
m_document = documentW();
|
||||
|
||||
m_selectedWordColor = QColor(g_config->getEditorSelectedWordBg());
|
||||
m_searchedWordColor = QColor(g_config->getEditorSearchedWordBg());
|
||||
m_searchedWordCursorColor = QColor(g_config->getEditorSearchedWordCursorBg());
|
||||
m_incrementalSearchedWordColor = QColor(g_config->getEditorIncrementalSearchedWordBg());
|
||||
m_trailingSpaceColor = QColor(g_config->getEditorTrailingSpaceBg());
|
||||
|
||||
QPixmap wrapPixmap(":/resources/icons/search_wrap.svg");
|
||||
m_wrapLabel = new QLabel(m_editor);
|
||||
m_wrapLabel->setPixmap(wrapPixmap.scaled(labelSize, labelSize));
|
||||
m_wrapLabel->hide();
|
||||
m_labelTimer = new QTimer(m_editor);
|
||||
m_labelTimer->setSingleShot(true);
|
||||
m_labelTimer->setInterval(labelTimerInterval);
|
||||
QObject::connect(m_labelTimer, &QTimer::timeout,
|
||||
m_object, &VEditorObject::labelTimerTimeout);
|
||||
|
||||
m_highlightTimer = new QTimer(m_editor);
|
||||
m_highlightTimer->setSingleShot(true);
|
||||
m_highlightTimer->setInterval(extraSelectionHighlightTimer);
|
||||
QObject::connect(m_highlightTimer, &QTimer::timeout,
|
||||
m_object, &VEditorObject::doHighlightExtraSelections);
|
||||
|
||||
m_extraSelections.resize((int)SelectionId::MaxSelection);
|
||||
|
||||
QObject::connect(m_document, &QTextDocument::modificationChanged,
|
||||
(VFile *)m_file, &VFile::setModified);
|
||||
|
||||
updateFontAndPalette();
|
||||
|
||||
m_config.init(QFontMetrics(m_editor->font()), false);
|
||||
updateEditConfig();
|
||||
}
|
||||
|
||||
void VEditor::labelTimerTimeout()
|
||||
{
|
||||
m_wrapLabel->hide();
|
||||
}
|
||||
|
||||
void VEditor::doHighlightExtraSelections()
|
||||
{
|
||||
int nrExtra = m_extraSelections.size();
|
||||
Q_ASSERT(nrExtra == (int)SelectionId::MaxSelection);
|
||||
QList<QTextEdit::ExtraSelection> extraSelects;
|
||||
for (int i = 0; i < nrExtra; ++i) {
|
||||
extraSelects.append(m_extraSelections[i]);
|
||||
}
|
||||
|
||||
setExtraSelectionsW(extraSelects);
|
||||
}
|
||||
|
||||
void VEditor::updateEditConfig()
|
||||
{
|
||||
m_config.update(QFontMetrics(m_editor->font()));
|
||||
|
||||
if (m_config.m_tabStopWidth > 0) {
|
||||
setTabStopWidthW(m_config.m_tabStopWidth);
|
||||
}
|
||||
|
||||
emit m_object->configUpdated();
|
||||
}
|
||||
|
||||
void VEditor::highlightOnCursorPositionChanged()
|
||||
{
|
||||
static QTextCursor lastCursor;
|
||||
|
||||
QTextCursor cursor = textCursorW();
|
||||
if (lastCursor.isNull() || cursor.blockNumber() != lastCursor.blockNumber()) {
|
||||
highlightCurrentLine();
|
||||
highlightTrailingSpace();
|
||||
} else {
|
||||
// Judge whether we have trailing space at current line.
|
||||
QString text = cursor.block().text();
|
||||
if (text.rbegin()->isSpace()) {
|
||||
highlightTrailingSpace();
|
||||
}
|
||||
|
||||
// Handle word-wrap in one block.
|
||||
// Highlight current line if in different visual line.
|
||||
if ((lastCursor.positionInBlock() - lastCursor.columnNumber()) !=
|
||||
(cursor.positionInBlock() - cursor.columnNumber())) {
|
||||
highlightCurrentLine();
|
||||
}
|
||||
}
|
||||
|
||||
lastCursor = cursor;
|
||||
}
|
||||
|
||||
void VEditor::highlightCurrentLine()
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::CurrentLine];
|
||||
if (g_config->getHighlightCursorLine()) {
|
||||
// Need to highlight current line.
|
||||
selects.clear();
|
||||
|
||||
// A long block maybe splited into multiple visual lines.
|
||||
QTextEdit::ExtraSelection select;
|
||||
select.format.setBackground(m_config.m_cursorLineBg);
|
||||
select.format.setProperty(QTextFormat::FullWidthSelection, true);
|
||||
|
||||
QTextCursor cursor = textCursorW();
|
||||
if (m_config.m_highlightWholeBlock) {
|
||||
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor, 1);
|
||||
QTextBlock block = cursor.block();
|
||||
int blockEnd = block.position() + block.length();
|
||||
int pos = -1;
|
||||
while (cursor.position() < blockEnd && pos != cursor.position()) {
|
||||
QTextEdit::ExtraSelection newSelect = select;
|
||||
newSelect.cursor = cursor;
|
||||
selects.append(newSelect);
|
||||
|
||||
pos = cursor.position();
|
||||
cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, 1);
|
||||
}
|
||||
} else {
|
||||
cursor.clearSelection();
|
||||
select.cursor = cursor;
|
||||
selects.append(select);
|
||||
}
|
||||
} else {
|
||||
// Need to clear current line highlight.
|
||||
if (selects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
selects.clear();
|
||||
}
|
||||
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
// Do not highlight trailing spaces with current cursor right behind.
|
||||
static void trailingSpaceFilter(VEditor *p_editor, QList<QTextEdit::ExtraSelection> &p_result)
|
||||
{
|
||||
QTextCursor cursor = p_editor->textCursorW();
|
||||
if (!cursor.atBlockEnd()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cursorPos = cursor.position();
|
||||
for (auto it = p_result.begin(); it != p_result.end(); ++it) {
|
||||
if (it->cursor.selectionEnd() == cursorPos) {
|
||||
p_result.erase(it);
|
||||
|
||||
// There will be only one.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::highlightTrailingSpace()
|
||||
{
|
||||
if (!g_config->getEnableTrailingSpaceHighlight()) {
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::TrailingSapce];
|
||||
if (!selects.isEmpty()) {
|
||||
selects.clear();
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCharFormat format;
|
||||
format.setBackground(m_trailingSpaceColor);
|
||||
QString text("\\s+$");
|
||||
highlightTextAll(text,
|
||||
FindOption::RegularExpression,
|
||||
SelectionId::TrailingSapce,
|
||||
format,
|
||||
trailingSpaceFilter);
|
||||
}
|
||||
|
||||
void VEditor::highlightExtraSelections(bool p_now)
|
||||
{
|
||||
m_highlightTimer->stop();
|
||||
if (p_now) {
|
||||
doHighlightExtraSelections();
|
||||
} else {
|
||||
m_highlightTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::highlightTextAll(const QString &p_text,
|
||||
uint p_options,
|
||||
SelectionId p_id,
|
||||
QTextCharFormat p_format,
|
||||
void (*p_filter)(VEditor *,
|
||||
QList<QTextEdit::ExtraSelection> &))
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)p_id];
|
||||
if (!p_text.isEmpty()) {
|
||||
selects.clear();
|
||||
|
||||
QList<QTextCursor> occurs = findTextAll(p_text, p_options);
|
||||
for (int i = 0; i < occurs.size(); ++i) {
|
||||
QTextEdit::ExtraSelection select;
|
||||
select.format = p_format;
|
||||
select.cursor = occurs[i];
|
||||
selects.append(select);
|
||||
}
|
||||
} else {
|
||||
if (selects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
selects.clear();
|
||||
}
|
||||
|
||||
if (p_filter) {
|
||||
p_filter(this, selects);
|
||||
}
|
||||
|
||||
highlightExtraSelections();
|
||||
}
|
||||
|
||||
QList<QTextCursor> VEditor::findTextAll(const QString &p_text, uint p_options)
|
||||
{
|
||||
QList<QTextCursor> results;
|
||||
if (p_text.isEmpty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
// Options
|
||||
QTextDocument::FindFlags findFlags;
|
||||
bool caseSensitive = false;
|
||||
if (p_options & FindOption::CaseSensitive) {
|
||||
findFlags |= QTextDocument::FindCaseSensitively;
|
||||
caseSensitive = true;
|
||||
}
|
||||
|
||||
if (p_options & FindOption::WholeWordOnly) {
|
||||
findFlags |= QTextDocument::FindWholeWords;
|
||||
}
|
||||
|
||||
// Use regular expression
|
||||
bool useRegExp = false;
|
||||
QRegExp exp;
|
||||
if (p_options & FindOption::RegularExpression) {
|
||||
useRegExp = true;
|
||||
exp = QRegExp(p_text,
|
||||
caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
int startPos = 0;
|
||||
QTextCursor cursor;
|
||||
while (true) {
|
||||
if (useRegExp) {
|
||||
cursor = m_document->find(exp, startPos, findFlags);
|
||||
} else {
|
||||
cursor = m_document->find(p_text, startPos, findFlags);
|
||||
}
|
||||
|
||||
if (cursor.isNull()) {
|
||||
break;
|
||||
} else {
|
||||
results.append(cursor);
|
||||
startPos = cursor.selectionEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
void VEditor::highlightSelectedWord()
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SelectedWord];
|
||||
if (!g_config->getHighlightSelectedWord()) {
|
||||
if (!selects.isEmpty()) {
|
||||
selects.clear();
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = textCursorW().selectedText().trimmed();
|
||||
if (text.isEmpty() || wordInSearchedSelection(text)) {
|
||||
selects.clear();
|
||||
highlightExtraSelections(true);
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCharFormat format;
|
||||
format.setBackground(m_selectedWordColor);
|
||||
highlightTextAll(text,
|
||||
FindOption::CaseSensitive,
|
||||
SelectionId::SelectedWord,
|
||||
format);
|
||||
}
|
||||
|
||||
bool VEditor::wordInSearchedSelection(const QString &p_text)
|
||||
{
|
||||
QString text = p_text.trimmed();
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
|
||||
for (int i = 0; i < selects.size(); ++i) {
|
||||
QString searchedWord = selects[i].cursor.selectedText();
|
||||
if (text == searchedWord.trimmed()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VEditor::isModified() const
|
||||
{
|
||||
Q_ASSERT(m_file ? (m_file->isModified() == m_document->isModified())
|
||||
: true);
|
||||
return m_document->isModified();
|
||||
}
|
||||
|
||||
void VEditor::setModified(bool p_modified)
|
||||
{
|
||||
m_document->setModified(p_modified);
|
||||
if (m_file) {
|
||||
m_file->setModified(p_modified);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::insertImage()
|
||||
{
|
||||
if (m_editOps) {
|
||||
m_editOps->insertImage();
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::insertLink()
|
||||
{
|
||||
if (!m_editOps) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text;
|
||||
QString linkText, linkUrl;
|
||||
QTextCursor cursor = textCursorW();
|
||||
if (cursor.hasSelection()) {
|
||||
text = VEditUtils::selectedText(cursor).trimmed();
|
||||
// Only pure space is accepted.
|
||||
QRegExp reg("[\\S ]*");
|
||||
if (reg.exactMatch(text)) {
|
||||
QUrl url = QUrl::fromUserInput(text,
|
||||
m_file->fetchBasePath());
|
||||
QRegExp urlReg("[\\.\\\\/]");
|
||||
if (url.isValid()
|
||||
&& text.contains(urlReg)) {
|
||||
// Url.
|
||||
linkUrl = text;
|
||||
} else {
|
||||
// Text.
|
||||
linkText = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VInsertLinkDialog dialog(QObject::tr("Insert Link"),
|
||||
"",
|
||||
"",
|
||||
linkText,
|
||||
linkUrl,
|
||||
m_editor);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
linkText = dialog.getLinkText();
|
||||
linkUrl = dialog.getLinkUrl();
|
||||
Q_ASSERT(!linkText.isEmpty() && !linkUrl.isEmpty());
|
||||
|
||||
m_editOps->insertLink(linkText, linkUrl);
|
||||
}
|
||||
}
|
||||
|
||||
bool VEditor::peekText(const QString &p_text, uint p_options, bool p_forward)
|
||||
{
|
||||
if (p_text.isEmpty()) {
|
||||
makeBlockVisible(m_document->findBlock(textCursorW().selectionStart()));
|
||||
highlightIncrementalSearchedWord(QTextCursor());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool wrapped = false;
|
||||
QTextCursor retCursor;
|
||||
bool found = findTextHelper(p_text,
|
||||
p_options,
|
||||
p_forward,
|
||||
p_forward ? textCursorW().position() + 1
|
||||
: textCursorW().position(),
|
||||
wrapped,
|
||||
retCursor);
|
||||
if (found) {
|
||||
makeBlockVisible(m_document->findBlock(retCursor.selectionStart()));
|
||||
highlightIncrementalSearchedWord(retCursor);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool VEditor::findText(const QString &p_text,
|
||||
uint p_options,
|
||||
bool p_forward,
|
||||
QTextCursor *p_cursor,
|
||||
QTextCursor::MoveMode p_moveMode)
|
||||
{
|
||||
clearIncrementalSearchedWordHighlight();
|
||||
|
||||
if (p_text.isEmpty()) {
|
||||
clearSearchedWordHighlight();
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextCursor cursor = textCursorW();
|
||||
bool wrapped = false;
|
||||
QTextCursor retCursor;
|
||||
int matches = 0;
|
||||
int start = p_forward ? cursor.position() + 1 : cursor.position();
|
||||
if (p_cursor) {
|
||||
start = p_forward ? p_cursor->position() + 1 : p_cursor->position();
|
||||
}
|
||||
|
||||
bool found = findTextHelper(p_text, p_options, p_forward, start,
|
||||
wrapped, retCursor);
|
||||
if (found) {
|
||||
Q_ASSERT(!retCursor.isNull());
|
||||
if (wrapped) {
|
||||
showWrapLabel();
|
||||
}
|
||||
|
||||
if (p_cursor) {
|
||||
p_cursor->setPosition(retCursor.selectionStart(), p_moveMode);
|
||||
} else {
|
||||
cursor.setPosition(retCursor.selectionStart(), p_moveMode);
|
||||
setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
highlightSearchedWord(p_text, p_options);
|
||||
highlightSearchedWordUnderCursor(retCursor);
|
||||
matches = m_extraSelections[(int)SelectionId::SearchedKeyword].size();
|
||||
} else {
|
||||
clearSearchedWordHighlight();
|
||||
}
|
||||
|
||||
if (matches == 0) {
|
||||
emit m_object->statusMessage(QObject::tr("Found no match"));
|
||||
} else {
|
||||
emit m_object->statusMessage(QObject::tr("Found %1 %2").arg(matches)
|
||||
.arg(matches > 1 ? QObject::tr("matches")
|
||||
: QObject::tr("match")));
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void VEditor::highlightIncrementalSearchedWord(const QTextCursor &p_cursor)
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::IncrementalSearchedKeyword];
|
||||
if (!g_config->getHighlightSearchedWord() || !p_cursor.hasSelection()) {
|
||||
if (!selects.isEmpty()) {
|
||||
selects.clear();
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
selects.clear();
|
||||
QTextEdit::ExtraSelection select;
|
||||
select.format.setBackground(m_incrementalSearchedWordColor);
|
||||
select.cursor = p_cursor;
|
||||
selects.append(select);
|
||||
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
// Use QPlainTextEdit::find() instead of QTextDocument::find() because the later has
|
||||
// bugs in searching backward.
|
||||
bool VEditor::findTextHelper(const QString &p_text,
|
||||
uint p_options,
|
||||
bool p_forward,
|
||||
int p_start,
|
||||
bool &p_wrapped,
|
||||
QTextCursor &p_cursor)
|
||||
{
|
||||
p_wrapped = false;
|
||||
bool found = false;
|
||||
|
||||
// Options
|
||||
QTextDocument::FindFlags findFlags;
|
||||
bool caseSensitive = false;
|
||||
if (p_options & FindOption::CaseSensitive) {
|
||||
findFlags |= QTextDocument::FindCaseSensitively;
|
||||
caseSensitive = true;
|
||||
}
|
||||
|
||||
if (p_options & FindOption::WholeWordOnly) {
|
||||
findFlags |= QTextDocument::FindWholeWords;
|
||||
}
|
||||
|
||||
if (!p_forward) {
|
||||
findFlags |= QTextDocument::FindBackward;
|
||||
}
|
||||
|
||||
// Use regular expression
|
||||
bool useRegExp = false;
|
||||
QRegExp exp;
|
||||
if (p_options & FindOption::RegularExpression) {
|
||||
useRegExp = true;
|
||||
exp = QRegExp(p_text,
|
||||
caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
// Store current state of the cursor.
|
||||
QTextCursor cursor = textCursorW();
|
||||
if (cursor.position() != p_start) {
|
||||
if (p_start < 0) {
|
||||
p_start = 0;
|
||||
} else if (p_start > m_document->characterCount()) {
|
||||
p_start = m_document->characterCount();
|
||||
}
|
||||
|
||||
QTextCursor startCursor = cursor;
|
||||
startCursor.setPosition(p_start);
|
||||
setTextCursorW(startCursor);
|
||||
}
|
||||
|
||||
while (!found) {
|
||||
if (useRegExp) {
|
||||
found = findW(exp, findFlags);
|
||||
} else {
|
||||
found = findW(p_text, findFlags);
|
||||
}
|
||||
|
||||
if (p_wrapped) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Wrap to the other end of the document to search again.
|
||||
p_wrapped = true;
|
||||
QTextCursor wrapCursor = textCursorW();
|
||||
if (p_forward) {
|
||||
wrapCursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
|
||||
} else {
|
||||
wrapCursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
|
||||
}
|
||||
|
||||
setTextCursorW(wrapCursor);
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
p_cursor = textCursorW();
|
||||
}
|
||||
|
||||
// Restore the original cursor.
|
||||
setTextCursorW(cursor);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void VEditor::clearIncrementalSearchedWordHighlight(bool p_now)
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::IncrementalSearchedKeyword];
|
||||
if (selects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
selects.clear();
|
||||
highlightExtraSelections(p_now);
|
||||
}
|
||||
|
||||
void VEditor::clearSearchedWordHighlight()
|
||||
{
|
||||
clearIncrementalSearchedWordHighlight(false);
|
||||
clearSearchedWordUnderCursorHighlight(false);
|
||||
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
|
||||
if (selects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
selects.clear();
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
void VEditor::clearSearchedWordUnderCursorHighlight(bool p_now)
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeywordUnderCursor];
|
||||
if (selects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
selects.clear();
|
||||
highlightExtraSelections(p_now);
|
||||
}
|
||||
|
||||
void VEditor::showWrapLabel()
|
||||
{
|
||||
int labelW = m_wrapLabel->width();
|
||||
int labelH = m_wrapLabel->height();
|
||||
int x = (m_editor->width() - labelW) / 2;
|
||||
int y = (m_editor->height() - labelH) / 2;
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
}
|
||||
|
||||
m_wrapLabel->move(x, y);
|
||||
m_wrapLabel->show();
|
||||
m_labelTimer->stop();
|
||||
m_labelTimer->start();
|
||||
}
|
||||
|
||||
void VEditor::highlightSearchedWord(const QString &p_text, uint p_options)
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
|
||||
if (!g_config->getHighlightSearchedWord() || p_text.isEmpty()) {
|
||||
if (!selects.isEmpty()) {
|
||||
selects.clear();
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCharFormat format;
|
||||
format.setBackground(m_searchedWordColor);
|
||||
highlightTextAll(p_text, p_options, SelectionId::SearchedKeyword, format);
|
||||
}
|
||||
|
||||
void VEditor::highlightSearchedWordUnderCursor(const QTextCursor &p_cursor)
|
||||
{
|
||||
QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeywordUnderCursor];
|
||||
if (!p_cursor.hasSelection()) {
|
||||
if (!selects.isEmpty()) {
|
||||
selects.clear();
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
selects.clear();
|
||||
QTextEdit::ExtraSelection select;
|
||||
select.format.setBackground(m_searchedWordCursorColor);
|
||||
select.cursor = p_cursor;
|
||||
selects.append(select);
|
||||
|
||||
highlightExtraSelections(true);
|
||||
}
|
||||
|
||||
void VEditor::replaceText(const QString &p_text,
|
||||
uint p_options,
|
||||
const QString &p_replaceText,
|
||||
bool p_findNext)
|
||||
{
|
||||
QTextCursor cursor = textCursorW();
|
||||
bool wrapped = false;
|
||||
QTextCursor retCursor;
|
||||
bool found = findTextHelper(p_text,
|
||||
p_options, true,
|
||||
cursor.position(),
|
||||
wrapped,
|
||||
retCursor);
|
||||
if (found) {
|
||||
if (retCursor.selectionStart() == cursor.position()) {
|
||||
// Matched.
|
||||
retCursor.beginEditBlock();
|
||||
retCursor.insertText(p_replaceText);
|
||||
retCursor.endEditBlock();
|
||||
setTextCursorW(retCursor);
|
||||
}
|
||||
|
||||
if (p_findNext) {
|
||||
findText(p_text, p_options, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::replaceTextAll(const QString &p_text,
|
||||
uint p_options,
|
||||
const QString &p_replaceText)
|
||||
{
|
||||
// Replace from the start to the end and restore the cursor.
|
||||
QTextCursor cursor = textCursorW();
|
||||
int nrReplaces = 0;
|
||||
QTextCursor tmpCursor = cursor;
|
||||
tmpCursor.setPosition(0);
|
||||
setTextCursorW(tmpCursor);
|
||||
int start = tmpCursor.position();
|
||||
while (true) {
|
||||
bool wrapped = false;
|
||||
QTextCursor retCursor;
|
||||
bool found = findTextHelper(p_text,
|
||||
p_options,
|
||||
true,
|
||||
start,
|
||||
wrapped,
|
||||
retCursor);
|
||||
if (!found) {
|
||||
break;
|
||||
} else {
|
||||
if (wrapped) {
|
||||
// Wrap back.
|
||||
break;
|
||||
}
|
||||
|
||||
nrReplaces++;
|
||||
retCursor.beginEditBlock();
|
||||
retCursor.insertText(p_replaceText);
|
||||
retCursor.endEditBlock();
|
||||
setTextCursorW(retCursor);
|
||||
start = retCursor.position();
|
||||
}
|
||||
}
|
||||
|
||||
// Restore cursor position.
|
||||
cursor.clearSelection();
|
||||
setTextCursorW(cursor);
|
||||
qDebug() << "replace all" << nrReplaces << "occurences";
|
||||
|
||||
emit m_object->statusMessage(QObject::tr("Replace %1 %2").arg(nrReplaces)
|
||||
.arg(nrReplaces > 1 ? QObject::tr("occurences")
|
||||
: QObject::tr("occurence")));
|
||||
}
|
||||
|
||||
void VEditor::evaluateMagicWords()
|
||||
{
|
||||
QString text;
|
||||
QTextCursor cursor = textCursorW();
|
||||
if (!cursor.hasSelection()) {
|
||||
// Get the WORD in current cursor.
|
||||
int start, end;
|
||||
VEditUtils::findCurrentWORD(cursor, start, end);
|
||||
|
||||
if (start == end) {
|
||||
return;
|
||||
} else {
|
||||
cursor.setPosition(start);
|
||||
cursor.setPosition(end, QTextCursor::KeepAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
text = VEditUtils::selectedText(cursor);
|
||||
Q_ASSERT(!text.isEmpty());
|
||||
QString evaText = g_mwMgr->evaluate(text);
|
||||
if (text != evaText) {
|
||||
qDebug() << "evaluateMagicWords" << text << evaText;
|
||||
|
||||
cursor.insertText(evaText);
|
||||
|
||||
if (m_editOps) {
|
||||
m_editOps->setVimMode(VimMode::Insert);
|
||||
}
|
||||
|
||||
setTextCursorW(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::setReadOnlyAndHighlightCurrentLine(bool p_readonly)
|
||||
{
|
||||
setReadOnlyW(p_readonly);
|
||||
highlightCurrentLine();
|
||||
}
|
||||
|
||||
bool VEditor::handleMousePressEvent(QMouseEvent *p_event)
|
||||
{
|
||||
if (p_event->button() == Qt::LeftButton
|
||||
&& p_event->modifiers() == Qt::ControlModifier
|
||||
&& !textCursorW().hasSelection()) {
|
||||
m_oriMouseX = p_event->x();
|
||||
m_oriMouseY = p_event->y();
|
||||
m_readyToScroll = true;
|
||||
m_mouseMoveScrolled = false;
|
||||
p_event->accept();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_readyToScroll = false;
|
||||
m_mouseMoveScrolled = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VEditor::handleMouseReleaseEvent(QMouseEvent *p_event)
|
||||
{
|
||||
if (m_mouseMoveScrolled || m_readyToScroll) {
|
||||
viewportW()->setCursor(Qt::IBeamCursor);
|
||||
m_readyToScroll = false;
|
||||
m_mouseMoveScrolled = false;
|
||||
p_event->accept();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_readyToScroll = false;
|
||||
m_mouseMoveScrolled = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VEditor::handleMouseMoveEvent(QMouseEvent *p_event)
|
||||
{
|
||||
const int threshold = 5;
|
||||
|
||||
if (m_readyToScroll) {
|
||||
int deltaX = p_event->x() - m_oriMouseX;
|
||||
int deltaY = p_event->y() - m_oriMouseY;
|
||||
|
||||
if (qAbs(deltaX) >= threshold || qAbs(deltaY) >= threshold) {
|
||||
m_oriMouseX = p_event->x();
|
||||
m_oriMouseY = p_event->y();
|
||||
|
||||
if (!m_mouseMoveScrolled) {
|
||||
m_mouseMoveScrolled = true;
|
||||
viewportW()->setCursor(Qt::SizeAllCursor);
|
||||
}
|
||||
|
||||
QScrollBar *verBar = verticalScrollBarW();
|
||||
QScrollBar *horBar = horizontalScrollBarW();
|
||||
if (verBar->isVisible()) {
|
||||
verBar->setValue(verBar->value() - deltaY);
|
||||
}
|
||||
|
||||
if (horBar->isVisible()) {
|
||||
horBar->setValue(horBar->value() - deltaX);
|
||||
}
|
||||
}
|
||||
|
||||
p_event->accept();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void VEditor::requestUpdateVimStatus()
|
||||
{
|
||||
if (m_editOps) {
|
||||
m_editOps->requestUpdateVimStatus();
|
||||
} else {
|
||||
emit m_object->vimStatusUpdated(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool VEditor::handleInputMethodQuery(Qt::InputMethodQuery p_query,
|
||||
QVariant &p_var) const
|
||||
{
|
||||
if (p_query == Qt::ImEnabled) {
|
||||
p_var = m_enableInputMethod;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void VEditor::setInputMethodEnabled(bool p_enabled)
|
||||
{
|
||||
if (m_enableInputMethod != p_enabled) {
|
||||
m_enableInputMethod = p_enabled;
|
||||
|
||||
QInputMethod *im = QGuiApplication::inputMethod();
|
||||
im->reset();
|
||||
|
||||
// Ask input method to query current state, which will call inputMethodQuery().
|
||||
im->update(Qt::ImEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::decorateText(TextDecoration p_decoration)
|
||||
{
|
||||
if (m_editOps) {
|
||||
m_editOps->decorateText(p_decoration);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditor::updateConfig()
|
||||
{
|
||||
updateEditConfig();
|
||||
}
|
372
src/veditor.h
Normal file
372
src/veditor.h
Normal file
@ -0,0 +1,372 @@
|
||||
#ifndef VEDITOR_H
|
||||
#define VEDITOR_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
#include <QTextEdit>
|
||||
#include <QColor>
|
||||
|
||||
#include "veditconfig.h"
|
||||
#include "vfile.h"
|
||||
|
||||
class QWidget;
|
||||
class VEditorObject;
|
||||
class VEditOperations;
|
||||
class QTimer;
|
||||
class QLabel;
|
||||
class VVim;
|
||||
|
||||
|
||||
enum class SelectionId {
|
||||
CurrentLine = 0,
|
||||
SelectedWord,
|
||||
SearchedKeyword,
|
||||
SearchedKeywordUnderCursor,
|
||||
IncrementalSearchedKeyword,
|
||||
TrailingSapce,
|
||||
MaxSelection
|
||||
};
|
||||
|
||||
|
||||
// Abstract class for an edit.
|
||||
// Should inherit this class as well as QPlainTextEdit or QTextEdit.
|
||||
// Will replace VEdit eventually.
|
||||
class VEditor
|
||||
{
|
||||
public:
|
||||
explicit VEditor(VFile *p_file, QWidget *p_editor);
|
||||
|
||||
virtual ~VEditor();
|
||||
|
||||
void highlightCurrentLine();
|
||||
|
||||
virtual void beginEdit() = 0;
|
||||
|
||||
virtual void endEdit() = 0;
|
||||
|
||||
// Save buffer content to VFile.
|
||||
virtual void saveFile() = 0;
|
||||
|
||||
virtual void reloadFile() = 0;
|
||||
|
||||
virtual bool scrollToBlock(int p_blockNumber) = 0;
|
||||
|
||||
bool isModified() const;
|
||||
|
||||
void setModified(bool p_modified);
|
||||
|
||||
// User requests to insert an image.
|
||||
void insertImage();
|
||||
|
||||
// User requests to insert a link.
|
||||
void insertLink();
|
||||
|
||||
// Used for incremental search.
|
||||
// User has enter the content to search, but does not enter the "find" button yet.
|
||||
bool peekText(const QString &p_text, uint p_options, bool p_forward = true);
|
||||
|
||||
// If @p_cursor is not null, set the position of @p_cursor instead of current
|
||||
// cursor.
|
||||
bool findText(const QString &p_text,
|
||||
uint p_options,
|
||||
bool p_forward,
|
||||
QTextCursor *p_cursor = nullptr,
|
||||
QTextCursor::MoveMode p_moveMode = QTextCursor::MoveAnchor);
|
||||
|
||||
void replaceText(const QString &p_text,
|
||||
uint p_options,
|
||||
const QString &p_replaceText,
|
||||
bool p_findNext);
|
||||
|
||||
void replaceTextAll(const QString &p_text,
|
||||
uint p_options,
|
||||
const QString &p_replaceText);
|
||||
|
||||
// Scroll the content to make @p_block visible.
|
||||
// If the @p_block is too long to hold in one page, just let it occupy the
|
||||
// whole page.
|
||||
// Will not change current cursor.
|
||||
virtual void makeBlockVisible(const QTextBlock &p_block) = 0;
|
||||
|
||||
// Clear IncrementalSearchedKeyword highlight.
|
||||
void clearIncrementalSearchedWordHighlight(bool p_now = true);
|
||||
|
||||
// Clear SearchedKeyword highlight.
|
||||
void clearSearchedWordHighlight();
|
||||
|
||||
// Clear SearchedKeywordUnderCursor Highlight.
|
||||
void clearSearchedWordUnderCursorHighlight(bool p_now = true);
|
||||
|
||||
// Evaluate selected text or cursor word as magic words.
|
||||
void evaluateMagicWords();
|
||||
|
||||
VFile *getFile() const;
|
||||
|
||||
VEditConfig &getConfig();
|
||||
|
||||
// Request to update Vim status.
|
||||
void requestUpdateVimStatus();
|
||||
|
||||
// Jump to a title.
|
||||
// @p_forward: jump forward or backward.
|
||||
// @p_relativeLevel: 0 for the same level as current header;
|
||||
// negative value for upper level;
|
||||
// positive value is ignored.
|
||||
// Returns true if the jump succeeded.
|
||||
virtual bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) = 0;
|
||||
|
||||
void setInputMethodEnabled(bool p_enabled);
|
||||
|
||||
// Insert decoration markers or decorate selected text.
|
||||
void decorateText(TextDecoration p_decoration);
|
||||
|
||||
virtual bool isBlockVisible(const QTextBlock &p_block) = 0;
|
||||
|
||||
VEditorObject *object() const;
|
||||
|
||||
QWidget *getEditor() const;
|
||||
|
||||
// Scroll block @p_blockNum into the visual window.
|
||||
// @p_dest is the position of the window: 0 for top, 1 for center, 2 for bottom.
|
||||
// @p_blockNum is based on 0.
|
||||
// Will set the cursor to the block.
|
||||
virtual void scrollBlockInPage(int p_blockNum, int p_dest) = 0;
|
||||
|
||||
// Update config according to global configurations.
|
||||
virtual void updateConfig();
|
||||
|
||||
// Wrapper functions for QPlainTextEdit/QTextEdit.
|
||||
// Ends with W to distinguish it from the original interfaces.
|
||||
public:
|
||||
virtual void setExtraSelectionsW(const QList<QTextEdit::ExtraSelection> &p_selections) = 0;
|
||||
|
||||
virtual QTextDocument *documentW() const = 0;
|
||||
|
||||
virtual void setTabStopWidthW(int p_width) = 0;
|
||||
|
||||
virtual QTextCursor textCursorW() const = 0;
|
||||
|
||||
virtual void setTextCursorW(const QTextCursor &p_cursor) = 0;
|
||||
|
||||
virtual void moveCursorW(QTextCursor::MoveOperation p_operation,
|
||||
QTextCursor::MoveMode p_mode = QTextCursor::MoveAnchor) = 0;
|
||||
|
||||
virtual QScrollBar *verticalScrollBarW() const = 0;
|
||||
|
||||
virtual QScrollBar *horizontalScrollBarW() const = 0;
|
||||
|
||||
virtual bool findW(const QString &p_exp,
|
||||
QTextDocument::FindFlags p_options = QTextDocument::FindFlags()) = 0;
|
||||
|
||||
virtual bool findW(const QRegExp &p_exp,
|
||||
QTextDocument::FindFlags p_options = QTextDocument::FindFlags()) = 0;
|
||||
|
||||
virtual void setReadOnlyW(bool p_ro) = 0;
|
||||
|
||||
virtual QWidget *viewportW() const = 0;
|
||||
|
||||
virtual void insertPlainTextW(const QString &p_text) = 0;
|
||||
|
||||
virtual void undoW() = 0;
|
||||
|
||||
virtual void redoW() = 0;
|
||||
|
||||
protected:
|
||||
void init();
|
||||
|
||||
virtual void updateFontAndPalette() = 0;
|
||||
|
||||
// Update m_config according to VConfigManager.
|
||||
void updateEditConfig();
|
||||
|
||||
// Do some highlight on cursor position changed.
|
||||
void highlightOnCursorPositionChanged();
|
||||
|
||||
// Highlight selected text.
|
||||
void highlightSelectedWord();
|
||||
|
||||
bool wordInSearchedSelection(const QString &p_text);
|
||||
|
||||
// Set read-only property and highlight current line.
|
||||
void setReadOnlyAndHighlightCurrentLine(bool p_readonly);
|
||||
|
||||
// Handle the mouse press event of m_editor.
|
||||
// Returns true if no further process is needed.
|
||||
bool handleMousePressEvent(QMouseEvent *p_event);
|
||||
|
||||
bool handleMouseReleaseEvent(QMouseEvent *p_event);
|
||||
|
||||
bool handleMouseMoveEvent(QMouseEvent *p_event);
|
||||
|
||||
bool handleInputMethodQuery(Qt::InputMethodQuery p_query,
|
||||
QVariant &p_var) const;
|
||||
|
||||
QWidget *m_editor;
|
||||
|
||||
VEditorObject *m_object;
|
||||
|
||||
QPointer<VFile> m_file;
|
||||
|
||||
VEditOperations *m_editOps;
|
||||
|
||||
VEditConfig m_config;
|
||||
|
||||
private:
|
||||
friend class VEditorObject;
|
||||
|
||||
void highlightTrailingSpace();
|
||||
|
||||
// Trigger the timer to request highlight.
|
||||
// If @p_now is true, stop the timer and highlight immediately.
|
||||
void highlightExtraSelections(bool p_now = false);
|
||||
|
||||
// @p_fileter: a function to filter out highlight results.
|
||||
void highlightTextAll(const QString &p_text,
|
||||
uint p_options,
|
||||
SelectionId p_id,
|
||||
QTextCharFormat p_format,
|
||||
void (*p_filter)(VEditor *,
|
||||
QList<QTextEdit::ExtraSelection> &) = NULL);
|
||||
|
||||
// Find all the occurences of @p_text.
|
||||
QList<QTextCursor> findTextAll(const QString &p_text, uint p_options);
|
||||
|
||||
// Highlight @p_cursor as the incremental searched keyword.
|
||||
void highlightIncrementalSearchedWord(const QTextCursor &p_cursor);
|
||||
|
||||
// Find @p_text in the document starting from @p_start.
|
||||
// Returns true if @p_text is found and set @p_cursor to indicate
|
||||
// the position.
|
||||
// Will NOT change current cursor.
|
||||
bool findTextHelper(const QString &p_text,
|
||||
uint p_options,
|
||||
bool p_forward,
|
||||
int p_start,
|
||||
bool &p_wrapped,
|
||||
QTextCursor &p_cursor);
|
||||
|
||||
void showWrapLabel();
|
||||
|
||||
void highlightSearchedWord(const QString &p_text, uint p_options);
|
||||
|
||||
// Highlight @p_cursor as the searched keyword under cursor.
|
||||
void highlightSearchedWordUnderCursor(const QTextCursor &p_cursor);
|
||||
|
||||
QLabel *m_wrapLabel;
|
||||
QTimer *m_labelTimer;
|
||||
|
||||
QTextDocument *m_document;
|
||||
|
||||
// doHighlightExtraSelections() will highlight these selections.
|
||||
// Selections are indexed by SelectionId.
|
||||
QVector<QList<QTextEdit::ExtraSelection> > m_extraSelections;
|
||||
|
||||
QColor m_selectedWordColor;
|
||||
QColor m_searchedWordColor;
|
||||
QColor m_searchedWordCursorColor;
|
||||
QColor m_incrementalSearchedWordColor;
|
||||
QColor m_trailingSpaceColor;
|
||||
|
||||
// Timer for extra selections highlight.
|
||||
QTimer *m_highlightTimer;
|
||||
|
||||
bool m_readyToScroll;
|
||||
bool m_mouseMoveScrolled;
|
||||
int m_oriMouseX;
|
||||
int m_oriMouseY;
|
||||
|
||||
// Whether enable input method.
|
||||
bool m_enableInputMethod;
|
||||
|
||||
// Functions for private slots.
|
||||
private:
|
||||
void labelTimerTimeout();
|
||||
|
||||
// Do the real work to highlight extra selections.
|
||||
void doHighlightExtraSelections();
|
||||
};
|
||||
|
||||
|
||||
// Since one class could not inherit QObject multiple times, we use this class
|
||||
// for VEditor to signal/slot.
|
||||
class VEditorObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VEditorObject(VEditor *p_editor, QObject *p_parent = nullptr)
|
||||
: QObject(p_parent), m_editor(p_editor)
|
||||
{
|
||||
}
|
||||
|
||||
signals:
|
||||
// Emit when editor config has been updated.
|
||||
void configUpdated();
|
||||
|
||||
// Emit when want to show message in status bar.
|
||||
void statusMessage(const QString &p_msg);
|
||||
|
||||
// Request VEditTab to save and exit edit mode.
|
||||
void saveAndRead();
|
||||
|
||||
// Request VEditTab to discard and exit edit mode.
|
||||
void discardAndRead();
|
||||
|
||||
// Request VEditTab to edit current note.
|
||||
void editNote();
|
||||
|
||||
// Request VEditTab to save this file.
|
||||
void saveNote();
|
||||
|
||||
// Selection changed by mouse.
|
||||
void selectionChangedByMouse(bool p_hasSelection);
|
||||
|
||||
// Emit when Vim status updated.
|
||||
void vimStatusUpdated(const VVim *p_vim);
|
||||
|
||||
// Emit when all initialization is ready.
|
||||
void ready();
|
||||
|
||||
// Request the edit tab to close find and replace dialog.
|
||||
void requestCloseFindReplaceDialog();
|
||||
|
||||
private slots:
|
||||
// Timer for find-wrap label.
|
||||
void labelTimerTimeout()
|
||||
{
|
||||
m_editor->labelTimerTimeout();
|
||||
}
|
||||
|
||||
// Do the real work to highlight extra selections.
|
||||
void doHighlightExtraSelections()
|
||||
{
|
||||
m_editor->doHighlightExtraSelections();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class VEditor;
|
||||
|
||||
VEditor *m_editor;
|
||||
};
|
||||
|
||||
inline VFile *VEditor::getFile() const
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
|
||||
inline VEditConfig &VEditor::getConfig()
|
||||
{
|
||||
return m_config;
|
||||
}
|
||||
|
||||
inline VEditorObject *VEditor::object() const
|
||||
{
|
||||
return m_object;
|
||||
}
|
||||
|
||||
inline QWidget *VEditor::getEditor() const
|
||||
{
|
||||
return m_editor;
|
||||
}
|
||||
|
||||
#endif // VEDITOR_H
|
@ -10,7 +10,7 @@
|
||||
#include "utils/vutils.h"
|
||||
#include "vnotefile.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vmdedit.h"
|
||||
#include "vmdeditor.h"
|
||||
#include "vmdtab.h"
|
||||
#include "dialog/vconfirmdeletiondialog.h"
|
||||
#include "dialog/vsortdialog.h"
|
||||
@ -376,7 +376,7 @@ void VFileList::newFile()
|
||||
if (contentInserted) {
|
||||
const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->getCurrentTab());
|
||||
if (tab) {
|
||||
VMdEdit *edit = dynamic_cast<VMdEdit *>(tab->getEditor());
|
||||
VMdEditor *edit = tab->getEditor();
|
||||
if (edit && edit->getFile() == file) {
|
||||
QTextCursor cursor = edit->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
|
@ -84,7 +84,7 @@ private:
|
||||
int m_endPos;
|
||||
QString m_linkUrl;
|
||||
|
||||
// Whether it is a image block.
|
||||
// Whether it is an image block.
|
||||
bool m_isBlock;
|
||||
|
||||
// The previewed image ID if this link has been previewed.
|
||||
|
@ -44,7 +44,7 @@ void VImageResourceManager::updateBlockInfos(const QVector<VBlockImageInfo> &p_b
|
||||
|
||||
// Clear unused images.
|
||||
for (auto it = m_images.begin(); it != m_images.end();) {
|
||||
if (!m_images.contains(it.key())) {
|
||||
if (!usedImages.contains(it.key())) {
|
||||
// Remove the image.
|
||||
it = m_images.erase(it);
|
||||
} else {
|
||||
|
@ -13,7 +13,8 @@ enum class LineNumberType
|
||||
None = 0,
|
||||
Absolute,
|
||||
Relative,
|
||||
CodeBlock
|
||||
CodeBlock,
|
||||
Invalid
|
||||
};
|
||||
|
||||
|
||||
|
@ -1630,6 +1630,7 @@ void VMainWindow::initEditorLineNumberMenu(QMenu *p_menu)
|
||||
}
|
||||
|
||||
g_config->setEditorLineNumber(p_action->data().toInt());
|
||||
emit editorConfigUpdated();
|
||||
});
|
||||
|
||||
int lineNumberMode = g_config->getEditorLineNumber();
|
||||
@ -2280,6 +2281,8 @@ void VMainWindow::enableImagePreview(bool p_checked)
|
||||
void VMainWindow::enableImagePreviewConstraint(bool p_checked)
|
||||
{
|
||||
g_config->setEnablePreviewImageConstraint(p_checked);
|
||||
|
||||
emit editorConfigUpdated();
|
||||
}
|
||||
|
||||
void VMainWindow::enableImageConstraint(bool p_checked)
|
||||
|
@ -86,6 +86,10 @@ public:
|
||||
// Prompt user for new notebook if there is no notebook.
|
||||
void promptNewNotebookIfEmpty();
|
||||
|
||||
signals:
|
||||
// Emit when editor related configurations were changed by user.
|
||||
void editorConfigUpdated();
|
||||
|
||||
private slots:
|
||||
void importNoteFromFile();
|
||||
void viewSettings();
|
||||
|
@ -66,6 +66,8 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||
}
|
||||
});
|
||||
|
||||
// Comment out these lines since we use VMdEditor to replace VMdEdit.
|
||||
/*
|
||||
m_editOps = new VMdEditOperations(this, m_file);
|
||||
|
||||
connect(m_editOps, &VEditOperations::statusMessage,
|
||||
@ -78,6 +80,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||
|
||||
connect(QApplication::clipboard(), &QClipboard::changed,
|
||||
this, &VMdEdit::handleClipboardChanged);
|
||||
*/
|
||||
|
||||
updateFontAndPalette();
|
||||
|
||||
|
@ -16,10 +16,10 @@
|
||||
#include "dialog/vinsertimagedialog.h"
|
||||
#include "dialog/vselectdialog.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vedit.h"
|
||||
#include "veditor.h"
|
||||
#include "vdownloader.h"
|
||||
#include "vfile.h"
|
||||
#include "vmdedit.h"
|
||||
#include "vmdeditor.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vvim.h"
|
||||
#include "utils/veditutils.h"
|
||||
@ -28,7 +28,7 @@ extern VConfigManager *g_config;
|
||||
|
||||
const QString VMdEditOperations::c_defaultImageTitle = "";
|
||||
|
||||
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
|
||||
VMdEditOperations::VMdEditOperations(VEditor *p_editor, VFile *p_file)
|
||||
: VEditOperations(p_editor, p_file), m_autoIndentPos(-1)
|
||||
{
|
||||
}
|
||||
@ -40,7 +40,9 @@ bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
|
||||
return false;
|
||||
}
|
||||
VInsertImageDialog dialog(tr("Insert Image From Clipboard"),
|
||||
c_defaultImageTitle, "", (QWidget *)m_editor);
|
||||
c_defaultImageTitle,
|
||||
"",
|
||||
m_editor->getEditor());
|
||||
dialog.setBrowseable(false);
|
||||
dialog.setImage(image);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
@ -78,7 +80,7 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
|
||||
errStr,
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
(QWidget *)m_editor);
|
||||
m_editor->getEditor());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -87,7 +89,7 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
|
||||
|
||||
qDebug() << "insert image" << title << filePath;
|
||||
|
||||
VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
|
||||
VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
|
||||
Q_ASSERT(mdEditor);
|
||||
mdEditor->imageInserted(filePath);
|
||||
}
|
||||
@ -118,7 +120,7 @@ void VMdEditOperations::insertImageFromPath(const QString &title, const QString
|
||||
errStr,
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
(QWidget *)m_editor);
|
||||
m_editor->getEditor());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -127,7 +129,7 @@ void VMdEditOperations::insertImageFromPath(const QString &title, const QString
|
||||
|
||||
qDebug() << "insert image" << title << filePath;
|
||||
|
||||
VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
|
||||
VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
|
||||
Q_ASSERT(mdEditor);
|
||||
mdEditor->imageInserted(filePath);
|
||||
}
|
||||
@ -156,7 +158,7 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
|
||||
|
||||
|
||||
VInsertImageDialog dialog(title, c_defaultImageTitle,
|
||||
imagePath, (QWidget *)m_editor);
|
||||
imagePath, m_editor->getEditor());
|
||||
dialog.setBrowseable(false, true);
|
||||
if (isLocal) {
|
||||
dialog.setImage(image);
|
||||
@ -186,7 +188,7 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
|
||||
bool VMdEditOperations::insertImage()
|
||||
{
|
||||
VInsertImageDialog dialog(tr("Insert Image From File"),
|
||||
c_defaultImageTitle, "", (QWidget *)m_editor);
|
||||
c_defaultImageTitle, "", m_editor->getEditor());
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QString title = dialog.getImageTitleInput();
|
||||
QString imagePath = dialog.getPathInput();
|
||||
@ -393,10 +395,10 @@ bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
|
||||
// 1. If there is any selection, clear it.
|
||||
// 2. Otherwise, ignore this event and let parent handles it.
|
||||
if (p_event->modifiers() == Qt::ControlModifier) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.clearSelection();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
p_event->accept();
|
||||
return true;
|
||||
}
|
||||
@ -407,18 +409,18 @@ bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
|
||||
|
||||
bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
|
||||
{
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
QString text(m_editConfig->m_tabSpaces);
|
||||
|
||||
if (p_event->modifiers() == Qt::NoModifier) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (cursor.hasSelection()) {
|
||||
m_autoIndentPos = -1;
|
||||
cursor.beginEditBlock();
|
||||
// Indent each selected line.
|
||||
VEditUtils::indentSelectedBlocks(doc, cursor, text, true);
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
} else {
|
||||
// If it is a Tab key following auto list, increase the indent level.
|
||||
QTextBlock block = cursor.block();
|
||||
@ -433,7 +435,7 @@ bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)
|
||||
}
|
||||
blockCursor.endEditBlock();
|
||||
// Change m_autoIndentPos to let it can be repeated.
|
||||
m_autoIndentPos = m_editor->textCursor().position();
|
||||
m_autoIndentPos = m_editor->textCursorW().position();
|
||||
} else {
|
||||
// Just insert "tab".
|
||||
insertTextAtCurPos(text);
|
||||
@ -454,8 +456,8 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
|
||||
m_autoIndentPos = -1;
|
||||
return false;
|
||||
}
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
QTextBlock block = doc->findBlock(cursor.selectionStart());
|
||||
bool continueAutoIndent = false;
|
||||
int seq = -1;
|
||||
@ -474,7 +476,7 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event)
|
||||
cursor.endEditBlock();
|
||||
|
||||
if (continueAutoIndent) {
|
||||
m_autoIndentPos = m_editor->textCursor().position();
|
||||
m_autoIndentPos = m_editor->textCursorW().position();
|
||||
} else {
|
||||
m_autoIndentPos = -1;
|
||||
}
|
||||
@ -486,7 +488,7 @@ bool VMdEditOperations::handleKeyH(QKeyEvent *p_event)
|
||||
{
|
||||
if (p_event->modifiers() == Qt::ControlModifier) {
|
||||
// Ctrl+H, equal to backspace.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.deletePreviousChar();
|
||||
|
||||
p_event->accept();
|
||||
@ -499,7 +501,7 @@ bool VMdEditOperations::handleKeyU(QKeyEvent *p_event)
|
||||
{
|
||||
if (p_event->modifiers() == Qt::ControlModifier) {
|
||||
// Ctrl+U, delete till the start of line.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
bool ret;
|
||||
if (cursor.atBlockStart()) {
|
||||
ret = cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
|
||||
@ -520,7 +522,7 @@ bool VMdEditOperations::handleKeyW(QKeyEvent *p_event)
|
||||
{
|
||||
if (p_event->modifiers() == Qt::ControlModifier) {
|
||||
// Ctrl+W, delete till the start of previous word.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.removeSelectedText();
|
||||
} else {
|
||||
@ -539,10 +541,10 @@ bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
|
||||
{
|
||||
// 1. If there is any selection, clear it.
|
||||
// 2. Otherwise, ignore this event and let parent handles it.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.clearSelection();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
p_event->accept();
|
||||
return true;
|
||||
}
|
||||
@ -560,7 +562,7 @@ bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
|
||||
// Insert two spaces and a new line.
|
||||
m_autoIndentPos = -1;
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
cursor.removeSelectedText();
|
||||
cursor.insertText(" ");
|
||||
@ -575,11 +577,11 @@ bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
|
||||
if (m_autoIndentPos > -1) {
|
||||
// Cancel the auto indent/list if the pos is the same and cursor is at
|
||||
// the end of a block.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
if (VEditUtils::needToCancelAutoIndent(m_autoIndentPos, cursor)) {
|
||||
m_autoIndentPos = -1;
|
||||
VEditUtils::deleteIndentAndListMark(cursor);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -589,7 +591,7 @@ bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
|
||||
if (g_config->getAutoIndent()) {
|
||||
handled = true;
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
bool textInserted = false;
|
||||
cursor.beginEditBlock();
|
||||
cursor.removeSelectedText();
|
||||
@ -603,9 +605,9 @@ bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
if (textInserted) {
|
||||
m_autoIndentPos = m_editor->textCursor().position();
|
||||
m_autoIndentPos = m_editor->textCursorW().position();
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,8 +650,8 @@ void VMdEditOperations::changeListBlockSeqNumber(QTextBlock &p_block, int p_seq)
|
||||
|
||||
bool VMdEditOperations::insertTitle(int p_level)
|
||||
{
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
int firstBlock = cursor.block().blockNumber();
|
||||
int lastBlock = firstBlock;
|
||||
|
||||
@ -667,7 +669,7 @@ bool VMdEditOperations::insertTitle(int p_level)
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -713,7 +715,7 @@ void VMdEditOperations::decorateText(TextDecoration p_decoration)
|
||||
|
||||
void VMdEditOperations::decorateBold()
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (cursor.hasSelection()) {
|
||||
// Insert ** around the selected text.
|
||||
@ -745,12 +747,12 @@ void VMdEditOperations::decorateBold()
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
void VMdEditOperations::decorateItalic()
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (cursor.hasSelection()) {
|
||||
// Insert * around the selected text.
|
||||
@ -782,12 +784,12 @@ void VMdEditOperations::decorateItalic()
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
void VMdEditOperations::decorateInlineCode()
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (cursor.hasSelection()) {
|
||||
// Insert ` around the selected text.
|
||||
@ -819,14 +821,14 @@ void VMdEditOperations::decorateInlineCode()
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
void VMdEditOperations::decorateCodeBlock()
|
||||
{
|
||||
const QString marker("```");
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (cursor.hasSelection()) {
|
||||
// Insert ``` around the selected text.
|
||||
@ -899,12 +901,12 @@ void VMdEditOperations::decorateCodeBlock()
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
void VMdEditOperations::decorateStrikethrough()
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.beginEditBlock();
|
||||
if (cursor.hasSelection()) {
|
||||
// Insert ~~ around the selected text.
|
||||
@ -936,16 +938,16 @@ void VMdEditOperations::decorateStrikethrough()
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
}
|
||||
|
||||
bool VMdEditOperations::insertLink(const QString &p_linkText,
|
||||
const QString &p_linkUrl)
|
||||
{
|
||||
QString link = QString("[%1](%2)").arg(p_linkText).arg(p_linkUrl);
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
cursor.insertText(link);
|
||||
m_editor->setTextCursor(cursor);
|
||||
m_editor->setTextCursorW(cursor);
|
||||
|
||||
setVimMode(VimMode::Insert);
|
||||
|
||||
|
@ -15,7 +15,7 @@ class VMdEditOperations : public VEditOperations
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VMdEditOperations(VEdit *p_editor, VFile *p_file);
|
||||
VMdEditOperations(VEditor *p_editor, VFile *p_file);
|
||||
|
||||
bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
|
||||
|
||||
|
862
src/vmdeditor.cpp
Normal file
862
src/vmdeditor.cpp
Normal file
@ -0,0 +1,862 @@
|
||||
#include "vmdeditor.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QMenu>
|
||||
#include <QDebug>
|
||||
|
||||
#include "vdocument.h"
|
||||
#include "utils/veditutils.h"
|
||||
#include "vedittab.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vcodeblockhighlighthelper.h"
|
||||
#include "vmdeditoperations.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "utils/veditutils.h"
|
||||
#include "dialog/vselectdialog.h"
|
||||
#include "dialog/vconfirmdeletiondialog.h"
|
||||
#include "vtextblockdata.h"
|
||||
#include "vorphanfile.h"
|
||||
#include "vnotefile.h"
|
||||
#include "vpreviewmanager.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VMdEditor::VMdEditor(VFile *p_file,
|
||||
VDocument *p_doc,
|
||||
MarkdownConverterType p_type,
|
||||
QWidget *p_parent)
|
||||
: VPlainTextEdit(p_parent),
|
||||
VEditor(p_file, this),
|
||||
m_mdHighlighter(NULL),
|
||||
m_freshEdit(true)
|
||||
{
|
||||
Q_ASSERT(p_file->getDocType() == DocType::Markdown);
|
||||
|
||||
VEditor::init();
|
||||
|
||||
// Hook functions from VEditor.
|
||||
connect(this, &VPlainTextEdit::cursorPositionChanged,
|
||||
this, [this]() {
|
||||
highlightOnCursorPositionChanged();
|
||||
});
|
||||
|
||||
connect(this, &VPlainTextEdit::selectionChanged,
|
||||
this, [this]() {
|
||||
highlightSelectedWord();
|
||||
});
|
||||
// End.
|
||||
|
||||
m_mdHighlighter = new HGMarkdownHighlighter(g_config->getMdHighlightingStyles(),
|
||||
g_config->getCodeBlockStyles(),
|
||||
g_config->getMarkdownHighlightInterval(),
|
||||
document());
|
||||
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
|
||||
this, &VMdEditor::updateHeaders);
|
||||
|
||||
// After highlight, the cursor may trun into non-visible. We should make it visible
|
||||
// in this case.
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
||||
this, [this]() {
|
||||
makeBlockVisible(textCursor().block());
|
||||
});
|
||||
|
||||
m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter,
|
||||
p_doc,
|
||||
p_type);
|
||||
|
||||
m_previewMgr = new VPreviewManager(this);
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
|
||||
m_previewMgr, &VPreviewManager::imageLinksUpdated);
|
||||
connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
|
||||
m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight);
|
||||
|
||||
m_editOps = new VMdEditOperations(this, m_file);
|
||||
connect(m_editOps, &VEditOperations::statusMessage,
|
||||
m_object, &VEditorObject::statusMessage);
|
||||
connect(m_editOps, &VEditOperations::vimStatusUpdated,
|
||||
m_object, &VEditorObject::vimStatusUpdated);
|
||||
|
||||
connect(this, &VPlainTextEdit::cursorPositionChanged,
|
||||
this, &VMdEditor::updateCurrentHeader);
|
||||
|
||||
updateFontAndPalette();
|
||||
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
void VMdEditor::updateFontAndPalette()
|
||||
{
|
||||
setFont(g_config->getMdEditFont());
|
||||
setPalette(g_config->getMdEditPalette());
|
||||
}
|
||||
|
||||
void VMdEditor::beginEdit()
|
||||
{
|
||||
updateFontAndPalette();
|
||||
|
||||
updateConfig();
|
||||
|
||||
initInitImages();
|
||||
|
||||
setModified(false);
|
||||
|
||||
setReadOnlyAndHighlightCurrentLine(false);
|
||||
|
||||
emit statusChanged();
|
||||
|
||||
updateHeaders(m_mdHighlighter->getHeaderRegions());
|
||||
|
||||
if (m_freshEdit) {
|
||||
m_freshEdit = false;
|
||||
emit m_object->ready();
|
||||
}
|
||||
}
|
||||
|
||||
void VMdEditor::endEdit()
|
||||
{
|
||||
setReadOnlyAndHighlightCurrentLine(true);
|
||||
clearUnusedImages();
|
||||
}
|
||||
|
||||
void VMdEditor::saveFile()
|
||||
{
|
||||
Q_ASSERT(m_file->isModifiable());
|
||||
|
||||
if (!document()->isModified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_file->setContent(toPlainText());
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
void VMdEditor::reloadFile()
|
||||
{
|
||||
const QString &content = m_file->getContent();
|
||||
setPlainText(content);
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
bool VMdEditor::scrollToBlock(int p_blockNumber)
|
||||
{
|
||||
QTextBlock block = document()->findBlockByNumber(p_blockNumber);
|
||||
if (block.isValid()) {
|
||||
VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
|
||||
moveCursor(QTextCursor::EndOfBlock);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the visual offset of a block.
|
||||
#define GETVISUALOFFSETY ((int)(contentOffset().y() + rect.y()))
|
||||
|
||||
void VMdEditor::makeBlockVisible(const QTextBlock &p_block)
|
||||
{
|
||||
if (!p_block.isValid() || !p_block.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QScrollBar *vbar = verticalScrollBar();
|
||||
if (!vbar || !vbar->isVisible()) {
|
||||
// No vertical scrollbar. No need to scroll.
|
||||
return;
|
||||
}
|
||||
|
||||
int height = rect().height();
|
||||
QScrollBar *hbar = horizontalScrollBar();
|
||||
if (hbar && hbar->isVisible()) {
|
||||
height -= hbar->height();
|
||||
}
|
||||
|
||||
bool moved = false;
|
||||
|
||||
QRectF rect = blockBoundingGeometry(p_block);
|
||||
int y = GETVISUALOFFSETY;
|
||||
int rectHeight = (int)rect.height();
|
||||
|
||||
// Handle the case rectHeight >= height.
|
||||
if (rectHeight >= height) {
|
||||
if (y <= 0) {
|
||||
if (y + rectHeight < height) {
|
||||
// Need to scroll up.
|
||||
while (y + rectHeight < height && vbar->value() > vbar->minimum()) {
|
||||
moved = true;
|
||||
vbar->setValue(vbar->value() - vbar->singleStep());
|
||||
rect = blockBoundingGeometry(p_block);
|
||||
rectHeight = (int)rect.height();
|
||||
y = GETVISUALOFFSETY;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Need to scroll down.
|
||||
while (y > 0 && vbar->value() < vbar->maximum()) {
|
||||
moved = true;
|
||||
vbar->setValue(vbar->value() + vbar->singleStep());
|
||||
rect = blockBoundingGeometry(p_block);
|
||||
rectHeight = (int)rect.height();
|
||||
y = GETVISUALOFFSETY;
|
||||
}
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
qDebug() << "scroll to make huge block visible";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (y < 0 && vbar->value() > vbar->minimum()) {
|
||||
qDebug() << y << vbar->value() << vbar->minimum() << rectHeight;
|
||||
moved = true;
|
||||
vbar->setValue(vbar->value() - vbar->singleStep());
|
||||
rect = blockBoundingGeometry(p_block);
|
||||
rectHeight = (int)rect.height();
|
||||
y = GETVISUALOFFSETY;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
qDebug() << "scroll page down to make block visible";
|
||||
return;
|
||||
}
|
||||
|
||||
while (y + rectHeight > height && vbar->value() < vbar->maximum()) {
|
||||
moved = true;
|
||||
vbar->setValue(vbar->value() + vbar->singleStep());
|
||||
rect = blockBoundingGeometry(p_block);
|
||||
rectHeight = (int)rect.height();
|
||||
y = GETVISUALOFFSETY;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
qDebug() << "scroll page up to make block visible";
|
||||
}
|
||||
}
|
||||
|
||||
void VMdEditor::contextMenuEvent(QContextMenuEvent *p_event)
|
||||
{
|
||||
QMenu *menu = createStandardContextMenu();
|
||||
menu->setToolTipsVisible(true);
|
||||
|
||||
const QList<QAction *> actions = menu->actions();
|
||||
|
||||
if (!textCursor().hasSelection()) {
|
||||
VEditTab *editTab = dynamic_cast<VEditTab *>(parent());
|
||||
Q_ASSERT(editTab);
|
||||
if (editTab->isEditMode()) {
|
||||
QAction *saveExitAct = new QAction(QIcon(":/resources/icons/save_exit.svg"),
|
||||
tr("&Save Changes And Read"),
|
||||
menu);
|
||||
saveExitAct->setToolTip(tr("Save changes and exit edit mode"));
|
||||
connect(saveExitAct, &QAction::triggered,
|
||||
this, [this]() {
|
||||
emit m_object->saveAndRead();
|
||||
});
|
||||
|
||||
QAction *discardExitAct = new QAction(QIcon(":/resources/icons/discard_exit.svg"),
|
||||
tr("&Discard Changes And Read"),
|
||||
menu);
|
||||
discardExitAct->setToolTip(tr("Discard changes and exit edit mode"));
|
||||
connect(discardExitAct, &QAction::triggered,
|
||||
this, [this]() {
|
||||
emit m_object->discardAndRead();
|
||||
});
|
||||
|
||||
menu->insertAction(actions.isEmpty() ? NULL : actions[0], discardExitAct);
|
||||
menu->insertAction(discardExitAct, saveExitAct);
|
||||
if (!actions.isEmpty()) {
|
||||
menu->insertSeparator(actions[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu->exec(p_event->globalPos());
|
||||
delete menu;
|
||||
}
|
||||
|
||||
void VMdEditor::mousePressEvent(QMouseEvent *p_event)
|
||||
{
|
||||
if (handleMousePressEvent(p_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VPlainTextEdit::mousePressEvent(p_event);
|
||||
|
||||
emit m_object->selectionChangedByMouse(textCursor().hasSelection());
|
||||
}
|
||||
|
||||
void VMdEditor::mouseReleaseEvent(QMouseEvent *p_event)
|
||||
{
|
||||
if (handleMouseReleaseEvent(p_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VPlainTextEdit::mousePressEvent(p_event);
|
||||
}
|
||||
|
||||
void VMdEditor::mouseMoveEvent(QMouseEvent *p_event)
|
||||
{
|
||||
if (handleMouseMoveEvent(p_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VPlainTextEdit::mouseMoveEvent(p_event);
|
||||
|
||||
emit m_object->selectionChangedByMouse(textCursor().hasSelection());
|
||||
}
|
||||
|
||||
QVariant VMdEditor::inputMethodQuery(Qt::InputMethodQuery p_query) const
|
||||
{
|
||||
QVariant ret;
|
||||
if (handleInputMethodQuery(p_query, ret)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return VPlainTextEdit::inputMethodQuery(p_query);
|
||||
}
|
||||
|
||||
bool VMdEditor::isBlockVisible(const QTextBlock &p_block)
|
||||
{
|
||||
if (!p_block.isValid() || !p_block.isVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QScrollBar *vbar = verticalScrollBar();
|
||||
if (!vbar || !vbar->isVisible()) {
|
||||
// No vertical scrollbar.
|
||||
return true;
|
||||
}
|
||||
|
||||
int height = rect().height();
|
||||
QScrollBar *hbar = horizontalScrollBar();
|
||||
if (hbar && hbar->isVisible()) {
|
||||
height -= hbar->height();
|
||||
}
|
||||
|
||||
QRectF rect = blockBoundingGeometry(p_block);
|
||||
int y = GETVISUALOFFSETY;
|
||||
int rectHeight = (int)rect.height();
|
||||
|
||||
return (y >= 0 && y < height) || (y < 0 && y + rectHeight > 0);
|
||||
}
|
||||
|
||||
static void addHeaderSequence(QVector<int> &p_sequence, int p_level, int p_baseLevel)
|
||||
{
|
||||
Q_ASSERT(p_level >= 1 && p_level < p_sequence.size());
|
||||
if (p_level < p_baseLevel) {
|
||||
p_sequence.fill(0);
|
||||
return;
|
||||
}
|
||||
|
||||
++p_sequence[p_level];
|
||||
for (int i = p_level + 1; i < p_sequence.size(); ++i) {
|
||||
p_sequence[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static QString headerSequenceStr(const QVector<int> &p_sequence)
|
||||
{
|
||||
QString res;
|
||||
for (int i = 1; i < p_sequence.size(); ++i) {
|
||||
if (p_sequence[i] != 0) {
|
||||
res = res + QString::number(p_sequence[i]) + '.';
|
||||
} else if (res.isEmpty()) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void insertSequenceToHeader(QTextBlock p_block,
|
||||
QRegExp &p_reg,
|
||||
QRegExp &p_preReg,
|
||||
const QString &p_seq)
|
||||
{
|
||||
if (!p_block.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = p_block.text();
|
||||
bool matched = p_reg.exactMatch(text);
|
||||
Q_ASSERT(matched);
|
||||
|
||||
matched = p_preReg.exactMatch(text);
|
||||
Q_ASSERT(matched);
|
||||
|
||||
int start = p_reg.cap(1).length() + 1;
|
||||
int end = p_preReg.cap(1).length();
|
||||
|
||||
Q_ASSERT(start <= end);
|
||||
|
||||
QTextCursor cursor(p_block);
|
||||
cursor.setPosition(p_block.position() + start);
|
||||
if (start != end) {
|
||||
cursor.setPosition(p_block.position() + end, QTextCursor::KeepAnchor);
|
||||
}
|
||||
|
||||
if (p_seq.isEmpty()) {
|
||||
cursor.removeSelectedText();
|
||||
} else {
|
||||
cursor.insertText(p_seq + ' ');
|
||||
}
|
||||
}
|
||||
|
||||
void VMdEditor::updateHeaders(const QVector<VElementRegion> &p_headerRegions)
|
||||
{
|
||||
QTextDocument *doc = document();
|
||||
|
||||
QVector<VTableOfContentItem> headers;
|
||||
QVector<int> headerBlockNumbers;
|
||||
QVector<QString> headerSequences;
|
||||
if (!p_headerRegions.isEmpty()) {
|
||||
headers.reserve(p_headerRegions.size());
|
||||
headerBlockNumbers.reserve(p_headerRegions.size());
|
||||
headerSequences.reserve(p_headerRegions.size());
|
||||
}
|
||||
|
||||
// Assume that each block contains only one line
|
||||
// Only support # syntax for now
|
||||
QRegExp headerReg(VUtils::c_headerRegExp);
|
||||
int baseLevel = -1;
|
||||
for (auto const & reg : p_headerRegions) {
|
||||
QTextBlock block = doc->findBlock(reg.m_startPos);
|
||||
if (!block.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!block.contains(reg.m_endPos - 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((block.userState() == HighlightBlockState::Normal)
|
||||
&& headerReg.exactMatch(block.text())) {
|
||||
int level = headerReg.cap(1).length();
|
||||
VTableOfContentItem header(headerReg.cap(2).trimmed(),
|
||||
level,
|
||||
block.blockNumber(),
|
||||
headers.size());
|
||||
headers.append(header);
|
||||
headerBlockNumbers.append(block.blockNumber());
|
||||
headerSequences.append(headerReg.cap(3));
|
||||
|
||||
if (baseLevel == -1) {
|
||||
baseLevel = level;
|
||||
} else if (baseLevel > level) {
|
||||
baseLevel = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_headers.clear();
|
||||
|
||||
bool autoSequence = m_config.m_enableHeadingSequence
|
||||
&& !isReadOnly()
|
||||
&& m_file->isModifiable();
|
||||
int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel();
|
||||
if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) {
|
||||
headingSequenceBaseLevel = 1;
|
||||
}
|
||||
|
||||
QVector<int> seqs(7, 0);
|
||||
QRegExp preReg(VUtils::c_headerPrefixRegExp);
|
||||
int curLevel = baseLevel - 1;
|
||||
for (int i = 0; i < headers.size(); ++i) {
|
||||
VTableOfContentItem &item = headers[i];
|
||||
while (item.m_level > curLevel + 1) {
|
||||
curLevel += 1;
|
||||
|
||||
// Insert empty level which is an invalid header.
|
||||
m_headers.append(VTableOfContentItem(c_emptyHeaderName,
|
||||
curLevel,
|
||||
-1,
|
||||
m_headers.size()));
|
||||
if (autoSequence) {
|
||||
addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
|
||||
}
|
||||
}
|
||||
|
||||
item.m_index = m_headers.size();
|
||||
m_headers.append(item);
|
||||
curLevel = item.m_level;
|
||||
if (autoSequence) {
|
||||
addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
|
||||
|
||||
QString seqStr = headerSequenceStr(seqs);
|
||||
if (headerSequences[i] != seqStr) {
|
||||
// Insert correct sequence.
|
||||
insertSequenceToHeader(doc->findBlockByNumber(headerBlockNumbers[i]),
|
||||
headerReg,
|
||||
preReg,
|
||||
seqStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit headersChanged(m_headers);
|
||||
|
||||
updateCurrentHeader();
|
||||
}
|
||||
|
||||
void VMdEditor::updateCurrentHeader()
|
||||
{
|
||||
emit currentHeaderChanged(textCursor().block().blockNumber());
|
||||
}
|
||||
|
||||
void VMdEditor::initInitImages()
|
||||
{
|
||||
m_initImages = VUtils::fetchImagesFromMarkdownFile(m_file,
|
||||
ImageLink::LocalRelativeInternal);
|
||||
}
|
||||
|
||||
void VMdEditor::clearUnusedImages()
|
||||
{
|
||||
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,
|
||||
ImageLink::LocalRelativeInternal);
|
||||
|
||||
QVector<QString> unusedImages;
|
||||
|
||||
if (!m_insertedImages.isEmpty()) {
|
||||
for (int i = 0; i < m_insertedImages.size(); ++i) {
|
||||
const ImageLink &link = m_insertedImages[i];
|
||||
|
||||
if (link.m_type != ImageLink::LocalRelativeInternal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int j;
|
||||
for (j = 0; j < images.size(); ++j) {
|
||||
if (VUtils::equalPath(link.m_path, images[j].m_path)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This inserted image is no longer in the file.
|
||||
if (j == images.size()) {
|
||||
unusedImages.push_back(link.m_path);
|
||||
}
|
||||
}
|
||||
|
||||
m_insertedImages.clear();
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_initImages.size(); ++i) {
|
||||
const ImageLink &link = m_initImages[i];
|
||||
|
||||
V_ASSERT(link.m_type == ImageLink::LocalRelativeInternal);
|
||||
|
||||
int j;
|
||||
for (j = 0; j < images.size(); ++j) {
|
||||
if (VUtils::equalPath(link.m_path, images[j].m_path)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Original local relative image is no longer in the file.
|
||||
if (j == images.size()) {
|
||||
unusedImages.push_back(link.m_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!unusedImages.isEmpty()) {
|
||||
if (g_config->getConfirmImagesCleanUp()) {
|
||||
QVector<ConfirmItemInfo> items;
|
||||
for (auto const & img : unusedImages) {
|
||||
items.push_back(ConfirmItemInfo(img,
|
||||
img,
|
||||
img,
|
||||
NULL));
|
||||
|
||||
}
|
||||
|
||||
QString text = tr("Following images seems not to be used in this note anymore. "
|
||||
"Please confirm the deletion of these images.");
|
||||
|
||||
QString info = tr("Deleted files could be found in the recycle "
|
||||
"bin of this note.<br>"
|
||||
"Click \"Cancel\" to leave them untouched.");
|
||||
|
||||
VConfirmDeletionDialog dialog(tr("Confirm Cleaning Up Unused Images"),
|
||||
text,
|
||||
info,
|
||||
items,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
this);
|
||||
|
||||
unusedImages.clear();
|
||||
if (dialog.exec()) {
|
||||
items = dialog.getConfirmedItems();
|
||||
g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
|
||||
|
||||
for (auto const & item : items) {
|
||||
unusedImages.push_back(item.m_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < unusedImages.size(); ++i) {
|
||||
bool ret = false;
|
||||
if (m_file->getType() == FileType::Note) {
|
||||
const VNoteFile *tmpFile = dynamic_cast<const VNoteFile *>((VFile *)m_file);
|
||||
ret = VUtils::deleteFile(tmpFile->getNotebook(), unusedImages[i], false);
|
||||
} else if (m_file->getType() == FileType::Orphan) {
|
||||
const VOrphanFile *tmpFile = dynamic_cast<const VOrphanFile *>((VFile *)m_file);
|
||||
ret = VUtils::deleteFile(tmpFile, unusedImages[i], false);
|
||||
} else {
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
qWarning() << "fail to delete unused original image" << unusedImages[i];
|
||||
} else {
|
||||
qDebug() << "delete unused image" << unusedImages[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_initImages.clear();
|
||||
}
|
||||
|
||||
void VMdEditor::keyPressEvent(QKeyEvent *p_event)
|
||||
{
|
||||
if (m_editOps && m_editOps->handleKeyPressEvent(p_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VPlainTextEdit::keyPressEvent(p_event);
|
||||
}
|
||||
|
||||
bool VMdEditor::canInsertFromMimeData(const QMimeData *p_source) const
|
||||
{
|
||||
return p_source->hasImage()
|
||||
|| p_source->hasUrls()
|
||||
|| VPlainTextEdit::canInsertFromMimeData(p_source);
|
||||
}
|
||||
|
||||
void VMdEditor::insertFromMimeData(const QMimeData *p_source)
|
||||
{
|
||||
VSelectDialog dialog(tr("Insert From Clipboard"), this);
|
||||
dialog.addSelection(tr("Insert As Image"), 0);
|
||||
dialog.addSelection(tr("Insert As Text"), 1);
|
||||
|
||||
if (p_source->hasImage()) {
|
||||
// Image data in the clipboard
|
||||
if (p_source->hasText()) {
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
if (dialog.getSelection() == 1) {
|
||||
// Insert as text.
|
||||
Q_ASSERT(p_source->hasText() && p_source->hasImage());
|
||||
VPlainTextEdit::insertFromMimeData(p_source);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_editOps->insertImageFromMimeData(p_source);
|
||||
return;
|
||||
} else if (p_source->hasUrls()) {
|
||||
QList<QUrl> urls = p_source->urls();
|
||||
if (urls.size() == 1 && VUtils::isImageURL(urls[0])) {
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
// FIXME: After calling dialog.exec(), p_source->hasUrl() returns false.
|
||||
if (dialog.getSelection() == 0) {
|
||||
// Insert as image.
|
||||
m_editOps->insertImageFromURL(urls[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
QMimeData newSource;
|
||||
newSource.setUrls(urls);
|
||||
VPlainTextEdit::insertFromMimeData(&newSource);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (p_source->hasText()) {
|
||||
QString text = p_source->text();
|
||||
if (VUtils::isImageURLText(text)) {
|
||||
// The text is a URL to an image.
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
if (dialog.getSelection() == 0) {
|
||||
// Insert as image.
|
||||
QUrl url(text);
|
||||
if (url.isValid()) {
|
||||
m_editOps->insertImageFromURL(QUrl(text));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(p_source->hasText());
|
||||
}
|
||||
|
||||
VPlainTextEdit::insertFromMimeData(p_source);
|
||||
}
|
||||
|
||||
void VMdEditor::imageInserted(const QString &p_path)
|
||||
{
|
||||
ImageLink link;
|
||||
link.m_path = p_path;
|
||||
if (m_file->useRelativeImageFolder()) {
|
||||
link.m_type = ImageLink::LocalRelativeInternal;
|
||||
} else {
|
||||
link.m_type = ImageLink::LocalAbsolute;
|
||||
}
|
||||
|
||||
m_insertedImages.append(link);
|
||||
}
|
||||
|
||||
bool VMdEditor::scrollToHeader(int p_blockNumber)
|
||||
{
|
||||
if (p_blockNumber < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return scrollToBlock(p_blockNumber);
|
||||
}
|
||||
|
||||
int VMdEditor::indexOfCurrentHeader() const
|
||||
{
|
||||
if (m_headers.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int blockNumber = textCursor().block().blockNumber();
|
||||
for (int i = m_headers.size() - 1; i >= 0; --i) {
|
||||
if (!m_headers[i].isEmpty()
|
||||
&& m_headers[i].m_blockNumber <= blockNumber) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool VMdEditor::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
|
||||
{
|
||||
if (m_headers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
int cursorLine = cursor.block().blockNumber();
|
||||
int targetIdx = -1;
|
||||
// -1: skip level check.
|
||||
int targetLevel = 0;
|
||||
int idx = indexOfCurrentHeader();
|
||||
if (idx == -1) {
|
||||
// Cursor locates at the beginning, before any headers.
|
||||
if (p_relativeLevel < 0 || !p_forward) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int delta = 1;
|
||||
if (!p_forward) {
|
||||
delta = -1;
|
||||
}
|
||||
|
||||
bool firstHeader = true;
|
||||
for (targetIdx = idx == -1 ? 0 : idx;
|
||||
targetIdx >= 0 && targetIdx < m_headers.size();
|
||||
targetIdx += delta) {
|
||||
const VTableOfContentItem &header = m_headers[targetIdx];
|
||||
if (header.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetLevel == 0) {
|
||||
// The target level has not been init yet.
|
||||
Q_ASSERT(firstHeader);
|
||||
targetLevel = header.m_level;
|
||||
if (p_relativeLevel < 0) {
|
||||
targetLevel += p_relativeLevel;
|
||||
if (targetLevel < 1) {
|
||||
// Invalid level.
|
||||
return false;
|
||||
}
|
||||
} else if (p_relativeLevel > 0) {
|
||||
targetLevel = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetLevel == -1 || header.m_level == targetLevel) {
|
||||
if (firstHeader
|
||||
&& (cursorLine == header.m_blockNumber
|
||||
|| p_forward)
|
||||
&& idx != -1) {
|
||||
// This header is not counted for the repeat.
|
||||
firstHeader = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (--p_repeat == 0) {
|
||||
// Found.
|
||||
break;
|
||||
}
|
||||
} else if (header.m_level < targetLevel) {
|
||||
// Stop by higher level.
|
||||
return false;
|
||||
}
|
||||
|
||||
firstHeader = false;
|
||||
}
|
||||
|
||||
if (targetIdx < 0 || targetIdx >= m_headers.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Jump to target header.
|
||||
int line = m_headers[targetIdx].m_blockNumber;
|
||||
if (line > -1) {
|
||||
QTextBlock block = document()->findBlockByNumber(line);
|
||||
if (block.isValid()) {
|
||||
cursor.setPosition(block.position());
|
||||
setTextCursor(cursor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void VMdEditor::scrollBlockInPage(int p_blockNum, int p_dest)
|
||||
{
|
||||
VEditUtils::scrollBlockInPage(this, p_blockNum, p_dest);
|
||||
}
|
||||
|
||||
void VMdEditor::updatePlainTextEditConfig()
|
||||
{
|
||||
m_previewMgr->setPreviewEnabled(g_config->getEnablePreviewImages());
|
||||
setBlockImageEnabled(g_config->getEnablePreviewImages());
|
||||
|
||||
setImageWidthConstrainted(g_config->getEnablePreviewImageConstraint());
|
||||
|
||||
int lineNumber = g_config->getEditorLineNumber();
|
||||
if (lineNumber < (int)LineNumberType::None || lineNumber >= (int)LineNumberType::Invalid) {
|
||||
lineNumber = (int)LineNumberType::None;
|
||||
}
|
||||
|
||||
setLineNumberType((LineNumberType)lineNumber);
|
||||
setLineNumberColor(g_config->getEditorLineNumberFg(),
|
||||
g_config->getEditorLineNumberBg());
|
||||
}
|
||||
|
||||
void VMdEditor::updateConfig()
|
||||
{
|
||||
updatePlainTextEditConfig();
|
||||
updateEditConfig();
|
||||
}
|
212
src/vmdeditor.h
Normal file
212
src/vmdeditor.h
Normal file
@ -0,0 +1,212 @@
|
||||
#ifndef VMDEDITOR_H
|
||||
#define VMDEDITOR_H
|
||||
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QClipboard>
|
||||
#include <QImage>
|
||||
|
||||
#include "vplaintextedit.h"
|
||||
#include "veditor.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "veditoperations.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
class HGMarkdownHighlighter;
|
||||
class VCodeBlockHighlightHelper;
|
||||
class VDocument;
|
||||
class VPreviewManager;
|
||||
|
||||
class VMdEditor : public VPlainTextEdit, public VEditor
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VMdEditor(VFile *p_file,
|
||||
VDocument *p_doc,
|
||||
MarkdownConverterType p_type,
|
||||
QWidget *p_parent = nullptr);
|
||||
|
||||
void beginEdit() Q_DECL_OVERRIDE;
|
||||
|
||||
void endEdit() Q_DECL_OVERRIDE;
|
||||
|
||||
void saveFile() Q_DECL_OVERRIDE;
|
||||
|
||||
void reloadFile() Q_DECL_OVERRIDE;
|
||||
|
||||
bool scrollToBlock(int p_blockNumber) Q_DECL_OVERRIDE;
|
||||
|
||||
void makeBlockVisible(const QTextBlock &p_block) Q_DECL_OVERRIDE;
|
||||
|
||||
QVariant inputMethodQuery(Qt::InputMethodQuery p_query) const Q_DECL_OVERRIDE;
|
||||
|
||||
bool isBlockVisible(const QTextBlock &p_block) Q_DECL_OVERRIDE;
|
||||
|
||||
// An image has been inserted. The image is relative.
|
||||
// @p_path is the absolute path of the inserted image.
|
||||
void imageInserted(const QString &p_path);
|
||||
|
||||
// Scroll to header @p_blockNumber.
|
||||
// Return true if @p_blockNumber is valid to scroll to.
|
||||
bool scrollToHeader(int p_blockNumber);
|
||||
|
||||
void scrollBlockInPage(int p_blockNum, int p_dest) Q_DECL_OVERRIDE;
|
||||
|
||||
void updateConfig() Q_DECL_OVERRIDE;
|
||||
|
||||
public slots:
|
||||
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
|
||||
|
||||
// Wrapper functions for QPlainTextEdit/QTextEdit.
|
||||
public:
|
||||
void setExtraSelectionsW(const QList<QTextEdit::ExtraSelection> &p_selections) Q_DECL_OVERRIDE
|
||||
{
|
||||
setExtraSelections(p_selections);
|
||||
}
|
||||
|
||||
QTextDocument *documentW() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return document();
|
||||
}
|
||||
|
||||
void setTabStopWidthW(int p_width) Q_DECL_OVERRIDE
|
||||
{
|
||||
setTabStopWidth(p_width);
|
||||
}
|
||||
|
||||
QTextCursor textCursorW() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return textCursor();
|
||||
}
|
||||
|
||||
void moveCursorW(QTextCursor::MoveOperation p_operation,
|
||||
QTextCursor::MoveMode p_mode) Q_DECL_OVERRIDE
|
||||
{
|
||||
moveCursor(p_operation, p_mode);
|
||||
}
|
||||
|
||||
QScrollBar *verticalScrollBarW() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return verticalScrollBar();
|
||||
}
|
||||
|
||||
QScrollBar *horizontalScrollBarW() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return horizontalScrollBar();
|
||||
}
|
||||
|
||||
void setTextCursorW(const QTextCursor &p_cursor) Q_DECL_OVERRIDE
|
||||
{
|
||||
setTextCursor(p_cursor);
|
||||
}
|
||||
|
||||
bool findW(const QString &p_exp,
|
||||
QTextDocument::FindFlags p_options = QTextDocument::FindFlags()) Q_DECL_OVERRIDE
|
||||
{
|
||||
return find(p_exp, p_options);
|
||||
}
|
||||
|
||||
bool findW(const QRegExp &p_exp,
|
||||
QTextDocument::FindFlags p_options = QTextDocument::FindFlags()) Q_DECL_OVERRIDE
|
||||
{
|
||||
return find(p_exp, p_options);
|
||||
}
|
||||
|
||||
void setReadOnlyW(bool p_ro) Q_DECL_OVERRIDE
|
||||
{
|
||||
setReadOnly(p_ro);
|
||||
}
|
||||
|
||||
QWidget *viewportW() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return viewport();
|
||||
}
|
||||
|
||||
void insertPlainTextW(const QString &p_text) Q_DECL_OVERRIDE
|
||||
{
|
||||
insertPlainText(p_text);
|
||||
}
|
||||
|
||||
void undoW() Q_DECL_OVERRIDE
|
||||
{
|
||||
undo();
|
||||
}
|
||||
|
||||
void redoW() Q_DECL_OVERRIDE
|
||||
{
|
||||
redo();
|
||||
}
|
||||
|
||||
signals:
|
||||
// Signal when headers change.
|
||||
void headersChanged(const QVector<VTableOfContentItem> &p_headers);
|
||||
|
||||
// Signal when current header change.
|
||||
void currentHeaderChanged(int p_blockNumber);
|
||||
|
||||
// Signal when the status of VMdEdit changed.
|
||||
// Will be emitted by VImagePreviewer for now.
|
||||
void statusChanged();
|
||||
|
||||
protected:
|
||||
void updateFontAndPalette() Q_DECL_OVERRIDE;
|
||||
|
||||
void contextMenuEvent(QContextMenuEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
// Used to implement dragging mouse with Ctrl and left button pressed to scroll.
|
||||
void mousePressEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
void mouseReleaseEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
void mouseMoveEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
bool canInsertFromMimeData(const QMimeData *p_source) const Q_DECL_OVERRIDE;
|
||||
|
||||
void insertFromMimeData(const QMimeData *p_source) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
// Update m_headers according to elements.
|
||||
void updateHeaders(const QVector<VElementRegion> &p_headerRegions);
|
||||
|
||||
// Update current header according to cursor position.
|
||||
// When there is no header in current cursor, will signal an invalid header.
|
||||
void updateCurrentHeader();
|
||||
|
||||
private:
|
||||
// Update the config of VPlainTextEdit according to global configurations.
|
||||
void updatePlainTextEditConfig();
|
||||
|
||||
// Get the initial images from file before edit.
|
||||
void initInitImages();
|
||||
|
||||
// Clear two kind of images according to initial images and current images:
|
||||
// 1. Newly inserted images which are deleted later;
|
||||
// 2. Initial images which are deleted;
|
||||
void clearUnusedImages();
|
||||
|
||||
// Index in m_headers of current header which contains the cursor.
|
||||
int indexOfCurrentHeader() const;
|
||||
|
||||
HGMarkdownHighlighter *m_mdHighlighter;
|
||||
|
||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||
|
||||
VPreviewManager *m_previewMgr;
|
||||
|
||||
// Image links inserted while editing.
|
||||
QVector<ImageLink> m_insertedImages;
|
||||
|
||||
// Image links right at the beginning of the edit.
|
||||
QVector<ImageLink> m_initImages;
|
||||
|
||||
// Mainly used for title jump.
|
||||
QVector<VTableOfContentItem> m_headers;
|
||||
|
||||
bool m_freshEdit;
|
||||
};
|
||||
|
||||
#endif // VMDEDITOR_H
|
@ -12,11 +12,14 @@
|
||||
#include "vmarkdownconverter.h"
|
||||
#include "vnotebook.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "vmdedit.h"
|
||||
#include "dialog/vfindreplacedialog.h"
|
||||
#include "veditarea.h"
|
||||
#include "vconstants.h"
|
||||
#include "vwebview.h"
|
||||
#include "vmdeditor.h"
|
||||
#include "vmainwindow.h"
|
||||
|
||||
extern VMainWindow *g_mainWin;
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -124,7 +127,7 @@ bool VMdTab::scrollEditorToHeader(const VHeaderPointer &p_header)
|
||||
return false;
|
||||
}
|
||||
|
||||
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor());
|
||||
VMdEditor *mdEdit = getEditor();
|
||||
|
||||
int blockNumber = -1;
|
||||
if (p_header.isValid()) {
|
||||
@ -185,8 +188,7 @@ void VMdTab::showFileEditMode()
|
||||
|
||||
m_isEditMode = true;
|
||||
|
||||
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor());
|
||||
V_ASSERT(mdEdit);
|
||||
VMdEditor *mdEdit = getEditor();
|
||||
|
||||
mdEdit->beginEdit();
|
||||
m_stacks->setCurrentWidget(mdEdit);
|
||||
@ -376,34 +378,35 @@ void VMdTab::setupMarkdownViewer()
|
||||
void VMdTab::setupMarkdownEditor()
|
||||
{
|
||||
Q_ASSERT(!m_editor);
|
||||
qDebug() << "create Markdown editor";
|
||||
|
||||
m_editor = new VMdEdit(m_file, m_document, m_mdConType, this);
|
||||
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::headersChanged,
|
||||
m_editor = new VMdEditor(m_file, m_document, m_mdConType, this);
|
||||
connect(m_editor, &VMdEditor::headersChanged,
|
||||
this, &VMdTab::updateOutlineFromHeaders);
|
||||
connect(dynamic_cast<VMdEdit *>(m_editor), SIGNAL(currentHeaderChanged(int)),
|
||||
connect(m_editor, SIGNAL(currentHeaderChanged(int)),
|
||||
this, SLOT(updateCurrentHeader(int)));
|
||||
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::statusChanged,
|
||||
connect(m_editor, &VMdEditor::statusChanged,
|
||||
this, &VMdTab::updateStatus);
|
||||
connect(m_editor, &VEdit::textChanged,
|
||||
connect(m_editor, &VMdEditor::textChanged,
|
||||
this, &VMdTab::updateStatus);
|
||||
connect(m_editor, &VEdit::cursorPositionChanged,
|
||||
connect(m_editor, &VMdEditor::cursorPositionChanged,
|
||||
this, &VMdTab::updateStatus);
|
||||
connect(m_editor, &VEdit::saveAndRead,
|
||||
connect(g_mainWin, &VMainWindow::editorConfigUpdated,
|
||||
m_editor, &VMdEditor::updateConfig);
|
||||
connect(m_editor->object(), &VEditorObject::saveAndRead,
|
||||
this, &VMdTab::saveAndRead);
|
||||
connect(m_editor, &VEdit::discardAndRead,
|
||||
connect(m_editor->object(), &VEditorObject::discardAndRead,
|
||||
this, &VMdTab::discardAndRead);
|
||||
connect(m_editor, &VEdit::saveNote,
|
||||
connect(m_editor->object(), &VEditorObject::saveNote,
|
||||
this, &VMdTab::saveFile);
|
||||
connect(m_editor, &VEdit::statusMessage,
|
||||
connect(m_editor->object(), &VEditorObject::statusMessage,
|
||||
this, &VEditTab::statusMessage);
|
||||
connect(m_editor, &VEdit::vimStatusUpdated,
|
||||
connect(m_editor->object(), &VEditorObject::vimStatusUpdated,
|
||||
this, &VEditTab::vimStatusUpdated);
|
||||
connect(m_editor, &VEdit::requestCloseFindReplaceDialog,
|
||||
connect(m_editor->object(), &VEditorObject::requestCloseFindReplaceDialog,
|
||||
this, [this]() {
|
||||
this->m_editArea->getFindReplaceDialog()->closeDialog();
|
||||
});
|
||||
connect(m_editor, SIGNAL(ready(void)),
|
||||
connect(m_editor->object(), SIGNAL(ready(void)),
|
||||
this, SLOT(restoreFromTabInfo(void)));
|
||||
|
||||
enableHeadingSequence(m_enableHeadingSequence);
|
||||
|
12
src/vmdtab.h
12
src/vmdtab.h
@ -10,8 +10,8 @@
|
||||
|
||||
class VWebView;
|
||||
class QStackedLayout;
|
||||
class VEdit;
|
||||
class VDocument;
|
||||
class VMdEditor;
|
||||
|
||||
class VMdTab : public VEditTab
|
||||
{
|
||||
@ -55,7 +55,7 @@ public:
|
||||
|
||||
VWebView *getWebViewer() const;
|
||||
|
||||
VEdit *getEditor() const;
|
||||
VMdEditor *getEditor() const;
|
||||
|
||||
MarkdownConverterType getMarkdownConverterType() const;
|
||||
|
||||
@ -148,13 +148,13 @@ private:
|
||||
void focusChild() Q_DECL_OVERRIDE;
|
||||
|
||||
// Get the markdown editor. If not init yet, init and return it.
|
||||
VEdit *getEditor();
|
||||
VMdEditor *getEditor();
|
||||
|
||||
// Restore from @p_fino.
|
||||
// Return true if succeed.
|
||||
bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
|
||||
|
||||
VEdit *m_editor;
|
||||
VMdEditor *m_editor;
|
||||
VWebView *m_webViewer;
|
||||
VDocument *m_document;
|
||||
MarkdownConverterType m_mdConType;
|
||||
@ -165,7 +165,7 @@ private:
|
||||
QStackedLayout *m_stacks;
|
||||
};
|
||||
|
||||
inline VEdit *VMdTab::getEditor()
|
||||
inline VMdEditor *VMdTab::getEditor()
|
||||
{
|
||||
if (m_editor) {
|
||||
return m_editor;
|
||||
@ -175,7 +175,7 @@ inline VEdit *VMdTab::getEditor()
|
||||
}
|
||||
}
|
||||
|
||||
inline VEdit *VMdTab::getEditor() const
|
||||
inline VMdEditor *VMdTab::getEditor() const
|
||||
{
|
||||
return m_editor;
|
||||
}
|
||||
|
@ -14,10 +14,11 @@ const int VPlainTextEdit::c_minimumImageWidth = 100;
|
||||
|
||||
enum class BlockState
|
||||
{
|
||||
Normal = 1,
|
||||
Normal = 0,
|
||||
CodeBlockStart,
|
||||
CodeBlock,
|
||||
CodeBlockEnd
|
||||
CodeBlockEnd,
|
||||
Comment
|
||||
};
|
||||
|
||||
|
||||
@ -79,6 +80,8 @@ void VPlainTextEdit::updateBlockImages(const QVector<VBlockImageInfo> &p_blocksI
|
||||
{
|
||||
if (m_blockImageEnabled) {
|
||||
m_imageMgr->updateBlockInfos(p_blocksInfo, m_maximumImageWidth);
|
||||
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,6 +301,9 @@ void VPlainTextEdit::drawImageOfBlock(const QTextBlock &p_block,
|
||||
qMax(info->m_imageHeight, tmpRect.height() - oriHeight));
|
||||
|
||||
p_painter->drawPixmap(targetRect, *image);
|
||||
|
||||
auto *layout = getLayout();
|
||||
emit layout->documentSizeChanged(layout->documentSize());
|
||||
}
|
||||
|
||||
QRectF VPlainTextEdit::originalBlockBoundingRect(const QTextBlock &p_block) const
|
||||
@ -322,14 +328,23 @@ void VPlainTextEdit::setBlockImageEnabled(bool p_enabled)
|
||||
void VPlainTextEdit::setImageWidthConstrainted(bool p_enabled)
|
||||
{
|
||||
m_imageWidthConstrainted = p_enabled;
|
||||
|
||||
updateImageWidth();
|
||||
|
||||
auto *layout = getLayout();
|
||||
emit layout->documentSizeChanged(layout->documentSize());
|
||||
}
|
||||
|
||||
void VPlainTextEdit::resizeEvent(QResizeEvent *p_event)
|
||||
void VPlainTextEdit::updateImageWidth()
|
||||
{
|
||||
bool needUpdate = false;
|
||||
if (m_imageWidthConstrainted) {
|
||||
const QSize &si = p_event->size();
|
||||
m_maximumImageWidth = si.width();
|
||||
int viewWidth = viewport()->size().width();
|
||||
m_maximumImageWidth = viewWidth - 10;
|
||||
if (m_maximumImageWidth < 0) {
|
||||
m_maximumImageWidth = viewWidth;
|
||||
}
|
||||
|
||||
needUpdate = true;
|
||||
} else if (m_maximumImageWidth != INT_MAX) {
|
||||
needUpdate = true;
|
||||
@ -339,6 +354,11 @@ void VPlainTextEdit::resizeEvent(QResizeEvent *p_event)
|
||||
if (needUpdate) {
|
||||
m_imageMgr->updateImageWidth(m_maximumImageWidth);
|
||||
}
|
||||
}
|
||||
|
||||
void VPlainTextEdit::resizeEvent(QResizeEvent *p_event)
|
||||
{
|
||||
updateImageWidth();
|
||||
|
||||
QPlainTextEdit::resizeEvent(p_event);
|
||||
|
||||
@ -368,9 +388,8 @@ void VPlainTextEdit::paintLineNumberArea(QPaintEvent *p_event)
|
||||
}
|
||||
|
||||
int blockNumber = block.blockNumber();
|
||||
int offsetY = (int)contentOffset().y();
|
||||
QRectF rect = blockBoundingRect(block);
|
||||
int top = offsetY + (int)rect.y();
|
||||
QRectF rect = blockBoundingGeometry(block);
|
||||
int top = (int)(contentOffset().y() + rect.y());
|
||||
int bottom = top + (int)rect.height();
|
||||
int eventTop = p_event->rect().top();
|
||||
int eventBtm = p_event->rect().bottom();
|
||||
@ -496,7 +515,9 @@ void VPlainTextEdit::updateLineNumberAreaMargin()
|
||||
width = m_lineNumberArea->calculateWidth();
|
||||
}
|
||||
|
||||
if (width != viewportMargins().left()) {
|
||||
setViewportMargins(width, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void VPlainTextEdit::updateLineNumberArea()
|
||||
|
@ -83,6 +83,8 @@ public:
|
||||
|
||||
void setLineNumberType(LineNumberType p_type);
|
||||
|
||||
void setLineNumberColor(const QColor &p_foreground, const QColor &p_background);
|
||||
|
||||
// The minimum width of an image in pixels.
|
||||
static const int c_minimumImageWidth;
|
||||
|
||||
@ -111,6 +113,8 @@ private:
|
||||
|
||||
VPlainTextDocumentLayout *getLayout() const;
|
||||
|
||||
void updateImageWidth();
|
||||
|
||||
// Widget to display line number area.
|
||||
VLineNumberArea *m_lineNumberArea;
|
||||
|
||||
@ -176,4 +180,11 @@ inline void VPlainTextEdit::setLineNumberType(LineNumberType p_type)
|
||||
updateLineNumberArea();
|
||||
}
|
||||
|
||||
inline void VPlainTextEdit::setLineNumberColor(const QColor &p_foreground,
|
||||
const QColor &p_background)
|
||||
{
|
||||
m_lineNumberArea->setForegroundColor(p_foreground);
|
||||
m_lineNumberArea->setBackgroundColor(p_background);
|
||||
}
|
||||
|
||||
#endif // VPLAINTEXTEDIT_H
|
||||
|
316
src/vpreviewmanager.cpp
Normal file
316
src/vpreviewmanager.cpp
Normal file
@ -0,0 +1,316 @@
|
||||
#include "vpreviewmanager.h"
|
||||
|
||||
#include <QTextDocument>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vdownloader.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VPreviewManager::VPreviewManager(VMdEditor *p_editor)
|
||||
: QObject(p_editor),
|
||||
m_editor(p_editor),
|
||||
m_previewEnabled(false)
|
||||
{
|
||||
m_blockImageInfo.resize(PreviewSource::Invalid);
|
||||
|
||||
m_downloader = new VDownloader(this);
|
||||
connect(m_downloader, &VDownloader::downloadFinished,
|
||||
this, &VPreviewManager::imageDownloaded);
|
||||
}
|
||||
|
||||
void VPreviewManager::imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions)
|
||||
{
|
||||
if (!m_previewEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_imageRegions = p_imageRegions;
|
||||
|
||||
previewImages();
|
||||
}
|
||||
|
||||
void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url)
|
||||
{
|
||||
if (!m_previewEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = m_urlToName.find(p_url);
|
||||
if (it == m_urlToName.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString name = it.value();
|
||||
m_urlToName.erase(it);
|
||||
|
||||
if (m_editor->containsImage(name) || name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPixmap image;
|
||||
image.loadFromData(p_data);
|
||||
|
||||
if (!image.isNull()) {
|
||||
m_editor->addImage(name, image);
|
||||
qDebug() << "downloaded image inserted in resource manager" << p_url << name;
|
||||
emit requestUpdateImageLinks();
|
||||
}
|
||||
}
|
||||
|
||||
void VPreviewManager::setPreviewEnabled(bool p_enabled)
|
||||
{
|
||||
if (m_previewEnabled != p_enabled) {
|
||||
m_previewEnabled = p_enabled;
|
||||
|
||||
if (!m_previewEnabled) {
|
||||
clearPreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VPreviewManager::clearPreview()
|
||||
{
|
||||
for (int i = 0; i < m_blockImageInfo.size(); ++i) {
|
||||
m_blockImageInfo[i].clear();
|
||||
}
|
||||
|
||||
updateEditorBlockImages();
|
||||
}
|
||||
|
||||
void VPreviewManager::previewImages()
|
||||
{
|
||||
QVector<ImageLinkInfo> imageLinks;
|
||||
fetchImageLinksFromRegions(imageLinks);
|
||||
|
||||
updateBlockImageInfo(imageLinks);
|
||||
|
||||
updateEditorBlockImages();
|
||||
}
|
||||
|
||||
// Returns true if p_text[p_start, p_end) is all spaces.
|
||||
static bool isAllSpaces(const QString &p_text, int p_start, int p_end)
|
||||
{
|
||||
int len = qMin(p_text.size(), p_end);
|
||||
for (int i = p_start; i < len; ++i) {
|
||||
if (!p_text[i].isSpace()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VPreviewManager::fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks)
|
||||
{
|
||||
p_imageLinks.clear();
|
||||
|
||||
if (m_imageRegions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
p_imageLinks.reserve(m_imageRegions.size());
|
||||
|
||||
QTextDocument *doc = m_editor->document();
|
||||
|
||||
for (int i = 0; i < m_imageRegions.size(); ++i) {
|
||||
VElementRegion ® = m_imageRegions[i];
|
||||
QTextBlock block = doc->findBlock(reg.m_startPos);
|
||||
if (!block.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int blockStart = block.position();
|
||||
int blockEnd = blockStart + block.length() - 1;
|
||||
QString text = block.text();
|
||||
Q_ASSERT(reg.m_endPos <= blockEnd);
|
||||
ImageLinkInfo info(reg.m_startPos,
|
||||
reg.m_endPos,
|
||||
block.blockNumber(),
|
||||
calculateBlockMargin(block));
|
||||
if ((reg.m_startPos == blockStart
|
||||
|| isAllSpaces(text, 0, reg.m_startPos - blockStart))
|
||||
&& (reg.m_endPos == blockEnd
|
||||
|| isAllSpaces(text, reg.m_endPos - blockStart, blockEnd - blockStart))) {
|
||||
// Image block.
|
||||
info.m_isBlock = true;
|
||||
info.m_linkUrl = fetchImagePathToPreview(text, info.m_linkShortUrl);
|
||||
} else {
|
||||
// Inline image.
|
||||
info.m_isBlock = false;
|
||||
info.m_linkUrl = fetchImagePathToPreview(text.mid(reg.m_startPos - blockStart,
|
||||
reg.m_endPos - reg.m_startPos),
|
||||
info.m_linkShortUrl);
|
||||
}
|
||||
|
||||
if (info.m_linkUrl.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
p_imageLinks.append(info);
|
||||
|
||||
qDebug() << "image region" << i
|
||||
<< info.m_startPos << info.m_endPos << info.m_blockNumber
|
||||
<< info.m_linkShortUrl << info.m_linkUrl << info.m_isBlock;
|
||||
}
|
||||
}
|
||||
|
||||
QString VPreviewManager::fetchImageUrlToPreview(const QString &p_text)
|
||||
{
|
||||
QRegExp regExp(VUtils::c_imageLinkRegExp);
|
||||
|
||||
int index = regExp.indexIn(p_text);
|
||||
if (index == -1) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
int lastIndex = regExp.lastIndexIn(p_text);
|
||||
if (lastIndex != index) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return regExp.capturedTexts()[2].trimmed();
|
||||
}
|
||||
|
||||
QString VPreviewManager::fetchImagePathToPreview(const QString &p_text, QString &p_url)
|
||||
{
|
||||
p_url = fetchImageUrlToPreview(p_text);
|
||||
if (p_url.isEmpty()) {
|
||||
return p_url;
|
||||
}
|
||||
|
||||
const VFile *file = m_editor->getFile();
|
||||
|
||||
QString imagePath;
|
||||
QFileInfo info(file->fetchBasePath(), p_url);
|
||||
|
||||
if (info.exists()) {
|
||||
if (info.isNativePath()) {
|
||||
// Local file.
|
||||
imagePath = QDir::cleanPath(info.absoluteFilePath());
|
||||
} else {
|
||||
imagePath = p_url;
|
||||
}
|
||||
} else {
|
||||
QString decodedUrl(p_url);
|
||||
VUtils::decodeUrl(decodedUrl);
|
||||
QFileInfo dinfo(file->fetchBasePath(), decodedUrl);
|
||||
if (dinfo.exists()) {
|
||||
if (dinfo.isNativePath()) {
|
||||
// Local file.
|
||||
imagePath = QDir::cleanPath(dinfo.absoluteFilePath());
|
||||
} else {
|
||||
imagePath = p_url;
|
||||
}
|
||||
} else {
|
||||
QUrl url(p_url);
|
||||
imagePath = url.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
void VPreviewManager::updateBlockImageInfo(const QVector<ImageLinkInfo> &p_imageLinks)
|
||||
{
|
||||
QVector<VBlockImageInfo> &blockInfos = m_blockImageInfo[PreviewSource::ImageLink];
|
||||
blockInfos.clear();
|
||||
|
||||
for (int i = 0; i < p_imageLinks.size(); ++i) {
|
||||
const ImageLinkInfo &link = p_imageLinks[i];
|
||||
|
||||
// Skip inline images.
|
||||
if (!link.m_isBlock) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString name = imageResourceName(link);
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VBlockImageInfo info(link.m_blockNumber, name, link.m_margin);
|
||||
blockInfos.push_back(info);
|
||||
}
|
||||
}
|
||||
|
||||
QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
|
||||
{
|
||||
QString name = p_link.m_linkShortUrl;
|
||||
if (m_editor->containsImage(name)
|
||||
|| name.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Add it to the resource.
|
||||
QString imgPath = p_link.m_linkUrl;
|
||||
QFileInfo info(imgPath);
|
||||
QPixmap image;
|
||||
if (info.exists()) {
|
||||
// Local file.
|
||||
image = QPixmap(imgPath);
|
||||
} else {
|
||||
// URL. Try to download it.
|
||||
m_downloader->download(imgPath);
|
||||
m_urlToName.insert(imgPath, name);
|
||||
}
|
||||
|
||||
if (image.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
m_editor->addImage(name, image);
|
||||
return name;
|
||||
}
|
||||
|
||||
void VPreviewManager::updateEditorBlockImages()
|
||||
{
|
||||
// TODO: need to combine all preview sources.
|
||||
Q_ASSERT(m_blockImageInfo.size() == 1);
|
||||
|
||||
m_editor->updateBlockImages(m_blockImageInfo[PreviewSource::ImageLink]);
|
||||
}
|
||||
|
||||
int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
|
||||
{
|
||||
static QHash<QString, int> spaceWidthOfFonts;
|
||||
|
||||
if (!p_block.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString text = p_block.text();
|
||||
int nrSpaces = 0;
|
||||
for (int i = 0; i < text.size(); ++i) {
|
||||
if (!text[i].isSpace()) {
|
||||
break;
|
||||
} else if (text[i] == ' ') {
|
||||
++nrSpaces;
|
||||
} else if (text[i] == '\t') {
|
||||
nrSpaces += m_editor->tabStopWidth();
|
||||
}
|
||||
}
|
||||
|
||||
if (nrSpaces == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spaceWidth = 0;
|
||||
QFont font = p_block.charFormat().font();
|
||||
QString fontName = font.toString();
|
||||
auto it = spaceWidthOfFonts.find(fontName);
|
||||
if (it != spaceWidthOfFonts.end()) {
|
||||
spaceWidth = it.value();
|
||||
} else {
|
||||
spaceWidth = QFontMetrics(font).width(' ');
|
||||
spaceWidthOfFonts.insert(fontName, spaceWidth);
|
||||
}
|
||||
|
||||
return spaceWidth * nrSpaces;
|
||||
}
|
135
src/vpreviewmanager.h
Normal file
135
src/vpreviewmanager.h
Normal file
@ -0,0 +1,135 @@
|
||||
#ifndef VPREVIEWMANAGER_H
|
||||
#define VPREVIEWMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTextBlock>
|
||||
#include <QHash>
|
||||
#include <QVector>
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vmdeditor.h"
|
||||
|
||||
class VDownloader;
|
||||
|
||||
|
||||
class VPreviewManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VPreviewManager(VMdEditor *p_editor);
|
||||
|
||||
void setPreviewEnabled(bool p_enabled);
|
||||
|
||||
// Clear all the preview.
|
||||
void clearPreview();
|
||||
|
||||
public slots:
|
||||
// Image links were updated from the highlighter.
|
||||
void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions);
|
||||
|
||||
signals:
|
||||
// Request highlighter to update image links.
|
||||
void requestUpdateImageLinks();
|
||||
|
||||
private slots:
|
||||
// Non-local image downloaded for preview.
|
||||
void imageDownloaded(const QByteArray &p_data, const QString &p_url);
|
||||
|
||||
private:
|
||||
// Sources of the preview.
|
||||
enum PreviewSource
|
||||
{
|
||||
ImageLink = 0,
|
||||
Invalid
|
||||
};
|
||||
|
||||
struct ImageLinkInfo
|
||||
{
|
||||
ImageLinkInfo()
|
||||
: m_startPos(-1),
|
||||
m_endPos(-1),
|
||||
m_blockNumber(-1),
|
||||
m_margin(0),
|
||||
m_isBlock(false)
|
||||
{
|
||||
}
|
||||
|
||||
ImageLinkInfo(int p_startPos,
|
||||
int p_endPos,
|
||||
int p_blockNumber,
|
||||
int p_margin)
|
||||
: m_startPos(p_startPos),
|
||||
m_endPos(p_endPos),
|
||||
m_blockNumber(p_blockNumber),
|
||||
m_margin(p_margin),
|
||||
m_isBlock(false)
|
||||
{
|
||||
}
|
||||
|
||||
int m_startPos;
|
||||
|
||||
int m_endPos;
|
||||
|
||||
int m_blockNumber;
|
||||
|
||||
// Left margin of this block in pixels.
|
||||
int m_margin;
|
||||
|
||||
// Short URL within the () of ![]().
|
||||
// Used as the ID of the image.
|
||||
QString m_linkShortUrl;
|
||||
|
||||
// Full URL of the link.
|
||||
QString m_linkUrl;
|
||||
|
||||
// Whether it is an image block.
|
||||
bool m_isBlock;
|
||||
};
|
||||
|
||||
// Start to preview images according to image links.
|
||||
void previewImages();
|
||||
|
||||
// According to m_imageRegions, fetch the image link Url.
|
||||
// @p_imageRegions: output.
|
||||
void fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks);
|
||||
|
||||
// Fetch the image link's URL if there is only one link.
|
||||
QString fetchImageUrlToPreview(const QString &p_text);
|
||||
|
||||
// Fetch teh image's full path if there is only one image link.
|
||||
// @p_url: contains the short URL in ![]().
|
||||
QString fetchImagePathToPreview(const QString &p_text, QString &p_url);
|
||||
|
||||
void updateBlockImageInfo(const QVector<ImageLinkInfo> &p_imageLinks);
|
||||
|
||||
// Get the name of the image in the resource manager.
|
||||
// Will add the image to the resource manager if not exists.
|
||||
// Returns empty if fail to add the image to the resource manager.
|
||||
QString imageResourceName(const ImageLinkInfo &p_link);
|
||||
|
||||
// Ask the editor to preview images.
|
||||
void updateEditorBlockImages();
|
||||
|
||||
// Calculate the block margin (prefix spaces) in pixels.
|
||||
int calculateBlockMargin(const QTextBlock &p_block);
|
||||
|
||||
VMdEditor *m_editor;
|
||||
|
||||
VDownloader *m_downloader;
|
||||
|
||||
// Whether preview is enabled.
|
||||
bool m_previewEnabled;
|
||||
|
||||
// Regions of all the image links.
|
||||
QVector<VElementRegion> m_imageRegions;
|
||||
|
||||
// All preview images and information.
|
||||
// Each preview source corresponds to one vector.
|
||||
QVector<QVector<VBlockImageInfo>> m_blockImageInfo;
|
||||
|
||||
// Map from URL to name in the resource manager.
|
||||
// Used for downloading images.
|
||||
QHash<QString, QString> m_urlToName;
|
||||
};
|
||||
|
||||
#endif // VPREVIEWMANAGER_H
|
Loading…
x
Reference in New Issue
Block a user