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