bin.cpp 190 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/*
Copyright (C) 2012  Till Theato <root@ttill.de>
Copyright (C) 2014  Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
11
by the membership of KDE e.V.), which shall act as a proxy
12
13
14
15
16
17
18
19
20
21
22
23
defined in Section 14 of version 3 of the license.

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, see <http://www.gnu.org/licenses/>.
*/

#include "bin.h"
Nicolas Carion's avatar
Nicolas Carion committed
24
#include "bincommands.h"
25
#include "clipcreator.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
26
27
#include "core.h"
#include "dialogs/clipcreationdialog.h"
28
#include "dialogs/timeremap.h"
Nicolas Carion's avatar
Nicolas Carion committed
29
30
31
#include "doc/documentchecker.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
Nicolas Carion's avatar
Nicolas Carion committed
32
#include "effects/effectstack/model/effectstackmodel.hpp"
33
#include "jobs/transcodetask.h"
34
#include "jobs/taskmanager.h"
35
#include "jobs/abstracttask.h"
36
#include "jobs/cliploadtask.h"
37
#include "kdenlive_debug.h"
38
#include "kdenlivesettings.h"
Nicolas Carion's avatar
Nicolas Carion committed
39
40
41
42
43
#include "mainwindow.h"
#include "mlt++/Mlt.h"
#include "mltcontroller/clipcontroller.h"
#include "mltcontroller/clippropertiescontroller.h"
#include "monitor/monitor.h"
Julius Künzel's avatar
Julius Künzel committed
44
#include "monitor/monitormanager.h"
45
#include "project/dialogs/slideshowclip.h"
Nicolas Carion's avatar
Nicolas Carion committed
46
#include "project/invaliddialog.h"
47
#include "project/transcodeseek.h"
48
#include "project/projectcommands.h"
Nicolas Carion's avatar
Nicolas Carion committed
49
50
51
52
#include "project/projectmanager.h"
#include "projectclip.h"
#include "projectfolder.h"
#include "projectitemmodel.h"
53
#include "projectsortproxymodel.h"
Nicolas Carion's avatar
Nicolas Carion committed
54
#include "projectsubclip.h"
55
#include "tagwidget.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
56
57
#include "titler/titlewidget.h"
#include "ui_qtextclip_ui.h"
Nicolas Carion's avatar
Nicolas Carion committed
58
#include "undohelper.hpp"
59
#include "xml/xml.hpp"
60
#include <dialogs/textbasededit.h>
61
62
63
#include <memory>
#include <profiles/profilemodel.hpp>
#include <utils/thumbnailcache.hpp>
64

65
#include <KColorScheme>
66
#include <KRatingPainter>
67
#include <KMessageBox>
68
#include <KXMLGUIFactory>
69
#include <KIO/OpenFileManagerWindowJob>
70

71
#include <QToolBar>
Nicolas Carion's avatar
Nicolas Carion committed
72
#include <QCryptographicHash>
73
#include <QDrag>
Nicolas Carion's avatar
Nicolas Carion committed
74
#include <QFile>
75
#include <QMenu>
76
#include <QActionGroup>
Nicolas Carion's avatar
Nicolas Carion committed
77
78
#include <QSlider>
#include <QTimeLine>
79
#include <QUndoCommand>
Nicolas Carion's avatar
Nicolas Carion committed
80
81
#include <QUrl>
#include <QVBoxLayout>
Nicolas Carion's avatar
Nicolas Carion committed
82
#include <utility>
83
#include <jobs/audiolevelstask.h>
84

85
86
87
88
89
90
91
92
93
/**
 * @class BinItemDelegate
 * @brief This class is responsible for drawing items in the QTreeView.
 */
class BinItemDelegate : public QStyledItemDelegate
{
public:
    explicit BinItemDelegate(QObject *parent = nullptr)
        : QStyledItemDelegate(parent)
Nicolas Carion's avatar
Nicolas Carion committed
94

95
    {
96
        connect(this, &QStyledItemDelegate::closeEditor, [&]() { m_editorOpen = false; });
97
    }
98
99
100
101
102
103
104
    void setEditorData(QWidget *w, const QModelIndex &i) const override
    {
        if (!m_editorOpen) {
            QStyledItemDelegate::setEditorData(w, i);
            m_editorOpen = true;
        }
    }
105
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
106
107
    {
        if (event->type() == QEvent::MouseButtonPress) {
108
            auto *me = static_cast<QMouseEvent *>(event);
109
110
111
112
113
114
115
116
            if (index.column() == 0) {
                if (m_audioDragRect.contains(me->pos())) {
                    dragType = PlaylistState::AudioOnly;
                } else if (m_videoDragRect.contains(me->pos())) {
                    dragType = PlaylistState::VideoOnly;
                } else {
                    dragType = PlaylistState::Disabled;
                }
117
118
            } else {
                dragType = PlaylistState::Disabled;
119
120
                if (index.column() == 7) {
                    // Rating
121
                    QRect rect = option.rect;
122
                    rect.adjust(option.rect.width() / 12, 0, 0, 0);
123
124
                    int rate = 0;
                    if (me->pos().x() > rect.x()) {
125
                        rate = KRatingPainter::getRatingFromPosition(rect, Qt::AlignLeft | Qt::AlignVCenter, qApp->layoutDirection(), me->pos());
126
                    }
127
128
129
130
131
                    if (rate > -1) {
                        // Full star rating only
                        if (rate %2 == 1) {
                            rate++;
                        }
132
                        emit static_cast<ProjectSortProxyModel *>(model)->updateRating(index, uint(rate));
133
134
                    }
                }
135
136
137
138
139
140
141
142
            }
        }
        event->ignore();
        return false;
    }
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        if (index.column() != 0) {
143
144
            QStyledItemDelegate::updateEditorGeometry(editor, option, index);
            return;
145
146
147
148
149
        }
        QStyleOptionViewItem opt = option;
        initStyleOption(&opt, index);
        QRect r1 = option.rect;
        int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
150
        int decoWidth = 0;
151
        if (opt.decorationSize.height() > 0) {
Vincent Pinon's avatar
Vincent Pinon committed
152
            decoWidth += int(r1.height() * pCore->getCurrentDar());
153
        }
154
        int mid = 0;
155
        if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) {
156
            mid = int((r1.height() / 2));
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
        }
        r1.adjust(decoWidth, 0, 0, -mid);
        QFont ft = option.font;
        ft.setBold(true);
        QFontMetricsF fm(ft);
        QRect r2 = fm.boundingRect(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString()).toRect();
        editor->setGeometry(r2);
    }

    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        QSize hint = QStyledItemDelegate::sizeHint(option, index);
        QString text = index.data(AbstractProjectItem::DataName).toString();
        QRectF r = option.rect;
        QFont ft = option.font;
        ft.setBold(true);
        QFontMetricsF fm(ft);
        QStyle *style = option.widget ? option.widget->style() : QApplication::style();
        const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
