Implemented QSortFilterProxyModel

This commit is contained in:
Scharel Clemens 2019-11-11 23:23:41 +01:00
parent 3f128bcd63
commit cefa7259ad
13 changed files with 228 additions and 399 deletions

View file

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

View file

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

View file

@ -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(/^<li>(<p>)?\[ \] (.*)(<.*)$/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
}
}
}

View file

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

View file

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

View file

@ -18,7 +18,7 @@ int main(int argc, char *argv[])
qDebug() << app->applicationDisplayName() << app->applicationVersion();
qmlRegisterType<NotesApi>("harbour.nextcloudnotes.notesapi", 1, 0, "NotesApi");
qmlRegisterType<Note>("harbour.nextcloudnotes.note", 1, 0, "Note");
qmlRegisterType<NotesModel>("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel");
qmlRegisterType<NotesProxyModel>("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel");
QQuickView* view = SailfishApp::createView();

View file

@ -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 &note) 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 &note, 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();
}

View file

@ -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 &note, 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

View file

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

View file

@ -5,7 +5,6 @@
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QSortFilterProxyModel>
#include <QDebug>
#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<QNetworkReply*> m_replies;
NotesModel* mp_model;
QSortFilterProxyModel* mp_modelProxy; // TODO: use!
NotesProxyModel* mp_modelProxy; // TODO: use!
};
#endif // NOTESAPI_H

View file

@ -6,65 +6,53 @@
#include <QtMath>
#include <QDebug>
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<int, QByteArray> NotesProxyModel::sortingNames() const {
return QHash<int, QByteArray> {
{ModifiedRole, roleNames()[ModifiedRole]},
{CategoryRole, roleNames()[CategoryRole]},
{TitleRole, roleNames()[TitleRole]},
{noSorting, "none"}
};
}
void NotesModel::search(QString searchText) {
setSearchText(searchText);
QList<int> 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 &note) 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 &note) {
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 &note) {
}
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 &note) {
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 &note) {
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<int, QByteArray> NotesModel::roleNames() const {
return QHash<int, QByteArray> {
{NotesModel::IdRole, "id"},
@ -182,28 +177,10 @@ QHash<int, QByteArray> NotesModel::roleNames() const {
{NotesModel::FavoriteRole, "favorite"},
{NotesModel::EtagRole, "etag"},
{NotesModel::ErrorRole, "error"},
{NotesModel::ErrorMessageRole, "errorMessage"},
{NotesModel::InSearchRole, "inSearch"}
{NotesModel::ErrorMessageRole, "errorMessage"}
};
}
QHash<int, QByteArray> NotesModel::sortingNames() const {
return QHash<int, QByteArray> {
{NotesModel::sortByDate, "date"},
{NotesModel::sortByCategory, "category"},
{NotesModel::sortByTitle, "title"},
{NotesModel::noSorting, "none"}
};
}
QStringList NotesModel::sortingCriteria() const {
QStringList criteria;
QHash<int, QByteArray> 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<int, QVariant> NotesModel::itemData(const QModelIndex &index) const {
QMap<int, QVariant> 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<QPersistentModelIndex> (), 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<QPersistentModelIndex> (), 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;
}

View file

@ -1,31 +1,47 @@
#ifndef NOTESMODEL_H
#define NOTESMODEL_H
#include <QSortFilterProxyModel>
#include <QAbstractListModel>
#include <QDateTime>
#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<int, QByteArray> sortingNames() const;
Q_INVOKABLE QList<int> 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<int, QByteArray> roleNames() const;
enum SortingCriteria {
sortByDate,
sortByCategory,
sortByTitle,
noSorting
};
QHash<int, QByteArray> 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<int, QVariant> itemData(const QModelIndex &index) const;
protected:
signals:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int> ());
void sortByChanged(QString sortBy);
void favoritesOnTopChanged(bool favoritesOnTop);
void searchTextChanged(QString searchText);
private:
QVector<Note> m_notes;
QVector<int> m_invisibleIds;
QString m_sortBy;
bool m_favoritesOnTop;
QString m_searchText;
void sort();
//void update();
int indexOf(const Note &note) const;
int indexOf(int id) const;
int insertNote(const Note &note);
bool replaceNote(const Note &note);
bool removeNote(const Note &note);
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<int> &roles = QVector<int> ());
private:
QVector<Note> m_notes;
};
#endif // NOTESMODEL_H

View file

