File indexing completed on 2025-03-09 04:45:05

0001 /*
0002   SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
0003   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0004   SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0005   SPDX-FileContributor: Kevin Krammer <krake@kdab.com>
0006   SPDX-FileContributor: Sergio Martins <sergio@kdab.com>
0007 
0008   Marcus Bains line.
0009   SPDX-FileCopyrightText: 2001 Ali Rahimi <ali@mit.edu>
0010 
0011   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0012 */
0013 #include "agenda.h"
0014 #include "agendaview.h"
0015 #include "prefs.h"
0016 
0017 #include <Akonadi/CalendarUtils>
0018 #include <Akonadi/IncidenceChanger>
0019 #include <CalendarSupport/Utils>
0020 
0021 #include <KCalendarCore/Incidence>
0022 
0023 #include <KCalUtils/RecurrenceActions>
0024 
0025 #include "calendarview_debug.h"
0026 #include <KMessageBox>
0027 
0028 #include <KLocalizedString>
0029 #include <QApplication>
0030 #include <QHash>
0031 #include <QLabel>
0032 #include <QMouseEvent>
0033 #include <QMultiHash>
0034 #include <QPainter>
0035 #include <QPointer>
0036 #include <QResizeEvent>
0037 #include <QScrollBar>
0038 #include <QTimer>
0039 #include <QWheelEvent>
0040 
0041 #include <chrono>
0042 #include <cmath>
0043 
0044 using namespace std::chrono_literals; // for fabs()
0045 
0046 using namespace EventViews;
0047 
0048 ///////////////////////////////////////////////////////////////////////////////
0049 class EventViews::MarcusBainsPrivate
0050 {
0051 public:
0052     MarcusBainsPrivate(EventView *eventView, Agenda *agenda)
0053         : mEventView(eventView)
0054         , mAgenda(agenda)
0055     {
0056     }
0057 
0058     [[nodiscard]] int todayColumn() const;
0059 
0060 public:
0061     EventView *const mEventView;
0062     Agenda *const mAgenda;
0063     QTimer *mTimer = nullptr;
0064     QLabel *mTimeBox = nullptr; // Label showing the current time
0065     QDateTime mOldDateTime;
0066     int mOldTodayCol = -1;
0067 };
0068 
0069 int MarcusBainsPrivate::todayColumn() const
0070 {
0071     const QDate currentDate = QDate::currentDate();
0072 
0073     int col = 0;
0074     const KCalendarCore::DateList dateList = mAgenda->dateList();
0075     for (const QDate &date : dateList) {
0076         if (date == currentDate) {
0077             return QApplication::isRightToLeft() ? mAgenda->columns() - 1 - col : col;
0078         }
0079         ++col;
0080     }
0081 
0082     return -1;
0083 }
0084 
0085 MarcusBains::MarcusBains(EventView *eventView, Agenda *agenda)
0086     : QFrame(agenda)
0087     , d(new MarcusBainsPrivate(eventView, agenda))
0088 {
0089     d->mTimeBox = new QLabel(d->mAgenda);
0090     d->mTimeBox->setAlignment(Qt::AlignRight | Qt::AlignBottom);
0091 
0092     d->mTimer = new QTimer(this);
0093     d->mTimer->setSingleShot(true);
0094     connect(d->mTimer, &QTimer::timeout, this, &MarcusBains::updateLocation);
0095     d->mTimer->start(0);
0096 }
0097 
0098 MarcusBains::~MarcusBains() = default;
0099 
0100 void MarcusBains::updateLocation()
0101 {
0102     updateLocationRecalc();
0103 }
0104 
0105 void MarcusBains::updateLocationRecalc(bool recalculate)
0106 {
0107     const bool showSeconds = d->mEventView->preferences()->marcusBainsShowSeconds();
0108     const QColor color = d->mEventView->preferences()->agendaMarcusBainsLineLineColor();
0109 
0110     const QDateTime now = QDateTime::currentDateTime();
0111     const QTime time = now.time();
0112 
0113     if (now.date() != d->mOldDateTime.date()) {
0114         recalculate = true; // New day
0115     }
0116     const int todayCol = recalculate ? d->todayColumn() : d->mOldTodayCol;
0117 
0118     // Number of minutes since beginning of the day
0119     const int minutes = time.hour() * 60 + time.minute();
0120     const int minutesPerCell = 24 * 60 / d->mAgenda->rows();
0121 
0122     d->mOldDateTime = now;
0123     d->mOldTodayCol = todayCol;
0124 
0125     int y = int(minutes * d->mAgenda->gridSpacingY() / minutesPerCell);
0126     int x = int(d->mAgenda->gridSpacingX() * todayCol);
0127 
0128     bool hideIt = !(d->mEventView->preferences()->marcusBainsEnabled());
0129     if (!isHidden() && (hideIt || (todayCol < 0))) {
0130         hide();
0131         d->mTimeBox->hide();
0132         return;
0133     }
0134 
0135     if (isHidden() && !hideIt) {
0136         show();
0137         d->mTimeBox->show();
0138     }
0139 
0140     /* Line */
0141     // It seems logical to adjust the line width with the label's font weight
0142     const int fw = d->mEventView->preferences()->agendaMarcusBainsLineFont().weight();
0143     setLineWidth(1 + abs(fw - QFont::Normal) / QFont::Light);
0144     setFrameStyle(QFrame::HLine | QFrame::Plain);
0145     QPalette pal = palette();
0146     pal.setColor(QPalette::Window, color); // for Oxygen
0147     pal.setColor(QPalette::WindowText, color); // for Plastique
0148     setPalette(pal);
0149     if (recalculate) {
0150         setFixedSize(int(d->mAgenda->gridSpacingX()), 1);
0151     }
0152     move(x, y);
0153     raise();
0154 
0155     /* Label */
0156     d->mTimeBox->setFont(d->mEventView->preferences()->agendaMarcusBainsLineFont());
0157     QPalette pal1 = d->mTimeBox->palette();
0158     pal1.setColor(QPalette::WindowText, color);
0159     d->mTimeBox->setPalette(pal1);
0160     d->mTimeBox->setText(QLocale::system().toString(time, showSeconds ? QLocale::LongFormat : QLocale::ShortFormat));
0161     d->mTimeBox->adjustSize();
0162     if (y - d->mTimeBox->height() >= 0) {
0163         y -= d->mTimeBox->height();
0164     } else {
0165         y++;
0166     }
0167     if (x - d->mTimeBox->width() + d->mAgenda->gridSpacingX() > 0) {
0168         x += int(d->mAgenda->gridSpacingX() - d->mTimeBox->width() - 1);
0169     } else {
0170         x++;
0171     }
0172     d->mTimeBox->move(x, y);
0173     d->mTimeBox->raise();
0174 
0175     if (showSeconds || recalculate) {
0176         d->mTimer->start(1s);
0177     } else {
0178         d->mTimer->start(1000 * (60 - time.second()));
0179     }
0180 }
0181 
0182 ////////////////////////////////////////////////////////////////////////////
0183 
0184 class EventViews::AgendaPrivate
0185 {
0186 public:
0187     AgendaPrivate(AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive)
0188         : mAgendaView(agendaView)
0189         , mScrollArea(scrollArea)
0190         , mAllDayMode(false)
0191         , mColumns(columns)
0192         , mRows(rows)
0193         , mGridSpacingX(0.0)
0194         , mGridSpacingY(rowSize)
0195         , mDesiredGridSpacingY(rowSize)
0196         , mChanger(nullptr)
0197         , mResizeBorderWidth(0)
0198         , mScrollBorderWidth(0)
0199         , mScrollDelay(0)
0200         , mScrollOffset(0)
0201         , mWorkingHoursEnable(false)
0202         , mHolidayMask(nullptr)
0203         , mWorkingHoursYTop(0)
0204         , mWorkingHoursYBottom(0)
0205         , mHasSelection(false)
0206         , mMarcusBains(nullptr)
0207         , mActionType(Agenda::NOP)
0208         , mItemMoved(false)
0209         , mOldLowerScrollValue(0)
0210         , mOldUpperScrollValue(0)
0211         , mReturnPressed(false)
0212         , mIsInteractive(isInteractive)
0213     {
0214         if (mGridSpacingY < 4 || mGridSpacingY > 30) {
0215             mGridSpacingY = 10;
0216         }
0217     }
0218 
0219 public:
0220     PrefsPtr preferences() const
0221     {
0222         return mAgendaView->preferences();
0223     }
0224 
0225     bool isQueuedForDeletion(const QString &uid) const
0226     {
0227         // if mAgendaItemsById contains it it means that a createAgendaItem() was called
0228         // before the previous agenda items were deleted.
0229         return mItemsQueuedForDeletion.contains(uid) && !mAgendaItemsById.contains(uid);
0230     }
0231 
0232     QMultiHash<QString, AgendaItem::QPtr> mAgendaItemsById; // A QMultiHash because recurring incs
0233                                                             // might have many agenda items
0234     QSet<QString> mItemsQueuedForDeletion;
0235 
0236     AgendaView *mAgendaView = nullptr;
0237     QScrollArea *mScrollArea = nullptr;
0238 
0239     bool mAllDayMode;
0240 
0241     // Number of Columns/Rows of agenda grid
0242     int mColumns;
0243     int mRows;
0244 
0245     // Width and height of agenda cells. mDesiredGridSpacingY is the height
0246     // set in the config. The actual height might be larger since otherwise
0247     // more than 24 hours might be displayed.
0248     double mGridSpacingX;
0249     double mGridSpacingY;
0250     double mDesiredGridSpacingY;
0251 
0252     Akonadi::IncidenceChanger *mChanger = nullptr;
0253 
0254     // size of border, where mouse action will resize the AgendaItem
0255     int mResizeBorderWidth;
0256 
0257     // size of border, where mouse mve will cause a scroll of the agenda
0258     int mScrollBorderWidth;
0259     int mScrollDelay;
0260     int mScrollOffset;
0261 
0262     QTimer mScrollUpTimer;
0263     QTimer mScrollDownTimer;
0264 
0265     // Cells to store Move and Resize coordinates while performing the action
0266     QPoint mStartCell;
0267     QPoint mEndCell;
0268 
0269     // Working Hour coordinates
0270     bool mWorkingHoursEnable;
0271     QList<bool> *mHolidayMask = nullptr;
0272     int mWorkingHoursYTop;
0273     int mWorkingHoursYBottom;
0274 
0275     // Selection
0276     bool mHasSelection;
0277     QPoint mSelectionStartPoint;
0278     QPoint mSelectionStartCell;
0279     QPoint mSelectionEndCell;
0280 
0281     // List of dates to be displayed
0282     KCalendarCore::DateList mSelectedDates;
0283 
0284     // The AgendaItem, which has been right-clicked last
0285     QPointer<AgendaItem> mClickedItem;
0286 
0287     // The AgendaItem, which is being moved/resized
0288     QPointer<AgendaItem> mActionItem;
0289 
0290     // Currently selected item
0291     QPointer<AgendaItem> mSelectedItem;
0292     // Uid of the last selected incidence. Used for reselecting in situations
0293     // where the selected item points to a no longer valid incidence, for
0294     // example during resource reload.
0295     QString mSelectedId;
0296 
0297     // The Marcus Bains Line widget.
0298     MarcusBains *mMarcusBains = nullptr;
0299 
0300     Agenda::MouseActionType mActionType;
0301 
0302     bool mItemMoved;
0303 
0304     // List of all Items contained in agenda
0305     QList<AgendaItem::QPtr> mItems;
0306     QList<AgendaItem::QPtr> mItemsToDelete;
0307 
0308     int mOldLowerScrollValue;
0309     int mOldUpperScrollValue;
0310 
0311     bool mReturnPressed;
0312     bool mIsInteractive;
0313 
0314     MultiViewCalendar::Ptr mCalendar;
0315 };
0316 
0317 /*
0318   Create an agenda widget with rows rows and columns columns.
0319 */
0320 Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive)
0321     : QWidget(scrollArea)
0322     , d(new AgendaPrivate(agendaView, scrollArea, columns, rows, rowSize, isInteractive))
0323 {
0324     setMouseTracking(true);
0325 
0326     init();
0327 }
0328 
0329 /*
0330   Create an agenda widget with columns columns and one row. This is used for
0331   all-day events.
0332 */
0333 Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, bool isInteractive)
0334     : QWidget(scrollArea)
0335     , d(new AgendaPrivate(agendaView, scrollArea, columns, 1, 24, isInteractive))
0336 {
0337     d->mAllDayMode = true;
0338 
0339     init();
0340 }
0341 
0342 Agenda::~Agenda()
0343 {
0344     delete d->mMarcusBains;
0345 }
0346 
0347 KCalendarCore::Incidence::Ptr Agenda::selectedIncidence() const
0348 {
0349     return d->mSelectedItem ? d->mSelectedItem->incidence() : KCalendarCore::Incidence::Ptr();
0350 }
0351 
0352 QDate Agenda::selectedIncidenceDate() const
0353 {
0354     return d->mSelectedItem ? d->mSelectedItem->occurrenceDate() : QDate();
0355 }
0356 
0357 QString Agenda::lastSelectedItemUid() const
0358 {
0359     return d->mSelectedId;
0360 }
0361 
0362 void Agenda::init()
0363 {
0364     setAttribute(Qt::WA_OpaquePaintEvent);
0365 
0366     d->mGridSpacingX = static_cast<double>(d->mScrollArea->width()) / d->mColumns;
0367     d->mDesiredGridSpacingY = d->preferences()->hourSize();
0368     if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) {
0369         d->mDesiredGridSpacingY = 10;
0370     }
0371 
0372     // make sure that there are not more than 24 per day
0373     d->mGridSpacingY = static_cast<double>(height()) / d->mRows;
0374     if (d->mGridSpacingY < d->mDesiredGridSpacingY) {
0375         d->mGridSpacingY = d->mDesiredGridSpacingY;
0376     }
0377 
0378     d->mResizeBorderWidth = 12;
0379     d->mScrollBorderWidth = 12;
0380     d->mScrollDelay = 30;
0381     d->mScrollOffset = 10;
0382 
0383     // Grab key strokes for keyboard navigation of agenda. Seems to have no
0384     // effect. Has to be fixed.
0385     setFocusPolicy(Qt::WheelFocus);
0386 
0387     connect(&d->mScrollUpTimer, &QTimer::timeout, this, &Agenda::scrollUp);
0388     connect(&d->mScrollDownTimer, &QTimer::timeout, this, &Agenda::scrollDown);
0389 
0390     d->mStartCell = QPoint(0, 0);
0391     d->mEndCell = QPoint(0, 0);
0392 
0393     d->mHasSelection = false;
0394     d->mSelectionStartPoint = QPoint(0, 0);
0395     d->mSelectionStartCell = QPoint(0, 0);
0396     d->mSelectionEndCell = QPoint(0, 0);
0397 
0398     d->mOldLowerScrollValue = -1;
0399     d->mOldUpperScrollValue = -1;
0400 
0401     d->mClickedItem = nullptr;
0402 
0403     d->mActionItem = nullptr;
0404     d->mActionType = NOP;
0405     d->mItemMoved = false;
0406 
0407     d->mSelectedItem = nullptr;
0408 
0409     setAcceptDrops(true);
0410     installEventFilter(this);
0411 
0412     /*  resizeContents(int(mGridSpacingX * mColumns), int(mGridSpacingY * mRows)); */
0413 
0414     d->mScrollArea->viewport()->update();
0415     //  mScrollArea->viewport()->setAttribute(Qt::WA_NoSystemBackground, true);
0416     d->mScrollArea->viewport()->setFocusPolicy(Qt::WheelFocus);
0417 
0418     calculateWorkingHours();
0419 
0420     connect(verticalScrollBar(), &QScrollBar::valueChanged, this, qOverload<int>(&Agenda::checkScrollBoundaries));
0421 
0422     // Create the Marcus Bains line.
0423     if (d->mAllDayMode) {
0424         d->mMarcusBains = nullptr;
0425     } else {
0426         d->mMarcusBains = new MarcusBains(d->mAgendaView, this);
0427     }
0428 }
0429 
0430 void Agenda::clear()
0431 {
0432     qDeleteAll(d->mItems);
0433     qDeleteAll(d->mItemsToDelete);
0434     d->mItems.clear();
0435     d->mItemsToDelete.clear();
0436     d->mAgendaItemsById.clear();
0437     d->mItemsQueuedForDeletion.clear();
0438 
0439     d->mSelectedItem = nullptr;
0440 
0441     clearSelection();
0442 }
0443 
0444 void Agenda::clearSelection()
0445 {
0446     d->mHasSelection = false;
0447     d->mActionType = NOP;
0448     update();
0449 }
0450 
0451 void Agenda::marcus_bains()
0452 {
0453     if (d->mMarcusBains) {
0454         d->mMarcusBains->updateLocationRecalc(true);
0455     }
0456 }
0457 
0458 void Agenda::changeColumns(int columns)
0459 {
0460     if (columns == 0) {
0461         qCDebug(CALENDARVIEW_LOG) << "called with argument 0";
0462         return;
0463     }
0464 
0465     clear();
0466     d->mColumns = columns;
0467     //  setMinimumSize(mColumns * 10, mGridSpacingY + 1);
0468     //  init();
0469     //  update();
0470 
0471     QResizeEvent event(size(), size());
0472 
0473     QApplication::sendEvent(this, &event);
0474 }
0475 
0476 int Agenda::columns() const
0477 {
0478     return d->mColumns;
0479 }
0480 
0481 int Agenda::rows() const
0482 {
0483     return d->mRows;
0484 }
0485 
0486 double Agenda::gridSpacingX() const
0487 {
0488     return d->mGridSpacingX;
0489 }
0490 
0491 double Agenda::gridSpacingY() const
0492 {
0493     return d->mGridSpacingY;
0494 }
0495 
0496 /*
0497   This is the eventFilter function, which gets all events from the AgendaItems
0498   contained in the agenda. It has to handle moving and resizing for all items.
0499 */
0500 bool Agenda::eventFilter(QObject *object, QEvent *event)
0501 {
0502     switch (event->type()) {
0503     case QEvent::MouseButtonPress:
0504     case QEvent::MouseButtonDblClick:
0505     case QEvent::MouseButtonRelease:
0506     case QEvent::MouseMove:
0507         return eventFilter_mouse(object, static_cast<QMouseEvent *>(event));
0508 #ifndef QT_NO_WHEELEVENT
0509     case QEvent::Wheel:
0510         return eventFilter_wheel(object, static_cast<QWheelEvent *>(event));
0511 #endif
0512     case QEvent::KeyPress:
0513     case QEvent::KeyRelease:
0514         return eventFilter_key(object, static_cast<QKeyEvent *>(event));
0515 
0516     case QEvent::Leave:
0517 #ifndef QT_NO_CURSOR
0518         if (!d->mActionItem) {
0519             setCursor(Qt::ArrowCursor);
0520         }
0521 #endif
0522 
0523         if (object == this) {
0524             // so timelabels hide the mouse cursor
0525             Q_EMIT leaveAgenda();
0526         }
0527         return true;
0528 
0529     case QEvent::Enter:
0530         Q_EMIT enterAgenda();
0531         return QWidget::eventFilter(object, event);
0532 
0533 #ifndef QT_NO_DRAGANDDROP
0534     case QEvent::DragEnter:
0535     case QEvent::DragMove:
0536     case QEvent::DragLeave:
0537     case QEvent::Drop:
0538         //  case QEvent::DragResponse:
0539         return eventFilter_drag(object, static_cast<QDropEvent *>(event));
0540 #endif
0541 
0542     default:
0543         return QWidget::eventFilter(object, event);
0544     }
0545 }
0546 
0547 bool Agenda::eventFilter_drag(QObject *obj, QDropEvent *de)
0548 {
0549 #ifndef QT_NO_DRAGANDDROP
0550     const QMimeData *md = de->mimeData();
0551 
0552     switch (de->type()) {
0553     case QEvent::DragEnter:
0554     case QEvent::DragMove:
0555         if (!CalendarSupport::canDecode(md)) {
0556             return false;
0557         }
0558 
0559         if (CalendarSupport::mimeDataHasIncidence(md)) {
0560             de->accept();
0561         } else {
0562             de->ignore();
0563         }
0564         return true;
0565         break;
0566     case QEvent::DragLeave:
0567         return false;
0568         break;
0569     case QEvent::Drop: {
0570         if (!CalendarSupport::canDecode(md)) {
0571             return false;
0572         }
0573 
0574         const QList<QUrl> incidenceUrls = CalendarSupport::incidenceItemUrls(md);
0575         const KCalendarCore::Incidence::List incidences = CalendarSupport::incidences(md);
0576 
0577         Q_ASSERT(!incidenceUrls.isEmpty() || !incidences.isEmpty());
0578 
0579         de->setDropAction(Qt::MoveAction);
0580 
0581         QWidget *dropTarget = qobject_cast<QWidget *>(obj);
0582         QPoint dropPosition = de->position().toPoint();
0583         if (dropTarget && dropTarget != this) {
0584             dropPosition = dropTarget->mapTo(this, dropPosition);
0585         }
0586 
0587         const QPoint gridPosition = contentsToGrid(dropPosition);
0588         if (!incidenceUrls.isEmpty()) {
0589             Q_EMIT droppedIncidences(incidenceUrls, gridPosition, d->mAllDayMode);
0590         } else {
0591             Q_EMIT droppedIncidences(incidences, gridPosition, d->mAllDayMode);
0592         }
0593         return true;
0594     }
0595 
0596     case QEvent::DragResponse:
0597     default:
0598         break;
0599     }
0600 #endif
0601     return false;
0602 }
0603 
0604 #ifndef QT_NO_WHEELEVENT
0605 bool Agenda::eventFilter_wheel(QObject *object, QWheelEvent *e)
0606 {
0607     QPoint viewportPos;
0608     bool accepted = false;
0609     const QPoint pos = e->position().toPoint();
0610     if ((e->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier) {
0611         if (object != this) {
0612             viewportPos = ((QWidget *)object)->mapToParent(pos);
0613         } else {
0614             viewportPos = pos;
0615         }
0616         // qCDebug(CALENDARVIEW_LOG) << type:" << e->type() << "angleDelta:" << e->angleDelta();
0617         Q_EMIT zoomView(-e->angleDelta().y(), contentsToGrid(viewportPos), Qt::Horizontal);
0618         accepted = true;
0619     }
0620 
0621     if ((e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) {
0622         if (object != this) {
0623             viewportPos = ((QWidget *)object)->mapToParent(pos);
0624         } else {
0625             viewportPos = pos;
0626         }
0627         Q_EMIT zoomView(-e->angleDelta().y(), contentsToGrid(viewportPos), Qt::Vertical);
0628         Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos)));
0629         accepted = true;
0630     }
0631     if (accepted) {
0632         e->accept();
0633     }
0634     return accepted;
0635 }
0636 
0637 #endif
0638 
0639 bool Agenda::eventFilter_key(QObject *, QKeyEvent *ke)
0640 {
0641     return d->mAgendaView->processKeyEvent(ke);
0642 }
0643 
0644 bool Agenda::eventFilter_mouse(QObject *object, QMouseEvent *me)
0645 {
0646     QPoint viewportPos;
0647     if (object != this) {
0648         viewportPos = static_cast<QWidget *>(object)->mapToParent(me->pos());
0649     } else {
0650         viewportPos = me->pos();
0651     }
0652 
0653     switch (me->type()) {
0654     case QEvent::MouseButtonPress:
0655         if (object != this) {
0656             if (me->button() == Qt::RightButton) {
0657                 d->mClickedItem = qobject_cast<AgendaItem *>(object);
0658                 if (d->mClickedItem) {
0659                     selectItem(d->mClickedItem);
0660                     Q_EMIT showIncidencePopupSignal(d->mClickedItem->incidence(), d->mClickedItem->occurrenceDate());
0661                 }
0662             } else {
0663                 AgendaItem::QPtr item = qobject_cast<AgendaItem *>(object);
0664                 if (item) {
0665                     KCalendarCore::Incidence::Ptr incidence = item->incidence();
0666                     if (incidence->isReadOnly()) {
0667                         d->mActionItem = nullptr;
0668                     } else {
0669                         d->mActionItem = item;
0670                         startItemAction(viewportPos);
0671                     }
0672                     // Warning: do selectItem() as late as possible, since all
0673                     // sorts of things happen during this call. Some can lead to
0674                     // this filter being run again and mActionItem being set to
0675                     // null.
0676                     selectItem(item);
0677                 }
0678             }
0679         } else {
0680             if (me->button() == Qt::RightButton) {
0681                 // if mouse pointer is not in selection, select the cell below the cursor
0682                 QPoint gpos = contentsToGrid(viewportPos);
0683                 if (!ptInSelection(gpos)) {
0684                     d->mSelectionStartCell = gpos;
0685                     d->mSelectionEndCell = gpos;
0686                     d->mHasSelection = true;
0687                     Q_EMIT newStartSelectSignal();
0688                     Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell);
0689                     //          updateContents();
0690                 }
0691                 Q_EMIT showNewEventPopupSignal();
0692             } else {
0693                 selectItem(nullptr);
0694                 d->mActionItem = nullptr;
0695 #ifndef QT_NO_CURSOR
0696                 setCursor(Qt::ArrowCursor);
0697 #endif
0698                 startSelectAction(viewportPos);
0699                 update();
0700             }
0701         }
0702         break;
0703 
0704     case QEvent::MouseButtonRelease:
0705         if (d->mActionItem) {
0706             endItemAction();
0707         } else if (d->mActionType == SELECT) {
0708             endSelectAction(viewportPos);
0709         }
0710         // This nasty gridToContents(contentsToGrid(..)) is needed to
0711         // avoid an offset of a few pixels. Don't ask me why...
0712         Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos)));
0713         break;
0714 
0715     case QEvent::MouseMove: {
0716         if (!d->mIsInteractive) {
0717             return true;
0718         }
0719 
0720         // This nasty gridToContents(contentsToGrid(..)) is needed todos
0721         // avoid an offset of a few pixels. Don't ask me why...
0722         QPoint indicatorPos = gridToContents(contentsToGrid(viewportPos));
0723         if (object != this) {
0724             AgendaItem::QPtr moveItem = qobject_cast<AgendaItem *>(object);
0725             KCalendarCore::Incidence::Ptr incidence = moveItem ? moveItem->incidence() : KCalendarCore::Incidence::Ptr();
0726             if (incidence && !incidence->isReadOnly()) {
0727                 if (!d->mActionItem) {
0728                     setNoActionCursor(moveItem, viewportPos);
0729                 } else {
0730                     performItemAction(viewportPos);
0731 
0732                     if (d->mActionType == MOVE) {
0733                         // show cursor at the current begin of the item
0734                         AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem();
0735                         if (!firstItem) {
0736                             firstItem = d->mActionItem;
0737                         }
0738                         indicatorPos = gridToContents(QPoint(firstItem->cellXLeft(), firstItem->cellYTop()));
0739                     } else if (d->mActionType == RESIZEBOTTOM) {
0740                         // RESIZETOP is handled correctly, only resizebottom works differently
0741                         indicatorPos = gridToContents(QPoint(d->mActionItem->cellXLeft(), d->mActionItem->cellYBottom() + 1));
0742                     }
0743                 } // If we have an action item
0744             } // If move item && !read only
0745         } else {
0746             if (d->mActionType == SELECT) {
0747                 performSelectAction(viewportPos);
0748 
0749                 // show cursor at end of timespan
0750                 if (((d->mStartCell.y() < d->mEndCell.y()) && (d->mEndCell.x() >= d->mStartCell.x())) || (d->mEndCell.x() > d->mStartCell.x())) {
0751                     indicatorPos = gridToContents(QPoint(d->mEndCell.x(), d->mEndCell.y() + 1));
0752                 } else {
0753                     indicatorPos = gridToContents(d->mEndCell);
0754                 }
0755             }
0756         }
0757         Q_EMIT mousePosSignal(indicatorPos);
0758         break;
0759     }
0760 
0761     case QEvent::MouseButtonDblClick:
0762         if (object == this) {
0763             selectItem(nullptr);
0764             Q_EMIT newEventSignal();
0765         } else {
0766             AgendaItem::QPtr doubleClickedItem = qobject_cast<AgendaItem *>(object);
0767             if (doubleClickedItem) {
0768                 selectItem(doubleClickedItem);
0769                 Q_EMIT editIncidenceSignal(doubleClickedItem->incidence());
0770             }
0771         }
0772         break;
0773 
0774     default:
0775         break;
0776     }
0777 
0778     return true;
0779 }
0780 
0781 bool Agenda::ptInSelection(QPoint gpos) const
0782 {
0783     if (!d->mHasSelection) {
0784         return false;
0785     } else if (gpos.x() < d->mSelectionStartCell.x() || gpos.x() > d->mSelectionEndCell.x()) {
0786         return false;
0787     } else if ((gpos.x() == d->mSelectionStartCell.x()) && (gpos.y() < d->mSelectionStartCell.y())) {
0788         return false;
0789     } else if ((gpos.x() == d->mSelectionEndCell.x()) && (gpos.y() > d->mSelectionEndCell.y())) {
0790         return false;
0791     }
0792     return true;
0793 }
0794 
0795 void Agenda::startSelectAction(QPoint viewportPos)
0796 {
0797     Q_EMIT newStartSelectSignal();
0798 
0799     d->mActionType = SELECT;
0800     d->mSelectionStartPoint = viewportPos;
0801     d->mHasSelection = true;
0802 
0803     QPoint pos = viewportPos;
0804     QPoint gpos = contentsToGrid(pos);
0805 
0806     // Store new selection
0807     d->mStartCell = gpos;
0808     d->mEndCell = gpos;
0809     d->mSelectionStartCell = gpos;
0810     d->mSelectionEndCell = gpos;
0811 
0812     //  updateContents();
0813 }
0814 
0815 void Agenda::performSelectAction(QPoint pos)
0816 {
0817     const QPoint gpos = contentsToGrid(pos);
0818 
0819     // Scroll if cursor was moved to upper or lower end of agenda.
0820     if (pos.y() - contentsY() < d->mScrollBorderWidth && contentsY() > 0) {
0821         d->mScrollUpTimer.start(d->mScrollDelay);
0822     } else if (contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y()) {
0823         d->mScrollDownTimer.start(d->mScrollDelay);
0824     } else {
0825         d->mScrollUpTimer.stop();
0826         d->mScrollDownTimer.stop();
0827     }
0828 
0829     if (gpos != d->mEndCell) {
0830         d->mEndCell = gpos;
0831         if (d->mStartCell.x() > d->mEndCell.x() || (d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() > d->mEndCell.y())) {
0832             // backward selection
0833             d->mSelectionStartCell = d->mEndCell;
0834             d->mSelectionEndCell = d->mStartCell;
0835         } else {
0836             d->mSelectionStartCell = d->mStartCell;
0837             d->mSelectionEndCell = d->mEndCell;
0838         }
0839 
0840         update();
0841     }
0842 }
0843 
0844 void Agenda::endSelectAction(const QPoint &currentPos)
0845 {
0846     d->mScrollUpTimer.stop();
0847     d->mScrollDownTimer.stop();
0848 
0849     d->mActionType = NOP;
0850 
0851     Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell);
0852 
0853     if (d->preferences()->selectionStartsEditor()) {
0854         if ((d->mSelectionStartPoint - currentPos).manhattanLength() > QApplication::startDragDistance()) {
0855             Q_EMIT newEventSignal();
0856         }
0857     }
0858 }
0859 
0860 Agenda::MouseActionType Agenda::isInResizeArea(bool horizontal, QPoint pos, const AgendaItem::QPtr &item)
0861 {
0862     if (!item) {
0863         return NOP;
0864     }
0865     QPoint gridpos = contentsToGrid(pos);
0866     QPoint contpos = gridToContents(gridpos + QPoint((QApplication::isRightToLeft()) ? 1 : 0, 0));
0867 
0868     if (horizontal) {
0869         int clXLeft = item->cellXLeft();
0870         int clXRight = item->cellXRight();
0871         if (QApplication::isRightToLeft()) {
0872             int tmp = clXLeft;
0873             clXLeft = clXRight;
0874             clXRight = tmp;
0875         }
0876         int gridDistanceX = int(pos.x() - contpos.x());
0877         if (gridDistanceX < d->mResizeBorderWidth && clXLeft == gridpos.x()) {
0878             if (QApplication::isRightToLeft()) {
0879                 return RESIZERIGHT;
0880             } else {
0881                 return RESIZELEFT;
0882             }
0883         } else if ((d->mGridSpacingX - gridDistanceX) < d->mResizeBorderWidth && clXRight == gridpos.x()) {
0884             if (QApplication::isRightToLeft()) {
0885                 return RESIZELEFT;
0886             } else {
0887                 return RESIZERIGHT;
0888             }
0889         } else {
0890             return MOVE;
0891         }
0892     } else {
0893         int gridDistanceY = int(pos.y() - contpos.y());
0894         if (gridDistanceY < d->mResizeBorderWidth && item->cellYTop() == gridpos.y() && !item->firstMultiItem()) {
0895             return RESIZETOP;
0896         } else if ((d->mGridSpacingY - gridDistanceY) < d->mResizeBorderWidth && item->cellYBottom() == gridpos.y() && !item->lastMultiItem()) {
0897             return RESIZEBOTTOM;
0898         } else {
0899             return MOVE;
0900         }
0901     }
0902 }
0903 
0904 void Agenda::startItemAction(const QPoint &pos)
0905 {
0906     Q_ASSERT(d->mActionItem);
0907 
0908     d->mStartCell = contentsToGrid(pos);
0909     d->mEndCell = d->mStartCell;
0910 
0911     bool noResize = CalendarSupport::hasTodo(d->mActionItem->incidence());
0912 
0913     d->mActionType = MOVE;
0914     if (!noResize) {
0915         d->mActionType = isInResizeArea(d->mAllDayMode, pos, d->mActionItem);
0916     }
0917 
0918     d->mActionItem->startMove();
0919     setActionCursor(d->mActionType, true);
0920 }
0921 
0922 void Agenda::performItemAction(QPoint pos)
0923 {
0924     QPoint gpos = contentsToGrid(pos);
0925 
0926     // Cursor left active agenda area.
0927     // This starts a drag.
0928     if (pos.y() < 0 || pos.y() >= contentsY() + d->mScrollArea->viewport()->height() || pos.x() < 0 || pos.x() >= width()) {
0929         if (d->mActionType == MOVE) {
0930             d->mScrollUpTimer.stop();
0931             d->mScrollDownTimer.stop();
0932             d->mActionItem->resetMove();
0933             placeSubCells(d->mActionItem);
0934             Q_EMIT startDragSignal(d->mActionItem->incidence());
0935 #ifndef QT_NO_CURSOR
0936             setCursor(Qt::ArrowCursor);
0937 #endif
0938             if (d->mChanger) {
0939                 //        d->mChanger->cancelChange(d->mActionItem->incidence());
0940             }
0941             d->mActionItem = nullptr;
0942             d->mActionType = NOP;
0943             d->mItemMoved = false;
0944             return;
0945         }
0946     } else {
0947         setActionCursor(d->mActionType, true);
0948     }
0949 
0950     // Scroll if item was moved to upper or lower end of agenda.
0951     const int distanceToTop = pos.y() - contentsY();
0952     if (distanceToTop < d->mScrollBorderWidth && distanceToTop > -d->mScrollBorderWidth) {
0953         d->mScrollUpTimer.start(d->mScrollDelay);
0954     } else if (contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y()) {
0955         d->mScrollDownTimer.start(d->mScrollDelay);
0956     } else {
0957         d->mScrollUpTimer.stop();
0958         d->mScrollDownTimer.stop();
0959     }
0960 
0961     // Move or resize item if necessary
0962     if (d->mEndCell != gpos) {
0963         if (!d->mItemMoved) {
0964             if (!d->mChanger) {
0965                 KMessageBox::information(this,
0966                                          i18n("Unable to lock item for modification. "
0967                                               "You cannot make any changes."),
0968                                          i18nc("@title:window", "Locking Failed"),
0969                                          QStringLiteral("AgendaLockingFailed"));
0970                 d->mScrollUpTimer.stop();
0971                 d->mScrollDownTimer.stop();
0972                 d->mActionItem->resetMove();
0973                 placeSubCells(d->mActionItem);
0974 #ifndef QT_NO_CURSOR
0975                 setCursor(Qt::ArrowCursor);
0976 #endif
0977                 d->mActionItem = nullptr;
0978                 d->mActionType = NOP;
0979                 d->mItemMoved = false;
0980                 return;
0981             }
0982             d->mItemMoved = true;
0983         }
0984         d->mActionItem->raise();
0985         if (d->mActionType == MOVE) {
0986             // Move all items belonging to a multi item
0987             AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem();
0988             if (!firstItem) {
0989                 firstItem = d->mActionItem;
0990             }
0991             AgendaItem::QPtr lastItem = d->mActionItem->lastMultiItem();
0992             if (!lastItem) {
0993                 lastItem = d->mActionItem;
0994             }
0995             QPoint deltapos = gpos - d->mEndCell;
0996             AgendaItem::QPtr moveItem = firstItem;
0997             while (moveItem) {
0998                 bool changed = false;
0999                 if (deltapos.x() != 0) {
1000                     moveItem->moveRelative(deltapos.x(), 0);
1001                     changed = true;
1002                 }
1003                 // in all day view don't try to move multi items, since there are none
1004                 if (moveItem == firstItem && !d->mAllDayMode) { // is the first item
1005                     int newY = deltapos.y() + moveItem->cellYTop();
1006                     // If event start moved earlier than 0:00, it starts the previous day
1007                     if (newY < 0 && newY > d->mScrollBorderWidth) {
1008                         moveItem->expandTop(-moveItem->cellYTop());
1009                         // prepend a new item at (x-1, rows()+newY to rows())
1010                         AgendaItem::QPtr newFirst = firstItem->prevMoveItem();
1011                         // cell's y values are first and last cell of the bar,
1012                         // so if newY=-1, they need to be the same
1013                         if (newFirst) {
1014                             newFirst->setCellXY(moveItem->cellXLeft() - 1, rows() + newY, rows() - 1);
1015                             d->mItems.append(newFirst);
1016                             moveItem->resize(int(d->mGridSpacingX * newFirst->cellWidth()), int(d->mGridSpacingY * newFirst->cellHeight()));
1017                             QPoint cpos = gridToContents(QPoint(newFirst->cellXLeft(), newFirst->cellYTop()));
1018                             newFirst->setParent(this);
1019                             newFirst->move(cpos.x(), cpos.y());
1020                         } else {
1021                             newFirst = insertItem(moveItem->incidence(),
1022                                                   moveItem->occurrenceDateTime(),
1023                                                   moveItem->cellXLeft() - 1,
1024                                                   rows() + newY,
1025                                                   rows() - 1,
1026                                                   moveItem->itemPos(),
1027                                                   moveItem->itemCount(),
1028                                                   false);
1029                         }
1030                         if (newFirst) {
1031                             newFirst->show();
1032                         }
1033                         moveItem->prependMoveItem(newFirst);
1034                         firstItem = newFirst;
1035                     } else if (newY >= rows()) {
1036                         // If event start is moved past 24:00, it starts the next day
1037                         // erase current item (i.e. remove it from the multiItem list)
1038                         firstItem = moveItem->nextMultiItem();
1039                         moveItem->hide();
1040                         d->mItems.removeAll(moveItem);
1041                         //            removeChild(moveItem);
1042                         d->mActionItem->removeMoveItem(moveItem);
1043                         moveItem = firstItem;
1044                         // adjust next day's item
1045                         if (moveItem) {
1046                             moveItem->expandTop(rows() - newY);
1047                         }
1048                     } else {
1049                         moveItem->expandTop(deltapos.y(), true);
1050                     }
1051                     changed = true;
1052                 }
1053                 if (moveItem && !moveItem->lastMultiItem() && !d->mAllDayMode) { // is the last item
1054                     int newY = deltapos.y() + moveItem->cellYBottom();
1055                     if (newY < 0) {
1056                         // erase current item
1057                         lastItem = moveItem->prevMultiItem();
1058                         moveItem->hide();
1059                         d->mItems.removeAll(moveItem);
1060                         //            removeChild(moveItem);
1061                         moveItem->removeMoveItem(moveItem);
1062                         moveItem = lastItem;
1063                         moveItem->expandBottom(newY + 1);
1064                     } else if (newY >= rows()) {
1065                         moveItem->expandBottom(rows() - moveItem->cellYBottom() - 1);
1066                         // append item at (x+1, 0 to newY-rows())
1067                         AgendaItem::QPtr newLast = lastItem->nextMoveItem();
1068                         if (newLast) {
1069                             newLast->setCellXY(moveItem->cellXLeft() + 1, 0, newY - rows() - 1);
1070                             d->mItems.append(newLast);
1071                             moveItem->resize(int(d->mGridSpacingX * newLast->cellWidth()), int(d->mGridSpacingY * newLast->cellHeight()));
1072                             QPoint cpos = gridToContents(QPoint(newLast->cellXLeft(), newLast->cellYTop()));
1073                             newLast->setParent(this);
1074                             newLast->move(cpos.x(), cpos.y());
1075                         } else {
1076                             newLast = insertItem(moveItem->incidence(),
1077                                                  moveItem->occurrenceDateTime(),
1078                                                  moveItem->cellXLeft() + 1,
1079                                                  0,
1080                                                  newY - rows() - 1,
1081                                                  moveItem->itemPos(),
1082                                                  moveItem->itemCount(),
1083                                                  false);
1084                         }
1085                         moveItem->appendMoveItem(newLast);
1086                         newLast->show();
1087                         lastItem = newLast;
1088                     } else {
1089                         moveItem->expandBottom(deltapos.y());
1090                     }
1091                     changed = true;
1092                 }
1093                 if (changed) {
1094                     adjustItemPosition(moveItem);
1095                 }
1096                 if (moveItem) {
1097                     moveItem = moveItem->nextMultiItem();
1098                 }
1099             }
1100         } else if (d->mActionType == RESIZETOP) {
1101             if (d->mEndCell.y() <= d->mActionItem->cellYBottom()) {
1102                 d->mActionItem->expandTop(gpos.y() - d->mEndCell.y());
1103                 adjustItemPosition(d->mActionItem);
1104             }
1105         } else if (d->mActionType == RESIZEBOTTOM) {
1106             if (d->mEndCell.y() >= d->mActionItem->cellYTop()) {
1107                 d->mActionItem->expandBottom(gpos.y() - d->mEndCell.y());
1108                 adjustItemPosition(d->mActionItem);
1109             }
1110         } else if (d->mActionType == RESIZELEFT) {
1111             if (d->mEndCell.x() <= d->mActionItem->cellXRight()) {
1112                 d->mActionItem->expandLeft(gpos.x() - d->mEndCell.x());
1113                 adjustItemPosition(d->mActionItem);
1114             }
1115         } else if (d->mActionType == RESIZERIGHT) {
1116             if (d->mEndCell.x() >= d->mActionItem->cellXLeft()) {
1117                 d->mActionItem->expandRight(gpos.x() - d->mEndCell.x());
1118                 adjustItemPosition(d->mActionItem);
1119             }
1120         }
1121         d->mEndCell = gpos;
1122     }
1123 }
1124 
1125 void Agenda::endItemAction()
1126 {
1127     // PENDING(AKONADI_PORT) review all this cloning and changer calls
1128     d->mActionType = NOP;
1129     d->mScrollUpTimer.stop();
1130     d->mScrollDownTimer.stop();
1131 #ifndef QT_NO_CURSOR
1132     setCursor(Qt::ArrowCursor);
1133 #endif
1134 
1135     if (!d->mChanger) {
1136         qCCritical(CALENDARVIEW_LOG) << "No IncidenceChanger set";
1137         return;
1138     }
1139 
1140     bool multiModify = false;
1141     // FIXME: do the cloning here...
1142     KCalendarCore::Incidence::Ptr incidence = d->mActionItem->incidence();
1143     const auto recurrenceId = d->mActionItem->occurrenceDateTime();
1144 
1145     d->mItemMoved = d->mItemMoved && !(d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() == d->mEndCell.y());
1146 
1147     if (d->mItemMoved) {
1148         bool addIncidence = false;
1149         bool modify = false;
1150 
1151         // get the main event and not the exception
1152         if (incidence->hasRecurrenceId() && !incidence->recurs()) {
1153             KCalendarCore::Incidence::Ptr mainIncidence;
1154             KCalendarCore::Calendar::Ptr cal = d->mCalendar->findCalendar(incidence)->getCalendar();
1155             if (CalendarSupport::hasEvent(incidence)) {
1156                 mainIncidence = cal->event(incidence->uid());
1157             } else if (CalendarSupport::hasTodo(incidence)) {
1158                 mainIncidence = cal->todo(incidence->uid());
1159             }
1160             incidence = mainIncidence;
1161         }
1162 
1163         Akonadi::Item item = d->mCalendar->item(incidence);
1164         if (incidence && incidence->recurs()) {
1165             const int res = d->mAgendaView->showMoveRecurDialog(incidence, recurrenceId.date());
1166 
1167             if (!d->mActionItem) {
1168                 qCWarning(CALENDARVIEW_LOG) << "mActionItem was reset while the 'move' dialog was active";
1169                 d->mItemMoved = false;
1170                 return;
1171             }
1172 
1173             switch (res) {
1174             case KCalUtils::RecurrenceActions::AllOccurrences: // All occurrences
1175                 // Moving the whole sequence of events is handled by the itemModified below.
1176                 modify = true;
1177                 break;
1178             case KCalUtils::RecurrenceActions::SelectedOccurrence:
1179             case KCalUtils::RecurrenceActions::FutureOccurrences: {
1180                 const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences);
1181                 modify = true;
1182                 multiModify = true;
1183                 d->mChanger->startAtomicOperation(i18n("Dissociate event from recurrence"));
1184                 KCalendarCore::Incidence::Ptr newInc(KCalendarCore::Calendar::createException(incidence, recurrenceId, thisAndFuture));
1185                 if (newInc) {
1186                     newInc->removeCustomProperty("VOLATILE", "AKONADI-ID");
1187                     Akonadi::Item newItem = d->mCalendar->item(newInc);
1188 
1189                     if (newItem.isValid() && newItem != item) { // it is not a new exception
1190                         item = newItem;
1191                         newInc->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(newItem.id()));
1192                         addIncidence = false;
1193                     } else {
1194                         addIncidence = true;
1195                     }
1196                     // don't recreate items, they already have the correct position
1197                     d->mAgendaView->enableAgendaUpdate(false);
1198 
1199                     d->mActionItem->setIncidence(newInc);
1200                     d->mActionItem->dissociateFromMultiItem();
1201 
1202                     d->mAgendaView->enableAgendaUpdate(true);
1203                 } else {
1204                     KMessageBox::error(this,
1205                                        i18n("Unable to add the exception item to the calendar. "
1206                                             "No change will be done."),
1207                                        i18nc("@title:window", "Error Occurred"));
1208                 }
1209                 break;
1210             }
1211             default:
1212                 modify = false;
1213                 d->mActionItem->resetMove();
1214                 placeSubCells(d->mActionItem); // PENDING(AKONADI_PORT) should this be done after
1215                 // the new item was asynchronously added?
1216             }
1217         }
1218 
1219         AgendaItem::QPtr placeItem = d->mActionItem->firstMultiItem();
1220         if (!placeItem) {
1221             placeItem = d->mActionItem;
1222         }
1223 
1224         Akonadi::Collection::Id saveCollection = -1;
1225 
1226         if (item.isValid()) {
1227             saveCollection = item.parentCollection().id();
1228 
1229             // if parent collection is only a search collection for example
1230             if (!(item.parentCollection().rights() & Akonadi::Collection::CanCreateItem)) {
1231                 saveCollection = item.storageCollectionId();
1232             }
1233         }
1234 
1235         if (modify) {
1236             d->mActionItem->endMove();
1237 
1238             AgendaItem::QPtr modif = placeItem;
1239 
1240             QList<AgendaItem::QPtr> oldconflictItems = placeItem->conflictItems();
1241             QList<AgendaItem::QPtr>::iterator it;
1242             for (it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it) {
1243                 if (*it) {
1244                     placeSubCells(*it);
1245                 }
1246             }
1247             while (placeItem) {
1248                 placeSubCells(placeItem);
1249                 placeItem = placeItem->nextMultiItem();
1250             }
1251 
1252             // Notify about change
1253             // The agenda view will apply the changes to the actual Incidence*!
1254             // Bug #228696 don't call endChanged now it's async in Akonadi so it can
1255             // be called before that modified item was done.  And endChange is
1256             // calling when we move item.
1257             // Not perfect need to improve it!
1258             // mChanger->endChange(inc);
1259             if (item.isValid()) {
1260                 d->mAgendaView->updateEventDates(modif, addIncidence, saveCollection);
1261             }
1262             if (addIncidence) {
1263                 // delete the one we dragged, there's a new one being added async, due to dissociation.
1264                 delete modif;
1265             }
1266         } else {
1267             // the item was moved, but not further modified, since it's not recurring
1268             // make sure the view updates anyhow, with the right item
1269             if (item.isValid()) {
1270                 d->mAgendaView->updateEventDates(placeItem, addIncidence, saveCollection);
1271             }
1272         }
1273     }
1274 
1275     d->mActionItem = nullptr;
1276     d->mItemMoved = false;
1277 
1278     if (multiModify) {
1279         d->mChanger->endAtomicOperation();
1280     }
1281 }
1282 
1283 void Agenda::setActionCursor(int actionType, bool acting)
1284 {
1285 #ifndef QT_NO_CURSOR
1286     switch (actionType) {
1287     case MOVE:
1288         if (acting) {
1289             setCursor(Qt::SizeAllCursor);
1290         } else {
1291             setCursor(Qt::ArrowCursor);
1292         }
1293         break;
1294     case RESIZETOP:
1295     case RESIZEBOTTOM:
1296         setCursor(Qt::SizeVerCursor);
1297         break;
1298     case RESIZELEFT:
1299     case RESIZERIGHT:
1300         setCursor(Qt::SizeHorCursor);
1301         break;
1302     default:
1303         setCursor(Qt::ArrowCursor);
1304     }
1305 #endif
1306 }
1307 
1308 void Agenda::setNoActionCursor(const AgendaItem::QPtr &moveItem, QPoint pos)
1309 {
1310     const KCalendarCore::Incidence::Ptr item = moveItem ? moveItem->incidence() : KCalendarCore::Incidence::Ptr();
1311 
1312     const bool noResize = CalendarSupport::hasTodo(item);
1313 
1314     Agenda::MouseActionType resizeType = MOVE;
1315     if (!noResize) {
1316         resizeType = isInResizeArea(d->mAllDayMode, pos, moveItem);
1317     }
1318     setActionCursor(resizeType);
1319 }
1320 
1321 /** calculate the width of the column subcells of the given item
1322  */
1323 double Agenda::calcSubCellWidth(const AgendaItem::QPtr &item)
1324 {
1325     QPoint pt;
1326     QPoint pt1;
1327     pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()));
1328     pt1 = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()) + QPoint(1, 1));
1329     pt1 -= pt;
1330     int maxSubCells = item->subCells();
1331     double newSubCellWidth;
1332     if (d->mAllDayMode) {
1333         newSubCellWidth = static_cast<double>(pt1.y()) / maxSubCells;
1334     } else {
1335         newSubCellWidth = static_cast<double>(pt1.x()) / maxSubCells;
1336     }
1337     return newSubCellWidth;
1338 }
1339 
1340 void Agenda::adjustItemPosition(const AgendaItem::QPtr &item)
1341 {
1342     if (!item) {
1343         return;
1344     }
1345     item->resize(int(d->mGridSpacingX * item->cellWidth()), int(d->mGridSpacingY * item->cellHeight()));
1346     int clXLeft = item->cellXLeft();
1347     if (QApplication::isRightToLeft()) {
1348         clXLeft = item->cellXRight() + 1;
1349     }
1350     QPoint cpos = gridToContents(QPoint(clXLeft, item->cellYTop()));
1351     item->move(cpos.x(), cpos.y());
1352 }
1353 
1354 void Agenda::placeAgendaItem(const AgendaItem::QPtr &item, double subCellWidth)
1355 {
1356     // "left" upper corner, no subcells yet, RTL layouts have right/left
1357     // switched, widths are negative then
1358     QPoint pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()));
1359     // right lower corner
1360     QPoint pt1 = gridToContents(QPoint(item->cellXLeft() + item->cellWidth(), item->cellYBottom() + 1));
1361 
1362     double subCellPos = item->subCell() * subCellWidth;
1363 
1364     // we need to add 0.01 to make sure we don't loose one pixed due to numerics
1365     // (i.e. if it would be x.9998, we want the integer, not rounded down.
1366     double delta = 0.01;
1367     if (subCellWidth < 0) {
1368         delta = -delta;
1369     }
1370     int height;
1371     int width;
1372     int xpos;
1373     int ypos;
1374     if (d->mAllDayMode) {
1375         width = pt1.x() - pt.x();
1376         height = int(subCellPos + subCellWidth + delta) - int(subCellPos);
1377         xpos = pt.x();
1378         ypos = pt.y() + int(subCellPos);
1379     } else {
1380         width = int(subCellPos + subCellWidth + delta) - int(subCellPos);
1381         height = pt1.y() - pt.y();
1382         xpos = pt.x() + int(subCellPos);
1383         ypos = pt.y();
1384     }
1385     if (QApplication::isRightToLeft()) { // RTL language/layout
1386         xpos += width;
1387         width = -width;
1388     }
1389     if (height < 0) { // BTT (bottom-to-top) layout ?!?
1390         ypos += height;
1391         height = -height;
1392     }
1393     item->resize(width, height);
1394     item->move(xpos, ypos);
1395 }
1396 
1397 /*
1398   Place item in cell and take care that multiple items using the same cell do
1399   not overlap. This method is not yet optimal. It doesn't use the maximum space
1400   it can get in all cases.
1401   At the moment the method has a bug: When an item is placed only the sub cell
1402   widths of the items are changed, which are within the Y region the item to
1403   place spans. When the sub cell width change of one of this items affects a
1404   cell, where other items are, which do not overlap in Y with the item to
1405   place, the display gets corrupted, although the corruption looks quite nice.
1406 */
1407 void Agenda::placeSubCells(const AgendaItem::QPtr &placeItem)
1408 {
1409 #if 0
1410     qCDebug(CALENDARVIEW_LOG);
1411     if (placeItem) {
1412         KCalendarCore::Incidence::Ptr event = placeItem->incidence();
1413         if (!event) {
1414             qCDebug(CALENDARVIEW_LOG) << "  event is 0";
1415         } else {
1416             qCDebug(CALENDARVIEW_LOG) << "  event:" << event->summary();
1417         }
1418     } else {
1419         qCDebug(CALENDARVIEW_LOG) << "  placeItem is 0";
1420     }
1421     qCDebug(CALENDARVIEW_LOG) << "Agenda::placeSubCells()...";
1422 #endif
1423 
1424     QList<CalendarSupport::CellItem *> cells;
1425     for (CalendarSupport::CellItem *item : std::as_const(d->mItems)) {
1426         if (item) {
1427             cells.append(item);
1428         }
1429     }
1430 
1431     QList<CalendarSupport::CellItem *> items = CalendarSupport::CellItem::placeItem(cells, placeItem);
1432 
1433     placeItem->setConflictItems(QList<AgendaItem::QPtr>());
1434     double newSubCellWidth = calcSubCellWidth(placeItem);
1435     QList<CalendarSupport::CellItem *>::iterator it;
1436     for (it = items.begin(); it != items.end(); ++it) {
1437         if (*it) {
1438             AgendaItem::QPtr item = static_cast<AgendaItem *>(*it);
1439             placeAgendaItem(item, newSubCellWidth);
1440             item->addConflictItem(placeItem);
1441             placeItem->addConflictItem(item);
1442         }
1443     }
1444     if (items.isEmpty()) {
1445         placeAgendaItem(placeItem, newSubCellWidth);
1446     }
1447     placeItem->update();
1448 }
1449 
1450 int Agenda::columnWidth(int column) const
1451 {
1452     int start = gridToContents(QPoint(column, 0)).x();
1453     if (QApplication::isRightToLeft()) {
1454         column--;
1455     } else {
1456         column++;
1457     }
1458     int end = gridToContents(QPoint(column, 0)).x();
1459     return end - start;
1460 }
1461 
1462 void Agenda::paintEvent(QPaintEvent *)
1463 {
1464     QPainter p(this);
1465     drawContents(&p, 0, -y(), d->mGridSpacingX * d->mColumns, d->mGridSpacingY * d->mRows + y());
1466 }
1467 
1468 /*
1469   Draw grid in the background of the agenda.
1470 */
1471 void Agenda::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
1472 {
1473     QPixmap db(cw, ch);
1474     db.fill(); // We don't want to see leftovers from previous paints
1475     QPainter dbp(&db);
1476     // TODO: CHECK THIS
1477     //  if (! d->preferences()->agendaGridBackgroundImage().isEmpty()) {
1478     //    QPixmap bgImage(d->preferences()->agendaGridBackgroundImage());
1479     //    dbp.drawPixmap(0, 0, cw, ch, bgImage); FIXME
1480     //  }
1481     if (!d->preferences()->useSystemColor()) {
1482         dbp.fillRect(0, 0, cw, ch, d->preferences()->agendaGridBackgroundColor());
1483     } else {
1484         dbp.fillRect(0, 0, cw, ch, palette().color(QPalette::Window));
1485     }
1486 
1487     dbp.translate(-cx, -cy);
1488 
1489     double lGridSpacingY = d->mGridSpacingY * 2;
1490 
1491     // If work day, use work color
1492     // If busy day, use busy color
1493     // if work and busy day, mix both, and busy color has alpha
1494 
1495     const QList<bool> busyDayMask = d->mAgendaView->busyDayMask();
1496 
1497     // Highlight working hours
1498     if (d->mWorkingHoursEnable && d->mHolidayMask) {
1499         QColor workColor;
1500         if (!d->preferences()->useSystemColor()) {
1501             workColor = d->preferences()->workingHoursColor();
1502         } else {
1503             workColor = palette().color(QPalette::Base);
1504         }
1505 
1506         QPoint pt1(cx, d->mWorkingHoursYTop);
1507         QPoint pt2(cx + cw, d->mWorkingHoursYBottom);
1508         if (pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) {
1509             int gxStart = contentsToGrid(pt1).x();
1510             int gxEnd = contentsToGrid(pt2).x();
1511             // correct start/end for rtl layouts
1512             if (gxStart > gxEnd) {
1513                 int tmp = gxStart;
1514                 gxStart = gxEnd;
1515                 gxEnd = tmp;
1516             }
1517             int xoffset = (QApplication::isRightToLeft() ? 1 : 0);
1518             while (gxStart <= gxEnd) {
1519                 int xStart = gridToContents(QPoint(gxStart + xoffset, 0)).x();
1520                 int xWidth = columnWidth(gxStart) + 1;
1521 
1522                 if (pt2.y() < pt1.y()) {
1523                     // overnight working hours
1524                     if (((gxStart == 0) && !d->mHolidayMask->at(d->mHolidayMask->count() - 1))
1525                         || ((gxStart > 0) && (gxStart < int(d->mHolidayMask->count())) && (!d->mHolidayMask->at(gxStart - 1)))) {
1526                         if (pt2.y() > cy) {
1527                             dbp.fillRect(xStart, cy, xWidth, pt2.y() - cy + 1, workColor);
1528                         }
1529                     }
1530                     if ((gxStart < int(d->mHolidayMask->count() - 1)) && (!d->mHolidayMask->at(gxStart))) {
1531                         if (pt1.y() < cy + ch - 1) {
1532                             dbp.fillRect(xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1, workColor);
1533                         }
1534                     }
1535                 } else {
1536                     // last entry in holiday mask denotes the previous day not visible
1537                     // (needed for overnight shifts)
1538                     if (gxStart < int(d->mHolidayMask->count() - 1) && !d->mHolidayMask->at(gxStart)) {
1539                         dbp.fillRect(xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1, workColor);
1540                     }
1541                 }
1542                 ++gxStart;
1543             }
1544         }
1545     }
1546 
1547     // busy days
1548     if (d->preferences()->colorAgendaBusyDays() && !d->mAllDayMode) {
1549         for (int i = 0; i < busyDayMask.count(); ++i) {
1550             if (busyDayMask[i]) {
1551                 const QPoint pt1(cx + d->mGridSpacingX * i, 0);
1552                 // const QPoint pt2(cx + mGridSpacingX * (i+1), ch);
1553                 QColor busyColor;
1554                 if (!d->preferences()->useSystemColor()) {
1555                     busyColor = d->preferences()->viewBgBusyColor();
1556                 } else {
1557                     busyColor = palette().color(QPalette::Window);
1558                     if ((busyColor.blue() + busyColor.red() + busyColor.green()) > (256 / 2 * 3)) {
1559                         // dark
1560                         busyColor = busyColor.lighter(140);
1561                     } else {
1562                         // light
1563                         busyColor = busyColor.darker(140);
1564                     }
1565                 }
1566                 busyColor.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA);
1567                 dbp.fillRect(pt1.x(), pt1.y(), d->mGridSpacingX, cy + ch, busyColor);
1568             }
1569         }
1570     }
1571 
1572     // draw selection
1573     if (d->mHasSelection && d->mAgendaView->dateRangeSelectionEnabled()) {
1574         QPoint pt;
1575         QPoint pt1;
1576         QColor highlightColor;
1577         if (!d->preferences()->useSystemColor()) {
1578             highlightColor = d->preferences()->agendaGridHighlightColor();
1579         } else {
1580             highlightColor = palette().color(QPalette::Highlight);
1581         }
1582 
1583         if (d->mSelectionEndCell.x() > d->mSelectionStartCell.x()) { // multi day selection
1584             // draw start day
1585             pt = gridToContents(d->mSelectionStartCell);
1586             pt1 = gridToContents(QPoint(d->mSelectionStartCell.x() + 1, d->mRows + 1));
1587             dbp.fillRect(QRect(pt, pt1), highlightColor);
1588             // draw all other days between the start day and the day of the selection end
1589             for (int c = d->mSelectionStartCell.x() + 1; c < d->mSelectionEndCell.x(); ++c) {
1590                 pt = gridToContents(QPoint(c, 0));
1591                 pt1 = gridToContents(QPoint(c + 1, d->mRows + 1));
1592                 dbp.fillRect(QRect(pt, pt1), highlightColor);
1593             }
1594             // draw end day
1595             pt = gridToContents(QPoint(d->mSelectionEndCell.x(), 0));
1596             pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1));
1597             dbp.fillRect(QRect(pt, pt1), highlightColor);
1598         } else { // single day selection
1599             pt = gridToContents(d->mSelectionStartCell);
1600             pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1));
1601             dbp.fillRect(QRect(pt, pt1), highlightColor);
1602         }
1603     }
1604 
1605     // Compute the grid line color for both the hour and half-hour
1606     // The grid colors are always computed as a function of the palette's windowText color.
1607     QPen hourPen;
1608     QPen halfHourPen;
1609 
1610     const QColor windowTextColor = palette().color(QPalette::WindowText);
1611     if (windowTextColor.red() + windowTextColor.green() + windowTextColor.blue() < (256 / 2 * 3)) {
1612         // dark grey line
1613         hourPen = windowTextColor.lighter(200);
1614         halfHourPen = windowTextColor.lighter(500);
1615     } else {
1616         // light grey line
1617         hourPen = windowTextColor.darker(150);
1618         halfHourPen = windowTextColor.darker(200);
1619     }
1620 
1621     dbp.setPen(hourPen);
1622 
1623     // Draw vertical lines of grid, start with the last line not yet visible
1624     double x = (int(cx / d->mGridSpacingX)) * d->mGridSpacingX;
1625     while (x < cx + cw) {
1626         dbp.drawLine(int(x), cy, int(x), cy + ch);
1627         x += d->mGridSpacingX;
1628     }
1629 
1630     // Draw horizontal lines of grid
1631     double y = (int(cy / (2 * lGridSpacingY))) * 2 * lGridSpacingY;
1632     while (y < cy + ch) {
1633         dbp.drawLine(cx, int(y), cx + cw, int(y));
1634         y += 2 * lGridSpacingY;
1635     }
1636     y = (2 * int(cy / (2 * lGridSpacingY)) + 1) * lGridSpacingY;
1637     dbp.setPen(halfHourPen);
1638     while (y < cy + ch) {
1639         dbp.drawLine(cx, int(y), cx + cw, int(y));
1640         y += 2 * lGridSpacingY;
1641     }
1642     p->drawPixmap(cx, cy, db);
1643 }
1644 
1645 /*
1646   Convert srcollview contents coordinates to agenda grid coordinates.
1647 */
1648 QPoint Agenda::contentsToGrid(QPoint pos) const
1649 {
1650     int gx = int(QApplication::isRightToLeft() ? d->mColumns - pos.x() / d->mGridSpacingX : pos.x() / d->mGridSpacingX);
1651     int gy = int(pos.y() / d->mGridSpacingY);
1652     return {gx, gy};
1653 }
1654 
1655 /*
1656   Convert agenda grid coordinates to scrollview contents coordinates.
1657 */
1658 QPoint Agenda::gridToContents(QPoint gpos) const
1659 {
1660     int x = int(QApplication::isRightToLeft() ? (d->mColumns - gpos.x()) * d->mGridSpacingX : gpos.x() * d->mGridSpacingX);
1661     int y = int(gpos.y() * d->mGridSpacingY);
1662     return {x, y};
1663 }
1664 
1665 /*
1666   Return Y coordinate corresponding to time. Coordinates are rounded to
1667   fit into the grid.
1668 */
1669 int Agenda::timeToY(QTime time) const
1670 {
1671     int minutesPerCell = 24 * 60 / d->mRows;
1672     int timeMinutes = time.hour() * 60 + time.minute();
1673     int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell;
1674 
1675     return Y;
1676 }
1677 
1678 /*
1679   Return time corresponding to cell y coordinate. Coordinates are rounded to
1680   fit into the grid.
1681 */
1682 QTime Agenda::gyToTime(int gy) const
1683 {
1684     int secondsPerCell = 24 * 60 * 60 / d->mRows;
1685     int timeSeconds = secondsPerCell * gy;
1686 
1687     QTime time(0, 0, 0);
1688     if (timeSeconds < 24 * 60 * 60) {
1689         time = time.addSecs(timeSeconds);
1690     } else {
1691         time.setHMS(23, 59, 59);
1692     }
1693     return time;
1694 }
1695 
1696 QList<int> Agenda::minContentsY() const
1697 {
1698     QList<int> minArray;
1699     minArray.fill(timeToY(QTime(23, 59)), d->mSelectedDates.count());
1700     for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) {
1701         if (item) {
1702             int ymin = item->cellYTop();
1703             int index = item->cellXLeft();
1704             if (index >= 0 && index < (int)(d->mSelectedDates.count())) {
1705                 if (ymin < minArray[index] && !d->mItemsToDelete.contains(item)) {
1706                     minArray[index] = ymin;
1707                 }
1708             }
1709         }
1710     }
1711 
1712     return minArray;
1713 }
1714 
1715 QList<int> Agenda::maxContentsY() const
1716 {
1717     QList<int> maxArray;
1718     maxArray.fill(timeToY(QTime(0, 0)), d->mSelectedDates.count());
1719     for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) {
1720         if (item) {
1721             int ymax = item->cellYBottom();
1722 
1723             int index = item->cellXLeft();
1724             if (index >= 0 && index < (int)(d->mSelectedDates.count())) {
1725                 if (ymax > maxArray[index] && !d->mItemsToDelete.contains(item)) {
1726                     maxArray[index] = ymax;
1727                 }
1728             }
1729         }
1730     }
1731 
1732     return maxArray;
1733 }
1734 
1735 void Agenda::setStartTime(QTime startHour)
1736 {
1737     const double startPos = (startHour.hour() / 24. + startHour.minute() / 1440. + startHour.second() / 86400.) * d->mRows * gridSpacingY();
1738 
1739     verticalScrollBar()->setValue(startPos);
1740 }
1741 
1742 /*
1743   Insert AgendaItem into agenda.
1744 */
1745 AgendaItem::QPtr Agenda::insertItem(const KCalendarCore::Incidence::Ptr &incidence,
1746                                     const QDateTime &recurrenceId,
1747                                     int X,
1748                                     int YTop,
1749                                     int YBottom,
1750                                     int itemPos,
1751                                     int itemCount,
1752                                     bool isSelected)
1753 {
1754     if (d->mAllDayMode) {
1755         qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal.";
1756         return nullptr;
1757     }
1758 
1759     d->mActionType = NOP;
1760 
1761     AgendaItem::QPtr agendaItem = createAgendaItem(incidence, itemPos, itemCount, recurrenceId, isSelected);
1762     if (!agendaItem) {
1763         return {};
1764     }
1765 
1766     if (YTop >= d->mRows) {
1767         YBottom -= YTop - (d->mRows - 1); // Slide the item up into view.
1768         YTop = d->mRows - 1;
1769     }
1770     if (YBottom <= YTop) {
1771         qCDebug(CALENDARVIEW_LOG) << "Text:" << agendaItem->text() << " YSize<0";
1772         YBottom = YTop;
1773     }
1774 
1775     agendaItem->resize(int((X + 1) * d->mGridSpacingX) - int(X * d->mGridSpacingX), int(YTop * d->mGridSpacingY) - int((YBottom + 1) * d->mGridSpacingY));
1776     agendaItem->setCellXY(X, YTop, YBottom);
1777     agendaItem->setCellXRight(X);
1778     agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence));
1779     agendaItem->installEventFilter(this);
1780 
1781     agendaItem->move(int(X * d->mGridSpacingX), int(YTop * d->mGridSpacingY));
1782 
1783     d->mItems.append(agendaItem);
1784 
1785     placeSubCells(agendaItem);
1786 
1787     agendaItem->show();
1788 
1789     marcus_bains();
1790 
1791     return agendaItem;
1792 }
1793 
1794 /*
1795   Insert all-day AgendaItem into agenda.
1796 */
1797 AgendaItem::QPtr Agenda::insertAllDayItem(const KCalendarCore::Incidence::Ptr &incidence, const QDateTime &recurrenceId, int XBegin, int XEnd, bool isSelected)
1798 {
1799     if (!d->mAllDayMode) {
1800         qCCritical(CALENDARVIEW_LOG) << "using this in non all-day mode is illegal.";
1801         return nullptr;
1802     }
1803 
1804     d->mActionType = NOP;
1805 
1806     AgendaItem::QPtr agendaItem = createAgendaItem(incidence, 1, 1, recurrenceId, isSelected);
1807     if (!agendaItem) {
1808         return {};
1809     }
1810 
1811     agendaItem->setCellXY(XBegin, 0, 0);
1812     agendaItem->setCellXRight(XEnd);
1813 
1814     const double startIt = d->mGridSpacingX * (agendaItem->cellXLeft());
1815     const double endIt = d->mGridSpacingX * (agendaItem->cellWidth() + agendaItem->cellXLeft());
1816 
1817     agendaItem->resize(int(endIt) - int(startIt), int(d->mGridSpacingY));
1818 
1819     agendaItem->installEventFilter(this);
1820     agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence));
1821     agendaItem->move(int(XBegin * d->mGridSpacingX), 0);
1822     d->mItems.append(agendaItem);
1823 
1824     placeSubCells(agendaItem);
1825 
1826     agendaItem->show();
1827 
1828     return agendaItem;
1829 }
1830 
1831 AgendaItem::QPtr
1832 Agenda::createAgendaItem(const KCalendarCore::Incidence::Ptr &incidence, int itemPos, int itemCount, const QDateTime &recurrenceId, bool isSelected)
1833 {
1834     if (!incidence) {
1835         qCWarning(CALENDARVIEW_LOG) << "Agenda::createAgendaItem() item is invalid.";
1836         return {};
1837     }
1838 
1839     AgendaItem::QPtr agendaItem = new AgendaItem(d->mAgendaView, d->mCalendar, incidence, itemPos, itemCount, recurrenceId, isSelected, this);
1840 
1841     connect(agendaItem.data(), &AgendaItem::removeAgendaItem, this, &Agenda::removeAgendaItem);
1842     connect(agendaItem.data(), &AgendaItem::showAgendaItem, this, &Agenda::showAgendaItem);
1843 
1844     d->mAgendaItemsById.insert(incidence->uid(), agendaItem);
1845 
1846     return agendaItem;
1847 }
1848 
1849 void Agenda::insertMultiItem(const KCalendarCore::Incidence::Ptr &event,
1850                              const QDateTime &recurrenceId,
1851                              int XBegin,
1852                              int XEnd,
1853                              int YTop,
1854                              int YBottom,
1855                              bool isSelected)
1856 {
1857     KCalendarCore::Event::Ptr ev = CalendarSupport::event(event);
1858     Q_ASSERT(ev);
1859     if (d->mAllDayMode) {
1860         qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal.";
1861         return;
1862     }
1863 
1864     d->mActionType = NOP;
1865     int cellX;
1866     int cellYTop;
1867     int cellYBottom;
1868     QString newtext;
1869     int width = XEnd - XBegin + 1;
1870     int count = 0;
1871     AgendaItem::QPtr current = nullptr;
1872     QList<AgendaItem::QPtr> multiItems;
1873     int visibleCount = d->mSelectedDates.first().daysTo(d->mSelectedDates.last());
1874     for (cellX = XBegin; cellX <= XEnd; ++cellX) {
1875         ++count;
1876         // Only add the items that are visible.
1877         if (cellX >= 0 && cellX <= visibleCount) {
1878             if (cellX == XBegin) {
1879                 cellYTop = YTop;
1880             } else {
1881                 cellYTop = 0;
1882             }
1883             if (cellX == XEnd) {
1884                 cellYBottom = YBottom;
1885             } else {
1886                 cellYBottom = rows() - 1;
1887             }
1888             newtext = QStringLiteral("(%1/%2): ").arg(count).arg(width);
1889             newtext.append(ev->summary());
1890 
1891             current = insertItem(event, recurrenceId, cellX, cellYTop, cellYBottom, count, width, isSelected);
1892             Q_ASSERT(current);
1893             current->setText(newtext);
1894             multiItems.append(current);
1895         }
1896     }
1897 
1898     QList<AgendaItem::QPtr>::iterator it = multiItems.begin();
1899     QList<AgendaItem::QPtr>::iterator e = multiItems.end();
1900 
1901     if (it != e) { // .first asserts if the list is empty
1902         AgendaItem::QPtr first = multiItems.first();
1903         AgendaItem::QPtr last = multiItems.last();
1904         AgendaItem::QPtr prev = nullptr;
1905         AgendaItem::QPtr next = nullptr;
1906 
1907         while (it != e) {
1908             AgendaItem::QPtr item = *it;
1909             ++it;
1910             next = (it == e) ? nullptr : (*it);
1911             if (item) {
1912                 item->setMultiItem((item == first) ? nullptr : first, prev, next, (item == last) ? nullptr : last);
1913             }
1914             prev = item;
1915         }
1916     }
1917 
1918     marcus_bains();
1919 }
1920 
1921 void Agenda::removeIncidence(const KCalendarCore::Incidence::Ptr &incidence)
1922 {
1923     if (!incidence) {
1924         qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() incidence is invalid" << incidence->uid();
1925         return;
1926     }
1927 
1928     if (d->isQueuedForDeletion(incidence->uid())) {
1929         return; // It's already queued for deletion
1930     }
1931 
1932     const AgendaItem::List agendaItems = d->mAgendaItemsById.values(incidence->uid());
1933     if (agendaItems.isEmpty()) {
1934         // We're not displaying such item
1935         // qCDebug(CALENDARVIEW_LOG) << "Ignoring";
1936         return;
1937     }
1938     for (const AgendaItem::QPtr &agendaItem : agendaItems) {
1939         if (agendaItem) {
1940             if (incidence->instanceIdentifier() != agendaItem->incidence()->instanceIdentifier()) {
1941                 continue;
1942             }
1943             if (!removeAgendaItem(agendaItem)) {
1944                 qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() Failed to remove " << incidence->uid();
1945             }
1946         }
1947     }
1948 }
1949 
1950 void Agenda::showAgendaItem(const AgendaItem::QPtr &agendaItem)
1951 {
1952     if (!agendaItem) {
1953         qCCritical(CALENDARVIEW_LOG) << "Show what?";
1954         return;
1955     }
1956 
1957     agendaItem->hide();
1958 
1959     agendaItem->setParent(this);
1960 
1961     if (!d->mItems.contains(agendaItem)) {
1962         d->mItems.append(agendaItem);
1963     }
1964     placeSubCells(agendaItem);
1965 
1966     agendaItem->show();
1967 }
1968 
1969 bool Agenda::removeAgendaItem(const AgendaItem::QPtr &agendaItem)
1970 {
1971     Q_ASSERT(agendaItem);
1972     // we found the item. Let's remove it and update the conflicts
1973     QList<AgendaItem::QPtr> conflictItems = agendaItem->conflictItems();
1974     // removeChild(thisItem);
1975 
1976     bool taken = d->mItems.removeAll(agendaItem) > 0;
1977     d->mAgendaItemsById.remove(agendaItem->incidence()->uid(), agendaItem);
1978 
1979     QList<AgendaItem::QPtr>::iterator it;
1980     for (it = conflictItems.begin(); it != conflictItems.end(); ++it) {
1981         if (*it) {
1982             (*it)->setSubCells((*it)->subCells() - 1);
1983         }
1984     }
1985 
1986     for (it = conflictItems.begin(); it != conflictItems.end(); ++it) {
1987         // the item itself is also in its own conflictItems list!
1988         if (*it && *it != agendaItem) {
1989             placeSubCells(*it);
1990         }
1991     }
1992     d->mItemsToDelete.append(agendaItem);
1993     d->mItemsQueuedForDeletion.insert(agendaItem->incidence()->uid());
1994     agendaItem->setVisible(false);
1995     QTimer::singleShot(0, this, &Agenda::deleteItemsToDelete);
1996     return taken;
1997 }
1998 
1999 void Agenda::deleteItemsToDelete()
2000 {
2001     qDeleteAll(d->mItemsToDelete);
2002     d->mItemsToDelete.clear();
2003     d->mItemsQueuedForDeletion.clear();
2004 }
2005 
2006 /*QSizePolicy Agenda::sizePolicy() const
2007 {
2008   // Thought this would make the all-day event agenda minimum size and the
2009   // normal agenda take the remaining space. But it doesn't work. The QSplitter
2010   // don't seem to think that an Expanding widget needs more space than a
2011   // Preferred one.
2012   // But it doesn't hurt, so it stays.
2013   if (mAllDayMode) {
2014     return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
2015   } else {
2016   return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
2017   }
2018 }*/
2019 
2020 /*
2021   Overridden from QScrollView to provide proper resizing of AgendaItems.
2022 */
2023 void Agenda::resizeEvent(QResizeEvent *ev)
2024 {
2025     QSize newSize(ev->size());
2026 
2027     if (d->mAllDayMode) {
2028         d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns;
2029         d->mGridSpacingY = newSize.height();
2030     } else {
2031         d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns;
2032         // make sure that there are not more than 24 per day
2033         d->mGridSpacingY = static_cast<double>(newSize.height()) / d->mRows;
2034         if (d->mGridSpacingY < d->mDesiredGridSpacingY) {
2035             d->mGridSpacingY = d->mDesiredGridSpacingY;
2036         }
2037     }
2038     calculateWorkingHours();
2039 
2040     QTimer::singleShot(0, this, &Agenda::resizeAllContents);
2041     Q_EMIT gridSpacingYChanged(d->mGridSpacingY * 4);
2042 
2043     QWidget::resizeEvent(ev);
2044     updateGeometry();
2045 }
2046 
2047 void Agenda::resizeAllContents()
2048 {
2049     double subCellWidth;
2050     for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) {
2051         if (item) {
2052             subCellWidth = calcSubCellWidth(item);
2053             placeAgendaItem(item, subCellWidth);
2054         }
2055     }
2056     /*
2057     if (d->mAllDayMode) {
2058         foreach (const AgendaItem::QPtr &item, d->mItems) {
2059             if (item) {
2060                 subCellWidth = calcSubCellWidth(item);
2061                 placeAgendaItem(item, subCellWidth);
2062             }
2063         }
2064     } else {
2065         foreach (const AgendaItem::QPtr &item, d->mItems) {
2066             if (item) {
2067                 subCellWidth = calcSubCellWidth(item);
2068                 placeAgendaItem(item, subCellWidth);
2069             }
2070         }
2071     }
2072     */
2073     checkScrollBoundaries();
2074     marcus_bains();
2075     update();
2076 }
2077 
2078 void Agenda::scrollUp()
2079 {
2080     int currentValue = verticalScrollBar()->value();
2081     verticalScrollBar()->setValue(currentValue - d->mScrollOffset);
2082 }
2083 
2084 void Agenda::scrollDown()
2085 {
2086     int currentValue = verticalScrollBar()->value();
2087     verticalScrollBar()->setValue(currentValue + d->mScrollOffset);
2088 }
2089 
2090 QSize Agenda::minimumSize() const
2091 {
2092     return sizeHint();
2093 }
2094 
2095 QSize Agenda::minimumSizeHint() const
2096 {
2097     return sizeHint();
2098 }
2099 
2100 int Agenda::minimumHeight() const
2101 {
2102     // all day agenda never has scrollbars and the scrollarea will
2103     // resize it to fit exactly on the viewport.
2104 
2105     if (d->mAllDayMode) {
2106         return 0;
2107     } else {
2108         return d->mGridSpacingY * d->mRows;
2109     }
2110 }
2111 
2112 void Agenda::updateConfig()
2113 {
2114     const double oldGridSpacingY = d->mGridSpacingY;
2115 
2116     if (!d->mAllDayMode) {
2117         d->mDesiredGridSpacingY = d->preferences()->hourSize();
2118         if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) {
2119             d->mDesiredGridSpacingY = 10;
2120         }
2121 
2122         /*
2123         // make sure that there are not more than 24 per day
2124         d->mGridSpacingY = static_cast<double>(height()) / d->mRows;
2125         if (d->mGridSpacingY < d->mDesiredGridSpacingY  || true) {
2126           d->mGridSpacingY = d->mDesiredGridSpacingY;
2127         }
2128         */
2129 
2130         // can be two doubles equal?, it's better to compare them with an epsilon
2131         if (fabs(oldGridSpacingY - d->mDesiredGridSpacingY) > 0.1) {
2132             d->mGridSpacingY = d->mDesiredGridSpacingY;
2133             updateGeometry();
2134         }
2135     }
2136 
2137     calculateWorkingHours();
2138 
2139     marcus_bains();
2140 }
2141 
2142 void Agenda::checkScrollBoundaries()
2143 {
2144     // Invalidate old values to force update
2145     d->mOldLowerScrollValue = -1;
2146     d->mOldUpperScrollValue = -1;
2147 
2148     checkScrollBoundaries(verticalScrollBar()->value());
2149 }
2150 
2151 void Agenda::checkScrollBoundaries(int v)
2152 {
2153     int yMin = int((v) / d->mGridSpacingY);
2154     int yMax = int((v + d->mScrollArea->height()) / d->mGridSpacingY);
2155 
2156     if (yMin != d->mOldLowerScrollValue) {
2157         d->mOldLowerScrollValue = yMin;
2158         Q_EMIT lowerYChanged(yMin);
2159     }
2160     if (yMax != d->mOldUpperScrollValue) {
2161         d->mOldUpperScrollValue = yMax;
2162         Q_EMIT upperYChanged(yMax);
2163     }
2164 }
2165 
2166 int Agenda::visibleContentsYMin() const
2167 {
2168     int v = verticalScrollBar()->value();
2169     return int(v / d->mGridSpacingY);
2170 }
2171 
2172 int Agenda::visibleContentsYMax() const
2173 {
2174     int v = verticalScrollBar()->value();
2175     return int((v + d->mScrollArea->height()) / d->mGridSpacingY);
2176 }
2177 
2178 void Agenda::deselectItem()
2179 {
2180     if (d->mSelectedItem.isNull()) {
2181         return;
2182     }
2183 
2184     const KCalendarCore::Incidence::Ptr selectedItem = d->mSelectedItem->incidence();
2185 
2186     for (AgendaItem::QPtr item : std::as_const(d->mItems)) {
2187         if (item) {
2188             const KCalendarCore::Incidence::Ptr itemInc = item->incidence();
2189             if (itemInc && selectedItem && itemInc->uid() == selectedItem->uid()) {
2190                 item->select(false);
2191             }
2192         }
2193     }
2194 
2195     d->mSelectedItem = nullptr;
2196 }
2197 
2198 void Agenda::selectItem(const AgendaItem::QPtr &item)
2199 {
2200     if ((AgendaItem::QPtr)d->mSelectedItem == item) {
2201         return;
2202     }
2203     deselectItem();
2204     if (item == nullptr) {
2205         Q_EMIT incidenceSelected(KCalendarCore::Incidence::Ptr(), QDate());
2206         return;
2207     }
2208     d->mSelectedItem = item;
2209     d->mSelectedItem->select();
2210     Q_ASSERT(d->mSelectedItem->incidence());
2211     d->mSelectedId = d->mSelectedItem->incidence()->uid();
2212 
2213     for (AgendaItem::QPtr item : std::as_const(d->mItems)) {
2214         if (item && item->incidence()->uid() == d->mSelectedId) {
2215             item->select();
2216         }
2217     }
2218     Q_EMIT incidenceSelected(d->mSelectedItem->incidence(), d->mSelectedItem->occurrenceDate());
2219 }
2220 
2221 void Agenda::selectIncidenceByUid(const QString &uid)
2222 {
2223     for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) {
2224         if (item && item->incidence()->uid() == uid) {
2225             selectItem(item);
2226             break;
2227         }
2228     }
2229 }
2230 
2231 void Agenda::selectItem(const Akonadi::Item &item)
2232 {
2233     selectIncidenceByUid(Akonadi::CalendarUtils::incidence(item)->uid());
2234 }
2235 
2236 // This function seems never be called.
2237 void Agenda::keyPressEvent(QKeyEvent *kev)
2238 {
2239     switch (kev->key()) {
2240     case Qt::Key_PageDown:
2241         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
2242         break;
2243     case Qt::Key_PageUp:
2244         verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
2245         break;
2246     case Qt::Key_Down:
2247         verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd);
2248         break;
2249     case Qt::Key_Up:
2250         verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub);
2251         break;
2252     default:;
2253     }
2254 }
2255 
2256 void Agenda::calculateWorkingHours()
2257 {
2258     d->mWorkingHoursEnable = !d->mAllDayMode;
2259 
2260     QTime tmp = d->preferences()->workingHoursStart().time();
2261     d->mWorkingHoursYTop = int(4 * d->mGridSpacingY * (tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600.));
2262     tmp = d->preferences()->workingHoursEnd().time();
2263     d->mWorkingHoursYBottom = int(4 * d->mGridSpacingY * (tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600.) - 1);
2264 }
2265 
2266 void Agenda::setDateList(const KCalendarCore::DateList &selectedDates)
2267 {
2268     d->mSelectedDates = selectedDates;
2269     marcus_bains();
2270 }
2271 
2272 KCalendarCore::DateList Agenda::dateList() const
2273 {
2274     return d->mSelectedDates;
2275 }
2276 
2277 void Agenda::setCalendar(const MultiViewCalendar::Ptr &cal)
2278 {
2279     d->mCalendar = cal;
2280 }
2281 
2282 void Agenda::setIncidenceChanger(Akonadi::IncidenceChanger *changer)
2283 {
2284     d->mChanger = changer;
2285 }
2286 
2287 void Agenda::setHolidayMask(QList<bool> *mask)
2288 {
2289     d->mHolidayMask = mask;
2290 }
2291 
2292 void Agenda::contentsMousePressEvent(QMouseEvent *event)
2293 {
2294     Q_UNUSED(event)
2295 }
2296 
2297 QSize Agenda::sizeHint() const
2298 {
2299     if (d->mAllDayMode) {
2300         return QWidget::sizeHint();
2301     } else {
2302         return {parentWidget()->width(), static_cast<int>(d->mGridSpacingY * d->mRows)};
2303     }
2304 }
2305 
2306 QScrollBar *Agenda::verticalScrollBar() const
2307 {
2308     return d->mScrollArea->verticalScrollBar();
2309 }
2310 
2311 QScrollArea *Agenda::scrollArea() const
2312 {
2313     return d->mScrollArea;
2314 }
2315 
2316 AgendaItem::List Agenda::agendaItems(const QString &uid) const
2317 {
2318     return d->mAgendaItemsById.values(uid);
2319 }
2320 
2321 AgendaScrollArea::AgendaScrollArea(bool isAllDay, AgendaView *agendaView, bool isInteractive, QWidget *parent)
2322     : QScrollArea(parent)
2323 {
2324     if (isAllDay) {
2325         mAgenda = new Agenda(agendaView, this, 1, isInteractive);
2326         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2327     } else {
2328         mAgenda = new Agenda(agendaView, this, 1, 96, agendaView->preferences()->hourSize(), isInteractive);
2329     }
2330 
2331     setWidgetResizable(true);
2332     setWidget(mAgenda);
2333     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2334 
2335     mAgenda->setStartTime(agendaView->preferences()->dayBegins().time());
2336 }
2337 
2338 AgendaScrollArea::~AgendaScrollArea() = default;
2339 
2340 Agenda *AgendaScrollArea::agenda() const
2341 {
2342     return mAgenda;
2343 }
2344 
2345 #include "moc_agenda.cpp"