Baloo Widgets

kedittagsdialog.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz@gmx.at>
3 SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
4 SPDX-FileCopyrightText: 2017 James D. Smith <smithjd15@gmail.com
5
6 SPDX-License-Identifier: LGPL-2.0-only
7*/
8
9#include "kedittagsdialog_p.h"
10
11#include <KLocalizedString>
12
13#include <QDialogButtonBox>
14#include <QHBoxLayout>
15#include <QLabel>
16#include <QLineEdit>
17#include <QPushButton>
18#include <QSortFilterProxyModel>
19#include <QStandardItem>
20#include <QStandardItemModel>
21#include <QTreeWidget>
22#include <QVBoxLayout>
23#include <QWidget>
24
25#include <Baloo/TagListJob>
26
27namespace
28{
29const int canonicalTagPathRole = Qt::UserRole + 1;
30}
31
32KEditTagsDialog::KEditTagsDialog(const QStringList &tags, QWidget *parent)
33 : QDialog(parent)
34 , m_tags(tags)
35{
36 const QString captionText = (tags.count() > 0) ? i18nc("@title:window", "Edit Tags") : i18nc("@title:window", "Add Tags");
37 setWindowTitle(captionText);
38 auto buttonBox = new QDialogButtonBox(this);
39
40 buttonBox->addButton(i18n("Save"), QDialogButtonBox::AcceptRole);
41 buttonBox->addButton(QDialogButtonBox::Cancel);
42
43 connect(buttonBox, &QDialogButtonBox::accepted, this, &KEditTagsDialog::slotAcceptedButtonClicked);
45
46 auto topLayout = new QVBoxLayout;
47 setLayout(topLayout);
48
49 auto label = new QLabel(i18nc("@label:textbox",
50 "Configure which tags should "
51 "be applied."),
52 this);
53
54 m_model = new QStandardItemModel(this);
55
56 m_treeView = new QTreeView(this);
57 m_treeView->setSortingEnabled(true);
58 m_treeView->setSelectionMode(QAbstractItemView::NoSelection);
59 m_treeView->setHeaderHidden(true);
60 m_treeView->setExpandsOnDoubleClick(true);
61 m_treeView->setModel(m_model);
62 connect(m_treeView, &QTreeView::clicked, this, &KEditTagsDialog::slotItemActivated);
63
64 auto newTagLabel = new QLabel(i18nc("@label", "Create new tag:"));
65 m_newTagEdit = new QLineEdit(this);
66 m_newTagEdit->setClearButtonEnabled(true);
67 m_newTagEdit->setFocus();
68 connect(m_newTagEdit, &QLineEdit::textEdited, this, &KEditTagsDialog::slotTextEdited);
69
70 auto newTagLayout = new QHBoxLayout();
71 newTagLayout->addWidget(newTagLabel);
72 newTagLayout->addWidget(m_newTagEdit, 1);
73
74 topLayout->addWidget(label);
75 topLayout->addWidget(m_treeView);
76 topLayout->addLayout(newTagLayout);
77 topLayout->addWidget(buttonBox);
78
79 resize(sizeHint());
80
81 auto job = new Baloo::TagListJob();
82 connect(job, &Baloo::TagListJob::finished, this, [this](KJob *job) {
83 auto tjob = static_cast<Baloo::TagListJob *>(job);
84 m_allTags = tjob->tags();
85 setupModel(m_allTags, m_tags);
86 });
87
88 job->start();
89}
90
91void KEditTagsDialog::setupModel(const QStringList &allTags, const QStringList &selectedTags)
92{
93 for (const auto &tag : allTags) {
94 ensureItemForTagExists(tag);
95 }
96
97 for (const auto &tag : selectedTags) {
98 auto currentItem = ensureItemForTagExists(tag);
99 currentItem->setCheckState(Qt::Checked);
100 }
101
102 m_treeView->expandAll();
103}
104
105QStandardItem *KEditTagsDialog::addTag(QStandardItem *parentItem, const QString &cannonicalTagPath, const QString &tagName)
106{
107 auto newItem = new QStandardItem(tagName);
108 newItem->setIcon(QIcon::fromTheme(QLatin1String("tag")));
109 newItem->setCheckable(true);
110 newItem->setData(cannonicalTagPath, canonicalTagPathRole);
111 parentItem->appendRow(newItem);
112 return newItem;
113}
114
115QStandardItem *KEditTagsDialog::findSubItem(QString currentName, QStandardItem *parentItem)
116{
117 for (int i = 0; i < parentItem->rowCount(); ++i) {
118 auto child = parentItem->child(i);
119 if (child->text() == currentName) {
120 return child;
121 }
122 }
123 return nullptr;
124}
125
126QStandardItem *KEditTagsDialog::findTag(const QString tag)
127{
128 QStandardItem *subItem = m_model->invisibleRootItem();
129 const QStringList splitTags = tag.split(QLatin1Char('/'), Qt::SkipEmptyParts);
130 for (int i = 0; i < splitTags.size() && subItem != nullptr; ++i) {
131 auto currentName = splitTags[i];
132 subItem = findSubItem(currentName, subItem);
133 }
134
135 return subItem;
136}
137
138QStandardItem *KEditTagsDialog::ensureItemForTagExists(const QString tag)
139{
140 QStandardItem *parentItem = m_model->invisibleRootItem();
141 QStandardItem *currentItem = nullptr;
142 const QStringList splitTags = tag.split(QLatin1Char('/'), Qt::SkipEmptyParts);
143 for (int i = 0; i < splitTags.size(); ++i) {
144 auto currentName = splitTags[i];
145
146 currentItem = findSubItem(currentName, parentItem);
147 if (currentItem == nullptr) {
148 QString tagPath = QLatin1Char('/') + splitTags.mid(0, i + 1).join(QLatin1Char('/'));
149 currentItem = addTag(parentItem, tagPath, currentName);
150 }
151
152 parentItem = currentItem;
153 }
154
155 return currentItem;
156}
157
158KEditTagsDialog::~KEditTagsDialog() = default;
159
160QStringList KEditTagsDialog::tags() const
161{
162 return m_tags;
163}
164
165void KEditTagsDialog::slotAcceptedButtonClicked()
166{
167 m_tags.clear();
168
169 std::function<void(QStandardItem *)> recurseInTree;
170 recurseInTree = [this, &recurseInTree](QStandardItem *item) {
171 QString canonicalPath = item->data(canonicalTagPathRole).toString();
172 if (item->checkState() == Qt::Checked) {
173 m_tags << canonicalPath;
174 }
175
176 for (int i = 0; i < item->rowCount(); ++i) {
177 recurseInTree(item->child(i));
178 }
179 };
180 recurseInTree(m_model->invisibleRootItem());
181
182 accept();
183}
184
185void KEditTagsDialog::slotItemActivated(const QModelIndex &index)
186{
187 const auto tag = m_treeView->model()->data(index, canonicalTagPathRole).toString();
188 m_newTagEdit->setText(tag + QLatin1Char('/'));
189 m_newTagEdit->setFocus();
190}
191
192void KEditTagsDialog::slotTextEdited(const QString &text)
193{
194 // Remove unnecessary spaces from a new tag is
195 // mandatory, as the user cannot see the difference
196 // between a tag "Test" and "Test ".
197 QString tagText = text.simplified();
198 while (tagText.endsWith(QLatin1Char('/'))) {
199 tagText.chop(1);
200 }
201
202 while(m_newItem != nullptr) {
203 auto parent = m_newItem->parent();
204 if (!parent) {
205 parent = m_model->invisibleRootItem();
206 }
207 parent->removeRow(m_newItem->row());
208 QString canonicalPath = parent->data(canonicalTagPathRole).toString();
209 if (m_allTags.contains(canonicalPath) || m_tags.contains(canonicalPath) || parent == m_model->invisibleRootItem()) {
210 m_newItem = nullptr;
211 } else {
212 m_newItem = parent;
213 }
214 }
215
216 if (tagText.isEmpty()) {
217 m_treeView->setModel(m_model);
218 m_treeView->expandAll();
219 return;
220 }
221
222 if (!findTag(text)) {
223 m_newItem = ensureItemForTagExists(text);
224 m_newItem->setIcon(QIcon::fromTheme(QStringLiteral("tag-new")));
225 m_newItem->setCheckState(Qt::Checked);
226 }
227
228 m_treeView->expandAll();
229}
230
231#include "moc_kedittagsdialog_p.cpp"
virtual Q_SCRIPTABLE void start()=0
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString label(StandardShortcut id)
void clicked(const QModelIndex &index)
virtual void reject()
QIcon fromTheme(const QString &name)
void textEdited(const QString &text)
void clear()
qsizetype count() const const
QList< T > mid(qsizetype pos, qsizetype length) const const
qsizetype size() const const
QObject * parent() const const
void appendRow(QStandardItem *item)
QStandardItem * child(int row, int column) const const
int rowCount() const const
void chop(qsizetype n)
QChar * data()
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString simplified() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
UserRole
SkipEmptyParts
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:22 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.