Further worked on C++ implementation of the notes Model.

This commit is contained in:
scharel 2018-12-30 15:16:23 +01:00
parent 86a3755ad2
commit 72f3b3f44c
3 changed files with 214 additions and 192 deletions

View file

@ -1,72 +0,0 @@
#
# Do NOT Edit the Auto-generated Part!
# Generated by: spectacle version 0.27
#
Name: harbour-nextcloudnotes
# >> macros
# << macros
%{!?qtc_qmake:%define qtc_qmake %qmake}
%{!?qtc_qmake5:%define qtc_qmake5 %qmake5}
%{!?qtc_make:%define qtc_make make}
%{?qtc_builddir:%define _builddir %qtc_builddir}
Summary: Nextcloud Notes
Version: 0.3
Release: 0
Group: Applications/Editors
License: MIT
URL: https://github.com/scharel/harbour-nextcloudnotes
Source0: %{name}-%{version}.tar.bz2
Source100: harbour-nextcloudnotes.yaml
Requires: sailfishsilica-qt5 >= 0.10.9
BuildRequires: pkgconfig(sailfishapp) >= 1.0.2
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5Quick)
BuildRequires: desktop-file-utils
%description
A client app for the Nextcloud Notes server app
%prep
%setup -q -n %{name}-%{version}
# >> setup
# << setup
%build
# >> build pre
# << build pre
%qtc_qmake5 \
VERSION='%{version}-%{release}'
%qtc_make %{?_smp_mflags}
# >> build post
# << build post
%install
rm -rf %{buildroot}
# >> install pre
# << install pre
%qmake5_install
# >> install post
# << install post
desktop-file-install --delete-original \
--dir %{buildroot}%{_datadir}/applications \
%{buildroot}%{_datadir}/applications/*.desktop
%files
%defattr(-,root,root,-)
%{_bindir}
%{_datadir}/%{name}
%{_datadir}/applications/%{name}.desktop
%{_datadir}/icons/hicolor/*/apps/%{name}.png
# >> files
# << files

View file

