improved tagging ui for bin clips

parent 1cd3bb99
Pipeline #12482 passed with stage
in 15 minutes and 33 seconds
......@@ -13,5 +13,6 @@ set(kdenlive_SRCS
bin/projectitemmodel.cpp
bin/projectsortproxymodel.cpp
bin/projectsubclip.cpp
bin/tagwidget.cpp
PARENT_SCOPE
)
......@@ -49,6 +49,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "projectitemmodel.h"
#include "projectsortproxymodel.h"
#include "projectsubclip.h"
#include "tagwidget.hpp"
#include "titler/titlewidget.h"
#include "ui_qtextclip_ui.h"
#include "undohelper.hpp"
......@@ -78,30 +79,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
* @brief This class is responsible for drawing items in the QTreeView.
*/
TagListView::TagListView(QWidget *parent)
: QListWidget(parent)
{
setFrameStyle(QFrame::NoFrame);
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragOnly);
//connect(this, &QListWidget::itemActivated, this, &EffectBasket::slotAddEffect);
}
QMimeData *TagListView::mimeData(const QList<QListWidgetItem *> list) const
{
if (list.isEmpty()) {
return new QMimeData;
}
QDomDocument doc;
QListWidgetItem *item = list.at(0);
QString effectId = item->data(Qt::UserRole).toString();
auto *mime = new QMimeData;
mime->setData(QStringLiteral("kdenlive/tag"), effectId.toUtf8());
qDebug()<<"=== DRAGGING TAG DATA: "<<effectId;
return mime;
}
class BinItemDelegate : public QStyledItemDelegate
{
public:
......@@ -240,12 +217,13 @@ public:
QString tags = index.data(AbstractProjectItem::DataTag).toString();
if (!tags.isEmpty()) {
QStringList t = tags.split(QLatin1Char(';'));
QRectF tagRect = m_thumbRect;
tagRect.setWidth(r1.height() / 5);
tagRect.setHeight(m_thumbRect.height() / t.size());
QRectF tagRect = m_thumbRect.adjusted(2, 2, 0, 2);
tagRect.setWidth(r1.height() / 3.5);
tagRect.setHeight(tagRect.width());
for (const QString &color : t) {
painter->fillRect(tagRect, QColor(color));
tagRect.moveTop(tagRect.bottom());
painter->setBrush(QColor(color));
painter->drawRoundedRect(tagRect, tagRect.height() / 2, tagRect.height() / 2);
tagRect.moveTop(tagRect.bottom() + tagRect.height() / 4);
}
}
if (!subText.isEmpty()) {
......@@ -403,26 +381,41 @@ public:
int adjust = (opt.rect.width() - opt.decorationSize.width()) / 2;
QRect rect(opt.rect.x(), opt.rect.y(), opt.decorationSize.width(), opt.decorationSize.height());
m_thumbRect = adjust > 0 && adjust < rect.width() ? rect.adjusted(adjust, 0, -adjust, 0) : rect;
//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());
for (const QString &color : t) {
painter->setBrush(QColor(color));
painter->drawRoundedRect(tagRect, tagRect.height() / 2, tagRect.height() / 2);
tagRect.moveTop(tagRect.bottom() + tagRect.height() / 4);
}
}
// Add audio/video icons for selective drag
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();
}
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();
}
}
}
......@@ -774,6 +767,14 @@ Bin::Bin(std::shared_ptr<ProjectItemModel> model, QWidget *parent)
m_toolbar->setIconSize(iconSize);
m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_layout->addWidget(m_toolbar);
// Tags panel
m_tagsWidget = new TagWidget(this);
connect(m_tagsWidget, &TagWidget::switchTag, this, &Bin::switchTag);
m_tagsWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
m_layout->addWidget(m_tagsWidget);
m_tagsWidget->setVisible(false);
m_layout->setSpacing(0);
m_layout->setContentsMargins(0, 0, 0, 0);
// Search line
......@@ -783,29 +784,6 @@ Bin::Bin(std::shared_ptr<ProjectItemModel> model, QWidget *parent)
m_searchLine->setPlaceholderText(i18n("Search..."));
m_searchLine->setFocusPolicy(Qt::ClickFocus);
// Tags panel
m_tagsPanel = new QWidget(this);
QLabel *lab = new QLabel(i18n("Tags"), m_tagsPanel);
QVBoxLayout *tagsLay = new QVBoxLayout;
tagsLay->addWidget(lab);
TagListView *lw = new TagListView(m_tagsPanel);
lw->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
tagsLay->addWidget(lw);
QPixmap pix(iconSize);
pix.fill(Qt::red);
QIcon icon(pix);
QListWidgetItem *item = new QListWidgetItem(icon, i18n("Red"), lw);
item->setData(Qt::UserRole, QColor(Qt::red).name());
pix.fill(Qt::green);
icon = QIcon(pix);
item = new QListWidgetItem(icon, i18n("Green"), lw);
item->setData(Qt::UserRole, QColor(Qt::green).name());
pix.fill(Qt::blue);
icon = QIcon(pix);
item = new QListWidgetItem(icon, i18n("Blue"), lw);
item->setData(Qt::UserRole, QColor(Qt::blue).name());
m_tagsPanel->setLayout(tagsLay);
auto *leventEater = new LineEventEater(this);
m_searchLine->installEventFilter(leventEater);
connect(leventEater, &LineEventEater::clearSearchLine, m_searchLine, &QLineEdit::clear);
......@@ -951,9 +929,9 @@ Bin::Bin(std::shared_ptr<ProjectItemModel> model, QWidget *parent)
m_toolbar->addAction(m_tagAction);
connect(m_tagAction, &QAction::triggered, [&] (bool triggered) {
if (triggered) {
m_splitter->setSizes({100, 50});
m_tagsWidget->setVisible(true);
} else {
m_splitter->setSizes({100, 0});
m_tagsWidget->setVisible(false);
}
});
......@@ -1528,6 +1506,7 @@ void Bin::selectProxyModel(const QModelIndex &id)
m_locateAction->setEnabled(true);
m_duplicateAction->setEnabled(true);
std::shared_ptr<ProjectClip> clip = std::static_pointer_cast<ProjectClip>(currentItem);
m_tagsWidget->setTagData(clip->getProducerProperty(QStringLiteral("kdenlive:tags")));
ClipType::ProducerType type = clip->clipType();
m_openAction->setEnabled(type == ClipType::Image || type == ClipType::Audio || type == ClipType::Text || type == ClipType::TextTemplate);
showClipProperties(clip, false);
......@@ -1663,15 +1642,7 @@ void Bin::slotInitView(QAction *action)
m_itemView->setModel(m_proxyModel.get());
m_itemView->setSelectionModel(m_proxyModel->selectionModel());
m_proxyModel->setDynamicSortFilter(true);
m_tagsPanel->setParent(nullptr);
m_tagsPanel->setVisible(false);
m_splitter.reset(new QSplitter(this));
m_splitter->addWidget(m_itemView);
m_splitter->addWidget(m_tagsPanel);
m_tagAction->setChecked(false);
m_splitter->setSizes({100, 0});
m_tagsPanel->setVisible(true);
m_layout->insertWidget(1, m_splitter.get());
m_layout->insertWidget(2, m_itemView);
// Reset drag type to normal
m_itemModel->setDragType(PlaylistState::Disabled);
......@@ -2559,6 +2530,39 @@ void Bin::slotTagDropped(const QString &tag, const QModelIndex &parent)
pCore->displayMessage(i18n("Select a clip to add a tag"), InformationMessage);
}
void Bin::switchTag(const QString &tag, bool add)
{
qDebug()<<"=== READY TO TAG: "<<tag<<" = "<<add;
const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
if (indexes.isEmpty()) {
pCore->displayMessage(i18n("Select a clip to add a tag"), InformationMessage);
}
for (const QModelIndex &ix : indexes) {
std::shared_ptr<AbstractProjectItem> parentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
if (parentItem->itemType() == AbstractProjectItem::ClipItem) {
// effect only supported on clip/subclip items
std::shared_ptr<ProjectClip> clip = std::static_pointer_cast<ProjectClip>(parentItem);
QString currentTag = clip->getProducerProperty(QStringLiteral("kdenlive:tags"));
QMap <QString, QString> oldProps;
oldProps.insert(QStringLiteral("kdenlive:tags"), currentTag);
QMap <QString, QString> newProps;
if (add) {
if (currentTag.isEmpty()) {
currentTag = tag;
} else if (!currentTag.contains(tag)) {
currentTag.append(QStringLiteral(";") + tag);
}
newProps.insert(QStringLiteral("kdenlive:tags"), currentTag);
} else {
QStringList tags = currentTag.split(QLatin1Char(';'));
tags.removeAll(tag);
newProps.insert(QStringLiteral("kdenlive:tags"), tags.join(QLatin1Char(';')));
}
slotEditClipCommand(parentItem->clipId(), oldProps, newProps);
}
}
}
void Bin::editMasterEffect(const std::shared_ptr<AbstractProjectItem> &clip)
{
if (m_gainedFocus) {
......
......@@ -47,6 +47,7 @@ class ClipController;
class EffectStackModel;
class InvalidDialog;
class KdenliveDoc;
class TagWidget;
class Monitor;
class ProjectClip;
class ProjectFolder;
......@@ -62,23 +63,11 @@ class QUndoCommand;
class QVBoxLayout;
class QActionGroup;
class SmallJobLabel;
class QSplitter;
namespace Mlt {
class Producer;
}
class TagListView : public QListWidget
{
Q_OBJECT
public:
explicit TagListView(QWidget *parent);
protected:
QMimeData *mimeData(const QList<QListWidgetItem *> list) const override;
};
class MyListView : public QListView
{
Q_OBJECT
......@@ -332,6 +321,9 @@ private slots:
/** @brief Display a defined frame in bin clip thumbnail
*/
void showBinFrame(QModelIndex ix, int frame);
/** @brief Switch a tag on/off on current selection
*/
void switchTag(const QString &tag, bool add);
public slots:
void slotRemoveInvalidClip(const QString &id, bool replace, const QString &errorMessage);
......@@ -433,8 +425,7 @@ private:
QAction *m_tagAction;
QActionGroup *m_sortGroup;
SmallJobLabel *m_infoLabel;
QWidget *m_tagsPanel;
std::unique_ptr<QSplitter> m_splitter;
TagWidget *m_tagsWidget;
/** @brief The info widget for failed jobs. */
KMessageWidget *m_infoMessage;
QStringList m_errorLog;
......
......@@ -1038,6 +1038,12 @@ void ProjectClip::setProperties(const QMap<QString, QString> &properties, bool r
reload = true;
}
if (properties.contains(QStringLiteral("kdenlive:tags"))) {
if (auto ptr = m_model.lock()) {
std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()),
AbstractProjectItem::DataTag);
}
}
if (properties.contains(QStringLiteral("kdenlive:clipname"))) {
m_name = properties.value(QStringLiteral("kdenlive:clipname"));
refreshPanel = true;
......
/*
Copyright (C) 2019 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
by the membership of KDE e.V.), which shall act as a proxy
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 "tagwidget.hpp"
#include "mainwindow.h"
#include "core.h"
#include <KLocalizedString>
#include <KActionCollection>
#include <QVBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QDebug>
#include <QMimeData>
#include <QMouseEvent>
#include <QDomDocument>
#include <QToolButton>
#include <QApplication>
#include <QFontDatabase>
#include <QDrag>
TagListView::TagListView(QWidget *parent)
: QListWidget(parent)
{
setFrameStyle(QFrame::NoFrame);
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragOnly);
//connect(this, &QListWidget::itemActivated, this, &EffectBasket::slotAddEffect);
}
QMimeData *TagListView::mimeData(const QList<QListWidgetItem *> list) const
{
if (list.isEmpty()) {
return new QMimeData;
}
QDomDocument doc;
QListWidgetItem *item = list.at(0);
QString effectId = item->data(Qt::UserRole).toString();
auto *mime = new QMimeData;
mime->setData(QStringLiteral("kdenlive/tag"), effectId.toUtf8());
return mime;
}
DragButton::DragButton(int ix, const QString tag, const QString description, QWidget *parent)
: QToolButton(parent)
, m_tag(tag.toLower())
, m_description(description)
, m_dragging(false)
{
setToolTip(description);
int iconSize = QFontInfo(font()).pixelSize() - 2;
QPixmap pix(iconSize, iconSize);
pix.fill(Qt::transparent);
QPainter p(&pix);
p.setRenderHint(QPainter::Antialiasing, true);
p.setBrush(QColor(m_tag));
p.drawRoundedRect(0, 0, iconSize, iconSize, iconSize/2, iconSize/2);
setAutoRaise(true);
setText(description);
setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
setCheckable(true);
QAction *ac = new QAction(i18n("Tag %1", ix), this);
ac->setIcon(QIcon(pix));
ac->setCheckable(true);
setDefaultAction(ac);
pCore->window()->actionCollection()->addAction(QString("tag_%1").arg(ix), ac);
connect(ac, &QAction::triggered, [&, ac] (bool checked) {
emit switchTag(m_tag, checked);
});
}
void DragButton::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
m_dragStartPosition = event->pos();
QToolButton::mousePressEvent(event);
m_dragging = false;
}
void DragButton::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton) || m_dragging) {
event->accept();
return;
}
if ((event->pos() - m_dragStartPosition).manhattanLength()
< QApplication::startDragDistance()) {
QToolButton::mouseMoveEvent(event);
return;
}
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData(QStringLiteral("kdenlive/tag"), m_tag.toUtf8());
drag->setMimeData(mimeData);
m_dragging = true;
Qt::DropAction dropAction = drag->exec(Qt::CopyAction);
event->accept();
}
void DragButton::mouseReleaseEvent(QMouseEvent *event)
{
QToolButton::mouseReleaseEvent(event);
if ((event->button() == Qt::LeftButton) && !m_dragging) {
emit switchTag(m_tag, isChecked());
}
m_dragging = false;
}
const QString &DragButton::tag() const
{
return m_tag;
}
TagWidget::TagWidget(QWidget *parent)
: QWidget(parent)
{
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
QHBoxLayout *lay = new QHBoxLayout;
lay->setContentsMargins(0, 0, 0, 0);
QMap <QString, QString> projectTags = pCore->getProjectTags();
QMapIterator<QString, QString> i(projectTags);
int ix = 1;
while (i.hasNext()) {
i.next();
DragButton *tag1 = new DragButton(ix, i.key(), i.value(), this);
tag1->setFont(font());
connect(tag1, &DragButton::switchTag, this, &TagWidget::switchTag);
tags << tag1;
lay->addWidget(tag1);
ix++;
}
lay->addStretch(10);
setLayout(lay);
}
void TagWidget::setTagData(const QString tagData)
{
QStringList colors = tagData.toLower().split(QLatin1Char(';'));
for (DragButton *tb : tags) {
const QString color = tb->tag();
tb->defaultAction()->setChecked(colors.contains(color));
}
}
/*
Copyright (C) 2019 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
by the membership of KDE e.V.), which shall act as a proxy
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/>.
*/
#ifndef KDENLIVE_TAGWIDGET_H
#define KDENLIVE_TAGWIDGET_H
#include <QListWidget>
#include <QToolButton>
class TagListView : public QListWidget
{
Q_OBJECT
public:
explicit TagListView(QWidget *parent);
protected:
QMimeData *mimeData(const QList<QListWidgetItem *> list) const override;
};
/**
* @class DragButton
* @brief A draggable QToolButton subclass
*/
class DragButton : public QToolButton
{
Q_OBJECT
public:
explicit DragButton(int ix, const QString tag, const QString description = QString(), QWidget *parent = nullptr);
const QString &tag() const;
private:
QPoint m_dragStartPosition;
/** @brief the color tag */
QString m_tag;
/** @brief the tag description */
QString m_description;
/** @brief True if a drag action is in progress */
bool m_dragging;
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
signals:
void switchTag(const QString &tag, bool add);
};
/**
* @class TagWidget
* @brief The tag widget takes care context menu tagging
*/
class TagWidget : public QWidget
{
Q_OBJECT
public:
explicit TagWidget(QWidget *parent = nullptr);
void setTagData(const QString tagData);
private:
QList <DragButton *> tags;
signals:
void switchTag(const QString &tag, bool add);
};
#endif
......@@ -816,3 +816,14 @@ void Core::processInvalidFilter(const QString service, const QString id, const Q
{
if (m_guiConstructed) m_mainWindow->assetPanelWarning(service, id, message);
}
QMap <QString, QString> Core::getProjectTags()
{
QMap <QString, QString> tags;
tags.insert(QStringLiteral("#ff0000"), i18n("Red"));
tags.insert(QStringLiteral("#00ff00"), i18n("Green"));
tags.insert(QStringLiteral("#0000ff"), i18n("Blue"));
tags.insert(QStringLiteral("#ffff00"), i18n("Yellow"));
tags.insert(QStringLiteral("#00ffff"), i18n("Cyan"));
return tags;
}
......@@ -201,6 +201,8 @@ public:
int getDurationFromString(const QString &time);
/** @brief An error occured within a filter, inform user */
void processInvalidFilter(const QString service, const QString id, const QString message);
/** @brief Returns a list of project tags (color / description) */
QMap <QString, QString> getProjectTags();
private:
explicit Core();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment