statusbarmessagelabel.cpp 11 KB
Newer Older
1
2
/***************************************************************************
 *   Copyright (C) 2006 by Peter Penz                                      *
3
 *                 2012    Simon A. Eugster <simon.eu@gmail.com>           *
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 *   peter.penz@gmx.at                                                     *
 *   Code borrowed from Dolphin, adapted (2008) to Kdenlive by             *
 *   Jean-Baptiste Mardelle, jb@kdenlive.org                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
 ***************************************************************************/

#include "statusbarmessagelabel.h"
25
#include "kdenlivesettings.h"
26

Nicolas Carion's avatar
Nicolas Carion committed
27
#include <KNotification>
28
29
#include <kcolorscheme.h>
#include <kiconloader.h>
30
#include <klocalizedstring.h>
31

32
33
#include <QDialog>
#include <QDialogButtonBox>
Nicolas Carion's avatar
Nicolas Carion committed
34
#include <QHBoxLayout>
35
#include <QIcon>
Nicolas Carion's avatar
Nicolas Carion committed
36
37
38
39
#include <QLabel>
#include <QMouseEvent>
#include <QPixmap>
#include <QProgressBar>
Nicolas Carion's avatar
linting    
Nicolas Carion committed
40
#include <QPropertyAnimation>
Nicolas Carion's avatar
Nicolas Carion committed
41
#include <QPushButton>
42
#include <QStyle>
43
#include <QTextEdit>
44

45
46
FlashLabel::FlashLabel(QWidget *parent)
    : QWidget(parent)
47
48
49
50
{
    setAutoFillBackground(true);
}

Nicolas Carion's avatar
Nicolas Carion committed
51
FlashLabel::~FlashLabel() = default;
52

Laurent Montel's avatar
Laurent Montel committed
53
void FlashLabel::setColor(const QColor &col)
54
55
56
57
58
59
60
61
62
63
64
65
{
    QPalette pal = palette();
    pal.setColor(QPalette::Window, col);
    setPalette(pal);
    update();
}

QColor FlashLabel::color() const
{
    return palette().window().color();
}

66
67
68
69
StatusBarMessageLabel::StatusBarMessageLabel(QWidget *parent)
    : FlashLabel(parent)
    , m_minTextHeight(-1)
    , m_queueSemaphore(1)
70
{
71
    setMinimumHeight(KIconLoader::SizeSmall);
Nicolas Carion's avatar
Nicolas Carion committed
72
    auto *lay = new QHBoxLayout(this);
73
74
75
76
77
78
79
80
81
82
    m_pixmap = new QLabel(this);
    m_pixmap->setAlignment(Qt::AlignCenter);
    m_label = new QLabel(this);
    m_label->setAlignment(Qt::AlignLeft);
    m_progress = new QProgressBar(this);
    lay->addWidget(m_pixmap);
    lay->addWidget(m_label);
    lay->addWidget(m_progress);
    setLayout(lay);
    m_progress->setVisible(false);
83
    lay->setContentsMargins(BorderGap, 0, 2 * BorderGap, 0);
84
    m_queueTimer.setSingleShot(true);
Laurent Montel's avatar
Laurent Montel committed
85
86
    connect(&m_queueTimer, &QTimer::timeout, this, &StatusBarMessageLabel::slotMessageTimeout);
    connect(m_label, &QLabel::linkActivated, this, &StatusBarMessageLabel::slotShowJobLog);
87
88
}

Nicolas Carion's avatar
Nicolas Carion committed
89
StatusBarMessageLabel::~StatusBarMessageLabel() = default;
90

91
92
93
void StatusBarMessageLabel::mousePressEvent(QMouseEvent *event)
{
    QWidget::mousePressEvent(event);
Laurent Montel's avatar
Laurent Montel committed
94
    if (m_pixmap->rect().contains(event->localPos().toPoint()) && m_currentMessage.type == MltError) {
95
        confirmErrorMessage();
Laurent Montel's avatar
Laurent Montel committed
96
    }
97
98
}

Laurent Montel's avatar
Laurent Montel committed
99
void StatusBarMessageLabel::setProgressMessage(const QString &text, int progress, MessageType type, int timeoutMS)
100
101
102
103
{
    if (type == ProcessingJobMessage) {
        m_progress->setValue(progress);
        m_progress->setVisible(progress < 100);
Laurent Montel's avatar
Laurent Montel committed
104
    } else if (m_currentMessage.type != ProcessingJobMessage || type == OperationCompletedMessage) {
105
106
        m_progress->setVisible(progress < 100);
    }
Laurent Montel's avatar
Laurent Montel committed
107
    if (text == m_currentMessage.text) {
108
        return;
Laurent Montel's avatar
Laurent Montel committed
109
    }
110
111
    setMessage(text, type, timeoutMS);
}
112

