From 8ed224ef2c6323cf40ac34c3b33c8116e9535f2c Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 1 Sep 2022 21:16:40 +0800 Subject: [PATCH 01/61] Change the url prefix in NWebView::downloadRequested() from file://// to file:///, to make the file downloading work under Windows. --- src/gui/nwebview.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/nwebview.cpp b/src/gui/nwebview.cpp index 73422e62..eab60100 100644 --- a/src/gui/nwebview.cpp +++ b/src/gui/nwebview.cpp @@ -499,10 +499,11 @@ void NWebView::setDefaultTitle() { void NWebView::downloadRequested(QNetworkRequest req) { QString urlString = req.url().toString(); + if (urlString == "") { - downloadImageAction()->trigger(); return; } + if (urlString.startsWith("nnres:")) { int pos = urlString.indexOf(global.attachmentNameDelimeter); QString extension = ""; @@ -555,7 +556,7 @@ void NWebView::downloadRequested(QNetworkRequest req) { return; } } - if (urlString.startsWith("file:////")) { + if (urlString.startsWith("file:///")) { if (!req.url().isValid()) return; urlString = urlString.mid(8); From fe7d7c2263b70b12a47d304ceb378013355e7d6d Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 21 Sep 2022 12:59:22 +0800 Subject: [PATCH 02/61] Release the splash screen memory when it is closed. --- src/nixnote.cpp | 11 ++++++++--- src/nixnote.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/nixnote.cpp b/src/nixnote.cpp index 92ff867e..92dfe948 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -113,12 +113,12 @@ NixNote *NixNote::singleton; //* everything else. //************************************************* NixNote::NixNote(QWidget *parent) : QMainWindow(parent) { - splashScreen = new QSplashScreen(this, global.getPixmapResource(":splashLogoImage")); global.settings->beginGroup(INI_GROUP_APPEARANCE); if (global.settings->value("showSplashScreen", false).toBool()) { + splashScreen = new QSplashScreen(this, global.getPixmapResource(":splashLogoImage")); splashScreen->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::SplashScreen | Qt::FramelessWindowHint); splashScreen->show(); - QTimer::singleShot(2500, splashScreen, SLOT(close())); + QTimer::singleShot(2500, this, SLOT(clearSplashScreen())); } global.settings->endGroup(); QString css = global.getThemeCss("mainWindowCss"); @@ -250,7 +250,6 @@ NixNote::NixNote(QWidget *parent) : QMainWindow(parent) { // Destructor to call when all done NixNote::~NixNote() { - delete splashScreen; syncThread.quit(); indexThread.quit(); counterThread.quit(); @@ -278,6 +277,12 @@ NixNote::~NixNote() { } +void NixNote::clearSplashScreen() { + splashScreen->close(); + delete splashScreen; +} + + //**************************************************************** //* Public static method to get the singleton instance of NixNote //**************************************************************** diff --git a/src/nixnote.h b/src/nixnote.h index 517440cb..0f7b2ef9 100644 --- a/src/nixnote.h +++ b/src/nixnote.h @@ -208,6 +208,7 @@ void showAnnouncementMessage(); private slots: void onNetworkManagerFinished(QNetworkReply *reply); void onExportAsPdfReady(bool); + void clearSplashScreen(); public slots: void quitNixNote(); From aac27ef70fb810b10fd0102df8ba9fd828c64f40 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 22 Sep 2022 15:34:28 +0800 Subject: [PATCH 03/61] Low down the ram usage of the sync button by reducing the updating frequency of the sync icon. --- src/nixnote.cpp | 30 ++++++++++++++++++------------ src/nixnote.h | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/nixnote.cpp b/src/nixnote.cpp index 92dfe948..95b187f0 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -1529,7 +1529,7 @@ void NixNote::synchronize() { this->saveContents(); tabWindow->saveAllNotes(); - syncButtonTimer.start(3); + syncButtonTimer.start(36); emit syncRequested(); } @@ -1606,31 +1606,37 @@ void NixNote::syncButtonReset() { //* Evernote and are transmitting & receiving info //***************************************************** void NixNote::updateSyncButton() { - + const int N_ICONS = 30; if (syncIcons.size() == 0) { double angle = 0.0; - synchronizeIconAngle = 0; + synchronizeIconIndex = 0; QPixmap pix(":synchronizeIcon"); syncIcons.push_back(pix); - for (qint32 i = 0; i <= 360; i++) { + + QPainter p; + p.setBackgroundMode(Qt::OpaqueMode); + QSize size = pix.size(); + + for (qint32 i = 1; i < N_ICONS; ++i) { QPixmap rotatedPix(pix.size()); - QPainter p(&rotatedPix); rotatedPix.fill(toolBar->palette().color(QPalette::Background)); - QSize size = pix.size(); + + p.begin(&rotatedPix); p.translate(size.width() / 2, size.height() / 2); - angle = angle + 1.0; + angle = angle + 360.0f/N_ICONS; p.rotate(angle); - p.setBackgroundMode(Qt::OpaqueMode); p.translate(-size.width() / 2, -size.height() / 2); + p.drawPixmap(0, 0, pix); p.end(); + syncIcons.push_back(rotatedPix); } } - synchronizeIconAngle++; - if (synchronizeIconAngle > 359) - synchronizeIconAngle = 0; - syncButton->setIcon(syncIcons[synchronizeIconAngle]); + synchronizeIconIndex++; + if (synchronizeIconIndex == N_ICONS) + synchronizeIconIndex = 0; + syncButton->setIcon(syncIcons[synchronizeIconIndex]); } diff --git a/src/nixnote.h b/src/nixnote.h index 0f7b2ef9..7c1e1298 100644 --- a/src/nixnote.h +++ b/src/nixnote.h @@ -137,7 +137,7 @@ class NixNote : public QMainWindow QTimer syncButtonTimer; QTimer syncTimer; QList syncIcons; - unsigned int synchronizeIconAngle; + unsigned int synchronizeIconIndex; // Timer to check shared memory for other instance commands QTimer heartbeatTimer; From 6767e32958143e1d69dccd07d21f9bc19468ebb2 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 22 Sep 2022 15:34:28 +0800 Subject: [PATCH 04/61] Reduce the updating frequency of the sync icon. --- src/nixnote.cpp | 30 ++++++++++++++++++------------ src/nixnote.h | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/nixnote.cpp b/src/nixnote.cpp index 92dfe948..95b187f0 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -1529,7 +1529,7 @@ void NixNote::synchronize() { this->saveContents(); tabWindow->saveAllNotes(); - syncButtonTimer.start(3); + syncButtonTimer.start(36); emit syncRequested(); } @@ -1606,31 +1606,37 @@ void NixNote::syncButtonReset() { //* Evernote and are transmitting & receiving info //***************************************************** void NixNote::updateSyncButton() { - + const int N_ICONS = 30; if (syncIcons.size() == 0) { double angle = 0.0; - synchronizeIconAngle = 0; + synchronizeIconIndex = 0; QPixmap pix(":synchronizeIcon"); syncIcons.push_back(pix); - for (qint32 i = 0; i <= 360; i++) { + + QPainter p; + p.setBackgroundMode(Qt::OpaqueMode); + QSize size = pix.size(); + + for (qint32 i = 1; i < N_ICONS; ++i) { QPixmap rotatedPix(pix.size()); - QPainter p(&rotatedPix); rotatedPix.fill(toolBar->palette().color(QPalette::Background)); - QSize size = pix.size(); + + p.begin(&rotatedPix); p.translate(size.width() / 2, size.height() / 2); - angle = angle + 1.0; + angle = angle + 360.0f/N_ICONS; p.rotate(angle); - p.setBackgroundMode(Qt::OpaqueMode); p.translate(-size.width() / 2, -size.height() / 2); + p.drawPixmap(0, 0, pix); p.end(); + syncIcons.push_back(rotatedPix); } } - synchronizeIconAngle++; - if (synchronizeIconAngle > 359) - synchronizeIconAngle = 0; - syncButton->setIcon(syncIcons[synchronizeIconAngle]); + synchronizeIconIndex++; + if (synchronizeIconIndex == N_ICONS) + synchronizeIconIndex = 0; + syncButton->setIcon(syncIcons[synchronizeIconIndex]); } diff --git a/src/nixnote.h b/src/nixnote.h index 0f7b2ef9..7c1e1298 100644 --- a/src/nixnote.h +++ b/src/nixnote.h @@ -137,7 +137,7 @@ class NixNote : public QMainWindow QTimer syncButtonTimer; QTimer syncTimer; QList syncIcons; - unsigned int synchronizeIconAngle; + unsigned int synchronizeIconIndex; // Timer to check shared memory for other instance commands QTimer heartbeatTimer; From 1bb2d96693e985167c07aff709496eb4f018c8d8 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 1 Oct 2022 15:22:13 +0800 Subject: [PATCH 05/61] Add a default theme named with 'Default' when no theme is found in the THEME_FILE. --- src/global.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/global.cpp b/src/global.cpp index 3a94b12b..6aa02834 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1026,8 +1026,7 @@ QStringList Global::getThemeNames() { //if (!nonAsciiSortBug) // qSort(values.begin(), values.end(), caseInsensitiveLessThan); if (values.size() == 0) { - QLOG_FATAL() << "No themes found"; - exit(16); + values.append("Default"); } return values; From a95688e990f489514e0a80158e25a05fdad32531 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sun, 2 Oct 2022 19:14:12 +0800 Subject: [PATCH 06/61] Delete the clearButton when TagViewer is desctructed; Make every QIcon object unique by storing them into a hash table. --- src/global.cpp | 62 +++++++++++++++++++++------- src/global.h | 13 ++++-- src/gui/browserWidgets/tagviewer.cpp | 12 +++--- src/gui/browserWidgets/tagviewer.h | 1 + src/gui/reminderorderdelegate.cpp | 4 +- src/gui/truefalsedelegate.cpp | 4 +- src/nixnote.cpp | 2 + 7 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/global.cpp b/src/global.cpp index 6aa02834..128b94e8 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -739,10 +739,19 @@ QFont Global::getGuiFont(QFont f) { // Get a QIcon of in an icon theme -QIcon Global::getIconResource(QHash &resourceList, QString key) { - if (resourceList.contains(key) && resourceList[key].trimmed() != "") - return QIcon(resourceList[key]); - return QIcon(key); +const QIcon* Global::getIconResource(const QHash &resourceList, + const QString &key) { + if (qIconList.contains(key)) { + return qIconList[key]; + } + + if (resourceList.contains(key) && resourceList[key].trimmed() != "") { + qIconList[key] = new QIcon(resourceList[key]); + return qIconList[key]; + } + + qIconList[key] = new QIcon(key); + return qIconList[key]; } @@ -897,22 +906,31 @@ QString Global::getEditorCss() { } // Get a QIcon in an icon theme -QIcon Global::getIconResource(QString key) { - return this->getIconResource(resourceList, key); +const QIcon& Global::getIconResource(const QString &key) { + return *(this->getIconResource(resourceList, key)); } // Get a QPixmap from an icon theme -QPixmap Global::getPixmapResource(QString key) { - return this->getPixmapResource(resourceList, key); +const QPixmap& Global::getPixmapResource(const QString &key) { + return *(this->getPixmapResource(resourceList, key)); } // Get a QPixmap from an icon theme -QPixmap Global::getPixmapResource(QHash &resourceList, QString key) { - if (resourceList.contains(key) && resourceList[key] != "") - return QPixmap(resourceList[key]); - return QPixmap(key); +const QPixmap* Global::getPixmapResource(const QHash &resourceList, + const QString &key) { + if (qPixmapList.contains(key)) { + return qPixmapList[key]; + } + + if (resourceList.contains(key) && resourceList[key] != "") { + qPixmapList[key] = new QPixmap(resourceList[key]); + return qPixmapList[key]; + } + + qPixmapList[key] = new QPixmap(key); + return qPixmapList[key]; } // renamed on 20.6.2018 because of structure changes prevent loading of legacy user themes (they need minor fixes) @@ -923,8 +941,7 @@ QPixmap Global::getPixmapResource(QHash &resourceList, QString void Global::loadTheme(QHash &resourceList, QHash &colorList, QString theme) { QLOG_DEBUG() << "Loading theme " << theme; - resourceList.clear(); - colorList.clear(); + clearResourceList(); if (theme.trimmed() == "") { return; } @@ -1561,3 +1578,20 @@ QString Global::getCheckboxElement(bool checked, bool escapeTwice) const { (escapeTwice ? QString("\\\':\\\'") : QString("\':\'")) + checkedUrl + (escapeTwice ? QString("\\\';\">") : QString("\';\">")); } + + +void Global::clearResourceList() { + resourceList.clear(); + colorList.clear(); + + for(QHash::iterator iter = qIconList.begin(); + iter != qIconList.end(); ++iter) { + delete iter.value(); + } + for(QHash::iterator iter = qPixmapList.begin(); + iter != qPixmapList.end(); ++iter) { + delete iter.value(); + } + qIconList.clear(); + qPixmapList.clear(); +} diff --git a/src/global.h b/src/global.h index 03747464..9b732746 100644 --- a/src/global.h +++ b/src/global.h @@ -195,6 +195,9 @@ class Global : public QObject { QString getCheckboxImageUrl(bool checked) const; + QHash qIconList; + QHash qPixmapList; + public: const QString &getDateFormat() const; const QString &getTimeFormat() const; @@ -399,10 +402,10 @@ class Global : public QObject { QString getDateTimeEditorInactiveStyle(); QString getEditorCss(); - QPixmap getPixmapResource(QHash &resourceList, QString key); // Get a pixmap from the user's (or default) theme - QPixmap getPixmapResource(QString key); // Get a pixmap from the user's (or default) theme - QIcon getIconResource(QHash &resourceList, QString key); // Get an icon from the user's (or default) theme - QIcon getIconResource(QString key); // Get an icon from the user's (or default) theme + const QPixmap* getPixmapResource(const QHash &resourceList, const QString &key); // Get a pixmap from the user's (or default) theme + const QPixmap& getPixmapResource(const QString &key); // Get a pixmap from the user's (or default) theme + const QIcon* getIconResource(const QHash &resourceList, const QString &key); // Get an icon from the user's (or default) theme + const QIcon& getIconResource(const QString &key); // Get an icon from the user's (or default) theme void loadTheme(QHash &resourceList, QHash &colorList, QString themeName); // Load an icon theme into the resourceList void loadThemeFile(QFile &file, QString themeName); // Load a given theme's values from a a file. void loadThemeFile(QHash &resourceList, QHash &colorList, QFile &file, QString themeName); // Load a given theme's values from a file @@ -480,6 +483,8 @@ class Global : public QObject { QString getCheckboxElement(bool checked, bool escapeTwice) const; + void clearResourceList(); + signals: // global can send signal about updating status bar void setMessageSignal(QString msg, int timeout); diff --git a/src/gui/browserWidgets/tagviewer.cpp b/src/gui/browserWidgets/tagviewer.cpp index b763a2f6..be02c45c 100644 --- a/src/gui/browserWidgets/tagviewer.cpp +++ b/src/gui/browserWidgets/tagviewer.cpp @@ -27,9 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. extern Global global; TagViewer::TagViewer(QWidget *parent) : - QLabel(parent) -{ - + QLabel(parent) { clearButton = new QToolButton(this); clearButton->setIcon(global.getIconResource(":filecloseIcon")); clearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); @@ -46,8 +44,12 @@ TagViewer::TagViewer(QWidget *parent) : } -void TagViewer::resizeEvent(QResizeEvent *) -{ +TagViewer::~TagViewer() { + delete clearButton; +} + + +void TagViewer::resizeEvent(QResizeEvent *) { resize(); } diff --git a/src/gui/browserWidgets/tagviewer.h b/src/gui/browserWidgets/tagviewer.h index 73f2a644..1adc5b0e 100644 --- a/src/gui/browserWidgets/tagviewer.h +++ b/src/gui/browserWidgets/tagviewer.h @@ -36,6 +36,7 @@ class TagViewer : public QLabel public: explicit TagViewer(QWidget *parent = 0); + ~TagViewer(); void resize(); signals: diff --git a/src/gui/reminderorderdelegate.cpp b/src/gui/reminderorderdelegate.cpp index 6c71acd5..fc63fcd0 100644 --- a/src/gui/reminderorderdelegate.cpp +++ b/src/gui/reminderorderdelegate.cpp @@ -34,11 +34,11 @@ void ReminderOrderDelegate::paint(QPainter *painter, const QStyleOptionViewItem if(value != "0") { painter->save(); - QPixmap dot = global.getPixmapResource(":blackDotIcon"); + const QPixmap &dot = global.getPixmapResource(":blackDotIcon"); int centerDot = dot.width()/2; int len = (option.rect.right() - option.rect.left())/2; len = len-centerDot; - painter->drawPixmap(option.rect.x()+len,option.rect.y()+2,dot); + painter->drawPixmap(option.rect.x()+len,option.rect.y()+2, dot); painter->restore(); } diff --git a/src/gui/truefalsedelegate.cpp b/src/gui/truefalsedelegate.cpp index 62949a79..5fe091b5 100644 --- a/src/gui/truefalsedelegate.cpp +++ b/src/gui/truefalsedelegate.cpp @@ -45,11 +45,11 @@ void TrueFalseDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt if(value) { painter->save(); - QPixmap dot = global.getPixmapResource(":blackDotIcon"); + const QPixmap &dot = global.getPixmapResource(":blackDotIcon"); int centerDot = dot.width()/2; int len = (option.rect.right() - option.rect.left())/2; len = len-centerDot; - painter->drawPixmap(option.rect.x()+len,option.rect.y()+2,dot); + painter->drawPixmap(option.rect.x()+len,option.rect.y()+2, dot); painter->restore(); } diff --git a/src/nixnote.cpp b/src/nixnote.cpp index 95b187f0..6c6dee94 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -3442,6 +3442,8 @@ void NixNote::reloadIcons() { favoritesTreeView->reloadIcons(); tabWindow->reloadIcons(); + global.clearResourceList(); + tabWindow->changeEditorStyle(); QString themeInformation = global.getResourceFileName(global.resourceList, ":themeInformation"); From 103efc484ee8bedb8dcf65ef4123b7971c0ad92b Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Tue, 4 Oct 2022 19:48:16 +0800 Subject: [PATCH 07/61] In tageditor.cpp, allocate the tags dynamically. --- src/gui/browserWidgets/tageditor.cpp | 78 ++++++++++++++-------------- src/gui/browserWidgets/tageditor.h | 5 +- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/gui/browserWidgets/tageditor.cpp b/src/gui/browserWidgets/tageditor.cpp index b9255304..17a174c7 100644 --- a/src/gui/browserWidgets/tageditor.cpp +++ b/src/gui/browserWidgets/tageditor.cpp @@ -42,20 +42,12 @@ TagEditor::TagEditor(QWidget *parent) : currentLid = 0; newEditorHasFocus = false; - QPixmap pix = global.getPixmapResource(":tagIcon"); - tagIcon.setPixmap(pix); - - for (int i=0; iaddWidget(&tags[i]); - connect(&tags[i],SIGNAL(closeClicked(QString)), this, SLOT(removeTag(QString))); - } + tagIcon.setPixmap(global.getPixmapResource(":tagIcon")); connect(&newTag, SIGNAL(focussed(bool)), this, SLOT(newTagFocusLost(bool))); connect(&newTag, SIGNAL(tabPressed()), this, SLOT(newTagTabPressed())); tagNames.clear(); layout->addWidget(&newTag); - //delete pix; account = 0; hide(); @@ -63,6 +55,12 @@ TagEditor::TagEditor(QWidget *parent) : } +TagEditor::~TagEditor() { + delete layout; + emptyTags(); +} + + //******************************************************** //* Set the current note lid //******************************************************** @@ -160,9 +158,17 @@ void TagEditor::loadTags() { qSort(tagNames.begin(), tagNames.end(), caseInsensitiveLessThan); for (qint32 i=0; isetText(tagNames[i]); + tag->resize(); + tag->setVisible(false); + + layout->addWidget(tag); + + tag->setVisible(true); + connect(tag, SIGNAL(closeClicked(QString)), this, SLOT(removeTag(QString))); + + tags.append(tag); } layout->invalidate(); QLOG_TRACE_OUT() << typeid(*this).name(); @@ -239,32 +245,25 @@ void TagEditor::getTags(QStringList &names) { //******************************************************* void TagEditor::removeTag(QString text) { QLOG_TRACE_IN() << typeid(*this).name(); - bool found = false; - qint32 j=-1; + qint32 j = -1; + for (qint32 i=0; i0) { - NoteTable noteTable(global.db); - noteTable.removeTag(currentLid, lid, true); - } + if (tags[i]->text().toLower() == text.toLower()) { j = i; + break; } - if (found && i=0) { - tags[tagNames.size()-1].clear(); - tags[tagNames.size()-1].setVisible(false); - tags[tagNames.size()-1].setMinimumWidth(0); + if (j != -1) { + TagTable tagTable(global.db); + QString name = tags[j]->text(); + qint32 lid = tagTable.findByName(name, account); + if (lid > 0) { + NoteTable noteTable(global.db); + noteTable.removeTag(currentLid, lid, true); + } + delete tags[j]; + tags.removeAt(j); tagNames.removeAt(j); } @@ -296,8 +295,8 @@ void TagEditor::hideEvent(QHideEvent* event) { Q_UNUSED(event); // suppress unused tagIcon.hide(); newTag.hide(); - for (qint32 i=0; ihide(); } QLOG_TRACE_OUT() << typeid(*this).name(); } @@ -313,7 +312,7 @@ void TagEditor::showEvent(QShowEvent* event) { tagIcon.show(); newTag.show(); for (qint32 i=0; ishow(); QLOG_TRACE_OUT() << typeid(*this).name(); } @@ -324,11 +323,10 @@ void TagEditor::showEvent(QShowEvent* event) { //******************************************************* void TagEditor::emptyTags() { QLOG_TRACE_IN() << typeid(*this).name(); - for (qint32 i=MAX_TAGS-1; i>=0; i--) { - tags[i].clear(); - tags[i].setMinimumWidth(0); - tags[i].setVisible(false); + for (qint32 i=tags.size()-1; i>=0; i--) { + delete tags[i]; } + tags.clear(); QLOG_TRACE_OUT() << typeid(*this).name(); } diff --git a/src/gui/browserWidgets/tageditor.h b/src/gui/browserWidgets/tageditor.h index b59c4a51..8e640892 100644 --- a/src/gui/browserWidgets/tageditor.h +++ b/src/gui/browserWidgets/tageditor.h @@ -26,14 +26,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "tagviewer.h" #include #include "src/gui/flowlayout.h" +#include -#define MAX_TAGS 100 class TagEditor : public QWidget { Q_OBJECT public: explicit TagEditor(QWidget *parent = 0); + ~TagEditor(); void addTag(QString text); void setTags(QStringList names); void getTags(QStringList &names); @@ -49,7 +50,7 @@ class TagEditor : public QWidget qint32 currentLid; qint32 account; QLabel tagIcon; - TagViewer tags[MAX_TAGS]; + QVector tags; FlowLayout *layout; QStringList tagNames; void emptyTags(); From 833b6847344d2e69b01d3f72b812a8211fab7e7b Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 6 Oct 2022 20:16:24 +0800 Subject: [PATCH 08/61] Format the code of getCursorPos() in NBrowserWindow::noteContentUpdated(); Save the note content when Nixnote gets a SIGINT, which is sent by the OS when it is shutdown. --- src/gui/nbrowserwindow.cpp | 75 ++++++++++++++++++-------------------- src/main.cpp | 21 +++++++---- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index 2539d64e..d780711e 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -822,7 +822,7 @@ void NBrowserWindow::noteContentUpdated() { // Save the note's content void NBrowserWindow::saveNoteContent() { - microFocusChanged(); + //microFocusChanged(); if (this->editor->isDirty) { QLOG_DEBUG() << "saveNoteContent() dirty=true"; @@ -2080,47 +2080,44 @@ void NBrowserWindow::microFocusChanged() { else editor->encryptAction->setEnabled(false); - // +QString(" window.browserWindow.printNodeName(workingNode.firstChild.nodeValue);") QString js = QString("function getCursorPos() {") - + QString("var cursorPos;") - + QString("var insideUrl=false;") - + QString("if (window.getSelection) {") - + QString(" var selObj = window.getSelection();") - + QString(" var selRange = selObj.getRangeAt(0);") - + QString(" var workingNode = window.getSelection().anchorNode.parentNode;") - //+QString(" window.browserWindow.printNodeName(workingNode.nodeName);") - + QString(" while(workingNode != null) { ") - //+QString(" window.browserWindow.printNodeName(workingNode.nodeName);") - + QString(" if (workingNode.nodeName=='TABLE') {") - + QString( - " if (workingNode.getAttribute('class').toLowerCase() == 'en-crypt-temp') window.browserWindow.insideEncryptionArea();") - + QString(" }") - + QString(" if (workingNode.nodeName=='PRE') window.browserWindow.setInsidePre();") - + QString(" if (workingNode.nodeName=='B') window.browserWindow.boldActive();") - + QString(" if (workingNode.nodeName=='I') window.browserWindow.italicsActive();") - + QString(" if (workingNode.nodeName=='U') window.browserWindow.underlineActive();") - + QString(" if (workingNode.nodeName=='UL') window.browserWindow.setInsideList();") - + QString(" if (workingNode.nodeName=='OL') window.browserWindow.setInsideList();") - + QString(" if (workingNode.nodeName=='LI') window.browserWindow.setInsideList();") - + QString(" if (workingNode.nodeName=='TBODY') window.browserWindow.setInsideTable();") - + QString(" if (workingNode.nodeName=='A') {") - + QString(" insideUrl = true;") - + QString(" for(var x = 0; x < workingNode.attributes.length; x++ ) {") - + QString(" if (workingNode.attributes[x].nodeName.toLowerCase() == 'href')") - + QString(" window.browserWindow.setInsideLink(workingNode.attributes[x].nodeValue);") - + QString(" }") - + QString(" }") - + QString(" if (workingNode.nodeName=='SPAN') {") - + QString( - " if (workingNode.getAttribute('style') == 'text-decoration: underline;') window.browserWindow.underlineActive();") - + QString(" }") - + QString(" workingNode = workingNode.parentNode;") - + QString(" }") - + QString("}") - + QString("} getCursorPos();"); - editor->page()->mainFrame()->evaluateJavaScript(js); + + QString(" var cursorPos;") + + QString(" var insideUrl=false;") + + QString(" if (window.getSelection) {") + + QString(" var selObj = window.getSelection();") + + QString(" var selRange = selObj.getRangeAt(0);") + + QString(" var workingNode = window.getSelection().anchorNode.parentNode;") + //+QString(" window.browserWindow.printNodeName(workingNode.nodeName);") + + QString(" while(workingNode != null) { ") + //+QString(" window.browserWindow.printNodeName(workingNode.nodeName);") + + QString(" if (workingNode.nodeName=='TABLE') {") + + QString(" if (workingNode.getAttribute('class').toLowerCase() == 'en-crypt-temp') window.browserWindow.insideEncryptionArea();") + + QString(" }") + + QString(" if (workingNode.nodeName=='PRE') window.browserWindow.setInsidePre();") + + QString(" if (workingNode.nodeName=='B') window.browserWindow.boldActive();") + + QString(" if (workingNode.nodeName=='I') window.browserWindow.italicsActive();") + + QString(" if (workingNode.nodeName=='U') window.browserWindow.underlineActive();") + + QString(" if (workingNode.nodeName=='UL') window.browserWindow.setInsideList();") + + QString(" if (workingNode.nodeName=='OL') window.browserWindow.setInsideList();") + + QString(" if (workingNode.nodeName=='LI') window.browserWindow.setInsideList();") + + QString(" if (workingNode.nodeName=='TBODY') window.browserWindow.setInsideTable();") + + QString(" if (workingNode.nodeName=='A') {") + + QString(" insideUrl = true;") + + QString(" for(var x = 0; x < workingNode.attributes.length; x++ ) {") + + QString(" if (workingNode.attributes[x].nodeName.toLowerCase() == 'href')") + + QString(" window.browserWindow.setInsideLink(workingNode.attributes[x].nodeValue);") + + QString(" }") + + QString(" }") + + QString(" if (workingNode.nodeName=='SPAN') {") + + QString(" if (workingNode.getAttribute('style') == 'text-decoration: underline;') window.browserWindow.underlineActive();") + + QString(" }") + + QString(" workingNode = workingNode.parentNode;") + + QString(" }") + + QString(" }") + + QString("} getCursorPos();"); + editor->page()->mainFrame()->evaluateJavaScript(js); QString js2 = QString("function getFontSize() {") + QString(" var node = document.getSelection().anchorNode;") + diff --git a/src/main.cpp b/src/main.cpp index c3d5035e..a5950208 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,22 +67,26 @@ extern Global global; //********************************************************************* //* Segmentation fault. This is triggered to print a stack trace. //********************************************************************* +void fault_handler(int sig) { // Windows Check #ifndef _WIN32 - -void fault_handler(int sig) { void *array[30]; size_t size; // get void*'s for all entries on the stack size = backtrace(array, 30); +#endif // End Windows check // print out all the frames to stderr fprintf(stderr, "Error: signal %d:\n", sig); +// Windows Check +#ifndef _WIN32 backtrace_symbols_fd(array, size, 2); +#endif // End Windows check if (w != NULL) { fprintf(stderr, "Forcing save\n"); w->saveState(); + w->saveOnExit(); } exit(1); } @@ -94,11 +98,15 @@ void sighup_handler(int sig) { if (w != NULL) { fprintf(stderr, "Forcing save\n"); w->saveState(); + w->saveOnExit(); } } -#endif // End Windows check +void sigint_handler(int sig) { + sighup_handler(sig); + exit(1); +} //using namespace cv; @@ -121,10 +129,7 @@ int main(int argc, char *argv[]) { QsLogging::DestinationFactory::MakeDebugOutputDestination()); logger.addDestination(debugDestination.get()); -// Windows Check -#ifndef _WIN32 signal(SIGSEGV, fault_handler); // install our handler -#endif // Begin setting up the environment StartupConfig startupConfig; @@ -309,9 +314,11 @@ int main(int argc, char *argv[]) { global.fileManager.setupFileAttachmentLogging(); #ifndef _WIN32 - if (global.getInterceptSigHup()) + if (global.getInterceptSigHup()) { signal(SIGHUP, sighup_handler); // install our handler + } #endif + signal(SIGINT, sigint_handler); QLOG_DEBUG() << "Setting up, guiAvailable=" << guiAvailable; w = new NixNote(); From b88592365d2c4add53c63630d8d7739607479b99 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 14 Oct 2022 21:18:00 +0800 Subject: [PATCH 09/61] In NixNote::setupGui(), use the QIcon object got from getIconResource() directly, without making a copy of it. --- src/nixnote.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nixnote.cpp b/src/nixnote.cpp index 6c6dee94..9d5126f0 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -298,7 +298,7 @@ void NixNote::setupGui() { //this->setStyleSheet("background-color: white;"); //statusBar(); setWindowTitle(tr(NN_APP_DISPLAY_NAME_GUI)); QLOG_DEBUG() << "setupGui: Setting up window icon"; - const auto wIcon = QIcon(global.getIconResource(":windowIcon")); + const auto wIcon = global.getIconResource(":windowIcon"); if (!wIcon.isNull()) { setWindowIcon(wIcon); } From 6556bc9a876fc9f86e99aa316b3d38da0115bfdb Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 15 Oct 2022 14:31:34 +0800 Subject: [PATCH 10/61] Delay the memory allication of hammer(for thumbnailing), printPage and printPagePreview in nbrowserwindow.cpp. --- src/gui/nbrowserwindow.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index d780711e..354080af 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -242,14 +242,19 @@ NBrowserWindow::NBrowserWindow(QWidget *parent) : buttonBar->getButtonbarState(); - printPage = new QTextEdit(); - printPage->setVisible(false); + //printPage = new QTextEdit(); + //printPage->setVisible(false); //connect(printPage, SIGNAL(loadFinished(bool)), this, SLOT(printReady(bool))); - printPreviewPage = new QTextEdit(); - printPreviewPage->setVisible(false); + //printPreviewPage = new QTextEdit(); + //printPreviewPage->setVisible(false); - hammer = new Thumbnailer(global.db); + printPreviewPage = nullptr; + printPage = nullptr; + + if (!global.disableThumbnails) { + hammer = new Thumbnailer(global.db); + } lid = -1; //Setup shortcuts for context menu @@ -3078,6 +3083,10 @@ QString NBrowserWindow::stripContentsForPrint() { // in much the same way as printNote(). It removes all the // tags & replaces them with . void NBrowserWindow::printPreviewNote() { + if (printPreviewPage == nullptr) { + printPreviewPage = new QTextEdit(); + printPreviewPage->setVisible(false); + } QString contents = stripContentsForPrint(); // Load the print page. When it is ready the printReady() slot will @@ -3101,6 +3110,10 @@ void NBrowserWindow::printPreviewReady(QPrinter *printer) { // note and replaces the tags with tags. The plugin // object should be creating temporary images for the print. void NBrowserWindow::printNote() { + if (printPage == nullptr) { + printPage = new QTextEdit(); + printPage->setVisible(false); + } QString contents = stripContentsForPrint(); // Load the print page. When it is ready the printReady() slot will From 12a2de8d027f729b816d1f0ff423b1d0cbce2a1d Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 15 Oct 2022 14:34:48 +0800 Subject: [PATCH 11/61] Compress the icon files. --- resources/images/alarmclock.png | Bin 35137 -> 524 bytes resources/images/copy.png | Bin 16098 -> 3819 bytes resources/images/delete.png | Bin 18148 -> 2189 bytes resources/images/emailIcon.png | Bin 18322 -> 796 bytes resources/images/fontColor.png | Bin 11021 -> 2134 bytes resources/images/formatCode.png | Bin 2394 -> 914 bytes resources/images/password.png | Bin 10084 -> 1325 bytes resources/images/paste.png | Bin 9764 -> 1641 bytes resources/images/printer.png | Bin 21048 -> 994 bytes resources/images/spellCheck.png | Bin 12254 -> 3108 bytes resources/images/splash_logo.png | Bin 7618 -> 1453 bytes resources/images/trayicon.png | Bin 7618 -> 1453 bytes resources/images/windowIcon.ico | Bin 67646 -> 7566 bytes resources/images/windowIcon.png | Bin 7618 -> 1453 bytes 14 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/alarmclock.png b/resources/images/alarmclock.png index 044921ac28781581b2dec7d0c3431ce208b05ec4..f74d9772ab4cf86f16f183ba8ed6926947983b52 100644 GIT binary patch literal 524 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 diff --git a/resources/images/copy.png b/resources/images/copy.png index da5ba9e999dffc9b253d342a02d00777596cce6b..db034788fd1ecffb69564a617b561fa5717310b5 100644 GIT binary patch literal 3819 zcmVjB<>=qAt^4yZDz=sdGFr-@rH{P zi8c}$?F9z&hBJKcd*^)T`_8#fa2eLt$4_$bg|)MvcR$e-_8>wQHh;TtUhc^iROqBy=WVbDcv$ zz9KaNVC5$U+Bc?l-JVl*LWmoz<<2x>rz@bW#HFTEvwC@$9UJmlcVz?=j3l6A3`SDm z8*mi35*!Iqfa3_H6d(kb74(|BKL}&_id5oG3rnUlRDg9>FAsCuwnA=Lmq&Gl#tAD% zrUZkNaOfV%Tt(n_#?Rg^n+6tMfc0!j)M zlNsoa!@C18l?7J^0F?wi51`CNR?WQU0vnAewBu?wRG_Dm!ygOKkCT>%^A^sGR$8h| z*7Er~OZn5sDrq?nCx>A&1{p5^;%X=e1;=V0xS7b#zKc2VzJ-!Pg;YvO?S=sX%tn0I z-VWY=Zuu;b?T;N(3NP|-p7=;<)LIFfn6liyy^s?z=otl_yKuzKMODm!odGW9gTLTd z3NRMRixk>%9Ve6*1|;AE?0l>ZYpvL|w=FNH>yJq(cS|YUE%*J~o+B?m@XE|@FCJ+n z(aK_hu!fKVrE(xI06RaTGw)h~>wq;dHfeDb6y*!7h0-F0c0#TcnGnGCM>^_-Fl&X7 zTa2|gdRcKzI4meB&`eGllmhLzPXb4Qz6Iz3p^#u~+@q!pLi*A)Q1iI!>VVgMLxvC9 z4V~u<;|bWdUa|Wcg`?n8iybna>(pKIw|g6QFaH{XimWf9*0*Z)vM{%AFJSBXD1}kU zTa8Jc|M?hFni8OVVbD?_bCsmUT%pZ!RR&TffK1jhm4>0XrM=hCJ77>!u&7LM?Nyp3 zl?F%n9C|Ow!Cxmid@O@jjvKN*)Kp5=H7M@dt{I=m($qZ3 zuij45JCJ2y*kg3uV`;6+y*CxHZG9fgsvYtRHPc2iI0?-|koCY21jkiD4M-)B>SIWr zl)`9@nMzx!b5X@Y;|ZVEvxdo}B^rjMHIl8nHFZ^zfx&6s*dOOF4~)|{n58gZ5e`Wf zS2*mwuaw)i7qO&DllGy1JTRcicyMk<%@w+vqnnnHACW}DlH|uQA}y8mTp?4jp=^G{ z2~L#G2GQQSK~YsEs3;MHa>e%Uu}Qx2S3O86*s(dE2X8N7?TRq<%fdwSH6tk)oP@SE znDPQpu7W)GBDFDJkt8@;ptK`}kXUOLiZmIiL~?k{&v$fA4K!@qppeWNhw(sb=o|Dv z2)_Dg6;FJ&jJ|Q`jKkqrpv3b{C*sTlc7dKNIVYQQ5-V{+p^$c?p=>&_5U0(wlzMb{ z+^=%T66^LLfBuU6FKm%69mQgLF9b0yhzjIhC_~<7nx4RFAaMCKixTf%a`{LWerRu3`$BI z4ZnEDBQ|JRRxSAS7EOJvKmgzTVc>0^Z;3d9w0Dv8%Q<6`^IX*TA(OU@PeNPzmQ-o9 zn-|qOR0Ki=rGJTjS_pemNkMGbqk54`+6y8iL;zS402xo1fV)~^u|e;wF6c)DOsC;Y zr_ZSlLwwYtHPlv0c5c!%)CnSP(0^ld+$OkAb-BWcM8j@4uM}(u$X*h(mmWP3jx1(i z#3!U7>*u6O!}++S_q?U6-_Rbj^p8qhR}vrhDXegCvyjO`TaTfC&@eD!kysX$ORl;? zQd=dcE|)~Z0X?h@0BQklt{yVkpew8@(YT=qrHfY7#@Y|N3r;RS`_870ho3n&);H`G ztDI~_Lh$S>9w*ujCHaC)DI(L3xt>iHH& z3Opa~*`~N>o1(TXaH-di__&TB6p{>$d8~H=dMv3D{Qe#XW9H~2WdKkKn9e{vVHp{< zwDcG{V;0|7DoX?_>tsL=3yu<06iX(TZ(0SEM)HfgrxN3r2A!RbKRcl#kH&^QmM(Hg z`%qaVKtRS1qL%i+bp-iQMc=STC}&1efR}U8kcN;0scC3#H#Bz``i3o2Y0Kg=!G;FO zu8oSqJcxz_jtu7)9vG&X(8PB3*aO;Dk zme&sZ^bT1jl9q@Ip;V)L1Fa2G|Yg&aL+J7>wZ{84| zy_i(+;7y9t-3HHx(NRSr<&jJol#mDukKe6vbxzZB&LDDzBAAOPKzE+z=Sd;xdKn3M#6XHPkNOxbj>_Q~xFD0U)f~wd|^Ge~_FOd7JB9q!JY62{zVC z%8LXoy#`|i@$oFlDWB`sM2JS!tUhA{pjrmJb~TJA;cUO5sm1Wx5uaZi@M$||DJv3e zzgltMPQ~rlDXa}{F6;<;M+}o$9Zrl6w0C}R;N1&({UOk?&O_e%onJ`#s_2f|a!pmS z#OC?}A)vG0kjaAQS^7sZtXbw#Q{iB9d~_8=LlB>^{LeANzyFudzN0?Pot9__c5P66 zW`}0SI>ovbk|mXbXegK`g_wISE>^tPg~%_679DxxA72B;F9{l;@jrjixc#1|9%(;M z@uqs0kQTF*-EuDAwTI-~`6+5E9ab-O@k}tezw(a9zr5jd;Dk?V8dfir+_PQt;LVE7 zD59SBv_fi4Uo1|2jo|6em5|H^#n{|o$d5v4p~xx39LHzY zd~OeqTi$QAe9&#^h#9{AMTg4b;LEX;<>f|?v9WmU&-T>pHpXw80e;chrXYWD=&2tx z{W6+Yc%!ibB`s29y@~NmHrCl1`{3}uHWk-wS-j%vJGOuKkBTu?U~JHK&hUEX#4^`g zcwW#R42>DyJ#Bfv%`l#{6h>jqQpuL96!kS9hI6#j=Xj5$>-6Ctefvv0{$ys4`;r6M z4Yxf}y>Zurf0LO`td6xe4aB-mw6`?9d1@*-(E-GEJ@DPiWg8!S`^nD~vY|di+7D8W z2&^3-m4>!nLtD(!(HHdPMR|hyC4%}I$&!j-l3!^3w8>{Q<i8?*>u>0-f- zt0il%P=rGl;pHV(Dr5QCaf?Bc7#ke@!%tW31ezDzUogfW1DY=O-SM}cP40N$o4@KC z@mKYa_*9h!(s%H*q4Bh#p;mJ5Ho=POV5pc`b^*YX)0UAW3@0oD<1n0(ghB%0dF*@f z?~ee*{H`0vPkj6M)}peycdlu0#bmX1@6jle!&_;=pz{^}3=-zzN4+xX<&c@#zGZTO4dq(R1mRN4|x zS~>;|-6JsV3tVOKGKq9%Y9cw%+ZsD|@YUuwfAYQK*3y;R9*Y6zXDt1Hx z{m8@4+Fi}BzMJNuU6J4T-BiZXao*59Y#2>irm_-GXtb2Xhq@-ZP95!NdH=uL2G6yf z?K=JIGr2mT$cs(qu+RK)$%o4j|$}!hFpP< zMoC2`HIyFiYwhnp+uYOFb+W7Z=Y1&TxQV!@%fLveR&asBvx z&p!9=e|zsUduHa$ncXN2HF+E?N-O{XfTO4&qxC!^{`WvIp5I+>%mx8~H^quFk~-dh zj#_;Zm}h3iGPw{O;RyL4Hn486m?6}djrUgLOL>+#CZesJoGQ#dFgKZ-SwsL@Rt56 zDcS+Q-i-NCQBg8fGIVu!ydl2Iw(-Bw=QTzZ0Cbx2?z66N2aYHrXo1d|5KWL?$=0Fi z8C zJ&DST$T0MEO|*KnGBRo*KqmuI?j=Fo#W~t8XbhJgtsZleN*uGn?_Plj#Y9Kfkf=?; z+U0M6$OcSpFqa+4L`ucD;p4AKsow^i~V(ylhyb`V{vaD9va6z8LVJDL;XVSQ(Fddwudwrn8M0K#Y_uLTzMId1V1; z?{Ac$3VxW-4HpR-w5ds%UVMTw5pFVo1U~Vqb3~7?VJ2+Ka&0%8-7`My^mL8=wwWum zY@7TgdZI1rJ%s@$FpP4H0^QT|-;Q5H*jO5$MjZ+E8b$E!xgV+jDmOj~jjqW8K(6hq z1|56ea5AW!D5P4X0lN)Yh(d_QNeyd8%7T4GRjKDFdONbrsOO0~va4YqKF?@1ec)X^ zu!PrvH#t-ke03b)-)SIe60ejLdeMKDj$n+gnb3KK0@VDkKky{_M@Fgv_stNr#1^%A z(7E)30v(Mn8Z{(RqhjB<6D92&ZzXVVXlPqZ2fK359gtwO+48=If>eOy z!B6_prU!YO;Wls$(BkAqJw7Vnty?wDZ($XgctYp5xwH)b$&Jgz`yEpN`hfQG!H``W z-tlRx_0Q&-hfAcr&kJK;zK6wbO9)}H>yV%7rc5gHZ@anz1#zx9(0}kUhRqxwkDWjR zQ1JteFF`_=c`pbGZ2iD#@ti;I02l0^t79N}&t^ZSZU&OeH(SR^8BWMpXCRrHyM%rT zMw|P6Bd4muj}ag{z>=uODoecn%u{3FMf?=L?L6p(yWffd{;r0CfLAdhYOU9(GT-8w zD}$Xk(a5mz<#IC{@W-EhMx|2(q#;$i>N>m@2ckA)BqiBZN}p?I3XX`AsSc7SzpIx` z_~ca4H#;a%zZ!Fq-nCK=6ePf3_z{G$k6bxzyAzEf&tv)s7iK$%dvGD=g07z4je2nk z$wSVGbJYN=u+o^6v3IrfDazi*X_f>c3*hiOsYa(D;=U_c7aK2dYZhzF3hTFRSPIYP z{Cm^VD%x4cjF*Un>d)T2KOpM zsERk3$?w_)ZvDN$kCjBDq8it9s3s%15)emacGd06i>me*!4!BhLXVqd+I@7PR# z^~v$Nq$8JEJr|_>A<7>NhOIV901_GJwAOj)jytU1ETRqC=k?LxmUELKd#KS&e*AhA zGMXLQIxSpO;THTgnnB?t2o{@Wn!Qfrn%}K8TAQByIF&QVBGsA2g!B`qkOB9_3?aL! za&FTc{V&OU57acA(O{MsV%p9RXBgs+KviKwB2aK;HPqX1?1?L?pvM{@fkV0cSu&p) ze>RZ(n3@9B`Vu`D3?RgAJ7UK^e}BCh;oE|yuZ%k6h`B7tKZxg?wRs`81j_sbz|RKT zfcnEXsDN?@ZW+O!4uVoZ`}9c@twHKo90fcyfqJLeRFWjCE)hrUnL_ygTzBm(R}DAp z$DkjoDgN_6LAhnH!?g3E5rQRv>y>e(`)~8Y8Gx1RcVk%#t57iOhwrmx+RAGN@~_f& zV-x?T!IiG-30&4-C8h`7IW$wfR?y(H{32HL$1?jfz9;(>q+2odArjrmMnn8 z*=65>4YV?S=1wQ^eLwmo^PDA7ezvxt%q%5azzdWg20c2$7Px4IiotLeREP>c(LOyQ zMHJAk&N8tJO;mFtOmW;N30DWyRXwGI7bMn+36?wa+|5%V3@3uEc1-vkk==f8{5ENx z&VF;6-ldpgq!f}Jg9+?4fRY3hxj5wAc(xd>OoD=btrUF?*Vs0|dmLvUZ7I;~9oSfM zklCOx$dNr0(zk&5!ERoWsEw_v;Ov<*;1t{XMRmVs6odYdS4b5FcDqB~Q)N6p-FG2x z^6Rr(=@`ti^(RPI+d?~Om;vJNHYq}UnY+raLNE;Rr{t=Qt*0FFyE3Bgsenx+pS@)` z0bL%FG<|{x5UQf;hAs*92xMJL=t~GAbqsTJeVy6gJBjjQ|nayv; zr3VZBB00s$i6YGv2oKI6P)yLs1>$)#7hqMj6l#>PV_#y8?#!+jzBs>S=L1Lrpgg#_ zEQmfbr<$x(lmeo!aFA7?f8Kg%Sl zxUtFq_Jjd105d36js{gMzB#~g2CJFpctr+{YYdBk0$>`vN{VvAzff&796GMH$HZFO(T^jBH&>2Z%(_ z#h(P+KT%hgb$^RK1>~xIfq5d~P_k-~Bn2ZhGtuY+dTJ>#)+#SQ$X!E3m7UNh=hUif zuhS4-+06F$7bRc#f#jY4g_`vMnLD=j8pOqD5&)-IuT`bZJeHQ7;l_NGB>I9rGrg-9 z`F^(|W8Q%Xg<1naba{ptmM|SXGe=D`vnWZPWZ@Jha0mX7&%c<9P{`j5IWX`DS|MsQ z^3s6eaV+waXDsbQ-NusW^2Mdi2k&ke)13t4yOTd!S=}3uZ^1{-9}S&~kPo7aLO(@= z#TTT6S~!E9rO);;vu(QQf&wW_?<8@nBiD?Jm`D<9RRLyKW*><7-Ic*8-yE@o;Of9~ zLOviL$&+O3^0f~qK^r;~H~S1QS3}Fd6j(7LtYD4oA&D|VdWbb^__ye6w$4sU3RRnW zuT;KNTEO(3Dj~G9(|yIIM68UP*>Bz_A1D5ZFgt}x2D7e5TlO8vSJ7mDs}LX(4d z>1+CmCfd1s@F&wnnsiN@?y&oYljM8y{vDA9U2MzI+Agac9ADO{!rAVl_)OpX?oL|*4LWGN+TF6O(ZEXI7(;p%J4HVKEigc7R{6tTJ zkBcl10J#cB&03Xu-$)Ty_YHbqxKiA-Y#{9+X5<*sm6h5Y4H!~NLbzko+^{*vHDt)^ zXVTUC=&+{x)iTa7zfEg1y2!yelUlhK{LEB44r_cg`dNflRL}TUvX~Hn>%hmACzU>6 zx+~v%qVP8rYUtoMM2x7dU`~W9df54w&hr)-D_OITs(D(L)sEzwdx7m#kz>j9*cbTq zcv#pM1i${wHDaYbo|Ns7Ozd%(iH|Gwus?+5cJ#mbb9ePbwm7jH zxix1)X$cXkMk5swmQf-pnu}pSzvp7%cNiefIV_P?rTR!;{?!qEyRKOO>fzDpArOg+}Px))jc@tZ~7N5o@fuGK)<^&XW4;Z^Uu^abn<=>(Gr!1iLLRakR zJusyr(Z#YbdP|pz->{PpEX5%8it%17oL&JsRf1K&S$~#|OC;UO8h0GKdfmp3dmNqH zFlhgdRnNnqtTOpsZDoqEdz?hnFUu)q#Uo9AJ+`kENf6h0V+H^V&sW$98=$b**|IHK zOO}z8AoAB&>ce6?mKlDsci~PJNt}n-mdPaBq|6iV!ltQXbPKM9xO~2YO6sM=ly)82 zu&kX0taa*8`D&JH7_Lp37tqf$L)^-Dii zbAOJ6+S5VT#zr7&)SuSpmLcJH0^=KBnnZR;+ctkoj6427Ldb_>ht?Ej31CVD^a%)S zmstql`+t(Aty;PXz_VJ0KeP1Pbr)Pl)!Bob7WyPsWXuyWSe(LPB@2ya(Wqfg3?@QW zcP~JQIEX1A6lsE7#=RUCuU50C8yeAnEo|H|xDzcTXws=t60IaF#Zmu*OPij0LZ*kT zqBO}tzT-dWH#E{`3gGe~i1^Mk)Wt^s;Jq9=MvDe|GFB$#XtOxf*Rj^7GUjV7V0loG z4>9zQ#wN7&g%*A43z&z2)`|v`M|8L~0$HmnKnjHyL&*+ceo;-W9$N(BONlq?3; zPu=}Om%D?6w1OUtML4#pGLtx9ITasl+rnqLiBJE85wcqUuAtS2sY>-T$}m;X&d&S- zm++(a9Z~P?!K&Q&K4IeZI6GT|`z~e2J}=6|FJS+51sZTv6X?4zMTCpK&4YhjAVth} z6Y9iDexW+%{T%7y^GmWD>}q{hSp&p&K(N2d;UaQevzp${xPo+M?e2^UHNswMzN((m zfTJlJ?#F>k2o<`a0a+-UcH|>%r*ox*7>FnAa85-fjfJXM2fb1~@oZuqRH34uU|ihc z5^$uUKu<_&pVDZ=P4Zl@*=NZ5`AwikKEsaRlJ`7URfh|-y_b2Xh2qdAj+CNG;wH8{ zgNu{t@2uib!vM!TfsjvN01XtWkg8EJmE)xk0fgSZeBS$QOXhN0QhCI027Fal^pTvl1qO|{PI zj!{pN!KocZL+899uxFh}}pKVtUC}c^Q&tIh6PKwyje++BvdSA1Mjb#LQ`&IDbV6 z{tFktmoR^)h3cB{h*BC5{iRVL4+v-Y1b>$rm*kfd6Z=_$1=+^ksovhzX`4;vxO>Fd z%~-~fIPvKIAhY`vauW7xb=`vE#XQFsC~xZLkPSdVbhku&)aN%Z`tZ7)|Jq>t)rY;F zva4%pD(T{~N(<(k8Ke%FY&V>>oguH80~>Im%XU5R#ULD$(dOacefK)ML5P`#(4Rp0 zSvD+i@pNV-2%+_E?TJ|7M)g)33ot1&#F;9H#sCqG^xSV=FFM9vfabBvya%**@sN~d~Mb)0#2KOF& zNdMu&ZaWBTtII2ucWOq;=)gZ_k(I#8tEL-{^4e~qf~Huz9C*|&AQHcSCn~iGbBVR(}EL5g}+~*M{7x3yFYG7&W8T z<~DsOFtPLvFjUnPCwDrw4$wCJbZa0fQP=AknkPL0VozpSzI4vVy^kIqX4~^{lpD}p z{6V!Z8W|#)Dv}U#;?S8Vh;|>oQQ;QHtvXwXa9tU@fY`GsNX6tQE+_$}*Vhm76bzA( zgoh0;hPY>1&~U~zh~`57T&SAjd8e!ihr^va3vJQD2#fMUxVQc2oUSSuT5&V8EvqXe z`GOIc0DI_v4mhackz%xS5ip4FNe#z*?8WLWcq<}A5IYEn=}E zBl%fM*!0D3a#InPmv?4w zm|Q$e%$A4m6$etD*-hv4nG!Hd(WJC8Q5*Qez=z?3c*{1iEue{FY#0L0z(?T#R`#j+ z?o6)Y*Zz~)XyNaiPjXKwaVd~6yt?G(B*vRA2@^d)2WWuPX2>8Ps$xj?jIA}!F`P#J zL+I$Oo%>+A#RLu{t6l7Os>E@F&e&m#isXAthqNTSYM`AWqxYtia2)27CR1Q=5}}gYo&j4NWHIb&H5&eQabv5NXP|eH zOR&RSyY>-_{sX|Lgh#cRLjU;JEge-nP=JjQ*zm{zU$vSgAx&W}BqO$(#G+B`RB!D$ z-6w)pY|$w+`$2`S_%u1Oipi4BmOcl1kQADB|Wk?nEp zPJHV!D3mT?KMBR=jcimJde~vKRNiKgdX+?>6ihU9bQLktf=OpZ>QuZ zV?mybK128(Ymow^pdMH{CVF}L9o8i-1MbpD9i00y(>Nfh|Ce3&?Z-GY>?;B}Ol*^| zNryjU#zK+-vkGDuXEI6!F}i321lz!eob?hwK-!e(z{B(2A8{>;Afv?r>u?rM6kP=Km{cmT<g0`1L%`04a^{24 z>+l3ppHL-LGgf11FDjmVizY$rKh$%>CI@V5mq21=krO(D60Y0>0-=?!07IZR(Q1gx zi>}kj*;3&O%S^*WgQ5RclMu9(L>AxTHw13wmYU^j2S3=EZMO`R3~MLFnRF*w`@bumq1_ z9<#YGXi15!c|dHe_ilE_MirhGn(c`^q-=3^W?m*B>a6`iPro zxGtlHPDq0BzjDFWUei-nM+|Sow4y9WA}7`wK020~(JHzVd7eBUn7n|sG|FBI&|$;$ zc|o$JH+;cg&dg_+OA{~BV=z?zr%4UcBD2ZY9s!$T8(SKY$t?)iX+tX8;QF7wr`l2K zFl|QRA42I-5C`LLOM1)%6~y||jl>^y(imRJI(!YeY>)WY_^i42I+nl>^*oJM3uldGIYyt3ygX)^iwAr_iR z@ijPK{6(<=r7V+#6O~z6&H;s=NMrptU%)ewX`e9Q&B&joo>t_)3X;Fs4v_D3vS4A9 z**H40Y2jU#05tUAFG>J-0xwpQJ!$Ud$@*(OzrMJ(@0F{5^*IuYXUBw$&`}n$NfaAL zR<=#@Uny!cvwMpoY^X#guk0akoc>XmbXAq~z2J|1>QJpkpG0HLRAsk}dE1I8KdWp0 zXeq!FO=vc5XN9IzP>O^#6~j6yo#LLG4^wp=s9`UY@AaFteDcK+xyq437Bitgq;?0i zsuflhw&thxO~J#r$p!8uWRdH~-B^jGuh1`@HYlpGP3Z*Cmj8Q5zcPZR8tbGEvVR(N zm9m*9X-Y@Ar=?j#(D(0R!wnrsA%x&5ThzQVA@9eix=g8x+?Kguh{1u#-X`ZOANJ?L+Wk4L3bk@~&)+KgN6B|w^rSmw3Gyz*}-2cO1 z{)Uo4wc!WrhR}ADe5c0@=UQ#jcgPGh_U+zD`q3O#97R5s##~`aZGr0)bN52QMzEqd zW^VJz3MDuaQNMYyU)=GVZWsV**&&e7q9^=-rzkHkdgtkBz9B`>AeK&mNYC-#t`lbq zbVP;<_fyNI4&H|6egxz*loYL~Tar8;I@uWv+_WMKJG8oz+xE%#ENM7NG)hiQ(2`1` z+{j*)ZP_~F%fLU>Z%2KkiFSOYP6lGve^5;P;)gBr)u=#hU5-`s%O)3JUQavQK)MeZ z*CXDt8!zKn1q;$4`jCUS<2gOv5T0tsQ_#U7r&V_iw1yL>8sqhw! z7N_|u)vlsVK}v>?;4Iqnj#{Ba-w(WeRSw+XC7higRJNmj4W4`P8SA>>Dn0WUmpQB! z>r*)N2#T!BKR?W?c+7e@tIw8kWFbM);hl1AE+_&3-_27@s~FF&2%@i9(bQumWg8kEB7On9z`=&|_ww#N)>1u(HkbTO+^;be}C0dyOtgm&;K%TH*5;@K0I7eaFv!23K2n8MXw_rx_~BcG#}ghlUywAWC#+vSV1A zS%97NQhE8#AAfBVAYR_%SJ1|7zIika%n4Ic%@ZHFLBsKG+Ey>g29y&f>O{(6*EXc} zm+35qCZOIgcdY-6rIBW+1k~mW)SI}axBzU*X@8)fmb?1`u2;Rl=Flw;So=_Es!`^; z)m&1h(++vC8zgQ+?uL0mD1fFVwZ~jOEJYIA7bK}))x30ShXJi(96z0Flb4&AH4#1_ zlsJe>U62ImkNBVUn5#ej`3{rzJMaE9I(=H}hiHJBcQK26`S)ZNBLpt_2l~O53|}-= zM=qZA;}Gv38L&C&IezA<0L0k5TDHp$va(WgnkqQN7=w|)RqzizJ^{|L&>K*Y{J(_{ zn6*Z2`&&+rW{>^mIe{IuQht%%WL3qrv2`snep23Fzh51de#@4~qT=@_2QQ+59{oN? zo(c`~L2kZCc}{m2v~NXCxWY1K0QO5yhJ4%SR|bVVX?kuFR=Q@Mn(MzayVs+oD*AV9 zxbX}#@x}@qT4{{#svHni<_fu$U-9FwUBL2xfAYtMmQ*ZLIt#hJPj#A^0%#_9k`irYRVF$nXg_qY zvK&aUyHsDicpScH(e^TN{+MJ%(QyE*jGt`^0Hu@|D=R)%C))oA-6(M13nM+ErC6Jz z-+Qo_qns;Ia{#kG1WH1%(g$$8x2lC+UO>421f>klf^uWOZ1w=sOL;ww{Lf*sl=q^9 z{6$xju{(w#66IE`Ik8^Y6-1gD^tndbjeJz}fHf+CVMI}{phnO?k@NQ{bg&* zzbrYw85vB9%}Y@SinA<+g==!l)NmJBvo~E$4^5w%Dc{kXnEkR!9@!0(q86g`Y)wOQ zOF3Z@mAH#1xO#khK=dAT^j@7OaF>|Hz!uHn!cNjx%5q<6&c<8u@OTQ?M$pEN;ph!; zh@%p~^yXhq+U;gg<83BE)LYe6-Letm(mW&G$WV7oyMWK;ysO0nDYILTYehbOBCphU z(7@6f0TEhb7!qzHGb?mc8(>E%Ma%j3ImQ;^*_^+QY`Cl66*jB34&F$MhmMg1G~goM~v#kr4#z`o8wypPD)oEf&wA*7T;Q5%#=8AwKQAg1 z&$a4r^67A}_hj&@I-JqSC1{p7laY4sqMBq)EL@-M$5jcC&GsX$?=OXWGLI!~Uf_7* z5XjxVi)S-`v+5aQumWFPW93KR;D}oat%Js(&S)UdJR2aKd6M&w#k!k zOY76hSJ;<7=puq!L%w5NuX;w=E=5tz`cx;`L~uDQXQ!T5BZsvrZsaeby!3vb5WdfLD}*LyhO z{P1r@smONZ?0s!YDQYUX`#C#z zQwzqu+S)h23kNE&JgXjz3jy(ile1X3B7|~T%_K(t9eju_kFe2z9yw3lPIrCc>EXh- zF0R95bnJ8d`sUw`HJgdp3q3Sa#D9)jI7VJKXBS%(%{m6RIcqlSFNZDX`J-Yc zb*Wc|d*K>5U?(F?o3?W_OdgXj1|~atyj{jUq87KrmBeHj?#$4hOo@3_0xB$aedd{e zgf!5u7<3_|;w))EUU#^$kW6+zDZp2>DcAf8qJOqKl!LMO`IO8`s#T}SY?aEyuGkX# zcJAxDtwS3xQM0o6{rU=qYfl6{d?y!q=F?muV!N}}cZsIZ7!N}*dW)tlr~?3*doNaQ zT^GL5-5GabjB9tCTXCV%Fg z=xIqj)=t{S(H8nHv#8VB&hLp0u*y~xK^jljH!JTKpHP@j%e9?!Z0owstlIp~`5W6K zV;Sri2}N9d^aC02fC@pkB1j}RGQj*$Dl09a5lYOcz@mEs$}(Ui16KzgD(_s%$$>F5!?5A`%q>?^{@9!ymgF7{8C|Lvwzxv_-R z= zu>QRhu&o?Xw$KwE9(KWLznf3ae2fi4c8P*<9m$p{L>! zk6mM9tc?bQ8jn`v9V_@>haS|;7QR?GJ z;*H}Ai_|Fx>T6^1eheE(?5<0EmetKkL?ZD2gVtjhRZIH8Gf*}>RemzLl_6u3$4?2z z!M~1M_~wAgKFUb5l ztLx=!SF$-t_W$|AV&1cXa;jY%0ZC@f+@#HFZ86;>0y+JEU(N0_VAbyP;iXIISnbSiZ`9%=P}ee+JND*~ZUw|^~2Fy&MaX#`qd=p&&YFC1bo zgR?)$=;YQ6cl&mD4t5c5{}gbQjj~X=4l4$daa{8<#4Mq7l8GcYcn?!5N#@iAjR!t7 z`5NtDFlxf(@yxDZW;+QIK}8EBn}k2U-P6bmk~>Y^>t%p~vZ5}<(oqi=4MdkX6y6Sh z&N3Y6cc+@)o}KfW-hQP67=V?7xlXu!d1+Yp_YYRMY5nfa#jCw{UNUC)r=xw>r#YnK zC#d$<9rG^gX!s7UC?rvuzLgqqRb|k}(EE$)8b*0S%sN-_D<}wTtmvpaYuxZlUCAw| zAH;2xbiFuhFXq?Vp`5~^rA+;>F~RHA;M_Afg?&MIyufuj6@syRlZ)(&rx2>my6|Qm zBJ6OX^A*04TUt_@GCdIzSC4&&i_JM0rzL6><6+1v05HstPa1Pqj2btecJdkEBv#A5 zbgto&E2;6a_qFM(&f!<#9idh-;9@7{L?JcZq{AF|Ut2@qX6Ob+N$h&aX9dYb)4*sa zP|^^z^Z6Wc}ndvFGE{;gtkDr&{QucPPVmIvi?vpe$QdRY_MK+AiY zt&Nc_#O?fKf+oEbYLF=E>ad5l{K=aiW;lHo+s9wusCyK9MerTt!BcD~d`m^I!`T7I z5Q))1L;d)u|FkaLSFwd!MkU)LGguJ=H7gv{KAT0d`K>1ov^!~_8f9ECQ1sT&E+xGj z*3A4m`-}HQbco%@u%uwAitjC{jcdUs9Cxt{N@u~p@s8uYYw`n*b7|mdsPktQml(<* zMBHkpu=@A_I`Ql7UQi?V-h`!;vaM6w{G@63DAN>d0JRYmsd|V_ha4-I#H=Pk!|0xm z5H*a72zjY~cw(Og@CBNkHv9d7%>EqW6M+${);6&HX;!Lydo?|H>P6Y^*t%NIM^Un< zWa~P5R>ZJbG(yzy@`P6a?gZB;LD#n9II6N}<|V z!%p|RhK7dcK}U3tus5lDQLv>tM}fQ?*9eH*h#|CtesPrHn0j0 z!2F>lV!|rak|IeJK%4AAaQZ`cM zG*W#zWYze(-s(=yrhh5A&$ChRCzB#q5R=d)9+nC>mdY!I(4{3c^Qg=EoSW&V>w%!4 zTe{wkUU&}+B3Z_{)S-93?j$Ji_}pOyLXOgF0X&CY5XFQGN7cX68Q^r)H8m*(6~ z2Do86Dg+b!IPeYQGS)HRD5Ar&(?Tw9|2iObV5ALs{8;@b?c{06vP#)t=@Y=w?aIUB-P;HGs~$p{h~PXW6wf+%-?RJj!yi3Jm0i#N?cbrlTKle^e-8yKhfRgE zj8lCa-idhjV9OpI$p>9Z{IIh~tuaT<4{!wTuchJz%S`_29xmIn%K3)A+wm{N$Wa+y zo(oITN=^Nmn!59Q-RO1IX(+r>=A_XI;`nLlHFK4d4!ki!aN?&UD9R$y*|q`cWO|Cm z7>tO>d2Gtbp$NL^Y-qnO`orXGmnlFQ)}REylDIg;du(s|Y}^-g@EkcWOr<|}YZ%vH z@PqIaimWOtUsZ687!Ro!`wDwpHaQJhS&ddE$CS!m1#`k_^r}h=q?!_Z%3Cv_^cI8@ zoC2hJ)XyX>EI~hJ416v3x}BtROqvhCO(GAmCS_*bCT87XA=MMgH>i0Cl*DOFM95+- zIe@+epdKAC>aYIA{ir_W8fg)I291R)mh}#Cr{knIJ8|s&c~Q{Gijb$rQQFf5nTVK? z4AHk!J*Whcjv>! z=n7oV*glJ9b$iFn!lUPpf)4HRXZ(9|aDfNqpq2@6*)M8e)b=>xtq%91E#Q2gp(#ru z#{RG&lx%-;%a(K9@MP#~B^I!kbYjb2#h~OZZfIT_g4*k^FqU8Ty}sUfd-s@~x71gl z9g~Pvdbw#gI;5T%;OXLWIYDW_mSi-R0nGM#JKLvo;`cYlE64xJ>8X_<`jGYXjg-7s z*x5W@y$_j&$36XUEV*Z_kx4Od&@L00nW~*?vib_%Q9guIA^yGT4Jn44Z)4{Ds(>T) zo|2aGfGb)x;1q)HE7{^MB{W4Mgx$YxK6zpLapwJ{qL zx-wE-RdHJ9Hep0kCtNn^o{r(MtA_daon7@z*dRBiBgWS&O&h{d>mjd== zn;?Jg+1ode#u;tNkRI->8q+vdk+&V35r6}hs6jXWcz}D(2hLTTqx|3lq8(nN?>;W? z8C&xccwi_>XJha8N8DNjR>Jvjj7aay?T>|DTw`|09YtpR)Dz4&zT zS^rkg%S@H~(h+!~0DoNsOVdp4XCH0d_S=qQBt%WDs{ke5B1m8%+b$6h3AA@Wv`qO2 zB~nSRO*syOX|KoI*%VJYzpE9)wdGc@qej!0pRd1GznD{hCg-Xt{6mL$y zW$7s1bVTz2c=beB0kxR)qSz_uY_t+(TuRy2jqbbySyjWn7$OV2JCAsC7p5=uc}@|; zU{5e&Qoc^Z#%L4It2tW?49Ev9`~8*}wi*nWg`vHfT9S!hL8!EAqx2o<>k6x?3KIr6 z%ZPo%3sye1*m9&V!zg``V4m6Qki>qL^=C;8j&Qz`w7fDatoJ?YQz@ znanUU>sgi&^=apQdL~6jM-){z^U9Lk_12Yri*a2V<1gh!6AwgPzKku2Lgh<~rCMLr z+jp8vV&BMOC)SOd{ch`+9Nrn2jKsEIh7>F|#TKjm)PfrC=)#NCX*xz?_U@8O_z^=c5A=PO z`KK_(n+*LQjRiKzFWjAr5`4KU1@~?1T=~Vt+k|+1JYCl7cD8xi{x0>TMb&Vgek&2_ zugAp@$AJS?usx8uf*D{9z~SM2SbKDXS5RhuD}w;-JK#DWgK%8OTK;1NIY`;&D$eMZZ>@CX4{>anx?ctYpoR1yT`>QvbLmj+P5`E zrFwjGB8e5|ubNQ*DEB$TPZZd%9dSYUcZW|Ndpv{=0d(U7g|4b=Xhr$nhY3#8{5aUg zJ0nh>QuYIO6vLHDL*)wJ^^yKzO-2pTd*TVHUk|={K$wRU-7Odzv=oe+tDLv)_ik!N z1I#@ye>Wx76}SU0n!d`RJalx~rrlTN`-~j@1Wsegu_X9Cwzl3bxgG9ZeSxhz*MzPF zv_^jueO22uST1O1&}xk0L?`CJD%K6_@rc?hlc^{(vRiT@+xT)0m;;7z^>0lpstYl~TKimvWbh_gX)pc0B!#~fw6G@ewMmW9P{%e&80QmWxncjUILH0{4F_MczL7ZVa;n zSC`>Fp7%{}MNd(8%bZ3p@o@=EvTI!|nq$SKk&mI_UmLJ~9aYr@axr~=my&9j+^8brRYw-QuX&N*k8CL3^7 zpUF9sdEq}s8)t{b#l8`y*L~ftrJ!o8FlsE^aQ&Cpv_-&c>mq5-CdM+>-|!;|K+?j(wQ%1M6}?5P1MiXS1d}DCU6B zO_|Vb5O+J7MM*cV_a@`Rjc78);Gc85;fsmldF2>w<$qtT$+&ZFjt`qAS5gN@OpW!G z_REU4*XhS3tCwwxR}yL4lg_Nz03bN6$W6y*JpXO=Ho__`OY zx)oPxrM7!=C;jqtba_^8c6eIvkKA9k=Nnd1UNj0UQa+qz6eAlsH%#aOh}N`A)2C7E zNA1zEiXe<@IM!MzdZ6Rx-`iQwa;?D;b?y8vZ*b0X=u6s$1FQ-#jh^{nLML`CGj_u|NV_PG;ZaeKZ?AP;D+o$` z24oF(yRRaZV|l(~>~UKCOb|2BNxh%5S zd*8D+AEi&m)Bd-7e(x+L*(k_J{}pCe+3;db`OJTh{qcTL07wPLoXhEWfcn$@iGRJd z^Ui>b{AjK**KG2H{(r^Avt&GN*H^qmmEtuT{aj>g5o-La!yL&OHydxYeDC$xj!Y=Q z6+Q=G7zj>3BAHRlbi)hkxM z$&J_#4;42XPE(Du%m`Z0(3$3`R>|w@ZpSW6Ln^0O#L8qYW?!ok;C2}sc8YPY=UudR lW{!TX-uwR}{Ox0qX1!w$Nyfa`^WAlTqO6)sg%mva{{Z5~Ya9Ro diff --git a/resources/images/delete.png b/resources/images/delete.png index d083dec4e17d8d7c63b894d380d8fb2c0660b481..46864d558893a2169b18354d973dd670fb4e6f74 100644 GIT binary patch literal 2189 zcmV;82y*v{P)z2&Is&yv`!sloC?tqEL)VXlcRF7~=1^s`WX3GkR~{{p@C*7|D{Ykz`6CcQcy% z-rW1U=lt(E=UxGP+x}16`|rO$RR8~^UAb~)sQ0g3y9RH+{q}+8#KQo;eEBjKMUiN& zUj~3z5OIWvJOF_A9-MPMB6`5g_pV>Ro*i^P@hms*)unTDbGW>`e4^cMzte8Ff8?BN zGP6fSFvcJv=8Z<<&&_7@x7V*2|yGh&YLe(tH1um%aC>wKh={{qywc)4v0NyF)=|Sr&z1I3GpP z&xvT1h(ral9h!&$5h)BqmQn%{LA5wU1SuuNag18)Q_OrqN{Ksp*;)(67_ipWH^su-EHB7>2N27CV6>K0AVX?{^)iTAbDzwASps9~~NcdV1O?NunE#hOO0V zU1oMKI#KmK!f_m9qtRF!8hUAF1ybxl`MB8<_TmN7*@!mhBe`_uDdOZXHA4Sn0Zr!@IFqAxQ z+_(X=v$GI}p-a=W+iJB+V+O#Y6FKKLA^M?jv(J0K8C9z9RfHg-%^2>z2V+dt z*Gtp17#kZKuJ)Lnodo~@V~i)F4I#va_udf^Rk7MR2hO=o@Jcp_xMeZh->;yB5L?2i za}Jmpm^lZ4ywmCUp*JD`fDpnnb1tPUwARi!N0n8LG2onYFTM2Ae`>YbN|t3uR#sMy z7-J|&l5%Ql>K*{tm6es3vn(3{0BE&Zt7Bth-xNjBSXx?|G{(rmMFD`9Qs&Iu+t}FH zb-z7xS0zc}g%DjKL-|qBgvbzaqt$BpeL*XwoRsp3lyV&r z&0unH&Vf=2+wFFDV`Jm*tE;P-QVO)zP!xs#`s=Une)ZK?H}gC9=2%g==eTjEISI4*LsZX0E== z0ZYmU4;~0aOuY9zI8R^eR!aF85wr2}@%=&1&CU5nqp=!A(IZ6E#+YZNbKGgsPe)SR zweA1_69mD7D2lQy%k~Ei0C4{N`PDEC?;@hw1>{f?OCMS@^PM=3+w=4D!$aS^c@qEt z#>dCYFbwY?Vp)Z;7nKqFhI!CzHoJ)E@87>a9Q2tpXEy(4=4B!(s*QNq-~*a2t+kyr zO%(vZ%*@RGHe&y?Wai$B0v@n*kD1Fn&;6}iw_snI9@sRBnR6oYyA|*UwQ@fjmWXJr zwQ%axDcJW$>1h)LK}kez+kHnjj^l?D6BE7Xu{;<0%P+qK0DzH^kws=+ zt)Q71q?F!!Z$JF-!x|!LDW#KALJ$OIeSO_Oe*CyDgs}ZSU}pQl2Oo?WW2_Lu3|bly z+an_*_byzxp!R*$TeS-pE&u>PqtWOR(P}kG5CmYY<>lq&pBQ5%*4Ea(D}>-QO+hKu zeE9I;FP(EX2!fMw90LHrlP6Ekm1X&{QtA~%9FbCjwHB{%kp z5YZ^5MBj3xwf+tfo%P;R5CqgeBi4*DKkbW{qbRBludgEFjB^g9lnek+mL)jnoRo5H za&oc@0C4;EZ8&k_#KARft%X{xc6W4ibk#WrMNxn;1`v^%Ss)@+2i?AWgb+d?B3AtX zfXpn2h^@6y6a^@yAWhRwy-97MJQH{*OTYg5>uzmr?FKXdNeJ=dPN&mSN^QBv zd)__W4y7nG8jbGcthEsU&>q9x zQ?ZYT$}kLPb68kdfN$Hk?Evk+_#eFa+LkIs P00000NkvXXu0mjfQGg{o literal 18148 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 OVeoYIb6Mw<&;$VN2mVC> diff --git a/resources/images/emailIcon.png b/resources/images/emailIcon.png index bfdcf20a10cc588460b23f875008f6b45af3e6ec..bf56a81411ee689e6ab54495d78f98519613d03e 100644 GIT binary patch delta 788 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 G-~I)VI+{WN diff --git a/resources/images/fontColor.png b/resources/images/fontColor.png index 30ba6e1de506ffd9ab75552370b094ca82aafc2f..f9f95befc798d36ccc8d3b65f80057edd03cef34 100644 GIT binary patch literal 2134 zcmV-c2&wmpP)G}2jfXZ zK~z}7?U;LTRMj2FzjyCGv#&gNL$V)M5^p@1)lW# z9?({mokB*Mb=i?!#lMN>9ExZ|Ps%TRnR94unc2t~I2>22>kEGS{to?`;%!lzBWi!$ ziIe9LpPH>ddHVd?emSF)&G8oKjd5tGtw?Tf_wWD1+ppI5I;9^TJrvb-_Q493c6eI< z!C))|fMBr=5FAeDF9G25!(a72d(>TPT$!4g1I~G;zmb^S7mY3MsaroikTGTQxZVZ5 zcEcw_nw{QUOG;)0m;mVXCg}8Ltlv_yw%2*u88mLy=;N~bW(^4ga~`=gIz6f?E?8$g zKX-RfYjxVMoV&Bo>$jA^lssTi`K2o>hdq?5QK_}yoPjt4CmA}%2vw8k6w(01*ddctKFWoM1)i=?m`zz@$gU^gMLVef>a0!avF{ukMr9A8L&bl|dhnf`tIE|InAWANsR{#{mFepF00myfq!f2~7=G zkrZz}QaCTaY*o?1wLY&0?$$;yCg(M0VQ~2&V&kl+xLmuOh{UP6Ke+wSPd)JnHg4aS zajnrYJt-vvL>!H^myk0s<8=UdY{G-p{nGpFZEmOrCk`GdMkcS23|fO3oCHjKX3=Xs zW{(ZqN>=IgW|-nEXsD?Kg#ss&t%(B@+>maQ+K~v^fUkB7xR=vqy(<+94{{k!3ynIg>Y1Ce+tgK7Qob zH~lA#A9MMyU!B5_#yt?7gYE8pe?i`p6EJJ>iswpCf44nn)OaWq;&q#Sl2^U(g7UqG z!zUPkv96gd`Fkc3hhqZ_T)Fh3w31g;EQZMrW$5s(mUqzi`dE)vsdE zfb?5~{`PDc1VO;4NwelA_Q_BSf{4c2O2o&R&+pmv&d2}j_m9t5+SXiEQxJ^nV*n6Q zxYIsF1dTo(e!pw+i31zg%=UP1E%MfVxD%s>W#g@PH>dmj!DnKVZ0PWN;c+=Ie(dNX z0I0V&|JMl)?)cpqwMtd)aa@yA<9c9#Acz{c+a%kZB`fEYez6(TUo5(HPmi29>kL;J zhg)p}(9!ONr}2{G%+alh-K~c?OI8&e`})kzq?9ZW3E|U?TnHrBg5>vr1>Ej$4}EMi z7z}MUrSyg^C6W8fSL%eZQ|GUAd)tR6Sku87LntUgtr9N-z=1#i?FPeM@5#--U;b&V zN$(AG`2GX1ylvGP!5En*=DoC6aVpbkG))?fw&LiX;$sShf_IKF{e_}*dsEA!07RePuP4G%CK}V! zTEo8pf8TQ+M8MO;Vofn0iK2KqE7Nv-+j}d%>sBE;obD+uPy1TEISCd^5)?`$SSSbr zASgs6*#^PqZT)E|;D^)eBc-qXkRS+O0*FAjw7Q2iIh^Z7l~xHsA*j?bP-_jcj}hs+ zf(wEY8ly$@_`M5w(2F>;dnthKL374Lg;E1ise!M}*){vR7)yj&X9NHt)ZzZOaTw0uCiNS-}K4;TYfL`+`?Z)?fqahczQ(>KHIzZ zWHjID^+01vNs>m~ZO{q;>@IT$Q{0W7z6X5|`W|#IXi5OmUB)c^6Mb-T&j(mODF6Tf M07*qoM6N<$f@t*hApigX literal 11021 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 diff --git a/resources/images/formatCode.png b/resources/images/formatCode.png index a823a5d47a9d425d6b5811aa0cc2190e7b968c46..7ea060fae8f33014ae1dfdd18f4e3bba460dec90 100644 GIT binary patch delta 244 zcmca5G>KiaGr-TCmrII^fq{Y7)59eQNGpIa2Q!d#S<-ckfq`jFfKQ0)M19eXj?Ih+L^k;M!Q+`=Ht$S`Y;1W=H@#M9T6 z{TUZOo3N(kssAlNp?FUh#}JFt$q5Q#9~Kl}Ft zXM9{CwR*E$LgK__4+M{@+1MR;wovi)3HJVinIAaBmM&KGw0A$Jn7~x#Zj<41t~26E j;1NCh11I(b{AKtt-6gm2-|`7SyBIuO{an^LB{Ts5yzEbg delta 1733 zcmV;$20HnY2ig)WiBL{Q4GJ0x0000DNk~Le0001h0001h2m=5B0QBII`2YX{uTV@> zMF0Q*u_>$qfBv>SIRF3v;dD|?Qvd-10|W&I2M7T$WQ?J!h!_=#pk^=$JDlv%1P9jZ znmGy!LdH+SJ`542K~SO(4l>}*6V%-=5Ayxr#jJiFfvLCH{P96I^t)*mRkr^0KpX1F zM#AYQz=;rg!DIboTVDw1eRSprYp$?p_bVmyDv^sPe{eG;Y^vJSr_-0u?s58H`BYe0 zVPaigh4AlzU7>^$LtFh;{Aux!+qks#3my1kaM%(Q-sc03cz%o>`u90L_>|)wF6WON z=q^3w4o&wFeL#BIWf^VViM?AlT4|2Jc9D}sF-=kv7lgIk)<)Nv@n^aK00ntTL_t(| z+U=X^e}B|O7{{~O&5|PGy=trIrfMVArdBajMaAM-(W>BqRj}T8-xpeCfp)1?)OsO! zA4L%rLGh|6o_H(X=WO{y%sfdZlkHAs^)my!n0eJc^GtrfZZgk&KiNK-wxx}fuIs7I z5DP~dkIfJZtz`_u$fP!TjLaF?Y}QC^@)(&Le`T8HD0;4D^j2fG9=px9DNbJH*mm2G z-C@j*JJs;=$efwWHqbxr_!FwrV#k`=Pn_yIIO*gOd8xD>?2VjvcgS4F zDZc)xr~NlAR&4Y%|9aC;FW1lb22zHZf3u=zqvi}h|ID+*=zCY3F%|CT*=72M;Ty6g%~cumS0Y5WYNjjU-#`R8@P-+#VHecibL>!S-VqGG1$ z8Gr}D$kMg(V$i=ttiJ1B3VU)kRl9O#HUmGUjo8^xi}f$NT(G`-#g*7VOH_BPf1K&- z>gm|ocopowt3~TO`Zcf@uMO*zZRLW`T~Ei(hU?&Yy^$R4ybbS{ITG}>IoQDMLCj&DNzNU3zs!)JuXSSsAu*85 zgitRFGMF$lz+pnDmjxM27#L7YsF+YOp<+VCgo+6j6DlTDOsJT!B9qlD>_g%AWRjUM z)VRB!&g_iMNo7K!f$pJFf5(^2Nn}Ezf$pVp7nRLPWJ02WCQ-@v)yC%}G9l4uX`<8X z?th@SSnO>t6l60Y*1)5&PBEckLdArN2^AA6CR9wQ-V>_#gz7z^VnW4)iU}1HDkfA+ zsF+YOp<=={g9)eM8n=YYgz(~g>Eq({pLi12xPC4Z*26Y{h))*XgQz>?+3I_4VHyiegMA^m+o{Z-x7{wtFy&RoP=$ zG4{cS@cE;*b#z`nq3`me;CrQyKN%viYI-ay#y5&Qd}w!Xzn`Wpt5 b^8N*_$>yL!558m900000NkvXXu0mjfU>#|+ diff --git a/resources/images/password.png b/resources/images/password.png index d3ad4739497dffebcfe9b6c39b5f825f71f7c9f7..ee43ff8f20a5af14fb6fb12ff7889a09798b8986 100644 GIT binary patch literal 1325 zcmV+|1=9M7P)sQc|Nm+E&YF@PKgE}GdB9o{AppH z#$uCn7M)*h)r!E-k2?*#{&5EY8~Qp%WQ~Zbyp&~?&LaJaREqQms$3PIRaV>b4u@|T z6bHb@R7b(_6*lP{(ov)Xet0=PF%{_Y=N7Fog><}#RY~!yYYHrq6Y1x_!msAq^xfbt z+MO1nx&m}1!>qMN(6Ww+et35>`uBLEBt1+zfs}s_{1j4!p`BBQc!9A`S*)ptCI?H$ zP*u8F>j+GLAo$8*@c?`~Gp1e@Z6ZH~bkVo;zq~d@7=f_>CF4V-^KsJt2vsjIDHH3b z54QA!yMOI+nxunm;FU<%eeNzFtK!+?TZ$+3Zg3SLRZW((SlS({Ywwt}2+N$-`7HMX zPalvlYTR2_pAR&=;00_zFc5OjGbjMF*~yUkO#$2v48T4PqMrmgpuU>gGJ|b~$Aj7- z;Ba%Tv(K&&ci&;REEqtLAKdCe79a+~V<9{q=mywHQZ(*c##+POno#^IQNH=07 zrAnG2)lQX42T9r@9hxX*qLVYMnr90>1|+pR?>`*cXKMbENMCKQLFbR4TUkgA4#|}w z)sK`aM@u_U(-%lvQPUcfJ&Dnib= zA=)(|Tv?;S)z4;K0GMV)guDSHJQzIf(HC--+kNmlbb2pp*=tBAEmASkPpIgXFpaZ! z+4fkdb$E=`GNHqc*?D7P8)JcRv4<01M{0ZrxoF1Sbn2Il%zCjoK0y8B3xYD{gvAs~VaM z^Dk6y?#NZYBf#x8MR&E|rffeX4=_QWZs5F3VO|7FN-j+U_H;&bdq;q)(QFUx(oC;@ zK%*CqUch?;z9|Se4&--McYSvYLfxLc%@OQ1L~FC)YPPVi^7=}A?MJ7W(Pr#un_58@ j(-oK7>v?$b>@52eC|`8HAeILu00000NkvXXu0mjf`*~`m literal 10084 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 diff --git a/resources/images/paste.png b/resources/images/paste.png index 820948e25abc33411b1233708c43f731e32856c0..225c818a2e73898821219e7af44bb0896fdbfc6e 100644 GIT binary patch literal 1641 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

