File indexing completed on 2024-12-08 04:27:22
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 }