File indexing completed on 2024-04-21 04:52:39

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