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"