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}
578
579void TodoView::restoreLayout(KConfig *config, const QString &group, bool minimalDefaults)
580{
581 KConfigGroup cfgGroup = config->group(group);
582 QHeaderView *header = mView->header();
583
584 QVariantList columnVisibility = cfgGroup.readEntry("ColumnVisibility", QVariantList());
585 QVariantList columnOrder = cfgGroup.readEntry("ColumnOrder", QVariantList());
586 QVariantList columnWidths = cfgGroup.readEntry("ColumnWidths", QVariantList());
587
588 if (columnVisibility.isEmpty()) {
589 // if config is empty then use default settings
590 mView->hideColumn(Akonadi::TodoModel::RecurColumn);
591 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn);
592 mView->hideColumn(Akonadi::TodoModel::CalendarColumn);
593 mView->hideColumn(Akonadi::TodoModel::CompletedDateColumn);
594
595 if (minimalDefaults) {
596 mView->hideColumn(Akonadi::TodoModel::PriorityColumn);
597 mView->hideColumn(Akonadi::TodoModel::PercentColumn);
598 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn);
599 mView->hideColumn(Akonadi::TodoModel::CategoriesColumn);
600 }
601
602 // We don't have any incidences (content) yet, so we delay resizing
603 QTimer::singleShot(0, this, &TodoView::resizeColumns);
604 } else {
605 for (int i = 0; i < header->count() && i < columnOrder.size() && i < columnWidths.size() && i < columnVisibility.size(); i++) {
606 bool visible = columnVisibility[i].toBool();
607 int width = columnWidths[i].toInt();
608 int order = columnOrder[i].toInt();
609
610 header->resizeSection(i, width);
611 header->moveSection(header->visualIndex(i), order);
612 if (i != 0 && !visible) {
613 mView->hideColumn(i);
614 }
615 }
616 }
617
618 int sortOrder = cfgGroup.readEntry("SortAscending", (int)Qt::AscendingOrder);
619 int sortColumn = cfgGroup.readEntry("SortColumn", -1);
620 if (sortColumn >= 0) {
621 mView->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
622 }
623
624 mFlatViewButton->setChecked(cfgGroup.readEntry("FlatView", false));
625}
626
627void TodoView::setIncidenceChanger(Akonadi::IncidenceChanger *changer)
628{
630 sModels->todoModel->setIncidenceChanger(changer);
631}
632
633void TodoView::showDates(const QDate &start, const QDate &end, const QDate &)
634{
635 // There is nothing to do here for the Todo View
636 Q_UNUSED(start)
637 Q_UNUSED(end)
638}
639
640void TodoView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date)
641{
642 Q_UNUSED(incidenceList)
643 Q_UNUSED(date)
644}
645
646void TodoView::updateView()
647{
648 if (calendars().empty()) {
649 return;
650 }
651
652 auto calendar = calendars().first();
653 mProxyModel->setCalFilter(calendar->filter());
654}
655
656void TodoView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType)
657{
658 // Don't do anything, model is connected to ETM, it's up to date
659}
660
661void TodoView::updateConfig()
662{
663 Q_ASSERT(preferences());
664 if (!mSidebarView && mQuickSearch) {
665 mQuickSearch->setVisible(preferences()->enableTodoQuickSearch());
666 }
667
668 if (mQuickAdd) {
669 mQuickAdd->setVisible(preferences()->enableQuickTodo());
670 }
671
672 if (mProxyModel) {
673 mProxyModel->invalidate();
674 }
675
676 updateView();
677}
678
679void TodoView::clearSelection()
680{
681 mView->selectionModel()->clearSelection();
682}
683
684void TodoView::addTodo(const QString &summary, const Akonadi::Item &parentItem, const QStringList &categories)
685{
686 const QString summaryTrimmed = summary.trimmed();
687 if (!changer() || summaryTrimmed.isEmpty()) {
688 return;
689 }
690
692
693 KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
694 todo->setSummary(summaryTrimmed);
695 todo->setOrganizer(Person(CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email()));
696
697 todo->setCategories(categories);
698
699 if (parent && !parent->hasRecurrenceId()) {
700 todo->setRelatedTo(parent->uid());
701 }
702
703 /* A todo without a start datetime can't have a reminder so don't bother adding one
704 if (CalendarSupport::KCalPrefs::instance()->defaultTodoReminders()) {
705 KCalendarCore::Alarm::Ptr alarm = todo->newAlarm();
706 CalendarSupport::createAlarmReminder(alarm, todo->type());
707 }
708 */
709
710 // TODO: use the default todo calendar id (once we have one)
711 // Akonadi::Collection collection = Akonadi::EntityTreeModel::updatedCollection(model(), CalendarSupport::KCalPrefs::instance()->defaultCalendarId());
712 Akonadi::Collection collection;
713 // Use the same collection of the parent.
714 if (parentItem.isValid()) {
715 // Don't use parentCollection() since it might be a virtual collection
716 collection = Akonadi::EntityTreeModel::updatedCollection(model(), parentItem.storageCollectionId());
717 }
718
719 changer()->createIncidence(todo, collection, this);
720}
721
722void TodoView::addQuickTodo(Qt::KeyboardModifiers modifiers)
723{
724 if (modifiers == Qt::NoModifier) {
725 /*const QModelIndex index = */
726 addTodo(mQuickAdd->text(), Akonadi::Item(), mProxyModel->categories());
727 } else if (modifiers == Qt::ControlModifier) {
728 QModelIndexList selection = mView->selectionModel()->selectedRows();
729 if (selection.count() != 1) {
730 qCWarning(CALENDARVIEW_LOG) << "No to-do selected" << selection;
731 return;
732 }
733 const QModelIndex idx = mProxyModel->mapToSource(selection[0]);
734 mView->expand(selection[0]);
735 const auto parent = sModels->coloredTodoModel->data(idx, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
736 addTodo(mQuickAdd->text(), parent, mProxyModel->categories());
737 } else {
738 return;
739 }
740 mQuickAdd->setText(QString());
741}
742
743void TodoView::contextMenu(QPoint pos)
744{
745 const bool hasItem = mView->indexAt(pos).isValid();
746 Incidence::Ptr incidencePtr;
747
748 for (QAction *entry : std::as_const(mItemPopupMenuItemOnlyEntries)) {
749 bool enable;
750
751 if (hasItem) {
752 const Akonadi::Item::List incidences = selectedIncidences();
753
754 if (incidences.isEmpty()) {
755 enable = false;
756 } else {
757 Akonadi::Item item = incidences.first();
758 incidencePtr = Akonadi::CalendarUtils::incidence(item);
759
760 // Action isn't RO, it can change the incidence, "Edit" for example.
761 const bool actionIsRw = mItemPopupMenuReadWriteEntries.contains(entry);
762
763 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), item.storageCollectionId());
764 const bool incidenceIsRO = (collection.rights() & Akonadi::Collection::CanChangeItem) == 0;
765
766 enable = hasItem && (!actionIsRw || !incidenceIsRO);
767 }
768 } else {
769 enable = false;
770 }
771
772 entry->setEnabled(enable);
773 }
774 mCopyPopupMenu->setEnabled(hasItem);
775 mMovePopupMenu->setEnabled(hasItem);
776
777 if (hasItem) {
778 if (incidencePtr) {
779 const bool hasRecId = incidencePtr->hasRecurrenceId();
780 const bool hasSubtodos = mView->model()->hasChildren(mView->indexAt(pos));
781
782 mMakeSubtodosIndependent->setEnabled(!hasRecId && hasSubtodos);
783 mMakeTodoIndependent->setEnabled(!hasRecId && !incidencePtr->relatedTo().isEmpty());
784 }
785
786 switch (mView->indexAt(pos).column()) {
787 case Akonadi::TodoModel::PriorityColumn:
788 mPriorityPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
789 break;
790 case Akonadi::TodoModel::PercentColumn:
791 mPercentageCompletedPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
792 break;
793 case Akonadi::TodoModel::StartDateColumn:
794 mView->startPopupMenu()->popup(mView->viewport()->mapToGlobal(pos));
795 break;
796 case Akonadi::TodoModel::DueDateColumn:
797 mMovePopupMenu->popup(mView->viewport()->mapToGlobal(pos));
798 break;
799 case Akonadi::TodoModel::CategoriesColumn:
800 createCategoryPopupMenu()->popup(mView->viewport()->mapToGlobal(pos));
801 break;
802 default:
803 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
804 break;
805 }
806 } else {
807 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
808 }
809}
810
811void TodoView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
812{
813 Q_UNUSED(previous);
814 if (!current.isValid()) {
815 Q_EMIT incidenceSelected(Akonadi::Item(), QDate());
816 return;
817 }
818
819 const auto todoItem = current.data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
820
821 if (selectedIncidenceDates().isEmpty()) {
822 Q_EMIT incidenceSelected(todoItem, QDate());
823 } else {
824 Q_EMIT incidenceSelected(todoItem, selectedIncidenceDates().at(0));
825 }
826}
827
828void TodoView::showTodo()
829{
830 QModelIndexList selection = mView->selectionModel()->selectedRows();
831 if (selection.size() != 1) {
832 return;
833 }
834
835 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
836
837 Q_EMIT showIncidenceSignal(todoItem);
838}
839
840void TodoView::editTodo()
841{
842 QModelIndexList selection = mView->selectionModel()->selectedRows();
843 if (selection.size() != 1) {
844 return;
845 }
846
847 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
848 Q_EMIT editIncidenceSignal(todoItem);
849}
850
851void TodoView::deleteTodo()
852{
853 QModelIndexList selection = mView->selectionModel()->selectedRows();
854 if (selection.size() == 1) {
855 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
856
857 if (!changer()->deletedRecently(todoItem.id())) {
859 }
860 }
861}
862
863void TodoView::newTodo()
864{
865 Q_EMIT newTodoSignal(QDate::currentDate().addDays(7));
866}
867
868void TodoView::newSubTodo()
869{
870 QModelIndexList selection = mView->selectionModel()->selectedRows();
871 if (selection.size() == 1) {
872 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
873
874 Q_EMIT newSubTodoSignal(todoItem);
875 } else {
876 // This never happens
877 qCWarning(CALENDARVIEW_LOG) << "Selection size isn't 1";
878 }
879}
880
881void TodoView::copyTodoToDate(QDate date)
882{
883 if (!changer()) {
884 return;
885 }
886
887 QModelIndexList selection = mView->selectionModel()->selectedRows();
888 if (selection.size() != 1) {
889 return;
890 }
891
892 const QModelIndex origIndex = mProxyModel->mapToSource(selection[0]);
893 Q_ASSERT(origIndex.isValid());
894
895 const auto origItem = sModels->coloredTodoModel->data(origIndex, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
896
898 if (!orig) {
899 return;
900 }
901
902 KCalendarCore::Todo::Ptr todo(orig->clone());
903
905
906 QDateTime due = todo->dtDue();
907 due.setDate(date);
908 todo->setDtDue(due);
909
910 changer()->createIncidence(todo, Akonadi::Collection(), this);
911}
912
913void TodoView::scheduleResizeColumns()
914{
915 mResizeColumnsScheduled = true;
916 mResizeColumnsTimer->start(); // restarts the timer if already active
917}
918
919void TodoView::itemDoubleClicked(const QModelIndex &index)
920{
921 if (index.isValid()) {
922 QModelIndex summary = index.sibling(index.row(), Akonadi::TodoModel::SummaryColumn);
923 if (summary.flags() & Qt::ItemIsEditable) {
924 editTodo();
925 } else {
926 showTodo();
927 }
928 }
929}
930
931QMenu *TodoView::createCategoryPopupMenu()
932{
933 auto tempMenu = new QMenu(this);
934
935 QModelIndexList selection = mView->selectionModel()->selectedRows();
936 if (selection.size() != 1) {
937 return tempMenu;
938 }
939
940 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
942 Q_ASSERT(todo);
943
944 const QStringList checkedCategories = todo->categories();
945
946 auto tagFetchJob = new Akonadi::TagFetchJob(this);
947 connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TodoView::onTagsFetched);
948 tagFetchJob->setProperty("menu", QVariant::fromValue(QPointer<QMenu>(tempMenu)));
949 tagFetchJob->setProperty("checkedCategories", checkedCategories);
950
951 connect(tempMenu, &QMenu::triggered, this, &TodoView::changedCategories);
952 connect(tempMenu, &QMenu::aboutToHide, tempMenu, &QMenu::deleteLater);
953 return tempMenu;
954}
955
956void TodoView::onTagsFetched(KJob *job)
957{
958 if (job->error()) {
959 qCWarning(CALENDARVIEW_LOG) << "Failed to fetch tags " << job->errorString();
960 return;
961 }
962 auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
963 const QStringList checkedCategories = job->property("checkedCategories").toStringList();
964 auto menu = job->property("menu").value<QPointer<QMenu>>();
965 if (menu) {
966 const auto lst = fetchJob->tags();
967 for (const Akonadi::Tag &tag : lst) {
968 const QString name = tag.name();
969 QAction *action = menu->addAction(name);
970 action->setCheckable(true);
971 action->setData(name);
972 if (checkedCategories.contains(name)) {
973 action->setChecked(true);
974 }
975 }
976 }
977}
978
979void TodoView::setNewDate(QDate date)
980{
981 QModelIndexList selection = mView->selectionModel()->selectedRows();
982 if (selection.size() != 1) {
983 return;
984 }
985
986 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
988 Q_ASSERT(todo);
989
990 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
991 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
992 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
993 QDateTime dt(date.startOfDay());
994
995 if (!todo->allDay()) {
996 dt.setTime(todo->dtDue().time());
997 }
998
999 if (todo->hasStartDate() && dt < todo->dtStart()) {
1000 todo->setDtStart(dt);
1001 }
1002 todo->setDtDue(dt);
1003
1004 changer()->modifyIncidence(todoItem, oldTodo, this);
1005 } else {
1006 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly";
1007 }
1008}
1009
1010void TodoView::setStartDate(QDate date)
1011{
1012 QModelIndexList selection = mView->selectionModel()->selectedRows();
1013 if (selection.size() != 1) {
1014 return;
1015 }
1016
1017 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1019 Q_ASSERT(todo);
1020
1021 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1022 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1023 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1024 QDateTime dt(date.startOfDay());
1025
1026 if (!todo->allDay()) {
1027 dt.setTime(todo->dtStart().time());
1028 }
1029
1030 if (todo->hasDueDate() && dt > todo->dtDue()) {
1031 todo->setDtDue(dt);
1032 }
1033 todo->setDtStart(dt);
1034
1035 changer()->modifyIncidence(todoItem, oldTodo, this);
1036 } else {
1037 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly";
1038 }
1039}
1040
1041void TodoView::setNewPercentage(QAction *action)
1042{
1043 QModelIndexList selection = mView->selectionModel()->selectedRows();
1044 if (selection.size() != 1) {
1045 return;
1046 }
1047
1048 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1050 Q_ASSERT(todo);
1051
1052 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1053 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1054 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1055
1056 int percentage = mPercentage.value(action);
1057 if (percentage == 100) {
1058 todo->setCompleted(QDateTime::currentDateTime());
1059 todo->setPercentComplete(100);
1060 } else {
1061 todo->setPercentComplete(percentage);
1062 }
1063 changer()->modifyIncidence(todoItem, oldTodo, this);
1064 } else {
1065 qCDebug(CALENDARVIEW_LOG) << "Item is read only";
1066 }
1067}
1068
1069void TodoView::setNewPriority(QAction *action)
1070{
1071 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1072 if (selection.size() != 1) {
1073 return;
1074 }
1075 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1077 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1078 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1079 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1080 todo->setPriority(mPriority[action]);
1081
1082 changer()->modifyIncidence(todoItem, oldTodo, this);
1083 }
1084}
1085
1086void TodoView::changedCategories(QAction *action)
1087{
1088 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1089 if (selection.size() != 1) {
1090 return;
1091 }
1092
1093 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1095 Q_ASSERT(todo);
1096 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1097 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1098 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1099
1100 const QString cat = action->data().toString();
1101 QStringList categories = todo->categories();
1102 if (categories.contains(cat)) {
1103 categories.removeAll(cat);
1104 } else {
1105 categories.append(cat);
1106 }
1107 categories.sort();
1108 todo->setCategories(categories);
1109 changer()->modifyIncidence(todoItem, oldTodo, this);
1110 } else {
1111 qCDebug(CALENDARVIEW_LOG) << "No active item, active item is read-only, or locking failed";
1112 }
1113}
1114
1115void TodoView::setFullView(bool fullView)
1116{
1117 if (!mFullViewButton) {
1118 return;
1119 }
1120
1121 mFullViewButton->setChecked(fullView);
1122 if (fullView) {
1123 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-restore")));
1124 } else {
1125 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
1126 }
1127
1128 mFullViewButton->blockSignals(true);
1129 // We block signals to avoid recursion; there are two TodoViews and
1130 // also mFullViewButton is synchronized.
1131 mFullViewButton->setChecked(fullView);
1132 mFullViewButton->blockSignals(false);
1133
1134 preferences()->setFullViewTodo(fullView);
1135 preferences()->writeConfig();
1136
1137 Q_EMIT fullViewChanged(fullView);
1138}
1139
1140void TodoView::setFlatView(bool flatView, bool notifyOtherViews)
1141{
1142 if (flatView) {
1143 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
1144 } else {
1145 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details")));
1146 }
1147
1148 if (notifyOtherViews) {
1149 sModels->setFlatView(flatView);
1150 }
1151}
1152
1153void TodoView::onRowsInserted(const QModelIndex &parent, int start, int end)
1154{
1155 if (start != end || !entityTreeModel()) {
1156 return;
1157 }
1158
1159 QModelIndex idx = mView->model()->index(start, 0);
1160
1161 // If the collection is currently being populated, we don't do anything
1162 QVariant v = idx.data(Akonadi::EntityTreeModel::ItemRole);
1163 if (!v.isValid()) {
1164 return;
1165 }
1166
1167 auto item = v.value<Akonadi::Item>();
1168 if (!item.isValid()) {
1169 return;
1170 }
1171
1172 const bool isPopulated = entityTreeModel()->isCollectionPopulated(item.storageCollectionId());
1173 if (!isPopulated) {
1174 return;
1175 }
1176
1177 // Case #1, adding an item that doesn't have parent: We select it
1178 if (!parent.isValid()) {
1179 QModelIndexList selection = mView->selectionModel()->selectedRows();
1180 if (selection.size() <= 1) {
1181 // don't destroy complex selections, not applicable now (only single
1182 // selection allowed), but for the future...
1183 int colCount = static_cast<int>(Akonadi::TodoModel::ColumnCount);
1184 mView->selectionModel()->select(QItemSelection(idx, mView->model()->index(start, colCount - 1)),
1186 }
1187 return;
1188 }
1189
1190 // Case 2: Adding an item that has a parent: we expand the parent
1191 if (sModels->isFlatView()) {
1192 return;
1193 }
1194
1195 QModelIndex index = parent;
1196 mView->expand(index);
1197 while (index.parent().isValid()) {
1198 mView->expand(index.parent());
1199 index = index.parent();
1200 }
1201}
1202
1203void TodoView::getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals)
1204{
1205 highlightTodos = preferences()->highlightTodos();
1206 highlightEvents = !highlightTodos;
1207 highlightJournals = false;
1208}
1209
1210bool TodoView::usesFullWindow()
1211{
1212 return preferences()->fullViewTodo();
1213}
1214
1215void TodoView::resizeColumns()
1216{
1217 mResizeColumnsScheduled = false;
1218
1219 mView->resizeColumnToContents(Akonadi::TodoModel::StartDateColumn);
1220 mView->resizeColumnToContents(Akonadi::TodoModel::DueDateColumn);
1221 mView->resizeColumnToContents(Akonadi::TodoModel::CompletedDateColumn);
1222 mView->resizeColumnToContents(Akonadi::TodoModel::PriorityColumn);
1223 mView->resizeColumnToContents(Akonadi::TodoModel::CalendarColumn);
1224 mView->resizeColumnToContents(Akonadi::TodoModel::RecurColumn);
1225 mView->resizeColumnToContents(Akonadi::TodoModel::PercentColumn);
1226
1227 // We have 3 columns that should stretch: summary, description and categories.
1228 // Summary is always visible.
1229 const bool descriptionVisible = !mView->isColumnHidden(Akonadi::TodoModel::DescriptionColumn);
1230 const bool categoriesVisible = !mView->isColumnHidden(Akonadi::TodoModel::CategoriesColumn);
1231
1232 // Calculate size of non-stretchable columns:
1233 int size = 0;
1234 for (int i = 0; i < Akonadi::TodoModel::ColumnCount; ++i) {
1235 if (!mView->isColumnHidden(i) && i != Akonadi::TodoModel::SummaryColumn && i != Akonadi::TodoModel::DescriptionColumn
1236 && i != Akonadi::TodoModel::CategoriesColumn) {
1237 size += mView->columnWidth(i);
1238 }
1239 }
1240
1241 // Calculate the remaining space that we have for the stretchable columns
1242 int remainingSize = mView->header()->width() - size;
1243
1244 // 100 for summary, 100 for description
1245 const int requiredSize = descriptionVisible ? 200 : 100;
1246
1247 if (categoriesVisible) {
1248 const int categorySize = 100;
1249 mView->setColumnWidth(Akonadi::TodoModel::CategoriesColumn, categorySize);
1250 remainingSize -= categorySize;
1251 }
1252
1253 if (remainingSize < requiredSize) {
1254 // Too little size, so let's use a horizontal scrollbar
1255 mView->resizeColumnToContents(Akonadi::TodoModel::SummaryColumn);
1256 mView->resizeColumnToContents(Akonadi::TodoModel::DescriptionColumn);
1257 } else if (descriptionVisible) {
1258 mView->setColumnWidth(Akonadi::TodoModel::SummaryColumn, remainingSize / 2);
1259 mView->setColumnWidth(Akonadi::TodoModel::DescriptionColumn, remainingSize / 2);
1260 } else {
1261 mView->setColumnWidth(Akonadi::TodoModel::SummaryColumn, remainingSize);
1262 }
1263}
1264
1265void TodoView::restoreViewState()
1266{
1267 if (sModels->isFlatView()) {
1268 return;
1269 }
1270
1271 if (sModels->todoTreeModel && !sModels->todoTreeModel->sourceModel()) {
1272 return;
1273 }
1274
1275 // QElapsedTimer timer;
1276 // timer.start();
1277 delete mTreeStateRestorer;
1278 mTreeStateRestorer = new Akonadi::ETMViewStateSaver();
1279 KSharedConfig::Ptr config = KSharedConfig::openConfig();
1280 KConfigGroup group(config, stateSaverGroup());
1281 mTreeStateRestorer->setView(mView);
1282 mTreeStateRestorer->restoreState(group);
1283 // qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed();
1284}
1285
1286QString TodoView::stateSaverGroup() const
1287{
1288 QString str = QStringLiteral("TodoTreeViewState");
1289 if (mSidebarView) {
1290 str += QLatin1Char('S');
1291 }
1292
1293 return str;
1294}
1295
1296void TodoView::saveViewState()
1297{
1298 Akonadi::ETMViewStateSaver treeStateSaver;
1299 KConfigGroup group(preferences()->config(), stateSaverGroup());
1300 treeStateSaver.setView(mView);
1301 treeStateSaver.saveState(group);
1302}
1303
1304void TodoView::resizeEvent(QResizeEvent *event)
1305{
1307 scheduleResizeColumns();
1308}
1309
1310void TodoView::createEvent()
1311{
1312 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1313 if (selection.size() != 1) {
1314 return;
1315 }
1316
1317 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1318
1319 Q_EMIT createEvent(todoItem);
1320}
1321
1322#include "todoview.moc"
1323
1324#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 Apr 25 2025 11:46:21 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.