176
        int width = int(fm.boundingRect(r, Qt::AlignLeft | Qt::AlignTop, text).width() + option.decorationSize.width()) + 2 * textMargin;
177
178
        hint.setWidth(width);
        int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
179
        if (type == AbstractProjectItem::FolderItem) {
180
181
182
183
184
185
            return QSize(hint.width(), qMin(option.fontMetrics.lineSpacing() + 4, hint.height()));
        }
        if (type == AbstractProjectItem::ClipItem) {
            return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMax(hint.height(), option.decorationSize.height())));
        }
        if (type == AbstractProjectItem::SubClipItem) {
186
            return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMin(hint.height(), int(option.decorationSize.height() / 1.5))));
187
188
189
190
191
        }
        QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
        QString line1 = index.data(Qt::DisplayRole).toString();
        QString line2 = index.data(Qt::UserRole).toString();

Vincent Pinon's avatar
Vincent Pinon committed
192
        int textW = qMax(option.fontMetrics.horizontalAdvance(line1), option.fontMetrics.horizontalAdvance(line2));
193
        QSize iconSize = icon.actualSize(option.decorationSize);
Nicolas Carion's avatar
Nicolas Carion committed
194
        return {qMax(textW, iconSize.width()) + 4, option.fontMetrics.lineSpacing() * 2 + 4};
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        if (index.column() == 0 && !index.data().isNull()) {
            QRect r1 = option.rect;
            painter->save();
            painter->setClipRect(r1);
            QStyleOptionViewItem opt(option);
            initStyleOption(&opt, index);
            int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
            QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
            const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
            // QRect r = QStyle::alignedRect(opt.direction, Qt::AlignVCenter | Qt::AlignLeft, opt.decorationSize, r1);

            style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
211
            if ((option.state & static_cast<int>(QStyle::State_Selected)) != 0) {
212
213
214
215
216
217
218
219
220
                painter->setPen(option.palette.highlightedText().color());
            } else {
                painter->setPen(option.palette.text().color());
            }
            QRect r = r1;
            QFont font = painter->font();
            font.setBold(true);
            painter->setFont(font);
            if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) {
221
                int decoWidth = 0;
222
                FileStatus::ClipStatus clipStatus = FileStatus::ClipStatus(index.data(AbstractProjectItem::ClipStatus).toInt());
223
                if (opt.decorationSize.height() > 0) {
224
                    r.setWidth(int(r.height() * pCore->getCurrentDar()));
225
                    QPixmap pix = opt.icon.pixmap(opt.icon.actualSize(r.size()));
226
227
228
229
230
231
                    if (!pix.isNull()) {
                        // Draw icon
                        decoWidth += r.width() + textMargin;
                        r.setWidth(r.height() * pix.width() / pix.height());
                        painter->drawPixmap(r, pix, QRect(0, 0, pix.width(), pix.height()));
                    }
232
                    m_thumbRect = r;
233
                }
234
                // Draw frame in case of missing source
235
                int cType = index.data(AbstractProjectItem::ClipType).toInt();
236
                if (clipStatus == FileStatus::StatusMissing || clipStatus == FileStatus::StatusProxyOnly) {
237
                    painter->save();
238
                    painter->setPen(QPen(clipStatus == FileStatus::StatusProxyOnly ? Qt::yellow : Qt::red, 3));
239
                    painter->drawRect(m_thumbRect.adjusted(0, 0, -1, -1));
240
                    painter->restore();
241
242
243
244
245
246
247
248
249
250
251
                } else if (cType == ClipType::Image || cType == ClipType::SlideShow) {
                    // Draw 'photo' frame to identify image clips
                    painter->save();
                    int penWidth = m_thumbRect.height() / 14;
                    penWidth += penWidth % 2;
                    painter->setPen(QPen(QColor(255, 255, 255, 160), penWidth));
                    penWidth /= 2;
                    painter->drawRoundedRect(m_thumbRect.adjusted(penWidth, penWidth, -penWidth - 1, -penWidth - 1), 4, 4);
                    painter->setPen(QPen(Qt::black, 1));
                    painter->drawRoundedRect(m_thumbRect.adjusted(0, 0, -1, -1), 4, 4);
                    painter->restore();
252
                }
253
                int mid = int((r1.height() / 2));
254
255
256
257
258
259
260
261
                r1.adjust(decoWidth, 0, 0, -mid);
                QRect r2 = option.rect;
                r2.adjust(decoWidth, mid, 0, 0);
                QRectF bounding;
                painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding);
                font.setBold(false);
                painter->setFont(font);
                QString subText = index.data(AbstractProjectItem::DataDuration).toString();
