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"
9
10#include "agenda/agenda.h"
11#include "agenda/agendaview.h"
12#include "agenda/timelabelszone.h"
13#include "calendarview_debug.h"
14#include "configdialoginterface.h"
15#include "prefs.h"
16
17#include <Akonadi/CalendarUtils>
18#include <Akonadi/ETMViewStateSaver>
19#include <Akonadi/EntityTreeModel>
20
21#include <CalendarSupport/CollectionSelection>
22
23#include <KCheckableProxyModel>
24#include <KLocalizedString>
25#include <KRearrangeColumnsProxyModel>
26#include <KViewStateMaintainer>
27
28#include <QHBoxLayout>
29#include <QLabel>
30#include <QPainter>
31#include <QResizeEvent>
32#include <QScrollArea>
33#include <QScrollBar>
34#include <QSortFilterProxyModel>
35#include <QSplitter>
36#include <QTimer>
37
38using namespace Akonadi;
39using namespace EventViews;
40
41/**
42 Function for debugging purposes:
43 prints an object's sizeHint()/minimumSizeHint()/policy
44 and it's children's too, recursively
45*/
46/*
47static void printObject( QObject *o, int level = 0 )
48{
49 QMap<int,QString> map;
50 map.insert( 0, "Fixed" );
51 map.insert( 1, "Minimum" );
52 map.insert( 4, "Maximum" );
53 map.insert( 5, "Preferred" );
54 map.insert( 7, "Expanding" );
55 map.insert( 3, "MinimumExpaning" );
56 map.insert( 13, "Ignored" );
57
58 QWidget *w = qobject_cast<QWidget*>( o );
59
60 if ( w ) {
61 qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o
62 << w->sizeHint() << "/" << map[w->sizePolicy().verticalPolicy()]
63 << "; minimumSize = " << w->minimumSize()
64 << "; minimumSizeHint = " << w->minimumSizeHint();
65 } else {
66 qCDebug(CALENDARVIEW_LOG) << QString( level*2, '-' ) << o ;
67 }
68
69 foreach( QObject *child, o->children() ) {
70 printObject( child, level + 1 );
71 }
72}
73*/
74
75class DefaultCalendarFactory : public MultiAgendaView::CalendarFactory
76{
77public:
79
80 explicit DefaultCalendarFactory(MultiAgendaView *view)
81 : mView(view)
82 {
83 }
84
85 Akonadi::CollectionCalendar::Ptr calendarForCollection(const Akonadi::Collection &collection) override
86 {
87 return Akonadi::CollectionCalendar::Ptr::create(mView->entityTreeModel(), collection);
88 }
89
90private:
91 MultiAgendaView *mView;
92};
93
94static QString generateColumnLabel(int c)
95{
96 return i18n("Agenda %1", c + 1);
97}
98
99class EventViews::MultiAgendaViewPrivate
100{
101public:
102 explicit MultiAgendaViewPrivate(const MultiAgendaView::CalendarFactory::Ptr &factory, MultiAgendaView *qq)
103 : q(qq)
104 , mCalendarFactory(factory)
105 {
106 }
107
108 ~MultiAgendaViewPrivate()
109 {
110 qDeleteAll(mSelectionSavers);
111 }
112
113 void addView(const Akonadi::CollectionCalendar::Ptr &calendar);
114 void addView(KCheckableProxyModel *selectionProxy, const QString &title);
115 AgendaView *createView(const QString &calendar);
116 void deleteViews();
117 void setupViews();
118 void resizeScrollView(QSize size);
119 void setActiveAgenda(AgendaView *view);
120
121 MultiAgendaView *const q;
122 QList<AgendaView *> mAgendaViews;
123 QList<QWidget *> mAgendaWidgets;
124 QWidget *mTopBox = nullptr;
125 QScrollArea *mScrollArea = nullptr;
126 TimeLabelsZone *mTimeLabelsZone = nullptr;
127 QSplitter *mLeftSplitter = nullptr;
128 QSplitter *mRightSplitter = nullptr;
129 QScrollBar *mScrollBar = nullptr;
130 QWidget *mLeftBottomSpacer = nullptr;
131 QWidget *mRightBottomSpacer = nullptr;
132 QDate mStartDate, mEndDate;
133 bool mUpdateOnShow = true;
134 bool mPendingChanges = true;
135 bool mCustomColumnSetupUsed = false;
136 QList<KCheckableProxyModel *> mCollectionSelectionModels;
137 QStringList mCustomColumnTitles;
138 int mCustomNumberOfColumns = 2;
139 QLabel *mLabel = nullptr;
140 QWidget *mRightDummyWidget = nullptr;
142 QMetaObject::Connection m_selectionChangeConn;
144};
145
146MultiAgendaView::MultiAgendaView(QWidget *parent)
147 : MultiAgendaView(DefaultCalendarFactory::Ptr::create(this), parent)
148{
149}
150
151MultiAgendaView::MultiAgendaView(const CalendarFactory::Ptr &factory, QWidget *parent)
152 : EventView(parent)
153 , d(new MultiAgendaViewPrivate(factory, this))
154{
155 auto topLevelLayout = new QHBoxLayout(this);
156 topLevelLayout->setSpacing(0);
157 topLevelLayout->setContentsMargins(0, 0, 0, 0);
158
159 // agendaheader is a VBox layout with default spacing containing two labels,
160 // so the height is 2 * default font height + 2 * default vertical layout spacing
161 // (that's vertical spacing between the labels and spacing between the header and the
162 // top of the agenda grid)
163 const auto spacing = style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing, nullptr, this);
164 const int agendeHeaderHeight = 2 * QFontMetrics(font()).height() + 2 * spacing;
165
166 // Left sidebox
167 {
168 auto sideBox = new QWidget(this);
169 auto sideBoxLayout = new QVBoxLayout(sideBox);
170 sideBoxLayout->setSpacing(0);
171 sideBoxLayout->setContentsMargins(0, agendeHeaderHeight, 0, 0);
172
173 // Splitter for full-day and regular agenda views
174 d->mLeftSplitter = new QSplitter(Qt::Vertical, sideBox);
175 sideBoxLayout->addWidget(d->mLeftSplitter, 1);
176
177 // Label for all-day view
178 d->mLabel = new QLabel(i18n("All Day"), d->mLeftSplitter);
179 d->mLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
180 d->mLabel->setWordWrap(true);
181
182 auto timeLabelsBox = new QWidget(d->mLeftSplitter);
183 auto timeLabelsBoxLayout = new QVBoxLayout(timeLabelsBox);
184 timeLabelsBoxLayout->setSpacing(0);
185 timeLabelsBoxLayout->setContentsMargins(0, 0, 0, 0);
186
187 d->mTimeLabelsZone = new TimeLabelsZone(timeLabelsBox, PrefsPtr(new Prefs()));
188 timeLabelsBoxLayout->addWidget(d->mTimeLabelsZone);
189
190 // Compensate for horizontal scrollbars, if needed
191 d->mLeftBottomSpacer = new QWidget(timeLabelsBox);
192 timeLabelsBoxLayout->addWidget(d->mLeftBottomSpacer);
193
194 topLevelLayout->addWidget(sideBox);
195 }
196
197 // Central area
198 {
199 d->mScrollArea = new QScrollArea(this);
200 d->mScrollArea->setWidgetResizable(true);
201
202 d->mScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
203 d->mScrollArea->setFrameShape(QFrame::NoFrame);
204
205 d->mTopBox = new QWidget(d->mScrollArea->viewport());
206 auto mTopBoxHBoxLayout = new QHBoxLayout(d->mTopBox);
207 mTopBoxHBoxLayout->setContentsMargins(0, 0, 0, 0);
208 d->mScrollArea->setWidget(d->mTopBox);
209
210 topLevelLayout->addWidget(d->mScrollArea, 100);
211 }
212
213 // Right side box (scrollbar)
214 {
215 auto sideBox = new QWidget(this);
216 auto sideBoxLayout = new QVBoxLayout(sideBox);
217 sideBoxLayout->setSpacing(0);
218 sideBoxLayout->setContentsMargins(0, agendeHeaderHeight, 0, 0);
219
220 d->mRightSplitter = new QSplitter(Qt::Vertical, sideBox);
221 sideBoxLayout->addWidget(d->mRightSplitter);
222
223 // Empty widget, equivalent to mLabel in the left box
224 d->mRightDummyWidget = new QWidget(d->mRightSplitter);
225
226 d->mScrollBar = new QScrollBar(Qt::Vertical, d->mRightSplitter);
227
228 // Compensate for horizontal scrollbar, if needed
229 d->mRightBottomSpacer = new QWidget(sideBox);
230 sideBoxLayout->addWidget(d->mRightBottomSpacer);
231
232 topLevelLayout->addWidget(sideBox);
233 }
234
235 // BUG: compensate for agenda view's frames to make sure time labels are aligned
236 d->mTimeLabelsZone->setContentsMargins(0, d->mScrollArea->frameWidth(), 0, d->mScrollArea->frameWidth());
237
238 connect(d->mLeftSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters);
239 connect(d->mRightSplitter, &QSplitter::splitterMoved, this, &MultiAgendaView::resizeSplitters);
240}
241
242void MultiAgendaView::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
243{
244 EventView::addCalendar(calendar);
245 d->mPendingChanges = true;
246 recreateViews();
247}
248
249void MultiAgendaView::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar)
250{
251 EventView::removeCalendar(calendar);
252 d->mPendingChanges = true;
253 recreateViews();
254}
255
256void MultiAgendaView::setModel(QAbstractItemModel *model)
257{
258 EventView::setModel(model);
259 // Workaround: when we create the multiagendaview with custom columns too early
260 // during start, when Collections in ETM are not fully loaded yet, then
261 // the KCheckableProxyModels are restored from config with incomplete selections.
262 // But when the Collections are finally loaded into ETM, there's nothing to update
263 // the selections, so we end up with some calendars not displayed in the individual
264 // AgendaViews. Thus, we force-recreate everything once collection tree is fetched.
265 connect(
266 entityTreeModel(),
268 this,
269 [this]() {
270 d->mPendingChanges = true;
271 recreateViews();
272 },
274}
275
276void MultiAgendaView::recreateViews()
277{
278 if (!d->mPendingChanges) {
279 return;
280 }
281 d->mPendingChanges = false;
282
283 d->deleteViews();
284
285 if (d->mCustomColumnSetupUsed) {
286 Q_ASSERT(d->mCollectionSelectionModels.size() == d->mCustomNumberOfColumns);
287 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) {
288 d->addView(d->mCollectionSelectionModels[i], d->mCustomColumnTitles[i]);
289 }
290 } else {
291 for (const auto &calendar : calendars()) {
292 d->addView(calendar);
293 }
294 }
295
296 // no resources activated, so stop here to avoid crashing somewhere down the line
297 // TODO: show a nice message instead
298 if (d->mAgendaViews.isEmpty()) {
299 return;
300 }
301
302 d->setupViews();
303 QTimer::singleShot(0, this, &MultiAgendaView::slotResizeScrollView);
304 d->mTimeLabelsZone->updateAll();
305
306 QScrollArea *timeLabel = d->mTimeLabelsZone->timeLabels().at(0);
309
310 // On initial view, sync our splitter sizes with the agenda
311 if (d->mAgendaViews.size() == 1) {
312 d->mLeftSplitter->setSizes(d->mAgendaViews[0]->splitter()->sizes());
313 d->mRightSplitter->setSizes(d->mAgendaViews[0]->splitter()->sizes());
314 }
315 resizeSplitters();
316 QTimer::singleShot(0, this, &MultiAgendaView::setupScrollBar);
317
318 d->mTimeLabelsZone->updateTimeLabelsPosition();
319}
320
321void MultiAgendaView::forceRecreateViews()
322{
323 d->mPendingChanges = true;
324 recreateViews();
325}
326
327void MultiAgendaViewPrivate::deleteViews()
328{
329 for (AgendaView *const i : std::as_const(mAgendaViews)) {
330 KCheckableProxyModel *proxy = i->takeCustomCollectionSelectionProxyModel();
331 if (proxy && !mCollectionSelectionModels.contains(proxy)) {
332 delete proxy;
333 }
334 delete i;
335 }
336
337 mAgendaViews.clear();
338 mTimeLabelsZone->setAgendaView(nullptr);
339 qDeleteAll(mAgendaWidgets);
340 mAgendaWidgets.clear();
341}
342
343void MultiAgendaViewPrivate::setupViews()
344{
345 for (AgendaView *agendaView : std::as_const(mAgendaViews)) {
346 q->connect(agendaView, qOverload<>(&EventView::newEventSignal), q, qOverload<>(&EventView::newEventSignal));
347 q->connect(agendaView, qOverload<const QDate &>(&EventView::newEventSignal), q, qOverload<const QDate &>(&EventView::newEventSignal));
348 q->connect(agendaView, qOverload<const QDateTime &>(&EventView::newEventSignal), q, qOverload<const QDateTime &>(&EventView::newEventSignal));
349 q->connect(agendaView,
350 qOverload<const QDateTime &, const QDateTime &>(&EventView::newEventSignal),
351 q,
352 qOverload<const QDateTime &>(&EventView::newEventSignal));
353
357
358 q->connect(agendaView, &EventView::incidenceSelected, q, &EventView::incidenceSelected);
359
365
366 q->connect(agendaView, &EventView::newTodoSignal, q, &EventView::newTodoSignal);
367
368 q->connect(agendaView, &EventView::incidenceSelected, q, &MultiAgendaView::slotSelectionChanged);
369
370 q->connect(agendaView, &AgendaView::timeSpanSelectionChanged, q, &MultiAgendaView::slotClearTimeSpanSelection);
371
372 q->disconnect(agendaView->agenda(), &Agenda::zoomView, agendaView, nullptr);
373 q->connect(agendaView->agenda(), &Agenda::zoomView, q, &MultiAgendaView::zoomView);
374 }
375
376 AgendaView *lastView = mAgendaViews.last();
377 for (AgendaView *agendaView : std::as_const(mAgendaViews)) {
378 if (agendaView != lastView) {
379 q->connect(agendaView->agenda()->verticalScrollBar(),
381 lastView->agenda()->verticalScrollBar(),
383 }
384 }
385
386 for (AgendaView *agenda : std::as_const(mAgendaViews)) {
387 agenda->readSettings();
388 }
389}
390
391MultiAgendaView::~MultiAgendaView() = default;
392
394{
396 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
397 list += agendaView->selectedIncidences();
398 }
399 return list;
400}
401
403{
405 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
406 list += agendaView->selectedIncidenceDates();
407 }
408 return list;
409}
410
412{
413 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
414 return agendaView->currentDateCount();
415 }
416 return 0;
417}
418
419void MultiAgendaView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth)
420{
421 Q_UNUSED(preferredMonth)
422 d->mStartDate = start;
423 d->mEndDate = end;
424 slotResizeScrollView();
425 d->mTimeLabelsZone->updateAll();
426 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
427 agendaView->showDates(start, end);
428 }
429}
430
431void MultiAgendaView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date)
432{
433 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
434 agendaView->showIncidences(incidenceList, date);
435 }
436}
437
438void MultiAgendaView::updateView()
439{
440 recreateViews();
441 for (AgendaView *agendaView : std::as_const(d->mAgendaViews)) {
442 agendaView->updateView();
443 }
444}
445
446int MultiAgendaView::maxDatesHint() const
447{
448 // these maxDatesHint functions aren't used
449 return AgendaView::MAX_DAY_COUNT;
450}
451
452void MultiAgendaView::slotSelectionChanged()
453{
454 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
455 if (agenda != sender()) {
456 agenda->clearSelection();
457 }
458 }
459}
460
461bool MultiAgendaView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const
462{
463 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
464 bool valid = agenda->eventDurationHint(startDt, endDt, allDay);
465 if (valid) {
466 return true;
467 }
468 }
469 return false;
470}
471
472// Invoked when user selects a cell or a span of cells in agendaview
473void MultiAgendaView::slotClearTimeSpanSelection()
474{
475 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
476 if (agenda != sender()) {
477 agenda->clearTimeSpanSelection();
478 } else if (!d->mCustomColumnSetupUsed) {
479 d->setActiveAgenda(agenda);
480 }
481 }
482}
483
484void MultiAgendaViewPrivate::setActiveAgenda(AgendaView *view)
485{
486 // Only makes sense in the one-agenda-per-calendar set up
487 if (mCustomColumnSetupUsed) {
488 return;
489 }
490
491 if (!view) {
492 return;
493 }
494
495 auto calendars = view->calendars();
496 if (calendars.empty()) {
497 return;
498 }
499 Q_ASSERT(calendars.size() == 1);
500
501 Q_EMIT q->activeCalendarChanged(calendars.at(0));
502}
503
504AgendaView *MultiAgendaViewPrivate::createView(const QString &title)
505{
506 auto box = new QWidget(mTopBox);
507 mTopBox->layout()->addWidget(box);
508 auto layout = new QVBoxLayout(box);
509 layout->setContentsMargins(0, 0, 0, 0);
510 auto av = new AgendaView(q->preferences(), q->startDateTime().date(), q->endDateTime().date(), true, true, q);
511 layout->addWidget(av);
512 av->setIncidenceChanger(q->changer());
513 av->setTitle(title);
514 av->agenda()->scrollArea()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
515 mAgendaViews.append(av);
516 mAgendaWidgets.append(box);
517 box->show();
518 mTimeLabelsZone->setAgendaView(av);
519
520 q->connect(mScrollBar, &QAbstractSlider::valueChanged, av->agenda()->verticalScrollBar(), &QAbstractSlider::setValue);
521
522 q->connect(av->splitter(), &QSplitter::splitterMoved, q, &MultiAgendaView::resizeSplitters);
523 // The change in all-day and regular agenda height ratio affects scrollbars as well
524 q->connect(av->splitter(), &QSplitter::splitterMoved, q, &MultiAgendaView::setupScrollBar);
525 q->connect(av, &AgendaView::showIncidencePopupSignal, q, &MultiAgendaView::showIncidencePopupSignal);
526
527 q->connect(av, &AgendaView::showNewEventPopupSignal, q, &MultiAgendaView::showNewEventPopupSignal);
528
529 const QSize minHint = av->allDayAgenda()->scrollArea()->minimumSizeHint();
530
531 if (minHint.isValid()) {
532 mLabel->setMinimumHeight(minHint.height());
533 mRightDummyWidget->setMinimumHeight(minHint.height());
534 }
535
536 return av;
537}
538
539void MultiAgendaViewPrivate::addView(const Akonadi::CollectionCalendar::Ptr &calendar)
540{
541 const auto title = Akonadi::CalendarUtils::displayName(calendar->model(), calendar->collection());
542 auto *view = createView(title);
543 view->addCalendar(calendar);
544}
545
546static void updateViewFromSelection(AgendaView *view,
547 const QItemSelection &selected,
548 const QItemSelection &deselected,
550{
551 for (const auto index : selected.indexes()) {
552 if (const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); col.isValid()) {
553 const auto calendar = factory->calendarForCollection(col);
554 view->addCalendar(calendar);
555 }
556 }
557 for (const auto index : deselected.indexes()) {
558 if (const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); col.isValid()) {
559 if (const auto calendar = view->calendarForCollection(col); calendar) {
560 view->removeCalendar(calendar);
561 }
562 }
563 }
564}
565
566void MultiAgendaViewPrivate::addView(KCheckableProxyModel *sm, const QString &title)
567{
568 auto *view = createView(title);
569 // During launch the underlying ETM doesn't have the entire Collection tree populated,
570 // so the "sm" contains an icomplete selection - we must listen for changes and upated
571 // the view later on
572 QObject::connect(sm->selectionModel(),
574 view,
575 [this, view](const QItemSelection &selected, const QItemSelection &deselected) {
576 updateViewFromSelection(view, selected, deselected, mCalendarFactory);
577 });
578
579 // Initial update
580 updateViewFromSelection(view, sm->selectionModel()->selection(), QItemSelection{}, mCalendarFactory);
581}
582
583void MultiAgendaView::resizeEvent(QResizeEvent *ev)
584{
585 d->resizeScrollView(ev->size());
587 setupScrollBar();
588}
589
590void MultiAgendaViewPrivate::resizeScrollView(QSize size)
591{
592 const int widgetWidth = size.width() - mTimeLabelsZone->width() - mScrollBar->width();
593
594 int height = size.height();
595 if (mScrollArea->horizontalScrollBar()->isVisible()) {
596 const int sbHeight = mScrollArea->horizontalScrollBar()->height();
597 height -= sbHeight;
598 mLeftBottomSpacer->setFixedHeight(sbHeight);
599 mRightBottomSpacer->setFixedHeight(sbHeight);
600 } else {
601 mLeftBottomSpacer->setFixedHeight(0);
602 mRightBottomSpacer->setFixedHeight(0);
603 }
604
605 mTopBox->resize(widgetWidth, height);
606}
607
608void MultiAgendaView::setIncidenceChanger(Akonadi::IncidenceChanger *changer)
609{
611 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
612 agenda->setIncidenceChanger(changer);
613 }
614}
615
616void MultiAgendaView::setPreferences(const PrefsPtr &prefs)
617{
618 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
619 agenda->setPreferences(prefs);
620 }
621 EventView::setPreferences(prefs);
622}
623
624void MultiAgendaView::updateConfig()
625{
627 d->mTimeLabelsZone->setPreferences(preferences());
628 d->mTimeLabelsZone->updateAll();
629 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
630 agenda->updateConfig();
631 }
632}
633
634void MultiAgendaView::resizeSplitters()
635{
636 if (d->mAgendaViews.isEmpty()) {
637 return;
638 }
639
640 auto lastMovedSplitter = qobject_cast<QSplitter *>(sender());
641 if (!lastMovedSplitter) {
642 lastMovedSplitter = d->mLeftSplitter;
643 }
644 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
645 if (agenda->splitter() == lastMovedSplitter) {
646 continue;
647 }
648 agenda->splitter()->setSizes(lastMovedSplitter->sizes());
649 }
650 if (lastMovedSplitter != d->mLeftSplitter) {
651 d->mLeftSplitter->setSizes(lastMovedSplitter->sizes());
652 }
653 if (lastMovedSplitter != d->mRightSplitter) {
654 d->mRightSplitter->setSizes(lastMovedSplitter->sizes());
655 }
656}
657
658void MultiAgendaView::zoomView(const int delta, QPoint pos, const Qt::Orientation ori)
659{
660 const int hourSz = preferences()->hourSize();
661 if (ori == Qt::Vertical) {
662 if (delta > 0) {
663 if (hourSz > 4) {
664 preferences()->setHourSize(hourSz - 1);
665 }
666 } else {
667 preferences()->setHourSize(hourSz + 1);
668 }
669 }
670
671 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
672 agenda->zoomView(delta, pos, ori);
673 }
674
675 d->mTimeLabelsZone->updateAll();
676}
677
678void MultiAgendaView::slotResizeScrollView()
679{
680 d->resizeScrollView(size());
681}
682
683void MultiAgendaView::showEvent(QShowEvent *event)
684{
686 if (d->mUpdateOnShow) {
687 d->mUpdateOnShow = false;
688 d->mPendingChanges = true; // force a full view recreation
689 showDates(d->mStartDate, d->mEndDate);
690 }
691}
692
694{
696 for (AgendaView *agenda : std::as_const(d->mAgendaViews)) {
697 agenda->setChanges(changes);
698 }
699}
700
701void MultiAgendaView::setupScrollBar()
702{
703 if (!d->mAgendaViews.isEmpty() && d->mAgendaViews.constFirst()->agenda()) {
704 QScrollBar *scrollBar = d->mAgendaViews.constFirst()->agenda()->verticalScrollBar();
705 d->mScrollBar->setMinimum(scrollBar->minimum());
706 d->mScrollBar->setMaximum(scrollBar->maximum());
707 d->mScrollBar->setSingleStep(scrollBar->singleStep());
708 d->mScrollBar->setPageStep(scrollBar->pageStep());
709 d->mScrollBar->setValue(scrollBar->value());
710 }
711}
712
714{
715 qCDebug(CALENDARVIEW_LOG);
716 d->mPendingChanges = true;
717 recreateViews();
718}
719
721{
722 /** The wrapper in korg has the dialog. Too complicated to move to CalendarViews.
723 Depends on korg/AkonadiCollectionView, and will be refactored some day
724 to get rid of CollectionSelectionProxyModel/EntityStateSaver */
725 return false;
726}
727
729{
730 /*
731 if (!calendar()) {
732 qCCritical(CALENDARVIEW_LOG) << "Calendar is not set.";
733 Q_ASSERT(false);
734 return;
735 }
736 */
737
738 d->mCustomColumnSetupUsed = configGroup.readEntry("UseCustomColumnSetup", false);
739 d->mCustomNumberOfColumns = configGroup.readEntry("CustomNumberOfColumns", 2);
740 d->mCustomColumnTitles = configGroup.readEntry("ColumnTitles", QStringList());
741 if (d->mCustomColumnTitles.size() != d->mCustomNumberOfColumns) {
742 const int orig = d->mCustomColumnTitles.size();
743 d->mCustomColumnTitles.reserve(d->mCustomNumberOfColumns);
744 for (int i = orig; i < d->mCustomNumberOfColumns; ++i) {
745 d->mCustomColumnTitles.push_back(generateColumnLabel(i));
746 }
747 }
748
749 QList<KCheckableProxyModel *> oldModels = d->mCollectionSelectionModels;
750 d->mCollectionSelectionModels.clear();
751
752 if (d->mCustomColumnSetupUsed) {
753 d->mCollectionSelectionModels.resize(d->mCustomNumberOfColumns);
754 for (int i = 0; i < d->mCustomNumberOfColumns; ++i) {
755 // Sort the calanders by name
756 auto sortProxy = new QSortFilterProxyModel(this);
757 sortProxy->setDynamicSortFilter(true);
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() + QLatin1StringView("_subView_") + 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() + QLatin1StringView("_subView_") + 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"
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:67
EventView is the abstract base class from which all other calendar views for event data are derived.
Definition eventview.h:67
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 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(GameStandardAction id, const QObject *recvr, const char *slot, QObject *parent)
QScrollBar * horizontalScrollBar() const const
QScrollBar * verticalScrollBar() const const
void setValue(int)
void valueChanged(int value)
QDate date() const const
int height() 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)
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 Tue Mar 26 2024 11:12:29 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.