diff --git a/src/helper.cpp b/src/helper.cpp index cca1196..bf5d065 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -1,98 +1,103 @@ /* Copyright (C) 2005 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "helper.h" #include "prefs.h" #include #include #include #include #include -QColor EventViews::getTextColor(const QColor &c) +bool EventViews::isColorDark(const QColor &c) { double luminance = (c.red() * 0.299) + (c.green() * 0.587) + (c.blue() * 0.114); - return (luminance > 128.0) ? QColor(0, 0, 0) : QColor(255, 255, 255); + return (luminance < 128.0) ? true : false; +} + +QColor EventViews::getTextColor(const QColor &c) +{ + return (!isColorDark(c)) ? QColor(0, 0, 0) : QColor(255, 255, 255); } void EventViews::setResourceColor(const Akonadi::Collection &coll, const QColor &color, const PrefsPtr &preferences) { if (!coll.isValid()) { return; } const QString id = QString::number(coll.id()); if (coll.hasAttribute()) { const Akonadi::CollectionColorAttribute *colorAttr = coll.attribute(); if (colorAttr && colorAttr->color().isValid() && (colorAttr->color() == color)) { // It's the same color: we save an invalid color preferences->setResourceColor(id, QColor()); } } // in all other cases, we save the resourceColor preferences->setResourceColor(id, color); } QColor EventViews::resourceColor(const Akonadi::Collection &coll, const PrefsPtr &preferences) { if (!coll.isValid()) { return QColor(); } const QString id = QString::number(coll.id()); // Color stored in eventviewsrc (and in memory) QColor color = preferences->resourceColorKnown(id); if (color.isValid()) { return color; } // Color stored in akonadi if (coll.hasAttribute()) { const Akonadi::CollectionColorAttribute *colorAttr = coll.attribute(); if (colorAttr && colorAttr->color().isValid()) { return colorAttr->color(); } } // Generate new color and store it in eventsviewsrc (and in memory) return preferences->resourceColor(id); } QColor EventViews::resourceColor(const Akonadi::Item &item, const PrefsPtr &preferences) { if (!item.isValid()) { return QColor(); } return resourceColor(item.parentCollection(), preferences); } int EventViews::yearDiff(const QDate &start, const QDate &end) { return end.year() - start.year(); } QPixmap EventViews::cachedSmallIcon(const QString &name) { return QIcon::fromTheme(name).pixmap(16, 16); } diff --git a/src/helper.h b/src/helper.h index 0c85125..f1cfe99 100644 --- a/src/helper.h +++ b/src/helper.h @@ -1,92 +1,101 @@ /* Copyright (C) 2005 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_HELPER_H #define EVENTVIEWS_HELPER_H #include "eventviews_export.h" #include #include namespace Akonadi { class Collection; class Item; } class QPixmap; class QDate; // Provides static methods that are useful to all views. namespace EventViews { class Prefs; typedef QSharedPointer PrefsPtr; /** Returns a nice QColor for text, give the input color &c. */ Q_REQUIRED_RESULT QColor getTextColor(const QColor &c); +/** + * Determines if the @p color is "dark" or "light" by looking at its luminance. + * idea taken from: + * https://stackoverflow.com/questions/9780632/how-do-i-determine-if-a-color-is-closer-to-white-or-black + * + * @return true if the specified color is closer to black than white. + */ +Q_REQUIRED_RESULT bool isColorDark(const QColor &color); + /** This method returns the proper resource / subresource color for the view. If a value is stored in the preferences, we use it, else we try to find a CollectionColorAttribute in the collection. If everything else fails, a random color can be set. It is preferred to use this function instead of the EventViews::Prefs::resourceColor function. @return The resource color for the incidence. If the incidence belongs to a subresource, the color for the subresource is returned (if set). @param calendar the calendar for which the resource color should be obtained @param incidence the incidence for which the color is needed (to determine which subresource needs to be used) */ Q_REQUIRED_RESULT EVENTVIEWS_EXPORT QColor resourceColor(const Akonadi::Item &incidence, const PrefsPtr &preferences); Q_REQUIRED_RESULT EVENTVIEWS_EXPORT QColor resourceColor(const Akonadi::Collection &collection, const PrefsPtr &preferences); /** This method sets the resource color in the preferences, only if it is different from the CollectionColorAttribute. It is preferred to use this instead of the EventViews::Prefs::setResourceColor function. @param collection the collection for which the resource color should be stored @param color the color to stored @param preferences a pointer to the EventViews::Prefs to use */ EVENTVIEWS_EXPORT void setResourceColor(const Akonadi::Collection &collection, const QColor &color, const PrefsPtr &preferences); /** Returns the number of years between the @p start QDate and the @p end QDate (i.e. the difference in the year number of both dates) */ Q_REQUIRED_RESULT int yearDiff(const QDate &start, const QDate &end); /** Equivalent to SmallIcon( name ), but uses QPixmapCache. KIconLoader already uses a cache, but it's 20x slower on my tests. @return A new pixmap if it isn't yet in cache, otherwise returns the cached one. */ Q_REQUIRED_RESULT QPixmap cachedSmallIcon(const QString &name); } #endif diff --git a/src/month/monthscene.cpp b/src/month/monthscene.cpp index 3d77946..ff83794 100644 --- a/src/month/monthscene.cpp +++ b/src/month/monthscene.cpp @@ -1,819 +1,822 @@ /* Copyright (c) 2008 Bruno Virlet This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthscene.h" #include "monthgraphicsitems.h" #include "monthitem.h" #include "monthview.h" +#include "helper.h" #include "prefs.h" #include #include #include #include #include #include static const int AUTO_REPEAT_DELAY = 600; using namespace EventViews; MonthScene::MonthScene(MonthView *parent) : QGraphicsScene(parent) , mMonthView(parent) , mInitialized(false) , mClickedItem(nullptr) , mActionItem(nullptr) , mActionInitiated(false) , mSelectedItem(nullptr) , mStartCell(nullptr) , mPreviousCell(nullptr) , mActionType(None) , mStartHeight(0) , mCurrentIndicator(nullptr) { mBirthdayPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-birthday")).pixmap(16, 16); mAnniversaryPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-wedding-anniversary")).pixmap(16, 16); mAlarmPixmap = QIcon::fromTheme(QStringLiteral("appointment-reminder")).pixmap(16, 16); mRecurPixmap = QIcon::fromTheme(QStringLiteral("appointment-recurring")).pixmap(16, 16); mReadonlyPixmap = QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(16, 16); mReplyPixmap = QIcon::fromTheme(QStringLiteral("mail-reply-sender")).pixmap(16, 16); mHolidayPixmap = QIcon::fromTheme(QStringLiteral("view-calendar-holiday")).pixmap(16, 16); setSceneRect(0, 0, parent->width(), parent->height()); } MonthScene::~MonthScene() { qDeleteAll(mMonthCellMap); qDeleteAll(mManagerList); } MonthCell *MonthScene::selectedCell() const { return mMonthCellMap.value(mSelectedCellDate); } MonthCell *MonthScene::previousCell() const { return mPreviousCell; } int MonthScene::getRightSpan(const QDate &date) const { MonthCell *cell = mMonthCellMap.value(date); if (!cell) { return 0; } return 7 - cell->x() - 1; } int MonthScene::getLeftSpan(const QDate &date) const { MonthCell *cell = mMonthCellMap.value(date); if (!cell) { return 0; } return cell->x(); } int MonthScene::maxRowCount() { return (rowHeight() - MonthCell::topMargin()) / itemHeightIncludingSpacing(); } int MonthScene::itemHeightIncludingSpacing() { return MonthCell::topMargin() + 2; } int MonthScene::itemHeight() { return MonthCell::topMargin(); } MonthCell *MonthScene::firstCellForMonthItem(MonthItem *manager) { for (QDate d = manager->startDate(); d <= manager->endDate(); d = d.addDays(1)) { MonthCell *monthCell = mMonthCellMap.value(d); if (monthCell) { return monthCell; } } return nullptr; } void MonthScene::updateGeometry() { for (MonthItem *manager : qAsConst(mManagerList)) { manager->updateGeometry(); } } int MonthScene::availableWidth() const { return static_cast(sceneRect().width()); } int MonthScene::availableHeight() const { return static_cast(sceneRect().height() - headerHeight()); } int MonthScene::columnWidth() const { return static_cast((availableWidth() - 1) / 7.); } int MonthScene::rowHeight() const { return static_cast((availableHeight() - 1) / 6.); } int MonthScene::headerHeight() const { return 50; } int MonthScene::cellVerticalPos(const MonthCell *cell) const { return headerHeight() + cell->y() * rowHeight(); } int MonthScene::cellHorizontalPos(const MonthCell *cell) const { return cell->x() * columnWidth(); } int MonthScene::sceneYToMonthGridY(int yScene) { return yScene - headerHeight(); } int MonthScene::sceneXToMonthGridX(int xScene) { return xScene; } void MonthGraphicsView::drawBackground(QPainter *p, const QRectF &rect) { Q_ASSERT(mScene); PrefsPtr prefs = mScene->monthView()->preferences(); p->setFont(prefs->monthViewFont()); p->fillRect(rect, palette().color(QPalette::Window)); /* Headers */ QFont font = prefs->monthViewFont(); font.setBold(true); font.setPointSize(15); p->setFont(font); const int dayLabelsHeight = 20; p->drawText(QRect(0, 0, // top right static_cast(mScene->sceneRect().width()), static_cast(mScene->headerHeight() - dayLabelsHeight)), Qt::AlignCenter, mMonthView->averageDate().toString(QStringLiteral("MMMM yyyy"))); font.setPointSize(dayLabelsHeight - 10); p->setFont(font); const QDate start = mMonthView->actualStartDateTime().date(); const QDate end = mMonthView->actualEndDateTime().date(); for (QDate d = start; d <= start.addDays(6); d = d.addDays(1)) { MonthCell *cell = mScene->mMonthCellMap.value(d); if (!cell) { // This means drawBackground() is being called before reloadIncidences(). Can happen with some // themes. Bug #190191 return; } p->drawText(QRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell) - 15, mScene->columnWidth(), 15), Qt::AlignCenter, QLocale::system().dayName(d.dayOfWeek(), QLocale::LongFormat)); } /* Month grid */ int columnWidth = mScene->columnWidth(); int rowHeight = mScene->rowHeight(); const QList workDays = CalendarSupport::workDays( mMonthView->actualStartDateTime().date(), mMonthView->actualEndDateTime().date()); for (QDate d = start; d <= end; d = d.addDays(1)) { if (!mScene->mMonthCellMap.contains(d)) { // This means drawBackground() is being called before reloadIncidences(). Can happen with some // themes. Bug #190191 return; } MonthCell *cell = mScene->mMonthCellMap[ d ]; QColor color; if (!mMonthView->preferences()->useSystemColor()) { if (workDays.contains(d)) { color = mMonthView->preferences()->monthGridWorkHoursBackgroundColor(); } else { color = mMonthView->preferences()->monthGridBackgroundColor(); } } else { if (workDays.contains(d)) { color = palette().color(QPalette::Base); } else { color = palette().color(QPalette::AlternateBase); } } + const bool usingDark = EventViews::isColorDark(color); if (cell == mScene->selectedCell()) { - color = color.darker(115); + color = (usingDark) ? color.lighter(150) : color.darker(115); } if (cell->date() == QDate::currentDate()) { - color = color.darker(140); + color = (usingDark) ? color.lighter(200) : color.darker(140); } // Draw cell p->setPen(mMonthView->preferences()->monthGridBackgroundColor().darker(150)); p->setBrush(color); p->drawRect(QRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell), columnWidth, rowHeight)); if (mMonthView->isBusyDay(d)) { QColor busyColor = mMonthView->preferences()->viewBgBusyColor(); busyColor.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA); p->setBrush(busyColor); p->drawRect(QRect(mScene->cellHorizontalPos(cell), mScene->cellVerticalPos(cell), columnWidth, rowHeight)); } // Draw cell header int cellHeaderX = mScene->cellHorizontalPos(cell) + 1; int cellHeaderY = mScene->cellVerticalPos(cell) + 1; int cellHeaderWidth = columnWidth - 2; int cellHeaderHeight = cell->topMargin() - 2; QLinearGradient bgGradient(QPointF(cellHeaderX, cellHeaderY), QPointF(cellHeaderX + cellHeaderWidth, cellHeaderY + cellHeaderHeight)); - if ((color.blue() + color.red() + color.green()) > (256 / 2 * 3)) { + // Compute color of grid lines based on dark/lightness + if (!usingDark) { p->setBrush(color.darker(110)); } else { p->setBrush(color.lighter(140)); } bgGradient.setColorAt(1, color); p->setPen(Qt::NoPen); p->drawRect(QRect(cellHeaderX, cellHeaderY, cellHeaderWidth, cellHeaderHeight)); } font = mMonthView->preferences()->monthViewFont(); font.setPixelSize(MonthCell::topMargin() - 4); p->setFont(font); QPen oldPen; if (!mMonthView->preferences()->useSystemColor()) { oldPen = mMonthView->preferences()->monthGridBackgroundColor().darker(150); } else { oldPen = palette().color(QPalette::WindowText).darker(150); } // Draw dates for (QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays(1)) { MonthCell *cell = mScene->mMonthCellMap.value(d); QFont font = p->font(); if (cell->date() == QDate::currentDate()) { font.setBold(true); } else { font.setBold(false); } p->setFont(font); if (d.month() == mMonthView->currentMonth()) { p->setPen(palette().color(QPalette::WindowText)); } else { p->setPen(oldPen); } /* Draw arrows if all items won't fit */ // Up arrow if first item is above cell top if (mScene->startHeight() != 0 && cell->hasEventBelow(mScene->startHeight())) { cell->upArrow()->setPos( mScene->cellHorizontalPos(cell) + columnWidth / 2, mScene->cellVerticalPos(cell) + cell->upArrow()->boundingRect().height() / 2 + 2); cell->upArrow()->show(); } else { cell->upArrow()->hide(); } // Down arrow if last item is below cell bottom if (!mScene->lastItemFit(cell)) { cell->downArrow()->setPos( mScene->cellHorizontalPos(cell) + columnWidth / 2, mScene->cellVerticalPos(cell) + rowHeight -cell->downArrow()->boundingRect().height() / 2 - 2); cell->downArrow()->show(); } else { cell->downArrow()->hide(); } QString dayText; // Prepend month name if d is the first or last day of month if (d.day() == 1 || // d is the first day of month d.addDays(1).day() == 1) { // d is the last day of month dayText = i18nc("'Month day' for month view cells", "%1 %2", QLocale::system().monthName(d.month(), QLocale::ShortFormat), d.day()); } else { dayText = QString::number(d.day()); } p->drawText(QRect(mScene->cellHorizontalPos(cell), // top right mScene->cellVerticalPos(cell), // of the cell mScene->columnWidth() - 2, cell->topMargin()), Qt::AlignRight, dayText); } // ... } void MonthScene::resetAll() { qDeleteAll(mMonthCellMap); mMonthCellMap.clear(); qDeleteAll(mManagerList); mManagerList.clear(); mSelectedItem = nullptr; mActionItem = nullptr; mClickedItem = nullptr; } Akonadi::IncidenceChanger *MonthScene::incidenceChanger() const { return mMonthView->changer(); } QDate MonthScene::firstDateOnRow(int row) const { return mMonthView->actualStartDateTime().date().addDays(7 * row); } bool MonthScene::lastItemFit(MonthCell *cell) { if (cell->firstFreeSpace() > maxRowCount() + startHeight()) { return false; } else { return true; } } int MonthScene::totalHeight() { int max = 0; for (QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays(1)) { int c = mMonthCellMap[ d ]->firstFreeSpace(); if (c > max) { max = c; } } return max; } void MonthScene::wheelEvent(QGraphicsSceneWheelEvent *event) { Q_UNUSED(event); // until we figure out what to do in here /* int numDegrees = -event->delta() / 8; int numSteps = numDegrees / 15; if (startHeight() + numSteps < 0) { numSteps = -startHeight(); } int cellHeight = 0; MonthCell *currentCell = getCellFromPos(event->scenePos()); if (currentCell) { cellHeight = currentCell->firstFreeSpace(); } if (cellHeight == 0) { // no items in this cell, there's no point to scroll return; } int newHeight; int maxStartHeight = qMax(0, cellHeight - maxRowCount()); if (numSteps > 0 && startHeight() + numSteps >= maxStartHeight) { newHeight = maxStartHeight; } else { newHeight = startHeight() + numSteps; } if (newHeight == startHeight()) { return; } setStartHeight(newHeight); foreach (MonthItem *manager, mManagerList) { manager->updateGeometry(); } invalidate(QRectF(), BackgroundLayer); event->accept(); */ } void MonthScene::scrollCellsDown() { int newHeight = startHeight() + 1; setStartHeight(newHeight); for (MonthItem *manager : qAsConst(mManagerList)) { manager->updateGeometry(); } invalidate(QRectF(), BackgroundLayer); } void MonthScene::scrollCellsUp() { int newHeight = startHeight() - 1; setStartHeight(newHeight); for (MonthItem *manager : qAsConst(mManagerList)) { manager->updateGeometry(); } invalidate(QRectF(), BackgroundLayer); } void MonthScene::clickOnScrollIndicator(ScrollIndicator *scrollItem) { if (scrollItem->direction() == ScrollIndicator::UpArrow) { scrollCellsUp(); } else if (scrollItem->direction() == ScrollIndicator::DownArrow) { scrollCellsDown(); } } void MonthScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); repeatTimer.stop(); MonthGraphicsItem *iItem = dynamic_cast(itemAt(pos, {})); if (iItem) { if (iItem->monthItem()) { IncidenceMonthItem *tmp = qobject_cast(iItem->monthItem()); if (tmp) { selectItem(iItem->monthItem()); mMonthView->defaultAction(tmp->akonadiItem()); mouseEvent->accept(); } } } else { Q_EMIT newEventSignal(); } } void MonthScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); MonthGraphicsView *view = static_cast(views().at(0)); // Change cursor depending on the part of the item it hovers to inform // the user that he can resize the item. if (mActionType == None) { MonthGraphicsItem *iItem = dynamic_cast(itemAt(pos, {})); if (iItem) { if (iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene(pos).x() <= 10) { view->setActionCursor(Resize); } else if (iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene(pos).x() >= iItem->boundingRect().width() - 10) { view->setActionCursor(Resize); } else { view->setActionCursor(None); } } else { view->setActionCursor(None); } mouseEvent->accept(); return; } // If an item was selected during the click, we maybe have an item to move ! if (mActionItem) { MonthCell *currentCell = getCellFromPos(pos); // Initiate action if not already done if (!mActionInitiated && mActionType != None) { if (mActionType == Move) { mActionItem->beginMove(); } else if (mActionType == Resize) { mActionItem->beginResize(); } mActionInitiated = true; } view->setActionCursor(mActionType); // Move or resize action if (currentCell && currentCell != mPreviousCell) { bool ok = true; if (mActionType == Move) { mActionItem->moveBy(mPreviousCell->date().daysTo(currentCell->date())); } else if (mActionType == Resize) { ok = mActionItem->resizeBy(mPreviousCell->date().daysTo(currentCell->date())); } if (ok) { mPreviousCell = currentCell; } mActionItem->updateGeometry(); update(); } mouseEvent->accept(); } } void MonthScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); mClickedItem = nullptr; mCurrentIndicator = nullptr; MonthGraphicsItem *iItem = dynamic_cast(itemAt(pos, {})); if (iItem) { mClickedItem = iItem->monthItem(); selectItem(mClickedItem); if (mouseEvent->button() == Qt::RightButton) { IncidenceMonthItem *tmp = qobject_cast(mClickedItem); if (tmp) { Q_EMIT showIncidencePopupSignal(tmp->akonadiItem(), tmp->realStartDate()); } } if (mouseEvent->button() == Qt::LeftButton) { // Basic initialization for resize and move mActionItem = mClickedItem; mStartCell = getCellFromPos(pos); mPreviousCell = mStartCell; mActionInitiated = false; // Move or resize ? if (iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene(pos).x() <= 10) { mActionType = Resize; mResizeType = ResizeLeft; } else if (iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene(pos).x() >= iItem->boundingRect().width() - 10) { mActionType = Resize; mResizeType = ResizeRight; } else if (iItem->monthItem()->isMoveable()) { mActionType = Move; } } mouseEvent->accept(); } else if (ScrollIndicator *scrollItem = dynamic_cast(itemAt(pos, {}))) { clickOnScrollIndicator(scrollItem); mCurrentIndicator = scrollItem; repeatTimer.start(AUTO_REPEAT_DELAY, this); } else { // unselect items when clicking somewhere else selectItem(nullptr); MonthCell *cell = getCellFromPos(pos); if (cell) { mSelectedCellDate = cell->date(); update(); if (mouseEvent->button() == Qt::RightButton) { Q_EMIT showNewEventPopupSignal(); } mouseEvent->accept(); } } } void MonthScene::timerEvent(QTimerEvent *e) { if (e->timerId() == repeatTimer.timerId()) { if (mCurrentIndicator->isVisible()) { clickOnScrollIndicator(mCurrentIndicator); repeatTimer.start(AUTO_REPEAT_DELAY, this); } else { mCurrentIndicator = nullptr; repeatTimer.stop(); } } } void MonthScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent) { // Find the first item that does tooltips const QPointF pos = helpEvent->scenePos(); MonthGraphicsItem *toolTipItem = dynamic_cast(itemAt(pos, {})); // Show or hide the tooltip QString text; QPoint point; if (toolTipItem) { text = toolTipItem->getToolTip(); point = helpEvent->screenPos(); } QToolTip::showText(point, text, helpEvent->widget()); helpEvent->setAccepted(!text.isEmpty()); } void MonthScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { QPointF pos = mouseEvent->scenePos(); static_cast(views().at(0))->setActionCursor(None); repeatTimer.stop(); mCurrentIndicator = nullptr; if (mActionItem) { MonthCell *currentCell = getCellFromPos(pos); const bool somethingChanged = currentCell && currentCell != mStartCell; if (somethingChanged) { // We want to act if a move really happened if (mActionType == Resize) { mActionItem->endResize(); } else if (mActionType == Move) { mActionItem->endMove(); } } mActionItem = nullptr; mActionType = None; mStartCell = nullptr; mouseEvent->accept(); } } // returns true if the point is in the monthgrid (allows to avoid selecting a cell when // a click is outside the month grid bool MonthScene::isInMonthGrid(int x, int y) const { return x >= 0 && y >= 0 && x <= availableWidth() && y <= availableHeight(); } // The function converts the coordinates to the month grid coordinates to // be able to locate the cell. MonthCell *MonthScene::getCellFromPos(const QPointF &pos) { int y = sceneYToMonthGridY(static_cast(pos.y())); int x = sceneXToMonthGridX(static_cast(pos.x())); if (!isInMonthGrid(x, y)) { return nullptr; } int id = (int)(y / rowHeight()) * 7 + (int)(x / columnWidth()); return mMonthCellMap.value(mMonthView->actualStartDateTime().date().addDays(id)); } void MonthScene::selectItem(MonthItem *item) { /* if (mSelectedItem == item) { return; } I commented the above code so it's possible to selected a selected item. korg-mobile needs that, otherwise clicking on a selected item wont bring the editor up. Another solution would be to have two Q_SIGNALS: incidenceSelected() and incidenceClicked() */ IncidenceMonthItem *tmp = qobject_cast(item); if (!tmp) { mSelectedItem = nullptr; Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); return; } mSelectedItem = item; Q_ASSERT(CalendarSupport::hasIncidence(tmp->akonadiItem())); if (mMonthView->selectedIncidenceDates().isEmpty()) { Q_EMIT incidenceSelected(tmp->akonadiItem(), QDate()); } else { Q_EMIT incidenceSelected(tmp->akonadiItem(), mMonthView->selectedIncidenceDates().at(0)); } update(); } void MonthScene::removeIncidence(const QString &uid) { for (MonthItem *manager : qAsConst(mManagerList)) { IncidenceMonthItem *imi = qobject_cast(manager); if (!imi) { continue; } KCalendarCore::Incidence::Ptr incidence = imi->incidence(); if (!incidence) { continue; } if (incidence->uid() == uid) { const auto lst = imi->monthGraphicsItems(); for (MonthGraphicsItem *gitem : lst) { removeItem(gitem); } } } } //---------------------------------------------------------- MonthGraphicsView::MonthGraphicsView(MonthView *parent) : QGraphicsView(parent) , mMonthView(parent) { setMouseTracking(true); } void MonthGraphicsView::setActionCursor(MonthScene::ActionType actionType) { switch (actionType) { case MonthScene::Move: #ifndef QT_NO_CURSOR setCursor(Qt::ArrowCursor); #endif break; case MonthScene::Resize: #ifndef QT_NO_CURSOR setCursor(Qt::SizeHorCursor); #endif break; #ifndef QT_NO_CURSOR default: setCursor(Qt::ArrowCursor); #endif } } void MonthGraphicsView::setScene(MonthScene *scene) { mScene = scene; QGraphicsView::setScene(scene); } void MonthGraphicsView::resizeEvent(QResizeEvent *event) { mScene->setSceneRect(0, 0, event->size().width(), event->size().height()); mScene->updateGeometry(); }