File indexing completed on 2022-10-04 15:37:25

0001 /*
0002     SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
0003     SPDX-FileCopyrightText: 2008 Jean-Baptiste Mardelle <jb@kdenlive.org>
0004     SPDX-FileCopyrightText: 2012 Simon A. Eugster <simon.eu@gmail.com>
0005 
0006     Some code borrowed from Dolphin, adapted (2008) to Kdenlive
0007 
0008     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0009 */
0010 
0011 #include "statusbarmessagelabel.h"
0012 #include "core.h"
0013 #include "kdenlivesettings.h"
0014 #include "mainwindow.h"
0015 
0016 #include <KColorScheme>
0017 #include <KNotification>
0018 #include <kconfigwidgets_version.h>
0019 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 93, 0)
0020 #include <KStatefulBrush>
0021 #endif
0022 #include <KIconLoader>
0023 #include <KLocalizedString>
0024 
0025 #include <QDialog>
0026 #include <QDialogButtonBox>
0027 #include <QFontDatabase>
0028 #include <QHBoxLayout>
0029 #include <QIcon>
0030 #include <QMouseEvent>
0031 #include <QPixmap>
0032 #include <QProgressBar>
0033 #include <QPropertyAnimation>
0034 #include <QPushButton>
0035 #include <QStyle>
0036 #include <QTextEdit>
0037 
0038 FlashLabel::FlashLabel(QWidget *parent)
0039     : QWidget(parent)
0040 {
0041     setAutoFillBackground(true);
0042 }
0043 
0044 FlashLabel::~FlashLabel() = default;
0045 
0046 void FlashLabel::setColor(const QColor &col)
0047 {
0048     QPalette pal = palette();
0049     pal.setColor(QPalette::Window, col);
0050     setPalette(pal);
0051     update();
0052 }
0053 
0054 QColor FlashLabel::color() const
0055 {
0056     return palette().window().color();
0057 }
0058 
0059 StatusBarMessageLabel::StatusBarMessageLabel(QWidget *parent)
0060     : QWidget(parent)
0061     , m_minTextHeight(-1)
0062     , m_keymapText()
0063     , m_tooltipText()
0064     , m_queueSemaphore(1)
0065 {
0066     setMinimumHeight(KIconLoader::SizeSmall);
0067     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0068     m_container = new FlashLabel(this);
0069     auto *lay = new QHBoxLayout(this);
0070     auto *lay2 = new QHBoxLayout(m_container);
0071     m_pixmap = new QLabel(this);
0072     m_pixmap->setAlignment(Qt::AlignCenter);
0073     m_selectionLabel = new QLabel(this);
0074     m_selectionLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0075     m_selectionLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0076     m_label = new QLabel(this);
0077     m_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0078     m_label->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0079     m_keyMap = new QLabel(this);
0080     m_keyMap->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0081     m_keyMap->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0082     m_progress = new QProgressBar(this);
0083     lay2->addWidget(m_selectionLabel);
0084     lay2->addWidget(m_pixmap);
0085     lay2->addWidget(m_label);
0086     lay2->addWidget(m_progress);
0087     lay->addWidget(m_keyMap);
0088 
0089     auto *spacer = new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
0090     lay->addItem(spacer);
0091     lay->addWidget(m_container);
0092     setLayout(lay);
0093     m_progress->setVisible(false);
0094     lay->setContentsMargins(BorderGap, 0, 2 * BorderGap, 0);
0095     m_queueTimer.setSingleShot(true);
0096     connect(&m_queueTimer, &QTimer::timeout, this, &StatusBarMessageLabel::slotMessageTimeout);
0097     connect(m_label, &QLabel::linkActivated, this, &StatusBarMessageLabel::slotShowJobLog);
0098 }
0099 
0100 StatusBarMessageLabel::~StatusBarMessageLabel() = default;
0101 
0102 void StatusBarMessageLabel::mousePressEvent(QMouseEvent *event)
0103 {
0104     QWidget::mousePressEvent(event);
0105     if (m_pixmap->rect().contains(event->localPos().toPoint()) && m_currentMessage.type == MltError) {
0106         confirmErrorMessage();
0107     }
0108 }
0109 
0110 void StatusBarMessageLabel::setKeyMap(const QString &text)
0111 {
0112     m_keyMap->setText(text);
0113     m_keymapText = text;
0114 }
0115 
0116 void StatusBarMessageLabel::setTmpKeyMap(const QString &text)
0117 {
0118     if (text.isEmpty()) {
0119         m_keyMap->setText(m_keymapText);
0120     } else {
0121         m_keyMap->setText(text);
0122     }
0123 }
0124 
0125 void StatusBarMessageLabel::setProgressMessage(const QString &text, MessageType type, int progress)
0126 {
0127     if (type == ProcessingJobMessage) {
0128         m_progress->setValue(progress);
0129         m_progress->setVisible(progress < 100);
0130     } else if (m_currentMessage.type != ProcessingJobMessage || type == OperationCompletedMessage) {
0131         m_progress->setVisible(progress < 100);
0132     }
0133     if (text == m_currentMessage.text) {
0134         return;
0135     }
0136     setMessage(text, type, 0);
0137 }
0138 
0139 void StatusBarMessageLabel::setSelectionMessage(const QString &text)
0140 {
0141     m_selectionLabel->setText(text);
0142 }
0143 
0144 void StatusBarMessageLabel::setMessage(const QString &text, MessageType type, int timeoutMS)
0145 {
0146     if (type == TooltipMessage) {
0147         m_tooltipText = text;
0148         if (m_currentMessage.type == DefaultMessage) {
0149             m_label->setText(m_tooltipText);
0150         }
0151         return;
0152     }
0153     if (type == m_currentMessage.type && text == m_currentMessage.text) {
0154         return;
0155     }
0156     StatusBarMessageItem item(text, type, timeoutMS);
0157     if (type == OperationCompletedMessage) {
0158         m_progress->setVisible(false);
0159     }
0160     if (item.type == ErrorMessage || item.type == MltError) {
0161         KNotification::event(QStringLiteral("ErrorMessage"), item.text);
0162     }
0163 
0164     m_queueSemaphore.acquire();
0165     if (!m_messageQueue.contains(item)) {
0166         if (item.type == ErrorMessage || item.type == MltError || item.type == ProcessingJobMessage) {
0167             qCDebug(KDENLIVE_LOG) << item.text;
0168 
0169             // Put the new error message at first place and immediately show it
0170             item.timeoutMillis = qMax(item.timeoutMillis, 4000);
0171 
0172             if (item.type == ProcessingJobMessage) {
0173                 // This is a job progress info, discard previous ones
0174                 QList<StatusBarMessageItem> cleanList;
0175                 for (const StatusBarMessageItem &msg : qAsConst(m_messageQueue)) {
0176                     if (msg.type != ProcessingJobMessage) {
0177                         cleanList << msg;
0178                     }
0179                 }
0180                 m_messageQueue = cleanList;
0181             } else {
0182                 // Important error message, delete previous queue so they don't appear afterwards out of context
0183                 m_messageQueue.clear();
0184             }
0185 
0186             m_messageQueue.push_front(item);
0187 
0188             // In case we are already displaying an error message, add a little delay
0189             int delay = 800 * static_cast<int>(m_currentMessage.type == ErrorMessage || m_currentMessage.type == MltError);
0190             m_queueTimer.start(delay);
0191         } else {
0192             // Message with normal priority
0193             item.timeoutMillis = qMax(item.timeoutMillis, 4000);
0194             m_messageQueue.push_back(item);
0195             if (!m_queueTimer.isValid() || m_queueTimer.elapsed() >= m_currentMessage.timeoutMillis) {
0196                 m_queueTimer.start(0);
0197             }
0198         }
0199     }
0200     m_queueSemaphore.release();
0201 }
0202 
0203 bool StatusBarMessageLabel::slotMessageTimeout()
0204 {
0205     m_queueSemaphore.acquire();
0206 
0207     bool newMessage = false;
0208 
0209     // Get the next message from the queue, unless the current one needs to be confirmed
0210     if (m_currentMessage.type == ProcessingJobMessage) {
0211         // Check if we have a job completed message to cancel this one
0212         StatusBarMessageItem item;
0213         while (!m_messageQueue.isEmpty()) {
0214             item = m_messageQueue.at(0);
0215             m_messageQueue.removeFirst();
0216             if (item.type == OperationCompletedMessage || item.type == ErrorMessage || item.type == MltError || item.type == ProcessingJobMessage) {
0217                 m_currentMessage = item;
0218                 m_label->setText(m_currentMessage.text);
0219                 newMessage = true;
0220                 break;
0221             }
0222         }
0223     } else if (!m_messageQueue.isEmpty()) {
0224         if (!m_currentMessage.needsConfirmation()) {
0225             m_currentMessage = m_messageQueue.at(0);
0226             m_label->setText(m_currentMessage.text);
0227             m_messageQueue.removeFirst();
0228             newMessage = true;
0229         }
0230     }
0231 
0232     // If the queue is empty, add a default (empty) message
0233     if (m_messageQueue.isEmpty() && m_currentMessage.type != DefaultMessage) {
0234         m_messageQueue.push_back(StatusBarMessageItem());
0235     }
0236 
0237     // Start a new timer, unless the current message still needs to be confirmed
0238     if (!m_messageQueue.isEmpty()) {
0239 
0240         if (!m_currentMessage.needsConfirmation()) {
0241             m_queueTimer.start(m_currentMessage.timeoutMillis);
0242         }
0243     }
0244 
0245     QColor errorBgColor = KStatefulBrush(KColorScheme::Window, KColorScheme::NegativeBackground).brush(m_container->palette()).color();
0246     const char *iconName = nullptr;
0247     m_container->setColor(m_container->palette().window().color());
0248     switch (m_currentMessage.type) {
0249     case ProcessingJobMessage:
0250         iconName = "chronometer";
0251         m_pixmap->setCursor(Qt::ArrowCursor);
0252         break;
0253     case OperationCompletedMessage:
0254         iconName = "dialog-ok";
0255         m_pixmap->setCursor(Qt::ArrowCursor);
0256         break;
0257 
0258     case InformationMessage: {
0259         iconName = "dialog-information";
0260         m_pixmap->setCursor(Qt::ArrowCursor);
0261         QPropertyAnimation *anim = new QPropertyAnimation(m_container, "color", this);
0262         anim->setDuration(qMin(m_currentMessage.timeoutMillis, 3000));
0263         anim->setEasingCurve(QEasingCurve::InOutQuad);
0264         anim->setKeyValueAt(0.2, m_container->palette().highlight().color());
0265         anim->setEndValue(m_container->palette().window().color());
0266         anim->start(QPropertyAnimation::DeleteWhenStopped);
0267         break;
0268     }
0269 
0270     case ErrorMessage: {
0271         iconName = "dialog-warning";
0272         m_pixmap->setCursor(Qt::ArrowCursor);
0273         QPropertyAnimation *anim = new QPropertyAnimation(m_container, "color", this);
0274         anim->setStartValue(errorBgColor);
0275         anim->setKeyValueAt(0.8, errorBgColor);
0276         anim->setEndValue(m_container->palette().window().color());
0277         anim->setEasingCurve(QEasingCurve::OutCubic);
0278         anim->setDuration(qMin(m_currentMessage.timeoutMillis, 4000));
0279         anim->start(QPropertyAnimation::DeleteWhenStopped);
0280         break;
0281     }
0282     case MltError: {
0283         iconName = "dialog-close";
0284         m_pixmap->setCursor(Qt::PointingHandCursor);
0285         QPropertyAnimation *anim = new QPropertyAnimation(m_container, "color", this);
0286         anim->setStartValue(errorBgColor);
0287         anim->setEndValue(errorBgColor);
0288         anim->setEasingCurve(QEasingCurve::OutCubic);
0289         anim->setDuration(qMin(m_currentMessage.timeoutMillis, 3000));
0290         anim->start(QPropertyAnimation::DeleteWhenStopped);
0291         break;
0292     }
0293     case DefaultMessage:
0294         m_pixmap->setCursor(Qt::ArrowCursor);
0295         m_label->setText(m_tooltipText);
0296     default:
0297         break;
0298     }
0299 
0300     if (iconName == nullptr) {
0301         m_pixmap->setVisible(false);
0302     } else {
0303         m_pixmap->setPixmap(QIcon::fromTheme(iconName).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)));
0304         m_pixmap->setVisible(true);
0305     }
0306     m_queueSemaphore.release();
0307 
0308     return newMessage;
0309 }
0310 
0311 void StatusBarMessageLabel::confirmErrorMessage()
0312 {
0313     m_currentMessage.confirmed = true;
0314     m_queueTimer.start(0);
0315 }
0316 
0317 void StatusBarMessageLabel::resizeEvent(QResizeEvent *event)
0318 {
0319     QWidget::resizeEvent(event);
0320 }
0321 
0322 void StatusBarMessageLabel::slotShowJobLog(const QString &text)
0323 {
0324     // Special actions
0325     if (text.startsWith(QLatin1Char('#'))) {
0326         if (text == QLatin1String("#projectmonitor")) {
0327             // Raise project monitor
0328             pCore->window()->raiseMonitor(false);
0329             return;
0330         } else if (text == QLatin1String("#clipmonitor")) {
0331             // Raise project monitor
0332             pCore->window()->raiseMonitor(true);
0333             return;
0334         }
0335     }
0336     QDialog d(this);
0337     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0338     QWidget *mainWidget = new QWidget(this);
0339     auto *l = new QVBoxLayout;
0340     QTextEdit t(&d);
0341     t.insertPlainText(QUrl::fromPercentEncoding(text.toUtf8()));
0342     t.setReadOnly(true);
0343     l->addWidget(&t);
0344     mainWidget->setLayout(l);
0345     auto *mainLayout = new QVBoxLayout;
0346     d.setLayout(mainLayout);
0347     mainLayout->addWidget(mainWidget);
0348     mainLayout->addWidget(buttonBox);
0349     d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::accept);
0350     d.exec();
0351     confirmErrorMessage();
0352 }