262
263
264
                QString tags = index.data(AbstractProjectItem::DataTag).toString();
                if (!tags.isEmpty()) {
                    QStringList t = tags.split(QLatin1Char(';'));
265
266
267
                    QRectF tagRect = m_thumbRect.adjusted(2, 2, 0, 2);
                    tagRect.setWidth(r1.height() / 3.5);
                    tagRect.setHeight(tagRect.width());
Vincent Pinon's avatar
Vincent Pinon committed
268
                    for (const QString &color : qAsConst(t)) {
269
270
271
                        painter->setBrush(QColor(color));
                        painter->drawRoundedRect(tagRect, tagRect.height() / 2, tagRect.height() / 2);
                        tagRect.moveTop(tagRect.bottom() + tagRect.height() / 4);
272
273
                    }
                }
274
                if (!subText.isEmpty()) {
275
                    r2.adjust(0, int(bounding.bottom() - r2.top()), 0, 0);
276
277
278
279
280
281
                    QColor subTextColor = painter->pen().color();
                    subTextColor.setAlphaF(.5);
                    painter->setPen(subTextColor);
                    // Draw usage counter
                    int usage = index.data(AbstractProjectItem::UsageCount).toInt();
                    if (usage > 0) {
Laurent Montel's avatar
Laurent Montel committed
282
                        subText.append(QString::asprintf(" [%d]", usage));
283
284
                    }
                    painter->drawText(r2, Qt::AlignLeft | Qt::AlignTop, subText, &bounding);
285
                    // Add audio/video icons for selective drag
286
287
                    bool hasAudioAndVideo = index.data(AbstractProjectItem::ClipHasAudioAndVideo).toBool();
                    if (hasAudioAndVideo && (cType == ClipType::AV || cType == ClipType::Playlist) && (opt.state & QStyle::State_MouseOver)) {
288
289
290
291
292
293
294
295
296
297
298
                        bounding.moveLeft(bounding.right() + (2 * textMargin));
                        bounding.adjust(0, textMargin, 0, -textMargin);
                        QIcon aDrag = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
                        m_audioDragRect = bounding.toRect();
                        m_audioDragRect.setWidth(m_audioDragRect.height());
                        aDrag.paint(painter, m_audioDragRect, Qt::AlignLeft);
                        m_videoDragRect = m_audioDragRect;
                        m_videoDragRect.moveLeft(m_audioDragRect.right());
                        QIcon vDrag = QIcon::fromTheme(QStringLiteral("kdenlive-show-video"));
                        vDrag.paint(painter, m_videoDragRect, Qt::AlignLeft);
                    } else {
299
300
                        //m_audioDragRect = QRect();
                        //m_videoDragRect = QRect();
301
302
303
304
305
306
307
                    }
                }
                if (type == AbstractProjectItem::ClipItem) {
                    // Overlay icon if necessary
                    QVariant v = index.data(AbstractProjectItem::IconOverlay);
                    if (!v.isNull()) {
                        QIcon reload = QIcon::fromTheme(v.toString());
308
309
                        r.setTop(int(r.bottom() - bounding.height()));
                        r.setWidth(int(bounding.height()));
310
311
312
                        reload.paint(painter, r);
                    }
                    int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt();
313
314
                    auto status = index.data(AbstractProjectItem::JobStatus).value<TaskManagerStatus>();
                    if (status == TaskManagerStatus::Pending || status == TaskManagerStatus::Running) {
315
316
317
318
319
320
                        // Draw job progress bar
                        int progressWidth = option.fontMetrics.averageCharWidth() * 8;
                        int progressHeight = option.fontMetrics.ascent() / 4;
                        QRect progress(r1.x() + 1, opt.rect.bottom() - progressHeight - 2, progressWidth, progressHeight);
                        painter->setPen(Qt::NoPen);
                        painter->setBrush(Qt::darkGray);
321
                        if (status == TaskManagerStatus::Running) {
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
                            painter->drawRoundedRect(progress, 2, 2);
                            painter->setBrush((option.state & static_cast<int>((QStyle::State_Selected) != 0)) != 0 ? option.palette.text()
                                                                                                                    : option.palette.highlight());
                            progress.setWidth((progressWidth - 2) * jobProgress / 100);
                            painter->drawRoundedRect(progress, 2, 2);
                        } else {
                            // Draw kind of a pause icon
                            progress.setWidth(3);
                            painter->drawRect(progress);
                            progress.moveLeft(progress.right() + 3);
                            painter->drawRect(progress);
                        }
                    }
                    bool jobsucceeded = index.data(AbstractProjectItem::JobSuccess).toBool();
                    if (!jobsucceeded) {
                        QIcon warning = QIcon::fromTheme(QStringLiteral("process-stop"));
                        warning.paint(painter, r2);
                    }
                }
            } else {
342
                // Folder
343
344
                int decoWidth = 0;
                if (opt.decorationSize.height() > 0) {
345
                    r.setWidth(int(r.height() * pCore->getCurrentDar()));
346
                    QPixmap pix = opt.icon.pixmap(opt.icon.actualSize(r.size()));
347
                    // Draw icon
348
349
350
                    decoWidth += r.width() + textMargin;
                    r.setWidth(r.height() * pix.width() / pix.height());
                    painter->drawPixmap(r, pix, QRect(0, 0, pix.width(), pix.height()));
351
352
353
354
355
356
                }
                r1.adjust(decoWidth, 0, 0, 0);
                QRectF bounding;
                painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding);
            }
            painter->restore();
