diff --git a/src/agenda/timelabels.h b/src/agenda/timelabels.h index 850bbb0..9404819 100644 --- a/src/agenda/timelabels.h +++ b/src/agenda/timelabels.h @@ -1,102 +1,102 @@ /* Copyright (c) 2000,2001,2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2007 Bruno Virlet 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) any later version. 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. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_TIMELABELS_H #define EVENTVIEWS_TIMELABELS_H #include #include namespace EventViews { class Agenda; class TimeLabelsZone; class Prefs; typedef QSharedPointer PrefsPtr; class TimeLabels : public QFrame { Q_OBJECT public: typedef QList List; - TimeLabels(const QTimeZone &zone, int rows, TimeLabelsZone *parent = nullptr, Qt::WindowFlags f = nullptr); + TimeLabels(const QTimeZone &zone, int rows, TimeLabelsZone *parent = nullptr, Qt::WindowFlags f = {}); /** updates widget's internal state */ void updateConfig(); /** */ void setAgenda(Agenda *agenda); /** */ void paintEvent(QPaintEvent *e) override; /** */ void contextMenuEvent(QContextMenuEvent *event) override; /** Returns the time zone of this label */ Q_REQUIRED_RESULT QTimeZone timeZone() const; /** Return string which can be used as a header for the time label. */ Q_REQUIRED_RESULT QString header() const; /** Return string which can be used as a tool tip for the header. */ Q_REQUIRED_RESULT QString headerToolTip() const; QSize sizeHint() const override; QSize minimumSizeHint() const override; /** */ bool event(QEvent *event) override; private: Q_REQUIRED_RESULT int yposToCell(const int ypos) const; Q_REQUIRED_RESULT int cellToHour(const int cell) const; Q_REQUIRED_RESULT QString cellToSuffix(const int cell) const; /** update the position of the marker showing the mouse position */ void mousePosChanged(const QPoint &pos); void showMousePos(); void hideMousePos(); void setCellHeight(double height); void colorMousePos(); QTimeZone mTimezone; int mRows; double mCellHeight; int mMiniWidth; Agenda *mAgenda = nullptr; TimeLabelsZone *mTimeLabelsZone = nullptr; QFrame *mMousePos = nullptr; // shows a marker for the current mouse position in y direction }; } #endif diff --git a/src/todo/todomodel.cpp b/src/todo/todomodel.cpp index 83f9952..a113a10 100644 --- a/src/todo/todomodel.cpp +++ b/src/todo/todomodel.cpp @@ -1,926 +1,926 @@ /* Copyright (c) 2008 Thomas Thrainer Copyright (c) 2012 Sérgio Martins 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) any later version. 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. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todomodel_p.h" #include "incidencetreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "calendarview_debug.h" #include #include struct SourceModelIndex { SourceModelIndex(int _r, int _c, void *_p, QAbstractItemModel *_m) : r(_r) , c(_c) , p(_p) , m(_m) { } operator QModelIndex() { return reinterpret_cast(*this); } int r, c; void *p; const QAbstractItemModel *m = nullptr; }; static bool isDueToday(const KCalendarCore::Todo::Ptr &todo) { return !todo->isCompleted() && todo->dtDue().date() == QDate::currentDate(); } TodoModel::Private::Private(const EventViews::PrefsPtr &preferences, TodoModel *qq) : QObject() , m_changer(nullptr) , m_preferences(preferences) , q(qq) { } Akonadi::Item TodoModel::Private::findItemByUid(const QString &uid, const QModelIndex &parent) const { Q_ASSERT(!uid.isEmpty()); IncidenceTreeModel *treeModel = qobject_cast(q->sourceModel()); if (treeModel) { // O(1) Shortcut return treeModel->item(uid); } Akonadi::Item item; const int count = q->rowCount(parent); for (int i = 0; i < count; ++i) { const QModelIndex currentIndex = q->index(i, 0, parent); Q_ASSERT(currentIndex.isValid()); item = q->data(currentIndex, Akonadi::EntityTreeModel::ItemRole).value(); if (item.isValid()) { return item; } else { item = findItemByUid(uid, currentIndex); if (item.isValid()) { return item; } } } return item; } void TodoModel::Private::onDataChanged(const QModelIndex &begin, const QModelIndex &end) { Q_ASSERT(begin.isValid()); Q_ASSERT(end.isValid()); const QModelIndex proxyBegin = q->mapFromSource(begin); Q_ASSERT(proxyBegin.column() == 0); const QModelIndex proxyEnd = q->mapFromSource(end); Q_EMIT q->dataChanged(proxyBegin, proxyEnd.sibling(proxyEnd.row(), TodoModel::ColumnCount - 1)); } void TodoModel::Private::onHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_EMIT q->headerDataChanged(orientation, first, last); } void TodoModel::Private::onRowsAboutToBeInserted(const QModelIndex &parent, int begin, int end) { const QModelIndex index = q->mapFromSource(parent); Q_ASSERT(!(parent.isValid() ^ index.isValid())); // Both must be valid, or both invalid Q_ASSERT(!(index.isValid() && index.model() != q)); q->beginInsertRows(index, begin, end); } void TodoModel::Private::onRowsInserted(const QModelIndex &, int, int) { q->endInsertRows(); } void TodoModel::Private::onRowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) { const QModelIndex index = q->mapFromSource(parent); Q_ASSERT(!(parent.isValid() ^ index.isValid())); // Both must be valid, or both invalid Q_ASSERT(!(index.isValid() && index.model() != q)); q->beginRemoveRows(index, begin, end); } void TodoModel::Private::onRowsRemoved(const QModelIndex &, int, int) { q->endRemoveRows(); } void TodoModel::Private::onRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) { Q_UNUSED(sourceParent); Q_UNUSED(sourceStart); Q_UNUSED(sourceEnd); Q_UNUSED(destinationParent); Q_UNUSED(destinationRow); /* Disabled for now, layoutAboutToBeChanged() is emitted q->beginMoveRows( q->mapFromSource( sourceParent ), sourceStart, sourceEnd, q->mapFromSource( destinationParent ), destinationRow ); */ } void TodoModel::Private::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int) { /*q->endMoveRows();*/ } void TodoModel::Private::onModelAboutToBeReset() { q->beginResetModel(); } void TodoModel::Private::onModelReset() { q->endResetModel(); } void TodoModel::Private::onLayoutAboutToBeChanged() { Q_ASSERT(m_persistentIndexes.isEmpty()); Q_ASSERT(m_layoutChangePersistentIndexes.isEmpty()); Q_ASSERT(m_columns.isEmpty()); const QModelIndexList persistentIndexes = q->persistentIndexList(); for (const QPersistentModelIndex &persistentIndex : persistentIndexes) { m_persistentIndexes << persistentIndex; // Stuff we have to update onLayoutChanged Q_ASSERT(persistentIndex.isValid()); QModelIndex index_col0 = q->createIndex(persistentIndex.row(), 0, persistentIndex.internalPointer()); const QPersistentModelIndex srcPersistentIndex = q->mapToSource(index_col0); Q_ASSERT(srcPersistentIndex.isValid()); m_layoutChangePersistentIndexes << srcPersistentIndex; m_columns << persistentIndex.column(); } Q_EMIT q->layoutAboutToBeChanged(); } void TodoModel::Private::onLayoutChanged() { for (int i = 0; i < m_persistentIndexes.size(); ++i) { QModelIndex newIndex_col0 = q->mapFromSource(m_layoutChangePersistentIndexes.at(i)); Q_ASSERT(newIndex_col0.isValid()); const int column = m_columns.at(i); QModelIndex newIndex = column == 0 ? newIndex_col0 : q->createIndex(newIndex_col0.row(), column, newIndex_col0.internalPointer()); q->changePersistentIndex(m_persistentIndexes.at(i), newIndex); } m_layoutChangePersistentIndexes.clear(); m_persistentIndexes.clear(); m_columns.clear(); Q_EMIT q->layoutChanged(); } TodoModel::TodoModel(const EventViews::PrefsPtr &preferences, QObject *parent) : QAbstractProxyModel(parent) , d(new Private(preferences, this)) { setObjectName(QStringLiteral("TodoModel")); } TodoModel::~TodoModel() { delete d; } QVariant TodoModel::data(const QModelIndex &index, int role) const { Q_ASSERT(index.isValid()); if (!index.isValid() || !d->m_calendar) { return QVariant(); } const QModelIndex sourceIndex = mapToSource(index.sibling(index.row(), 0)); if (!sourceIndex.isValid()) { return QVariant(); } Q_ASSERT(sourceIndex.isValid()); const Akonadi::Item item = sourceIndex.data(Akonadi::EntityTreeModel::ItemRole).value(); if (!item.isValid()) { qCWarning(CALENDARVIEW_LOG) << "Invalid index: " << sourceIndex; //Q_ASSERT( false ); return QVariant(); } const KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); if (!todo) { qCCritical(CALENDARVIEW_LOG) << "item.hasPayload()" << item.hasPayload(); if (item.hasPayload()) { KCalendarCore::Incidence::Ptr incidence = item.payload(); if (incidence) { qCCritical(CALENDARVIEW_LOG) << "It's actually " << incidence->type(); } } Q_ASSERT(!"There's no to-do."); return QVariant(); } if (role == Qt::DisplayRole) { switch (index.column()) { case SummaryColumn: return QVariant(todo->summary()); case RecurColumn: if (todo->recurs()) { if (todo->hasRecurrenceId()) { return i18nc("yes, an exception to a recurring to-do", "Exception"); } else { return i18nc("yes, recurring to-do", "Yes"); } } else { return i18nc("no, not a recurring to-do", "No"); } case PriorityColumn: if (todo->priority() == 0) { return QVariant(QStringLiteral("--")); } return QVariant(todo->priority()); case PercentColumn: return QVariant(todo->percentComplete()); case StartDateColumn: return todo->hasStartDate() ? QLocale().toString( todo->dtStart().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); case DueDateColumn: return todo->hasDueDate() ? QLocale().toString( todo->dtDue().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); case CategoriesColumn: { QString categories = todo->categories().join( i18nc("delimiter for joining category names", ",")); return QVariant(categories); } case DescriptionColumn: return QVariant(todo->description()); case CalendarColumn: return QVariant(CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection())); } return QVariant(); } if (role == Qt::EditRole) { switch (index.column()) { case SummaryColumn: return QVariant(todo->summary()); case RecurColumn: return QVariant(todo->recurs()); case PriorityColumn: return QVariant(todo->priority()); case PercentColumn: return QVariant(todo->percentComplete()); case StartDateColumn: return QVariant(todo->dtStart().date()); case DueDateColumn: return QVariant(todo->dtDue().date()); case CategoriesColumn: return QVariant(todo->categories()); case DescriptionColumn: return QVariant(todo->description()); case CalendarColumn: return QVariant(CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection())); } return QVariant(); } // set the tooltip for every item if (role == Qt::ToolTipRole) { if (d->m_preferences->enableToolTips()) { return QVariant(KCalUtils::IncidenceFormatter::toolTipStr( CalendarSupport::displayName(d->m_calendar.data(), item.parentCollection()), todo, QDate(), true)); } else { return QVariant(); } } // background colour for todos due today or overdue todos if (role == Qt::BackgroundRole) { if (todo->isOverdue()) { return QVariant( QBrush(d->m_preferences->todoOverdueColor())); } else if (isDueToday(todo)) { return QVariant( QBrush(d->m_preferences->todoDueTodayColor())); } } // indicate if a row is checked (=completed) only in the first column if (role == Qt::CheckStateRole && index.column() == 0) { if (hasChildren(index) && !index.parent().isValid()) { return QVariant(); } if (todo->isCompleted()) { return QVariant(Qt::Checked); } else { return QVariant(Qt::Unchecked); } } // icon for recurring todos // It's in the summary column so you don't accidentally click // the checkbox ( which increments the next occurrence date ). if (role == Qt::DecorationRole && index.column() == SummaryColumn) { if (todo->recurs()) { return QVariant(QIcon::fromTheme(QStringLiteral("task-recurring"))); } } // category colour if (role == Qt::DecorationRole && index.column() == SummaryColumn) { QStringList categories = todo->categories(); return categories.isEmpty() ? QVariant() : QVariant(CalendarSupport::KCalPrefs::instance()->categoryColor(categories.first())); } else if (role == Qt::DecorationRole) { return QVariant(); } if (role == TodoRole) { return QVariant::fromValue(item); } if (role == IsRichTextRole) { if (index.column() == SummaryColumn) { return QVariant(todo->summaryIsRich()); } else if (index.column() == DescriptionColumn) { return QVariant(todo->descriptionIsRich()); } else { return QVariant(); } } if (role == Qt::TextAlignmentRole) { switch (index.column()) { // If you change this, change headerData() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } if (sourceModel()) { return sourceModel()->data(mapToSource(index.sibling(index.row(), 0)), role); } return QVariant(); } bool TodoModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_ASSERT(index.isValid()); if (!d->m_changer) { return false; } const QVariant oldValue = data(index, role); if (oldValue == value) { // Nothing changed, the user used one of the QStyledDelegate's editors but seted the old value // Lets just skip this then and avoid a roundtrip to akonadi, and avoid sending invitations return true; } const Akonadi::Item item = data(index, Akonadi::EntityTreeModel::ItemRole).value(); const KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); if (!item.isValid() || !todo) { qCWarning(CALENDARVIEW_LOG) << "TodoModel::setData() called, bug item is invalid or doesn't have payload"; Q_ASSERT(false); return false; } if (d->m_calendar->hasRight(item, Akonadi::Collection::CanChangeItem)) { KCalendarCore::Todo::Ptr oldTodo(todo->clone()); if (role == Qt::CheckStateRole && index.column() == 0) { const bool checked = static_cast(value.toInt()) == Qt::Checked; if (checked) { todo->setCompleted(QDateTime::currentDateTimeUtc()); // Because it calls Todo::recurTodo() } else { todo->setCompleted(false); } } if (role == Qt::EditRole) { switch (index.column()) { case SummaryColumn: if (!value.toString().isEmpty()) { todo->setSummary(value.toString()); } break; case PriorityColumn: todo->setPriority(value.toInt()); break; case PercentColumn: todo->setPercentComplete(value.toInt()); break; case StartDateColumn: { QDateTime tmp = todo->dtStart(); tmp.setDate(value.toDate()); todo->setDtStart(tmp); break; } case DueDateColumn: { QDateTime tmp = todo->dtDue(); tmp.setDate(value.toDate()); todo->setDtDue(tmp); break; } case CategoriesColumn: todo->setCategories(value.toStringList()); break; case DescriptionColumn: todo->setDescription(value.toString()); break; } } if (!todo->dirtyFields().isEmpty()) { d->m_changer->modifyIncidence(item, oldTodo); // modifyIncidence will eventually call the view's // changeIncidenceDisplay method, which in turn // will call processChange. processChange will then emit // dataChanged to the view, so we don't have to // do it here } return true; } else { if (!(role == Qt::CheckStateRole && index.column() == 0)) { //KOHelper::showSaveIncidenceErrorMsg( 0, todo ); //TODO pass parent qCCritical(CALENDARVIEW_LOG) << "Unable to modify incidence"; } return false; } } int TodoModel::rowCount(const QModelIndex &parent) const { if (sourceModel()) { if (parent.isValid()) { QModelIndex parent_col0 = createIndex(parent.row(), 0, parent.internalPointer()); return sourceModel()->rowCount(mapToSource(parent_col0)); } else { return sourceModel()->rowCount(); } } return 0; } int TodoModel::columnCount(const QModelIndex &) const { return ColumnCount; } void TodoModel::setSourceModel(QAbstractItemModel *model) { if (model == sourceModel()) { return; } beginResetModel(); if (sourceModel()) { disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex))); disconnect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int))); disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int))); disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int))); disconnect(sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset())); disconnect(sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset())); disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged())); disconnect(sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged())); } QAbstractProxyModel::setSourceModel(model); if (sourceModel()) { connect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex))); connect(sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int))); connect(sourceModel(), SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); connect(sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int))); connect(sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset())); connect(sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset())); connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged())); connect(sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged())); } endResetModel(); } void TodoModel::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { d->m_changer = changer; } QVariant TodoModel::headerData(int column, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); } if (role == Qt::DisplayRole) { switch (column) { case SummaryColumn: return QVariant(i18n("Summary")); case RecurColumn: return QVariant(i18n("Recurs")); case PriorityColumn: return QVariant(i18n("Priority")); case PercentColumn: return QVariant(i18nc("@title:column percent complete", "Complete")); case StartDateColumn: return QVariant(i18n("Start Date")); case DueDateColumn: return QVariant(i18n("Due Date")); case CategoriesColumn: return QVariant(i18n("Categories")); case DescriptionColumn: return QVariant(i18n("Description")); case CalendarColumn: return QVariant(i18n("Calendar")); } } if (role == Qt::TextAlignmentRole) { switch (column) { // If you change this, change data() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant(Qt::AlignHCenter); } return QVariant(); } return QVariant(); } void TodoModel::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { d->m_calendar = calendar; } Qt::DropActions TodoModel::supportedDropActions() const { // Qt::CopyAction not supported yet return Qt::MoveAction; } QStringList TodoModel::mimeTypes() const { static QStringList list; if (list.isEmpty()) { list << KCalUtils::ICalDrag::mimeType() << KCalUtils::VCalDrag::mimeType(); } return list; } QMimeData *TodoModel::mimeData(const QModelIndexList &indexes) const { Akonadi::Item::List items; for (const QModelIndex &index : indexes) { const Akonadi::Item item = this->data(index, Akonadi::EntityTreeModel::ItemRole).value(); if (item.isValid() && !items.contains(item)) { items.push_back(item); } } return CalendarSupport::createMimeData(items); } bool TodoModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); if (action != Qt::MoveAction) { qCWarning(CALENDARVIEW_LOG) << "No action other than MoveAction currently supported!"; //TODO return false; } if (d->m_calendar && d->m_changer && (KCalUtils::ICalDrag::canDecode(data) || KCalUtils::VCalDrag::canDecode(data))) { KCalUtils::DndFactory dndFactory(d->m_calendar); KCalendarCore::Todo::Ptr t = dndFactory.createDropTodo(data); KCalendarCore::Event::Ptr e = dndFactory.createDropEvent(data); if (t) { // we don't want to change the created todo, but the one which is already // stored in our calendar / tree const Akonadi::Item item = d->findItemByUid(t->uid(), QModelIndex()); KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); KCalendarCore::Todo::Ptr destTodo; if (parent.isValid()) { const Akonadi::Item parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value(); if (parentItem.isValid()) { destTodo = CalendarSupport::todo(parentItem); } } KCalendarCore::Incidence::Ptr tmp = destTodo; while (tmp) { if (tmp->uid() == todo->uid()) { //correct, don't use instanceIdentifier() here KMessageBox::information( nullptr, i18n("Cannot move to-do to itself or a child of itself."), i18n("Drop To-do"), QStringLiteral("NoDropTodoOntoItself")); return false; } const QString parentUid = tmp->relatedTo(); tmp = CalendarSupport::incidence(d->m_calendar->item(parentUid)); } if (!destTodo || !destTodo->hasRecurrenceId()) { KCalendarCore::Todo::Ptr oldTodo = KCalendarCore::Todo::Ptr(todo->clone()); // destTodo is empty when we drag a to-do out of a relationship todo->setRelatedTo(destTodo ? destTodo->uid() : QString()); d->m_changer->modifyIncidence(item, oldTodo); // again, no need to Q_EMIT dataChanged, that's done by processChange return true; } else { qCDebug(CALENDARVIEW_LOG) << "Todo's with recurring id can't have child todos yet."; return false; } } else if (e) { // TODO: Implement dropping an event onto a to-do: Generate a relationship to the event! } else { if (!parent.isValid()) { // TODO we should create a new todo with the data in the drop object qCDebug(CALENDARVIEW_LOG) << "TODO: Create a new todo with the given data"; return false; } const Akonadi::Item parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value(); KCalendarCore::Todo::Ptr destTodo = CalendarSupport::todo(parentItem); if (data->hasText()) { QString text = data->text(); KCalendarCore::Todo::Ptr oldTodo = KCalendarCore::Todo::Ptr(destTodo->clone()); if (text.startsWith(QLatin1String("file:"))) { destTodo->addAttachment(KCalendarCore::Attachment(text)); } else { QStringList emails = KEmailAddress::splitAddressList(text); for (QStringList::ConstIterator it = emails.constBegin(); it != emails.constEnd(); ++it) { QString name, email, comment; if (KEmailAddress::splitAddress(*it, name, email, comment) == KEmailAddress::AddressOk) { destTodo->addAttendee(KCalendarCore::Attendee(name, email)); } } } d->m_changer->modifyIncidence(parentItem, oldTodo); return true; } } } return false; } Qt::ItemFlags TodoModel::flags(const QModelIndex &index) const { if (!index.isValid()) { - return nullptr; + return Qt::NoItemFlags; } Qt::ItemFlags ret = QAbstractItemModel::flags(index); const Akonadi::Item item = data(index, Akonadi::EntityTreeModel::ItemRole).value(); if (!item.isValid()) { Q_ASSERT(mapToSource(index).isValid()); qCWarning(CALENDARVIEW_LOG) << "Item is invalid " << index; Q_ASSERT(false); - return nullptr; + return Qt::NoItemFlags; } ret |= Qt::ItemIsDragEnabled; const KCalendarCore::Todo::Ptr todo = CalendarSupport::todo(item); if (d->m_calendar->hasRight(item, Akonadi::Collection::CanChangeItem)) { // the following columns are editable: switch (index.column()) { case SummaryColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: ret |= Qt::ItemIsEditable; break; case DescriptionColumn: if (!todo->descriptionIsRich()) { ret |= Qt::ItemIsEditable; } break; } } if (index.column() == 0) { // whole rows should have checkboxes, so append the flag for the // first item of every row only. Also, only the first item of every // row should be used as a target for a drag and drop operation. ret |= Qt::ItemIsUserCheckable |Qt::ItemIsDropEnabled; } return ret; } QModelIndex TodoModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceModel() || !sourceIndex.isValid()) { return QModelIndex(); } Q_ASSERT(sourceIndex.internalPointer()); return createIndex(sourceIndex.row(), 0, sourceIndex.internalPointer()); } QModelIndex TodoModel::mapToSource(const QModelIndex &proxyIndex) const { if (!sourceModel() || !proxyIndex.isValid()) { return QModelIndex(); } if (proxyIndex.column() != 0) { qCCritical(CALENDARVIEW_LOG) << "Map to source called with column>0, but source model only has 1 column"; Q_ASSERT(false); } Q_ASSERT(proxyIndex.internalPointer()); // we convert to column 0 const QModelIndex sourceIndex = SourceModelIndex(proxyIndex.row(), 0, proxyIndex.internalPointer(), sourceModel()); return sourceIndex; } QModelIndex TodoModel::index(int row, int column, const QModelIndex &parent) const { if (!sourceModel()) { return QModelIndex(); } Q_ASSERT(!parent.isValid() || parent.internalPointer()); QModelIndex parent_col0 = parent.isValid() ? createIndex(parent.row(), 0, parent.internalPointer()) : QModelIndex(); // Lets preserve the original internalPointer const QModelIndex index = mapFromSource(sourceModel()->index(row, 0, mapToSource(parent_col0))); Q_ASSERT(!index.isValid() || index.internalPointer()); if (index.isValid()) { return createIndex(row, column, index.internalPointer()); } return QModelIndex(); } QModelIndex TodoModel::parent(const QModelIndex &child) const { if (!sourceModel() || !child.isValid()) { return QModelIndex(); } Q_ASSERT(child.internalPointer()); const QModelIndex child_col0 = createIndex(child.row(), 0, child.internalPointer()); QModelIndex parentIndex = mapFromSource(sourceModel()->parent(mapToSource(child_col0))); Q_ASSERT(!parentIndex.isValid() || parentIndex.internalPointer()); if (parentIndex.isValid()) { // preserve original column return createIndex(parentIndex.row(), child.column(), parentIndex.internalPointer()); } return QModelIndex(); } QModelIndex TodoModel::buddy(const QModelIndex &index) const { // We reimplement because the default implementation calls mapToSource() and // source model doesn't have the same number of columns. return index; }