File indexing completed on 2024-06-16 04:51:16
0001 /* 0002 This file is part of KOrganizer. 0003 0004 SPDX-FileCopyrightText: 2000, 2001, 2003 Cornelius Schumacher <schumacher@kde.org> 0005 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0006 SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net> 0007 SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at> 0008 SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0011 */ 0012 0013 #include "todoview.h" 0014 0015 #include "calendarview_debug.h" 0016 #include "coloredtodoproxymodel.h" 0017 #include "tododelegates.h" 0018 #include "todoviewquickaddline.h" 0019 #include "todoviewquicksearch.h" 0020 #include "todoviewsortfilterproxymodel.h" 0021 #include "todoviewview.h" 0022 0023 #include <Akonadi/CalendarUtils> 0024 #include <Akonadi/EntityMimeTypeFilterModel> 0025 #include <Akonadi/EntityTreeModel> 0026 #include <Akonadi/TagFetchJob> 0027 0028 #include <Akonadi/ETMViewStateSaver> 0029 #include <Akonadi/IncidenceTreeModel> 0030 #include <Akonadi/TodoModel> 0031 0032 #include <CalendarSupport/KCalPrefs> 0033 0034 #include <KCalendarCore/CalFormat> 0035 0036 #include <KConfig> 0037 #include <KDatePickerPopup> 0038 #include <KDescendantsProxyModel> 0039 #include <KJob> 0040 #include <KMessageBox> 0041 0042 #include <QGridLayout> 0043 #include <QHeaderView> 0044 #include <QIcon> 0045 #include <QMenu> 0046 #include <QSortFilterProxyModel> 0047 #include <QToolButton> 0048 0049 #include <chrono> 0050 0051 using namespace std::chrono_literals; 0052 0053 Q_DECLARE_METATYPE(QPointer<QMenu>) 0054 0055 using namespace EventViews; 0056 using namespace KCalendarCore; 0057 0058 namespace EventViews 0059 { 0060 0061 class CalendarFilterModel : public QSortFilterProxyModel 0062 { 0063 Q_OBJECT 0064 public: 0065 explicit CalendarFilterModel(QObject *parent = nullptr) 0066 : QSortFilterProxyModel(parent) 0067 { 0068 mDescendantsProxy.setDisplayAncestorData(false); 0069 QSortFilterProxyModel::setSourceModel(&mDescendantsProxy); 0070 } 0071 0072 void setSourceModel(QAbstractItemModel *model) override 0073 { 0074 mDescendantsProxy.setSourceModel(model); 0075 } 0076 0077 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override 0078 { 0079 const auto source_index = sourceModel()->index(source_row, 0, source_parent); 0080 const auto item = sourceModel()->data(source_index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0081 0082 if (!item.isValid()) { 0083 return false; 0084 } 0085 return mEnabledCalendars.contains(item.parentCollection().id()); 0086 } 0087 0088 void addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0089 { 0090 mEnabledCalendars.insert(calendar->collection().id()); 0091 invalidateFilter(); 0092 } 0093 0094 void removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0095 { 0096 mEnabledCalendars.remove(calendar->collection().id()); 0097 invalidateFilter(); 0098 } 0099 0100 private: 0101 KDescendantsProxyModel mDescendantsProxy; 0102 QSet<Akonadi::Collection::Id> mEnabledCalendars; 0103 }; 0104 0105 // We share this struct between all views, for performance and memory purposes 0106 class ModelStack 0107 { 0108 public: 0109 ModelStack(const EventViews::PrefsPtr &preferences, QObject *parent_) 0110 : todoModel(new Akonadi::TodoModel()) 0111 , coloredTodoModel(new ColoredTodoProxyModel(preferences)) 0112 , parent(parent_) 0113 , prefs(preferences) 0114 { 0115 coloredTodoModel->setSourceModel(todoModel); 0116 } 0117 0118 ~ModelStack() 0119 { 0120 delete coloredTodoModel; 0121 delete todoModel; 0122 delete todoTreeModel; 0123 delete todoFlatModel; 0124 } 0125 0126 void registerView(TodoView *view) 0127 { 0128 views << view; 0129 } 0130 0131 void unregisterView(TodoView *view) 0132 { 0133 views.removeAll(view); 0134 } 0135 0136 void setFlatView(bool flat) 0137 { 0138 const QString todoMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.todo"); 0139 if (flat) { 0140 for (TodoView *view : std::as_const(views)) { 0141 // In flatview dropping confuses users and it's very easy to drop into a child item 0142 view->mView->setDragDropMode(QAbstractItemView::DragOnly); 0143 view->setFlatView(flat, /**propagate=*/false); // So other views update their toggle icon 0144 0145 if (todoTreeModel) { 0146 view->saveViewState(); // Save the tree state before it's gone 0147 } 0148 } 0149 0150 delete todoFlatModel; 0151 todoFlatModel = new Akonadi::EntityMimeTypeFilterModel(parent); 0152 todoFlatModel->addMimeTypeInclusionFilter(todoMimeType); 0153 todoFlatModel->setSourceModel(model); 0154 todoModel->setSourceModel(todoFlatModel); 0155 0156 delete todoTreeModel; 0157 todoTreeModel = nullptr; 0158 } else { 0159 delete todoTreeModel; 0160 todoTreeModel = new Akonadi::IncidenceTreeModel(QStringList() << todoMimeType, parent); 0161 for (TodoView *view : std::as_const(views)) { 0162 QObject::connect(todoTreeModel, &Akonadi::IncidenceTreeModel::indexChangedParent, view, &TodoView::expandIndex); 0163 QObject::connect(todoTreeModel, &Akonadi::IncidenceTreeModel::batchInsertionFinished, view, &TodoView::restoreViewState); 0164 view->mView->setDragDropMode(QAbstractItemView::DragDrop); 0165 view->setFlatView(flat, /**propagate=*/false); // So other views update their toggle icon 0166 } 0167 todoTreeModel->setSourceModel(model); 0168 todoModel->setSourceModel(todoTreeModel); 0169 delete todoFlatModel; 0170 todoFlatModel = nullptr; 0171 } 0172 0173 for (TodoView *view : std::as_const(views)) { 0174 view->mFlatViewButton->blockSignals(true); 0175 // We block signals to avoid recursion, we have two TodoViews and mFlatViewButton is synchronized 0176 view->mFlatViewButton->setChecked(flat); 0177 view->mFlatViewButton->blockSignals(false); 0178 view->mView->setRootIsDecorated(!flat); 0179 view->restoreViewState(); 0180 } 0181 0182 prefs->setFlatListTodo(flat); 0183 prefs->writeConfig(); 0184 } 0185 0186 void setModel(QAbstractItemModel *model) 0187 { 0188 this->model = model; 0189 if (todoTreeModel) { 0190 todoTreeModel->setSourceModel(this->model); 0191 } 0192 } 0193 0194 bool isFlatView() const 0195 { 0196 return todoFlatModel != nullptr; 0197 } 0198 0199 Akonadi::TodoModel *const todoModel; 0200 ColoredTodoProxyModel *const coloredTodoModel; 0201 QList<TodoView *> views; 0202 QObject *parent = nullptr; 0203 0204 QAbstractItemModel *model = nullptr; 0205 Akonadi::IncidenceTreeModel *todoTreeModel = nullptr; 0206 Akonadi::EntityMimeTypeFilterModel *todoFlatModel = nullptr; 0207 EventViews::PrefsPtr prefs; 0208 }; 0209 } 0210 0211 // Don't use K_GLOBAL_STATIC, see QTBUG-22667 0212 static ModelStack *sModels = nullptr; 0213 0214 TodoView::TodoView(const EventViews::PrefsPtr &prefs, bool sidebarView, QWidget *parent) 0215 : EventView(parent) 0216 , mCalendarFilterModel(std::make_unique<CalendarFilterModel>()) 0217 , mQuickSearch(nullptr) 0218 , mQuickAdd(nullptr) 0219 , mTreeStateRestorer(nullptr) 0220 , mSidebarView(sidebarView) 0221 , mResizeColumnsScheduled(false) 0222 { 0223 mResizeColumnsTimer = new QTimer(this); 0224 connect(mResizeColumnsTimer, &QTimer::timeout, this, &TodoView::resizeColumns); 0225 mResizeColumnsTimer->setInterval(100ms); // so we don't overdue it when user resizes window manually 0226 mResizeColumnsTimer->setSingleShot(true); 0227 0228 setPreferences(prefs); 0229 if (!sModels) { 0230 sModels = new ModelStack(prefs, parent); 0231 connect(sModels->todoModel, &Akonadi::TodoModel::dropOnSelfRejected, this, []() { 0232 KMessageBox::information(nullptr, 0233 i18n("Cannot move to-do to itself or a child of itself."), 0234 i18nc("@title:window", "Drop To-do"), 0235 QStringLiteral("NoDropTodoOntoItself")); 0236 }); 0237 } 0238 sModels->registerView(this); 0239 sModels->setModel(mCalendarFilterModel.get()); 0240 0241 mProxyModel = new TodoViewSortFilterProxyModel(preferences(), this); 0242 mProxyModel->setSourceModel(sModels->coloredTodoModel); 0243 mProxyModel->setDynamicSortFilter(true); 0244 mProxyModel->setFilterKeyColumn(Akonadi::TodoModel::SummaryColumn); 0245 mProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 0246 mProxyModel->setSortRole(Qt::EditRole); 0247 connect(mProxyModel, &TodoViewSortFilterProxyModel::rowsInserted, this, &TodoView::onRowsInserted); 0248 0249 if (!mSidebarView) { 0250 mQuickSearch = new TodoViewQuickSearch(this); 0251 mQuickSearch->setVisible(prefs->enableTodoQuickSearch()); 0252 connect(mQuickSearch, 0253 &TodoViewQuickSearch::searchTextChanged, 0254 mProxyModel, 0255 qOverload<const QString &>(&QSortFilterProxyModel::setFilterRegularExpression)); 0256 connect(mQuickSearch, &TodoViewQuickSearch::searchTextChanged, this, &TodoView::restoreViewState); 0257 connect(mQuickSearch, &TodoViewQuickSearch::filterCategoryChanged, mProxyModel, &TodoViewSortFilterProxyModel::setCategoryFilter); 0258 connect(mQuickSearch, &TodoViewQuickSearch::filterCategoryChanged, this, &TodoView::restoreViewState); 0259 connect(mQuickSearch, &TodoViewQuickSearch::filterPriorityChanged, mProxyModel, &TodoViewSortFilterProxyModel::setPriorityFilter); 0260 connect(mQuickSearch, &TodoViewQuickSearch::filterPriorityChanged, this, &TodoView::restoreViewState); 0261 } 0262 0263 mView = new TodoViewView(this); 0264 mView->setModel(mProxyModel); 0265 0266 mView->setContextMenuPolicy(Qt::CustomContextMenu); 0267 0268 mView->setSortingEnabled(true); 0269 0270 mView->setAutoExpandDelay(250); 0271 mView->setDragDropMode(QAbstractItemView::DragDrop); 0272 0273 mView->setExpandsOnDoubleClick(false); 0274 mView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); 0275 0276 connect(mView->header(), &QHeaderView::geometriesChanged, this, &TodoView::scheduleResizeColumns); 0277 connect(mView, &TodoViewView::visibleColumnCountChanged, this, &TodoView::resizeColumns); 0278 0279 auto richTextDelegate = new TodoRichTextDelegate(mView); 0280 mView->setItemDelegateForColumn(Akonadi::TodoModel::SummaryColumn, richTextDelegate); 0281 mView->setItemDelegateForColumn(Akonadi::TodoModel::DescriptionColumn, richTextDelegate); 0282 0283 auto priorityDelegate = new TodoPriorityDelegate(mView); 0284 mView->setItemDelegateForColumn(Akonadi::TodoModel::PriorityColumn, priorityDelegate); 0285 0286 auto startDateDelegate = new TodoDueDateDelegate(mView); 0287 mView->setItemDelegateForColumn(Akonadi::TodoModel::StartDateColumn, startDateDelegate); 0288 0289 auto dueDateDelegate = new TodoDueDateDelegate(mView); 0290 mView->setItemDelegateForColumn(Akonadi::TodoModel::DueDateColumn, dueDateDelegate); 0291 0292 auto completeDelegate = new TodoCompleteDelegate(mView); 0293 mView->setItemDelegateForColumn(Akonadi::TodoModel::PercentColumn, completeDelegate); 0294 0295 mCategoriesDelegate = new TodoCategoriesDelegate(mView); 0296 mView->setItemDelegateForColumn(Akonadi::TodoModel::CategoriesColumn, mCategoriesDelegate); 0297 0298 connect(mView, &TodoViewView::customContextMenuRequested, this, &TodoView::contextMenu); 0299 connect(mView, &TodoViewView::doubleClicked, this, &TodoView::itemDoubleClicked); 0300 0301 connect(mView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &TodoView::selectionChanged); 0302 0303 mQuickAdd = new TodoViewQuickAddLine(this); 0304 mQuickAdd->setClearButtonEnabled(true); 0305 mQuickAdd->setVisible(preferences()->enableQuickTodo()); 0306 connect(mQuickAdd, &TodoViewQuickAddLine::returnPressed, this, &TodoView::addQuickTodo); 0307 0308 mFullViewButton = nullptr; 0309 if (!mSidebarView) { 0310 mFullViewButton = new QToolButton(this); 0311 mFullViewButton->setAutoRaise(true); 0312 mFullViewButton->setCheckable(true); 0313 0314 mFullViewButton->setToolTip(i18nc("@info:tooltip", "Display to-do list in a full window")); 0315 mFullViewButton->setWhatsThis(i18nc("@info:whatsthis", "Checking this option will cause the to-do view to use the full window.")); 0316 } 0317 mFlatViewButton = new QToolButton(this); 0318 mFlatViewButton->setAutoRaise(true); 0319 mFlatViewButton->setCheckable(true); 0320 mFlatViewButton->setToolTip(i18nc("@info:tooltip", "Display to-dos in flat list instead of a tree")); 0321 mFlatViewButton->setWhatsThis(i18nc("@info:whatsthis", 0322 "Checking this option will cause the to-dos to be displayed as a " 0323 "flat list instead of a hierarchical tree; the parental " 0324 "relationships are removed in the display.")); 0325 0326 connect(mFlatViewButton, &QToolButton::toggled, this, [this](bool flatView) { 0327 setFlatView(flatView, true); 0328 }); 0329 if (mFullViewButton) { 0330 connect(mFullViewButton, &QToolButton::toggled, this, &TodoView::setFullView); 0331 } 0332 0333 auto layout = new QGridLayout(this); 0334 layout->setContentsMargins(0, 0, 0, 0); 0335 if (!mSidebarView) { 0336 layout->addWidget(mQuickSearch, 0, 0, 1, 2); 0337 } 0338 layout->addWidget(mView, 1, 0, 1, 2); 0339 layout->setRowStretch(1, 1); 0340 layout->addWidget(mQuickAdd, 2, 0); 0341 0342 // Dummy layout just to add a few px of right margin so the checkbox is aligned 0343 // with the QAbstractItemView's viewport. 0344 auto dummyLayout = new QHBoxLayout(); 0345 dummyLayout->setContentsMargins(0, 0, mView->frameWidth() /*right*/, 0); 0346 if (!mSidebarView) { 0347 auto f = new QFrame(this); 0348 f->setFrameShape(QFrame::VLine); 0349 f->setFrameShadow(QFrame::Sunken); 0350 dummyLayout->addWidget(f); 0351 dummyLayout->addWidget(mFullViewButton); 0352 } 0353 dummyLayout->addWidget(mFlatViewButton); 0354 0355 layout->addLayout(dummyLayout, 2, 1); 0356 0357 // ---------------- POPUP-MENUS ----------------------- 0358 mItemPopupMenu = new QMenu(this); 0359 0360 mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-preview")), 0361 i18nc("@action:inmenu show the to-do", "&Show"), 0362 this, 0363 &TodoView::showTodo); 0364 0365 QAction *a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-edit")), 0366 i18nc("@action:inmenu edit the to-do", "&Edit..."), 0367 this, 0368 &TodoView::editTodo); 0369 mItemPopupMenuReadWriteEntries << a; 0370 mItemPopupMenuItemOnlyEntries << a; 0371 0372 a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), 0373 i18nc("@action:inmenu delete the to-do", "&Delete"), 0374 this, 0375 &TodoView::deleteTodo); 0376 mItemPopupMenuReadWriteEntries << a; 0377 mItemPopupMenuItemOnlyEntries << a; 0378 0379 mItemPopupMenu->addSeparator(); 0380 0381 mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-print")), 0382 i18nc("@action:inmenu print the to-do", "&Print..."), 0383 this, 0384 &TodoView::printTodo); 0385 0386 mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("document-print-preview")), 0387 i18nc("@action:inmenu print preview the to-do", "Print Previe&w..."), 0388 this, 0389 &TodoView::printPreviewTodo); 0390 0391 mItemPopupMenu->addSeparator(); 0392 0393 mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("view-calendar-tasks")), 0394 i18nc("@action:inmenu create a new to-do", "New &To-do..."), 0395 this, 0396 &TodoView::newTodo); 0397 0398 a = mItemPopupMenu->addAction(i18nc("@action:inmenu create a new sub-to-do", "New Su&b-to-do..."), this, &TodoView::newSubTodo); 0399 mItemPopupMenuReadWriteEntries << a; 0400 mItemPopupMenuItemOnlyEntries << a; 0401 0402 mMakeTodoIndependent = mItemPopupMenu->addAction(i18nc("@action:inmenu", "&Make this To-do Independent"), this, &TodoView::unSubTodoSignal); 0403 0404 mMakeSubtodosIndependent = mItemPopupMenu->addAction(i18nc("@action:inmenu", "Make all Sub-to-dos &Independent"), this, &TodoView::unAllSubTodoSignal); 0405 0406 mItemPopupMenuItemOnlyEntries << mMakeTodoIndependent; 0407 mItemPopupMenuItemOnlyEntries << mMakeSubtodosIndependent; 0408 0409 mItemPopupMenuReadWriteEntries << mMakeTodoIndependent; 0410 mItemPopupMenuReadWriteEntries << mMakeSubtodosIndependent; 0411 0412 mItemPopupMenu->addSeparator(); 0413 0414 a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("appointment-new")), 0415 i18nc("@action:inmenu", "Create Event from To-do"), 0416 this, 0417 qOverload<>(&TodoView::createEvent)); 0418 a->setObjectName(QLatin1StringView("createevent")); 0419 mItemPopupMenuReadWriteEntries << a; 0420 mItemPopupMenuItemOnlyEntries << a; 0421 0422 a = mItemPopupMenu->addAction(QIcon::fromTheme(QStringLiteral("view-pim-notes")), 0423 i18nc("@action:inmenu", "Create Note for To-do"), 0424 this, 0425 qOverload<>(&TodoView::createNote)); 0426 a->setObjectName(QLatin1StringView("createnote")); 0427 mItemPopupMenuReadWriteEntries << a; 0428 mItemPopupMenuItemOnlyEntries << a; 0429 0430 mItemPopupMenu->addSeparator(); 0431 0432 mCopyPopupMenu = new KDatePickerPopup(KDatePickerPopup::NoDate | KDatePickerPopup::DatePicker | KDatePickerPopup::Words, QDate::currentDate(), this); 0433 mCopyPopupMenu->setTitle(i18nc("@title:menu", "&Copy To")); 0434 0435 connect(mCopyPopupMenu, &KDatePickerPopup::dateChanged, this, &TodoView::copyTodoToDate); 0436 0437 connect(mCopyPopupMenu, &KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide); 0438 0439 mMovePopupMenu = new KDatePickerPopup(KDatePickerPopup::NoDate | KDatePickerPopup::DatePicker | KDatePickerPopup::Words, QDate::currentDate(), this); 0440 mMovePopupMenu->setTitle(i18nc("@title:menu", "&Move To")); 0441 0442 connect(mMovePopupMenu, &KDatePickerPopup::dateChanged, this, &TodoView::setNewDate); 0443 connect(mView->startPopupMenu(), &KDatePickerPopup::dateChanged, this, &TodoView::setStartDate); 0444 0445 connect(mMovePopupMenu, &KDatePickerPopup::dateChanged, mItemPopupMenu, &QMenu::hide); 0446 0447 mItemPopupMenu->insertMenu(nullptr, mCopyPopupMenu); 0448 mItemPopupMenu->insertMenu(nullptr, mMovePopupMenu); 0449 0450 mItemPopupMenu->addSeparator(); 0451 mItemPopupMenu->addAction(i18nc("@action:inmenu delete completed to-dos", "Pur&ge Completed"), this, &TodoView::purgeCompletedSignal); 0452 0453 mPriorityPopupMenu = new QMenu(this); 0454 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu unspecified priority", "unspecified"))] = 0; 0455 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu highest priority", "1 (highest)"))] = 1; 0456 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=2", "2"))] = 2; 0457 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=3", "3"))] = 3; 0458 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=4", "4"))] = 4; 0459 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu medium priority", "5 (medium)"))] = 5; 0460 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=6", "6"))] = 6; 0461 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=7", "7"))] = 7; 0462 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu priority value=8", "8"))] = 8; 0463 mPriority[mPriorityPopupMenu->addAction(i18nc("@action:inmenu lowest priority", "9 (lowest)"))] = 9; 0464 connect(mPriorityPopupMenu, &QMenu::triggered, this, &TodoView::setNewPriority); 0465 0466 mPercentageCompletedPopupMenu = new QMenu(this); 0467 for (int i = 0; i <= 100; i += 10) { 0468 const QString label = QStringLiteral("%1 %").arg(i); 0469 mPercentage[mPercentageCompletedPopupMenu->addAction(label)] = i; 0470 } 0471 connect(mPercentageCompletedPopupMenu, &QMenu::triggered, this, &TodoView::setNewPercentage); 0472 0473 setMinimumHeight(50); 0474 0475 // Initialize our proxy models 0476 setFlatView(preferences()->flatListTodo()); 0477 setFullView(preferences()->fullViewTodo()); 0478 0479 updateConfig(); 0480 } 0481 0482 TodoView::~TodoView() 0483 { 0484 saveViewState(); 0485 0486 sModels->unregisterView(this); 0487 if (sModels->views.isEmpty()) { 0488 delete sModels; 0489 sModels = nullptr; 0490 } 0491 } 0492 0493 void TodoView::expandIndex(const QModelIndex &index) 0494 { 0495 QModelIndex todoModelIndex = sModels->todoModel->mapFromSource(index); 0496 Q_ASSERT(todoModelIndex.isValid()); 0497 const auto coloredIndex = sModels->coloredTodoModel->mapFromSource(todoModelIndex); 0498 Q_ASSERT(coloredIndex.isValid()); 0499 QModelIndex realIndex = mProxyModel->mapFromSource(coloredIndex); 0500 Q_ASSERT(realIndex.isValid()); 0501 while (realIndex.isValid()) { 0502 mView->expand(realIndex); 0503 realIndex = mProxyModel->parent(realIndex); 0504 } 0505 } 0506 0507 void TodoView::setModel(QAbstractItemModel *model) 0508 { 0509 EventView::setModel(model); 0510 0511 mCalendarFilterModel->setSourceModel(model); 0512 restoreViewState(); 0513 } 0514 0515 void TodoView::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0516 { 0517 EventView::addCalendar(calendar); 0518 mCalendarFilterModel->addCalendar(calendar); 0519 } 0520 0521 void TodoView::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0522 { 0523 mCalendarFilterModel->removeCalendar(calendar); 0524 EventView::removeCalendar(calendar); 0525 } 0526 0527 Akonadi::Item::List TodoView::selectedIncidences() const 0528 { 0529 Akonadi::Item::List ret; 0530 const QModelIndexList selection = mView->selectionModel()->selectedRows(); 0531 ret.reserve(selection.count()); 0532 for (const QModelIndex &mi : selection) { 0533 ret << mi.data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0534 } 0535 return ret; 0536 } 0537 0538 DateList TodoView::selectedIncidenceDates() const 0539 { 0540 // The todo view only lists todo's. It's probably not a good idea to 0541 // return something about the selected todo here, because it has got 0542 // a couple of dates (creation, due date, completion date), and the 0543 // caller could not figure out what he gets. So just return an empty list. 0544 return {}; 0545 } 0546 0547 void TodoView::saveLayout(KConfig *config, const QString &group) const 0548 { 0549 KConfigGroup cfgGroup = config->group(group); 0550 QHeaderView *header = mView->header(); 0551 0552 QVariantList columnVisibility; 0553 QVariantList columnOrder; 0554 QVariantList columnWidths; 0555 const int headerCount = header->count(); 0556 columnVisibility.reserve(headerCount); 0557 columnWidths.reserve(headerCount); 0558 columnOrder.reserve(headerCount); 0559 for (int i = 0; i < headerCount; ++i) { 0560 columnVisibility << QVariant(!mView->isColumnHidden(i)); 0561 columnWidths << QVariant(header->sectionSize(i)); 0562 columnOrder << QVariant(header->visualIndex(i)); 0563 } 0564 cfgGroup.writeEntry("ColumnVisibility", columnVisibility); 0565 cfgGroup.writeEntry("ColumnOrder", columnOrder); 0566 cfgGroup.writeEntry("ColumnWidths", columnWidths); 0567 0568 cfgGroup.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); 0569 if (header->isSortIndicatorShown()) { 0570 cfgGroup.writeEntry("SortColumn", header->sortIndicatorSection()); 0571 } else { 0572 cfgGroup.writeEntry("SortColumn", -1); 0573 } 0574 0575 if (!mSidebarView) { 0576 preferences()->setFullViewTodo(mFullViewButton->isChecked()); 0577 } 0578 preferences()->setFlatListTodo(mFlatViewButton->isChecked()); 0579 } 0580 0581 void TodoView::restoreLayout(KConfig *config, const QString &group, bool minimalDefaults) 0582 { 0583 KConfigGroup cfgGroup = config->group(group); 0584 QHeaderView *header = mView->header(); 0585 0586 QVariantList columnVisibility = cfgGroup.readEntry("ColumnVisibility", QVariantList()); 0587 QVariantList columnOrder = cfgGroup.readEntry("ColumnOrder", QVariantList()); 0588 QVariantList columnWidths = cfgGroup.readEntry("ColumnWidths", QVariantList()); 0589 0590 if (columnVisibility.isEmpty()) { 0591 // if config is empty then use default settings 0592 mView->hideColumn(Akonadi::TodoModel::RecurColumn); 0593 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn); 0594 mView->hideColumn(Akonadi::TodoModel::CalendarColumn); 0595 mView->hideColumn(Akonadi::TodoModel::CompletedDateColumn); 0596 0597 if (minimalDefaults) { 0598 mView->hideColumn(Akonadi::TodoModel::PriorityColumn); 0599 mView->hideColumn(Akonadi::TodoModel::PercentColumn); 0600 mView->hideColumn(Akonadi::TodoModel::DescriptionColumn); 0601 mView->hideColumn(Akonadi::TodoModel::CategoriesColumn); 0602 } 0603 0604 // We don't have any incidences (content) yet, so we delay resizing 0605 QTimer::singleShot(0, this, &TodoView::resizeColumns); 0606 } else { 0607 for (int i = 0; i < header->count() && i < columnOrder.size() && i < columnWidths.size() && i < columnVisibility.size(); i++) { 0608 bool visible = columnVisibility[i].toBool(); 0609 int width = columnWidths[i].toInt(); 0610 int order = columnOrder[i].toInt(); 0611 0612 header->resizeSection(i, width); 0613 header->moveSection(header->visualIndex(i), order); 0614 if (i != 0 && !visible) { 0615 mView->hideColumn(i); 0616 } 0617 } 0618 } 0619 0620 int sortOrder = cfgGroup.readEntry("SortAscending", (int)Qt::AscendingOrder); 0621 int sortColumn = cfgGroup.readEntry("SortColumn", -1); 0622 if (sortColumn >= 0) { 0623 mView->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); 0624 } 0625 0626 mFlatViewButton->setChecked(cfgGroup.readEntry("FlatView", false)); 0627 } 0628 0629 void TodoView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) 0630 { 0631 EventView::setIncidenceChanger(changer); 0632 sModels->todoModel->setIncidenceChanger(changer); 0633 } 0634 0635 void TodoView::showDates(const QDate &start, const QDate &end, const QDate &) 0636 { 0637 // There is nothing to do here for the Todo View 0638 Q_UNUSED(start) 0639 Q_UNUSED(end) 0640 } 0641 0642 void TodoView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) 0643 { 0644 Q_UNUSED(incidenceList) 0645 Q_UNUSED(date) 0646 } 0647 0648 void TodoView::updateView() 0649 { 0650 // View is always updated, it's connected to ETM. 0651 } 0652 0653 void TodoView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType) 0654 { 0655 // Don't do anything, model is connected to ETM, it's up to date 0656 } 0657 0658 void TodoView::updateConfig() 0659 { 0660 Q_ASSERT(preferences()); 0661 if (!mSidebarView && mQuickSearch) { 0662 mQuickSearch->setVisible(preferences()->enableTodoQuickSearch()); 0663 } 0664 0665 if (mQuickAdd) { 0666 mQuickAdd->setVisible(preferences()->enableQuickTodo()); 0667 } 0668 0669 if (mProxyModel) { 0670 mProxyModel->invalidate(); 0671 } 0672 0673 updateView(); 0674 } 0675 0676 void TodoView::clearSelection() 0677 { 0678 mView->selectionModel()->clearSelection(); 0679 } 0680 0681 void TodoView::addTodo(const QString &summary, const Akonadi::Item &parentItem, const QStringList &categories) 0682 { 0683 const QString summaryTrimmed = summary.trimmed(); 0684 if (!changer() || summaryTrimmed.isEmpty()) { 0685 return; 0686 } 0687 0688 KCalendarCore::Todo::Ptr parent = Akonadi::CalendarUtils::todo(parentItem); 0689 0690 KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo); 0691 todo->setSummary(summaryTrimmed); 0692 todo->setOrganizer(Person(CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email())); 0693 0694 todo->setCategories(categories); 0695 0696 if (parent && !parent->hasRecurrenceId()) { 0697 todo->setRelatedTo(parent->uid()); 0698 } 0699 0700 Akonadi::Collection collection; 0701 0702 // Use the same collection of the parent. 0703 if (parentItem.isValid()) { 0704 // Don't use parentColection() since it might be a virtual collection 0705 collection = Akonadi::EntityTreeModel::updatedCollection(model(), parentItem.storageCollectionId()); 0706 } 0707 0708 changer()->createIncidence(todo, collection, this); 0709 } 0710 0711 void TodoView::addQuickTodo(Qt::KeyboardModifiers modifiers) 0712 { 0713 if (modifiers == Qt::NoModifier) { 0714 /*const QModelIndex index = */ 0715 addTodo(mQuickAdd->text(), Akonadi::Item(), mProxyModel->categories()); 0716 } else if (modifiers == Qt::ControlModifier) { 0717 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0718 if (selection.count() != 1) { 0719 qCWarning(CALENDARVIEW_LOG) << "No to-do selected" << selection; 0720 return; 0721 } 0722 const QModelIndex idx = mProxyModel->mapToSource(selection[0]); 0723 mView->expand(selection[0]); 0724 const auto parent = sModels->coloredTodoModel->data(idx, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0725 addTodo(mQuickAdd->text(), parent, mProxyModel->categories()); 0726 } else { 0727 return; 0728 } 0729 mQuickAdd->setText(QString()); 0730 } 0731 0732 void TodoView::contextMenu(QPoint pos) 0733 { 0734 const bool hasItem = mView->indexAt(pos).isValid(); 0735 Incidence::Ptr incidencePtr; 0736 0737 for (QAction *entry : std::as_const(mItemPopupMenuItemOnlyEntries)) { 0738 bool enable; 0739 0740 if (hasItem) { 0741 const Akonadi::Item::List incidences = selectedIncidences(); 0742 0743 if (incidences.isEmpty()) { 0744 enable = false; 0745 } else { 0746 Akonadi::Item item = incidences.first(); 0747 incidencePtr = Akonadi::CalendarUtils::incidence(item); 0748 0749 // Action isn't RO, it can change the incidence, "Edit" for example. 0750 const bool actionIsRw = mItemPopupMenuReadWriteEntries.contains(entry); 0751 0752 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), item.storageCollectionId()); 0753 const bool incidenceIsRO = collection.rights() & Akonadi::Collection::CanChangeItem; 0754 0755 enable = hasItem && (!actionIsRw || !incidenceIsRO); 0756 } 0757 } else { 0758 enable = false; 0759 } 0760 0761 entry->setEnabled(enable); 0762 } 0763 mCopyPopupMenu->setEnabled(hasItem); 0764 mMovePopupMenu->setEnabled(hasItem); 0765 0766 if (hasItem) { 0767 if (incidencePtr) { 0768 const bool hasRecId = incidencePtr->hasRecurrenceId(); 0769 const bool hasSubtodos = mView->model()->hasChildren(mView->indexAt(pos)); 0770 0771 mMakeSubtodosIndependent->setEnabled(!hasRecId && hasSubtodos); 0772 mMakeTodoIndependent->setEnabled(!hasRecId && !incidencePtr->relatedTo().isEmpty()); 0773 } 0774 0775 switch (mView->indexAt(pos).column()) { 0776 case Akonadi::TodoModel::PriorityColumn: 0777 mPriorityPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); 0778 break; 0779 case Akonadi::TodoModel::PercentColumn: 0780 mPercentageCompletedPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); 0781 break; 0782 case Akonadi::TodoModel::StartDateColumn: 0783 mView->startPopupMenu()->popup(mView->viewport()->mapToGlobal(pos)); 0784 break; 0785 case Akonadi::TodoModel::DueDateColumn: 0786 mMovePopupMenu->popup(mView->viewport()->mapToGlobal(pos)); 0787 break; 0788 case Akonadi::TodoModel::CategoriesColumn: 0789 createCategoryPopupMenu()->popup(mView->viewport()->mapToGlobal(pos)); 0790 break; 0791 default: 0792 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); 0793 break; 0794 } 0795 } else { 0796 mItemPopupMenu->popup(mView->viewport()->mapToGlobal(pos)); 0797 } 0798 } 0799 0800 void TodoView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) 0801 { 0802 Q_UNUSED(deselected) 0803 QModelIndexList selection = selected.indexes(); 0804 if (selection.isEmpty() || !selection[0].isValid()) { 0805 Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); 0806 return; 0807 } 0808 0809 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0810 0811 if (selectedIncidenceDates().isEmpty()) { 0812 Q_EMIT incidenceSelected(todoItem, QDate()); 0813 } else { 0814 Q_EMIT incidenceSelected(todoItem, selectedIncidenceDates().at(0)); 0815 } 0816 } 0817 0818 void TodoView::showTodo() 0819 { 0820 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0821 if (selection.size() != 1) { 0822 return; 0823 } 0824 0825 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0826 0827 Q_EMIT showIncidenceSignal(todoItem); 0828 } 0829 0830 void TodoView::editTodo() 0831 { 0832 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0833 if (selection.size() != 1) { 0834 return; 0835 } 0836 0837 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0838 Q_EMIT editIncidenceSignal(todoItem); 0839 } 0840 0841 void TodoView::deleteTodo() 0842 { 0843 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0844 if (selection.size() == 1) { 0845 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0846 0847 if (!changer()->deletedRecently(todoItem.id())) { 0848 Q_EMIT deleteIncidenceSignal(todoItem); 0849 } 0850 } 0851 } 0852 0853 void TodoView::newTodo() 0854 { 0855 Q_EMIT newTodoSignal(QDate::currentDate().addDays(7)); 0856 } 0857 0858 void TodoView::newSubTodo() 0859 { 0860 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0861 if (selection.size() == 1) { 0862 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0863 0864 Q_EMIT newSubTodoSignal(todoItem); 0865 } else { 0866 // This never happens 0867 qCWarning(CALENDARVIEW_LOG) << "Selection size isn't 1"; 0868 } 0869 } 0870 0871 void TodoView::copyTodoToDate(QDate date) 0872 { 0873 if (!changer()) { 0874 return; 0875 } 0876 0877 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0878 if (selection.size() != 1) { 0879 return; 0880 } 0881 0882 const QModelIndex origIndex = mProxyModel->mapToSource(selection[0]); 0883 Q_ASSERT(origIndex.isValid()); 0884 0885 const auto origItem = sModels->coloredTodoModel->data(origIndex, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0886 0887 const KCalendarCore::Todo::Ptr orig = Akonadi::CalendarUtils::todo(origItem); 0888 if (!orig) { 0889 return; 0890 } 0891 0892 KCalendarCore::Todo::Ptr todo(orig->clone()); 0893 0894 todo->setUid(KCalendarCore::CalFormat::createUniqueId()); 0895 0896 QDateTime due = todo->dtDue(); 0897 due.setDate(date); 0898 todo->setDtDue(due); 0899 0900 changer()->createIncidence(todo, Akonadi::Collection(), this); 0901 } 0902 0903 void TodoView::scheduleResizeColumns() 0904 { 0905 mResizeColumnsScheduled = true; 0906 mResizeColumnsTimer->start(); // restarts the timer if already active 0907 } 0908 0909 void TodoView::itemDoubleClicked(const QModelIndex &index) 0910 { 0911 if (index.isValid()) { 0912 QModelIndex summary = index.sibling(index.row(), Akonadi::TodoModel::SummaryColumn); 0913 if (summary.flags() & Qt::ItemIsEditable) { 0914 editTodo(); 0915 } else { 0916 showTodo(); 0917 } 0918 } 0919 } 0920 0921 QMenu *TodoView::createCategoryPopupMenu() 0922 { 0923 auto tempMenu = new QMenu(this); 0924 0925 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0926 if (selection.size() != 1) { 0927 return tempMenu; 0928 } 0929 0930 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0931 KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem); 0932 Q_ASSERT(todo); 0933 0934 const QStringList checkedCategories = todo->categories(); 0935 0936 auto tagFetchJob = new Akonadi::TagFetchJob(this); 0937 connect(tagFetchJob, &Akonadi::TagFetchJob::result, this, &TodoView::onTagsFetched); 0938 tagFetchJob->setProperty("menu", QVariant::fromValue(QPointer<QMenu>(tempMenu))); 0939 tagFetchJob->setProperty("checkedCategories", checkedCategories); 0940 0941 connect(tempMenu, &QMenu::triggered, this, &TodoView::changedCategories); 0942 connect(tempMenu, &QMenu::aboutToHide, tempMenu, &QMenu::deleteLater); 0943 return tempMenu; 0944 } 0945 0946 void TodoView::onTagsFetched(KJob *job) 0947 { 0948 if (job->error()) { 0949 qCWarning(CALENDARVIEW_LOG) << "Failed to fetch tags " << job->errorString(); 0950 return; 0951 } 0952 auto fetchJob = static_cast<Akonadi::TagFetchJob *>(job); 0953 const QStringList checkedCategories = job->property("checkedCategories").toStringList(); 0954 auto menu = job->property("menu").value<QPointer<QMenu>>(); 0955 if (menu) { 0956 const auto lst = fetchJob->tags(); 0957 for (const Akonadi::Tag &tag : lst) { 0958 const QString name = tag.name(); 0959 QAction *action = menu->addAction(name); 0960 action->setCheckable(true); 0961 action->setData(name); 0962 if (checkedCategories.contains(name)) { 0963 action->setChecked(true); 0964 } 0965 } 0966 } 0967 } 0968 0969 void TodoView::setNewDate(QDate date) 0970 { 0971 QModelIndexList selection = mView->selectionModel()->selectedRows(); 0972 if (selection.size() != 1) { 0973 return; 0974 } 0975 0976 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 0977 KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem); 0978 Q_ASSERT(todo); 0979 0980 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId()); 0981 if (collection.rights() & Akonadi::Collection::CanChangeItem) { 0982 KCalendarCore::Todo::Ptr oldTodo(todo->clone()); 0983 QDateTime dt(date.startOfDay()); 0984 0985 if (!todo->allDay()) { 0986 dt.setTime(todo->dtDue().time()); 0987 } 0988 0989 if (todo->hasStartDate() && dt < todo->dtStart()) { 0990 todo->setDtStart(dt); 0991 } 0992 todo->setDtDue(dt); 0993 0994 changer()->modifyIncidence(todoItem, oldTodo, this); 0995 } else { 0996 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly"; 0997 } 0998 } 0999 1000 void TodoView::setStartDate(QDate date) 1001 { 1002 QModelIndexList selection = mView->selectionModel()->selectedRows(); 1003 if (selection.size() != 1) { 1004 return; 1005 } 1006 1007 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 1008 KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem); 1009 Q_ASSERT(todo); 1010 1011 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId()); 1012 if (collection.rights() & Akonadi::Collection::CanChangeItem) { 1013 KCalendarCore::Todo::Ptr oldTodo(todo->clone()); 1014 QDateTime dt(date.startOfDay()); 1015 1016 if (!todo->allDay()) { 1017 dt.setTime(todo->dtStart().time()); 1018 } 1019 1020 if (todo->hasDueDate() && dt > todo->dtDue()) { 1021 todo->setDtDue(dt); 1022 } 1023 todo->setDtStart(dt); 1024 1025 changer()->modifyIncidence(todoItem, oldTodo, this); 1026 } else { 1027 qCDebug(CALENDARVIEW_LOG) << "Item is readOnly"; 1028 } 1029 } 1030 1031 void TodoView::setNewPercentage(QAction *action) 1032 { 1033 QModelIndexList selection = mView->selectionModel()->selectedRows(); 1034 if (selection.size() != 1) { 1035 return; 1036 } 1037 1038 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 1039 KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem); 1040 Q_ASSERT(todo); 1041 1042 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId()); 1043 if (collection.rights() & Akonadi::Collection::CanChangeItem) { 1044 KCalendarCore::Todo::Ptr oldTodo(todo->clone()); 1045 1046 int percentage = mPercentage.value(action); 1047 if (percentage == 100) { 1048 todo->setCompleted(QDateTime::currentDateTime()); 1049 todo->setPercentComplete(100); 1050 } else { 1051 todo->setPercentComplete(percentage); 1052 } 1053 changer()->modifyIncidence(todoItem, oldTodo, this); 1054 } else { 1055 qCDebug(CALENDARVIEW_LOG) << "Item is read only"; 1056 } 1057 } 1058 1059 void TodoView::setNewPriority(QAction *action) 1060 { 1061 const QModelIndexList selection = mView->selectionModel()->selectedRows(); 1062 if (selection.size() != 1) { 1063 return; 1064 } 1065 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 1066 KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem); 1067 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId()); 1068 if (collection.rights() & Akonadi::Collection::CanChangeItem) { 1069 KCalendarCore::Todo::Ptr oldTodo(todo->clone()); 1070 todo->setPriority(mPriority[action]); 1071 1072 changer()->modifyIncidence(todoItem, oldTodo, this); 1073 } 1074 } 1075 1076 void TodoView::changedCategories(QAction *action) 1077 { 1078 const QModelIndexList selection = mView->selectionModel()->selectedRows(); 1079 if (selection.size() != 1) { 1080 return; 1081 } 1082 1083 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 1084 KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(todoItem); 1085 Q_ASSERT(todo); 1086 const auto collection = Akonadi::EntityTreeModel::updatedCollection(model(), todoItem.storageCollectionId()); 1087 if (collection.rights() & Akonadi::Collection::CanChangeItem) { 1088 KCalendarCore::Todo::Ptr oldTodo(todo->clone()); 1089 1090 const QString cat = action->data().toString(); 1091 QStringList categories = todo->categories(); 1092 if (categories.contains(cat)) { 1093 categories.removeAll(cat); 1094 } else { 1095 categories.append(cat); 1096 } 1097 categories.sort(); 1098 todo->setCategories(categories); 1099 changer()->modifyIncidence(todoItem, oldTodo, this); 1100 } else { 1101 qCDebug(CALENDARVIEW_LOG) << "No active item, active item is read-only, or locking failed"; 1102 } 1103 } 1104 1105 void TodoView::setFullView(bool fullView) 1106 { 1107 if (!mFullViewButton) { 1108 return; 1109 } 1110 1111 mFullViewButton->setChecked(fullView); 1112 if (fullView) { 1113 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-restore"))); 1114 } else { 1115 mFullViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); 1116 } 1117 1118 mFullViewButton->blockSignals(true); 1119 // We block signals to avoid recursion; there are two TodoViews and 1120 // also mFullViewButton is synchronized. 1121 mFullViewButton->setChecked(fullView); 1122 mFullViewButton->blockSignals(false); 1123 1124 preferences()->setFullViewTodo(fullView); 1125 preferences()->writeConfig(); 1126 1127 Q_EMIT fullViewChanged(fullView); 1128 } 1129 1130 void TodoView::setFlatView(bool flatView, bool notifyOtherViews) 1131 { 1132 if (flatView) { 1133 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); 1134 } else { 1135 mFlatViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details"))); 1136 } 1137 1138 if (notifyOtherViews) { 1139 sModels->setFlatView(flatView); 1140 } 1141 } 1142 1143 void TodoView::onRowsInserted(const QModelIndex &parent, int start, int end) 1144 { 1145 if (start != end || !entityTreeModel()) { 1146 return; 1147 } 1148 1149 QModelIndex idx = mView->model()->index(start, 0); 1150 1151 // If the collection is currently being populated, we don't do anything 1152 QVariant v = idx.data(Akonadi::EntityTreeModel::ItemRole); 1153 if (!v.isValid()) { 1154 return; 1155 } 1156 1157 auto item = v.value<Akonadi::Item>(); 1158 if (!item.isValid()) { 1159 return; 1160 } 1161 1162 const bool isPopulated = entityTreeModel()->isCollectionPopulated(item.storageCollectionId()); 1163 if (!isPopulated) { 1164 return; 1165 } 1166 1167 // Case #1, adding an item that doesn't have parent: We select it 1168 if (!parent.isValid()) { 1169 QModelIndexList selection = mView->selectionModel()->selectedRows(); 1170 if (selection.size() <= 1) { 1171 // don't destroy complex selections, not applicable now (only single 1172 // selection allowed), but for the future... 1173 int colCount = static_cast<int>(Akonadi::TodoModel::ColumnCount); 1174 mView->selectionModel()->select(QItemSelection(idx, mView->model()->index(start, colCount - 1)), 1175 QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); 1176 } 1177 return; 1178 } 1179 1180 // Case 2: Adding an item that has a parent: we expand the parent 1181 if (sModels->isFlatView()) { 1182 return; 1183 } 1184 1185 QModelIndex index = parent; 1186 mView->expand(index); 1187 while (index.parent().isValid()) { 1188 mView->expand(index.parent()); 1189 index = index.parent(); 1190 } 1191 } 1192 1193 void TodoView::getHighlightMode(bool &highlightEvents, bool &highlightTodos, bool &highlightJournals) 1194 { 1195 highlightTodos = preferences()->highlightTodos(); 1196 highlightEvents = !highlightTodos; 1197 highlightJournals = false; 1198 } 1199 1200 bool TodoView::usesFullWindow() 1201 { 1202 return preferences()->fullViewTodo(); 1203 } 1204 1205 void TodoView::resizeColumns() 1206 { 1207 mResizeColumnsScheduled = false; 1208 1209 mView->resizeColumnToContents(Akonadi::TodoModel::StartDateColumn); 1210 mView->resizeColumnToContents(Akonadi::TodoModel::DueDateColumn); 1211 mView->resizeColumnToContents(Akonadi::TodoModel::CompletedDateColumn); 1212 mView->resizeColumnToContents(Akonadi::TodoModel::PriorityColumn); 1213 mView->resizeColumnToContents(Akonadi::TodoModel::CalendarColumn); 1214 mView->resizeColumnToContents(Akonadi::TodoModel::RecurColumn); 1215 mView->resizeColumnToContents(Akonadi::TodoModel::PercentColumn); 1216 1217 // We have 3 columns that should stretch: summary, description and categories. 1218 // Summary is always visible. 1219 const bool descriptionVisible = !mView->isColumnHidden(Akonadi::TodoModel::DescriptionColumn); 1220 const bool categoriesVisible = !mView->isColumnHidden(Akonadi::TodoModel::CategoriesColumn); 1221 1222 // Calculate size of non-stretchable columns: 1223 int size = 0; 1224 for (int i = 0; i < Akonadi::TodoModel::ColumnCount; ++i) { 1225 if (!mView->isColumnHidden(i) && i != Akonadi::TodoModel::SummaryColumn && i != Akonadi::TodoModel::DescriptionColumn && 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 1254 void 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 1275 QString TodoView::stateSaverGroup() const 1276 { 1277 QString str = QStringLiteral("TodoTreeViewState"); 1278 if (mSidebarView) { 1279 str += QLatin1Char('S'); 1280 } 1281 1282 return str; 1283 } 1284 1285 void TodoView::saveViewState() 1286 { 1287 Akonadi::ETMViewStateSaver treeStateSaver; 1288 KConfigGroup group(preferences()->config(), stateSaverGroup()); 1289 treeStateSaver.setView(mView); 1290 treeStateSaver.saveState(group); 1291 } 1292 1293 void TodoView::resizeEvent(QResizeEvent *event) 1294 { 1295 EventViews::EventView::resizeEvent(event); 1296 scheduleResizeColumns(); 1297 } 1298 1299 void 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 void TodoView::createNote() 1312 { 1313 const QModelIndexList selection = mView->selectionModel()->selectedRows(); 1314 if (selection.size() != 1) { 1315 return; 1316 } 1317 1318 const auto todoItem = selection[0].data(Akonadi::TodoModel::TodoRole).value<Akonadi::Item>(); 1319 1320 Q_EMIT createNote(todoItem); 1321 } 1322 1323 #include "todoview.moc" 1324 1325 #include "moc_todoview.cpp"