357
        } else if (index.column() == 7) {
358
359
360
            QStyleOptionViewItem opt(option);
            initStyleOption(&opt, index);
            QRect r1 = opt.rect;
361
362
            // Tweak bg opacity since breeze dark star has same color as highlighted background
            painter->setOpacity(0.5);
363
364
            QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
            style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
365
            painter->setOpacity(1);
366
            if (index.data(AbstractProjectItem::ItemTypeRole).toInt() != AbstractProjectItem::FolderItem) {
367
                r1.adjust(r1.width() / 12, 0, 0, 0);
368
                KRatingPainter::paintRating(painter, r1, Qt::AlignLeft | Qt::AlignVCenter, index.data().toInt());
369
            }
370
371
372
373
        } else {
            QStyledItemDelegate::paint(painter, option, index);
        }
    }
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407

    int getFrame(QModelIndex index, int mouseX)
    {
        int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
        if ((type != AbstractProjectItem::ClipItem && type != AbstractProjectItem::SubClipItem) || mouseX < m_thumbRect.x() || mouseX > m_thumbRect.right()) {
            return 0;
        }
        return 100 * (mouseX - m_thumbRect.x()) / m_thumbRect.width();
    }

private:
    mutable bool m_editorOpen{false};
    mutable QRect m_audioDragRect;
    mutable QRect m_videoDragRect;
    mutable QRect m_thumbRect;

public:
    PlaylistState::ClipState dragType{PlaylistState::Disabled};
};

/**
 * @class BinListItemDelegate
 * @brief This class is responsible for drawing items in the QListView.
 */

class BinListItemDelegate : public QStyledItemDelegate
{
public:
    explicit BinListItemDelegate(QObject *parent = nullptr)
        : QStyledItemDelegate(parent)

    {
        connect(this, &QStyledItemDelegate::closeEditor, [&]() { m_editorOpen = false; });
    }
408
409
410
411
412
413
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
    {
        Q_UNUSED(model);
        Q_UNUSED(option);
        Q_UNUSED(index);
        if (event->type() == QEvent::MouseButtonPress) {
414
            auto *me = static_cast<QMouseEvent *>(event);
415
416
417
418
419
420
421
422
423
424
425
            if (m_audioDragRect.contains(me->pos())) {
                dragType = PlaylistState::AudioOnly;
            } else if (m_videoDragRect.contains(me->pos())) {
                dragType = PlaylistState::VideoOnly;
            } else {
                dragType = PlaylistState::Disabled;
            }
        }
        event->ignore();
        return false;
    }
426
427
428
429

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        if (!index.data().isNull()) {
430
            QStyleOptionViewItem opt(option);
431
            initStyleOption(&opt, index);
432
            QStyledItemDelegate::paint(painter, option, index);
433
            int adjust = (opt.rect.width() - opt.decorationSize.width()) / 2;
434
            QRect rect(opt.rect.x(), opt.rect.y(), opt.decorationSize.width(), opt.decorationSize.height());
435
436
437
438
            if (adjust > 0 && adjust < rect.width()) {
                rect.translate(adjust, 0);
            }
            m_thumbRect = rect;
439

440
441
442
443
444
445
446
            //Tags
            QString tags = index.data(AbstractProjectItem::DataTag).toString();
            if (!tags.isEmpty()) {
                QStringList t = tags.split(QLatin1Char(';'));
                QRectF tagRect = m_thumbRect.adjusted(2, 2, 0, 2);
                tagRect.setWidth(m_thumbRect.height() / 5);
                tagRect.setHeight(tagRect.width());
Vincent Pinon's avatar
Vincent Pinon committed
447
                for (const QString &color : qAsConst(t)) {
448
449
450
451
452
                    painter->setBrush(QColor(color));
                    painter->drawRoundedRect(tagRect, tagRect.height() / 2, tagRect.height() / 2);
                    tagRect.moveTop(tagRect.bottom() + tagRect.height() / 4);
                }
            }
453

454
            // Add audio/video icons for selective drag
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
            int cType = index.data(AbstractProjectItem::ClipType).toInt();
            bool hasAudioAndVideo = index.data(AbstractProjectItem::ClipHasAudioAndVideo).toBool();
            if (hasAudioAndVideo && (cType == ClipType::AV || cType == ClipType::Playlist) && (opt.state & QStyle::State_MouseOver)) {
                QRect thumbRect = m_thumbRect;
                int iconSize = painter->boundingRect(thumbRect, Qt::AlignLeft, QStringLiteral("O")).height();
                thumbRect.setLeft(opt.rect.right() - iconSize - 4);
                thumbRect.setWidth(iconSize);
                thumbRect.setBottom(m_thumbRect.top() + iconSize);
                QIcon aDrag = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
                m_audioDragRect = thumbRect;
                aDrag.paint(painter, m_audioDragRect, Qt::AlignRight);
                m_videoDragRect = m_audioDragRect;
                m_videoDragRect.moveTop(thumbRect.bottom());
                QIcon vDrag = QIcon::fromTheme(QStringLiteral("kdenlive-show-video"));
                vDrag.paint(painter, m_videoDragRect, Qt::AlignRight);
            } else {
                //m_audioDragRect = QRect();
                //m_videoDragRect = QRect();
            }
474
            // Draw frame in case of missing source
475
            FileStatus::ClipStatus clipStatus = FileStatus::ClipStatus(index.data(AbstractProjectItem::ClipStatus).toInt());
476
            if (clipStatus == FileStatus::StatusMissing || clipStatus == FileStatus::StatusProxyOnly) {
477
                painter->save();
478
                painter->setPen(QPen(clipStatus == FileStatus::StatusProxyOnly ? Qt::yellow : Qt::red, 3));
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
                painter->drawRect(m_thumbRect);
                painter->restore();
            } else if (cType == ClipType::Image || cType == ClipType::SlideShow) {
                // Draw 'photo' frame to identify image clips
                painter->save();
                int penWidth = m_thumbRect.height() / 14;
                penWidth += penWidth % 2;
                painter->setPen(QPen(QColor(255, 255, 255, 160), penWidth));
                penWidth /= 2;
                painter->drawRoundedRect(m_thumbRect.adjusted(penWidth, penWidth, -penWidth - 1, -penWidth + 1), 4, 4);
                painter->setPen(QPen(Qt::black, 1));
                painter->drawRoundedRect(m_thumbRect.adjusted(0, 0, -1, 1), 4, 4);
                painter->restore();
            }
            int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
            if (type == AbstractProjectItem::ClipItem) {
                // Overlay icon if necessary
                QVariant v = index.data(AbstractProjectItem::IconOverlay);
                if (!v.isNull()) {
                    QRect r = m_thumbRect;
                    QIcon reload = QIcon::fromTheme(v.toString());
                    r.setTop(r.bottom() - (opt.rect.height() - r.height()));
                    r.setWidth(r.height());
                    reload.paint(painter, r);
                }
            }
505
506
507
        }
    }

