File indexing completed on 2024-04-28 03:58:59

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kcapacitybar.h"
0009 #include "kstyleextensions.h"
0010 
0011 #include <math.h>
0012 
0013 #include <QLinearGradient>
0014 #include <QPaintEvent>
0015 #include <QPainter>
0016 #include <QPainterPath>
0017 #include <QStyle>
0018 #include <QStyleOptionProgressBar>
0019 
0020 #define ROUND_MARGIN 6
0021 #define VERTICAL_SPACING 1
0022 
0023 static const int LightShade = 100;
0024 static const int MidShade = 200;
0025 static const int DarkShade = 300;
0026 
0027 class KCapacityBarPrivate
0028 {
0029 public:
0030     KCapacityBarPrivate(KCapacityBar::DrawTextMode drawTextMode)
0031         : drawTextMode(drawTextMode)
0032     {
0033     }
0034 
0035     QString text;
0036     int value = 0;
0037     bool fillFullBlocks = true;
0038     bool continuous = true;
0039     int barHeight = 12;
0040     Qt::Alignment horizontalTextAlignment = Qt::AlignCenter;
0041     QStyle::ControlElement ce_capacityBar = QStyle::ControlElement(0);
0042 
0043     KCapacityBar::DrawTextMode drawTextMode;
0044 };
0045 
0046 KCapacityBar::KCapacityBar(QWidget *parent)
0047     : KCapacityBar(DrawTextOutline, parent)
0048 {
0049 }
0050 
0051 KCapacityBar::KCapacityBar(KCapacityBar::DrawTextMode drawTextMode, QWidget *parent)
0052     : QWidget(parent)
0053     , d(new KCapacityBarPrivate(drawTextMode))
0054 {
0055     d->ce_capacityBar = KStyleExtensions::customControlElement(QStringLiteral("CE_CapacityBar"), this);
0056 }
0057 
0058 KCapacityBar::~KCapacityBar() = default;
0059 
0060 void KCapacityBar::setValue(int value)
0061 {
0062     d->value = value;
0063     update();
0064 }
0065 
0066 int KCapacityBar::value() const
0067 {
0068     return d->value;
0069 }
0070 
0071 void KCapacityBar::setText(const QString &text)
0072 {
0073     bool updateGeom = d->text.isEmpty() || text.isEmpty();
0074     d->text = text;
0075     if (updateGeom) {
0076         updateGeometry();
0077     }
0078 
0079 #ifndef QT_NO_ACCESSIBILITY
0080     setAccessibleName(text);
0081 #endif
0082 
0083     update();
0084 }
0085 
0086 QString KCapacityBar::text() const
0087 {
0088     return d->text;
0089 }
0090 
0091 void KCapacityBar::setFillFullBlocks(bool fillFullBlocks)
0092 {
0093     d->fillFullBlocks = fillFullBlocks;
0094     update();
0095 }
0096 
0097 bool KCapacityBar::fillFullBlocks() const
0098 {
0099     return d->fillFullBlocks;
0100 }
0101 
0102 void KCapacityBar::setContinuous(bool continuous)
0103 {
0104     d->continuous = continuous;
0105     update();
0106 }
0107 
0108 bool KCapacityBar::continuous() const
0109 {
0110     return d->continuous;
0111 }
0112 
0113 void KCapacityBar::setBarHeight(int barHeight)
0114 {
0115     // automatically convert odd values to even. This will make the bar look
0116     // better.
0117     d->barHeight = (barHeight % 2) ? barHeight + 1 : barHeight;
0118     updateGeometry();
0119 }
0120 
0121 int KCapacityBar::barHeight() const
0122 {
0123     return d->barHeight;
0124 }
0125 
0126 void KCapacityBar::setHorizontalTextAlignment(Qt::Alignment horizontalTextAlignment)
0127 {
0128     Qt::Alignment alignment = horizontalTextAlignment;
0129 
0130     // if the value came with any vertical alignment flag, remove it.
0131     alignment &= ~Qt::AlignTop;
0132     alignment &= ~Qt::AlignBottom;
0133     alignment &= ~Qt::AlignVCenter;
0134 
0135     d->horizontalTextAlignment = alignment;
0136     update();
0137 }
0138 
0139 Qt::Alignment KCapacityBar::horizontalTextAlignment() const
0140 {
0141     return d->horizontalTextAlignment;
0142 }
0143 
0144 void KCapacityBar::setDrawTextMode(DrawTextMode mode)
0145 {
0146     d->drawTextMode = mode;
0147     update();
0148 }
0149 
0150 KCapacityBar::DrawTextMode KCapacityBar::drawTextMode() const
0151 {
0152     return d->drawTextMode;
0153 }
0154 
0155 void KCapacityBar::drawCapacityBar(QPainter *p, const QRect &rect) const
0156 {
0157     if (d->ce_capacityBar) {
0158         QStyleOptionProgressBar opt;
0159         opt.initFrom(this);
0160         opt.rect = rect;
0161         opt.minimum = 0;
0162         opt.maximum = 100;
0163         opt.progress = d->value;
0164         opt.state |= QStyle::State_Horizontal;
0165         opt.text = d->text;
0166         opt.textAlignment = Qt::AlignCenter;
0167         opt.textVisible = !d->text.isEmpty();
0168         style()->drawControl(d->ce_capacityBar, &opt, p, this);
0169 
0170         return;
0171     }
0172 
0173     p->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
0174 
0175     p->save();
0176 
0177     QRect drawRect(rect);
0178 
0179     if (d->drawTextMode == DrawTextOutline) {
0180         drawRect.setHeight(d->barHeight);
0181     }
0182 
0183     QPainterPath outline;
0184     outline.moveTo(rect.left() + ROUND_MARGIN / 4 + 1, rect.top());
0185     outline.lineTo(rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1, rect.top());
0186     outline.quadTo(rect.left() + drawRect.width() + ROUND_MARGIN / 2,
0187                    drawRect.height() / 2 + rect.top(),
0188                    rect.left() + drawRect.width() - ROUND_MARGIN / 4 - 1,
0189                    drawRect.height() + rect.top());
0190     outline.lineTo(rect.left() + ROUND_MARGIN / 4 + 1, drawRect.height() + rect.top());
0191     outline.quadTo(-ROUND_MARGIN / 2 + rect.left(), drawRect.height() / 2 + rect.top(), rect.left() + ROUND_MARGIN / 4 + 1, rect.top());
0192     const QColor fillColor = palette().window().color().darker(DarkShade);
0193     p->fillPath(outline, QColor(fillColor.red(), fillColor.green(), fillColor.blue(), 50));
0194 
0195     QRadialGradient bottomGradient(QPointF(rect.width() / 2, drawRect.bottom() + 1), rect.width() / 2);
0196     bottomGradient.setColorAt(0, palette().window().color().darker(LightShade));
0197     bottomGradient.setColorAt(1, Qt::transparent);
0198     p->fillRect(QRect(rect.left(), drawRect.bottom() + rect.top(), rect.width(), 1), bottomGradient);
0199 
0200     p->translate(rect.left() + 2, rect.top() + 1);
0201 
0202     drawRect.setWidth(drawRect.width() - 4);
0203     drawRect.setHeight(drawRect.height() - 2);
0204 
0205     QPainterPath path;
0206     path.moveTo(ROUND_MARGIN / 4, 0);
0207     path.lineTo(drawRect.width() - ROUND_MARGIN / 4, 0);
0208     path.quadTo(drawRect.width() + ROUND_MARGIN / 2, drawRect.height() / 2, drawRect.width() - ROUND_MARGIN / 4, drawRect.height());
0209     path.lineTo(ROUND_MARGIN / 4, drawRect.height());
0210     path.quadTo(-ROUND_MARGIN / 2, drawRect.height() / 2, ROUND_MARGIN / 4, 0);
0211 
0212     QLinearGradient linearGradient(0, 0, 0, drawRect.height());
0213     linearGradient.setColorAt(0.5, palette().window().color().darker(MidShade));
0214     linearGradient.setColorAt(1, palette().window().color().darker(LightShade));
0215     p->fillPath(path, linearGradient);
0216 
0217     p->setBrush(Qt::NoBrush);
0218     p->setPen(Qt::NoPen);
0219 
0220     if (d->continuous || !d->fillFullBlocks) {
0221         int start = (layoutDirection() == Qt::LeftToRight) ? -1 : (drawRect.width() + 2) - (drawRect.width() + 2) * (d->value / 100.0);
0222 
0223         p->setClipRect(QRect(start, 0, (drawRect.width() + 2) * (d->value / 100.0), drawRect.height()), Qt::IntersectClip);
0224     }
0225 
0226     int left = (layoutDirection() == Qt::LeftToRight) ? 0 : drawRect.width();
0227 
0228     int right = (layoutDirection() == Qt::LeftToRight) ? drawRect.width() : 0;
0229 
0230     int roundMargin = (layoutDirection() == Qt::LeftToRight) ? ROUND_MARGIN : -ROUND_MARGIN;
0231 
0232     int spacing = 2;
0233     int verticalSpacing = VERTICAL_SPACING;
0234     int slotWidth = 6;
0235     int start = roundMargin / 4;
0236 
0237     QPainterPath internalBar;
0238     internalBar.moveTo(left + roundMargin / 4, 0);
0239     internalBar.lineTo(right - roundMargin / 4, 0);
0240     internalBar.quadTo(right + roundMargin / 2, drawRect.height() / 2, right - roundMargin / 4, drawRect.height());
0241     internalBar.lineTo(left + roundMargin / 4, drawRect.height());
0242     internalBar.quadTo(left - roundMargin / 2, drawRect.height() / 2, left + roundMargin / 4, 0);
0243 
0244     QLinearGradient fillInternalBar(left, 0, right, 0);
0245     fillInternalBar.setColorAt(0, palette().window().color().darker(MidShade));
0246     fillInternalBar.setColorAt(0.5, palette().window().color().darker(LightShade));
0247     fillInternalBar.setColorAt(1, palette().window().color().darker(MidShade));
0248 
0249     if (d->drawTextMode == KCapacityBar::DrawTextInline) {
0250         p->save();
0251         p->setOpacity(p->opacity() * 0.7);
0252     }
0253 
0254     if (!d->continuous) {
0255         int numSlots = (drawRect.width() - ROUND_MARGIN - ((slotWidth + spacing) * 2)) / (slotWidth + spacing);
0256         int stopSlot = floor((numSlots + 2) * (d->value / 100.0));
0257 
0258         int plusOffset = d->fillFullBlocks ? ((drawRect.width() - ROUND_MARGIN - ((slotWidth + spacing) * 2)) - (numSlots * (slotWidth + spacing))) / 2.0 : 0;
0259 
0260         if (!d->fillFullBlocks || stopSlot) {
0261             QPainterPath firstSlot;
0262             firstSlot.moveTo(left + roundMargin / 4, verticalSpacing);
0263             firstSlot.lineTo(left + slotWidth + roundMargin / 4 + plusOffset, verticalSpacing);
0264             firstSlot.lineTo(left + slotWidth + roundMargin / 4 + plusOffset, drawRect.height() - verticalSpacing);
0265             firstSlot.lineTo(left + roundMargin / 4, drawRect.height() - verticalSpacing);
0266             firstSlot.quadTo(left, drawRect.height() / 2, left + roundMargin / 4, verticalSpacing);
0267             p->fillPath(firstSlot, fillInternalBar);
0268             start += slotWidth + spacing + plusOffset;
0269 
0270             bool stopped = false;
0271             for (int i = 0; i < numSlots + 1; i++) {
0272                 if (d->fillFullBlocks && (i == (stopSlot + 1))) {
0273                     stopped = true;
0274                     break;
0275                 }
0276                 p->fillRect(QRect(rect.left() + start, rect.top() + verticalSpacing, slotWidth, drawRect.height() - verticalSpacing * 2), fillInternalBar);
0277                 start += slotWidth + spacing;
0278             }
0279 
0280             if (!d->fillFullBlocks || (!stopped && (stopSlot != (numSlots + 1)) && (stopSlot != numSlots))) {
0281                 QPainterPath lastSlot;
0282                 lastSlot.moveTo(start, verticalSpacing);
0283                 lastSlot.lineTo(start, drawRect.height() - verticalSpacing);
0284                 lastSlot.lineTo(start + slotWidth + plusOffset, drawRect.height() - verticalSpacing);
0285                 lastSlot.quadTo(start + roundMargin, drawRect.height() / 2, start + slotWidth + plusOffset, verticalSpacing);
0286                 lastSlot.lineTo(start, verticalSpacing);
0287                 p->fillPath(lastSlot, fillInternalBar);
0288             }
0289         }
0290     } else {
0291         p->fillPath(internalBar, fillInternalBar);
0292     }
0293 
0294     if (d->drawTextMode == KCapacityBar::DrawTextInline) {
0295         p->restore();
0296     }
0297 
0298     p->save();
0299     p->setClipping(false);
0300     QRadialGradient topGradient(QPointF(rect.width() / 2, drawRect.top()), rect.width() / 2);
0301     const QColor fillTopColor = palette().window().color().darker(LightShade);
0302     topGradient.setColorAt(0, QColor(fillTopColor.red(), fillTopColor.green(), fillTopColor.blue(), 127));
0303     topGradient.setColorAt(1, Qt::transparent);
0304     p->fillRect(QRect(rect.left(), rect.top() + drawRect.top(), rect.width(), 2), topGradient);
0305     p->restore();
0306 
0307     p->save();
0308     p->setClipRect(QRect(-1, 0, rect.width(), drawRect.height() / 2), Qt::ReplaceClip);
0309     QLinearGradient glassGradient(0, -5, 0, drawRect.height());
0310     const QColor fillGlassColor = palette().base().color();
0311     glassGradient.setColorAt(0, QColor(fillGlassColor.red(), fillGlassColor.green(), fillGlassColor.blue(), 255));
0312     glassGradient.setColorAt(1, Qt::transparent);
0313     p->fillPath(internalBar, glassGradient);
0314     p->restore();
0315 
0316     p->restore();
0317 
0318     if (d->drawTextMode == KCapacityBar::DrawTextInline) {
0319         QRect rect(drawRect);
0320         rect.setHeight(rect.height() + 4);
0321         p->drawText(rect, Qt::AlignCenter, fontMetrics().elidedText(d->text, Qt::ElideRight, drawRect.width() - 2 * ROUND_MARGIN));
0322     } else {
0323         p->drawText(rect, Qt::AlignBottom | d->horizontalTextAlignment, fontMetrics().elidedText(d->text, Qt::ElideRight, drawRect.width()));
0324     }
0325 }
0326 
0327 QSize KCapacityBar::minimumSizeHint() const
0328 {
0329     int width = fontMetrics().boundingRect(d->text).width() + ((d->drawTextMode == KCapacityBar::DrawTextInline) ? ROUND_MARGIN * 2 : 0);
0330 
0331     int height = (d->drawTextMode == KCapacityBar::DrawTextInline) ? qMax(fontMetrics().height(), d->barHeight)
0332                                                                    : (d->text.isEmpty() ? 0 : fontMetrics().height() + VERTICAL_SPACING * 2) + d->barHeight;
0333 
0334     if (height % 2) {
0335         height++;
0336     }
0337 
0338     return QSize(width, height);
0339 }
0340 
0341 void KCapacityBar::paintEvent(QPaintEvent *event)
0342 {
0343     QPainter p(this);
0344     p.setClipRect(event->rect());
0345     drawCapacityBar(&p, contentsRect());
0346     p.end();
0347 }
0348 
0349 void KCapacityBar::changeEvent(QEvent *event)
0350 {
0351     QWidget::changeEvent(event);
0352     if (event->type() == QEvent::StyleChange) {
0353         d->ce_capacityBar = KStyleExtensions::customControlElement(QStringLiteral("CE_CapacityBar"), this);
0354     }
0355 }
0356 
0357 #include "moc_kcapacitybar.cpp"