Laurent Montel's avatar
Laurent Montel committed
113
void StatusBarMessageLabel::setMessage(const QString &text, MessageType type, int timeoutMS)
114
115
116
117
118
{
    StatusBarMessageItem item(text, type, timeoutMS);
    if (type == OperationCompletedMessage) {
        m_progress->setVisible(false);
    }
119
    if (item.type == ErrorMessage || item.type == MltError) {
120
        KNotification::event(QStringLiteral("ErrorMessage"), item.text);
121
122
123
124
    }

    m_queueSemaphore.acquire();
    if (!m_messageQueue.contains(item)) {
125
        if (item.type == ErrorMessage || item.type == MltError || item.type == ProcessingJobMessage) {
Laurent Montel's avatar
Laurent Montel committed
126
            qCDebug(KDENLIVE_LOG) << item.text;
127

Yuri Chornoivan's avatar
Yuri Chornoivan committed
128
            // Put the new error message at first place and immediately show it
129
130
            if (item.timeoutMillis < 3000) {
                item.timeoutMillis = 3000;
131
            }
132
133
            if (item.type == ProcessingJobMessage) {
                // This is a job progress info, discard previous ones
Laurent Montel's avatar
Laurent Montel committed
134
                QList<StatusBarMessageItem> cleanList;
Nicolas Carion's avatar
Nicolas Carion committed
135
                for (const StatusBarMessageItem &msg : m_messageQueue) {
136
137
138
139
140
                    if (msg.type != ProcessingJobMessage) {
                        cleanList << msg;
                    }
                }
                m_messageQueue = cleanList;
141
142
143
            } else {
                // Important error message, delete previous queue so they don't appear afterwards out of context
                m_messageQueue.clear();
144
145
            }

146
147
148
            m_messageQueue.push_front(item);

            // In case we are already displaying an error message, add a little delay
Nicolas Carion's avatar
Nicolas Carion committed
149
            int delay = 800 * static_cast<int>(m_currentMessage.type == ErrorMessage || m_currentMessage.type == MltError);
150
151
152
153
            m_queueTimer.start(delay);
        } else {
            // Message with normal priority
            m_messageQueue.push_back(item);
154
            if (!m_queueTimer.isValid() || m_queueTimer.elapsed() >= m_currentMessage.timeoutMillis) {
155
156
157
158
159
160
161
162
                m_queueTimer.start(0);
            }
        }
    }
    m_queueSemaphore.release();
}