508
509
510
    int getFrame(QModelIndex index, int mouseX)
    {
        int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
511
        if ((type != AbstractProjectItem::ClipItem && type != AbstractProjectItem::SubClipItem)|| mouseX < m_thumbRect.x() || mouseX > m_thumbRect.right()) {
512
513
514
515
            return 0;
        }
        return 100 * (mouseX - m_thumbRect.x()) / m_thumbRect.width();
    }
516
517

private:
Nicolas Carion's avatar
Nicolas Carion committed
518
    mutable bool m_editorOpen{false};
519
520
    mutable QRect m_audioDragRect;
    mutable QRect m_videoDragRect;
521
    mutable QRect m_thumbRect;
522
523

public:
Nicolas Carion's avatar
Nicolas Carion committed
524
    PlaylistState::ClipState dragType{PlaylistState::Disabled};
525
526
};

527

528
529
MyListView::MyListView(QWidget *parent)
    : QListView(parent)
530
531
532
533
{
    setViewMode(QListView::IconMode);
    setMovement(QListView::Static);
    setResizeMode(QListView::Adjust);
534
    setWordWrap(true);
535
536
537
538
539
540
541
542
543
    setDragDropMode(QAbstractItemView::DragDrop);
    setAcceptDrops(true);
    setDragEnabled(true);
    viewport()->setAcceptDrops(true);
}

void MyListView::focusInEvent(QFocusEvent *event)
{
    QListView::focusInEvent(event);
544
545
546
    if (event->reason() == Qt::MouseFocusReason) {
        emit focusView();
    }
547
548
}

549
550
551
void MyListView::enterEvent(QEvent *event)
{
    QListView::enterEvent(event);
552
    pCore->setWidgetKeyBinding(i18n("<b>Double click</b> to add a file to the project"));
553
554
555
556
557
}

void MyListView::leaveEvent(QEvent *event)
{
    QListView::leaveEvent(event);
558
    pCore->setWidgetKeyBinding();
559
560
}

561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
void MyListView::mousePressEvent(QMouseEvent *event)
{
    QListView::mousePressEvent(event);
    if (event->button() == Qt::LeftButton) {
        m_startPos = event->pos();
        QModelIndex ix = indexAt(m_startPos);
        if (ix.isValid()) {
            QAbstractItemDelegate *del = itemDelegate(ix);
            m_dragType = static_cast<BinListItemDelegate *>(del)->dragType;
        } else {
            m_dragType = PlaylistState::Disabled;
        }
        emit updateDragMode(m_dragType);
    }
}

577
578
void MyListView::mouseMoveEvent(QMouseEvent *event)
{
579
580
581
582
    QModelIndex index = indexAt(event->pos());
    if (index.isValid()) {
        QAbstractItemDelegate *del = itemDelegate(index);
        if (KdenliveSettings::hoverPreview()) {
583
584
585
586
587
588
589
590
591
            if (del) {
                auto delegate = static_cast<BinListItemDelegate *>(del);
                QRect vRect = visualRect(index);
                int frame = delegate->getFrame(index, event->pos().x() - vRect.x());
                emit displayBinFrame(index, frame);
            } else {
                qDebug()<<"<<< NO DELEGATE!!!";
            }
        }
592
        pCore->window()->showKeyBinding(i18n("<b>F2</b> to rename selected item"));
593
594
    } else {
        pCore->window()->showKeyBinding();
595
596
597
598
    }
    QListView::mouseMoveEvent(event);
}

599
600
MyTreeView::MyTreeView(QWidget *parent)
    : QTreeView(parent)
601
602
{
    setEditing(false);
603
    setAcceptDrops(true);
604
}
605
606
607

void MyTreeView::mousePressEvent(QMouseEvent *event)
{
608
    QTreeView::mousePressEvent(event);
609
610
    if (event->button() == Qt::LeftButton) {
        m_startPos = event->pos();
611
612
613
        QModelIndex ix = indexAt(m_startPos);
        if (ix.isValid()) {
            QAbstractItemDelegate *del = itemDelegate(ix);
614
            m_dragType = static_cast<BinItemDelegate *>(del)->dragType;
615
616
617
        } else {
            m_dragType = PlaylistState::Disabled;
        }
618
619
620
    }
}

621
622
623
void MyTreeView::focusInEvent(QFocusEvent *event)
{
    QTreeView::focusInEvent(event);
624
625
626
    if (event->reason() == Qt::MouseFocusReason) {
        emit focusView();
    }
627
628
}

629
630
631
void MyTreeView::enterEvent(QEvent *event)
{
    QTreeView::enterEvent(event);
632
    pCore->setWidgetKeyBinding(i18n("<b>Double click</b> to add a file to the project"));
633
634
635
636
637
}

void MyTreeView::leaveEvent(QEvent *event)
{
    QTreeView::leaveEvent(event);
638
    pCore->setWidgetKeyBinding();
639
640
}

