File indexing completed on 2024-04-21 14:59:17

0001 /* vi: ts=8 sts=4 sw=4
0002 
0003     This file is part of the KDE project, module kfile.
0004     SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
0005     SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
0006     SPDX-FileCopyrightText: 1997 Christoph Neerfeld <chris@kde.org>
0007     SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
0008     SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-only
0011 */
0012 
0013 #include "kicondialog.h"
0014 #include "kicondialog_p.h"
0015 #include "kicondialogmodel_p.h"
0016 
0017 #include <KLazyLocalizedString>
0018 #include <KLocalizedString>
0019 #include <KStandardAction>
0020 
0021 #include <QAbstractListModel>
0022 #include <QApplication>
0023 #include <QComboBox>
0024 #include <QDialogButtonBox>
0025 #include <QFileInfo>
0026 #include <QGraphicsOpacityEffect>
0027 #include <QLabel>
0028 #include <QPainter>
0029 #include <QScrollBar>
0030 #include <QStandardItemModel> // for manipulatig QComboBox
0031 #include <QStandardPaths>
0032 #include <QSvgRenderer>
0033 #include <QVector>
0034 
0035 #include <algorithm>
0036 #include <math.h>
0037 
0038 static const int s_edgePad = 3;
0039 
0040 KIconDialogModel::KIconDialogModel(QObject *parent)
0041     : QAbstractListModel(parent)
0042 {
0043 }
0044 
0045 KIconDialogModel::~KIconDialogModel() = default;
0046 
0047 qreal KIconDialogModel::devicePixelRatio() const
0048 {
0049     return m_dpr;
0050 }
0051 
0052 void KIconDialogModel::setDevicePixelRatio(qreal dpr)
0053 {
0054     m_dpr = dpr;
0055 }
0056 
0057 QSize KIconDialogModel::iconSize() const
0058 {
0059     return m_iconSize;
0060 }
0061 
0062 void KIconDialogModel::setIconSize(const QSize &iconSize)
0063 {
0064     m_iconSize = iconSize;
0065 }
0066 
0067 void KIconDialogModel::load(const QStringList &paths)
0068 {
0069     beginResetModel();
0070 
0071     m_data.clear();
0072     m_data.reserve(paths.count());
0073 
0074     for (const QString &path : paths) {
0075         const QFileInfo fi(path);
0076 
0077         KIconDialogModelData item;
0078         item.name = fi.completeBaseName();
0079         item.path = path;
0080         // pixmap is created on demand
0081 
0082         m_data.append(item);
0083     }
0084 
0085     endResetModel();
0086 }
0087 
0088 int KIconDialogModel::rowCount(const QModelIndex &parent) const
0089 {
0090     if (parent.isValid()) {
0091         return 0;
0092     }
0093     return m_data.count();
0094 }
0095 
0096 QVariant KIconDialogModel::data(const QModelIndex &index, int role) const
0097 {
0098     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0099         return QVariant();
0100     }
0101 
0102     const auto &item = m_data.at(index.row());
0103 
0104     switch (role) {
0105     case Qt::DisplayRole:
0106         return item.name;
0107     case Qt::DecorationRole:
0108         if (item.pixmap.isNull()) {
0109             const_cast<KIconDialogModel *>(this)->loadPixmap(index);
0110         }
0111         return item.pixmap;
0112     case Qt::ToolTipRole:
0113         return item.name;
0114     case PathRole:
0115         return item.path;
0116     }
0117 
0118     return QVariant();
0119 }
0120 
0121 void KIconDialogModel::loadPixmap(const QModelIndex &index)
0122 {
0123     Q_ASSERT(index.isValid());
0124 
0125     auto &item = m_data[index.row()];
0126     Q_ASSERT(item.pixmap.isNull());
0127 
0128     const auto dpr = devicePixelRatio();
0129     const auto canvasIconWidth = iconSize().width();
0130     const auto canvasIconHeight = iconSize().height();
0131 
0132     QImage img;
0133 
0134     if (item.path.endsWith(QLatin1String(".svg"), Qt::CaseInsensitive) || item.path.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive)) {
0135         QSvgRenderer renderer(item.path);
0136         if (renderer.isValid()) {
0137             img = QImage(canvasIconWidth * dpr, canvasIconHeight * dpr, QImage::Format_ARGB32_Premultiplied);
0138             img.setDevicePixelRatio(dpr);
0139             img.fill(Qt::transparent);
0140 
0141             QPainter p(&img);
0142             // FIXME why do I need to specify bounds for it to not crop the SVG on dpr > 1?
0143             renderer.render(&p, QRect(0, 0, canvasIconWidth, canvasIconHeight));
0144         }
0145     } else {
0146         img.load(item.path);
0147 
0148         if (!img.isNull()) {
0149             if (img.width() > canvasIconWidth || img.height() > canvasIconHeight) {
0150                 if (img.width() / (float)canvasIconWidth > img.height() / (float)canvasIconHeight) {
0151                     int height = (int)(((float)canvasIconWidth / img.width()) * img.height());
0152                     img = img.scaled(canvasIconWidth, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0153                 } else {
0154                     int width = (int)(((float)canvasIconHeight / img.height()) * img.width());
0155                     img = img.scaled(width, canvasIconHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0156                 }
0157             }
0158 
0159             if (/*uniformIconSize &&*/ (img.width() != canvasIconWidth || img.height() != canvasIconHeight)) {
0160                 // Image is smaller than desired.  Draw onto a transparent QImage of the required dimensions.
0161                 // (Unpleasant glitches occur if we break the uniformIconSizes() contract).
0162                 QImage paddedImage =
0163                     QImage(canvasIconWidth * img.devicePixelRatioF(), canvasIconHeight * img.devicePixelRatioF(), QImage::Format_ARGB32_Premultiplied);
0164                 paddedImage.setDevicePixelRatio(img.devicePixelRatioF());
0165                 paddedImage.fill(0);
0166                 QPainter painter(&paddedImage);
0167                 painter.drawImage((canvasIconWidth - img.width()) / 2, (canvasIconHeight - img.height()) / 2, img);
0168                 img = paddedImage;
0169             }
0170         }
0171     }
0172 
0173     if (!img.isNull()) {
0174         item.pixmap = QPixmap::fromImage(img);
0175     }
0176 }
0177 
0178 /**
0179  * Qt allocates very little horizontal space for the icon name,
0180  * even if the gridSize width is large.  This delegate allocates
0181  * the gridSize width (minus some padding) for the icon and icon name.
0182  */
0183 class KIconCanvasDelegate : public QAbstractItemDelegate
0184 {
0185     Q_OBJECT
0186 public:
0187     KIconCanvasDelegate(QListView *parent, QAbstractItemDelegate *defaultDelegate);
0188     ~KIconCanvasDelegate() override
0189     {
0190     }
0191     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
0192     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
0193 
0194 private:
0195     QAbstractItemDelegate *m_defaultDelegate = nullptr;
0196 };
0197 
0198 KIconCanvasDelegate::KIconCanvasDelegate(QListView *parent, QAbstractItemDelegate *defaultDelegate)
0199     : QAbstractItemDelegate(parent)
0200 {
0201     m_defaultDelegate = defaultDelegate;
0202 }
0203 
0204 void KIconCanvasDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0205 {
0206     auto *canvas = static_cast<QListView *>(parent());
0207     const int gridWidth = canvas->gridSize().width();
0208     QStyleOptionViewItem newOption = option;
0209     newOption.displayAlignment = Qt::AlignHCenter | Qt::AlignTop;
0210     newOption.features.setFlag(QStyleOptionViewItem::WrapText);
0211     // Manipulate the width available.
0212     newOption.rect.setX((option.rect.x() / gridWidth) * gridWidth + s_edgePad);
0213     newOption.rect.setY(option.rect.y() + s_edgePad);
0214     newOption.rect.setWidth(gridWidth - 2 * s_edgePad);
0215     newOption.rect.setHeight(option.rect.height() - 2 * s_edgePad);
0216     m_defaultDelegate->paint(painter, newOption, index);
0217 }
0218 
0219 QSize KIconCanvasDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0220 {
0221     auto *canvas = static_cast<QListView *>(parent());
0222 
0223     // TODO can we set wrap text and display alignment somewhere globally?
0224     QStyleOptionViewItem newOption = option;
0225     newOption.displayAlignment = Qt::AlignHCenter | Qt::AlignTop;
0226     newOption.features.setFlag(QStyleOptionViewItem::WrapText);
0227 
0228     QSize size = m_defaultDelegate->sizeHint(newOption, index);
0229     const int gridWidth = canvas->gridSize().width();
0230     const int gridHeight = canvas->gridSize().height();
0231     size.setWidth(gridWidth - 2 * s_edgePad);
0232     size.setHeight(gridHeight - 2 * s_edgePad);
0233     QFontMetrics metrics(option.font);
0234     size.setHeight(gridHeight + metrics.height() * 3);
0235     return size;
0236 }
0237 
0238 // TODO KF6 remove and override KIconDialog::showEvent()
0239 class ShowEventFilter : public QObject
0240 {
0241 public:
0242     explicit ShowEventFilter(QObject *parent)
0243         : QObject(parent)
0244     {
0245     }
0246     ~ShowEventFilter() override
0247     {
0248     }
0249 
0250 private:
0251     bool eventFilter(QObject *watched, QEvent *event) override
0252     {
0253         if (event->type() == QEvent::Show) {
0254             KIconDialog *q = static_cast<KIconDialog *>(parent());
0255             q->d->showIcons();
0256             q->d->ui.searchLine->setFocus();
0257         }
0258 
0259         return QObject::eventFilter(watched, event);
0260     }
0261 };
0262 
0263 /*
0264  * KIconDialog: Dialog for selecting icons. Both system and user
0265  * specified icons can be chosen.
0266  */
0267 
0268 KIconDialog::KIconDialog(QWidget *parent)
0269     : QDialog(parent)
0270     , d(new KIconDialogPrivate(this))
0271 {
0272     setModal(true);
0273 
0274     d->mpLoader = KIconLoader::global();
0275     d->init();
0276 
0277     installEventFilter(new ShowEventFilter(this));
0278 }
0279 
0280 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 104)
0281 KIconDialog::KIconDialog(KIconLoader *loader, QWidget *parent)
0282     : QDialog(parent)
0283     , d(new KIconDialogPrivate(this))
0284 {
0285     setModal(true);
0286 
0287     d->mpLoader = loader;
0288     d->init();
0289 
0290     installEventFilter(new ShowEventFilter(this));
0291 }
0292 #endif
0293 
0294 void KIconDialogPrivate::init()
0295 {
0296     mGroupOrSize = KIconLoader::Desktop;
0297     mContext = KIconLoader::Any;
0298 
0299     ui.setupUi(q);
0300 
0301     auto updatePlaceholder = [this] {
0302         updatePlaceholderLabel();
0303     };
0304     QObject::connect(proxyModel, &QSortFilterProxyModel::modelReset, q, updatePlaceholder);
0305     QObject::connect(proxyModel, &QSortFilterProxyModel::rowsInserted, q, updatePlaceholder);
0306     QObject::connect(proxyModel, &QSortFilterProxyModel::rowsRemoved, q, updatePlaceholder);
0307 
0308     QAction *findAction = KStandardAction::find(ui.searchLine, qOverload<>(&QWidget::setFocus), q);
0309     q->addAction(findAction);
0310 
0311     QObject::connect(ui.searchLine, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterFixedString);
0312 
0313     static const KLazyLocalizedString context_text[] = {
0314         kli18n("All"),
0315         kli18n("Actions"),
0316         kli18n("Applications"),
0317         kli18n("Categories"),
0318         kli18n("Devices"),
0319         kli18n("Emblems"),
0320         kli18n("Emotes"),
0321         kli18n("Mimetypes"),
0322         kli18n("Places"),
0323         kli18n("Status"),
0324     };
0325     static const KIconLoader::Context context_id[] = {
0326         KIconLoader::Any,
0327         KIconLoader::Action,
0328         KIconLoader::Application,
0329         KIconLoader::Category,
0330         KIconLoader::Device,
0331         KIconLoader::Emblem,
0332         KIconLoader::Emote,
0333         KIconLoader::MimeType,
0334         KIconLoader::Place,
0335         KIconLoader::StatusIcon,
0336     };
0337     const int cnt = sizeof(context_text) / sizeof(context_text[0]);
0338     for (int i = 0; i < cnt; ++i) {
0339         if (mpLoader->hasContext(context_id[i])) {
0340             ui.contextCombo->addItem(context_text[i].toString(), context_id[i]);
0341             if (i == 0) {
0342                 ui.contextCombo->insertSeparator(i + 1);
0343             }
0344         }
0345     }
0346     ui.contextCombo->insertSeparator(ui.contextCombo->count());
0347     ui.contextCombo->addItem(i18nc("Other icons", "Other"));
0348     ui.contextCombo->setMaxVisibleItems(ui.contextCombo->count());
0349     ui.contextCombo->setFixedSize(ui.contextCombo->sizeHint());
0350 
0351     QObject::connect(ui.contextCombo, qOverload<int>(&QComboBox::activated), q, [this]() {
0352         const auto currentData = ui.contextCombo->currentData();
0353         if (currentData.isValid()) {
0354             mContext = static_cast<KIconLoader::Context>(ui.contextCombo->currentData().toInt());
0355         } else {
0356             mContext = static_cast<KIconLoader::Context>(-1);
0357         }
0358         showIcons();
0359     });
0360 
0361     auto *delegate = new KIconCanvasDelegate(ui.canvas, ui.canvas->itemDelegate());
0362     ui.canvas->setItemDelegate(delegate);
0363 
0364     ui.canvas->setModel(proxyModel);
0365 
0366     QObject::connect(ui.canvas, &QAbstractItemView::activated, q, [this]() {
0367         custom.clear();
0368         q->slotOk();
0369     });
0370 
0371     // You can't just stack widgets on top of each other in Qt Designer
0372     auto *placeholderLayout = new QVBoxLayout(ui.canvas);
0373 
0374     placeholderLabel = new QLabel();
0375     QFont placeholderLabelFont;
0376     // To match the size of a level 2 Heading/KTitleWidget
0377     placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3));
0378     placeholderLabel->setFont(placeholderLabelFont);
0379     placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
0380     placeholderLabel->setWordWrap(true);
0381     placeholderLabel->setAlignment(Qt::AlignCenter);
0382 
0383     // Match opacity of QML placeholder label component
0384     auto *effect = new QGraphicsOpacityEffect(placeholderLabel);
0385     effect->setOpacity(0.5);
0386     placeholderLabel->setGraphicsEffect(effect);
0387 
0388     updatePlaceholderLabel();
0389 
0390     placeholderLayout->addWidget(placeholderLabel);
0391     placeholderLayout->setAlignment(placeholderLabel, Qt::AlignCenter);
0392 
0393     // TODO I bet there is a KStandardAction for that?
0394     browseButton = new QPushButton(QIcon::fromTheme(QStringLiteral("folder-open")), i18n("Browse…"));
0395     // TODO does this have implicatons? I just want the "Browse" button on the left side :)
0396     ui.buttonBox->addButton(browseButton, QDialogButtonBox::HelpRole);
0397     QObject::connect(browseButton, &QPushButton::clicked, q, [this] {
0398         browse();
0399     });
0400 
0401     QObject::connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &KIconDialog::slotOk);
0402     QObject::connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
0403 
0404     q->adjustSize();
0405 }
0406 
0407 KIconDialog::~KIconDialog() = default;
0408 
0409 static bool sortByFileName(const QString &path1, const QString &path2)
0410 {
0411     const QString fileName1 = path1.mid(path1.lastIndexOf(QLatin1Char('/')) + 1);
0412     const QString fileName2 = path2.mid(path2.lastIndexOf(QLatin1Char('/')) + 1);
0413     return QString::compare(fileName1, fileName2, Qt::CaseInsensitive) < 0;
0414 }
0415 
0416 void KIconDialogPrivate::showIcons()
0417 {
0418     QStringList filelist;
0419     if (isSystemIconsContext()) {
0420         if (m_bStrictIconSize) {
0421             filelist = mpLoader->queryIcons(mGroupOrSize, mContext);
0422         } else {
0423             filelist = mpLoader->queryIconsByContext(mGroupOrSize, mContext);
0424         }
0425     } else if (!customLocation.isEmpty()) {
0426         filelist = mpLoader->queryIconsByDir(customLocation);
0427     } else {
0428         // List PNG files found directly in the kiconload search paths.
0429         const QStringList pngNameFilter(QStringLiteral("*.png"));
0430         for (const QString &relDir : KIconLoader::global()->searchPaths()) {
0431             const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, relDir, QStandardPaths::LocateDirectory);
0432             for (const QString &dir : dirs) {
0433                 const auto files = QDir(dir).entryList(pngNameFilter);
0434                 for (const QString &fileName : files) {
0435                     filelist << dir + QLatin1Char('/') + fileName;
0436                 }
0437             }
0438         }
0439     }
0440 
0441     std::sort(filelist.begin(), filelist.end(), sortByFileName);
0442 
0443     // The KIconCanvas has uniformItemSizes set which really expects
0444     // all added icons to be the same size, otherwise weirdness ensues :)
0445     // Ensure all SVGs are scaled to the desired size and that as few icons
0446     // need to be padded as possible by specifying a sensible size.
0447     if (mGroupOrSize < -1) {
0448         // mGroupOrSize can be -1 if NoGroup is chosen.
0449         // Explicit size.
0450         ui.canvas->setIconSize(QSize(-mGroupOrSize, -mGroupOrSize));
0451     } else {
0452         // Icon group.
0453         int groupSize = mpLoader->currentSize(static_cast<KIconLoader::Group>(mGroupOrSize));
0454         ui.canvas->setIconSize(QSize(groupSize, groupSize));
0455     }
0456 
0457     // Try to make room for three lines of text...
0458     QFontMetrics metrics(ui.canvas->font());
0459     const int frameHMargin = ui.canvas->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, ui.canvas) + 1;
0460     const int lineCount = 3;
0461     ui.canvas->setGridSize(QSize(100, ui.canvas->iconSize().height() + lineCount * metrics.height() + 3 * frameHMargin));
0462 
0463     // Set a minimum size of 6x3 icons
0464     const int columnCount = 6;
0465     const int rowCount = 3;
0466     QStyleOption opt;
0467     opt.initFrom(ui.canvas);
0468     int width = columnCount * ui.canvas->gridSize().width();
0469     width += ui.canvas->verticalScrollBar()->sizeHint().width() + 1;
0470     width += 2 * ui.canvas->frameWidth();
0471     if (ui.canvas->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, ui.canvas)) {
0472         width += ui.canvas->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &opt, ui.canvas);
0473     }
0474     int height = rowCount * ui.canvas->gridSize().height() + 1;
0475     height += 2 * ui.canvas->frameWidth();
0476 
0477     ui.canvas->setMinimumSize(QSize(width, height));
0478 
0479     model->setIconSize(ui.canvas->iconSize());
0480     model->setDevicePixelRatio(q->devicePixelRatioF());
0481     model->load(filelist);
0482 
0483     if (!pendingSelectedIcon.isEmpty()) {
0484         selectIcon(pendingSelectedIcon);
0485         pendingSelectedIcon.clear();
0486     }
0487 }
0488 
0489 bool KIconDialogPrivate::selectIcon(const QString &iconName)
0490 {
0491     for (int i = 0; i < proxyModel->rowCount(); ++i) {
0492         const QModelIndex idx = proxyModel->index(i, 0);
0493 
0494         QString name = idx.data(KIconDialogModel::PathRole).toString();
0495         if (!name.isEmpty() && isSystemIconsContext()) {
0496             const QFileInfo fi(name);
0497             name = fi.completeBaseName();
0498         }
0499 
0500         if (iconName == name) {
0501             ui.canvas->setCurrentIndex(idx);
0502             return true;
0503         }
0504     }
0505 
0506     return false;
0507 }
0508 
0509 void KIconDialog::setStrictIconSize(bool b)
0510 {
0511     d->m_bStrictIconSize = b;
0512 }
0513 
0514 bool KIconDialog::strictIconSize() const
0515 {
0516     return d->m_bStrictIconSize;
0517 }
0518 
0519 void KIconDialog::setIconSize(int size)
0520 {
0521     // see KIconLoader, if you think this is weird
0522     if (size == 0) {
0523         d->mGroupOrSize = KIconLoader::Desktop; // default Group
0524     } else {
0525         d->mGroupOrSize = -size; // yes, KIconLoader::queryIconsByContext is weird
0526     }
0527 }
0528 
0529 int KIconDialog::iconSize() const
0530 {
0531     // 0 or any other value ==> mGroupOrSize is a group, so we return 0
0532     return (d->mGroupOrSize < 0) ? -d->mGroupOrSize : 0;
0533 }
0534 
0535 void KIconDialog::setSelectedIcon(const QString &iconName)
0536 {
0537     // TODO Update live when dialog is already open
0538     d->pendingSelectedIcon = iconName;
0539 }
0540 
0541 void KIconDialog::setup(KIconLoader::Group group, KIconLoader::Context context, bool strictIconSize, int iconSize, bool user, bool lockUser, bool lockCustomDir)
0542 {
0543     d->m_bStrictIconSize = strictIconSize;
0544     d->m_bLockUser = lockUser;
0545     d->m_bLockCustomDir = lockCustomDir;
0546     if (iconSize == 0) {
0547         if (group == KIconLoader::NoGroup) {
0548             // NoGroup has numeric value -1, which should
0549             // not really be used with KIconLoader::queryIcons*(...);
0550             // pick a proper group.
0551             d->mGroupOrSize = KIconLoader::Small;
0552         } else {
0553             d->mGroupOrSize = group;
0554         }
0555     } else {
0556         d->mGroupOrSize = -iconSize;
0557     }
0558 
0559     if (user) {
0560         d->ui.contextCombo->setCurrentIndex(d->ui.contextCombo->count() - 1);
0561     } else {
0562         d->setContext(context);
0563     }
0564 
0565     d->ui.contextCombo->setEnabled(!user || !lockUser);
0566 
0567     // Disable "Other" entry when user is locked
0568     auto *model = qobject_cast<QStandardItemModel *>(d->ui.contextCombo->model());
0569     auto *otherItem = model->item(model->rowCount() - 1);
0570     auto flags = otherItem->flags();
0571     flags.setFlag(Qt::ItemIsEnabled, !lockUser);
0572     otherItem->setFlags(flags);
0573 
0574     // Only allow browsing when explicitly allowed and user icons are allowed
0575     // An app may not expect a path when asking only about system icons
0576     d->browseButton->setVisible(!lockCustomDir && (!user || !lockUser));
0577 }
0578 
0579 void KIconDialogPrivate::setContext(KIconLoader::Context context)
0580 {
0581     mContext = context;
0582     const int index = ui.contextCombo->findData(context);
0583     if (index > -1) {
0584         ui.contextCombo->setCurrentIndex(index);
0585     }
0586 }
0587 
0588 void KIconDialogPrivate::updatePlaceholderLabel()
0589 {
0590     if (proxyModel->rowCount() > 0) {
0591         placeholderLabel->hide();
0592         return;
0593     }
0594 
0595     if (!ui.searchLine->text().isEmpty()) {
0596         placeholderLabel->setText(i18n("No icons matching the search"));
0597     } else {
0598         placeholderLabel->setText(i18n("No icons in this category"));
0599     }
0600 
0601     placeholderLabel->show();
0602 }
0603 
0604 void KIconDialog::setCustomLocation(const QString &location)
0605 {
0606     d->customLocation = location;
0607 }
0608 
0609 QString KIconDialog::openDialog()
0610 {
0611     if (exec() == Accepted) {
0612         if (!d->custom.isEmpty()) {
0613             return d->custom;
0614         }
0615 
0616         const QString name = d->ui.canvas->currentIndex().data(KIconDialogModel::PathRole).toString();
0617         if (name.isEmpty() || !d->ui.contextCombo->currentData().isValid()) {
0618             return name;
0619         }
0620 
0621         QFileInfo fi(name);
0622         return fi.completeBaseName();
0623     }
0624 
0625     return QString();
0626 }
0627 
0628 void KIconDialog::showDialog()
0629 {
0630     setModal(false);
0631     show();
0632 }
0633 
0634 void KIconDialog::slotOk()
0635 {
0636     QString name;
0637     if (!d->custom.isEmpty()) {
0638         name = d->custom;
0639     } else {
0640         name = d->ui.canvas->currentIndex().data(KIconDialogModel::PathRole).toString();
0641         if (!name.isEmpty() && d->isSystemIconsContext()) {
0642             const QFileInfo fi(name);
0643             name = fi.completeBaseName();
0644         }
0645     }
0646 
0647     Q_EMIT newIconName(name);
0648     QDialog::accept();
0649 }
0650 
0651 QString KIconDialog::getIcon(KIconLoader::Group group,
0652                              KIconLoader::Context context,
0653                              bool strictIconSize,
0654                              int iconSize,
0655                              bool user,
0656                              QWidget *parent,
0657                              const QString &title)
0658 {
0659     KIconDialog dlg(parent);
0660     dlg.setup(group, context, strictIconSize, iconSize, user);
0661     if (!title.isEmpty()) {
0662         dlg.setWindowTitle(title);
0663     }
0664 
0665     return dlg.openDialog();
0666 }
0667 
0668 void KIconDialogPrivate::browse()
0669 {
0670     if (browseDialog) {
0671         browseDialog.data()->show();
0672         browseDialog.data()->raise();
0673         return;
0674     }
0675 
0676     // Create a file dialog to select an ICO, PNG, XPM or SVG file,
0677     // with the image previewer shown.
0678     QFileDialog *dlg = new QFileDialog(q, i18n("Select Icon"), QString(), i18n("*.ico *.png *.xpm *.svg *.svgz|Icon Files (*.ico *.png *.xpm *.svg *.svgz)"));
0679     // TODO This was deliberately modal before, why? Or just because "window modal" wasn't a thing?
0680     dlg->setWindowModality(Qt::WindowModal);
0681     dlg->setFileMode(QFileDialog::ExistingFile);
0682     QObject::connect(dlg, &QFileDialog::fileSelected, q, [this](const QString &path) {
0683         if (!path.isEmpty()) {
0684             custom = path;
0685             if (isSystemIconsContext()) {
0686                 customLocation = QFileInfo(custom).absolutePath();
0687             }
0688             q->slotOk();
0689         }
0690     });
0691     browseDialog = dlg;
0692     dlg->show();
0693 }
0694 
0695 bool KIconDialogPrivate::isSystemIconsContext() const
0696 {
0697     return ui.contextCombo->currentData().isValid();
0698 }
0699 
0700 #include "kicondialog.moc"
0701 #include "moc_kicondialog.cpp"
0702 #include "moc_kicondialogmodel_p.cpp"