File indexing completed on 2024-04-28 07:46:50

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"