diff --git a/src/akonadi/akonadiserializer.cpp b/src/akonadi/akonadiserializer.cpp index 6c9fc37b..1f82a6cd 100644 --- a/src/akonadi/akonadiserializer.cpp +++ b/src/akonadi/akonadiserializer.cpp @@ -1,971 +1,1049 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 RĂ©mi Benoit This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadiserializer.h" #include #include #include #include #include #include #include #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" #include using namespace Akonadi; Serializer::Serializer() { } Serializer::~Serializer() { } bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection) { return object->property("collectionId").toLongLong() == collection.id(); } bool Serializer::representsItem(QObjectPtr object, Item item) { return object->property("itemId").toLongLong() == item.id(); } bool Serializer::representsAkonadiTag(Domain::Tag::Ptr tag, Tag akonadiTag) const { return tag->property("tagId").toLongLong() == akonadiTag.id(); } QString Serializer::objectUid(SerializerInterface::QObjectPtr object) { return object->property("todoUid").toString(); } Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return Domain::DataSource::Ptr(); auto dataSource = Domain::DataSource::Ptr::create(); updateDataSourceFromCollection(dataSource, collection, naming); return dataSource; } void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return; QString name = collection.displayName(); if (naming == FullPath) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { name = parent.displayName() + "/" + name; parent = parent.parentCollection(); } } dataSource->setName(name); const auto mimeTypes = collection.contentMimeTypes(); auto types = Domain::DataSource::ContentTypes(); if (mimeTypes.contains(NoteUtils::noteMimeType())) types |= Domain::DataSource::Notes; if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) types |= Domain::DataSource::Tasks; dataSource->setContentTypes(types); if (collection.hasAttribute()) { auto iconName = collection.attribute()->iconName(); dataSource->setIconName(iconName); } if (!collection.hasAttribute()) { dataSource->setSelected(true); } else { auto isSelected = collection.attribute()->isSelected(); dataSource->setSelected(isSelected); } if (collection.enabled()) dataSource->setListStatus(Domain::DataSource::Bookmarked); else if (collection.referenced()) dataSource->setListStatus(Domain::DataSource::Listed); else dataSource->setListStatus(Domain::DataSource::Unlisted); dataSource->setProperty("collectionId", collection.id()); dataSource->setProperty("collection", QVariant::fromValue(collection)); dataSource->setPerson(isPersonCollection(collection)); if (isPersonCollection(collection)) { dataSource->setIconName("meeting-participant"); } } Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) { if (!dataSource) { return Akonadi::Collection(); } auto collection = dataSource->property("collection").value(); collection.attribute(Akonadi::Collection::AddIfMissing); auto selectedAttribute = collection.attribute(Akonadi::Collection::AddIfMissing); selectedAttribute->setSelected(dataSource->isSelected()); switch (dataSource->listStatus()) { case Domain::DataSource::Unlisted: collection.setReferenced(false); collection.setEnabled(false); break; case Domain::DataSource::Listed: collection.setReferenced(true); collection.setEnabled(false); break; case Domain::DataSource::Bookmarked: collection.setReferenced(false); collection.setEnabled(true); break; default: qFatal("Shouldn't happen"); break; } return collection; } bool Serializer::isListedCollection(Collection collection) { return collection.enabled() || collection.referenced(); } bool Serializer::isSelectedCollection(Collection collection) { if (!isListedCollection(collection)) return false; if (!isNoteCollection(collection) && !isTaskCollection(collection)) return false; if (!collection.hasAttribute()) return true; return collection.attribute()->isSelected(); } bool Akonadi::Serializer::isNoteCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(NoteUtils::noteMimeType()); } bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType()); } bool Akonadi::Serializer::isPersonCollection(Akonadi::Collection collection) { auto attr = collection.attribute(); return (attr && attr->collectionNamespace() == "usertoplevel"); } bool Serializer::isTaskItem(Item item) { if (!item.hasPayload()) return false; auto todo = item.payload(); return todo->customProperty("Zanshin", "Project").isEmpty(); } Domain::Task::Ptr Serializer::createTaskFromItem(Item item) { if (!isTaskItem(item)) return Domain::Task::Ptr(); auto task = Domain::Task::Ptr::create(); updateTaskFromItem(task, item); return task; } static Domain::Task::Status fromKCalStatus(KCalCore::Incidence::Status status) { switch (status) { case KCalCore::Incidence::StatusNone: return Domain::Task::None; case KCalCore::Incidence::StatusInProcess: return Domain::Task::InProcess; case KCalCore::Incidence::StatusCanceled: return Domain::Task::Cancelled; case KCalCore::Incidence::StatusCompleted: return Domain::Task::Complete; case KCalCore::Incidence::StatusNeedsAction: return Domain::Task::NeedsAction; default: break; } return Domain::Task::None; } static KCalCore::Incidence::Status toKCalStatus(Domain::Task::Status status) { switch (status) { case Domain::Task::None: return KCalCore::Incidence::StatusNone; case Domain::Task::InProcess: return KCalCore::Incidence::StatusInProcess; case Domain::Task::Cancelled: return KCalCore::Incidence::StatusCanceled; case Domain::Task::Complete: case Domain::Task::FullComplete: return KCalCore::Incidence::StatusCompleted; case Domain::Task::NeedsAction: return KCalCore::Incidence::StatusNeedsAction; default: break; } return KCalCore::Incidence::StatusNone; } int toWeekDay(Domain::Recurrence::Weekday wday) { switch (wday) { case Domain::Recurrence::Monday: return 1; case Domain::Recurrence::Tuesday: return 2; case Domain::Recurrence::Wednesday: return 3; case Domain::Recurrence::Thursday: return 4; case Domain::Recurrence::Friday: return 5; case Domain::Recurrence::Saturday: return 6; case Domain::Recurrence::Sunday: return 7; default: qFatal("unhddandled wday"); } return 1; } Domain::Recurrence::Weekday fromWeekDay(int wday) { switch (wday) { case 1: return Domain::Recurrence::Monday; case 2: return Domain::Recurrence::Tuesday; case 3: return Domain::Recurrence::Wednesday; case 4: return Domain::Recurrence::Thursday; case 5: return Domain::Recurrence::Friday; case 6: return Domain::Recurrence::Saturday; case 7: return Domain::Recurrence::Sunday; default: qFatal("unhandled wday"); } return Domain::Recurrence::Monday; } KCalCore::RecurrenceRule::PeriodType toRecurrenceType(Domain::Recurrence::Frequency freq) { switch(freq) { case Domain::Recurrence::Frequency::None: qWarning() << "no recurrence?"; break; case Domain::Recurrence::Frequency::Yearly: return KCalCore::RecurrenceRule::rYearly; case Domain::Recurrence::Frequency::Monthly: return KCalCore::RecurrenceRule::rMonthly; case Domain::Recurrence::Frequency::Weekly: return KCalCore::RecurrenceRule::rWeekly; case Domain::Recurrence::Frequency::Daily: return KCalCore::RecurrenceRule::rDaily; case Domain::Recurrence::Frequency::Hourly: return KCalCore::RecurrenceRule::rHourly; case Domain::Recurrence::Frequency::Minutely: return KCalCore::RecurrenceRule::rMinutely; case Domain::Recurrence::Frequency::Secondly: return KCalCore::RecurrenceRule::rSecondly; default: qFatal("unhandled recurrencetype"); } return KCalCore::RecurrenceRule::rNone; } Domain::Recurrence::Frequency fromRecurrenceType(KCalCore::RecurrenceRule::PeriodType freq) { switch(freq) { case KCalCore::RecurrenceRule::rNone: qWarning() << "no recurrence?"; break; case KCalCore::RecurrenceRule::rYearly: return Domain::Recurrence::Frequency::Yearly; case KCalCore::RecurrenceRule::rMonthly: return Domain::Recurrence::Frequency::Monthly; case KCalCore::RecurrenceRule::rWeekly: return Domain::Recurrence::Frequency::Weekly; case KCalCore::RecurrenceRule::rDaily: return Domain::Recurrence::Frequency::Daily; case KCalCore::RecurrenceRule::rHourly: return Domain::Recurrence::Frequency::Hourly; case KCalCore::RecurrenceRule::rMinutely: return Domain::Recurrence::Frequency::Minutely; case KCalCore::RecurrenceRule::rSecondly: return Domain::Recurrence::Frequency::Secondly; default: qFatal("unhandled recurrenceType"); } return Domain::Recurrence::Frequency::None; } Domain::Recurrence::Ptr fromKCalRecurrence(const KCalCore::Recurrence *rec) { Domain::Recurrence recurrence; recurrence.setAllDay(rec->allDay()); recurrence.setInterval(rec->frequency()); QList rdates; foreach (const KDateTime &dt, rec->rDateTimes()) { rdates.append(dt.dateTime()); } foreach (const QDate &dt, rec->rDates()) { rdates.append(QDateTime(dt)); } recurrence.setRecurrenceDates(rdates); QList exdates; foreach (const KDateTime &dt, rec->exDateTimes()) { exdates.append(dt.dateTime()); } foreach (const QDate &dt, rec->exDates()) { exdates.append(QDateTime(dt)); } recurrence.setExceptionDates(exdates); const KCalCore::RecurrenceRule *defaultRR = rec->defaultRRuleConst(); if (defaultRR) { if (defaultRR->duration() != 0) { //Inidcates if end date is set or not if (defaultRR->duration() > 0) { recurrence.setCount(defaultRR->duration()); } else if (defaultRR->duration() == -1) { //infinite duration recurrence.setCount(-1); } } else { recurrence.setEnd(defaultRR->endDt().dateTime()); } recurrence.setWeekStart(fromWeekDay(defaultRR->weekStart())); recurrence.setFrequency(fromRecurrenceType(defaultRR->recurrenceType())); recurrence.setBysecond(defaultRR->bySeconds()); recurrence.setByminute(defaultRR->byMinutes()); recurrence.setByhour(defaultRR->byHours()); recurrence.setBymonthday(defaultRR->byMonthDays()); recurrence.setByyearday(defaultRR->byYearDays()); recurrence.setByweekno(defaultRR->byWeekNumbers()); recurrence.setBymonth(defaultRR->byMonths()); QList daypos; const auto positions = rec->monthPositions(); if (!positions.isEmpty()) { recurrence.setByDayPosition((Domain::Recurrence::WeekPosition) positions.at(0).pos()); for (int i = positions.size()-1; i > -1; i--) { if (positions.at(i).pos() == recurrence.byDayPosition()) { daypos.append((Domain::Recurrence::Weekday) (Domain::Recurrence::Monday + positions.at(i).day() - 1)); } } } recurrence.setByday(daypos); } return Domain::Recurrence::Ptr(new Domain::Recurrence(recurrence)); } +KCalCore::Alarm::Type toKCalAlarmType(Domain::Alarm::Type type) +{ + switch(type) { + case Domain::Alarm::Audio: + return KCalCore::Alarm::Audio; + case Domain::Alarm::Display: + return KCalCore::Alarm::Display; + default: + qFatal("unhandled Alarm tpye"); + } + return KCalCore::Alarm::Invalid; +} + +Domain::Alarm::Type fromKCalAlarmType(KCalCore::Alarm::Type type) +{ + switch(type) { + case KCalCore::Alarm::Audio: + return Domain::Alarm::Audio; + case KCalCore::Alarm::Display: + return Domain::Alarm::Display; + default: + qFatal("unhandled Alarm tpye"); + } + return Domain::Alarm::Display; +} + +Domain::Alarm::List fromKCalAlarm(KCalCore::Alarm::List alist) +{ + Domain::Alarm::List alarms; + + foreach(auto a, alist) { + Domain::Alarm::Ptr alarm(new Domain::Alarm()); + alarm->setType(fromKCalAlarmType(a->type())); + if (a->hasTime()) { + alarm->setPosition(Domain::Alarm::Exact); + alarm->setDateTime(a->time().dateTime()); + } else { + if (a->hasStartOffset()) { + alarm->setPosition(Domain::Alarm::Start); + alarm->setOffset(a->startOffset().asSeconds()); + } else { + alarm->setPosition(Domain::Alarm::End); + alarm->setOffset(a->endOffset().asSeconds()); + } + } + alarms.append(alarm); + } + + return alarms; +} + void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); task->setTitle(todo->summary()); task->setText(todo->description()); task->setStartDate(todo->dtStart().dateTime()); task->setDueDate(todo->dtDue().dateTime()); task->setProperty("itemId", item.id()); task->setProperty("parentCollectionId", item.parentCollection().id()); task->setProperty("todoUid", todo->uid()); task->setProperty("relatedUid", todo->relatedTo()); task->setProgress(todo->percentComplete()); task->setStatus(fromKCalStatus(todo->status())); Domain::Artifact::Attachment::List attachments; for (const auto &attachment : todo->attachments()) { if (attachment->isBinary()) { auto a = Domain::Artifact::Attachment::Ptr::create(); a->data = attachment->data(); a->label = attachment->label(); a->mimetype = attachment->mimeType(); attachments << a; } else { kDebug() << "ignored non-binary attachment"; } } task->setAttachments(attachments); if (todo->attendeeCount() > 0) { const auto attendees = todo->attendees(); const auto delegate = std::find_if(attendees.begin(), attendees.end(), [] (const KCalCore::Attendee::Ptr &attendee) { return attendee->status() == KCalCore::Attendee::Accepted || attendee->status() == KCalCore::Attendee::NeedsAction; }); if (delegate != attendees.end()) { task->setDelegate(Domain::Task::Delegate((*delegate)->name(), (*delegate)->email())); } } + if (!todo->alarms().isEmpty()) { + task->setAlarms(fromKCalAlarm(todo->alarms())); + } else { + task->setAlarms(Domain::Alarm::List()); + } if (todo->recurs()) { Domain::Recurrence::Ptr recurrence(fromKCalRecurrence(todo->recurrence())); task->setRecurrence(recurrence); if (task->status() == Domain::Task::Complete) { task->setStatus(Domain::Task::FullComplete); } } else { task->setRecurrence(Domain::Recurrence::Ptr(0)); } } bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) { if (!isTaskItem(item)) return false; auto todo = item.payload(); if (todo->relatedTo() == task->property("todoUid")) return true; return false; } void updateKCalRecurrence(const Domain::Recurrence::Ptr &from, KCalCore::Recurrence *recurrence) { KCalCore::RecurrenceRule *defaultRR = recurrence->defaultRRule(true); Q_ASSERT(defaultRR); defaultRR->setWeekStart(toWeekDay(from->weekStart())); defaultRR->setRecurrenceType(toRecurrenceType(from->frequency())); defaultRR->setFrequency(from->interval()); recurrence->setAllDay(from->allDay()); if (from->end().isValid()) { recurrence->setDuration(0); recurrence->setEndDateTime(KDateTime(from->end())); } else { recurrence->setDuration(from->count()); } if (!from->bysecond().empty()) { defaultRR->setBySeconds(from->bysecond()); } if (!from->byminute().empty()) { defaultRR->setByMinutes(from->byminute()); } if (!from->byhour().empty()) { defaultRR->setByHours(from->byhour()); } if (!from->bymonthday().empty()) { defaultRR->setByMonthDays(from->bymonthday()); } if (!from->byyearday().empty()) { defaultRR->setByYearDays(from->byyearday()); } if (!from->byweekno().empty()) { defaultRR->setByWeekNumbers(from->byweekno()); } if (!from->bymonth().empty()) { defaultRR->setByMonths(from->bymonth()); } if (!from->byday().empty()) { QBitArray days(7, 0); foreach(auto day, from->byday()) { days.setBit(day - Domain::Recurrence::Monday); } recurrence->addMonthlyPos(from->byDayPosition(), days); } foreach (const auto &dt, from->recurrenceDates()) { if (dt.time() == QTime(0,0,0)) { recurrence->addRDate(dt.date()); } else { recurrence->addRDateTime(KDateTime(dt)); } } foreach (const auto &dt, from->exceptionDates()) { if (dt.time() == QTime(0,0,0)) { recurrence->addExDate(dt.date()); } else { recurrence->addExDateTime(KDateTime(dt)); } } } Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(task->title()); todo->setDescription(task->text()); todo->setDtStart(KDateTime(task->startDate())); todo->setDtDue(KDateTime(task->dueDate())); todo->setPercentComplete(task->progress()); todo->setStatus(toKCalStatus(task->status())); + if (task->status() != Domain::Task::Complete || task->status() != Domain::Task::FullComplete) { //Because the serializer doesn't serialize the status as it should but instead serializes the isComplete status todo->setCompleted(false); } if (task->recurrence()) { todo->setDtRecurrence(KDateTime(task->startDate())); updateKCalRecurrence(task->recurrence(), todo->recurrence()); } + if(!task->alarms().isEmpty()) { + foreach(auto a, task->alarms()) { + KCalCore::Alarm::Ptr alarm(new KCalCore::Alarm(todo.data())); + KCalCore::Duration duration(a->offset(), KCalCore::Duration::Seconds); + alarm->setEnabled(true); + alarm->setType(toKCalAlarmType(a->type())); + switch (a->position()) { + case Domain::Alarm::Exact: + alarm->setTime(KDateTime(a->datetime())); + break; + case Domain::Alarm::Start: + alarm->setStartOffset(duration); + break; + case Domain::Alarm::End: + alarm->setEndOffset(duration); + break; + } + todo->addAlarm(alarm); + } + } + if (task->property("todoUid").isValid()) { todo->setUid(task->property("todoUid").toString()); } if (task->property("relatedUid").isValid()) { todo->setRelatedTo(task->property("relatedUid").toString()); } if (task->property("organizer").isValid()) { todo->setOrganizer(task->property("organizer").toString()); } if (task->delegate().isValid()) { KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(task->delegate().name(), task->delegate().email(), true, KCalCore::Attendee::NeedsAction)); todo->addAttendee(attendee); } for (const auto &a : task->attachments()) { auto attachment = KCalCore::Attachment::Ptr(new KCalCore::Attachment(QByteArray())); attachment->setMimeType(a->mimetype); attachment->setLabel(a->label); attachment->setData(a->data); todo->addAttachment(attachment); } Akonadi::Item item; if (task->property("itemId").isValid()) { item.setId(task->property("itemId").value()); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); item.setParentCollection(Akonadi::Collection(task->property("parentCollectionId").value())); return item; } QString Serializer::relatedUidFromItem(Akonadi::Item item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->relatedTo(); } else if (isNoteItem(item)) { const auto message = item.payload(); const auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid"); return relatedHeader ? relatedHeader->asUnicodeString() : QString(); } else { return QString(); } } void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(parent->property("todoUid").toString()); } void Serializer::updateItemProject(Item item, Domain::Project::Ptr project) { if (isTaskItem(item)) { auto todo = item.payload(); todo->setRelatedTo(project->property("todoUid").toString()); } else if (isNoteItem(item)) { auto note = item.payload(); note->removeHeader("X-Zanshin-RelatedProjectUid"); const QByteArray parentUid = project->property("todoUid").toString().toUtf8(); if (!parentUid.isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(parentUid); note->appendHeader(relatedHeader); } note->assemble(); } } void Serializer::removeItemParent(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); } Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) { if (potentialChildren.isEmpty()) return Akonadi::Item::List(); Akonadi::Item::List itemsToProcess = potentialChildren; Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload()); KCalCore::Todo::Ptr todo = ancestorItem.payload(); const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(), [ancestorItem, todo](Akonadi::Item currentItem) { return (!currentItem.hasPayload() || currentItem == ancestorItem || currentItem.payload()->relatedTo() != todo->uid()); }); Akonadi::Item::List itemsRemoved; std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved)); itemsToProcess.erase(itemsToProcess.begin(), bound); auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(), [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) { result << currentItem; return result += filterDescendantItems(itemsRemoved, currentItem); }); return result; } bool Serializer::isNoteItem(Item item) { return item.hasPayload(); } Domain::Note::Ptr Serializer::createNoteFromItem(Akonadi::Item item) { if (!isNoteItem(item)) return Domain::Note::Ptr(); Domain::Note::Ptr note = Domain::Note::Ptr::create(); updateNoteFromItem(note, item); return note; } void Serializer::updateNoteFromItem(Domain::Note::Ptr note, Item item) { if (!isNoteItem(item)) return; auto message = item.payload(); NoteUtils::NoteMessageWrapper wrappedNote(message); note->setTitle(wrappedNote.title()); note->setText(wrappedNote.text()); note->setProperty("itemId", item.id()); if (auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid")) { note->setProperty("relatedUid", relatedHeader->asUnicodeString()); } else { note->setProperty("relatedUid", QVariant()); } } Item Serializer::createItemFromNote(Domain::Note::Ptr note) { NoteUtils::NoteMessageWrapper builder; builder.setTitle(note->title()); builder.setText(note->text()); KMime::Message::Ptr message = builder.message(); if (!note->property("relatedUid").toString().isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(note->property("relatedUid").toString().toUtf8()); message->appendHeader(relatedHeader); } Akonadi::Item item; if (note->property("itemId").isValid()) { item.setId(note->property("itemId").value()); } item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); return item; } bool Serializer::isProjectItem(Item item) { if (!item.hasPayload()) return false; return !isTaskItem(item); } Domain::Project::Ptr Serializer::createProjectFromItem(Item item) { if (!isProjectItem(item)) return Domain::Project::Ptr(); auto project = Domain::Project::Ptr::create(); updateProjectFromItem(project, item); return project; } void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item) { if (!isProjectItem(item)) return; auto todo = item.payload(); project->setName(todo->summary()); project->setProperty("itemId", item.id()); project->setProperty("parentCollectionId", item.parentCollection().id()); project->setProperty("todoUid", todo->uid()); } Item Serializer::createItemFromProject(Domain::Project::Ptr project) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(project->name()); todo->setCustomProperty("Zanshin", "Project", "1"); if (project->property("todoUid").isValid()) { todo->setUid(project->property("todoUid").toString()); } Akonadi::Item item; if (project->property("itemId").isValid()) { item.setId(project->property("itemId").value()); } if (project->property("parentCollectionId").isValid()) { auto parentId = project->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item) { const QString todoUid = project->property("todoUid").toString(); const QString relatedUid = relatedUidFromItem(item); return !todoUid.isEmpty() && !relatedUid.isEmpty() && todoUid == relatedUid; } Domain::Context::Ptr Serializer::createContextFromTag(Akonadi::Tag tag) { if (!isContext(tag)) return Domain::Context::Ptr(); auto context = Domain::Context::Ptr::create(); updateContextFromTag(context, tag); return context; } Akonadi::Tag Serializer::createTagFromContext(Domain::Context::Ptr context) { auto tag = Akonadi::Tag(); tag.setName(context->name()); tag.setType(Akonadi::SerializerInterface::contextTagType()); tag.setGid(QByteArray(context->name().toLatin1())); if (context->property("tagId").isValid()) tag.setId(context->property("tagId").value()); return tag; } void Serializer::updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) { if (!isContext(tag)) return; context->setProperty("tagId", tag.id()); context->setName(tag.name()); } bool Serializer::hasContextTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(std::mem_fn(&Serializer::isContext), this, _1)); } bool Serializer::hasAkonadiTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(std::mem_fn(&Serializer::isAkonadiTag), this, _1)); } bool Serializer::isContext(const Akonadi::Tag &tag) const { return (tag.type() == Akonadi::SerializerInterface::contextTagType()); } bool Serializer::isAkonadiTag(const Tag &tag) const { return tag.type() == Akonadi::Tag::PLAIN || tag.type() == Akonadi::Tag::GENERIC; } bool Serializer::isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const { return (context->property("tagId").value() == tag.id()); } bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const { if (!context->property("tagId").isValid()) return false; auto tagId = context->property("tagId").value(); Akonadi::Tag tag(tagId); return item.hasTag(tag); } Domain::Tag::Ptr Serializer::createTagFromAkonadiTag(Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return Domain::Tag::Ptr(); auto tag = Domain::Tag::Ptr::create(); updateTagFromAkonadiTag(tag, akonadiTag); return tag; } void Serializer::updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return; tag->setProperty("tagId", akonadiTag.id()); tag->setProperty("uid", akonadiTag.gid()); tag->setName(akonadiTag.name()); } Akonadi::Tag Serializer::createAkonadiTagFromTag(Domain::Tag::Ptr tag) { auto akonadiTag = Akonadi::Tag::genericTag(tag->name()); const auto tagUidProperty = tag->property("uid"); if (tagUidProperty.isValid()) { akonadiTag.setGid(tagUidProperty.toByteArray()); } const auto tagProperty = tag->property("tagId"); if (tagProperty.isValid()) akonadiTag.setId(tagProperty.value()); return akonadiTag; } bool Serializer::isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) { if (!tag->property("tagId").isValid()) return false; auto tagId = tag->property("tagId").value(); Akonadi::Tag akonadiTag(tagId); return item.hasTag(akonadiTag); } Akonadi::Item Serializer::createItemFromArtifact(Domain::Artifact::Ptr artifact) { if (auto task = artifact.objectCast()) { return createItemFromTask(task); } if (auto note = artifact.objectCast()) { return createItemFromNote(note); } return Akonadi::Item(); } Domain::Relation::Ptr Serializer::createRelationFromAkonadiRelation(const QPair &akonadiRelation) { auto relation = Domain::Relation::Ptr::create(); relation->setUrl(akonadiRelation.first.url()); if (akonadiRelation.first.hasPayload()) { QString subject; auto header = akonadiRelation.first.payload()->subject(); if (header) { subject = header->asUnicodeString(); } else { subject = "Unknown mail"; } relation->setName(subject); } relation->setProperty("akonadiRelation", QVariant::fromValue(akonadiRelation.second)); return relation; } bool Serializer::representsAkonadiRelation(Domain::Relation::Ptr relation, const QPair &akonadiRelation) const { return relation->url() == akonadiRelation.first.url(); } Akonadi::Relation Serializer::createAkonadiRelationFromRelation(Domain::Relation::Ptr relation) { return relation->property("akonadiRelation").value(); } diff --git a/src/domain/task.cpp b/src/domain/task.cpp index 59988c40..899c74c1 100644 --- a/src/domain/task.cpp +++ b/src/domain/task.cpp @@ -1,485 +1,595 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "task.h" #include using namespace Domain; Task::Task(QObject *parent) : Artifact(parent), m_progress(0), m_status(None) { } Task::Task(const Task &other) : Artifact(other.parent()) , m_startDate(other.m_startDate) , m_dueDate(other.m_dueDate) , m_delegate(other.m_delegate) , m_recurrence(other.m_recurrence) , m_progress(other.m_progress) , m_status(other.m_status) + , m_alarms(other.m_alarms) { } Task::~Task() { } Task &Task::operator=(const Task &other) { Task copy(other); std::swap(m_startDate, copy.m_startDate); std::swap(m_dueDate, copy.m_dueDate); std::swap(m_delegate, copy.m_delegate); std::swap(m_recurrence, copy.m_recurrence); std::swap(m_progress, copy.m_progress); std::swap(m_status, copy.m_status); + std::swap(m_alarms, copy.m_alarms); setText(other.text()); setTitle(other.title()); return *this; } bool Task::isDone() const { if (!recurrence()) { return (status() == Complete); } else { return (status() == FullComplete); } } void Task::setDone(bool done) { if (done) { setStatus(Complete); } else { setStatus(None); } } QDateTime Task::startDate() const { return m_startDate; } void Task::setStartDate(const QDateTime &startDate) { if (m_startDate == startDate) return; m_startDate = startDate; emit startDateChanged(startDate); } QDateTime Task::dueDate() const { return m_dueDate; } Task::Delegate Task::delegate() const { return m_delegate; } void Task::setDueDate(const QDateTime &dueDate) { if (m_dueDate == dueDate) return; m_dueDate = dueDate; emit dueDateChanged(dueDate); } void Task::setDelegate(const Task::Delegate &delegate) { if (m_delegate == delegate) return; m_delegate = delegate; emit delegateChanged(delegate); } int Task::progress() const { return m_progress; } void Task::setProgress(int progress) { if (m_progress == progress) return; m_progress = progress; emit progressChanged(progress); } Task::Status Task::status() const { return m_status; } void Task::setStatus(int status) { if (m_status == static_cast(status)) return; m_status = static_cast(status); emit statusChanged(status); } Recurrence::Ptr Task::recurrence() const { return m_recurrence; } void Task::setRecurrence(const Domain::Recurrence::Ptr &recurrence) { if (m_recurrence == recurrence || (m_recurrence && recurrence && *m_recurrence == *recurrence)) { return; } m_recurrence = recurrence; emit recurrenceChanged(recurrence); } +Alarm::List Task::alarms() const +{ + return m_alarms; +} + +void Task::setAlarms(const Alarm::List &alarms) +{ + if (m_alarms == alarms) { + return; + } + + m_alarms = alarms; + emit alarmsChanged(alarms); +} Task::Delegate::Delegate() { } Task::Delegate::Delegate(const QString &name, const QString &email) : m_name(name), m_email(email) { } Task::Delegate::Delegate(const Task::Delegate &other) : m_name(other.m_name), m_email(other.m_email) { } Task::Delegate &Task::Delegate::operator=(const Task::Delegate &other) { Delegate copy(other); std::swap(m_name, copy.m_name); std::swap(m_email, copy.m_email); return *this; } bool Task::Delegate::operator==(const Task::Delegate &other) const { return m_name == other.m_name && m_email == other.m_email; } bool Task::Delegate::isValid() const { return !m_email.isEmpty(); } QString Task::Delegate::display() const { return !isValid() ? QString() : !m_name.isEmpty() ? m_name : m_email; } QString Task::Delegate::name() const { return m_name; } void Task::Delegate::setName(const QString &name) { m_name = name; } QString Task::Delegate::email() const { return m_email; } void Task::Delegate::setEmail(const QString &email) { m_email = email; } Recurrence::Recurrence(QObject *parent) : QObject(parent) , m_frequency(Recurrence::None) , m_weekStart(Recurrence::Monday) , m_allDay(true) , m_count(0) , m_interval(0) , m_weekPosition(All) { } Recurrence::Recurrence(const Recurrence &other) : QObject(other.parent()) , m_frequency(other.m_frequency) , m_weekStart(other.m_weekStart) , m_allDay(other.m_allDay) , m_end(other.m_end) , m_count(other.m_count) , m_interval(other.m_interval) , m_bysecond(other.m_bysecond) , m_byminute(other.m_byminute) , m_byhour(other.m_byhour) , m_byday(other.m_byday) , m_weekPosition(other.m_weekPosition) , m_bymonthday(other.m_bymonthday) , m_byyearday(other.m_byyearday) , m_byweekno(other.m_byweekno) , m_bymonth(other.m_bymonth) , m_recurrenceDates(other.m_recurrenceDates) , m_exceptionDates(other.m_exceptionDates) { } Recurrence::~Recurrence() { } Recurrence &Recurrence::operator=(const Recurrence &other) { Recurrence copy(other); std::swap(m_frequency, copy.m_frequency); std::swap(m_weekStart, copy.m_weekStart); std::swap(m_allDay, copy.m_allDay); std::swap(m_end, copy.m_end); std::swap(m_count, copy.m_count); std::swap(m_interval, copy.m_interval); std::swap(m_bysecond, copy.m_bysecond); std::swap(m_byminute, copy.m_byminute); std::swap(m_byhour, copy.m_byhour); std::swap(m_byday, copy.m_byday); std::swap(m_weekPosition, copy.m_weekPosition); std::swap(m_bymonthday, copy.m_bymonthday); std::swap(m_byyearday, copy.m_byyearday); std::swap(m_byweekno, copy.m_byweekno); std::swap(m_bymonth, copy.m_bymonth); std::swap(m_recurrenceDates, copy.m_recurrenceDates); std::swap(m_exceptionDates, copy.m_exceptionDates); return *this; } bool Recurrence::operator==(const Recurrence &other) const { return m_frequency == other.m_frequency && m_weekStart == other.m_weekStart && m_allDay == other.m_allDay && m_end == other.m_end && m_count == other.m_count && m_interval == other.m_interval && m_bysecond == other.m_bysecond && m_byminute == other.m_byminute && m_byhour == other.m_byhour && m_byday == other.m_byday && m_weekPosition == other.m_weekPosition && m_bymonthday == other.m_bymonthday && m_byyearday == other.m_byyearday && m_byweekno == other.m_byweekno && m_bymonth == other.m_bymonth && m_recurrenceDates == other.m_recurrenceDates && m_exceptionDates == other.m_exceptionDates; } void Recurrence::setFrequency(Recurrence::Frequency frequency) { m_frequency = frequency; } Recurrence::Frequency Recurrence::frequency() const { return m_frequency; } void Recurrence::setWeekStart(Recurrence::Weekday weekStart) { m_weekStart = weekStart; } Recurrence::Weekday Recurrence::weekStart() const { return m_weekStart; } void Recurrence::setAllDay(bool allDay) { m_allDay = allDay; } bool Recurrence::allDay() const { return m_allDay; } void Recurrence::setEnd(const QDateTime &end) { m_end = end; } QDateTime Recurrence::end() const { return m_end; } void Recurrence::setCount(int count) { m_count = count; } int Recurrence::count() const { return m_count; } void Recurrence::setInterval(int interval) { m_interval = interval; } int Recurrence::interval() const { return m_interval; } void Recurrence::setBysecond(const QList &bysecond) { m_bysecond = bysecond; } QList Recurrence::bysecond() const { return m_bysecond; } void Recurrence::setByminute(const QList &byminute) { m_byminute = byminute; } QList Recurrence::byminute() const { return m_byminute; } void Recurrence::setByhour(const QList &byhour) { m_byhour = byhour; } QList Recurrence::byhour() const { return m_byhour; } void Recurrence::setByday(const QList &byday) { m_byday = byday; } QList Recurrence::byday() const { return m_byday; } Recurrence::WeekPosition Recurrence::byDayPosition() { return m_weekPosition; } void Recurrence::setByDayPosition(Recurrence::WeekPosition weekPosition) { m_weekPosition = weekPosition; } void Recurrence::setBymonthday(const QList &bymonthday) { m_bymonthday = bymonthday; } QList Recurrence::bymonthday() const { return m_bymonthday; } void Recurrence::setByyearday(const QList &byyearday) { m_byyearday = byyearday; } QList Recurrence::byyearday() const { return m_byyearday; } void Recurrence::setByweekno(const QList &byweekno) { m_byweekno = byweekno; } QList Recurrence::byweekno() const { return m_byweekno; } void Recurrence::setBymonth(const QList &bymonth) { m_bymonth = bymonth; } QList Recurrence::bymonth() const { return m_bymonth; } void Recurrence::setRecurrenceDates(const QList &recurrenceDates) { m_recurrenceDates = recurrenceDates; } QList Recurrence::recurrenceDates() const { return m_recurrenceDates; } void Recurrence::setExceptionDates(const QList &exceptionDates) { m_exceptionDates = exceptionDates; } QList Recurrence::exceptionDates() const { return m_exceptionDates; } + +Alarm::Alarm(QObject *parent) + : QObject(parent) + , m_position(End) + , m_type(Display) +{ + +} + +Alarm::Alarm(const Alarm &other) + : QObject(other.parent()) + , m_position(other.m_position) + , m_type(other.m_type) + , m_datetime(other.m_datetime) + , m_offset(other.m_offset) +{ + +} + +Alarm::~Alarm() +{ + +} + +Alarm &Alarm::operator=(const Alarm &other) +{ + Alarm copy(other); + std::swap(m_position, copy.m_position); + std::swap(m_type, copy.m_type); + std::swap(m_datetime, copy.m_datetime); + std::swap(m_offset, copy.m_offset); + return *this; +} + +bool Alarm::operator==(const Alarm &other) const +{ + bool ret = m_type == other.m_type && + m_position == other.m_position; + if (!ret) { + return false; + } + + switch(m_position) { + case Exact: + return m_datetime == other.m_datetime; + break; + case Start: + case End: + return m_offset == other.m_offset; + break; + default: + return false; + } +} + +QDateTime Alarm::datetime() const +{ + return m_datetime; +} + +void Alarm::setDateTime(const QDateTime &datetime) +{ + m_datetime = datetime; +} + +int Alarm::offset() const +{ + return m_offset; +} + +void Alarm::setOffset(int offset) +{ + m_offset = offset; +} + +Alarm::Position Alarm::position() const +{ + return m_position; +} + +void Alarm::setPosition(Alarm::Position postition) +{ + m_position = postition; +} + +Alarm::Type Alarm::type() const +{ + return m_type; +} + +void Alarm::setType(Alarm::Type type) +{ + m_type = type; +} diff --git a/src/domain/task.h b/src/domain/task.h index f8fd7512..1c754121 100644 --- a/src/domain/task.h +++ b/src/domain/task.h @@ -1,241 +1,294 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DOMAIN_TASK_H #define DOMAIN_TASK_H #include "artifact.h" #include namespace Domain { +class Alarm : public QObject +{ + Q_OBJECT +public: + typedef QSharedPointer Ptr; + typedef QList List; + + enum Type { + None, /**< no alarm set */ + Display, /**< Display a dialog box */ + Audio /**< Play an audio file */ + }; + + enum Position { + Exact, /**< exact datetime */ + Start, /**< relative to start date */ + End /**< relative to end date */ + }; + + explicit Alarm(QObject *parent = 0); + explicit Alarm(const Domain::Alarm &other); + virtual ~Alarm(); + + Alarm &operator=(const Alarm &other); + bool operator==(const Alarm &other) const; + + void setPosition(Position postition); + Position position() const; + + void setType(Type type); + Type type() const; + + void setDateTime(const QDateTime &datetime); + QDateTime datetime() const; + + void setOffset(int offset); + int offset() const; + +private: + Position m_position; + Type m_type; + QDateTime m_datetime; /**< exacpt datetime to start alarm */ + int m_offset; /**< offset in seconds */ +}; + class Recurrence : public QObject { Q_OBJECT public: typedef QSharedPointer Ptr; typedef QList List; enum Frequency { None, Yearly, Monthly, Weekly, Daily, Hourly, Minutely, Secondly }; enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }; enum WeekPosition { All = 0, First = 1, Second = 2, Third = 3, Fourth = 4, Last = -1 }; explicit Recurrence(QObject *parent = 0); explicit Recurrence(const Domain::Recurrence &other); virtual ~Recurrence(); Recurrence &operator=(const Recurrence &other); bool operator==(const Recurrence &other) const; void setFrequency(Frequency); Frequency frequency() const; void setWeekStart(Weekday); Weekday weekStart() const; void setAllDay(bool); bool allDay() const; void setEnd(const QDateTime &); QDateTime end() const; void setCount(int count); int count() const; void setInterval(int interval); int interval() const; void setBysecond(const QList &); QList bysecond() const; void setByminute(const QList &); QList byminute() const; void setByhour(const QList &); QList byhour() const; void setByday(const QList &); QList byday() const; //What Weeks in moth are matching void setByDayPosition(WeekPosition); WeekPosition byDayPosition(); void setBymonthday(const QList &); QList bymonthday() const; void setByyearday(const QList &); QList byyearday() const; void setByweekno(const QList &); QList byweekno() const; void setBymonth(const QList &); QList bymonth() const; void setRecurrenceDates(const QList &rdates); QList recurrenceDates() const; void setExceptionDates(const QList &exceptions); QList exceptionDates() const; + private: Frequency m_frequency; Weekday m_weekStart; bool m_allDay; QDateTime m_end; int m_count; int m_interval; QList m_bysecond; QList m_byminute; QList m_byhour; QList m_byday; WeekPosition m_weekPosition; QList m_bymonthday; QList m_byyearday; QList m_byweekno; QList m_bymonth; QList m_recurrenceDates; QList m_exceptionDates; }; class Task : public Artifact { Q_OBJECT Q_PROPERTY(QDateTime startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) Q_PROPERTY(QDateTime dueDate READ dueDate WRITE setDueDate NOTIFY dueDateChanged) Q_PROPERTY(Domain::Task::Delegate delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) Q_PROPERTY(Domain::Recurrence::Ptr recurrence READ recurrence WRITE setRecurrence NOTIFY recurrenceChanged) + Q_PROPERTY(Domain::Alarm::List alarms READ alarms WRITE setAlarms NOTIFY alarmsChanged) public: typedef QSharedPointer Ptr; typedef QList List; enum Status { None, NeedsAction, InProcess, Complete, FullComplete, Cancelled }; class Delegate { public: Delegate(); Delegate(const QString &name, const QString &email); Delegate(const Delegate &other); Delegate &operator=(const Delegate &other); bool operator==(const Delegate &other) const; bool isValid() const; QString display() const; QString name() const; void setName(const QString &name); QString email() const; void setEmail(const QString &email); private: QString m_name; QString m_email; }; explicit Task(QObject *parent = 0); Task(const Task &other); virtual ~Task(); Task &operator=(const Task &other); bool isDone() const; QDateTime startDate() const; QDateTime dueDate() const; Delegate delegate() const; int progress() const; Status status() const; Recurrence::Ptr recurrence() const; + Alarm::List alarms() const; public slots: void setDone(bool done); void setStartDate(const QDateTime &startDate); void setDueDate(const QDateTime &dueDate); void setDelegate(const Domain::Task::Delegate &delegate); void setProgress(int progress); void setStatus(int status); void setRecurrence(const Domain::Recurrence::Ptr &recurrence); - + void setAlarms(const Alarm::List &alarms); signals: void startDateChanged(const QDateTime &startDate); void dueDateChanged(const QDateTime &dueDate); void delegateChanged(const Domain::Task::Delegate &delegate); void progressChanged(int progress); void statusChanged(int status); void recurrenceChanged(const Domain::Recurrence::Ptr &recurrence); + void alarmsChanged(const Domain::Alarm::List &alarms); private: QDateTime m_startDate; QDateTime m_dueDate; Delegate m_delegate; Recurrence::Ptr m_recurrence; int m_progress; Status m_status; + Alarm::List m_alarms; }; } Q_DECLARE_METATYPE(Domain::Task::Ptr) Q_DECLARE_METATYPE(Domain::Task::List) Q_DECLARE_METATYPE(Domain::Task::Delegate) Q_DECLARE_METATYPE(Domain::Recurrence::Ptr) Q_DECLARE_METATYPE(Domain::Recurrence::List) + +Q_DECLARE_METATYPE(Domain::Alarm::Ptr) +Q_DECLARE_METATYPE(Domain::Alarm::List) #endif // DOMAIN_TASK_H diff --git a/src/presentation/artifacteditormodel.cpp b/src/presentation/artifacteditormodel.cpp index 3d986f51..0d660fb5 100644 --- a/src/presentation/artifacteditormodel.cpp +++ b/src/presentation/artifacteditormodel.cpp @@ -1,593 +1,686 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "artifacteditormodel.h" #include #include #include "domain/task.h" #include "domain/taskrepository.h" #include "domain/note.h" #include "domain/noterepository.h" #include "domain/relation.h" #include "domain/relationqueries.h" #include "domain/relationrepository.h" using namespace Presentation; ArtifactEditorModel::ArtifactEditorModel(Domain::TaskRepository *taskRepository, Domain::NoteRepository *noteRepository, Domain::RelationQueries *relationQueries, Domain::RelationRepository *relationRepository, QObject *parent) : QObject(parent), m_taskRepository(taskRepository), m_noteRepository(noteRepository), m_relationQueries(relationQueries), m_relationRepository(relationRepository), m_saveTimer(new QTimer(this)), m_saveNeeded(false) { m_saveTimer->setSingleShot(true); m_saveTimer->setInterval(autoSaveDelay()); connect(m_saveTimer, SIGNAL(timeout()), this, SLOT(save())); } ArtifactEditorModel::~ArtifactEditorModel() { save(); } Domain::Artifact::Ptr ArtifactEditorModel::artifact() const { return m_artifact; } void ArtifactEditorModel::setArtifact(const Domain::Artifact::Ptr &artifact) { if (m_artifact == artifact) return; save(); m_text = QString(); m_title = QString(); m_start = QDateTime(); m_due = QDateTime(); m_delegate = Domain::Task::Delegate(); m_progress = 0; m_status = Domain::Task::None; m_relations.clear(); m_attachments.clear(); m_recurrence = Domain::Recurrence::Ptr(0); m_artifact = artifact; if (m_artifact) { disconnect(m_artifact.data(), 0, this, 0); m_text = artifact->text(); m_title = artifact->title(); connect(m_artifact.data(), SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString))); connect(m_artifact.data(), SIGNAL(titleChanged(QString)), this, SLOT(onTitleChanged(QString))); } if (auto task = artifact.objectCast()) { m_start = task->startDate(); m_due = task->dueDate(); m_delegate = task->delegate(); m_progress = task->progress(); m_status = task->status(); m_recurrence = task->recurrence(); m_attachments = task->attachments(); + if (!task->alarms().isEmpty()) { + m_alarm = task->alarms().at(0); + } else { + m_alarm = Domain::Alarm::Ptr(0); + } connect(m_artifact.data(), SIGNAL(startDateChanged(QDateTime)), this, SLOT(onStartDateChanged(QDateTime))); connect(m_artifact.data(), SIGNAL(dueDateChanged(QDateTime)), this, SLOT(onDueDateChanged(QDateTime))); connect(m_artifact.data(), SIGNAL(delegateChanged(Domain::Task::Delegate)), this, SLOT(onDelegateChanged(Domain::Task::Delegate))); connect(m_artifact.data(), SIGNAL(progressChanged(int)), this, SLOT(onProgressChanged(int))); connect(m_artifact.data(), SIGNAL(statusChanged(int)), this, SLOT(onStatusChanged(int))); connect(m_artifact.data(), SIGNAL(recurrenceChanged(Domain::Recurrence::Ptr)), this, SLOT(onRecurrenceChanged(Domain::Recurrence::Ptr))); connect(m_artifact.data(), SIGNAL(attachmentsChanged(Domain::Artifact::Attachment::List)), this, SLOT(onAttachmentsChanged(Domain::Artifact::Attachment::List))); + connect(m_artifact.data(), SIGNAL(alarmsChanged(Domain::Alarm::List)), + this, SLOT(onAlarmsChanged(Domain::Alarm::List))); } auto relationQuery = m_relationQueries->findRelations(m_artifact); relationQuery->addPostInsertHandler([this, relationQuery](const Domain::Relation::Ptr &rel, int) { //hack to keep query alive Q_ASSERT(relationQuery); addRelation(rel); }); emit textChanged(m_text); emit titleChanged(m_title); emit startDateChanged(m_start); emit dueDateChanged(m_due); emit delegateTextChanged(m_delegate.display()); emit hasTaskPropertiesChanged(hasTaskProperties()); emit artifactChanged(m_artifact); emit progressChanged(m_progress); emit statusChanged(m_status); emit relationsChanged(m_relations); emit recurrenceChanged(m_recurrence); emit attachmentsChanged(m_attachments); + emit alarmChanged(m_alarm); } void ArtifactEditorModel::addRelation(const Domain::Relation::Ptr &relation) { m_relations << relation; emit relationsChanged(m_relations); } void ArtifactEditorModel::removeRelation(const Domain::Relation::Ptr &relation) { m_relations.removeAll(relation); emit relationsChanged(m_relations); m_relationRepository->remove(relation); } void ArtifactEditorModel::removeAttachment(const Domain::Artifact::Attachment::Ptr &attachment) { m_attachments.removeAll(attachment); emit attachmentsChanged(m_attachments); setSaveNeeded(true); } void ArtifactEditorModel::addAttachment(const Domain::Artifact::Attachment::Ptr &attachment) { m_attachments << attachment; emit attachmentsChanged(m_attachments); setSaveNeeded(true); } Domain::Artifact::Attachment::List ArtifactEditorModel::attachments() const { return m_attachments; } bool ArtifactEditorModel::hasTaskProperties() const { return m_artifact.objectCast(); } QString ArtifactEditorModel::text() const { return m_text; } QString ArtifactEditorModel::title() const { return m_title; } QDateTime ArtifactEditorModel::startDate() const { return m_start; } QDateTime ArtifactEditorModel::dueDate() const { return m_due; } QString ArtifactEditorModel::delegateText() const { return m_delegate.display(); } int ArtifactEditorModel::progress() const { return m_progress; } int ArtifactEditorModel::status() const { return m_status; } Domain::Recurrence::Ptr ArtifactEditorModel::recurrence() const { return m_recurrence; } int ArtifactEditorModel::autoSaveDelay() { return 500; } void ArtifactEditorModel::setText(const QString &text) { if (m_text == text) return; onTextChanged(text); setSaveNeeded(true); } void ArtifactEditorModel::setTitle(const QString &title) { if (m_title == title) return; onTitleChanged(title); setSaveNeeded(true); } void ArtifactEditorModel::setStartDate(const QDateTime &start) { if (m_start == start) return; onStartDateChanged(start); setSaveNeeded(true); } void ArtifactEditorModel::setDueDate(const QDateTime &due) { if (m_due == due) return; onDueDateChanged(due); setSaveNeeded(true); } void ArtifactEditorModel::setDelegate(const QString &name, const QString &email) { if ((m_delegate.name() == name) && (m_delegate.email() == email)) return; onDelegateChanged(Domain::Task::Delegate(name, email)); setSaveNeeded(true); } void ArtifactEditorModel::delegate(const QString &name, const QString &email) { auto task = m_artifact.objectCast(); Q_ASSERT(task); auto delegate = Domain::Task::Delegate(name, email); m_taskRepository->delegate(task, delegate); } void ArtifactEditorModel::setProgress(int progress) { if (m_progress == progress) return; onProgressChanged(progress); setSaveNeeded(true); } void ArtifactEditorModel::setStatus(int status) { if (m_status == status) return; onStatusChanged(status); setSaveNeeded(true); } void ArtifactEditorModel::setRecurrence(const Domain::Recurrence::Ptr &recurrence) { if (m_recurrence == recurrence || (m_recurrence && recurrence && *m_recurrence == *recurrence)) { return; } onRecurrenceChanged(recurrence); setSaveNeeded(true); } void ArtifactEditorModel::setExceptionDates(const QList &exceptionDates) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->exceptionDates() != exceptionDates) { recurrence->setExceptionDates(exceptionDates); setRecurrence(recurrence); } } void ArtifactEditorModel::setFrequency(Domain::Recurrence::Frequency frequency, int intervall) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->frequency() != frequency || recurrence->interval() != intervall) { if (frequency == Domain::Recurrence::None) { setRecurrence(Domain::Recurrence::Ptr(0)); } else { recurrence->setFrequency(frequency); recurrence->setInterval(intervall); switch(frequency) { case Domain::Recurrence::Yearly: recurrence->setBysecond(QList()); recurrence->setByminute(QList()); recurrence->setByhour(QList()); recurrence->setByday(QList()); recurrence->setBymonthday(QList()); recurrence->setByyearday(QList()); recurrence->setByweekno(QList()); break; case Domain::Recurrence::Monthly: recurrence->setBysecond(QList()); recurrence->setByminute(QList()); recurrence->setByhour(QList()); recurrence->setByday(QList()); recurrence->setBymonthday(QList()); recurrence->setByyearday(QList()); recurrence->setBymonth(QList()); break; case Domain::Recurrence::Weekly: recurrence->setBysecond(QList()); recurrence->setByminute(QList()); recurrence->setByhour(QList()); recurrence->setBymonthday(QList()); recurrence->setByyearday(QList()); recurrence->setByweekno(QList()); recurrence->setBymonth(QList()); break; case Domain::Recurrence::Daily: recurrence->setBysecond(QList()); recurrence->setByminute(QList()); recurrence->setByhour(QList()); recurrence->setByday(QList()); recurrence->setBymonthday(QList()); recurrence->setByyearday(QList()); recurrence->setByweekno(QList()); recurrence->setBymonth(QList()); break; } setRecurrence(recurrence); } } } void ArtifactEditorModel::setNoRepeat() { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->count() != 0 || recurrence->end().isValid()) { recurrence->setCount(0); recurrence->setEnd(QDateTime()); setRecurrence(recurrence); } } void ArtifactEditorModel::setRepeatEnd(QDateTime endDate) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->end().date() != endDate.date() || recurrence->count() != 0 ) { recurrence->setEnd(endDate); recurrence->setCount(0); setRecurrence(recurrence); } } void ArtifactEditorModel::setRepeatEnd(int count) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->count() != count || recurrence->end().isValid()) { recurrence->setCount(count); recurrence->setEnd(QDateTime()); setRecurrence(recurrence); } } void ArtifactEditorModel::setRepeatEndless() { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->count() != -1 || recurrence->end().isValid()) { recurrence->setCount(-1); recurrence->setEnd(QDateTime()); setRecurrence(recurrence); } } void ArtifactEditorModel::setByDay(const QList &dayList) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->byday() != dayList || (recurrence->frequency() == Domain::Recurrence::Monthly && !recurrence->bymonthday().isEmpty())) { recurrence->setByday(dayList); if (!recurrence->byday().isEmpty() && recurrence->frequency() == Domain::Recurrence::Monthly) { recurrence->setBymonthday(QList()); } setRecurrence(recurrence); } } void ArtifactEditorModel::setByMonth(const QList &monthList) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->bymonth() != monthList) { recurrence->setBymonth(monthList); setRecurrence(recurrence); } } void ArtifactEditorModel::setByMonthDays(const QList &dayList) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->bymonthday() != dayList || (recurrence->frequency() == Domain::Recurrence::Monthly && !recurrence->byday().isEmpty())) { recurrence->setBymonthday(dayList); if (!recurrence->bymonthday().isEmpty() && recurrence->frequency() == Domain::Recurrence::Monthly) { recurrence->setByday(QList()); } setRecurrence(recurrence); } } void ArtifactEditorModel::setByDayPosition(Domain::Recurrence::WeekPosition position) { Domain::Recurrence::Ptr recurrence(new Domain::Recurrence); if (m_recurrence) { *recurrence = *m_recurrence; } if (!m_recurrence || recurrence->byDayPosition() != position) { if (!recurrence->byday().isEmpty()) { recurrence->setByDayPosition(position); setRecurrence(recurrence); } else { // Till there are no days to save, we keep this only in memory m_recurrence->setByDayPosition(position); } } } QList ArtifactEditorModel::relations() const { return m_relations; } +Domain::Alarm::Ptr ArtifactEditorModel::alarm() const +{ + return m_alarm; +} + +void ArtifactEditorModel::setAlarm(const Domain::Alarm::Ptr &alarm) +{ + if (alarm == m_alarm) { + return; + } + + onAlarmChanged(alarm); + setSaveNeeded(true); +} + +void ArtifactEditorModel::setAlarmOffset(int offset) +{ + if (m_alarm->offset() == offset) { + return; + } + + m_alarm->setOffset(offset); + onAlarmChanged(m_alarm); + setSaveNeeded(true); +} + +void ArtifactEditorModel::setAlarmPosition(int p) +{ + auto position = static_cast(p); + if (m_alarm->position() == position) { + return; + } + + m_alarm->setPosition(position); + onAlarmChanged(m_alarm); + setSaveNeeded(true); +} + +void ArtifactEditorModel::setAlarmType(int t) +{ + auto type = static_cast(t); + if ((m_alarm && m_alarm->type() == type) + || (!m_alarm && type == Domain::Alarm::None)) { + return; + } + + if (!m_alarm && type != Domain::Alarm::None) { + m_alarm = Domain::Alarm::Ptr(new Domain::Alarm()); + } else if (m_alarm && type == Domain::Alarm::None) { + m_alarm = Domain::Alarm::Ptr(0); + } + if (m_alarm) { + m_alarm->setType(type); + } + onAlarmChanged(m_alarm); + setSaveNeeded(true); +} + void ArtifactEditorModel::onTextChanged(const QString &text) { m_text = text; emit textChanged(m_text); } void ArtifactEditorModel::onTitleChanged(const QString &title) { m_title = title; emit titleChanged(m_title); } void ArtifactEditorModel::onStartDateChanged(const QDateTime &start) { m_start = start; emit startDateChanged(m_start); } void ArtifactEditorModel::onDueDateChanged(const QDateTime &due) { m_due = due; emit dueDateChanged(m_due); } void ArtifactEditorModel::onDelegateChanged(const Domain::Task::Delegate &delegate) { m_delegate = delegate; emit delegateTextChanged(m_delegate.display()); } void ArtifactEditorModel::onProgressChanged(int progress) { m_progress = progress; emit progressChanged(progress); } void ArtifactEditorModel::onStatusChanged(int status) { m_status = static_cast(status); emit statusChanged(status); } void ArtifactEditorModel::onRecurrenceChanged(const Domain::Recurrence::Ptr &recurrence) { m_recurrence = recurrence; emit recurrenceChanged(recurrence); } void ArtifactEditorModel::onAttachmentsChanged(const Domain::Artifact::Attachment::List &attachments) { m_attachments = attachments; emit attachmentsChanged(attachments); } +void ArtifactEditorModel::onAlarmChanged(const Domain::Alarm::Ptr &alarm) +{ + m_alarm = alarm; + emit alarmChanged(alarm); +} + +void ArtifactEditorModel::onAlarmsChanged(const Domain::Alarm::List &alarms) +{ + if (!alarms.isEmpty()) { + m_alarm = alarms.first(); + } else { + m_alarm = Domain::Alarm::Ptr(0); + } + emit alarmChanged(m_alarm); +} + void ArtifactEditorModel::save() { if (!isSaveNeeded()) return; Q_ASSERT(m_artifact); m_artifact->setTitle(m_title); m_artifact->setText(m_text); m_artifact->setAttachments(m_attachments); if (auto task = m_artifact.objectCast()) { task->setStartDate(m_start); task->setDueDate(m_due); task->setDelegate(m_delegate); task->setProgress(m_progress); task->setStatus(m_status); task->setRecurrence(m_recurrence); + Domain::Alarm::List alarms = task->alarms(); + if (m_alarm) { + if (alarms.isEmpty()) { + alarms.append(m_alarm); + } else { + alarms[0] = m_alarm; + } + } else { + alarms = Domain::Alarm::List(); + } + task->setAlarms(alarms); m_taskRepository->update(task); } else { auto note = m_artifact.objectCast(); Q_ASSERT(note); m_noteRepository->save(note); } setSaveNeeded(false); } void ArtifactEditorModel::setSaveNeeded(bool needed) { if (needed) m_saveTimer->start(); else m_saveTimer->stop(); m_saveNeeded = needed; } bool ArtifactEditorModel::isSaveNeeded() const { return m_saveNeeded; } diff --git a/src/presentation/artifacteditormodel.h b/src/presentation/artifacteditormodel.h index 1c0a4a01..fc06967b 100644 --- a/src/presentation/artifacteditormodel.h +++ b/src/presentation/artifacteditormodel.h @@ -1,168 +1,179 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PRESENTATION_ARTIFACTEDITORMODEL_H #define PRESENTATION_ARTIFACTEDITORMODEL_H #include #include #include "domain/task.h" #include "domain/relation.h" class QTimer; namespace Domain { class NoteRepository; class TaskRepository; class RelationQueries; class RelationRepository; } namespace Presentation { class ArtifactEditorModel : public QObject { Q_OBJECT Q_PROPERTY(Domain::Artifact::Ptr artifact READ artifact WRITE setArtifact NOTIFY artifactChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(QDateTime startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) Q_PROPERTY(QDateTime dueDate READ dueDate WRITE setDueDate NOTIFY dueDateChanged) Q_PROPERTY(QString delegateText READ delegateText NOTIFY delegateTextChanged) Q_PROPERTY(bool hasTaskProperties READ hasTaskProperties NOTIFY hasTaskPropertiesChanged) Q_PROPERTY(int progress READ progress WRITE setProgress NOTIFY progressChanged) Q_PROPERTY(int status READ status WRITE setStatus NOTIFY statusChanged) Q_PROPERTY(QList relations READ relations NOTIFY relationsChanged) Q_PROPERTY(Domain::Artifact::Attachment::List attachments READ attachments NOTIFY attachmentsChanged) Q_PROPERTY(Domain::Recurrence::Ptr recurrence READ recurrence WRITE setRecurrence NOTIFY recurrenceChanged) + Q_PROPERTY(Domain::Alarm::Ptr alarm READ alarm READ alarm WRITE setAlarm NOTIFY alarmChanged) public: explicit ArtifactEditorModel(Domain::TaskRepository *taskRepository, Domain::NoteRepository *noteRepository, Domain::RelationQueries *relationQueries = 0, Domain::RelationRepository *relationRepository = 0, QObject *parent = 0); ~ArtifactEditorModel(); Domain::Artifact::Ptr artifact() const; void setArtifact(const Domain::Artifact::Ptr &artifact); bool hasTaskProperties() const; QString text() const; QString title() const; QDateTime startDate() const; QDateTime dueDate() const; QString delegateText() const; int progress() const; int status() const; QList relations() const; Domain::Artifact::Attachment::List attachments() const; Domain::Recurrence::Ptr recurrence() const; + Domain::Alarm::Ptr alarm() const; static int autoSaveDelay(); public slots: void setText(const QString &text); void setTitle(const QString &title); void setStartDate(const QDateTime &start); void setDueDate(const QDateTime &due); void setDelegate(const QString &name, const QString &email); void delegate(const QString &name, const QString &email); void setProgress(int progress); void setStatus(int status); void removeRelation(const Domain::Relation::Ptr &); void setRecurrence(const Domain::Recurrence::Ptr&); void removeAttachment(const Domain::Artifact::Attachment::Ptr &); void addAttachment(const Domain::Artifact::Attachment::Ptr &); void setFrequency(Domain::Recurrence::Frequency frequency, int intervall); void setRepeatEnd(QDateTime endDate); void setRepeatEnd(int count); void setRepeatEndless(); void setNoRepeat(); void setExceptionDates(const QList &exceptionDates); void setByDay(const QList< Domain::Recurrence::Weekday > &dayList); void setByDayPosition(Domain::Recurrence::WeekPosition position); void setByMonth(const QList< int > &monthList); void setByMonthDays(const QList< int > &dayList); + void setAlarm(const Domain::Alarm::Ptr &alarm); + void setAlarmType(int type); + void setAlarmPosition(int position); + void setAlarmOffset(int offset); + signals: void artifactChanged(const Domain::Artifact::Ptr &artifact); void hasTaskPropertiesChanged(bool hasTaskProperties); void textChanged(const QString &text); void titleChanged(const QString &title); void startDateChanged(const QDateTime &date); void dueDateChanged(const QDateTime &due); void delegateTextChanged(const QString &delegateText); void progressChanged(int progress); void statusChanged(int status); void relationsChanged(const QList &relations); void attachmentsChanged(const Domain::Artifact::Attachment::List &attachments); void recurrenceChanged(const Domain::Recurrence::Ptr &recurrence); + void alarmChanged(const Domain::Alarm::Ptr &alarm); private slots: void onTextChanged(const QString &text); void onTitleChanged(const QString &title); void onStartDateChanged(const QDateTime &start); void onDueDateChanged(const QDateTime &due); void onDelegateChanged(const Domain::Task::Delegate &delegate); void onProgressChanged(int status); void onStatusChanged(int status); void onRecurrenceChanged(const Domain::Recurrence::Ptr &recurrence); void onAttachmentsChanged(const Domain::Artifact::Attachment::List &attachments); + void onAlarmChanged(const Domain::Alarm::Ptr &alarm); + void onAlarmsChanged(const Domain::Alarm::List &alarms); void save(); private: void setSaveNeeded(bool needed); bool isSaveNeeded() const; void addRelation(const Domain::Relation::Ptr &); Domain::TaskRepository *m_taskRepository; Domain::NoteRepository *m_noteRepository; Domain::RelationQueries *m_relationQueries; Domain::RelationRepository *m_relationRepository; Domain::Artifact::Ptr m_artifact; QString m_text; QString m_title; QDateTime m_start; QDateTime m_due; Domain::Task::Delegate m_delegate; int m_progress; Domain::Task::Status m_status; QList m_relations; Domain::Recurrence::Ptr m_recurrence; Domain::Artifact::Attachment::List m_attachments; + Domain::Alarm::Ptr m_alarm; QTimer *m_saveTimer; bool m_saveNeeded; }; } #endif // PRESENTATION_ARTIFACTEDITORMODEL_H diff --git a/src/widgets/editorview.cpp b/src/widgets/editorview.cpp index d273e2b2..719bc67f 100644 --- a/src/widgets/editorview.cpp +++ b/src/widgets/editorview.cpp @@ -1,611 +1,731 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "editorview.h" #include "recurrencewidget.h" #include "editorwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdateedit.h" #include #include "addressline/addresseelineedit.h" #include "presentation/metatypes.h" #include #include #include #include #include #include "domain/artifact.h" #include "domain/task.h" #include "domain/relation.h" using namespace Widgets; +enum AlarmType { + AlarmNone, + AlarmDisplay, + AlarmAudio +}; + +enum AlarmPosition { + AlarmStart, + AlarmEnd +}; + EditorView::EditorView(QWidget *parent) : QWidget(parent), m_model(0), m_delegateLabel(new QLabel(this)), m_titleEdit(new QLineEdit(this)), m_textEdit(new EditorWidget(this)), m_taskGroup(new QWidget(this)), m_startDateEdit(new KPIM::KDateEdit(m_taskGroup)), m_startTimeEdit(new KTimeComboBox(m_taskGroup)), m_dueDateEdit(new KPIM::KDateEdit(m_taskGroup)), m_dueTimeEdit(new KTimeComboBox(m_taskGroup)), m_startTodayButton(new QPushButton(tr("Start today"), m_taskGroup)), m_delegateEdit(0), m_statusComboBox(new QComboBox(m_taskGroup)), m_progressEdit(new QSpinBox(m_taskGroup)), m_relationsLayout(new QVBoxLayout), m_attachmentsLayout(new QVBoxLayout), - m_addAttachmentButton(new QPushButton(tr("Add attachment"), m_taskGroup)) + m_addAttachmentButton(new QPushButton(tr("Add attachment"), m_taskGroup)), + m_alarmTypeWidget(new QComboBox(m_taskGroup)), + m_alarmPositionWidget(new QComboBox(m_taskGroup)), + m_alarmOffsetEdit(new QSpinBox(m_taskGroup)) { // To avoid having unit tests talking to akonadi // while we don't need the completion for them if (qgetenv("ZANSHIN_UNIT_TEST_RUN").isEmpty()) m_delegateEdit = new KPIM::AddresseeLineEdit(this); else m_delegateEdit = new KLineEdit(this); m_delegateLabel->setObjectName("delegateLabel"); m_delegateEdit->setObjectName("delegateEdit"); m_textEdit->setObjectName("textEdit"); m_titleEdit->setObjectName("titleEdit"); m_startDateEdit->setObjectName("startDateEdit"); m_startTimeEdit->setObjectName("startTimeEdit"); m_dueDateEdit->setObjectName("dueDateEdit"); m_dueTimeEdit->setObjectName("dueTimeEdit"); m_startTodayButton->setObjectName("startTodayButton"); m_statusComboBox->setObjectName("statusComboBox"); m_progressEdit->setObjectName("progressEdit"); + m_alarmTypeWidget->setObjectName("alarmTypeWidget"); + m_alarmPositionWidget->setObjectName("alarmPositionWidget"); + m_alarmOffsetEdit->setObjectName("alarmOffsetEdit"); m_startDateEdit->setMinimumContentsLength(10); m_dueDateEdit->setMinimumContentsLength(10); m_progressEdit->setRange(0, 100); + m_alarmOffsetEdit->setRange(0,10000); + m_alarmOffsetEdit->setSuffix(" seconds"); + m_statusComboBox->addItem(tr("None"), Domain::Task::None); m_statusComboBox->addItem(tr("Needs action"), Domain::Task::NeedsAction); m_statusComboBox->addItem(tr("In process"), Domain::Task::InProcess); m_statusComboBox->addItem(tr("Completed"), Domain::Task::Complete); m_statusComboBox->addItem(tr("Cancelled"), Domain::Task::Cancelled); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(m_delegateLabel); layout->addWidget(m_titleEdit); layout->addWidget(m_textEdit); layout->addLayout(m_relationsLayout); layout->addWidget(m_taskGroup); setLayout(layout); connect(m_addAttachmentButton, SIGNAL(clicked()), this, SLOT(onAddAttachmentClicked())); QVBoxLayout *vbox = new QVBoxLayout; vbox->addLayout(m_attachmentsLayout); QHBoxLayout *attachmentsHBox = new QHBoxLayout; attachmentsHBox->addWidget(m_addAttachmentButton); attachmentsHBox->addStretch(); vbox->addLayout(attachmentsHBox); auto delegateHBox = new QHBoxLayout; delegateHBox->addWidget(new QLabel(tr("Delegate to"), m_taskGroup)); delegateHBox->addWidget(m_delegateEdit); vbox->addLayout(delegateHBox); QHBoxLayout *datesHBox = new QHBoxLayout; datesHBox->addWidget(new QLabel(tr("Start date"), m_taskGroup)); datesHBox->addWidget(m_startDateEdit, 1); datesHBox->addWidget(m_startTimeEdit, 1); datesHBox->addWidget(new QLabel(tr("Due date"), m_taskGroup)); datesHBox->addWidget(m_dueDateEdit, 1); datesHBox->addWidget(m_dueTimeEdit, 1); vbox->addLayout(datesHBox); QHBoxLayout *bottomHBox = new QHBoxLayout; bottomHBox->addWidget(m_startTodayButton); bottomHBox->addStretch(); vbox->addLayout(bottomHBox); + auto alarmHBox= new QHBoxLayout; + alarmHBox->addWidget(new QLabel(tr("Alarm"), m_taskGroup)); + alarmHBox->addWidget(m_alarmTypeWidget, 1); + alarmHBox->addWidget(m_alarmOffsetEdit, 1); + alarmHBox->addWidget(m_alarmPositionWidget, 1); + vbox->addLayout(alarmHBox); auto progressHBox = new QHBoxLayout; progressHBox->addWidget(new QLabel(tr("Progress"), m_taskGroup)); progressHBox->addWidget(m_progressEdit, 1); vbox->addLayout(progressHBox); auto statusHBox = new QHBoxLayout; statusHBox->addWidget(new QLabel(tr("Status"), m_taskGroup)); statusHBox->addWidget(m_statusComboBox, 1); vbox->addLayout(statusHBox); m_recurrenceWidget = new RecurrenceWidget; vbox->addWidget(m_recurrenceWidget); m_taskGroup->setLayout(vbox); // Make sure our minimum width is always the one with // the task group visible layout->activate(); setMinimumWidth(minimumSizeHint().width()); m_delegateLabel->setVisible(false); m_taskGroup->setVisible(false); auto editMenu = new QMenu; for(auto action : m_textEdit->editActions()) { editMenu->addAction(action); } auto menuAction = new QAction(0); menuAction->setText(tr("Edit")); menuAction->setMenu(editMenu); addAction(menuAction); for(auto action : m_textEdit->actions()) { addAction(action); } connect(m_textEdit, SIGNAL(fullscreenToggled(bool)), SLOT(toggleFullscreenEditor())); auto action = new QAction(this); action->setText(tr("Fullscreen &Editor")); action->setIcon(KIcon("go-up")); action->setShortcut(QKeySequence(Qt::Key_F5)); connect(action, SIGNAL(triggered()), SLOT(toggleFullscreenEditor())); addAction(action); connect(m_textEdit->editor(), SIGNAL(textChanged()), this, SLOT(onTextEditChanged())); connect(m_titleEdit, SIGNAL(editingFinished()), this, SLOT(onTextEditChanged())); connect(m_startDateEdit, SIGNAL(dateEntered(QDate)), this, SLOT(onStartEditEntered(QDate))); connect(m_startTimeEdit, SIGNAL(timeEntered(QTime)), this, SLOT(onStartTimeEntered(QTime))); connect(m_dueDateEdit, SIGNAL(dateEntered(QDate)), this, SLOT(onDueEditEntered(QDate))); connect(m_dueTimeEdit, SIGNAL(timeEntered(QTime)), this, SLOT(onDueTimeEntered(QTime))); connect(m_startTodayButton, SIGNAL(clicked()), this, SLOT(onStartTodayClicked())); connect(m_delegateEdit, SIGNAL(returnPressed()), this, SLOT(onDelegateEntered())); connect(m_progressEdit, SIGNAL(valueChanged(int)), this, SLOT(onProgressChanged(int))); connect(m_statusComboBox, SIGNAL(activated(int)), this, SLOT(onStatusChanged(int))); + connect(m_alarmTypeWidget, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlarmEnableChanged(int))); + connect(m_alarmTypeWidget, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlarmTypeChanged(int))); + connect(m_alarmPositionWidget, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlarmPositionChanged(int))); + connect(m_alarmOffsetEdit, SIGNAL(valueChanged(int)), this, SLOT(onAlarmOffsetChanged(int))); setEnabled(false); + + fillAlarmCombo(); +} + +void EditorView::fillAlarmCombo() +{ + m_alarmTypeWidget->clear(); + m_alarmPositionWidget->clear(); + + for (int i=AlarmNone; i <= AlarmAudio; ++i) { + switch(i) { + case AlarmNone: + m_alarmTypeWidget->addItem(tr("None"), Domain::Alarm::None); + break; + case AlarmDisplay: + m_alarmTypeWidget->addItem(tr("Message"), Domain::Alarm::Display); + break; + case AlarmAudio: + m_alarmTypeWidget->addItem(tr("Audio"), Domain::Alarm::Audio); + } + } + + for(int i=AlarmStart; i <= AlarmEnd; ++i) { + switch(i) { + case AlarmStart: + m_alarmPositionWidget->addItem(tr("before start"), Domain::Alarm::Start); + break; + case AlarmEnd: + m_alarmPositionWidget->addItem(tr("before end"), Domain::Alarm::End); + } + } + onAlarmTypeChanged(AlarmNone); } void EditorView::toggleFullscreenEditor() { if (m_textEdit->windowState() & Qt::WindowFullScreen) { m_textEdit->setParent(this); static_cast(layout())->insertWidget(2, m_textEdit); } else { m_textEdit->setParent(0); } m_textEdit->setWindowState(m_textEdit->windowState() ^ Qt::WindowFullScreen); m_textEdit->show(); } QObject *EditorView::model() const { return m_model; } void EditorView::setModel(QObject *model) { if (model == m_model) return; if (m_model) { disconnect(m_model, 0, this, 0); disconnect(this, 0, m_model, 0); } m_model = model; onArtifactChanged(); onTextOrTitleChanged(); onHasTaskPropertiesChanged(); onStartDateChanged(); onDueDateChanged(); onDelegateTextChanged(); onProgressChanged(); onRecurrenceChanged(); onRelationsChanged(); onAttachmentsChanged(); onStatusChanged(); + onAlarmChanged(); connect(m_model, SIGNAL(artifactChanged(Domain::Artifact::Ptr)), this, SLOT(onArtifactChanged())); connect(m_model, SIGNAL(hasTaskPropertiesChanged(bool)), this, SLOT(onHasTaskPropertiesChanged())); connect(m_model, SIGNAL(titleChanged(QString)), this, SLOT(onTextOrTitleChanged())); connect(m_model, SIGNAL(textChanged(QString)), this, SLOT(onTextOrTitleChanged())); connect(m_model, SIGNAL(startDateChanged(QDateTime)), this, SLOT(onStartDateChanged())); connect(m_model, SIGNAL(startDateChanged(QDateTime)), m_recurrenceWidget, SLOT(setStartDate(QDateTime))); connect(m_model, SIGNAL(dueDateChanged(QDateTime)), this, SLOT(onDueDateChanged())); connect(m_model, SIGNAL(delegateTextChanged(QString)), this, SLOT(onDelegateTextChanged())); connect(m_model, SIGNAL(progressChanged(int)), this, SLOT(onProgressChanged())); connect(m_model, SIGNAL(statusChanged(int)), this, SLOT(onStatusChanged())); connect(m_model, SIGNAL(recurrenceChanged(Domain::Recurrence::Ptr)), this, SLOT(onRecurrenceChanged())); connect(m_model, SIGNAL(relationsChanged(QList)), this, SLOT(onRelationsChanged())); connect(m_model, SIGNAL(attachmentsChanged(Domain::Artifact::Attachment::List)), this, SLOT(onAttachmentsChanged())); + connect(m_model, SIGNAL(alarmChanged(Domain::Alarm::Ptr)), this, SLOT(onAlarmChanged())); connect(this, SIGNAL(titleChanged(QString)), m_model, SLOT(setTitle(QString))); connect(this, SIGNAL(textChanged(QString)), m_model, SLOT(setText(QString))); connect(this, SIGNAL(startDateChanged(QDateTime)), m_model, SLOT(setStartDate(QDateTime))); connect(this, SIGNAL(dueDateChanged(QDateTime)), m_model, SLOT(setDueDate(QDateTime))); connect(this, SIGNAL(delegateChanged(QString, QString)), m_model, SLOT(setDelegate(QString, QString))); connect(this, SIGNAL(progressChanged(int)), m_model, SLOT(setProgress(int))); connect(this, SIGNAL(statusChanged(int)), m_model, SLOT(setStatus(int))); + connect(this, SIGNAL(alarmTypeChanged(int)),m_model, SLOT(setAlarmType(int))); + connect(this, SIGNAL(alarmPositionChanged(int)),m_model, SLOT(setAlarmPosition(int))); + connect(this, SIGNAL(alarmOffsetChanged(int)),m_model, SLOT(setAlarmOffset(int))); connect(m_recurrenceWidget, SIGNAL(frequencyChanged(Domain::Recurrence::Frequency,int)), m_model, SLOT(setFrequency(Domain::Recurrence::Frequency, int))); connect(m_recurrenceWidget, SIGNAL(endChanged(QDateTime)), m_model, SLOT(setRepeatEnd(QDateTime))); connect(m_recurrenceWidget, SIGNAL(endChanged(int)), m_model, SLOT(setRepeatEnd(int))); connect(m_recurrenceWidget, SIGNAL(noEnd()), m_model, SLOT(setRepeatEndless())); connect(m_recurrenceWidget, SIGNAL(exceptionDatesChanged(QList)), m_model, SLOT(setExceptionDates(QList))); connect(m_recurrenceWidget, SIGNAL(byDayChanged(QList)), m_model, SLOT(setByDay(QList))); connect(m_recurrenceWidget, SIGNAL(byMonthChanged(QList)), m_model, SLOT(setByMonth(QList))); connect(m_recurrenceWidget, SIGNAL(byMonthDaysChanged(QList)), m_model, SLOT(setByMonthDays(QList))); connect(m_recurrenceWidget, SIGNAL(byDayPositionChanged(Domain::Recurrence::WeekPosition)), m_model, SLOT(setByDayPosition(Domain::Recurrence::WeekPosition))); } void EditorView::onArtifactChanged() { auto artifact = m_model->property("artifact").value(); setEnabled(artifact); } void EditorView::onHasTaskPropertiesChanged() { m_taskGroup->setVisible(m_model->property("hasTaskProperties").toBool()); } void EditorView::onTextOrTitleChanged() { //We have to temporarilly disconnect these signals to avoid triggering a save when setting the content of the editor. disconnect(m_textEdit->editor(), SIGNAL(textChanged()), this, SLOT(onTextEditChanged())); disconnect(m_titleEdit, SIGNAL(editingFinished()), this, SLOT(onTextEditChanged())); const QString text = m_model->property("text").toString(); if (text != m_textEdit->editor()->toHtml()) m_textEdit->editor()->setText(text); const QString title = m_model->property("title").toString(); if (title != m_titleEdit->text()) m_titleEdit->setText(title); connect(m_textEdit->editor(), SIGNAL(textChanged()), this, SLOT(onTextEditChanged())); connect(m_titleEdit, SIGNAL(editingFinished()), this, SLOT(onTextEditChanged())); } void EditorView::onStartDateChanged() { m_startDateEdit->setDate(m_model->property("startDate").toDateTime().date()); m_startTimeEdit->setTime(m_model->property("startDate").toDateTime().time()); } void EditorView::onDueDateChanged() { m_dueDateEdit->setDate(m_model->property("dueDate").toDateTime().date()); m_dueTimeEdit->setTime(m_model->property("dueDate").toDateTime().time()); } void EditorView::onProgressChanged() { m_progressEdit->setValue(m_model->property("progress").toInt()); } void EditorView::onStatusChanged() { for (int i = 0; i < m_statusComboBox->count(); i++) { if (m_statusComboBox->itemData(i).toInt() == m_model->property("status").toInt()) { m_statusComboBox->setCurrentIndex(i); return; } } m_statusComboBox->setCurrentIndex(0); } void EditorView::onDelegateTextChanged() { const auto delegateText = m_model->property("delegateText").toString(); const auto labelText = delegateText.isEmpty() ? QString() : tr("Delegated to: %1").arg(delegateText); m_delegateLabel->setVisible(!labelText.isEmpty()); m_delegateLabel->setText(labelText); m_delegateEdit->clear(); } void EditorView::onRelationsChanged() { const auto relations = m_model->property("relations").value >(); for (auto widget : m_relationWidgets) { m_relationsLayout->removeWidget(widget); delete widget; } m_relationWidgets.clear(); for (auto relation : relations) { auto widget = new QWidget(this); auto layout = new QHBoxLayout(widget); widget->setLayout(layout); auto labelText = QString("%2").arg(relation->url().toString()).arg(relation->name()); auto label = new QLabel(widget); label->setTextInteractionFlags(Qt::LinksAccessibleByMouse); label->setTextFormat(Qt::RichText); label->setText(labelText); connect(label, SIGNAL(linkActivated(QString)), this, SLOT(onLinkActivated(QString))); layout->addWidget(label); auto button = new QPushButton(widget); button->setProperty("relation", QVariant::fromValue(relation)); button->setIcon(QIcon::fromTheme("list-remove")); connect(button, SIGNAL(clicked()), this, SLOT(onRemoveRelationClicked())); layout->addWidget(button); layout->addStretch(); m_relationsLayout->addWidget(widget); m_relationWidgets << widget; } } void EditorView::onAttachmentsChanged() { const auto attachments = m_model->property("attachments").value(); for (auto widget : m_attachmentWidgets) { m_attachmentsLayout->removeWidget(widget); delete widget; } m_attachmentWidgets.clear(); for (const auto &attachment : attachments) { auto widget = new QWidget(this); auto layout = new QHBoxLayout(widget); widget->setLayout(layout); auto labelText = QString("%2").arg("attachmentDummyUrl").arg(attachment->label); auto label = new QLabel(widget); label->setTextInteractionFlags(Qt::LinksAccessibleByMouse); label->setTextFormat(Qt::RichText); label->setText(labelText); label->setProperty("attachment", QVariant::fromValue(attachment)); connect(label, SIGNAL(linkActivated(QString)), this, SLOT(onAttachmentLinkActivated(QString))); layout->addWidget(label); auto button = new QPushButton(widget); button->setProperty("attachment", QVariant::fromValue(attachment)); button->setIcon(QIcon::fromTheme("list-remove")); connect(button, SIGNAL(clicked()), this, SLOT(onRemoveAttachmentClicked())); layout->addWidget(button); layout->addStretch(); m_attachmentsLayout->addWidget(widget); m_attachmentWidgets << widget; } } void EditorView::onRecurrenceChanged() { const auto recurrence = m_model->property("recurrence").value(); m_recurrenceWidget->blockSignals(true); if (recurrence) { m_recurrenceWidget->setRecurrenceType(recurrence->frequency()); m_recurrenceWidget->setRecurrenceIntervall(recurrence->interval()); m_recurrenceWidget->setExceptionDateTimes(recurrence->exceptionDates()); m_recurrenceWidget->setByDayPosition(recurrence->byDayPosition()); m_recurrenceWidget->setByDay(recurrence->byday()); m_recurrenceWidget->setByMonth(recurrence->bymonth()); m_recurrenceWidget->setByMonthDay(recurrence->bymonthday()); if (recurrence->end().isValid()) { m_recurrenceWidget->setEnd(recurrence->end()); } else if (recurrence->count() >= 0) { m_recurrenceWidget->setEnd(recurrence->count()); } else if (recurrence->count() == -1) { m_recurrenceWidget->setNoEnd(); } } else { m_recurrenceWidget->clear(); } m_recurrenceWidget->blockSignals(false); if (recurrence && !m_statusComboBox->itemData(5).isValid()) { m_statusComboBox->addItem(tr("All ocurrences completed"), Domain::Task::FullComplete); onStatusChanged(); } else if (!recurrence && m_statusComboBox->itemData(5).isValid()) { m_statusComboBox->removeItem(5); } } +void EditorView::onAlarmChanged() +{ + const auto alarm = m_model->property("alarm").value(); + if (!alarm) { + m_alarmTypeWidget->setCurrentIndex(AlarmNone); + onAlarmEnableChanged(AlarmNone); + return; + } + switch(alarm->type()) { + case Domain::Alarm::Display: + m_alarmTypeWidget->setCurrentIndex(AlarmDisplay); + onAlarmEnableChanged(AlarmDisplay); + break; + case Domain::Alarm::Audio: + m_alarmTypeWidget->setCurrentIndex(AlarmAudio); + onAlarmEnableChanged(AlarmAudio); + break; + } + + switch(alarm->position()) { + case Domain::Alarm::Start: + m_alarmPositionWidget->setCurrentIndex(AlarmStart); + break; + case Domain::Alarm::End: + m_alarmPositionWidget->setCurrentIndex(AlarmEnd); + break; + } + + m_alarmOffsetEdit->setValue(-alarm->offset()); +} + +void EditorView::onAlarmEnableChanged(int t) +{ + AlarmType type = static_cast(t); + bool enabled = (type != AlarmNone); + m_alarmOffsetEdit->setEnabled(enabled); + m_alarmPositionWidget->setEnabled(enabled); +} + +void EditorView::onAlarmTypeChanged(int index) +{ + emit alarmTypeChanged(m_alarmTypeWidget->itemData(index).toInt()); +} + +void EditorView::onAlarmPositionChanged(int index) +{ + emit alarmPositionChanged(m_alarmPositionWidget->itemData(index).toInt()); +} + +void EditorView::onAlarmOffsetChanged(int offset) +{ + emit alarmOffsetChanged(-offset); +} void EditorView::onRemoveRelationClicked() { auto relation = sender()->property("relation").value(); QMetaObject::invokeMethod(m_model, "removeRelation", Q_ARG(Domain::Relation::Ptr, relation)); } void EditorView::onLinkActivated(const QString &link) { KRun::run("kmail --view %u", KUrl::List() << KUrl(link), 0); } void EditorView::onRemoveAttachmentClicked() { auto attachment = sender()->property("attachment").value(); QMetaObject::invokeMethod(m_model, "removeAttachment", Q_ARG(Domain::Artifact::Attachment::Ptr, attachment)); } void EditorView::onAddAttachmentClicked() { auto attachmentFileUrl = KFileDialog::getOpenUrl(KUrl(), QString(), 0, tr("Add Attachment")); auto attachment = Domain::Artifact::Attachment::Ptr::create(); attachment->label = attachmentFileUrl.fileName(KUrl::DirectoryOption::IgnoreTrailingSlash); auto mimeType = KMimeType::findByUrl(attachmentFileUrl); attachment->mimetype = mimeType->name(); QString tmpFile; if (KIO::NetAccess::download(attachmentFileUrl, tmpFile, this)) { QFile f(tmpFile); if (!f.open(QIODevice::ReadOnly)) { return; } QByteArray data = f.readAll(); f.close(); attachment->data = data; } KIO::NetAccess::removeTempFile(tmpFile); QMetaObject::invokeMethod(m_model, "addAttachment", Q_ARG(Domain::Artifact::Attachment::Ptr, attachment)); } KUrl tempFileForAttachment(const Domain::Artifact::Attachment::Ptr &attachment) { KTemporaryFile *file = new KTemporaryFile(); QStringList patterns = KMimeType::mimeType(attachment->mimetype)->patterns(); if (!patterns.empty()) { file->setSuffix(QString(patterns.first()).remove('*')); } file->setAutoRemove(true); file->open(); // read-only not to give the idea that it could be written to file->setPermissions(QFile::ReadUser); file->write(attachment->data); file->close(); return file->fileName(); } void EditorView::onAttachmentLinkActivated(const QString &link) { const auto attachment = sender()->property("attachment").value(); if (!attachment) { qWarning() << "Couldn't find attachment"; } QString saveAsFile = KFileDialog::getSaveFileName(attachment->label, QString(), 0, tr("Save Attachment")); if (saveAsFile.isEmpty() || (QFile(saveAsFile).exists() && (KMessageBox::warningYesNo(0,tr("%1 already exists. Do you want to overwrite it?").arg(saveAsFile)) == KMessageBox::No))) { return; } KUrl sourceUrl = tempFileForAttachment(attachment); // save the attachment url if (!KIO::NetAccess::file_copy(sourceUrl, KUrl(saveAsFile)) && KIO::NetAccess::lastError()) { KMessageBox::error(0, KIO::NetAccess::lastErrorString()); } } void EditorView::onTextEditChanged() { emit titleChanged(m_titleEdit->text()); emit textChanged(m_textEdit->editor()->toHtml()); } void EditorView::onStartEditEntered(const QDate &start) { emit startDateChanged(QDateTime(start, m_startTimeEdit->time())); } void EditorView::onStartTimeEntered(const QTime &start) { emit startDateChanged(QDateTime(m_startDateEdit->date() , start)); } void EditorView::onDueEditEntered(const QDate &due) { emit dueDateChanged(QDateTime(due, m_dueTimeEdit->time())); } void EditorView::onDueTimeEntered(const QTime &due) { emit dueDateChanged(QDateTime(m_dueDateEdit->date(), due)); } void EditorView::onStartTodayClicked() { QDate today(QDate::currentDate()); m_startDateEdit->setDate(today); emit startDateChanged(QDateTime(today)); } void EditorView::onDelegateEntered() { const auto input = m_delegateEdit->text(); auto name = QString(); auto email = QString(); auto gotMatch = false; QRegExp fullRx("\\s*(.*) <([\\w\\.]+@[\\w\\.]+)>\\s*"); QRegExp emailOnlyRx("\\s*?\\s*"); if (input.contains(fullRx)) { name = fullRx.cap(1); email = fullRx.cap(2); gotMatch = true; } else if (input.contains(emailOnlyRx)) { email = emailOnlyRx.cap(1); gotMatch = true; } if (gotMatch) { QMetaObject::invokeMethod(m_model, "delegate", Q_ARG(QString, name), Q_ARG(QString, email)); } emit delegateChanged(name, email); } void EditorView::onProgressChanged(int progress) { emit progressChanged(progress); } void EditorView::onStatusChanged(int index) { emit statusChanged(m_statusComboBox->itemData(index).toInt()); } diff --git a/src/widgets/editorview.h b/src/widgets/editorview.h index b7a21445..4cebce1d 100644 --- a/src/widgets/editorview.h +++ b/src/widgets/editorview.h @@ -1,129 +1,144 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef WIDGETS_EDITORVIEW_H #define WIDGETS_EDITORVIEW_H #include #include class QAbstractButton; class QLabel; class QLineEdit; class QComboBox; class QSpinBox; class QVBoxLayout; class QListWidget; class KLineEdit; class KTimeComboBox; namespace KPIM { class KDateEdit; } namespace Widgets { class RecurrenceWidget; class EditorWidget; class EditorView : public QWidget { Q_OBJECT public: explicit EditorView(QWidget *parent = 0); virtual ~EditorView() {}; QObject *model() const; public slots: void setModel(QObject *model); signals: void textChanged(const QString &text); void titleChanged(const QString &title); void startDateChanged(const QDateTime &start); void dueDateChanged(const QDateTime &due); void delegateChanged(const QString &name, const QString &email); void progressChanged(int progress); void statusChanged(int status); + void alarmTypeChanged(int type); + void alarmPositionChanged(int position); + void alarmOffsetChanged(int offset); + private slots: void onArtifactChanged(); void onHasTaskPropertiesChanged(); void onTextOrTitleChanged(); void onStartDateChanged(); void onDueDateChanged(); void onDelegateTextChanged(); void onProgressChanged(); void onStatusChanged(); void onRelationsChanged(); void onAttachmentsChanged(); void onRecurrenceChanged(); void onTextEditChanged(); void onStartEditEntered(const QDate &start); void onDueEditEntered(const QDate &due); void onStartTodayClicked(); void onDelegateEntered(); void onProgressChanged(int progress); void onStatusChanged(int status); void onLinkActivated(const QString &link); void onRemoveRelationClicked(); void onAttachmentLinkActivated(const QString &link); void onRemoveAttachmentClicked(); void onAddAttachmentClicked(); void onStartTimeEntered(const QTime &start); void onDueTimeEntered(const QTime &due); + void onAlarmChanged(); + void onAlarmTypeChanged(int type); + void onAlarmPositionChanged(int); + void onAlarmOffsetChanged(int); + void onAlarmEnableChanged(int); + void toggleFullscreenEditor(); private: + void fillAlarmCombo(); + QObject *m_model; QLabel *m_delegateLabel; QLineEdit *m_titleEdit; EditorWidget *m_textEdit; QWidget *m_taskGroup; KPIM::KDateEdit *m_startDateEdit; KTimeComboBox *m_startTimeEdit; KPIM::KDateEdit *m_dueDateEdit; KTimeComboBox *m_dueTimeEdit; QAbstractButton *m_startTodayButton; KLineEdit *m_delegateEdit; QComboBox *m_statusComboBox; QSpinBox *m_progressEdit; QList m_relationWidgets; QVBoxLayout *m_relationsLayout; QList m_attachmentWidgets; QVBoxLayout *m_attachmentsLayout; QAbstractButton *m_addAttachmentButton; RecurrenceWidget *m_recurrenceWidget; + QComboBox *m_alarmTypeWidget; + QComboBox *m_alarmPositionWidget; + QSpinBox *m_alarmOffsetEdit; }; } #endif // WIDGETS_EDITORVIEW_H