@ -1,8 +1,10 @@
#include "notesmodel.h" #include "notesmodel.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonArray> #include <QJsonArray>
#include <QtMath>
const QHash<int, QByteArray> noteRoles = QHash<int, QByteArray>{ const QHash<int, QByteArray> noteRoles = QHash<int, QByteArray>{
{NotesModel::visible, "visible"},
{NotesModel::idRole, "id"}, {NotesModel::idRole, "id"},
{NotesModel::modifiedRole, "modified"}, {NotesModel::modifiedRole, "modified"},
{NotesModel::titleRole, "title"}, {NotesModel::titleRole, "title"},
@ -27,18 +29,42 @@ struct Note {
bool operator==(const Note& n) { bool operator==(const Note& n) {
return id == n.id; return id == n.id;
} }
void fromjson(const QJsonObject& jobj) { enum SearchAttribute {
id = jobj.value(noteRoles[NotesModel::idRole]).toInt(); NoSearchAttribute = 0x0,
modified = jobj.value(noteRoles[NotesModel::modifiedRole]).toInt(); SearchInTitle = 0x1,
title = jobj.value(noteRoles[NotesModel::titleRole]).toString(); SearchInCategory = 0x2,
category = jobj.value(noteRoles[NotesModel::categoryRole]).toString(); SearchInContent = 0x4,
content = jobj.value(noteRoles[NotesModel::contentRole]).toString(); SearchAll = 0x7
favorite = jobj.value(noteRoles[NotesModel::favoriteRole]).toBool(); };
etag = jobj.value(noteRoles[NotesModel::etagRole]).toString(); Q_DECLARE_FLAGS(SearchAttributes, SearchAttribute)
error = jobj.value(noteRoles[NotesModel::errorRole]).toBool(true); static const Note fromjson(const QJsonObject& jobj) {
errorMessage = jobj.value(noteRoles[NotesModel::errorMessageRole]).toString(); Note note;
note.id = jobj.value(noteRoles[NotesModel::idRole]).toInt();
note.modified = jobj.value(noteRoles[NotesModel::modifiedRole]).toInt();
note.title = jobj.value(noteRoles[NotesModel::titleRole]).toString();
note.category = jobj.value(noteRoles[NotesModel::categoryRole]).toString();
note.content = jobj.value(noteRoles[NotesModel::contentRole]).toString();
note.favorite = jobj.value(noteRoles[NotesModel::favoriteRole]).toBool();
note.etag = jobj.value(noteRoles[NotesModel::etagRole]).toString();
note.error = jobj.value(noteRoles[NotesModel::errorRole]).toBool(true);
note.errorMessage = jobj.value(noteRoles[NotesModel::errorMessageRole]).toString();
return note;
}
static bool searchInNote(const QString &query, const Note &note, SearchAttributes criteria = QFlag(SearchAll)) {
bool queryFound = false;
if (criteria.testFlag(SearchInTitle)) {
queryFound |= note.title.contains(query, Qt::CaseInsensitive);
}
if (criteria.testFlag(SearchInContent)) {
queryFound |= note.content.contains(query, Qt::CaseInsensitive);
}
if (criteria.testFlag(SearchInCategory)) {
queryFound |= note.category.contains(query, Qt::CaseInsensitive);
}
return queryFound;
} }
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(Note::SearchAttributes)
NotesModel::NotesModel(QObject *parent) : QAbstractListModel(parent) NotesModel::NotesModel(QObject *parent) : QAbstractListModel(parent)
{ {
@ -68,43 +94,75 @@ void NotesModel::setFavoritesOnTop(bool favoritesOnTop) {
bool NotesModel::applyJSON(QString json, bool replaceIfArray) { bool NotesModel::applyJSON(QString json, bool replaceIfArray) {
QJsonDocument jdoc = QJsonDocument::fromJson(json.toUtf8()); QJsonDocument jdoc = QJsonDocument::fromJson(json.toUtf8());
int notesModified = 0;
if (!jdoc.isNull()) { if (!jdoc.isNull()) {
if (jdoc.isArray()) { if (jdoc.isArray()) {
QJsonArray jarr = jdoc.array(); QJsonArray jarr = jdoc.array();
QList<int> notesToRemove;
QList<ModelNote<Note, int> > notesToAdd;
for (int i = 0; i < m_notes.size(); i++)
notesToRemove << i;
while (!jarr.empty()) { while (!jarr.empty()) {
QJsonValue jval = jarr.first(); QJsonValue jval = jarr.first();
if (jval.isObject()) { if (jval.isObject()) {
QJsonObject jobj = jval.toObject(); QJsonObject jobj = jval.toObject();
if (!jobj.isEmpty() && !jobj.value(noteRoles[errorRole]).toBool(true)) { if (!jobj.isEmpty() && !jobj.value(noteRoles[errorRole]).toBool(true)) {
Note note; Note note = Note::fromjson(jobj);
note.fromjson(jobj); int position = indexOf(note.id);
int index = m_notes.indexOf(note); if (position >= 0) {
if (index >= 0) { m_notes[position].note = note;
m_notes.replace(index, note); emit dataChanged(index(position), index(position));
notesToRemove.removeAt(position);
} }
else { else {
// TODO position = insertPosition(note);
//beginInsertRows(QModelIndex(), position, position);
ModelNote<Note, int> noteToAdd;
noteToAdd.note = note; noteToAdd.param = position;
notesToAdd << noteToAdd;
//m_notes[position].note = note;
//endInsertRows();
} }
notesModified++;
} }
} }
jarr.pop_front(); jarr.pop_front();
} }
for (int i = 0; i < notesToRemove.size(); i++) {
beginRemoveRows(QModelIndex(), notesToRemove[i], notesToRemove[i]);
m_notes.removeAt(notesToRemove[i]);
endRemoveRows();
}
for (int i = 0; i < notesToAdd.size(); i++) {
beginInsertRows(QModelIndex(), notesToAdd[i].param, notesToAdd[i].param);
ModelNote<Note, bool> note;
note.note = notesToAdd[i].note;
m_notes.insert(notesToAdd[i].param, note);
endInsertRows();
}
} }
else if (jdoc.isObject()) { else if (jdoc.isObject()) {
QJsonObject jobj = jdoc.object(); QJsonObject jobj = jdoc.object();
if (!jobj.isEmpty() && !jobj.value(noteRoles[errorRole]).toBool(true)) { if (!jobj.isEmpty() && !jobj.value(noteRoles[errorRole]).toBool(true)) {
Note note; Note note;
note.fromjson(jobj); note.fromjson(jobj);
int index = m_notes.indexOf(note); int position = indexOf(note.id);
if (index >= 0) { if (position >= 0) {
m_notes.replace(index, note); m_notes[position].note = note;
} }
else { else {
// TODO position = insertPosition(note);
beginInsertRows(index(position), position, position);
m_notes[position].note = note;
endInsertRows();
} }
notesModified++;
} }
} }
sort(); if (notesModified > 0) {
sort(); // TODO react to signal connect()
search(m_searchQuery);
}
return true; return true;
} }
return false; return false;
@ -115,12 +173,20 @@ bool NotesModel::removeNote(int id) {
return false; return false;
} }
void NotesModel::search(QString query) const { void NotesModel::search(QString query) {
// TODO m_searchQuery = query;
} }
void NotesModel::clearSearch() const { void NotesModel::clearSearch() {
// TODO search("");
}
int NotesModel::indexOf(int id) const {
for (int i = 0; i < m_notes.size(); i++) {
if (m_notes[i].note.id == id)
return i;
}
return -1;
} }
/* /*
@ -138,16 +204,6 @@ bool NotesModel::addNotes(QList<Note> &notes) {
*/ */
QHash<int, QByteArray> NotesModel::roleNames() const { QHash<int, QByteArray> NotesModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[idRole] = "id";
roles[modifiedRole] = "modified";
roles[titleRole] = "title";
roles[categoryRole] = "category";
roles[contentRole] = "content";
roles[favoriteRole] = "favorite";
roles[etagRole] = "etag";
roles[errorRole] = "error";
roles[errorMessageRole] = "errorMessage";
return noteRoles; return noteRoles;
} }
@ -160,7 +216,7 @@ QHash<int, QByteArray> NotesModel::sortingNames() const {
} }
Qt::ItemFlags NotesModel::flags(const QModelIndex &index) const { Qt::ItemFlags NotesModel::flags(const QModelIndex &index) const {
return Qt::ItemIsEnabled; //| Qt::ItemIsEditable | Qt::ItemIsSelectable return Qt::ItemIsEnabled | Qt::ItemIsEditable; // | Qt::ItemIsSelectable
} }
int NotesModel::rowCount(const QModelIndex &parent) const { int NotesModel::rowCount(const QModelIndex &parent) const {
@ -169,67 +225,137 @@ int NotesModel::rowCount(const QModelIndex &parent) const {
QVariant NotesModel::data(const QModelIndex &index, int role) const { QVariant NotesModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant(); if (!index.isValid()) return QVariant();
else if (role == idRole) return m_notes[index.row()].id; else if (role == visible) return m_notes[index.row()].param;
else if (role == modifiedRole) return m_notes[index.row()].modified; else if (role == idRole) return m_notes[index.row()].note.id;
else if (role == titleRole) return m_notes[index.row()].title; else if (role == modifiedRole) return m_notes[index.row()].note.modified;
else if (role == categoryRole) return m_notes[index.row()].category; else if (role == titleRole) return m_notes[index.row()].note.title;
else if (role == contentRole) return m_notes[index.row()].content; else if (role == categoryRole) return m_notes[index.row()].note.category;
else if (role == favoriteRole) return m_notes[index.row()].favorite; else if (role == contentRole) return m_notes[index.row()].note.content;
else if (role == etagRole) return m_notes[index.row()].etag; else if (role == favoriteRole) return m_notes[index.row()].note.favorite;
else if (role == errorRole) return m_notes[index.row()].error; else if (role == etagRole) return m_notes[index.row()].note.etag;
else if (role == errorMessageRole) return m_notes[index.row()].errorMessage; else if (role == errorRole) return m_notes[index.row()].note.error;
else if (role == errorMessageRole) return m_notes[index.row()].note.errorMessage;
return QVariant(); return QVariant();
} }
bool NotesModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) return false;
else if (role == modifiedRole) {
m_notes[index.row()].note.modified = value.toInt();
emit dataChanged(this->index(index.row()), this->index(index.row()), QVector<int> { 1, role } );
sort();
return true;
}
else if (role == categoryRole) {
m_notes[index.row()].note.category = value.toString();
emit dataChanged(this->index(index.row()), this->index(index.row()), QVector<int> { 1, role } );
sort();
return true;
}
else if (role == contentRole) {
m_notes[index.row()].note.content = value.toString();
emit dataChanged(this->index(index.row()), this->index(index.row()), QVector<int> { 1, role } );
sort();
return true;
}
else if (role == favoriteRole) {
m_notes[index.row()].note.favorite = value.toBool();
emit dataChanged(this->index(index.row()), this->index(index.row()), QVector<int> { 1, role } );
sort();
return true;
}
return false;
}
void NotesModel::sort() { void NotesModel::sort() {
QList<Note> notes; QList<ModelNote<Note, bool> > notes;
QMap<QString, Note> map; QMap<QString, ModelNote<Note, bool> > map;
QMap<QString, Note> favorites; QMap<QString, ModelNote<Note, bool> > favorites;
switch (m_sortBy) { switch (m_sortBy) {
case sortByDate: case sortByDate:
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
foreach (const Note &note, m_notes) { for (int i = 0; i < m_notes.size(); i++) {
if (m_favoritesOnTop && note.favorite) if (m_favoritesOnTop && m_notes[i].note.favorite)
favorites.insert(QString::number(note.modified), note); favorites.insert(QString::number(m_notes[i].note.modified), m_notes[i]);
else else
map.insert(QString::number(note.modified), note); map.insert(QString::number(m_notes[i].note.modified), m_notes[i]);
} }
notes = favorites.values(); notes = favorites.values();
notes.append(map.values()); notes.append(map.values());
m_notes = notes; m_notes = notes;
emit layoutChanged(); emit layoutChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
break; break;
case sortByCategory: case sortByCategory:
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
foreach (const Note &note, m_notes) { for (int i = 0; i < m_notes.size(); i++) {
if (m_favoritesOnTop && note.favorite) if (m_favoritesOnTop && m_notes[i].note.favorite)
favorites.insert(note.category, note); favorites.insert(m_notes[i].note.category, m_notes[i]);
else else
map.insert(note.category, note); map.insert(m_notes[i].note.category, m_notes[i]);
} }
notes = favorites.values(); notes = favorites.values();
notes.append(map.values()); notes.append(map.values());
m_notes = notes; m_notes = notes;
emit layoutChanged(); emit layoutChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
break; break;
case sortByTitle: case sortByTitle:
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
foreach (const Note &note, m_notes) { for (int i = 0; i < m_notes.size(); i++) {
if (m_favoritesOnTop && note.favorite) if (m_favoritesOnTop && m_notes[i].note.favorite)
favorites.insert(note.title, note); favorites.insert(m_notes[i].note.title, m_notes[i]);
else else
map.insert(note.title, note); map.insert(m_notes[i].note.title, m_notes[i]);
} }
notes = favorites.values(); notes = favorites.values();
notes.append(map.values()); notes.append(map.values());
m_notes = notes; m_notes = notes;
emit layoutChanged(); emit layoutChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
break; break;
default: default:
break; break;
} }
} }
bool NotesModel::noteLessThan(const Note &n1, const Note &n2) const {
switch (m_sortBy) {
case sortByDate:
if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite;
else
return n1.modified > n2.modified;
break;
case sortByCategory:
if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite;
else
return n1.category < n2.category;
break;
case sortByTitle:
if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite;
else
return n1.title < n2.title;
break;
default:
break;
}
return true;
}
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].note);
if (result)
upper = middle;
else
lower = middle + 1;
}
return lower;
}
/*bool NotesModel::noteLessThanByDate(const Note &n1, const Note &n2) { /*bool NotesModel::noteLessThanByDate(const Note &n1, const Note &n2) {
if (m_favoritesOnTop && n1.favorite != n2.favorite) if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite; return n1.favorite;
@ -251,53 +377,7 @@ bool NotesModel::noteLessThanByTitle(const Note &n1, const Note &n2) {
return n1.title < n2.title; return n1.title < n2.title;
}*/ }*/
/*bool NotesModel::noteLessThan(const Note &n1, const Note &n2) const {
switch (m_sortBy) {
case sortByDate:
if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite;
else
return n1.modified > n2.modified;
break;
case sortByCategory:
if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite;
else
return n1.category < n2.category;
break;
case sortByTitle:
if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite;
else
return n1.title < n2.title;
break;
default:
break;
}
}*/
/* /*
bool NotesModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) return false;
else if (role == modifiedRole) {
m_notes[index.row()].modified = value.toDateTime();
return true;
}
else if (role == categoryRole) {
m_notes[index.row()].category = value.toString();
return true;
}
else if (role == contentRole) {
m_notes[index.row()].content = value.toString();
return true;
}
else if (role == favoriteRole) {
m_notes[index.row()].favorite = value.toBool();
return true;
}
return false;
}
bool NotesModel::insertRow(int row, const QModelIndex &parent) { bool NotesModel::insertRow(int row, const QModelIndex &parent) {
beginInsertRows(parent, row, row); beginInsertRows(parent, row, row);
m_notes.insert(row, Note()); m_notes.insert(row, Note());

View file

@ -7,6 +7,12 @@
struct Note; struct Note;
template <typename N, typename P>
struct ModelNote {
N note;
P param;
};
class NotesModel : public QAbstractListModel { class NotesModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
public: public:
@ -14,20 +20,23 @@ public:
virtual ~NotesModel(); virtual ~NotesModel();
Q_PROPERTY(int sortBy READ sortBy WRITE setSortBy NOTIFY sortByChanged) Q_PROPERTY(int sortBy READ sortBy WRITE setSortBy NOTIFY sortByChanged)
int sortBy() { return m_sortBy; } int sortBy() const { return m_sortBy; }
void setSortBy(int sortBy); void setSortBy(int sortBy);
Q_PROPERTY(bool favoritesOnTop READ favoritesOnTop WRITE setFavoritesOnTop NOTIFY favoritesOnTopChanged) Q_PROPERTY(bool favoritesOnTop READ favoritesOnTop WRITE setFavoritesOnTop NOTIFY favoritesOnTopChanged)
bool favoritesOnTop() { return m_favoritesOnTop; } bool favoritesOnTop() const { return m_favoritesOnTop; }
void setFavoritesOnTop(bool favoritesOnTop); void setFavoritesOnTop(bool favoritesOnTop);
Q_INVOKABLE bool applyJSON(QString json, bool replaceIfArray = true); Q_INVOKABLE bool applyJSON(QString json, bool replaceIfArray = true);
Q_INVOKABLE bool removeNote(int id); Q_INVOKABLE bool removeNote(int id);
Q_INVOKABLE void search(QString query) const; Q_INVOKABLE void search(QString query);
Q_INVOKABLE void clearSearch() const; Q_INVOKABLE void clearSearch();
Q_INVOKABLE int indexOf(int id) const;
enum NoteRoles { enum NoteRoles {
visible = Qt::UserRole,
idRole = Qt::UserRole + 1, idRole = Qt::UserRole + 1,
modifiedRole = Qt::UserRole + 2, modifiedRole = Qt::UserRole + 2,
titleRole = Qt::UserRole + 3, titleRole = Qt::UserRole + 3,
@ -51,7 +60,7 @@ public:
virtual int rowCount(const QModelIndex &parent) const; virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const; virtual QVariant data(const QModelIndex &index, int role) const;
//virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; //virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
//virtual bool setData(const QModelIndex &index, const QVariant &value, int role); virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
//bool insertRow(int row, const QModelIndex &parent); //bool insertRow(int row, const QModelIndex &parent);
//bool insertRows(int row, int count, const QModelIndex &parent); //bool insertRows(int row, int count, const QModelIndex &parent);
@ -59,20 +68,25 @@ public:
//bool removeRows(int row, int count, const QModelIndex &parent); //bool removeRows(int row, int count, const QModelIndex &parent);
protected: protected:
static bool noteLessThanByDate(const Note &n1, const Note &n2); bool noteLessThan(const Note &n1, const Note &n2) const;
/*static bool noteLessThanByDate(const Note &n1, const Note &n2);
static bool noteLessThanByCategory(const Note &n1, const Note &n2); static bool noteLessThanByCategory(const Note &n1, const Note &n2);
static bool noteLessThanByTitle(const Note &n1, const Note &n2); static bool noteLessThanByTitle(const Note &n1, const Note &n2);*/
signals: signals:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int> ());
void sortByChanged(int sortBy); void sortByChanged(int sortBy);
void favoritesOnTopChanged(bool favoritesOnTop); void favoritesOnTopChanged(bool favoritesOnTop);
private: private:
QList<Note> m_notes; QList<ModelNote<Note, bool> > m_notes;
int m_sortBy; int m_sortBy;
bool m_favoritesOnTop; bool m_favoritesOnTop;
QString m_searchQuery;
void sort(); void sort();
void update();
int insertPosition(const Note &n) const;
}; };
#endif // NOTESMODEL_H #endif // NOTESMODEL_H