641
void MyTreeView::mouseMoveEvent(QMouseEvent *event)
642
643
{
    bool dragged = false;
644
    if ((event->buttons() & Qt::LeftButton) != 0u) {
645
        int distance = (event->pos() - m_startPos).manhattanLength();
646
647
648
        if (distance >= QApplication::startDragDistance()) {
            dragged = performDrag();
        }
649
    } else {
650
651
        QModelIndex index = indexAt(event->pos());
        if (index.isValid()) {
652
653
654
655
656
            if (KdenliveSettings::hoverPreview()) {
                QAbstractItemDelegate *del = itemDelegate(index);
                int frame = static_cast<BinItemDelegate *>(del)->getFrame(index, event->pos().x());
                emit displayBinFrame(index, frame);
            }
657
            pCore->window()->showKeyBinding(i18n("<b>F2</b> to rename selected item"));
658
659
        } else {
            pCore->window()->showKeyBinding();
660
        }
661
662
663
    }
    if (!dragged) {
        QTreeView::mouseMoveEvent(event);
664
665
666
    }
}

667
668
669
670
671
672
673
674
675
676
677
678
void MyTreeView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
{
    QAbstractItemView::closeEditor(editor, hint);
    setEditing(false);
}

void MyTreeView::editorDestroyed(QObject *editor)
{
    QAbstractItemView::editorDestroyed(editor);
    setEditing(false);
}

Laurent Montel's avatar
Laurent Montel committed
679
bool MyTreeView::isEditing() const
680
{
681
    return state() == QAbstractItemView::EditingState;
682
683
684
685
}

void MyTreeView::setEditing(bool edit)
{
686
    setState(edit ? QAbstractItemView::EditingState : QAbstractItemView::NoState);
687
688
689
690
    if (!edit) {
        // Ensure edited item is selected
        emit selectCurrent();
    }
691
692
}

693
694
695
696
697
698
699
700
701
bool MyTreeView::performDrag()
{
    QModelIndexList bases = selectedIndexes();
    QModelIndexList indexes;
    for (int i = 0; i < bases.count(); i++) {
        if (bases.at(i).column() == 0) {
            indexes << bases.at(i);
        }
    }
702
703
704
    if (indexes.isEmpty()) {
        return false;
    }
705
706
    // Check if we want audio or video only
    emit updateDragMode(m_dragType);
Nicolas Carion's avatar
Nicolas Carion committed
707
    auto *drag = new QDrag(this);
708
    drag->setMimeData(model()->mimeData(indexes));
Laurent Montel's avatar
Laurent Montel committed
709
    QModelIndex ix = indexes.constFirst();
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
    if (ix.isValid()) {
        QIcon icon = ix.data(AbstractProjectItem::DataThumbnail).value<QIcon>();
        QPixmap pix = icon.pixmap(iconSize());
        QSize size = pix.size();
        QImage image(size, QImage::Format_ARGB32_Premultiplied);
        image.fill(Qt::transparent);
        QPainter p(&image);
        p.setOpacity(0.7);
        p.drawPixmap(0, 0, pix);
        p.setOpacity(1);
        if (indexes.count() > 1) {
            QPalette palette;
            int radius = size.height() / 3;
            p.setBrush(palette.highlight());
            p.setPen(palette.highlightedText().color());
            p.drawEllipse(QPoint(size.width() / 2, size.height() / 2), radius, radius);
            p.drawText(size.width() / 2 - radius, size.height() / 2 - radius, 2 * radius, 2 * radius, Qt::AlignCenter, QString::number(indexes.count()));
        }
        p.end();
        drag->setPixmap(QPixmap::fromImage(image));
    }
    drag->exec();
732
    emit processDragEnd();
733
734
735
    return true;
}

736
737
SmallJobLabel::SmallJobLabel(QWidget *parent)
    : QPushButton(parent)
Nicolas Carion's avatar
Nicolas Carion committed
738

739
740
741
742
{
    setFixedWidth(0);
    setFlat(true);
    m_timeLine = new QTimeLine(500, this);
Laurent Montel's avatar
Laurent Montel committed
743
744
    QObject::connect(m_timeLine, &QTimeLine::valueChanged, this, &SmallJobLabel::slotTimeLineChanged);
    QObject::connect(m_timeLine, &QTimeLine::finished, this, &SmallJobLabel::slotTimeLineFinished);
745
746
747
748
749
    hide();
}

const QString SmallJobLabel::getStyleSheet(const QPalette &p)
{
750
    KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window);
751
752
    QColor bg = scheme.background(KColorScheme::LinkBackground).color();
    QColor fg = scheme.foreground(KColorScheme::LinkText).color();
Nicolas Carion's avatar
Nicolas Carion committed
753
754
755
756
757
758
759
760
    QString style =
        QStringLiteral("QPushButton {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}")
            .arg(bg.red())
            .arg(bg.green())
            .arg(bg.blue())
            .arg(fg.red())
            .arg(fg.green())
            .arg(fg.blue());
761

762
763
    bg = scheme.background(KColorScheme::ActiveBackground).color();
    fg = scheme.foreground(KColorScheme::ActiveText).color();
Nicolas Carion's avatar
Nicolas Carion committed
764
765
766
767
768
769
770
771
    style.append(
        QStringLiteral("\nQPushButton:hover {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}")
            .arg(bg.red())
            .arg(bg.green())
            .arg(bg.blue())
            .arg(fg.red())
            .arg(fg.green())
            .arg(fg.blue()));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
772

773
774
775
776
777
778
779
780
781
782
    return style;
}

void SmallJobLabel::setAction(QAction *action)
{
    m_action = action;
}

void SmallJobLabel::slotTimeLineChanged(qreal value)
{
783
    setFixedWidth(int(qMin(value * 2, qreal(1.0)) * sizeHint().width()));
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
    update();
}

