File indexing completed on 2025-01-19 04:56:39

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org>
0003  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004  */
0005 
0006 
0007 #include "editormodel.h"
0008 
0009 #include <QAbstractListModel>
0010 #include <QDebug>
0011 #include <QDesktopServices>
0012 #include <QDir>
0013 #include <QIcon>
0014 #include <QMimeDatabase>
0015 #include <QTemporaryFile>
0016 #include <QTimer>
0017 
0018 #include <KLocalizedString>
0019 
0020 #include "errorhandler.h"
0021 
0022 namespace Presentation {
0023 class AttachmentModel : public QAbstractListModel
0024 {
0025     Q_OBJECT
0026 public:
0027     explicit AttachmentModel(QObject *parent = nullptr)
0028         : QAbstractListModel(parent)
0029     {
0030     }
0031 
0032     void setTask(const Domain::Task::Ptr &task)
0033     {
0034         if (m_task == task)
0035             return;
0036 
0037         beginResetModel();
0038         if (m_task) {
0039             disconnect(m_task.data(), &Domain::Task::attachmentsChanged,
0040                        this, &AttachmentModel::triggerReset);
0041         }
0042         m_task = task;
0043         if (m_task) {
0044             connect(m_task.data(), &Domain::Task::attachmentsChanged,
0045                     this, &AttachmentModel::triggerReset);
0046         }
0047         endResetModel();
0048     }
0049 
0050     int rowCount(const QModelIndex &parent) const override
0051     {
0052         if (parent.isValid() || !m_task)
0053             return 0;
0054 
0055         return m_task->attachments().size();
0056     }
0057 
0058     QVariant data(const QModelIndex &index, int role) const override
0059     {
0060         if (!index.isValid())
0061             return QVariant();
0062 
0063         auto attachment = m_task->attachments().at(index.row());
0064 
0065         switch (role) {
0066         case Qt::DisplayRole:
0067             return attachment.label();
0068         case Qt::DecorationRole:
0069             return QVariant::fromValue(QIcon::fromTheme(attachment.iconName()));
0070         default:
0071             return QVariant();
0072         }
0073     }
0074 
0075 private slots:
0076     void triggerReset()
0077     {
0078         beginResetModel();
0079         endResetModel();
0080     }
0081 
0082 private:
0083     Domain::Task::Ptr m_task;
0084 };
0085 }
0086 
0087 using namespace Presentation;
0088 
0089 static int s_autoSaveDelay = 500;
0090 
0091 EditorModel::EditorModel(QObject *parent)
0092     : QObject(parent),
0093       m_done(false),
0094       m_recurrence(Domain::Task::NoRecurrence),
0095       m_attachmentModel(new AttachmentModel(this)),
0096       m_saveTimer(new QTimer(this)),
0097       m_saveNeeded(false),
0098       m_editingInProgress(false)
0099 {
0100     m_saveTimer->setSingleShot(true);
0101     connect(m_saveTimer, &QTimer::timeout, this, &EditorModel::save);
0102 }
0103 
0104 EditorModel::~EditorModel()
0105 {
0106     save();
0107 }
0108 
0109 Domain::Task::Ptr EditorModel::task() const
0110 {
0111     return m_task;
0112 }
0113 
0114 void EditorModel::setTask(const Domain::Task::Ptr &task)
0115 {
0116     if (m_task == task)
0117         return;
0118 
0119     save();
0120 
0121     m_text = QString();
0122     m_title = QString();
0123     m_done = false;
0124     m_start = QDate();
0125     m_due = QDate();
0126     m_recurrence = Domain::Task::NoRecurrence;
0127     m_attachmentModel->setTask(Domain::Task::Ptr());
0128 
0129     if (m_task)
0130         disconnect(m_task.data(), nullptr, this, nullptr);
0131 
0132     m_task = task;
0133 
0134     if (m_task) {
0135         m_text = m_task->text();
0136         m_title = m_task->title();
0137         m_done = m_task->isDone();
0138         m_start = m_task->startDate();
0139         m_due = m_task->dueDate();
0140         m_recurrence = m_task->recurrence();
0141         m_attachmentModel->setTask(m_task);
0142 
0143         connect(m_task.data(), &Domain::Task::textChanged, this, &EditorModel::onTextChanged);
0144         connect(m_task.data(), &Domain::Task::titleChanged, this, &EditorModel::onTitleChanged);
0145         connect(m_task.data(), &Domain::Task::doneChanged, this, &EditorModel::onDoneChanged);
0146         connect(m_task.data(), &Domain::Task::startDateChanged, this, &EditorModel::onStartDateChanged);
0147         connect(m_task.data(), &Domain::Task::dueDateChanged, this, &EditorModel::onDueDateChanged);
0148         connect(m_task.data(), &Domain::Task::recurrenceChanged, this, &EditorModel::onRecurrenceChanged);
0149     }
0150 
0151 
0152     emit textChanged(m_text);
0153     emit titleChanged(m_title);
0154     emit doneChanged(m_done);
0155     emit startDateChanged(m_start);
0156     emit dueDateChanged(m_due);
0157     emit recurrenceChanged(m_recurrence);
0158     emit taskChanged(m_task);
0159 }
0160 
0161 bool EditorModel::hasSaveFunction() const
0162 {
0163     return bool(m_saveFunction);
0164 }
0165 
0166 void EditorModel::setSaveFunction(const SaveFunction &function)
0167 {
0168     m_saveFunction = function;
0169 }
0170 
0171 QString EditorModel::text() const
0172 {
0173     return m_text;
0174 }
0175 
0176 QString EditorModel::title() const
0177 {
0178     return m_title;
0179 }
0180 
0181 bool EditorModel::isDone() const
0182 {
0183     return m_done;
0184 }
0185 
0186 QDate EditorModel::startDate() const
0187 {
0188     return m_start;
0189 }
0190 
0191 QDate EditorModel::dueDate() const
0192 {
0193     return m_due;
0194 }
0195 
0196 Domain::Task::Recurrence EditorModel::recurrence() const
0197 {
0198     return m_recurrence;
0199 }
0200 
0201 QAbstractItemModel *EditorModel::attachmentModel() const
0202 {
0203     return m_attachmentModel;
0204 }
0205 
0206 int EditorModel::autoSaveDelay()
0207 {
0208     return s_autoSaveDelay;
0209 }
0210 
0211 void EditorModel::setAutoSaveDelay(int delay)
0212 {
0213     s_autoSaveDelay = delay;
0214 }
0215 
0216 bool EditorModel::editingInProgress() const
0217 {
0218     return m_editingInProgress;
0219 }
0220 
0221 void EditorModel::setText(const QString &text)
0222 {
0223     if (m_text == text)
0224         return;
0225     applyNewText(text);
0226     setSaveNeeded(true);
0227 }
0228 
0229 void EditorModel::setTitle(const QString &title)
0230 {
0231     if (m_title == title)
0232         return;
0233     applyNewTitle(title);
0234     setSaveNeeded(true);
0235 }
0236 
0237 void EditorModel::setDone(bool done)
0238 {
0239     if (m_done == done)
0240         return;
0241     applyNewDone(done);
0242     setSaveNeeded(true);
0243 }
0244 
0245 void EditorModel::setStartDate(const QDate &start)
0246 {
0247     if (m_start == start)
0248         return;
0249     applyNewStartDate(start);
0250     setSaveNeeded(true);
0251 }
0252 
0253 void EditorModel::setDueDate(const QDate &due)
0254 {
0255     if (m_due == due)
0256         return;
0257     applyNewDueDate(due);
0258     setSaveNeeded(true);
0259 }
0260 
0261 void EditorModel::setRecurrence(Domain::Task::Recurrence recurrence)
0262 {
0263     if (m_recurrence == recurrence)
0264         return;
0265     applyNewRecurrence(recurrence);
0266     setSaveNeeded(true);
0267 }
0268 
0269 void EditorModel::addAttachment(const QString &fileName)
0270 {
0271     if (!m_task)
0272         return;
0273 
0274     QMimeDatabase mimeDb;
0275     auto mimeType = mimeDb.mimeTypeForFile(fileName);
0276 
0277     auto attachment = Domain::Task::Attachment();
0278     attachment.setLabel(QFileInfo(fileName).fileName());
0279     attachment.setMimeType(mimeType.name());
0280     attachment.setIconName(mimeType.iconName());
0281 
0282     QFile file(fileName);
0283     if (!file.open(QFile::ReadOnly)) {
0284         // TODO: Might be worth extending error handling
0285         // to deal with job-less errors later on
0286         qWarning() << "Couldn't open" << fileName;
0287         return;
0288     }
0289 
0290     attachment.setData(file.readAll());
0291 
0292     file.close();
0293 
0294     auto attachments = m_task->attachments();
0295     attachments.append(attachment);
0296     m_task->setAttachments(attachments);
0297 
0298     setSaveNeeded(true);
0299 }
0300 
0301 void EditorModel::removeAttachment(const QModelIndex &index)
0302 {
0303     if (!m_task)
0304         return;
0305 
0306     auto attachments = m_task->attachments();
0307     attachments.removeAt(index.row());
0308     m_task->setAttachments(attachments);
0309 
0310     setSaveNeeded(true);
0311 }
0312 
0313 void EditorModel::openAttachment(const QModelIndex &index)
0314 {
0315     Q_ASSERT(m_task);
0316     auto attachment = m_task->attachments().at(index.row());
0317 
0318     auto uri = attachment.uri();
0319     if (!attachment.isUri()) {
0320         auto tempFile = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/zanshin_attachment_XXXXXX"), this);
0321         tempFile->open();
0322         tempFile->setPermissions(QFile::ReadUser);
0323         tempFile->write(attachment.data());
0324         tempFile->close();
0325         uri = QUrl::fromLocalFile(tempFile->fileName());
0326     }
0327 
0328     QDesktopServices::openUrl(uri);
0329 }
0330 
0331 void EditorModel::setEditingInProgress(bool editing)
0332 {
0333     m_editingInProgress = editing;
0334 }
0335 
0336 void EditorModel::onTextChanged(const QString &text)
0337 {
0338     if (!m_editingInProgress)
0339         applyNewText(text);
0340 }
0341 
0342 void EditorModel::onTitleChanged(const QString &title)
0343 {
0344     if (!m_editingInProgress)
0345         applyNewTitle(title);
0346 }
0347 
0348 void EditorModel::onDoneChanged(bool done)
0349 {
0350     if (!m_editingInProgress)
0351         applyNewDone(done);
0352 }
0353 
0354 void EditorModel::onStartDateChanged(const QDate &start)
0355 {
0356     if (!m_editingInProgress)
0357         applyNewStartDate(start);
0358 }
0359 
0360 void EditorModel::onDueDateChanged(const QDate &due)
0361 {
0362     if (!m_editingInProgress)
0363         applyNewDueDate(due);
0364 }
0365 
0366 void EditorModel::onRecurrenceChanged(Domain::Task::Recurrence recurrence)
0367 {
0368     if (!m_editingInProgress)
0369         applyNewRecurrence(recurrence);
0370 }
0371 
0372 void EditorModel::save()
0373 {
0374     if (!isSaveNeeded())
0375         return;
0376 
0377     Q_ASSERT(m_task);
0378 
0379     const auto currentTitle = m_task->title();
0380     m_task->setTitle(m_title);
0381     m_task->setText(m_text);
0382 
0383     m_task->setDone(m_done);
0384     m_task->setStartDate(m_start);
0385     m_task->setDueDate(m_due);
0386     m_task->setRecurrence(m_recurrence);
0387 
0388     const auto job = m_saveFunction(m_task);
0389     installHandler(job, i18n("Cannot modify task %1", currentTitle));
0390     setSaveNeeded(false);
0391 }
0392 
0393 void EditorModel::setSaveNeeded(bool needed)
0394 {
0395     if (needed)
0396         m_saveTimer->start(autoSaveDelay());
0397     else
0398         m_saveTimer->stop();
0399 
0400     m_saveNeeded = needed;
0401 }
0402 
0403 bool EditorModel::isSaveNeeded() const
0404 {
0405     return m_saveNeeded;
0406 }
0407 
0408 void EditorModel::applyNewText(const QString &text)
0409 {
0410     m_text = text;
0411     emit textChanged(m_text);
0412 }
0413 
0414 void EditorModel::applyNewTitle(const QString &title)
0415 {
0416     m_title = title;
0417     emit titleChanged(m_title);
0418 }
0419 
0420 void EditorModel::applyNewDone(bool done)
0421 {
0422     m_done = done;
0423     emit doneChanged(m_done);
0424 }
0425 
0426 void EditorModel::applyNewStartDate(const QDate &start)
0427 {
0428     m_start = start;
0429     emit startDateChanged(m_start);
0430 }
0431 
0432 void EditorModel::applyNewDueDate(const QDate &due)
0433 {
0434     m_due = due;
0435     emit dueDateChanged(m_due);
0436 }
0437 
0438 void EditorModel::applyNewRecurrence(Domain::Task::Recurrence recurrence)
0439 {
0440     m_recurrence = recurrence;
0441     emit recurrenceChanged(m_recurrence);
0442 }
0443 
0444 #include "editormodel.moc"
0445 
0446 #include "moc_editormodel.cpp"