File indexing completed on 2025-03-09 04:51:38
0001 /* 0002 This file is part of KOrganizer. 0003 0004 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "multiagendaview.h" 0010 #include "akonadicollectionview.h" 0011 #include "calendarview.h" 0012 #include "koeventpopupmenu.h" 0013 #include "prefs/koprefs.h" 0014 #include "ui_multiagendaviewconfigwidget.h" 0015 0016 #include <EventViews/AgendaView> 0017 #include <EventViews/MultiAgendaView> 0018 0019 #include <Akonadi/EntityTreeModel> 0020 #include <Akonadi/EntityTreeView> 0021 0022 #include <KCheckableProxyModel> 0023 0024 #include <KRearrangeColumnsProxyModel> 0025 #include <QDialogButtonBox> 0026 #include <QPushButton> 0027 0028 #include <KConfigGroup> 0029 #include <QHBoxLayout> 0030 #include <QSortFilterProxyModel> 0031 #include <QStandardItem> 0032 0033 using namespace KOrg; 0034 0035 static QString generateColumnLabel(int c) 0036 { 0037 return i18n("Agenda %1", c + 1); 0038 } 0039 0040 class CalendarViewCalendarFactory : public EventViews::MultiAgendaView::CalendarFactory 0041 { 0042 public: 0043 using Ptr = QSharedPointer<CalendarViewCalendarFactory>; 0044 0045 CalendarViewCalendarFactory(CalendarViewBase *calendarView) 0046 : mView(calendarView) 0047 { 0048 } 0049 0050 Akonadi::CollectionCalendar::Ptr calendarForCollection(const Akonadi::Collection &collection) override 0051 { 0052 return mView->calendarForCollection(collection); 0053 } 0054 0055 private: 0056 CalendarViewBase *const mView; 0057 }; 0058 0059 class KOrg::MultiAgendaViewPrivate 0060 { 0061 public: 0062 MultiAgendaViewPrivate(CalendarViewBase *calendarView, MultiAgendaView *qq) 0063 : q(qq) 0064 { 0065 auto layout = new QHBoxLayout(q); 0066 mMultiAgendaView = new EventViews::MultiAgendaView(CalendarViewCalendarFactory::Ptr::create(calendarView), q); 0067 mMultiAgendaView->setPreferences(KOPrefs::instance()->eventViewsPreferences()); 0068 layout->addWidget(mMultiAgendaView); 0069 0070 mPopup = q->eventPopup(); 0071 } 0072 0073 EventViews::MultiAgendaView *mMultiAgendaView = nullptr; 0074 KOEventPopupMenu *mPopup = nullptr; 0075 KCheckableProxyModel *mCollectionSelectionModel = nullptr; 0076 Akonadi::Collection::Id mCollectionId = -1; 0077 0078 private: 0079 MultiAgendaView *const q; 0080 }; 0081 0082 MultiAgendaView::MultiAgendaView(CalendarViewBase *calendarView, QWidget *parent) 0083 : KOEventView(parent) 0084 , d(new MultiAgendaViewPrivate(calendarView, this)) 0085 { 0086 connect(d->mMultiAgendaView, &EventViews::EventView::datesSelected, this, &KOEventView::datesSelected); 0087 0088 connect(d->mMultiAgendaView, &EventViews::EventView::shiftedEvent, this, &KOEventView::shiftedEvent); 0089 0090 connect(d->mMultiAgendaView, &EventViews::MultiAgendaView::showIncidencePopupSignal, d->mPopup, &KOEventPopupMenu::showIncidencePopup); 0091 0092 connect(d->mMultiAgendaView, &EventViews::MultiAgendaView::showNewEventPopupSignal, this, &MultiAgendaView::showNewEventPopup); 0093 0094 connect(d->mMultiAgendaView, &EventViews::EventView::incidenceSelected, this, &BaseView::incidenceSelected); 0095 0096 connect(d->mMultiAgendaView, &EventViews::EventView::showIncidenceSignal, this, &BaseView::showIncidenceSignal); 0097 0098 connect(d->mMultiAgendaView, &EventViews::EventView::editIncidenceSignal, this, &BaseView::editIncidenceSignal); 0099 0100 connect(d->mMultiAgendaView, &EventViews::EventView::deleteIncidenceSignal, this, &BaseView::deleteIncidenceSignal); 0101 0102 connect(d->mMultiAgendaView, &EventViews::EventView::cutIncidenceSignal, this, &BaseView::cutIncidenceSignal); 0103 0104 connect(d->mMultiAgendaView, &EventViews::EventView::copyIncidenceSignal, this, &BaseView::copyIncidenceSignal); 0105 0106 connect(d->mMultiAgendaView, &EventViews::EventView::pasteIncidenceSignal, this, &BaseView::pasteIncidenceSignal); 0107 0108 connect(d->mMultiAgendaView, &EventViews::EventView::toggleAlarmSignal, this, &BaseView::toggleAlarmSignal); 0109 0110 connect(d->mMultiAgendaView, &EventViews::EventView::toggleTodoCompletedSignal, this, &BaseView::toggleTodoCompletedSignal); 0111 0112 connect(d->mMultiAgendaView, &EventViews::EventView::copyIncidenceToResourceSignal, this, &BaseView::copyIncidenceToResourceSignal); 0113 0114 connect(d->mMultiAgendaView, &EventViews::EventView::moveIncidenceToResourceSignal, this, &BaseView::moveIncidenceToResourceSignal); 0115 0116 connect(d->mMultiAgendaView, &EventViews::EventView::dissociateOccurrencesSignal, this, &BaseView::dissociateOccurrencesSignal); 0117 0118 connect(d->mMultiAgendaView, qOverload<>(&EventViews::MultiAgendaView::newEventSignal), this, qOverload<>(&KOrg::MultiAgendaView::newEventSignal)); 0119 0120 connect(d->mMultiAgendaView, 0121 qOverload<const QDate &>(&EventViews::MultiAgendaView::newEventSignal), 0122 this, 0123 qOverload<const QDate &>(&KOrg::MultiAgendaView::newEventSignal)); 0124 0125 connect(d->mMultiAgendaView, 0126 qOverload<const QDateTime &>(&EventViews::MultiAgendaView::newEventSignal), 0127 this, 0128 qOverload<const QDateTime &>(&KOrg::MultiAgendaView::newEventSignal)); 0129 0130 connect(d->mMultiAgendaView, 0131 qOverload<const QDateTime &, const QDateTime &>(&EventViews::MultiAgendaView::newEventSignal), 0132 this, 0133 qOverload<const QDateTime &, const QDateTime &>(&KOrg::MultiAgendaView::newEventSignal)); 0134 0135 connect(d->mMultiAgendaView, &EventViews::EventView::newTodoSignal, this, &BaseView::newTodoSignal); 0136 0137 connect(d->mMultiAgendaView, &EventViews::EventView::newSubTodoSignal, this, &BaseView::newSubTodoSignal); 0138 0139 connect(d->mMultiAgendaView, &EventViews::EventView::newJournalSignal, this, &BaseView::newJournalSignal); 0140 0141 connect(d->mMultiAgendaView, &EventViews::MultiAgendaView::activeCalendarChanged, this, [this](const Akonadi::CollectionCalendar::Ptr &calendar) { 0142 if (calendar) { 0143 d->mCollectionId = calendar->collection().id(); 0144 } else { 0145 d->mCollectionId = -1; 0146 } 0147 }); 0148 } 0149 0150 MultiAgendaView::~MultiAgendaView() = default; 0151 0152 void MultiAgendaView::setModel(QAbstractItemModel *model) 0153 { 0154 KOEventView::setModel(model); 0155 d->mMultiAgendaView->setModel(model); 0156 } 0157 0158 Akonadi::Item::List MultiAgendaView::selectedIncidences() 0159 { 0160 return d->mMultiAgendaView->selectedIncidences(); 0161 } 0162 0163 KCalendarCore::DateList MultiAgendaView::selectedIncidenceDates() 0164 { 0165 return d->mMultiAgendaView->selectedIncidenceDates(); 0166 } 0167 0168 int MultiAgendaView::currentDateCount() const 0169 { 0170 return d->mMultiAgendaView->currentDateCount(); 0171 } 0172 0173 void MultiAgendaView::showDates(const QDate &start, const QDate &end, const QDate &) 0174 { 0175 d->mMultiAgendaView->showDates(start, end); 0176 } 0177 0178 void MultiAgendaView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) 0179 { 0180 d->mMultiAgendaView->showIncidences(incidenceList, date); 0181 } 0182 0183 void MultiAgendaView::updateView() 0184 { 0185 d->mMultiAgendaView->updateView(); 0186 } 0187 0188 Akonadi::Collection::Id MultiAgendaView::collectionId() const 0189 { 0190 return d->mCollectionId; 0191 } 0192 0193 void MultiAgendaView::changeIncidenceDisplay(const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType) 0194 { 0195 } 0196 0197 int MultiAgendaView::maxDatesHint() const 0198 { 0199 return EventViews::AgendaView::MAX_DAY_COUNT; 0200 } 0201 0202 void MultiAgendaView::setDateRange(const QDateTime &start, const QDateTime &end, const QDate &) 0203 { 0204 d->mMultiAgendaView->setDateRange(start, end); 0205 } 0206 0207 bool MultiAgendaView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) 0208 { 0209 return d->mMultiAgendaView->eventDurationHint(startDt, endDt, allDay); 0210 } 0211 0212 void MultiAgendaView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) 0213 { 0214 d->mMultiAgendaView->setIncidenceChanger(changer); 0215 } 0216 0217 void MultiAgendaView::updateConfig() 0218 { 0219 d->mMultiAgendaView->updateConfig(); 0220 } 0221 0222 void MultiAgendaView::setChanges(EventViews::EventView::Changes changes) 0223 { 0224 // Only ConfigChanged and FilterChanged should go from korg->AgendaView 0225 // All other values are already detected inside AgendaView. 0226 // We could just pass "changes", but korganizer does a very bad job at 0227 // determining what changed, for example if you move an incidence 0228 // the BaseView::setDateRange(...) is called causing DatesChanged 0229 // flag to be on, when no dates changed. 0230 EventViews::EventView::Changes c; 0231 if (changes.testFlag(EventViews::EventView::ConfigChanged)) { 0232 c = EventViews::EventView::ConfigChanged; 0233 } 0234 0235 if (changes.testFlag(EventViews::EventView::FilterChanged)) { 0236 c |= EventViews::EventView::FilterChanged; 0237 } 0238 0239 d->mMultiAgendaView->setChanges(c | d->mMultiAgendaView->changes()); 0240 } 0241 0242 bool MultiAgendaView::hasConfigurationDialog() const 0243 { 0244 // It has. And it's implemented in korg, not libeventviews. 0245 return true; 0246 } 0247 0248 void MultiAgendaView::showConfigurationDialog(QWidget *parent) 0249 { 0250 QPointer<MultiAgendaViewConfigDialog> dlg(new MultiAgendaViewConfigDialog(d->mCollectionSelectionModel, parent)); 0251 0252 dlg->setUseCustomColumns(d->mMultiAgendaView->customColumnSetupUsed()); 0253 dlg->setNumberOfColumns(d->mMultiAgendaView->customNumberOfColumns()); 0254 0255 QList<KCheckableProxyModel *> models = d->mMultiAgendaView->collectionSelectionModels(); 0256 for (int i = 0; i < models.size(); ++i) { 0257 dlg->setSelectionModel(i, models[i]); 0258 } 0259 0260 QStringList customColumnTitles = d->mMultiAgendaView->customColumnTitles(); 0261 const int numTitles = customColumnTitles.size(); 0262 for (int i = 0; i < numTitles; ++i) { 0263 dlg->setColumnTitle(i, customColumnTitles[i]); 0264 } 0265 0266 if (dlg->exec() == QDialog::Accepted) { 0267 d->mMultiAgendaView->customCollectionsChanged(dlg); 0268 } 0269 0270 delete dlg; 0271 } 0272 0273 KCheckableProxyModel *MultiAgendaView::takeCustomCollectionSelectionProxyModel() 0274 { 0275 return d->mMultiAgendaView->takeCustomCollectionSelectionProxyModel(); 0276 } 0277 0278 void MultiAgendaView::setCustomCollectionSelectionProxyModel(KCheckableProxyModel *model) 0279 { 0280 d->mMultiAgendaView->setCustomCollectionSelectionProxyModel(model); 0281 } 0282 0283 void MultiAgendaView::setCollectionSelectionProxyModel(KCheckableProxyModel *model) 0284 { 0285 d->mCollectionSelectionModel = model; 0286 } 0287 0288 class KOrg::MultiAgendaViewConfigDialogPrivate 0289 { 0290 public: 0291 MultiAgendaViewConfigDialog *const q; 0292 explicit MultiAgendaViewConfigDialogPrivate(QAbstractItemModel *base, MultiAgendaViewConfigDialog *qq) 0293 : q(qq) 0294 , baseModel(base) 0295 , currentColumn(0) 0296 { 0297 } 0298 0299 ~MultiAgendaViewConfigDialogPrivate() 0300 { 0301 qDeleteAll(newlyCreated); 0302 } 0303 0304 void setUpColumns(int n); 0305 AkonadiCollectionView *createView(KCheckableProxyModel *model); 0306 [[nodiscard]] AkonadiCollectionView *view(int index) const; 0307 QList<KCheckableProxyModel *> newlyCreated; 0308 QList<KCheckableProxyModel *> selections; 0309 QList<QString> titles; 0310 Ui::MultiAgendaViewConfigWidget ui; 0311 QStandardItemModel listModel; 0312 QAbstractItemModel *baseModel = nullptr; 0313 int currentColumn; 0314 }; 0315 0316 void MultiAgendaView::restoreConfig(const KConfigGroup &configGroup) 0317 { 0318 d->mMultiAgendaView->restoreConfig(configGroup); 0319 } 0320 0321 void MultiAgendaView::saveConfig(KConfigGroup &configGroup) 0322 { 0323 d->mMultiAgendaView->saveConfig(configGroup); 0324 } 0325 0326 void MultiAgendaView::calendarAdded(const Akonadi::CollectionCalendar::Ptr &calendar) 0327 { 0328 d->mMultiAgendaView->addCalendar(calendar); 0329 } 0330 0331 void MultiAgendaView::calendarRemoved(const Akonadi::CollectionCalendar::Ptr &calendar) 0332 { 0333 d->mMultiAgendaView->removeCalendar(calendar); 0334 } 0335 0336 MultiAgendaViewConfigDialog::MultiAgendaViewConfigDialog(QAbstractItemModel *baseModel, QWidget *parent) 0337 : QDialog(parent) 0338 , d(new MultiAgendaViewConfigDialogPrivate(baseModel, this)) 0339 { 0340 setWindowTitle(i18nc("@title:window", "Configure Side-By-Side View")); 0341 auto mainLayout = new QVBoxLayout(this); 0342 auto widget = new QWidget; 0343 d->ui.setupUi(widget); 0344 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0345 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0346 okButton->setDefault(true); 0347 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0348 connect(buttonBox, &QDialogButtonBox::accepted, this, &MultiAgendaViewConfigDialog::accept); 0349 connect(buttonBox, &QDialogButtonBox::rejected, this, &MultiAgendaViewConfigDialog::reject); 0350 mainLayout->addWidget(buttonBox); 0351 mainLayout->addWidget(widget); 0352 0353 d->ui.columnList->setModel(&d->listModel); 0354 connect(d->ui.columnList->selectionModel(), &QItemSelectionModel::currentChanged, this, &MultiAgendaViewConfigDialog::currentChanged); 0355 connect(d->ui.useCustomRB, &QAbstractButton::toggled, this, &MultiAgendaViewConfigDialog::useCustomToggled); 0356 connect(d->ui.columnNumberSB, &QSpinBox::valueChanged, this, &MultiAgendaViewConfigDialog::numberOfColumnsChanged); 0357 connect(d->ui.titleLE, &QLineEdit::textEdited, this, &MultiAgendaViewConfigDialog::titleEdited); 0358 d->setUpColumns(numberOfColumns()); 0359 useCustomToggled(false); 0360 } 0361 0362 void MultiAgendaViewConfigDialog::currentChanged(const QModelIndex &index) 0363 { 0364 if (!index.isValid()) { 0365 return; 0366 } 0367 0368 const int idx = index.data(Qt::UserRole).toInt(); 0369 d->ui.titleLE->setText(index.data(Qt::DisplayRole).toString()); 0370 d->ui.selectionStack->setCurrentIndex(idx); 0371 d->currentColumn = idx; 0372 } 0373 0374 void MultiAgendaViewConfigDialog::useCustomToggled(bool on) 0375 { 0376 d->ui.columnList->setEnabled(on); 0377 d->ui.columnNumberLabel->setEnabled(on); 0378 d->ui.columnNumberSB->setEnabled(on); 0379 d->ui.selectedCalendarsLabel->setEnabled(on); 0380 d->ui.selectionStack->setEnabled(on); 0381 d->ui.titleLabel->setEnabled(on); 0382 d->ui.titleLE->setEnabled(on); 0383 // this explicit enabling/disabling of the ETV is necessary, as the stack 0384 // widget state is not propagated to the collection views. Probably because 0385 // the Akonadi error overlays enable/disable the ETV explicitly and thus 0386 // override the parent-child relationship? 0387 for (int i = 0; i < d->ui.selectionStack->count(); ++i) { 0388 d->view(i)->view()->setEnabled(on); 0389 } 0390 } 0391 0392 AkonadiCollectionView *MultiAgendaViewConfigDialogPrivate::createView(KCheckableProxyModel *model) 0393 { 0394 auto cview = new AkonadiCollectionView(nullptr, false, q); 0395 cview->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0396 cview->setCollectionSelectionProxyModel(model); 0397 return cview; 0398 } 0399 0400 void MultiAgendaViewConfigDialogPrivate::setUpColumns(int n) 0401 { 0402 Q_ASSERT(n > 0); 0403 const int oldN = selections.size(); 0404 if (oldN == n) { 0405 return; 0406 } 0407 0408 if (n < oldN) { 0409 for (int i = oldN - 1; i >= n; --i) { 0410 QWidget *w = ui.selectionStack->widget(i); 0411 ui.selectionStack->removeWidget(w); 0412 delete w; 0413 qDeleteAll(listModel.takeRow(i)); 0414 KCheckableProxyModel *const m = selections[i]; 0415 selections.remove(i); 0416 const int pos = newlyCreated.indexOf(m); 0417 if (pos != -1) { 0418 delete m; 0419 newlyCreated.remove(pos); 0420 } 0421 } 0422 } else { 0423 selections.resize(n); 0424 for (int i = oldN; i < n; ++i) { 0425 auto item = new QStandardItem; 0426 item->setEditable(false); 0427 if (titles.count() <= i) { 0428 titles.resize(i + 1); 0429 titles[i] = generateColumnLabel(i); 0430 } 0431 item->setText(titles[i]); 0432 item->setData(i, Qt::UserRole); 0433 listModel.appendRow(item); 0434 0435 auto sortProxy = new QSortFilterProxyModel; 0436 sortProxy->setDynamicSortFilter(true); 0437 sortProxy->setSourceModel(baseModel); 0438 sortProxy->setObjectName(QStringLiteral("MultiAgendaColumnSetupProxyModel-%1").arg(i)); 0439 0440 auto columnFilterProxy = new KRearrangeColumnsProxyModel(sortProxy); 0441 columnFilterProxy->setSourceColumns(QList<int>() << Akonadi::ETMCalendar::CollectionTitle); 0442 columnFilterProxy->setSourceModel(sortProxy); 0443 0444 auto qsm = new QItemSelectionModel(columnFilterProxy, columnFilterProxy); 0445 0446 auto selection = new KCheckableProxyModel; 0447 selection->setObjectName(QStringLiteral("MultiAgendaColumnCheckableProxy-%1").arg(i)); 0448 selection->setSourceModel(columnFilterProxy); 0449 selection->setSelectionModel(qsm); 0450 0451 AkonadiCollectionView *cview = createView(selection); 0452 const int idx = ui.selectionStack->addWidget(cview); 0453 Q_ASSERT(i == idx); 0454 Q_UNUSED(idx) 0455 selections[i] = selection; 0456 newlyCreated.push_back(selection); 0457 } 0458 } 0459 } 0460 0461 bool MultiAgendaViewConfigDialog::useCustomColumns() const 0462 { 0463 return d->ui.useCustomRB->isChecked(); 0464 } 0465 0466 void MultiAgendaViewConfigDialog::setUseCustomColumns(bool custom) 0467 { 0468 if (custom) { 0469 d->ui.useCustomRB->setChecked(true); 0470 } else { 0471 d->ui.useDefaultRB->setChecked(true); 0472 } 0473 } 0474 0475 int MultiAgendaViewConfigDialog::numberOfColumns() const 0476 { 0477 return d->ui.columnNumberSB->value(); 0478 } 0479 0480 void MultiAgendaViewConfigDialog::setNumberOfColumns(int n) 0481 { 0482 d->ui.columnNumberSB->setValue(n); 0483 d->setUpColumns(n); 0484 } 0485 0486 KCheckableProxyModel *MultiAgendaViewConfigDialog::takeSelectionModel(int column) 0487 { 0488 if (column < 0 || column >= d->selections.size()) { 0489 return nullptr; 0490 } 0491 0492 KCheckableProxyModel *const m = d->selections[column]; 0493 d->newlyCreated.erase(std::remove(d->newlyCreated.begin(), d->newlyCreated.end(), m), d->newlyCreated.end()); 0494 return m; 0495 } 0496 0497 AkonadiCollectionView *MultiAgendaViewConfigDialogPrivate::view(int index) const 0498 { 0499 return qobject_cast<AkonadiCollectionView *>(ui.selectionStack->widget(index)); 0500 } 0501 0502 void MultiAgendaViewConfigDialog::setSelectionModel(int column, KCheckableProxyModel *model) 0503 { 0504 Q_ASSERT(column >= 0 && column < d->selections.size()); 0505 0506 KCheckableProxyModel *const m = d->selections[column]; 0507 if (m == model) { 0508 return; 0509 } 0510 0511 AkonadiCollectionView *cview = d->view(column); 0512 Q_ASSERT(cview); 0513 cview->setCollectionSelectionProxyModel(model); 0514 0515 if (d->newlyCreated.contains(m)) { 0516 d->newlyCreated.erase(std::remove(d->newlyCreated.begin(), d->newlyCreated.end(), m), d->newlyCreated.end()); 0517 delete m; 0518 } 0519 0520 d->selections[column] = model; 0521 } 0522 0523 void MultiAgendaViewConfigDialog::titleEdited(const QString &text) 0524 { 0525 d->titles[d->currentColumn] = text; 0526 d->listModel.item(d->currentColumn)->setText(text); 0527 } 0528 0529 void MultiAgendaViewConfigDialog::numberOfColumnsChanged(int number) 0530 { 0531 d->setUpColumns(number); 0532 } 0533 0534 QString MultiAgendaViewConfigDialog::columnTitle(int column) const 0535 { 0536 Q_ASSERT(column >= 0); 0537 return column >= d->titles.count() ? QString() : d->titles[column]; 0538 } 0539 0540 void MultiAgendaViewConfigDialog::setColumnTitle(int column, const QString &title) 0541 { 0542 Q_ASSERT(column >= 0); 0543 d->titles.resize(qMax(d->titles.size(), column + 1)); 0544 d->titles[column] = title; 0545 if (QStandardItem *const item = d->listModel.item(column)) { 0546 item->setText(title); 0547 } 0548 // TODO update LE if item is selected 0549 } 0550 0551 void MultiAgendaViewConfigDialog::accept() 0552 { 0553 d->newlyCreated.clear(); 0554 QDialog::accept(); 0555 } 0556 0557 MultiAgendaViewConfigDialog::~MultiAgendaViewConfigDialog() = default; 0558 0559 #include "moc_multiagendaview.cpp"