File indexing completed on 2024-05-26 05:15:29
0001 /* 0002 SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org> 0003 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0004 SPDX-FileCopyrightText: 2010 Andras Mantia <andras@kdab.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0007 */ 0008 0009 #include "timelineview.h" 0010 #include "helper.h" 0011 #include "timelineitem.h" 0012 #include "timelineview_p.h" 0013 0014 #include <KGanttAbstractRowController> 0015 #include <KGanttDateTimeGrid> 0016 #include <KGanttGraphicsItem> 0017 #include <KGanttGraphicsView> 0018 #include <KGanttItemDelegate> 0019 #include <KGanttStyleOptionGanttItem> 0020 0021 #include <Akonadi/CalendarUtils> 0022 #include <Akonadi/IncidenceChanger> 0023 #include <CalendarSupport/CollectionSelection> 0024 0025 #include "calendarview_debug.h" 0026 0027 #include <KLocalizedString> 0028 #include <QApplication> 0029 #include <QHeaderView> 0030 #include <QHelpEvent> 0031 #include <QPainter> 0032 #include <QPointer> 0033 #include <QSplitter> 0034 #include <QStandardItemModel> 0035 #include <QTreeWidget> 0036 #include <QVBoxLayout> 0037 0038 using namespace KCalendarCore; 0039 using namespace EventViews; 0040 0041 namespace EventViews 0042 { 0043 class RowController : public KGantt::AbstractRowController 0044 { 0045 private: 0046 static const int ROW_HEIGHT; 0047 QPointer<QAbstractItemModel> m_model; 0048 0049 public: 0050 RowController() 0051 { 0052 mRowHeight = 20; 0053 } 0054 0055 void setModel(QAbstractItemModel *model) 0056 { 0057 m_model = model; 0058 } 0059 0060 int headerHeight() const override 0061 { 0062 return 2 * mRowHeight + 10; 0063 } 0064 0065 bool isRowVisible(const QModelIndex &) const override 0066 { 0067 return true; 0068 } 0069 0070 bool isRowExpanded(const QModelIndex &) const override 0071 { 0072 return false; 0073 } 0074 0075 KGantt::Span rowGeometry(const QModelIndex &idx) const override 0076 { 0077 return KGantt::Span(idx.row() * mRowHeight, mRowHeight); 0078 } 0079 0080 int maximumItemHeight() const override 0081 { 0082 return mRowHeight / 2; 0083 } 0084 0085 int totalHeight() const override 0086 { 0087 return m_model->rowCount() * mRowHeight; 0088 } 0089 0090 QModelIndex indexAt(int height) const override 0091 { 0092 return m_model->index(height / mRowHeight, 0); 0093 } 0094 0095 QModelIndex indexBelow(const QModelIndex &idx) const override 0096 { 0097 if (!idx.isValid()) { 0098 return {}; 0099 } 0100 return idx.model()->index(idx.row() + 1, idx.column(), idx.parent()); 0101 } 0102 0103 QModelIndex indexAbove(const QModelIndex &idx) const override 0104 { 0105 if (!idx.isValid()) { 0106 return {}; 0107 } 0108 return idx.model()->index(idx.row() - 1, idx.column(), idx.parent()); 0109 } 0110 0111 void setRowHeight(int height) 0112 { 0113 mRowHeight = height; 0114 } 0115 0116 private: 0117 int mRowHeight; 0118 }; 0119 0120 class GanttHeaderView : public QHeaderView 0121 { 0122 public: 0123 explicit GanttHeaderView(QWidget *parent = nullptr) 0124 : QHeaderView(Qt::Horizontal, parent) 0125 { 0126 setSectionResizeMode(QHeaderView::Stretch); 0127 } 0128 0129 QSize sizeHint() const override 0130 { 0131 QSize s = QHeaderView::sizeHint(); 0132 s.rheight() *= 2; 0133 return s; 0134 } 0135 }; 0136 class GanttItemDelegate : public KGantt::ItemDelegate 0137 { 0138 public: 0139 explicit GanttItemDelegate(QObject *parent) 0140 : KGantt::ItemDelegate(parent) 0141 { 0142 } 0143 0144 private: 0145 void paintGanttItem(QPainter *painter, const KGantt::StyleOptionGanttItem &opt, const QModelIndex &idx) override 0146 { 0147 painter->setRenderHints(QPainter::Antialiasing); 0148 if (!idx.isValid()) { 0149 return; 0150 } 0151 const KGantt::ItemType type = static_cast<KGantt::ItemType>(idx.model()->data(idx, KGantt::ItemTypeRole).toInt()); 0152 0153 const QString txt = idx.model()->data(idx, Qt::DisplayRole).toString(); 0154 QRectF itemRect = opt.itemRect; 0155 QRectF boundingRect = opt.boundingRect; 0156 boundingRect.setY(itemRect.y()); 0157 boundingRect.setHeight(itemRect.height()); 0158 0159 QBrush brush = defaultBrush(type); 0160 if (opt.state & QStyle::State_Selected) { 0161 QLinearGradient selectedGrad(0., 0., 0., QFontMetricsF(painter->font()).height()); 0162 selectedGrad.setColorAt(0., Qt::red); 0163 selectedGrad.setColorAt(1., Qt::darkRed); 0164 0165 brush = QBrush(selectedGrad); 0166 painter->setBrush(brush); 0167 } else { 0168 painter->setBrush(idx.model()->data(idx, Qt::DecorationRole).value<QColor>()); 0169 } 0170 0171 painter->setPen(defaultPen(type)); 0172 painter->setBrushOrigin(itemRect.topLeft()); 0173 0174 switch (type) { 0175 case KGantt::TypeTask: 0176 if (itemRect.isValid()) { 0177 QRectF r = itemRect; 0178 painter->drawRect(r); 0179 bool drawText = true; 0180 Qt::Alignment ta; 0181 switch (opt.displayPosition) { 0182 case KGantt::StyleOptionGanttItem::Left: 0183 ta = Qt::AlignLeft; 0184 break; 0185 case KGantt::StyleOptionGanttItem::Right: 0186 ta = Qt::AlignRight; 0187 break; 0188 case KGantt::StyleOptionGanttItem::Center: 0189 ta = Qt::AlignCenter; 0190 break; 0191 case KGantt::StyleOptionGanttItem::Hidden: 0192 drawText = false; 0193 break; 0194 } 0195 if (drawText) { 0196 painter->drawText(boundingRect, ta, txt); 0197 } 0198 } 0199 break; 0200 default: 0201 KGantt::ItemDelegate::paintGanttItem(painter, opt, idx); 0202 break; 0203 } 0204 } 0205 }; 0206 } 0207 0208 TimelineView::TimelineView(const EventViews::PrefsPtr &preferences, QWidget *parent) 0209 : TimelineView(parent) 0210 { 0211 setPreferences(preferences); 0212 } 0213 0214 TimelineView::TimelineView(QWidget *parent) 0215 : EventView(parent) 0216 , d(new TimelineViewPrivate(this)) 0217 { 0218 auto vbox = new QVBoxLayout(this); 0219 auto splitter = new QSplitter(Qt::Horizontal, this); 0220 d->mLeftView = new QTreeWidget; 0221 d->mLeftView->setColumnCount(1); 0222 d->mLeftView->setHeader(new GanttHeaderView); 0223 d->mLeftView->setHeaderLabel(i18n("Calendar")); 0224 d->mLeftView->setRootIsDecorated(false); 0225 d->mLeftView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0226 d->mLeftView->setUniformRowHeights(true); 0227 0228 d->mGantt = new KGantt::GraphicsView(this); 0229 splitter->addWidget(d->mLeftView); 0230 splitter->addWidget(d->mGantt); 0231 splitter->setSizes({200, 600}); 0232 auto model = new QStandardItemModel(this); 0233 0234 d->mRowController = new RowController; 0235 0236 QStyleOptionViewItem opt; 0237 opt.initFrom(d->mLeftView); 0238 const auto h = d->mLeftView->style()->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), d->mLeftView).height(); 0239 d->mRowController->setRowHeight(h); 0240 0241 d->mRowController->setModel(model); 0242 d->mGantt->setRowController(d->mRowController); 0243 auto grid = new KGantt::DateTimeGrid(); 0244 grid->setScale(KGantt::DateTimeGrid::ScaleHour); 0245 grid->setDayWidth(800); 0246 grid->setRowSeparators(true); 0247 d->mGantt->setGrid(grid); 0248 d->mGantt->setModel(model); 0249 d->mGantt->viewport()->setFixedWidth(8000); 0250 0251 d->mGantt->viewport()->installEventFilter(this); 0252 d->mGantt->setItemDelegate(new GanttItemDelegate(this)); 0253 0254 vbox->addWidget(splitter); 0255 0256 connect(model, &QStandardItemModel::itemChanged, d.get(), &TimelineViewPrivate::itemChanged); 0257 0258 connect(d->mGantt, &KGantt::GraphicsView::activated, d.get(), &TimelineViewPrivate::itemSelected); 0259 d->mGantt->setContextMenuPolicy(Qt::CustomContextMenu); 0260 connect(d->mGantt, &QWidget::customContextMenuRequested, d.get(), &TimelineViewPrivate::contextMenuRequested); 0261 } 0262 0263 TimelineView::~TimelineView() 0264 { 0265 delete d->mRowController; 0266 } 0267 0268 Akonadi::Item::List TimelineView::selectedIncidences() const 0269 { 0270 return d->mSelectedItemList; 0271 } 0272 0273 KCalendarCore::DateList TimelineView::selectedIncidenceDates() const 0274 { 0275 return {}; 0276 } 0277 0278 int TimelineView::currentDateCount() const 0279 { 0280 return 0; 0281 } 0282 0283 void TimelineView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) 0284 { 0285 Q_UNUSED(preferredMonth) 0286 Q_ASSERT_X(start.isValid(), "showDates()", "start date must be valid"); 0287 Q_ASSERT_X(end.isValid(), "showDates()", "end date must be valid"); 0288 0289 qCDebug(CALENDARVIEW_LOG) << "start=" << start << "end=" << end; 0290 0291 d->mStartDate = start; 0292 d->mEndDate = end; 0293 d->mHintDate = QDateTime(); 0294 0295 auto grid = static_cast<KGantt::DateTimeGrid *>(d->mGantt->grid()); 0296 grid->setStartDateTime(QDateTime(start.startOfDay())); 0297 d->mLeftView->clear(); 0298 qDeleteAll(d->mCalendarItemMap); 0299 d->mCalendarItemMap.clear(); 0300 0301 uint index = 0; 0302 for (const auto &calendar : calendars()) { 0303 auto item = new TimelineItem(calendar, index++, static_cast<QStandardItemModel *>(d->mGantt->model()), d->mGantt); 0304 const auto name = Akonadi::CalendarUtils::displayName(calendar->model(), calendar->collection()); 0305 d->mLeftView->addTopLevelItem(new QTreeWidgetItem(QStringList{name})); 0306 const QColor resourceColor = EventViews::resourceColor(calendar->collection(), preferences()); 0307 if (resourceColor.isValid()) { 0308 item->setColor(resourceColor); 0309 } 0310 qCDebug(CALENDARVIEW_LOG) << "Created item " << item << " (" << name << ")" 0311 << "with index " << index - 1 << " from collection " << calendar->collection().id(); 0312 d->mCalendarItemMap.insert(calendar->collection().id(), item); 0313 } 0314 0315 // add incidences 0316 0317 /** 0318 * We remove the model from the view here while we fill it with items, 0319 * because every call to insertIncidence will cause the view to do an expensive 0320 * updateScene() call otherwise. 0321 */ 0322 QAbstractItemModel *ganttModel = d->mGantt->model(); 0323 d->mGantt->setModel(nullptr); 0324 0325 for (const auto &calendar : calendars()) { 0326 for (QDate day = d->mStartDate; day <= d->mEndDate; day = day.addDays(1)) { 0327 const auto events = calendar->events(day, QTimeZone::systemTimeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending); 0328 for (const KCalendarCore::Event::Ptr &event : std::as_const(events)) { 0329 if (event->hasRecurrenceId()) { 0330 continue; 0331 } 0332 Akonadi::Item item = calendar->item(event); 0333 d->insertIncidence(calendar, item, day); 0334 } 0335 } 0336 } 0337 d->mGantt->setModel(ganttModel); 0338 } 0339 0340 void TimelineView::showIncidences(const Akonadi::Item::List &incidenceList, const QDate &date) 0341 { 0342 Q_UNUSED(incidenceList) 0343 Q_UNUSED(date) 0344 } 0345 0346 void TimelineView::updateView() 0347 { 0348 if (d->mStartDate.isValid() && d->mEndDate.isValid()) { 0349 showDates(d->mStartDate, d->mEndDate); 0350 } 0351 } 0352 0353 void TimelineView::changeIncidenceDisplay(const Akonadi::Item &incidence, int mode) 0354 { 0355 const auto cal = calendar3(incidence); 0356 switch (mode) { 0357 case Akonadi::IncidenceChanger::ChangeTypeCreate: 0358 d->insertIncidence(cal, incidence); 0359 break; 0360 case Akonadi::IncidenceChanger::ChangeTypeModify: 0361 d->removeIncidence(incidence); 0362 d->insertIncidence(cal, incidence); 0363 break; 0364 case Akonadi::IncidenceChanger::ChangeTypeDelete: 0365 d->removeIncidence(incidence); 0366 break; 0367 default: 0368 updateView(); 0369 } 0370 } 0371 0372 bool TimelineView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const 0373 { 0374 startDt = QDateTime(d->mHintDate); 0375 endDt = QDateTime(d->mHintDate.addSecs(2 * 60 * 60)); 0376 allDay = false; 0377 return d->mHintDate.isValid(); 0378 } 0379 0380 QDate TimelineView::startDate() const 0381 { 0382 return d->mStartDate; 0383 } 0384 0385 QDate TimelineView::endDate() const 0386 { 0387 return d->mEndDate; 0388 } 0389 0390 bool TimelineView::eventFilter(QObject *object, QEvent *event) 0391 { 0392 if (event->type() == QEvent::ToolTip) { 0393 auto helpEvent = static_cast<QHelpEvent *>(event); 0394 QGraphicsItem *item = d->mGantt->itemAt(helpEvent->pos()); 0395 if (item) { 0396 if (item->type() == KGantt::GraphicsItem::Type) { 0397 auto graphicsItem = static_cast<KGantt::GraphicsItem *>(item); 0398 const QModelIndex itemIndex = graphicsItem->index(); 0399 0400 auto itemModel = qobject_cast<QStandardItemModel *>(d->mGantt->model()); 0401 0402 auto timelineItem = dynamic_cast<TimelineSubItem *>(itemModel->item(itemIndex.row(), itemIndex.column())); 0403 0404 if (timelineItem) { 0405 timelineItem->updateToolTip(); 0406 } 0407 } 0408 } 0409 } 0410 0411 return EventView::eventFilter(object, event); 0412 } 0413 0414 #include "moc_timelineview.cpp"