#include "noteeditorlogic.h" #include "customDocument.h" #include "customMarkdownHighlighter.h" #include "dbmanager.h" #include "taglistview.h" #include "taglistmodel.h" #include "tagpool.h" #include "taglistdelegate.h" #include #include #include #include #include #include #define FIRST_LINE_MAX 80 // 构造函数,根据 Qt 版本不同,参数有所不同 #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) NoteEditorLogic::NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLabel, QLineEdit *searchEdit, QWidget *kanbanWidget, TagListView *tagListView, TagPool *tagPool, DBManager *dbManager, QObject *parent) #else NoteEditorLogic::NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLabel, QLineEdit *searchEdit, TagListView *tagListView, TagPool *tagPool, DBManager *dbManager, QObject *parent) #endif : QObject(parent), m_textEdit{ textEdit }, m_highlighter{ new CustomMarkdownHighlighter{ m_textEdit->document() } }, m_editorDateLabel{ editorDateLabel }, m_searchEdit{ searchEdit }, #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) m_kanbanWidget{ kanbanWidget }, #endif m_tagListView{ tagListView }, m_dbManager{ dbManager }, m_isContentModified{ false }, m_spacerColor{ 191, 191, 191 }, m_currentAdaptableEditorPadding{ 0 }, m_currentMinimumEditorPadding{ 0 } {// 连接文本编辑器的文本变化信号 connect(m_textEdit, &QTextEdit::textChanged, this, &NoteEditorLogic::onTextEditTextChanged); // 连接创建或更新笔记请求信号 connect(this, &NoteEditorLogic::requestCreateUpdateNote, m_dbManager, &DBManager::onCreateUpdateRequestedNoteContent, Qt::QueuedConnection); // auto save timer 自动保存定时器 m_autoSaveTimer.setSingleShot(true); m_autoSaveTimer.setInterval(50); connect(&m_autoSaveTimer, &QTimer::timeout, this, [this]() { saveNoteToDB(); }); // 初始化标签列表模型和委托 m_tagListModel = new TagListModel{ this }; m_tagListModel->setTagPool(tagPool); m_tagListView->setModel(m_tagListModel); m_tagListDelegate = new TagListDelegate{ this }; m_tagListView->setItemDelegate(m_tagListDelegate); // 连接标签池数据更新信号 connect(tagPool, &TagPool::dataUpdated, this, [this](int) { showTagListForCurrentNote(); }); // 连接滚动条位置变化信号 connect(m_textEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) { if (m_currentNotes.size() == 1 && m_currentNotes[0].id() != SpecialNodeID::InvalidNodeId) { m_currentNotes[0].setScrollBarPosition(value); emit updateNoteDataInList(m_currentNotes[0]); m_isContentModified = true; m_autoSaveTimer.start(); } }); #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) // 连接显示看板视图信号 connect(this, &NoteEditorLogic::showKanbanView, this, [this]() { if (m_kanbanWidget != nullptr) { emit setVisibilityOfFrameRightNonEditor(false); bool shouldRecheck = checkForTasksInEditor(); if (shouldRecheck) { checkForTasksInEditor(); } m_kanbanWidget->show(); m_textEdit->hide(); m_textEdit->clearFocus(); emit kanbanShown(); } }); // 连接隐藏看板视图信号 connect(this, &NoteEditorLogic::hideKanbanView, this, [this]() { if (m_kanbanWidget != nullptr) { emit setVisibilityOfFrameRightNonEditor(true); m_kanbanWidget->hide(); m_textEdit->show(); emit clearKanbanModel(); emit textShown(); } }); #endif } // 获取是否启用 Markdown bool NoteEditorLogic::markdownEnabled() const { return m_highlighter->document() != nullptr; } // 设置是否启用 Markdown void NoteEditorLogic::setMarkdownEnabled(bool enabled) { m_highlighter->setDocument(enabled ? m_textEdit->document() : nullptr); } // 在编辑器中显示笔记 void NoteEditorLogic::showNotesInEditor(const QVector ¬es) { auto currentId = currentEditingNoteId(); if (notes.size() == 1 && notes[0].id() != SpecialNodeID::InvalidNodeId) { if (currentId != SpecialNodeID::InvalidNodeId && notes[0].id() != currentId) { emit noteEditClosed(m_currentNotes[0], false); } #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) emit resetKanbanSettings(); emit checkMultipleNotesSelected( QVariant(false)); // TODO: if not PRO version, should be true #endif m_textEdit->blockSignals(true); m_currentNotes = notes; showTagListForCurrentNote(); // fixing bug #202 m_textEdit->setTextBackgroundColor(QColor(247, 247, 247, 0)); QString content = notes[0].content(); QDateTime dateTime = notes[0].lastModificationdateTime(); int scrollbarPos = notes[0].scrollBarPosition(); // set text and date bool isTextChanged = content != m_textEdit->toPlainText(); if (isTextChanged) { m_textEdit->setText(content); } QString noteDate = dateTime.toString(Qt::ISODate); QString noteDateEditor = getNoteDateEditor(noteDate); m_editorDateLabel->setText(noteDateEditor); // set scrollbar position m_textEdit->verticalScrollBar()->setValue(scrollbarPos); m_textEdit->blockSignals(false); m_textEdit->setReadOnly(false); m_textEdit->setTextInteractionFlags(Qt::TextEditorInteraction); m_textEdit->setFocusPolicy(Qt::StrongFocus); highlightSearch(); #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) if (m_kanbanWidget != nullptr && m_kanbanWidget->isVisible()) { emit clearKanbanModel(); bool shouldRecheck = checkForTasksInEditor(); if (shouldRecheck) { checkForTasksInEditor(); } m_textEdit->setVisible(false); return; } else { m_textEdit->setVisible(true); } #else m_textEdit->setVisible(true); #endif emit textShown(); } else if (notes.size() > 1) { #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) emit checkMultipleNotesSelected(QVariant(true)); #endif m_currentNotes = notes; m_tagListView->setVisible(false); m_textEdit->blockSignals(true); auto verticalScrollBarValueToRestore = m_textEdit->verticalScrollBar()->value(); m_textEdit->clear(); auto padding = m_currentAdaptableEditorPadding > m_currentMinimumEditorPadding ? m_currentAdaptableEditorPadding : m_currentMinimumEditorPadding; QPixmap sep(QSize{ m_textEdit->width() - padding * 2 - 12, 4 }); sep.fill(Qt::transparent); QPainter painter(&sep); painter.setPen(m_spacerColor); painter.drawRect(0, 1, sep.width(), 1); m_textEdit->document()->addResource(QTextDocument::ImageResource, QUrl("mydata://sep.png"), sep); for (int i = 0; i < notes.size(); ++i) { auto cursor = m_textEdit->textCursor(); cursor.movePosition(QTextCursor::End); if (!notes[i].content().endsWith("\n")) { if (i != 0) { cursor.insertText("\n" + notes[i].content() + "\n"); } else { cursor.insertText(notes[i].content() + "\n"); } } else { cursor.insertText(notes[i].content()); } if (i != notes.size() - 1) { cursor.movePosition(QTextCursor::End); cursor.insertText("\n"); cursor.insertImage("mydata://sep.png"); cursor.insertText("\n"); } } m_textEdit->verticalScrollBar()->setValue(verticalScrollBarValueToRestore); m_textEdit->blockSignals(false); m_textEdit->setReadOnly(true); m_textEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); m_textEdit->setFocusPolicy(Qt::NoFocus); highlightSearch(); } } // 文本编辑器内容变化时的处理 void NoteEditorLogic::onTextEditTextChanged() { if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) { m_textEdit->blockSignals(true); QString content = m_currentNotes[0].content(); if (m_textEdit->toPlainText() != content) { // move note to the top of the list emit moveNoteToListViewTop(m_currentNotes[0]); // Get the new data QString firstline = getFirstLine(m_textEdit->toPlainText()); QDateTime dateTime = QDateTime::currentDateTime(); QString noteDate = dateTime.toString(Qt::ISODate); m_editorDateLabel->setText(NoteEditorLogic::getNoteDateEditor(noteDate)); // update note data m_currentNotes[0].setContent(m_textEdit->toPlainText()); m_currentNotes[0].setFullTitle(firstline); m_currentNotes[0].setLastModificationDateTime(dateTime); m_currentNotes[0].setIsTempNote(false); m_currentNotes[0].setScrollBarPosition(m_textEdit->verticalScrollBar()->value()); // update note data in list view emit updateNoteDataInList(m_currentNotes[0]); m_isContentModified = true; m_autoSaveTimer.start(); emit setVisibilityOfFrameRightWidgets(false); } m_textEdit->blockSignals(false); } else { qDebug() << "NoteEditorLogic::onTextEditTextChanged() : m_currentNote is not valid"; } } #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) // 重新排列文本编辑器中的任务 void NoteEditorLogic::rearrangeTasksInTextEditor(int startLinePosition, int endLinePosition, int newLinePosition) { QTextDocument *document = m_textEdit->document(); QTextCursor cursor(document); cursor.setPosition(document->findBlockByNumber(startLinePosition).position()); cursor.setPosition(document->findBlockByNumber(endLinePosition).position(), QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); if (document->findBlockByNumber(endLinePosition + 1).isValid()) { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } QString selectedText = cursor.selectedText(); cursor.removeSelectedText(); if (newLinePosition <= startLinePosition) { cursor.setPosition(document->findBlockByLineNumber(newLinePosition).position()); } else { int newPositionBecauseOfRemoval = newLinePosition - (endLinePosition - startLinePosition + 1); if (newPositionBecauseOfRemoval == document->lineCount()) { cursor.setPosition( document->findBlockByLineNumber(newPositionBecauseOfRemoval - 1).position()); cursor.movePosition(QTextCursor::EndOfBlock); selectedText = "\n" + selectedText; } else { cursor.setPosition( document->findBlockByLineNumber(newPositionBecauseOfRemoval).position()); } } cursor.insertText(selectedText); checkForTasksInEditor(); } // 重新排列文本编辑器中的列 void NoteEditorLogic::rearrangeColumnsInTextEditor(int startLinePosition, int endLinePosition, int newLinePosition) { QTextDocument *document = m_textEdit->document(); QTextCursor cursor(document); cursor.setPosition(document->findBlockByNumber(startLinePosition).position()); cursor.setPosition(document->findBlockByNumber(endLinePosition).position(), QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); if (document->findBlockByNumber(endLinePosition + 1).isValid()) { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } QString selectedText = cursor.selectedText(); cursor.removeSelectedText(); cursor.setPosition(document->findBlockByNumber(startLinePosition).position()); if (document->findBlockByNumber(startLinePosition + 1).isValid()) { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); cursor.removeSelectedText(); } if (startLinePosition < newLinePosition) { // Goes down int newPositionBecauseOfRemoval = newLinePosition - (endLinePosition - startLinePosition + 1); cursor.setPosition(document->findBlockByLineNumber(newPositionBecauseOfRemoval).position()); cursor.movePosition(QTextCursor::EndOfBlock); cursor.insertText("\n" + selectedText); } else { // Goes up cursor.setPosition(document->findBlockByLineNumber(newLinePosition).position()); cursor.insertText(selectedText + "\n"); } checkForTasksInEditor(); } // 获取某行的任务数据 QMap NoteEditorLogic::getTaskDataInLine(const QString &line) { QStringList taskExpressions = { "- [ ]", "- [x]", "* [ ]", "* [x]", "- [X]", "* [X]" }; QMap taskMatchLineData; taskMatchLineData["taskMatchIndex"] = -1; int taskMatchIndex = -1; for (int j = 0; j < taskExpressions.size(); j++) { taskMatchIndex = line.indexOf(taskExpressions[j]); if (taskMatchIndex != -1) { taskMatchLineData["taskMatchIndex"] = taskMatchIndex; taskMatchLineData["taskExpressionSize"] = taskExpressions[j].size(); taskMatchLineData["taskChecked"] = taskExpressions[j][3] == 'x' ? 1 : 0; return taskMatchLineData; } } return taskMatchLineData; } // 检查某行的任务 void NoteEditorLogic::checkTaskInLine(int lineNumber) { QTextDocument *document = m_textEdit->document(); QTextBlock block = document->findBlockByLineNumber(lineNumber); if (block.isValid()) { int indexOfTaskInLine = getTaskDataInLine(block.text())["taskMatchIndex"]; if (indexOfTaskInLine == -1) return; QTextCursor cursor(block); cursor.setPosition(block.position() + indexOfTaskInLine + 3, QTextCursor::MoveAnchor); // Remove the old character and insert the new one cursor.deleteChar(); cursor.insertText("x"); } } // 取消检查某行的任务 void NoteEditorLogic::uncheckTaskInLine(int lineNumber) { QTextDocument *document = m_textEdit->document(); QTextBlock block = document->findBlockByLineNumber(lineNumber); if (block.isValid()) { int indexOfTaskInLine = getTaskDataInLine(block.text())["taskMatchIndex"]; if (indexOfTaskInLine == -1) return; QTextCursor cursor(block); cursor.setPosition(block.position() + indexOfTaskInLine + 3, QTextCursor::MoveAnchor); // Remove the old character and insert the new one cursor.deleteChar(); cursor.insertText(" "); } } // 替换某行之间的文本 void NoteEditorLogic::replaceTextBetweenLines(int startLinePosition, int endLinePosition, QString &newText) { QTextDocument *document = m_textEdit->document(); QTextBlock startBlock = document->findBlockByLineNumber(startLinePosition); QTextBlock endBlock = document->findBlockByLineNumber(endLinePosition); QTextCursor cursor(startBlock); cursor.setPosition(endBlock.position() + endBlock.length() - 1, QTextCursor::KeepAnchor); cursor.removeSelectedText(); cursor.insertText(newText); } // 更新任务文本 void NoteEditorLogic::updateTaskText(int startLinePosition, int endLinePosition, const QString &newText) { QTextDocument *document = m_textEdit->document(); QTextBlock block = document->findBlockByLineNumber(startLinePosition); if (block.isValid()) { QMap taskData = getTaskDataInLine(block.text()); int indexOfTaskInLine = taskData["taskMatchIndex"]; if (indexOfTaskInLine == -1) return; QString taskExpressionText = block.text().mid(0, taskData["taskMatchIndex"] + taskData["taskExpressionSize"]); QString newTextModified = newText; newTextModified.replace("\n\n", "\n"); newTextModified.replace("~~", ""); QStringList taskExpressions = { "- [ ]", "- [x]", "* [ ]", "* [x]", "- [X]", "* [X]" }; for (const auto &taskExpression : taskExpressions) { newTextModified.replace(taskExpression, ""); } // We must allow hashtags solely for the first line, otherwise it will mess up // the parser - interprate the task's description as columns if (newTextModified.count('\n') > 1) { QStringList newTextModifiedSplitted = newTextModified.split('\n'); if (newTextModifiedSplitted.size() > 1) { for (int i = 1; i < newTextModifiedSplitted.size(); i++) { // Skipping the first line newTextModifiedSplitted[i].replace("# ", ""); newTextModifiedSplitted[i].replace("#", ""); } newTextModified = newTextModifiedSplitted.join('\n'); } } QString newTaskText = taskExpressionText + " " + newTextModified; if (newTaskText.size() > 0 && newTaskText[newTaskText.size() - 1] == '\n') { newTaskText.remove(newTaskText.size() - 1, 1); } replaceTextBetweenLines(startLinePosition, endLinePosition, newTaskText); checkForTasksInEditor(); } } // 添加新任务 void NoteEditorLogic::addNewTask(int startLinePosition, const QString newTaskText) { QString newText = "\n- [ ] " + newTaskText; QTextDocument *document = m_textEdit->document(); QTextBlock startBlock = document->findBlockByLineNumber(startLinePosition); if (!startBlock.isValid()) return; QTextCursor cursor(startBlock); cursor.movePosition(QTextCursor::EndOfBlock); cursor.insertText(newText); checkForTasksInEditor(); } // 移除某行之间的文本 void NoteEditorLogic::removeTextBetweenLines(int startLinePosition, int endLinePosition) { if (startLinePosition < 0 || endLinePosition < startLinePosition) { return; } QTextDocument *document = m_textEdit->document(); QTextCursor cursor(document); cursor.setPosition(document->findBlockByNumber(startLinePosition).position()); cursor.setPosition(document->findBlockByNumber(endLinePosition).position(), QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); if (document->findBlockByNumber(endLinePosition + 1).isValid()) { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } cursor.removeSelectedText(); } // 移除任务 void NoteEditorLogic::removeTask(int startLinePosition, int endLinePosition) { removeTextBetweenLines(startLinePosition, endLinePosition); checkForTasksInEditor(); } // 添加新列 void NoteEditorLogic::addNewColumn(int startLinePosition, const QString &columnTitle) { if (startLinePosition < 0) return; QTextDocument *document = m_textEdit->document(); QTextBlock block = document->findBlockByNumber(startLinePosition); if (block.isValid()) { QTextCursor cursor(block); if (startLinePosition == 0) { cursor.movePosition(QTextCursor::StartOfBlock); } else { cursor.movePosition(QTextCursor::EndOfBlock); } cursor.insertText(columnTitle); m_textEdit->setTextCursor(cursor); } else { m_textEdit->append(columnTitle); } checkForTasksInEditor(); } // 移除列 void NoteEditorLogic::removeColumn(int startLinePosition, int endLinePosition) { removeTextBetweenLines(startLinePosition, endLinePosition); if (startLinePosition < 0 || endLinePosition < startLinePosition) return; QTextDocument *document = m_textEdit->document(); QTextCursor cursor(document); cursor.setPosition(document->findBlockByNumber(startLinePosition).position()); if (cursor.block().isValid() && cursor.block().text().isEmpty()) { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); cursor.removeSelectedText(); } checkForTasksInEditor(); } // 更新列标题 void NoteEditorLogic::updateColumnTitle(int lineNumber, const QString &newText) { QTextDocument *document = m_textEdit->document(); QTextBlock block = document->findBlockByLineNumber(lineNumber); if (block.isValid()) { // Header by hashtag int lastIndexOfHashTag = block.text().lastIndexOf("#"); if (lastIndexOfHashTag != -1) { QTextCursor cursor(block); cursor.setPosition(block.position() + lastIndexOfHashTag + 1, QTextCursor::MoveAnchor); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); cursor.removeSelectedText(); cursor.setPosition(block.position() + lastIndexOfHashTag + 1); cursor.insertText(" " + newText); } else { int lastIndexofColon = block.text().lastIndexOf("::"); if (lastIndexofColon != -1) { // Header by double colons QTextCursor cursor(block); cursor.setPosition(block.position(), QTextCursor::MoveAnchor); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); cursor.removeSelectedText(); cursor.setPosition(block.position()); cursor.insertText(newText + "::"); } } } } // 向文本编辑器添加无标题列 void NoteEditorLogic::addUntitledColumnToTextEditor(int startLinePosition) { QString columnTitle = "# Untitled\n\n"; QTextDocument *document = m_textEdit->document(); QTextBlock block = document->findBlockByNumber(startLinePosition); if (block.isValid()) { QTextCursor cursor(block); cursor.movePosition(QTextCursor::StartOfBlock); cursor.insertText(columnTitle); } } // 向 JSON 数据添加新列 void NoteEditorLogic::appendNewColumn(QJsonArray &data, QJsonObject ¤tColumn, QString ¤tTitle, QJsonArray &tasks) { if (!tasks.isEmpty()) { currentColumn["title"] = currentTitle; currentColumn["tasks"] = tasks; currentColumn["columnEndLine"] = tasks.last()["taskEndLine"]; data.append(currentColumn); currentColumn = QJsonObject(); tasks = QJsonArray(); } } // Check if there are any tasks in the current note. // If there are, sends the data to the kanban view. // Structure: // QJsonArray([ // { // "title":"TODO", // "columnStartLine": 1 // "columnEndLine": 4 // "tasks":[ // {"checked":false,"text":"todo 1", "taskStartine": 3, "taskEndLine": 3}, // {"checked":false,"text":"todo 2", "taskStartine": 4, "taskEndLine": 4}}] // }, // ]) bool NoteEditorLogic::checkForTasksInEditor()// 检查编辑器中的任务 { QStringList lines = m_textEdit->toPlainText().split("\n"); QJsonArray data; QJsonObject currentColumn; QJsonArray tasks; QString currentTitle = ""; bool isPreviousLineATask = false; for (int i = 0; i < lines.size(); i++) { QString line = lines[i]; QString lineTrimmed = line.trimmed(); // Header title if (lineTrimmed.startsWith("#")) { if (!tasks.isEmpty() && currentTitle.isEmpty()) { // If we have only tasks without a header we insert one and call this function again addUntitledColumnToTextEditor(tasks.first()["taskStartLine"].toInt()); return true; } appendNewColumn(data, currentColumn, currentTitle, tasks); currentColumn["columnStartLine"] = i; int countOfHashTags = lineTrimmed.count('#'); currentTitle = lineTrimmed.mid(countOfHashTags); isPreviousLineATask = false; } // Non-header text with double colons else if (lineTrimmed.endsWith("::") && getTaskDataInLine(line)["taskMatchIndex"] == -1) { if (!tasks.isEmpty() && currentTitle.isEmpty()) { // If we have only tasks without a header we insert one and call this function again addUntitledColumnToTextEditor(tasks.first()["taskStartLine"].toInt()); return true; } appendNewColumn(data, currentColumn, currentTitle, tasks); currentColumn["columnStartLine"] = i; QStringList parts = line.split("::"); currentTitle = parts[0].trimmed(); isPreviousLineATask = false; } // Todo item else { QMap taskDataInLine = getTaskDataInLine(line); int indexOfTaskInLine = taskDataInLine["taskMatchIndex"]; if (indexOfTaskInLine != -1) { QJsonObject taskObject; QString taskText = line.mid(indexOfTaskInLine + taskDataInLine["taskExpressionSize"]) .trimmed(); taskObject["text"] = taskText; taskObject["checked"] = taskDataInLine["taskChecked"] == 1; taskObject["taskStartLine"] = i; taskObject["taskEndLine"] = i; tasks.append(taskObject); isPreviousLineATask = true; } // If it's a continues description of the task push current line's text to the last task else if (!line.isEmpty() && isPreviousLineATask) { if (tasks.size() > 0) { QJsonObject newTask = tasks[tasks.size() - 1].toObject(); QString newTaskText = newTask["text"].toString() + " \n" + lineTrimmed; // For markdown rendering a line break needs two white // spaces newTask["text"] = newTaskText; newTask["taskEndLine"] = i; tasks[tasks.size() - 1] = newTask; } } else { isPreviousLineATask = false; } } } if (!tasks.isEmpty() && currentTitle.isEmpty()) { // If we have only tasks without a header we insert one and call this function again addUntitledColumnToTextEditor(tasks.first()["taskStartLine"].toInt()); return true; } appendNewColumn(data, currentColumn, currentTitle, tasks); emit tasksFoundInEditor(QVariant(data)); return false; } #endif // 将字符串转换为 QDateTime QDateTime NoteEditorLogic::getQDateTime(const QString &date) { QDateTime dateTime = QDateTime::fromString(date, Qt::ISODate); return dateTime; } // 显示当前笔记的标签列表 void NoteEditorLogic::showTagListForCurrentNote() { if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) { auto tagIds = m_currentNotes[0].tagIds(); if (tagIds.count() > 0) { m_tagListView->setVisible(true); m_tagListModel->setModelData(tagIds); return; } m_tagListModel->setModelData(tagIds); } m_tagListView->setVisible(false); } bool NoteEditorLogic::isInEditMode() const// 获取是否处于编辑模式 { if (m_currentNotes.size() == 1) { return true; } return false; } int NoteEditorLogic::currentMinimumEditorPadding() const// 获取当前最小编辑器填充 { return m_currentMinimumEditorPadding; } // 设置当前最小编辑器填充 void NoteEditorLogic::setCurrentMinimumEditorPadding(int newCurrentMinimumEditorPadding) { m_currentMinimumEditorPadding = newCurrentMinimumEditorPadding; } // 获取当前可适应的编辑器填充 int NoteEditorLogic::currentAdaptableEditorPadding() const { return m_currentAdaptableEditorPadding; } // 设置当前可适应的编辑器填充 void NoteEditorLogic::setCurrentAdaptableEditorPadding(int newCurrentAdaptableEditorPadding) { m_currentAdaptableEditorPadding = newCurrentAdaptableEditorPadding; } // 获取当前编辑的笔记 ID int NoteEditorLogic::currentEditingNoteId() const { if (isInEditMode()) { return m_currentNotes[0].id(); } return SpecialNodeID::InvalidNodeId; } // 将笔记保存到数据库 void NoteEditorLogic::saveNoteToDB() { if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId && m_isContentModified && !m_currentNotes[0].isTempNote()) { emit requestCreateUpdateNote(m_currentNotes[0]); m_isContentModified = false; } } // 关闭编辑器 void NoteEditorLogic::closeEditor() { if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) { saveNoteToDB(); emit noteEditClosed(m_currentNotes[0], false); } m_currentNotes.clear(); m_textEdit->blockSignals(true); m_textEdit->clear(); m_textEdit->clearFocus(); m_textEdit->blockSignals(false); m_tagListModel->setModelData({}); } // 笔记标签列表发生变化时的处理 void NoteEditorLogic::onNoteTagListChanged(int noteId, const QSet &tagIds) { if (currentEditingNoteId() == noteId) { m_currentNotes[0].setTagIds(tagIds); showTagListForCurrentNote(); } } // 删除当前笔记 void NoteEditorLogic::deleteCurrentNote() { if (isTempNote()) { auto noteNeedDeleted = m_currentNotes[0]; m_currentNotes.clear(); m_textEdit->blockSignals(true); m_textEdit->clear(); m_textEdit->clearFocus(); m_textEdit->blockSignals(false); emit noteEditClosed(noteNeedDeleted, true); } else if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) { auto noteNeedDeleted = m_currentNotes[0]; saveNoteToDB(); m_currentNotes.clear(); m_textEdit->blockSignals(true); m_textEdit->clear(); m_textEdit->clearFocus(); m_textEdit->blockSignals(false); emit noteEditClosed(noteNeedDeleted, false); emit deleteNoteRequested(noteNeedDeleted); } } /*! * \brief NoteEditorLogic::getFirstLine * Get a string 'str' and return only the first line of it * If the string contain no text, return "New Note" * TODO: We might make it more efficient by not loading the entire string into the memory * \param str * \return */ QString NoteEditorLogic::getFirstLine(const QString &str)// 获取文本的首行 { QString text = str.trimmed(); if (text.isEmpty()) { return "New Note"; } QTextStream ts(&text); return ts.readLine(FIRST_LINE_MAX); } QString NoteEditorLogic::getSecondLine(const QString &str)// 获取文本的第二行 { #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) auto sl = str.split("\n", QString::SkipEmptyParts); #else auto sl = str.split("\n", Qt::SkipEmptyParts); #endif if (sl.size() < 2) { return getFirstLine(str); } int i = 1; QString text; do { if (i >= sl.size()) { return getFirstLine(str); } text = sl[i].trimmed(); ++i; } while (text.isEmpty()); QTextStream ts(&text); return ts.readLine(FIRST_LINE_MAX); } // 设置主题 void NoteEditorLogic::setTheme(Theme::Value theme, QColor textColor, qreal fontSize) { m_tagListDelegate->setTheme(theme); m_highlighter->setTheme(theme, textColor, fontSize); switch (theme) { case Theme::Light: { m_spacerColor = QColor(191, 191, 191); break; } case Theme::Dark: { m_spacerColor = QColor(212, 212, 212); break; } case Theme::Sepia: { m_spacerColor = QColor(191, 191, 191); break; } } if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) { int verticalScrollBarValueToRestore = m_textEdit->verticalScrollBar()->value(); m_textEdit->setText( m_textEdit->toPlainText()); // TODO: Update the text color without setting the text m_textEdit->verticalScrollBar()->setValue(verticalScrollBarValueToRestore); } else { int verticalScrollBarValueToRestore = m_textEdit->verticalScrollBar()->value(); showNotesInEditor(m_currentNotes); m_textEdit->verticalScrollBar()->setValue(verticalScrollBarValueToRestore); } } // 获取笔记编辑日期 QString NoteEditorLogic::getNoteDateEditor(const QString &dateEdited) { QDateTime dateTimeEdited(getQDateTime(dateEdited)); QLocale usLocale(QLocale(QStringLiteral("en_US"))); return usLocale.toString(dateTimeEdited, QStringLiteral("MMMM d, yyyy, h:mm A")); } // 高亮搜索结果 void NoteEditorLogic::highlightSearch() const { QString searchString = m_searchEdit->text(); if (searchString.isEmpty()) return; m_textEdit->moveCursor(QTextCursor::Start); QList extraSelections; QTextCharFormat highlightFormat; highlightFormat.setBackground(Qt::yellow); while (m_textEdit->find(searchString)) extraSelections.append({ m_textEdit->textCursor(), highlightFormat }); if (!extraSelections.isEmpty()) { m_textEdit->setTextCursor(extraSelections.first().cursor); m_textEdit->setExtraSelections(extraSelections); } } // 获取是否是临时笔记 bool NoteEditorLogic::isTempNote() const { if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId && m_currentNotes[0].isTempNote()) { return true; } return false; }