1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

Laod note

This commit is contained in:
Laurent Cozic 2016-12-14 21:50:26 +00:00
parent ac166d7976
commit 40e9b82137
16 changed files with 441 additions and 75 deletions

View File

@ -1,4 +1,4 @@
QT += qml quick sql
QT += qml quick sql quickcontrols2
CONFIG += c++11
@ -15,7 +15,8 @@ SOURCES += \
services/noteservice.cpp \
application.cpp \
models/notecollection.cpp \
services/notecache.cpp
services/notecache.cpp \
models/qmlnote.cpp
RESOURCES += qml.qrc
@ -40,7 +41,9 @@ HEADERS += \
services/noteservice.h \
application.h \
models/notecollection.h \
services/notecache.h
services/notecache.h \
sparsevector.hpp \
models/qmlnote.h
DISTFILES +=

View File

@ -0,0 +1,51 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
Item {
property QtObject model
Connections {
target: model
onChanged: {
if (!model) {
titleField.text = ""
bodyField.text = ""
} else {
titleField.text = model.title
bodyField.text = model.body
}
}
}
Rectangle {
color: "#eeeeee"
border.color: "#0000ff"
anchors.fill: parent
}
ColumnLayout {
anchors.fill: parent
spacing: 2
TextField {
id: titleField
Layout.fillWidth: true
Layout.minimumWidth: 50
Layout.preferredWidth: 100
}
TextArea {
id: bodyField
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 50
Layout.preferredWidth: 100
Layout.minimumHeight: 150
}
}
}

View File

