File indexing completed on 2024-04-28 04:37:33

0001 /*
0002     SPDX-FileCopyrightText: 2012 Dominik Haumann <dhaumann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "messagewidget.h"
0008 
0009 // lib
0010 #include "message.h"
0011 // KF
0012 #include <KMessageWidget>
0013 // Qt
0014 #include <QTimer>
0015 #include <QToolTip>
0016 #include <QVBoxLayout>
0017 
0018 namespace Sublime {
0019 
0020 constexpr int s_defaultAutoHideTime = 6 * 1000;
0021 
0022 // the enums values do not necessarily match, hence translate case-by-case
0023 KMessageWidget::MessageType kMessageWidgetMessageType(Message::MessageType messageType)
0024 {
0025     return
0026         messageType == Message::Positive ?    KMessageWidget::Positive :
0027         messageType == Message::Information ? KMessageWidget::Information :
0028         messageType == Message::Warning ?     KMessageWidget::Warning :
0029         messageType == Message::Error ?       KMessageWidget::Error :
0030         /* else */                            KMessageWidget::Information;
0031 }
0032 
0033 
0034 MessageWidget::MessageWidget(QWidget* parent)
0035     : QWidget(parent)
0036     , m_autoHideTimer(new QTimer(this))
0037 {
0038     auto* l = new QVBoxLayout();
0039     l->setContentsMargins(0, 0, 0, 0);
0040 
0041     m_messageWidget = new KMessageWidget(this);
0042     m_messageWidget->setCloseButtonVisible(false);
0043 
0044     l->addWidget(m_messageWidget);
0045     setLayout(l);
0046 
0047     // tell the widget to always use the minimum size.
0048     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
0049 
0050     // by default, hide widgets
0051     m_messageWidget->hide();
0052     hide();
0053 
0054     // setup autoHide timer details
0055     m_autoHideTimer->setSingleShot(true);
0056 
0057     connect(m_messageWidget, &KMessageWidget::hideAnimationFinished,
0058             this, &MessageWidget::showNextMessage);
0059     connect(m_messageWidget, &KMessageWidget::linkHovered,
0060             this, &MessageWidget::linkHovered);
0061 }
0062 
0063 void MessageWidget::showNextMessage()
0064 {
0065     // at this point, we should not have a currently shown message
0066     Q_ASSERT(!m_currentMessage);
0067 
0068     // if not message to show, just stop
0069     if (m_messageQueue.isEmpty()) {
0070         hide();
0071         return;
0072     }
0073 
0074     // track current message
0075     m_currentMessage = m_messageQueue[0];
0076 
0077     // set text etc.
0078     m_messageWidget->setText(m_currentMessage->text());
0079     m_messageWidget->setIcon(m_currentMessage->icon());
0080 
0081     // connect textChanged() and iconChanged(), so it's possible to change this on the fly
0082     connect(m_currentMessage, &Message::textChanged,
0083             m_messageWidget, &KMessageWidget::setText, Qt::UniqueConnection);
0084     connect(m_currentMessage, &Message::iconChanged,
0085             m_messageWidget, &KMessageWidget::setIcon, Qt::UniqueConnection);
0086 
0087     const KMessageWidget::MessageType widgetMessageType =
0088         kMessageWidgetMessageType(m_currentMessage->messageType());
0089     m_messageWidget->setMessageType(widgetMessageType);
0090 
0091     // remove all actions from the message widget
0092     const auto messageWidgetActions = m_messageWidget->actions();
0093     for (QAction* action : messageWidgetActions) {
0094         m_messageWidget->removeAction(action);
0095     }
0096 
0097     // add new actions to the message widget
0098     const auto m_currentMessageActions = m_currentMessage->actions();
0099     for (QAction* action : m_currentMessageActions) {
0100         m_messageWidget->addAction(action);
0101     }
0102 
0103     // set word wrap of the message
0104     setWordWrap(m_currentMessage);
0105 
0106     // setup auto-hide timer, and start if requested
0107     m_autoHideTime = m_currentMessage->autoHide();
0108     m_autoHideTimer->stop();
0109     if (m_autoHideTime >= 0) {
0110         connect(m_autoHideTimer, &QTimer::timeout,
0111                 m_currentMessage, &Message::deleteLater, Qt::UniqueConnection);
0112         m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime);
0113     }
0114 
0115     // finally show
0116     show();
0117     // NOTE: use a singleShot timer to avoid resizing issues when showing the message widget the first time (bug #316666)
0118     QTimer::singleShot(0, m_messageWidget, &KMessageWidget::animatedShow);
0119 }
0120 
0121 void MessageWidget::setWordWrap(Message* message)
0122 {
0123     // want word wrap anyway? -> ok
0124     if (message->wordWrap()) {
0125         m_messageWidget->setWordWrap(message->wordWrap());
0126         return;
0127     }
0128 
0129     // word wrap not wanted, that's ok if a parent widget does not exist
0130     if (!parentWidget()) {
0131         m_messageWidget->setWordWrap(false);
0132         return;
0133     }
0134 
0135     // word wrap not wanted -> enable word wrap if it breaks the layout otherwise
0136     int margin = 0;
0137     if (parentWidget()->layout()) {
0138         // get left/right margin of the layout, since we need to subtract these
0139         int leftMargin = 0, rightMargin = 0;
0140         parentWidget()->layout()->getContentsMargins(&leftMargin, nullptr, &rightMargin, nullptr);
0141         margin = leftMargin + rightMargin;
0142     }
0143 
0144     // if word wrap enabled, first disable it
0145     if (m_messageWidget->wordWrap()) {
0146         m_messageWidget->setWordWrap(false);
0147     }
0148 
0149     // make sure the widget's size is up-to-date in its hidden state
0150     m_messageWidget->ensurePolished();
0151     m_messageWidget->adjustSize();
0152 
0153     // finally enable word wrap, if there is not enough free horizontal space
0154     const int freeSpace = (parentWidget()->width() - margin) - m_messageWidget->width();
0155     if (freeSpace < 0) {
0156         //     qCDebug(LOG_KTE) << "force word wrap to avoid breaking the layout" << freeSpace;
0157         m_messageWidget->setWordWrap(true);
0158     }
0159 }
0160 
0161 void MessageWidget::postMessage(Message* message, const QVector<QSharedPointer<QAction>>& actions)
0162 {
0163     Q_ASSERT(!m_messageHash.contains(message));
0164     m_messageHash.insert(message, actions);
0165 
0166     // insert message sorted after priority
0167     int i = 0;
0168     for (; i < m_messageQueue.count(); ++i) {
0169         if (message->priority() > m_messageQueue[i]->priority()) {
0170             break;
0171         }
0172     }
0173 
0174     // queue message
0175     m_messageQueue.insert(i, message);
0176 
0177     // catch if the message gets deleted
0178     connect(message, &Message::closed,
0179             this, &MessageWidget::messageDestroyed);
0180 
0181     if (i == 0 && !m_messageWidget->isHideAnimationRunning()) {
0182         // if message has higher priority than the one currently shown,
0183         // then hide the current one and then show the new one.
0184         if (m_currentMessage) {
0185             // autoHide timer may be running for currently shown message, therefore
0186             // simply disconnect autoHide timer to all timeout() receivers
0187             disconnect(m_autoHideTimer, SIGNAL(timeout()), nullptr, nullptr);
0188             m_autoHideTimer->stop();
0189 
0190             // if there is a current message, the message queue must contain 2 messages
0191             Q_ASSERT(m_messageQueue.size() > 1);
0192             Q_ASSERT(m_currentMessage == m_messageQueue[1]);
0193 
0194             // a bit unnice: disconnect textChanged() and iconChanged() signals of previously visible message
0195             disconnect(m_currentMessage, &Message::textChanged,
0196                        m_messageWidget, &KMessageWidget::setText);
0197             disconnect(m_currentMessage, &Message::iconChanged,
0198                        m_messageWidget, &KMessageWidget::setIcon);
0199 
0200             m_currentMessage = nullptr;
0201             m_messageWidget->animatedHide();
0202         } else {
0203             showNextMessage();
0204         }
0205     }
0206 }
0207 
0208 void MessageWidget::messageDestroyed(Message* message)
0209 {
0210     // last moment when message is valid, since KTE::Message is already in
0211     // destructor we have to do the following:
0212     // 1. remove message from m_messageQueue, so we don't care about it anymore
0213     // 2. activate hide animation or show a new message()
0214 
0215     // remove widget from m_messageQueue
0216     int i = 0;
0217     for (; i < m_messageQueue.count(); ++i) {
0218         if (m_messageQueue[i] == message) {
0219             break;
0220         }
0221     }
0222 
0223     // the message must be in the list
0224     Q_ASSERT(i < m_messageQueue.count());
0225 
0226     // remove message
0227     m_messageQueue.removeAt(i);
0228 
0229     // remove message from hash -> release QActions
0230     Q_ASSERT(m_messageHash.contains(message));
0231     m_messageHash.remove(message);
0232 
0233     // if deleted message is the current message, launch hide animation
0234     if (message == m_currentMessage) {
0235         m_currentMessage = nullptr;
0236         m_messageWidget->animatedHide();
0237     }
0238 }
0239 
0240 void MessageWidget::startAutoHideTimer()
0241 {
0242     // message does not want autohide, or timer already running
0243     if (!m_currentMessage                        // no message, nothing to do
0244         || m_autoHideTime < 0                    // message does not want auto-hide
0245         || m_autoHideTimer->isActive()           // auto-hide timer is already active
0246         || m_messageWidget->isHideAnimationRunning() // widget is in hide animation phase
0247         || m_messageWidget->isShowAnimationRunning() // widget is in show animation phase
0248     ) {
0249         return;
0250     }
0251 
0252     // safety checks: the message must still be valid
0253     Q_ASSERT(m_messageQueue.size());
0254     Q_ASSERT(m_currentMessage->autoHide() == m_autoHideTime);
0255 
0256     // start autoHide timer as requested
0257     m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime);
0258 }
0259 
0260 void MessageWidget::linkHovered(const QString &link)
0261 {
0262     QToolTip::showText(QCursor::pos(), link, m_messageWidget);
0263 }
0264 
0265 QString MessageWidget::text() const
0266 {
0267     return m_messageWidget->text();
0268 }
0269 
0270 }
0271 
0272 #include "moc_messagewidget.cpp"