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 <QJsonDocument>
#include <QJsonArray>
#include <QtMath>
const QHash<int, QByteArray> noteRoles = QHash<int, QByteArray>{
{NotesModel::visible, "visible"},
{NotesModel::idRole, "id"},
{NotesModel::modifiedRole, "modified"},
{NotesModel::titleRole, "title"},
@ -27,18 +29,42 @@ struct Note {
bool operator==(const Note& n) {
return id == n.id;
}
void fromjson(const QJsonObject& jobj) {
id = jobj.value(noteRoles[NotesModel::idRole]).toInt();
modified = jobj.value(noteRoles[NotesModel::modifiedRole]).toInt();
title = jobj.value(noteRoles[NotesModel::titleRole]).toString();
category = jobj.value(noteRoles[NotesModel::categoryRole]).toString();
content = jobj.value(noteRoles[NotesModel::contentRole]).toString();
favorite = jobj.value(noteRoles[NotesModel::favoriteRole]).toBool();
etag = jobj.value(noteRoles[NotesModel::etagRole]).toString();
error = jobj.value(noteRoles[NotesModel::errorRole]).toBool(true);
errorMessage = jobj.value(noteRoles[NotesModel::errorMessageRole]).toString();
enum SearchAttribute {
NoSearchAttribute = 0x0,
SearchInTitle = 0x1,
SearchInCategory = 0x2,
SearchInContent = 0x4,
SearchAll = 0x7
};
Q_DECLARE_FLAGS(SearchAttributes, SearchAttribute)
static const Note fromjson(const QJsonObject& jobj) {
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)
{
@ -68,43 +94,75 @@ void NotesModel::setFavoritesOnTop(bool favoritesOnTop) {
bool NotesModel::applyJSON(QString json, bool replaceIfArray) {
QJsonDocument jdoc = QJsonDocument::fromJson(json.toUtf8());
int notesModified = 0;
if (!jdoc.isNull()) {
if (jdoc.isArray()) {
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()) {
QJsonValue jval = jarr.first();
if (jval.isObject()) {
QJsonObject jobj = jval.toObject();
if (!jobj.isEmpty() && !jobj.value(noteRoles[errorRole]).toBool(true)) {
Note note;
note.fromjson(jobj);
int index = m_notes.indexOf(note);
if (index >= 0) {
m_notes.replace(index, note);
Note note = Note::fromjson(jobj);
int position = indexOf(note.id);
if (position >= 0) {
m_notes[position].note = note;
emit dataChanged(index(position), index(position));
notesToRemove.removeAt(position);
}
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();
}
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()) {
QJsonObject jobj = jdoc.object();
if (!jobj.isEmpty() && !jobj.value(noteRoles[errorRole]).toBool(true)) {
Note note;
note.fromjson(jobj);
int index = m_notes.indexOf(note);
if (index >= 0) {
m_notes.replace(index, note);
int position = indexOf(note.id);
if (position >= 0) {
m_notes[position].note = note;
}
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 false;
@ -115,12 +173,20 @@ bool NotesModel::removeNote(int id) {
return false;
}
void NotesModel::search(QString query) const {
// TODO
void NotesModel::search(QString query) {
m_searchQuery = query;
}
void NotesModel::clearSearch() const {
// TODO
void NotesModel::clearSearch() {
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> 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;
}
@ -160,7 +216,7 @@ QHash<int, QByteArray> NotesModel::sortingNames() 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 {
@ -169,67 +225,137 @@ int NotesModel::rowCount(const QModelIndex &parent) const {
QVariant NotesModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
else if (role == idRole) return m_notes[index.row()].id;
else if (role == modifiedRole) return m_notes[index.row()].modified;
else if (role == titleRole) return m_notes[index.row()].title;
else if (role == categoryRole) return m_notes[index.row()].category;
else if (role == contentRole) return m_notes[index.row()].content;
else if (role == favoriteRole) return m_notes[index.row()].favorite;
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 == visible) return m_notes[index.row()].param;
else if (role == idRole) return m_notes[index.row()].note.id;
else if (role == modifiedRole) return m_notes[index.row()].note.modified;
else if (role == titleRole) return m_notes[index.row()].note.title;
else if (role == categoryRole) return m_notes[index.row()].note.category;
else if (role == contentRole) return m_notes[index.row()].note.content;
else if (role == favoriteRole) return m_notes[index.row()].note.favorite;
else if (role == etagRole) return m_notes[index.row()].note.etag;
else if (role == errorRole) return m_notes[index.row()].note.error;
else if (role == errorMessageRole) return m_notes[index.row()].note.errorMessage;
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() {
QList<Note> notes;
QMap<QString, Note> map;
QMap<QString, Note> favorites;
QList<ModelNote<Note, bool> > notes;
QMap<QString, ModelNote<Note, bool> > map;
QMap<QString, ModelNote<Note, bool> > favorites;
switch (m_sortBy) {
case sortByDate:
emit layoutAboutToBeChanged();
foreach (const Note &note, m_notes) {
if (m_favoritesOnTop && note.favorite)
favorites.insert(QString::number(note.modified), note);
emit layoutAboutToBeChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
for (int i = 0; i < m_notes.size(); i++) {
if (m_favoritesOnTop && m_notes[i].note.favorite)
favorites.insert(QString::number(m_notes[i].note.modified), m_notes[i]);
else
map.insert(QString::number(note.modified), note);
map.insert(QString::number(m_notes[i].note.modified), m_notes[i]);
}
notes = favorites.values();
notes.append(map.values());
m_notes = notes;
emit layoutChanged();
emit layoutChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
break;
case sortByCategory:
emit layoutAboutToBeChanged();
foreach (const Note &note, m_notes) {
if (m_favoritesOnTop && note.favorite)
favorites.insert(note.category, note);
emit layoutAboutToBeChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
for (int i = 0; i < m_notes.size(); i++) {
if (m_favoritesOnTop && m_notes[i].note.favorite)
favorites.insert(m_notes[i].note.category, m_notes[i]);
else
map.insert(note.category, note);
map.insert(m_notes[i].note.category, m_notes[i]);
}
notes = favorites.values();
notes.append(map.values());
m_notes = notes;
emit layoutChanged();
emit layoutChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
break;
case sortByTitle:
emit layoutAboutToBeChanged();
foreach (const Note &note, m_notes) {
if (m_favoritesOnTop && note.favorite)
favorites.insert(note.title, note);
emit layoutAboutToBeChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
for (int i = 0; i < m_notes.size(); i++) {
if (m_favoritesOnTop && m_notes[i].note.favorite)
favorites.insert(m_notes[i].note.title, m_notes[i]);
else
map.insert(note.title, note);
map.insert(m_notes[i].note.title, m_notes[i]);
}
notes = favorites.values();
notes.append(map.values());
m_notes = notes;
emit layoutChanged();
emit layoutChanged(QList<QPersistentModelIndex> (), VerticalSortHint);
break;
default:
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) {
if (m_favoritesOnTop && n1.favorite != n2.favorite)
return n1.favorite;
@ -251,53 +377,7 @@ bool NotesModel::noteLessThanByTitle(const Note &n1, const Note &n2) {
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) {
beginInsertRows(parent, row, row);
m_notes.insert(row, Note());

View file

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