replace VEdit and VMdEdit with VEditor and VMdEditor

This commit is contained in:
Le Tan 2017-10-26 19:36:12 +08:00
parent 5abcb1a8d9
commit 404b5329a1
31 changed files with 3240 additions and 272 deletions

View File

@ -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 \

View File

@ -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();

View File

@ -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.

View File

@ -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);

View File

@ -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;

View File

@ -95,14 +95,6 @@ enum HighlightBlockState
Comment
};
enum class LineNumberType
{
None = 0,
Absolute,
Relative,
CodeBlock
};
// Pages to open on start up.
enum class StartupPageType
{

View File

@ -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)

View File

@ -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
View 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
View 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

View File

@ -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()

View File

@ -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
View 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
View 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

View File

@ -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);

View File

@ -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.

View File

@ -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 {

View File

@ -13,7 +13,8 @@ enum class LineNumberType
None = 0,
Absolute,
Relative,
CodeBlock
CodeBlock,
Invalid
};

View File

@ -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)

View File

@ -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();

View File

@ -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();

View File

@ -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);

View File

@ -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
View 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
View 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

View File

@ -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);

View File

@ -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;
}

View File

@ -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();
}
setViewportMargins(width, 0, 0, 0);
if (width != viewportMargins().left()) {
setViewportMargins(width, 0, 0, 0);
}
}
void VPlainTextEdit::updateLineNumberArea()

View File

@ -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
View 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 &reg = 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
View 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