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 }