Eventviews

timelineview.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org>
3 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
4 SPDX-FileCopyrightText: 2010 Andras Mantia <andras@kdab.com>
5
6 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
7*/
8
9#include "timelineview.h"
10#include "helper.h"
11#include "timelineitem.h"
12#include "timelineview_p.h"
13
14#include <KGanttAbstractRowController>
15#include <KGanttDateTimeGrid>
16#include <KGanttGraphicsItem>
17#include <KGanttGraphicsView>
18#include <KGanttItemDelegate>
19#include <KGanttStyleOptionGanttItem>
20
21#include <Akonadi/CalendarUtils>
22#include <Akonadi/IncidenceChanger>
23#include <CalendarSupport/CollectionSelection>
24
25#include "calendarview_debug.h"
26
27#include <KLocalizedString>
28#include <QApplication>
29#include <QHeaderView>
30#include <QHelpEvent>
31#include <QPainter>
32#include <QPointer>
33#include <QSplitter>
34#include <QStandardItemModel>
35#include <QTreeWidget>
36#include <QVBoxLayout>
37
38#include <chrono>
39
40using namespace KCalendarCore;
41using namespace EventViews;
42
43namespace EventViews
44{
45class RowController : public KGantt::AbstractRowController
46{
47private:
48 static const int ROW_HEIGHT;
49 QPointer<QAbstractItemModel> m_model;
50
51public:
52 RowController()
53 {
54 mRowHeight = 20;
55 }
56
57 void setModel(QAbstractItemModel *model)
58 {
59 m_model = model;
60 }
61
62 int headerHeight() const override
63 {
64 return 2 * mRowHeight + 10;
65 }
66
67 bool isRowVisible(const QModelIndex &) const override
68 {
69 return true;
70 }
71
72 bool isRowExpanded(const QModelIndex &) const override
73 {
74 return false;
75 }
76
77 KGantt::Span rowGeometry(const QModelIndex &idx) const override
78 {
79 return KGantt::Span(idx.row() * mRowHeight, mRowHeight);
80 }
81
82 int maximumItemHeight() const override
83 {
84 return mRowHeight / 2;
85 }
86
87 int totalHeight() const override
88 {
89 return m_model->rowCount() * mRowHeight;
90 }
91
92 QModelIndex indexAt(int height) const override
93 {
94 return m_model->index(height / mRowHeight, 0);
95 }
96
97 QModelIndex indexBelow(const QModelIndex &idx) const override
98 {
99 if (!idx.isValid()) {
100 return {};
101 }
102 return idx.model()->index(idx.row() + 1, idx.column(), idx.parent());
103 }
104
105 QModelIndex indexAbove(const QModelIndex &idx) const override
106 {
107 if (!idx.isValid()) {
108 return {};
109 }
110 return idx.model()->index(idx.row() - 1, idx.column(), idx.parent());
111 }
112
113 void setRowHeight(int height)
114 {
115 mRowHeight = height;
116 }
117
118private:
119 int mRowHeight;
120};
121
122class GanttHeaderView : public QHeaderView
123{
124public:
125 explicit GanttHeaderView(QWidget *parent = nullptr)
127 {
129 }
130
131 QSize sizeHint() const override
132 {
133 QSize s = QHeaderView::sizeHint();
134 s.rheight() *= 2;
135 return s;
136 }
137};
138class GanttItemDelegate : public KGantt::ItemDelegate
139{
140public:
141 explicit GanttItemDelegate(QObject *parent)
142 : KGantt::ItemDelegate(parent)
143 {
144 }
145
146private:
147 void paintGanttItem(QPainter *painter, const KGantt::StyleOptionGanttItem &opt, const QModelIndex &idx) override
148 {
150 if (!idx.isValid()) {
151 return;
152 }
153 const KGantt::ItemType type = static_cast<KGantt::ItemType>(idx.model()->data(idx, KGantt::ItemTypeRole).toInt());
154
155 const QString txt = idx.model()->data(idx, Qt::DisplayRole).toString();
156 QRectF itemRect = opt.itemRect;
157 QRectF boundingRect = opt.boundingRect;
158 boundingRect.setY(itemRect.y());
159 boundingRect.setHeight(itemRect.height());
160
161 QBrush brush = defaultBrush(type);
162 if (opt.state & QStyle::State_Selected) {
163 QLinearGradient selectedGrad(0., 0., 0., QFontMetricsF(painter->font()).height());
164 selectedGrad.setColorAt(0., Qt::red);
165 selectedGrad.setColorAt(1., Qt::darkRed);
166
167 brush = QBrush(selectedGrad);
168 painter->setBrush(brush);
169 } else {
170 painter->setBrush(idx.model()->data(idx, Qt::DecorationRole).value<QColor>());
171 }
172
173 painter->setPen(defaultPen(type));
174 painter->setBrushOrigin(itemRect.topLeft());
175
176 switch (type) {
177 case KGantt::TypeTask:
178 if (itemRect.isValid()) {
179 QRectF r = itemRect;
180 painter->drawRect(r);
181 bool drawText = true;
182 Qt::Alignment ta;
183 switch (opt.displayPosition) {
185 ta = Qt::AlignLeft;
186 break;
188 ta = Qt::AlignRight;
189 break;
190 case KGantt::StyleOptionGanttItem::Center:
191 ta = Qt::AlignCenter;
192 break;
193 case KGantt::StyleOptionGanttItem::Hidden:
194 drawText = false;
195 break;
196 }
197 if (drawText) {
198 painter->drawText(boundingRect, ta, txt);
199 }
200 }
201 break;
202 default:
203 KGantt::ItemDelegate::paintGanttItem(painter, opt, idx);
204 break;
205 }
206 }
207};
208}
209
210TimelineView::TimelineView(const EventViews::PrefsPtr &preferences, QWidget *parent)
212{
213 setPreferences(preferences);
214}
215
218 , d(new TimelineViewPrivate(this))
219{
220 auto vbox = new QVBoxLayout(this);
221 auto splitter = new QSplitter(Qt::Horizontal, this);
222 d->mLeftView = new QTreeWidget;
223 d->mLeftView->setColumnCount(1);
224 d->mLeftView->setHeader(new GanttHeaderView);
225 d->mLeftView->setHeaderLabel(i18n("Calendar"));
226 d->mLeftView->setRootIsDecorated(false);
227 d->mLeftView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
228 d->mLeftView->setUniformRowHeights(true);
229
230 d->mGantt = new KGantt::GraphicsView(this);
231 splitter->addWidget(d->mLeftView);
232 splitter->addWidget(d->mGantt);
233 splitter->setSizes({200, 600});
234 auto model = new QStandardItemModel(this);
235
236 d->mRowController = new RowController;
237
239 opt.initFrom(d->mLeftView);
240 const auto h = d->mLeftView->style()->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), d->mLeftView).height();
241 d->mRowController->setRowHeight(h);
242
243 d->mRowController->setModel(model);
244 d->mGantt->setRowController(d->mRowController);
245 auto grid = new KGantt::DateTimeGrid();
246 grid->setScale(KGantt::DateTimeGrid::ScaleHour);
247 grid->setDayWidth(800);
248 grid->setRowSeparators(true);
249 d->mGantt->setGrid(grid);
250 d->mGantt->setModel(model);
251 d->mGantt->viewport()->setFixedWidth(8000);
252
253 d->mGantt->viewport()->installEventFilter(this);
254 d->mGantt->setItemDelegate(new GanttItemDelegate(this));
255
256 vbox->addWidget(splitter);
257
258 connect(model, &QStandardItemModel::itemChanged, d.get(), &TimelineViewPrivate::itemChanged);
259
260 connect(d->mGantt, &KGantt::GraphicsView::activated, d.get(), &TimelineViewPrivate::itemSelected);
261 d->mGantt->setContextMenuPolicy(Qt::CustomContextMenu);
262 connect(d->mGantt, &QWidget::customContextMenuRequested, d.get(), &TimelineViewPrivate::contextMenuRequested);
263}
264
265TimelineView::~TimelineView()
266{
267 delete d->mRowController;
268}
269
271{
272 return d->mSelectedItemList;
273}
274
279
281{
282 return 0;
283}
284
285void TimelineView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth)
286{
287 Q_UNUSED(preferredMonth)
288 Q_ASSERT_X(start.isValid(), "showDates()", "start date must be valid");
289 Q_ASSERT_X(end.isValid(), "showDates()", "end date must be valid");
290
291 qCDebug(CALENDARVIEW_LOG) << "start=" << start << "end=" << end;
292
293 d->mStartDate = start;
294 d->mEndDate = end;
295 d->mHintDate = QDateTime();
296
297 auto grid = static_cast<KGantt::DateTimeGrid *>(d->mGantt->grid());
298 grid->setStartDateTime(QDateTime(start.startOfDay()));
299 d->mLeftView->clear();
300 qDeleteAll(d->mCalendarItemMap);
301 d->mCalendarItemMap.clear();
302
303 uint index = 0;
304 for (const auto &calendar : calendars()) {
305 auto item = new TimelineItem(calendar, index++, static_cast<QStandardItemModel *>(d->mGantt->model()), d->mGantt);
306 const auto name = Akonadi::CalendarUtils::displayName(calendar->model(), calendar->collection());
307 d->mLeftView->addTopLevelItem(new QTreeWidgetItem(QStringList{name}));
308 const QColor resourceColor = EventViews::resourceColor(calendar->collection(), preferences());
309 if (resourceColor.isValid()) {
310 item->setColor(resourceColor);
311 }
312 qCDebug(CALENDARVIEW_LOG) << "Created item " << item << " (" << name << ")"
313 << "with index " << index - 1 << " from collection " << calendar->collection().id();
314 d->mCalendarItemMap.insert(calendar->collection().id(), item);
315 }
316
317 // add incidences
318
319 /**
320 * We remove the model from the view here while we fill it with items,
321 * because every call to insertIncidence will cause the view to do an expensive
322 * updateScene() call otherwise.
323 */
324 QAbstractItemModel *ganttModel = d->mGantt->model();
325 d->mGantt->setModel(nullptr);
326
327 for (const auto &calendar : calendars()) {
328 for (QDate day = d->mStartDate; day <= d->mEndDate; day = day.addDays(1)) {
330 for (const KCalendarCore::Event::Ptr &event : std::as_const(events)) {
331 if (event->hasRecurrenceId()) {
332 continue;
333 }
334 Akonadi::Item item = calendar->item(event);
335 d->insertIncidence(calendar, item, day);
336 }
337 }
338 }
339 d->mGantt->setModel(ganttModel);
340}
341
342void TimelineView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date)
343{
344 Q_UNUSED(incidenceList)
345 Q_UNUSED(date)
346}
347
349{
350 if (d->mStartDate.isValid() && d->mEndDate.isValid()) {
351 showDates(d->mStartDate, d->mEndDate);
352 }
353}
354
355void TimelineView::changeIncidenceDisplay(const Akonadi::Item &incidence, int mode)
356{
357 const auto cal = calendar3(incidence);
358 switch (mode) {
359 case Akonadi::IncidenceChanger::ChangeTypeCreate:
360 d->insertIncidence(cal, incidence);
361 break;
362 case Akonadi::IncidenceChanger::ChangeTypeModify:
363 d->removeIncidence(incidence);
364 d->insertIncidence(cal, incidence);
365 break;
366 case Akonadi::IncidenceChanger::ChangeTypeDelete:
367 d->removeIncidence(incidence);
368 break;
369 default:
370 updateView();
371 }
372}
373
374bool TimelineView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const
375{
376 bool modified = false;
377 if (d->mHintDate.isValid() && !startDt.isValid()) {
378 startDt = QDateTime(d->mHintDate);
379 modified = true;
380 }
381
382 if (modified || !endDt.isValid() || endDt == startDt) {
383 endDt = QDateTime(startDt.addDuration(std::chrono::hours(2)));
384 modified = true;
385 }
386
387 if (allDay) {
388 allDay = false;
389 modified = true;
390 }
391
392 return modified;
393}
394
396{
397 return d->mStartDate;
398}
399
401{
402 return d->mEndDate;
403}
404
405bool TimelineView::eventFilter(QObject *object, QEvent *event)
406{
407 if (event->type() == QEvent::ToolTip) {
408 auto helpEvent = static_cast<QHelpEvent *>(event);
409 QGraphicsItem *item = d->mGantt->itemAt(helpEvent->pos());
410 if (item) {
411 if (item->type() == KGantt::GraphicsItem::Type) {
412 auto graphicsItem = static_cast<KGantt::GraphicsItem *>(item);
413 const QModelIndex itemIndex = graphicsItem->index();
414
415 auto itemModel = qobject_cast<QStandardItemModel *>(d->mGantt->model());
416
417 auto timelineItem = dynamic_cast<TimelineSubItem *>(itemModel->item(itemIndex.row(), itemIndex.column()));
418
419 if (timelineItem) {
420 timelineItem->updateToolTip();
421 }
422 }
423 }
424 }
425
426 return EventView::eventFilter(object, event);
427}
428
429#include "moc_timelineview.cpp"
QList< Item > List
EventView(QWidget *parent=nullptr)
Constructs a view.
Definition eventview.cpp:54
int currentDateCount() const override
Returns the number of currently shown dates.
void showDates(const QDate &, const QDate &, const QDate &preferredMonth=QDate()) override
TimelineView(const EventViews::PrefsPtr &preferences, QWidget *parent=nullptr)
Create a TimelineView.
KCalendarCore::DateList selectedIncidenceDates() const override
Returns a list of the dates of selected events.
Akonadi::Item::List selectedIncidences() const override
bool eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const override
Sets the default start/end date/time for new events.
void showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) override
Shows given incidences.
void updateView() override
Updates the current display to reflect changes that may have happened in the calendar since the last ...
QSharedPointer< Event > Ptr
void setStartDateTime(const QDateTime &dt)
QPen defaultPen(ItemType type) const
virtual void paintGanttItem(QPainter *p, const StyleOptionGanttItem &opt, const QModelIndex &idx)
QBrush defaultBrush(ItemType type) const
ItemDelegate(QObject *parent=nullptr)
Q_SCRIPTABLE QString start(QString train="")
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
Type type(const QSqlDatabase &db)
Namespace EventViews provides facilities for displaying incidences, including events,...
Definition agenda.h:33
EVENTVIEWS_EXPORT QColor resourceColor(const Akonadi::Collection &collection, const PrefsPtr &preferences)
This method returns the proper resource / subresource color for the view.
Definition helper.cpp:56
QList< QDate > DateList
virtual QVariant data(const QModelIndex &index, int role) const const=0
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
QDate addDays(qint64 ndays) const const
QDateTime addDuration(std::chrono::milliseconds msecs) const const
bool isValid() const const
virtual int type() const const
QHeaderView(Qt::Orientation orientation, QWidget *parent)
void setSectionResizeMode(ResizeMode mode)
virtual QSize sizeHint() const const override
int column() const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QObject(QObject *parent)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
virtual bool eventFilter(QObject *watched, QEvent *event)
QObject * parent() const const
T qobject_cast(QObject *object)
void drawRect(const QRect &rectangle)
void drawText(const QPoint &position, const QString &text)
const QFont & font() const const
void setBrush(Qt::BrushStyle style)
void setBrushOrigin(const QPoint &position)
void setPen(Qt::PenStyle style)
void setRenderHints(RenderHints hints, bool on)
qreal height() const const
bool isValid() const const
void setHeight(qreal height)
void setY(qreal y)
QPointF topLeft() const const
qreal y() const const
int & rheight()
void itemChanged(QStandardItem *item)
void initFrom(const QWidget *widget)
typedef Alignment
CustomContextMenu
DisplayRole
Horizontal
ScrollBarAlwaysOff
QTimeZone systemTimeZone()
void setColumnCount(int columns)
QString toString() const const
T value() const const
QWidget(QWidget *parent, Qt::WindowFlags f)
void customContextMenuRequested(const QPoint &pos)
virtual bool event(QEvent *event) override
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:47:03 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.