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"