TD)Wkw#!J~*6s+u?OxO9q?x>=Ds=Tx9*3XySu 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%0Df8Emy=4vmh<06@ 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 TWijhlkQ-bbJstQbPv7}JSDn1~ diff --git a/resources/images/spellCheck.png b/resources/images/spellCheck.png index b908d91541a88133845c9937a73cee8d8f0af067..0cf613b03e743c0775dc4f4cea02d023f0d0255d 100644 GIT binary patch literal 3108 zcmV+<4BPXGP)g(&zDW%2~i^W+xcIUAe<-CA<>lo| zfc01P^^GzAA|j`Oj8bYO@T^kG88>d+#-m4%-ZyE|Bv&c*WW?%rV3&xT1d2e~7}KV; zHa7*jrKQF9Ja1oTXXl>U+S)}*sXDFoc%@VxaFtRI%$+-TV{>zJVaV1th{zk+Y<9oz z`+dn|vLY6X%>^C@?g5xGWy;1#HkW{x3x&cC;B+RFF}7{H#u$J9{{4M7Ylvp~^5qqW z4jp<|L{cK6LK7tHya~ z$T(HN<>-DjFnYLPww5w|TP2^no*}*v)n|Zz1%4W7xZ@+j{k432<)GdH7sy1e*b7K4*o7arusSg9~4q>4k*a~a}_5c$h$%#|c9VM~vKRfET8P`AoxDY*02daU^zyY8zIJtR(Lg=0U%G{s{1cp+RU!Sq(hi zP(fL&qO3hOdQ92`0f8Sddc`U_cWj~9ah_r}!`t2cOaA&2jf1R7#L97Cax`&!H1W}B zvO~ZxBmY_jTm)W>>U6YQzllIJ0K0&mA-lK-D2XKFN5*~tSp6LvRWqUFP<-t8Ql%}d zSd8(1{xp?;x{_4wY%+)TA^8C+w0Z2_xOH%IZ_uKK$YgXRk1^4}r+{-&dl5Jmfv8eY zA#mnV&FnRXV)WbxoQxLVeU${uBKxR{7HKqeFSF_T_O!BX@rhFtf?$Gx(T{zVM9mCL zUk)q_;}>cD?lP9-7>`QUO5)+tzt)d+Kg4D3ziTIisK&1Us(KnSMb~i|My!( zA|oyALB>_h81?I9?W|Jm#?ZFSsE5CTH(@eC_a(&lA@C8uh!hIgj!VhBPf|G9Y8`Gn zx#xz@#LZKj=hL;bA9O}mAiX_UF^^e|mb2%34MFtByb%j2f^3h=xw$ZpXaYWtp5R+7#}nGpf__~39bjWL;C)eSY;#dFJA-^ z2m&wxDljNzA;ADDF!g6Yd* zIxphQsDr*7mgA@qPVl);o$c8BQjrtb*h0Bu4b`UYd?pccT_hI@kf+!FJ z#-PMtVIii77y|?S^sfDPzWvV0-Rpb&H5wWWoEjErM=`;hT!S<1ZgK~jLe04`Faa0` z35paCHW8aXm!eW6y||v2AK38XzLo_Qb0%&|Ec$E`$3?V-J9i<{+k@%t4mB)-ifRju zm;#7Fa#=DP*77&+w(nZk;tx*!7jEf36@(S>J6rHaG zt>GA=Zs`wBEb_Y_DoMsI%W=@gV5uGU)u1 zc-&LUanQ!cmH-okV6_=i7Y3l?EY@AK(D4-V?p{=HSGXuY^bjg2Vu^wNUTFR$rfCP} z?E5_0*1vsgj+cOK0%xxkWrv%9)WFzsi&ckfobOdsR#=pcM8^||7st0_C?}K%+D2Ix zN-0RC!Lnf>2igJ=q)eayba*K!Gzf1i?+H*Cw3yu>8f^08Z0ZNAqYaL!nMaM1! zUIMg&Vi*C7g*;1+2d}=_$IH>H-nr|6xX}ZXlv(t42edc#>#h}J(hF0GgheclN|m7E zsSso$3CR>BQsKRq051+DW#N6~aeC46L~DM>mW)|vg&ID8os;6tCDn&AUhU~e4%|2K z?~|UVu)A$evv{x&&*%6t#8TsTO)=qe3T?^2WVWSbU|e7 zgks(VE3AzVzE4-KU+No=ZfL%o+n}M*3_}vPB9AL2 z0Et^V+nM)IHC^uOUU^s5;#exBaATn?yac%MP#!T492;zx0-E~Q4*q=arR;{tOI-M! ze{=aTcUb1RtJ?lz`S;4xDQocc>B$sT-Z$~y?$~`CB zJy2}ld!eUi`HZOx6RET&=7H;h69YF!&WL}}O1&;O3HWv~{Q={wOvN8= zedcTbm`ZpGr2%1JAkVb#{P@35cJ;h^D-7RGrmrww5_5Ym_vhQUwEU*?!B5OtlqxN= zy7IoP_|e*p2Tz~>xmIes+#=xL&4)>)G*Gp8+LUkYeCF%t@2#1<0eEaka`N~8@pD+) yrCm4?q-1nzWsfF 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 bdkV!LPnRSYlJrGB*axU8z6I4Pn1}xlKj9`L diff --git a/resources/images/splash_logo.png b/resources/images/splash_logo.png index 5fdddda7583b6ee4501977d65c77d7fb9a5cdda6..d23c0a456aa808b081dfb49b4bc37b960fb2f09b 100644 GIT binary patch delta 1448 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 diff --git a/resources/images/trayicon.png b/resources/images/trayicon.png index 5fdddda7583b6ee4501977d65c77d7fb9a5cdda6..d23c0a456aa808b081dfb49b4bc37b960fb2f09b 100644 GIT binary patch delta 1448 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 diff --git a/resources/images/windowIcon.ico b/resources/images/windowIcon.ico index a499d486a2c86c29a0e46beb318468eea5daf062..73fcac1726622a153b22e594269243f90c6d103e 100644 GIT binary patch literal 7566 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 literal 67646 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 JzER5i{|}SOF_8cO diff --git a/resources/images/windowIcon.png b/resources/images/windowIcon.png index 5fdddda7583b6ee4501977d65c77d7fb9a5cdda6..d23c0a456aa808b081dfb49b4bc37b960fb2f09b 100644 GIT binary patch delta 1448 zcmV;Z1y}mQJFN>diBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^Z)<=24YJ` zL;(K){{a7>y{D6rF&KXbbV*G`2j&SB4K_FD+gGmu00lZpL_t(o!?l-Ph*eb-$A4?@ zbMBpcof$voHfc6GG>UytF@ceZS{m3>4}pSMl+{Z}Q7=8zb1xMHQWQoaQ4oDR1c4Ow zlF_HogF#SJ>6o8g&6ynMew@8m59gk9&pD3fOtxTg_TFdjz1DyFueJ7GTj3{eLz_%TpK@(guBqR&-V@Y2@XZoM~ zOU8U>jwe9$o9fb&n8`iMw;*yIMM_qAhi^@xK@G}7s8>`Yd=#OhRTD4Dbl5U7^z5%zFrLeu4h`&d_)F53uxcBIkc8t$LNd^(U#0en(T6#mg_o zu`o1I!jbLo2vAmcp>&vmwGf0TZRU+WkL_QPf)btmfxb0gVtYpbXx2X=X#a$iLqJgl z@g4X*7x+sx$M~X(EE@p#ve5S?JnJp5Qo>jZ?tro5wQ6AWgx&U=hzG^1`!Mss4YIhKbIp9CU zK#@wddzdmysVv_aZxuzjs6_A3QvjIKwfL69l)8Z*#IWlXQ2=1U9bX}~N^9araBZrq z-pYT#6eE(}2?{1jpEPx5N$qogF!}ojT^Y6t!1U1%sb0UEaMAkopKqT8L9Aa_4> zrFjrrX8PApuek8&{BarwpTkNJ8yR)j70k5~F!k9pMCVRkQ3c%jqm)diR&d!0si1%W zI6sMyM_7}kH_FPP9xst1mgrE!4!`l3-tIYPVK&()?bI_q(Fc3 z$UbU!zC>;PYn|ZxxD)-cP$Ef?P?^{@Tn7d&fK8#%YS6RxSr)E&V$NtZ>Ia$Jw~?Mo zC0}gEnNT;t_cJZD0@ZR4?zaKO#k#2VQZXH zsf3YCNtl06Y!!fsUBhF*$ffVw&S5EvNAbRm^AYMjDsk;+tic8r8-^HTu(t5Xkz7qW z*8jlxN}1R-j8$q*I0l6OrbNm{(>Dkvt~HVFH|50K{L3j4SmG^pGylm$Gy`m32d)9e zE~$VP(#Q*<66bf3U2@nYK2QDs8!AiMPU`a?9FNa|ft}+3_4!X8S_y0g_5t;aRtTD} zY;lruQfjH|R9X%tqK;=Do~`|l93H__U-al<0R92KJ&$zR>2|3A0000aJX6CIfYP&M$H48&dNrM|kb*BG(2GDQgY1(;3c8sarU)~WPy%IQ+mfwJ?6m-i zq=^kjl?&t}!ZRgd~wkyEb8?1kzYw`2@kQ2EHF!+t7mk^5$Ak0q`jGdN?0GV!Dn+cG& zSGI#_Dc9ob{mH^tCAeD<-c(w!=SRo^ku@X#6#CHDm)%G#?gcPE&q>+`4o5Yk!=|fD zT={0h@xljy1(LCGbl1Ean4vY(tXS~+)y3i7`qx3YEN^dauLj641k6z;mj=jso@TBm z^R*x>{GbG_O=*Ns z+$+b8$&CjN^meD6I*|Z;=hCuXYup3iytImDG0lk506y~Z`K6k14{upfN~$K70Q^(v z!{g!(@T~9;8N_K9LL3p!8!wK3BgrR-CBX2uWgiD{Y0f6jVk*H2;kY#5)nhvsTo+em zB&PpL92g%u_JY}N5%AN@TqyYa-X>oXn7}5Yk8lIm^VaIUJ;!z&Jz`MKX&s zJC^Ny#L4E<0D|Q8!MMW;k@qCdP)3w#XD`oEufK=-!XYyd)w?<3+SK^4IS_*6$JFd@ z4WnOLzRnbQ6a+fht8{LAGnOF{aUQj`oVpK&RV4Wf3^2s$ zjuvoxdqdDQ301ZaF};CV+2G%#1SE$yH;-K|vMvT#df`wV5nE!L+a6XJAEgE2G=LM1 z+7J;|>SQUeR?ZF#d~R81nRuB>Ek6>l*dB~C!7efNa#qbd!r9G^KeS3+^0DO^n{p#Xy$7DZX zJ;0e+o#k+V9F@uT1+i`Rm~<4@ycgv)@5wB905B(qu>JclM7cN#O#_7O$&gk=an&^_ zFMB&IU7n2n&wLJpn{Gh41QZNF(*Yp}M6eyp=xd5MX^2iL1*$_@$A%s^HX;O=c*AEgQ45Bo-d%{Z=% z%7iw0rq0Z#d$f*eXad;rHdD&8eP5UXRFlokr6O>DUp;-bK0w9-CIjvr1TaRerW+8!77VwiItzA*a_TB`ap#yYmrV9%u zMKLj^yh9QOAdN0`pZl$*g>Tg>y?a1abf5b5T*3!{LS-o=S;DupF+q^$NTxY;kv>9J zEzk@JfG`oVB2ohohi|j38({LCUl+rSs2|XUoEa^0|!l;0P>{~utWz?jk>tFusc)7fMkYgW@t#8LBc@a+24RxoYzV%)-I?X zn{n!5dwfLMIJ27Q_>t0N&L({II^!L}s-FPCf>CEu>^jT*=BQoJD=qjC0N9)eL?B(= zN91+2Ko)f$RY%aVi;DAwRa>Q2rbHx~0AIK&02E^jC4j-MHaHwn8+t0e@ey`2W=Itu z7q#N2?xW&D=U4JZ#u2>9l?-LPa5;LmB&iYb{F1{N^}#2+2_!WkO%M@r91Mi&h#*js z@Plbdf=$|@@(1b{P0R8yCqaGSVNZ2CNECFk7T~BfU!IIwBt}ra<6wO7Txmp5!Xmao zN#WoL4>L$}9F3vJtXW&dzw!+c{jfzhom2l5H!-SlK zg=c>Y#f7i+t8aVDCj6A49a2c(t0%%FB&qLUTr(y>iOZk?A}%%9|`VsR}7Q;rmDc z#0WVf2~vfxhAkqv?N9`-#x-vOyqQCH6u^imlop;rD9*bWo$KxeimTnn(0#ovai4(% z!MG^xqQqN5CrC*!C84zPhcFg>2GaC|pNRxc`&^;>+nP}WG_hWM7(%bCMAs?b2X&qA zE_C^Pf>ubtUVSd22mwXF&iS|>UI*nGo|i$IoL&7#_56?t$Ryk& z!wk3}f$FiJf;Q(hO_4wqzLY>|)domynZF*@EJTrp0m%Zzo(tS&6?-p$l!fGHF|rZ? zS*L=JP)z_5s2+C{M9)j}lYq$;e!~PrWi3!T)$hrxDj$AxW9=EDbTYJp_lA_vIzg4w z!1m-tq!~~VM+Ey^At!Lw3gp0D4pPYc{GUx-dqCN?RI(nt z)+*KzQzeO8E*)cyj~* zV${DEW(M@iS^$7vUJ*7S%6(OPH8X=Mr@*MJ3bmzJJqadNJvrK@CbIJO zp>WLFz!8Abyo;ePd=pq&ueG~9FiMTvLSzXT-RA%R^wJWTW?+UdLO2ddigRK<&Pp1z z@`+YE(oBJhi1>IXFbcC80o7wZ3N9UM4aERYovUhB>ER~pDD}Pw0IY8tP{9_yf8O6U zD=sk#sLXp+Y&*2dN%@9?F{UElL{L)P5dzXm=zPI?NGZZS7~bCLnMU_H5JtCBz8cUN z-t3*X;KR`SUJYS{zUt<}sGjVtTf`xW2x&e*q8Uhf{`?4l(RCv91y@=sUUjkpet~45 zFxTIU+5P~VPvyII-x)dzRF=Fuxeb+LBZrfj)IS3b>EhP{@@Rx35FIP8fr+lr)UG?r zCmGQCP+stIf5G6(6si#IW6^a1jE>}2HjVOfNa;5r$-Y3uf)E2D$*Ifi3?6~digQt1 z_C9M|A9#?&5k_(GJ0P?UUpbx}igf%OAxaaey&tel5$OwT0ye)OlzkpxRwIPaanzf^ z`W(*`z##&IjfJQzeTy%@oE!v8`}cnVr_m`#-7iMbb{f+l3QMv%eTYxXJ`WI?eq?I_ z(#j|-ejB6-YpPfR(qu?U=s4!zAp#$!YE0|_817s2j)wK-{dIJ%xHfwOM0bv70^prg z+59g@WpM@r=|?&`mc0cgRXpJ{P&n#+D9*hgl($ja3sUd3Dtwb+EO;x7&TOA+007dv zqelTbh#*ph8x@`vz6)V=L+`s7k_i$Tip#G-=c<2-%-a}!7Mg~TFjz8J%t6QU59B&G zX&tujz|lf>PY|)$(7jBA&yM|CV7Sly?i9&k&@S@gM4LWa`Hb0u{^(-$i-exhT&&H#J{l@b@sB z1DgcQfIk0nl;*!Ysc1ZsdVX3tgrD z=)O#|T42gBzW*T*YL*koc3%_SsIL7INUJner>qZHra6xWNH+n=zQC;Tn}hX2s_?va zDxtb|1N6$#P1h+W`=PaL0wN-P;XLyKvxr4)_fM>EMoECVgwD0MqR@LrOLfZ0f%Hr% z!$Cb--5+^cKu(Te|5LYOeBYx02px;gMei}MhtQi-&nhMkJPw}N0vH5nRaDn~6~(@@ zTCSJt{dOQMuIK{62vU3iHB*W&5bF>grl<}61v?(N3RK?)-Tq&|(7qpI@RcYEV zR2H^W|IpTNLL-7!UXJdQZ-voyTwB`BlLH_Fu3wRifP?Qd0g>wd$awybWdpl@@pmXR zwxL`g6bgibK`0jqh5Baf{?$LURKJ`Y#`yNTQCe^b`p&+ueZn_u08b$I+d;7=z*l=p z+ziqJW4nF{G5Q1ygCLwAtdccEGju|&|3TD;Hlxs$?_0zUhPQ9T+;yKs$I|$xdz!)I z=vM0+M=gv1fT&?$lrJ!Yhk%pATc8QDz821Ke>32899<(!4*#WT0(7r<4M4HK?rj>i z!6!Y{-;6BmGX7y;mbn0BKNGZoUYcWlBbedS0M3mi0<_Yc=1(vM!UurKp{Kl$Tm_Ls zCg~rF46YQ;FrT2Z=sa)P)}BvSfBKc7N|0~!77LT}gIBDDu!1o-Ze;An4 z1k4mvmR$<1JRjpve;VfghXJ8MpZ8bjT6-<@&gN(8rV69GehQM-2q5&%XohqRpoV>>V3q;Xjls>|gqYX{tw8P^MQ$+!^2ZZEEf6#XF_q0?>LASt z3_f)eiuSK3NWCBFle>crem?goqp#M)_gjU}Pz0e---$8Z_pjMbPK{SiD zV{+s<3~ySGvQF-_wX@HS+$KOR=<@^Y{VWHj7>wNyT@3?c(8yN*&g&)3`T<_mIsX?B zF*>O2cm$cBPMt+=^gnegu@8HGd|gvj%_16&(VfsIcS09vP3S3P z$V~us+0hB>8lnEwcQN$jJxyHdETW*5dO-wt$=4b7x#Rx4h#^Ar8bE1`fc5;`{`)_S zdjGRcRXGbkM4`v$YpIhzt@L;otq{O{goP(Yh>(3EC;;`B>CViQ1^^nN zzIh{d{ph1uc=5+uX!Xp&*sg~$wB-k=4?Y7hqcs0y%sKI$&d=f#Q4_l!O#ert#3#7{tRPV zH)8pv_kpxRWLb0Kfc@CF^OA>?l9vU^9%D-cu;p$KQONq(fB+zYl0g{x)u$nhGUlB1 zu2xQSSc1(uwr_j`xc?D!6ooa8w+Ub{P#=2+jfs6QIu=KkpVYAfcKA4M5o-j6QS=cGX6(@S^K*WUL-B#{T>b6m@bh z*I4voR8Rdl^y=~q#U<){evK0Rb1qYXw*5F#9j^+qzClZ5%@mdl!uapLitXQd7i4{; zU8g)`At%S6$q@*OMBhNhmQQPbN*xBAPy#Nx-ld=f_wfvVDjWiON zTkgZw4X;A&z?OEM@{oiuI$-o12bO?7?;LcW{h!Fwhll?7C1`@YIe@sLStots^N-Wu z`sI58nwwB87u^5l*qdaAwBscM4au0$s#x&y^{B26e@tIRzX6Z$QC&1VF>>sn&w~lq&q1KnQC^OV^lBxt2x6y~kVC480*?0N8?p=;qEI)?uL)57Ly3K|vw?`Z3l1ED!=9|>U16I66U z{mDD9|21t0@chOP!5q)<(W4oP!Ds#lqmO?YhVJ=JzTsn==KftU0OqHKAWRtnfEZ0+ z#|Z%xG(ty#Q2*oY*!=l(u=iJ=gKSJpWs?p$jPCd)_Wk^QDCpLDinZ9*;=>~^qhOm))^I)*mii(U6!g|e80 zq5Gft3oZXPi#QTA@EhXmPAClZjrId*{d;I>?Qlj2_Us{(^{rfIlwge-tdT;!!JwX1 z=sxYO=soc@5XO`@Z(6~!fxQpkgyG-cg0jxgb?2Esw%+?|v*#ZKF~&N(ihTqCgX@>y z4^a3YrBeiExa%9%W_@R$;Sj_A=dP5fHvrSiXf@n}BH}fV+O+wvixgZQ!hbkN|`b z&^3lBM=e9QeY@Zc&pv37Gaf%F5o+2C63T{>8(dc`zBQb)dBBLR62r z7}cZCgI4aD?xtr(eP}xlJpN^jJb62GjGV3g%_@-U{33 zU%z~v06mJS<6SCTZAGd-;Qf)y%ENwV)}CUP%xIWEgBg+qwBFSyEjS*qN?>H3a ztc20E1SHzB02)GLY#(X|{*2Kb4`b}P2Qj(l5eTls&;VU0Z+NE+ft`7N$KeBV3O{L$ zAOR9Mt>+Ef9t~A7xPHY(0GH%wLi<79XhC%pfuL5fVjT_PhT#!HO2Bl&3@|ik)kV;| zmO!h_gI4K-R_=pd?gQxskY0w+!%yh2Y(UnAVb+FV)&?PKLog=>V2y3g^(@9i+GxLSBS6E)(a5;v71TBEb?e zfh2|lXVPm|Fl@GaFhKKJ)(T-YF* zjSoZDUkzZv^fW60j&taAN_F_iLCs)|pL~7m0!SlxTR#LjOm&86*@8ylCKML*F2vQO*>3QR}`!k#|isxNRn0nBA5Du&K$YjKAbnH?skG1*LS~p+ef3)Vx7I}!Y$Vl z_}f$0tR~q6-u=twZ33pK}x;K0uGe0CsTs|=M6z; zL%EK)t$EzjRoQlZTw*K%=yI6V1D$UG_{S}seku?l{J5|~1fn286p!656C{aShaC=; zjjB&=pW2p?Afre@@d8gHNYp0jXH_Y#pvz&#-O5zOV^0%*kAU;j@*Wh#Icu?Dj*d?% zqZ}uih`6|<_^j#q@xPOXap|0^o*8QSw7hROeIZ>2^EE*t`79J7gI#%K`5h5)u zPo6L&gKp^ReD?YzwcpZd25vd}r6l<_918OUahMJwq{M}irhfRhOE6#6`Sxu;$x|qI zPwsi|miwiN7XVlfWMkR1BZG4oPE#B)H7!^+&cO@BsXuaH!2dXQoOhqs$+I8<+y=d| zHm~r37CATYnU$vzxUX5q#DrC@`u3syGxhMGGFkcN{ZKmtPZ102l@^%)oPku?Yg3K=Pq# kch^I7`5)^EH8zd^2QF6V?ocQ~n*aa+07*qoM6N<$f;!kYS^xk5 From cc0f37e8a56c6bde089db0607a3e1f4442f0922f Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 15 Oct 2022 20:31:43 +0800 Subject: [PATCH 12/61] Allocate memory for sourceEdit only when the user enable it; In NBrowserWindow's and NWebView's destructer functions, delete all the class members allocated on heap. --- src/gui/nbrowserwindow.cpp | 130 ++++++++++++++++++++++++------------- src/gui/nbrowserwindow.h | 11 ++-- src/gui/nwebview.cpp | 50 +++++++++++++- src/gui/nwebview.h | 4 ++ 4 files changed, 142 insertions(+), 53 deletions(-) diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index 354080af..750bdfd5 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -60,7 +60,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include #include #include -#include #include #include #include @@ -110,9 +109,7 @@ NBrowserWindow::NBrowserWindow(QWidget *parent) : // this->setStyleSheet("margins:0px;"); - QHBoxLayout *line1Layout = new QHBoxLayout(); - QVBoxLayout *layout = new QVBoxLayout(); // Note content layout - + line1Layout = new QHBoxLayout(); // Setup the alarm button & display alarmText.setStyleSheet("QPushButton {background-color: transparent; border-radius: 0px;}"); @@ -123,7 +120,7 @@ NBrowserWindow::NBrowserWindow(QWidget *parent) : // Setup line #1 of the window. The text & notebook connect(&alarmText, SIGNAL(clicked()), this, SLOT(alarmCompleted())); - layout->addLayout(line1Layout); + layout.addLayout(line1Layout); line1Layout->addWidget(¬eTitle, 20); line1Layout->addWidget(&alarmText); line1Layout->addWidget(&alarmButton); @@ -132,44 +129,35 @@ NBrowserWindow::NBrowserWindow(QWidget *parent) : // Add the second layout display - layout->addLayout(&line2Layout); + layout.addLayout(&line2Layout); line2Layout.addWidget(&urlEditor, 1); line2Layout.addWidget(&tagEditor, 3); // Add the third layout display - layout->addLayout(&line3Layout); + layout.addLayout(&line3Layout); line3Layout.addWidget(&dateEditor); editor = new NWebView(this); editor->setTitleEditor(¬eTitle); setupToolBar(); - layout->addWidget(buttonBar); - - // setup the source editor - sourceEdit = new QTextEdit(this); - sourceEdit->setVisible(false); - sourceEdit->setTabChangesFocus(true); - + layout.addWidget(buttonBar); - QFont font; - font.setFamily("Courier"); - font.setFixedPitch(true); - global.getGuiFont(font); - sourceEdit->setFont(global.getGuiFont(font)); - sourceEditorTimer = new QTimer(); - connect(sourceEditorTimer, SIGNAL(timeout()), this, SLOT(setSource())); + sourceEdit = nullptr; + hammer = nullptr; + spellChecker = nullptr; + printPreviewPage = nullptr; + printPage = nullptr; // add the actual note editor & source view - QSplitter *editorSplitter = new QSplitter(Qt::Vertical, this); + editorSplitter = new QSplitter(Qt::Vertical, this); editorSplitter->addWidget(editor); - editorSplitter->addWidget(sourceEdit); - layout->addWidget(editorSplitter); - setLayout(layout); - layout->setMargin(0); + layout.addWidget(editorSplitter); + setLayout(&layout); + layout.setMargin(0); findReplace = new FindReplace(); - layout->addWidget(findReplace); + layout.addWidget(findReplace); findReplace->setVisible(false); connect(findReplace->nextButton, SIGNAL(clicked()), this, SLOT(findNextInNote())); @@ -226,7 +214,6 @@ NBrowserWindow::NBrowserWindow(QWidget *parent) : connect(&tagEditor, SIGNAL(tagsUpdated()), this, SLOT(sendTagUpdateSignal())); connect(&tagEditor, SIGNAL(newTagCreated(qint32)), this, SLOT(newTagAdded(qint32))); connect(editor, SIGNAL(noteChanged()), this, SLOT(noteContentUpdated())); - connect(sourceEdit, SIGNAL(textChanged()), this, SLOT(noteSourceUpdated())); connect(editor, SIGNAL(htmlEditAlert()), this, SLOT(noteContentEdited())); connect(editor->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); connect(editor->page(), SIGNAL(microFocusChanged()), this, SLOT(microFocusChanged())); @@ -242,13 +229,6 @@ NBrowserWindow::NBrowserWindow(QWidget *parent) : buttonBar->getButtonbarState(); - //printPage = new QTextEdit(); - //printPage->setVisible(false); - //connect(printPage, SIGNAL(loadFinished(bool)), this, SLOT(printReady(bool))); - - //printPreviewPage = new QTextEdit(); - //printPreviewPage->setVisible(false); - printPreviewPage = nullptr; printPage = nullptr; @@ -328,6 +308,47 @@ NBrowserWindow::NBrowserWindow(QWidget *parent) : NBrowserWindow::~NBrowserWindow() { browserThread->quit(); while (!browserRunner->isIdle); + + delete browserThread; + delete browserRunner; + delete editor; + + delete editorSplitter; + delete findReplace; + delete factory; + delete buttonBar; + delete line1Layout; + + delete focusNoteShortcut; + delete focusTitleShortcut; + delete insertDatetimeShortcut; + delete insertDateShortcut; + delete insertTimeShortcut; + delete fontColorShortcut; + delete fontHighlightShortcut; + delete copyNoteUrlShortcut; + delete removeFormattingShortcut; + delete insertHtmlEntitiesShortcut; + delete encryptTextShortcut; + delete insertHyperlinkShortcut; + delete insertQuicklinkShortcut; + delete removeHyperlinkShortcut; + delete attachFileShortcut; + delete insertLatexShortcut; + + if (hammer != nullptr) { + delete hammer; + } + + if (spellChecker != nullptr) { + delete spellChecker; + } + if (printPreviewPage != nullptr) { + delete printPreviewPage; + } + if (printPage != nullptr) { + delete printPage; + } } @@ -2405,18 +2426,33 @@ void NBrowserWindow::linkClicked(const QUrl url) { // show/hide view source window void NBrowserWindow::showSource(bool value) { - setSource(); + if (value) { + setSource(); + } sourceEdit->setVisible(value); - sourceEditorTimer->setInterval(1000); - if (!value) - sourceEditorTimer->stop(); - else - sourceEditorTimer->start(); } // Toggle the show source button void NBrowserWindow::toggleSource() { + if (sourceEdit == nullptr) { + // setup the source editor + sourceEdit = new QTextEdit(this); + sourceEdit->setVisible(false); + sourceEdit->setTabChangesFocus(true); + + QFont font; + font.setFamily("Courier"); + font.setFixedPitch(true); + global.getGuiFont(font); + sourceEdit->setFont(global.getGuiFont(font)); + + editorSplitter->addWidget(sourceEdit); + + connect(sourceEdit, SIGNAL(textChanged()), this, SLOT(noteSourceUpdated())); + connect(editor->page(), SIGNAL(contentsChanged()), this, SLOT(setSource())); + } + if (sourceEdit->isVisible()) showSource(false); else @@ -2426,15 +2462,17 @@ void NBrowserWindow::toggleSource() { // Clear out the window's contents void NBrowserWindow::clear() { - sourceEdit->blockSignals(true); + if (sourceEdit != nullptr) { + sourceEdit->blockSignals(true); + sourceEdit->setPlainText(""); + sourceEdit->setReadOnly(true); + sourceEdit->blockSignals(false); + } editor->blockSignals(true); - sourceEdit->setPlainText(""); editor->setContent(""); - sourceEdit->setReadOnly(true); editor->page()->setContentEditable(false); lid = -1; editor->blockSignals(false); - sourceEdit->blockSignals(false); noteTitle.blockSignals(true); noteTitle.setTitle(-1, "", ""); @@ -2463,7 +2501,7 @@ void NBrowserWindow::clear() { // Set the source for the "show source" button void NBrowserWindow::setSource() { - if (sourceEdit->hasFocus()) + if (sourceEdit == nullptr || sourceEdit->hasFocus()) return; QString text = editor->editorPage->mainFrame()->toHtml(); diff --git a/src/gui/nbrowserwindow.h b/src/gui/nbrowserwindow.h index 5c451ae4..b3a56843 100644 --- a/src/gui/nbrowserwindow.h +++ b/src/gui/nbrowserwindow.h @@ -38,6 +38,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include #include #include +#include #include "src/gui/nwebview.h" @@ -81,7 +82,6 @@ class NBrowserWindow : public QWidget QThread *browserThread; BrowserRunner *browserRunner; void setupToolBar(); - QTimer *sourceEditorTimer; bool insertHyperlink; QString currentHyperlink; bool insideList; @@ -133,6 +133,11 @@ class NBrowserWindow : public QWidget QString tableStyle; QPoint scrollPoint; + QTextEdit *sourceEdit; + QString sourceEditHeader; + QSplitter *editorSplitter; + QHBoxLayout *line1Layout; + // To mark the simulated backspace button events in the undoStack. QVector autoExecCommands; @@ -186,14 +191,12 @@ class NBrowserWindow : public QWidget bool fastPrint; //QShortcut *leftJustifyButtonShortcut; - + QVBoxLayout layout; // Note content layout QHBoxLayout line2Layout; QHBoxLayout line3Layout; qint32 lid; void setBackgroundColor(QString); void showSource(bool); - QTextEdit *sourceEdit; - QString sourceEditHeader; XmlHighlighter *highlighter; void tabPressed(); diff --git a/src/gui/nwebview.cpp b/src/gui/nwebview.cpp index eab60100..0dd76903 100644 --- a/src/gui/nwebview.cpp +++ b/src/gui/nwebview.cpp @@ -94,7 +94,7 @@ NWebView::NWebView(NBrowserWindow *parent) : contextMenu->addSeparator(); // change background color of WHOLE note (this is different from changing font/text background color) - QMenu *colorMenu = new QMenu(tr("Note Background Color"), this); + colorMenu = new QMenu(tr("Note Background Color"), this); colorMenu->setFont(global.getGuiFont(font())); // Build the background color menu backgroundColorMapper = new QSignalMapper(this); @@ -122,7 +122,7 @@ NWebView::NWebView(NBrowserWindow *parent) : contextMenu->addMenu(colorMenu); contextMenu->addSeparator(); - QMenu *todoMenu = new QMenu(tr("To-do"), this); + todoMenu = new QMenu(tr("To-do"), this); todoMenu->setFont(global.getGuiFont(font())); contextMenu->addMenu(todoMenu); todoAction = new QAction(tr("To-do"), this); @@ -284,6 +284,48 @@ NWebView::NWebView(NBrowserWindow *parent) : QString css = global.getThemeCss("noteContentsCss"); if (css!="") this->setStyleSheet(css); + + backgroundColor = nullptr; +} + + +NWebView::~NWebView() { + delete editorPage; + delete contextMenu; + delete backgroundColorMapper; + delete tableMenu; + delete imageMenu; + + delete openAction; + delete cutAction; + delete copyAction; + delete pasteAction; + delete pasteWithoutFormatAction; + delete copyInAppNoteLinkAction; + delete htmlSimplifyAction; + delete fontColorAction; + + delete fontBackgroundColorAction; + delete todoAction; + delete todoSelectAllAction; + delete todoUnselectAllAction; + delete insertHtmlEntitiesAction; + delete encryptAction; + delete insertDateTimeAction; + delete insertLinkAction; + + delete insertQuickLinkAction; + delete removeLinkAction; + delete attachFileAction; + delete insertLatexAction; + delete insertTableAction; + delete insertTableRowAction; + delete insertTableColumnAction; + delete deleteTableRowAction; + delete deleteTableColumnAction; + delete tablePropertiesAction; + delete rotateImageLeftAction; + delete rotateImageRightAction; } @@ -297,7 +339,9 @@ QAction *NWebView::downloadImageAction() { } QAction* NWebView::setupColorMenuOption(QString color) { - QAction *backgroundColor = new QAction(color, this); + if (backgroundColor != nullptr) { + backgroundColor = new QAction(color, this); + } color = color.replace(" ", ""); //connect(backgroundColor, SIGNAL(triggered()), this, SLOT() return backgroundColor; diff --git a/src/gui/nwebview.h b/src/gui/nwebview.h index ea868d81..1a3f4d5a 100644 --- a/src/gui/nwebview.h +++ b/src/gui/nwebview.h @@ -44,9 +44,13 @@ class NWebView : public QWebView private: QAction *setupColorMenuOption(QString color); QSignalMapper *backgroundColorMapper; + QMenu *todoMenu; + QMenu *colorMenu; + QAction *backgroundColor; public: explicit NWebView(NBrowserWindow *parent = 0); + ~NWebView(); NBrowserWindow *parent; NTitleEditor *titleEditor; QString pasteSequence; From bbf95ad310d7761134b0950e5e423c17e2f3faed Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 26 Oct 2022 15:06:15 +0800 Subject: [PATCH 13/61] Delete trunk image from nixnote2.qrc. --- nixnote2.qrc | 1 - 1 file changed, 1 deletion(-) diff --git a/nixnote2.qrc b/nixnote2.qrc index 716e08f6..8ecc7747 100644 --- a/nixnote2.qrc +++ b/nixnote2.qrc @@ -35,7 +35,6 @@ resources/images/todo.png resources/images/italic.png resources/images/newNote.png - resources/images/trunk.png resources/images/usage.png resources/images/black_dot.png resources/images/notebookSmall.png From 995889d6f0fc1785226d6f60b19219a812c4b959 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 26 Oct 2022 15:12:16 +0800 Subject: [PATCH 14/61] Comment out the code of setting icon for the attribute tree. (The attribute tree has already one collapse icon.) --- src/gui/nattributetree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/nattributetree.cpp b/src/gui/nattributetree.cpp index 0117719b..6220d267 100644 --- a/src/gui/nattributetree.cpp +++ b/src/gui/nattributetree.cpp @@ -42,8 +42,8 @@ NAttributeTree::NAttributeTree(QWidget *parent) : // Build the root item root = new QTreeWidgetItem(this); - QIcon icon(":attributes.png"); - root->setIcon(0,icon); + //QIcon icon(":attributes.png"); + //root->setIcon(0,icon); root->setData(0, Qt::UserRole, "root"); root->setData(0, Qt::DisplayRole, tr("Attributes")); QFont font = root->font(0); From 12f8d1d7ef88526550dd888a3e1d7add7f9d4b6f Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 27 Oct 2022 16:07:49 +0800 Subject: [PATCH 15/61] Add an option: save the ui changes(such as the window state, geometry, splitter position, selected notebooks and so on) when Nixnote exits. --- src/dialog/preferences/appearancepreferences.cpp | 5 +++++ src/dialog/preferences/appearancepreferences.h | 1 + src/global.cpp | 14 ++++++++++++++ src/global.h | 4 ++++ src/nixnote.cpp | 12 +++++++++--- 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/dialog/preferences/appearancepreferences.cpp b/src/dialog/preferences/appearancepreferences.cpp index fcde934b..861051fd 100644 --- a/src/dialog/preferences/appearancepreferences.cpp +++ b/src/dialog/preferences/appearancepreferences.cpp @@ -57,6 +57,8 @@ AppearancePreferences::AppearancePreferences(QWidget *parent) : autosetUserid->setChecked(global.autosetUsername()); fontPreviewInDialog = new QCheckBox(tr("Preview fonts in editor dialog*")); fontPreviewInDialog->setChecked(global.previewFontsInDialog()); + saveUiState = new QCheckBox(tr("Save the state of the UI when exiting"), this); + saveUiState->setChecked(global.getSaveUiState()); trayMiddleClickAction = new QComboBox(); trayMiddleClickAction->addItem(tr("Do nothing"), TRAY_ACTION_NOTHING); @@ -125,6 +127,7 @@ AppearancePreferences::AppearancePreferences(QWidget *parent) : mainLayout->addWidget(alternateNoteListColors,row++,1); mainLayout->addWidget(autosetUserid, row,0); mainLayout->addWidget(fontPreviewInDialog, row++, 1); + mainLayout->addWidget(saveUiState, row++, 0); mainLayout->addWidget(defaultNotebookOnStartupLabel,row,0); mainLayout->addWidget(defaultNotebookOnStartup, row++,1); @@ -251,6 +254,8 @@ void AppearancePreferences::saveValues() { global.settings->setValue("showNoteListGrid", showNoteListGrid->isChecked()); global.settings->setValue("alternateNoteListColors", alternateNoteListColors->isChecked()); global.pdfPreview = showPDFs->isChecked(); + global.setSaveUiState(saveUiState->isChecked()); + if (minimizeToTray!= nullptr) global.settings->setValue("minimizeToTray", minimizeToTray->isChecked()); else diff --git a/src/dialog/preferences/appearancepreferences.h b/src/dialog/preferences/appearancepreferences.h index 22365e7c..42fa583a 100644 --- a/src/dialog/preferences/appearancepreferences.h +++ b/src/dialog/preferences/appearancepreferences.h @@ -62,6 +62,7 @@ class AppearancePreferences : public QWidget explicit AppearancePreferences(QWidget *parent = 0); void saveValues(); QCheckBox *showTrayIcon; + QCheckBox *saveUiState; QCheckBox *showPDFs; QCheckBox *showSplashScreen; QCheckBox *showMissedReminders; diff --git a/src/global.cpp b/src/global.cpp index 128b94e8..41cb0dd2 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1397,6 +1397,20 @@ void Global::setMultiThreadSave(bool value) { } +bool Global::getSaveUiState() { + global.settings->beginGroup(INI_GROUP_APPEARANCE); + bool value = global.settings->value("saveUiState", true).toBool(); + global.settings->endGroup(); + return value; +} + +void Global::setSaveUiState(bool value) { + global.settings->beginGroup(INI_GROUP_APPEARANCE); + global.settings->setValue("saveUiState", value); + global.settings->endGroup(); +} + + QString Global::formatShortcutKeyString(QString shortcutKeyString) { return shortcutKeyString.toUpper() .replace("SPACE", "Space") diff --git a/src/global.h b/src/global.h index 9b732746..0bcab67f 100644 --- a/src/global.h +++ b/src/global.h @@ -438,6 +438,10 @@ class Global : public QObject { bool getMultiThreadSave(); bool multiThreadSaveEnabled; + bool getSaveUiState(); + void setSaveUiState(bool value); + + ExitManager *exitManager; // Utility to manage exit points. QString getProgramDataDir() { return fileManager.getProgramDataDir(); } diff --git a/src/nixnote.cpp b/src/nixnote.cpp index 9d5126f0..ed59552a 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -1189,6 +1189,15 @@ void NixNote::saveOnExit() { QLOG_DEBUG() << "saveOnExit: Shutting down threads"; indexRunner.keepRunning = false; counterRunner.keepRunning = false; + + QLOG_DEBUG() << "saveOnExit: Closing threads"; + indexThread.quit(); + counterThread.quit(); + + if (!global.getSaveUiState()) { + return; + } + QCoreApplication::processEvents(); QLOG_DEBUG() << "Saving window states"; @@ -1301,9 +1310,6 @@ void NixNote::saveOnExit() { saveNoteColumnPositions(); noteTableView->saveColumnsVisible(); - QLOG_DEBUG() << "saveOnExit: Closing threads"; - indexThread.quit(); - counterThread.quit(); QLOG_DEBUG() << "Exiting saveOnExit()"; } From 09a5e2a0720e9eee1b18bfe05fc21d64f4154521 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 27 Oct 2022 19:02:48 +0800 Subject: [PATCH 16/61] About the css of the checkbox, use an inline css string instead of a css file; Fix a typo about _WIN32 macro --- src/global.cpp | 2 +- src/gui/nbrowserwindow.cpp | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/global.cpp b/src/global.cpp index 41cb0dd2..347f4306 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1572,7 +1572,7 @@ QString Global::getCheckboxImageUrl(bool checked) const { QString filePath = global.fileManager.getImageDirPath("").append(fileName); QString prefix = QString("file://"); -#ifdef WIN32 +#ifdef _WIN32 prefix.append("/"); #endif return prefix + filePath; diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index 750bdfd5..52d811b3 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -4156,19 +4156,18 @@ QString base64_encode(QString string) { void NBrowserWindow::setEditorStyle() { QString css = global.getEditorCss(); - QString path = global.fileManager.getImageDirPath("") + - QString("checkbox.css"); - path.replace("file:///", "").replace("file://", ""); - QFile f(QDir::toNativeSeparators(path)); - f.open(QFile::ReadOnly); - QTextStream in(&f); - QString checkbox = in.readAll(); - f.close(); + QString checkbox = + QString("img.todo-icon {") + + QString(" vertical-align: baseline !important;") + + QString(" cursor: pointer;") + + QString(" padding-right: 5px;") + + QString("\n") + + QString(" user-drag: none;") + + QString(" -webkit-user-drag: none;") + + QString("}"); + css += checkbox; - if (css.isEmpty()) { - return; - } QString url = QString("data:text/css;charset=utf-8;base64,").append(base64_encode(css)); // http://doc.qt.io/archives/qt-5.5/qwebsettings.html#setUserStyleSheetUrl // hack to pass inline css to avoid putting it in file From 7990356c3e2cbd0d8f0550847545e9e051944fae Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 28 Oct 2022 15:52:15 +0800 Subject: [PATCH 17/61] Move the code of fixing the memory leaks of QWebView to NWebView::setContent(). --- src/gui/nbrowserwindow.cpp | 7 ------- src/gui/nwebview.cpp | 12 ++++++++++++ src/gui/nwebview.h | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index 52d811b3..c819844c 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -581,13 +581,6 @@ void NBrowserWindow::setContent(qint32 lid) { editor->setContent(content); - // Qt Webview memory leaks solution: - // https://forum.qt.io/topic/10832/memory-size-increases-per-page-load/4 - QWebElement body = editor->page()->mainFrame()->findFirstElement("body"); - body.setAttribute("onunload", "_function() {}"); - QWebSettings::clearMemoryCaches(); - editor->history()->clear(); - // is this an ink note? if (inkNote) editor->page()->setContentEditable(false); diff --git a/src/gui/nwebview.cpp b/src/gui/nwebview.cpp index 0dd76903..5ddd4b9f 100644 --- a/src/gui/nwebview.cpp +++ b/src/gui/nwebview.cpp @@ -631,3 +631,15 @@ void NWebView::dropEvent(QDropEvent *e) { parent->handleUrls(mime); parent->contentChanged(); } + + +void NWebView::setContent(const QByteArray &data) { + // Qt Webview memory leaks solution: + // https://forum.qt.io/topic/10832/memory-size-increases-per-page-load/4 + QByteArray content = data; + content.replace("history()->clear(); +} diff --git a/src/gui/nwebview.h b/src/gui/nwebview.h index 1a3f4d5a..1a615173 100644 --- a/src/gui/nwebview.h +++ b/src/gui/nwebview.h @@ -102,6 +102,7 @@ class NWebView : public QWebView void keyPressEvent(QKeyEvent *); void downloadAttachment(QNetworkRequest *req); void focusLostNotify(QString text); + void setContent(const QByteArray &data); signals: void noteChanged(); From bf41f332d95d8fb584e8ff36df0257ebffb34b2c Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 28 Oct 2022 21:42:13 +0800 Subject: [PATCH 18/61] Set saveUiState value with setValue() so that the group name will not be added to the option's name. --- src/dialog/preferences/appearancepreferences.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialog/preferences/appearancepreferences.cpp b/src/dialog/preferences/appearancepreferences.cpp index 861051fd..aa177b1b 100644 --- a/src/dialog/preferences/appearancepreferences.cpp +++ b/src/dialog/preferences/appearancepreferences.cpp @@ -254,7 +254,7 @@ void AppearancePreferences::saveValues() { global.settings->setValue("showNoteListGrid", showNoteListGrid->isChecked()); global.settings->setValue("alternateNoteListColors", alternateNoteListColors->isChecked()); global.pdfPreview = showPDFs->isChecked(); - global.setSaveUiState(saveUiState->isChecked()); + global.settings->setValue("saveUiState", saveUiState->isChecked()); if (minimizeToTray!= nullptr) global.settings->setValue("minimizeToTray", minimizeToTray->isChecked()); From b362ff86ff3d156736d2fa26eed94db1cf604793 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 29 Oct 2022 21:23:08 +0800 Subject: [PATCH 19/61] In saveOnExit(), quit the indexThread and ounterThread at the end, so that when saveUiState is disabled, they will not be quitted a second time by ~NixNote(). --- src/nixnote.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/nixnote.cpp b/src/nixnote.cpp index ed59552a..b955c70d 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -1190,10 +1190,6 @@ void NixNote::saveOnExit() { indexRunner.keepRunning = false; counterRunner.keepRunning = false; - QLOG_DEBUG() << "saveOnExit: Closing threads"; - indexThread.quit(); - counterThread.quit(); - if (!global.getSaveUiState()) { return; } @@ -1310,6 +1306,9 @@ void NixNote::saveOnExit() { saveNoteColumnPositions(); noteTableView->saveColumnsVisible(); + QLOG_DEBUG() << "saveOnExit: Closing threads"; + indexThread.quit(); + counterThread.quit(); QLOG_DEBUG() << "Exiting saveOnExit()"; } From 666cb1327a5b3118b90874d0ac72e07153e6c5e9 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sun, 30 Oct 2022 20:19:11 +0800 Subject: [PATCH 20/61] Delete the checkbox.css from images folder; Update two button icons. --- resources/images/alarmclock.png | Bin 524 -> 498 bytes resources/images/checkbox.css | 10 ---------- resources/images/printer.png | Bin 994 -> 838 bytes 3 files changed, 10 deletions(-) delete mode 100644 resources/images/checkbox.css diff --git a/resources/images/alarmclock.png b/resources/images/alarmclock.png index f74d9772ab4cf86f16f183ba8ed6926947983b52..f693ee7e1d853192a9a3845c82590c844fef488b 100644 GIT binary patch delta 483 zcmV<90UZ8}1o8ur8Gi%-004fbn1SlqNaOFNT&UaqrTUaqP);;wTm*V-`UY09JC z)XGX9rTOqoy=zy?xqhExyii#B*Qz91%Ff~g8H{=DXDv+ZGIY zpFDAFG}1__cO9cdzZ9L?{T!G`k2z$A8|hl-mz6oC7<1jMra7}Q6I)cF Z=0BgkW>`PMcQ60|002ovPDHLkV1fpI)}#Oc delta 509 zcmV010qNS#tmY4#WTe z4#WYKD-Ig~00FK^L_t(IPkmEC%K=doz4Nk0DMiZUM@Sa-ENql)E%*=0O0$;y4htKW z6v;|iODTzDBUZDz-<@AWXgQ?I%2-gE9fZ|04|0q6@H$A6K^KNM27w%hGeBod*? zWI}~P!ITgqhZ;CG9PQw8xllTtHnmEnLZwnk_KU@WrqijZozG`VrBc6pGyocn29Cmm z!9b_@cs$C|==b}=0arsB^m;uhXtL*d98BcEHjhRl;RY_`2@bf>i2J`I7^3dnYPEQ| zT=IB4Hq&%~rhnSJ>U27!l%iU#M)UdHoHL)#({{U+7iR+;aG`;gGMS9A^FAsXjq+?Z z`*5I-4XrwzPE;%wLvKR8UZ>~t8Crt|TKN#G)rt>?1K;oWmuc8vuh)+pxX?hWVzC%) zHXCwXmk`^)KAB8@*mJo00000NkvXXu0mjf6FKeI diff --git a/resources/images/checkbox.css b/resources/images/checkbox.css deleted file mode 100644 index edfc967a..00000000 --- a/resources/images/checkbox.css +++ /dev/null @@ -1,10 +0,0 @@ -img.todo-icon { - vertical-align: baseline !important; - cursor: pointer; - padding-right: 5px; - - user-drag: none; - -webkit-user-drag: none; -} - - diff --git a/resources/images/printer.png b/resources/images/printer.png index ff18530ccc9325976ac42066c2ac0ad1d04fcde5..f7fe37fed974885c08b21d53a5fedb11ec84d2d0 100644 GIT binary patch delta 828 zcmV-C1H=5{2gU}F7k?WF1ONa4x6VZo00002VoOIv0RM-N%)bBt010qNS#tmY3ljhU z3ljkVnw%H_000McNliru<_aDR1vH~(lcoRw0@q1IK~y-)m6XA6R8GIE z`mod%Q6VigVu}eUqNF4yCR!UoAmYNLE=q!~Ozf^ZB)B5_2Y;Au3JZym#26*U1#N;F z6Af%gfCS8lWJ;h6F4~!S@4kCnbXumRQ^xNkH}9UC^WE>9d)^V2;j6w)-$dKh(Fp1E z2jkz@JM8lFPU#Livzl!buYQAB?pZ;gcs%-OAOM2u%Ci*&ezjXMSOOp=AzhmwA_Nqv zg$fz67zC;5>wj9Cz!I{KgcLGD4+tQ~Mxzr~y62fArH9s4 z`rsi72q8W6{{HpI_3LjT$jHHgvls1EGE1TxLl6R#GQ)J;I5}7;nzy@lPOT3wGebhW zRI)~vkPs7-GRI)_;=t4y``jnbvd9sO%$@%`Y7tvQHms!Lx@!{tpC4j70r= zZQGOKntxe0QentpgSN&c&HFqa2*}j>zM1lxCR#|T zUOYc3aBjGF%)UVGhWR4@F1b|+9_p&{3$Ij$Vt;?HGCh6b)4v4Z^X?r3QD)-E-4Dlb zk0FTpk>YnFr{m&#e-Qt+Fi^&4n1NkKw^1N@Oa(m3&Gf(&v(Uw>$U!QmWJ=~*`r3nR zE@h7k?TE1^@s6)|*~G00004XF*Lt00D-eG3b_G00009a7bBm000ie z000ie0hKEb8vp{I8PvwYN?gFdAcBR32>uNV zv9nMtgfvu^mf*>eqf}&z$p@IwUyLtJJoXa&Yxi{XIH-B)L+|0~5-+X78W5nVE zHe+pV4V9IZ`1<;?+m>|g^Yar`RaIzgY)pC9umeDBJ3Bj~#0>zwTSG%bN&p_GYte8z z!rM|lK0ZYFo97$|h9dwfNTo#Al%3q%T=@Ndk(`{Iv{y?>9!GCg%zaJkTQC(f_E(QqT=;#O)6%}}Ye-{vHI4dg)6syrhrU>Hg?adZ| zqYGYNU&T1VP@%`;k(uB8p9VNn)c-mwXuCMG1wdm2KqIL{jVGIVrl6p}HqQS7@bvVA z_4Rd3PfufDU;sr$MRH>|Qe0e&$;nA9EG%GqdpjwRAAdo(va*7`y*)%C5uBc$%7Jh= zEJ5a`4iLb|$Os-D9?;s_imt9M3=a>Zxw+W{V*;3(ni3x_E-s>}sY!fFHtz23Tp3pj zWU!>9L|jQA1klsdgZ}=0*&+aDW@Zow1TZ%@hs(=LWM^l8FIv*hsbJZyYj(W6yr83_ z1HHYy7=IcXLT6_uJfu7?FHed`d3iZ*Zf@k(l-t|ejEL%cetyQ`;h`|hH%2bGw6tVC zMg&6NlP;$c`Wr^HC^3#|YilKy>Eci*1S9VA80lAoEH0U%J{d?YW}Jh;AdKbZ94P^H}{*7Mx+dqnwlD^R&8xCrQei-wPDUzU zKGtZqQlv|VmEvS{C&r2On>tyJ&(F`r|5D5_ZETFVaRH!REe|pVxIsJH#7 Date: Sun, 30 Oct 2022 20:35:56 +0800 Subject: [PATCH 21/61] Update the changelogs. --- changelog.txt | 7 +++++++ debian/changelog | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/changelog.txt b/changelog.txt index 02d7e6b0..706a5d5c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,11 @@ NixNote (2.1.7) stable; urgency=low + * Add an option: the user can choose to save the ui changes or not when Nixnote exits. + * Save the note content when Nixnote receives signal interruption. + * Allocate the tageditor's tags dynamically. + * Add a default theme named with 'Default' when no theme is found in the THEME_FILE. + * RAM usage optimizations. + * Fix file downloading under Windows. + * Make the editor not render the note content when Key_Up or Key_Down is kept being pressed. * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. * Limited the max length of the recently updated menu of the tray. diff --git a/debian/changelog b/debian/changelog index 02d7e6b0..706a5d5c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,11 @@ NixNote (2.1.7) stable; urgency=low + * Add an option: the user can choose to save the ui changes or not when Nixnote exits. + * Save the note content when Nixnote receives signal interruption. + * Allocate the tageditor's tags dynamically. + * Add a default theme named with 'Default' when no theme is found in the THEME_FILE. + * RAM usage optimizations. + * Fix file downloading under Windows. + * Make the editor not render the note content when Key_Up or Key_Down is kept being pressed. * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. * Limited the max length of the recently updated menu of the tray. From 75bebf1429c8692c5059029cf253fd940e230f9e Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Mon, 31 Oct 2022 16:24:50 +0800 Subject: [PATCH 22/61] Export the stack attribute of a Notebook object when exporting the notes, so that the notebook structure can remain. --- changelog.txt | 1 + debian/changelog | 1 + src/xml/exportdata.cpp | 3 +++ 3 files changed, 5 insertions(+) diff --git a/changelog.txt b/changelog.txt index 706a5d5c..b1de7067 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ NixNote (2.1.7) stable; urgency=low + * Export the stack attribute when exporting the notes. * Add an option: the user can choose to save the ui changes or not when Nixnote exits. * Save the note content when Nixnote receives signal interruption. * Allocate the tageditor's tags dynamically. diff --git a/debian/changelog b/debian/changelog index 706a5d5c..b1de7067 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,5 @@ NixNote (2.1.7) stable; urgency=low + * Export the stack attribute when exporting the notes. * Add an option: the user can choose to save the ui changes or not when Nixnote exits. * Save the note content when Nixnote receives signal interruption. * Allocate the tageditor's tags dynamically. diff --git a/src/xml/exportdata.cpp b/src/xml/exportdata.cpp index 7557a8df..28072245 100644 --- a/src/xml/exportdata.cpp +++ b/src/xml/exportdata.cpp @@ -177,6 +177,9 @@ void ExportData::writeNotebooks() { createTimestampNode("ServiceUpdated", book.serviceUpdated); if (book.updateSequenceNum.isSet()) createNode("UpdateSequenceNumber", book.updateSequenceNum); + if (book.stack.isSet()) + createNode("Stack", book.stack); + createNode("Dirty",dirtyLids.contains(lids[i])); if (book.businessNotebook.isSet()) { From f25c349d41f68a8f4ff1e2409f7ed42c8e8fb8bf Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 3 Nov 2022 13:56:51 +0800 Subject: [PATCH 23/61] Strip html tags with RegEx in ExtractNoteText::stripTags(). --- src/cmdtools/extractnotetext.cpp | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/cmdtools/extractnotetext.cpp b/src/cmdtools/extractnotetext.cpp index bfb9637c..92b12ce6 100644 --- a/src/cmdtools/extractnotetext.cpp +++ b/src/cmdtools/extractnotetext.cpp @@ -86,23 +86,5 @@ void ExtractNoteText::unwrap(QString data) { QString ExtractNoteText::stripTags(QString content) { - - // Start looking through the note - qint32 startPos =content.indexOf(QChar('<')); - qint32 endPos = content.indexOf(QChar('>'),startPos)+1; - content.remove(startPos,endPos-startPos); - - // Remove encrypted text - while (content.contains("") + 11; - content = content.mid(0,startPos)+content.mid(endPos); - } - - // Get the content as an HTML doc. This will strip any - // remaining tags out, but will also return it as a formatted - // string that preserves things like a newline. - QTextDocument textDocument; - textDocument.setHtml(content); - return textDocument.toPlainText(); + return content.remove(QRegExp("<[^>]*>")); } From 83d22369aad80b418cdcb6e39ab272c623e52ed2 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 3 Nov 2022 15:42:10 +0800 Subject: [PATCH 24/61] Decouple the functions of CmdLineTool(except signalGui()) from shared memory. --- src/cmdtools/cmdlinetool.cpp | 632 ++++++++++++----------------------- 1 file changed, 207 insertions(+), 425 deletions(-) diff --git a/src/cmdtools/cmdlinetool.cpp b/src/cmdtools/cmdlinetool.cpp index c596114a..5b0f536d 100644 --- a/src/cmdtools/cmdlinetool.cpp +++ b/src/cmdtools/cmdlinetool.cpp @@ -137,32 +137,15 @@ int CmdLineTool::run(StartupConfig &config) { } - // Email a note via the command line. int CmdLineTool::emailNote(StartupConfig config) { - // Look to see if another NixNote is running. If so, then we - // expect a response if the note was delete. Otherwise, we - // do it ourself. - bool useCrossMemory = true; - global.sharedMemory->unlock(); - global.sharedMemory->detach(); - if (!global.sharedMemory->attach()) { - useCrossMemory = false; - } - if (useCrossMemory) { - global.sharedMemory->write("EMAIL_NOTE:" + config.email->wrap()); - } else { - global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database - return config.email->sendEmail(); - } - return 0; + global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database + return config.email->sendEmail(); } // Delete a note via the command line int CmdLineTool::deleteNote(StartupConfig config) { - bool useCrossMemory = true; - if (config.delNote->verifyDelete) { std::string verify; std::cout << QString(tr("Type DELETE to verify: ")).toStdString(); @@ -172,77 +155,25 @@ int CmdLineTool::deleteNote(StartupConfig config) { return 16; } - // Look to see if another NixNote is running. If so, then we - // expect a response if the note was delete. Otherwise, we - // do it ourself. - global.sharedMemory->unlock(); - global.sharedMemory->detach(); - if (!global.sharedMemory->attach()) { - useCrossMemory = false; - } - if (useCrossMemory) { - global.sharedMemory->write("DELETE_NOTE:" + QString::number(config.delNote->lid)); - } else { - global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database - NoteTable noteTable(global.db); - noteTable.deleteNote(config.delNote->lid,true); - } + global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database + NoteTable noteTable(global.db); + noteTable.deleteNote(config.delNote->lid,true); return 0; } // Query notes via the command line int CmdLineTool::queryNotes(StartupConfig config) { - bool expectResponse = true; - - // Look to see if another NixNote is running. If so, then we - // expect a response of the LID created. First we detach it so - // we are not talking to ourselves. - global.sharedMemory->unlock(); - global.sharedMemory->detach(); - if (!global.sharedMemory->attach()) { - expectResponse = false; - } - if (expectResponse) { - NUuid uuid; - config.queryNotes->returnUuid = uuid.create(); - QString queryString = config.queryNotes->wrap(); - global.sharedMemory->write("CMDLINE_QUERY:" + queryString); - - int cnt = 0; - int maxWait=10; - QString tmpFile = global.fileManager.getTmpDirPath()+config.queryNotes->returnUuid+".txt"; - QFile responseFile(tmpFile); - while (!responseFile.exists() && cntsetSearchString(config.queryNotes->query); - QList lids; - engine.filter(filter, &lids); - config.queryNotes->write(lids, ""); - } + // The other NixNote isn't found, so we do the query ourself + global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database + FilterCriteria *filter = new FilterCriteria(); + global.filterCriteria.append(filter); + global.filterPosition = 0; + FilterEngine engine; + filter->setSearchString(config.queryNotes->query); + QList lids; + engine.filter(filter, &lids); + config.queryNotes->write(lids, ""); return 0; } @@ -286,185 +217,139 @@ int CmdLineTool::addNote(StartupConfig config) { formatter.rebuildNoteEnml(); config.newNote->content = formatter.getContent(); - bool expectResponse = true; - - // Look to see if another NixNote is running. If so, then we - // expect a response of the LID created. First we detach it so - // we are not talking to ourselves. - global.sharedMemory->unlock(); - global.sharedMemory->detach(); - if (!global.sharedMemory->attach()) { - expectResponse = false; - } else { - global.sharedMemory->detach(); - } - if (expectResponse) { - NUuid uuid; - QString id = uuid.create(); - - // Setup cross memory for response - CrossMemoryMapper crossMemory(id); - if (crossMemory.allocate(512) != QSharedMemory::SharedMemoryError::NoError) - expectResponse = false; - - // Write out the segment - config.newNote->write(id); - // Start checking for the response - int cnt = 0; - qint32 newLid = -1; - int maxWait = 5; - while (expectResponse && cntcontent; + newNote.active = true; + newNote.created = QDateTime::currentMSecsSinceEpoch(); + newNote.guid = uuid.create(); + if (config.newNote->title != "") + newNote.title = config.newNote->title; + else + newNote.title = tr("Untitled Note"); + + // Process tags + newNote.tagGuids = QList(); + newNote.tagNames=QStringList(); + for (int i=0; itags.size(); i++) { + QString tagName = config.newNote->tags[i]; + TagTable tagTable(global.db); + qint32 tagLid = tagTable.findByName(tagName, 0); + QString tagGuid; + + // Do we need to add the tag? + if (tagLid == 0) { + Tag tag; + tag.name = tagName; + NUuid uuid; + tagGuid = uuid.create(); + tag.guid = tagGuid; + tagTable.add(0, tag, true, 0); + } else { + tagTable.getGuid(tagGuid, tagLid); } - if (newLid > 0) { - std::cout << newLid << QString(tr(" has been created.\n")).toStdString(); - return newLid; + newNote.tagNames->append(tagName); + newNote.tagNames->append(tagGuid); + } + + // Process the notebook + if (config.newNote->notebook != "") { + QString notebookName = config.newNote->notebook; + NotebookTable notebookTable(global.db); + qint32 lid = notebookTable.findByName(notebookName); + QString notebookGuid; + + // Do we need to add the notebook? + if (lid == 0) { + Notebook book; + book.name = notebookName; + NUuid uuid; + QString newGuid = uuid.create(); + book.guid = newGuid; + notebookGuid = newGuid; + lid = notebookTable.add(0, book, true, false); } else { - std::cout << QString(tr("No response from NixNote. Please verify that the note was created.\n")).toStdString(); + notebookTable.getGuid(notebookGuid, lid); } + newNote.notebookGuid = notebookGuid; } else { - // Another NN isn't found, so we do this ourself - global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database - NUuid uuid; - Note newNote; - newNote.content = config.newNote->content; - newNote.active = true; - newNote.created = QDateTime::currentMSecsSinceEpoch(); - newNote.guid = uuid.create(); - if (config.newNote->title != "") - newNote.title = config.newNote->title; - else - newNote.title = tr("Untitled Note"); - - // Process tags - newNote.tagGuids = QList(); - newNote.tagNames=QStringList(); - for (int i=0; itags.size(); i++) { - QString tagName = config.newNote->tags[i]; - TagTable tagTable(global.db); - qint32 tagLid = tagTable.findByName(tagName, 0); - QString tagGuid; - - // Do we need to add the tag? - if (tagLid == 0) { - Tag tag; - tag.name = tagName; - NUuid uuid; - tagGuid = uuid.create(); - tag.guid = tagGuid; - tagTable.add(0, tag, true, 0); - } else { - tagTable.getGuid(tagGuid, tagLid); + NotebookTable notebookTable(global.db); + newNote.notebookGuid = notebookTable.getDefaultNotebookGuid(); + } + + // Do the dates + if (config.newNote->created != "") { + QString dateString = config.newNote->created; + QDateTime date = QDateTime::fromString(dateString.trimmed(), "yyyy-MM-ddTHH:mm:ss.zzzZ"); + newNote.created = date.toMSecsSinceEpoch(); + } + if (config.newNote->updated != "") { + QString dateString = config.newNote->updated; + QDateTime date = QDateTime::fromString(dateString, "yyyy-MM-ddTHH:mm:ss.zzzZ"); + newNote.updated = date.toMSecsSinceEpoch(); + } + if (config.newNote->reminder != "") { + QString dateString = config.newNote->reminder; + QDateTime date = QDateTime::fromString(dateString, "yyyy-MM-ddTHH:mm:ss.zzzZ"); + if (date > QDateTime::currentDateTime()) { + if (!newNote.attributes.isSet()) { + NoteAttributes na; + newNote.attributes = na; } - newNote.tagNames->append(tagName); - newNote.tagNames->append(tagGuid); + newNote.attributes->reminderTime = date.toMSecsSinceEpoch(); } + } - // Process the notebook - if (config.newNote->notebook != "") { - QString notebookName = config.newNote->notebook; - NotebookTable notebookTable(global.db); - qint32 lid = notebookTable.findByName(notebookName); - QString notebookGuid; - - // Do we need to add the notebook? - if (lid == 0) { - Notebook book; - book.name = notebookName; - NUuid uuid; - QString newGuid = uuid.create(); - book.guid = newGuid; - notebookGuid = newGuid; - lid = notebookTable.add(0, book, true, false); - } else { - notebookTable.getGuid(notebookGuid, lid); - } - newNote.notebookGuid = notebookGuid; - } else { - NotebookTable notebookTable(global.db); - newNote.notebookGuid = notebookTable.getDefaultNotebookGuid(); - } - // Do the dates - if (config.newNote->created != "") { - QString dateString = config.newNote->created; - QDateTime date = QDateTime::fromString(dateString.trimmed(), "yyyy-MM-ddTHH:mm:ss.zzzZ"); - newNote.created = date.toMSecsSinceEpoch(); - } - if (config.newNote->updated != "") { - QString dateString = config.newNote->updated; - QDateTime date = QDateTime::fromString(dateString, "yyyy-MM-ddTHH:mm:ss.zzzZ"); - newNote.updated = date.toMSecsSinceEpoch(); - } - if (config.newNote->reminder != "") { - QString dateString = config.newNote->reminder; - QDateTime date = QDateTime::fromString(dateString, "yyyy-MM-ddTHH:mm:ss.zzzZ"); - if (date > QDateTime::currentDateTime()) { - if (!newNote.attributes.isSet()) { - NoteAttributes na; - newNote.attributes = na; - } - newNote.attributes->reminderTime = date.toMSecsSinceEpoch(); + NoteTable noteTable(global.db); + qint32 newLid = noteTable.addStub(newNote.guid); + // Do the attachments + for (int i=0; iattachments.size(); i++) { + QString filename = config.newNote->attachments[i]; + QFile file(filename); + if (file.exists()) { + + file.open(QIODevice::ReadOnly); + QByteArray ba = file.readAll(); + file.close(); + + MimeReference mimeRef; + QString extension = filename; + int endPos = filename.lastIndexOf("."); + if (endPos != -1) + extension = extension.mid(endPos); + QString mime = mimeRef.getMimeFromExtension(extension); + Resource newRes; + bool attachment = true; + if (mime == "application/pdf" || mime.startsWith("image/")) + attachment = false; + config.newNote->createResource(newRes, 0, ba, mime, attachment, QFileInfo(filename).fileName(), newLid); + QByteArray hash; + if (newRes.data.isSet()) { + Data d = newRes.data; + if (d.bodyHash.isSet()) + hash = d.bodyHash; } - } - - - NoteTable noteTable(global.db); - qint32 newLid = noteTable.addStub(newNote.guid); - // Do the attachments - for (int i=0; iattachments.size(); i++) { - QString filename = config.newNote->attachments[i]; - QFile file(filename); - if (file.exists()) { - - file.open(QIODevice::ReadOnly); - QByteArray ba = file.readAll(); - file.close(); - - MimeReference mimeRef; - QString extension = filename; - int endPos = filename.lastIndexOf("."); - if (endPos != -1) - extension = extension.mid(endPos); - QString mime = mimeRef.getMimeFromExtension(extension); - Resource newRes; - bool attachment = true; - if (mime == "application/pdf" || mime.startsWith("image/")) - attachment = false; - config.newNote->createResource(newRes, 0, ba, mime, attachment, QFileInfo(filename).fileName(), newLid); - QByteArray hash; - if (newRes.data.isSet()) { - Data d = newRes.data; - if (d.bodyHash.isSet()) - hash = d.bodyHash; - } - if (!newNote.resources.isSet()) { - newNote.resources = QList(); - } - QString mediaString = ""; - if (newNote.content->contains(config.newNote->attachmentDelimiter)) { - //newNote.content = newNote.content->replace(config.newNote->attachmentDelimiter,mediaString); - newNote.content = newNote.content->replace(newNote.content->indexOf(config.newNote->attachmentDelimiter), - config.newNote->attachmentDelimiter.size(), mediaString); - } else { - newNote.content = newNote.content->replace("","
"+mediaString+""); - } - newNote.resources->append(newRes); + if (!newNote.resources.isSet()) { + newNote.resources = QList(); } + QString mediaString = ""; + if (newNote.content->contains(config.newNote->attachmentDelimiter)) { + //newNote.content = newNote.content->replace(config.newNote->attachmentDelimiter,mediaString); + newNote.content = newNote.content->replace(newNote.content->indexOf(config.newNote->attachmentDelimiter), + config.newNote->attachmentDelimiter.size(), mediaString); + } else { + newNote.content = newNote.content->replace("","
"+mediaString+""); + } + newNote.resources->append(newRes); } - noteTable.expunge(newLid); - noteTable.add(newLid,newNote,true); - std::cout << newLid << QString(tr(" has been created.\n")).toStdString(); - return newLid; } - return 0; + noteTable.expunge(newLid); + noteTable.add(newLid,newNote,true); + std::cout << newLid << QString(tr(" has been created.\n")).toStdString(); + return newLid; } @@ -506,177 +391,89 @@ int CmdLineTool::appendNote(StartupConfig config) { formatter.rebuildNoteEnml(); config.newNote->content = formatter.getContent(); - bool expectResponse = true; - - // Look to see if another NixNote is running. If so, then we - // expect a response of the LID created. First we detach it so - // we are not talking to ourselves. - global.sharedMemory->unlock(); - global.sharedMemory->detach(); - if (!global.sharedMemory->attach()) { - expectResponse = false; - } else { - global.sharedMemory->detach(); - } - if (expectResponse) { - NUuid uuid; - QString id = uuid.create(); - - // Setup cross memory for response - CrossMemoryMapper crossMemory(id); - if (crossMemory.allocate(512) != QSharedMemory::SharedMemoryError::NoError) - expectResponse = false; - - // Write out the segment - config.newNote->write(id); - // Start checking for the response - int cnt = 0; - qint32 newLid = -1; - int maxWait = 5; - while (expectResponse && cntlid, true, true)) { + std::cerr << config.newNote->lid << QString(tr(" was not found.\n")).toStdString(); + return -1; + } + + // Append the text to the existing note + newNote.content->replace("", "
"); + + // Chop off the beginning of the new text to remove the content.indexOf("content = config.newNote->content.mid(startOfNote+9); + + // Append the two notes + newNote.content = newNote.content + config.newNote->content; + + // Do the attachments + for (int i=0; iattachments.size(); i++) { + QString filename = config.newNote->attachments[i]; + QFile file(filename); + if (file.exists()) { + + file.open(QIODevice::ReadOnly); + QByteArray ba = file.readAll(); + file.close(); + + MimeReference mimeRef; + QString extension = filename; + int endPos = filename.lastIndexOf("."); + if (endPos != -1) + extension = extension.mid(endPos); + QString mime = mimeRef.getMimeFromExtension(extension); + Resource newRes; + bool attachment = true; + if (mime == "application/pdf" || mime.startsWith("image/")) + attachment = false; + config.newNote->createResource(newRes, 0, ba, mime, attachment, QFileInfo(filename).fileName(), config.newNote->lid); + QByteArray hash; + if (newRes.data.isSet()) { + Data d = newRes.data; + if (d.bodyHash.isSet()) + hash = d.bodyHash; } - cnt++; - } - if (newLid == -1) { - std::cout << newLid << QString(tr(" was not found.")).toStdString(); - } else { - if (newLid > 0) { - std::cout << newLid << QString(tr(" has been appended.\n")).toStdString(); - return newLid; - } else { - std::cout << QString(tr("No response from NixNote. Please verify that the note was appended.\n")).toStdString(); + if (!newNote.resources.isSet()) { + newNote.resources = QList(); } - } - } else { - // Another NN isn't found, so we do this ourself - global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database - Note newNote; - - // Fetch the existing note - NoteTable noteTable(global.db); - if (!noteTable.get(newNote, config.newNote->lid, true, true)) { - std::cerr << config.newNote->lid << QString(tr(" was not found.\n")).toStdString(); - return -1; - } - - // Append the text to the existing note - newNote.content->replace("", "
"); - - // Chop off the beginning of the new text to remove the content.indexOf("content = config.newNote->content.mid(startOfNote+9); - - // Append the two notes - newNote.content = newNote.content + config.newNote->content; - - // Do the attachments - for (int i=0; iattachments.size(); i++) { - QString filename = config.newNote->attachments[i]; - QFile file(filename); - if (file.exists()) { - - file.open(QIODevice::ReadOnly); - QByteArray ba = file.readAll(); - file.close(); - - MimeReference mimeRef; - QString extension = filename; - int endPos = filename.lastIndexOf("."); - if (endPos != -1) - extension = extension.mid(endPos); - QString mime = mimeRef.getMimeFromExtension(extension); - Resource newRes; - bool attachment = true; - if (mime == "application/pdf" || mime.startsWith("image/")) - attachment = false; - config.newNote->createResource(newRes, 0, ba, mime, attachment, QFileInfo(filename).fileName(), config.newNote->lid); - QByteArray hash; - if (newRes.data.isSet()) { - Data d = newRes.data; - if (d.bodyHash.isSet()) - hash = d.bodyHash; - } - if (!newNote.resources.isSet()) { - newNote.resources = QList(); - } - QString mediaString = ""; - if (newNote.content->contains(config.newNote->attachmentDelimiter)) { - //newNote.content = newNote.content->replace(config.newNote->attachmentDelimiter,mediaString); - newNote.content = newNote.content->replace(newNote.content->indexOf(config.newNote->attachmentDelimiter), - config.newNote->attachmentDelimiter.size(), mediaString); - } else { - newNote.content = newNote.content->replace("","
"+mediaString+""); - } - newNote.resources->append(newRes); + QString mediaString = ""; + if (newNote.content->contains(config.newNote->attachmentDelimiter)) { + //newNote.content = newNote.content->replace(config.newNote->attachmentDelimiter,mediaString); + newNote.content = newNote.content->replace(newNote.content->indexOf(config.newNote->attachmentDelimiter), + config.newNote->attachmentDelimiter.size(), mediaString); + } else { + newNote.content = newNote.content->replace("","
"+mediaString+""); } + newNote.resources->append(newRes); } - noteTable.expunge(config.newNote->lid); - noteTable.add(config.newNote->lid,newNote,true); - std::cout << config.newNote->lid << QString(tr(" has been appended.\n")).toStdString(); - return config.newNote->lid; } - return 0; + noteTable.expunge(config.newNote->lid); + noteTable.add(config.newNote->lid,newNote,true); + std::cout << config.newNote->lid << QString(tr(" has been appended.\n")).toStdString(); + return config.newNote->lid; } - // Read a note via the command line and extract the text // contents. int CmdLineTool::readNote(StartupConfig config) { - bool useCrossMemory = true; - - // Look to see if another NixNote is running. If so, then we - // expect a response. Otherwise, we do it ourself. - global.sharedMemory->unlock(); - global.sharedMemory->detach(); - if (!global.sharedMemory->attach()) { - useCrossMemory = false; - } - if (useCrossMemory) { - NUuid uuid; - config.extractText->returnUuid = uuid.create(); - CrossMemoryMapper sharedMemory(config.extractText->returnUuid); - if (sharedMemory.allocate(500 * 1024) != QSharedMemory::SharedMemoryError::NoError) - return 16; - - sharedMemory.clearMemory(); - global.sharedMemory->write("READ_NOTE:" + config.extractText->wrap()); - - int maxWait = 5; - bool expectResponse = true; - int cnt = 0; - while (expectResponse && cntunwrap(data); - } else { - sleep(1); - } - cnt++; - } - if (!expectResponse) - std::cout << config.extractText->text.toStdString() << endl; - else - std::cout << tr("No response received from NixNote.").toStdString(); + global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database + NoteTable noteTable(global.db); + Note n; + QString text; + if (noteTable.get(n,config.extractText->lid,false,false)) { + text = config.extractText->stripTags(n.content); } else { - global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database - NoteTable noteTable(global.db); - Note n; - QString text; - if (noteTable.get(n,config.extractText->lid,false,false)) - text = config.extractText->stripTags(n.content); - else - text = tr("Note not found."); - std::cout << text.toStdString() << endl; + text = tr("Note not found."); } + std::cout << text.toStdString() << endl; return 0; } @@ -712,21 +509,8 @@ int CmdLineTool::importNotes(StartupConfig config) { // Alter a note's notebook or add/remove tags for a note. int CmdLineTool::alterNote(StartupConfig config) { - // Look to see if another NixNote is running. If so, then we - // expect a response, otherwise we do it ourself. - bool useCrossMemory = true; - global.sharedMemory->unlock(); - global.sharedMemory->detach(); - if (!global.sharedMemory->attach()) { - useCrossMemory = false; - } - if (useCrossMemory) { - global.sharedMemory->write("ALTER_NOTE:" + config.alter->wrap()); - } else { - global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database - return config.alter->alterNote(); - } - return 0; + global.db = new DatabaseConnection(NN_DB_CONNECTION_NAME); // Startup the database + return config.alter->alterNote(); } @@ -802,8 +586,6 @@ int CmdLineTool::sync() { } - - int CmdLineTool::signalGui(StartupConfig config) { // Make sure another one is actually running. If not, we exit out. From 0b89bbcab9b3ef69f365ef41e13f04dae9ac620d Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 3 Nov 2022 19:22:16 +0800 Subject: [PATCH 25/61] Add an option: Listen to the commands from command line. When it is disabled, the Nixnote of GUI version will not read the commands from the shared memory periodically, which are writen by the command line tools of Nixnote. As default, it is enabled. Only the operations about UI are affected. Non-GUI operations such as addNote, removeNote, appendNote, query and so on are no affected. --- src/dialog/preferences/debugpreferences.cpp | 22 ++++++++++++++++++++- src/dialog/preferences/debugpreferences.h | 2 ++ src/global.cpp | 16 +++++++++++++++ src/global.h | 4 ++++ src/nixnote.cpp | 10 ++++++---- 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/dialog/preferences/debugpreferences.cpp b/src/dialog/preferences/debugpreferences.cpp index 3f33cbfb..60fd6ef5 100644 --- a/src/dialog/preferences/debugpreferences.cpp +++ b/src/dialog/preferences/debugpreferences.cpp @@ -55,13 +55,20 @@ DebugPreferences::DebugPreferences(QWidget *parent) : interceptSigHup = new QCheckBox(tr("Intercept Unix SIGHUP (requires restart).")); interceptSigHup->setChecked(global.getInterceptSigHup()); mainLayout->addWidget(interceptSigHup, row++, 1); +#else + interceptSigHup = nullptr; #endif multiThreadSave = new QCheckBox(tr("Use multiple threads to save note contents (experimental).")); multiThreadSave->setChecked(global.getMultiThreadSave()); mainLayout->addWidget(multiThreadSave, row++, 1); - mainLayout->addWidget(new QLabel(tr("Auto-Save Interval (in seconds).")), row, 0); + listenToCommands = new QCheckBox(tr("Listen to commands from command line.*")); + listenToCommands->setChecked(global.getListenToCommands()); + mainLayout->addWidget(listenToCommands, row++, 1); + + autoSaveIntervalLabel = new QLabel(tr("Auto-Save Interval (in seconds).")); + mainLayout->addWidget(autoSaveIntervalLabel, row, 0); autoSaveInterval = new QSpinBox(); autoSaveInterval->setMinimum(5); autoSaveInterval->setMaximum(300); @@ -77,6 +84,18 @@ DebugPreferences::DebugPreferences(QWidget *parent) : DebugPreferences::~DebugPreferences() { delete mainLayout; + delete disableUploads; + delete disableImageHighlight; + delete showLidColumn; + delete nonAsciiSortBug; + delete forceUTF8; + if (interceptSigHup != nullptr) { + delete interceptSigHup; + } + delete multiThreadSave; + delete listenToCommands; + delete autoSaveIntervalLabel; + delete autoSaveInterval; } @@ -92,6 +111,7 @@ void DebugPreferences::saveValues() { if (disableUploads->isChecked() || disableUploads->isChecked() != global.disableUploads) global.settings->setValue("disableUploads", disableUploads->isChecked()); + global.settings->setValue("listenToCommands", listenToCommands->isChecked()); global.settings->endGroup(); global.setAutoSaveInterval(autoSaveInterval->value()); global.disableUploads = disableUploads->isChecked(); diff --git a/src/dialog/preferences/debugpreferences.h b/src/dialog/preferences/debugpreferences.h index 040ff6b1..85289e25 100644 --- a/src/dialog/preferences/debugpreferences.h +++ b/src/dialog/preferences/debugpreferences.h @@ -40,7 +40,9 @@ class DebugPreferences : public QWidget QCheckBox *forceUTF8; QCheckBox *interceptSigHup; QCheckBox *multiThreadSave; + QLabel *autoSaveIntervalLabel; QSpinBox *autoSaveInterval; + QCheckBox *listenToCommands; public: explicit DebugPreferences(QWidget *parent = 0); diff --git a/src/global.cpp b/src/global.cpp index 347f4306..cefdbece 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -214,6 +214,7 @@ void Global::setup(StartupConfig startupConfig, bool guiAvailable) { autoSaveInterval = getAutoSaveInterval() * 1000; multiThreadSaveEnabled = this->getMultiThreadSave(); + listenToCommands = this->getListenToCommands(); exitManager = new ExitManager(); exitManager->loadExits(); } @@ -1397,6 +1398,21 @@ void Global::setMultiThreadSave(bool value) { } +bool Global::getListenToCommands() { + global.settings->beginGroup(INI_GROUP_DEBUGGING); + bool value = global.settings->value("listenToCommands", true).toBool(); + global.settings->endGroup(); + return value; +} + +void Global::setListenToCommands(bool value) { + global.settings->beginGroup(INI_GROUP_DEBUGGING); + global.settings->setValue("listenToCommands", value); + global.settings->endGroup(); + this->listenToCommands = value; +} + + bool Global::getSaveUiState() { global.settings->beginGroup(INI_GROUP_APPEARANCE); bool value = global.settings->value("saveUiState", true).toBool(); diff --git a/src/global.h b/src/global.h index 0bcab67f..9c7b428b 100644 --- a/src/global.h +++ b/src/global.h @@ -438,6 +438,10 @@ class Global : public QObject { bool getMultiThreadSave(); bool multiThreadSaveEnabled; + bool getListenToCommands(); + void setListenToCommands(bool value); + bool listenToCommands; + bool getSaveUiState(); void setSaveUiState(bool value); diff --git a/src/nixnote.cpp b/src/nixnote.cpp index b955c70d..7926eb0d 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -146,10 +146,12 @@ NixNote::NixNote(QWidget *parent) : QMainWindow(parent) { indexThread.start(QThread::LowestPriority); this->thread()->setPriority(QThread::HighestPriority); - heartbeatTimer.setInterval(1000); - heartbeatTimer.setSingleShot(false); - connect(&heartbeatTimer, SIGNAL(timeout()), this, SLOT(heartbeatTimerTriggered())); - heartbeatTimer.start(); + if (global.getListenToCommands()) { + heartbeatTimer.setInterval(1000); + heartbeatTimer.setSingleShot(false); + connect(&heartbeatTimer, SIGNAL(timeout()), this, SLOT(heartbeatTimerTriggered())); + heartbeatTimer.start(); + } this->setFont(global.getGuiFont(this->font())); From 77352d4d465fc2449c074896b43436b26cc84879 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 3 Nov 2022 21:54:05 +0800 Subject: [PATCH 26/61] Set shared memory size to 1024(512KiB previously) if Nixnote is set to listen to command line, and to 1 if not, in main.cpp. --- src/main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index a5950208..b8d36670 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -223,7 +223,12 @@ int main(int argc, char *argv[]) { // with any other instance that may be running. If another instance // is found we need to either show that one or kill this one. bool memInitNeeded = true; - int sharedMemSize = 512 * 1024; + int sharedMemSize; + if (global.getListenToCommands()) { + sharedMemSize = 1024; + } else { + sharedMemSize = 1; + } QSharedMemory::SharedMemoryError allocateRecCode1 = sharedMemory->allocate(sharedMemSize); if (allocateRecCode1 != QSharedMemory::SharedMemoryError::NoError) { // failed to create new memory segment (#1) From 3eab851e3382d55e7a55dee2699c26f85e28cf58 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Mon, 5 Dec 2022 14:52:09 +0800 Subject: [PATCH 27/61] Revert commit 17e34424c5: Call unpin functions in NTableview::deleteSelectedNotes and Nixnote::deleteCurrentNote, so that pinned notes will not be displayed after being deleted. --- src/gui/ntableview.cpp | 5 ----- src/nixnote.cpp | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/gui/ntableview.cpp b/src/gui/ntableview.cpp index 33c05aa8..ec3d2e4a 100644 --- a/src/gui/ntableview.cpp +++ b/src/gui/ntableview.cpp @@ -728,11 +728,6 @@ void NTableView::deleteSelectedNotes() { //transaction.exec("commit"); sql.finish(); - // Unpin the note being deleted, so that the table view - // will not display a pinned note even after it - // has been deleted. - unpinNote(); - emit(notesDeleted(lids, expunged)); } diff --git a/src/nixnote.cpp b/src/nixnote.cpp index 7926eb0d..54afac7c 100644 --- a/src/nixnote.cpp +++ b/src/nixnote.cpp @@ -3295,10 +3295,6 @@ void NixNote::deleteCurrentNote() { QList lids; lids.append(lid); emit(notesDeleted(lids)); - // Unpin the note being deleted, so that the table view - // will not display a pinned note even after it - // has been deleted. - unpinCurrentNote(); } From f251d72e3c65d9e90d7bfe63069c39a3359ed828 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Mon, 5 Dec 2022 15:12:09 +0800 Subject: [PATCH 28/61] Limit the max length of the favourite(shortcuts) menu of the tray to a constant value, 30. --- src/gui/traymenu.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gui/traymenu.cpp b/src/gui/traymenu.cpp index 801f5c26..887346b2 100644 --- a/src/gui/traymenu.cpp +++ b/src/gui/traymenu.cpp @@ -78,10 +78,10 @@ void TrayMenu::buildActionMenu() { buildMenu("pinnedMenu", pinnedMenu, records); } + const int MAX_LENGTH = 30; if (recentlyUpdatedMenu) { - records.clear();; + records.clear(); noteTable.getRecentlyUpdated(records); - const int MAX_LENGTH = 30; for (int i = 0; i < records.length(); i++) { if (records[i].second.length() > MAX_LENGTH) { records[i].second = records[i].second.left(MAX_LENGTH) + "..."; @@ -101,7 +101,11 @@ void TrayMenu::buildActionMenu() { if (record.type == FavoritesRecord::Note) { QPair pair; pair.first = record.target.toInt(); - pair.second = record.displayName; + if (record.displayName.length() > MAX_LENGTH) { + pair.second = record.displayName.left(MAX_LENGTH) + "..."; + } else { + pair.second = record.displayName; + } records.prepend(pair); } } From 15832d4a055236df7df6da9f17725f66ca85d88a Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 14 Jan 2023 14:36:17 +0800 Subject: [PATCH 29/61] Optimise the SQL statements about batch moving of notes from one notebook to another. --- src/gui/nnotebookview.cpp | 12 +- src/sql/notetable.cpp | 201 ++++++++++++++++++++++------------ src/sql/notetable.h | 3 + src/utilities/noteindexer.cpp | 124 +++++++++++++-------- src/utilities/noteindexer.h | 4 + 5 files changed, 223 insertions(+), 121 deletions(-) diff --git a/src/gui/nnotebookview.cpp b/src/gui/nnotebookview.cpp index 8617aeaf..ae15a85f 100644 --- a/src/gui/nnotebookview.cpp +++ b/src/gui/nnotebookview.cpp @@ -965,15 +965,16 @@ bool NNotebookView::dropMimeData(QTreeWidgetItem *parent, int index, const QMime // The string has a long list of note lids. We parse them out & update the note QStringList stringLids = data.split(" "); + QList noteLids; + noteLids.clear(); + NoteTable noteTable(global.db); for (int i=0; i 0) { - NoteTable noteTable(global.db); qint32 currentNotebook = noteTable.getNotebookLid(noteLid); if (currentNotebook != bookLid) { - noteTable.updateNotebook(noteLid, bookLid, true); - emit(updateNoteList(noteLid, NOTE_TABLE_NOTEBOOK_POSITION, notebook.name.value())); + noteLids.append(noteLid); // qint64 dt = QDateTime::currentMSecsSinceEpoch(); // noteTable.updateDate(noteLid, dt, NOTE_UPDATED_DATE, true); // emit(updateNoteList(noteLid, NOTE_TABLE_DATE_UPDATED_POSITION, dt)); @@ -981,13 +982,16 @@ bool NNotebookView::dropMimeData(QTreeWidgetItem *parent, int index, const QMime } } } + + noteTable.updateNotebook(noteLids, bookLid, true); + emit(updateNoteList(noteLids[0], NOTE_TABLE_NOTEBOOK_POSITION, + notebook.name.value())); if (stringLids.size() > 0) { emit(updateCounts()); } return true; } - return false; } diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index 4fff06c2..8caa4f60 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -1104,37 +1104,65 @@ qint32 NoteTable::getNotesWithTag(QList &retval, QString tag) { // Set if a note needs to be indexed void NoteTable::setIndexNeeded(qint32 lid, bool indexNeeded) { + QList noteLids; + noteLids.clear(); + noteLids.append(lid); + setIndexNeeded(noteLids, indexNeeded); +} + + +// Set if notes need to be indexed +void NoteTable::setIndexNeeded(const QList &indexNoteLids, bool indexNeeded) { QLOG_TRACE_IN(); - if (lid <= 0) { - QLOG_TRACE_OUT(); - return; + QList noteLids; + noteLids.clear(); + for (int i = 0; i < indexNoteLids.size(); ++i) { + // If it is already set to this value, then we don't need to + // do anything. + if (this->isIndexNeeded(indexNoteLids[i]) != indexNeeded) { + noteLids.append(indexNoteLids[i]); + } } - // If it is already set to this value, then we don't need to - // do anything. - if (this->isIndexNeeded(lid) == indexNeeded) { + if (noteLids.size() == 0) { QLOG_TRACE_OUT(); return; } + QString lids = ""; + for (int i = 0; i < noteLids.size(); ++i) { + lids += QString::number(noteLids[i]) + ","; + } + lids.chop(1); // chop the trailing ',' + NSqlQuery query(db); db->lockForWrite(); - query.prepare("Delete from DataStore where lid=:lid and key=:key"); - query.bindValue(":lid", lid); + + query.prepare("Delete from DataStore where lid in (" + + lids + ") and key=:key"); query.bindValue(":key", NOTE_INDEX_NEEDED); query.exec(); // We don't really need to do anything after deleting the flag if (!indexNeeded) { + query.finish(); + db->unlock(); + QLOG_TRACE_OUT(); return; } - query.prepare("Insert into DataStore (lid, key, data) values (:lid, :key, :data)"); - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_INDEX_NEEDED); - query.bindValue(":data", indexNeeded); + QString values = ""; + for (int i = 0; i < noteLids.size(); ++i) { + values += "(" + QString::number(noteLids[i]) + "," + + QString::number(NOTE_INDEX_NEEDED) + "," + + QString::number(indexNeeded) + "),"; + } + values.chop(1); + + query.prepare("Insert into DataStore (lid, key, data) values " + values); query.exec(); + query.finish(); db->unlock(); @@ -1142,13 +1170,12 @@ void NoteTable::setIndexNeeded(qint32 lid, bool indexNeeded) { if (!global.enableIndexing) { QLOG_TRACE() << "Calling indexNote"; NoteIndexer indexer(db); - indexer.indexNote(lid); + indexer.indexNotes(noteLids); } QLOG_TRACE_OUT(); } - // Set if a note needs to be indexed qint32 NoteTable::getIndexNeeded(QList &lids) { NSqlQuery query(db); @@ -1173,39 +1200,61 @@ qint32 NoteTable::getIndexNeeded(QList &lids) { // Update the notebook for a note void NoteTable::updateNotebook(qint32 noteLid, qint32 notebookLid, bool setAsDirty) { + QList noteLids; + noteLids.clear(); + noteLids.append(noteLid); + updateNotebook(noteLids, notebookLid, setAsDirty); +} + + +// Update the notebook for multiple notes +void NoteTable::updateNotebook(const QList ¬eLids, + qint32 notebookLid, bool setAsDirty) { Notebook book; NotebookTable notebookTable(db); notebookTable.get(book, notebookLid); - if (book.guid.isSet()) { - NSqlQuery query(db); - db->lockForWrite(); - query.prepare("Update DataStore set data=:notebookLid where lid=:lid and key=:key;"); - query.bindValue(":notebookLid", notebookLid); - query.bindValue(":lid", noteLid); - query.bindValue(":key", NOTE_NOTEBOOK_LID); - query.exec(); + if (!book.guid.isSet()) { + return; + } - if (setAsDirty) { - setDirty(noteLid, setAsDirty, false); - } + int n = noteLids.size(); - QString bookName = book.name; - query.prepare("Update NoteTable set notebook=:name where lid=:lid"); - query.bindValue(":name", bookName); - query.bindValue(":lid", noteLid); - query.exec(); + QString lids = ""; + for (int i = 0; i < n; ++i) { + lids += QString::number(noteLids[i]) + ","; + } + lids.chop(1); // chop the trailing ',' - query.prepare("Update NoteTable set notebookLid=:nlid where lid=:lid"); - query.bindValue(":nlid", notebookLid); - query.bindValue(":lid", noteLid); - query.exec(); - query.finish(); - db->unlock(); + NSqlQuery query(db); + db->lockForWrite(); + + query.prepare("Update DataStore set data=:notebookLid where lid in (" + + lids + ") and key=:key;"); + query.bindValue(":notebookLid", notebookLid); + query.bindValue(":key", NOTE_NOTEBOOK_LID); + query.exec(); + + if (setAsDirty) { + setDirty(noteLids, setAsDirty, false); } -} + QString bookName = book.name; + query.prepare("Update NoteTable set notebook=:name where lid in (" + + lids + ")"); + query.bindValue(":name", bookName); + query.exec(); + + query.prepare("Update NoteTable set notebookLid=:nlid where lid in (" + + lids + ")"); + query.bindValue(":nlid", notebookLid); + query.exec(); + + query.finish(); + + db->unlock(); +} // Update the URL for a note @@ -1472,68 +1521,80 @@ QString NoteTable::getNoteListTags(qint32 lid) { // setDateUpdated: default true void NoteTable::setDirty(qint32 lid, bool dirty, bool setDateUpdated) { - if (lid <= 0) + QList noteLids; + noteLids.clear(); + noteLids.append(lid); + setDirty(noteLids, dirty, setDateUpdated); +} + + +// Set multiple notes as dirty. +void NoteTable::setDirty(const QList ¬eLids, bool dirty, + bool setDateUpdated) { + QString lids = ""; + for (int i = 0; i < noteLids.size(); ++i) { + lids += QString::number(noteLids[i]) + ","; + } + if (lids == "") { return; + } + lids.chop(1); - db->lockForWrite(); NSqlQuery query(db); + db->lockForWrite(); // If it is setting it as dirty, we need to update the // update date & time. if (dirty && setDateUpdated) { qint64 dt = QDateTime::currentMSecsSinceEpoch(); - query.prepare("Delete from DataStore where lid=:lid and key=:key"); - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_UPDATED_DATE); - query.exec(); - query.prepare("Insert into DataStore (lid, key, data) values (:lid, :key, :value)"); - query.bindValue(":lid", lid); + query.prepare("Update DataStore set data=:value where lid in (" + + lids + ") and key=:key"); query.bindValue(":key", NOTE_UPDATED_DATE); query.bindValue(":value", dt); query.exec(); - query.prepare("Update NoteTable set dateUpdated=:value where lid=:lid"); - query.bindValue(":lid", lid); + query.prepare("Update NoteTable set dateUpdated=:value where lid in (" + + lids + ")"); query.bindValue(":value", dt); query.exec(); } - // If it is already set to the value, then we don't - // need to do anything more. - if (isDirty(lid) == dirty) { - query.finish(); - db->unlock(); - return; - } - // If we got here, then the current dirty state doesn't match // what the caller wants. - query.prepare("Update NoteTable set isDirty=:isDirty where lid=:lid"); + // If it is already set to the value, then we don't + // need to do anything more. + query.prepare("Update NoteTable set isDirty=:isDirty where lid in (" + + lids + ") and isDirty!=:isDirty"); query.bindValue(":isDirty", dirty); - query.bindValue(":lid", lid); query.exec(); - query.prepare("Delete from DataStore where lid=:lid and key=:key"); - query.bindValue(":lid", lid); + query.prepare("Delete from DataStore where lid in (" + + lids + ") and key=:key and data!=:data"); query.bindValue(":key", NOTE_ISDIRTY); + query.bindValue(":data", dirty); query.exec(); if (dirty) { - query.prepare("Insert into DataStore (lid, key, data) values (:lid, :key, :data)"); - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ISDIRTY); - query.bindValue(":data", dirty); + QString values = ""; + for (int i = 0; i < noteLids.size(); ++i) { + values += "(" + QString::number(noteLids[i]) + "," + + QString::number(NOTE_ISDIRTY) + "," + + QString::number(dirty) + "),"; + } + values.chop(1); + + query.prepare("Insert into DataStore (lid, key, data) values " + values); query.exec(); - query.finish(); - db->unlock(); - setIndexNeeded(lid, true); - } else { - query.finish(); - db->unlock(); } -} + query.finish(); + db->unlock(); + + if (dirty) { + setIndexNeeded(const_cast &>(noteLids), true); + } +} bool NoteTable::isDeleted(qint32 lid) { diff --git a/src/sql/notetable.h b/src/sql/notetable.h index b8bc0461..29f71cc7 100644 --- a/src/sql/notetable.h +++ b/src/sql/notetable.h @@ -167,7 +167,9 @@ class NoteTable bool updateNotebookName(qint32 lid, QString name); // Update a notebook's name in the user listing void updateNotebook(qint32 noteLid, qint32 notebookLid); // Set the current note's notebook void setDirty(qint32 lid, bool dirty, bool setDateUpdated=true); // Set if a note needs a sync + void setDirty(const QList ¬eLids, bool dirty, bool setDateUpdated=true); // Set if a list of notes need a sync void updateNotebook(qint32 noteLid, qint32 notebookLid, bool setAsDirty=false); // Update the notebook for a note + void updateNotebook(const QList ¬eLids, qint32 notebookLid, bool setAsDirty=false); void updateUrl(qint32 lid, QString text, bool dirty); // Update a URL for a note void updateTitle(qint32 noteLid, QString title, bool setAsDirty); // Update a title for a note void updateDate(qint32 lid, Timestamp ts, qint32 key, bool isDirty); // Update a date for a note @@ -188,6 +190,7 @@ class NoteTable void sync(qint32 lid, const Note ¬e, qint32 account=0); // Sync a note with a new record qint32 add(qint32 lid, const Note &t, bool isDirty, qint32 account=0); // Add a new note void setIndexNeeded(qint32 lid, bool indexNeeded); // flag if a note needs reindexing + void setIndexNeeded(const QList &indexNoteLids, bool indexNeeded); // flag if multiple notes need reindexing void updateNoteListTags(qint32 noteLid, QString tags); // Update the tag names in the note list void updateNoteListNotebooks(QString guid, QString name); // Update the notebook name in the note list void addToDeleteQueue(qint32 lid, Note n); // Add to the notes that need to be deleted from Evernote diff --git a/src/utilities/noteindexer.cpp b/src/utilities/noteindexer.cpp index 494fd48a..691a8249 100644 --- a/src/utilities/noteindexer.cpp +++ b/src/utilities/noteindexer.cpp @@ -43,77 +43,107 @@ NoteIndexer::NoteIndexer(DatabaseConnection *db) void NoteIndexer::indexNote(qint32 lid) { - NoteTable ntable(db); - Note n; - ntable.get(n, lid,false,false); + QList noteLids; + noteLids.clear(); + noteLids.append(lid); - if (n.title.isSet()) { - QLOG_DEBUG() << "Indexing note: " << n.title; - } + indexNotes(noteLids); +} - QString content = ""; - if (n.content.isSet()) - content = n.content; +void NoteIndexer::indexNotes(const QList &lids) { + QStringList contentList; + contentList.clear(); + for (int i = 0; i < lids.size(); ++i) { + NoteTable ntable(db); + Note n; + ntable.get(n, lids[i],false,false); - // Start looking through the note - qint32 startPos =content.indexOf(QChar('<')); - qint32 endPos = content.indexOf(QChar('>'),startPos)+1; - content.remove(startPos,endPos-startPos); + if (n.title.isSet()) { + QLOG_DEBUG() << "Indexing note: " << n.title; + } - // Remove encrypted text - while (content.contains("") + 11; - content = content.mid(0,startPos)+content.mid(endPos); - } + QString content = ""; + if (n.content.isSet()) + content = n.content; - // Remove any XML tags - while (content.contains(QChar('<'))) { - startPos = content.indexOf(QChar('<')); - endPos = content.indexOf(QChar('>'),startPos)+1; + + // Start looking through the note + qint32 startPos =content.indexOf(QChar('<')); + qint32 endPos = content.indexOf(QChar('>'),startPos)+1; content.remove(startPos,endPos-startPos); - }; - - // Get the content as an HTML doc. - QTextDocument textDocument; - textDocument.setHtml(content); - QString title = ""; - if (n.title.isSet()) - title = n.title; - content = textDocument.toPlainText() + " " + title; - this->addTextIndex(lid, content); + + // Remove encrypted text + while (content.contains("") + 11; + content = content.mid(0,startPos)+content.mid(endPos); + } + + // Remove any XML tags + while (content.contains(QChar('<'))) { + startPos = content.indexOf(QChar('<')); + endPos = content.indexOf(QChar('>'),startPos)+1; + content.remove(startPos,endPos-startPos); + }; + + // Get the content as an HTML doc. + QTextDocument textDocument; + textDocument.setHtml(content); + QString title = ""; + if (n.title.isSet()) + title = n.title; + content = textDocument.toPlainText() + " " + title; + contentList.append(content); + } + + this->addTextIndices(lids, contentList); } void NoteIndexer::addTextIndex(int lid, QString content) { + QList noteLids; + noteLids.clear(); + noteLids.append(lid); + + QStringList contentList; + contentList.clear(); + contentList.append(content); + + addTextIndices(noteLids, contentList); +} + + +void NoteIndexer::addTextIndices(const QList ¬eLids, + const QStringList &contentList) { + QString values = "", lids = ""; + for (int i = 0; i < noteLids.size(); ++i) { + lids += noteLids[i] + ","; + values += "(" + QString::number(noteLids[i]) + "," + + QString::number(100) + "," + "text" + "," + + global.normalizeTermForSearchAndIndex(contentList[i]) + "),"; + } + lids.chop(1); + values.chop(1); // chop the trailing ',' + // Delete any old content NSqlQuery sql(db); - sql.prepare("Delete from SearchIndex where lid=:lid and source=:source"); - sql.bindValue(":lid", lid); + sql.prepare("Delete from SearchIndex where lid in (" + + lids + ") and source=:source"); sql.bindValue(":source", "text"); sql.exec(); // Add the new content. it is basically a text version of the note with a weight of 100. - sql.prepare("Insert into SearchIndex (lid, weight, source, content) values (:lid, :weight, :source, :content)"); - sql.bindValue(":lid", lid); - sql.bindValue(":weight", 100); - sql.bindValue(":source", "text"); - - content = global.normalizeTermForSearchAndIndex(content); - sql.bindValue(":content", content); - + sql.prepare("Insert into SearchIndex (lid, weight, source, content) values " + + values); sql.exec(); - sql.prepare("Delete from DataStore where lid=:lid and key=:key"); - sql.bindValue(":lid", lid); + sql.prepare("Delete from DataStore where lid in (" + lids + ") and key=:key"); sql.bindValue(":key", NOTE_INDEX_NEEDED); sql.exec(); } - - void NoteIndexer::indexResource(qint32 lid) { // Since this can be called from multiple threads, we need to know which DB connection we are using. diff --git a/src/utilities/noteindexer.h b/src/utilities/noteindexer.h index deb5bdd9..90e75dc5 100644 --- a/src/utilities/noteindexer.h +++ b/src/utilities/noteindexer.h @@ -30,6 +30,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include #include #include +#include #include "src/qevercloud/QEverCloud/headers/QEverCloud.h" using namespace qevercloud; @@ -44,7 +45,10 @@ class NoteIndexer public: NoteIndexer(DatabaseConnection *db); void indexNote(qint32 lid); + void indexNotes(const QList &lids); void addTextIndex(qint32 lid, QString content); + void addTextIndices(const QList ¬eLids, + const QStringList &contentList); void indexResource(qint32 lid); void indexRecognition(qint32 reslid, Resource &r); void indexPdf(qint32 reslid); From 4a2e128528c2badef4098512c27e55fbb5bed6cd Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Tue, 17 Jan 2023 21:11:06 +0800 Subject: [PATCH 30/61] Optimise the SQL statements about batch deleting. --- src/gui/ntableview.cpp | 22 ++++-- src/sql/notetable.cpp | 142 ++++++++++++++++++++++++++++++-------- src/sql/notetable.h | 2 + src/sql/resourcetable.cpp | 54 ++++++++++----- src/sql/resourcetable.h | 1 + 5 files changed, 167 insertions(+), 54 deletions(-) diff --git a/src/gui/ntableview.cpp b/src/gui/ntableview.cpp index ec3d2e4a..45dbce3b 100644 --- a/src/gui/ntableview.cpp +++ b/src/gui/ntableview.cpp @@ -715,18 +715,26 @@ void NTableView::deleteSelectedNotes() { NoteTable ntable(global.db); NSqlQuery sql(global.db); - sql.prepare("Delete from filter where lid=:lid"); + + QString slids = ""; + for (int i = 0; i < lids.size(); i++) { + slids += QString::number(lids[i]) + ","; + } + slids.chop(1); + + sql.prepare("Delete from filter where lid in (" + slids + ")"); + sql.exec(); + sql.finish(); + + ntable.deleteNotes(lids, true); + if (expunged) + ntable.expunge(lids); + for (int i = 0; i < lids.size(); i++) { - ntable.deleteNote(lids[i], true); - if (expunged) - ntable.expunge(lids[i]); - sql.bindValue(":lid", lids[i]); - sql.exec(); delete global.cache[lids[i]]; global.cache.remove(lids[i]); } //transaction.exec("commit"); - sql.finish(); emit(notesDeleted(lids, expunged)); } diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index 8caa4f60..d257c76d 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -1663,6 +1663,72 @@ void NoteTable::deleteNote(qint32 lid, bool isDirty=true) { } +void NoteTable::deleteNotes(const QList &lids, bool isDirty=true) { + QString slids = ""; + QList noteLids; + noteLids.clear(); + for (int i = 0; i < lids.size(); ++i) { + if (lids[i] > 0) { + noteLids.append(lids[i]); + slids += QString::number(lids[i]) + ","; + } + } + if (noteLids.size() == 0) { + return; + } + slids.chop(1); + + NSqlQuery query(db); + db->lockForWrite(); + + query.prepare("delete from DataStore where key=:key and lid in (" + + slids + ")"); + query.bindValue(":key", NOTE_ACTIVE); + query.exec(); + + query.prepare("delete from DataStore where key=:key and lid in (" + + slids + ")"); + query.bindValue(":key", NOTE_DELETED_DATE); + query.exec(); + + if (isDirty) { + query.prepare("delete from DataStore where key=:key and lid in (" + slids + ")"); + query.bindValue(":key", NOTE_ISDIRTY); + query.exec(); + } + + QString values = ""; + for (int i = 0; i < noteLids.size(); ++i) { + values += "(" + QString::number(noteLids[i]) + "," + + QString::number(NOTE_ACTIVE) + "," + + QString::number(false) + "),"; + } + values.chop(1); + + query.prepare("Insert into DataStore (lid, key, data) values " + values); + query.exec(); + + query.prepare("update notetable set dateDeleted=strftime('%s','now') where lid in (" + slids + ")"); + query.exec(); + + if (isDirty) { + values = ""; + for (int i = 0; i < noteLids.size(); ++i) { + values += "(" + QString::number(noteLids[i]) + "," + + QString::number(NOTE_ISDIRTY) + "," + + QString::number(true) + "),"; + } + values.chop(1); + + query.prepare("Insert into DataStore (lid, key, data) values " + values); + query.exec(); + } + + query.finish(); + db->unlock(); +} + + void NoteTable::restoreNote(qint32 lid, bool isDirty=true) { if (lid <=0) @@ -1731,34 +1797,10 @@ qint32 NoteTable::getAllDeleted(QList &lids) { void NoteTable::expunge(qint32 lid) { - // Expunge the thumbnail - QString thumbnail = global.fileManager.getThumbnailDirPath() + QString::number(lid) + ".png"; - QFile f(thumbnail); - if (f.exists()) { - QDir d; - d.remove(thumbnail); - } - - Note note; - this->get(note, lid, true, false); - ResourceTable resTable(db); - QList resources; - if (note.resources.isSet()) - resources = note.resources; - for (int i=0; ilockForWrite(); - query.prepare("delete from DataStore where lid=:lid"); - query.bindValue(":lid", lid); - query.exec(); - query.prepare("delete from NoteTable where lid=:lid"); - query.bindValue(":lid", lid); - query.exec(); - query.finish(); - db->unlock(); + QList lids; + lids.clear(); + lids.append(lid); + expunge(lids); } @@ -1774,6 +1816,50 @@ void NoteTable::expunge(QString guid) { } +void NoteTable::expunge(const QList &lids) { + QString slids = ""; + + for (int i = 0; i < lids.size(); ++i) { + // Expunge the thumbnail + QString thumbnail = global.fileManager.getThumbnailDirPath() + + QString::number(lids[i]) + ".png"; + QFile f(thumbnail); + if (f.exists()) { + QDir d; + d.remove(thumbnail); + } + + Note note; + this->get(note, lids[i], true, false); + ResourceTable resTable(db); + QList resources; + if (note.resources.isSet()) + resources = note.resources; + + QList resourceGuids; + resourceGuids.clear(); + for (int i=0; ilockForWrite(); + + query.prepare("delete from DataStore where lid in (" + slids + ")"); + query.exec(); + + query.prepare("delete from NoteTable where lid in (" + slids + ")"); + query.exec(); + + query.finish(); + db->unlock(); +} + // Add to the deletion queue void NoteTable::addToDeleteQueue(qint32 lid, Note n) { diff --git a/src/sql/notetable.h b/src/sql/notetable.h index 29f71cc7..0f4ee353 100644 --- a/src/sql/notetable.h +++ b/src/sql/notetable.h @@ -178,8 +178,10 @@ class NoteTable void addTag(qint32 lid, qint32 tag, bool isDirty); // Add a tag to a note void rebuildNoteListTags(qint32 lid); // Update the note's tags in the display table void deleteNote(qint32 lid, bool isDirty); // mark a note for deletion + void deleteNotes(const QList &lids, bool isDirty); // mark a list of notes for deletion void restoreNote(qint32 lid, bool isDirty); // unmark a note for deletion void expunge(qint32 lid); // expunge a note permanently + void expunge(const QList &lids); // expunge multiple notes permanently void expunge(QString guid); // expunge a note permanently void expunge(string guid); // expunge a note permanently void pinNote(string guid, bool value); // pin the current note diff --git a/src/sql/resourcetable.cpp b/src/sql/resourcetable.cpp index 9daac5c9..9d350721 100644 --- a/src/sql/resourcetable.cpp +++ b/src/sql/resourcetable.cpp @@ -862,33 +862,51 @@ bool ResourceTable::getResourceList(QList &resourceList, qint32 noteLid) // Permanently delete a resource void ResourceTable::expunge(qint32 lid) { + QList lids; + lids.clear(); + lids.append(lid); + expunge(lids); +} + - if (!this->exists(lid)) { +// Permanently delete resource files of a list of notes +void ResourceTable::expunge(const QList &lids) { + QString slids = ""; + for (int i = 0; i < lids.size(); ++i) { + if (this->exists(lids[i])) { + slids += lids[i] + ","; + } + } + if (slids == "") { return; } + slids.chop(1); + NSqlQuery query(db); db->lockForWrite(); - query.prepare("delete from DataStore where lid=:lid"); - query.bindValue(":lid", lid); + query.prepare("delete from DataStore where lid in (" + slids + ")"); query.exec(); + query.finish(); db->unlock(); - // Delete the physical files (resource) QDir myDir(global.fileManager.getDbaDirPath()); - QString num = QString::number(lid); - QStringList filter; - filter.append(num + QString(".*")); - QStringList list = myDir.entryList(filter, QDir::Files, QDir::NoSort); // filter resource files - for (int i = 0; i < list.size(); i++) { - myDir.remove(list[i]); - } - - // Delete the physical files (thumbnail) QDir myTDir(global.fileManager.getThumbnailDirPath()); - list = myTDir.entryList(filter, QDir::Files, QDir::NoSort); // filter resource files - for (int i = 0; i < list.size(); i++) { - myTDir.remove(list[i]); + for (int i = 0; i < lids.size(); ++i) { + // Delete the physical files (resource) + QString num = QString::number(lids[i]); + QStringList filter; + filter.append(num + QString(".*")); + QStringList list = myDir.entryList(filter, QDir::Files, QDir::NoSort); // filter resource files + for (int i = 0; i < list.size(); i++) { + myDir.remove(list[i]); + } + + // Delete the physical files (thumbnail) + list = myTDir.entryList(filter, QDir::Files, QDir::NoSort); // filter resource files + for (int i = 0; i < list.size(); i++) { + myTDir.remove(list[i]); + } } } @@ -918,9 +936,7 @@ void ResourceTable::expungeByNote(qint32 notebookLid) { query.finish(); } - for (int i = 0; i < lids.size(); i++) { - expunge(lids[i]); - } + expunge(lids); } diff --git a/src/sql/resourcetable.h b/src/sql/resourcetable.h index bf5f19a0..143f1f40 100644 --- a/src/sql/resourcetable.h +++ b/src/sql/resourcetable.h @@ -110,6 +110,7 @@ class ResourceTable qint32 add(qint32 lid, Resource &t, bool isDirty, int noteLid=0); // Add a new resource void setIndexNeeded(qint32 lid, bool indexNeeded); // flag if a resource needs reindexing void expunge(int lid); // erase a resource + void expunge(const QList &lids); // erase a list of resources void expunge(QString guid); // erase a resource void updateResourceHash(qint32 lid, QByteArray newhash); // Update a resource's hash value qint32 addStub(qint32 resLid, qint32 noteLid); // Add a basic "stub" record. Useful when duplicating notes From ee12107334d2a9c7467169c78967e1bbcbe20c2b Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 20 Jan 2023 22:00:08 +0800 Subject: [PATCH 31/61] Abstract the process of construction of the sql statements to functions. --- src/sql/notetable.cpp | 148 ++++++++++++++++++++++-------------------- src/sql/notetable.h | 7 +- 2 files changed, 85 insertions(+), 70 deletions(-) diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index d257c76d..44eac6e1 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -22,7 +22,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "configstore.h" #include "notebooktable.h" #include "linkednotebooktable.h" -#include "src/sql/nsqlquery.h" #include "tagtable.h" #include "src/global.h" #include "src/utilities/noteindexer.h" @@ -1114,6 +1113,7 @@ void NoteTable::setIndexNeeded(qint32 lid, bool indexNeeded) { // Set if notes need to be indexed void NoteTable::setIndexNeeded(const QList &indexNoteLids, bool indexNeeded) { QLOG_TRACE_IN(); + QList noteLids; noteLids.clear(); for (int i = 0; i < indexNoteLids.size(); ++i) { @@ -1124,22 +1124,18 @@ void NoteTable::setIndexNeeded(const QList &indexNoteLids, bool indexNee } } - if (noteLids.size() == 0) { + QString lids = joinLids(noteLids); + if (lids == "") { QLOG_TRACE_OUT(); return; } - QString lids = ""; - for (int i = 0; i < noteLids.size(); ++i) { - lids += QString::number(noteLids[i]) + ","; - } - lids.chop(1); // chop the trailing ',' - NSqlQuery query(db); db->lockForWrite(); query.prepare("Delete from DataStore where lid in (" + lids + ") and key=:key"); + bindLids(query, noteLids); query.bindValue(":key", NOTE_INDEX_NEEDED); query.exec(); @@ -1152,15 +1148,9 @@ void NoteTable::setIndexNeeded(const QList &indexNoteLids, bool indexNee return; } - QString values = ""; - for (int i = 0; i < noteLids.size(); ++i) { - values += "(" + QString::number(noteLids[i]) + "," + - QString::number(NOTE_INDEX_NEEDED) + "," + - QString::number(indexNeeded) + "),"; - } - values.chop(1); - + QString values = joinValues(noteLids, NOTE_INDEX_NEEDED, indexNeeded); query.prepare("Insert into DataStore (lid, key, data) values " + values); + bindLids(query, noteLids); query.exec(); query.finish(); @@ -1218,19 +1208,14 @@ void NoteTable::updateNotebook(const QList ¬eLids, return; } - int n = noteLids.size(); - - QString lids = ""; - for (int i = 0; i < n; ++i) { - lids += QString::number(noteLids[i]) + ","; - } - lids.chop(1); // chop the trailing ',' + QString lids = joinLids(noteLids); NSqlQuery query(db); db->lockForWrite(); query.prepare("Update DataStore set data=:notebookLid where lid in (" + lids + ") and key=:key;"); + bindLids(query, noteLids); query.bindValue(":notebookLid", notebookLid); query.bindValue(":key", NOTE_NOTEBOOK_LID); query.exec(); @@ -1242,12 +1227,13 @@ void NoteTable::updateNotebook(const QList ¬eLids, QString bookName = book.name; query.prepare("Update NoteTable set notebook=:name where lid in (" + lids + ")"); - + bindLids(query, noteLids); query.bindValue(":name", bookName); query.exec(); query.prepare("Update NoteTable set notebookLid=:nlid where lid in (" + lids + ")"); + bindLids(query, noteLids); query.bindValue(":nlid", notebookLid); query.exec(); @@ -1531,14 +1517,10 @@ void NoteTable::setDirty(qint32 lid, bool dirty, bool setDateUpdated) { // Set multiple notes as dirty. void NoteTable::setDirty(const QList ¬eLids, bool dirty, bool setDateUpdated) { - QString lids = ""; - for (int i = 0; i < noteLids.size(); ++i) { - lids += QString::number(noteLids[i]) + ","; - } + QString lids = joinLids(noteLids); if (lids == "") { return; } - lids.chop(1); NSqlQuery query(db); db->lockForWrite(); @@ -1550,12 +1532,14 @@ void NoteTable::setDirty(const QList ¬eLids, bool dirty, query.prepare("Update DataStore set data=:value where lid in (" + lids + ") and key=:key"); + bindLids(query, noteLids); query.bindValue(":key", NOTE_UPDATED_DATE); query.bindValue(":value", dt); query.exec(); query.prepare("Update NoteTable set dateUpdated=:value where lid in (" + lids + ")"); + bindLids(query, noteLids); query.bindValue(":value", dt); query.exec(); } @@ -1566,25 +1550,21 @@ void NoteTable::setDirty(const QList ¬eLids, bool dirty, // need to do anything more. query.prepare("Update NoteTable set isDirty=:isDirty where lid in (" + lids + ") and isDirty!=:isDirty"); + bindLids(query, noteLids); query.bindValue(":isDirty", dirty); query.exec(); query.prepare("Delete from DataStore where lid in (" + lids + ") and key=:key and data!=:data"); + bindLids(query, noteLids); query.bindValue(":key", NOTE_ISDIRTY); query.bindValue(":data", dirty); query.exec(); if (dirty) { - QString values = ""; - for (int i = 0; i < noteLids.size(); ++i) { - values += "(" + QString::number(noteLids[i]) + "," - + QString::number(NOTE_ISDIRTY) + "," + - QString::number(dirty) + "),"; - } - values.chop(1); - + QString values = joinValues(noteLids, NOTE_ISDIRTY, dirty); query.prepare("Insert into DataStore (lid, key, data) values " + values); + bindLids(query, noteLids); query.exec(); } @@ -1592,7 +1572,7 @@ void NoteTable::setDirty(const QList ¬eLids, bool dirty, db->unlock(); if (dirty) { - setIndexNeeded(const_cast &>(noteLids), true); + setIndexNeeded(noteLids, true); } } @@ -1663,64 +1643,48 @@ void NoteTable::deleteNote(qint32 lid, bool isDirty=true) { } -void NoteTable::deleteNotes(const QList &lids, bool isDirty=true) { - QString slids = ""; - QList noteLids; - noteLids.clear(); - for (int i = 0; i < lids.size(); ++i) { - if (lids[i] > 0) { - noteLids.append(lids[i]); - slids += QString::number(lids[i]) + ","; - } - } - if (noteLids.size() == 0) { +void NoteTable::deleteNotes(const QList ¬eLids, bool isDirty=true) { + QString slids = joinLids(noteLids); + if (slids == "") { return; } - slids.chop(1); NSqlQuery query(db); db->lockForWrite(); query.prepare("delete from DataStore where key=:key and lid in (" + slids + ")"); + bindLids(query, noteLids); query.bindValue(":key", NOTE_ACTIVE); query.exec(); query.prepare("delete from DataStore where key=:key and lid in (" + slids + ")"); + bindLids(query, noteLids); query.bindValue(":key", NOTE_DELETED_DATE); query.exec(); if (isDirty) { query.prepare("delete from DataStore where key=:key and lid in (" + slids + ")"); + bindLids(query, noteLids); query.bindValue(":key", NOTE_ISDIRTY); query.exec(); } - QString values = ""; - for (int i = 0; i < noteLids.size(); ++i) { - values += "(" + QString::number(noteLids[i]) + "," - + QString::number(NOTE_ACTIVE) + "," + - QString::number(false) + "),"; - } - values.chop(1); + QString values = joinValues(noteLids, NOTE_ACTIVE, false); query.prepare("Insert into DataStore (lid, key, data) values " + values); + bindLids(query, noteLids); query.exec(); query.prepare("update notetable set dateDeleted=strftime('%s','now') where lid in (" + slids + ")"); + bindLids(query, noteLids); query.exec(); if (isDirty) { - values = ""; - for (int i = 0; i < noteLids.size(); ++i) { - values += "(" + QString::number(noteLids[i]) + "," - + QString::number(NOTE_ISDIRTY) + "," + - QString::number(true) + "),"; - } - values.chop(1); - + values = joinValues(noteLids, NOTE_ISDIRTY, true); query.prepare("Insert into DataStore (lid, key, data) values " + values); + bindLids(query, noteLids); query.exec(); } @@ -1817,7 +1781,10 @@ void NoteTable::expunge(QString guid) { void NoteTable::expunge(const QList &lids) { - QString slids = ""; + QString slids = joinLids(lids); + if (slids == "") { + return; + } for (int i = 0; i < lids.size(); ++i) { // Expunge the thumbnail @@ -1842,18 +1809,17 @@ void NoteTable::expunge(const QList &lids) { resourceGuids.append(resources[i].guid.ref().toInt()); } resTable.expunge(resourceGuids); - - slids += QString::number(lids[i]) + ","; } - slids.chop(1); NSqlQuery query(db); db->lockForWrite(); query.prepare("delete from DataStore where lid in (" + slids + ")"); + bindLids(query, lids); query.exec(); query.prepare("delete from NoteTable where lid in (" + slids + ")"); + bindLids(query, lids); query.exec(); query.finish(); @@ -2822,3 +2788,47 @@ qlonglong NoteTable::getSize(qint32 lid) { } return returnValue; } + + +QString NoteTable::joinLids(const QList ¬eLids) { + QString lids = ""; + + for (int i = 0; i < noteLids.size(); ++i) { + if (noteLids[i] > 0) { + lids += ":lid" + QString::number(i) + ","; + } + } + if (lids != "") { + lids.chop(1); // chop the trailing ',' + } + + return lids; +} + + +QString NoteTable::joinValues(const QList ¬eLids, + int key, bool value) { + QString values = ""; + for (int i = 0; i < noteLids.size(); ++i) { + if (noteLids[i] > 0) { + values += "(:lid" + QString::number(i) + "," + + QString::number(key) + "," + + QString::number(value) + "),"; + } + } + if (values != "") { + values.chop(1); // chop the trailing ',' + } + + return values; +} + + +void NoteTable::bindLids(NSqlQuery &sql, const QList ¬eLids) { + for (int i = 0; i < noteLids.size(); ++i) { + if (noteLids[i] > 0) { + sql.bindValue(":lid" + QString::number(i), + noteLids[i]); + } + } +} diff --git a/src/sql/notetable.h b/src/sql/notetable.h index 0f4ee353..86d623f3 100644 --- a/src/sql/notetable.h +++ b/src/sql/notetable.h @@ -34,6 +34,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include #include #include "src/sql/databaseconnection.h" +#include "src/sql/nsqlquery.h" #include "src/qevercloud/QEverCloud/headers/QEverCloud.h" using namespace qevercloud; @@ -93,6 +94,10 @@ class NoteTable { private: + QString joinLids(const QList ¬eLids); + QString joinValues(const QList ¬eLids, int key, bool value); + void bindLids(NSqlQuery &sql, const QList ¬eLids); + DatabaseConnection *db; public: @@ -178,7 +183,7 @@ class NoteTable void addTag(qint32 lid, qint32 tag, bool isDirty); // Add a tag to a note void rebuildNoteListTags(qint32 lid); // Update the note's tags in the display table void deleteNote(qint32 lid, bool isDirty); // mark a note for deletion - void deleteNotes(const QList &lids, bool isDirty); // mark a list of notes for deletion + void deleteNotes(const QList ¬eLids, bool isDirty); // mark a list of notes for deletion void restoreNote(qint32 lid, bool isDirty); // unmark a note for deletion void expunge(qint32 lid); // expunge a note permanently void expunge(const QList &lids); // expunge multiple notes permanently From b3d53508034638f1d2260aea22ad4a918b1a5eb4 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 20 Jan 2023 22:12:59 +0800 Subject: [PATCH 32/61] Correct the binding of parameters of sql statements in addTextIndices(), noteindexer.cpp. --- src/utilities/noteindexer.cpp | 40 +++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/utilities/noteindexer.cpp b/src/utilities/noteindexer.cpp index 691a8249..87cb5959 100644 --- a/src/utilities/noteindexer.cpp +++ b/src/utilities/noteindexer.cpp @@ -117,25 +117,53 @@ void NoteIndexer::addTextIndex(int lid, QString content) { void NoteIndexer::addTextIndices(const QList ¬eLids, const QStringList &contentList) { QString values = "", lids = ""; + for (int i = 0; i < noteLids.size(); ++i) { - lids += noteLids[i] + ","; - values += "(" + QString::number(noteLids[i]) + "," + - QString::number(100) + "," + "text" + "," + - global.normalizeTermForSearchAndIndex(contentList[i]) + "),"; + if (noteLids[i] <= 0) { + continue; + } + lids += ":lid" + QString::number(i) + ","; + values += "(:lid" + QString::number(i) + "," + + ":weight,:source," + + ":content" + QString::number(i) + "),"; + } + + // chop the trailing ',' + if (lids != "") { + lids.chop(1); + } + + if (values != "") { + values.chop(1); } - lids.chop(1); - values.chop(1); // chop the trailing ',' // Delete any old content NSqlQuery sql(db); sql.prepare("Delete from SearchIndex where lid in (" + lids + ") and source=:source"); + for (int i = 0; i < noteLids.size(); ++i) { + if (noteLids[i] > 0) { + sql.bindValue(":lid" + QString::number(i), noteLids[i]); + } + } sql.bindValue(":source", "text"); sql.exec(); // Add the new content. it is basically a text version of the note with a weight of 100. sql.prepare("Insert into SearchIndex (lid, weight, source, content) values " + values); + sql.bindValue(":weight", 100); + sql.bindValue(":source", "text"); + for (int i = 0; i < noteLids.size(); ++i) { + if (noteLids[i] <= 0) { + continue; + } + sql.bindValue(":lid" + QString::number(i), + noteLids[i]); + sql.bindValue(":content" + QString::number(i), + global.normalizeTermForSearchAndIndex(contentList[0])); + } + sql.exec(); sql.prepare("Delete from DataStore where lid in (" + lids + ") and key=:key"); From 4868c01629bc89e536e468122b05897e41951af4 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sun, 22 Jan 2023 19:45:47 +0800 Subject: [PATCH 33/61] Optimise the SQL statements about batch restoring. --- src/gui/ntableview.cpp | 7 +++--- src/gui/ntrashtree.cpp | 3 ++- src/sql/notetable.cpp | 50 ++++++++++++++++++++---------------------- src/sql/notetable.h | 1 + 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/gui/ntableview.cpp b/src/gui/ntableview.cpp index 45dbce3b..a7b85c43 100644 --- a/src/gui/ntableview.cpp +++ b/src/gui/ntableview.cpp @@ -672,13 +672,12 @@ void NTableView::restoreSelectedNotes() { NSqlQuery sql(global.db); //transaction.exec("begin"); sql.prepare("Delete from filter where lid=:lid"); + sql.exec(); + sql.finish(); + ntable.restoreNotes(lids, true); for (int i = 0; i < lids.size(); i++) { - ntable.restoreNote(lids[i], true); - sql.bindValue(":lid", lids[i]); - sql.exec(); global.cache.remove(lids[i]); } - sql.finish(); emit(notesRestored(lids)); } diff --git a/src/gui/ntrashtree.cpp b/src/gui/ntrashtree.cpp index 9cf184ca..6a9d42e8 100644 --- a/src/gui/ntrashtree.cpp +++ b/src/gui/ntrashtree.cpp @@ -225,8 +225,9 @@ void NTrashTree::restoreAll() { NoteTable ntable(global.db); QList lids; ntable.getAllDeleted(lids); + ntable.restoreNotes(lids, true); + for (int i=0; i ¬eLids, bool isDirty=true) { } - void NoteTable::restoreNote(qint32 lid, bool isDirty=true) { - if (lid <=0) + QList lids; + lids.clear(); + lids.append(lid); + restoreNotes(lids, isDirty); +} + + +void NoteTable::restoreNotes(const QList &lids, bool isDirty=true) { + QString slids = joinLids(lids); + if (slids == "") { return; + } NSqlQuery query(db); db->lockForWrite(); - query.prepare("delete from DataStore where key=:key and lid=:lid"); + + query.prepare("Update DataStore set data=:data where key=:key and lid in (" + slids + ")"); + query.bindValue(":data", true); query.bindValue(":key", NOTE_ACTIVE); - query.bindValue(":lid", lid); + bindLids(query, lids); query.exec(); - query.prepare("delete from DataStore where key=:key and lid=:lid"); + query.prepare("delete from DataStore where key=:key and lid in (" + + slids + ")"); query.bindValue(":key", NOTE_DELETED_DATE); - query.bindValue(":lid", lid); + bindLids(query, lids); query.exec(); if (isDirty) { - query.prepare("delete from DataStore where key=:key and lid=:lid"); + query.prepare("Update DataStore set data=:data where key=:key and lid in (" + slids + ")"); query.bindValue(":key", NOTE_ISDIRTY); - query.bindValue(":lid", lid); + query.bindValue(":data", true); + bindLids(query, lids); query.exec(); } - query.prepare("Insert into DataStore (lid, key, data) values (:lid, :key, :data)"); - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ACTIVE); - query.bindValue(":data", true); - query.exec(); - - query.prepare("update notetable set dateDeleted=0 where lid=:lid"); - query.bindValue(":lid", lid); + query.prepare("Update notetable set dateDeleted=0 where lid in (" + + slids + ")"); + bindLids(query, lids); query.exec(); - if (isDirty) { - query.prepare("Insert into DataStore (lid, key, data) values (:lid, :key, :data)"); - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ISDIRTY); - query.bindValue(":data", true); - query.exec(); - - } query.finish(); db->unlock(); } - - qint32 NoteTable::getAllDeleted(QList &lids) { db->lockForRead(); lids.clear(); diff --git a/src/sql/notetable.h b/src/sql/notetable.h index 86d623f3..8b72cafd 100644 --- a/src/sql/notetable.h +++ b/src/sql/notetable.h @@ -185,6 +185,7 @@ class NoteTable void deleteNote(qint32 lid, bool isDirty); // mark a note for deletion void deleteNotes(const QList ¬eLids, bool isDirty); // mark a list of notes for deletion void restoreNote(qint32 lid, bool isDirty); // unmark a note for deletion + void restoreNotes(const QList &lids, bool isDirty); // unmark multiple notes for deletion void expunge(qint32 lid); // expunge a note permanently void expunge(const QList &lids); // expunge multiple notes permanently void expunge(QString guid); // expunge a note permanently From bca0c15105c2287d8a5c773e22b9f0b69399bb17 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sun, 22 Jan 2023 21:50:21 +0800 Subject: [PATCH 34/61] Update changelogs. --- changelog.txt | 5 ++++- debian/changelog | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index b1de7067..8a59fc84 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,7 @@ NixNote (2.1.7) stable; urgency=low + * Optimise the time complexity of batch operations, including moving, deleting, and restoring. + * Add an option: the user can choose to make Nixnote listen to the commands from command line or not. + * Decouple the functions of CmdLineTool(except signalGui()) from shared memory. * Export the stack attribute when exporting the notes. * Add an option: the user can choose to save the ui changes or not when Nixnote exits. * Save the note content when Nixnote receives signal interruption. @@ -9,7 +12,7 @@ NixNote (2.1.7) stable; urgency=low * Make the editor not render the note content when Key_Up or Key_Down is kept being pressed. * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. - * Limited the max length of the recently updated menu of the tray. + * Limited the max length of the recently updated menu and favourite menu of the tray. * Fixed memory leaks caused by Qt Webview. * Fixed: To-do Formatting is not in parity with Official Client - issue #131 * Fixed: In-App Note links don't work when there is an apostrophe in the title - issue #168 diff --git a/debian/changelog b/debian/changelog index b1de7067..8a59fc84 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,7 @@ NixNote (2.1.7) stable; urgency=low + * Optimise the time complexity of batch operations, including moving, deleting, and restoring. + * Add an option: the user can choose to make Nixnote listen to the commands from command line or not. + * Decouple the functions of CmdLineTool(except signalGui()) from shared memory. * Export the stack attribute when exporting the notes. * Add an option: the user can choose to save the ui changes or not when Nixnote exits. * Save the note content when Nixnote receives signal interruption. @@ -9,7 +12,7 @@ NixNote (2.1.7) stable; urgency=low * Make the editor not render the note content when Key_Up or Key_Down is kept being pressed. * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. - * Limited the max length of the recently updated menu of the tray. + * Limited the max length of the recently updated menu and favourite menu of the tray. * Fixed memory leaks caused by Qt Webview. * Fixed: To-do Formatting is not in parity with Official Client - issue #131 * Fixed: In-App Note links don't work when there is an apostrophe in the title - issue #168 From 7d0c65c8ed7c8e257b0afb1b33f4519dbbef7074 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Mon, 23 Jan 2023 21:33:26 +0800 Subject: [PATCH 35/61] Resize the font with macro, which is more reasonable and flexible, when no text is selected. --- src/gui/nbrowserwindow.cpp | 59 +++++++++----------------------------- src/gui/nbrowserwindow.h | 5 ---- 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index c819844c..940ebd58 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -923,15 +923,6 @@ void NBrowserWindow::saveNoteContent() { // The undo edit button was pressed void NBrowserWindow::undoButtonPressed() { this->editor->triggerPageAction(QWebPage::Undo); - - QUndoStack *stack = this->editor->page()->undoStack(); - if (stack->count() >= 1 && stack->text(stack->index() - 1) == "AUTO_EXEC") { - // jump over the AUTO_EXEC QUndoCommand - this->editor->triggerPageAction(QWebPage::Undo); - // undo the inserting of 0 width character - this->editor->triggerPageAction(QWebPage::Undo); - } - this->editor->setFocus(); microFocusChanged(); } @@ -940,15 +931,6 @@ void NBrowserWindow::undoButtonPressed() { // The redo edit button was pressed void NBrowserWindow::redoButtonPressed() { this->editor->triggerPageAction(QWebPage::Redo); - - QUndoStack *stack = this->editor->page()->undoStack(); - if (stack->text(stack->index()) == "AUTO_EXEC") { - // jump over the AUTO_EXEC QUndoCommand - this->editor->triggerPageAction(QWebPage::Redo); - // redo the simulated backspace - this->editor->triggerPageAction(QWebPage::Redo); - } - this->editor->setFocus(); microFocusChanged(); } @@ -1497,25 +1479,29 @@ void NBrowserWindow::fontSizeSelected(int index) { QString text = editor->selectedHtml(); if (text.trimmed() == "") { + QUndoStack *stack = this->editor->page()->undoStack(); + + // Simulate a backspace press down event to delete + // the invisible charactor inserted above. + QKeyEvent *backspacePressed = new QKeyEvent(QKeyEvent::KeyPress, + Qt::Key_Backspace, Qt::NoModifier, ""); + + stack->beginMacro("SetFontSize"); + // Add an invisible charactor in order to focus on the innerhtml // part of the tags added below. If not, the text typed // in after font size changed will be added beyond the // tags scope. text = "‌"; - - QString newText = "" + text + ""; + QString newText = "" + text + ""; QString script2 = QString("document.execCommand('insertHtml', false, '" + newText + "');"); editor->page()->mainFrame()->evaluateJavaScript(script2); - QUndoStack *stack = this->editor->page()->undoStack(); - stack->push(newAutoExecCommand()); + QApplication::sendEvent(editor, backspacePressed); - // Simulate a backspace press down event to delete - // the invisible charactor inserted above. - QKeyEvent *key_press = new QKeyEvent(QKeyEvent::KeyPress, - Qt::Key_Backspace, Qt::NoModifier, ""); - QApplication::sendEvent(editor, key_press); - delete key_press; + stack->endMacro(); + + delete backspacePressed; } else { QString script = QString("document.execCommand('fontSize', false, 5);"); editor->page()->mainFrame()->evaluateJavaScript(script); @@ -2487,8 +2473,6 @@ void NBrowserWindow::clear() { // editor->page()->setContentEditable(false); dateEditor.clear(); - - clearAutoExecCommands(); } @@ -4506,18 +4490,3 @@ void NBrowserWindow::exitPoint(ExitPoint *exit) { QLOG_TRACE_OUT(); } - -QUndoCommand* NBrowserWindow::newAutoExecCommand() { - QUndoCommand *cmd = new QUndoCommand(); - cmd->setText("AUTO_EXEC"); - autoExecCommands.append(cmd); - return cmd; -} - - -void NBrowserWindow::clearAutoExecCommands() { - for (int i = 0; i < autoExecCommands.length(); i++) { - delete autoExecCommands[i]; - } - autoExecCommands.clear(); -} diff --git a/src/gui/nbrowserwindow.h b/src/gui/nbrowserwindow.h index b3a56843..9df77680 100644 --- a/src/gui/nbrowserwindow.h +++ b/src/gui/nbrowserwindow.h @@ -138,9 +138,6 @@ class NBrowserWindow : public QWidget QSplitter *editorSplitter; QHBoxLayout *line1Layout; - // To mark the simulated backspace button events in the undoStack. - QVector autoExecCommands; - void exitPoint(ExitPoint *exit); // get note title from the title UI field - and do some fixup (like discard line feeds) @@ -150,8 +147,6 @@ class NBrowserWindow : public QWidget void htmlCleanup(HtmlCleanupMode mode); void insertDateTimeUsingFormat(const QString &format) const; - QUndoCommand *newAutoExecCommand(); - void clearAutoExecCommands(); void todoSetAllChecked(bool allSelected); public: From d5b9a6c64d37d6bc6f3043b72bf432c47bb14683 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 25 Jan 2023 20:46:44 +0800 Subject: [PATCH 36/61] Optimise the SQL statements about creation of new note. --- src/sql/notetable.cpp | 249 ++++++++++++++++++------------------------ src/sql/notetable.h | 3 +- 2 files changed, 106 insertions(+), 146 deletions(-) diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index 701051c1..2cf9105d 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -162,42 +162,38 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { qint32 lid = l; qint32 notebookLid = account; - query.prepare("Insert into DataStore (lid, key, data) values (:lid, :key, :data)"); if (lid <= 0) { lid = cs.incrementLidCounter(); } + QString values = ""; + QList lids; + lids.clear(); + lids.append(lid); + + QList valueList; + valueList.clear(); + QLOG_DEBUG() << "Adding note; lid=" << lid << ", title=" << (t.title.isSet() ? t.title : "title is empty"); if (t.guid.isSet()) { QString guid = t.guid; QLOG_DEBUG() << "Adding note; guid=" << guid; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_GUID); - query.bindValue(":data", guid); - query.exec(); + values += joinValues(lids, NOTE_GUID, ":guid") + ","; + valueList.append(guid); } - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_INDEX_NEEDED); - query.bindValue(":data", true); - query.exec(); - - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_THUMBNAIL_NEEDED); - query.bindValue(":data", true); - query.exec(); + if (!global.disableThumbnails) { + values += joinValues(lids, NOTE_THUMBNAIL_NEEDED, ":note_thumbnail_needed") + ","; + valueList.append(true); + } if (t.title.isSet()) { - query.bindValue(":lid", lid); QString title = t.title; - query.bindValue(":key", NOTE_TITLE); - query.bindValue(":data", title); - query.exec(); + values += joinValues(lids, NOTE_TITLE, ":title") + ","; + valueList.append(title); } if (t.content.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_CONTENT); QByteArray b; QString content = t.content; #if QT_VERSION < 0x050000 @@ -206,80 +202,60 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { b.append(content); #endif - QLOG_DEBUG_FILE("incoming.enml", content); - - query.bindValue(":data", b); - query.exec(); + values += joinValues(lids, NOTE_CONTENT, ":note_content") + ","; + valueList.append(b); } if (t.contentHash.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_CONTENT_HASH); QByteArray contentHash = t.contentHash; - query.bindValue(":data", contentHash); - query.exec(); + values += joinValues(lids, NOTE_CONTENT_HASH, ":note_content_hash") + ","; + valueList.append(contentHash); } if (t.contentLength.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_CONTENT_LENGTH); qint32 len = t.contentLength; - query.bindValue(":data", len); - query.exec(); + values += joinValues(lids, NOTE_CONTENT_LENGTH, ":note_content_length") + ","; + valueList.append(len); } if (t.updateSequenceNum.isSet()) { - query.bindValue(":lid", lid); qint32 usn = t.updateSequenceNum; - query.bindValue(":key", NOTE_UPDATE_SEQUENCE_NUMBER); - query.bindValue(":data", usn); - query.exec(); + values += joinValues(lids, NOTE_UPDATE_SEQUENCE_NUMBER, ":note_update_sequence_number") + ","; + valueList.append(usn); } if (isDirty) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ISDIRTY); - query.bindValue(":data", isDirty); - query.exec(); + values += joinValues(lids, NOTE_ISDIRTY, ":is_dirty") + ","; + valueList.append(isDirty); } if (t.created.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_CREATED_DATE); qlonglong date = t.created; - query.bindValue(":data",date); - query.exec(); + values += joinValues(lids, NOTE_CREATED_DATE, ":created_date") + ","; + valueList.append(date); } if (t.updated.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_UPDATED_DATE); qlonglong date = t.updated; - query.bindValue(":data", date); - query.exec(); + values += joinValues(lids, NOTE_UPDATED_DATE, ":updated_date") + ","; + valueList.append(date); } if (t.deleted.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_DELETED_DATE); qlonglong date = t.deleted; - query.bindValue(":data", date); - query.exec(); + values += joinValues(lids, NOTE_DELETED_DATE, ":deleted_date") + ","; + valueList.append(date); } if (t.active.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ACTIVE); bool active = t.active; - query.bindValue(":data", active); - query.exec(); + values += joinValues(lids, NOTE_ACTIVE, ":note_ative") + ","; + valueList.append(active); } if (t.notebookGuid.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_NOTEBOOK_LID); NotebookTable notebookTable(db); LinkedNotebookTable linkedTable(db); if (account > 0) @@ -300,8 +276,8 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { notebook.name = ""; notebookTable.add(notebookLid, notebook, false, false); } - query.bindValue(":data", notebookLid); - query.exec(); + values += joinValues(lids, NOTE_NOTEBOOK_LID, ":note_notebook_lid") + ","; + valueList.append(notebookLid); } QList tagGuids; @@ -319,10 +295,8 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { tagTable.add(tagLid, newTag, false, 0); } - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_TAG_LID); - query.bindValue(":data", tagLid); - query.exec(); + values += joinValues(lids, NOTE_TAG_LID, ":note_tag_lid") + ","; + valueList.append(tagLid); } QList resources; @@ -349,10 +323,9 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { if (r.mime.isSet()) { QString mime = r.mime; if (!mime.startsWith("image/") && mime != "vnd.evernote.ink") { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_HAS_ATTACHMENT); - query.bindValue(":data", true); - query.exec(); + values += joinValues(lids, NOTE_HAS_ATTACHMENT, ":note_has_attachment") + ","; + valueList.append(true); + break; } } } @@ -360,102 +333,74 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { if (t.attributes.isSet()) { NoteAttributes na = t.attributes; if (na.subjectDate.isSet()) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_SUBJECT_DATE); qlonglong ts = na.subjectDate; - query.bindValue(":data",ts); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_SUBJECT_DATE, ":subject_date") + ","; + valueList.append(ts); } if (na.latitude.isSet()) { double lat = na.latitude; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_LATITUDE); - query.bindValue(":data", lat); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_LATITUDE, ":lat") + ","; + valueList.append(lat); } if (na.longitude.isSet()) { double lon = na.longitude; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_LONGITUDE); - query.bindValue(":data", lon); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_LONGITUDE, ":lon") + ","; + valueList.append(lon); } if (na.altitude.isSet()) { double alt = na.altitude; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_ALTITUDE); - query.bindValue(":data", alt); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_ALTITUDE, ":alt") + ","; + valueList.append(alt); } if (na.author.isSet()) { QString author = na.author; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_AUTHOR); - query.bindValue(":data", author); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_AUTHOR, ":author") + ","; + valueList.append(author); } if (na.source.isSet()) { QString source = na.source; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_SOURCE); - query.bindValue(":data", source); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE, ":source") + ","; + valueList.append(source); } if (na.sourceURL.isSet()) { QString sourceURL = na.sourceURL; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_SOURCE_URL); - query.bindValue(":data", sourceURL); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE_URL, ":sourceURL") + ","; + valueList.append(sourceURL); } if (na.sourceApplication.isSet()) { QString sourceApplication = na.sourceApplication; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_SOURCE_APPLICATION); - query.bindValue(":data", sourceApplication); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE_APPLICATION, ":sourceApplication") + ","; + valueList.append(sourceApplication); } if (na.shareDate.isSet()) { double date = na.shareDate; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_SHARE_DATE); - query.bindValue(":data",date); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_SHARE_DATE, ":share_date") + ","; + valueList.append(date); } if (na.placeName.isSet()) { QString placename = na.placeName; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_PLACE_NAME); - query.bindValue(":data", placename); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_PLACE_NAME, ":placename") + ","; + valueList.append(placename); } if (na.contentClass.isSet()) { QString cc = na.contentClass; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_CONTENT_CLASS); - query.bindValue(":data", cc); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_CONTENT_CLASS, ":content_class") + ","; + valueList.append(cc); } if (na.reminderTime.isSet()) { double rt = na.reminderTime; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_REMINDER_TIME); - query.bindValue(":data",rt); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_TIME, ":reminder_time") + ","; + valueList.append(rt); } if (na.reminderDoneTime.isSet()) { double rt = na.reminderDoneTime; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_REMINDER_DONE_TIME); - query.bindValue(":data", rt); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_DONE_TIME, ":reminder_done_time") + ","; + valueList.append(rt); } if (na.reminderOrder.isSet()) { bool rt = na.reminderOrder; - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_ATTRIBUTE_REMINDER_ORDER); - query.bindValue(":data", rt); - query.exec(); + values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_ORDER, ":reminder_order") + ","; + valueList.append(rt); } } @@ -468,24 +413,18 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { content = ""; if (content.contains("")) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_HAS_TODO_UNCOMPLETED); - query.bindValue(":data", true); - query.exec(); + values += joinValues(lids, NOTE_HAS_TODO_UNCOMPLETED, ":note_has_todo_uncompleted") + ","; + valueList.append(true); } } query.finish(); @@ -495,14 +434,24 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { // Experimental index helper if (global.enableIndexing) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_INDEX_NEEDED); - query.bindValue(":data", true); - query.exec(); + values += joinValues(lids, NOTE_INDEX_NEEDED, ":note_index_needed") + ","; + valueList.append(true); } else { NoteIndexer indexer(db); indexer.indexNote(lid); } + if (values != "") { + values.chop(1); + } + + QString sql = "Insert into DataStore (lid, key, data) values " + values; + query.prepare(sql); + bindLids(query, lids); + for (int i = 0; i < valueList.size(); ++i) { + query.bindValue(i*2 + 1, valueList[i]); + } + query.exec(); + return lid; } @@ -2805,15 +2754,25 @@ QString NoteTable::joinLids(const QList ¬eLids) { QString NoteTable::joinValues(const QList ¬eLids, - int key, bool value) { + int key, const QVariant &value) { QString values = ""; for (int i = 0; i < noteLids.size(); ++i) { - if (noteLids[i] > 0) { - values += "(:lid" + QString::number(i) + "," + - QString::number(key) + "," + - QString::number(value) + "),"; + if (noteLids[i] <= 0) { + continue; + } + values += "(:lid" + QString::number(i) + "," + + QString::number(key) + ","; + + if (value.type() == QVariant::Bool || + value.type() == QVariant::Int) { + values += QString::number(value.toInt()) + "),"; + } else if (value.type() == QVariant::LongLong) { + values += QString::number(value.toLongLong()) + "),"; + } else { + values += value.toString() + "),"; } } + if (values != "") { values.chop(1); // chop the trailing ',' } diff --git a/src/sql/notetable.h b/src/sql/notetable.h index 8b72cafd..2f2604ac 100644 --- a/src/sql/notetable.h +++ b/src/sql/notetable.h @@ -95,7 +95,8 @@ class NoteTable private: QString joinLids(const QList ¬eLids); - QString joinValues(const QList ¬eLids, int key, bool value); + QString joinValues(const QList ¬eLids, + int key, const QVariant &value); void bindLids(NSqlQuery &sql, const QList ¬eLids); DatabaseConnection *db; From c2d62059b80e43bedcc0b1bc1a76d4e54826dee8 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 25 Jan 2023 21:51:59 +0800 Subject: [PATCH 37/61] In NTrashTree::expungeAll(), expunge the notes in batch, instead of one by one. --- src/gui/ntrashtree.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/ntrashtree.cpp b/src/gui/ntrashtree.cpp index 6a9d42e8..b21b697b 100644 --- a/src/gui/ntrashtree.cpp +++ b/src/gui/ntrashtree.cpp @@ -253,11 +253,10 @@ void NTrashTree::expungeAll() { NoteTable ntable(global.db); QList lids; ntable.getAllDeleted(lids); + ntable.expunge(lids); for (int i=0; i Date: Thu, 26 Jan 2023 18:55:47 +0800 Subject: [PATCH 38/61] Merge the sql statements in NoteTable::updateNoteContent() for better effiency. --- src/sql/notetable.cpp | 122 ++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index 2cf9105d..8b6f474a 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -1897,99 +1897,91 @@ void NoteTable::updateNoteContent(qint32 lid, QString content, bool isDirty) { query.bindValue(":key", NOTE_CONTENT); query.exec(); - // Update the note size - query.prepare("Delete from datastore where lid=:lid and key=:key"); - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_CONTENT_LENGTH); - query.exec(); - query.prepare("Insert into datastore (lid, key, data) values (:lid, :key, :content)"); + // Delete values from the table of datastore, to prepare to insert later. + query.prepare("Delete from datastore where lid=:lid and \ + (key=:content_length or key=:has_todo_completed or \ + key=:has_todo_uncompleted or key=:has_encrypt or \ + key=:note_index_needed)"); query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_CONTENT_LENGTH); - query.bindValue(":content", content.length()); + query.bindValue(":content_length", NOTE_CONTENT_LENGTH); + query.bindValue(":has_todo_completed", NOTE_HAS_TODO_COMPLETED); + query.bindValue(":has_todo_uncompleted", NOTE_HAS_TODO_UNCOMPLETED); + query.bindValue(":has_encrypt", NOTE_HAS_ENCRYPT); + query.bindValue(":note_index_needed", NOTE_INDEX_NEEDED); query.exec(); - // Make sure we don't have a todo - NSqlQuery query2(db); - query2.prepare("Update NoteTable set hasTodo=0 where lid=:lid"); - query2.bindValue(":lid", lid); - query2.exec(); - query2.prepare("delete from datastore where lid=:lid and (key=:key1 or key=:key2)"); - query2.bindValue(":lid", lid); - query2.bindValue(":key1", NOTE_HAS_TODO_COMPLETED); - query2.bindValue(":key2", NOTE_HAS_TODO_UNCOMPLETED); - query2.exec(); - - query2.prepare("Update NoteTable set hasEncryption=0 where lid=:lid"); - query2.bindValue(":lid", lid); - query2.exec(); - query2.prepare("delete from datastore where lid=:lid and key=:key"); - query2.bindValue(":lid", lid); - query2.bindValue(":key", NOTE_HAS_ENCRYPT); - query2.exec(); + // Insert values into the table of datastore. + QString sql = "Insert into datastore (lid, key, data) values "; + // 'values' is the value part in string in the sql statement. + QString values = "(:lid, :key_content_length, :value_content_length),"; + // 'valueList' contains all the real parameters of + // the sql statement, including the keys used in + // DataStore and values correspoding to them. + QList valueList; + valueList.clear(); if (content.contains("")) { - query.bindValue(":lid", lid); - query.bindValue(":key", NOTE_HAS_TODO_UNCOMPLETED); - query.exec(); + values += "(:lid, :key_has_todo_uncompleted, :value_has_todo_uncompleted),"; + valueList.append(NOTE_HAS_TODO_UNCOMPLETED); + valueList.append(true); } - NSqlQuery query2(db); - query2.prepare("Update NoteTable set hasTodo=:value where lid=:lid"); - query2.bindValue(":lid", lid); - query2.bindValue(":value", 1); - query2.exec(); } if (content.contains("getSize(lid); - NSqlQuery query3(db); - query3.prepare("Update notetable set size=:size where lid=:lid"); - query3.bindValue(":size", totalsize); - query3.bindValue(":lid", lid); - query3.exec(); + query.bindValue(":size", totalsize); + query.exec(); + query.finish(); db->unlock(); + setDirty(lid, isDirty); } From eb84398d3df45eae397767d03374d7b42e177a62 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 28 Jan 2023 17:05:36 +0800 Subject: [PATCH 39/61] Bind the lid in a more compatible way; convert QVariant variable to real types when calling bindValue. --- src/sql/notetable.cpp | 99 +++++++++++++++++++++++++++---------------- src/sql/notetable.h | 2 +- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index 8b6f474a..13b8f87a 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -175,21 +175,22 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { valueList.clear(); QLOG_DEBUG() << "Adding note; lid=" << lid << ", title=" << (t.title.isSet() ? t.title : "title is empty"); + int lidSeqNum = 0; if (t.guid.isSet()) { QString guid = t.guid; QLOG_DEBUG() << "Adding note; guid=" << guid; - values += joinValues(lids, NOTE_GUID, ":guid") + ","; + values += joinValues(lids, NOTE_GUID, ":guid", lidSeqNum++) + ","; valueList.append(guid); } if (!global.disableThumbnails) { - values += joinValues(lids, NOTE_THUMBNAIL_NEEDED, ":note_thumbnail_needed") + ","; + values += joinValues(lids, NOTE_THUMBNAIL_NEEDED, ":note_thumbnail_needed", lidSeqNum++) + ","; valueList.append(true); } if (t.title.isSet()) { QString title = t.title; - values += joinValues(lids, NOTE_TITLE, ":title") + ","; + values += joinValues(lids, NOTE_TITLE, ":title", lidSeqNum++) + ","; valueList.append(title); } @@ -204,54 +205,54 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { QLOG_DEBUG_FILE("incoming.enml", content); - values += joinValues(lids, NOTE_CONTENT, ":note_content") + ","; + values += joinValues(lids, NOTE_CONTENT, ":note_content", lidSeqNum++) + ","; valueList.append(b); } if (t.contentHash.isSet()) { QByteArray contentHash = t.contentHash; - values += joinValues(lids, NOTE_CONTENT_HASH, ":note_content_hash") + ","; + values += joinValues(lids, NOTE_CONTENT_HASH, ":note_content_hash", lidSeqNum++) + ","; valueList.append(contentHash); } if (t.contentLength.isSet()) { qint32 len = t.contentLength; - values += joinValues(lids, NOTE_CONTENT_LENGTH, ":note_content_length") + ","; + values += joinValues(lids, NOTE_CONTENT_LENGTH, ":note_content_length", lidSeqNum++) + ","; valueList.append(len); } if (t.updateSequenceNum.isSet()) { qint32 usn = t.updateSequenceNum; - values += joinValues(lids, NOTE_UPDATE_SEQUENCE_NUMBER, ":note_update_sequence_number") + ","; + values += joinValues(lids, NOTE_UPDATE_SEQUENCE_NUMBER, ":note_update_sequence_number", lidSeqNum++) + ","; valueList.append(usn); } if (isDirty) { - values += joinValues(lids, NOTE_ISDIRTY, ":is_dirty") + ","; + values += joinValues(lids, NOTE_ISDIRTY, ":is_dirty", lidSeqNum++) + ","; valueList.append(isDirty); } if (t.created.isSet()) { qlonglong date = t.created; - values += joinValues(lids, NOTE_CREATED_DATE, ":created_date") + ","; + values += joinValues(lids, NOTE_CREATED_DATE, ":created_date", lidSeqNum++) + ","; valueList.append(date); } if (t.updated.isSet()) { qlonglong date = t.updated; - values += joinValues(lids, NOTE_UPDATED_DATE, ":updated_date") + ","; + values += joinValues(lids, NOTE_UPDATED_DATE, ":updated_date", lidSeqNum++) + ","; valueList.append(date); } if (t.deleted.isSet()) { qlonglong date = t.deleted; - values += joinValues(lids, NOTE_DELETED_DATE, ":deleted_date") + ","; + values += joinValues(lids, NOTE_DELETED_DATE, ":deleted_date", lidSeqNum++) + ","; valueList.append(date); } if (t.active.isSet()) { bool active = t.active; - values += joinValues(lids, NOTE_ACTIVE, ":note_ative") + ","; + values += joinValues(lids, NOTE_ACTIVE, ":note_ative", lidSeqNum++) + ","; valueList.append(active); } @@ -276,7 +277,7 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { notebook.name = ""; notebookTable.add(notebookLid, notebook, false, false); } - values += joinValues(lids, NOTE_NOTEBOOK_LID, ":note_notebook_lid") + ","; + values += joinValues(lids, NOTE_NOTEBOOK_LID, ":note_notebook_lid", lidSeqNum++) + ","; valueList.append(notebookLid); } @@ -295,7 +296,7 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { tagTable.add(tagLid, newTag, false, 0); } - values += joinValues(lids, NOTE_TAG_LID, ":note_tag_lid") + ","; + values += joinValues(lids, NOTE_TAG_LID, ":note_tag_lid", lidSeqNum++) + ","; valueList.append(tagLid); } @@ -323,7 +324,7 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { if (r.mime.isSet()) { QString mime = r.mime; if (!mime.startsWith("image/") && mime != "vnd.evernote.ink") { - values += joinValues(lids, NOTE_HAS_ATTACHMENT, ":note_has_attachment") + ","; + values += joinValues(lids, NOTE_HAS_ATTACHMENT, ":note_has_attachment", lidSeqNum++) + ","; valueList.append(true); break; } @@ -334,72 +335,72 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { NoteAttributes na = t.attributes; if (na.subjectDate.isSet()) { qlonglong ts = na.subjectDate; - values += joinValues(lids, NOTE_ATTRIBUTE_SUBJECT_DATE, ":subject_date") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_SUBJECT_DATE, ":subject_date", lidSeqNum++) + ","; valueList.append(ts); } if (na.latitude.isSet()) { double lat = na.latitude; - values += joinValues(lids, NOTE_ATTRIBUTE_LATITUDE, ":lat") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_LATITUDE, ":lat", lidSeqNum++) + ","; valueList.append(lat); } if (na.longitude.isSet()) { double lon = na.longitude; - values += joinValues(lids, NOTE_ATTRIBUTE_LONGITUDE, ":lon") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_LONGITUDE, ":lon", lidSeqNum++) + ","; valueList.append(lon); } if (na.altitude.isSet()) { double alt = na.altitude; - values += joinValues(lids, NOTE_ATTRIBUTE_ALTITUDE, ":alt") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_ALTITUDE, ":alt", lidSeqNum++) + ","; valueList.append(alt); } if (na.author.isSet()) { QString author = na.author; - values += joinValues(lids, NOTE_ATTRIBUTE_AUTHOR, ":author") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_AUTHOR, ":author", lidSeqNum++) + ","; valueList.append(author); } if (na.source.isSet()) { QString source = na.source; - values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE, ":source") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE, ":source", lidSeqNum++) + ","; valueList.append(source); } if (na.sourceURL.isSet()) { QString sourceURL = na.sourceURL; - values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE_URL, ":sourceURL") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE_URL, ":sourceURL", lidSeqNum++) + ","; valueList.append(sourceURL); } if (na.sourceApplication.isSet()) { QString sourceApplication = na.sourceApplication; - values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE_APPLICATION, ":sourceApplication") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_SOURCE_APPLICATION, ":sourceApplication", lidSeqNum++) + ","; valueList.append(sourceApplication); } if (na.shareDate.isSet()) { double date = na.shareDate; - values += joinValues(lids, NOTE_ATTRIBUTE_SHARE_DATE, ":share_date") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_SHARE_DATE, ":share_date", lidSeqNum++) + ","; valueList.append(date); } if (na.placeName.isSet()) { QString placename = na.placeName; - values += joinValues(lids, NOTE_ATTRIBUTE_PLACE_NAME, ":placename") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_PLACE_NAME, ":placename", lidSeqNum++) + ","; valueList.append(placename); } if (na.contentClass.isSet()) { QString cc = na.contentClass; - values += joinValues(lids, NOTE_ATTRIBUTE_CONTENT_CLASS, ":content_class") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_CONTENT_CLASS, ":content_class", lidSeqNum++) + ","; valueList.append(cc); } if (na.reminderTime.isSet()) { double rt = na.reminderTime; - values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_TIME, ":reminder_time") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_TIME, ":reminder_time", lidSeqNum++) + ","; valueList.append(rt); } if (na.reminderDoneTime.isSet()) { double rt = na.reminderDoneTime; - values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_DONE_TIME, ":reminder_done_time") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_DONE_TIME, ":reminder_done_time", lidSeqNum++) + ","; valueList.append(rt); } if (na.reminderOrder.isSet()) { bool rt = na.reminderOrder; - values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_ORDER, ":reminder_order") + ","; + values += joinValues(lids, NOTE_ATTRIBUTE_REMINDER_ORDER, ":reminder_order", lidSeqNum++) + ","; valueList.append(rt); } } @@ -413,17 +414,17 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { content = ""; if (content.contains("")) { - values += joinValues(lids, NOTE_HAS_TODO_UNCOMPLETED, ":note_has_todo_uncompleted") + ","; + values += joinValues(lids, NOTE_HAS_TODO_UNCOMPLETED, ":note_has_todo_uncompleted", lidSeqNum++) + ","; valueList.append(true); } } @@ -434,7 +435,7 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { // Experimental index helper if (global.enableIndexing) { - values += joinValues(lids, NOTE_INDEX_NEEDED, ":note_index_needed") + ","; + values += joinValues(lids, NOTE_INDEX_NEEDED, ":note_index_needed", lidSeqNum++) + ","; valueList.append(true); } else { NoteIndexer indexer(db); @@ -446,9 +447,26 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { QString sql = "Insert into DataStore (lid, key, data) values " + values; query.prepare(sql); + // Some versions of QSqlQuery don't allow binding all the keys with + // one same name appearing in the query string with one same value + // through one call of bindValue(), so for compatibilty, we have + // to append a sequence number to the key name to differentiate + // them. And correspondingly we have to append the same lid + // muliple times, so that we can use bindLids() function to bind + // them. + for (int i = 0; i < lidSeqNum - 1; ++i) { + lids.append(lid); + } bindLids(query, lids); for (int i = 0; i < valueList.size(); ++i) { - query.bindValue(i*2 + 1, valueList[i]); + if (valueList[i].type() == QVariant::Bool || + valueList[i].type() == QVariant::Int) { + query.bindValue(i*2 + 1, valueList[i].toInt()); + } else if (valueList[i].type() == QVariant::LongLong) { + query.bindValue(i*2 + 1, valueList[i].toLongLong()); + } else { + query.bindValue(i*2 + 1, valueList[i].toString()); + } } query.exec(); @@ -2745,15 +2763,21 @@ QString NoteTable::joinLids(const QList ¬eLids) { } + QString NoteTable::joinValues(const QList ¬eLids, - int key, const QVariant &value) { + int key, const QVariant &value, int lidSeqNum/* =-1 */) { QString values = ""; for (int i = 0; i < noteLids.size(); ++i) { if (noteLids[i] <= 0) { continue; } - values += "(:lid" + QString::number(i) + "," + - QString::number(key) + ","; + if (lidSeqNum == -1) { + values += "(:lid" + QString::number(i) + "," + + QString::number(key) + ","; + } else { + values += "(:lid" + QString::number(lidSeqNum) + "," + + QString::number(key) + ","; + } if (value.type() == QVariant::Bool || value.type() == QVariant::Int) { @@ -2773,6 +2797,7 @@ QString NoteTable::joinValues(const QList ¬eLids, } + void NoteTable::bindLids(NSqlQuery &sql, const QList ¬eLids) { for (int i = 0; i < noteLids.size(); ++i) { if (noteLids[i] > 0) { diff --git a/src/sql/notetable.h b/src/sql/notetable.h index 2f2604ac..7b9a2826 100644 --- a/src/sql/notetable.h +++ b/src/sql/notetable.h @@ -96,7 +96,7 @@ class NoteTable private: QString joinLids(const QList ¬eLids); QString joinValues(const QList ¬eLids, - int key, const QVariant &value); + int key, const QVariant &value, int lidSeqNum = -1); void bindLids(NSqlQuery &sql, const QList ¬eLids); DatabaseConnection *db; From 13e04962d78bc57be6b81201c2570b27a9aeb361 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sun, 29 Jan 2023 19:39:58 +0800 Subject: [PATCH 40/61] Make A node(link) not be removed if its inner text is not empty. --- src/html/enmlformatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/html/enmlformatter.cpp b/src/html/enmlformatter.cpp index 2e3083ba..5b11be37 100644 --- a/src/html/enmlformatter.cpp +++ b/src/html/enmlformatter.cpp @@ -659,7 +659,7 @@ void EnmlFormatter::fixANode(QWebElement &e) { e.setAttribute("title", attr); e.setAttribute("href", attr); QLOG_TRACE() << ENML_MODULE_LOGPREFIX "fixed latex a tag to " << e.toOuterXml(); - } else if (href.isEmpty()) { + } else if (href.isEmpty() && e.toInnerXml() == "") { QLOG_TRACE() << ENML_MODULE_LOGPREFIX " a tag with empty href => removing"; e.removeFromDocument(); } else { From f1038b67ade0e3af862b276116f328dbba587317 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Mon, 30 Jan 2023 20:41:48 +0800 Subject: [PATCH 41/61] Update the changelogs. --- changelog.txt | 21 +++++++++++---------- debian/changelog | 1 + 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/changelog.txt b/changelog.txt index 8a59fc84..5d9503f2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,15 +1,16 @@ NixNote (2.1.7) stable; urgency=low - * Optimise the time complexity of batch operations, including moving, deleting, and restoring. - * Add an option: the user can choose to make Nixnote listen to the commands from command line or not. - * Decouple the functions of CmdLineTool(except signalGui()) from shared memory. - * Export the stack attribute when exporting the notes. - * Add an option: the user can choose to save the ui changes or not when Nixnote exits. - * Save the note content when Nixnote receives signal interruption. - * Allocate the tageditor's tags dynamically. - * Add a default theme named with 'Default' when no theme is found in the THEME_FILE. + * Keep A node(link in HTML) without href attribute when saving the note if its inner text is not empty. + * Optimised the time complexity of batch operations, including moving, deleting, and restoring. + * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. + * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. + * Exported the stack attribute when exporting the notes. + * Added an option: the user can choose to save the ui changes or not when Nixnote exits. + * Saved the note content when Nixnote receives signal interruption. + * Allocated the tageditor's tags dynamically. + * Added a default theme named with 'Default' when no theme is found in the THEME_FILE. * RAM usage optimizations. - * Fix file downloading under Windows. - * Make the editor not render the note content when Key_Up or Key_Down is kept being pressed. + * Fixed file downloading under Windows. + * Made the editor not render the note content when Key_Up or Key_Down is kept being pressed. * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. * Limited the max length of the recently updated menu and favourite menu of the tray. diff --git a/debian/changelog b/debian/changelog index 8a59fc84..2217b6b2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,5 @@ NixNote (2.1.7) stable; urgency=low + * Keep A node(link in HTML) without href attribute when saving the note if its inner text is not empty. * Optimise the time complexity of batch operations, including moving, deleting, and restoring. * Add an option: the user can choose to make Nixnote listen to the commands from command line or not. * Decouple the functions of CmdLineTool(except signalGui()) from shared memory. From 711eedce43dbd88fb1a87bfcb81c2a52f7bf1c2e Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Tue, 31 Jan 2023 13:14:12 +0800 Subject: [PATCH 42/61] Change the wildcard trailing the option of 'Listen to commands from command line' to text description of 'requires restart' to keep consistent with the other options. --- src/dialog/preferences/debugpreferences.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialog/preferences/debugpreferences.cpp b/src/dialog/preferences/debugpreferences.cpp index 60fd6ef5..4d966f9b 100644 --- a/src/dialog/preferences/debugpreferences.cpp +++ b/src/dialog/preferences/debugpreferences.cpp @@ -63,7 +63,7 @@ DebugPreferences::DebugPreferences(QWidget *parent) : multiThreadSave->setChecked(global.getMultiThreadSave()); mainLayout->addWidget(multiThreadSave, row++, 1); - listenToCommands = new QCheckBox(tr("Listen to commands from command line.*")); + listenToCommands = new QCheckBox(tr("Listen to commands from command line (requires restart).")); listenToCommands->setChecked(global.getListenToCommands()); mainLayout->addWidget(listenToCommands, row++, 1); From fa300cf2bb25f9c576f13f4ccb606931de096bb0 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Tue, 31 Jan 2023 17:06:55 +0800 Subject: [PATCH 43/61] Update the changelogs. --- changelog.txt | 11 +++++++---- debian/changelog | 27 +++++++++++++++------------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/changelog.txt b/changelog.txt index 5d9503f2..4574ea4b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,11 +1,14 @@ NixNote (2.1.7) stable; urgency=low * Keep A node(link in HTML) without href attribute when saving the note if its inner text is not empty. + * Optimised the sql statements of note creating and updating. * Optimised the time complexity of batch operations, including moving, deleting, and restoring. * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. + When it is enabled, the shared memory size will be set to 1024 bytes(it was 512KiB previously); + when disabled, the size will be set to 1 byte. * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. * Exported the stack attribute when exporting the notes. - * Added an option: the user can choose to save the ui changes or not when Nixnote exits. - * Saved the note content when Nixnote receives signal interruption. + * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. + * Saved the note content when Nixnote receives specific signal interruptions. * Allocated the tageditor's tags dynamically. * Added a default theme named with 'Default' when no theme is found in the THEME_FILE. * RAM usage optimizations. @@ -22,9 +25,9 @@ NixNote (2.1.7) stable; urgency=low * Fixed: Local images cannot be pasted, web image's html element cannot be saved * Fixed: Search by date attributes not working with PPA builds - issue #149 * Fixed: Editor fonts and sorting notes - issue #156 - * Fixed: Nixnote often crashes + * Fixed: Nixnote often crashes. * Fixed: Can't search / replace with as replacement. - issue #180 - * Enabled the spell check code for Windows edition + * Enabled the spell check code for Windows edition. * Fixed Windows build (thanks to https://github.com/boo-yee) -- Robert Spiegel Tue, 17 May 2022 10:00:00 +0200 diff --git a/debian/changelog b/debian/changelog index 2217b6b2..4574ea4b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,16 +1,19 @@ NixNote (2.1.7) stable; urgency=low * Keep A node(link in HTML) without href attribute when saving the note if its inner text is not empty. - * Optimise the time complexity of batch operations, including moving, deleting, and restoring. - * Add an option: the user can choose to make Nixnote listen to the commands from command line or not. - * Decouple the functions of CmdLineTool(except signalGui()) from shared memory. - * Export the stack attribute when exporting the notes. - * Add an option: the user can choose to save the ui changes or not when Nixnote exits. - * Save the note content when Nixnote receives signal interruption. - * Allocate the tageditor's tags dynamically. - * Add a default theme named with 'Default' when no theme is found in the THEME_FILE. + * Optimised the sql statements of note creating and updating. + * Optimised the time complexity of batch operations, including moving, deleting, and restoring. + * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. + When it is enabled, the shared memory size will be set to 1024 bytes(it was 512KiB previously); + when disabled, the size will be set to 1 byte. + * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. + * Exported the stack attribute when exporting the notes. + * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. + * Saved the note content when Nixnote receives specific signal interruptions. + * Allocated the tageditor's tags dynamically. + * Added a default theme named with 'Default' when no theme is found in the THEME_FILE. * RAM usage optimizations. - * Fix file downloading under Windows. - * Make the editor not render the note content when Key_Up or Key_Down is kept being pressed. + * Fixed file downloading under Windows. + * Made the editor not render the note content when Key_Up or Key_Down is kept being pressed. * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. * Limited the max length of the recently updated menu and favourite menu of the tray. @@ -22,9 +25,9 @@ NixNote (2.1.7) stable; urgency=low * Fixed: Local images cannot be pasted, web image's html element cannot be saved * Fixed: Search by date attributes not working with PPA builds - issue #149 * Fixed: Editor fonts and sorting notes - issue #156 - * Fixed: Nixnote often crashes + * Fixed: Nixnote often crashes. * Fixed: Can't search / replace with as replacement. - issue #180 - * Enabled the spell check code for Windows edition + * Enabled the spell check code for Windows edition. * Fixed Windows build (thanks to https://github.com/boo-yee) -- Robert Spiegel Tue, 17 May 2022 10:00:00 +0200 From f203cce909c300b7f3fbf54a909556c105545e6e Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 15 Feb 2023 13:13:11 +0800 Subject: [PATCH 44/61] Update the about page and the changelogs. --- changelog.txt | 4 ++-- debian/changelog | 4 ++-- help/about.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4574ea4b..ecc135df 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,5 @@ NixNote (2.1.7) stable; urgency=low - * Keep A node(link in HTML) without href attribute when saving the note if its inner text is not empty. + * Made tidy keep A nodes(link in HTML) without href attribute when saving the note if its inner text is not empty. * Optimised the sql statements of note creating and updating. * Optimised the time complexity of batch operations, including moving, deleting, and restoring. * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. @@ -10,7 +10,7 @@ NixNote (2.1.7) stable; urgency=low * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. * Saved the note content when Nixnote receives specific signal interruptions. * Allocated the tageditor's tags dynamically. - * Added a default theme named with 'Default' when no theme is found in the THEME_FILE. + * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. * Fixed file downloading under Windows. * Made the editor not render the note content when Key_Up or Key_Down is kept being pressed. diff --git a/debian/changelog b/debian/changelog index 4574ea4b..ecc135df 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,5 @@ NixNote (2.1.7) stable; urgency=low - * Keep A node(link in HTML) without href attribute when saving the note if its inner text is not empty. + * Made tidy keep A nodes(link in HTML) without href attribute when saving the note if its inner text is not empty. * Optimised the sql statements of note creating and updating. * Optimised the time complexity of batch operations, including moving, deleting, and restoring. * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. @@ -10,7 +10,7 @@ NixNote (2.1.7) stable; urgency=low * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. * Saved the note content when Nixnote receives specific signal interruptions. * Allocated the tageditor's tags dynamically. - * Added a default theme named with 'Default' when no theme is found in the THEME_FILE. + * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. * Fixed file downloading under Windows. * Made the editor not render the note content when Key_Up or Key_Down is kept being pressed. diff --git a/help/about.html b/help/about.html index c4a281d7..c79dc1b0 100644 --- a/help/about.html +++ b/help/about.html @@ -6,7 +6,7 @@

__VERSION__

(c) 2008-2018 Randy Baumgarte
- (c) 2018-2020 Robert Spiegel + (c) 2018-2023 Robert Spiegel & contributors.
License: GNU GPLv3

From d1efc8cecfbc2708fe5d700bbe311779836d852e Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 15 Feb 2023 18:16:17 +0800 Subject: [PATCH 45/61] Move getCheckboxImageUrl() and getCheckboxElement() from Global to NixnoteStringUtils, which is more suitable for these two functions; set shared momory size to 512 KiB(as previous) if NixNote listens to commands from command line; update the changelogs. --- changelog.txt | 4 ++-- debian/changelog | 4 ++-- src/global.cpp | 27 -------------------------- src/global.h | 4 ---- src/gui/nbrowserwindow.cpp | 22 ++++++++++----------- src/html/noteformatter.cpp | 2 +- src/main.cpp | 2 +- src/utilities/NixnoteStringUtils.cpp | 29 ++++++++++++++++++++++++++++ src/utilities/NixnoteStringUtils.h | 5 +++++ 9 files changed, 51 insertions(+), 48 deletions(-) diff --git a/changelog.txt b/changelog.txt index ecc135df..a1dc16eb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,10 +3,10 @@ NixNote (2.1.7) stable; urgency=low * Optimised the sql statements of note creating and updating. * Optimised the time complexity of batch operations, including moving, deleting, and restoring. * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. - When it is enabled, the shared memory size will be set to 1024 bytes(it was 512KiB previously); + When it is enabled, the shared memory size will be set to 512 KiB; when disabled, the size will be set to 1 byte. * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. - * Exported the stack attribute when exporting the notes. + * Made Nixnote export the stack attribute when exporting notes. * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. * Saved the note content when Nixnote receives specific signal interruptions. * Allocated the tageditor's tags dynamically. diff --git a/debian/changelog b/debian/changelog index ecc135df..a1dc16eb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,10 +3,10 @@ NixNote (2.1.7) stable; urgency=low * Optimised the sql statements of note creating and updating. * Optimised the time complexity of batch operations, including moving, deleting, and restoring. * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. - When it is enabled, the shared memory size will be set to 1024 bytes(it was 512KiB previously); + When it is enabled, the shared memory size will be set to 512 KiB; when disabled, the size will be set to 1 byte. * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. - * Exported the stack attribute when exporting the notes. + * Made Nixnote export the stack attribute when exporting notes. * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. * Saved the note content when Nixnote receives specific signal interruptions. * Allocated the tageditor's tags dynamically. diff --git a/src/global.cpp b/src/global.cpp index cefdbece..10e55cd3 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1583,33 +1583,6 @@ void Global::setSortOrder(const QString &sortOrder) { saveSettingSortOrder(sortOrder); } -QString Global::getCheckboxImageUrl(bool checked) const { - QString fileName = checked ? "checkbox_checked.png": "checkbox.png"; - QString filePath = global.fileManager.getImageDirPath("").append(fileName); - - QString prefix = QString("file://"); -#ifdef _WIN32 - prefix.append("/"); -#endif - return prefix + filePath; -} - -QString Global::getCheckboxElement(bool checked, bool escapeTwice) const { - QString defaultUrl = getCheckboxImageUrl(false); - QString checkedUrl = getCheckboxImageUrl(true); - QString url = !checked ? defaultUrl : checkedUrl; - return QString("") : QString("\';\">")); -} - - void Global::clearResourceList() { resourceList.clear(); colorList.clear(); diff --git a/src/global.h b/src/global.h index 9c7b428b..b1962f94 100644 --- a/src/global.h +++ b/src/global.h @@ -193,8 +193,6 @@ class Global : public QObject { QString sortOrder; - QString getCheckboxImageUrl(bool checked) const; - QHash qIconList; QHash qPixmapList; @@ -489,8 +487,6 @@ class Global : public QObject { const QString getSortOrder() const; void setSortOrder(const QString &sortOrder); - QString getCheckboxElement(bool checked, bool escapeTwice) const; - void clearResourceList(); signals: diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index 940ebd58..39d76831 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -1410,8 +1410,8 @@ void NBrowserWindow::todoButtonPressed() { QString selectedHtml = editor->selectedHtml(); int length = selectedHtml.length(); - selectedHtml.replace(global.getCheckboxElement(true, false), ""); - selectedHtml.replace(global.getCheckboxElement(false, false), ""); + selectedHtml.replace(NixnoteStringUtils::getCheckboxElement(true, false), ""); + selectedHtml.replace(NixnoteStringUtils::getCheckboxElement(false, false), ""); if (selectedHtml.length() < length) { editor->page()->mainFrame()->evaluateJavaScript(script_start + selectedHtml + script_end); @@ -1424,7 +1424,7 @@ void NBrowserWindow::todoButtonPressed() { QString html = ""; for (int i = 0; i < items.size(); i++) { - html += "
" + global.getCheckboxElement(false, true) + items[i] + + html += "
" + NixnoteStringUtils::getCheckboxElement(false, true) + items[i] + "
"; } @@ -1447,15 +1447,15 @@ void NBrowserWindow::todoSetAllChecked(bool allSelected) { QString html = editor->selectedHtml(); if (allSelected) { - html.replace(global.getCheckboxElement(false, false), - global.getCheckboxElement(true, true)); - html.replace(global.getCheckboxElement(true, false), - global.getCheckboxElement(true, true)); + html.replace(NixnoteStringUtils::getCheckboxElement(false, false), + NixnoteStringUtils::getCheckboxElement(true, true)); + html.replace(NixnoteStringUtils::getCheckboxElement(true, false), + NixnoteStringUtils::getCheckboxElement(true, true)); } else { - html.replace(global.getCheckboxElement(true, false), - global.getCheckboxElement(false, true)); - html.replace(global.getCheckboxElement(false, false), - global.getCheckboxElement(false, true)); + html.replace(NixnoteStringUtils::getCheckboxElement(true, false), + NixnoteStringUtils::getCheckboxElement(false, true)); + html.replace(NixnoteStringUtils::getCheckboxElement(false, false), + NixnoteStringUtils::getCheckboxElement(false, true)); } editor->page()->mainFrame()->evaluateJavaScript(script_start + html + script_end); diff --git a/src/html/noteformatter.cpp b/src/html/noteformatter.cpp index 3b1682ee..45f52214 100644 --- a/src/html/noteformatter.cpp +++ b/src/html/noteformatter.cpp @@ -790,7 +790,7 @@ void NoteFormatter::modifyTodoTags(QWebElement &todo) { isChecked = true; } - QString xml = global.getCheckboxElement(isChecked, false); + QString xml = NixnoteStringUtils::getCheckboxElement(isChecked, false); todo.setOuterXml(xml + todo.toInnerXml()); QLOG_TRACE_OUT(); } diff --git a/src/main.cpp b/src/main.cpp index b8d36670..34b63abc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -225,7 +225,7 @@ int main(int argc, char *argv[]) { bool memInitNeeded = true; int sharedMemSize; if (global.getListenToCommands()) { - sharedMemSize = 1024; + sharedMemSize = 512*1024; } else { sharedMemSize = 1; } diff --git a/src/utilities/NixnoteStringUtils.cpp b/src/utilities/NixnoteStringUtils.cpp index 904a595e..3a0e14eb 100644 --- a/src/utilities/NixnoteStringUtils.cpp +++ b/src/utilities/NixnoteStringUtils.cpp @@ -19,6 +19,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "NixnoteStringUtils.h" +#include "src/global.h" + +extern Global global; NixnoteStringUtils::NixnoteStringUtils() { @@ -85,3 +88,29 @@ QString NixnoteStringUtils::extractNoteGuid(QString noteUrl) { QStringList splitNoteUrl = noteUrl.split('/'); return splitNoteUrl[splitNoteUrl.count()-1]; } + +QString NixnoteStringUtils::getCheckboxImageUrl(bool checked) { + QString fileName = checked ? "checkbox_checked.png": "checkbox.png"; + QString filePath = global.fileManager.getImageDirPath("").append(fileName); + + QString prefix = QString("file://"); +#ifdef _WIN32 + prefix.append("/"); +#endif + return prefix + filePath; +} + +QString NixnoteStringUtils::getCheckboxElement(bool checked, bool escapeTwice) { + QString defaultUrl = getCheckboxImageUrl(false); + QString checkedUrl = getCheckboxImageUrl(true); + QString url = !checked ? defaultUrl : checkedUrl; + return QString("") : QString("\';\">")); +} diff --git a/src/utilities/NixnoteStringUtils.h b/src/utilities/NixnoteStringUtils.h index 00b4a175..ec7038b3 100644 --- a/src/utilities/NixnoteStringUtils.h +++ b/src/utilities/NixnoteStringUtils.h @@ -71,6 +71,11 @@ class NixnoteStringUtils { * @return note GUID */ static QString extractNoteGuid(QString noteUrl); + + static QString getCheckboxElement(bool checked, bool escapeTwice); + +private: + static QString getCheckboxImageUrl(bool checked); }; #endif // NIXNOTE_STRING_UTILS_H From 2706dc62a3581a32538b02123ca9ae32dd7ddd87 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Wed, 15 Feb 2023 21:36:51 +0800 Subject: [PATCH 46/61] Update the README. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1131eb11..b35b3837 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ Unlike Unix-like systems, Windows is not shipped with a bash environment, so you #### Download development dependencies: ##### Download the third-party libraries: -If you want to download binary third-party library files, you can get them from [winlib](https://github.com/boo-yee/winlib). Inside it, Hunspell and tidy are built with MinGW 32 5.3.0, and poppler is downloaded from sourceforge as binary. If you want to build by yourself, you can download them from the following links: +If you want to download binary third-party library files compatible with Qt 5.5.0, you can get them from [winlib](https://github.com/boo-yee/winlib). Inside it, Hunspell and tidy are built with MinGW 32 4.9.2(shipped with Qt 5.5.0), and poppler is downloaded from sourceforge as binary. If you want to build by yourself, you can download them from the following links: [poppler](https://sourceforge.net/projects/poppler-qt5-mingw32/) @@ -193,9 +193,9 @@ If you want to download binary third-party library files, you can get them from ##### Download Qt: [Qt](https://download.qt.io/)(with MinGW32) -If your Qt version is 5.6 or higher, you need to download QtWebKit separately and copy the files under QtWebKit include folder to /your_path_to_qt/[version]/mingw[version]/include. +Qt 5.5.0 is enough. But if you want to build with a newer version, you need to download QtWebKit separately and copy the files under QtWebKit include folder to /your_path_to_qt/[version]/mingw[version]/include. -[QtWebKit](https://github.com/qtwebkit/qtwebkit/releases/download/qtwebkit-tp5/qtwebkit-tp5-qt58-mingw530-x86.zip) +[QtWebKit](https://github.com/qtwebkit/qtwebkit/releases) (Advice: You may want to add the path to qmake.exe and ming32-make.exe to the PATH environment, so that you do not have to type the full path when building the application and libraries later. You can do this by hand or running qtenv2.bat.) @@ -213,7 +213,7 @@ Then you will get the hunspell dll file. Then, create folders as winlib/includes in this repository folder and copy the files under poppler, tidy and hunspell include folders to winlib/includes. The structure is: ```bash -nixnote_repo +nixnote2 | `--winlib | From 5cf005c75ff8ba252c576161eaf897fa7cb53d25 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 16 Feb 2023 20:13:19 +0800 Subject: [PATCH 47/61] Set shared memory size to 128 bytes if Nixnote is configured to listen to commands from command line. --- changelog.txt | 2 +- debian/changelog | 2 +- src/main.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index a1dc16eb..7b0f28d7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,7 +3,7 @@ NixNote (2.1.7) stable; urgency=low * Optimised the sql statements of note creating and updating. * Optimised the time complexity of batch operations, including moving, deleting, and restoring. * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. - When it is enabled, the shared memory size will be set to 512 KiB; + When it is enabled, the shared memory size will be set to 128 bytes; when disabled, the size will be set to 1 byte. * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. * Made Nixnote export the stack attribute when exporting notes. diff --git a/debian/changelog b/debian/changelog index a1dc16eb..7b0f28d7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,7 +3,7 @@ NixNote (2.1.7) stable; urgency=low * Optimised the sql statements of note creating and updating. * Optimised the time complexity of batch operations, including moving, deleting, and restoring. * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. - When it is enabled, the shared memory size will be set to 512 KiB; + When it is enabled, the shared memory size will be set to 128 bytes; when disabled, the size will be set to 1 byte. * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. * Made Nixnote export the stack attribute when exporting notes. diff --git a/src/main.cpp b/src/main.cpp index 34b63abc..d81fe403 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -225,7 +225,7 @@ int main(int argc, char *argv[]) { bool memInitNeeded = true; int sharedMemSize; if (global.getListenToCommands()) { - sharedMemSize = 512*1024; + sharedMemSize = 128; } else { sharedMemSize = 1; } From ceba20d8e75b924c065642a13dd6889b0453b5c6 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 13 Apr 2023 10:46:15 +0800 Subject: [PATCH 48/61] Update the changelogs. --- changelog.txt | 12 +++++------- debian/changelog | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7b0f28d7..086debdf 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,14 +1,12 @@ NixNote (2.1.7) stable; urgency=low - * Made tidy keep A nodes(link in HTML) without href attribute when saving the note if its inner text is not empty. + * Made tidy keep A nodes(HTML link element) having no href attribute if its inner text is not empty when saving the note. * Optimised the sql statements of note creating and updating. - * Optimised the time complexity of batch operations, including moving, deleting, and restoring. - * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. - When it is enabled, the shared memory size will be set to 128 bytes; - when disabled, the size will be set to 1 byte. + * Optimised the sql statements of batch operations, including movings, deletings, and restorings. + * Added an option to listen to the commands from command line.(When it is enabled, the shared memory size will be set to 128 bytes; when disabled, it will be set to 1 byte.) * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. * Made Nixnote export the stack attribute when exporting notes. - * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. - * Saved the note content when Nixnote receives specific signal interruptions. + * Added an option to save the UI state changes when Nixnote exits. + * Saved the note content when Nixnote receives specific signal interruptions(SIGINT, SIGHUP). * Allocated the tageditor's tags dynamically. * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. diff --git a/debian/changelog b/debian/changelog index 7b0f28d7..086debdf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,14 +1,12 @@ NixNote (2.1.7) stable; urgency=low - * Made tidy keep A nodes(link in HTML) without href attribute when saving the note if its inner text is not empty. + * Made tidy keep A nodes(HTML link element) having no href attribute if its inner text is not empty when saving the note. * Optimised the sql statements of note creating and updating. - * Optimised the time complexity of batch operations, including moving, deleting, and restoring. - * Added an option: the user can choose to make Nixnote listen to the commands from command line or not. - When it is enabled, the shared memory size will be set to 128 bytes; - when disabled, the size will be set to 1 byte. + * Optimised the sql statements of batch operations, including movings, deletings, and restorings. + * Added an option to listen to the commands from command line.(When it is enabled, the shared memory size will be set to 128 bytes; when disabled, it will be set to 1 byte.) * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. * Made Nixnote export the stack attribute when exporting notes. - * Added an option: the user can choose to save the ui state changes or not when Nixnote exits. - * Saved the note content when Nixnote receives specific signal interruptions. + * Added an option to save the UI state changes when Nixnote exits. + * Saved the note content when Nixnote receives specific signal interruptions(SIGINT, SIGHUP). * Allocated the tageditor's tags dynamically. * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. From 63f637fc785bdcc28399432fa195626d040c4502 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 13 Apr 2023 16:57:40 +0800 Subject: [PATCH 49/61] Fix a typo about note adding in cmdline tools. --- src/cmdtools/cmdlinetool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdtools/cmdlinetool.cpp b/src/cmdtools/cmdlinetool.cpp index 5b0f536d..0311451a 100644 --- a/src/cmdtools/cmdlinetool.cpp +++ b/src/cmdtools/cmdlinetool.cpp @@ -251,7 +251,7 @@ int CmdLineTool::addNote(StartupConfig config) { tagTable.getGuid(tagGuid, tagLid); } newNote.tagNames->append(tagName); - newNote.tagNames->append(tagGuid); + newNote.tagGuids->append(tagGuid); } // Process the notebook From 504883511402df386dd9ace4109ed9df99fb3c69 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Thu, 13 Apr 2023 17:14:13 +0800 Subject: [PATCH 50/61] Add a global dark theme. --- themes.ini | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/themes.ini b/themes.ini index aa8ca039..a90ee49b 100644 --- a/themes.ini +++ b/themes.ini @@ -300,3 +300,113 @@ stackIcon =purple-theme/stack-purple.png windowIcon=purple-theme/windowIcon5.png +# global dark theme +[Dark Blue - 6] +editorCss=body { color: white; background-color: #272a34;} +# Note title editor style when inactive +noteTitleEditorInactiveCss=background-color: #23252e; color: white; QLineEdit {background-color: transparent; border-radius: 0px;} QLineEdit:hover {border: 1px solid #808080; background-color: white; border-radius: 4px;} +# Note title editor when it has user focus. +noteTitleEditorActiveCss=QLineEdit {border: 1px solid #808080; background-color: white; border-radius: 4px;} +# +searchInputCss= background-color: #272a34; QLineEdit { padding-right: 1px; } ## Search across notes text input field + + + +mainWindowCss=background-color: #23252e; color: white; ## Default window +menuCss=QMenu {background-color: #23252e; color: white;} ## Window menu +mainToolbarCss= background-color: #23252e; ## Main toolbar +#noteAuthorInactiveCss=background-color: transparent; border-radius: 0px; ## Color when author field doesn't have focus +#noteAuthorActiveCss=border: 1px solid #808080; background-color: white; border-radius: 4px; ## color when author field has focus +#colorMenuCss= ## style sheet for font & highlight color chooser buttons when open. +#dateMenuCss= ## date & time chooser for a note. +#dateMenuActiveCss=QDateTimeEdit {border: 1px solid #808080; background-color: white; border-radius: 4px;} QDateTimeEdit::up-button {width: 14px;} QDateTimeEdit::down-button{width: 14px;} ## Date editor CSS when active +#dateMenuInactiveCss=QDateTimeEdit {background-color: transparent; border-radius: 1px;} QDateTimeEdit:hover {border: 1px solid #808080; background-color: white; border-radius: 4px;} QDateTimeEdit::up-button {width: 0px; image:none;} QDateTimeEdit::down-button{width: 0px; image: none;} ## Date editor CSS when inactive +editorButtonBarCss=background-color: #23252e; ## Note editor button bar +#nodeAttributesExpandButtonCss= ## small expand button on note editor +#fontNameComboBoxCss= ## note editor combo box that shows available fonts +#fontSizeComboBoxCss= ## note editor combo box that shows available font sizes +#noteLocationCss=QToolButton {background-color: transparent; border-radius: 0px; border:none; margin 0px; padding: 4px} ## Note geographical coordinates +#notebookMenuButtonCss=QPushButton {text-align:left;} ## Notebook menu button used to change a note's notebook. +#reminderButtonCss=QPushButton {text-align: left;} ## Button used to show the reminder dialog. +#urlEditorCss= ## Note URL in editor. +#noteTagViewerCss= ## Existing tags in the note editor +#noteTagAddActiveCss=QLineEdit {border: 1px solid #808080; background-color: white; border-radius: 4px;} ## Style when the user clicks on the tag-add field in the note editor. +#noteTagAddInactiveCss=QLineEdit {background-color: transparent; border-radius: 0px;} ## Style when the new tag field in the note editor is inactive. +#externalNoteWindowCss= ## External window opened for a note. +#shortcutsTreeCss=QTreeView {border-image:none; image:none;} ## Shortcuts tree list +#findReplaceCss= ## Find/Replace in note line edits +#attributeTreeCss= ## Attribute selection tree +#browserWindowCss= ## Overall note browser window (note+buttons above it). +#notebookTreeCss= ## Notebook selection tree +#savedSearchTreeCss= ## Saved search tree +noteTableViewCss=background-color: #23252e; alternate-background-color: #23252e; ## Grid of notes +noteTableViewHeaderCss=background-color: #23252e; ## Heading for note table grid +noteTabCss= ## Note tab header +#tagTreeCss= ## Tag selection tree +#trashTreeCss= ## Trash selection tree +#noteContentsCss= ## Note contents QWebView +#trayMenuCss= ## Tray icon popup menu +#treeWidgetEditorCss= ## Used when changing a tree item name +treeWidgetPanelCss=background-color: #23252e; color: white; ## Left panel that holds shortcuts, notebooks, tags, saved searches & trash. +### Icons +#windowIcon=windowIcon0.png ## Main window icon +#passwordIcon=password.png ## Small password lock icon +#homeIcon=home.png ## Home button on main button bar +#leftArrowIcon=left_arrow.png ## Left arrow on main button bar +#rightArrowIcon=right_arrow.png ## Right arrow on main button bar +#synchronizeIcon=synchronize.png ## Spinning sync button +#filecloseIcon=fileclose.png ## Very small "x" next to tag names in the editor +#tagIcon=tag.png ## Icon for tags +#searchIcon=lens.png ## Search icon +#trashIcon=trash.png ## trashcan for deleted notes +#attributesIcon=attribute.png ## Gear icon for attributes +#upArrowSmallIcon=up_arrow_small.png ## Small up arrow +#downArrowSmallIcon=down_arrow_small.png ## Small down arrow +#undoIcon=undo.png ## Undo button on editor button bar +#redoIcon=redo.png ## Redo button on editor button bar +#boldIcon=bold.png ## Bold icon on editor button bar +#bulletListIcon=bulletList.png ## Bullet list icon on editor button bar +#copyIcon=copy.png ## Copy icon on editor button bar +#cutIcon=cut.png ## Cut icon on editor button bar +#hlineIcon=hline.png ## Horizontal line on editor button bar +#fontHighlightIcon=fontHilight.png ## Font Hilight icon on editor button bar +#fontColorIcon=fontColor.png ## Font color icon on editor button bar +#shiftRightIcon=indent.png ## Indent button on editor button bar +#centerAlignIcon=justifyCenter.png ## Center justify icon on editor button bar +#leftAlignIcon=justifyLeft.png ## Left justify icon on editor button bar +#rightAlignIcon=justifyRight.png ## Right justify on icon button bar +#linkIcon=link.png ## Paper clip link icon on editor button bar +#numberListIcon=numberList.png ## Number list on icon on editor button bar +#shiftLeftIcon=outdent.png ## Outdent icon on editor button bar +#underlineIcon=underline.png ## Underline icon on editor button bar +#strikethroughIcon=strikethrough.png ## Strikethrough icon on editor button bar +#spellCheckIcon=spellCheck.png ## Spell check on editor button bar +#pasteIcon=paste.png ## Paste icon on editor button bar +#todoIcon=todo.png ## To-do icon on editor button bar +#italicsIcon=italic.png ## Italics icon on editor button bar +#newNoteIcon=newNote.png ## New note icon on main button bar +#trunkIcon=trunk.png ## Open Evernote Trunk icon +#usageIcon=usage.png ## Usage icon on main button bar +#blackDotIcon=black_dot.png ## Small black dot used in main note list +#notebookSmallIcon=iconza-notebook-green.png ## Small notebook icon +#notebookLocalIcon=local-notebook.png ## local notebook icon +#notebookLinkedIcon=linked-notebook.png ## linked notebook icon +#notebookConflictIcon=notebook-conflict.png ## conflict notebook icon +#notebookSharedIcon=public-notebook.png ## Public notebook icon +#stackIcon=stack-green.png ## Stack icon +#silhouetteIcon=silhouette.png ## Person outline for linked notebooks +#eraserIcon=eraser.png ## Eraser icon on editor button bar +#gridIcon=grid.png ## Table icon on editor button bar +#navigationIcon=navigation.png ## GPS navigator icon in note editor +#splashLogoImage=splash_logo.png ## Splash screen logo +#alarmclockIcon=alarmclock.png ## Alarm clock icon for reminders +#printerIcon=printer.png ## Printer icon in main button bar +#deleteIcon=delete.png ## Delete note icon +#expandedIcon=expanded.png ## Down arrow used to expand & collapse note attribute editor +#collapsedIcon=collapsed.png ## Up arrow used to expand & collapse note attribute editor +#htmlentitiesIcon=htmlentities.png ## HTML entities icon on editor button bar +#favoritesIcon=favorites.png ## Favorites icon +#trayIcon=trayicon.png ## Small tray icon. +##editorFontColor=white ## Editor font color +##editorBackgroundColor=black ## Editor background color + From 0747d196c20ec2184648c109e6e3061424396b8c Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 14 Apr 2023 12:28:36 +0800 Subject: [PATCH 51/61] Typo in themes.ini. --- themes.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes.ini b/themes.ini index a90ee49b..25e2f10f 100644 --- a/themes.ini +++ b/themes.ini @@ -301,7 +301,7 @@ windowIcon=purple-theme/windowIcon5.png # global dark theme -[Dark Blue - 6] +[Dark Blue] editorCss=body { color: white; background-color: #272a34;} # Note title editor style when inactive noteTitleEditorInactiveCss=background-color: #23252e; color: white; QLineEdit {background-color: transparent; border-radius: 0px;} QLineEdit:hover {border: 1px solid #808080; background-color: white; border-radius: 4px;} From a28ff7aa351be69176b99936052209e5264f825b Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 14 Apr 2023 14:18:04 +0800 Subject: [PATCH 52/61] Append a serial number to each note_tag_lid key in the sql statements in NoteTable::add(), if not, bindValue() will not work as expected; move the db lock release afterwards to a right place; swap the if branches related with cmdline.run() in main.cpp. --- src/main.cpp | 5 ++--- src/sql/notetable.cpp | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d81fe403..bd6ef003 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -209,10 +209,9 @@ int main(int argc, char *argv[]) { sharedMemory->detach(); } if (retval1) { - QLOG_ERROR() << "Exit FAILURE: retcode=" << retval1; - - } else { QLOG_INFO() << "Exit OK: retcode=" << retval1; + } else { + QLOG_ERROR() << "Exit FAILURE: retcode=" << retval1; } delete a; diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index 13b8f87a..c12c4636 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -296,7 +296,7 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { tagTable.add(tagLid, newTag, false, 0); } - values += joinValues(lids, NOTE_TAG_LID, ":note_tag_lid", lidSeqNum++) + ","; + values += joinValues(lids, NOTE_TAG_LID, ":note_tag_lid" + QString::number(i), lidSeqNum++) + ","; valueList.append(tagLid); } @@ -428,8 +428,6 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { valueList.append(true); } } - query.finish(); - db->unlock(); updateNoteList(lid, t, isDirty, account); @@ -469,7 +467,9 @@ qint32 NoteTable::add(qint32 l, const Note &t, bool isDirty, qint32 account) { } } query.exec(); + query.finish(); + db->unlock(); return lid; } From f45be196f1d054dd2777dcdcf59afc37892f107e Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 14 Apr 2023 15:07:32 +0800 Subject: [PATCH 53/61] Revert commit 13e04962d7: Make A node(link) not be removed if its inner text is not empty. --- src/html/enmlformatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/html/enmlformatter.cpp b/src/html/enmlformatter.cpp index 5b11be37..2e3083ba 100644 --- a/src/html/enmlformatter.cpp +++ b/src/html/enmlformatter.cpp @@ -659,7 +659,7 @@ void EnmlFormatter::fixANode(QWebElement &e) { e.setAttribute("title", attr); e.setAttribute("href", attr); QLOG_TRACE() << ENML_MODULE_LOGPREFIX "fixed latex a tag to " << e.toOuterXml(); - } else if (href.isEmpty() && e.toInnerXml() == "") { + } else if (href.isEmpty()) { QLOG_TRACE() << ENML_MODULE_LOGPREFIX " a tag with empty href => removing"; e.removeFromDocument(); } else { From ebc5215ead74c4e85389afc6d247aff065874ace Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Fri, 14 Apr 2023 15:11:09 +0800 Subject: [PATCH 54/61] Update the changelogs. --- changelog.txt | 2 +- debian/changelog | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 086debdf..c562f300 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,5 @@ NixNote (2.1.7) stable; urgency=low - * Made tidy keep A nodes(HTML link element) having no href attribute if its inner text is not empty when saving the note. + * Added a new dark theme. * Optimised the sql statements of note creating and updating. * Optimised the sql statements of batch operations, including movings, deletings, and restorings. * Added an option to listen to the commands from command line.(When it is enabled, the shared memory size will be set to 128 bytes; when disabled, it will be set to 1 byte.) diff --git a/debian/changelog b/debian/changelog index 086debdf..c562f300 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,5 @@ NixNote (2.1.7) stable; urgency=low - * Made tidy keep A nodes(HTML link element) having no href attribute if its inner text is not empty when saving the note. + * Added a new dark theme. * Optimised the sql statements of note creating and updating. * Optimised the sql statements of batch operations, including movings, deletings, and restorings. * Added an option to listen to the commands from command line.(When it is enabled, the shared memory size will be set to 128 bytes; when disabled, it will be set to 1 byte.) From 76ef573aaeacb4c7c7ce8b943a5756164d03fdae Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 15 Apr 2023 16:32:52 +0800 Subject: [PATCH 55/61] Move getCheckboxImageUrl() and getCheckboxElement() from NixnoteStringUtils to Global, to decouple NixnoteStringUtils from global object, so that unit test can be easier to pass. --- src/global.cpp | 26 ++++++++++++++++++++++++++ src/global.h | 2 ++ src/gui/nbrowserwindow.cpp | 22 +++++++++++----------- src/html/noteformatter.cpp | 2 +- src/utilities/NixnoteStringUtils.cpp | 25 ------------------------- src/utilities/NixnoteStringUtils.h | 5 ----- 6 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/global.cpp b/src/global.cpp index 10e55cd3..31963c2c 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1598,3 +1598,29 @@ void Global::clearResourceList() { qIconList.clear(); qPixmapList.clear(); } + +QString Global::getCheckboxImageUrl(bool checked) { + QString fileName = checked ? "checkbox_checked.png": "checkbox.png"; + QString filePath = global.fileManager.getImageDirPath("").append(fileName); + + QString prefix = QString("file://"); +#ifdef _WIN32 + prefix.append("/"); +#endif + return prefix + filePath; +} + +QString Global::getCheckboxElement(bool checked, bool escapeTwice) { + QString defaultUrl = getCheckboxImageUrl(false); + QString checkedUrl = getCheckboxImageUrl(true); + QString url = !checked ? defaultUrl : checkedUrl; + return QString("") : QString("\';\">")); +} diff --git a/src/global.h b/src/global.h index b1962f94..743cee3f 100644 --- a/src/global.h +++ b/src/global.h @@ -178,6 +178,7 @@ class Global : public QObject { private: void getThemeNamesFromFile(QString fileName, QStringList &values); + QString getCheckboxImageUrl(bool checked); int accountId; // Force notes search text to be lower case. Useful for some non-ASCII languages. @@ -207,6 +208,7 @@ class Global : public QObject { void setDateFormat(int fmtNo); void setTimeFormat(int time); + QString getCheckboxElement(bool checked, bool escapeTwice); Global(); // Generic constructor virtual ~Global() {}; // destructor diff --git a/src/gui/nbrowserwindow.cpp b/src/gui/nbrowserwindow.cpp index 39d76831..940ebd58 100644 --- a/src/gui/nbrowserwindow.cpp +++ b/src/gui/nbrowserwindow.cpp @@ -1410,8 +1410,8 @@ void NBrowserWindow::todoButtonPressed() { QString selectedHtml = editor->selectedHtml(); int length = selectedHtml.length(); - selectedHtml.replace(NixnoteStringUtils::getCheckboxElement(true, false), ""); - selectedHtml.replace(NixnoteStringUtils::getCheckboxElement(false, false), ""); + selectedHtml.replace(global.getCheckboxElement(true, false), ""); + selectedHtml.replace(global.getCheckboxElement(false, false), ""); if (selectedHtml.length() < length) { editor->page()->mainFrame()->evaluateJavaScript(script_start + selectedHtml + script_end); @@ -1424,7 +1424,7 @@ void NBrowserWindow::todoButtonPressed() { QString html = ""; for (int i = 0; i < items.size(); i++) { - html += "
" + NixnoteStringUtils::getCheckboxElement(false, true) + items[i] + + html += "
" + global.getCheckboxElement(false, true) + items[i] + "
"; } @@ -1447,15 +1447,15 @@ void NBrowserWindow::todoSetAllChecked(bool allSelected) { QString html = editor->selectedHtml(); if (allSelected) { - html.replace(NixnoteStringUtils::getCheckboxElement(false, false), - NixnoteStringUtils::getCheckboxElement(true, true)); - html.replace(NixnoteStringUtils::getCheckboxElement(true, false), - NixnoteStringUtils::getCheckboxElement(true, true)); + html.replace(global.getCheckboxElement(false, false), + global.getCheckboxElement(true, true)); + html.replace(global.getCheckboxElement(true, false), + global.getCheckboxElement(true, true)); } else { - html.replace(NixnoteStringUtils::getCheckboxElement(true, false), - NixnoteStringUtils::getCheckboxElement(false, true)); - html.replace(NixnoteStringUtils::getCheckboxElement(false, false), - NixnoteStringUtils::getCheckboxElement(false, true)); + html.replace(global.getCheckboxElement(true, false), + global.getCheckboxElement(false, true)); + html.replace(global.getCheckboxElement(false, false), + global.getCheckboxElement(false, true)); } editor->page()->mainFrame()->evaluateJavaScript(script_start + html + script_end); diff --git a/src/html/noteformatter.cpp b/src/html/noteformatter.cpp index 45f52214..3b1682ee 100644 --- a/src/html/noteformatter.cpp +++ b/src/html/noteformatter.cpp @@ -790,7 +790,7 @@ void NoteFormatter::modifyTodoTags(QWebElement &todo) { isChecked = true; } - QString xml = NixnoteStringUtils::getCheckboxElement(isChecked, false); + QString xml = global.getCheckboxElement(isChecked, false); todo.setOuterXml(xml + todo.toInnerXml()); QLOG_TRACE_OUT(); } diff --git a/src/utilities/NixnoteStringUtils.cpp b/src/utilities/NixnoteStringUtils.cpp index 3a0e14eb..d1ed2822 100644 --- a/src/utilities/NixnoteStringUtils.cpp +++ b/src/utilities/NixnoteStringUtils.cpp @@ -89,28 +89,3 @@ QString NixnoteStringUtils::extractNoteGuid(QString noteUrl) { return splitNoteUrl[splitNoteUrl.count()-1]; } -QString NixnoteStringUtils::getCheckboxImageUrl(bool checked) { - QString fileName = checked ? "checkbox_checked.png": "checkbox.png"; - QString filePath = global.fileManager.getImageDirPath("").append(fileName); - - QString prefix = QString("file://"); -#ifdef _WIN32 - prefix.append("/"); -#endif - return prefix + filePath; -} - -QString NixnoteStringUtils::getCheckboxElement(bool checked, bool escapeTwice) { - QString defaultUrl = getCheckboxImageUrl(false); - QString checkedUrl = getCheckboxImageUrl(true); - QString url = !checked ? defaultUrl : checkedUrl; - return QString("") : QString("\';\">")); -} diff --git a/src/utilities/NixnoteStringUtils.h b/src/utilities/NixnoteStringUtils.h index ec7038b3..00b4a175 100644 --- a/src/utilities/NixnoteStringUtils.h +++ b/src/utilities/NixnoteStringUtils.h @@ -71,11 +71,6 @@ class NixnoteStringUtils { * @return note GUID */ static QString extractNoteGuid(QString noteUrl); - - static QString getCheckboxElement(bool checked, bool escapeTwice); - -private: - static QString getCheckboxImageUrl(bool checked); }; #endif // NIXNOTE_STRING_UTILS_H From 183b62ca091fcbd380d5380e0662f686c761fbc7 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 15 Apr 2023 16:35:00 +0800 Subject: [PATCH 56/61] Move the sql statement of deleting the restored notes from filter from NTableView::restoreSelectedNotes() to NoteTable::restoreNotes(), where it is more convenient and suitable to execute. --- src/gui/ntableview.cpp | 3 --- src/sql/notetable.cpp | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/ntableview.cpp b/src/gui/ntableview.cpp index a7b85c43..9249c927 100644 --- a/src/gui/ntableview.cpp +++ b/src/gui/ntableview.cpp @@ -671,9 +671,6 @@ void NTableView::restoreSelectedNotes() { NoteTable ntable(global.db); NSqlQuery sql(global.db); //transaction.exec("begin"); - sql.prepare("Delete from filter where lid=:lid"); - sql.exec(); - sql.finish(); ntable.restoreNotes(lids, true); for (int i = 0; i < lids.size(); i++) { global.cache.remove(lids[i]); diff --git a/src/sql/notetable.cpp b/src/sql/notetable.cpp index c12c4636..f415cdcd 100644 --- a/src/sql/notetable.cpp +++ b/src/sql/notetable.cpp @@ -1702,6 +1702,10 @@ void NoteTable::restoreNotes(const QList &lids, bool isDirty=true) { bindLids(query, lids); query.exec(); + query.prepare("Delete from filter where lid in (" + slids + ")"); + bindLids(query, lids); + query.exec(); + query.finish(); db->unlock(); } From de122fffc49e78d6dd1c81f11c96a11a2e9a9b42 Mon Sep 17 00:00:00 2001 From: Robert Spiegel Date: Sat, 15 Apr 2023 12:17:59 +0200 Subject: [PATCH 57/61] Develop branch marked as being version 2.1.9 --- changelog.txt | 4 ++++ debian/changelog | 4 ++++ help/about.html | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 07e22ee6..a6818ebc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +NixNote (2.1.9) stable; urgency=low + * ... + -- Robert Spiegel Sat, 15 Apr 2023 12:15:22 +0200 + NixNote (2.1.8) stable; urgency=low * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. diff --git a/debian/changelog b/debian/changelog index 07e22ee6..a6818ebc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,7 @@ +NixNote (2.1.9) stable; urgency=low + * ... + -- Robert Spiegel Sat, 15 Apr 2023 12:15:22 +0200 + NixNote (2.1.8) stable; urgency=low * Made the font size in the setting dialog consistent with the one in the editor button bar. * Made the Windows editon's results of find more recognizable. diff --git a/help/about.html b/help/about.html index acec8d37..c79dc1b0 100644 --- a/help/about.html +++ b/help/about.html @@ -6,7 +6,7 @@

__VERSION__

(c) 2008-2018 Randy Baumgarte
- (c) 2018-2022 Robert Spiegel + (c) 2018-2023 Robert Spiegel & contributors.
License: GNU GPLv3

From 6fe189031e6ead0f8b61145d2fa44f216b0530ac Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Sat, 15 Apr 2023 22:06:15 +0800 Subject: [PATCH 58/61] Delete tedious header including and statement. --- src/utilities/NixnoteStringUtils.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utilities/NixnoteStringUtils.cpp b/src/utilities/NixnoteStringUtils.cpp index d1ed2822..f317480b 100644 --- a/src/utilities/NixnoteStringUtils.cpp +++ b/src/utilities/NixnoteStringUtils.cpp @@ -19,9 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "NixnoteStringUtils.h" -#include "src/global.h" -extern Global global; NixnoteStringUtils::NixnoteStringUtils() { From 9e1d51aa0c6c13bc39c090837d07e6f83e521a3f Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Mon, 17 Apr 2023 16:18:38 +0800 Subject: [PATCH 59/61] Update the changelogs. --- changelog.txt | 6 +++--- debian/changelog | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/changelog.txt b/changelog.txt index 95f50165..b03a64b7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -6,8 +6,8 @@ NixNote (2.1.9) stable; urgency=low * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. * Made Nixnote export the stack attribute when exporting notes. * Added an option to save the UI state changes when Nixnote exits. - * Saved the note content when Nixnote receives specific signal interruptions(SIGINT, SIGHUP). - * Allocated the tageditor's tags dynamically. + * Save the note content when Nixnote receives specific signal interruptions(SIGINT, SIGHUP). + * Allocate the tageditor's tags dynamically. * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. * Fixed file downloading under Windows. @@ -23,7 +23,7 @@ NixNote (2.1.8) stable; urgency=low * Fixed: In-App Note links don't work when there is an apostrophe in the title - issue #168 * Fixed: Nixnote2 exits when network gets disconnected - issue #189 * Fixed: Import all notes, tag issue - issue #153 - * Fixed: Local images cannot be pasted, web image's html element cannot be saved + * Fixed: Local images cannot be pasted, images in notes cannot be saved. -- Robert Spiegel Sat, 10 Sep 2022 10:00:00 +0200 NixNote (2.1.7) stable; urgency=low diff --git a/debian/changelog b/debian/changelog index 6b68b87c..5e26bb17 100644 --- a/debian/changelog +++ b/debian/changelog @@ -6,8 +6,8 @@ NixNote (2.1.9) stable; urgency=low * Decoupled the functions of CmdLineTool(except signalGui()) from shared memory. * Made Nixnote export the stack attribute when exporting notes. * Added an option to save the UI state changes when Nixnote exits. - * Saved the note content when Nixnote receives specific signal interruptions(SIGINT, SIGHUP). - * Allocated the tageditor's tags dynamically. + * Save the note content when Nixnote receives specific signal interruptions(SIGINT, SIGHUP). + * Allocate the tageditor's tags dynamically. * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. * Fixed file downloading under Windows. @@ -23,7 +23,7 @@ NixNote (2.1.8) stable; urgency=low * Fixed: In-App Note links don't work when there is an apostrophe in the title - issue #168 * Fixed: Nixnote2 exits when network gets disconnected - issue #189 * Fixed: Import all notes, tag issue - issue #153 - * Fixed: Local images cannot be pasted, web image's html element cannot be saved + * Fixed: Local images cannot be pasted, images in notes cannot be saved. -- Robert Spiegel Sat, 10 Sep 2022 10:00:00 +0200 NixNote (2.1.7) stable; urgency=low From 6b2865eb69740a646539c116779baf4609102ff1 Mon Sep 17 00:00:00 2001 From: Boo Yee Date: Mon, 17 Apr 2023 16:33:52 +0800 Subject: [PATCH 60/61] Update the changelogs. --- changelog.txt | 2 +- debian/changelog | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index b03a64b7..d21c0efd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -11,7 +11,7 @@ NixNote (2.1.9) stable; urgency=low * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. * Fixed file downloading under Windows. - * Made the editor not render the note content when Key_Up or Key_Down is kept being pressed. + * Made the editor not render the note content when Key_Up or Key_Down keeps being pressed. -- Robert Spiegel Sat, 15 Apr 2023 12:15:22 +0200 NixNote (2.1.8) stable; urgency=low diff --git a/debian/changelog b/debian/changelog index 5e26bb17..31531315 100644 --- a/debian/changelog +++ b/debian/changelog @@ -11,7 +11,7 @@ NixNote (2.1.9) stable; urgency=low * Added a default theme named with 'Default' when no theme is found in themes.ini. * RAM usage optimizations. * Fixed file downloading under Windows. - * Made the editor not render the note content when Key_Up or Key_Down is kept being pressed. + * Made the editor not render the note content when Key_Up or Key_Down keeps being pressed. -- Robert Spiegel Sat, 15 Apr 2023 12:15:22 +0200 NixNote (2.1.8) stable; urgency=low From fa5206f73e4695f31d0d13e8d510aed213d7ca0f Mon Sep 17 00:00:00 2001 From: Robert Spiegel Date: Wed, 19 Apr 2023 20:36:53 +0200 Subject: [PATCH 61/61] Removed not obsolete naming based on "2.1" --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5a5f5eb..934a1dc2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NixNote v2.1 +# NixNote2 ## Introduction Nixnote is Evernote desktop client for Linux (can be also build on macOS and Windows). @@ -23,7 +23,7 @@ So far I know Nixnote works quite fine for many people... in case there would be ## Packages ### Debian, Ubuntu and derivatives official repositories In case you distribution is based on **Debian 10 (Buster) or Ubuntu 19.04 (Disco) or later distribution -versions**, you can install Nixnote 2.1 from official repositories using: +versions**, you can install Nixnote2 from official repositories using: ``` bash sudo apt update