@ -196,12 +196,12 @@
<context>
<name>Note</name>
<message>
<location filename="../src/note.cpp" line="82"/>
<location filename="../src/note.cpp" line="66"/>
<source>Today</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/note.cpp" line="84"/>
<location filename="../src/note.cpp" line="68"/>
<source>Yesterday</source>
<translation type="unfinished"></translation>
</message>
@ -262,97 +262,97 @@
<context>
<name>NotesPage</name>
<message>
<location filename="../qml/pages/NotesPage.qml" line="38"/>
<location filename="../qml/pages/NotesPage.qml" line="36"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="42"/>
<location filename="../qml/pages/NotesPage.qml" line="40"/>
<source>Add note</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="47"/>
<location filename="../qml/pages/NotesPage.qml" line="45"/>
<source>Reload</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="47"/>
<location filename="../qml/pages/NotesPage.qml" line="45"/>
<source>Updating...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="53"/>
<location filename="../qml/pages/NotesPage.qml" line="51"/>
<source>Last update</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="56"/>
<location filename="../qml/pages/NotesPage.qml" line="54"/>
<source>never</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="66"/>
<location filename="../qml/pages/NotesPage.qml" line="64"/>
<source>Nextcloud Notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="206"/>
<location filename="../qml/pages/NotesPage.qml" line="203"/>
<source>Modified</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="209"/>
<location filename="../qml/pages/NotesPage.qml" line="206"/>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="211"/>
<location filename="../qml/pages/NotesPage.qml" line="208"/>
<source>Deleting note</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="241"/>
<location filename="../qml/pages/NotesPage.qml" line="238"/>
<source>Loading notes...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="247"/>
<location filename="../qml/pages/NotesPage.qml" line="244"/>
<source>No account yet</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="248"/>
<location filename="../qml/pages/NotesPage.qml" line="245"/>
<source>Got to the settings to add an account</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="254"/>
<location filename="../qml/pages/NotesPage.qml" line="251"/>
<source>No notes yet</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="255"/>
<location filename="../qml/pages/NotesPage.qml" line="252"/>
<source>Pull down to add a note</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="261"/>
<location filename="../qml/pages/NotesPage.qml" line="258"/>
<source>No result</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="262"/>
<location filename="../qml/pages/NotesPage.qml" line="259"/>
<source>Try another query</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="268"/>
<location filename="../qml/pages/NotesPage.qml" line="265"/>
<source>An error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="279"/>
<location filename="../qml/pages/NotesPage.qml" line="276"/>
<source>Open the settings to configure your Nextcloud accounts</source>
<translation type="unfinished"></translation>
</message>
@ -460,87 +460,87 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="150"/>
<location filename="../qml/pages/SettingsPage.qml" line="151"/>
<source>No sorting</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="173"/>
<location filename="../qml/pages/SettingsPage.qml" line="175"/>
<source>Favorites on top</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="174"/>
<location filename="../qml/pages/SettingsPage.qml" line="176"/>
<source>Show notes marked as favorite above the others</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="147"/>
<location filename="../qml/pages/SettingsPage.qml" line="148"/>
<source>Last edited</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="148"/>
<location filename="../qml/pages/SettingsPage.qml" line="149"/>
<source>Category</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="149"/>
<location filename="../qml/pages/SettingsPage.qml" line="150"/>
<source>Title alphabetically</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="151"/>
<location filename="../qml/pages/SettingsPage.qml" line="153"/>
<source>Sort notes by</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="152"/>
<location filename="../qml/pages/SettingsPage.qml" line="154"/>
<source>This will also change how the notes are grouped</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="179"/>
<location filename="../qml/pages/SettingsPage.qml" line="181"/>
<source>Show separator</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="180"/>
<location filename="../qml/pages/SettingsPage.qml" line="182"/>
<source>Show a separator line between the notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="190"/>
<location filename="../qml/pages/SettingsPage.qml" line="192"/>
<source>lines</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="191"/>
<location filename="../qml/pages/SettingsPage.qml" line="193"/>
<source>Number of lines in the preview</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="196"/>
<location filename="../qml/pages/SettingsPage.qml" line="198"/>
<source>Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="199"/>
<location filename="../qml/pages/SettingsPage.qml" line="201"/>
<source>Monospaced font</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="200"/>
<location filename="../qml/pages/SettingsPage.qml" line="202"/>
<source>Use a monospeced font to edit a note</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="205"/>
<location filename="../qml/pages/SettingsPage.qml" line="207"/>
<source>Capital &apos;X&apos; in checkboxes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="206"/>
<location filename="../qml/pages/SettingsPage.qml" line="208"/>
<source>For interoperability with other apps such as Joplin</source>
<translation type="unfinished"></translation>
</message>