File indexing completed on 2024-06-09 05:08:36

0001 /*
0002   SPDX-FileCopyrightText: 2008 Bruno Virlet <bruno.virlet@gmail.com>
0003   SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at>
0004 
0005   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0006 */
0007 
0008 #include "monthgraphicsitems.h"
0009 #include "eventview.h"
0010 #include "helper.h"
0011 #include "monthitem.h"
0012 #include "monthscene.h"
0013 #include "monthview.h"
0014 #include "prefs.h"
0015 
0016 #include <QGraphicsScene>
0017 #include <QPainter>
0018 
0019 using namespace EventViews;
0020 
0021 ScrollIndicator::ScrollIndicator(ScrollIndicator::ArrowDirection dir)
0022     : mDirection(dir)
0023 {
0024     setZValue(200); // on top of everything
0025     hide();
0026 }
0027 
0028 QRectF ScrollIndicator::boundingRect() const
0029 {
0030     return {-mWidth / 2, -mHeight / 2, mWidth, mHeight};
0031 }
0032 
0033 void ScrollIndicator::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
0034 {
0035     Q_UNUSED(option)
0036     Q_UNUSED(widget)
0037 
0038     painter->setRenderHint(QPainter::Antialiasing);
0039 
0040     QPolygon arrow(3);
0041     if (mDirection == ScrollIndicator::UpArrow) {
0042         arrow.setPoint(0, 0, -mHeight / 2);
0043         arrow.setPoint(1, mWidth / 2, mHeight / 2);
0044         arrow.setPoint(2, -mWidth / 2, mHeight / 2);
0045     } else if (mDirection == ScrollIndicator::DownArrow) { // down
0046         arrow.setPoint(1, mWidth / 2, -mHeight / 2);
0047         arrow.setPoint(2, -mWidth / 2, -mHeight / 2);
0048         arrow.setPoint(0, 0, mHeight / 2);
0049     }
0050     QColor color(QPalette().color(QPalette::WindowText));
0051     color.setAlpha(155);
0052     painter->setBrush(color);
0053     painter->setPen(color);
0054     painter->drawPolygon(arrow);
0055 }
0056 
0057 //-------------------------------------------------------------
0058 MonthCell::MonthCell(int id, QDate date, QGraphicsScene *scene)
0059     : mId(id)
0060     , mDate(date)
0061     , mScene(scene)
0062 {
0063     mUpArrow = new ScrollIndicator(ScrollIndicator::UpArrow);
0064     mDownArrow = new ScrollIndicator(ScrollIndicator::DownArrow);
0065     mScene->addItem(mUpArrow);
0066     mScene->addItem(mDownArrow);
0067 }
0068 
0069 MonthCell::~MonthCell()
0070 {
0071     mScene->removeItem(mUpArrow);
0072     mScene->removeItem(mDownArrow);
0073     delete mUpArrow; // we've taken ownership, so this is safe
0074     delete mDownArrow;
0075 }
0076 
0077 bool MonthCell::hasEventBelow(int height)
0078 {
0079     if (mHeightHash.isEmpty()) {
0080         return false;
0081     }
0082 
0083     for (int i = 0; i < height; ++i) {
0084         if (mHeightHash.value(i) != nullptr) {
0085             return true;
0086         }
0087     }
0088 
0089     return false;
0090 }
0091 
0092 int MonthCell::topMargin()
0093 {
0094     return 18;
0095 }
0096 
0097 void MonthCell::addMonthItem(MonthItem *manager, int height)
0098 {
0099     mHeightHash[height] = manager;
0100 }
0101 
0102 int MonthCell::firstFreeSpace()
0103 {
0104     MonthItem *manager = nullptr;
0105     int i = 0;
0106     while (true) {
0107         manager = mHeightHash[i];
0108         if (manager == nullptr) {
0109             return i;
0110         }
0111         i++;
0112     }
0113 }
0114 
0115 //-------------------------------------------------------------
0116 // MONTHGRAPHICSITEM
0117 static const int ft = 1; // frame thickness
0118 
0119 MonthGraphicsItem::MonthGraphicsItem(MonthItem *manager)
0120     : QGraphicsItem(nullptr)
0121     , mMonthItem(manager)
0122 {
0123     manager->monthScene()->addItem(this);
0124     QTransform transform;
0125     transform = transform.translate(0.5, 0.5);
0126     setTransform(transform);
0127 }
0128 
0129 MonthGraphicsItem::~MonthGraphicsItem() = default;
0130 
0131 bool MonthGraphicsItem::isMoving() const
0132 {
0133     return mMonthItem->isMoving();
0134 }
0135 
0136 bool MonthGraphicsItem::isEndItem() const
0137 {
0138     return startDate().addDays(daySpan()) == mMonthItem->endDate();
0139 }
0140 
0141 bool MonthGraphicsItem::isBeginItem() const
0142 {
0143     return startDate() == mMonthItem->startDate();
0144 }
0145 
0146 QPainterPath MonthGraphicsItem::shape() const
0147 {
0148     // The returned shape must be a closed path,
0149     // otherwise MonthScene:itemAt(pos) can have
0150     // problems detecting the item
0151     return widgetPath(false);
0152 }
0153 
0154 // TODO: remove this method.
0155 QPainterPath MonthGraphicsItem::widgetPath(bool border) const
0156 {
0157     // If border is set we won't draw all the path. Items spanning on multiple
0158     // rows won't have borders on their boundaries.
0159     // If this is the mask, we draw it one pixel bigger
0160     const int x0 = (!border && !isBeginItem()) ? -1 : 0;
0161     const int y0 = 0;
0162     const int x1 = static_cast<int>(boundingRect().width());
0163     const int y1 = static_cast<int>(boundingRect().height());
0164 
0165     const int beginRound = 2;
0166     const int margin = 1;
0167 
0168     QPainterPath path(QPoint(x0 + beginRound, y0));
0169     if (isBeginItem()) {
0170         path.quadTo(QPoint(x0 + margin, y0), QPoint(x0 + margin, y0 + beginRound));
0171         path.lineTo(QPoint(x0 + margin, y1 - beginRound));
0172         path.quadTo(QPoint(x0 + margin, y1), QPoint(x0 + beginRound + margin, y1));
0173     } else {
0174         path.lineTo(x0, y0);
0175         if (!border) {
0176             path.lineTo(x0, y1);
0177         } else {
0178             path.moveTo(x0, y1);
0179         }
0180         path.lineTo(x0 + beginRound, y1);
0181     }
0182 
0183     if (isEndItem()) {
0184         path.lineTo(x1 - beginRound, y1);
0185         path.quadTo(QPoint(x1 - margin, y1), QPoint(x1 - margin, y1 - beginRound));
0186         path.lineTo(QPoint(x1 - margin, y0 + beginRound));
0187         path.quadTo(QPoint(x1 - margin, y0), QPoint(x1 - margin - beginRound, y0));
0188     } else {
0189         path.lineTo(x1, y1);
0190         if (!border) {
0191             path.lineTo(x1, y0);
0192         } else {
0193             path.moveTo(x1, y0);
0194         }
0195     }
0196 
0197     // close path
0198     path.lineTo(x0 + beginRound, y0);
0199 
0200     return path;
0201 }
0202 
0203 QRectF MonthGraphicsItem::boundingRect() const
0204 {
0205     // width - 2 because of the cell-dividing line with width == 1 at beginning and end
0206     return QRectF(0, 0, (daySpan() + 1) * mMonthItem->monthScene()->columnWidth() - 2, mMonthItem->monthScene()->itemHeight());
0207 }
0208 
0209 void MonthGraphicsItem::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
0210 {
0211     if (!mMonthItem->monthScene()->initialized()) {
0212         return;
0213     }
0214 
0215     MonthScene *scene = mMonthItem->monthScene();
0216 
0217     int textMargin = 7;
0218 
0219     QColor bgColor = mMonthItem->bgColor();
0220     bgColor = mMonthItem->selected() ? bgColor.lighter(EventView::BRIGHTNESS_FACTOR) : bgColor;
0221     QColor frameColor = mMonthItem->frameColor();
0222     frameColor = mMonthItem->selected() ? frameColor.lighter(EventView::BRIGHTNESS_FACTOR) : frameColor;
0223     QColor textColor = EventViews::getTextColor(bgColor);
0224 
0225     // make moving or resizing items translucent
0226     if (mMonthItem->isMoving() || mMonthItem->isResizing()) {
0227         bgColor.setAlphaF(0.75f);
0228     }
0229 
0230     // draw the widget without border
0231     p->setRenderHint(QPainter::Antialiasing, false);
0232     p->setBrush(bgColor);
0233     p->setPen(Qt::NoPen);
0234     p->drawPath(widgetPath(false));
0235 
0236     p->setRenderHint(QPainter::Antialiasing, true);
0237     // draw the border without fill
0238     const QPen pen(frameColor, ft, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
0239     p->setPen(pen);
0240     p->setBrush(Qt::NoBrush);
0241     p->drawPath(widgetPath(true));
0242 
0243     // draw text
0244     p->setPen(textColor);
0245 
0246     int alignFlag = Qt::AlignVCenter;
0247     if (isBeginItem()) {
0248         alignFlag |= Qt::AlignLeft;
0249     } else if (isEndItem()) {
0250         alignFlag |= Qt::AlignRight;
0251     } else {
0252         alignFlag |= Qt::AlignHCenter;
0253     }
0254 
0255     // !isBeginItem() is not always isEndItem()
0256     QString text = mMonthItem->text(!isBeginItem());
0257     p->setFont(mMonthItem->monthScene()->monthView()->preferences()->monthViewFont());
0258 
0259     // Every item should set its own LayoutDirection, or eliding fails miserably
0260     p->setLayoutDirection(text.isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight);
0261 
0262     QRect textRect = QRect(textMargin, 0, static_cast<int>(boundingRect().width() - 2 * textMargin), scene->itemHeight());
0263 
0264     if (mMonthItem->monthScene()->monthView()->preferences()->enableMonthItemIcons()) {
0265         const QList<QPixmap> icons = mMonthItem->icons();
0266         int iconWidths = 0;
0267 
0268         for (const QPixmap &icon : icons) {
0269             iconWidths += icon.width();
0270         }
0271 
0272         if (!icons.isEmpty()) {
0273             // add some margin between the icons and the text
0274             iconWidths += textMargin / 2;
0275         }
0276 
0277         int textWidth = p->fontMetrics().size(0, text).width();
0278         if (textWidth + iconWidths > textRect.width()) {
0279             textWidth = textRect.width() - iconWidths;
0280             text = p->fontMetrics().elidedText(text, Qt::ElideRight, textWidth);
0281         }
0282 
0283         int curXPos = textRect.left();
0284         if (alignFlag & Qt::AlignRight) {
0285             curXPos += textRect.width() - textWidth - iconWidths;
0286         } else if (alignFlag & Qt::AlignHCenter) {
0287             curXPos += (textRect.width() - textWidth - iconWidths) / 2;
0288         }
0289         alignFlag &= ~(Qt::AlignRight | Qt::AlignCenter);
0290         alignFlag |= Qt::AlignLeft;
0291 
0292         // update the rect, where the text will be displayed
0293         textRect.setLeft(curXPos + iconWidths);
0294 
0295         // assume that all pixmaps have the same height
0296         int pixYPos = icons.isEmpty() ? 0 : (textRect.height() - icons.at(0).height()) / 2;
0297         for (const QPixmap &icon : std::as_const(icons)) {
0298             p->drawPixmap(curXPos, pixYPos, icon);
0299             curXPos += icon.width();
0300         }
0301 
0302         p->drawText(textRect, alignFlag | Qt::AlignVCenter, text);
0303     } else {
0304         text = p->fontMetrics().elidedText(text, Qt::ElideRight, textRect.width());
0305         p->drawText(textRect, alignFlag, text);
0306     }
0307 }
0308 
0309 void MonthGraphicsItem::setStartDate(QDate date)
0310 {
0311     mStartDate = date;
0312 }
0313 
0314 QDate MonthGraphicsItem::endDate() const
0315 {
0316     return startDate().addDays(daySpan());
0317 }
0318 
0319 QDate MonthGraphicsItem::startDate() const
0320 {
0321     return mStartDate;
0322 }
0323 
0324 void MonthGraphicsItem::setDaySpan(int span)
0325 {
0326     mDaySpan = span;
0327 }
0328 
0329 int MonthGraphicsItem::daySpan() const
0330 {
0331     return mDaySpan;
0332 }
0333 
0334 void MonthGraphicsItem::updateGeometry()
0335 {
0336     MonthCell *cell = mMonthItem->monthScene()->mMonthCellMap.value(startDate());
0337 
0338     // If the item is moving and this one is moved outside the view,
0339     // cell will be null
0340     if (mMonthItem->isMoving() && !cell) {
0341         hide();
0342         return;
0343     }
0344 
0345     Q_ASSERT(cell);
0346 
0347     prepareGeometryChange();
0348 
0349     int beginX = 1 + mMonthItem->monthScene()->cellHorizontalPos(cell);
0350     int beginY = 1 + cell->topMargin() + mMonthItem->monthScene()->cellVerticalPos(cell);
0351 
0352     beginY += mMonthItem->position() * mMonthItem->monthScene()->itemHeightIncludingSpacing()
0353         - mMonthItem->monthScene()->startHeight() * mMonthItem->monthScene()->itemHeightIncludingSpacing(); // scrolling
0354 
0355     setPos(beginX, beginY);
0356 
0357     if (mMonthItem->position() < mMonthItem->monthScene()->startHeight()
0358         || mMonthItem->position() - mMonthItem->monthScene()->startHeight() >= mMonthItem->monthScene()->maxRowCount()) {
0359         hide();
0360     } else {
0361         show();
0362         update();
0363     }
0364 }
0365 
0366 QString MonthGraphicsItem::getToolTip() const
0367 {
0368     return mMonthItem->toolTipText(mStartDate);
0369 }
0370 
0371 #include "moc_monthgraphicsitems.cpp"