File indexing completed on 2024-04-21 04:58:21

0001 /*
0002     SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "konqstatusbarmessagelabel.h"
0008 #include <QStyle>
0009 #include <QTextDocument>
0010 
0011 #include <kcolorscheme.h>
0012 #include <QIcon>
0013 #include <KLocalizedString>
0014 #include "konqdebug.h"
0015 
0016 #include <QFontMetrics>
0017 #include <QPainter>
0018 #include <QPixmap>
0019 #include <QToolButton>
0020 #include <QTimer>
0021 
0022 enum { GeometryTimeout = 100 };
0023 enum { BorderGap = 2 };
0024 
0025 class KonqStatusBarMessageLabel::Private
0026 {
0027 public:
0028     Private() :
0029         m_type(Default),
0030         m_state(DefaultState),
0031         m_illumination(0),
0032         m_minTextHeight(-1),
0033         m_timer(nullptr),
0034         m_closeButton(nullptr)
0035     {}
0036 
0037     bool isRichText() const
0038     {
0039         return m_text.startsWith(QLatin1String("<html>")) || m_text.startsWith(QLatin1String("<qt>"));
0040     }
0041 
0042     KonqStatusBarMessageLabel::Type m_type;
0043     KonqStatusBarMessageLabel::State m_state;
0044     int m_illumination;
0045     int m_minTextHeight;
0046     QTimer *m_timer;
0047     QString m_text;
0048     QString m_defaultText;
0049     QTextDocument m_textDocument;
0050     QList<QString> m_pendingMessages;
0051     QPixmap m_pixmap;
0052     QToolButton *m_closeButton;
0053 };
0054 
0055 KonqStatusBarMessageLabel::KonqStatusBarMessageLabel(QWidget *parent) :
0056     QWidget(parent), d(new Private)
0057 {
0058     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum /*the sizeHint is the max*/);
0059 
0060     d->m_timer = new QTimer(this);
0061     connect(d->m_timer, &QTimer::timeout,
0062             this, &KonqStatusBarMessageLabel::timerDone);
0063 
0064     d->m_closeButton = new QToolButton(this);
0065     d->m_closeButton->setAutoRaise(true);
0066     d->m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
0067     d->m_closeButton->setToolTip(i18nc("@info", "Close"));
0068     d->m_closeButton->setAccessibleName(i18n("Close"));
0069     d->m_closeButton->hide();
0070     connect(d->m_closeButton, &QToolButton::clicked,
0071             this, &KonqStatusBarMessageLabel::closeErrorMessage);
0072 }
0073 
0074 KonqStatusBarMessageLabel::~KonqStatusBarMessageLabel()
0075 {
0076     delete d;
0077 }
0078 
0079 void KonqStatusBarMessageLabel::setMessage(const QString &text,
0080         Type type)
0081 {
0082     if ((text == d->m_text) && (type == d->m_type)) {
0083         return;
0084     }
0085 
0086     if (d->m_type == Error) {
0087         if (type == Error) {
0088             d->m_pendingMessages.insert(0, d->m_text);
0089         } else if ((d->m_state != DefaultState) || !d->m_pendingMessages.isEmpty()) {
0090             // a non-error message should not be shown, as there
0091             // are other pending error messages in the queue
0092             return;
0093         }
0094     }
0095 
0096     d->m_text = text;
0097     d->m_type = type;
0098 
0099     if (d->isRichText()) {
0100         d->m_textDocument.setTextWidth(-1);
0101         d->m_textDocument.setDefaultFont(font());
0102         QString html = QStringLiteral("<html><font color=\"");
0103         html += palette().windowText().color().name();
0104         html += QLatin1String("\">");
0105         html += d->m_text;
0106         d->m_textDocument.setHtml(html);
0107     }
0108 
0109     d->m_timer->stop();
0110     d->m_illumination = 0;
0111     d->m_state = DefaultState;
0112 
0113     const char *iconName = nullptr;
0114     QPixmap pixmap;
0115     switch (type) {
0116     case OperationCompleted:
0117         iconName = "dialog-ok";
0118         // "ok" icon should probably be "dialog-success", but we don't have that icon in KDE 4.0
0119         d->m_closeButton->hide();
0120         break;
0121 
0122     case Information:
0123         iconName = "dialog-information";
0124         d->m_closeButton->hide();
0125         break;
0126 
0127     case Error:
0128         d->m_timer->start(100);
0129         d->m_state = Illuminate;
0130 
0131         updateCloseButtonPosition();
0132         d->m_closeButton->show();
0133         updateGeometry();
0134         break;
0135 
0136     case Default:
0137     default:
0138         d->m_closeButton->hide();
0139         updateGeometry();
0140         break;
0141     }
0142 
0143     d->m_pixmap = (iconName == nullptr) ? QPixmap() : QIcon::fromTheme(iconName).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize));
0144     QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText()));
0145 
0146     if (type == Error) {
0147         setAccessibleName(i18n("Error: %1", text));
0148     } else {
0149         setAccessibleName(text);
0150     }
0151 
0152     update();
0153 }
0154 
0155 KonqStatusBarMessageLabel::Type KonqStatusBarMessageLabel::type() const
0156 {
0157     return d->m_type;
0158 }
0159 
0160 QString KonqStatusBarMessageLabel::text() const
0161 {
0162     return d->m_text;
0163 }
0164 
0165 void KonqStatusBarMessageLabel::setDefaultText(const QString &text)
0166 {
0167     d->m_defaultText = text;
0168 }
0169 
0170 QString KonqStatusBarMessageLabel::defaultText() const
0171 {
0172     return d->m_defaultText;
0173 }
0174 
0175 void KonqStatusBarMessageLabel::setMinimumTextHeight(int min)
0176 {
0177     if (min != d->m_minTextHeight) {
0178         d->m_minTextHeight = min;
0179         setMinimumHeight(min);
0180         if (d->m_closeButton->height() > min) {
0181             d->m_closeButton->setFixedHeight(min);
0182         }
0183     }
0184 }
0185 
0186 int KonqStatusBarMessageLabel::minimumTextHeight() const
0187 {
0188     return d->m_minTextHeight;
0189 }
0190 
0191 void KonqStatusBarMessageLabel::paintEvent(QPaintEvent * /* event */)
0192 {
0193     QPainter painter(this);
0194 
0195     if (d->m_illumination > 0) {
0196         // at this point, a: we are a second label being drawn over the already
0197         // painted status area, so we can be translucent, and b: our palette's
0198         // window color (bg only) seems to be wrong (always black)
0199         KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
0200         QColor backgroundColor = scheme.background(KColorScheme::NegativeBackground).color();
0201         backgroundColor.setAlpha(qMin(255, d->m_illumination * 2));
0202         painter.setBrush(backgroundColor);
0203         painter.setPen(Qt::NoPen);
0204         painter.drawRect(QRect(0, 0, width(), height()));
0205     }
0206 
0207     // draw pixmap
0208     int x = BorderGap;
0209     const int y = (d->m_minTextHeight - d->m_pixmap.height()) / 2;
0210 
0211     if (!d->m_pixmap.isNull()) {
0212         painter.drawPixmap(x, y, d->m_pixmap);
0213         x += d->m_pixmap.width() + BorderGap;
0214     }
0215 
0216     // draw text
0217 
0218     const QRect availTextRect(x, 0, availableTextWidth(), height());
0219 
0220     if (d->isRichText()) {
0221         const QSize sz = d->m_textDocument.size().toSize();
0222 
0223         // Vertical centering
0224         const QRect textRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignLeft | Qt::AlignVCenter, sz, availTextRect);
0225         //qCDebug(KONQUEROR_LOG) << d->m_text << " sz=" << sz << textRect;
0226 
0227         // What about wordwrap here?
0228 
0229         painter.translate(textRect.left(), textRect.top());
0230         d->m_textDocument.drawContents(&painter);
0231     } else {
0232         // plain text
0233         painter.setPen(palette().windowText().color());
0234         int flags = Qt::AlignVCenter;
0235         if (height() > d->m_minTextHeight) {
0236             flags = flags | Qt::TextWordWrap;
0237         }
0238         painter.drawText(availTextRect, flags, d->m_text);
0239     }
0240     painter.end();
0241 }
0242 
0243 void KonqStatusBarMessageLabel::resizeEvent(QResizeEvent *event)
0244 {
0245     QWidget::resizeEvent(event);
0246     updateCloseButtonPosition();
0247     QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText()));
0248 }
0249 
0250 void KonqStatusBarMessageLabel::timerDone()
0251 {
0252     switch (d->m_state) {
0253     case Illuminate: {
0254         // increase the illumination
0255         const int illumination_max = 128;
0256         if (d->m_illumination < illumination_max) {
0257             d->m_illumination += 32;
0258             if (d->m_illumination > illumination_max) {
0259                 d->m_illumination = illumination_max;
0260             }
0261             update();
0262         } else {
0263             d->m_state = Illuminated;
0264             d->m_timer->start(5000);
0265         }
0266         break;
0267     }
0268 
0269     case Illuminated: {
0270         // start desaturation
0271         d->m_state = Desaturate;
0272         d->m_timer->start(100);
0273         break;
0274     }
0275 
0276     case Desaturate: {
0277         // desaturate
0278         if (d->m_illumination > 0) {
0279             d->m_illumination -= 5;
0280             update();
0281         } else {
0282             d->m_state = DefaultState;
0283             d->m_timer->stop();
0284         }
0285         break;
0286     }
0287 
0288     default:
0289         break;
0290     }
0291 }
0292 
0293 void KonqStatusBarMessageLabel::assureVisibleText()
0294 {
0295     if (d->m_text.isEmpty()) {
0296         return;
0297     }
0298 
0299     int requiredHeight = d->m_minTextHeight;
0300     if (d->m_type != Default) {
0301         // Calculate the required height of the widget thats
0302         // needed for having a fully visible text. Note that for the default
0303         // statusbar type (e. g. hover information) increasing the text height
0304         // is not wanted, as this might rearrange the layout of items.
0305 
0306         QFontMetrics fontMetrics(font());
0307         const QRect bounds(fontMetrics.boundingRect(0, 0, availableTextWidth(), height(),
0308                            Qt::AlignVCenter | Qt::TextWordWrap, d->m_text));
0309         requiredHeight = bounds.height();
0310         if (requiredHeight < d->m_minTextHeight) {
0311             requiredHeight = d->m_minTextHeight;
0312         }
0313     }
0314 
0315     // Increase/decrease the current height of the widget to the
0316     // required height. The increasing/decreasing is done in several
0317     // steps to have an animation if the height is modified
0318     // (see KonqStatusBarMessageLabel::resizeEvent())
0319     const int gap = d->m_minTextHeight / 2;
0320     int minHeight = minimumHeight();
0321     if (minHeight < requiredHeight) {
0322         minHeight += gap;
0323         if (minHeight > requiredHeight) {
0324             minHeight = requiredHeight;
0325         }
0326         setMinimumHeight(minHeight);
0327         updateGeometry();
0328     } else if (minHeight > requiredHeight) {
0329         minHeight -= gap;
0330         if (minHeight < requiredHeight) {
0331             minHeight = requiredHeight;
0332         }
0333         setMinimumHeight(minHeight);
0334         updateGeometry();
0335     }
0336 
0337     updateCloseButtonPosition();
0338 }
0339 
0340 int KonqStatusBarMessageLabel::availableTextWidth() const
0341 {
0342     const int buttonWidth = (d->m_type == Error) ?
0343                             d->m_closeButton->width() + BorderGap : 0;
0344     return width() - d->m_pixmap.width() - (BorderGap * 4) - buttonWidth;
0345 }
0346 
0347 void KonqStatusBarMessageLabel::updateCloseButtonPosition()
0348 {
0349     const int x = width() - d->m_closeButton->width() - BorderGap;
0350     d->m_closeButton->move(x, 0);
0351 }
0352 
0353 void KonqStatusBarMessageLabel::closeErrorMessage()
0354 {
0355     if (!showPendingMessage()) {
0356         d->m_state = DefaultState;
0357         setMessage(d->m_defaultText, Default);
0358     }
0359 }
0360 
0361 bool KonqStatusBarMessageLabel::showPendingMessage()
0362 {
0363     if (!d->m_pendingMessages.isEmpty()) {
0364         reset();
0365         setMessage(d->m_pendingMessages.takeFirst(), Error);
0366         return true;
0367     }
0368     return false;
0369 }
0370 
0371 void KonqStatusBarMessageLabel::reset()
0372 {
0373     d->m_text.clear();
0374     d->m_type = Default;
0375 }
0376 
0377 QSize KonqStatusBarMessageLabel::sizeHint() const
0378 {
0379     return minimumSizeHint();
0380 }
0381 
0382 QSize KonqStatusBarMessageLabel::minimumSizeHint() const
0383 {
0384     const int fontHeight = fontMetrics().height();
0385     QSize sz(100, fontHeight);
0386     if (d->m_closeButton->isVisible()) {
0387         const QSize toolButtonSize = d->m_closeButton->sizeHint();
0388         sz = toolButtonSize.expandedTo(sz);
0389     }
0390     return sz;
0391 }