File indexing completed on 2024-03-24 04:00:40
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::setPosition(KateMessageWidget::Position position) 0056 { 0057 switch (position) { 0058 case Position::Inline: 0059 m_messageWidget->setPosition(KMessageWidget::Inline); 0060 return; 0061 case Position::Header: 0062 m_messageWidget->setPosition(KMessageWidget::Header); 0063 return; 0064 case Position::Footer: 0065 m_messageWidget->setPosition(KMessageWidget::Footer); 0066 return; 0067 } 0068 } 0069 0070 void KateMessageWidget::showNextMessage() 0071 { 0072 // at this point, we should not have a currently shown message 0073 Q_ASSERT(m_currentMessage == nullptr); 0074 0075 // if not message to show, just stop 0076 if (m_messageQueue.size() == 0) { 0077 hide(); 0078 return; 0079 } 0080 0081 // track current message 0082 m_currentMessage = m_messageQueue[0]; 0083 0084 // set text etc. 0085 m_messageWidget->setText(m_currentMessage->text()); 0086 m_messageWidget->setIcon(m_currentMessage->icon()); 0087 0088 // connect textChanged() and iconChanged(), so it's possible to change this on the fly 0089 connect(m_currentMessage, &KTextEditor::Message::textChanged, m_messageWidget, &KMessageWidget::setText, Qt::UniqueConnection); 0090 connect(m_currentMessage, &KTextEditor::Message::iconChanged, m_messageWidget, &KMessageWidget::setIcon, Qt::UniqueConnection); 0091 0092 // the enums values do not necessarily match, hence translate with switch 0093 switch (m_currentMessage->messageType()) { 0094 case KTextEditor::Message::Positive: 0095 m_messageWidget->setMessageType(KMessageWidget::Positive); 0096 break; 0097 case KTextEditor::Message::Information: 0098 m_messageWidget->setMessageType(KMessageWidget::Information); 0099 break; 0100 case KTextEditor::Message::Warning: 0101 m_messageWidget->setMessageType(KMessageWidget::Warning); 0102 break; 0103 case KTextEditor::Message::Error: 0104 m_messageWidget->setMessageType(KMessageWidget::Error); 0105 break; 0106 default: 0107 m_messageWidget->setMessageType(KMessageWidget::Information); 0108 break; 0109 } 0110 0111 // remove all actions from the message widget 0112 const auto messageWidgetActions = m_messageWidget->actions(); 0113 for (QAction *a : messageWidgetActions) { 0114 m_messageWidget->removeAction(a); 0115 } 0116 0117 // add new actions to the message widget 0118 const auto m_currentMessageActions = m_currentMessage->actions(); 0119 for (QAction *a : m_currentMessageActions) { 0120 m_messageWidget->addAction(a); 0121 } 0122 0123 // set word wrap of the message 0124 setWordWrap(m_currentMessage); 0125 0126 // setup auto-hide timer, and start if requested 0127 m_autoHideTime = m_currentMessage->autoHide(); 0128 m_autoHideTimer->stop(); 0129 if (m_autoHideTime >= 0) { 0130 connect(m_autoHideTimer, &QTimer::timeout, m_currentMessage, &QObject::deleteLater, Qt::UniqueConnection); 0131 if (m_currentMessage->autoHideMode() == KTextEditor::Message::Immediate) { 0132 m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime); 0133 } 0134 } 0135 0136 // finally show 0137 show(); 0138 m_animation->show(); 0139 } 0140 0141 void KateMessageWidget::setWordWrap(KTextEditor::Message *message) 0142 { 0143 // want word wrap anyway? -> ok 0144 if (message->wordWrap()) { 0145 m_messageWidget->setWordWrap(message->wordWrap()); 0146 return; 0147 } 0148 0149 // word wrap not wanted, that's ok if a parent widget does not exist 0150 if (!parentWidget()) { 0151 m_messageWidget->setWordWrap(false); 0152 return; 0153 } 0154 0155 // word wrap not wanted -> enable word wrap if it breaks the layout otherwise 0156 int margin = 0; 0157 if (parentWidget()->layout()) { 0158 // get left/right margin of the layout, since we need to subtract these 0159 int leftMargin = 0; 0160 int rightMargin = 0; 0161 parentWidget()->layout()->getContentsMargins(&leftMargin, nullptr, &rightMargin, nullptr); 0162 margin = leftMargin + rightMargin; 0163 } 0164 0165 // if word wrap enabled, first disable it 0166 if (m_messageWidget->wordWrap()) { 0167 m_messageWidget->setWordWrap(false); 0168 } 0169 0170 // make sure the widget's size is up-to-date in its hidden state 0171 m_messageWidget->ensurePolished(); 0172 m_messageWidget->adjustSize(); 0173 0174 // finally enable word wrap, if there is not enough free horizontal space 0175 const int freeSpace = (parentWidget()->width() - margin) - m_messageWidget->width(); 0176 if (freeSpace < 0) { 0177 // qCDebug(LOG_KTE) << "force word wrap to avoid breaking the layout" << freeSpace; 0178 m_messageWidget->setWordWrap(true); 0179 } 0180 } 0181 0182 void KateMessageWidget::postMessage(KTextEditor::Message *message, QList<std::shared_ptr<QAction>> actions) 0183 { 0184 Q_ASSERT(!m_messageHash.contains(message)); 0185 m_messageHash[message] = std::move(actions); 0186 0187 // insert message sorted after priority 0188 int i = 0; 0189 for (; i < m_messageQueue.count(); ++i) { 0190 if (message->priority() > m_messageQueue[i]->priority()) { 0191 break; 0192 } 0193 } 0194 0195 // queue message 0196 m_messageQueue.insert(i, message); 0197 0198 // catch if the message gets deleted 0199 connect(message, &KTextEditor::Message::closed, this, &KateMessageWidget::messageDestroyed); 0200 0201 if (i == 0 && !m_animation->isHideAnimationRunning()) { 0202 // if message has higher priority than the one currently shown, 0203 // then hide the current one and then show the new one. 0204 if (m_currentMessage) { 0205 // autoHide timer may be running for currently shown message, therefore 0206 // simply disconnect autoHide timer to all timeout() receivers 0207 disconnect(m_autoHideTimer, &QTimer::timeout, nullptr, nullptr); 0208 m_autoHideTimer->stop(); 0209 0210 // if there is a current message, the message queue must contain 2 messages 0211 Q_ASSERT(m_messageQueue.size() > 1); 0212 Q_ASSERT(m_currentMessage == m_messageQueue[1]); 0213 0214 // a bit unnice: disconnect textChanged() and iconChanged() signals of previously visible message 0215 disconnect(m_currentMessage, &KTextEditor::Message::textChanged, m_messageWidget, &KMessageWidget::setText); 0216 disconnect(m_currentMessage, &KTextEditor::Message::iconChanged, m_messageWidget, &KMessageWidget::setIcon); 0217 0218 m_currentMessage = nullptr; 0219 m_animation->hide(); 0220 } else { 0221 showNextMessage(); 0222 } 0223 } 0224 } 0225 0226 void KateMessageWidget::messageDestroyed(KTextEditor::Message *message) 0227 { 0228 // last moment when message is valid, since KTE::Message is already in 0229 // destructor we have to do the following: 0230 // 1. remove message from m_messageQueue, so we don't care about it anymore 0231 // 2. activate hide animation or show a new message() 0232 0233 // remove widget from m_messageQueue 0234 int i = 0; 0235 for (; i < m_messageQueue.count(); ++i) { 0236 if (m_messageQueue[i] == message) { 0237 break; 0238 } 0239 } 0240 0241 // the message must be in the list 0242 Q_ASSERT(i < m_messageQueue.count()); 0243 0244 // remove message 0245 m_messageQueue.removeAt(i); 0246 0247 // remove message from hash -> release QActions 0248 Q_ASSERT(m_messageHash.contains(message)); 0249 m_messageHash.remove(message); 0250 0251 // if deleted message is the current message, launch hide animation 0252 if (message == m_currentMessage) { 0253 m_currentMessage = nullptr; 0254 m_animation->hide(); 0255 } 0256 } 0257 0258 void KateMessageWidget::startAutoHideTimer() 0259 { 0260 // message does not want autohide, or timer already running 0261 if (!m_currentMessage // no message, nothing to do 0262 || m_autoHideTime < 0 // message does not want auto-hide 0263 || m_autoHideTimer->isActive() // auto-hide timer is already active 0264 || m_animation->isHideAnimationRunning() // widget is in hide animation phase 0265 || m_animation->isShowAnimationRunning() // widget is in show animation phase 0266 ) { 0267 return; 0268 } 0269 0270 // safety checks: the message must still be valid 0271 Q_ASSERT(m_messageQueue.size()); 0272 Q_ASSERT(m_currentMessage->autoHide() == m_autoHideTime); 0273 0274 // start autoHide timer as requested 0275 m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime); 0276 } 0277 0278 void KateMessageWidget::linkHovered(const QString &link) 0279 { 0280 QToolTip::showText(QCursor::pos(), link, m_messageWidget); 0281 } 0282 0283 QString KateMessageWidget::text() const 0284 { 0285 return m_messageWidget->text(); 0286 } 0287 0288 #include "moc_katemessagewidget.cpp"