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 }