File indexing completed on 2024-04-21 03:55:50

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kurlcombobox.h"
0009 
0010 #include "../utils_p.h"
0011 
0012 #include <QApplication>
0013 #include <QDir>
0014 #include <QDrag>
0015 #include <QMimeData>
0016 #include <QMouseEvent>
0017 
0018 #include <KIconLoader>
0019 #include <KLocalizedString>
0020 #include <QDebug>
0021 #include <kio/global.h>
0022 
0023 #include <algorithm>
0024 #include <vector>
0025 
0026 class KUrlComboBoxPrivate
0027 {
0028 public:
0029     KUrlComboBoxPrivate(KUrlComboBox *parent)
0030         : m_parent(parent)
0031         , dirIcon(QIcon::fromTheme(QStringLiteral("folder")))
0032     {
0033     }
0034 
0035     struct KUrlComboItem {
0036         KUrlComboItem(const QUrl &url, const QIcon &icon, const QString &text = QString())
0037             : url(url)
0038             , icon(icon)
0039             , text(text)
0040         {
0041         }
0042         QUrl url;
0043         QIcon icon;
0044         QString text; // if empty, calculated from the QUrl
0045     };
0046 
0047     void init(KUrlComboBox::Mode mode);
0048     QString textForItem(const KUrlComboItem *item) const;
0049     void insertUrlItem(const KUrlComboItem *);
0050     QIcon getIcon(const QUrl &url) const;
0051     void updateItem(const KUrlComboItem *item, int index, const QIcon &icon);
0052 
0053     void slotActivated(int);
0054 
0055     KUrlComboBox *const m_parent;
0056     QIcon dirIcon;
0057     bool urlAdded;
0058     int myMaximum;
0059     KUrlComboBox::Mode myMode;
0060     QPoint m_dragPoint;
0061 
0062     using KUrlComboItemList = std::vector<std::unique_ptr<const KUrlComboItem>>;
0063     KUrlComboItemList itemList;
0064     KUrlComboItemList defaultList;
0065     QMap<int, const KUrlComboItem *> itemMapper;
0066 
0067     QIcon opendirIcon;
0068 };
0069 
0070 QString KUrlComboBoxPrivate::textForItem(const KUrlComboItem *item) const
0071 {
0072     if (!item->text.isEmpty()) {
0073         return item->text;
0074     }
0075     QUrl url = item->url;
0076 
0077     if (myMode == KUrlComboBox::Directories) {
0078         Utils::appendSlashToPath(url);
0079     } else {
0080         url = url.adjusted(QUrl::StripTrailingSlash);
0081     }
0082     if (url.isLocalFile()) {
0083         return url.toLocalFile();
0084     } else {
0085         return url.toDisplayString();
0086     }
0087 }
0088 
0089 KUrlComboBox::KUrlComboBox(Mode mode, QWidget *parent)
0090     : KComboBox(parent)
0091     , d(new KUrlComboBoxPrivate(this))
0092 {
0093     d->init(mode);
0094 }
0095 
0096 KUrlComboBox::KUrlComboBox(Mode mode, bool rw, QWidget *parent)
0097     : KComboBox(rw, parent)
0098     , d(new KUrlComboBoxPrivate(this))
0099 {
0100     d->init(mode);
0101 }
0102 
0103 KUrlComboBox::~KUrlComboBox() = default;
0104 
0105 void KUrlComboBoxPrivate::init(KUrlComboBox::Mode mode)
0106 {
0107     myMode = mode;
0108     urlAdded = false;
0109     myMaximum = 10; // default
0110     m_parent->setInsertPolicy(KUrlComboBox::NoInsert);
0111     m_parent->setTrapReturnKey(true);
0112     m_parent->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0113     m_parent->setLayoutDirection(Qt::LeftToRight);
0114     if (m_parent->completionObject()) {
0115         m_parent->completionObject()->setOrder(KCompletion::Sorted);
0116     }
0117 
0118     opendirIcon = QIcon::fromTheme(QStringLiteral("folder-open"));
0119 
0120     m_parent->connect(m_parent, &KUrlComboBox::activated, m_parent, [this](int index) {
0121         slotActivated(index);
0122     });
0123 }
0124 
0125 QStringList KUrlComboBox::urls() const
0126 {
0127     // qDebug() << "::urls()";
0128     QStringList list;
0129     QString url;
0130     for (int i = static_cast<int>(d->defaultList.size()); i < count(); i++) {
0131         url = itemText(i);
0132         if (!url.isEmpty()) {
0133             if (Utils::isAbsoluteLocalPath(url)) {
0134                 list.append(QUrl::fromLocalFile(url).toString());
0135             } else {
0136                 list.append(url);
0137             }
0138         }
0139     }
0140 
0141     return list;
0142 }
0143 
0144 void KUrlComboBox::addDefaultUrl(const QUrl &url, const QString &text)
0145 {
0146     addDefaultUrl(url, d->getIcon(url), text);
0147 }
0148 
0149 void KUrlComboBox::addDefaultUrl(const QUrl &url, const QIcon &icon, const QString &text)
0150 {
0151     d->defaultList.push_back(std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem>(new KUrlComboBoxPrivate::KUrlComboItem(url, icon, text)));
0152 }
0153 
0154 void KUrlComboBox::setDefaults()
0155 {
0156     clear();
0157     d->itemMapper.clear();
0158 
0159     for (const auto &item : d->defaultList) {
0160         d->insertUrlItem(item.get());
0161     }
0162 }
0163 
0164 void KUrlComboBox::setUrls(const QStringList &urls)
0165 {
0166     setUrls(urls, RemoveBottom);
0167 }
0168 
0169 void KUrlComboBox::setUrls(const QStringList &_urls, OverLoadResolving remove)
0170 {
0171     setDefaults();
0172     d->itemList.clear();
0173     d->urlAdded = false;
0174 
0175     if (_urls.isEmpty()) {
0176         return;
0177     }
0178 
0179     QStringList urls;
0180     QStringList::ConstIterator it = _urls.constBegin();
0181 
0182     // kill duplicates
0183     while (it != _urls.constEnd()) {
0184         if (!urls.contains(*it)) {
0185             urls += *it;
0186         }
0187         ++it;
0188     }
0189 
0190     // limit to myMaximum items
0191     /* Note: overload is an (old) C++ keyword, some compilers (KCC) choke
0192        on that, so call it Overload (capital 'O').  (matz) */
0193     int Overload = urls.count() - d->myMaximum + static_cast<int>(d->defaultList.size());
0194     while (Overload > 0) {
0195         if (remove == RemoveBottom) {
0196             if (!urls.isEmpty()) {
0197                 urls.removeLast();
0198             }
0199         } else {
0200             if (!urls.isEmpty()) {
0201                 urls.removeFirst();
0202             }
0203         }
0204         Overload--;
0205     }
0206 
0207     it = urls.constBegin();
0208 
0209     while (it != urls.constEnd()) {
0210         if ((*it).isEmpty()) {
0211             ++it;
0212             continue;
0213         }
0214         QUrl u;
0215         if (Utils::isAbsoluteLocalPath(*it)) {
0216             u = QUrl::fromLocalFile(*it);
0217         } else {
0218             u.setUrl(*it);
0219         }
0220 
0221         // Don't restore if file doesn't exist anymore
0222         if (u.isLocalFile() && !QFile::exists(u.toLocalFile())) {
0223             ++it;
0224             continue;
0225         }
0226 
0227         std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem> item(new KUrlComboBoxPrivate::KUrlComboItem(u, d->getIcon(u)));
0228         d->insertUrlItem(item.get());
0229         d->itemList.push_back(std::move(item));
0230         ++it;
0231     }
0232 }
0233 
0234 void KUrlComboBox::setUrl(const QUrl &url)
0235 {
0236     if (url.isEmpty()) {
0237         return;
0238     }
0239 
0240     bool blocked = blockSignals(true);
0241 
0242     // check for duplicates
0243     auto mit = d->itemMapper.constBegin();
0244     QString urlToInsert = url.toString(QUrl::StripTrailingSlash);
0245     while (mit != d->itemMapper.constEnd()) {
0246         Q_ASSERT(mit.value());
0247 
0248         if (urlToInsert == mit.value()->url.toString(QUrl::StripTrailingSlash)) {
0249             setCurrentIndex(mit.key());
0250 
0251             if (d->myMode == Directories) {
0252                 d->updateItem(mit.value(), mit.key(), d->opendirIcon);
0253             }
0254 
0255             blockSignals(blocked);
0256             return;
0257         }
0258         ++mit;
0259     }
0260 
0261     // not in the combo yet -> create a new item and insert it
0262 
0263     // first remove the old item
0264     if (d->urlAdded) {
0265         Q_ASSERT(!d->itemList.empty());
0266         d->itemList.pop_back();
0267         d->urlAdded = false;
0268     }
0269 
0270     setDefaults();
0271 
0272     const int offset = qMax(0, static_cast<int>(d->itemList.size() + d->defaultList.size()) - d->myMaximum);
0273     for (size_t i = offset; i < d->itemList.size(); ++i) {
0274         d->insertUrlItem(d->itemList.at(i).get());
0275     }
0276 
0277     std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem> item(new KUrlComboBoxPrivate::KUrlComboItem(url, d->getIcon(url)));
0278 
0279     const int id = count();
0280     const QString text = d->textForItem(item.get());
0281     if (d->myMode == Directories) {
0282         KComboBox::insertItem(id, d->opendirIcon, text);
0283     } else {
0284         KComboBox::insertItem(id, item->icon, text);
0285     }
0286 
0287     d->itemMapper.insert(id, item.get());
0288     d->itemList.push_back(std::move(item));
0289 
0290     setCurrentIndex(id);
0291     Q_ASSERT(!d->itemList.empty());
0292     d->urlAdded = true;
0293     blockSignals(blocked);
0294 }
0295 
0296 void KUrlComboBoxPrivate::slotActivated(int index)
0297 {
0298     auto item = itemMapper.value(index);
0299 
0300     if (item) {
0301         m_parent->setUrl(item->url);
0302         Q_EMIT m_parent->urlActivated(item->url);
0303     }
0304 }
0305 
0306 void KUrlComboBoxPrivate::insertUrlItem(const KUrlComboItem *item)
0307 {
0308     Q_ASSERT(item);
0309 
0310     // qDebug() << "insertURLItem " << d->textForItem(item);
0311     int id = m_parent->count();
0312     m_parent->KComboBox::insertItem(id, item->icon, textForItem(item));
0313     itemMapper.insert(id, item);
0314 }
0315 
0316 void KUrlComboBox::setMaxItems(int max)
0317 {
0318     d->myMaximum = max;
0319 
0320     if (count() > d->myMaximum) {
0321         int oldCurrent = currentIndex();
0322 
0323         setDefaults();
0324 
0325         const int offset = qMax(0, static_cast<int>(d->itemList.size() + d->defaultList.size()) - d->myMaximum);
0326         for (size_t i = offset; i < d->itemList.size(); ++i) {
0327             d->insertUrlItem(d->itemList.at(i).get());
0328         }
0329 
0330         if (count() > 0) { // restore the previous currentItem
0331             if (oldCurrent >= count()) {
0332                 oldCurrent = count() - 1;
0333             }
0334             setCurrentIndex(oldCurrent);
0335         }
0336     }
0337 }
0338 
0339 int KUrlComboBox::maxItems() const
0340 {
0341     return d->myMaximum;
0342 }
0343 
0344 void KUrlComboBox::removeUrl(const QUrl &url, bool checkDefaultURLs)
0345 {
0346     auto mit = d->itemMapper.constBegin();
0347     while (mit != d->itemMapper.constEnd()) {
0348         if (url.toString(QUrl::StripTrailingSlash) == mit.value()->url.toString(QUrl::StripTrailingSlash)) {
0349             auto removePredicate = [&mit](const std::unique_ptr<const KUrlComboBoxPrivate::KUrlComboItem> &item) {
0350                 return item.get() == mit.value();
0351             };
0352             d->itemList.erase(std::remove_if(d->itemList.begin(), d->itemList.end(), removePredicate), d->itemList.end());
0353             if (checkDefaultURLs) {
0354                 d->defaultList.erase(std::remove_if(d->defaultList.begin(), d->defaultList.end(), removePredicate), d->defaultList.end());
0355             }
0356         }
0357         ++mit;
0358     }
0359 
0360     bool blocked = blockSignals(true);
0361     setDefaults();
0362     for (const auto &item : d->itemList) {
0363         d->insertUrlItem(item.get());
0364     }
0365     blockSignals(blocked);
0366 }
0367 
0368 void KUrlComboBox::setCompletionObject(KCompletion *compObj, bool hsig)
0369 {
0370     if (compObj) {
0371         // on a url combo box we want completion matches to be sorted. This way, if we are given
0372         // a suggestion, we match the "best" one. For instance, if we have "foo" and "foobar",
0373         // and we write "foo", the match is "foo" and never "foobar". (ereslibre)
0374         compObj->setOrder(KCompletion::Sorted);
0375     }
0376     KComboBox::setCompletionObject(compObj, hsig);
0377 }
0378 
0379 void KUrlComboBox::mousePressEvent(QMouseEvent *event)
0380 {
0381     QStyleOptionComboBox comboOpt;
0382     comboOpt.initFrom(this);
0383     const int x0 =
0384         QStyle::visualRect(layoutDirection(), rect(), style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField, this)).x();
0385     const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &comboOpt, this);
0386 
0387     if (qRound(event->position().x()) < (x0 + KIconLoader::SizeSmall + frameWidth)) {
0388         d->m_dragPoint = event->pos();
0389     } else {
0390         d->m_dragPoint = QPoint();
0391     }
0392 
0393     KComboBox::mousePressEvent(event);
0394 }
0395 
0396 void KUrlComboBox::mouseMoveEvent(QMouseEvent *event)
0397 {
0398     const int index = currentIndex();
0399     auto item = d->itemMapper.value(index);
0400 
0401     if (item && !d->m_dragPoint.isNull() && event->buttons() & Qt::LeftButton
0402         && (event->pos() - d->m_dragPoint).manhattanLength() > QApplication::startDragDistance()) {
0403         QDrag *drag = new QDrag(this);
0404         QMimeData *mime = new QMimeData();
0405         mime->setUrls(QList<QUrl>() << item->url);
0406         mime->setText(itemText(index));
0407         if (!itemIcon(index).isNull()) {
0408             drag->setPixmap(itemIcon(index).pixmap(KIconLoader::SizeMedium));
0409         }
0410         drag->setMimeData(mime);
0411         drag->exec();
0412     }
0413 
0414     KComboBox::mouseMoveEvent(event);
0415 }
0416 
0417 QIcon KUrlComboBoxPrivate::getIcon(const QUrl &url) const
0418 {
0419     if (myMode == KUrlComboBox::Directories) {
0420         return dirIcon;
0421     } else {
0422         return QIcon::fromTheme(KIO::iconNameForUrl(url));
0423     }
0424 }
0425 
0426 // updates "item" with icon "icon"
0427 // kdelibs4 used to also say "and sets the URL instead of text", but this breaks const-ness,
0428 // now that it would require clearing the text, and I don't see the point since the URL was already in the text.
0429 void KUrlComboBoxPrivate::updateItem(const KUrlComboItem *item, int index, const QIcon &icon)
0430 {
0431     m_parent->setItemIcon(index, icon);
0432     m_parent->setItemText(index, textForItem(item));
0433 }
0434 
0435 #include "moc_kurlcombobox.cpp"