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;
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 , mQuickSearch(nullptr)
219 , mQuickAdd(nullptr)
220 , mTreeStateRestorer(nullptr)
221 , mSidebarView(sidebarView)
222 , mResizeColumnsScheduled(false)
223{
224 mResizeColumnsTimer = new QTimer(this);
225 connect(mResizeColumnsTimer, &QTimer::timeout, this, &TodoView::resizeColumns);
226 mResizeColumnsTimer->setInterval(100ms); // so we don't overdue it when user resizes window manually
227 mResizeColumnsTimer->setSingleShot(true);
228
229 setPreferences(prefs);
230 if (!sModels) {
231 sModels = new ModelStack(prefs, parent);
232 connect(sModels->todoModel, &Akonadi::TodoModel::dropOnSelfRejected, this, []() {
233 KMessageBox::information(nullptr,
234 i18n("Cannot move to-do to itself or a child of itself."),
235 i18nc("@title:window", "Drop To-do"),
236 QStringLiteral("NoDropTodoOntoItself"));
237 });
238 }
239 sModels->registerView(this);
240 sModels->setModel(mCalendarFilterModel.get());
241
242 mProxyModel = new TodoViewSortFilterProxyModel(preferences(), this);
243 mProxyModel->setSourceModel(sModels->coloredTodoModel);
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::currentChanged, this, &TodoView::currentChanged);
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({});
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("createevent"_L1);
419 mItemPopupMenuReadWriteEntries << a;
420 mItemPopupMenuItemOnlyEntries << a;
421
422 mItemPopupMenu->addSeparator();
423
425 mCopyPopupMenu->setTitle(i18nc("@title:menu", "&Copy To"));
426
427 connect(mCopyPopupMenu, &KDatePickerPopup::dateChanged, this, &TodoView::copyTodoToDate);
428
429 connect(mCopyPopupMenu, &KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide);
430
432 mMovePopupMenu->setTitle(i18nc("@title:menu", "&Move To"));
433
434 connect(mMovePopupMenu, &KDatePickerPopup::dateChanged, this, &TodoView::setNewDate);
435 connect(mView->startPopupMenu(), &KDatePickerPopup::dateChanged, this, &TodoView::setStartDate);
436
437 connect(mMovePopupMenu, &KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide);
438
439 mItemPopupMenu->insertMenu(nullptr, mCopyPopupMenu);
440 mItemPopupMenu->insertMenu(nullptr, mMovePopupMenu);
441
442 mItemPopupMenu->addSeparator();
443 mItemPopupMenu->addAction(i18nc("@action:inmenu delete completed to-dos", "Pur&ge Completed"), this, &TodoView::purgeCompletedSignal);
444
445 mPriorityPopupMenu = new QMenu(this);
446 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu unspecified priority", "unspecified"))] = 0;
447 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu highest priority", "1 (highest)"))] = 1;
448 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=2", "2"))] = 2;
449 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=3", "3"))] = 3;
450 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=4", "4"))] = 4;
451 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu medium priority", "5 (medium)"))] = 5;
452 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=6", "6"))] = 6;
453 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=7", "7"))] = 7;
454 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=8", "8"))] = 8;
455 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu lowest priority", "9 (lowest)"))] = 9;
456 connect(mPriorityPopupMenu, &QMenu::triggered, this, &TodoView::setNewPriority);
457
458 mPercentageCompletedPopupMenu = new QMenu(this);
459 for (int i = 0; i <= 100; i += 10) {
460 const QString label = QStringLiteral("%1 %").arg(i);
461 mPercentage[mPercentageCompletedPopupMenu->addAction(label)] = i;
462 }
463 connect(mPercentageCompletedPopupMenu, &QMenu::triggered, this, &TodoView::setNewPercentage);
464
465 setMinimumHeight(50);
466
467 // Initialize our proxy models
468 setFlatView(preferences()->flatListTodo());
469 setFullView(preferences()->fullViewTodo());
470
471 updateConfig();
472}
473
474TodoView::~TodoView()
475{
476 saveViewState();
477
478 sModels->unregisterView(this);
479 if (sModels->views.isEmpty()) {
480 delete sModels;
481 sModels = nullptr;
482 }
483}
484
485void TodoView::expandIndex(const QModelIndex &index)
486{
487 QModelIndex todoModelIndex = sModels->todoModel->mapFromSource(index);
488 Q_ASSERT(todoModelIndex.isValid());
489 const auto coloredIndex = sModels->coloredTodoModel->mapFromSource(todoModelIndex);
490 Q_ASSERT(coloredIndex.isValid());
491 QModelIndex realIndex = mProxyModel->mapFromSource(coloredIndex);
492 Q_ASSERT(realIndex.isValid());
493 while (realIndex.isValid()) {
494 mView->expand(realIndex);
495 realIndex = mProxyModel->parent(realIndex);
496 }
497}
498
499void TodoView::setModel(QAbstractItemModel *model)
500{
501 EventView::setModel(model);
502
503 mCalendarFilterModel->setSourceModel(model);
504 restoreViewState();
505}
506
507void TodoView::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
508{
509 EventView::addCalendar(calendar);
510 mCalendarFilterModel->addCalendar(calendar);
511 if (calendars().size() == 1) {
512 mProxyModel->setCalFilter(calendar->filter());
513 }
514}
515
516void TodoView::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
517{
518 mCalendarFilterModel->removeCalendar(calendar);
519 EventView::removeCalendar(calendar);
520}
521
523{
525 const QModelIndexList selection = mView->selectionModel()->selectedRows();
526 ret.reserve(selection.count());
527 for (const QModelIndex &mi : selection) {
528 ret << mi.data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
529 }
530 return ret;
531}
532
534{
535 // The todo view only lists todo's. It's probably not a good idea to
536 // return something about the selected todo here, because it has got
537 // a couple of dates (creation, due date, completion date), and the
538 // caller could not figure out what he gets. So just return an empty list.
539 return {};
540}
541
542void TodoView::saveLayout(KConfig *config, const QString &group) const
543{
544 KConfigGroup cfgGroup = config->group(group);
545 QHeaderView *header = mView->header();
546
547 QVariantList columnVisibility;
548 QVariantList columnOrder;
549 QVariantList columnWidths;
550 const int headerCount = header->count();
551 columnVisibility.reserve(headerCount);
552 columnWidths.reserve(headerCount);
553 columnOrder.reserve(headerCount);
554 for (int i = 0; i < headerCount; ++i) {
555 columnVisibility << QVariant(!mView->isColumnHidden(i));
556 columnWidths << QVariant(header->sectionSize(i));
557 columnOrder << QVariant(header->visualIndex(i));
558 }
559 cfgGroup.writeEntry("ColumnVisibility", columnVisibility);
560 cfgGroup.writeEntry("ColumnOrder", columnOrder);
561 cfgGroup.writeEntry("ColumnWidths", columnWidths);
562
563 cfgGroup.writeEntry("SortAscending", (int)header->sortIndicatorOrder());
564 if (header->isSortIndicatorShown()) {
565 cfgGroup.writeEntry("SortColumn", header->sortIndicatorSection());
566 } else {
567 cfgGroup.writeEntry("SortColumn", -1);
568 }
569
570 if (!mSidebarView) {
571 preferences()->setFullViewTodo(mFullViewButton->isChecked());
572 }
573 preferences()->setFlatListTodo(mFlatViewButton->isChecked());
574}
575
576void TodoView::restoreLayout(KConfig *config, const QString &group, bool minimalDefaults)
577{
578 KConfigGroup cfgGroup = config->group(group);
579 QHeaderView *header = mView->header();
580
581 QVariantList columnVisibility = cfgGroup.readEntry("ColumnVisibility", QVariantList());
582 QVariantList columnOrder = cfgGroup.readEntry("ColumnOrder", QVariantList());
583 QVariantList columnWidths = cfgGroup.readEntry("ColumnWidths", QVariantList());
584
585 if (columnVisibility.isEmpty()) {
586 // if config is empty then use default settings
587 mView->hideColumn(Akonadi::TodoModel::RecurColumn);
588 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn);
589 mView->hideColumn(Akonadi::TodoModel::CalendarColumn);
590 mView->hideColumn(Akonadi::TodoModel::CompletedDateColumn);
591
592 if (minimalDefaults) {
593 mView->hideColumn(Akonadi::TodoModel::PriorityColumn);
594 mView->hideColumn(Akonadi::TodoModel::PercentColumn);
595 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn);
596 mView->hideColumn(Akonadi::TodoModel::CategoriesColumn);
597 }
598
599 // We don't have any incidences (content) yet, so we delay resizing
600 QTimer::singleShot(0, this, &TodoView::resizeColumns);
601 } else {
602 for (int i = 0; i < header->count() && i < columnOrder.size() && i < columnWidths.size() && i < columnVisibility.size(); i++) {
603 bool visible = columnVisibility[i].toBool();
604 int width = columnWidths[i].toInt();
605 int order = columnOrder[i].toInt();
606
607 header->resizeSection(i, width);
608 header->moveSection(header->visualIndex(i), order);
609 if (i != 0 && !visible) {
610 mView->hideColumn(i);
611 }
612 }
613 }
614
615 int sortOrder = cfgGroup.readEntry("SortAscending", (int)Qt::AscendingOrder);
616 int sortColumn = cfgGroup.readEntry("SortColumn", -1);
617 if (sortColumn >= 0) {
618 mView->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
619 }
620
621 mFlatViewButton->setChecked(cfgGroup.readEntry("FlatView", false));
622}
623
624void TodoView::setIncidenceChanger(Akonadi::IncidenceChanger *changer)
625{
627 sModels->todoModel->setIncidenceChanger(changer);
628}
629
630void TodoView::showDates(const QDate &start, const QDate &end, const QDate &)
631{
632 // There is nothing to do here for the Todo View
633 Q_UNUSED(start)
634 Q_UNUSED(end)
635}
636
637void TodoView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date)
638{
639 Q_UNUSED(incidenceList)
640 Q_UNUSED(date)
641}
642
643void TodoView::updateView()
644{
645 if (calendars().empty()) {
646 return;
647 }
648
649 auto calendar = calendars().first();
650 mProxyModel->setCalFilter(calendar->filter());
651}
652
653void TodoView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType)
654{
655 // Don't do anything, model is connected to ETM, it's up to date
656}
657
658void TodoView::updateConfig()
659{
660 Q_ASSERT(preferences());
661 if (!mSidebarView && mQuickSearch) {
662 mQuickSearch->setVisible(preferences()->enableTodoQuickSearch());
663 }
664
665 if (mQuickAdd) {
666 mQuickAdd->setVisible(preferences()->enableQuickTodo());
667 }
668
669 if (mProxyModel) {
670 mProxyModel->invalidate();
671 }
672
673 updateView();
674}
675
676void TodoView::clearSelection()
677{
678 mView->selectionModel()->clearSelection();
679}
680
681void TodoView::addTodo(const QString &summary, const Akonadi::Item &parentItem, const QStringList &categories)
682{
683 const QString summaryTrimmed = summary.trimmed();
684 if (!changer() || summaryTrimmed.isEmpty()) {
685 return;
686 }
687
689
691 todo->setSummary(summaryTrimmed);
692 todo->setOrganizer(Person(CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email()));
693
694 todo->setCategories(categories);
695
696 if (parent && !parent->hasRecurrenceId()) {
697 todo->setRelatedTo(parent->uid());
698 }
699
700 Akonadi::Collection collection;
701
702 // Use the same collection of the parent.
703 if (parentItem.isValid()) {
704 // Don't use parentColection() since it might be a virtual collection
705 collection = Akonadi::EntityTreeModel::updatedCollection(model(), parentItem.storageCollectionId());
706 }
707
708 changer()->createIncidence(todo, collection, this);
709}
710
711void TodoView::addQuickTodo(Qt::KeyboardModifiers modifiers)
712{
713 if (modifiers == Qt::NoModifier) {
714 /*const QModelIndex index = */
715 addTodo(mQuickAdd->text(), Akonadi::Item(), mProxyModel->categories());
716 } else if (modifiers == Qt::ControlModifier) {
717 QModelIndexList selection = mView->selectionModel()->selectedRows();
718 if (selection.count() != 1) {
719 qCWarning(CALENDARVIEW_LOG) << "No to-do selected" << selection;
720 return;
721 }
722 const QModelIndex idx = mProxyModel->mapToSource(selection[0]);
723 mView->expand(selection[0]);
724 const auto parent = sModels->coloredTodoModel->data(idx, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
725 addTodo(mQuickAdd->text(), parent, mProxyModel->categories());
726 } else {
727 return;
728 }
729 mQuickAdd->setText(QString());
730}
731
732void TodoView::contextMenu(QPoint pos)
733{
734 const bool hasItem = mView->indexAt(pos).isValid();
735 Incidence::Ptr incidencePtr;
736
737 for (QAction *entry : std::as_const(mItemPopupMenuItemOnlyEntries)) {
738 bool enable;
739
740 if (hasItem) {
741 const Akonadi::Item::List incidences = selectedIncidences();
742
743 if (incidences.isEmpty()) {
744 enable = false;
745 } else {
746 Akonadi::Item item = incidences.first();
747 incidencePtr = Akonadi::CalendarUtils::incidence(item);
748
749 // Action isn't RO, it can change the incidence, "Edit" for example.
750 const bool actionIsRw = mItemPopupMenuReadWriteEntries.contains(entry);
751
752 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), item.storageCollectionId());
753 const bool incidenceIsRO = (collection.rights() & Akonadi::Collection::CanChangeItem) == 0;
754
755 enable = hasItem && (!actionIsRw || !incidenceIsRO);
756 }
757 } else {
758 enable = false;
759 }
760
761 entry->setEnabled(enable);
762 }
763 mCopyPopupMenu->setEnabled(hasItem);
764 mMovePopupMenu->setEnabled(hasItem);
765
766 if (hasItem) {
767 if (incidencePtr) {
768 const bool hasRecId = incidencePtr->hasRecurrenceId();
769 const bool hasSubtodos = mView->model()->hasChildren(mView->indexAt(pos));
770
771 mMakeSubtodosIndependent->setEnabled(!hasRecId && hasSubtodos);
772 mMakeTodoIndependent->setEnabled(!hasRecId && !incidencePtr->relatedTo().isEmpty());
773 }
774
775 switch (mView->indexAt(pos).column()) {
776 case Akonadi::TodoModel::PriorityColumn:
777 mPriorityPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
778 break;
779 case Akonadi::TodoModel::PercentColumn:
780 mPercentageCompletedPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
781 break;
782 case Akonadi::TodoModel::StartDateColumn:
783 mView->startPopupMenu()->popup(mView->viewport()->mapToGlobal(pos));
784 break;
785 case Akonadi::TodoModel::DueDateColumn:
786 mMovePopupMenu->popup(mView->viewport()->mapToGlobal(pos));
787 break;
788 case Akonadi::TodoModel::CategoriesColumn:
789 createCategoryPopupMenu()->popup(mView->viewport()->mapToGlobal(pos));
790 break;
791 default:
792 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
793 break;
794 }
795 } else {
796 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos));
797 }
798}
799
800void TodoView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
801{
802 Q_UNUSED(previous);
803 if (!current.isValid()) {
804 Q_EMIT incidenceSelected(Akonadi::Item(), QDate());
805 return;
806 }
807
808 const auto todoItem = current.data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
809
810 if (selectedIncidenceDates().isEmpty()) {
811 Q_EMIT incidenceSelected(todoItem, QDate());
812 } else {
813 Q_EMIT incidenceSelected(todoItem, selectedIncidenceDates().at(0));
814 }
815}
816
817void TodoView::showTodo()
818{
819 QModelIndexList selection = mView->selectionModel()->selectedRows();
820 if (selection.size() != 1) {
821 return;
822 }
823
824 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
825
826 Q_EMIT showIncidenceSignal(todoItem);
827}
828
829void TodoView::editTodo()
830{
831 QModelIndexList selection = mView->selectionModel()->selectedRows();
832 if (selection.size() != 1) {
833 return;
834 }
835
836 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
837 Q_EMIT editIncidenceSignal(todoItem);
838}
839
840void TodoView::deleteTodo()
841{
842 QModelIndexList selection = mView->selectionModel()->selectedRows();
843 if (selection.size() == 1) {
844 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
845
846 if (!changer()->deletedRecently(todoItem.id())) {
848 }
849 }
850}
851
852void TodoView::newTodo()
853{
854 Q_EMIT newTodoSignal(QDate::currentDate().addDays(7));
855}
856
857void TodoView::newSubTodo()
858{
859 QModelIndexList selection = mView->selectionModel()->selectedRows();
860 if (selection.size() == 1) {
861 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
862
863 Q_EMIT newSubTodoSignal(todoItem);
864 } else {
865 // This never happens
866 qCWarning(CALENDARVIEW_LOG) << "Selection size isn't 1";
867 }
868}
869
870void TodoView::copyTodoToDate(QDate date)
871{
872 if (!changer()) {
873 return;
874 }
875
876 QModelIndexList selection = mView->selectionModel()->selectedRows();
877 if (selection.size() != 1) {
878 return;
879 }
880
881 const QModelIndex origIndex = mProxyModel->mapToSource(selection[0]);
882 Q_ASSERT(origIndex.isValid());
883
884 const auto origItem = sModels->coloredTodoModel->data(origIndex, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
885
887 if (!orig) {
888 return;
889 }
890
891 KCalendarCore::Todo::Ptr todo(orig->clone());
892
894
895 QDateTime due = todo->dtDue();
896 due.setDate(date);
897 todo->setDtDue(due);
898
899 changer()->createIncidence(todo, Akonadi::Collection(), this);
900}
901
902void TodoView::scheduleResizeColumns()
903{
904 mResizeColumnsScheduled = true;
905 mResizeColumnsTimer->start(); // restarts the timer if already active
906}
907
908void TodoView::itemDoubleClicked(const QModelIndex &index)
909{
910 if (index.isValid()) {
911 QModelIndex summary = index.sibling(index.row(), Akonadi::TodoModel::SummaryColumn);
912 if (summary.flags() & Qt::ItemIsEditable) {
913 editTodo();
914 } else {
915 showTodo();
916 }
917 }
918}
919
920QMenu *TodoView::createCategoryPopupMenu()
921{
922 auto tempMenu = new QMenu(this);
923
924 QModelIndexList selection = mView->selectionModel()->selectedRows();
925 if (selection.size() != 1) {
926 return tempMenu;
927 }
928
929 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
931 Q_ASSERT(todo);
932
933 const QStringList checkedCategories = todo->categories();
934
935 auto tagFetchJob = new Akonadi::TagFetchJob(this);
936 connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TodoView::onTagsFetched);
937 tagFetchJob->setProperty("menu", QVariant::fromValue(QPointer<QMenu>(tempMenu)));
938 tagFetchJob->setProperty("checkedCategories", checkedCategories);
939
940 connect(tempMenu, &QMenu::triggered, this, &TodoView::changedCategories);
941 connect(tempMenu, &QMenu::aboutToHide, tempMenu, &QMenu::deleteLater);
942 return tempMenu;
943}
944
945void TodoView::onTagsFetched(KJob *job)
946{
947 if (job->error()) {
948 qCWarning(CALENDARVIEW_LOG) << "Failed to fetch tags " << job->errorString();
949 return;
950 }
951 auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
952 const QStringList checkedCategories = job->property("checkedCategories").toStringList();
953 auto menu = job->property("menu").value<QPointer<QMenu>>();
954 if (menu) {
955 const auto lst = fetchJob->tags();
956 for (const Akonadi::Tag &tag : lst) {
957 const QString name = tag.name();
958 QAction *action = menu->addAction(name);
959 action->setCheckable(true);
960 action->setData(name);
961 if (checkedCategories.contains(name)) {
962 action->setChecked(true);
963 }
964 }
965 }
966}
967
968void TodoView::setNewDate(QDate date)
969{
970 QModelIndexList selection = mView->selectionModel()->selectedRows();
971 if (selection.size() != 1) {
972 return;
973 }
974
975 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
977 Q_ASSERT(todo);
978
979 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
980 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
981 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
982 QDateTime dt(date.startOfDay());
983
984 if (!todo->allDay()) {
985 dt.setTime(todo->dtDue().time());
986 }
987
988 if (todo->hasStartDate() && dt < todo->dtStart()) {
989 todo->setDtStart(dt);
990 }
991 todo->setDtDue(dt);
992
993 changer()->modifyIncidence(todoItem, oldTodo, this);
994 } else {
995 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly";
996 }
997}
998
999void TodoView::setStartDate(QDate date)
1000{
1001 QModelIndexList selection = mView->selectionModel()->selectedRows();
1002 if (selection.size() != 1) {
1003 return;
1004 }
1005
1006 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1008 Q_ASSERT(todo);
1009
1010 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1011 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1012 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1013 QDateTime dt(date.startOfDay());
1014
1015 if (!todo->allDay()) {
1016 dt.setTime(todo->dtStart().time());
1017 }
1018
1019 if (todo->hasDueDate() && dt > todo->dtDue()) {
1020 todo->setDtDue(dt);
1021 }
1022 todo->setDtStart(dt);
1023
1024 changer()->modifyIncidence(todoItem, oldTodo, this);
1025 } else {
1026 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly";
1027 }
1028}
1029
1030void TodoView::setNewPercentage(QAction *action)
1031{
1032 QModelIndexList selection = mView->selectionModel()->selectedRows();
1033 if (selection.size() != 1) {
1034 return;
1035 }
1036
1037 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1039 Q_ASSERT(todo);
1040
1041 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1042 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1043 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1044
1045 int percentage = mPercentage.value(action);
1046 if (percentage == 100) {
1047 todo->setCompleted(QDateTime::currentDateTime());
1048 todo->setPercentComplete(100);
1049 } else {
1050 todo->setPercentComplete(percentage);
1051 }
1052 changer()->modifyIncidence(todoItem, oldTodo, this);
1053 } else {
1054 qCDebug(CALENDARVIEW_LOG) << "Item is read only";
1055 }
1056}
1057
1058void TodoView::setNewPriority(QAction *action)
1059{
1060 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1061 if (selection.size() != 1) {
1062 return;
1063 }
1064 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1066 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1067 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1068 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1069 todo->setPriority(mPriority[action]);
1070
1071 changer()->modifyIncidence(todoItem, oldTodo, this);
1072 }
1073}
1074
1075void TodoView::changedCategories(QAction *action)
1076{
1077 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1078 if (selection.size() != 1) {
1079 return;
1080 }
1081
1082 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1084 Q_ASSERT(todo);
1085 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId());
1086 if (collection.rights() & Akonadi::Collection::CanChangeItem) {
1087 KCalendarCore::Todo::Ptr oldTodo(todo->clone());
1088
1089 const QString cat = action->data().toString();
1090 QStringList categories = todo->categories();
1091 if (categories.contains(cat)) {
1092 categories.removeAll(cat);
1093 } else {
1094 categories.append(cat);
1095 }
1096 categories.sort();
1097 todo->setCategories(categories);
1098 changer()->modifyIncidence(todoItem, oldTodo, this);
1099 } else {
1100 qCDebug(CALENDARVIEW_LOG) << "No active item, active item is read-only, or locking failed";
1101 }
1102}
1103
1104void TodoView::setFullView(bool fullView)
1105{
1106 if (!mFullViewButton) {
1107 return;
1108 }
1109
1110 mFullViewButton->setChecked(fullView);
1111 if (fullView) {
1112 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-restore")));
1113 } else {
1114 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
1115 }
1116
1117 mFullViewButton->blockSignals(true);
1118 // We block signals to avoid recursion; there are two TodoViews and
1119 // also mFullViewButton is synchronized.
1120 mFullViewButton->setChecked(fullView);
1121 mFullViewButton->blockSignals(false);
1122
1123 preferences()->setFullViewTodo(fullView);
1124 preferences()->writeConfig();
1125
1126 Q_EMIT fullViewChanged(fullView);
1127}
1128
1129void TodoView::setFlatView(bool flatView, bool notifyOtherViews)
1130{
1131 if (flatView) {
1132 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
1133 } else {
1134 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details")));
1135 }
1136
1137 if (notifyOtherViews) {
1138 sModels->setFlatView(flatView);
1139 }
1140}
1141
1142void TodoView::onRowsInserted(const QModelIndex &parent, int start, int end)
1143{
1144 if (start != end || !entityTreeModel()) {
1145 return;
1146 }
1147
1148 QModelIndex idx = mView->model()->index(start, 0);
1149
1150 // If the collection is currently being populated, we don't do anything
1152 if (!v.isValid()) {
1153 return;
1154 }
1155
1156 auto item = v.value<Akonadi::Item>();
1157 if (!item.isValid()) {
1158 return;
1159 }
1160
1161 const bool isPopulated = entityTreeModel()->isCollectionPopulated(item.storageCollectionId());
1162 if (!isPopulated) {
1163 return;
1164 }
1165
1166 // Case #1, adding an item that doesn't have parent: We select it
1167 if (!parent.isValid()) {
1168 QModelIndexList selection = mView->selectionModel()->selectedRows();
1169 if (selection.size() <= 1) {
1170 // don't destroy complex selections, not applicable now (only single
1171 // selection allowed), but for the future...
1172 int colCount = static_cast<int>(Akonadi::TodoModel::ColumnCount);
1173 mView->selectionModel()->select(QItemSelection(idx, mView->model()->index(start, colCount - 1)),
1175 }
1176 return;
1177 }
1178
1179 // Case 2: Adding an item that has a parent: we expand the parent
1180 if (sModels->isFlatView()) {
1181 return;
1182 }
1183
1184 QModelIndex index = parent;
1185 mView->expand(index);
1186 while (index.parent().isValid()) {
1187 mView->expand(index.parent());
1188 index = index.parent();
1189 }
1190}
1191
1192void TodoView::getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals)
1193{
1194 highlightTodos = preferences()->highlightTodos();
1195 highlightEvents = !highlightTodos;
1196 highlightJournals = false;
1197}
1198
1199bool TodoView::usesFullWindow()
1200{
1201 return preferences()->fullViewTodo();
1202}
1203
1204void TodoView::resizeColumns()
1205{
1206 mResizeColumnsScheduled = false;
1207
1208 mView->resizeColumnToContents(Akonadi::TodoModel::StartDateColumn);
1209 mView->resizeColumnToContents(Akonadi::TodoModel::DueDateColumn);
1210 mView->resizeColumnToContents(Akonadi::TodoModel::CompletedDateColumn);
1211 mView->resizeColumnToContents(Akonadi::TodoModel::PriorityColumn);
1212 mView->resizeColumnToContents(Akonadi::TodoModel::CalendarColumn);
1213 mView->resizeColumnToContents(Akonadi::TodoModel::RecurColumn);
1214 mView->resizeColumnToContents(Akonadi::TodoModel::PercentColumn);
1215
1216 // We have 3 columns that should stretch: summary, description and categories.
1217 // Summary is always visible.
1218 const bool descriptionVisible = !mView->isColumnHidden(Akonadi::TodoModel::DescriptionColumn);
1219 const bool categoriesVisible = !mView->isColumnHidden(Akonadi::TodoModel::CategoriesColumn);
1220
1221 // Calculate size of non-stretchable columns:
1222 int size = 0;
1223 for (int i = 0; i < Akonadi::TodoModel::ColumnCount; ++i) {
1224 if (!mView->isColumnHidden(i) && i != Akonadi::TodoModel::SummaryColumn && i != Akonadi::TodoModel::DescriptionColumn
1225 && i != Akonadi::TodoModel::CategoriesColumn) {
1226 size += mView->columnWidth(i);
1227 }
1228 }
1229
1230 // Calculate the remaining space that we have for the stretchable columns
1231 int remainingSize = mView->header()->width() - size;
1232
1233 // 100 for summary, 100 for description
1234 const int requiredSize = descriptionVisible ? 200 : 100;
1235
1236 if (categoriesVisible) {
1237 const int categorySize = 100;
1238 mView->setColumnWidth(Akonadi::TodoModel::CategoriesColumn, categorySize);
1239 remainingSize -= categorySize;
1240 }
1241
1242 if (remainingSize < requiredSize) {
1243 // Too little size, so let's use a horizontal scrollbar
1244 mView->resizeColumnToContents(Akonadi::TodoModel::SummaryColumn);
1245 mView->resizeColumnToContents(Akonadi::TodoModel::DescriptionColumn);
1246 } else if (descriptionVisible) {
1247 mView->setColumnWidth(Akonadi::TodoModel::SummaryColumn, remainingSize / 2);
1248 mView->setColumnWidth(Akonadi::TodoModel::DescriptionColumn, remainingSize / 2);
1249 } else {
1250 mView->setColumnWidth(Akonadi::TodoModel::SummaryColumn, remainingSize);
1251 }
1252}
1253
1254void TodoView::restoreViewState()
1255{
1256 if (sModels->isFlatView()) {
1257 return;
1258 }
1259
1260 if (sModels->todoTreeModel && !sModels->todoTreeModel->sourceModel()) {
1261 return;
1262 }
1263
1264 // QElapsedTimer timer;
1265 // timer.start();
1266 delete mTreeStateRestorer;
1267 mTreeStateRestorer = new Akonadi::ETMViewStateSaver();
1268 KSharedConfig::Ptr config = KSharedConfig::openConfig();
1269 KConfigGroup group(config, stateSaverGroup());
1270 mTreeStateRestorer->setView(mView);
1271 mTreeStateRestorer->restoreState(group);
1272 // qCDebug(CALENDARVIEW_LOG) << "Took " << timer.elapsed();
1273}
1274
1275QString TodoView::stateSaverGroup() const
1276{
1277 QString str = QStringLiteral("TodoTreeViewState");
1278 if (mSidebarView) {
1279 str += QLatin1Char('S');
1280 }
1281
1282 return str;
1283}
1284
1285void TodoView::saveViewState()
1286{
1287 Akonadi::ETMViewStateSaver treeStateSaver;
1288 KConfigGroup group(preferences()->config(), stateSaverGroup());
1289 treeStateSaver.setView(mView);
1290 treeStateSaver.saveState(group);
1291}
1292
1293void TodoView::resizeEvent(QResizeEvent *event)
1294{
1296 scheduleResizeColumns();
1297}
1298
1299void TodoView::createEvent()
1300{
1301 const QModelIndexList selection = mView->selectionModel()->selectedRows();
1302 if (selection.size() != 1) {
1303 return;
1304 }
1305
1306 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>();
1307
1308 Q_EMIT createEvent(todoItem);
1309}
1310
1311#include "todoview.moc"
1312
1313#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: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.
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:522
KCalendarCore::DateList selectedIncidenceDates() const override
Returns a list of the dates of selected events.
Definition todoview.cpp:533
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 name(StandardAction id)
QString label(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
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
virtual void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
QModelIndexList selectedRows(int column) const const
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 Mon Nov 18 2024 12:07:11 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.