Eventviews

todoview.cpp
1/*
2 This file is part of KOrganizer.
3
4 SPDX-FileCopyrightText: 2000, 2001, 2003 Cornelius Schumacher <schumacher@kde.org>
5 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6 SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
7 SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at>
8 SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com>
9
10 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
11*/
12
13#include "todoview.h"
14using namespace Qt::Literals::StringLiterals;
15
16#include "calendarview_debug.h"
17#include "coloredtodoproxymodel.h"
18#include "tododelegates.h"
19#include "todoviewquickaddline.h"
20#include "todoviewquicksearch.h"
21#include "todoviewsortfilterproxymodel.h"
22#include "todoviewview.h"
23
24#include <Akonadi/CalendarUtils>
25#include <Akonadi/EntityMimeTypeFilterModel>
26#include <Akonadi/EntityTreeModel>
27#include <Akonadi/TagFetchJob>
28
29#include <Akonadi/ETMViewStateSaver>
30#include <Akonadi/IncidenceTreeModel>
31#include <Akonadi/TodoModel>
32
33#include <CalendarSupport/KCalPrefs>
34
35#include <KCalendarCore/CalFormat>
36
37#include <KConfig>
38#include <KDatePickerPopup>
39#include <KDescendantsProxyModel>
40#include <KJob>
41#include <KMessageBox>
42
43#include <QGridLayout>
44#include <QHeaderView>
45#include <QIcon>
46#include <QMenu>
47#include <QSortFilterProxyModel>
48#include <QToolButton>
49
50#include <chrono>
51
52using namespace std::chrono_literals;
53
54Q_DECLARE_METATYPE(QPointer<QMenu>)
55
56using namespace EventViews;
57using namespace KCalendarCore;
58
59namespace EventViews
60{
61
62class CalendarFilterModel : public QSortFilterProxyModel
63{
65public:
66 explicit CalendarFilterModel(QObject *parent = nullptr)
68 {
69 mDescendantsProxy.setDisplayAncestorData(false);
70 QSortFilterProxyModel::setSourceModel(&mDescendantsProxy);
71 }
72
73 void setSourceModel(QAbstractItemModel *model) override
74 {
75 mDescendantsProxy.setSourceModel(model);
76 }
77
78 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
79 {
80 const auto source_index = sourceModel()->index(source_row, 0, source_parent);
81 const auto item = sourceModel()->data(source_index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
82
83 if (!item.isValid()) {
84 return false;
85 }
86 return mEnabledCalendars.contains(item.parentCollection().id());
87 }
88
89 void addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
90 {
91 mEnabledCalendars.insert(calendar->collection().id());
93 }
94
95 void removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
96 {
97 mEnabledCalendars.remove(calendar->collection().id());
99 }
100
101private:
102 KDescendantsProxyModel mDescendantsProxy;
103 QSet<Akonadi::Collection::Id> mEnabledCalendars;
104};
105
106// We share this struct between all views, for performance and memory purposes
107class ModelStack
108{
109public:
110 ModelStack(const EventViews::PrefsPtr &preferences, QObject *parent_)
111 : todoModel(new Akonadi::TodoModel())
112 , coloredTodoModel(new ColoredTodoProxyModel(preferences))
113 , parent(parent_)
114 , prefs(preferences)
115 {
116 coloredTodoModel->setSourceModel(todoModel);
117 }
118
119 ~ModelStack()
120 {
121 delete coloredTodoModel;
122 delete todoModel;
123 delete todoTreeModel;
124 delete todoFlatModel;
125 }
126
127 void registerView(TodoView *view)
128 {
129 views << view;
130 }
131
132 void unregisterView(TodoView *view)
133 {
134 views.removeAll(view);
135 }
136
137 void setFlatView(bool flat)
138 {
139 const QString todoMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.todo");
140 if (flat) {
141 for (TodoView *view : std::as_const(views)) {
142 // In flatview dropping confuses users and it's very easy to drop into a child item
144 view->setFlatView(flat, /**propagate=*/false); // So other views update their toggle icon
145
146 if (todoTreeModel) {
147 view->saveViewState(); // Save the tree state before it's gone
148 }
149 }
150
151 delete todoFlatModel;
152 todoFlatModel = new Akonadi::EntityMimeTypeFilterModel(parent);
153 todoFlatModel->addMimeTypeInclusionFilter(todoMimeType);
154 todoFlatModel->setSourceModel(model);
155 todoModel->setSourceModel(todoFlatModel);
156
157 delete todoTreeModel;
158 todoTreeModel = nullptr;
159 } else {
160 delete todoTreeModel;
161 todoTreeModel = new Akonadi::IncidenceTreeModel(QStringList() << todoMimeType, parent);
162 for (TodoView *view : std::as_const(views)) {
163 QObject::connect(todoTreeModel, &Akonadi::IncidenceTreeModel::indexChangedParent, view, &TodoView::expandIndex);
164 QObject::connect(todoTreeModel, &Akonadi::IncidenceTreeModel::batchInsertionFinished, view, &TodoView::restoreViewState);
166 view->setFlatView(flat, /**propagate=*/false); // So other views update their toggle icon
167 }
168 todoTreeModel->setSourceModel(model);
169 todoModel->setSourceModel(todoTreeModel);
170 delete todoFlatModel;
171 todoFlatModel = nullptr;
172 }
173
174 for (TodoView *view : std::as_const(views)) {
175 view->mFlatViewButton->blockSignals(true);
176 // We block signals to avoid recursion, we have two TodoViews and mFlatViewButton is synchronized
177 view->mFlatViewButton->setChecked(flat);
178 view->mFlatViewButton->blockSignals(false);
179 view->mView->setRootIsDecorated(!flat);
180 view->restoreViewState();
181 }
182
183 prefs->setFlatListTodo(flat);
184 prefs->writeConfig();
185 }
186
187 void setModel(QAbstractItemModel *model)
188 {
189 this->model = model;
190 if (todoTreeModel) {
191 todoTreeModel->setSourceModel(this->model);
192 }
193 }
194
195 bool isFlatView() const
196 {
197 return todoFlatModel != nullptr;
198 }
199
200 Akonadi::TodoModel *const todoModel;
201 ColoredTodoProxyModel *const coloredTodoModel;
202 QList<TodoView *> views;
203 QObject *parent = nullptr;
204
205 QAbstractItemModel *model = nullptr;
206 Akonadi::IncidenceTreeModel *todoTreeModel = nullptr;
207 Akonadi::EntityMimeTypeFilterModel *todoFlatModel = nullptr;
208 EventViews::PrefsPtr prefs;
209};
210}
211
212// Don't use K_GLOBAL_STATIC, see QTBUG-22667
213static ModelStack *sModels = nullptr;
214
215TodoView::TodoView(const EventViews::PrefsPtr &prefs, bool sidebarView, QWidget *parent)
216 : EventView(parent)
217 , mCalendarFilterModel(std::make_unique<CalendarFilterModel>())
218 , mTreeStateRestorer(nullptr)
219 , mSidebarView(sidebarView)
220 , mResizeColumnsScheduled(false)
221{
222 mResizeColumnsTimer = new QTimer(this);
223 connect(mResizeColumnsTimer, &QTimer::timeout, this, &TodoView::resizeColumns);
224 mResizeColumnsTimer->setInterval(100ms); // so we don't overdue it when user resizes window manually
225 mResizeColumnsTimer->setSingleShot(true);
226
227 setPreferences(prefs);
228 if (!sModels) {
229 sModels = new ModelStack(prefs, parent);
230 connect(sModels->todoModel, &Akonadi::TodoModel::dropOnSelfRejected, this, []() {
231 KMessageBox::information(nullptr,
232 i18nc("@info", "Cannot move to-do to itself or a child of itself."),
233 i18nc("@title:window", "Drop To-do"),
234 QStringLiteral("NoDropTodoOntoItself"));
235 });
236 }
237 sModels->registerView(this);
238 sModels->setModel(mCalendarFilterModel.get());
239
240 mProxyModel = new TodoViewSortFilterProxyModel(preferences(), this);
241 mProxyModel->setSourceModel(sModels->coloredTodoModel);
242 mProxyModel->setFilterKeyColumn(Akonadi::TodoModel::SummaryColumn);
243 mProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
244 mProxyModel->setSortRole(Qt::EditRole);
245 connect(mProxyModel, &TodoViewSortFilterProxyModel::rowsInserted, this, &TodoView::onRowsInserted);
246
247 if (!mSidebarView) {
248 mQuickSearch = new TodoViewQuickSearch(this);
249 mQuickSearch->setVisible(prefs->enableTodoQuickSearch());
250 connect(mQuickSearch,
251 &TodoViewQuickSearch::searchTextChanged,
252 mProxyModel,
253 qOverload<const QString &>(&QSortFilterProxyModel::setFilterRegularExpression));
254 connect(mQuickSearch, &TodoViewQuickSearch::searchTextChanged, this, &TodoView::restoreViewState);
255 connect(mQuickSearch, &TodoViewQuickSearch::filterCategoryChanged, mProxyModel, &TodoViewSortFilterProxyModel::setCategoryFilter);
256 connect(mQuickSearch, &TodoViewQuickSearch::filterCategoryChanged, this, &TodoView::restoreViewState);
257 connect(mQuickSearch, &TodoViewQuickSearch::filterPriorityChanged, mProxyModel, &TodoViewSortFilterProxyModel::setPriorityFilter);
258 connect(mQuickSearch, &TodoViewQuickSearch::filterPriorityChanged, this, &TodoView::restoreViewState);
259 }
260
261 mView = new TodoViewView(this);
262 mView->setModel(mProxyModel);
263
264 mView->setContextMenuPolicy(Qt::CustomContextMenu);
265
266 mView->setSortingEnabled(true);
267
268 mView->setAutoExpandDelay(250);
269 mView->setDragDropMode(QAbstractItemView::DragDrop);
270
271 mView->setExpandsOnDoubleClick(false);
273
274 connect(mView->header(), &QHeaderView::geometriesChanged, this, &TodoView::scheduleResizeColumns);
275 connect(mView, &TodoViewView::visibleColumnCountChanged, this, &TodoView::resizeColumns);
276
277 auto richTextDelegate = new TodoRichTextDelegate(mView);
278 mView->setItemDelegateForColumn(Akonadi::TodoModel::SummaryColumn, richTextDelegate);
279 mView->setItemDelegateForColumn(Akonadi::TodoModel::DescriptionColumn, richTextDelegate);
280
281 auto priorityDelegate = new TodoPriorityDelegate(mView);
282 mView->setItemDelegateForColumn(Akonadi::TodoModel::PriorityColumn, priorityDelegate);
283
284 auto startDateDelegate = new TodoDueDateDelegate(mView);
285 mView->setItemDelegateForColumn(Akonadi::TodoModel::StartDateColumn, startDateDelegate);
286
287 auto dueDateDelegate = new TodoDueDateDelegate(mView);
288 mView->setItemDelegateForColumn(Akonadi::TodoModel::DueDateColumn, dueDateDelegate);
289
290 auto completeDelegate = new TodoCompleteDelegate(mView);
291 mView->setItemDelegateForColumn(Akonadi::TodoModel::PercentColumn, completeDelegate);
292
293 mCategoriesDelegate = new TodoCategoriesDelegate(mView);
294 mView->setItemDelegateForColumn(Akonadi::TodoModel::CategoriesColumn, mCategoriesDelegate);
295
296 connect(mView, &TodoViewView::customContextMenuRequested, this, &TodoView::contextMenu);
297 connect(mView, &TodoViewView::doubleClicked, this, &TodoView::itemDoubleClicked);
298
299 connect(mView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TodoView::currentChanged);
300
301 mQuickAdd = new TodoViewQuickAddLine(this);
302 mQuickAdd->setClearButtonEnabled(true);
303 mQuickAdd->setVisible(preferences()->enableQuickTodo());
304 mQuickAdd->setToolTip(i18nc("@info:tooltip", "Create an open-ended to-do"));
305 mQuickAdd->setWhatsThis(
306 xi18nc("@info:whatsthis",
307 "Enter the summary for a new to-do. <para><note>The new to-do will be open-ended meaning that it has no start or end times nor "
308 "will it have a reminder or recurrence. Edit the newly created if you want to add more properties.</note></para>"));
309 connect(mQuickAdd, &TodoViewQuickAddLine::returnPressed, this, &TodoView::addQuickTodo);
310
311 mFullViewButton = nullptr;
312 if (!mSidebarView) {
313 mFullViewButton = new QToolButton(this);
314 mFullViewButton->setAutoRaise(true);
315 mFullViewButton->setCheckable(true);
316
317 mFullViewButton->setToolTip(i18nc("@info:tooltip", "Display to-do list in a full window"));
318 mFullViewButton->setWhatsThis(i18nc("@info:whatsthis", "Checking this option will cause the to-do view to use the full window."));
319 }
320 mFlatViewButton = new QToolButton(this);
321 mFlatViewButton->setAutoRaise(true);
322 mFlatViewButton->setCheckable(true);
323 mFlatViewButton->setToolTip(i18nc("@info:tooltip", "Display to-dos in a flat list or a tree"));
324 mFlatViewButton->setWhatsThis(i18nc("@info:whatsthis",
325 "Checking this button will cause the to-dos to be displayed either as a "
326 "flat list or a hierarchical tree where the parental "
327 "relationships are removed."));
328
329 connect(mFlatViewButton, &QToolButton::toggled, this, [this](bool flatView) {
330 setFlatView(flatView, true);
331 });
332 if (mFullViewButton) {
333 connect(mFullViewButton, &QToolButton::toggled, this, &TodoView::setFullView);
334 }
335
336 auto layout = new QGridLayout(this);
337 layout->setContentsMargins({});
338 if (!mSidebarView) {
339 layout->addWidget(mQuickSearch, 0, 0, 1, 2);
340 }
341 layout->addWidget(mView, 1, 0, 1, 2);
342 layout->setRowStretch(1, 1);
343 layout->addWidget(mQuickAdd, 2, 0);
344
345 // Dummy layout just to add a few px of right margin so the checkbox is aligned
346 // with the QAbstractItemView's viewport.
347 auto dummyLayout = new QHBoxLayout();
348 dummyLayout->setContentsMargins(0, 0, mView->frameWidth() /*right*/, 0);
349 if (!mSidebarView) {
350 auto f = new QFrame(this);
351 f->setFrameShape(QFrame::VLine);
352 f->setFrameShadow(QFrame::Sunken);
353 dummyLayout->addWidget(f);
354 dummyLayout->addWidget(mFullViewButton);
355 }
356 dummyLayout->addWidget(mFlatViewButton);
357
358 layout->addLayout(dummyLayout, 2, 1);
359
360 // ---------------- POPUP-MENUS -----------------------
361 mItemPopupMenu = new QMenu(this);
362
363 mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-preview")),
364 i18nc("@action:inmenu show the to-do", "&Show"),
365 this,
366 &TodoView::showTodo);
367
368 QAction *a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-edit")),
369 i18nc("@action:inmenu edit the to-do", "&Edit…"),
370 this,
371 &TodoView::editTodo);
372 mItemPopupMenuReadWriteEntries << a;
373 mItemPopupMenuItemOnlyEntries << a;
374
375 a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")),
376 i18nc("@action:inmenu delete the to-do", "&Delete"),
377 this,
378 &TodoView::deleteTodo);
379 mItemPopupMenuReadWriteEntries << a;
380 mItemPopupMenuItemOnlyEntries << a;
381
382 mItemPopupMenu->addSeparator();
383
384 mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-print")),
385 i18nc("@action:inmenu print the to-do", "&Print…"),
386 this,
387 &TodoView::printTodo);
388
389 mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-print-preview")),
390 i18nc("@action:inmenu print preview the to-do", "Print Previe&w…"),
391 this,
392 &TodoView::printPreviewTodo);
393
394 mItemPopupMenu->addSeparator();
395
396 mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("view-calendar-tasks")),
397 i18nc("@action:inmenu create a new to-do", "New &To-do…"),
398 this,
399 &TodoView::newTodo);
400
401 a = mItemPopupMenu->addAction(i18nc("@action:inmenu create a new sub-to-do", "New Su&b-to-do…"), this, &TodoView::newSubTodo);
402 mItemPopupMenuReadWriteEntries << a;
403 mItemPopupMenuItemOnlyEntries << a;
404
405 mMakeTodoIndependent = mItemPopupMenu->addAction(i18nc("@action:inmenu", "&Make this To-do Independent"), this, &TodoView::unSubTodoSignal);
406
407 mMakeSubtodosIndependent = mItemPopupMenu->addAction(i18nc("@action:inmenu", "Make all Sub-to-dos &Independent"), this, &TodoView::unAllSubTodoSignal);
408
409 mItemPopupMenuItemOnlyEntries << mMakeTodoIndependent;
410 mItemPopupMenuItemOnlyEntries << mMakeSubtodosIndependent;
411
412 mItemPopupMenuReadWriteEntries << mMakeTodoIndependent;
413 mItemPopupMenuReadWriteEntries << mMakeSubtodosIndependent;
414
415 mItemPopupMenu->addSeparator();
416
417 a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("appointment-new")),
418 i18nc("@action:inmenu", "Create Event from To-do"),
419 this,
420 qOverload<>(&TodoView::createEvent));
421 a->setObjectName("createevent"_L1);
422 mItemPopupMenuReadWriteEntries << a;
423 mItemPopupMenuItemOnlyEntries << a;
424
425 mItemPopupMenu->addSeparator();
426
428 mCopyPopupMenu->setTitle(i18nc("@title:menu", "&Copy To"));
429
430 connect(mCopyPopupMenu, &KDatePickerPopup::dateChanged, this, &TodoView::copyTodoToDate);
431
432 connect(mCopyPopupMenu, &KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide);
433
435 mMovePopupMenu->setTitle(i18nc("@title:menu", "&Move To"));
436
437 connect(mMovePopupMenu, &KDatePickerPopup::dateChanged, this, &TodoView::setNewDate);
438 connect(mView->startPopupMenu(), &KDatePickerPopup::dateChanged, this, &TodoView::setStartDate);
439
440 connect(mMovePopupMenu, &KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide);
441
442 mItemPopupMenu->insertMenu(nullptr, mCopyPopupMenu);
443 mItemPopupMenu->insertMenu(nullptr, mMovePopupMenu);
444
445 mItemPopupMenu->addSeparator();
446 mItemPopupMenu->addAction(i18nc("@action:inmenu delete completed to-dos", "Pur&ge Completed"), this, &TodoView::purgeCompletedSignal);
447
448 mPriorityPopupMenu = new QMenu(this);
449 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu unspecified priority", "unspecified"))] = 0;
450 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu highest priority", "1 (highest)"))] = 1;
451 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=2", "2"))] = 2;
452 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=3", "3"))] = 3;
453 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=4", "4"))] = 4;
454 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu medium priority", "5 (medium)"))] = 5;
455 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=6", "6"))] = 6;
456 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=7", "7"))] = 7;
457 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=8", "8"))] = 8;
458 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu lowest priority", "9 (lowest)"))] = 9;
459 connect(mPriorityPopupMenu, &QMenu::triggered, this, &TodoView::setNewPriority);
460
461 mPercentageCompletedPopupMenu = new QMenu(this);
462 for (int i = 0; i <= 100; i += 10) {
463 const QString label = QStringLiteral("%1 %").arg(i);
464 mPercentage[mPercentageCompletedPopupMenu->addAction(label)] = i;
465 }
466 connect(mPercentageCompletedPopupMenu, &QMenu::triggered, this, &TodoView::setNewPercentage);
467
468 setMinimumHeight(50);
469
470 // Initialize our proxy models
471 setFlatView(preferences()->flatListTodo());
472 setFullView(preferences()->fullViewTodo());
473
474 updateConfig();
475}
476
477TodoView::~TodoView()
478{
479 saveViewState();
480
481 sModels->unregisterView(this);
482 if (sModels->views.isEmpty()) {
483 delete sModels;
484 sModels = nullptr;
485 }
486}
487
488void TodoView::expandIndex(const QModelIndex &index)
489{
490 QModelIndex todoModelIndex = sModels->todoModel->mapFromSource(index);
491 Q_ASSERT(todoModelIndex.isValid());
492 const auto coloredIndex = sModels->coloredTodoModel->mapFromSource(todoModelIndex);
493 Q_ASSERT(coloredIndex.isValid());
494 QModelIndex realIndex = mProxyModel->mapFromSource(coloredIndex);
495 Q_ASSERT(realIndex.isValid());
496 while (realIndex.isValid()) {
497 mView->expand(realIndex);
498 realIndex = mProxyModel->parent(realIndex);
499 }
500}
501
502void TodoView::setModel(QAbstractItemModel *model)
503{
504 EventView::setModel(model);
505
506 mCalendarFilterModel->setSourceModel(model);
507 restoreViewState();
508}
509
510void TodoView::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
511{
512 EventView::addCalendar(calendar);
513 mCalendarFilterModel->addCalendar(calendar);
514 if (calendars().size() == 1) {
515 mProxyModel->setCalFilter(calendar->filter());
516 }
517}
518
519void TodoView::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
520{
521 mCalendarFilterModel->removeCalendar(calendar);
522 EventView::removeCalendar(calendar);
523}
524
526{
528 const QModelIndexList selection = mView->selectionModel()->selectedRows();
529 ret.reserve(selection.count());
530 for (const QModelIndex &mi : selection) {
531 ret << mi.data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
532 }
533 return ret;
534}
535
537{
538 // The todo view only lists todo's. It's probably not a good idea to
539 // return something about the selected todo here, because it has got
540 // a couple of dates (creation, due date, completion date), and the
541 // caller could not figure out what he gets. So just return an empty list.
542 return {};
543}
544
545void TodoView::saveLayout(KConfig *config, const QString &group) const
546{
547 KConfigGroup cfgGroup = config->group(group);
548 QHeaderView *header = mView->header();
549
550 QVariantList columnVisibility;
551 QVariantList columnOrder;
552 QVariantList columnWidths;
553 const int headerCount = header->count();
554 columnVisibility.reserve(headerCount);
555 columnWidths.reserve(headerCount);
556 columnOrder.reserve(headerCount);
557 for (int i = 0; i < headerCount; ++i) {
558 columnVisibility << QVariant(!mView->isColumnHidden(i));
559 columnWidths << QVariant(header->sectionSize(i));
560 columnOrder << QVariant(header->visualIndex(i));
561 }
562 cfgGroup.writeEntry("ColumnVisibility", columnVisibility);
563 cfgGroup.writeEntry("ColumnOrder", columnOrder);
564 cfgGroup.writeEntry("ColumnWidths", columnWidths);
565
566 cfgGroup.writeEntry("SortAscending", (int)header->sortIndicatorOrder());
567 if (header->isSortIndicatorShown()) {
568 cfgGroup.writeEntry("SortColumn", header->sortIndicatorSection());
569 } else {
570 cfgGroup.writeEntry("SortColumn", -1);
571 }
572
573 if (!mSidebarView) {
574 preferences()->setFullViewTodo(mFullViewButton->isChecked());
575 }
576 preferences()->setFlatListTodo(mFlatViewButton->isChecked());
577 cfgGroup.writeEntry("FlatView", mFlatViewButton->isChecked());
578}
579
580void TodoView::restoreLayout(KConfig *config, const QString &group, bool minimalDefaults)
581{
582 KConfigGroup cfgGroup = config->group(group);
583 QHeaderView *header = mView->header();
584
585 QVariantList columnVisibility = cfgGroup.readEntry("ColumnVisibility", QVariantList());
586 QVariantList columnOrder = cfgGroup.readEntry("ColumnOrder", QVariantList());
587 QVariantList columnWidths = cfgGroup.readEntry("ColumnWidths", QVariantList());
588
589 if (columnVisibility.isEmpty()) {
590 // if config is empty then use default settings
591 mView->hideColumn(Akonadi::TodoModel::RecurColumn);
592 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn);
593 mView->hideColumn(Akonadi::TodoModel::CalendarColumn);
594 mView->hideColumn(Akonadi::TodoModel::CompletedDateColumn);
595
596 if (minimalDefaults) {
597 mView->hideColumn(Akonadi::TodoModel::PriorityColumn);
598 mView->hideColumn(Akonadi::TodoModel::PercentColumn);
599 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn);
600 mView->hideColumn(Akonadi::TodoModel::CategoriesColumn);
601 }
602
603 // We don't have any incidences (content) yet, so we delay resizing
604 QTimer::singleShot(0, this, &TodoView::resizeColumns);
605 } else {
606 for (int i = 0; i < header->count() && i < columnOrder.size() && i < columnWidths.size() && i < columnVisibility.size(); i++) {
607 bool visible = columnVisibility[i].toBool();
608 int width = columnWidths[i].toInt();
609 int order = columnOrder[i].toInt();
610
611 header->resizeSection(i, width);
612 header->moveSection(header->visualIndex(i), order);
613 if (i != 0 && !visible) {
614 mView->hideColumn(i);
615 }
616 }
617 }
618
619 int sortOrder = cfgGroup.readEntry("SortAscending", (int)Qt::AscendingOrder);
620 int sortColumn = cfgGroup.readEntry("SortColumn", -1);
621 if (sortColumn >= 0) {
622 mView->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
623 }
624
625 mFlatViewButton->setChecked(cfgGroup.readEntry("FlatView", false));
626}
627
628void TodoView::setIncidenceChanger(Akonadi::IncidenceChanger *changer)
629{
631 sModels->todoModel->setIncidenceChanger(changer);
632}
633
634void TodoView::showDates(const QDate &start, const QDate &end, const QDate &)
635{
636 // There is nothing to do here for the Todo View
637 Q_UNUSED(start)
638 Q_UNUSED(end)
639}
640
641void TodoView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date)
642{
643 Q_UNUSED(incidenceList)
644 Q_UNUSED(date)
645}
646
647void TodoView::updateView()
648{
649 if (calendars().empty()) {
650 return;
651 }
652
653 auto calendar = calendars().first();
654 mProxyModel->setCalFilter(calendar->filter());
655}
656
657void TodoView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType)
658{
659 // Don't do anything, model is connected to ETM, it's up to date
660}
661
662void TodoView::updateConfig()
663{
664 Q_ASSERT(preferences());
665 if (!mSidebarView && mQuickSearch) {
666 mQuickSearch->setVisible(preferences()->enableTodoQuickSearch());
667 }
668
669 if (mQuickAdd) {
670 mQuickAdd->setVisible(preferences()->enableQuickTodo());
671 }
672
673 if (mProxyModel) {
674 mProxyModel->invalidate();
675 }
676
677 updateView();
678}
679
680void TodoView::clearSelection()
681{
682 mView->selectionModel()->clearSelection();
683}
684
685void TodoView::addTodo(const QString &summary, const Akonadi::Item &parentItem, const QStringList &categories)
686{
687 const QString summaryTrimmed = summary.trimmed();
688 if (!changer() || summaryTrimmed.isEmpty()) {
689 return;
690 }
691
693
694 KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
695 todo->setSummary(summaryTrimmed);
696 todo->setOrganizer(Person(CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email()));
697
698 todo->setCategories(categories);
699
700 if (parent && !parent->hasRecurrenceId()) {
701 todo->setRelatedTo(parent->uid());
702 }
703
704 /* A todo without a start datetime can't have a reminder so don't bother adding one
705 if (CalendarSupport::KCalPrefs::instance()->defaultTodoReminders()) {
706 KCalendarCore::Alarm::Ptr alarm = todo->newAlarm();
707 CalendarSupport::createAlarmReminder(alarm, todo->type());
708 }
709 */
710
711 // TODO: use the default todo calendar id (once we have one)
712 // Akonadi::Collection collection = Akonadi::EntityTreeModel::updatedCollection(model(), CalendarSupport::KCalPrefs::instance()->defaultCalendarId());
713 Akonadi::Collection collection;
714 // Use the same collection of the parent.
715 if (parentItem.isValid()) {
716 // Don't use parentCollection() since it might be a virtual collection
717 collection = Akonadi::EntityTreeModel::updatedCollection(model(), parentItem.storageCollectionId());
718 }
719
720 changer()->createIncidence(todo, collection, this);
721}
722
723void TodoView::addQuickTodo(Qt::KeyboardModifiers modifiers)
724{
725 if (modifiers == Qt::NoModifier) {
726 /*const QModelIndex index = */
727 addTodo(mQuickAdd->text(), Akonadi::Item(), mProxyModel->categories());
728 } else if (modifiers == Qt::ControlModifier) {
729 QModelIndexList selection = mView->selectionModel()->selectedRows();
730 if (selection.count() != 1) {
731 qCWarning(CALENDARVIEW_LOG) << "No to-do selected" << selection;
732 return;
733 }
734 const QModelIndex idx = mProxyModel->mapToSource(selection[0]);
735 mView->expand(selection[0]);
736 const auto parent = sModels->coloredTodoModel->data(idx, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
737 addTodo(mQuickAdd->text(), parent, mProxyModel->categories());
738 } else {
739 return;
740 }
741 mQuickAdd->setText(QString());
742}
743
744void TodoView::contextMenu(QPoint pos)
745{
746 const bool hasItem = mView->indexAt(pos).isValid();
747 Incidence::Ptr incidencePtr;
748
749 for (QAction *entry : std::as_const(mItemPopupMenuItemOnlyEntries)) {
750 bool enable;
751
752 if (hasItem) {
753 const Akonadi::Item::List incidences = selectedIncidences();
754
755 if (incidences.isEmpty()) {
756 enable = false;
757 } else {
758 Akonadi::Item item = incidences.first();
759 incidencePtr = Akonadi::CalendarUtils::incidence(item);
760
761 // Action isn't RO, it can change the incidence, "Edit" for example.
762 const bool actionIsRw = mItemPopupMenuReadWriteEntries.contains(entry);
763
764 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), item.storageCollectionId());
765 const bool incidenceIsRO = (collection.rights() & Akonadi::Collection::CanChangeItem) == 0;
766
767 enable = hasItem && (!actionIsRw || !incidenceIsRO);
768 }
769 } else {
770 enable = false;
771 }
772
773 entry->setEnabled(enable);
774 }
775 mCopyPopupMenu->setEnabled(hasItem);
776 mMovePopupMenu->setEnabled(hasItem);
777
778 if (hasItem) {
779 if (incidencePtr) {
780 const bool hasRecId = incidencePtr->hasRecurrenceId();
781 const bool hasSubtodos = mView->model()->hasChildren(mView->indexAt(pos));
782
783 mMakeSubtodosIndependent->setEnabled(!hasRecId && hasSubtodos);
784 mMakeTodoIndependent->setEnabled(!hasRecId && !incidencePtr->relatedTo().isEmpty());
785 }
786
787 switch (mView->indexAt(pos).column()) {
788 case Akonadi::TodoModel::PriorityColumn:
789 mPriorityPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
790 break;
791 case Akonadi::TodoModel::PercentColumn:
792 mPercentageCompletedPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
793 break;
794 case Akonadi::TodoModel::StartDateColumn:
795 mView->startPopupMenu()->popup(mView->viewport()->mapToGlobal(pos));
796 break;
797 case Akonadi::TodoModel::DueDateColumn:
798 mMovePopupMenu->popup(mView->viewport()->mapToGlobal(pos));
799 break;
800 case Akonadi::TodoModel::CategoriesColumn:
801 createCategoryPopupMenu()->popup(mView->viewport()->mapToGlobal(pos));
802 break;
803 default:
804 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
805 break;
806 }
807 } else {
808 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
809 }
810}
811
812void TodoView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
813{
814 Q_UNUSED(previous);
815 if (!current.isValid()) {
816 Q_EMIT incidenceSelected(Akonadi::Item(), QDate());
817 return;
818 }
819
820 const auto todoItem = current.data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
821
822 if (selectedIncidenceDates().isEmpty()) {
823 Q_EMIT incidenceSelected(todoItem, QDate());
824 } else {
825 Q_EMIT incidenceSelected(todoItem, selectedIncidenceDates().at(0));
826 }
827}
828
829void TodoView::showTodo()
830{
831 QModelIndexList selection = mView->selectionModel()->selectedRows();
832 if (selection.size() != 1) {
833 return;
834 }
835
836 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
837
838 Q_EMIT showIncidenceSignal(todoItem);
839}
840
841void TodoView::editTodo()
842{
843 QModelIndexList selection = mView->selectionModel()->selectedRows();
844 if (selection.size() != 1) {
845 return;
846 }
847
848 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
849 Q_EMIT editIncidenceSignal(todoItem);
850}
851
852void TodoView::deleteTodo()
853{
854 QModelIndexList selection = mView->selectionModel()->selectedRows();
855 if (selection.size() == 1) {
856 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
857
858 if (!changer()->deletedRecently(todoItem.id())) {
860 }
861 }
862}
863
864void TodoView::newTodo()
865{
866 Q_EMIT newTodoSignal(QDate::currentDate().addDays(7));
867}
868
869void TodoView::newSubTodo()
870{
871 QModelIndexList selection = mView->selectionModel()->selectedRows();
872 if (selection.size() == 1) {
873 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
874
875 Q_EMIT newSubTodoSignal(todoItem);
876 } else {
877 // This never happens
878 qCWarning(CALENDARVIEW_LOG) << "Selection size isn't 1";
879 }
880}
881
882void TodoView::copyTodoToDate(QDate date)
883{
884 if (!changer()) {
885 return;
886 }
887
888 QModelIndexList selection = mView->selectionModel()->selectedRows();
889 if (selection.size() != 1) {
890 return;
891 }
892
893 const QModelIndex origIndex = mProxyModel->mapToSource(selection[0]);
894 Q_ASSERT(origIndex.isValid());
895
896 const auto origItem = sModels->coloredTodoModel->data(origIndex, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
897
899 if (!orig) {
900 return;
901 }
902
903 KCalendarCore::Todo::Ptr todo(orig->clone());
904
906
907 QDateTime due = todo->dtDue();
908 due.setDate(date);
909 todo->setDtDue(due);
910
911 changer()->createIncidence(todo, Akonadi::Collection(), this);
912}
913
914void TodoView::scheduleResizeColumns()
915{
916 mResizeColumnsScheduled = true;
917 mResizeColumnsTimer->start(); // restarts the timer if already active
918}
919
920void TodoView::itemDoubleClicked(const QModelIndex &index)
921{
922 if (index.isValid()) {
923 QModelIndex summary = index.sibling(index.row(), Akonadi::TodoModel::SummaryColumn);
924 if (summary.flags() & Qt::ItemIsEditable) {
925 editTodo();
926 } else {
927 showTodo();
928 }
929 }
930}
931
932QMenu *TodoView::createCategoryPopupMenu()
933{
934 auto tempMenu = new QMenu(this);
935
936 QModelIndexList selection = mView->selectionModel()->selectedRows();
937 if (selection.size() != 1) {
938 return tempMenu;
939 }
940
941 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
943 Q_ASSERT(todo);
944
945 const QStringList checkedCategories = todo->categories();
946
947 auto tagFetchJob = new Akonadi::TagFetchJob(this);
948 connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TodoView::onTagsFetched);
949 tagFetchJob->setProperty("menu", QVariant::fromValue(QPointer<QMenu>(tempMenu)));
950 tagFetchJob->setProperty("checkedCategories", checkedCategories);
951
952 connect(tempMenu, &QMenu::triggered, this, &TodoView::changedCategories);
953 connect(tempMenu, &QMenu::aboutToHide, tempMenu, &QMenu::deleteLater);
954 return tempMenu;
955}
956
957void TodoView::onTagsFetched(KJob *job)
958{
959 if (job->error()) {
960 qCWarning(CALENDARVIEW_LOG) << "Failed to fetch tags " << job->errorString();
961 return;
962 }
963 auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
964 const QStringList checkedCategories = job->property("checkedCategories").toStringList();
965 auto menu = job->property("menu").value<QPointer<QMenu>>();
966 if (menu) {
967 const auto lst = fetchJob->tags();
968 for (const Akonadi::Tag &tag : lst) {
969 const QString name = tag.name();
970 QAction *action = menu->addAction(name);
971 action->setCheckable(true);
972 action->setData(name);
973 if (checkedCategories.contains(name)) {
974 action->setChecked(true);
975 }
976 }
977 }
978}
979
980void TodoView::setNewDate(QDate date)
981{
982 QModelIndexList selection = mView->selectionModel()->selectedRows();
983 if (selection.size() != 1) {
984 return;
985 }
986
987 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
989 Q_ASSERT(todo);
990
991 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
992 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
993 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
994 QDateTime dt(date.startOfDay());
995
996 if (!todo->allDay()) {
997 dt.setTime(todo->dtDue().time());
998 }
999
1000 if (todo->hasStartDate() && dt < todo->dtStart()) {
1001 todo->setDtStart(dt);
1002 }
1003 todo->setDtDue(dt);
1004
1005 changer()->modifyIncidence(todoItem, oldTodo, this);
1006 } else {
1007 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly";
1008 }
1009}
1010
1011void TodoView::setStartDate(QDate date)
1012{
1013 QModelIndexList selection = mView->selectionModel()->selectedRows();
1014 if (selection.size() != 1) {
1015 return;
1016 }
1017
1018 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1020 Q_ASSERT(todo);
1021
1022 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1023 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1024 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1025 QDateTime dt(date.startOfDay());
1026
1027 if (!todo->allDay()) {
1028 dt.setTime(todo->dtStart().time());
1029 }
1030
1031 if (todo->hasDueDate() && dt > todo->dtDue()) {
1032 todo->setDtDue(dt);
1033 }
1034 todo->setDtStart(dt);
1035
1036 changer()->modifyIncidence(todoItem, oldTodo, this);
1037 } else {
1038 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly";
1039 }
1040}
1041
1042void TodoView::setNewPercentage(QAction *action)
1043{
1044 QModelIndexList selection = mView->selectionModel()->selectedRows();
1045 if (selection.size() != 1) {
1046 return;
1047 }
1048
1049 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1051 Q_ASSERT(todo);
1052
1053 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1054 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1055 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1056
1057 int percentage = mPercentage.value(action);
1058 if (percentage == 100) {
1059 todo->setCompleted(QDateTime::currentDateTime());
1060 todo->setPercentComplete(100);
1061 } else {
1062 todo->setPercentComplete(percentage);
1063 }
1064 changer()->modifyIncidence(todoItem, oldTodo, this);
1065 } else {
1066 qCDebug(CALENDARVIEW_LOG) << "Item is read only";
1067 }
1068}
1069
1070void TodoView::setNewPriority(QAction *action)
1071{
1072 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1073 if (selection.size() != 1) {
1074 return;
1075 }
1076 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1078 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1079 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1080 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1081 todo->setPriority(mPriority[action]);
1082
1083 changer()->modifyIncidence(todoItem, oldTodo, this);
1084 }
1085}
1086
1087void TodoView::changedCategories(QAction *action)
1088{
1089 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1090 if (selection.size() != 1) {
1091 return;
1092 }
1093
1094 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1096 Q_ASSERT(todo);
1097 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1098 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1099 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1100
1101 const QString cat = action->data().toString();
1102 QStringList categories = todo->categories();
1103 if (categories.contains(cat)) {
1104 categories.removeAll(cat);
1105 } else {
1106 categories.append(cat);
1107 }
1108 categories.sort();
1109 todo->setCategories(categories);
1110 changer()->modifyIncidence(todoItem, oldTodo, this);
1111 } else {
1112 qCDebug(CALENDARVIEW_LOG) << "No active item, active item is read-only, or locking failed";
1113 }
1114}
1115
1116void TodoView::setFullView(bool fullView)
1117{
1118 if (!mFullViewButton) {
1119 return;
1120 }
1121
1122 mFullViewButton->setChecked(fullView);
1123 if (fullView) {
1124 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-restore")));
1125 } else {
1126 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
1127 }
1128
1129 mFullViewButton->blockSignals(true);
1130 // We block signals to avoid recursion; there are two TodoViews and
1131 // also mFullViewButton is synchronized.
1132 mFullViewButton->setChecked(fullView);
1133 mFullViewButton->blockSignals(false);
1134
1135 preferences()->setFullViewTodo(fullView);
1136 preferences()->writeConfig();
1137
1138 Q_EMIT fullViewChanged(fullView);
1139}
1140
1141void TodoView::setFlatView(bool flatView, bool notifyOtherViews)
1142{
1143 if (flatView) {
1144 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
1145 } else {
1146 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details")));
1147 }
1148
1149 if (notifyOtherViews) {
1150 sModels->setFlatView(flatView);
1151 }
1152}
1153
1154void TodoView::onRowsInserted(const QModelIndex &parent, int start, int end)
1155{
1156 if (start != end || !entityTreeModel()) {
1157 return;
1158 }
1159
1160 QModelIndex idx = mView->model()->index(start, 0);
1161
1162 // If the collection is currently being populated, we don't do anything
1163 QVariant v = idx.data(Akonadi::EntityTreeModel::ItemRole);
1164 if (!v.isValid()) {
1165 return;
1166 }
1167
1168 auto item = v.value<Akonadi::Item>();
1169 if (!item.isValid()) {
1170 return;
1171 }
1172
1173 const bool isPopulated = entityTreeModel()->isCollectionPopulated(item.storageCollectionId());
1174 if (!isPopulated) {
1175 return;
1176 }
1177
1178 // Case #1, adding an item that doesn't have parent: We select it
1179 if (!parent.isValid()) {
1180 QModelIndexList selection = mView->selectionModel()->selectedRows();
1181 if (selection.size() <= 1) {
1182 // don't destroy complex selections, not applicable now (only single
1183 // selection allowed), but for the future...
1184 int colCount = static_cast<int>(Akonadi::TodoModel::ColumnCount);
1185 mView->selectionModel()->select(QItemSelection(idx, mView->model()->index(start, colCount - 1)),
1187 }
1188 return;
1189 }
1190
1191 // Case 2: Adding an item that has a parent: we expand the parent
1192 if (sModels->isFlatView()) {
1193 return;
1194 }
1195
1196 QModelIndex index = parent;
1197 mView->expand(index);
1198 while (index.parent().isValid()) {
1199 mView->expand(index.parent());
1200 index = index.parent();
1201 }
1202}
1203
1204void TodoView::getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals)
1205{
1206 highlightTodos = preferences()->highlightTodos();
1207 highlightEvents = !highlightTodos;
1208 highlightJournals = false;
1209}
1210
1211bool TodoView::usesFullWindow()
1212{
1213 return preferences()->fullViewTodo();
1214}
1215
1216void TodoView::resizeColumns()
1217{
1218 mResizeColumnsScheduled = false;
1219
1220 mView->resizeColumnToContents(Akonadi::TodoModel::StartDateColumn);
1221 mView->resizeColumnToContents(Akonadi::TodoModel::DueDateColumn);
1222 mView->resizeColumnToContents(Akonadi::TodoModel::CompletedDateColumn);
1223 mView->resizeColumnToContents(Akonadi::TodoModel::PriorityColumn);
1224 mView->resizeColumnToContents(Akonadi::TodoModel::CalendarColumn);
1225 mView->resizeColumnToContents(Akonadi::TodoModel::RecurColumn);
1226 mView->resizeColumnToContents(Akonadi::TodoModel::PercentColumn);
1227
1228 // We have 3 columns that should stretch: summary, description and categories.
1229 // Summary is always visible.
1230 const bool descriptionVisible = !mView->isColumnHidden(Akonadi::TodoModel::DescriptionColumn);
1231 const bool categoriesVisible = !mView->isColumnHidden(Akonadi::TodoModel::CategoriesColumn);
1232
1233 // Calculate size of non-stretchable columns:
1234 int size = 0;
1235 for (int i = 0; i < Akonadi::TodoModel::ColumnCount; ++i) {
1236 if (!mView->isColumnHidden(i) && i != Akonadi::TodoModel::SummaryColumn && i != Akonadi::TodoModel::DescriptionColumn
1237 && i != Akonadi::TodoModel::CategoriesColumn) {
1238 size += mView->columnWidth(i);
1239 }
1240 }
1241
1242 // Calculate the remaining space that we have for the stretchable columns
1243 int remainingSize = mView->header()->width() - size;
1244
1245 // 100 for summary, 100 for description
1246 const int requiredSize = descriptionVisible ? 200 : 100;
1247
1248 if (categoriesVisible) {
1249 const int categorySize = 100;
1250 mView->setColumnWidth(Akonadi::TodoModel::CategoriesColumn, categorySize);
1251 remainingSize -= categorySize;
1252 }
1253
1254 if (remainingSize < requiredSize) {
1255 // Too little size, so let's use a horizontal scrollbar
1256 mView->resizeColumnToContents(Akonadi::TodoModel::SummaryColumn);
1257 mView->resizeColumnToContents(Akonadi::TodoModel::DescriptionColumn);
1258 } else if (descriptionVisible) {
1259 mView->setColumnWidth(Akonadi::TodoModel::SummaryColumn, remainingSize / 2);
1260 mView->setColumnWidth(Akonadi::TodoModel::DescriptionColumn, remainingSize / 2);
1261 } else {
1262 mView->setColumnWidth(Akonadi::TodoModel::SummaryColumn, remainingSize);
1263 }
1264}
1265
1266void TodoView::restoreViewState()
1267{
1268 if (sModels->isFlatView()) {
1269 return;
1270 }
1271
1272 if (sModels->todoTreeModel && !sModels->todoTreeModel->sourceModel()) {
1273 return;
1274 }
1275
1276 // QElapsedTimer timer;
1277 // timer.start();
1278 delete mTreeStateRestorer;
1279 mTreeStateRestorer = new Akonadi::ETMViewStateSaver();
1280 KSharedConfig::Ptr config = KSharedConfig::openConfig();
1281 KConfigGroup group(config, stateSaverGroup());
1282 mTreeStateRestorer->setView(mView);
1283 mTreeStateRestorer->restoreState(group);
1284 // qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed();
1285}
1286
1287QString TodoView::stateSaverGroup() const
1288{
1289 QString str = QStringLiteral("TodoTreeViewState");
1290 if (mSidebarView) {
1291 str += QLatin1Char('S');
1292 }
1293
1294 return str;
1295}
1296
1297void TodoView::saveViewState()
1298{
1299 Akonadi::ETMViewStateSaver treeStateSaver;
1300 KConfigGroup group(preferences()->config(), stateSaverGroup());
1301 treeStateSaver.setView(mView);
1302 treeStateSaver.saveState(group);
1303}
1304
1305void TodoView::resizeEvent(QResizeEvent *event)
1306{
1308 scheduleResizeColumns();
1309}
1310
1311void TodoView::createEvent()
1312{
1313 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1314 if (selection.size() != 1) {
1315 return;
1316 }
1317
1318 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1319
1320 Q_EMIT createEvent(todoItem);
1321}
1322
1323#include "todoview.moc"
1324
1325#include "moc_todoview.cpp"
Rights rights() const
static Collection updatedCollection(const QAbstractItemModel *model, qint64 collectionId)
void indexChangedParent(const QModelIndex &index)
Collection::Id storageCollectionId() const
bool isValid() const
QList< Item > List
EventView is the abstract base class from which all other calendar views for event data are derived.
Definition eventview.h:69
void showIncidenceSignal(const Akonadi::Item &)
instructs the receiver to show the incidence in read-only mode.
void deleteIncidenceSignal(const Akonadi::Item &)
instructs the receiver to delete the Incidence in some manner; some possibilities include automatical...
void editIncidenceSignal(const Akonadi::Item &)
instructs the receiver to begin editing the incidence specified in some manner.
virtual void setIncidenceChanger(Akonadi::IncidenceChanger *changer)
Assign a new incidence change helper object.
void getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals)
documentation in baseview.h
Akonadi::Item::List selectedIncidences() const override
Definition todoview.cpp:525
KCalendarCore::DateList selectedIncidenceDates() const override
Returns a list of the dates of selected events.
Definition todoview.cpp:536
static QString createUniqueId()
QSharedPointer< Calendar > Ptr
QSharedPointer< Incidence > Ptr
QSharedPointer< Todo > Ptr
KConfigGroup group(const QString &group)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
void dateChanged(const QDate &date)
virtual QString errorString() const
int error() const
void result(KJob *job)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
This delegate is responsible for displaying the categories of todos.
This delegate is responsible for displaying progress bars for the completion status of indivitual tod...
This delegate is responsible for displaying the due date of todos.
This delegate is responsible for displaying the priority of todos.
This delegate is responsible for displaying possible rich text elements of a todo.
Q_SCRIPTABLE Q_NOREPLY void start()
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Todo::Ptr todo(const Akonadi::Item &item)
QString fullName(const PartType &type)
Namespace EventViews provides facilities for displaying incidences, including events,...
Definition agenda.h:33
QList< QDate > DateList
QString name(StandardAction id)
QString label(StandardShortcut id)
const QList< QKeySequence > & preferences()
void setChecked(bool)
void toggled(bool checked)
QAbstractItemModel(QObject *parent)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual QModelIndex parent(const QModelIndex &index) const const=0
void rowsInserted(const QModelIndex &parent, int first, int last)
void doubleClicked(const QModelIndex &index)
void setDragDropMode(DragDropMode behavior)
void setCheckable(bool)
void setChecked(bool)
QVariant data() const const
void setData(const QVariant &data)
QDate currentDate()
QDateTime startOfDay() const const
QDateTime currentDateTime()
void setDate(QDate date)
int count() const const
void geometriesChanged()
void moveSection(int from, int to)
void resizeSection(int logicalIndex, int size)
int sectionSize(int logicalIndex) const const
bool isSortIndicatorShown() const const
Qt::SortOrder sortIndicatorOrder() const const
int sortIndicatorSection() const const
int visualIndex(int logicalIndex) const const
QIcon fromTheme(const QString &name)
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
void append(QList< T > &&value)
pointer data()
T & first()
bool isEmpty() const const
qsizetype removeAll(const AT &t)
void reserve(qsizetype size)
void aboutToHide()
void triggered(QAction *action)
QVariant data(int role) const const
Qt::ItemFlags flags() const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QObject(QObject *parent)
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * parent() const const
QVariant property(const char *name) const const
void setObjectName(QAnyStringView name)
QSortFilterProxyModel(QObject *parent)
void setFilterRegularExpression(const QRegularExpression &regularExpression)
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
QString arg(Args &&... args) const const
bool isEmpty() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
void sort(Qt::CaseSensitivity cs)
CaseInsensitive
CustomContextMenu
EditRole
ItemIsEditable
typedef KeyboardModifiers
AscendingOrder
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
int columnWidth(int column) const const
QHeaderView * header() const const
bool isColumnHidden(int column) const const
void resizeColumnToContents(int column)
void setRootIsDecorated(bool show)
QVariant fromValue(T &&value)
bool isValid() const const
QString toString() const const
QStringList toStringList() const const
T value() const const
void customContextMenuRequested(const QPoint &pos)
virtual bool event(QEvent *event) override
void hide()
virtual void resizeEvent(QResizeEvent *event)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 11:51:37 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.