void SmallJobLabel::slotTimeLineFinished()
{
    if (m_timeLine->direction() == QTimeLine::Forward) {
        // Show
        m_action->setVisible(true);
    } else {
        // Hide
        m_action->setVisible(false);
        setText(QString());
    }
}

void SmallJobLabel::slotSetJobCount(int jobCount)
{
Nicolas Carion's avatar
Nicolas Carion committed
801
    QMutexLocker lk(&m_locker);
802
803
804
805
    if (jobCount > 0) {
        // prepare animation
        setText(i18np("%1 job", "%1 jobs", jobCount));
        setToolTip(i18np("%1 pending job", "%1 pending jobs", jobCount));
806

Nicolas Carion's avatar
Nicolas Carion committed
807
        if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) {
808
809
810
811
            setFixedWidth(sizeHint().width());
            m_action->setVisible(true);
            return;
        }
812

813
814
815
816
817
        if (m_action->isVisible()) {
            setFixedWidth(sizeHint().width());
            update();
            return;
        }
818

819
820
821
822
823
824
825
826
        setFixedWidth(0);
        m_action->setVisible(true);
        int wantedWidth = sizeHint().width();
        setGeometry(-wantedWidth, 0, wantedWidth, height());
        m_timeLine->setDirection(QTimeLine::Forward);
        if (m_timeLine->state() == QTimeLine::NotRunning) {
            m_timeLine->start();
        }
827
    } else {
Nicolas Carion's avatar
Nicolas Carion committed
828
        if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) {
829
830
831
832
833
834
835
836
837
838
839
840
            setFixedWidth(0);
            m_action->setVisible(false);
            return;
        }
        // hide
        m_timeLine->setDirection(QTimeLine::Backward);
        if (m_timeLine->state() == QTimeLine::NotRunning) {
            m_timeLine->start();
        }
    }
}

841
842
LineEventEater::LineEventEater(QObject *parent)
    : QObject(parent)
843
844
845
846
847
{
}

bool LineEventEater::eventFilter(QObject *obj, QEvent *event)
{
848
    switch (event->type()) {
849
    case QEvent::ShortcutOverride:
850
        if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) {
851
852
853
854
855
            emit clearSearchLine();
        }
        break;
    case QEvent::Resize:
        // Workaround Qt BUG 54676
856
        emit showClearButton(static_cast<QResizeEvent *>(event)->size().width() > QFontMetrics(QApplication::font()).averageCharWidth() * 8);
857
858
859
        break;
    default:
        break;
860
861
862
863
    }
    return QObject::eventFilter(obj, event);
}

864
void ClipWidget::init(QDockWidget* m_DockClipWidget)
Vivek Yadav's avatar
Vivek Yadav committed
865
{
866
    ClipCreationDialog::clipWidget(m_DockClipWidget);
Vivek Yadav's avatar
Vivek Yadav committed
867
}
Vivek Yadav's avatar
Vivek Yadav committed
868

Nicolas Carion's avatar
Nicolas Carion committed
869
Bin::Bin(std::shared_ptr<ProjectItemModel> model, QWidget *parent)
870
871
    : QWidget(parent)
    , isLoading(false)
Nicolas Carion's avatar
Nicolas Carion committed
872
    , m_itemModel(std::move(model))
873
    , m_itemView(nullptr)
874
875
    , m_binTreeViewDelegate(nullptr)
    , m_binListViewDelegate(nullptr)
876
877
878
879
880
    , m_doc(nullptr)
    , m_extractAudioAction(nullptr)
    , m_transcodeAction(nullptr)
    , m_clipsActionsMenu(nullptr)
    , m_inTimelineAction(nullptr)
881
    , m_listType(BinViewType(KdenliveSettings::binMode()))
882
883
884
    , m_iconSize(160, 90)
    , m_propertiesPanel(nullptr)
    , m_blankThumb()
885
    , m_clipWidget()
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
886
    , m_filterGroup(this)
887
888
    , m_filterRateGroup(this)
    , m_filterTypeGroup(this)
889
    , m_invalidClipDialog(nullptr)
890
    , m_transcodingDialog(nullptr)
891
892
893
    , m_gainedFocus(false)
    , m_audioDuration(0)
    , m_processedAudio(0)
