File indexing completed on 2024-12-22 04:13:11

0001 /*
0002  *  This file is part of KimageShop^WKrayon^WKrita
0003  *
0004  *  SPDX-FileCopyrightText: 2004 Christian Muehlhaeuser <chris@chris.de>
0005  *  SPDX-FileCopyrightText: 2004-2006 Seb Ruiz <ruiz@kde.org>
0006  *  SPDX-FileCopyrightText: 2004, 2005 Max Howell <max.howell@methylblue.com>
0007  *  SPDX-FileCopyrightText: 2005 Gabor Lehel <illissius@gmail.com>
0008  *  SPDX-FileCopyrightText: 2008, 2009 Mark Kretschmann <kretschmann@kde.org>
0009  *  SPDX-FileCopyrightText: 2012 Boudewijn Rempt <boud@valdyas.org>
0010  *  SPDX-FileCopyrightText: 2021 Alvin Wong <alvin@alvinhc.com>
0011  *
0012  */
0013 #include "kis_floating_message.h"
0014 
0015 #include <QApplication>
0016 #include <QScreen>
0017 #include <QMouseEvent>
0018 #include <QPainter>
0019 #include <QTimer>
0020 #include <QRegExp>
0021 #include <QDesktopWidget>
0022 #include <QLabel>
0023 #include <QGraphicsDropShadowEffect>
0024 
0025 #include <kis_icon.h>
0026 #include <kis_debug.h>
0027 #include "kis_global.h"
0028 
0029 
0030 #define OSD_WINDOW_OPACITY 0.85
0031 
0032 static void addDropShadow(QWidget *widget, QColor color)
0033 {
0034     QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect(widget);
0035     effect->setBlurRadius(6);
0036     effect->setOffset(0);
0037     effect->setColor(color);
0038     widget->setGraphicsEffect(effect);
0039 }
0040 
0041 static Qt::AlignmentFlag flagsToAlignmentFlags(int flags)
0042 {
0043     constexpr int mask = Qt::AlignLeft
0044                        | Qt::AlignRight
0045                        | Qt::AlignHCenter
0046                        | Qt::AlignJustify
0047                        | Qt::AlignTop
0048                        | Qt::AlignBottom
0049                        | Qt::AlignVCenter
0050                        | Qt::AlignCenter;
0051     return Qt::AlignmentFlag(flags & mask);
0052 }
0053 
0054 KisFloatingMessage::KisFloatingMessage(const QString &message, QWidget *parent, bool showOverParent, int timeout, Priority priority, int alignment)
0055     : QWidget(parent)
0056     , m_message(message)
0057     , m_showOverParent(showOverParent)
0058     , m_timeout(timeout)
0059     , m_priority(priority)
0060     , m_alignment(alignment)
0061 {
0062     m_icon = KisIconUtils::loadIcon("krita-branding").pixmap(256, 256).toImage();
0063 
0064     setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip | Qt::WindowTransparentForInput);
0065     setFocusPolicy(Qt::NoFocus);
0066     setAttribute(Qt::WA_ShowWithoutActivating);
0067 
0068     m_messageLabel = new QLabel(message, this);
0069     m_messageLabel->setAttribute(Qt::WA_TranslucentBackground);
0070     m_iconLabel = new QLabel(this);
0071     m_iconLabel->setAttribute(Qt::WA_TranslucentBackground);
0072     {
0073         int h, s, v;
0074         palette().color( QPalette::Normal, QPalette::WindowText ).getHsv( &h, &s, &v );
0075         const QColor shadowColor = v > 128 ? Qt::black : Qt::white;
0076         addDropShadow(m_messageLabel, shadowColor);
0077         addDropShadow(m_iconLabel, shadowColor);
0078     }
0079 
0080     m_timer.setSingleShot( true );
0081     connect(&m_timer, SIGNAL(timeout()), SLOT(startFade()));
0082     connect(this, SIGNAL(destroyed()), SLOT(widgetDeleted()));
0083 }
0084 
0085 void KisFloatingMessage::tryOverrideMessage(const QString message,
0086                                             const QIcon& icon,
0087                                             int timeout,
0088                                             KisFloatingMessage::Priority priority,
0089                                             int alignment)
0090 {
0091     if ((int)priority > (int)m_priority) return;
0092 
0093     m_message = message;
0094     m_messageLabel->setText(message);
0095     setIcon(icon);
0096     m_timeout = timeout;
0097     m_priority = priority;
0098     m_alignment = alignment;
0099     showMessage();
0100     update();
0101 }
0102 
0103 void KisFloatingMessage::showMessage()
0104 {
0105     if (widgetQueuedForDeletion) return;
0106 
0107     m_messageLabel->setAlignment(flagsToAlignmentFlags(m_alignment));
0108     m_messageLabel->setWordWrap(m_alignment & Qt::TextWordWrap);
0109     m_messageLabel->adjustSize();
0110 
0111     QRect geom;
0112 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
0113     geom = determineMetrics(fontMetrics().horizontalAdvance('x'));
0114 #else
0115     geom = determineMetrics(fontMetrics().width('x'));
0116 #endif
0117     setGeometry(geom);
0118     setWindowOpacity(OSD_WINDOW_OPACITY);
0119 
0120     QRect rect(QPoint(), geom.size());
0121     rect.adjust(m_m, m_m, -m_m, -m_m);
0122     if (!m_icon.isNull()) {
0123         QRect r(rect);
0124         r.setTop((size().height() - m_scaledIcon.height() ) / 2);
0125         r.setSize(m_scaledIcon.size());
0126         m_iconLabel->setPixmap(m_scaledIcon);
0127         m_iconLabel->setFixedSize(r.size());
0128         m_iconLabel->move(r.topLeft());
0129         m_iconLabel->show();
0130         rect.setLeft(rect.left() + m_scaledIcon.width() + m_m);
0131     } else {
0132         m_iconLabel->hide();
0133     }
0134     m_messageLabel->setFixedSize(rect.size());
0135     m_messageLabel->move(rect.topLeft());
0136 
0137     QWidget::setVisible(true);
0138     m_fadeTimeLine.stop();
0139     m_timer.start(m_timeout);
0140 }
0141 
0142 void KisFloatingMessage::setShowOverParent(bool show)
0143 {
0144     m_showOverParent = show;
0145 }
0146 
0147 void KisFloatingMessage::setIcon(const QIcon& icon)
0148 {
0149     m_icon = icon.pixmap(256, 256).toImage();
0150 }
0151 
0152 const int MARGIN = 20;
0153 
0154 QRect KisFloatingMessage::determineMetrics( const int M )
0155 {
0156     m_m = M;
0157 
0158     const QSize minImageSize = m_icon.size().boundedTo(QSize(100, 100));
0159 
0160     // determine a sensible maximum size, don't cover the whole desktop or cross the screen
0161     const QSize margin( (M + MARGIN) * 2, (M + MARGIN) * 2); //margins
0162     const QSize image = m_icon.isNull() ? QSize(0, 0) : minImageSize;
0163 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
0164     QRect geom = parentWidget()->geometry();
0165     QPoint p(geom.width() / 2 + geom.left(), geom.height() / 2 + geom.top());
0166     QScreen *s = qApp->screenAt(p);
0167     QSize max;
0168     if (s) {
0169         max = QSize(s->availableGeometry().size() - margin);
0170     }
0171     else {
0172         max = QSize(1024, 768);
0173     }
0174 #else
0175     const QSize max = QApplication::desktop()->availableGeometry(parentWidget()).size() - margin;
0176 #endif
0177 
0178 
0179     // If we don't do that, the boundingRect() might not be suitable for drawText() (Qt issue N67674)
0180     m_message.replace(QRegExp( " +\n"), "\n");
0181     // remove consecutive line breaks
0182     m_message.replace(QRegExp( "\n+"), "\n");
0183 
0184     // The osd cannot be larger than the screen
0185     QRect rect = fontMetrics().boundingRect(0, 0, max.width() - image.width(), max.height(),
0186            m_alignment, m_message);
0187 
0188     if (!m_icon.isNull()) {
0189         const int availableWidth = max.width() - rect.width() - M; //WILL be >= (minImageSize.width() - M)
0190 
0191         m_scaledIcon = QPixmap::fromImage(m_icon.scaled(qMin(availableWidth, m_icon.width()),
0192                                                         qMin( rect.height(), m_icon.height()),
0193                                                         Qt::KeepAspectRatio, Qt::SmoothTransformation));
0194 
0195         const int widthIncludingImage = rect.width() + m_scaledIcon.width() + M; //margin between text + image
0196         rect.setWidth( widthIncludingImage );
0197     }
0198 
0199     // expand in all directions by 2*M
0200     //
0201     // take care with this rect, because it must be *bigger*
0202     // than the rect we paint the message in
0203     rect = kisGrowRect(rect, 2 * M);
0204 
0205 
0206 
0207     const QSize newSize = rect.size();
0208 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
0209     QRect screen;
0210     if (s) {
0211         screen = s->availableGeometry();
0212     }
0213     else {
0214         screen = QRect(0, 0, 1024, 768);
0215     }
0216 #else
0217     QRect screen = QApplication::desktop()->screenGeometry(parentWidget());
0218 #endif
0219 
0220 
0221     QPoint newPos(MARGIN, MARGIN);
0222 
0223     if (parentWidget() && m_showOverParent) {
0224         screen = parentWidget()->geometry();
0225         screen.setTopLeft(parentWidget()->mapToGlobal(QPoint(MARGIN, MARGIN + 50)));
0226         newPos = screen.topLeft();
0227     }
0228     else {
0229         // move to the right
0230         newPos.rx() = screen.width() - MARGIN - newSize.width();
0231 
0232         //ensure we don't dip below the screen
0233         if (newPos.y() + newSize.height() > screen.height() - MARGIN) {
0234             newPos.ry() = screen.height() - MARGIN - newSize.height();
0235         }
0236         // correct for screen position
0237         newPos += screen.topLeft();
0238 
0239         if (parentWidget()) {
0240             // Move a bit to the left as there could be a scrollbar
0241             newPos.setX(newPos.x() - MARGIN);
0242         }
0243     }
0244 
0245     QRect rc(newPos, rect.size());
0246 
0247     return rc;
0248 }
0249 
0250 void KisFloatingMessage::startFade()
0251 {
0252     m_fadeTimeLine.setDuration(500);
0253     m_fadeTimeLine.setEasingCurve(QEasingCurve::InCurve);
0254     m_fadeTimeLine.setLoopCount(1);
0255     m_fadeTimeLine.setFrameRange(0, 10);
0256     connect(&m_fadeTimeLine, SIGNAL(finished()), SLOT(removeMessage()));
0257     connect(&m_fadeTimeLine, SIGNAL(frameChanged(int)), SLOT(updateOpacity(int)));
0258     m_fadeTimeLine.start();
0259 }
0260 
0261 void KisFloatingMessage::removeMessage()
0262 {
0263     m_timer.stop();
0264     m_fadeTimeLine.stop();
0265     widgetQueuedForDeletion = true;
0266 
0267     hide();
0268     deleteLater();
0269 }
0270 
0271 void KisFloatingMessage::updateOpacity(int value)
0272 {
0273     setWindowOpacity(OSD_WINDOW_OPACITY / 10.0 * (10 - value));
0274 }
0275 
0276 void KisFloatingMessage::widgetDeleted()
0277 {
0278     widgetQueuedForDeletion = false;
0279 }