From cefa7259adf780aca756e87976b12628b306d112 Mon Sep 17 00:00:00 2001 From: Scharel Clemens Date: Mon, 11 Nov 2019 23:23:41 +0100 Subject: [PATCH] Implemented QSortFilterProxyModel --- qml/harbour-nextcloudnotes.qml | 2 +- qml/pages/EditPage.qml | 34 ++-- qml/pages/NotePage.qml | 38 ++--- qml/pages/NotesPage.qml | 10 +- qml/pages/SettingsPage.qml | 20 +-- src/harbour-nextcloudnotes.cpp | 2 +- src/note.cpp | 68 +------- src/note.h | 69 ++++---- src/notesapi.cpp | 7 + src/notesapi.h | 5 +- src/notesmodel.cpp | 211 +++++++------------------ src/notesmodel.h | 85 +++++----- translations/harbour-nextcloudnotes.ts | 76 ++++----- 13 files changed, 228 insertions(+), 399 deletions(-) diff --git a/qml/harbour-nextcloudnotes.qml b/qml/harbour-nextcloudnotes.qml index 543022b..a2053a2 100644 --- a/qml/harbour-nextcloudnotes.qml +++ b/qml/harbour-nextcloudnotes.qml @@ -40,7 +40,7 @@ ApplicationWindow property int autoSyncInterval: value("autoSyncInterval", 0, Number) property int previewLineCount: value("previewLineCount", 4, Number) property bool favoritesOnTop: value("favoritesOnTop", true, Boolean) - property string sortBy: value("sortBy", "date", String) + property string sortBy: value("sortBy", "modified", String) property bool showSeparator: value("showSeparator", false, Boolean) property bool useMonoFont: value("useMonoFont", false, Boolean) property bool useCapitalX: value("useCapitalX", false, Boolean) diff --git a/qml/pages/EditPage.qml b/qml/pages/EditPage.qml index 6d231aa..11d374e 100644 --- a/qml/pages/EditPage.qml +++ b/qml/pages/EditPage.qml @@ -17,17 +17,17 @@ Dialog { property string date onAccepted: { - api.updateNote(note.id, { 'category': categoryField.text, 'content': contentArea.text, 'favorite': favoriteButton.selected } ) + notesApi.updateNote(id, { 'category': categoryField.text, 'content': contentArea.text, 'favorite': favoriteButton.selected } ) } function reloadContent() { - api.getNoteFromApi(note.id) - /*note = api.getNote(note.id) - dialogHeader.title = note.title - contentArea.text = note.content - favoriteButton.selected = note.favorite - categoryField.text = note.category - modifiedDetail.modified = note.modified*/ + //notesApi.getNoteFromApi(id) + /*note = notesApi.getNote(id) + dialogHeader.title = title + contentArea.text = content + favoriteButton.selected = favorite + categoryField.text = category + modifiedDetail.modified = modified*/ } SilicaFlickable { @@ -51,7 +51,7 @@ Dialog { DialogHeader { id: dialogHeader - //title: note.title + title: title } Column { @@ -67,7 +67,7 @@ Dialog { TextArea { id: contentArea width: parent.width - //text: note.content + text: content placeholderText: qsTr("No content") font.family: appSettings.useMonoFont ? "DejaVu Sans Mono" : Theme.fontFamily property int preTextLength: 0 @@ -112,7 +112,7 @@ Dialog { Repeater { id: categoryRepeater - model: api.categories + model: notesApi.categories BackgroundItem { id: categoryBackground width: categoryRectangle.width @@ -141,18 +141,18 @@ Dialog { width: parent.width - x IconButton { id: favoriteButton - property bool selected//: note.favorite + property bool selected: favorite width: Theme.iconSizeMedium icon.source: (selected ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") + (favoriteButton.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor) onClicked: { - api.updateNote(note.id, {'favorite': !note.favorite}) + api.updateNote(id, {'favorite': !favorite}) } } TextField { id: categoryField width: parent.width - favoriteButton.width - //text: note.category + text: category placeholderText: qsTr("No category") label: qsTr("Category") EnterKey.iconSource: "image://theme/icon-m-enter-accept" @@ -160,8 +160,8 @@ Dialog { categoryField.focus = false } onFocusChanged: { - if (focus === false && text !== note.category) { - api.updateNote(note.id, {'content': note.content, 'category': text}) // This does not seem to work without adding the content + if (focus === false && text !== category) { + notesApi.updateNote(id, {'content': content, 'category': text}) // This does not seem to work without adding the content } } } @@ -170,7 +170,7 @@ Dialog { DetailItem { id: modifiedDetail label: qsTr("Modified") - property int modified//: note.modified + property int modified//: modified value: new Date(modified * 1000).toLocaleString(Qt.locale(), Locale.ShortFormat) } } diff --git a/qml/pages/NotePage.qml b/qml/pages/NotePage.qml index 83719a3..103b0cd 100644 --- a/qml/pages/NotePage.qml +++ b/qml/pages/NotePage.qml @@ -6,7 +6,7 @@ import "../js/showdown/dist/showdown.js" as ShowDown Dialog { id: noteDialog - property Note note + //property Note note property int id property int modified @@ -45,22 +45,22 @@ Dialog { errorMessage: errorMessage, date: date } ) onAccepted: { - acceptDestinationInstance.note = note + //acceptDestinationInstance.note = note acceptDestinationInstance.reloadContent() } onStatusChanged: { if (status === DialogStatus.Opened) { - api.getNoteFromApi(id) + //notesApi.getNoteFromApi(id) } } Component.onCompleted: { - console.log(note.title) + console.log(title) parseContent() } function reloadContent() { - api.getNoteFromApi(id) - /*note = api.getNote(id) + //notesApi.getNoteFromApi(id) + /*note = notesApi.getNote(id) dialogHeader.title = title favoriteButton.selected = favorite categoryField.text = category @@ -69,7 +69,7 @@ Dialog { } function parseContent() { - //note = api.getNoteFromApi(id, false) + //note = notesApi.getNoteFromApi(id, false) var convertedText = converter.makeHtml(content) var occurence = -1 convertedText = convertedText.replace(/^
  • (

    )?\[ \] (.*)(<.*)$/gmi, @@ -109,22 +109,22 @@ Dialog { onTriggered: pageStack.pop() } PullDownMenu { - busy: api.busy + busy: notesApi.busy MenuItem { text: qsTr("Delete") - onClicked: remorse.execute("Deleting", function() { api.deleteNote(id) } ) + onClicked: remorse.execute("Deleting", function() { notesApi.deleteNote(id) } ) } MenuItem { text: enabled ? qsTr("Reload") : qsTr("Updating...") - enabled: !api.busy - onClicked: api.getNoteFromApi(noteID) + enabled: !notesApi.busy + onClicked: notesApi.getNoteFromApi(noteID) } MenuLabel { visible: appSettings.currentAccount.length >= 0 text: qsTr("Last update") + ": " + ( - new Date(api.update).valueOf() !== 0 ? - new Date(api.update).toLocaleString(Qt.locale(), Locale.ShortFormat) : + new Date(notesApi.update).valueOf() !== 0 ? + new Date(notesApi.update).toLocaleString(Qt.locale(), Locale.ShortFormat) : qsTr("never")) } } @@ -140,7 +140,7 @@ Dialog { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter size: BusyIndicatorSize.Medium - running: api.busy + running: notesApi.busy } Column { @@ -174,7 +174,7 @@ Dialog { } ) content = newContent parseContent() - api.updateNote(id, { 'content': content } ) + notesApi.updateNote(id, { 'content': content } ) } else if (/^tasklist:uncheckbox_(\d+)$/m.test(link)) { newContent = newContent.replace(/- \[[xX]\] (.*)$/gm, @@ -186,7 +186,7 @@ Dialog { } ) content = newContent parseContent() - api.updateNote(id, { 'content': content } ) + notesApi.updateNote(id, { 'content': content } ) } else { Qt.openUrlExternally(link) @@ -210,7 +210,7 @@ Dialog { Repeater { id: categoryRepeater - model: api.categories + model: notesApi.categories BackgroundItem { id: categoryBackground width: categoryRectangle.width @@ -246,7 +246,7 @@ Dialog { icon.source: (selected ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") + (favoriteButton.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor) onClicked: { - api.updateNote(id, {'favorite': !favorite}) + notesApi.updateNote(id, {'favorite': !favorite}) } } TextField { @@ -261,7 +261,7 @@ Dialog { } onFocusChanged: { if (focus === false && text !== category) { - api.updateNote(id, {'content': content, 'category': text}) // This does not seem to work without adding the content + notesApi.updateNote(id, {'content': content, 'category': text}) // This does not seem to work without adding the content } } } diff --git a/qml/pages/NotesPage.qml b/qml/pages/NotesPage.qml index c73ac66..e36c310 100644 --- a/qml/pages/NotesPage.qml +++ b/qml/pages/NotesPage.qml @@ -7,6 +7,11 @@ Page { id: page property NotesModel notesModel: notesApi.model() + Connections { + target: appSettings + onSortByChanged: notesModel.sortRole = notesModel.sortingRole(appSettings.sortBy) + onFavoritesOnTopChanged: notesModel.favoritesOnTop = appSettings.favoritesOnTop + } onStatusChanged: { if (status === PageStatus.Active) { @@ -59,7 +64,7 @@ Page { placeholderText: account.name.length > 0 ? account.name : qsTr("Nextcloud Notes") EnterKey.iconSource: "image://theme/icon-m-enter-close" EnterKey.onClicked: focus = false - onTextChanged: notesModel.searchText = text + onTextChanged: notesModel.setFilterFixedString(text) } Label { id: description @@ -88,7 +93,6 @@ Page { delegate: BackgroundItem { id: note - visible: inSearch contentHeight: titleLabel.height + previewLabel.height + 2*Theme.paddingSmall height: contentHeight + menu.height width: parent.width @@ -250,7 +254,7 @@ Page { ViewPlaceholder { id: noSearchPlaceholder - enabled: notesList.count === 0 && notesModel.searchText !== "" + enabled: notesList.count === 0 && searchField.text !== "" text: qsTr("No result") hintText: qsTr("Try another query") } diff --git a/qml/pages/SettingsPage.qml b/qml/pages/SettingsPage.qml index fc5d420..19f81d0 100644 --- a/qml/pages/SettingsPage.qml +++ b/qml/pages/SettingsPage.qml @@ -2,7 +2,7 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import Nemo.Configuration 1.0 import Nemo.Notifications 1.0 - +import harbour.nextcloudnotes.notesmodel 1.0 Page { id: page @@ -144,24 +144,26 @@ Page { } ComboBox { id: sortByComboBox - property var names: { "date" : qsTr("Last edited"), - "category" : qsTr("Category"), - "title" : qsTr("Title alphabetically"), - "none" : qsTr("No sorting") } + property var criteria: [ + { role: "modified", text: qsTr("Last edited") }, + { role: "category", text: qsTr("Category") }, + { role: "title", text: qsTr("Title alphabetically") }, + { role: "none", text: qsTr("No sorting") } + ] label: qsTr("Sort notes by") description: qsTr("This will also change how the notes are grouped") menu: ContextMenu { Repeater { id: sortByRepeater - model: noteListModel.sortingCriteria() + model: sortByComboBox.criteria MenuItem { - text: sortByComboBox.names[modelData] + text: modelData.text Component.onCompleted: { - if (modelData === appSettings.sortBy) { + if (modelData.role === appSettings.sortBy) { sortByComboBox.currentIndex = index } } - onClicked: appSettings.sortBy = modelData + onClicked: appSettings.sortBy = modelData.role } } } diff --git a/src/harbour-nextcloudnotes.cpp b/src/harbour-nextcloudnotes.cpp index cc8640b..9327371 100644 --- a/src/harbour-nextcloudnotes.cpp +++ b/src/harbour-nextcloudnotes.cpp @@ -18,7 +18,7 @@ int main(int argc, char *argv[]) qDebug() << app->applicationDisplayName() << app->applicationVersion(); qmlRegisterType("harbour.nextcloudnotes.notesapi", 1, 0, "NotesApi"); qmlRegisterType("harbour.nextcloudnotes.note", 1, 0, "Note"); - qmlRegisterType("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel"); + qmlRegisterType("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel"); QQuickView* view = SailfishApp::createView(); diff --git a/src/note.cpp b/src/note.cpp index 5eca4d6..c82e106 100644 --- a/src/note.cpp +++ b/src/note.cpp @@ -42,18 +42,6 @@ Note& Note::operator=(const Note& note) { } bool Note::operator==(const Note& note) const { - return equal(note); -} - -bool Note::same(const Note& note) const { - return m_id == note.id(); -} - -bool Note::same(const int id) const { - return m_id == id; -} - -bool Note::equal(const Note& note) const { return m_id == note.id() && m_modified == note.modified() && m_title == note.title() && @@ -65,12 +53,8 @@ bool Note::equal(const Note& note) const { m_errorMessage == note.errorMessage(); } -bool Note::newer(const Note& note) const { - return same(note) && note.modified() > m_modified; -} - -bool Note::older(const Note& note) const { - return same(note) && note.modified() < m_modified; +bool Note::same(const Note ¬e) const { + return m_id == note.id(); } QString Note::dateString() const { @@ -104,51 +88,3 @@ Note Note::fromjson(const QJsonObject& jobj) { note.setErrorMessage(jobj.value("errorMessage").toString()); return note; } - -bool Note::searchInNote(const QString &query, const Note ¬e, SearchAttributes criteria, Qt::CaseSensitivity cs) { - bool queryFound = false; - if (criteria.testFlag(SearchInTitle)) { - queryFound |= note.title().contains(query, cs); - } - if (criteria.testFlag(SearchInContent)) { - queryFound |= note.content().contains(query, cs); - } - if (criteria.testFlag(SearchInCategory)) { - queryFound |= note.category().contains(query, cs); - } - return queryFound; -} - -bool Note::lessThanByDate(const Note &n1, const Note &n2) { - return n1.modified() > n2.modified(); -} - -bool Note::lessThanByCategory(const Note &n1, const Note &n2) { - return n1.category() > n2.category(); -} - -bool Note::lessThanByTitle(const Note &n1, const Note &n2) { - return n1.title() > n2.title(); -} - -bool Note::lessThanByDateFavOnTop(const Note &n1, const Note &n2) { - if (n1.favorite() != n2.favorite()) - return n1.favorite(); - else - return n1.modified() > n2.modified(); -} - -bool Note::lessThanByCategoryFavOnTop(const Note &n1, const Note &n2) { - if (n1.favorite() != n2.favorite()) - return n1.favorite(); - else - return n1.category() > n2.category(); -} - -bool Note::lessThanByTitleFavOnTop(const Note &n1, const Note &n2) { - if (n1.favorite() != n2.favorite()) - return n1.favorite(); - else - return n1.title() > n2.title(); -} - diff --git a/src/note.h b/src/note.h index eb28a2a..dc6cb60 100644 --- a/src/note.h +++ b/src/note.h @@ -9,17 +9,6 @@ class Note : public QObject { Q_OBJECT - Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged) - Q_PROPERTY(uint modified READ modified WRITE setModified NOTIFY modifiedChanged) - Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) - Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged) - Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged) - Q_PROPERTY(bool favorite READ favorite WRITE setFavorite NOTIFY favoriteChanged) - Q_PROPERTY(QString etag READ etag WRITE setEtag NOTIFY etagChanged) - Q_PROPERTY(bool error READ error WRITE setError NOTIFY errorChanged) - Q_PROPERTY(QString errorMessage READ errorMessage WRITE setErrorMessage NOTIFY errorMessageChanged) - Q_PROPERTY(QString dateString READ dateString NOTIFY dateStringChanged) - public: Note(QObject *parent = NULL); Note(const Note& note, QObject *parent = NULL); @@ -27,49 +16,46 @@ public: Note& operator=(const Note& note); bool operator==(const Note& note) const; bool same(const Note& note) const; - bool same(const int id) const; - bool equal(const Note& note) const; - bool newer(const Note& note) const; - bool older(const Note& note) const; - - enum SearchAttribute { - NoSearchAttribute = 0x0, - SearchInTitle = 0x1, - SearchInCategory = 0x2, - SearchInContent = 0x4, - SearchAll = 0x7 - }; - Q_DECLARE_FLAGS(SearchAttributes, SearchAttribute) + Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged) int id() const { return m_id; } - uint modified() const { return m_modified; } - QString title() const { return m_title; } - QString category() const { return m_category; } - QString content() const { return m_content; } - bool favorite() const { return m_favorite; } - QString etag() const { return m_etag; } - bool error() const { return m_error; } - QString errorMessage() const { return m_errorMessage; } - QString dateString() const; - void setId(int id) { if (id != m_id) { m_id = id; emit idChanged(id); } } + + Q_PROPERTY(uint modified READ modified WRITE setModified NOTIFY modifiedChanged) + uint modified() const { return m_modified; } void setModified(uint modified) { if (modified != m_modified) { m_modified = modified; emit modifiedChanged(modified); emit dateStringChanged(dateString()); } } + + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) + QString title() const { return m_title; } void setTitle(QString title) { if (title != m_title) { m_title = title; emit titleChanged(title); } } + + Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged) + QString category() const { return m_category; } void setCategory(QString category) { if (category != m_category) { m_category = category; emit categoryChanged(category); } } + + Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged) + QString content() const { return m_content; } void setContent(QString content) { if (content != m_content) { m_content = content; emit contentChanged(content); } } + + Q_PROPERTY(bool favorite READ favorite WRITE setFavorite NOTIFY favoriteChanged) + bool favorite() const { return m_favorite; } void setFavorite(bool favorite) { if (favorite != m_favorite) { m_favorite = favorite; emit favoriteChanged(favorite); } } + + Q_PROPERTY(QString etag READ etag WRITE setEtag NOTIFY etagChanged) + QString etag() const { return m_etag; } void setEtag(QString etag) { if (etag != m_etag) { m_etag = etag; emit etagChanged(etag); } } + + Q_PROPERTY(bool error READ error WRITE setError NOTIFY errorChanged) + bool error() const { return m_error; } void setError(bool error) { if (error != m_error) { m_error = error; emit errorChanged(error); } } + + Q_PROPERTY(QString errorMessage READ errorMessage WRITE setErrorMessage NOTIFY errorMessageChanged) + QString errorMessage() const { return m_errorMessage; } void setErrorMessage(QString errorMessage) { if (errorMessage != m_errorMessage) { m_errorMessage = errorMessage; emit errorMessageChanged(errorMessage); } } + Q_PROPERTY(QString dateString READ dateString NOTIFY dateStringChanged) + QString dateString() const; static Note fromjson(const QJsonObject& jobj); - static bool searchInNote(const QString &query, const Note ¬e, SearchAttributes criteria = QFlag(SearchAll), Qt::CaseSensitivity cs = Qt::CaseInsensitive); - static bool lessThanByDate(const Note &n1, const Note &n2); - static bool lessThanByCategory(const Note &n1, const Note &n2); - static bool lessThanByTitle(const Note &n1, const Note &n2); - static bool lessThanByDateFavOnTop(const Note &n1, const Note &n2); - static bool lessThanByCategoryFavOnTop(const Note &n1, const Note &n2); - static bool lessThanByTitleFavOnTop(const Note &n1, const Note &n2); signals: void idChanged(int id); @@ -95,6 +81,5 @@ private: bool m_error; QString m_errorMessage; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(Note::SearchAttributes) #endif // NOTE_H diff --git a/src/notesapi.cpp b/src/notesapi.cpp index 119ae12..e93dbfd 100644 --- a/src/notesapi.cpp +++ b/src/notesapi.cpp @@ -7,6 +7,12 @@ NotesApi::NotesApi(QObject *parent) : QObject(parent) { mp_model = new NotesModel(this); + mp_modelProxy = new NotesProxyModel(this); + mp_modelProxy->setSourceModel(mp_model); + mp_modelProxy->setSortCaseSensitivity(Qt::CaseInsensitive); + mp_modelProxy->setSortLocaleAware(true); + mp_modelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + mp_modelProxy->setFilterRole(NotesModel::ContentRole); connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(verifyUrl(QUrl))); connect(&m_manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(requireAuthentication(QNetworkReply*,QAuthenticator*))); connect(&m_manager, SIGNAL(networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility)), this, SLOT(onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility))); @@ -19,6 +25,7 @@ NotesApi::NotesApi(QObject *parent) : QObject(parent) } NotesApi::~NotesApi() { + delete mp_modelProxy; delete mp_model; } diff --git a/src/notesapi.h b/src/notesapi.h index fd2f7e4..cea0a61 100644 --- a/src/notesapi.h +++ b/src/notesapi.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "notesmodel.h" @@ -62,7 +61,7 @@ public: Q_INVOKABLE void createNote(QVariantMap fields = QVariantMap()); Q_INVOKABLE void updateNote(int noteId, QVariantMap fields = QVariantMap()); Q_INVOKABLE void deleteNote(int noteId); - Q_INVOKABLE NotesModel* model() const { return mp_model; } + Q_INVOKABLE NotesProxyModel* model() const { return mp_modelProxy; } signals: void sslVerifyChanged(bool verify); @@ -92,7 +91,7 @@ private: QNetworkRequest m_request; QVector m_replies; NotesModel* mp_model; - QSortFilterProxyModel* mp_modelProxy; // TODO: use! + NotesProxyModel* mp_modelProxy; // TODO: use! }; #endif // NOTESAPI_H diff --git a/src/notesmodel.cpp b/src/notesmodel.cpp index e39cff5..6627fa7 100644 --- a/src/notesmodel.cpp +++ b/src/notesmodel.cpp @@ -6,65 +6,53 @@ #include #include -NotesModel::NotesModel(QObject *parent) : QAbstractListModel(parent) -{ - m_sortBy = noSorting; +NotesProxyModel::NotesProxyModel(QObject *parent) { m_favoritesOnTop = true; } -NotesModel::~NotesModel() { - //clear(); +NotesProxyModel::~NotesProxyModel() { + } -void NotesModel::setSortBy(QString sortBy) { - qDebug() << "Sorting by:" << sortBy; - if (sortBy != m_sortBy && sortingNames().values().contains(sortBy.toLocal8Bit())) { - m_sortBy = sortBy; - sort(); - emit sortByChanged(m_sortBy); - } -} - -void NotesModel::setFavoritesOnTop(bool favoritesOnTop) { +void NotesProxyModel::setFavoritesOnTop(bool favoritesOnTop) { qDebug() << "Favorites on top:" << favoritesOnTop; if (favoritesOnTop != m_favoritesOnTop) { m_favoritesOnTop = favoritesOnTop; - sort(); emit favoritesOnTopChanged(m_favoritesOnTop); } } -void NotesModel::setSearchText(QString searchText) { - qDebug() << "Searching by:" << searchText; - if (searchText != m_searchText) { - m_searchText = searchText; - emit searchTextChanged(m_searchText); - if (m_searchText.isEmpty()) { - m_invisibleIds.clear(); - emit dataChanged(this->index(0), this->index(m_notes.size())); - } - else { - for (int i = 0; i < m_notes.size(); i++) { - if (Note::searchInNote(m_searchText, m_notes[i])) { - //qDebug() << "Note" << m_notes[i].title() << "in search"; - m_invisibleIds.removeAll(m_notes[i].id()); - } - else { - //qDebug() << "Note" << m_notes[i].title() << "not in search"; - m_invisibleIds.append(m_notes[i].id()); - } - emit dataChanged(this->index(i), this->index(i)); - } - } - } +QHash NotesProxyModel::sortingNames() const { + return QHash { + {ModifiedRole, roleNames()[ModifiedRole]}, + {CategoryRole, roleNames()[CategoryRole]}, + {TitleRole, roleNames()[TitleRole]}, + {noSorting, "none"} + }; } -void NotesModel::search(QString searchText) { - setSearchText(searchText); +QList NotesProxyModel::sortingRoles() const { + return sortingNames().keys(); } -void NotesModel::clearSearch() { - search(); +int NotesProxyModel::sortingRole(const QString &name) const { + return sortingNames().key(name.toLocal8Bit()); +} + +bool NotesProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { + QAbstractItemModel* source = sourceModel(); + if (m_favoritesOnTop && source->data(source_left, NotesModel::FavoriteRole) != source->data(source_right, NotesModel::FavoriteRole)) + return source->data(source_left, NotesModel::FavoriteRole).toBool(); + else + return source->data(source_left, sortRole()) < source->data(source_right, sortRole()); +} + +NotesModel::NotesModel(QObject *parent) { + +} + +NotesModel::~NotesModel() { + } bool NotesModel::applyJSON(const QJsonDocument &jdoc) { @@ -128,13 +116,27 @@ bool NotesModel::applyJSON(const QString &json) { return error.error == QJsonParseError::NoError; } +int NotesModel::indexOf(const Note ¬e) const { + return indexOf(note.id()); +} + +int NotesModel::indexOf(int id) const { + int retval = -1; + for (int i = 0; i < m_notes.size(); ++i) { + if (m_notes[i].id() == id) { + retval = i; + } + } + return retval; +} + int NotesModel::insertNote(const Note ¬e) { int position = indexOf(note.id()); if (position >= 0) { if (note.etag() != m_notes[position].etag()) { qDebug() << "-- Existing note " << note.title() << "changed, updating the model."; m_notes.replace(position, note); - emit dataChanged(this->index(position), this->index(position)); + emit dataChanged(index(position), index(position)); } else { qDebug() << "-- Existing note " << note.title() << "unchanged, nothing to do."; @@ -142,16 +144,20 @@ int NotesModel::insertNote(const Note ¬e) { } else { qDebug() << "-- New note" << note.title() << ", adding it to the model."; - position = insertPosition(note); + position = rowCount(); beginInsertRows(QModelIndex(), position, position); - m_notes.insert(position, note); + m_notes.append(note); endInsertRows(); } return position; } bool NotesModel::removeNote(const Note ¬e) { - int position = m_notes.indexOf(note); + return removeNote(note.id()); +} + +bool NotesModel::removeNote(int id) { + int position = indexOf(id); if (position >= 0 && position < m_notes.size()) { beginRemoveRows(QModelIndex(), position, position); m_notes.removeAt(position); @@ -161,17 +167,6 @@ bool NotesModel::removeNote(const Note ¬e) { return false; } -bool NotesModel::removeNote(int id) { - bool retval = false; - for (int i = 0; i < m_notes.size(); ++i) { - if (m_notes[i].id() == id) { - retval |= removeNote(m_notes[i]); - if (i > 0) i--; - } - } - return retval; -} - QHash NotesModel::roleNames() const { return QHash { {NotesModel::IdRole, "id"}, @@ -182,28 +177,10 @@ QHash NotesModel::roleNames() const { {NotesModel::FavoriteRole, "favorite"}, {NotesModel::EtagRole, "etag"}, {NotesModel::ErrorRole, "error"}, - {NotesModel::ErrorMessageRole, "errorMessage"}, - {NotesModel::InSearchRole, "inSearch"} + {NotesModel::ErrorMessageRole, "errorMessage"} }; } -QHash NotesModel::sortingNames() const { - return QHash { - {NotesModel::sortByDate, "date"}, - {NotesModel::sortByCategory, "category"}, - {NotesModel::sortByTitle, "title"}, - {NotesModel::noSorting, "none"} - }; -} - -QStringList NotesModel::sortingCriteria() const { - QStringList criteria; - QHash names = sortingNames(); - for (int i = 0; i <= noSorting; i++) - criteria << names[i]; - return criteria; -} - Qt::ItemFlags NotesModel::flags(const QModelIndex &index) const { if (index.isValid()) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; // | Qt::ItemIsSelectable @@ -214,12 +191,7 @@ Qt::ItemFlags NotesModel::flags(const QModelIndex &index) const { } int NotesModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) { - return 0; - } - else { - return m_notes.size(); - } + return m_notes.size(); } QVariant NotesModel::data(const QModelIndex &index, int role) const { @@ -233,10 +205,6 @@ QVariant NotesModel::data(const QModelIndex &index, int role) const { else if (role == EtagRole) return m_notes[index.row()].etag(); else if (role == ErrorRole) return m_notes[index.row()].error(); else if (role == ErrorMessageRole) return m_notes[index.row()].errorMessage(); - else if (role == InSearchRole) { - qDebug() << "Invisible:" << m_invisibleIds.contains(m_notes[index.row()].id()); - return !m_invisibleIds.contains(m_notes[index.row()].id()); - } return QVariant(); } @@ -244,76 +212,9 @@ QMap NotesModel::itemData(const QModelIndex &index) const { QMap map; if (!index.isValid()) return map; else { - for (int role = Qt::UserRole; role < Qt::UserRole + 10; ++role) { + for (int role = IdRole; role <= ErrorMessageRole; ++role) { map.insert(role, data(index, role)); } } return map; } - -void NotesModel::sort() { - qDebug() << "Sorting notes in the model"; - emit layoutAboutToBeChanged(QList (), VerticalSortHint); - if (m_favoritesOnTop) { - if (m_sortBy == sortingNames()[sortByDate]) { - std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByDateFavOnTop); - } - else if (m_sortBy == sortingNames()[sortByCategory]) { - std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByCategoryFavOnTop); - } - else if (m_sortBy == sortingNames()[sortByTitle]) { - std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByTitleFavOnTop); - } - } - else { - if (m_sortBy == sortingNames()[sortByDate]) { - std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByDate); - } - else if (m_sortBy == sortingNames()[sortByCategory]) { - std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByCategory); - } - else if (m_sortBy == sortingNames()[sortByTitle]) { - std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByTitle); - } - } - emit layoutChanged(QList (), VerticalSortHint); -} - -int NotesModel::indexOf(int id) const { - for (int i = 0; i < m_notes.size(); i++) { - if (m_notes[i].id() == id) - return i; - } - return -1; -} - -int NotesModel::insertPosition(const Note &n) const { - int lower = 0; - int upper = m_notes.size(); - while (lower < upper) { - int middle = qFloor(lower + (upper-lower) / 2); - bool result = noteLessThan(n, m_notes[middle]); - if (result) - upper = middle; - else - lower = middle + 1; - } - return lower; -} - -bool NotesModel::noteLessThan(const Note &n1, const Note &n2) const { - if (m_sortBy == sortingNames()[sortByDate]) { - return m_favoritesOnTop ? Note::lessThanByDateFavOnTop(n1, n2) : Note::lessThanByDate(n1, n2); - } - else if (m_sortBy == sortingNames()[sortByCategory]) { - return m_favoritesOnTop ? Note::lessThanByCategoryFavOnTop(n1, n2) : Note::lessThanByCategory(n1, n2); - } - else if (m_sortBy == sortingNames()[sortByTitle]) { - return m_favoritesOnTop ? Note::lessThanByTitleFavOnTop(n1, n2) : Note::lessThanByTitle(n1, n2); - } - else { - if (m_favoritesOnTop && n1.favorite() != n2.favorite()) - return n1.favorite(); - } - return true; -} diff --git a/src/notesmodel.h b/src/notesmodel.h index c5c2e49..0d5653b 100644 --- a/src/notesmodel.h +++ b/src/notesmodel.h @@ -1,31 +1,47 @@ #ifndef NOTESMODEL_H #define NOTESMODEL_H +#include #include #include #include "note.h" +class NotesProxyModel : public QSortFilterProxyModel { + Q_OBJECT +public: + explicit NotesProxyModel(QObject *parent = 0); + virtual ~NotesProxyModel(); + + Q_PROPERTY(bool favoritesOnTop READ favoritesOnTop WRITE setFavoritesOnTop NOTIFY favoritesOnTopChanged) + bool favoritesOnTop() const { return m_favoritesOnTop; } + void setFavoritesOnTop(bool favoritesOnTop); + + enum SortingCriteria { + ModifiedRole, + CategoryRole, + TitleRole, + noSorting = Qt::UserRole + 9 + }; + QHash sortingNames() const; + Q_INVOKABLE QList sortingRoles() const; + Q_INVOKABLE int sortingRole(const QString &name) const; + +protected: + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + +signals: + void favoritesOnTopChanged(bool favoritesOnTop); + +private: + bool m_favoritesOnTop; +}; + class NotesModel : public QAbstractListModel { Q_OBJECT public: explicit NotesModel(QObject *parent = 0); virtual ~NotesModel(); - Q_PROPERTY(QString sortBy READ sortBy WRITE setSortBy NOTIFY sortByChanged) - QString sortBy() const { return m_sortBy; } - void setSortBy(QString sortBy); - - Q_PROPERTY(bool favoritesOnTop READ favoritesOnTop WRITE setFavoritesOnTop NOTIFY favoritesOnTopChanged) - bool favoritesOnTop() const { return m_favoritesOnTop; } - void setFavoritesOnTop(bool favoritesOnTop); - - Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged) - QString searchText() const { return m_searchText; } - void setSearchText(QString searchText); - - Q_INVOKABLE void search(QString searchText = QString()); - Q_INVOKABLE void clearSearch(); - bool applyJSON(const QJsonDocument &jdoc); bool applyJSON(const QString &json); @@ -38,49 +54,28 @@ public: FavoriteRole = Qt::UserRole + 5, EtagRole = Qt::UserRole + 6, ErrorRole = Qt::UserRole + 7, - ErrorMessageRole = Qt::UserRole + 8, - InSearchRole = Qt::UserRole + 9 + ErrorMessageRole = Qt::UserRole + 8 }; QHash roleNames() const; - enum SortingCriteria { - sortByDate, - sortByCategory, - sortByTitle, - noSorting - }; - QHash sortingNames() const; - Q_INVOKABLE QStringList sortingCriteria() const; - Qt::ItemFlags flags(const QModelIndex &index) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; protected: - -signals: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector ()); - void sortByChanged(QString sortBy); - void favoritesOnTopChanged(bool favoritesOnTop); - void searchTextChanged(QString searchText); - -private: - QVector m_notes; - QVector m_invisibleIds; - QString m_sortBy; - bool m_favoritesOnTop; - QString m_searchText; - - void sort(); - //void update(); + int indexOf(const Note ¬e) const; + int indexOf(int id) const; int insertNote(const Note ¬e); bool replaceNote(const Note ¬e); bool removeNote(const Note ¬e); bool removeNote(int id); - int indexOf(int id) const; - int insertPosition(const Note &n) const; - bool noteLessThan(const Note &n1, const Note &n2) const; + +signals: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector ()); + +private: + QVector m_notes; }; #endif // NOTESMODEL_H diff --git a/translations/harbour-nextcloudnotes.ts b/translations/harbour-nextcloudnotes.ts index 86df888..e457a5f 100644 --- a/translations/harbour-nextcloudnotes.ts +++ b/translations/harbour-nextcloudnotes.ts @@ -196,12 +196,12 @@ Note - + Today - + Yesterday @@ -262,97 +262,97 @@ NotesPage - + Settings - + Add note - + Reload - + Updating... - + Last update - + never - + Nextcloud Notes - + Modified - + Delete - + Deleting note - + Loading notes... - + No account yet - + Got to the settings to add an account - + No notes yet - + Pull down to add a note - + No result - + Try another query - + An error occurred - + Open the settings to configure your Nextcloud accounts @@ -460,87 +460,87 @@ - + No sorting - + Favorites on top - + Show notes marked as favorite above the others - + Last edited - + Category - + Title alphabetically - + Sort notes by - + This will also change how the notes are grouped - + Show separator - + Show a separator line between the notes - + lines - + Number of lines in the preview - + Editing - + Monospaced font - + Use a monospeced font to edit a note - + Capital 'X' in checkboxes - + For interoperability with other apps such as Joplin