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"