File indexing completed on 2024-04-14 04:35:08

0001 /*
0002     SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz@gmx.at>
0003     SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
0004     SPDX-FileCopyrightText: 2017 James D. Smith <smithjd15@gmail.com
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kedittagsdialog_p.h"
0010 
0011 #include <KLocalizedString>
0012 
0013 #include <QDialogButtonBox>
0014 #include <QHBoxLayout>
0015 #include <QLabel>
0016 #include <QLineEdit>
0017 #include <QPushButton>
0018 #include <QSortFilterProxyModel>
0019 #include <QStandardItem>
0020 #include <QStandardItemModel>
0021 #include <QTreeWidget>
0022 #include <QVBoxLayout>
0023 #include <QWidget>
0024 
0025 #include <Baloo/TagListJob>
0026 
0027 namespace
0028 {
0029 const int canonicalTagPathRole = Qt::UserRole + 1;
0030 }
0031 
0032 KEditTagsDialog::KEditTagsDialog(const QStringList &tags, QWidget *parent)
0033     : QDialog(parent)
0034     , m_tags(tags)
0035 {
0036     const QString captionText = (tags.count() > 0) ? i18nc("@title:window", "Edit Tags") : i18nc("@title:window", "Add Tags");
0037     setWindowTitle(captionText);
0038     auto buttonBox = new QDialogButtonBox(this);
0039 
0040     buttonBox->addButton(i18n("Save"), QDialogButtonBox::AcceptRole);
0041     buttonBox->addButton(QDialogButtonBox::Cancel);
0042 
0043     connect(buttonBox, &QDialogButtonBox::accepted, this, &KEditTagsDialog::slotAcceptedButtonClicked);
0044     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0045 
0046     auto topLayout = new QVBoxLayout;
0047     setLayout(topLayout);
0048 
0049     auto label = new QLabel(i18nc("@label:textbox",
0050                                   "Configure which tags should "
0051                                   "be applied."),
0052                             this);
0053 
0054     m_model = new QStandardItemModel(this);
0055 
0056     m_treeView = new QTreeView(this);
0057     m_treeView->setSortingEnabled(true);
0058     m_treeView->setSelectionMode(QAbstractItemView::NoSelection);
0059     m_treeView->setHeaderHidden(true);
0060     m_treeView->setExpandsOnDoubleClick(true);
0061     m_treeView->setModel(m_model);
0062     connect(m_treeView, &QTreeView::clicked, this, &KEditTagsDialog::slotItemActivated);
0063 
0064     auto newTagLabel = new QLabel(i18nc("@label", "Create new tag:"));
0065     m_newTagEdit = new QLineEdit(this);
0066     m_newTagEdit->setClearButtonEnabled(true);
0067     m_newTagEdit->setFocus();
0068     connect(m_newTagEdit, &QLineEdit::textEdited, this, &KEditTagsDialog::slotTextEdited);
0069 
0070     auto newTagLayout = new QHBoxLayout();
0071     newTagLayout->addWidget(newTagLabel);
0072     newTagLayout->addWidget(m_newTagEdit, 1);
0073 
0074     topLayout->addWidget(label);
0075     topLayout->addWidget(m_treeView);
0076     topLayout->addLayout(newTagLayout);
0077     topLayout->addWidget(buttonBox);
0078 
0079     resize(sizeHint());
0080 
0081     auto job = new Baloo::TagListJob();
0082     connect(job, &Baloo::TagListJob::finished, this, [this](KJob *job) {
0083         auto tjob = static_cast<Baloo::TagListJob *>(job);
0084         m_allTags = tjob->tags();
0085         setupModel(m_allTags, m_tags);
0086     });
0087 
0088     job->start();
0089 }
0090 
0091 void KEditTagsDialog::setupModel(const QStringList &allTags, const QStringList &selectedTags)
0092 {
0093     for (const auto &tag : allTags) {
0094         ensureItemForTagExists(tag);
0095     }
0096 
0097     for (const auto &tag : selectedTags) {
0098         auto currentItem = ensureItemForTagExists(tag);
0099         currentItem->setCheckState(Qt::Checked);
0100     }
0101 
0102     m_treeView->expandAll();
0103 }
0104 
0105 QStandardItem *KEditTagsDialog::addTag(QStandardItem *parentItem, const QString &cannonicalTagPath, const QString &tagName)
0106 {
0107     auto newItem = new QStandardItem(tagName);
0108     newItem->setIcon(QIcon::fromTheme(QLatin1String("tag")));
0109     newItem->setCheckable(true);
0110     newItem->setData(cannonicalTagPath, canonicalTagPathRole);
0111     parentItem->appendRow(newItem);
0112     return newItem;
0113 }
0114 
0115 QStandardItem *KEditTagsDialog::findSubItem(QString currentName, QStandardItem *parentItem)
0116 {
0117     for (int i = 0; i < parentItem->rowCount(); ++i) {
0118         auto child = parentItem->child(i);
0119         if (child->text() == currentName) {
0120             return child;
0121         }
0122     }
0123     return nullptr;
0124 }
0125 
0126 QStandardItem *KEditTagsDialog::findTag(const QString tag)
0127 {
0128     QStandardItem *subItem = m_model->invisibleRootItem();
0129     const QStringList splitTags = tag.split(QLatin1Char('/'), Qt::SkipEmptyParts);
0130     for (int i = 0; i < splitTags.size() && subItem != nullptr; ++i) {
0131         auto currentName = splitTags[i];
0132         subItem = findSubItem(currentName, subItem);
0133     }
0134 
0135     return subItem;
0136 }
0137 
0138 QStandardItem *KEditTagsDialog::ensureItemForTagExists(const QString tag)
0139 {
0140     QStandardItem *parentItem = m_model->invisibleRootItem();
0141     QStandardItem *currentItem = nullptr;
0142     const QStringList splitTags = tag.split(QLatin1Char('/'), Qt::SkipEmptyParts);
0143     for (int i = 0; i < splitTags.size(); ++i) {
0144         auto currentName = splitTags[i];
0145 
0146         currentItem = findSubItem(currentName, parentItem);
0147         if (currentItem == nullptr) {
0148             QString tagPath = QLatin1Char('/') + splitTags.mid(0, i + 1).join(QLatin1Char('/'));
0149             currentItem = addTag(parentItem, tagPath, currentName);
0150         }
0151 
0152         parentItem = currentItem;
0153     }
0154 
0155     return currentItem;
0156 }
0157 
0158 KEditTagsDialog::~KEditTagsDialog() = default;
0159 
0160 QStringList KEditTagsDialog::tags() const
0161 {
0162     return m_tags;
0163 }
0164 
0165 void KEditTagsDialog::slotAcceptedButtonClicked()
0166 {
0167     m_tags.clear();
0168 
0169     std::function<void(QStandardItem *)> recurseInTree;
0170     recurseInTree = [this, &recurseInTree](QStandardItem *item) {
0171         QString canonicalPath = item->data(canonicalTagPathRole).toString();
0172         if (item->checkState() == Qt::Checked) {
0173             m_tags << canonicalPath;
0174         }
0175 
0176         for (int i = 0; i < item->rowCount(); ++i) {
0177             recurseInTree(item->child(i));
0178         }
0179     };
0180     recurseInTree(m_model->invisibleRootItem());
0181 
0182     accept();
0183 }
0184 
0185 void KEditTagsDialog::slotItemActivated(const QModelIndex &index)
0186 {
0187     const auto tag = m_treeView->model()->data(index, canonicalTagPathRole).toString();
0188     m_newTagEdit->setText(tag + QLatin1Char('/'));
0189     m_newTagEdit->setFocus();
0190 }
0191 
0192 void KEditTagsDialog::slotTextEdited(const QString &text)
0193 {
0194     // Remove unnecessary spaces from a new tag is
0195     // mandatory, as the user cannot see the difference
0196     // between a tag "Test" and "Test ".
0197     QString tagText = text.simplified();
0198     while (tagText.endsWith(QLatin1Char('/'))) {
0199         tagText.chop(1);
0200     }
0201 
0202     while(m_newItem != nullptr) {
0203         auto parent = m_newItem->parent();
0204         if (!parent) {
0205             parent = m_model->invisibleRootItem();
0206         }
0207         parent->removeRow(m_newItem->row());
0208         QString canonicalPath = parent->data(canonicalTagPathRole).toString();
0209         if (m_allTags.contains(canonicalPath) || m_tags.contains(canonicalPath) || parent == m_model->invisibleRootItem()) {
0210             m_newItem = nullptr;
0211         } else {
0212             m_newItem = parent;
0213         }
0214     }
0215 
0216     if (tagText.isEmpty()) {
0217         m_treeView->setModel(m_model);
0218         m_treeView->expandAll();
0219         return;
0220     }
0221 
0222     if (!findTag(text)) {
0223         m_newItem = ensureItemForTagExists(text);
0224         m_newItem->setIcon(QIcon::fromTheme(QStringLiteral("tag-new")));
0225         m_newItem->setCheckState(Qt::Checked);
0226     }
0227 
0228     m_treeView->expandAll();
0229 }
0230 
0231 #include "moc_kedittagsdialog_p.cpp"