File indexing completed on 2024-04-14 04:46:16

0001 /*
0002 SPDX-FileCopyrightText: 2019 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 This file is part of Kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "tagwidget.hpp"
0009 #include "core.h"
0010 #include "mainwindow.h"
0011 
0012 #include <KActionCollection>
0013 #include <KColorCombo>
0014 #include <KIconEffect>
0015 #include <KLineEdit>
0016 #include <KLocalizedString>
0017 #include <KMessageWidget>
0018 
0019 #include <QApplication>
0020 #include <QDebug>
0021 #include <QDialog>
0022 #include <QDialogButtonBox>
0023 #include <QDomDocument>
0024 #include <QDrag>
0025 #include <QFontDatabase>
0026 #include <QLabel>
0027 #include <QListWidget>
0028 #include <QMimeData>
0029 #include <QMouseEvent>
0030 #include <QPainter>
0031 #include <QToolButton>
0032 #include <QVBoxLayout>
0033 
0034 DragButton::DragButton(int ix, const QString &tag, const QString &description, QWidget *parent)
0035     : QToolButton(parent)
0036     , m_tag(tag.toLower())
0037     , m_description(description)
0038     , m_dragging(false)
0039 {
0040     setToolTip(description);
0041     QImage img(iconSize(), QImage::Format_ARGB32_Premultiplied);
0042     img.fill(Qt::transparent);
0043     QIcon icon = QIcon::fromTheme(QStringLiteral("tag"));
0044     QPainter p(&img);
0045     icon.paint(&p, 0, 0, img.width(), img.height());
0046     p.end();
0047     KIconEffect::toMonochrome(img, QColor(m_tag), QColor(m_tag), 1);
0048     setAutoRaise(true);
0049     setText(description);
0050     setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
0051     setCheckable(true);
0052     QAction *ac = new QAction(description.isEmpty() ? i18n("Tag %1", ix) : description, this);
0053     ac->setData(m_tag);
0054     ac->setIcon(QIcon(QPixmap::fromImage(img)));
0055     ac->setCheckable(true);
0056     setDefaultAction(ac);
0057     pCore->window()->addAction(QString("tag_%1").arg(ix), ac, {}, QStringLiteral("bintags"));
0058     connect(ac, &QAction::triggered, this, [&](bool checked) { Q_EMIT switchTag(m_tag, checked); });
0059 }
0060 
0061 void DragButton::mousePressEvent(QMouseEvent *event)
0062 {
0063     if (event->button() == Qt::LeftButton) m_dragStartPosition = event->pos();
0064     QToolButton::mousePressEvent(event);
0065     m_dragging = false;
0066 }
0067 
0068 void DragButton::mouseMoveEvent(QMouseEvent *event)
0069 {
0070     QToolButton::mouseMoveEvent(event);
0071     if (!(event->buttons() & Qt::LeftButton) || m_dragging) {
0072         return;
0073     }
0074     if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) {
0075         return;
0076     }
0077 
0078     auto *drag = new QDrag(this);
0079     auto *mimeData = new QMimeData;
0080     mimeData->setData(QStringLiteral("kdenlive/tag"), m_tag.toUtf8());
0081     drag->setPixmap(defaultAction()->icon().pixmap(22, 22));
0082     drag->setMimeData(mimeData);
0083     m_dragging = true;
0084     drag->exec(Qt::CopyAction | Qt::MoveAction);
0085     // Disable / enable toolbutton to clear highlighted state because mouserelease is not triggered on drop end
0086     setEnabled(false);
0087     setEnabled(true);
0088 }
0089 
0090 void DragButton::mouseReleaseEvent(QMouseEvent *event)
0091 {
0092     QToolButton::mouseReleaseEvent(event);
0093     if ((event->button() == Qt::LeftButton) && !m_dragging) {
0094         Q_EMIT switchTag(m_tag, isChecked());
0095     }
0096     m_dragging = false;
0097 }
0098 
0099 const QString &DragButton::tag() const
0100 {
0101     return m_tag;
0102 }
0103 
0104 const QString &DragButton::description() const
0105 {
0106     return m_description;
0107 }
0108 
0109 TagWidget::TagWidget(QWidget *parent)
0110     : QWidget(parent)
0111 {
0112     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0113     auto *lay = new QHBoxLayout;
0114     lay->setContentsMargins(0, 0, 0, 0);
0115     lay->addStretch(10);
0116     auto *config = new QToolButton(this);
0117     QAction *ca = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure"), this);
0118     config->setAutoRaise(true);
0119     config->setDefaultAction(ca);
0120     connect(config, &QToolButton::triggered, this, [&]() { showTagsConfig(); });
0121     lay->addWidget(config);
0122     setLayout(lay);
0123 }
0124 
0125 void TagWidget::setTagData(const QString &tagData)
0126 {
0127     QStringList colors = tagData.toLower().split(QLatin1Char(';'));
0128     for (DragButton *tb : qAsConst(tags)) {
0129         const QString color = tb->tag();
0130         tb->defaultAction()->setChecked(colors.contains(color));
0131     }
0132 }
0133 
0134 void TagWidget::rebuildTags(const QMap<int, QStringList> &newTags)
0135 {
0136     auto *lay = static_cast<QHBoxLayout *>(layout());
0137     qDeleteAll(tags);
0138     tags.clear();
0139     int ix = 1;
0140     int width = 30;
0141     QMapIterator<int, QStringList> i(newTags);
0142     while (i.hasNext()) {
0143         i.next();
0144         DragButton *tag1 = new DragButton(ix, i.value().at(1), i.value().at(2), this);
0145         tag1->setFont(font());
0146         connect(tag1, &DragButton::switchTag, this, &TagWidget::switchTag);
0147         tags << tag1;
0148         lay->insertWidget(ix - 1, tag1);
0149         width += tag1->sizeHint().width();
0150         ix++;
0151     }
0152     setMinimumWidth(width);
0153     if (!tags.isEmpty()) {
0154         setFixedHeight(tags.first()->sizeHint().height());
0155     }
0156     updateGeometry();
0157 }
0158 
0159 void TagWidget::showTagsConfig()
0160 {
0161     QDialog d(this);
0162     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
0163     auto *l = new QVBoxLayout;
0164     d.setLayout(l);
0165     QLabel lab(i18n("Configure Project Tags"), &d);
0166     QListWidget list(&d);
0167     list.setDragEnabled(true);
0168     list.setDragDropMode(QAbstractItemView::InternalMove);
0169     l->addWidget(&lab);
0170     l->addWidget(&list);
0171     QHBoxLayout *lay = new QHBoxLayout;
0172     l->addLayout(lay);
0173     l->addWidget(buttonBox);
0174     QList<QColor> existingTagColors;
0175     int ix = 1;
0176     // Build list of tags
0177     QMap<int, QStringList> originalTags;
0178     for (DragButton *tb : qAsConst(tags)) {
0179         const QString color = tb->tag();
0180         existingTagColors << color;
0181         const QString desc = tb->description();
0182         QIcon ic = tb->icon();
0183         list.setIconSize(tb->iconSize());
0184         auto *item = new QListWidgetItem(ic, desc, &list);
0185         item->setData(Qt::UserRole, color);
0186         item->setData(Qt::UserRole + 1, ix);
0187         originalTags.insert(ix, {QString::number(ix), color, desc});
0188         item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
0189         ix++;
0190     }
0191     Fun editItem = [&d, &list, &colors = existingTagColors, &existingTagColors]() {
0192         QListWidgetItem *item = list.currentItem();
0193         if (!item) {
0194             return false;
0195         }
0196         // Edit an existing tag
0197         QDialog d2(&d);
0198         d2.setWindowTitle(i18n("Edit Tag"));
0199         QDialogButtonBox *buttonBox2 = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
0200         auto *l2 = new QVBoxLayout;
0201         d2.setLayout(l2);
0202         auto *l3 = new QHBoxLayout;
0203         KColorCombo cb;
0204         l3->addWidget(&cb);
0205         KLineEdit le;
0206         le.setText(item->text());
0207         QColor originalColor(item->data(Qt::UserRole).toString());
0208         colors.removeAll(originalColor);
0209         cb.setColor(originalColor);
0210         l3->addWidget(&le);
0211         l2->addLayout(l3);
0212         KMessageWidget mw;
0213         mw.setText(i18n("This color is already used in another tag"));
0214         mw.setMessageType(KMessageWidget::Warning);
0215         mw.setCloseButtonVisible(false);
0216         mw.hide();
0217         l2->addWidget(&mw);
0218         l2->addWidget(buttonBox2);
0219         d2.connect(buttonBox2, &QDialogButtonBox::rejected, &d2, &QDialog::reject);
0220         d2.connect(buttonBox2, &QDialogButtonBox::accepted, &d2, &QDialog::accept);
0221         connect(&le, &KLineEdit::textChanged, &d2, [buttonBox2, &le, &cb, &colors]() {
0222             if (le.text().isEmpty()) {
0223                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(false);
0224             } else {
0225                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(!colors.contains(cb.color()));
0226             }
0227         });
0228         connect(&cb, &KColorCombo::activated, &d2, [buttonBox2, &colors, &mw, &le](const QColor &selectedColor) {
0229             if (colors.contains(selectedColor)) {
0230                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(false);
0231                 mw.animatedShow();
0232             } else {
0233                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(!le.text().isEmpty());
0234                 mw.animatedHide();
0235             }
0236         });
0237         if (colors.contains(cb.color())) {
0238             buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(false);
0239             mw.animatedShow();
0240         }
0241         le.setFocus();
0242         le.selectAll();
0243         if (d2.exec() == QDialog::Accepted) {
0244             QImage img(list.iconSize(), QImage::Format_ARGB32_Premultiplied);
0245             img.fill(Qt::transparent);
0246             QIcon icon = QIcon::fromTheme(QStringLiteral("tag"));
0247             QPainter p(&img);
0248             icon.paint(&p, 0, 0, img.width(), img.height());
0249             p.end();
0250             KIconEffect::toMonochrome(img, QColor(cb.color()), QColor(cb.color()), 1);
0251             item->setIcon(QIcon(QPixmap::fromImage(img)));
0252             item->setText(le.text());
0253             item->setData(Qt::UserRole, cb.color());
0254             existingTagColors.removeAll(originalColor);
0255             existingTagColors << cb.color();
0256         }
0257         return true;
0258     };
0259     // set the editable triggers for the list widget
0260     QAction *a = KStandardAction::renameFile(this, editItem, &d);
0261     a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0262     list.addAction(a);
0263     d.connect(&list, &QListWidget::itemDoubleClicked, &d, [=]() { editItem(); });
0264     QToolButton *tb = new QToolButton(&d);
0265     tb->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
0266     lay->addWidget(tb);
0267     QToolButton *tb2 = new QToolButton(&d);
0268     tb2->setIcon(QIcon::fromTheme(QStringLiteral("tag-new")));
0269     lay->addWidget(tb2);
0270     lay->addStretch(10);
0271     d.connect(tb2, &QToolButton::clicked, &d, [&d, &list, &existingTagColors]() {
0272         // Add a new tag
0273         QDialog d2(&d);
0274         d2.setWindowTitle(i18n("Add Tag"));
0275         QDialogButtonBox *buttonBox2 = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
0276         auto *l2 = new QVBoxLayout;
0277         d2.setLayout(l2);
0278         auto *l3 = new QHBoxLayout;
0279         KColorCombo cb;
0280         l3->addWidget(&cb);
0281         KLineEdit le;
0282         le.setText(i18n("New tag"));
0283         l3->addWidget(&le);
0284         l2->addLayout(l3);
0285         KMessageWidget mw;
0286         mw.setText(i18n("This color is already used in another tag"));
0287         mw.setMessageType(KMessageWidget::Warning);
0288         mw.setCloseButtonVisible(false);
0289         mw.hide();
0290         l2->addWidget(&mw);
0291         l2->addWidget(buttonBox2);
0292         d2.connect(buttonBox2, &QDialogButtonBox::rejected, &d2, &QDialog::reject);
0293         d2.connect(buttonBox2, &QDialogButtonBox::accepted, &d2, &QDialog::accept);
0294         connect(&le, &KLineEdit::textChanged, &d2, [buttonBox2, &le, &cb, &existingTagColors]() {
0295             if (le.text().isEmpty()) {
0296                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(false);
0297             } else {
0298                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(!existingTagColors.contains(cb.color()));
0299             }
0300         });
0301         connect(&cb, &KColorCombo::activated, &d2, [buttonBox2, &existingTagColors, &mw, &le](const QColor &selectedColor) {
0302             if (existingTagColors.contains(selectedColor)) {
0303                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(false);
0304                 mw.animatedShow();
0305             } else {
0306                 buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(!le.text().isEmpty());
0307                 mw.animatedHide();
0308             }
0309         });
0310         if (existingTagColors.contains(cb.color())) {
0311             buttonBox2->button(QDialogButtonBox::Ok)->setEnabled(false);
0312             mw.animatedShow();
0313         }
0314         le.setFocus();
0315         le.selectAll();
0316         if (d2.exec() == QDialog::Accepted) {
0317             QImage img(list.iconSize(), QImage::Format_ARGB32_Premultiplied);
0318             img.fill(Qt::transparent);
0319             QIcon icon = QIcon::fromTheme(QStringLiteral("tag"));
0320             QPainter p(&img);
0321             icon.paint(&p, 0, 0, img.width(), img.height());
0322             p.end();
0323             KIconEffect::toMonochrome(img, QColor(cb.color()), QColor(cb.color()), 1);
0324             auto *item = new QListWidgetItem(QIcon(QPixmap::fromImage(img)), le.text(), &list);
0325             item->setData(Qt::UserRole, cb.color());
0326             item->setData(Qt::UserRole + 1, list.count());
0327             item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
0328             existingTagColors << cb.color();
0329         }
0330     });
0331     d.connect(tb, &QToolButton::clicked, &d, [&list]() {
0332         // Delete selected tag
0333         auto *item = list.currentItem();
0334         if (item) {
0335             delete item;
0336         }
0337     });
0338     d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
0339     d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept);
0340     if (d.exec() != QDialog::Accepted) {
0341         return;
0342     }
0343     QMap<int, QStringList> newTags;
0344     int newIx = 1;
0345     for (int i = 0; i < list.count(); i++) {
0346         QListWidgetItem *item = list.item(i);
0347         if (item) {
0348             newTags.insert(newIx, {QString::number(item->data(Qt::UserRole + 1).toInt()), item->data(Qt::UserRole).toString(), item->text()});
0349             newIx++;
0350         }
0351     }
0352     Q_EMIT updateProjectTags(originalTags, newTags);
0353 }