1 /*
2 * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
3 * SPDX-FileCopyrightText: 2019 Ismael Asensio <isma.af@mgmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "dolphinfacetswidget.h"
9
10 #include <KLocalizedString>
11 #include <KProtocolInfo>
12
13 #include <QComboBox>
14 #include <QDate>
15 #include <QEvent>
16 #include <QHBoxLayout>
17 #include <QIcon>
18 #include <QMenu>
19 #include <QToolButton>
20
DolphinFacetsWidget(QWidget * parent)21 DolphinFacetsWidget::DolphinFacetsWidget(QWidget* parent) :
22 QWidget(parent),
23 m_typeSelector(nullptr),
24 m_dateSelector(nullptr),
25 m_ratingSelector(nullptr),
26 m_tagsSelector(nullptr)
27 {
28 m_typeSelector = new QComboBox(this);
29 m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("none")), i18nc("@item:inlistbox", "Any Type"), QString());
30 m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("inode-directory")), i18nc("@item:inlistbox", "Folders") , QStringLiteral("Folder"));
31 m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("text-x-generic")), i18nc("@item:inlistbox", "Documents") , QStringLiteral("Document"));
32 m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("image-x-generic")), i18nc("@item:inlistbox", "Images") , QStringLiteral("Image"));
33 m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("audio-x-generic")), i18nc("@item:inlistbox", "Audio Files"), QStringLiteral("Audio"));
34 m_typeSelector->addItem(QIcon::fromTheme(QStringLiteral("video-x-generic")), i18nc("@item:inlistbox", "Videos") , QStringLiteral("Video"));
35 initComboBox(m_typeSelector);
36
37 const QDate currentDate = QDate::currentDate();
38
39 m_dateSelector = new QComboBox(this);
40 m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar")), i18nc("@item:inlistbox", "Any Date"), QDate());
41 m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18nc("@item:inlistbox", "Today") , currentDate);
42 m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("go-jump-today")), i18nc("@item:inlistbox", "Yesterday") , currentDate.addDays(-1));
43 m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-week")), i18nc("@item:inlistbox", "This Week") , currentDate.addDays(1 - currentDate.dayOfWeek()));
44 m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-month")), i18nc("@item:inlistbox", "This Month"), currentDate.addDays(1 - currentDate.day()));
45 m_dateSelector->addItem(QIcon::fromTheme(QStringLiteral("view-calendar-year")), i18nc("@item:inlistbox", "This Year") , currentDate.addDays(1 - currentDate.dayOfYear()));
46 initComboBox(m_dateSelector);
47
48 m_ratingSelector = new QComboBox(this);
49 m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("non-starred-symbolic")), i18nc("@item:inlistbox", "Any Rating"), 0);
50 m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "1 or more"), 1);
51 m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "2 or more"), 2);
52 m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "3 or more"), 3);
53 m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "4 or more"), 4);
54 m_ratingSelector->addItem(QIcon::fromTheme(QStringLiteral("starred-symbolic")), i18nc("@item:inlistbox", "Highest Rating"), 5);
55 initComboBox(m_ratingSelector);
56
57 m_clearTagsAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-all")), i18nc("@action:inmenu", "Clear Selection"), this);
58 connect(m_clearTagsAction, &QAction::triggered, this, [this]() {
59 resetSearchTags();
60 Q_EMIT facetChanged();
61 });
62
63 m_tagsSelector = new QToolButton(this);
64 m_tagsSelector->setIcon(QIcon::fromTheme(QStringLiteral("tag")));
65 m_tagsSelector->setMenu(new QMenu(this));
66 m_tagsSelector->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
67 m_tagsSelector->setPopupMode(QToolButton::MenuButtonPopup);
68 m_tagsSelector->setAutoRaise(true);
69 updateTagsSelector();
70
71 connect(m_tagsSelector, &QToolButton::clicked, m_tagsSelector, &QToolButton::showMenu);
72 connect(m_tagsSelector->menu(), &QMenu::aboutToShow, this, &DolphinFacetsWidget::updateTagsMenu);
73 connect(&m_tagsLister, &KCoreDirLister::itemsAdded, this, &DolphinFacetsWidget::updateTagsMenuItems);
74 updateTagsMenu();
75
76 QHBoxLayout* topLayout = new QHBoxLayout(this);
77 topLayout->setContentsMargins(0, 0, 0, 0);
78 topLayout->addWidget(m_typeSelector);
79 topLayout->addWidget(m_dateSelector);
80 topLayout->addWidget(m_ratingSelector);
81 topLayout->addWidget(m_tagsSelector);
82
83 resetSearchTerms();
84 }
85
~DolphinFacetsWidget()86 DolphinFacetsWidget::~DolphinFacetsWidget()
87 {
88 }
89
changeEvent(QEvent * event)90 void DolphinFacetsWidget::changeEvent(QEvent *event)
91 {
92 if (event->type() == QEvent::EnabledChange) {
93 if (isEnabled()) {
94 updateTagsSelector();
95 } else {
96 resetSearchTerms();
97 }
98 }
99 }
100
resetSearchTerms()101 void DolphinFacetsWidget::resetSearchTerms()
102 {
103 m_typeSelector->setCurrentIndex(0);
104 m_dateSelector->setCurrentIndex(0);
105 m_ratingSelector->setCurrentIndex(0);
106
107 resetSearchTags();
108 }
109
searchTerms() const110 QStringList DolphinFacetsWidget::searchTerms() const
111 {
112 QStringList terms;
113
114 if (m_ratingSelector->currentIndex() > 0) {
115 const int rating = m_ratingSelector->currentData().toInt() * 2;
116 terms << QStringLiteral("rating>=%1").arg(rating);
117 }
118
119 if (m_dateSelector->currentIndex() > 0) {
120 const QDate date = m_dateSelector->currentData().toDate();
121 terms << QStringLiteral("modified>=%1").arg(date.toString(Qt::ISODate));
122 }
123
124 if (!m_searchTags.isEmpty()) {
125 for (auto const &tag : m_searchTags) {
126 if (tag.contains(QLatin1Char(' '))) {
127 terms << QStringLiteral("tag:\"%1\"").arg(tag);
128 } else {
129 terms << QStringLiteral("tag:%1").arg(tag);
130 }
131 }
132 }
133
134 return terms;
135 }
136
facetType() const137 QString DolphinFacetsWidget::facetType() const
138 {
139 return m_typeSelector->currentData().toString();
140 }
141
isSearchTerm(const QString & term) const142 bool DolphinFacetsWidget::isSearchTerm(const QString& term) const
143 {
144 static const QLatin1String searchTokens[] {
145 QLatin1String("modified>="),
146 QLatin1String("rating>="),
147 QLatin1String("tag:"), QLatin1String("tag=")
148 };
149
150 for (const auto &searchToken : searchTokens) {
151 if (term.startsWith(searchToken)) {
152 return true;
153 }
154 }
155 return false;
156 }
157
setSearchTerm(const QString & term)158 void DolphinFacetsWidget::setSearchTerm(const QString& term)
159 {
160 if (term.startsWith(QLatin1String("modified>="))) {
161 const QString value = term.mid(10);
162 const QDate date = QDate::fromString(value, Qt::ISODate);
163 setTimespan(date);
164 } else if (term.startsWith(QLatin1String("rating>="))) {
165 const QString value = term.mid(8);
166 const int stars = value.toInt() / 2;
167 setRating(stars);
168 } else if (term.startsWith(QLatin1String("tag:")) ||
169 term.startsWith(QLatin1String("tag="))) {
170 const QString value = term.mid(4);
171 addSearchTag(value);
172 }
173 }
174
setFacetType(const QString & type)175 void DolphinFacetsWidget::setFacetType(const QString& type)
176 {
177 for (int index = 0; index <= m_typeSelector->count(); index++) {
178 if (type == m_typeSelector->itemData(index).toString()) {
179 m_typeSelector->setCurrentIndex(index);
180 break;
181 }
182 }
183 }
184
setRating(const int stars)185 void DolphinFacetsWidget::setRating(const int stars)
186 {
187 if (stars < 0 || stars > 5) {
188 return;
189 }
190 m_ratingSelector->setCurrentIndex(stars);
191 }
192
setTimespan(const QDate & date)193 void DolphinFacetsWidget::setTimespan(const QDate& date)
194 {
195 if (!date.isValid()) {
196 return;
197 }
198 m_dateSelector->setCurrentIndex(0);
199 for (int index = 1; index <= m_dateSelector->count(); index++) {
200 if (date >= m_dateSelector->itemData(index).toDate()) {
201 m_dateSelector->setCurrentIndex(index);
202 break;
203 }
204 }
205 }
206
addSearchTag(const QString & tag)207 void DolphinFacetsWidget::addSearchTag(const QString& tag)
208 {
209 if (tag.isEmpty() || m_searchTags.contains(tag)) {
210 return;
211 }
212 m_searchTags.append(tag);
213 m_searchTags.sort();
214 updateTagsSelector();
215 }
216
removeSearchTag(const QString & tag)217 void DolphinFacetsWidget::removeSearchTag(const QString& tag)
218 {
219 if (tag.isEmpty() || !m_searchTags.contains(tag)) {
220 return;
221 }
222 m_searchTags.removeAll(tag);
223 updateTagsSelector();
224 }
225
resetSearchTags()226 void DolphinFacetsWidget::resetSearchTags()
227 {
228 m_searchTags = QStringList();
229 updateTagsSelector();
230 updateTagsMenu();
231 }
232
initComboBox(QComboBox * combo)233 void DolphinFacetsWidget::initComboBox(QComboBox* combo)
234 {
235 combo->setFrame(false);
236 combo->setMinimumHeight(parentWidget()->height());
237 combo->setCurrentIndex(0);
238 connect(combo, QOverload<int>::of(&QComboBox::activated), this, &DolphinFacetsWidget::facetChanged);
239 }
240
updateTagsSelector()241 void DolphinFacetsWidget::updateTagsSelector()
242 {
243 const bool hasListedTags = !m_tagsSelector->menu()->isEmpty();
244 const bool hasSelectedTags = !m_searchTags.isEmpty();
245
246 if (hasSelectedTags) {
247 const QString tagsText = m_searchTags.join(i18nc("String list separator", ", "));
248 m_tagsSelector->setText(i18ncp("@action:button %2 is a list of tags",
249 "Tag: %2", "Tags: %2",m_searchTags.count(), tagsText));
250 } else {
251 m_tagsSelector->setText(i18nc("@action:button", "Add Tags"));
252 }
253
254 m_tagsSelector->setEnabled(isEnabled() && (hasListedTags || hasSelectedTags));
255 m_clearTagsAction->setEnabled(hasSelectedTags);
256 }
257
updateTagsMenu()258 void DolphinFacetsWidget::updateTagsMenu()
259 {
260 updateTagsMenuItems({}, {});
261 if (KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) {
262 m_tagsLister.openUrl(QUrl(QStringLiteral("tags:/")), KCoreDirLister::OpenUrlFlag::Reload);
263 }
264 }
265
updateTagsMenuItems(const QUrl &,const KFileItemList & items)266 void DolphinFacetsWidget::updateTagsMenuItems(const QUrl&, const KFileItemList& items)
267 {
268 QMenu *tagsMenu = m_tagsSelector->menu();
269 tagsMenu->clear();
270
271 QStringList allTags = QStringList(m_searchTags);
272 for (const KFileItem &item: items) {
273 allTags.append(item.name());
274 }
275 allTags.sort(Qt::CaseInsensitive);
276 allTags.removeDuplicates();
277
278 const bool onlyOneTag = allTags.count() == 1;
279
280 for (const QString& tagName : qAsConst(allTags)) {
281 QAction *action = tagsMenu->addAction(QIcon::fromTheme(QStringLiteral("tag")), tagName);
282 action->setCheckable(true);
283 action->setChecked(m_searchTags.contains(tagName));
284
285 connect(action, &QAction::triggered, this, [this, tagName, onlyOneTag](bool isChecked) {
286 if (isChecked) {
287 addSearchTag(tagName);
288 } else {
289 removeSearchTag(tagName);
290 }
291 Q_EMIT facetChanged();
292
293 if (!onlyOneTag) {
294 m_tagsSelector->menu()->show();
295 }
296 });
297 }
298
299 if (allTags.count() > 1) {
300 tagsMenu->addSeparator();
301 tagsMenu->addAction(m_clearTagsAction);
302 }
303
304 updateTagsSelector();
305 }
306