File indexing completed on 2024-11-24 04:41:35

0001 /*
0002   SPDX-FileCopyrightText: 2008 Bruno Virlet <bruno.virlet@gmail.com>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0005 */
0006 
0007 #include "monthscene.h"
0008 #include "helper.h"
0009 #include "monthgraphicsitems.h"
0010 #include "monthitem.h"
0011 #include "monthview.h"
0012 #include "prefs.h"
0013 
0014 #include <CalendarSupport/Utils>
0015 
0016 #include <KColorScheme>
0017 #include <KLocalizedString>
0018 #include <QGraphicsSceneMouseEvent>
0019 #include <QIcon>
0020 #include <QResizeEvent>
0021 #include <QToolTip>
0022 
0023 static const int AUTO_REPEAT_DELAY = 600;
0024 
0025 using namespace EventViews;
0026 
0027 MonthScene::MonthScene(MonthView *parent)
0028     : QGraphicsScene(parent)
0029     , mMonthView(parent)
0030     , mInitialized(false)
0031     , mClickedItem(nullptr)
0032     , mActionItem(nullptr)
0033     , mActionInitiated(false)
0034     , mSelectedItem(nullptr)
0035     , mStartCell(nullptr)
0036     , mPreviousCell(nullptr)
0037     , mActionType(None)
0038     , mStartHeight(0)
0039     , mCurrentIndicator(nullptr)
0040 {
0041     mBirthdayPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-birthday")).pixmap(16, 16);
0042     mAnniversaryPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-wedding-anniversary")).pixmap(16, 16);
0043     mAlarmPixmap = QIcon::fromTheme(QStringLiteral("appointment-reminder")).pixmap(16, 16);
0044     mRecurPixmap = QIcon::fromTheme(QStringLiteral("appointment-recurring")).pixmap(16, 16);
0045     mReadonlyPixmap = QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(16, 16);
0046     mReplyPixmap = QIcon::fromTheme(QStringLiteral("mail-reply-sender")).pixmap(16, 16);
0047     mHolidayPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-holiday")).pixmap(16, 16);
0048 
0049     setSceneRect(0, 0, parent->width(), parent->height());
0050 }
0051 
0052 MonthScene::~MonthScene()
0053 {
0054     qDeleteAll(mMonthCellMap);
0055     qDeleteAll(mManagerList);
0056 }
0057 
0058 MonthCell *MonthScene::selectedCell() const
0059 {
0060     return mMonthCellMap.value(mSelectedCellDate);
0061 }
0062 
0063 MonthCell *MonthScene::previousCell() const
0064 {
0065     return mPreviousCell;
0066 }
0067 
0068 int MonthScene::getRightSpan(QDate date) const
0069 {
0070     MonthCell *cell = mMonthCellMap.value(date);
0071     if (!cell) {
0072         return 0;
0073     }
0074 
0075     return 7 - cell->x() - 1;
0076 }
0077 
0078 int MonthScene::getLeftSpan(QDate date) const
0079 {
0080     MonthCell *cell = mMonthCellMap.value(date);
0081     if (!cell) {
0082         return 0;
0083     }
0084 
0085     return cell->x();
0086 }
0087 
0088 int MonthScene::maxRowCount()
0089 {
0090     return (rowHeight() - MonthCell::topMargin()) / itemHeightIncludingSpacing();
0091 }
0092 
0093 int MonthScene::itemHeightIncludingSpacing()
0094 {
0095     return MonthCell::topMargin() + 2;
0096 }
0097 
0098 int MonthScene::itemHeight()
0099 {
0100     return MonthCell::topMargin();
0101 }
0102 
0103 MonthCell *MonthScene::firstCellForMonthItem(MonthItem *manager)
0104 {
0105     for (QDate d = manager->startDate(); d <= manager->endDate(); d = d.addDays(1)) {
0106         MonthCell *monthCell = mMonthCellMap.value(d);
0107         if (monthCell) {
0108             return monthCell;
0109         }
0110     }
0111 
0112     return nullptr;
0113 }
0114 
0115 void MonthScene::updateGeometry()
0116 {
0117     for (MonthItem *manager : std::as_const(mManagerList)) {
0118         manager->updateGeometry();
0119     }
0120 }
0121 
0122 int MonthScene::availableWidth() const
0123 {
0124     return static_cast<int>(sceneRect().width());
0125 }
0126 
0127 int MonthScene::availableHeight() const
0128 {
0129     return static_cast<int>(sceneRect().height() - headerHeight());
0130 }
0131 
0132 int MonthScene::columnWidth() const
0133 {
0134     return static_cast<int>((availableWidth() - 1) / 7.);
0135 }
0136 
0137 int MonthScene::rowHeight() const
0138 {
0139     return static_cast<int>((availableHeight() - 1) / 6.);
0140 }
0141 
0142 int MonthScene::headerHeight() const
0143 {
0144     return 50;
0145 }
0146 
0147 int MonthScene::cellVerticalPos(const MonthCell *cell) const
0148 {
0149     return headerHeight() + cell->y() * rowHeight();
0150 }
0151 
0152 int MonthScene::cellHorizontalPos(const MonthCell *cell) const
0153 {
0154     return cell->x() * columnWidth();
0155 }
0156 
0157 int MonthScene::sceneYToMonthGridY(int yScene)
0158 {
0159     return yScene - headerHeight();
0160 }
0161 
0162 int MonthScene::sceneXToMonthGridX(int xScene)
0163 {
0164     return xScene;
0165 }
0166 
0167 void MonthGraphicsView::drawBackground(QPainter *p, const QRectF &rect)
0168 {
0169     Q_ASSERT(mScene);
0170 
0171     PrefsPtr prefs = mScene->monthView()->preferences();
0172     p->setFont(prefs->monthViewFont());
0173     p->fillRect(rect, palette().color(QPalette::Window));
0174 
0175     /*
0176       Headers
0177     */
0178     QFont font = prefs->monthViewFont();
0179     font.setBold(true);
0180     font.setPointSize(15);
0181     p->setFont(font);
0182     const int dayLabelsHeight = 20;
0183     const auto dayInMonth = mMonthView->averageDate();
0184     p->drawText(QRect(0,
0185                       0, // top right
0186                       static_cast<int>(mScene->sceneRect().width()),
0187                       static_cast<int>(mScene->headerHeight() - dayLabelsHeight)),
0188                 Qt::AlignCenter,
0189                 i18nc("monthname year", "%1 %2", QLocale().standaloneMonthName(dayInMonth.month(), QLocale::LongFormat), QString::number(dayInMonth.year())));
0190 
0191     font.setPointSize(dayLabelsHeight - 10);
0192     p->setFont(font);
0193 
0194     const QDate start = mMonthView->actualStartDateTime().date();
0195     const QDate end = mMonthView->actualEndDateTime().date();
0196 
0197     for (QDate d = start; d <= start.addDays(6); d = d.addDays(1)) {
0198         const MonthCell *const cell = mScene->mMonthCellMap.value(d);
0199 
0200         if (!cell) {
0201             // This means drawBackground() is being called before reloadIncidences(). Can happen with some
0202             // themes. Bug  #190191
0203             return;
0204         }
0205 
0206         p->drawText(QRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell) - 15, mScene->columnWidth(), 15),
0207                     Qt::AlignCenter,
0208                     QLocale::system().dayName(d.dayOfWeek(), QLocale::LongFormat));
0209     }
0210 
0211     /*
0212       Month grid
0213     */
0214     int columnWidth = mScene->columnWidth();
0215     int rowHeight = mScene->rowHeight();
0216     QDate todayDate{QDate::currentDate()};
0217 
0218     const QList<QDate> workDays = CalendarSupport::workDays(mMonthView->actualStartDateTime().date(), mMonthView->actualEndDateTime().date());
0219     QRect todayRect;
0220     QRect selectedRect;
0221     QColor holidayBg;
0222     QColor workdayBg;
0223     if (mMonthView->preferences()->useSystemColor()) {
0224         workdayBg = palette().color(QPalette::Base);
0225         holidayBg = palette().color(QPalette::AlternateBase);
0226     } else {
0227         workdayBg = mMonthView->preferences()->monthGridWorkHoursBackgroundColor();
0228         holidayBg = mMonthView->preferences()->monthGridBackgroundColor();
0229     }
0230 
0231     for (QDate d = start; d <= end; d = d.addDays(1)) {
0232         const MonthCell *const cell = mScene->mMonthCellMap.value(d);
0233 
0234         if (!cell) {
0235             // This means drawBackground() is being called before reloadIncidences(). Can happen with some
0236             // themes. Bug  #190191
0237             return;
0238         }
0239 
0240         const QRect cellRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell), columnWidth, rowHeight);
0241         if (cell == mScene->selectedCell()) {
0242             selectedRect = cellRect;
0243         }
0244         if (cell->date() == todayDate) {
0245             todayRect = cellRect;
0246         }
0247 
0248         // Draw cell
0249         p->setPen(mMonthView->preferences()->monthGridBackgroundColor().darker(150));
0250         p->setBrush(workDays.contains(d) ? workdayBg : holidayBg);
0251         p->drawRect(cellRect);
0252         if (mMonthView->isBusyDay(d)) {
0253             QColor busyColor = mMonthView->preferences()->viewBgBusyColor();
0254             busyColor.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA);
0255             p->setBrush(busyColor);
0256             p->drawRect(cellRect);
0257         }
0258     }
0259     if (!todayRect.isNull()) {
0260         KColorScheme scheme(QPalette::Normal, KColorScheme::ColorSet::View);
0261         p->setPen(scheme.foreground(KColorScheme::ForegroundRole::PositiveText).color());
0262         p->setBrush(scheme.background(KColorScheme::BackgroundRole::PositiveBackground));
0263         p->drawRect(todayRect);
0264     }
0265     if (!selectedRect.isNull()) {
0266         const KColorScheme scheme(QPalette::Normal, KColorScheme::ColorSet::Selection);
0267         auto color = scheme.background(KColorScheme::BackgroundRole::NormalBackground).color();
0268         p->setPen(color);
0269         color.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA);
0270         p->setBrush(color);
0271         p->drawRect(selectedRect);
0272     }
0273 
0274     /*
0275      * Draw Dates
0276      */
0277 
0278     font = mMonthView->preferences()->monthViewFont();
0279     font.setPixelSize(MonthCell::topMargin() - 4);
0280     p->setFont(font);
0281 
0282     QPen oldPen;
0283     if (mMonthView->preferences()->useSystemColor()) {
0284         oldPen = palette().color(QPalette::WindowText).darker(150);
0285     } else {
0286         oldPen = mMonthView->preferences()->monthGridBackgroundColor().darker(150);
0287     }
0288 
0289     for (QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays(1)) {
0290         MonthCell *const cell = mScene->mMonthCellMap.value(d);
0291 
0292         // Draw cell header
0293         int cellHeaderX = mScene->cellHorizontalPos(cell) + 1;
0294         int cellHeaderY = mScene->cellVerticalPos(cell) + 1;
0295         int cellHeaderWidth = columnWidth - 2;
0296         int cellHeaderHeight = cell->topMargin() - 2;
0297         const auto brush = KColorScheme(QPalette::Normal, KColorScheme::ColorSet::Header).background(KColorScheme::BackgroundRole::NormalBackground);
0298         p->setBrush(brush);
0299         p->setPen(Qt::NoPen);
0300         p->drawRect(QRect(cellHeaderX, cellHeaderY, cellHeaderWidth, cellHeaderHeight));
0301 
0302         QFont font = p->font();
0303         font.setBold(cell->date() == todayDate);
0304         p->setFont(font);
0305 
0306         if (d.month() == mMonthView->currentMonth()) {
0307             p->setPen(palette().color(QPalette::WindowText));
0308         } else {
0309             p->setPen(oldPen);
0310         }
0311 
0312         QString dayText;
0313         // Prepend month name if d is the first or last day of month
0314         if (d.day() == 1 || // d is the first day of month
0315             d.addDays(1).day() == 1) { // d is the last day of month
0316             dayText = i18nc("'Month day' for month view cells", "%1 %2", QLocale::system().monthName(d.month(), QLocale::ShortFormat), d.day());
0317         } else {
0318             dayText = QString::number(d.day());
0319         }
0320         p->drawText(QRect(mScene->cellHorizontalPos(cell), // top right
0321                           mScene->cellVerticalPos(cell), // of the cell
0322                           mScene->columnWidth() - 2,
0323                           cell->topMargin()),
0324                     Qt::AlignRight,
0325                     dayText);
0326 
0327         /*
0328           Draw arrows if all items won't fit
0329         */
0330 
0331         // Up arrow if first item is above cell top
0332         if (mScene->startHeight() != 0 && cell->hasEventBelow(mScene->startHeight())) {
0333             cell->upArrow()->setPos(mScene->cellHorizontalPos(cell) + columnWidth / 2,
0334                                     mScene->cellVerticalPos(cell) + cell->upArrow()->boundingRect().height() / 2 + 2);
0335             cell->upArrow()->show();
0336         } else {
0337             cell->upArrow()->hide();
0338         }
0339 
0340         // Down arrow if last item is below cell bottom
0341         if (!mScene->lastItemFit(cell)) {
0342             cell->downArrow()->setPos(mScene->cellHorizontalPos(cell) + columnWidth / 2,
0343                                       mScene->cellVerticalPos(cell) + rowHeight - cell->downArrow()->boundingRect().height() / 2 - 2);
0344             cell->downArrow()->show();
0345         } else {
0346             cell->downArrow()->hide();
0347         }
0348     }
0349 }
0350 
0351 void MonthScene::resetAll()
0352 {
0353     qDeleteAll(mMonthCellMap);
0354     mMonthCellMap.clear();
0355 
0356     qDeleteAll(mManagerList);
0357     mManagerList.clear();
0358 
0359     mSelectedItem = nullptr;
0360     mActionItem = nullptr;
0361     mClickedItem = nullptr;
0362 }
0363 
0364 Akonadi::IncidenceChanger *MonthScene::incidenceChanger() const
0365 {
0366     return mMonthView->changer();
0367 }
0368 
0369 QDate MonthScene::firstDateOnRow(int row) const
0370 {
0371     return mMonthView->actualStartDateTime().date().addDays(7 * row);
0372 }
0373 
0374 bool MonthScene::lastItemFit(MonthCell *cell)
0375 {
0376     if (cell->firstFreeSpace() > maxRowCount() + startHeight()) {
0377         return false;
0378     } else {
0379         return true;
0380     }
0381 }
0382 
0383 int MonthScene::totalHeight()
0384 {
0385     int max = 0;
0386     for (QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays(1)) {
0387         int c = mMonthCellMap[d]->firstFreeSpace();
0388         if (c > max) {
0389             max = c;
0390         }
0391     }
0392 
0393     return max;
0394 }
0395 
0396 void MonthScene::wheelEvent(QGraphicsSceneWheelEvent *event)
0397 {
0398     Q_UNUSED(event) // until we figure out what to do in here
0399 
0400     /*  int numDegrees = -event->delta() / 8;
0401       int numSteps = numDegrees / 15;
0402 
0403       if (startHeight() + numSteps < 0) {
0404         numSteps = -startHeight();
0405       }
0406 
0407       int cellHeight = 0;
0408 
0409       MonthCell *currentCell = getCellFromPos(event->scenePos());
0410       if (currentCell) {
0411         cellHeight = currentCell->firstFreeSpace();
0412       }
0413       if (cellHeight == 0) {
0414         // no items in this cell, there's no point to scroll
0415         return;
0416       }
0417 
0418       int newHeight;
0419       int maxStartHeight = qMax(0, cellHeight - maxRowCount());
0420       if (numSteps > 0  && startHeight() + numSteps >= maxStartHeight) {
0421         newHeight = maxStartHeight;
0422       } else {
0423         newHeight = startHeight() + numSteps;
0424       }
0425 
0426       if (newHeight == startHeight()) {
0427         return;
0428       }
0429 
0430       setStartHeight(newHeight);
0431 
0432       foreach (MonthItem *manager, mManagerList) {
0433         manager->updateGeometry();
0434       }
0435 
0436       invalidate(QRectF(), BackgroundLayer);
0437 
0438       event->accept();
0439     */
0440 }
0441 
0442 void MonthScene::scrollCellsDown()
0443 {
0444     int newHeight = startHeight() + 1;
0445     setStartHeight(newHeight);
0446 
0447     for (MonthItem *manager : std::as_const(mManagerList)) {
0448         manager->updateGeometry();
0449     }
0450 
0451     invalidate(QRectF(), BackgroundLayer);
0452 }
0453 
0454 void MonthScene::scrollCellsUp()
0455 {
0456     int newHeight = startHeight() - 1;
0457     setStartHeight(newHeight);
0458 
0459     for (MonthItem *manager : std::as_const(mManagerList)) {
0460         manager->updateGeometry();
0461     }
0462 
0463     invalidate(QRectF(), BackgroundLayer);
0464 }
0465 
0466 void MonthScene::clickOnScrollIndicator(ScrollIndicator *scrollItem)
0467 {
0468     if (scrollItem->direction() == ScrollIndicator::UpArrow) {
0469         scrollCellsUp();
0470     } else if (scrollItem->direction() == ScrollIndicator::DownArrow) {
0471         scrollCellsDown();
0472     }
0473 }
0474 
0475 void MonthScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent)
0476 {
0477     QPointF pos = mouseEvent->scenePos();
0478     repeatTimer.stop();
0479     MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {}));
0480     if (iItem) {
0481         if (iItem->monthItem()) {
0482             auto tmp = qobject_cast<IncidenceMonthItem *>(iItem->monthItem());
0483             if (tmp) {
0484                 selectItem(iItem->monthItem());
0485                 mMonthView->defaultAction(tmp->akonadiItem());
0486 
0487                 mouseEvent->accept();
0488             }
0489         }
0490     } else {
0491         Q_EMIT newEventSignal();
0492     }
0493 }
0494 
0495 void MonthScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
0496 {
0497     QPointF pos = mouseEvent->scenePos();
0498 
0499     MonthGraphicsView *view = static_cast<MonthGraphicsView *>(views().at(0));
0500 
0501     // Change cursor depending on the part of the item it hovers to inform
0502     // the user that he can resize the item.
0503     if (mActionType == None) {
0504         MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {}));
0505         if (iItem) {
0506             if (iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene(pos).x() <= 10) {
0507                 view->setActionCursor(Resize);
0508             } else if (iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene(pos).x() >= iItem->boundingRect().width() - 10) {
0509                 view->setActionCursor(Resize);
0510             } else {
0511                 view->setActionCursor(None);
0512             }
0513         } else {
0514             view->setActionCursor(None);
0515         }
0516         mouseEvent->accept();
0517         return;
0518     }
0519 
0520     // If an item was selected during the click, we maybe have an item to move !
0521     if (mActionItem) {
0522         // Initiate action if not already done
0523         if (!mActionInitiated && mActionType != None) {
0524             if (mActionType == Move) {
0525                 mActionItem->beginMove();
0526             } else if (mActionType == Resize) {
0527                 mActionItem->beginResize();
0528             }
0529             mActionInitiated = true;
0530         }
0531         view->setActionCursor(mActionType);
0532 
0533         // Move or resize action
0534         MonthCell *const currentCell = getCellFromPos(pos);
0535         if (currentCell && currentCell != mPreviousCell) {
0536             bool ok = true;
0537             if (mActionType == Move) {
0538                 if (currentCell) {
0539                     mActionItem->moveTo(currentCell->date());
0540                     mActionItem->updateGeometry();
0541                 } else {
0542                     mActionItem->moveTo(QDate());
0543                     mActionItem->updateGeometry();
0544                     mActionItem->endMove();
0545                     mActionItem = nullptr;
0546                     mActionType = None;
0547                     mStartCell = nullptr;
0548                 }
0549             } else if (mActionType == Resize) {
0550                 ok = mActionItem->resizeBy(mPreviousCell->date().daysTo(currentCell->date()));
0551                 mActionItem->updateGeometry();
0552             }
0553 
0554             if (ok) {
0555                 mPreviousCell = currentCell;
0556             }
0557             update();
0558         }
0559         mouseEvent->accept();
0560     }
0561 }
0562 
0563 void MonthScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
0564 {
0565     QPointF pos = mouseEvent->scenePos();
0566 
0567     mClickedItem = nullptr;
0568     mCurrentIndicator = nullptr;
0569 
0570     MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {}));
0571     if (iItem) {
0572         mClickedItem = iItem->monthItem();
0573 
0574         selectItem(mClickedItem);
0575         if (mouseEvent->button() == Qt::RightButton) {
0576             auto tmp = qobject_cast<IncidenceMonthItem *>(mClickedItem);
0577             if (tmp) {
0578                 Q_EMIT showIncidencePopupSignal(tmp->calendar(), tmp->akonadiItem(), tmp->realStartDate());
0579             }
0580         }
0581 
0582         if (mouseEvent->button() == Qt::LeftButton) {
0583             // Basic initialization for resize and move
0584             mActionItem = mClickedItem;
0585             mStartCell = getCellFromPos(pos);
0586             mPreviousCell = mStartCell;
0587             mActionInitiated = false;
0588 
0589             // Move or resize ?
0590             if (iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene(pos).x() <= 10) {
0591                 mActionType = Resize;
0592                 mResizeType = ResizeLeft;
0593             } else if (iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene(pos).x() >= iItem->boundingRect().width() - 10) {
0594                 mActionType = Resize;
0595                 mResizeType = ResizeRight;
0596             } else if (iItem->monthItem()->isMoveable()) {
0597                 mActionType = Move;
0598             }
0599         }
0600         mouseEvent->accept();
0601     } else if (ScrollIndicator *scrollItem = dynamic_cast<ScrollIndicator *>(itemAt(pos, {}))) {
0602         clickOnScrollIndicator(scrollItem);
0603         mCurrentIndicator = scrollItem;
0604         repeatTimer.start(AUTO_REPEAT_DELAY, this);
0605     } else {
0606         // unselect items when clicking somewhere else
0607         selectItem(nullptr);
0608 
0609         MonthCell *cell = getCellFromPos(pos);
0610         if (cell) {
0611             mSelectedCellDate = cell->date();
0612             update();
0613             if (mouseEvent->button() == Qt::RightButton) {
0614                 Q_EMIT showNewEventPopupSignal();
0615             }
0616             mouseEvent->accept();
0617         }
0618     }
0619 }
0620 
0621 void MonthScene::timerEvent(QTimerEvent *e)
0622 {
0623     if (e->timerId() == repeatTimer.timerId()) {
0624         if (mCurrentIndicator->isVisible()) {
0625             clickOnScrollIndicator(mCurrentIndicator);
0626             repeatTimer.start(AUTO_REPEAT_DELAY, this);
0627         } else {
0628             mCurrentIndicator = nullptr;
0629             repeatTimer.stop();
0630         }
0631     }
0632 }
0633 
0634 void MonthScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent)
0635 {
0636     // Find the first item that does tooltips
0637     const QPointF pos = helpEvent->scenePos();
0638     MonthGraphicsItem *toolTipItem = dynamic_cast<MonthGraphicsItem *>(itemAt(pos, {}));
0639 
0640     // Show or hide the tooltip
0641     QString text;
0642     QPoint point;
0643     if (toolTipItem) {
0644         text = toolTipItem->getToolTip();
0645         point = helpEvent->screenPos();
0646     }
0647     QToolTip::showText(point, text, helpEvent->widget());
0648     helpEvent->setAccepted(!text.isEmpty());
0649 }
0650 
0651 void MonthScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
0652 {
0653     QPointF pos = mouseEvent->scenePos();
0654 
0655     static_cast<MonthGraphicsView *>(views().at(0))->setActionCursor(None);
0656 
0657     repeatTimer.stop();
0658     mCurrentIndicator = nullptr;
0659 
0660     if (mActionItem) {
0661         MonthCell *currentCell = getCellFromPos(pos);
0662 
0663         const bool somethingChanged = currentCell && currentCell != mStartCell;
0664 
0665         if (somethingChanged) { // We want to act if a move really happened
0666             if (mActionType == Resize) {
0667                 mActionItem->endResize();
0668             } else if (mActionType == Move) {
0669                 mActionItem->endMove();
0670             }
0671         }
0672 
0673         mActionItem = nullptr;
0674         mActionType = None;
0675         mStartCell = nullptr;
0676 
0677         mouseEvent->accept();
0678     }
0679 }
0680 
0681 // returns true if the point is in the monthgrid (allows to avoid selecting a cell when
0682 // a click is outside the month grid
0683 bool MonthScene::isInMonthGrid(int x, int y) const
0684 {
0685     return x >= 0 && y >= 0 && x <= availableWidth() && y <= availableHeight();
0686 }
0687 
0688 // The function converts the coordinates to the month grid coordinates to
0689 // be able to locate the cell.
0690 MonthCell *MonthScene::getCellFromPos(QPointF pos)
0691 {
0692     int y = sceneYToMonthGridY(static_cast<int>(pos.y()));
0693     int x = sceneXToMonthGridX(static_cast<int>(pos.x()));
0694     if (!isInMonthGrid(x, y)) {
0695         return nullptr;
0696     }
0697     int id = (int)(y / rowHeight()) * 7 + (int)(x / columnWidth());
0698 
0699     return mMonthCellMap.value(mMonthView->actualStartDateTime().date().addDays(id));
0700 }
0701 
0702 void MonthScene::selectItem(MonthItem *item)
0703 {
0704     /*
0705       if (mSelectedItem == item) {
0706         return;
0707       }
0708 
0709       I commented the above code so it's possible to selected a selected item.
0710       korg-mobile needs that, otherwise clicking on a selected item won't bring the editor up.
0711       Another solution would be to have two Q_SIGNALS: incidenceSelected() and incidenceClicked()
0712     */
0713 
0714     auto tmp = qobject_cast<IncidenceMonthItem *>(item);
0715 
0716     if (!tmp) {
0717         mSelectedItem = nullptr;
0718         Q_EMIT incidenceSelected(Akonadi::Item(), QDate());
0719         return;
0720     }
0721 
0722     mSelectedItem = item;
0723     Q_ASSERT(CalendarSupport::hasIncidence(tmp->akonadiItem()));
0724 
0725     if (mMonthView->selectedIncidenceDates().isEmpty()) {
0726         Q_EMIT incidenceSelected(tmp->akonadiItem(), QDate());
0727     } else {
0728         Q_EMIT incidenceSelected(tmp->akonadiItem(), mMonthView->selectedIncidenceDates().at(0));
0729     }
0730     update();
0731 }
0732 
0733 void MonthScene::removeIncidence(const QString &uid)
0734 {
0735     for (MonthItem *manager : std::as_const(mManagerList)) {
0736         auto imi = qobject_cast<IncidenceMonthItem *>(manager);
0737         if (!imi) {
0738             continue;
0739         }
0740 
0741         KCalendarCore::Incidence::Ptr incidence = imi->incidence();
0742         if (!incidence) {
0743             continue;
0744         }
0745         if (incidence->uid() == uid) {
0746             const auto lst = imi->monthGraphicsItems();
0747             for (MonthGraphicsItem *gitem : lst) {
0748                 removeItem(gitem);
0749             }
0750         }
0751     }
0752 }
0753 
0754 //----------------------------------------------------------
0755 MonthGraphicsView::MonthGraphicsView(MonthView *parent)
0756     : QGraphicsView(parent)
0757     , mMonthView(parent)
0758 {
0759     setMouseTracking(true);
0760 }
0761 
0762 void MonthGraphicsView::setActionCursor(MonthScene::ActionType actionType)
0763 {
0764     switch (actionType) {
0765     case MonthScene::Move:
0766 #ifndef QT_NO_CURSOR
0767         setCursor(Qt::ArrowCursor);
0768 #endif
0769         break;
0770     case MonthScene::Resize:
0771 #ifndef QT_NO_CURSOR
0772         setCursor(Qt::SizeHorCursor);
0773 #endif
0774         break;
0775 #ifndef QT_NO_CURSOR
0776     default:
0777         setCursor(Qt::ArrowCursor);
0778 #endif
0779     }
0780 }
0781 
0782 void MonthGraphicsView::setScene(MonthScene *scene)
0783 {
0784     mScene = scene;
0785     QGraphicsView::setScene(scene);
0786 }
0787 
0788 void MonthGraphicsView::resizeEvent(QResizeEvent *event)
0789 {
0790     mScene->setSceneRect(0, 0, event->size().width(), event->size().height());
0791     mScene->updateGeometry();
0792 }
0793 
0794 #include "moc_monthscene.cpp"