File indexing completed on 2024-11-17 04:42:40
0001 /* 0002 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org> 0003 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0004 SPDX-FileContributor: Sergio Martins <sergio.martins@kdab.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 #include "multiagendaview.h" 0009 0010 #include "agenda/agenda.h" 0011 #include "agenda/agendaview.h" 0012 #include "agenda/timelabelszone.h" 0013 #include "calendarview_debug.h" 0014 #include "configdialoginterface.h" 0015 #include "prefs.h" 0016 0017 #include <Akonadi/CalendarUtils> 0018 #include <Akonadi/ETMViewStateSaver> 0019 #include <Akonadi/EntityTreeModel> 0020 0021 #include <CalendarSupport/CollectionSelection> 0022 0023 #include <KCheckableProxyModel> 0024 #include <KLocalizedString> 0025 #include <KRearrangeColumnsProxyModel> 0026 #include <KViewStateMaintainer> 0027 0028 #include <QHBoxLayout> 0029 #include <QLabel> 0030 #include <QPainter> 0031 #include <QResizeEvent> 0032 #include <QScrollArea> 0033 #include <QScrollBar> 0034 #include <QSortFilterProxyModel> 0035 #include <QSplitter> 0036 #include <QTimer> 0037 0038 using namespace Akonadi; 0039 using namespace EventViews; 0040 0041 /** 0042 Function for debugging purposes: 0043 prints an object's sizeHint()/minimumSizeHint()/policy 0044 and it's children's too, recursively 0045 */ 0046 /* 0047 static void printObject( QObject *o, int level = 0 ) 0048 { 0049 QMap<int,QString> map; 0050 map.insert( 0, "Fixed" ); 0051 map.insert( 1, "Minimum" ); 0052 map.insert( 4, "Maximum" ); 0053 map.insert( 5, "Preferred" ); 0054 map.insert( 7, "Expanding" ); 0055 map.insert( 3, "MinimumExpaning" ); 0056 map.insert( 13, "Ignored" ); 0057 0058 QWidget *w = qobject_cast<QWidget*>( o ); 0059 0060 if ( w ) { 0061 qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o 0062 << w->sizeHint() << "/" << map[w->sizePolicy().verticalPolicy()] 0063 << "; minimumSize = " << w->minimumSize() 0064 << "; minimumSizeHint = " << w->minimumSizeHint(); 0065 } else { 0066 qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o ; 0067 } 0068 0069 foreach( QObject *child, o->children() ) { 0070 printObject( child, level + 1 ); 0071 } 0072 } 0073 */ 0074 0075 class DefaultCalendarFactory : public MultiAgendaView::CalendarFactory 0076 { 0077 public: 0078 using Ptr = QSharedPointer<DefaultCalendarFactory>; 0079 0080 explicit DefaultCalendarFactory(MultiAgendaView *view) 0081 : mView(view) 0082 { 0083 } 0084 0085 Akonadi::CollectionCalendar::Ptr calendarForCollection(const Akonadi::Collection &collection) override 0086 { 0087 return Akonadi::CollectionCalendar::Ptr::create(mView->entityTreeModel(), collection); 0088 } 0089 0090 private: 0091 MultiAgendaView *mView; 0092 }; 0093 0094 static QString generateColumnLabel(int c) 0095 { 0096 return i18n("Agenda %1", c + 1); 0097 } 0098 0099 class EventViews::MultiAgendaViewPrivate 0100 { 0101 public: 0102 explicit MultiAgendaViewPrivate(const MultiAgendaView::CalendarFactory::Ptr &factory, MultiAgendaView *qq) 0103 : q(qq) 0104 , mCalendarFactory(factory) 0105 { 0106 } 0107 0108 ~MultiAgendaViewPrivate() 0109 { 0110 qDeleteAll(mSelectionSavers); 0111 } 0112 0113 void addView(const Akonadi::CollectionCalendar::Ptr &calendar); 0114 void addView(KCheckableProxyModel *selectionProxy, const QString &title); 0115 AgendaView *createView(const QString &calendar); 0116 void deleteViews(); 0117 void setupViews(); 0118 void resizeScrollView(QSize size); 0119 void setActiveAgenda(AgendaView *view); 0120 0121 MultiAgendaView *const q; 0122 QList<AgendaView *> mAgendaViews; 0123 QList<QWidget *> mAgendaWidgets; 0124 QWidget *mTopBox = nullptr; 0125 QScrollArea *mScrollArea = nullptr; 0126 TimeLabelsZone *mTimeLabelsZone = nullptr; 0127 QSplitter *mLeftSplitter = nullptr; 0128 QSplitter *mRightSplitter = nullptr; 0129 QScrollBar *mScrollBar = nullptr; 0130 QWidget *mLeftBottomSpacer = nullptr; 0131 QWidget *mRightBottomSpacer = nullptr; 0132 QDate mStartDate, mEndDate; 0133 bool mUpdateOnShow = true; 0134 bool mPendingChanges = true; 0135 bool mCustomColumnSetupUsed = false; 0136 QList<KCheckableProxyModel *> mCollectionSelectionModels; 0137 QStringList mCustomColumnTitles; 0138 int mCustomNumberOfColumns = 2; 0139 QLabel *mLabel = nullptr; 0140 QWidget *mRightDummyWidget = nullptr; 0141 QHash<QString, KViewStateMaintainer<ETMViewStateSaver> *> mSelectionSavers; 0142 QMetaObject::Connection m_selectionChangeConn; 0143 MultiAgendaView::CalendarFactory::Ptr mCalendarFactory; 0144 }; 0145 0146 MultiAgendaView::MultiAgendaView(QWidget *parent) 0147 : MultiAgendaView(DefaultCalendarFactory::Ptr::create(this), parent) 0148 { 0149 } 0150 0151 MultiAgendaView::MultiAgendaView(const CalendarFactory::Ptr &factory, QWidget *parent) 0152 : EventView(parent) 0153 , d(new MultiAgendaViewPrivate(factory, this)) 0154 { 0155 auto topLevelLayout = new QHBoxLayout(this); 0156 topLevelLayout->setSpacing(0); 0157 topLevelLayout->setContentsMargins(0, 0, 0, 0); 0158 0159 // agendaheader is a VBox layout with default spacing containing two labels, 0160 // so the height is 2 * default font height + 2 * default vertical layout spacing 0161 // (that's vertical spacing between the labels and spacing between the header and the 0162 // top of the agenda grid) 0163 const auto spacing = style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing, nullptr, this); 0164 const int agendeHeaderHeight = 2 * QFontMetrics(font()).height() + 2 * spacing; 0165 0166 // Left sidebox 0167 { 0168 auto sideBox = new QWidget(this); 0169 auto sideBoxLayout = new QVBoxLayout(sideBox); 0170 sideBoxLayout->setSpacing(0); 0171 sideBoxLayout->setContentsMargins(0, agendeHeaderHeight, 0, 0); 0172 0173 // Splitter for full-day and regular agenda views 0174 d->mLeftSplitter = new QSplitter(Qt::Vertical, sideBox); 0175 sideBoxLayout->addWidget(d->mLeftSplitter, 1); 0176 0177 // Label for all-day view 0178 d->mLabel = new QLabel(i18n("All Day"), d->mLeftSplitter); 0179 d->mLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 0180 d->mLabel->setWordWrap(true); 0181 0182 auto timeLabelsBox = new QWidget(d->mLeftSplitter); 0183 auto timeLabelsBoxLayout = new QVBoxLayout(timeLabelsBox); 0184 timeLabelsBoxLayout->setSpacing(0); 0185 timeLabelsBoxLayout->setContentsMargins(0, 0, 0, 0); 0186 0187 d->mTimeLabelsZone = new TimeLabelsZone(timeLabelsBox, PrefsPtr(new Prefs())); 0188 timeLabelsBoxLayout->addWidget(d->mTimeLabelsZone); 0189 0190 // Compensate for horizontal scrollbars, if needed 0191 d->mLeftBottomSpacer = new QWidget(timeLabelsBox); 0192 timeLabelsBoxLayout->addWidget(d->mLeftBottomSpacer); 0193 0194 topLevelLayout->addWidget(sideBox); 0195 } 0196 0197 // Central area 0198 { 0199 d->mScrollArea = new QScrollArea(this); 0200 d->mScrollArea->setWidgetResizable(true); 0201 0202 d->mScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0203 d->mScrollArea->setFrameShape(QFrame::NoFrame); 0204 0205 d->mTopBox = new QWidget(d->mScrollArea->viewport()); 0206 auto mTopBoxHBoxLayout = new QHBoxLayout(d->mTopBox); 0207 mTopBoxHBoxLayout->setContentsMargins(0, 0, 0, 0); 0208 d->mScrollArea->setWidget(d->mTopBox); 0209 0210 topLevelLayout->addWidget(d->mScrollArea, 100); 0211 } 0212 0213 // Right side box (scrollbar) 0214 { 0215 auto sideBox = new QWidget(this); 0216 auto sideBoxLayout = new QVBoxLayout(sideBox); 0217 sideBoxLayout->setSpacing(0); 0218 sideBoxLayout->setContentsMargins(0, agendeHeaderHeight, 0, 0); 0219 0220 d->mRightSplitter = new QSplitter(Qt::Vertical, sideBox); 0221 sideBoxLayout->addWidget(d->mRightSplitter); 0222 0223 // Empty widget, equivalent to mLabel in the left box 0224 d->mRightDummyWidget = new QWidget(d->mRightSplitter); 0225 0226 d->mScrollBar = new QScrollBar(Qt::Vertical, d->mRightSplitter); 0227 0228 // Compensate for horizontal scrollbar, if needed 0229 d->mRightBottomSpacer = new QWidget(sideBox); 0230 sideBoxLayout->addWidget(d->mRightBottomSpacer); 0231 0232 topLevelLayout->addWidget(sideBox); 0233 } 0234 0235 // BUG: compensate for agenda view's frames to make sure time labels are aligned 0236 d->mTimeLabelsZone->setContentsMargins(0, d->mScrollArea->frameWidth(), 0, d->mScrollArea->frameWidth()); 0237 0238 connect(d->mLeftSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters); 0239 connect(d->mRightSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters); 0240 } 0241 0242 void MultiAgendaView::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0243 { 0244 EventView::addCalendar(calendar); 0245 d->mPendingChanges = true; 0246 recreateViews(); 0247 } 0248 0249 void MultiAgendaView::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0250 { 0251 EventView::removeCalendar(calendar); 0252 d->mPendingChanges = true; 0253 recreateViews(); 0254 } 0255 0256 void MultiAgendaView::setModel(QAbstractItemModel *model) 0257 { 0258 EventView::setModel(model); 0259 // Workaround: when we create the multiagendaview with custom columns too early 0260 // during start, when Collections in ETM are not fully loaded yet, then 0261 // the KCheckableProxyModels are restored from config with incomplete selections. 0262 // But when the Collections are finally loaded into ETM, there's nothing to update 0263 // the selections, so we end up with some calendars not displayed in the individual 0264 // AgendaViews. Thus, we force-recreate everything once collection tree is fetched. 0265 connect( 0266 entityTreeModel(), 0267 &Akonadi::EntityTreeModel::collectionTreeFetched, 0268 this, 0269 [this]() { 0270 d->mPendingChanges = true; 0271 recreateViews(); 0272 }, 0273 Qt::QueuedConnection); 0274 } 0275 0276 void MultiAgendaView::recreateViews() 0277 { 0278 if (!d->mPendingChanges) { 0279 return; 0280 } 0281 d->mPendingChanges = false; 0282 0283 d->deleteViews(); 0284 0285 if (d->mCustomColumnSetupUsed) { 0286 Q_ASSERT(d->mCollectionSelectionModels.size() == d->mCustomNumberOfColumns); 0287 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) { 0288 d->addView(d->mCollectionSelectionModels[i], d->mCustomColumnTitles[i]); 0289 } 0290 } else { 0291 for (const auto &calendar : calendars()) { 0292 d->addView(calendar); 0293 } 0294 } 0295 0296 // no resources activated, so stop here to avoid crashing somewhere down the line 0297 // TODO: show a nice message instead 0298 if (d->mAgendaViews.isEmpty()) { 0299 return; 0300 } 0301 0302 d->setupViews(); 0303 QTimer::singleShot(0, this, &MultiAgendaView::slotResizeScrollView); 0304 d->mTimeLabelsZone->updateAll(); 0305 0306 QScrollArea *timeLabel = d->mTimeLabelsZone->timeLabels().at(0); 0307 connect(timeLabel->verticalScrollBar(), &QAbstractSlider::valueChanged, d->mScrollBar, &QAbstractSlider::setValue); 0308 connect(d->mScrollBar, &QAbstractSlider::valueChanged, timeLabel->verticalScrollBar(), &QAbstractSlider::setValue); 0309 0310 // On initial view, sync our splitter sizes with the agenda 0311 if (d->mAgendaViews.size() == 1) { 0312 d->mLeftSplitter->setSizes(d->mAgendaViews[0]->splitter()->sizes()); 0313 d->mRightSplitter->setSizes(d->mAgendaViews[0]->splitter()->sizes()); 0314 } 0315 resizeSplitters(); 0316 QTimer::singleShot(0, this, &MultiAgendaView::setupScrollBar); 0317 0318 d->mTimeLabelsZone->updateTimeLabelsPosition(); 0319 } 0320 0321 void MultiAgendaView::forceRecreateViews() 0322 { 0323 d->mPendingChanges = true; 0324 recreateViews(); 0325 } 0326 0327 void MultiAgendaViewPrivate::deleteViews() 0328 { 0329 for (AgendaView *const i : std::as_const(mAgendaViews)) { 0330 KCheckableProxyModel *proxy = i->takeCustomCollectionSelectionProxyModel(); 0331 if (proxy && !mCollectionSelectionModels.contains(proxy)) { 0332 delete proxy; 0333 } 0334 delete i; 0335 } 0336 0337 mAgendaViews.clear(); 0338 mTimeLabelsZone->setAgendaView(nullptr); 0339 qDeleteAll(mAgendaWidgets); 0340 mAgendaWidgets.clear(); 0341 } 0342 0343 void MultiAgendaViewPrivate::setupViews() 0344 { 0345 for (AgendaView *agendaView : std::as_const(mAgendaViews)) { 0346 q->connect(agendaView, qOverload<>(&EventView::newEventSignal), q, qOverload<>(&EventView::newEventSignal)); 0347 q->connect(agendaView, qOverload<const QDate &>(&EventView::newEventSignal), q, qOverload<const QDate &>(&EventView::newEventSignal)); 0348 q->connect(agendaView, qOverload<const QDateTime &>(&EventView::newEventSignal), q, qOverload<const QDateTime &>(&EventView::newEventSignal)); 0349 q->connect(agendaView, 0350 qOverload<const QDateTime &, const QDateTime &>(&EventView::newEventSignal), 0351 q, 0352 qOverload<const QDateTime &>(&EventView::newEventSignal)); 0353 0354 q->connect(agendaView, &EventView::editIncidenceSignal, q, &EventView::editIncidenceSignal); 0355 q->connect(agendaView, &EventView::showIncidenceSignal, q, &EventView::showIncidenceSignal); 0356 q->connect(agendaView, &EventView::deleteIncidenceSignal, q, &EventView::deleteIncidenceSignal); 0357 0358 q->connect(agendaView, &EventView::incidenceSelected, q, &EventView::incidenceSelected); 0359 0360 q->connect(agendaView, &EventView::cutIncidenceSignal, q, &EventView::cutIncidenceSignal); 0361 q->connect(agendaView, &EventView::copyIncidenceSignal, q, &EventView::copyIncidenceSignal); 0362 q->connect(agendaView, &EventView::pasteIncidenceSignal, q, &EventView::pasteIncidenceSignal); 0363 q->connect(agendaView, &EventView::toggleAlarmSignal, q, &EventView::toggleAlarmSignal); 0364 q->connect(agendaView, &EventView::dissociateOccurrencesSignal, q, &EventView::dissociateOccurrencesSignal); 0365 0366 q->connect(agendaView, &EventView::newTodoSignal, q, &EventView::newTodoSignal); 0367 0368 q->connect(agendaView, &EventView::incidenceSelected, q, &MultiAgendaView::slotSelectionChanged); 0369 0370 q->connect(agendaView, &AgendaView::timeSpanSelectionChanged, q, &MultiAgendaView::slotClearTimeSpanSelection); 0371 0372 q->disconnect(agendaView->agenda(), &Agenda::zoomView, agendaView, nullptr); 0373 q->connect(agendaView->agenda(), &Agenda::zoomView, q, &MultiAgendaView::zoomView); 0374 } 0375 0376 AgendaView *lastView = mAgendaViews.last(); 0377 for (AgendaView *agendaView : std::as_const(mAgendaViews)) { 0378 if (agendaView != lastView) { 0379 q->connect(agendaView->agenda()->verticalScrollBar(), 0380 &QAbstractSlider::valueChanged, 0381 lastView->agenda()->verticalScrollBar(), 0382 &QAbstractSlider::setValue); 0383 } 0384 } 0385 0386 for (AgendaView *agenda : std::as_const(mAgendaViews)) { 0387 agenda->readSettings(); 0388 } 0389 } 0390 0391 MultiAgendaView::~MultiAgendaView() = default; 0392 0393 Akonadi::Item::List MultiAgendaView::selectedIncidences() const 0394 { 0395 Akonadi::Item::List list; 0396 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) { 0397 list += agendaView->selectedIncidences(); 0398 } 0399 return list; 0400 } 0401 0402 KCalendarCore::DateList MultiAgendaView::selectedIncidenceDates() const 0403 { 0404 KCalendarCore::DateList list; 0405 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) { 0406 list += agendaView->selectedIncidenceDates(); 0407 } 0408 return list; 0409 } 0410 0411 int MultiAgendaView::currentDateCount() const 0412 { 0413 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) { 0414 return agendaView->currentDateCount(); 0415 } 0416 return 0; 0417 } 0418 0419 void MultiAgendaView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) 0420 { 0421 Q_UNUSED(preferredMonth) 0422 d->mStartDate = start; 0423 d->mEndDate = end; 0424 slotResizeScrollView(); 0425 d->mTimeLabelsZone->updateAll(); 0426 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) { 0427 agendaView->showDates(start, end); 0428 } 0429 } 0430 0431 void MultiAgendaView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) 0432 { 0433 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) { 0434 agendaView->showIncidences(incidenceList, date); 0435 } 0436 } 0437 0438 void MultiAgendaView::updateView() 0439 { 0440 recreateViews(); 0441 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) { 0442 agendaView->updateView(); 0443 } 0444 } 0445 0446 int MultiAgendaView::maxDatesHint() const 0447 { 0448 // these maxDatesHint functions aren't used 0449 return AgendaView::MAX_DAY_COUNT; 0450 } 0451 0452 void MultiAgendaView::slotSelectionChanged() 0453 { 0454 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0455 if (agenda != sender()) { 0456 agenda->clearSelection(); 0457 } 0458 } 0459 } 0460 0461 bool MultiAgendaView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const 0462 { 0463 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0464 bool valid = agenda->eventDurationHint(startDt, endDt, allDay); 0465 if (valid) { 0466 return true; 0467 } 0468 } 0469 return false; 0470 } 0471 0472 // Invoked when user selects a cell or a span of cells in agendaview 0473 void MultiAgendaView::slotClearTimeSpanSelection() 0474 { 0475 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0476 if (agenda != sender()) { 0477 agenda->clearTimeSpanSelection(); 0478 } else if (!d->mCustomColumnSetupUsed) { 0479 d->setActiveAgenda(agenda); 0480 } 0481 } 0482 } 0483 0484 void MultiAgendaViewPrivate::setActiveAgenda(AgendaView *view) 0485 { 0486 // Only makes sense in the one-agenda-per-calendar set up 0487 if (mCustomColumnSetupUsed) { 0488 return; 0489 } 0490 0491 if (!view) { 0492 return; 0493 } 0494 0495 auto calendars = view->calendars(); 0496 if (calendars.empty()) { 0497 return; 0498 } 0499 Q_ASSERT(calendars.size() == 1); 0500 0501 Q_EMIT q->activeCalendarChanged(calendars.at(0)); 0502 } 0503 0504 AgendaView *MultiAgendaViewPrivate::createView(const QString &title) 0505 { 0506 auto box = new QWidget(mTopBox); 0507 mTopBox->layout()->addWidget(box); 0508 auto layout = new QVBoxLayout(box); 0509 layout->setContentsMargins(0, 0, 0, 0); 0510 auto av = new AgendaView(q->preferences(), q->startDateTime().date(), q->endDateTime().date(), true, true, q); 0511 layout->addWidget(av); 0512 av->setIncidenceChanger(q->changer()); 0513 av->setTitle(title); 0514 av->agenda()->scrollArea()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0515 mAgendaViews.append(av); 0516 mAgendaWidgets.append(box); 0517 box->show(); 0518 mTimeLabelsZone->setAgendaView(av); 0519 0520 q->connect(mScrollBar, &QAbstractSlider::valueChanged, av->agenda()->verticalScrollBar(), &QAbstractSlider::setValue); 0521 0522 q->connect(av->splitter(), &QSplitter::splitterMoved, q, &MultiAgendaView::resizeSplitters); 0523 // The change in all-day and regular agenda height ratio affects scrollbars as well 0524 q->connect(av->splitter(), &QSplitter::splitterMoved, q, &MultiAgendaView::setupScrollBar); 0525 q->connect(av, &AgendaView::showIncidencePopupSignal, q, &MultiAgendaView::showIncidencePopupSignal); 0526 0527 q->connect(av, &AgendaView::showNewEventPopupSignal, q, &MultiAgendaView::showNewEventPopupSignal); 0528 0529 const QSize minHint = av->allDayAgenda()->scrollArea()->minimumSizeHint(); 0530 0531 if (minHint.isValid()) { 0532 mLabel->setMinimumHeight(minHint.height()); 0533 mRightDummyWidget->setMinimumHeight(minHint.height()); 0534 } 0535 0536 return av; 0537 } 0538 0539 void MultiAgendaViewPrivate::addView(const Akonadi::CollectionCalendar::Ptr &calendar) 0540 { 0541 const auto title = Akonadi::CalendarUtils::displayName(calendar->model(), calendar->collection()); 0542 auto *view = createView(title); 0543 view->addCalendar(calendar); 0544 } 0545 0546 static void updateViewFromSelection(AgendaView *view, 0547 const QItemSelection &selected, 0548 const QItemSelection &deselected, 0549 const MultiAgendaView::CalendarFactory::Ptr &factory) 0550 { 0551 for (const auto index : selected.indexes()) { 0552 if (const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); col.isValid()) { 0553 const auto calendar = factory->calendarForCollection(col); 0554 view->addCalendar(calendar); 0555 } 0556 } 0557 for (const auto index : deselected.indexes()) { 0558 if (const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); col.isValid()) { 0559 if (const auto calendar = view->calendarForCollection(col); calendar) { 0560 view->removeCalendar(calendar); 0561 } 0562 } 0563 } 0564 } 0565 0566 void MultiAgendaViewPrivate::addView(KCheckableProxyModel *sm, const QString &title) 0567 { 0568 auto *view = createView(title); 0569 // During launch the underlying ETM doesn't have the entire Collection tree populated, 0570 // so the "sm" contains an icomplete selection - we must listen for changes and upated 0571 // the view later on 0572 QObject::connect(sm->selectionModel(), 0573 &QItemSelectionModel::selectionChanged, 0574 view, 0575 [this, view](const QItemSelection &selected, const QItemSelection &deselected) { 0576 updateViewFromSelection(view, selected, deselected, mCalendarFactory); 0577 }); 0578 0579 // Initial update 0580 updateViewFromSelection(view, sm->selectionModel()->selection(), QItemSelection{}, mCalendarFactory); 0581 } 0582 0583 void MultiAgendaView::resizeEvent(QResizeEvent *ev) 0584 { 0585 d->resizeScrollView(ev->size()); 0586 EventView::resizeEvent(ev); 0587 setupScrollBar(); 0588 } 0589 0590 void MultiAgendaViewPrivate::resizeScrollView(QSize size) 0591 { 0592 const int widgetWidth = size.width() - mTimeLabelsZone->width() - mScrollBar->width(); 0593 0594 int height = size.height(); 0595 if (mScrollArea->horizontalScrollBar()->isVisible()) { 0596 const int sbHeight = mScrollArea->horizontalScrollBar()->height(); 0597 height -= sbHeight; 0598 mLeftBottomSpacer->setFixedHeight(sbHeight); 0599 mRightBottomSpacer->setFixedHeight(sbHeight); 0600 } else { 0601 mLeftBottomSpacer->setFixedHeight(0); 0602 mRightBottomSpacer->setFixedHeight(0); 0603 } 0604 0605 mTopBox->resize(widgetWidth, height); 0606 } 0607 0608 void MultiAgendaView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) 0609 { 0610 EventView::setIncidenceChanger(changer); 0611 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0612 agenda->setIncidenceChanger(changer); 0613 } 0614 } 0615 0616 void MultiAgendaView::setPreferences(const PrefsPtr &prefs) 0617 { 0618 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0619 agenda->setPreferences(prefs); 0620 } 0621 EventView::setPreferences(prefs); 0622 } 0623 0624 void MultiAgendaView::updateConfig() 0625 { 0626 EventView::updateConfig(); 0627 d->mTimeLabelsZone->setPreferences(preferences()); 0628 d->mTimeLabelsZone->updateAll(); 0629 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0630 agenda->updateConfig(); 0631 } 0632 } 0633 0634 void MultiAgendaView::resizeSplitters() 0635 { 0636 if (d->mAgendaViews.isEmpty()) { 0637 return; 0638 } 0639 0640 auto lastMovedSplitter = qobject_cast<QSplitter *>(sender()); 0641 if (!lastMovedSplitter) { 0642 lastMovedSplitter = d->mLeftSplitter; 0643 } 0644 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0645 if (agenda->splitter() == lastMovedSplitter) { 0646 continue; 0647 } 0648 agenda->splitter()->setSizes(lastMovedSplitter->sizes()); 0649 } 0650 if (lastMovedSplitter != d->mLeftSplitter) { 0651 d->mLeftSplitter->setSizes(lastMovedSplitter->sizes()); 0652 } 0653 if (lastMovedSplitter != d->mRightSplitter) { 0654 d->mRightSplitter->setSizes(lastMovedSplitter->sizes()); 0655 } 0656 } 0657 0658 void MultiAgendaView::zoomView(const int delta, QPoint pos, const Qt::Orientation ori) 0659 { 0660 const int hourSz = preferences()->hourSize(); 0661 if (ori == Qt::Vertical) { 0662 if (delta > 0) { 0663 if (hourSz > 4) { 0664 preferences()->setHourSize(hourSz - 1); 0665 } 0666 } else { 0667 preferences()->setHourSize(hourSz + 1); 0668 } 0669 } 0670 0671 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0672 agenda->zoomView(delta, pos, ori); 0673 } 0674 0675 d->mTimeLabelsZone->updateAll(); 0676 } 0677 0678 void MultiAgendaView::slotResizeScrollView() 0679 { 0680 d->resizeScrollView(size()); 0681 } 0682 0683 void MultiAgendaView::showEvent(QShowEvent *event) 0684 { 0685 EventView::showEvent(event); 0686 if (d->mUpdateOnShow) { 0687 d->mUpdateOnShow = false; 0688 d->mPendingChanges = true; // force a full view recreation 0689 showDates(d->mStartDate, d->mEndDate); 0690 } 0691 } 0692 0693 void MultiAgendaView::setChanges(Changes changes) 0694 { 0695 EventView::setChanges(changes); 0696 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) { 0697 agenda->setChanges(changes); 0698 } 0699 } 0700 0701 void MultiAgendaView::setupScrollBar() 0702 { 0703 if (!d->mAgendaViews.isEmpty() && d->mAgendaViews.constFirst()->agenda()) { 0704 QScrollBar *scrollBar = d->mAgendaViews.constFirst()->agenda()->verticalScrollBar(); 0705 d->mScrollBar->setMinimum(scrollBar->minimum()); 0706 d->mScrollBar->setMaximum(scrollBar->maximum()); 0707 d->mScrollBar->setSingleStep(scrollBar->singleStep()); 0708 d->mScrollBar->setPageStep(scrollBar->pageStep()); 0709 d->mScrollBar->setValue(scrollBar->value()); 0710 } 0711 } 0712 0713 void MultiAgendaView::collectionSelectionChanged() 0714 { 0715 qCDebug(CALENDARVIEW_LOG); 0716 d->mPendingChanges = true; 0717 recreateViews(); 0718 } 0719 0720 bool MultiAgendaView::hasConfigurationDialog() const 0721 { 0722 /** The wrapper in korg has the dialog. Too complicated to move to CalendarViews. 0723 Depends on korg/AkonadiCollectionView, and will be refactored some day 0724 to get rid of CollectionSelectionProxyModel/EntityStateSaver */ 0725 return false; 0726 } 0727 0728 void MultiAgendaView::doRestoreConfig(const KConfigGroup &configGroup) 0729 { 0730 /* 0731 if (!calendar()) { 0732 qCCritical(CALENDARVIEW_LOG) << "Calendar is not set."; 0733 Q_ASSERT(false); 0734 return; 0735 } 0736 */ 0737 0738 d->mCustomColumnSetupUsed = configGroup.readEntry("UseCustomColumnSetup", false); 0739 d->mCustomNumberOfColumns = configGroup.readEntry("CustomNumberOfColumns", 2); 0740 d->mCustomColumnTitles = configGroup.readEntry("ColumnTitles", QStringList()); 0741 if (d->mCustomColumnTitles.size() != d->mCustomNumberOfColumns) { 0742 const int orig = d->mCustomColumnTitles.size(); 0743 d->mCustomColumnTitles.reserve(d->mCustomNumberOfColumns); 0744 for (int i = orig; i < d->mCustomNumberOfColumns; ++i) { 0745 d->mCustomColumnTitles.push_back(generateColumnLabel(i)); 0746 } 0747 } 0748 0749 QList<KCheckableProxyModel *> oldModels = d->mCollectionSelectionModels; 0750 d->mCollectionSelectionModels.clear(); 0751 0752 if (d->mCustomColumnSetupUsed) { 0753 d->mCollectionSelectionModels.resize(d->mCustomNumberOfColumns); 0754 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) { 0755 // Sort the calanders by name 0756 auto sortProxy = new QSortFilterProxyModel(this); 0757 sortProxy->setDynamicSortFilter(true); 0758 sortProxy->setSourceModel(model()); 0759 0760 // Only show the first column 0761 auto columnFilterProxy = new KRearrangeColumnsProxyModel(this); 0762 columnFilterProxy->setSourceColumns(QList<int>() << 0); 0763 columnFilterProxy->setSourceModel(sortProxy); 0764 0765 // Keep track of selection. 0766 auto qsm = new QItemSelectionModel(columnFilterProxy); 0767 0768 // Make the model checkable. 0769 auto checkableProxy = new KCheckableProxyModel(this); 0770 checkableProxy->setSourceModel(columnFilterProxy); 0771 checkableProxy->setSelectionModel(qsm); 0772 const QString groupName = configGroup.name() + QLatin1StringView("_subView_") + QString::number(i); 0773 const KConfigGroup group = configGroup.config()->group(groupName); 0774 0775 if (!d->mSelectionSavers.contains(groupName)) { 0776 d->mSelectionSavers.insert(groupName, new KViewStateMaintainer<ETMViewStateSaver>(group)); 0777 d->mSelectionSavers[groupName]->setSelectionModel(checkableProxy->selectionModel()); 0778 } 0779 0780 d->mSelectionSavers[groupName]->restoreState(); 0781 d->mCollectionSelectionModels[i] = checkableProxy; 0782 } 0783 } 0784 0785 d->mPendingChanges = true; 0786 recreateViews(); 0787 qDeleteAll(oldModels); 0788 } 0789 0790 void MultiAgendaView::doSaveConfig(KConfigGroup &configGroup) 0791 { 0792 configGroup.writeEntry("UseCustomColumnSetup", d->mCustomColumnSetupUsed); 0793 configGroup.writeEntry("CustomNumberOfColumns", d->mCustomNumberOfColumns); 0794 configGroup.writeEntry("ColumnTitles", d->mCustomColumnTitles); 0795 int idx = 0; 0796 for (KCheckableProxyModel *checkableProxyModel : std::as_const(d->mCollectionSelectionModels)) { 0797 const QString groupName = configGroup.name() + QLatin1StringView("_subView_") + QString::number(idx); 0798 KConfigGroup group = configGroup.config()->group(groupName); 0799 ++idx; 0800 // TODO never used ? 0801 KViewStateMaintainer<ETMViewStateSaver> saver(group); 0802 if (!d->mSelectionSavers.contains(groupName)) { 0803 d->mSelectionSavers.insert(groupName, new KViewStateMaintainer<ETMViewStateSaver>(group)); 0804 d->mSelectionSavers[groupName]->setSelectionModel(checkableProxyModel->selectionModel()); 0805 } 0806 d->mSelectionSavers[groupName]->saveState(); 0807 } 0808 } 0809 0810 void MultiAgendaView::customCollectionsChanged(ConfigDialogInterface *dlg) 0811 { 0812 if (!d->mCustomColumnSetupUsed && !dlg->useCustomColumns()) { 0813 // Config didn't change, no need to recreate views 0814 return; 0815 } 0816 0817 d->mCustomColumnSetupUsed = dlg->useCustomColumns(); 0818 d->mCustomNumberOfColumns = dlg->numberOfColumns(); 0819 QList<KCheckableProxyModel *> newModels; 0820 newModels.resize(d->mCustomNumberOfColumns); 0821 d->mCustomColumnTitles.clear(); 0822 d->mCustomColumnTitles.reserve(d->mCustomNumberOfColumns); 0823 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) { 0824 newModels[i] = dlg->takeSelectionModel(i); 0825 d->mCustomColumnTitles.push_back(dlg->columnTitle(i)); 0826 } 0827 d->mCollectionSelectionModels = newModels; 0828 d->mPendingChanges = true; 0829 recreateViews(); 0830 } 0831 0832 bool MultiAgendaView::customColumnSetupUsed() const 0833 { 0834 return d->mCustomColumnSetupUsed; 0835 } 0836 0837 int MultiAgendaView::customNumberOfColumns() const 0838 { 0839 return d->mCustomNumberOfColumns; 0840 } 0841 0842 QList<KCheckableProxyModel *> MultiAgendaView::collectionSelectionModels() const 0843 { 0844 return d->mCollectionSelectionModels; 0845 } 0846 0847 QStringList MultiAgendaView::customColumnTitles() const 0848 { 0849 return d->mCustomColumnTitles; 0850 } 0851 0852 #include "moc_multiagendaview.cpp"