bool StatusBarMessageLabel::slotMessageTimeout()
163
{
164
165
166
167
168
    m_queueSemaphore.acquire();

    bool newMessage = false;

    // Get the next message from the queue, unless the current one needs to be confirmed
169
170
171
172
173
174
    if (m_currentMessage.type == ProcessingJobMessage) {
        // Check if we have a job completed message to cancel this one
        StatusBarMessageItem item;
        while (!m_messageQueue.isEmpty()) {
            item = m_messageQueue.at(0);
            m_messageQueue.removeFirst();
175
            if (item.type == OperationCompletedMessage || item.type == ErrorMessage || item.type == MltError || item.type == ProcessingJobMessage) {
176
                m_currentMessage = item;
177
                m_label->setText(m_currentMessage.text);
178
179
180
181
                newMessage = true;
                break;
            }
        }
Laurent Montel's avatar
Laurent Montel committed
182
    } else if (!m_messageQueue.isEmpty()) {
183
184
        if (!m_currentMessage.needsConfirmation()) {
            m_currentMessage = m_messageQueue.at(0);
185
            m_label->setText(m_currentMessage.text);
186
187
188
189
190
191
            m_messageQueue.removeFirst();
            newMessage = true;
        }
    }

    // If the queue is empty, add a default (empty) message
Laurent Montel's avatar
Laurent Montel committed
192
    if (m_messageQueue.isEmpty() && m_currentMessage.type != DefaultMessage) {
193
        m_messageQueue.push_back(StatusBarMessageItem());
194
195
    }

196
    // Start a new timer, unless the current message still needs to be confirmed
Laurent Montel's avatar
Laurent Montel committed
197
    if (!m_messageQueue.isEmpty()) {
198
199
200
201

        if (!m_currentMessage.needsConfirmation()) {
            // If we only have the default message left to show in the queue,
            // keep the current one for a little longer.
Nicolas Carion's avatar
Nicolas Carion committed
202
            m_queueTimer.start(m_currentMessage.timeoutMillis + 4000 * static_cast<int>(m_messageQueue.at(0).type == DefaultMessage));
203
        }
204
    }
205

206
    QColor bgColor = KStatefulBrush(KColorScheme::Window, KColorScheme::NegativeBackground).brush(this).color();
Laurent Montel's avatar
Laurent Montel committed
207
    const char *iconName = nullptr;
208
    setColor(parentWidget()->palette().window().color());
209
    switch (m_currentMessage.type) {
210
211
    case ProcessingJobMessage:
        iconName = "chronometer";
212
        m_pixmap->setCursor(Qt::ArrowCursor);
213
        break;
214
215
    case OperationCompletedMessage:
        iconName = "dialog-ok";
216
        m_pixmap->setCursor(Qt::ArrowCursor);
217
218
        break;

219
    case InformationMessage: {
220
        iconName = "dialog-information";
221
        m_pixmap->setCursor(Qt::ArrowCursor);
222
223
224
225
226
227
        QPropertyAnimation *anim = new QPropertyAnimation(this, "color", this);
        anim->setDuration(1500);
        anim->setEasingCurve(QEasingCurve::InOutQuad);
        anim->setKeyValueAt(0.2, parentWidget()->palette().highlight().color());
        anim->setEndValue(parentWidget()->palette().window().color());
        anim->start(QPropertyAnimation::DeleteWhenStopped);
228
        break;
229
    }
230

231
    case ErrorMessage: {
232
        iconName = "dialog-warning";
233
        m_pixmap->setCursor(Qt::ArrowCursor);
234
235
236
237
238
239
240
        QPropertyAnimation *anim = new QPropertyAnimation(this, "color", this);
        anim->setStartValue(bgColor);
        anim->setKeyValueAt(0.8, bgColor);
        anim->setEndValue(parentWidget()->palette().window().color());
        anim->setEasingCurve(QEasingCurve::OutCubic);
        anim->setDuration(4000);
        anim->start(QPropertyAnimation::DeleteWhenStopped);
241
        break;
242
243
    }
    case MltError: {
244
        iconName = "dialog-close";
245
        m_pixmap->setCursor(Qt::PointingHandCursor);
246
247
248
249
250
251
        QPropertyAnimation *anim = new QPropertyAnimation(this, "color", this);
        anim->setStartValue(bgColor);
        anim->setEndValue(bgColor);
        anim->setEasingCurve(QEasingCurve::OutCubic);
        anim->setDuration(1500);
        anim->start(QPropertyAnimation::DeleteWhenStopped);
252
        break;
253
    }
254
    case DefaultMessage:
255
        m_pixmap->setCursor(Qt::ArrowCursor);
256
257
258
259
    default:
        break;
    }

Laurent Montel's avatar
Laurent Montel committed
260
    if (iconName == nullptr) {
261
262
        m_pixmap->setVisible(false);
    } else {
263
        m_pixmap->setPixmap(QIcon::fromTheme(iconName).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)));
264
265
        m_pixmap->setVisible(true);
    }
266
    m_queueSemaphore.release();
267

268
269
270
271
272
273
274
    return newMessage;
}

void StatusBarMessageLabel::confirmErrorMessage()
{
    m_currentMessage.confirmed = true;
    m_queueTimer.start(0);
275
276
}

Laurent Montel's avatar
Laurent Montel committed
277
void StatusBarMessageLabel::resizeEvent(QResizeEvent *event)
278
{
279
280
281
    QWidget::resizeEvent(event);
}

282
283
284
285
286
void StatusBarMessageLabel::slotShowJobLog(const QString &text)
{
    QDialog d(this);
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
    QWidget *mainWidget = new QWidget(this);
Nicolas Carion's avatar
Nicolas Carion committed
287
    auto *l = new QVBoxLayout;
288
289
290
291
292
    QTextEdit t(&d);
    t.insertPlainText(QUrl::fromPercentEncoding(text.toUtf8()));
    t.setReadOnly(true);
    l->addWidget(&t);
    mainWidget->setLayout(l);
Nicolas Carion's avatar
Nicolas Carion committed
293
    auto *mainLayout = new QVBoxLayout;
294
295
296
    d.setLayout(mainLayout);
    mainLayout->addWidget(mainWidget);
    mainLayout->addWidget(buttonBox);
Laurent Montel's avatar
Laurent Montel committed
297
    d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::accept);
298
299
300
    d.exec();
    confirmErrorMessage();
}