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;
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 {
134 s.rheight() *= 2;
135 return s;
136 }
137};
138class GanttItemDelegate : public KGantt::ItemDelegate
139{
140public:
141 explicit GanttItemDelegate(QObject *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
211 : TimelineView(parent)
212{
213 setPreferences(preferences);
214}
215
217 : EventView(parent)
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"
EventView is the abstract base class from which all other calendar views for event data are derived.
Definition eventview.h:69
This class provides a view showing which blocks of time are occupied by events in the user's calendar...
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 ...
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
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)
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
virtual QVariant data(const QModelIndex &index, int role) const const=0
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
bool isValid() const const
QDateTime addDuration(std::chrono::milliseconds msecs) const const
bool isValid() const const
qreal height() const const
virtual int type() const const
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
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
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()
QString toString() const const
T value() const const
void customContextMenuRequested(const QPoint &pos)
virtual bool event(QEvent *event) override
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:07:11 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.