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
38using namespace KCalendarCore;
39using namespace EventViews;
40
41namespace EventViews
42{
43class RowController : public KGantt::AbstractRowController
44{
45private:
46 static const int ROW_HEIGHT;
48
49public:
50 RowController()
51 {
52 mRowHeight = 20;
53 }
54
55 void setModel(QAbstractItemModel *model)
56 {
57 m_model = model;
58 }
59
60 int headerHeight() const override
61 {
62 return 2 * mRowHeight + 10;
63 }
64
65 bool isRowVisible(const QModelIndex &) const override
66 {
67 return true;
68 }
69
70 bool isRowExpanded(const QModelIndex &) const override
71 {
72 return false;
73 }
74
75 KGantt::Span rowGeometry(const QModelIndex &idx) const override
76 {
77 return KGantt::Span(idx.row() * mRowHeight, mRowHeight);
78 }
79
80 int maximumItemHeight() const override
81 {
82 return mRowHeight / 2;
83 }
84
85 int totalHeight() const override
86 {
87 return m_model->rowCount() * mRowHeight;
88 }
89
90 QModelIndex indexAt(int height) const override
91 {
92 return m_model->index(height / mRowHeight, 0);
93 }
94
95 QModelIndex indexBelow(const QModelIndex &idx) const override
96 {
97 if (!idx.isValid()) {
98 return {};
99 }
100 return idx.model()->index(idx.row() + 1, idx.column(), idx.parent());
101 }
102
103 QModelIndex indexAbove(const QModelIndex &idx) const override
104 {
105 if (!idx.isValid()) {
106 return {};
107 }
108 return idx.model()->index(idx.row() - 1, idx.column(), idx.parent());
109 }
110
111 void setRowHeight(int height)
112 {
113 mRowHeight = height;
114 }
115
116private:
117 int mRowHeight;
118};
119
120class GanttHeaderView : public QHeaderView
121{
122public:
123 explicit GanttHeaderView(QWidget *parent = nullptr)
125 {
127 }
128
129 QSize sizeHint() const override
130 {
132 s.rheight() *= 2;
133 return s;
134 }
135};
136class GanttItemDelegate : public KGantt::ItemDelegate
137{
138public:
139 explicit GanttItemDelegate(QObject *parent)
141 {
142 }
143
144private:
145 void paintGanttItem(QPainter *painter, const KGantt::StyleOptionGanttItem &opt, const QModelIndex &idx) override
146 {
148 if (!idx.isValid()) {
149 return;
150 }
151 const KGantt::ItemType type = static_cast<KGantt::ItemType>(idx.model()->data(idx, KGantt::ItemTypeRole).toInt());
152
153 const QString txt = idx.model()->data(idx, Qt::DisplayRole).toString();
154 QRectF itemRect = opt.itemRect;
155 QRectF boundingRect = opt.boundingRect;
156 boundingRect.setY(itemRect.y());
157 boundingRect.setHeight(itemRect.height());
158
159 QBrush brush = defaultBrush(type);
160 if (opt.state & QStyle::State_Selected) {
161 QLinearGradient selectedGrad(0., 0., 0., QFontMetricsF(painter->font()).height());
162 selectedGrad.setColorAt(0., Qt::red);
163 selectedGrad.setColorAt(1., Qt::darkRed);
164
165 brush = QBrush(selectedGrad);
166 painter->setBrush(brush);
167 } else {
168 painter->setBrush(idx.model()->data(idx, Qt::DecorationRole).value<QColor>());
169 }
170
171 painter->setPen(defaultPen(type));
172 painter->setBrushOrigin(itemRect.topLeft());
173
174 switch (type) {
175 case KGantt::TypeTask:
176 if (itemRect.isValid()) {
177 QRectF r = itemRect;
178 painter->drawRect(r);
179 bool drawText = true;
180 Qt::Alignment ta;
181 switch (opt.displayPosition) {
183 ta = Qt::AlignLeft;
184 break;
186 ta = Qt::AlignRight;
187 break;
188 case KGantt::StyleOptionGanttItem::Center:
189 ta = Qt::AlignCenter;
190 break;
191 case KGantt::StyleOptionGanttItem::Hidden:
192 drawText = false;
193 break;
194 }
195 if (drawText) {
196 painter->drawText(boundingRect, ta, txt);
197 }
198 }
199 break;
200 default:
201 KGantt::ItemDelegate::paintGanttItem(painter, opt, idx);
202 break;
203 }
204 }
205};
206}
207
209 : TimelineView(parent)
210{
211 setPreferences(preferences);
212}
213
215 : EventView(parent)
216 , d(new TimelineViewPrivate(this))
217{
218 auto vbox = new QVBoxLayout(this);
219 auto splitter = new QSplitter(Qt::Horizontal, this);
220 d->mLeftView = new QTreeWidget;
221 d->mLeftView->setColumnCount(1);
222 d->mLeftView->setHeader(new GanttHeaderView);
223 d->mLeftView->setHeaderLabel(i18n("Calendar"));
224 d->mLeftView->setRootIsDecorated(false);
225 d->mLeftView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
226 d->mLeftView->setUniformRowHeights(true);
227
228 d->mGantt = new KGantt::GraphicsView(this);
229 splitter->addWidget(d->mLeftView);
230 splitter->addWidget(d->mGantt);
231 splitter->setSizes({200, 600});
232 auto model = new QStandardItemModel(this);
233
234 d->mRowController = new RowController;
235
237 opt.initFrom(d->mLeftView);
238 const auto h = d->mLeftView->style()->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), d->mLeftView).height();
239 d->mRowController->setRowHeight(h);
240
241 d->mRowController->setModel(model);
242 d->mGantt->setRowController(d->mRowController);
243 auto grid = new KGantt::DateTimeGrid();
244 grid->setScale(KGantt::DateTimeGrid::ScaleHour);
245 grid->setDayWidth(800);
246 grid->setRowSeparators(true);
247 d->mGantt->setGrid(grid);
248 d->mGantt->setModel(model);
249 d->mGantt->viewport()->setFixedWidth(8000);
250
251 d->mGantt->viewport()->installEventFilter(this);
252 d->mGantt->setItemDelegate(new GanttItemDelegate(this));
253
254 vbox->addWidget(splitter);
255
256 connect(model, &QStandardItemModel::itemChanged, d.get(), &TimelineViewPrivate::itemChanged);
257
258 connect(d->mGantt, &KGantt::GraphicsView::activated, d.get(), &TimelineViewPrivate::itemSelected);
259 d->mGantt->setContextMenuPolicy(Qt::CustomContextMenu);
260 connect(d->mGantt, &QWidget::customContextMenuRequested, d.get(), &TimelineViewPrivate::contextMenuRequested);
261}
262
263TimelineView::~TimelineView()
264{
265 delete d->mRowController;
266}
267
269{
270 return d->mSelectedItemList;
271}
272
277
279{
280 return 0;
281}
282
283void TimelineView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth)
284{
285 Q_UNUSED(preferredMonth)
286 Q_ASSERT_X(start.isValid(), "showDates()", "start date must be valid");
287 Q_ASSERT_X(end.isValid(), "showDates()", "end date must be valid");
288
289 qCDebug(CALENDARVIEW_LOG) << "start=" << start << "end=" << end;
290
291 d->mStartDate = start;
292 d->mEndDate = end;
293 d->mHintDate = QDateTime();
294
295 auto grid = static_cast<KGantt::DateTimeGrid *>(d->mGantt->grid());
296 grid->setStartDateTime(QDateTime(start.startOfDay()));
297 d->mLeftView->clear();
298 qDeleteAll(d->mCalendarItemMap);
299 d->mCalendarItemMap.clear();
300
301 uint index = 0;
302 for (const auto &calendar : calendars()) {
303 auto item = new TimelineItem(calendar, index++, static_cast<QStandardItemModel *>(d->mGantt->model()), d->mGantt);
304 const auto name = Akonadi::CalendarUtils::displayName(calendar->model(), calendar->collection());
305 d->mLeftView->addTopLevelItem(new QTreeWidgetItem(QStringList{name}));
306 const QColor resourceColor = EventViews::resourceColor(calendar->collection(), preferences());
307 if (resourceColor.isValid()) {
308 item->setColor(resourceColor);
309 }
310 qCDebug(CALENDARVIEW_LOG) << "Created item " << item << " (" << name << ")"
311 << "with index " << index - 1 << " from collection " << calendar->collection().id();
312 d->mCalendarItemMap.insert(calendar->collection().id(), item);
313 }
314
315 // add incidences
316
317 /**
318 * We remove the model from the view here while we fill it with items,
319 * because every call to insertIncidence will cause the view to do an expensive
320 * updateScene() call otherwise.
321 */
322 QAbstractItemModel *ganttModel = d->mGantt->model();
323 d->mGantt->setModel(nullptr);
324
325 for (const auto &calendar : calendars()) {
326 for (QDate day = d->mStartDate; day <= d->mEndDate; day = day.addDays(1)) {
328 for (const KCalendarCore::Event::Ptr &event : std::as_const(events)) {
329 if (event->hasRecurrenceId()) {
330 continue;
331 }
332 Akonadi::Item item = calendar->item(event);
333 d->insertIncidence(calendar, item, day);
334 }
335 }
336 }
337 d->mGantt->setModel(ganttModel);
338}
339
340void TimelineView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date)
341{
342 Q_UNUSED(incidenceList)
343 Q_UNUSED(date)
344}
345
347{
348 if (d->mStartDate.isValid() && d->mEndDate.isValid()) {
349 showDates(d->mStartDate, d->mEndDate);
350 }
351}
352
353void TimelineView::changeIncidenceDisplay(const Akonadi::Item &incidence, int mode)
354{
355 const auto cal = calendar3(incidence);
356 switch (mode) {
357 case Akonadi::IncidenceChanger::ChangeTypeCreate:
358 d->insertIncidence(cal, incidence);
359 break;
360 case Akonadi::IncidenceChanger::ChangeTypeModify:
361 d->removeIncidence(incidence);
362 d->insertIncidence(cal, incidence);
363 break;
364 case Akonadi::IncidenceChanger::ChangeTypeDelete:
365 d->removeIncidence(incidence);
366 break;
367 default:
368 updateView();
369 }
370}
371
372bool TimelineView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const
373{
374 startDt = QDateTime(d->mHintDate);
375 endDt = QDateTime(d->mHintDate.addSecs(2 * 60 * 60));
376 allDay = false;
377 return d->mHintDate.isValid();
378}
379
381{
382 return d->mStartDate;
383}
384
386{
387 return d->mEndDate;
388}
389
390bool TimelineView::eventFilter(QObject *object, QEvent *event)
391{
392 if (event->type() == QEvent::ToolTip) {
393 auto helpEvent = static_cast<QHelpEvent *>(event);
394 QGraphicsItem *item = d->mGantt->itemAt(helpEvent->pos());
395 if (item) {
396 if (item->type() == KGantt::GraphicsItem::Type) {
397 auto graphicsItem = static_cast<KGantt::GraphicsItem *>(item);
398 const QModelIndex itemIndex = graphicsItem->index();
399
400 auto itemModel = qobject_cast<QStandardItemModel *>(d->mGantt->model());
401
402 auto timelineItem = dynamic_cast<TimelineSubItem *>(itemModel->item(itemIndex.row(), itemIndex.column()));
403
404 if (timelineItem) {
405 timelineItem->updateToolTip();
406 }
407 }
408 }
409 }
410
411 return EventView::eventFilter(object, event);
412}
413
414#include "moc_timelineview.cpp"
EventView is the abstract base class from which all other calendar views for event data are derived.
Definition eventview.h:67
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
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
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()
int toInt(bool *ok) const const
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 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.