File indexing completed on 2024-05-19 15:44:21
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"