894
{
895
    m_layout = new QVBoxLayout(this);
896
897

    // Create toolbar for buttons
898
    m_toolbar = new QToolBar(this);
899
900
901
    int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
    QSize iconSize(size, size);
    m_toolbar->setIconSize(iconSize);
902
    m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
903
    m_layout->addWidget(m_toolbar);
904
905
906
907

    // Tags panel
    m_tagsWidget = new TagWidget(this);
    connect(m_tagsWidget, &TagWidget::switchTag, this, &Bin::switchTag);
908
    connect(m_tagsWidget, &TagWidget::updateProjectTags, this, &Bin::updateTags);
909
910
911
    m_tagsWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
    m_layout->addWidget(m_tagsWidget);
    m_tagsWidget->setVisible(false);
912

913
914
    m_layout->setSpacing(0);
    m_layout->setContentsMargins(0, 0, 0, 0);
915
    // Search line
916
917
    m_searchLine = new QLineEdit(this);
    m_searchLine->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
918
    m_searchLine->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
Nicolas Carion's avatar
Nicolas Carion committed
919
    // m_searchLine->setClearButtonEnabled(true);
920
    m_searchLine->setPlaceholderText(i18n("Search..."));
921
    m_searchLine->setFocusPolicy(Qt::ClickFocus);
922
923
924
925
926
927
    connect(m_searchLine, &QLineEdit::textChanged, [this] (const QString &str) {
        m_proxyModel->slotSetSearchString(str);
        if (str.isEmpty()) {
            // focus last selected item when clearing search line
            QModelIndex current = m_proxyModel->selectionModel()->currentIndex();
            if (current.isValid()) {
928
                m_itemView->scrollTo(current, QAbstractItemView::EnsureVisible);
929
930
931
            }
        }
    });
932

Nicolas Carion's avatar
Nicolas Carion committed
933
    auto *leventEater = new LineEventEater(this);
934
    m_searchLine->installEventFilter(leventEater);
Laurent Montel's avatar
Laurent Montel committed
935
    connect(leventEater, &LineEventEater::clearSearchLine, m_searchLine, &QLineEdit::clear);
936
    connect(leventEater, &LineEventEater::showClearButton, this, &Bin::showClearButton);
937

938
    setFocusPolicy(Qt::ClickFocus);
939

940
    connect(m_itemModel.get(), &ProjectItemModel::refreshPanel, this, &Bin::refreshPanel);
941
    connect(m_itemModel.get(), &ProjectItemModel::refreshClip, this, &Bin::refreshClip);
Nicolas Carion's avatar
Nicolas Carion committed
942
943
944
    connect(m_itemModel.get(), static_cast<void (ProjectItemModel::*)(const QStringList &, const QModelIndex &)>(&ProjectItemModel::itemDropped), this,
            static_cast<void (Bin::*)(const QStringList &, const QModelIndex &)>(&Bin::slotItemDropped));
    connect(m_itemModel.get(), static_cast<void (ProjectItemModel::*)(const QList<QUrl> &, const QModelIndex &)>(&ProjectItemModel::itemDropped), this,
945
            static_cast<const QString (Bin::*)(const QList<QUrl> &, const QModelIndex &)>(&Bin::slotItemDropped));
946
    connect(m_itemModel.get(), &ProjectItemModel::effectDropped, this, &Bin::slotEffectDropped);
947
    connect(m_itemModel.get(), &ProjectItemModel::addTag, this, &Bin::slotTagDropped);
948
    connect(m_itemModel.get(), &QAbstractItemModel::dataChanged, this, &Bin::slotItemEdited);
949
    connect(this, &Bin::refreshPanel, this, &Bin::doRefreshPanel);
950

951
    // Zoom slider
952
    QWidget *container = new QWidget(this);
Nicolas Carion's avatar
Nicolas Carion committed
953
    auto *lay = new QHBoxLayout;
954
955
    m_slider = new QSlider(Qt::Horizontal, this);
    m_slider->setMaximumWidth(100);
956
    m_slider->setMinimumWidth(40);
957
    m_slider->setRange(0, 10);
958
    m_slider->setValue(KdenliveSettings::bin_zoom());
Laurent Montel's avatar
Laurent Montel committed
959
    connect(m_slider, &QAbstractSlider::valueChanged, this, &Bin::slotSetIconSize);
Nicolas Carion's avatar
Nicolas Carion committed
960
    auto *tb1 = new QToolButton(this);
961
    tb1->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
Vincent Pinon's avatar
Vincent Pinon committed
962
    connect(tb1, &QToolButton::clicked, this, [&]() { m_slider->setValue(qMin(m_slider->value() + 1, m_slider->maximum())); });
Nicolas Carion's avatar
Nicolas Carion committed
963
    auto *tb2 = new QToolButton(this);
964
    tb2->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
Vincent Pinon's avatar
Vincent Pinon committed
965
    connect(tb2, &QToolButton::clicked, this, [&]() { m_slider->setValue(qMax(m_slider->value() - 1, m_slider->minimum())); });
966
    lay->addWidget(tb2);
967
968
    lay->addWidget(m_slider);
    lay->addWidget(tb1);
969
    container->setLayout(lay);
Nicolas Carion's avatar
Nicolas Carion committed
970
    auto *widgetslider = new QWidgetAction(this);
971
    widgetslider->setDefaultWidget(container);
972
973

    // View type
974
    KSelectAction *listType = new KSelectAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("View Mode"), this);
975
    pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode"), listType);
Julius Künzel's avatar
Julius Künzel committed
976
    pCore->window()->actionCollection()->setShortcutsConfigurable(listType, false);
977
    QAction *treeViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("Tree View"));
978
    listType->addAction(treeViewAction);
979
980
981
982
    treeViewAction->setData(BinTreeView);
    if (m_listType == treeViewAction->data().toInt()) {
        listType->setCurrentAction(treeViewAction);
    }
983
    pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_tree"), treeViewAction);
984

985
    QAction *iconViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-icons")), i18n("Icon View"));
986
987
988
989
    iconViewAction->setData(BinIconView);
    if (m_listType == iconViewAction->data().toInt()) {
        listType->setCurrentAction(iconViewAction);
    }
990
    pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_icon"), iconViewAction);
991

992
993
994
995
    // Sort menu
    m_sortDescend = new QAction(i18n("Descending"), this);
    m_sortDescend->setCheckable(true);
    m_sortDescend->setChecked(KdenliveSettings::binSorting() > 99);
Vincent Pinon's avatar
Vincent Pinon committed
996
    connect(m_sortDescend, &QAction::triggered, this, [&] () {
997
        if (m_sortGroup->checkedAction()) {
998
            int actionData = m_sortGroup->checkedAction()->data().toInt();
999
1000
            if ((m_itemView != nullptr) && m_listType == BinTreeView) {
                auto *view = static_cast<QTreeView *>(m_itemView);
1001
                view->header()->setSortIndicator(actionData, m_sortDescend->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder);
1002
            } else {
1003
                m_proxyModel->sort(actionData, m_sortDescend->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder);
1004
            }
1005
            KdenliveSettings::setBinSorting(actionData + (m_sortDescend->isChecked() ? 100 : 0));
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
        }
    });

    QMenu *sort = new QMenu(i18n("Sort By"), this);
    int binSort = KdenliveSettings::binSorting() % 100;
    m_sortGroup = new QActionGroup(sort);
    QAction *sortByName = new QAction(i18n("Name"), m_sortGroup);
    sortByName->setCheckable(true);