/* * Copyright 2012-2018 Morgan McMillian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "TaskModel.h" TaskModel::TaskModel(QObject* parent) { setParent(parent); QSettings settings; mOffline = false; mDelete = -1; ts_settings = new QSettings("Morgan McMillian", "RenamedTodo"); toast = new SystemToast(this); mSort = settings.value("sort").toInt(); if (!mSort) mSort = PRIORITY; QString fpath = settings.value("path").toString(); if (!fpath.isNull()) { localfile.setPath(fpath); } regxComp.setPattern("^x "); regxPri.setPattern("\\(([A-Z])\\) "); regxDate.setPattern("([0-9]{4}-[0-9]{2}-[0-9]{2}) ?"); regxProj.setPattern("(\\+[^ ]+)"); regxCtx.setPattern("(@[^ ]+)"); qDebug() << "I am life! " << this; } TaskModel::~TaskModel() { delete toast; qDebug() << "I am death! " << this; } int TaskModel::sort() const { return mSort; } void TaskModel::setSort(const int &sort) { QSettings settings; mSort = sort; settings.setValue("sort", sort); setView(); emit listUpdated(); } QVariant TaskModel::setting(const QString &setting, const QVariant &defVal) const { QSettings settings; if (settings.value(setting).isNull()) { return defVal; } return settings.value(setting); } QString TaskModel::activeFilters() { QString filterlist; foreach(QString item, mFilters) { filterlist = filterlist + item + ","; } if (filterlist.size() > 0) { return " Filter: " + filterlist; } else { return ""; } } QString TaskModel::formatDate(const QDateTime &date) const { return date.toString("yyyy-MM-dd"); } void TaskModel::modifySetting(const QString &setting, const QVariant &value) { QSettings settings; settings.setValue(setting, value); if (setting == "sort") mSort = value.toInt(); if (setting == "path") { localfile.setPath(value.toString()); load(); } } void TaskModel::onFileDataChanged(QStringList data) { localfile.save(data); load(); setView(); } void TaskModel::onArchiveData(QStringList data) { localfile.saveArchive(data); mArchiveData = data; } QVariantList TaskModel::filters() const { QVariantList filters; foreach (QString project, mProjects) { QVariantMap map; map["type"] = "Projects"; map["title"] = project; if (mFilters.indexOf(project) >= 0) { map["checked"] = true; } else { map["checked"] = false; } filters.append(map); } foreach (QString context, mContexts) { QVariantMap map; map["type"] = "Contexts"; map["title"] = context; if (mFilters.indexOf(context) >= 0) { map["checked"] = true; } else { map["checked"] = false; } filters.append(map); } foreach (QString duedate, mDueDates) { QVariantMap map; qDebug() << "::" + duedate; map["type"] = "Due Dates"; map["title"] = duedate; if (mFilters.indexOf(duedate) >= 0) { map["checked"] = true; } else { map["checked"] = false; } filters.append(map); } return filters; } QVariantList TaskModel::tododata() { QVariantList rData; foreach (QString item, mTodoList) { QVariantMap map = parseTask(item); rData.append(map); } return rData; } void TaskModel::refresh() { QSettings settings; load(); emit listUpdated(); } void TaskModel::commit() { QSettings settings; QString lnend = settings.value("windowsbreak").toBool() ? "\r\n" : "\n"; save(); } void TaskModel::load() { mTodoData.clear(); QStringList dummy; dummy = localfile.load(); foreach (QString item, dummy) { QVariantMap map = parseTask(item); mTodoData.append(map); } mArchiveData = localfile.loadArchive(); } void TaskModel::save() { QStringList data; foreach (QVariant map, mTodoData) { QString listitem = buildTask(map.toMap()); data.append(listitem); } localfile.save(data); load(); setView(); emit listUpdated(); } void TaskModel::archive() { QSettings settings; QString lnend = settings.value("windowsbreak").toBool() ? "\r\n" : "\n"; foreach (QVariant map, mTodoData) { QVariantMap task = map.toMap(); if (task["complete"].toBool()) { QString architem = buildTask(task); mArchiveData.append(architem); mTodoData.removeAt(mTodoData.indexOf(task)); } } if (mArchiveData.size() > 0) { localfile.saveArchive(mArchiveData); commit(); } else { load(); setView(); } } void TaskModel::search(const QString &text) { mSearch = text; setView(); } void TaskModel::setValue(int idx, const QString &key, const QVariant &value) { //qDebug() << "index: " << idx; //qDebug() << "key: " << key; //qDebug() << "value: " << value; //qDebug() << "check: " << QVariantListDataModel::value(idx).value(); QSettings settings; if (idx >=0 && idx < size()) { QVariantMap current = QVariantListDataModel::value(idx).value(); QVariantMap updated = current; updated[key] = value; if (key == "complete" && value == true) { updated["priority"] = ""; updated["dateCompleted"] = QDate::currentDate().toString("yyyy-MM-dd"); } else if (key == "complete" && value == false) { updated["dateCompleted"] = ""; } QString rtask = buildTask(updated); updated["text"] = QVariant(rtask); //qDebug() << "current: " << current; //qDebug() << "idx: " << idx; //qDebug() << "orig idx: " << mTodoData.indexOf(current); //qDebug() << "test0:" << mTodoData.value(0); //qDebug() << "testX:" << mTodoData.value(mTodoData.size()-1); int origidx = mTodoData.indexOf(current); mTodoData.replace(origidx, updated); //setView(); commit(); if (key == "complete" && value == true && settings.value("autoarchive").toBool()) { archive(); } } //qDebug() << "new: " << QVariantListDataModel::value(idx).value(); } void TaskModel::exportFiles(const QStringList &files) { QString epath = files[0]; QRegExp epathrgx("todo.txt$"); epath.replace(epathrgx, ""); LocalFile exportfrom; LocalFile exportto; exportto.setPath(epath); QStringList todo = exportfrom.load(); QStringList done = exportfrom.loadArchive(); exportto.save(todo); exportto.saveArchive(done); } void TaskModel::promptPurgeSandbox() { dialog = new SystemDialog("PURGE", "CANCEL"); dialog->setTitle("Purge local sandbox"); dialog->setBody("Erase local sandbox files? WARNING This cannot be undone."); bool success = QObject::connect(dialog, SIGNAL(finished(bb::system::SystemUiResult::Type)), this, SLOT(onConfirmPurge(bb::system::SystemUiResult::Type))); if (success) { dialog->show(); } else { dialog->deleteLater(); } } void TaskModel::onConfirmPurge(bb::system::SystemUiResult::Type value) { QStringList empty; LocalFile sandbox; switch(value) { case bb::system::SystemUiResult::ConfirmButtonSelection: qDebug() << "PURGE"; sandbox.save(empty); sandbox.saveArchive(empty); load(); setView(); break; case bb::system::SystemUiResult::CancelButtonSelection: qDebug() << "CANCEL"; break; default: break; } dialog->deleteLater(); } void TaskModel::promptDelete(int idx) { dialog = new SystemDialog("DELETE", "CANCEL"); dialog->setTitle("Delete Task"); dialog->setBody("Permanently delete this Entry?"); bool success = QObject::connect(dialog, SIGNAL(finished(bb::system::SystemUiResult::Type)), this, SLOT(onDeleteTask(bb::system::SystemUiResult::Type))); if (success) { mDelete = idx; dialog->show(); } else { mDelete = -1; dialog->deleteLater(); } } void TaskModel::onDeleteTask(bb::system::SystemUiResult::Type value) { switch(value) { case bb::system::SystemUiResult::ConfirmButtonSelection: //qDebug() << "DELETE"; delTask(mDelete); break; case bb::system::SystemUiResult::CancelButtonSelection: //qDebug() << "CANCEL"; mDelete = -1; break; default: break; } dialog->deleteLater(); } void TaskModel::delTask(int idx) { if (idx >=0 && idx < size()) { QVariantMap current = QVariantListDataModel::value(idx).value(); int origidx = mTodoData.indexOf(current); //qDebug() << "idx: " << idx; //qDebug() << "orig idx: " << mTodoData.indexOf(current); mTodoData.removeAt(origidx); //setView(); commit(); mDelete = -1; } } void TaskModel::changeFilter(const QString &filter, const bool &enabled) { int idx = mFilters.indexOf(filter); if (enabled) { if (idx == -1) mFilters.append(filter); } else { if (idx >= 0) mFilters.removeAt(idx); } //qDebug() << "Filter List: " << mFilters; } void TaskModel::resetFilter() { mFilters.clear(); setView(); toast->setBody("Filters removed."); toast->show(); } void TaskModel::addFullTask(const QString &task) { QVariantMap map = parseTask(task); QSettings settings; if (settings.value("datenew").toBool()) { map["dateCreated"] = QDate::currentDate().toString("yyyy-MM-dd"); } mTodoData.append(map); //setView(); commit(); } void TaskModel::updateTask(int idx, const QString &task) { if (idx >=0 && idx < size()) { QVariantMap current = QVariantListDataModel::value(idx).value(); int origidx = mTodoData.indexOf(current); QVariantMap map = parseTask(task); if (origidx >= 0) { mTodoData.replace(origidx, map); //setView(); commit(); } else { // TODO produce some error message qDebug() << "ERROR: origidx = " << origidx; } } } QVariantMap TaskModel::parseTask(QString rawTask) { QVariantMap task; int pos = 0; QString item = rawTask; bool complete = false; QString priority = ""; QString dateCompleted = ""; QString dateCreated = ""; QString dateDue = ""; QString detail = ""; //qDebug() << "..parsing projects.."; QStringList prj; pos = 0; while ((pos = regxProj.indexIn(item,pos)) != -1) { prj << regxProj.cap(1); pos += regxProj.matchedLength(); } if (prj.size() > 0) { foreach (QString p, prj) { if (mProjects.indexOf(p) < 0) { mProjects << p; } } } //qDebug() << "my projects: " << mProjects; //qDebug() << "..parsing contexts.."; QStringList ctx; pos = 0; while ((pos = regxCtx.indexIn(item,pos)) != -1) { ctx << regxCtx.cap(1); pos += regxCtx.matchedLength(); } if (ctx.size() > 0) { foreach (QString c, ctx) { if (mContexts.indexOf(c) < 0) { mContexts << c; } } } //qDebug() << "my contexts: " << mContexts; QRegExp regxDue("due:([0-9]{4}-[0-9]{2}-[0-9]{2}) ?"); if (regxDue.indexIn(item,0) != -1) { dateDue = regxDue.cap(1); QString duestr = "due:" + dateDue; if (dateDue.length() > 0) { if (mDueDates.indexOf(duestr) < 0) { mDueDates << duestr; } } } //qDebug() << "---*****---"; if (regxComp.indexIn(item) == 0) { //qDebug() << "complete: true"; complete = true; item.replace(regxComp, ""); pos = 0; QStringList dates; while ((pos = regxDate.indexIn(item,pos)) != -1) { dates << regxDate.cap(1); pos += regxDate.matchedLength(); } if (dates.size() > 0) { //qDebug() << "completed: " << dates.at(0); dateCompleted = dates.at(0); if (dates.size() >= 2) { //qDebug() << "created: " << dates.at(1); dateCreated = dates.at(1); pos = regxDate.indexIn(item,0); item.replace(QRegExp("^([0-9]{4}-[0-9]{2}-[0-9]{2} )"), ""); } } pos = regxDate.indexIn(item,0); item.replace(QRegExp("^([0-9]{4}-[0-9]{2}-[0-9]{2} )"), ""); } else if (regxDate.indexIn(item) >= 0 && regxDate.indexIn(item) < 5) { dateCreated = regxDate.cap(1); // item.replace(regxDate.cap(1) + " ", ""); // item.replace(QRegExp("^([0-9]{4}-[0-9]{2}-[0-9]{2} )"), ""); item.remove(regxDate.indexIn(item), 11); } if (regxPri.indexIn(item) >= 0) { //qDebug() << "priority: " << regxPri.cap(1); priority = regxPri.cap(1); item.replace(regxPri, ""); } detail = item; task["complete"] = QVariant(complete); task["priority"] = QVariant(priority); task["dateCompleted"] = QVariant(dateCompleted); task["dateCreated"] = QVariant(dateCreated); task["dateDue"] = QVariant(dateDue); task["detail"] = QVariant(detail); task["text"] = QVariant(rawTask); return task; } QString TaskModel::buildTask(const QVariantMap &task) { QString text = ""; bool complete = task["complete"].toBool(); QString priority = task["priority"].toString(); QString dateCompleted = task["dateCompleted"].toString(); QString dateCreated = task["dateCreated"].toString(); QString detail = task["detail"].toString(); if (complete) text = text + "x "; if (priority.length() > 0) text = text + "(" + priority + ") "; if (dateCompleted.length() > 0) text = text + dateCompleted + " "; if (dateCreated.length() > 0) text = text + dateCreated + " "; if (detail.length() > 0) text = text + detail; return text; } bool TaskModel::sortByPriority(const QString &a, const QString &b) { QString matchA; QString matchB; QRegExp pri("\\(([A-Z])\\) "); QRegExp done("^x "); QRegExp date("([0-9]{4}-[0-9]{2}-[0-9]{2}) ?"); QRegExp sca("^@"); QRegExp scp("^\\+"); if (pri.indexIn(a) >= 0) { matchA = pri.cap(1); } else if (done.indexIn(a) == 0) { matchA = "}"; } else { matchA = a.toLower(); matchA.replace(date,""); matchA.replace(sca,""); matchA.replace(scp,""); } //qDebug() << "a: " << a; //qDebug() << "matchA: " << matchA; if (pri.indexIn(b) >= 0) { matchB = pri.cap(1); } else if (done.indexIn(b) == 0) { matchB = "}"; } else { matchB = b.toLower(); matchB.replace(date,""); matchB.replace(sca,""); matchB.replace(scp,""); } //qDebug() << "b: " << b; //qDebug() << "matchB: " << matchB; return matchA < matchB; } bool TaskModel::sortByText(const QString &a, const QString &b) { QString matchA = a; QString matchB = b; QRegExp pri("\\([A-Z]\\) "); QRegExp done("^x "); QRegExp date("([0-9]{4}-[0-9]{2}-[0-9]{2}) ?"); QRegExp sca("^@"); QRegExp scp("^\\+"); matchA.replace(done, ""); matchA.replace(pri, ""); matchA.replace(date, ""); matchA.replace(sca,""); matchA.replace(scp,""); //qDebug() << "A: " << matchA; matchB.replace(done, ""); matchB.replace(pri, ""); matchB.replace(date, ""); matchB.replace(sca,""); matchB.replace(scp,""); //qDebug() << "B: " << matchB; return matchA.toLower() < matchB.toLower(); } bool TaskModel::sortByDueDate(const QString &a, const QString &b) { QString matchA = a; QString matchB = b; QRegExp regxDue("(due:)([0-9]{4}-[0-9]{2}-[0-9]{2}) ?"); QRegExp pri("\\([A-Z]\\) "); QRegExp done("^x "); QRegExp date("([0-9]{4}-[0-9]{2}-[0-9]{2}) ?"); QRegExp sca("^@"); QRegExp scp("^\\+"); if (regxDue.indexIn(a,0) != -1) { matchA = regxDue.cap(2); } else if (done.indexIn(a) == 0) { matchA = "}"; } else { matchA.replace(done, ""); matchA.replace(pri, ""); matchA.replace(date, ""); matchA.replace(sca,""); matchA.replace(scp,""); } if (regxDue.indexIn(b,0) != -1) { matchB = regxDue.cap(2); } else if (done.indexIn(b) == 0) { matchB = "}"; } else { matchB.replace(done, ""); matchB.replace(pri, ""); matchB.replace(date, ""); matchB.replace(sca,""); matchB.replace(scp,""); } return matchA.toLower() < matchB.toLower(); } void TaskModel::setView() { mTodoList.clear(); mProjects.clear(); mContexts.clear(); mDueDates.clear(); //qDebug() << "::" << mFilters; foreach (QVariant map, mTodoData) { QString listitem = buildTask(map.toMap()); if (mSearch.length() > 0) { if (listitem.contains(mSearch, Qt::CaseInsensitive)) { mTodoList.append(listitem); } } else if (mFilters.length() > 0) { // TODO this results in an "or" condition, probably should be "and" instead foreach (QString filter, mFilters) { if (listitem.indexOf(filter) >=0) { mTodoList.append(listitem); break; } } } else { mTodoList.append(listitem); } } clear(); switch (mSort) { case PRIORITY: qSort(mTodoList.begin(), mTodoList.end(), sortByPriority); break; case ID: // this is the default break; case TEXT: qSort(mTodoList.begin(), mTodoList.end(), sortByText); break; case DUE: qSort(mTodoList.begin(), mTodoList.end(), sortByDueDate); break; } foreach (QString item, mTodoList) { QVariantMap map = parseTask(item); append(map); } emit listUpdated(); }