diff --git a/autotests/kurlcomboboxtest.cpp b/autotests/kurlcomboboxtest.cpp index 270c379c..8fa9929a 100644 --- a/autotests/kurlcomboboxtest.cpp +++ b/autotests/kurlcomboboxtest.cpp @@ -1,47 +1,112 @@ /*************************************************************************** * Copyright (C) 2015 by Alejandro Fiestas Olivares QTEST_MAIN(KUrlComboBoxTest) void KUrlComboBoxTest::testTextForItem_data() { QTest::addColumn("url"); QTest::addColumn("expectedText"); QTest::newRow("with_host") << "ftp://foo.com/folder" << "ftp://foo.com/folder/"; QTest::newRow("with_no_host") << "smb://" << "smb://"; QTest::newRow("with_host_without_path") << "ftp://user@example.com" << "ftp://user@example.com"; } void KUrlComboBoxTest::testTextForItem() { QFETCH(QString, url); QFETCH(QString, expectedText); KUrlComboBox combo(KUrlComboBox::Directories); combo.setUrl(QUrl(url)); QCOMPARE(combo.itemText(0), expectedText); } + +void KUrlComboBoxTest::testSetUrlMultipleTimes() +{ + KUrlComboBox combo(KUrlComboBox::Directories); + combo.setUrl(QUrl("http://kde.org")); + combo.setUrl(QUrl("http://www.kde.org")); +} + +void KUrlComboBoxTest::testRemoveUrl() +{ + KUrlComboBox combo(KUrlComboBox::Both); + combo.addDefaultUrl(QUrl("http://kde.org")); + combo.addDefaultUrl(QUrl("http://www.kde.org")); + + QStringList urls{"http://foo.org", "http://bar.org"}; + combo.setUrls(urls); + + QCOMPARE(combo.urls(), urls); + QCOMPARE(combo.count(), 4); + QCOMPARE(combo.itemText(0), "http://kde.org"); + QCOMPARE(combo.itemText(1), "http://www.kde.org"); + QCOMPARE(combo.itemText(2), "http://foo.org"); + QCOMPARE(combo.itemText(3), "http://bar.org"); + + // Remove a url + combo.removeUrl(QUrl("http://foo.org")); + QCOMPARE(combo.count(), 3); + QCOMPARE(combo.urls(), QStringList{"http://bar.org"}); + QCOMPARE(combo.itemText(0), "http://kde.org"); + QCOMPARE(combo.itemText(1), "http://www.kde.org"); + QCOMPARE(combo.itemText(2), "http://bar.org"); + + // Removing a default url with checkDefaultURLs=true removes the url + combo.removeUrl(QUrl("http://kde.org")); + QCOMPARE(combo.count(), 2); + QCOMPARE(combo.urls(), QStringList{"http://bar.org"}); + QCOMPARE(combo.itemText(0), "http://www.kde.org"); + QCOMPARE(combo.itemText(1), "http://bar.org"); + + // Removing a default url with checkDefaultURLs=false does not remove the url + combo.removeUrl(QUrl("http://www.kde.org"), false); + QCOMPARE(combo.count(), 2); + QCOMPARE(combo.urls(), QStringList{"http://bar.org"}); + QCOMPARE(combo.itemText(0), "http://www.kde.org"); + QCOMPARE(combo.itemText(1), "http://bar.org"); + + // Removing a non-existing url is a no-op + combo.removeUrl(QUrl("http://www.foo.org")); + QCOMPARE(combo.count(), 2); + QCOMPARE(combo.urls(), QStringList{"http://bar.org"}); + QCOMPARE(combo.itemText(0), "http://www.kde.org"); + QCOMPARE(combo.itemText(1), "http://bar.org"); + + // Remove the last user provided url + combo.removeUrl(QUrl("http://bar.org")); + QCOMPARE(combo.count(), 1); + QCOMPARE(combo.urls(), QStringList{}); + QCOMPARE(combo.itemText(0), "http://www.kde.org"); + + // Remove the last url + combo.removeUrl(QUrl("http://www.kde.org")); + QCOMPARE(combo.count(), 0); + QCOMPARE(combo.urls(), QStringList{}); + QCOMPARE(combo.itemText(0), ""); +} diff --git a/autotests/kurlcomboboxtest.h b/autotests/kurlcomboboxtest.h index 6f0e9bea..8d91b117 100644 --- a/autotests/kurlcomboboxtest.h +++ b/autotests/kurlcomboboxtest.h @@ -1,35 +1,37 @@ /*************************************************************************** * Copyright (C) 2015 by Alejandro Fiestas Olivares class KUrlComboBoxTest : public QObject { Q_OBJECT private Q_SLOTS: void testTextForItem(); void testTextForItem_data(); + void testSetUrlMultipleTimes(); + void testRemoveUrl(); }; #endif //KURLCOMBOBOXTEST_H diff --git a/src/widgets/kurlcombobox.cpp b/src/widgets/kurlcombobox.cpp index 79d08073..0d21e8b8 100644 --- a/src/widgets/kurlcombobox.cpp +++ b/src/widgets/kurlcombobox.cpp @@ -1,451 +1,449 @@ /* This file is part of the KDE libraries Copyright (C) 2000,2001 Carsten Pfeiffer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2, as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kurlcombobox.h" #include #include #include #include #include #include #include #include #include +#include +#include +#include + class KUrlComboBoxPrivate { public: KUrlComboBoxPrivate(KUrlComboBox *parent) : m_parent(parent), dirIcon(QStringLiteral("folder")) {} - ~KUrlComboBoxPrivate() - { - qDeleteAll(itemList); - qDeleteAll(defaultList); - } - struct KUrlComboItem { KUrlComboItem(const QUrl &url, const QIcon &icon, const QString &text = QString()) : url(url), icon(icon), text(text) {} QUrl url; QIcon icon; QString text; // if empty, calculated from the QUrl }; void init(KUrlComboBox::Mode mode); QString textForItem(const KUrlComboItem *item) const; void insertUrlItem(const KUrlComboItem *); QIcon getIcon(const QUrl &url) const; void updateItem(const KUrlComboItem *item, int index, const QIcon &icon); void _k_slotActivated(int); KUrlComboBox *m_parent; QIcon dirIcon; bool urlAdded; int myMaximum; KUrlComboBox::Mode myMode; QPoint m_dragPoint; - QList itemList; - QList defaultList; + using KUrlComboItemList = std::vector>; + KUrlComboItemList itemList; + KUrlComboItemList defaultList; QMap itemMapper; QIcon opendirIcon; }; + QString KUrlComboBoxPrivate::textForItem(const KUrlComboItem *item) const { if (!item->text.isEmpty()) { return item->text; } QString text; QUrl url = item->url; if (myMode == KUrlComboBox::Directories) { if (!url.path().isEmpty() && !url.path().endsWith(QLatin1Char('/'))) { url.setPath(url.path() + QLatin1Char('/')); } } else { url = url.adjusted(QUrl::StripTrailingSlash); } if (url.isLocalFile()) { return url.toLocalFile(); } else { return url.toDisplayString(); } } KUrlComboBox::KUrlComboBox(Mode mode, QWidget *parent) : KComboBox(parent), d(new KUrlComboBoxPrivate(this)) { d->init(mode); } KUrlComboBox::KUrlComboBox(Mode mode, bool rw, QWidget *parent) : KComboBox(rw, parent), d(new KUrlComboBoxPrivate(this)) { d->init(mode); } KUrlComboBox::~KUrlComboBox() { delete d; } void KUrlComboBoxPrivate::init(KUrlComboBox::Mode mode) { myMode = mode; urlAdded = false; myMaximum = 10; // default m_parent->setInsertPolicy(KUrlComboBox::NoInsert); m_parent->setTrapReturnKey(true); m_parent->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_parent->setLayoutDirection(Qt::LeftToRight); if (m_parent->completionObject()) { m_parent->completionObject()->setOrder(KCompletion::Sorted); } opendirIcon = QIcon::fromTheme(QStringLiteral("folder-open")); m_parent->connect(m_parent, SIGNAL(activated(int)), SLOT(_k_slotActivated(int))); } QStringList KUrlComboBox::urls() const { // qDebug() << "::urls()"; QStringList list; QString url; - for (int i = d->defaultList.count(); i < count(); i++) { + for (int i = static_cast(d->defaultList.size()); i < count(); i++) { url = itemText(i); if (!url.isEmpty()) { if (QDir::isAbsolutePath(url)) list.append(QUrl::fromLocalFile(url).toString()); else list.append(url); } } return list; } void KUrlComboBox::addDefaultUrl(const QUrl &url, const QString &text) { addDefaultUrl(url, d->getIcon(url), text); } void KUrlComboBox::addDefaultUrl(const QUrl &url, const QIcon &icon, const QString &text) { - d->defaultList.append(new KUrlComboBoxPrivate::KUrlComboItem(url, icon, text)); + d->defaultList.push_back(std::unique_ptr(new KUrlComboBoxPrivate::KUrlComboItem(url, icon, text))); } void KUrlComboBox::setDefaults() { clear(); d->itemMapper.clear(); - const KUrlComboBoxPrivate::KUrlComboItem *item; - for (int id = 0; id < d->defaultList.count(); id++) { - item = d->defaultList.at(id); - d->insertUrlItem(item); + for (const auto& item : d->defaultList) { + d->insertUrlItem(item.get()); } } void KUrlComboBox::setUrls(const QStringList &urls) { setUrls(urls, RemoveBottom); } void KUrlComboBox::setUrls(const QStringList &_urls, OverLoadResolving remove) { setDefaults(); - qDeleteAll(d->itemList); d->itemList.clear(); d->urlAdded = false; if (_urls.isEmpty()) { return; } QStringList urls; QStringList::ConstIterator it = _urls.constBegin(); // kill duplicates while (it != _urls.constEnd()) { if (!urls.contains(*it)) { urls += *it; } ++it; } // limit to myMaximum items /* Note: overload is an (old) C++ keyword, some compilers (KCC) choke on that, so call it Overload (capital 'O'). (matz) */ - int Overload = urls.count() - d->myMaximum + d->defaultList.count(); + int Overload = urls.count() - d->myMaximum + static_cast(d->defaultList.size()); while (Overload > 0) { if (remove == RemoveBottom) { if (!urls.isEmpty()) { urls.removeLast(); } } else { if (!urls.isEmpty()) { urls.removeFirst(); } } Overload--; } it = urls.constBegin(); - KUrlComboBoxPrivate::KUrlComboItem *item = nullptr; - while (it != urls.constEnd()) { if ((*it).isEmpty()) { ++it; continue; } QUrl u; if (QDir::isAbsolutePath(*it)) { u = QUrl::fromLocalFile(*it); } else { u.setUrl(*it); } // Don't restore if file doesn't exist anymore if (u.isLocalFile() && !QFile::exists(u.toLocalFile())) { ++it; continue; } - item = new KUrlComboBoxPrivate::KUrlComboItem(u, d->getIcon(u)); - d->insertUrlItem(item); - d->itemList.append(item); + std::unique_ptr item(new KUrlComboBoxPrivate::KUrlComboItem(u, d->getIcon(u))); + d->insertUrlItem(item.get()); + d->itemList.push_back(std::move(item)); ++it; } } void KUrlComboBox::setUrl(const QUrl &url) { if (url.isEmpty()) { return; } bool blocked = blockSignals(true); // check for duplicates - QMap::ConstIterator mit = d->itemMapper.constBegin(); + auto mit = d->itemMapper.constBegin(); QString urlToInsert = url.toString(QUrl::StripTrailingSlash); while (mit != d->itemMapper.constEnd()) { Q_ASSERT(mit.value()); if (urlToInsert == mit.value()->url.toString(QUrl::StripTrailingSlash)) { setCurrentIndex(mit.key()); if (d->myMode == Directories) { d->updateItem(mit.value(), mit.key(), d->opendirIcon); } blockSignals(blocked); return; } ++mit; } // not in the combo yet -> create a new item and insert it // first remove the old item if (d->urlAdded) { - Q_ASSERT(!d->itemList.isEmpty()); - d->itemList.removeLast(); + Q_ASSERT(!d->itemList.empty()); + d->itemList.pop_back(); d->urlAdded = false; } setDefaults(); - int offset = qMax(0, d->itemList.count() - d->myMaximum + d->defaultList.count()); - for (int i = offset; i < d->itemList.count(); i++) { - d->insertUrlItem(d->itemList[i]); + KUrlComboBoxPrivate::KUrlComboItemList::size_type offset = qMax(KUrlComboBoxPrivate::KUrlComboItemList::size_type(0), d->itemList.size() - d->myMaximum + d->defaultList.size()); + for (auto i = offset; i < d->itemList.size(); i++) { + d->insertUrlItem(d->itemList.at(i).get()); } - KUrlComboBoxPrivate::KUrlComboItem *item = new KUrlComboBoxPrivate::KUrlComboItem(url, d->getIcon(url)); + std::unique_ptr item(new KUrlComboBoxPrivate::KUrlComboItem(url, d->getIcon(url))); const int id = count(); - const QString text = d->textForItem(item); + const QString text = d->textForItem(item.get()); if (d->myMode == Directories) { KComboBox::insertItem(id, d->opendirIcon, text); } else { KComboBox::insertItem(id, item->icon, text); } - d->itemMapper.insert(id, item); - d->itemList.append(item); + d->itemMapper.insert(id, item.get()); + d->itemList.push_back(std::move(item)); setCurrentIndex(id); - Q_ASSERT(!d->itemList.isEmpty()); + Q_ASSERT(!d->itemList.empty()); d->urlAdded = true; blockSignals(blocked); } void KUrlComboBoxPrivate::_k_slotActivated(int index) { - const KUrlComboItem *item = itemMapper.value(index); + auto item = itemMapper.value(index); if (item) { m_parent->setUrl(item->url); emit m_parent->urlActivated(item->url); } } -void KUrlComboBoxPrivate::insertUrlItem(const KUrlComboBoxPrivate::KUrlComboItem *item) +void KUrlComboBoxPrivate::insertUrlItem(const KUrlComboItem *item) { Q_ASSERT(item); // qDebug() << "insertURLItem " << d->textForItem(item); int id = m_parent->count(); m_parent->KComboBox::insertItem(id, item->icon, textForItem(item)); itemMapper.insert(id, item); } void KUrlComboBox::setMaxItems(int max) { d->myMaximum = max; if (count() > d->myMaximum) { int oldCurrent = currentIndex(); setDefaults(); - int offset = qMax(0, d->itemList.count() - d->myMaximum + d->defaultList.count()); - for (int i = offset; i < d->itemList.count(); i++) { - d->insertUrlItem(d->itemList[i]); + KUrlComboBoxPrivate::KUrlComboItemList::size_type offset = qMax(KUrlComboBoxPrivate::KUrlComboItemList::size_type(0), d->itemList.size() - d->myMaximum + d->defaultList.size()); + for (auto i = offset; i < d->itemList.size(); i++) { + d->insertUrlItem(d->itemList.at(i).get()); } if (count() > 0) { // restore the previous currentItem if (oldCurrent >= count()) { oldCurrent = count() - 1; } setCurrentIndex(oldCurrent); } } } int KUrlComboBox::maxItems() const { return d->myMaximum; } void KUrlComboBox::removeUrl(const QUrl &url, bool checkDefaultURLs) { - QMap::ConstIterator mit = d->itemMapper.constBegin(); + auto mit = d->itemMapper.constBegin(); while (mit != d->itemMapper.constEnd()) { if (url.toString(QUrl::StripTrailingSlash) == mit.value()->url.toString(QUrl::StripTrailingSlash)) { - if (!d->itemList.removeAll(mit.value()) && checkDefaultURLs) { - d->defaultList.removeAll(mit.value()); + auto removePredicate = [&mit](const std::unique_ptr& item) { + return item.get() == mit.value(); + }; + d->itemList.erase(std::remove_if(d->itemList.begin(), d->itemList.end(), removePredicate), d->itemList.end()); + if (checkDefaultURLs) { + d->defaultList.erase(std::remove_if(d->defaultList.begin(), d->defaultList.end(), removePredicate), d->defaultList.end()); } } ++mit; } bool blocked = blockSignals(true); setDefaults(); - QListIterator it(d->itemList); - while (it.hasNext()) { - d->insertUrlItem(it.next()); + for (const auto& item : d->itemList) { + d->insertUrlItem(item.get()); } blockSignals(blocked); } void KUrlComboBox::setCompletionObject(KCompletion *compObj, bool hsig) { if (compObj) { // on a url combo box we want completion matches to be sorted. This way, if we are given // a suggestion, we match the "best" one. For instance, if we have "foo" and "foobar", // and we write "foo", the match is "foo" and never "foobar". (ereslibre) compObj->setOrder(KCompletion::Sorted); } KComboBox::setCompletionObject(compObj, hsig); } void KUrlComboBox::mousePressEvent(QMouseEvent *event) { QStyleOptionComboBox comboOpt; comboOpt.initFrom(this); const int x0 = QStyle::visualRect(layoutDirection(), rect(), style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField, this)).x(); const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &comboOpt, this); if (event->x() < (x0 + KIconLoader::SizeSmall + frameWidth)) { d->m_dragPoint = event->pos(); } else { d->m_dragPoint = QPoint(); } KComboBox::mousePressEvent(event); } void KUrlComboBox::mouseMoveEvent(QMouseEvent *event) { const int index = currentIndex(); - const KUrlComboBoxPrivate::KUrlComboItem *item = d->itemMapper.value(index); + auto item = d->itemMapper.value(index); if (item && !d->m_dragPoint.isNull() && event->buttons() & Qt::LeftButton && (event->pos() - d->m_dragPoint).manhattanLength() > QApplication::startDragDistance()) { QDrag *drag = new QDrag(this); QMimeData *mime = new QMimeData(); mime->setUrls(QList() << item->url); mime->setText(itemText(index)); if (!itemIcon(index).isNull()) { drag->setPixmap(itemIcon(index).pixmap(KIconLoader::SizeMedium)); } drag->setMimeData(mime); drag->exec(); } KComboBox::mouseMoveEvent(event); } QIcon KUrlComboBoxPrivate::getIcon(const QUrl &url) const { if (myMode == KUrlComboBox::Directories) { return dirIcon; } else { return QIcon::fromTheme(KIO::iconNameForUrl(url)); } } // updates "item" with icon "icon" // kdelibs4 used to also say "and sets the URL instead of text", but this breaks const-ness, // now that it would require clearing the text, and I don't see the point since the URL was already in the text. -void KUrlComboBoxPrivate::updateItem(const KUrlComboBoxPrivate::KUrlComboItem *item, +void KUrlComboBoxPrivate::updateItem(const KUrlComboItem *item, int index, const QIcon &icon) { m_parent->setItemIcon(index, icon); #if 0 if (m_parent->isEditable()) { item->text.clear(); // so that it gets recalculated } #endif m_parent->setItemText(index, textForItem(item)); } #include "moc_kurlcombobox.cpp"