File indexing completed on 2024-04-28 08:31:14

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 <QTreeWidget>
0019 #include <QVBoxLayout>
0020 #include <QWidget>
0021 
0022 #include <Baloo/TagListJob>
0023 
0024 KEditTagsDialog::KEditTagsDialog(const QStringList &tags, QWidget *parent)
0025     : QDialog(parent)
0026     , m_tags(tags)
0027 {
0028     const QString captionText = (tags.count() > 0) ? i18nc("@title:window", "Edit Tags") : i18nc("@title:window", "Add Tags");
0029     setWindowTitle(captionText);
0030     auto buttonBox = new QDialogButtonBox(this);
0031 
0032     buttonBox->addButton(i18n("Save"), QDialogButtonBox::AcceptRole);
0033     buttonBox->addButton(QDialogButtonBox::Cancel);
0034 
0035     connect(buttonBox, &QDialogButtonBox::accepted, this, &KEditTagsDialog::slotAcceptedButtonClicked);
0036     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0037 
0038     auto topLayout = new QVBoxLayout;
0039     setLayout(topLayout);
0040 
0041     auto label = new QLabel(i18nc("@label:textbox",
0042                                   "Configure which tags should "
0043                                   "be applied."),
0044                             this);
0045 
0046     m_tagTree = new QTreeWidget();
0047     m_tagTree->setSortingEnabled(true);
0048     m_tagTree->setSelectionMode(QAbstractItemView::NoSelection);
0049     m_tagTree->setHeaderHidden(true);
0050 
0051     auto newTagLabel = new QLabel(i18nc("@label", "Create new tag:"));
0052     m_newTagEdit = new QLineEdit(this);
0053     m_newTagEdit->setClearButtonEnabled(true);
0054     m_newTagEdit->setFocus();
0055     connect(m_newTagEdit, &QLineEdit::textEdited, this, &KEditTagsDialog::slotTextEdited);
0056     connect(m_tagTree, &QTreeWidget::itemActivated, this, &KEditTagsDialog::slotItemActivated);
0057 
0058     auto newTagLayout = new QHBoxLayout();
0059     newTagLayout->addWidget(newTagLabel);
0060     newTagLayout->addWidget(m_newTagEdit, 1);
0061 
0062     topLayout->addWidget(label);
0063     topLayout->addWidget(m_tagTree);
0064     topLayout->addLayout(newTagLayout);
0065     topLayout->addWidget(buttonBox);
0066 
0067     resize(sizeHint());
0068 
0069     auto job = new Baloo::TagListJob();
0070     connect(job, &Baloo::TagListJob::finished, [this](KJob *job) {
0071         auto tjob = static_cast<Baloo::TagListJob *>(job);
0072         m_allTags = tjob->tags();
0073         loadTagWidget();
0074     });
0075 
0076     job->start();
0077 }
0078 
0079 KEditTagsDialog::~KEditTagsDialog() = default;
0080 
0081 QStringList KEditTagsDialog::tags() const
0082 {
0083     return m_tags;
0084 }
0085 
0086 void KEditTagsDialog::slotAcceptedButtonClicked()
0087 {
0088     m_tags.clear();
0089 
0090     for (const QTreeWidgetItem *item : std::as_const(m_allTagTreeItems)) {
0091         if (item->checkState(0) == Qt::Checked) {
0092             m_tags << qvariant_cast<QString>(item->data(0, Qt::UserRole));
0093         }
0094     }
0095 
0096     accept();
0097 }
0098 
0099 void KEditTagsDialog::slotItemActivated(const QTreeWidgetItem *item, int column)
0100 {
0101     Q_UNUSED(column)
0102 
0103     const auto tag = qvariant_cast<QString>(item->data(0, Qt::UserRole));
0104     m_newTagEdit->setText(tag + QLatin1Char('/'));
0105     m_newTagEdit->setFocus();
0106 }
0107 
0108 void KEditTagsDialog::slotTextEdited(const QString &text)
0109 {
0110     // Remove unnecessary spaces from a new tag is
0111     // mandatory, as the user cannot see the difference
0112     // between a tag "Test" and "Test ".
0113     QString tagText = text.simplified();
0114     while (tagText.endsWith(QLatin1String("//"))) {
0115         tagText.chop(1);
0116         m_newTagEdit->setText(tagText);
0117         return;
0118     }
0119 
0120     // Remove all tree items related to the previous new tag
0121     const QStringList splitTag = m_newTag.split(QLatin1Char('/'), Qt::SkipEmptyParts);
0122     for (int i = splitTag.size() - 1; i >= 0 && i < splitTag.size(); --i) {
0123         QString itemTag = m_newTag.section(QLatin1Char('/'), 0, i, QString::SectionSkipEmpty);
0124         QTreeWidgetItem *item = m_allTagTreeItems.value(itemTag);
0125 
0126         if (!m_allTags.contains(m_newTag) && (item->childCount() == 0)) {
0127             if (i != 0) {
0128                 QTreeWidgetItem *parentItem = item->parent();
0129                 parentItem->removeChild(item);
0130             } else {
0131                 const int row = m_tagTree->indexOfTopLevelItem(item);
0132                 m_tagTree->takeTopLevelItem(row);
0133             }
0134 
0135             m_allTagTreeItems.remove(itemTag);
0136         }
0137 
0138         if (!m_tags.contains(itemTag)) {
0139             item->setCheckState(0, Qt::Unchecked);
0140         }
0141 
0142         item->setExpanded(false);
0143     }
0144 
0145     if (!tagText.isEmpty()) {
0146         m_newTag = tagText;
0147         modifyTagWidget(tagText);
0148         m_tagTree->sortItems(0, Qt::SortOrder::AscendingOrder);
0149     } else {
0150         m_newTag.clear();
0151         m_allTagTreeItems.clear();
0152         m_tagTree->clear();
0153         loadTagWidget();
0154     }
0155 }
0156 
0157 void KEditTagsDialog::loadTagWidget()
0158 {
0159     for (const QString &tag : std::as_const(m_tags)) {
0160         modifyTagWidget(tag);
0161     }
0162 
0163     for (const QString &tag : std::as_const(m_allTags)) {
0164         modifyTagWidget(tag);
0165     }
0166 
0167     m_tagTree->sortItems(0, Qt::SortOrder::AscendingOrder);
0168 }
0169 
0170 void KEditTagsDialog::modifyTagWidget(const QString &tag)
0171 {
0172     const QStringList splitTag = tag.split(QLatin1Char('/'), Qt::SkipEmptyParts);
0173     for (int i = 0; i < splitTag.size(); ++i) {
0174         QTreeWidgetItem *item;
0175         QString itemTag = tag.section(QLatin1Char('/'), 0, i, QString::SectionSkipEmpty);
0176 
0177         if (!m_allTagTreeItems.contains(itemTag)) {
0178             item = new QTreeWidgetItem();
0179             item->setText(0, splitTag.at(i));
0180             item->setIcon(0, QIcon::fromTheme(QLatin1String("tag")));
0181             item->setData(0, Qt::UserRole, itemTag);
0182             m_allTagTreeItems.insert(itemTag, item);
0183             QString parentTag = tag.section(QLatin1Char('/'), 0, (i - 1), QString::SectionSkipEmpty);
0184             QTreeWidgetItem *parentItem = m_allTagTreeItems.value(parentTag);
0185 
0186             if (i != 0) {
0187                 parentItem->addChild(item);
0188             } else {
0189                 m_tagTree->addTopLevelItem(item);
0190             }
0191         } else {
0192             item = m_allTagTreeItems.value(itemTag);
0193         }
0194 
0195         if (!m_allTags.contains(tag)) {
0196             m_tagTree->scrollToItem(item, QAbstractItemView::PositionAtCenter);
0197         }
0198 
0199         if (((item->childCount() != 0) && m_tags.contains(tag)) || (m_newTag == tag)) {
0200             item->setExpanded(true);
0201         } else if (item->parent() && m_tags.contains(tag)) {
0202             item->parent()->setExpanded(true);
0203         }
0204 
0205         const bool check = (m_tags.contains(itemTag) || (m_newTag == itemTag));
0206         item->setCheckState(0, check ? Qt::Checked : Qt::Unchecked);
0207     }
0208 }
0209 
0210 #include "moc_kedittagsdialog_p.cpp"