File indexing completed on 2025-04-20 06:43:57
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"