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"