Eventviews

multiagendaview.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
4 SPDX-FileContributor: Sergio Martins <sergio.martins@kdab.com>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8#include "multiagendaview.h"
9using namespace Qt::Literals::StringLiterals;
10
11#include "agenda/agenda.h"
12#include "agenda/agendaview.h"
13#include "agenda/timelabelszone.h"
14#include "calendarview_debug.h"
15#include "configdialoginterface.h"
16#include "prefs.h"
17
18#include <Akonadi/CalendarUtils>
19#include <Akonadi/ETMViewStateSaver>
20#include <Akonadi/EntityTreeModel>
21
22#include <CalendarSupport/CollectionSelection>
23
24#include <KCheckableProxyModel>
25#include <KLocalizedString>
26#include <KRearrangeColumnsProxyModel>
27#include <KViewStateMaintainer>
28
29#include <QHBoxLayout>
30#include <QLabel>
31#include <QPainter>
32#include <QResizeEvent>
33#include <QScrollArea>
34#include <QScrollBar>
35#include <QSortFilterProxyModel>
36#include <QSplitter>
37#include <QTimer>
38
39using namespace Akonadi;
40using namespace EventViews;
41
42/**
43 Function for debugging purposes:
44 prints an object's sizeHint()/minimumSizeHint()/policy
45 and it's children's too, recursively
46*/
47/*
48static void printObject( QObject *o, int level = 0 )
49{
50 QMap<int,QString> map;
51 map.insert( 0, "Fixed" );
52 map.insert( 1, "Minimum" );
53 map.insert( 4, "Maximum" );
54 map.insert( 5, "Preferred" );
55 map.insert( 7, "Expanding" );
56 map.insert( 3, "MinimumExpaning" );
57 map.insert( 13, "Ignored" );
58
59 QWidget *w = qobject_cast<QWidget*>( o );
60
61 if ( w ) {
62 qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o
63 << w->sizeHint() << "/" << map[w->sizePolicy().verticalPolicy()]
64 << "; minimumSize = " << w->minimumSize()
65 << "; minimumSizeHint = " << w->minimumSizeHint();
66 } else {
67 qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o ;
68 }
69
70 foreach( QObject *child, o->children() ) {
71 printObject( child, level + 1 );
72 }
73}
74*/
75
76class DefaultCalendarFactory : public MultiAgendaView::CalendarFactory
77{
78public:
80
81 explicit DefaultCalendarFactory(MultiAgendaView *view)
82 : mView(view)
83 {
84 }
85
86 Akonadi::CollectionCalendar::Ptr calendarForCollection(const Akonadi::Collection &collection) override
87 {
88 return Akonadi::CollectionCalendar::Ptr::create(mView->entityTreeModel(), collection);
89 }
90
91private:
92 MultiAgendaView *mView;
93};
94
95static QString generateColumnLabel(int c)
96{
97 return i18n("Agenda %1", c + 1);
98}
99
100class EventViews::MultiAgendaViewPrivate
101{
102public:
103 explicit MultiAgendaViewPrivate(const MultiAgendaView::CalendarFactory::Ptr &factory, MultiAgendaView *qq)
104 : q(qq)
105 , mCalendarFactory(factory)
106 {
107 }
108
109 ~MultiAgendaViewPrivate()
110 {
111 qDeleteAll(mSelectionSavers);
112 }
113
114 void addView(const Akonadi::CollectionCalendar::Ptr &calendar);
115 void addView(KCheckableProxyModel *selectionProxy, const QString &title);
116 AgendaView *createView(const QString &calendar);
117 void deleteViews();
118 void setupViews();
119 void resizeScrollView(QSize size);
120 void setActiveAgenda(AgendaView *view);
121
122 MultiAgendaView *const q;
123 QList<AgendaView *> mAgendaViews;
124 QList<QWidget *> mAgendaWidgets;
125 QWidget *mTopBox = nullptr;
126 QScrollArea *mScrollArea = nullptr;
127 TimeLabelsZone *mTimeLabelsZone = nullptr;
128 QSplitter *mLeftSplitter = nullptr;
129 QSplitter *mRightSplitter = nullptr;
130 QScrollBar *mScrollBar = nullptr;
131 QWidget *mLeftBottomSpacer = nullptr;
132 QWidget *mRightBottomSpacer = nullptr;
133 QDate mStartDate, mEndDate;
134 bool mUpdateOnShow = true;
135 bool mPendingChanges = true;
136 bool mCustomColumnSetupUsed = false;
137 QList<KCheckableProxyModel *> mCollectionSelectionModels;
138 QStringList mCustomColumnTitles;
139 int mCustomNumberOfColumns = 2;
140 QLabel *mLabel = nullptr;
141 QWidget *mRightDummyWidget = nullptr;
143 QMetaObject::Connection m_selectionChangeConn;
145};
146
147MultiAgendaView::MultiAgendaView(QWidget *parent)
148 : MultiAgendaView(DefaultCalendarFactory::Ptr::create(this), parent)
149{
150}
151
152MultiAgendaView::MultiAgendaView(const CalendarFactory::Ptr &factory, QWidget *parent)
153 : EventView(parent)
154 , d(new MultiAgendaViewPrivate(factory, this))
155{
156 auto topLevelLayout = new QHBoxLayout(this);
157 topLevelLayout->setSpacing(0);
158 topLevelLayout->setContentsMargins({});
159
160 // agendaheader is a VBox layout with default spacing containing two labels,
161 // so the height is 2 * default font height + 2 * default vertical layout spacing
162 // (that's vertical spacing between the labels and spacing between the header and the
163 // top of the agenda grid)
164 const auto spacing = style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing, nullptr, this);
165 const int agendeHeaderHeight = 2 * QFontMetrics(font()).height() + 2 * spacing;
166
167 // Left sidebox
168 {
169 auto sideBox = new QWidget(this);
170 auto sideBoxLayout = new QVBoxLayout(sideBox);
171 sideBoxLayout->setSpacing(0);
172 sideBoxLayout->setContentsMargins(0, agendeHeaderHeight, 0, 0);
173
174 // Splitter for full-day and regular agenda views
175 d->mLeftSplitter = new QSplitter(Qt::Vertical, sideBox);
176 sideBoxLayout->addWidget(d->mLeftSplitter, 1);
177
178 // Label for all-day view
179 d->mLabel = new QLabel(i18nc("@label:textbox", "All Day"), d->mLeftSplitter);
180 d->mLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
181 d->mLabel->setWordWrap(true);
182
183 auto timeLabelsBox = new QWidget(d->mLeftSplitter);
184 auto timeLabelsBoxLayout = new QVBoxLayout(timeLabelsBox);
185 timeLabelsBoxLayout->setSpacing(0);
186 timeLabelsBoxLayout->setContentsMargins({});
187
188 d->mTimeLabelsZone = new TimeLabelsZone(timeLabelsBox, PrefsPtr(new Prefs()));
189 timeLabelsBoxLayout->addWidget(d->mTimeLabelsZone);
190
191 // Compensate for horizontal scrollbars, if needed
192 d->mLeftBottomSpacer = new QWidget(timeLabelsBox);
193 timeLabelsBoxLayout->addWidget(d->mLeftBottomSpacer);
194
195 topLevelLayout->addWidget(sideBox);
196 }
197
198 // Central area
199 {
200 d->mScrollArea = new QScrollArea(this);
201 d->mScrollArea->setWidgetResizable(true);
202
203 d->mScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
204 d->mScrollArea->setFrameShape(QFrame::NoFrame);
205
206 d->mTopBox = new QWidget(d->mScrollArea->viewport());
207 auto mTopBoxHBoxLayout = new QHBoxLayout(d->mTopBox);
208 mTopBoxHBoxLayout->setContentsMargins({});
209 d->mScrollArea->setWidget(d->mTopBox);
210
211 topLevelLayout->addWidget(d->mScrollArea, 100);
212 }
213
214 // Right side box (scrollbar)
215 {
216 auto sideBox = new QWidget(this);
217 auto sideBoxLayout = new QVBoxLayout(sideBox);
218 sideBoxLayout->setSpacing(0);
219 sideBoxLayout->setContentsMargins(0, agendeHeaderHeight, 0, 0);
220
221 d->mRightSplitter = new QSplitter(Qt::Vertical, sideBox);
222 sideBoxLayout->addWidget(d->mRightSplitter);
223
224 // Empty widget, equivalent to mLabel in the left box
225 d->mRightDummyWidget = new QWidget(d->mRightSplitter);
226
227 d->mScrollBar = new QScrollBar(Qt::Vertical, d->mRightSplitter);
228
229 // Compensate for horizontal scrollbar, if needed
230 d->mRightBottomSpacer = new QWidget(sideBox);
231 sideBoxLayout->addWidget(d->mRightBottomSpacer);
232
233 topLevelLayout->addWidget(sideBox);
234 }
235
236 // BUG: compensate for agenda view's frames to make sure time labels are aligned
237 d->mTimeLabelsZone->setContentsMargins(0, d->mScrollArea->frameWidth(), 0, d->mScrollArea->frameWidth());
238
239 connect(d->mLeftSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters);
240 connect(d->mRightSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters);
241}
242
243void MultiAgendaView::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
244{
245 EventView::addCalendar(calendar);
246 d->mPendingChanges = true;
247 recreateViews();
248}
249
250void MultiAgendaView::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
251{
252 EventView::removeCalendar(calendar);
253 d->mPendingChanges = true;
254 recreateViews();
255}
256
257void MultiAgendaView::setModel(QAbstractItemModel *model)
258{
259 EventView::setModel(model);
260 // Workaround: when we create the multiagendaview with custom columns too early
261 // during start, when Collections in ETM are not fully loaded yet, then
262 // the KCheckableProxyModels are restored from config with incomplete selections.
263 // But when the Collections are finally loaded into ETM, there's nothing to update
264 // the selections, so we end up with some calendars not displayed in the individual
265 // AgendaViews. Thus, we force-recreate everything once collection tree is fetched.
266 connect(
267 entityTreeModel(),
269 this,
270 [this]() {
271 d->mPendingChanges = true;
272 recreateViews();
273 },
275}
276
277void MultiAgendaView::recreateViews()
278{
279 if (!d->mPendingChanges) {
280 return;
281 }
282 d->mPendingChanges = false;
283
284 d->deleteViews();
285
286 if (d->mCustomColumnSetupUsed) {
287 Q_ASSERT(d->mCollectionSelectionModels.size() == d->mCustomNumberOfColumns);
288 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) {
289 d->addView(d->mCollectionSelectionModels[i], d->mCustomColumnTitles[i]);
290 }
291 } else {
292 for (const auto &calendar : calendars()) {
293 d->addView(calendar);
294 }
295 }
296
297 // no resources activated, so stop here to avoid crashing somewhere down the line
298 // TODO: show a nice message instead
299 if (d->mAgendaViews.isEmpty()) {
300 return;
301 }
302
303 d->setupViews();
304 QTimer::singleShot(0, this, &MultiAgendaView::slotResizeScrollView);
305 d->mTimeLabelsZone->updateAll();
306
307 QScrollArea *timeLabel = d->mTimeLabelsZone->timeLabels().at(0);
310
311 // On initial view, sync our splitter sizes with the agenda
312 if (d->mAgendaViews.size() == 1) {
313 d->mLeftSplitter->setSizes(d->mAgendaViews[0]->splitter()->sizes());
314 d->mRightSplitter->setSizes(d->mAgendaViews[0]->splitter()->sizes());
315 }
316 resizeSplitters();
317 QTimer::singleShot(0, this, &MultiAgendaView::setupScrollBar);
318
319 d->mTimeLabelsZone->updateTimeLabelsPosition();
320}
321
322void MultiAgendaView::forceRecreateViews()
323{
324 d->mPendingChanges = true;
325 recreateViews();
326}
327
328void MultiAgendaViewPrivate::deleteViews()
329{
330 for (AgendaView *const i : std::as_const(mAgendaViews)) {
331 KCheckableProxyModel *proxy = i->takeCustomCollectionSelectionProxyModel();
332 if (proxy && !mCollectionSelectionModels.contains(proxy)) {
333 delete proxy;
334 }
335 delete i;
336 }
337
338 mAgendaViews.clear();
339 mTimeLabelsZone->setAgendaView(nullptr);
340 qDeleteAll(mAgendaWidgets);
341 mAgendaWidgets.clear();
342}
343
344void MultiAgendaViewPrivate::setupViews()
345{
346 for (AgendaView *agendaView : std::as_const(mAgendaViews)) {
347 q->connect(agendaView, qOverload<>(&EventView::newEventSignal), q, qOverload<>(&EventView::newEventSignal));
348 q->connect(agendaView, qOverload<const QDate &>(&EventView::newEventSignal), q, qOverload<const QDate &>(&EventView::newEventSignal));
349 q->connect(agendaView, qOverload<const QDateTime &>(&EventView::newEventSignal), q, qOverload<const QDateTime &>(&EventView::newEventSignal));
350 q->connect(agendaView,
351 qOverload<const QDateTime &, const QDateTime &>(&EventView::newEventSignal),
352 q,
353 qOverload<const QDateTime &>(&EventView::newEventSignal));
354
358
359 q->connect(agendaView, &EventView::incidenceSelected, q, &EventView::incidenceSelected);
360
366
367 q->connect(agendaView, &EventView::newTodoSignal, q, &EventView::newTodoSignal);
368
369 q->connect(agendaView, &EventView::incidenceSelected, q, &MultiAgendaView::slotSelectionChanged);
370
371 q->connect(agendaView, &AgendaView::timeSpanSelectionChanged, q, &MultiAgendaView::slotClearTimeSpanSelection);
372
373 q->disconnect(agendaView->agenda(), &Agenda::zoomView, agendaView, nullptr);
374 q->connect(agendaView->agenda(), &Agenda::zoomView, q, &MultiAgendaView::zoomView);
375 }
376
377 AgendaView *lastView = mAgendaViews.last();
378 for (AgendaView *agendaView : std::as_const(mAgendaViews)) {
379 if (agendaView != lastView) {
380 q->connect(agendaView->agenda()->verticalScrollBar(),
382 lastView->agenda()->verticalScrollBar(),
384 }
385 }
386
387 for (AgendaView *agenda : std::as_const(mAgendaViews)) {
388 agenda->readSettings();
389 }
390}
391
392MultiAgendaView::~MultiAgendaView() = default;
393
395{
397 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
398 list += agendaView->selectedIncidences();
399 }
400 return list;
401}
402
404{
406 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
407 list += agendaView->selectedIncidenceDates();
408 }
409 return list;
410}
411
413{
414 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
415 return agendaView->currentDateCount();
416 }
417 return 0;
418}
419
420void MultiAgendaView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth)
421{
422 Q_UNUSED(preferredMonth)
423 d->mStartDate = start;
424 d->mEndDate = end;
425 slotResizeScrollView();
426 d->mTimeLabelsZone->updateAll();
427 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
428 agendaView->showDates(start, end);
429 }
430}
431
432void MultiAgendaView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date)
433{
434 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
435 agendaView->showIncidences(incidenceList, date);
436 }
437}
438
439void MultiAgendaView::updateView()
440{
441 recreateViews();
442 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
443 agendaView->updateView();
444 }
445}
446
447int MultiAgendaView::maxDatesHint() const
448{
449 // these maxDatesHint functions aren't used
450 return AgendaView::MAX_DAY_COUNT;
451}
452
453void MultiAgendaView::slotSelectionChanged()
454{
455 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
456 if (agenda != sender()) {
457 agenda->clearSelection();
458 }
459 }
460}
461
462bool MultiAgendaView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const
463{
464 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
465 bool valid = agenda->eventDurationHint(startDt, endDt, allDay);
466 if (valid) {
467 return true;
468 }
469 }
470 return false;
471}
472
473// Invoked when user selects a cell or a span of cells in agendaview
474void MultiAgendaView::slotClearTimeSpanSelection()
475{
476 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
477 if (agenda != sender()) {
478 agenda->clearTimeSpanSelection();
479 } else if (!d->mCustomColumnSetupUsed) {
480 d->setActiveAgenda(agenda);
481 }
482 }
483}
484
485void MultiAgendaViewPrivate::setActiveAgenda(AgendaView *view)
486{
487 // Only makes sense in the one-agenda-per-calendar set up
488 if (mCustomColumnSetupUsed) {
489 return;
490 }
491
492 if (!view) {
493 return;
494 }
495
496 auto calendars = view->calendars();
497 if (calendars.empty()) {
498 return;
499 }
500 Q_ASSERT(calendars.size() == 1);
501
502 Q_EMIT q->activeCalendarChanged(calendars.at(0));
503}
504
505AgendaView *MultiAgendaViewPrivate::createView(const QString &title)
506{
507 auto box = new QWidget(mTopBox);
508 mTopBox->layout()->addWidget(box);
509 auto layout = new QVBoxLayout(box);
510 layout->setContentsMargins({});
511 auto av = new AgendaView(q->preferences(), q->startDateTime().date(), q->endDateTime().date(), true, true, q);
512 layout->addWidget(av);
513 av->setIncidenceChanger(q->changer());
514 av->setTitle(title);
515 av->agenda()->scrollArea()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
516 mAgendaViews.append(av);
517 mAgendaWidgets.append(box);
518 box->show();
519 mTimeLabelsZone->setAgendaView(av);
520
521 q->connect(mScrollBar, &QAbstractSlider::valueChanged, av->agenda()->verticalScrollBar(), &QAbstractSlider::setValue);
522
523 q->connect(av->splitter(), &QSplitter::splitterMoved, q, &MultiAgendaView::resizeSplitters);
524 // The change in all-day and regular agenda height ratio affects scrollbars as well
525 q->connect(av->splitter(), &QSplitter::splitterMoved, q, &MultiAgendaView::setupScrollBar);
526 q->connect(av, &AgendaView::showIncidencePopupSignal, q, &MultiAgendaView::showIncidencePopupSignal);
527
528 q->connect(av, &AgendaView::showNewEventPopupSignal, q, &MultiAgendaView::showNewEventPopupSignal);
529
530 const QSize minHint = av->allDayAgenda()->scrollArea()->minimumSizeHint();
531
532 if (minHint.isValid()) {
533 mLabel->setMinimumHeight(minHint.height());
534 mRightDummyWidget->setMinimumHeight(minHint.height());
535 }
536
537 return av;
538}
539
540void MultiAgendaViewPrivate::addView(const Akonadi::CollectionCalendar::Ptr &calendar)
541{
542 const auto title = Akonadi::CalendarUtils::displayName(calendar->model(), calendar->collection());
543 auto *view = createView(title);
544 view->addCalendar(calendar);
545}
546
547static void updateViewFromSelection(AgendaView *view,
548 const QItemSelection &selected,
549 const QItemSelection &deselected,
551{
552 for (const auto index : selected.indexes()) {
553 if (const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); col.isValid()) {
554 const auto calendar = factory->calendarForCollection(col);
555 view->addCalendar(calendar);
556 }
557 }
558 for (const auto index : deselected.indexes()) {
559 if (const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); col.isValid()) {
560 if (const auto calendar = view->calendarForCollection(col); calendar) {
561 view->removeCalendar(calendar);
562 }
563 }
564 }
565}
566
567void MultiAgendaViewPrivate::addView(KCheckableProxyModel *sm, const QString &title)
568{
569 auto *view = createView(title);
570 // During launch the underlying ETM doesn't have the entire Collection tree populated,
571 // so the "sm" contains an incomplete selection - we must listen for changes and update
572 // the view later on
573 QObject::connect(sm->selectionModel(),
575 view,
576 [this, view](const QItemSelection &selected, const QItemSelection &deselected) {
577 updateViewFromSelection(view, selected, deselected, mCalendarFactory);
578 });
579
580 // Initial update
581 updateViewFromSelection(view, sm->selectionModel()->selection(), QItemSelection{}, mCalendarFactory);
582}
583
584void MultiAgendaView::resizeEvent(QResizeEvent *ev)
585{
586 d->resizeScrollView(ev->size());
588 setupScrollBar();
589}
590
591void MultiAgendaViewPrivate::resizeScrollView(QSize size)
592{
593 const int widgetWidth = size.width() - mTimeLabelsZone->width() - mScrollBar->width();
594
595 int height = size.height();
596 if (mScrollArea->horizontalScrollBar()->isVisible()) {
597 const int sbHeight = mScrollArea->horizontalScrollBar()->height();
598 height -= sbHeight;
599 mLeftBottomSpacer->setFixedHeight(sbHeight);
600 mRightBottomSpacer->setFixedHeight(sbHeight);
601 } else {
602 mLeftBottomSpacer->setFixedHeight(0);
603 mRightBottomSpacer->setFixedHeight(0);
604 }
605
606 mTopBox->resize(widgetWidth, height);
607}
608
609void MultiAgendaView::setIncidenceChanger(Akonadi::IncidenceChanger *changer)
610{
612 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
613 agenda->setIncidenceChanger(changer);
614 }
615}
616
617void MultiAgendaView::setPreferences(const PrefsPtr &prefs)
618{
619 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
620 agenda->setPreferences(prefs);
621 }
622 EventView::setPreferences(prefs);
623}
624
625void MultiAgendaView::updateConfig()
626{
628 d->mTimeLabelsZone->setPreferences(preferences());
629 d->mTimeLabelsZone->updateAll();
630 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
631 agenda->updateConfig();
632 }
633}
634
635void MultiAgendaView::resizeSplitters()
636{
637 if (d->mAgendaViews.isEmpty()) {
638 return;
639 }
640
641 auto lastMovedSplitter = qobject_cast<QSplitter *>(sender());
642 if (!lastMovedSplitter) {
643 lastMovedSplitter = d->mLeftSplitter;
644 }
645 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
646 if (agenda->splitter() == lastMovedSplitter) {
647 continue;
648 }
649 agenda->splitter()->setSizes(lastMovedSplitter->sizes());
650 }
651 if (lastMovedSplitter != d->mLeftSplitter) {
652 d->mLeftSplitter->setSizes(lastMovedSplitter->sizes());
653 }
654 if (lastMovedSplitter != d->mRightSplitter) {
655 d->mRightSplitter->setSizes(lastMovedSplitter->sizes());
656 }
657}
658
659void MultiAgendaView::zoomView(const int delta, QPoint pos, const Qt::Orientation ori)
660{
661 const int hourSz = preferences()->hourSize();
662 if (ori == Qt::Vertical) {
663 if (delta > 0) {
664 if (hourSz > 4) {
665 preferences()->setHourSize(hourSz - 1);
666 }
667 } else {
668 preferences()->setHourSize(hourSz + 1);
669 }
670 }
671
672 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
673 agenda->zoomView(delta, pos, ori);
674 }
675
676 d->mTimeLabelsZone->updateAll();
677}
678
679void MultiAgendaView::slotResizeScrollView()
680{
681 d->resizeScrollView(size());
682}
683
684void MultiAgendaView::showEvent(QShowEvent *event)
685{
687 if (d->mUpdateOnShow) {
688 d->mUpdateOnShow = false;
689 d->mPendingChanges = true; // force a full view recreation
690 showDates(d->mStartDate, d->mEndDate);
691 }
692}
693
695{
697 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
698 agenda->setChanges(changes);
699 }
700}
701
702void MultiAgendaView::setupScrollBar()
703{
704 if (!d->mAgendaViews.isEmpty() && d->mAgendaViews.constFirst()->agenda()) {
705 QScrollBar *scrollBar = d->mAgendaViews.constFirst()->agenda()->verticalScrollBar();
706 d->mScrollBar->setMinimum(scrollBar->minimum());
707 d->mScrollBar->setMaximum(scrollBar->maximum());
708 d->mScrollBar->setSingleStep(scrollBar->singleStep());
709 d->mScrollBar->setPageStep(scrollBar->pageStep());
710 d->mScrollBar->setValue(scrollBar->value());
711 }
712}
713
715{
716 qCDebug(CALENDARVIEW_LOG);
717 d->mPendingChanges = true;
718 recreateViews();
719}
720
722{
723 /** The wrapper in korg has the dialog. Too complicated to move to CalendarViews.
724 Depends on korg/AkonadiCollectionView, and will be refactored some day
725 to get rid of CollectionSelectionProxyModel/EntityStateSaver */
726 return false;
727}
728
730{
731 /*
732 if (!calendar()) {
733 qCCritical(CALENDARVIEW_LOG) << "Calendar is not set.";
734 Q_ASSERT(false);
735 return;
736 }
737 */
738
739 d->mCustomColumnSetupUsed = configGroup.readEntry("UseCustomColumnSetup", false);
740 d->mCustomNumberOfColumns = configGroup.readEntry("CustomNumberOfColumns", 2);
741 d->mCustomColumnTitles = configGroup.readEntry("ColumnTitles", QStringList());
742 if (d->mCustomColumnTitles.size() != d->mCustomNumberOfColumns) {
743 const int orig = d->mCustomColumnTitles.size();
744 d->mCustomColumnTitles.reserve(d->mCustomNumberOfColumns);
745 for (int i = orig; i < d->mCustomNumberOfColumns; ++i) {
746 d->mCustomColumnTitles.push_back(generateColumnLabel(i));
747 }
748 }
749
750 QList<KCheckableProxyModel *> oldModels = d->mCollectionSelectionModels;
751 d->mCollectionSelectionModels.clear();
752
753 if (d->mCustomColumnSetupUsed) {
754 d->mCollectionSelectionModels.resize(d->mCustomNumberOfColumns);
755 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) {
756 // Sort the calanders by name
757 auto sortProxy = new QSortFilterProxyModel(this);
758 sortProxy->setSourceModel(model());
759
760 // Only show the first column
761 auto columnFilterProxy = new KRearrangeColumnsProxyModel(this);
762 columnFilterProxy->setSourceColumns(QList<int>() << 0);
763 columnFilterProxy->setSourceModel(sortProxy);
764
765 // Keep track of selection.
766 auto qsm = new QItemSelectionModel(columnFilterProxy);
767
768 // Make the model checkable.
769 auto checkableProxy = new KCheckableProxyModel(this);
770 checkableProxy->setSourceModel(columnFilterProxy);
771 checkableProxy->setSelectionModel(qsm);
772 const QString groupName = configGroup.name() + "_subView_"_L1 + QString::number(i);
773 const KConfigGroup group = configGroup.config()->group(groupName);
774
775 if (!d->mSelectionSavers.contains(groupName)) {
776 d->mSelectionSavers.insert(groupName, new KViewStateMaintainer<ETMViewStateSaver>(group));
777 d->mSelectionSavers[groupName]->setSelectionModel(checkableProxy->selectionModel());
778 }
779
780 d->mSelectionSavers[groupName]->restoreState();
781 d->mCollectionSelectionModels[i] = checkableProxy;
782 }
783 }
784
785 d->mPendingChanges = true;
786 recreateViews();
787 qDeleteAll(oldModels);
788}
789
791{
792 configGroup.writeEntry("UseCustomColumnSetup", d->mCustomColumnSetupUsed);
793 configGroup.writeEntry("CustomNumberOfColumns", d->mCustomNumberOfColumns);
794 configGroup.writeEntry("ColumnTitles", d->mCustomColumnTitles);
795 int idx = 0;
796 for (KCheckableProxyModel *checkableProxyModel : std::as_const(d->mCollectionSelectionModels)) {
797 const QString groupName = configGroup.name() + "_subView_"_L1 + QString::number(idx);
798 KConfigGroup group = configGroup.config()->group(groupName);
799 ++idx;
800 // TODO never used ?
802 if (!d->mSelectionSavers.contains(groupName)) {
803 d->mSelectionSavers.insert(groupName, new KViewStateMaintainer<ETMViewStateSaver>(group));
804 d->mSelectionSavers[groupName]->setSelectionModel(checkableProxyModel->selectionModel());
805 }
806 d->mSelectionSavers[groupName]->saveState();
807 }
808}
809
810void MultiAgendaView::customCollectionsChanged(ConfigDialogInterface *dlg)
811{
812 if (!d->mCustomColumnSetupUsed && !dlg->useCustomColumns()) {
813 // Config didn't change, no need to recreate views
814 return;
815 }
816
817 d->mCustomColumnSetupUsed = dlg->useCustomColumns();
818 d->mCustomNumberOfColumns = dlg->numberOfColumns();
820 newModels.resize(d->mCustomNumberOfColumns);
821 d->mCustomColumnTitles.clear();
822 d->mCustomColumnTitles.reserve(d->mCustomNumberOfColumns);
823 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) {
824 newModels[i] = dlg->takeSelectionModel(i);
825 d->mCustomColumnTitles.push_back(dlg->columnTitle(i));
826 }
827 d->mCollectionSelectionModels = newModels;
828 d->mPendingChanges = true;
829 recreateViews();
830}
831
832bool MultiAgendaView::customColumnSetupUsed() const
833{
834 return d->mCustomColumnSetupUsed;
835}
836
837int MultiAgendaView::customNumberOfColumns() const
838{
839 return d->mCustomNumberOfColumns;
840}
841
842QList<KCheckableProxyModel *> MultiAgendaView::collectionSelectionModels() const
843{
844 return d->mCollectionSelectionModels;
845}
846
847QStringList MultiAgendaView::customColumnTitles() const
848{
849 return d->mCustomColumnTitles;
850}
851
852#include "moc_multiagendaview.cpp"
bool isValid() const
void collectionTreeFetched(const Akonadi::Collection::List &collections)
AgendaView is the agenda-like view that displays events in a single or multi-day view.
Definition agendaview.h:70
EventView is the abstract base class from which all other calendar views for event data are derived.
Definition eventview.h:69
void dissociateOccurrencesSignal(const Akonadi::Item &, const QDate &)
Dissociate from a recurring incidence the occurrence on the given date to a new incidence or dissocia...
void showIncidenceSignal(const Akonadi::Item &)
instructs the receiver to show the incidence in read-only mode.
void toggleAlarmSignal(const Akonadi::Item &)
instructs the receiver to toggle the alarms of the Incidence.
virtual void setChanges(Changes changes)
Notifies the view that there are pending changes so a redraw is needed.
void newEventSignal()
instructs the receiver to create a new event in given collection.
void deleteIncidenceSignal(const Akonadi::Item &)
instructs the receiver to delete the Incidence in some manner; some possibilities include automatical...
void pasteIncidenceSignal()
instructs the receiver to paste the incidence
Changes changes() const
Returns if there are pending changes and a redraw is needed.
void cutIncidenceSignal(const Akonadi::Item &)
instructs the receiver to cut the Incidence
void editIncidenceSignal(const Akonadi::Item &)
instructs the receiver to begin editing the incidence specified in some manner.
void copyIncidenceSignal(const Akonadi::Item &)
instructs the receiver to copy the incidence
virtual void updateConfig()
Re-reads the configuration and picks up relevant changes which are applicable to the view.
virtual void setIncidenceChanger(Akonadi::IncidenceChanger *changer)
Assign a new incidence change helper object.
Shows one agenda for every resource side-by-side.
int currentDateCount() const override
Returns the number of currently shown dates.
bool eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const override
Sets the default start/end date/time for new events.
Akonadi::Item::List selectedIncidences() const override
void doRestoreConfig(const KConfigGroup &configGroup) override
reimplement to read view-specific settings.
KCalendarCore::DateList selectedIncidenceDates() const override
Returns a list of the dates of selected events.
void doSaveConfig(KConfigGroup &configGroup) override
reimplement to write view-specific settings.
bool hasConfigurationDialog() const override
void setChanges(Changes changes) override
Notifies the view that there are pending changes so a redraw is needed.
void collectionSelectionChanged()
Reimplemented from KOrg::BaseView.
KConfigGroup group(const QString &group)
QString name() const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
KConfig * config()
QString readEntry(const char *key, const char *aDefault=nullptr) const
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
Namespace EventViews provides facilities for displaying incidences, including events,...
Definition agenda.h:33
QAction * create(StandardAction id, const Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
QScrollBar * horizontalScrollBar() const const
QScrollBar * verticalScrollBar() const const
void setValue(int)
void valueChanged(int value)
QDate date() const const
int height() const const
QModelIndexList indexes() const const
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
void addWidget(QWidget *w)
void append(QList< T > &&value)
void clear()
bool contains(const AT &value) const const
void resize(qsizetype size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T qobject_cast(QObject *object)
QObject * sender() const const
const QSize & size() const const
QSharedPointer< T > create(Args &&... args)
int height() const const
bool isValid() const const
int width() const const
void splitterMoved(int pos, int index)
QString number(double n, char format, int precision)
PM_LayoutVerticalSpacing
AlignRight
QueuedConnection
Vertical
ScrollBarAlwaysOff
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
virtual bool event(QEvent *event) override
QLayout * layout() const const
void setMinimumHeight(int minh)
virtual void resizeEvent(QResizeEvent *event)
void setFixedHeight(int h)
virtual void showEvent(QShowEvent *event)
void resize(const QSize &)
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:57:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.