@ -5,7 +5,7 @@ Item {
id: root
property alias model: listView.model
property alias currentIndex: listView.currentIndex
signal currentItemChanged()
property alias currentItem: listView.currentItem
Rectangle {
color: "#ffeeee"

View File

@ -19,12 +19,14 @@ Application::Application(int &argc, char **argv) : QGuiApplication(argc, argv) {
QQmlContext *ctxt = view_.rootContext();
ctxt->setContextProperty("folderListModel", &folderModel_);
ctxt->setContextProperty("noteListModel", &noteModel_);
ctxt->setContextProperty("noteModel", &selectedQmlNote_);
view_.setSource(QUrl("qrc:/main.qml"));
QObject* rootObject = (QObject*)view_.rootObject();
connect(rootObject, SIGNAL(currentFolderChanged()), this, SLOT(view_currentFolderChanged()));
connect(rootObject, SIGNAL(currentNoteChanged()), this, SLOT(view_currentNoteChanged()));
view_.show();
}
@ -37,8 +39,28 @@ int Application::selectedFolderId() const {
return folderModel_.data(modelIndex, FolderModel::IdRole).toInt();
}
int Application::selectedNoteId() const {
QObject* rootObject = (QObject*)view_.rootObject();
int index = rootObject->property("currentNoteIndex").toInt();
QModelIndex modelIndex = noteModel_.index(index);
return noteModel_.data(modelIndex, NoteModel::IdRole).toInt();
}
void Application::view_currentFolderChanged() {
int folderId = selectedFolderId();
noteCollection_ = NoteCollection(db_, noteCache_, folderId, "title ASC");
noteCollection_ = NoteCollection(db_, folderId, "title ASC");
noteModel_.setCollection(noteCollection_);
}
void Application::view_currentNoteChanged() {
int noteId = selectedNoteId();
Note note = noteCollection_.byId(noteId);
selectedQmlNote_.setNote(note);
// TODO: get note by id
//Note note = noteCollection_.by
//selectedQmlNote_ = QmlNote(noteId);
//noteCollection_ = NoteCollection(db_, folderId, "title ASC");
//noteModel_.setCollection(noteCollection_);
}

View File

@ -10,6 +10,7 @@
#include "models/notecollection.h"
#include "services/notecache.h"
#include "models/notemodel.h"
#include "models/qmlnote.h"
namespace jop {
@ -31,11 +32,14 @@ private:
FolderModel folderModel_;
NoteModel noteModel_;
int selectedFolderId() const;
int selectedNoteId() const;
NoteCache noteCache_;
QmlNote selectedQmlNote_;
public slots:
void view_currentFolderChanged();
void view_currentNoteChanged();
};

View File

@ -8,7 +8,9 @@ Item {
width: 800
height: 600
signal currentFolderChanged()
signal currentNoteChanged()
property alias currentFolderIndex: folderList.currentIndex
property alias currentNoteIndex: noteList.currentIndex
RowLayout {
id: layout
@ -22,7 +24,7 @@ Item {
Layout.fillHeight: true
Layout.minimumWidth: 50
Layout.preferredWidth: 100
Layout.maximumWidth: 300
Layout.maximumWidth: 200
Layout.minimumHeight: 150
onCurrentItemChanged: {
@ -36,8 +38,22 @@ Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 100
Layout.maximumWidth: 200
Layout.preferredWidth: 200
Layout.preferredHeight: 100
onCurrentItemChanged: {
root.currentNoteChanged()
}
}
NoteEditor {
id: noteEditor
model: noteModel
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 100
Layout.preferredHeight: 100
}
}

View File

@ -6,3 +6,11 @@ Note::Note()
{
}
QString Note::body() const {
return body_;
}
void Note::setBody(const QString &v) {
body_ = v;
}

View File

@ -11,10 +11,12 @@ class Note : public Item {
public:
Note();
QString body() const;
void setBody(const QString& v);
private:
QString body_;
};

View File

@ -4,77 +4,46 @@ using namespace jop;
NoteCollection::NoteCollection() {}
NoteCollection::NoteCollection(Database& db, NoteCache cache, int parentId, const QString& orderBy) {
NoteCollection::NoteCollection(Database& db, int parentId, const QString& orderBy) {
db_ = db;
parentId_ = parentId;
orderBy_ = orderBy;
cache_ = cache;
cacheMinIndex_ = 0;
cacheMaxIndex_ = -1;
}
int NoteCollection::cacheMinIndex() const {
return cacheMinIndex_;
}
int NoteCollection::cacheMaxIndex() const {
return cacheMaxIndex_;
}
Note NoteCollection::itemAt(int index) const {
Note NoteCollection::at(int index) const {
if (!parentId_) return Note();
qDebug() << "Note at" << index << "Min" << cacheMinIndex() << "Max" << cacheMaxIndex() << "Count" << count();
if (cache_.isset(index)) return cache_.get(index);
if (index >= cacheMinIndex() && index < cacheMaxIndex()) {
return notes_[index];
std::vector<int> indexes = cache_.availableBufferAround(index, 32);
if (!indexes.size()) {
qWarning() << "Couldn't acquire buffer"; // "Cannot happen"
return Note();
}
int buff = 16;
int from = indexes[0];
int to = indexes[indexes.size() - 1];
int from = index - buff;
int to = from + (buff - 1);
qDebug() << "Getting from" << from << "to" << to;
if (notes_.size()) {
if (from <= cacheMaxIndex()) {
from = cacheMaxIndex() + 1;
to = from + (buff - 1);
cacheMaxIndex_ = to;
} else if (to >= cacheMinIndex()) {
to = cacheMinIndex() - 1;
from = to - (buff - 1);
cacheMinIndex_ = from;
}
} else {
from = std::max(0, index - buff);
to = from + (buff - 1);
}
if (from < 0) from = 0;
if (to >= count()) to = count() - 1;
qDebug() << "Loading from" << from << "to" << to;
QSqlQuery q = db_.query("SELECT id, title FROM notes WHERE parent_id = :parent_id ORDER BY " + orderBy_ + " LIMIT " + QString::number(to - from + 1) + " OFFSET " + QString::number(from));
QSqlQuery q = db_.query("SELECT id, title, body FROM notes WHERE parent_id = :parent_id ORDER BY " + orderBy_ + " LIMIT " + QString::number(to - from + 1) + " OFFSET " + QString::number(from));
q.bindValue(":parent_id", parentId_);
q.exec();
int noteIndex = from;
while (q.next()) {
Note f;
f.setId(q.value(0).toInt());
f.setTitle(q.value(1).toString());
f.setIsPartial(true);
Note note;
note.setId(q.value(0).toInt());
note.setTitle(q.value(1).toString());
note.setBody(q.value(2).toString());
note.setIsPartial(true);
qDebug() << "Adding" << noteIndex;
notes_[noteIndex] = f;
cache_.set(noteIndex, note);
noteIndex++;
}
qDebug() << notes_.contains(index);
return notes_[index];
return cache_.get(index);
}
// TODO: cache result
@ -87,3 +56,27 @@ int NoteCollection::count() const {
q.next();
return q.value(0).toInt();
}
Note NoteCollection::byId(int id) const {
std::vector<int> indexes = cache_.indexes();
for (int i = 0; i < indexes.size(); i++) {
Note note = cache_.get(indexes[i]);
if (note.id() == id) return note;
}
QSqlQuery q = db_.query("SELECT id, title, body FROM notes WHERE id = :id");
q.bindValue(":id", id);
q.exec();
q.next();
if (!q.isValid()) {
qWarning() << "Invalid note ID:" << id;
return Note();
}
// TODO: refactor creation of note from SQL query object
Note note;
note.setId(q.value(0).toInt());
note.setTitle(q.value(1).toString());
note.setBody(q.value(2).toString());
return note;
}

View File

@ -6,6 +6,7 @@
#include "database.h"
#include "models/note.h"
#include "services/notecache.h"
#include "sparsevector.hpp"
namespace jop {
@ -14,23 +15,17 @@ class NoteCollection {
public:
NoteCollection();
NoteCollection(Database& db, NoteCache cache, int parentId, const QString& orderBy);
Note itemAt(int index) const;
NoteCollection(Database& db, int parentId, const QString& orderBy);
Note at(int index) const;
int count() const;
Note byId(int id) const;
private:
int parentId_;
QString orderBy_;
Database db_;
NoteCache cache_;
int cacheMinIndex() const;
int cacheMaxIndex() const;
mutable int cacheMinIndex_;
mutable int cacheMaxIndex_;
mutable QMap<int, Note> notes_;
mutable SparseVector<Note> cache_;
};

View File

@ -12,17 +12,13 @@ int jop::NoteModel::rowCount(const QModelIndex &parent) const {
QVariant jop::NoteModel::data(const QModelIndex &index, int role) const {
if (index.row() < 0 || index.row() >= rowCount()) return QVariant();
Note note = collection_.itemAt(index.row());
Note note = collection_.at(index.row());
if (role == IdRole) {
return QVariant(note.id());
}
return QVariant(note.title());
// int from = std::max(0, index.row() - 16);
// int to = from + 32;
// QList<Note> list = noteService_.overviewList(folderId_, from, to, "title ASC");
return QVariant();
}
void jop::NoteModel::setFolderId(const QString &v) {

View File

@ -14,6 +14,11 @@ class NoteModel : public QAbstractListModel {
public:
enum NoteRoles {
IdRole = Qt::UserRole + 1,
TitleRole
};
NoteModel();
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

View File

@ -0,0 +1,20 @@
#include "qmlnote.h"
using namespace jop;
QmlNote::QmlNote() {}
QString QmlNote::title() const {
return note_.title();
}
QString QmlNote::body() const {
return note_.body();
}
void QmlNote::setNote(const Note &note) {
note_ = note;
emit changed();
//emit titleChanged();
//emit bodyChanged();
}

View File

@ -0,0 +1,37 @@
#ifndef QMLNOTE_H
#define QMLNOTE_H
#include <stable.h>
#include "note.h"
namespace jop {
class QmlNote : public QObject {
Q_OBJECT
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
Q_PROPERTY(QString body READ body NOTIFY bodyChanged)
public:
QmlNote();
QString title() const;
QString body() const;
void setNote(const Note& note);
signals:
void titleChanged();
void bodyChanged();
void changed();
private:
Note note_;
};
}
#endif // QMLNOTE_H

View File

@ -3,5 +3,6 @@
<file>main.qml</file>
<file>FolderList.qml</file>
<file>NoteList.qml</file>
<file>NoteEditor.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,213 @@
#ifndef SPARSEARRAY_HPP
#define SPARSEARRAY_HPP
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>
#include <math.h>
template <class ClassType> class SparseVector {
public:
SparseVector() {
counter_ = -1;
count_ = -1;
}
ClassType get(int index) const {
if (index > count()) return ClassType();
if (!isset(index)) return ClassType();
typename IndexMap::const_iterator pos = indexes_.find(index);
int valueIndex = pos->second.valueIndex;
typename std::map<int, ClassType>::const_iterator pos2 = values_.find(valueIndex);
return pos2->second;
}
void set(int index, ClassType value) {
unset(index);
int valueIndex = ++counter_;
values_[valueIndex] = value;
IndexRecord r;
r.valueIndex = valueIndex;
r.time = 0; // Disabled / not needed
//r.time = time(0);
indexes_[index] = r;
count_ = -1;
}
void push(ClassType value) {
set(count(), value);
}
bool isset(int index) const {
return indexes_.find(index) != indexes_.end();
}
std::vector<int> indexes() const {
std::vector<int> output;
for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) {
output.push_back(it->first);
}
return output;
}
// Unsets that particular index, but without shifting the following indexes
void unset(int index) {
if (!isset(index)) return;
IndexRecord r = indexes_[index];
values_.erase(r.valueIndex);
indexes_.erase(index);
}
void insert(int index, ClassType value) {
IndexMap newIndexes;
for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) {
ClassType key = it->first;
if (key > index) key++;
newIndexes[key] = it->second;
}
indexes_ = newIndexes;
set(index, value);
}
// Removes the element at that particular index, and shift all the following elements
void remove(int index) {
if (index > count()) return;
if (isset(index)) {
int valueIndex = indexes_[index].valueIndex;
values_.erase(valueIndex);
}
IndexMap newIndexes;
for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) {
ClassType key = it->first;
if (key == index) continue;
if (key > index) key--;
newIndexes[key] = it->second;
}
indexes_ = newIndexes;
count_ = -1;
}
// Returns a vector containing the indexes that are not currently set around
// the given index, up to bufferSize indexes.
std::vector<int> availableBufferAround(int index, int bufferSize) const {
std::vector<int> temp;
// Doesn't make sense to search for an empty buffer around
// an index that is already set.
if (isset(index)) return temp;
temp.push_back(index);
// Probably not the most efficient algorithm but it works:
// First search 1 position to the left, then 1 position to the right,
// then 2 to the left, etc. If encountering an unavailable index on one
// of the side, the path is "blocked" and searching is now done in only
// one direction. If both sides are blocked, the algorithm exit.
int inc = 1;
int sign = -1;
bool leftBlocked = false;
bool rightBlocked = false;
while (temp.size() < bufferSize) {
int bufferIndex = index + (inc * sign);
bool blocked = isset(bufferIndex) || bufferIndex < 0;
if (blocked) {
if (sign < 0) {
leftBlocked = true;
} else {
rightBlocked = true;
}
}
if (leftBlocked && rightBlocked) break;
if (!blocked) temp.push_back(bufferIndex);
sign = -sign;
if (sign < 0) inc++;
if (leftBlocked && sign < 0) sign = 1;
if (rightBlocked && sign > 0) sign = -1;
}
std::sort(temp.begin(), temp.end());
return temp;
}
int count() const {
if (count_ >= 0) return count_;
int maxKey = -1;
for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) {
const int& key = it->first;
if (key > maxKey) maxKey = key;
}
count_ = maxKey + 1;
return count_;
}
void clearOlderThan(time_t time) {
IndexMap newIndexes;
for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) {
const IndexRecord& r = it->second;
if (r.time > time) {
newIndexes[it->first] = r;
} else {
values_.erase(r.valueIndex);
}
}
indexes_ = newIndexes;
count_ = -1;
}
// Unset all values outside of this interval
void unsetAllButInterval(int intervalFrom, int intervalTo) {
int count = this->count();
for (int i = 0; i < count; i++) {
if (i >= intervalFrom && i <= intervalTo) continue;
unset(i);
}
}
void clear() {
indexes_.clear();
values_.clear();
counter_ = 0;
count_ = -1;
}
void print() const {
for (int i = 0; i < count(); i++) {
std::cout << "|";
std::cout << " " << get(i) << " ";
}
}
private:
struct IndexRecord {
int valueIndex;
time_t time;
};
typedef std::map<int, IndexRecord> IndexMap;
int counter_;
IndexMap indexes_;
std::map<int, ClassType> values_;
// This is used to cache the result of ::count().
// Don't forget to set it to -1 whenever the list
// size changes, so that it can be recalculated.
mutable int count_;
};
#endif // SPARSEARRAY_HPP