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

KDE's Doxygen guidelines are available online.