| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693 |
- #include <QtWidgets>
- #include <QVector>
- #include <QDebug>
- #include "vedit.h"
- #include "vnote.h"
- #include "vconfigmanager.h"
- #include "vtoc.h"
- #include "utils/vutils.h"
- #include "veditoperations.h"
- #include "dialog/vfindreplacedialog.h"
- #include "vedittab.h"
- extern VConfigManager vconfig;
- extern VNote *g_vnote;
- void VEditConfig::init(const QFontMetrics &p_metric)
- {
- if (vconfig.getTabStopWidth() > 0) {
- m_tabStopWidth = vconfig.getTabStopWidth() * p_metric.width(' ');
- } else {
- m_tabStopWidth = 0;
- }
- m_expandTab = vconfig.getIsExpandTab();
- if (m_expandTab && (vconfig.getTabStopWidth() > 0)) {
- m_tabSpaces = QString(vconfig.getTabStopWidth(), ' ');
- } else {
- m_tabSpaces = "\t";
- }
- m_enableVimMode = vconfig.getEnableVimMode();
- m_cursorLineBg = QColor(vconfig.getEditorCurrentLineBg());
- }
- VEdit::VEdit(VFile *p_file, QWidget *p_parent)
- : QTextEdit(p_parent), m_file(p_file), m_editOps(NULL)
- {
- const int labelTimerInterval = 500;
- const int extraSelectionHighlightTimer = 500;
- const int labelSize = 64;
- m_selectedWordColor = QColor("Yellow");
- m_searchedWordColor = QColor(g_vnote->getColorFromPalette("Green4"));
- m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBackground());
- QPixmap wrapPixmap(":/resources/icons/search_wrap.svg");
- m_wrapLabel = new QLabel(this);
- m_wrapLabel->setPixmap(wrapPixmap.scaled(labelSize, labelSize));
- m_wrapLabel->hide();
- m_labelTimer = new QTimer(this);
- m_labelTimer->setSingleShot(true);
- m_labelTimer->setInterval(labelTimerInterval);
- connect(m_labelTimer, &QTimer::timeout,
- this, &VEdit::labelTimerTimeout);
- m_highlightTimer = new QTimer(this);
- m_highlightTimer->setSingleShot(true);
- m_highlightTimer->setInterval(extraSelectionHighlightTimer);
- connect(m_highlightTimer, &QTimer::timeout,
- this, &VEdit::doHighlightExtraSelections);
- connect(document(), &QTextDocument::modificationChanged,
- (VFile *)m_file, &VFile::setModified);
- m_extraSelections.resize((int)SelectionId::MaxSelection);
- updateFontAndPalette();
- updateConfig();
- connect(this, &VEdit::cursorPositionChanged,
- this, &VEdit::handleCursorPositionChanged);
- connect(this, &VEdit::selectionChanged,
- this, &VEdit::highlightSelectedWord);
- }
- VEdit::~VEdit()
- {
- if (m_file) {
- disconnect(document(), &QTextDocument::modificationChanged,
- (VFile *)m_file, &VFile::setModified);
- }
- }
- void VEdit::updateConfig()
- {
- m_config.init(QFontMetrics(font()));
- if (m_config.m_tabStopWidth > 0) {
- setTabStopWidth(m_config.m_tabStopWidth);
- }
- emit configUpdated();
- }
- void VEdit::beginEdit()
- {
- updateFontAndPalette();
- updateConfig();
- setReadOnly(false);
- setModified(false);
- }
- void VEdit::endEdit()
- {
- setReadOnly(true);
- }
- void VEdit::saveFile()
- {
- if (!document()->isModified()) {
- return;
- }
- m_file->setContent(toHtml());
- document()->setModified(false);
- }
- void VEdit::reloadFile()
- {
- setHtml(m_file->getContent());
- setModified(false);
- }
- void VEdit::scrollToLine(int p_lineNumber)
- {
- Q_ASSERT(p_lineNumber >= 0);
- // Move the cursor to the end first
- moveCursor(QTextCursor::End);
- QTextCursor cursor(document()->findBlockByLineNumber(p_lineNumber));
- cursor.movePosition(QTextCursor::EndOfBlock);
- setTextCursor(cursor);
- }
- bool VEdit::isModified() const
- {
- return document()->isModified();
- }
- void VEdit::setModified(bool p_modified)
- {
- document()->setModified(p_modified);
- if (m_file) {
- m_file->setModified(p_modified);
- }
- }
- void VEdit::insertImage()
- {
- if (m_editOps) {
- m_editOps->insertImage();
- }
- }
- bool VEdit::peekText(const QString &p_text, uint p_options)
- {
- static int startPos = textCursor().selectionStart();
- static int lastPos = startPos;
- bool found = false;
- if (p_text.isEmpty()) {
- // Clear previous selection
- QTextCursor cursor = textCursor();
- cursor.clearSelection();
- cursor.setPosition(startPos);
- setTextCursor(cursor);
- } else {
- QTextCursor cursor = textCursor();
- int curPos = cursor.selectionStart();
- if (curPos != lastPos) {
- // Cursor has been moved. Just start at current potition.
- startPos = curPos;
- lastPos = curPos;
- } else {
- cursor.setPosition(startPos);
- setTextCursor(cursor);
- }
- }
- bool wrapped = false;
- found = findTextHelper(p_text, p_options, true, wrapped);
- if (found) {
- lastPos = textCursor().selectionStart();
- found = true;
- }
- return found;
- }
- // Use QTextEdit::find() instead of QTextDocument::find() because the later has
- // bugs in searching backward.
- bool VEdit::findTextHelper(const QString &p_text, uint p_options,
- bool p_forward, bool &p_wrapped)
- {
- 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);
- }
- QTextCursor cursor = textCursor();
- while (!found) {
- if (useRegExp) {
- found = find(exp, findFlags);
- } else {
- found = find(p_text, findFlags);
- }
- if (p_wrapped) {
- if (!found) {
- setTextCursor(cursor);
- }
- break;
- }
- if (!found) {
- // Wrap to the other end of the document to search again.
- p_wrapped = true;
- QTextCursor wrapCursor = textCursor();
- wrapCursor.clearSelection();
- if (p_forward) {
- wrapCursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
- } else {
- wrapCursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
- }
- setTextCursor(wrapCursor);
- }
- }
- return found;
- }
- QList<QTextCursor> VEdit::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;
- QTextDocument *doc = document();
- while (true) {
- if (useRegExp) {
- cursor = doc->find(exp, startPos, findFlags);
- } else {
- cursor = doc->find(p_text, startPos, findFlags);
- }
- if (cursor.isNull()) {
- break;
- } else {
- results.append(cursor);
- startPos = cursor.selectionEnd();
- }
- }
- return results;
- }
- bool VEdit::findText(const QString &p_text, uint p_options, bool p_forward)
- {
- bool found = false;
- if (p_text.isEmpty()) {
- QTextCursor cursor = textCursor();
- cursor.clearSelection();
- setTextCursor(cursor);
- } else {
- bool wrapped = false;
- found = findTextHelper(p_text, p_options, p_forward, wrapped);
- if (found) {
- if (wrapped) {
- showWrapLabel();
- }
- highlightSearchedWord(p_text, p_options);
- } else {
- // Simply clear previous highlight.
- highlightSearchedWord("", p_options);
- }
- }
- qDebug() << "findText" << p_text << p_options << p_forward
- << (found ? "Found" : "NotFound");
- return found;
- }
- void VEdit::replaceText(const QString &p_text, uint p_options,
- const QString &p_replaceText, bool p_findNext)
- {
- QTextCursor cursor = textCursor();
- if (cursor.hasSelection()) {
- // Replace occurs only if the selected text matches @p_text with @p_options.
- QTextCursor tmpCursor = cursor;
- tmpCursor.setPosition(tmpCursor.selectionStart());
- tmpCursor.clearSelection();
- setTextCursor(tmpCursor);
- bool wrapped = false;
- bool found = findTextHelper(p_text, p_options, true, wrapped);
- bool matched = false;
- if (found) {
- tmpCursor = textCursor();
- matched = (cursor.selectionStart() == tmpCursor.selectionStart())
- && (cursor.selectionEnd() == tmpCursor.selectionEnd());
- }
- if (matched) {
- cursor.beginEditBlock();
- cursor.removeSelectedText();
- cursor.insertText(p_replaceText);
- cursor.endEditBlock();
- setTextCursor(cursor);
- } else {
- setTextCursor(cursor);
- }
- }
- if (p_findNext) {
- findText(p_text, p_options, true);
- }
- }
- void VEdit::replaceTextAll(const QString &p_text, uint p_options,
- const QString &p_replaceText)
- {
- // Replace from the start to the end and resotre the cursor.
- QTextCursor cursor = textCursor();
- int nrReplaces = 0;
- QTextCursor tmpCursor = cursor;
- tmpCursor.setPosition(0);
- setTextCursor(tmpCursor);
- while (true) {
- bool wrapped = false;
- bool found = findTextHelper(p_text, p_options, true, wrapped);
- if (!found) {
- break;
- } else {
- if (wrapped) {
- // Wrap back.
- break;
- }
- nrReplaces++;
- tmpCursor = textCursor();
- tmpCursor.beginEditBlock();
- tmpCursor.removeSelectedText();
- tmpCursor.insertText(p_replaceText);
- tmpCursor.endEditBlock();
- setTextCursor(tmpCursor);
- }
- }
- // Restore cursor position.
- cursor.clearSelection();
- setTextCursor(cursor);
- qDebug() << "replace all" << nrReplaces << "occurences";
- }
- void VEdit::showWrapLabel()
- {
- int labelW = m_wrapLabel->width();
- int labelH = m_wrapLabel->height();
- int x = (width() - labelW) / 2;
- int y = (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 VEdit::labelTimerTimeout()
- {
- m_wrapLabel->hide();
- }
- void VEdit::updateFontAndPalette()
- {
- setFont(vconfig.getBaseEditFont());
- setPalette(vconfig.getBaseEditPalette());
- }
- void VEdit::highlightExtraSelections(bool p_now)
- {
- m_highlightTimer->stop();
- if (p_now) {
- doHighlightExtraSelections();
- } else {
- m_highlightTimer->start();
- }
- }
- void VEdit::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]);
- }
- setExtraSelections(extraSelects);
- }
- void VEdit::highlightCurrentLine()
- {
- QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::CurrentLine];
- if (vconfig.getHighlightCursorLine() && !isReadOnly()) {
- // Need to highlight current line.
- QTextEdit::ExtraSelection select;
- select.format.setBackground(m_config.m_cursorLineBg);
- select.format.setProperty(QTextFormat::FullWidthSelection, true);
- select.cursor = textCursor();
- select.cursor.clearSelection();
- selects.clear();
- selects.append(select);
- } else {
- // Need to clear current line highlight.
- if (selects.isEmpty()) {
- return;
- }
- selects.clear();
- }
- highlightExtraSelections(true);
- }
- void VEdit::setReadOnly(bool p_ro)
- {
- QTextEdit::setReadOnly(p_ro);
- highlightCurrentLine();
- }
- void VEdit::highlightSelectedWord()
- {
- QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SelectedWord];
- if (!vconfig.getHighlightSelectedWord()) {
- if (!selects.isEmpty()) {
- selects.clear();
- highlightExtraSelections(true);
- }
- return;
- }
- QString text = textCursor().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);
- }
- // Do not highlight trailing spaces with current cursor right behind.
- static void trailingSpaceFilter(VEdit *p_editor, QList<QTextEdit::ExtraSelection> &p_result)
- {
- QTextCursor cursor = p_editor->textCursor();
- 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 VEdit::highlightTrailingSpace()
- {
- if (!vconfig.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);
- }
- bool VEdit::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;
- }
- void VEdit::highlightTextAll(const QString &p_text, uint p_options,
- SelectionId p_id, QTextCharFormat p_format,
- void (*p_filter)(VEdit *, 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();
- }
- void VEdit::highlightSearchedWord(const QString &p_text, uint p_options)
- {
- QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
- if (!vconfig.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 VEdit::clearSearchedWordHighlight()
- {
- QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
- selects.clear();
- highlightExtraSelections(true);
- }
- void VEdit::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());
- V_ASSERT(editTab);
- if (editTab->isEditMode()) {
- QAction *saveExitAct = new QAction(QIcon(":/resources/icons/save_exit.svg"),
- tr("&Save Changes And Read"), this);
- saveExitAct->setToolTip(tr("Save changes and exit edit mode"));
- connect(saveExitAct, &QAction::triggered,
- this, &VEdit::handleSaveExitAct);
- QAction *discardExitAct = new QAction(QIcon(":/resources/icons/discard_exit.svg"),
- tr("&Discard Changes And Read"), this);
- discardExitAct->setToolTip(tr("Discard changes and exit edit mode"));
- connect(discardExitAct, &QAction::triggered,
- this, &VEdit::handleDiscardExitAct);
- menu->insertAction(actions.isEmpty() ? NULL : actions[0], discardExitAct);
- menu->insertAction(discardExitAct, saveExitAct);
- if (!actions.isEmpty()) {
- menu->insertSeparator(actions[0]);
- }
- } else if (m_file->isModifiable()) {
- // HTML.
- QAction *editAct= new QAction(QIcon(":/resources/icons/edit_note.svg"),
- tr("&Edit"), this);
- editAct->setToolTip(tr("Edit current note"));
- connect(editAct, &QAction::triggered,
- this, &VEdit::handleEditAct);
- menu->insertAction(actions.isEmpty() ? NULL : actions[0], editAct);
- // actions does not contain editAction.
- if (!actions.isEmpty()) {
- menu->insertSeparator(actions[0]);
- }
- }
- }
- menu->exec(p_event->globalPos());
- delete menu;
- }
- void VEdit::handleSaveExitAct()
- {
- emit saveAndRead();
- }
- void VEdit::handleDiscardExitAct()
- {
- emit discardAndRead();
- }
- void VEdit::handleEditAct()
- {
- emit editNote();
- }
- VFile *VEdit::getFile() const
- {
- return m_file;
- }
- void VEdit::handleCursorPositionChanged()
- {
- static QTextCursor lastCursor;
- QTextCursor cursor = textCursor();
- 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;
- }
- VEditConfig &VEdit::getConfig()
- {
- return m_config;
- }
|