1/* vi: ts=8 sts=4 sw=4
2
3 This file is part of the KDE project, module kfile.
4 SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
5 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
6 SPDX-FileCopyrightText: 1997 Christoph Neerfeld <chris@kde.org>
7 SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
8 SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
9
10 SPDX-License-Identifier: LGPL-2.0-only
11*/
12
13#include "kicondialog.h"
14#include "kicondialog_p.h"
15#include "kicondialogmodel_p.h"
16
17#include <KLazyLocalizedString>
18#include <KLocalizedString>
19#include <KStandardAction>
20
21#include <QAbstractListModel>
22#include <QActionGroup>
23#include <QApplication>
24#include <QComboBox>
25#include <QDialogButtonBox>
26#include <QFileInfo>
27#include <QGraphicsOpacityEffect>
28#include <QLabel>
29#include <QList>
30#include <QMenu>
31#include <QPainter>
32#include <QScrollBar>
33#include <QSortFilterProxyModel>
34#include <QStandardItemModel> // for manipulatig QComboBox
35#include <QStandardPaths>
36#include <QSvgRenderer>
37
38#include <algorithm>
39#include <math.h>
40
41static const int s_edgePad = 3;
42
43class KIconDialogSortFilterProxyModel : public QSortFilterProxyModel
44{
45 Q_OBJECT
46
47public:
48 explicit KIconDialogSortFilterProxyModel(QObject *parent);
49
50 enum SymbolicIcons { AllSymbolicIcons, OnlySymbolicIcons, NoSymbolicIcons };
51
52 void setSymbolicIcons(SymbolicIcons symbolicIcons);
53 void setHasSymbolicIcon(bool hasSymbolicIcon);
54
55protected:
56 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
57
58private:
59 SymbolicIcons m_symbolicIcons = AllSymbolicIcons;
60 bool m_hasSymbolicIcon = false;
61};
62
63KIconDialogSortFilterProxyModel::KIconDialogSortFilterProxyModel(QObject *parent)
64 : QSortFilterProxyModel(parent)
65{
66}
67
68void KIconDialogSortFilterProxyModel::setSymbolicIcons(SymbolicIcons symbolicIcons)
69{
70 if (m_symbolicIcons == symbolicIcons) {
71 return;
72 }
73
74 m_symbolicIcons = symbolicIcons;
75 invalidateFilter();
76}
77
78void KIconDialogSortFilterProxyModel::setHasSymbolicIcon(bool hasSymbolicIcon)
79{
80 if (m_hasSymbolicIcon == hasSymbolicIcon) {
81 return;
82 }
83
84 m_hasSymbolicIcon = hasSymbolicIcon;
85 invalidateFilter();
86}
87
88bool KIconDialogSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
89{
90 if (m_hasSymbolicIcon) {
91 if (m_symbolicIcons == OnlySymbolicIcons || m_symbolicIcons == NoSymbolicIcons) {
92 const QString display = sourceModel()->index(row: source_row, column: 0, parent: source_parent).data(arole: Qt::DisplayRole).toString();
93 const bool isSymbolic = display.endsWith(s: KIconDialogModel::symbolicSuffix());
94 if ((m_symbolicIcons == OnlySymbolicIcons && !isSymbolic) || (m_symbolicIcons == NoSymbolicIcons && isSymbolic)) {
95 return false;
96 }
97 }
98 }
99
100 return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
101}
102
103KIconDialogModel::KIconDialogModel(KIconLoader *loader, QObject *parent)
104 : QAbstractListModel(parent)
105 , m_loader(loader)
106{
107}
108
109KIconDialogModel::~KIconDialogModel() = default;
110
111qreal KIconDialogModel::devicePixelRatio() const
112{
113 return m_dpr;
114}
115
116void KIconDialogModel::setDevicePixelRatio(qreal dpr)
117{
118 m_dpr = dpr;
119}
120
121QSize KIconDialogModel::iconSize() const
122{
123 return m_iconSize;
124}
125
126void KIconDialogModel::setIconSize(const QSize &iconSize)
127{
128 m_iconSize = iconSize;
129}
130
131QLatin1String KIconDialogModel::symbolicSuffix()
132{
133 return QLatin1String("-symbolic");
134}
135
136bool KIconDialogModel::hasSymbolicIcon() const
137{
138 return m_hasSymbolicIcon;
139}
140
141void KIconDialogModel::load(const QStringList &paths)
142{
143 beginResetModel();
144
145 const bool oldSymbolic = m_hasSymbolicIcon;
146 m_hasSymbolicIcon = false;
147
148 m_data.clear();
149 m_data.reserve(asize: paths.count());
150
151 for (const QString &path : paths) {
152 const QFileInfo fi(path);
153
154 KIconDialogModelData item;
155 item.name = fi.completeBaseName();
156 item.path = path;
157 // pixmap is created on demand
158
159 if (!m_hasSymbolicIcon && item.name.endsWith(s: symbolicSuffix())) {
160 m_hasSymbolicIcon = true;
161 }
162
163 m_data.append(t: item);
164 }
165
166 endResetModel();
167
168 if (oldSymbolic != m_hasSymbolicIcon) {
169 Q_EMIT hasSymbolicIconChanged(hasSymbolicIcon: m_hasSymbolicIcon);
170 }
171}
172
173int KIconDialogModel::rowCount(const QModelIndex &parent) const
174{
175 if (parent.isValid()) {
176 return 0;
177 }
178 return m_data.count();
179}
180
181QVariant KIconDialogModel::data(const QModelIndex &index, int role) const
182{
183 if (!checkIndex(index, options: QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
184 return QVariant();
185 }
186
187 const auto &item = m_data.at(i: index.row());
188
189 switch (role) {
190 case Qt::DisplayRole:
191 return item.name;
192 case Qt::DecorationRole:
193 if (item.pixmap.isNull()) {
194 const_cast<KIconDialogModel *>(this)->loadPixmap(index);
195 }
196 return item.pixmap;
197 case Qt::ToolTipRole:
198 return item.name;
199 case PathRole:
200 return item.path;
201 }
202
203 return QVariant();
204}
205
206void KIconDialogModel::loadPixmap(const QModelIndex &index)
207{
208 Q_ASSERT(index.isValid());
209
210 auto &item = m_data[index.row()];
211 Q_ASSERT(item.pixmap.isNull());
212
213 const auto dpr = devicePixelRatio();
214
215 item.pixmap = m_loader->loadScaledIcon(name: item.path, group: KIconLoader::Desktop, scale: dpr, size: iconSize(), state: KIconLoader::DefaultState, overlays: {}, path_store: nullptr, canReturnNull: true);
216 item.pixmap.setDevicePixelRatio(dpr);
217}
218
219/**
220 * Qt allocates very little horizontal space for the icon name,
221 * even if the gridSize width is large. This delegate allocates
222 * the gridSize width (minus some padding) for the icon and icon name.
223 */
224class KIconCanvasDelegate : public QAbstractItemDelegate
225{
226 Q_OBJECT
227public:
228 KIconCanvasDelegate(QListView *parent, QAbstractItemDelegate *defaultDelegate);
229 ~KIconCanvasDelegate() override
230 {
231 }
232 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
233 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
234
235private:
236 QAbstractItemDelegate *m_defaultDelegate = nullptr;
237};
238
239KIconCanvasDelegate::KIconCanvasDelegate(QListView *parent, QAbstractItemDelegate *defaultDelegate)
240 : QAbstractItemDelegate(parent)
241{
242 m_defaultDelegate = defaultDelegate;
243}
244
245void KIconCanvasDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
246{
247 auto *canvas = static_cast<QListView *>(parent());
248 const int gridWidth = canvas->gridSize().width();
249 QStyleOptionViewItem newOption = option;
250 newOption.displayAlignment = Qt::AlignHCenter | Qt::AlignTop;
251 newOption.features.setFlag(flag: QStyleOptionViewItem::WrapText);
252 // Manipulate the width available.
253 newOption.rect.setX((option.rect.x() / gridWidth) * gridWidth + s_edgePad);
254 newOption.rect.setY(option.rect.y() + s_edgePad);
255 newOption.rect.setWidth(gridWidth - 2 * s_edgePad);
256 newOption.rect.setHeight(option.rect.height() - 2 * s_edgePad);
257 m_defaultDelegate->paint(painter, option: newOption, index);
258}
259
260QSize KIconCanvasDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
261{
262 auto *canvas = static_cast<QListView *>(parent());
263
264 // TODO can we set wrap text and display alignment somewhere globally?
265 QStyleOptionViewItem newOption = option;
266 newOption.displayAlignment = Qt::AlignHCenter | Qt::AlignTop;
267 newOption.features.setFlag(flag: QStyleOptionViewItem::WrapText);
268
269 QSize size = m_defaultDelegate->sizeHint(option: newOption, index);
270 const int gridWidth = canvas->gridSize().width();
271 const int gridHeight = canvas->gridSize().height();
272 size.setWidth(gridWidth - 2 * s_edgePad);
273 size.setHeight(gridHeight - 2 * s_edgePad);
274 QFontMetrics metrics(option.font);
275 size.setHeight(gridHeight + metrics.height() * 3);
276 return size;
277}
278
279KIconDialogPrivate::KIconDialogPrivate(KIconDialog *qq)
280 : q(qq)
281 , mpLoader(KIconLoader::global())
282 , model(new KIconDialogModel(mpLoader, qq))
283 , proxyModel(new KIconDialogSortFilterProxyModel(qq))
284 , filterSymbolicAction(new QAction(qq))
285 , filterSymbolicGroup(new QActionGroup(qq))
286{
287 proxyModel->setSourceModel(model);
288 proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
289
290 filterSymbolicGroup->setExclusive(true);
291
292 QObject::connect(sender: model, signal: &KIconDialogModel::hasSymbolicIconChanged, context: filterSymbolicAction, slot: &QAction::setVisible);
293 QObject::connect(sender: model, signal: &KIconDialogModel::hasSymbolicIconChanged, context: proxyModel, slot: &KIconDialogSortFilterProxyModel::setHasSymbolicIcon);
294}
295
296/*
297 * KIconDialog: Dialog for selecting icons. Both system and user
298 * specified icons can be chosen.
299 */
300
301KIconDialog::KIconDialog(QWidget *parent)
302 : QDialog(parent)
303 , d(new KIconDialogPrivate(this))
304{
305 setModal(true);
306
307 d->init();
308}
309
310void KIconDialogPrivate::init()
311{
312 mGroupOrSize = KIconLoader::Desktop;
313 mContext = KIconLoader::Any;
314
315 ui.setupUi(q);
316
317 auto updatePlaceholder = [this] {
318 updatePlaceholderLabel();
319 };
320 QObject::connect(sender: proxyModel, signal: &QSortFilterProxyModel::modelReset, context: q, slot&: updatePlaceholder);
321 QObject::connect(sender: proxyModel, signal: &QSortFilterProxyModel::rowsInserted, context: q, slot&: updatePlaceholder);
322 QObject::connect(sender: proxyModel, signal: &QSortFilterProxyModel::rowsRemoved, context: q, slot&: updatePlaceholder);
323
324 QAction *findAction = KStandardAction::find(recvr: ui.searchLine, slot: qOverload<>(&QWidget::setFocus), parent: q);
325 q->addAction(action: findAction);
326
327 QMenu *filterSymbolicMenu = new QMenu(q);
328
329 QAction *filterSymbolicAll = filterSymbolicMenu->addAction(i18nc("@item:inmenu All icons", "All"));
330 filterSymbolicAll->setData(KIconDialogSortFilterProxyModel::AllSymbolicIcons);
331 filterSymbolicAll->setChecked(true); // Start with "All" icons.
332 filterSymbolicAll->setCheckable(true);
333
334 QAction *filterSymbolicOnly = filterSymbolicMenu->addAction(i18nc("@item:inmenu Show only symbolic icons", "Only Symbolic"));
335 filterSymbolicOnly->setData(KIconDialogSortFilterProxyModel::OnlySymbolicIcons);
336 filterSymbolicOnly->setCheckable(true);
337
338 QAction *filterSymbolicNone = filterSymbolicMenu->addAction(i18nc("@item:inmenu Hide symbolic icons", "No Symbolic"));
339 filterSymbolicNone->setData(KIconDialogSortFilterProxyModel::NoSymbolicIcons);
340 filterSymbolicNone->setCheckable(true);
341
342 filterSymbolicAction->setIcon(QIcon::fromTheme(QStringLiteral("view-filter")));
343 filterSymbolicAction->setCheckable(true);
344 filterSymbolicAction->setChecked(true);
345 filterSymbolicAction->setMenu(filterSymbolicMenu);
346
347 filterSymbolicGroup->addAction(a: filterSymbolicAll);
348 filterSymbolicGroup->addAction(a: filterSymbolicOnly);
349 filterSymbolicGroup->addAction(a: filterSymbolicNone);
350 QObject::connect(sender: filterSymbolicGroup, signal: &QActionGroup::triggered, context: q, slot: [this](QAction *action) {
351 proxyModel->setSymbolicIcons(static_cast<KIconDialogSortFilterProxyModel::SymbolicIcons>(action->data().toInt()));
352 });
353
354 ui.searchLine->addAction(action: filterSymbolicAction, position: QLineEdit::TrailingPosition);
355
356 QObject::connect(sender: ui.searchLine, signal: &QLineEdit::textChanged, context: proxyModel, slot: &QSortFilterProxyModel::setFilterFixedString);
357
358 static const KLazyLocalizedString context_text[] = {
359 kli18n(text: "All"),
360 kli18n(text: "Actions"),
361 kli18n(text: "Applications"),
362 kli18n(text: "Categories"),
363 kli18n(text: "Devices"),
364 kli18n(text: "Emblems"),
365 kli18n(text: "Emotes"),
366 kli18n(text: "Mimetypes"),
367 kli18n(text: "Places"),
368 kli18n(text: "Status"),
369 };
370 static const KIconLoader::Context context_id[] = {
371 KIconLoader::Any,
372 KIconLoader::Action,
373 KIconLoader::Application,
374 KIconLoader::Category,
375 KIconLoader::Device,
376 KIconLoader::Emblem,
377 KIconLoader::Emote,
378 KIconLoader::MimeType,
379 KIconLoader::Place,
380 KIconLoader::StatusIcon,
381 };
382 const int cnt = sizeof(context_text) / sizeof(context_text[0]);
383 for (int i = 0; i < cnt; ++i) {
384 if (mpLoader->hasContext(context: context_id[i])) {
385 ui.contextCombo->addItem(atext: context_text[i].toString(), auserData: context_id[i]);
386 if (i == 0) {
387 ui.contextCombo->insertSeparator(index: i + 1);
388 }
389 }
390 }
391 ui.contextCombo->insertSeparator(index: ui.contextCombo->count());
392 ui.contextCombo->addItem(i18nc("Other icons", "Other"));
393 ui.contextCombo->setMaxVisibleItems(ui.contextCombo->count());
394 ui.contextCombo->setFixedSize(ui.contextCombo->sizeHint());
395
396 QObject::connect(sender: ui.contextCombo, signal: qOverload<int>(&QComboBox::activated), context: q, slot: [this]() {
397 const auto currentData = ui.contextCombo->currentData();
398 if (currentData.isValid()) {
399 mContext = static_cast<KIconLoader::Context>(ui.contextCombo->currentData().toInt());
400 } else {
401 mContext = static_cast<KIconLoader::Context>(-1);
402 }
403 showIcons();
404 });
405
406 auto *delegate = new KIconCanvasDelegate(ui.canvas, ui.canvas->itemDelegate());
407 ui.canvas->setItemDelegate(delegate);
408
409 ui.canvas->setModel(proxyModel);
410
411 QObject::connect(sender: ui.canvas, signal: &QAbstractItemView::activated, context: q, slot: [this]() {
412 custom.clear();
413 q->slotOk();
414 });
415
416 // You can't just stack widgets on top of each other in Qt Designer
417 auto *placeholderLayout = new QVBoxLayout(ui.canvas);
418
419 placeholderLabel = new QLabel();
420 QFont placeholderLabelFont;
421 // To match the size of a level 2 Heading/KTitleWidget
422 placeholderLabelFont.setPointSize(qRound(d: placeholderLabelFont.pointSize() * 1.3));
423 placeholderLabel->setFont(placeholderLabelFont);
424 placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
425 placeholderLabel->setWordWrap(true);
426 placeholderLabel->setAlignment(Qt::AlignCenter);
427
428 // Match opacity of QML placeholder label component
429 auto *effect = new QGraphicsOpacityEffect(placeholderLabel);
430 effect->setOpacity(0.5);
431 placeholderLabel->setGraphicsEffect(effect);
432
433 updatePlaceholderLabel();
434
435 placeholderLayout->addWidget(placeholderLabel);
436 placeholderLayout->setAlignment(w: placeholderLabel, alignment: Qt::AlignCenter);
437
438 // TODO I bet there is a KStandardAction for that?
439 browseButton = new QPushButton(QIcon::fromTheme(QStringLiteral("folder-open")), i18n("Browse…"));
440 // TODO does this have implicatons? I just want the "Browse" button on the left side :)
441 ui.buttonBox->addButton(button: browseButton, role: QDialogButtonBox::HelpRole);
442 QObject::connect(sender: browseButton, signal: &QPushButton::clicked, context: q, slot: [this] {
443 browse();
444 });
445
446 QObject::connect(sender: ui.buttonBox, signal: &QDialogButtonBox::accepted, context: q, slot: &KIconDialog::slotOk);
447 QObject::connect(sender: ui.buttonBox, signal: &QDialogButtonBox::rejected, context: q, slot: &QDialog::reject);
448
449 q->adjustSize();
450}
451
452KIconDialog::~KIconDialog() = default;
453
454static bool sortByFileName(const QString &path1, const QString &path2)
455{
456 const QString fileName1 = path1.mid(position: path1.lastIndexOf(c: QLatin1Char('/')) + 1);
457 const QString fileName2 = path2.mid(position: path2.lastIndexOf(c: QLatin1Char('/')) + 1);
458 return QString::compare(s1: fileName1, s2: fileName2, cs: Qt::CaseInsensitive) < 0;
459}
460
461void KIconDialogPrivate::showIcons()
462{
463 QStringList filelist;
464 if (isSystemIconsContext()) {
465 if (m_bStrictIconSize) {
466 filelist = mpLoader->queryIcons(group_or_size: mGroupOrSize, context: mContext);
467 } else {
468 filelist = mpLoader->queryIconsByContext(group_or_size: mGroupOrSize, context: mContext);
469 }
470 } else if (!customLocation.isEmpty()) {
471 filelist = mpLoader->queryIconsByDir(iconsDir: customLocation);
472 } else {
473 // List PNG files found directly in the kiconload search paths.
474 const QStringList pngNameFilter(QStringLiteral("*.png"));
475 for (const QString &relDir : KIconLoader::global()->searchPaths()) {
476 const QStringList dirs = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, fileName: relDir, options: QStandardPaths::LocateDirectory);
477 for (const QString &dir : dirs) {
478 const auto files = QDir(dir).entryList(nameFilters: pngNameFilter);
479 for (const QString &fileName : files) {
480 filelist << dir + QLatin1Char('/') + fileName;
481 }
482 }
483 }
484 }
485
486 std::sort(first: filelist.begin(), last: filelist.end(), comp: sortByFileName);
487
488 // The KIconCanvas has uniformItemSizes set which really expects
489 // all added icons to be the same size, otherwise weirdness ensues :)
490 // Ensure all SVGs are scaled to the desired size and that as few icons
491 // need to be padded as possible by specifying a sensible size.
492 if (mGroupOrSize < -1) {
493 // mGroupOrSize can be -1 if NoGroup is chosen.
494 // Explicit size.
495 ui.canvas->setIconSize(QSize(-mGroupOrSize, -mGroupOrSize));
496 } else {
497 // Icon group.
498 int groupSize = mpLoader->currentSize(group: static_cast<KIconLoader::Group>(mGroupOrSize));
499 ui.canvas->setIconSize(QSize(groupSize, groupSize));
500 }
501
502 // Try to make room for three lines of text...
503 QFontMetrics metrics(ui.canvas->font());
504 const int frameHMargin = ui.canvas->style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: nullptr, widget: ui.canvas) + 1;
505 const int lineCount = 3;
506 ui.canvas->setGridSize(QSize(100, ui.canvas->iconSize().height() + lineCount * metrics.height() + 3 * frameHMargin));
507
508 // Set a minimum size of 6x3 icons
509 const int columnCount = 6;
510 const int rowCount = 3;
511 QStyleOption opt;
512 opt.initFrom(w: ui.canvas);
513 int width = columnCount * ui.canvas->gridSize().width();
514 width += ui.canvas->verticalScrollBar()->sizeHint().width() + 1;
515 width += 2 * ui.canvas->frameWidth();
516 if (ui.canvas->style()->styleHint(stylehint: QStyle::SH_ScrollView_FrameOnlyAroundContents, opt: &opt, widget: ui.canvas)) {
517 width += ui.canvas->style()->pixelMetric(metric: QStyle::PM_ScrollView_ScrollBarSpacing, option: &opt, widget: ui.canvas);
518 }
519 int height = rowCount * ui.canvas->gridSize().height() + 1;
520 height += 2 * ui.canvas->frameWidth();
521
522 ui.canvas->setMinimumSize(QSize(width, height));
523
524 model->setIconSize(ui.canvas->iconSize());
525 model->setDevicePixelRatio(q->devicePixelRatioF());
526 model->load(paths: filelist);
527
528 if (!pendingSelectedIcon.isEmpty()) {
529 selectIcon(iconName: pendingSelectedIcon);
530 pendingSelectedIcon.clear();
531 }
532}
533
534bool KIconDialogPrivate::selectIcon(const QString &iconName)
535{
536 for (int i = 0; i < proxyModel->rowCount(); ++i) {
537 const QModelIndex idx = proxyModel->index(row: i, column: 0);
538
539 QString name = idx.data(arole: KIconDialogModel::PathRole).toString();
540 if (!name.isEmpty() && isSystemIconsContext()) {
541 const QFileInfo fi(name);
542 name = fi.completeBaseName();
543 }
544
545 if (iconName == name) {
546 ui.canvas->setCurrentIndex(idx);
547 return true;
548 }
549 }
550
551 return false;
552}
553
554void KIconDialog::setStrictIconSize(bool b)
555{
556 d->m_bStrictIconSize = b;
557}
558
559bool KIconDialog::strictIconSize() const
560{
561 return d->m_bStrictIconSize;
562}
563
564void KIconDialog::setIconSize(int size)
565{
566 // see KIconLoader, if you think this is weird
567 if (size == 0) {
568 d->mGroupOrSize = KIconLoader::Desktop; // default Group
569 } else {
570 d->mGroupOrSize = -size; // yes, KIconLoader::queryIconsByContext is weird
571 }
572}
573
574int KIconDialog::iconSize() const
575{
576 // 0 or any other value ==> mGroupOrSize is a group, so we return 0
577 return (d->mGroupOrSize < 0) ? -d->mGroupOrSize : 0;
578}
579
580void KIconDialog::setSelectedIcon(const QString &iconName)
581{
582 // TODO Update live when dialog is already open
583 d->pendingSelectedIcon = iconName;
584}
585
586void KIconDialog::setup(KIconLoader::Group group, KIconLoader::Context context, bool strictIconSize, int iconSize, bool user, bool lockUser, bool lockCustomDir)
587{
588 d->m_bStrictIconSize = strictIconSize;
589 d->m_bLockUser = lockUser;
590 d->m_bLockCustomDir = lockCustomDir;
591 if (iconSize == 0) {
592 if (group == KIconLoader::NoGroup) {
593 // NoGroup has numeric value -1, which should
594 // not really be used with KIconLoader::queryIcons*(...);
595 // pick a proper group.
596 d->mGroupOrSize = KIconLoader::Small;
597 } else {
598 d->mGroupOrSize = group;
599 }
600 } else {
601 d->mGroupOrSize = -iconSize;
602 }
603
604 if (user) {
605 d->ui.contextCombo->setCurrentIndex(d->ui.contextCombo->count() - 1);
606 } else {
607 d->setContext(context);
608 }
609
610 d->ui.contextCombo->setEnabled(!user || !lockUser);
611
612 // Disable "Other" entry when user is locked
613 auto *model = qobject_cast<QStandardItemModel *>(object: d->ui.contextCombo->model());
614 auto *otherItem = model->item(row: model->rowCount() - 1);
615 auto flags = otherItem->flags();
616 flags.setFlag(flag: Qt::ItemIsEnabled, on: !lockUser);
617 otherItem->setFlags(flags);
618
619 // Only allow browsing when explicitly allowed and user icons are allowed
620 // An app may not expect a path when asking only about system icons
621 d->browseButton->setVisible(!lockCustomDir && (!user || !lockUser));
622}
623
624void KIconDialogPrivate::setContext(KIconLoader::Context context)
625{
626 mContext = context;
627 const int index = ui.contextCombo->findData(data: context);
628 if (index > -1) {
629 ui.contextCombo->setCurrentIndex(index);
630 }
631}
632
633void KIconDialogPrivate::updatePlaceholderLabel()
634{
635 if (proxyModel->rowCount() > 0) {
636 placeholderLabel->hide();
637 return;
638 }
639
640 if (!ui.searchLine->text().isEmpty()) {
641 placeholderLabel->setText(i18n("No icons matching the search"));
642 } else {
643 placeholderLabel->setText(i18n("No icons in this category"));
644 }
645
646 placeholderLabel->show();
647}
648
649void KIconDialog::setCustomLocation(const QString &location)
650{
651 d->customLocation = location;
652}
653
654QString KIconDialog::openDialog()
655{
656 if (exec() == Accepted) {
657 if (!d->custom.isEmpty()) {
658 return d->custom;
659 }
660
661 const QString name = d->ui.canvas->currentIndex().data(arole: KIconDialogModel::PathRole).toString();
662 if (name.isEmpty() || !d->ui.contextCombo->currentData().isValid()) {
663 return name;
664 }
665
666 QFileInfo fi(name);
667 return fi.completeBaseName();
668 }
669
670 return QString();
671}
672
673void KIconDialog::showDialog()
674{
675 setModal(false);
676 show();
677}
678
679void KIconDialog::slotOk()
680{
681 QString name;
682 if (!d->custom.isEmpty()) {
683 name = d->custom;
684 } else {
685 name = d->ui.canvas->currentIndex().data(arole: KIconDialogModel::PathRole).toString();
686 if (!name.isEmpty() && d->isSystemIconsContext()) {
687 const QFileInfo fi(name);
688 name = fi.completeBaseName();
689 }
690 }
691
692 Q_EMIT newIconName(iconName: name);
693 QDialog::accept();
694}
695
696void KIconDialog::showEvent(QShowEvent *event)
697{
698 QDialog::showEvent(event);
699 d->showIcons();
700 d->ui.searchLine->setFocus();
701}
702
703QString KIconDialog::getIcon(KIconLoader::Group group,
704 KIconLoader::Context context,
705 bool strictIconSize,
706 int iconSize,
707 bool user,
708 QWidget *parent,
709 const QString &title)
710{
711 KIconDialog dlg(parent);
712 dlg.setup(group, context, strictIconSize, iconSize, user);
713 if (!title.isEmpty()) {
714 dlg.setWindowTitle(title);
715 }
716
717 return dlg.openDialog();
718}
719
720void KIconDialogPrivate::browse()
721{
722 if (browseDialog) {
723 browseDialog.data()->show();
724 browseDialog.data()->raise();
725 return;
726 }
727
728 // Create a file dialog to select an ICO, PNG, XPM or SVG file,
729 // with the image previewer shown.
730 QFileDialog *dlg = new QFileDialog(q, i18n("Select Icon"), QString(), i18n("*.ico *.png *.xpm *.svg *.svgz|Icon Files (*.ico *.png *.xpm *.svg *.svgz)"));
731 // TODO This was deliberately modal before, why? Or just because "window modal" wasn't a thing?
732 dlg->setWindowModality(Qt::WindowModal);
733 dlg->setFileMode(QFileDialog::ExistingFile);
734 QObject::connect(sender: dlg, signal: &QFileDialog::fileSelected, context: q, slot: [this](const QString &path) {
735 if (!path.isEmpty()) {
736 custom = path;
737 if (isSystemIconsContext()) {
738 customLocation = QFileInfo(custom).absolutePath();
739 }
740 q->slotOk();
741 }
742 });
743 browseDialog = dlg;
744 dlg->show();
745}
746
747bool KIconDialogPrivate::isSystemIconsContext() const
748{
749 return ui.contextCombo->currentData().isValid();
750}
751
752#include "kicondialog.moc"
753#include "moc_kicondialog.cpp"
754#include "moc_kicondialogmodel_p.cpp"
755

source code of kiconthemes/src/widgets/kicondialog.cpp