diff --git a/src/kcombobox.cpp b/src/kcombobox.cpp index 9c28fad..b8c282d 100644 --- a/src/kcombobox.cpp +++ b/src/kcombobox.cpp @@ -1,410 +1,414 @@ /* This file is part of the KDE libraries Copyright (c) 2000,2001 Dawit Alemayehu Copyright (c) 2000,2001 Carsten Pfeiffer Copyright (c) 2000 Stefan Schimanski <1Stein@gmx.de> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser 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 "kcombobox.h" #include #include #include #include #include class KComboBoxPrivate { public: KComboBoxPrivate(KComboBox *parent) : q_ptr(parent) { } ~KComboBoxPrivate() { } /** * Initializes the variables upon construction. */ void init(); void _k_lineEditDeleted(); KLineEdit *klineEdit = nullptr; bool trapReturnKey = false; KComboBox * const q_ptr; Q_DECLARE_PUBLIC(KComboBox) }; void KComboBoxPrivate::init() { Q_Q(KComboBox); } void KComboBoxPrivate::_k_lineEditDeleted() { Q_Q(KComboBox); // yes, we need those ugly casts due to the multiple inheritance // sender() is guaranteed to be a KLineEdit (see the connect() to the // destroyed() signal const KCompletionBase *base = static_cast(static_cast(q->sender())); // is it our delegate, that is destroyed? if (base == q->delegate()) { q->setDelegate(nullptr); } } KComboBox::KComboBox(QWidget *parent) : QComboBox(parent), d_ptr(new KComboBoxPrivate(this)) { Q_D(KComboBox); d->init(); } KComboBox::KComboBox(bool rw, QWidget *parent) : QComboBox(parent), d_ptr(new KComboBoxPrivate(this)) { Q_D(KComboBox); d->init(); setEditable(rw); } KComboBox::~KComboBox() { } bool KComboBox::contains(const QString &text) const { if (text.isEmpty()) { return false; } const int itemCount = count(); for (int i = 0; i < itemCount; ++i) { if (itemText(i) == text) { return true; } } return false; } int KComboBox::cursorPosition() const { return (isEditable()) ? lineEdit()->cursorPosition() : -1; } void KComboBox::setAutoCompletion(bool autocomplete) { Q_D(KComboBox); if (d->klineEdit) { if (autocomplete) { d->klineEdit->setCompletionMode(KCompletion::CompletionAuto); setCompletionMode(KCompletion::CompletionAuto); } else { d->klineEdit->setCompletionMode(KCompletion::CompletionPopup); setCompletionMode(KCompletion::CompletionPopup); } } } bool KComboBox::autoCompletion() const { return completionMode() == KCompletion::CompletionAuto; } #if KCOMPLETION_BUILD_DEPRECATED_SINCE(4, 5) void KComboBox::setContextMenuEnabled(bool showMenu) { Q_D(KComboBox); if (d->klineEdit) { d->klineEdit->setContextMenuPolicy(showMenu ? Qt::DefaultContextMenu : Qt::NoContextMenu); } } #endif #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 0) void KComboBox::setUrlDropsEnabled(bool enable) { Q_D(KComboBox); if (d->klineEdit) { d->klineEdit->setUrlDropsEnabled(enable); } } #endif bool KComboBox::urlDropsEnabled() const { Q_D(const KComboBox); return d->klineEdit && d->klineEdit->urlDropsEnabled(); } void KComboBox::setCompletedText(const QString &text, bool marked) { Q_D(KComboBox); if (d->klineEdit) { d->klineEdit->setCompletedText(text, marked); } } void KComboBox::setCompletedText(const QString &text) { Q_D(KComboBox); if (d->klineEdit) { d->klineEdit->setCompletedText(text); } } void KComboBox::makeCompletion(const QString &text) { Q_D(KComboBox); if (d->klineEdit) { d->klineEdit->makeCompletion(text); } else { // read-only combo completion if (text.isNull() || !view()) { return; } view()->keyboardSearch(text); } } void KComboBox::rotateText(KCompletionBase::KeyBindingType type) { Q_D(KComboBox); if (d->klineEdit) { d->klineEdit->rotateText(type); } } void KComboBox::setTrapReturnKey(bool trap) { Q_D(KComboBox); d->trapReturnKey = trap; if (d->klineEdit) { d->klineEdit->setTrapReturnKey(trap); } else { qWarning("KComboBox::setTrapReturnKey not supported with a non-KLineEdit."); } } bool KComboBox::trapReturnKey() const { Q_D(const KComboBox); return d->trapReturnKey; } void KComboBox::setEditUrl(const QUrl &url) { QComboBox::setEditText(url.toDisplayString()); } void KComboBox::addUrl(const QUrl &url) { QComboBox::addItem(url.toDisplayString()); } void KComboBox::addUrl(const QIcon &icon, const QUrl &url) { QComboBox::addItem(icon, url.toDisplayString()); } void KComboBox::insertUrl(int index, const QUrl &url) { QComboBox::insertItem(index, url.toDisplayString()); } void KComboBox::insertUrl(int index, const QIcon &icon, const QUrl &url) { QComboBox::insertItem(index, icon, url.toDisplayString()); } void KComboBox::changeUrl(int index, const QUrl &url) { QComboBox::setItemText(index, url.toDisplayString()); } void KComboBox::changeUrl(int index, const QIcon &icon, const QUrl &url) { QComboBox::setItemIcon(index, icon); QComboBox::setItemText(index, url.toDisplayString()); } void KComboBox::setCompletedItems(const QStringList &items, bool autosubject) { Q_D(KComboBox); if (d->klineEdit) { d->klineEdit->setCompletedItems(items, autosubject); } } KCompletionBox *KComboBox::completionBox(bool create) { Q_D(KComboBox); if (d->klineEdit) { return d->klineEdit->completionBox(create); } return nullptr; } QSize KComboBox::minimumSizeHint() const { Q_D(const KComboBox); QSize size = QComboBox::minimumSizeHint(); if (isEditable() && d->klineEdit) { // if it's a KLineEdit and it's editable add the clear button size // to the minimum size hint, otherwise looks ugly because the // clear button will cover the last 2/3 letters of the biggest entry QSize bs = d->klineEdit->clearButtonUsedSize(); if (bs.isValid()) { size.rwidth() += bs.width(); size.rheight() = qMax(size.height(), bs.height()); } } return size; } void KComboBox::setLineEdit(QLineEdit *edit) { Q_D(KComboBox); if (!isEditable() && edit && !qstrcmp(edit->metaObject()->className(), "QLineEdit")) { // uic generates code that creates a read-only KComboBox and then // calls combo->setEditable(true), which causes QComboBox to set up // a dumb QLineEdit instead of our nice KLineEdit. // As some KComboBox features rely on the KLineEdit, we reject // this order here. delete edit; KLineEdit *kedit = new KLineEdit(this); if (isEditable()) { kedit->setClearButtonEnabled(true); } edit = kedit; } // reuse an existing completion object, if it does not belong to the previous // line edit and gets destroyed with it QPointer completion = compObj(); QComboBox::setLineEdit(edit); edit->setCompleter(nullptr); // remove Qt's builtin completer (set by setLineEdit), we have our own d->klineEdit = qobject_cast(edit); setDelegate(d->klineEdit); if (completion && d->klineEdit) { d->klineEdit->setCompletionObject(completion); } // Connect the returnPressed signal for both Q[K]LineEdits' if (edit) { connect(edit, QOverload<>::of(&QLineEdit::returnPressed), this, QOverload<>::of(&KComboBox::returnPressed)); } if (d->klineEdit) { // someone calling KComboBox::setEditable(false) destroys our // line edit without us noticing. And KCompletionBase::delegate would // be a dangling pointer then, so prevent that. Note: only do this // when it is a KLineEdit! connect(edit, SIGNAL(destroyed()), SLOT(_k_lineEditDeleted())); connect(d->klineEdit, QOverload::of(&KLineEdit::returnPressed), this, QOverload::of(&KComboBox::returnPressed)); connect(d->klineEdit, &KLineEdit::completion, this, &KComboBox::completion); connect(d->klineEdit, &KLineEdit::substringCompletion, this, &KComboBox::substringCompletion); connect(d->klineEdit, &KLineEdit::textRotation, this, &KComboBox::textRotation); connect(d->klineEdit, &KLineEdit::completionModeChanged, this, &KComboBox::completionModeChanged); connect(d->klineEdit, &KLineEdit::aboutToShowContextMenu, this, &KComboBox::aboutToShowContextMenu); // match the declaration of the deprecated signal #if QT_DEPRECATED_SINCE(5, 15) || QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") +QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") connect(d->klineEdit, &KLineEdit::completionBoxActivated, this, QOverload::of(&QComboBox::activated)); +QT_WARNING_POP #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) connect(d->klineEdit, &KLineEdit::completionBoxActivated, this, QOverload::of(&QComboBox::textActivated)); #endif d->klineEdit->setTrapReturnKey(d->trapReturnKey); } } void KComboBox::setCurrentItem(const QString &item, bool insert, int index) { int sel = -1; const int itemCount = count(); for (int i = 0; i < itemCount; ++i) { if (itemText(i) == item) { sel = i; break; } } if (sel == -1 && insert) { if (index >= 0) { insertItem(index, item); sel = index; } else { addItem(item); sel = count() - 1; } } setCurrentIndex(sel); } void KComboBox::setEditable(bool editable) { if (editable == isEditable()) { return; } if (editable) { // Create a KLineEdit instead of a QLineEdit // Compared to QComboBox::setEditable, we might be missing the SH_ComboBox_Popup code though... // If a style needs this, then we'll need to call QComboBox::setEditable and then setLineEdit again KLineEdit *edit = new KLineEdit(this); edit->setClearButtonEnabled(true); setLineEdit(edit); } else { QComboBox::setEditable(editable); } } #include "moc_kcombobox.cpp" diff --git a/src/khistorycombobox.cpp b/src/khistorycombobox.cpp index 5ae2789..4b4cdee 100644 --- a/src/khistorycombobox.cpp +++ b/src/khistorycombobox.cpp @@ -1,524 +1,532 @@ /* This file is part of the KDE libraries Copyright (c) 2000,2001 Dawit Alemayehu Copyright (c) 2000,2001 Carsten Pfeiffer Copyright (c) 2000 Stefan Schimanski <1Stein@gmx.de> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser 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 "khistorycombobox.h" #include #include #include #include #include #include #include class KHistoryComboBoxPrivate { public: KHistoryComboBoxPrivate(KHistoryComboBox *parent): q_ptr(parent) {} void init(bool useCompletion); void rotateUp(); void rotateDown(); /** * Called from the popupmenu, * calls clearHistory() and emits cleared() */ void _k_clear(); /** * Appends our own context menu entry. */ void _k_addContextMenuItems(QMenu *); /** * Used to emit the activated(QString) signal when enter is pressed */ void _k_simulateActivated(const QString &); /** * The text typed before Up or Down was pressed. */ QString typedText; #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66) KPixmapProvider *pixmapProvider = nullptr; #endif KHistoryComboBox * const q_ptr; /** * The current index in the combobox, used for Up and Down */ int currentIndex; /** * Indicates that the user at least once rotated Up through the entire list * Needed to allow going back after rotation. */ bool rotated = false; std::function iconProvider; Q_DECLARE_PUBLIC(KHistoryComboBox) }; void KHistoryComboBoxPrivate::init(bool useCompletion) { Q_Q(KHistoryComboBox); // Set a default history size to something reasonable, Qt sets it to INT_MAX by default q->setMaxCount(50); if (useCompletion) { q->completionObject()->setOrder(KCompletion::Weighted); } q->setInsertPolicy(KHistoryComboBox::NoInsert); currentIndex = -1; rotated = false; #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66) pixmapProvider = nullptr; #endif // obey HISTCONTROL setting QByteArray histControl = qgetenv("HISTCONTROL"); if (histControl == "ignoredups" || histControl == "ignoreboth") { q->setDuplicatesEnabled(false); } q->connect(q, SIGNAL(aboutToShowContextMenu(QMenu*)), SLOT(_k_addContextMenuItems(QMenu*))); QObject::connect(q, QOverload::of(&QComboBox::activated), q, &KHistoryComboBox::reset); QObject::connect(q, QOverload::of(&KComboBox::returnPressed), q, &KHistoryComboBox::reset); // We want _k_simulateActivated to be called _after_ QComboBoxPrivate::_q_returnPressed // otherwise there's a risk of emitting activated twice (_k_simulateActivated will find // the item, after some app's slotActivated inserted the item into the combo). q->connect(q, SIGNAL(returnPressed(QString)), SLOT(_k_simulateActivated(QString)), Qt::QueuedConnection); } // we are always read-write KHistoryComboBox::KHistoryComboBox(QWidget *parent) : KComboBox(true, parent), d_ptr(new KHistoryComboBoxPrivate(this)) { Q_D(KHistoryComboBox); d->init(true); // using completion } // we are always read-write KHistoryComboBox::KHistoryComboBox(bool useCompletion, QWidget *parent) : KComboBox(true, parent), d_ptr(new KHistoryComboBoxPrivate(this)) { Q_D(KHistoryComboBox); d->init(useCompletion); } KHistoryComboBox::~KHistoryComboBox() { #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66) Q_D(KHistoryComboBox); delete d->pixmapProvider; #endif } void KHistoryComboBox::setHistoryItems(const QStringList &items) { setHistoryItems(items, false); } void KHistoryComboBox::setHistoryItems(const QStringList &items, bool setCompletionList) { QStringList insertingItems = items; KComboBox::clear(); // limit to maxCount() const int itemCount = insertingItems.count(); const int toRemove = itemCount - maxCount(); if (toRemove >= itemCount) { insertingItems.clear(); } else { for (int i = 0; i < toRemove; ++i) { insertingItems.pop_front(); } } insertItems(insertingItems); if (setCompletionList && useCompletion()) { // we don't have any weighting information here ;( KCompletion *comp = completionObject(); comp->setOrder(KCompletion::Insertion); comp->setItems(insertingItems); comp->setOrder(KCompletion::Weighted); } clearEditText(); } QStringList KHistoryComboBox::historyItems() const { QStringList list; const int itemCount = count(); list.reserve(itemCount); for (int i = 0; i < itemCount; ++i) { list.append(itemText(i)); } return list; } bool KHistoryComboBox::useCompletion() const { return compObj(); } void KHistoryComboBox::clearHistory() { const QString temp = currentText(); KComboBox::clear(); if (useCompletion()) { completionObject()->clear(); } setEditText(temp); } void KHistoryComboBoxPrivate::_k_addContextMenuItems(QMenu *menu) { Q_Q(KHistoryComboBox); if (menu) { menu->addSeparator(); QAction *clearHistory = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), KHistoryComboBox::tr("Clear &History", "@action:inmenu"), q, SLOT(_k_clear())); if (!q->count()) { clearHistory->setEnabled(false); } } } void KHistoryComboBox::addToHistory(const QString &item) { Q_D(KHistoryComboBox); if (item.isEmpty() || (count() > 0 && item == itemText(0))) { return; } bool wasCurrent = false; // remove all existing items before adding if (!duplicatesEnabled()) { int i = 0; int itemCount = count(); while (i < itemCount) { if (itemText(i) == item) { if (!wasCurrent) { wasCurrent = (i == currentIndex()); } removeItem(i); --itemCount; } else { ++i; } } } // now add the item if (d->iconProvider) { insertItem(0, d->iconProvider(item), item); #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66) } else if (d->pixmapProvider) { insertItem(0, d->pixmapProvider->pixmapFor(item, iconSize().height()), item); #endif } else { insertItem(0, item); } if (wasCurrent) { setCurrentIndex(0); } const bool useComp = useCompletion(); const int last = count() - 1; // last valid index const int mc = maxCount(); const int stopAt = qMax(mc, 0); for (int rmIndex = last; rmIndex >= stopAt; --rmIndex) { // remove the last item, as long as we are longer than maxCount() // remove the removed item from the completionObject if it isn't // anymore available at all in the combobox. const QString rmItem = itemText(rmIndex); removeItem(rmIndex); if (useComp && !contains(rmItem)) { completionObject()->removeItem(rmItem); } } if (useComp) { completionObject()->addItem(item); } } bool KHistoryComboBox::removeFromHistory(const QString &item) { if (item.isEmpty()) { return false; } bool removed = false; const QString temp = currentText(); int i = 0; int itemCount = count(); while (i < itemCount) { if (item == itemText(i)) { removed = true; removeItem(i); --itemCount; } else { ++i; } } if (removed && useCompletion()) { completionObject()->removeItem(item); } setEditText(temp); return removed; } // going up in the history, rotating when reaching QListBox::count() // // Note: this differs from QComboBox because "up" means ++index here, // to simulate the way shell history works (up goes to the most // recent item). In QComboBox "down" means ++index, to match the popup... // void KHistoryComboBoxPrivate::rotateUp() { Q_Q(KHistoryComboBox); // save the current text in the lineedit // (This is also where this differs from standard up/down in QComboBox, // where a single keypress can make you lose your typed text) if (currentIndex == -1) { typedText = q->currentText(); } ++currentIndex; // skip duplicates/empty items const int last = q->count() - 1; // last valid index const QString currText = q->currentText(); while (currentIndex < last && (currText == q->itemText(currentIndex) || q->itemText(currentIndex).isEmpty())) { ++currentIndex; } if (currentIndex >= q->count()) { rotated = true; currentIndex = -1; // if the typed text is the same as the first item, skip the first if (q->count() > 0 && typedText == q->itemText(0)) { currentIndex = 0; } q->setEditText(typedText); } else { q->setCurrentIndex(currentIndex); } } // going down in the history, no rotation possible. Last item will be // the text that was in the lineedit before Up was called. void KHistoryComboBoxPrivate::rotateDown() { Q_Q(KHistoryComboBox); // save the current text in the lineedit if (currentIndex == -1) { typedText = q->currentText(); } --currentIndex; const QString currText = q->currentText(); // skip duplicates/empty items while (currentIndex >= 0 && (currText == q->itemText(currentIndex) || q->itemText(currentIndex).isEmpty())) { --currentIndex; } if (currentIndex < 0) { if (rotated && currentIndex == -2) { rotated = false; currentIndex = q->count() - 1; q->setEditText(q->itemText(currentIndex)); } else { // bottom of history currentIndex = -1; if (q->currentText() != typedText) { q->setEditText(typedText); } } } else { q->setCurrentIndex(currentIndex); } } void KHistoryComboBox::keyPressEvent(QKeyEvent *e) { Q_D(KHistoryComboBox); int event_key = e->key() | e->modifiers(); if (KStandardShortcut::rotateUp().contains(event_key)) { d->rotateUp(); } else if (KStandardShortcut::rotateDown().contains(event_key)) { d->rotateDown(); } else { KComboBox::keyPressEvent(e); } } void KHistoryComboBox::wheelEvent(QWheelEvent *ev) { Q_D(KHistoryComboBox); // Pass to poppable listbox if it's up QAbstractItemView *const iv = view(); if (iv && iv->isVisible()) { QApplication::sendEvent(iv, ev); return; } // Otherwise make it change the text without emitting activated if (ev->angleDelta().y() > 0) { d->rotateUp(); } else { d->rotateDown(); } ev->accept(); } #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66) void KHistoryComboBox::setPixmapProvider(KPixmapProvider *provider) { Q_D(KHistoryComboBox); if (d->pixmapProvider == provider) { return; } delete d->pixmapProvider; d->pixmapProvider = provider; // re-insert all the items with/without pixmap // I would prefer to use changeItem(), but that doesn't honor the pixmap // when using an editable combobox (what we do) if (count() > 0) { QStringList items(historyItems()); clear(); insertItems(items); } } #endif void KHistoryComboBox::setIconProvider(std::function providerFunction) { Q_D(KHistoryComboBox); d->iconProvider = providerFunction; } void KHistoryComboBox::insertItems(const QStringList &items) { Q_D(KHistoryComboBox); for (const QString item : items) { if (item.isEmpty()) { continue; } if (d->iconProvider) { addItem(d->iconProvider(item), item); #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66) } else if (d->pixmapProvider) { addItem(d->pixmapProvider->pixmapFor(item, iconSize().height()), item); #endif } else { addItem(item); } } } void KHistoryComboBoxPrivate::_k_clear() { Q_Q(KHistoryComboBox); q->clearHistory(); emit q->cleared(); } void KHistoryComboBoxPrivate::_k_simulateActivated(const QString &text) { Q_Q(KHistoryComboBox); /* With the insertion policy NoInsert, which we use by default, Qt doesn't emit activated on typed text if the item is not already there, which is perhaps reasonable. Generate the signal ourselves if that's the case. */ if ((q->insertPolicy() == q->NoInsert && q->findText(text, Qt::MatchFixedString | Qt::MatchCaseSensitive) == -1)) { #if QT_DEPRECATED_SINCE(5, 15) || QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") +QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") emit q->activated(text); +QT_WARNING_POP #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) emit q->textActivated(text); #endif } /* Qt also doesn't emit it if the box is full, and policy is not InsertAtCurrent */ else if (q->insertPolicy() != q->InsertAtCurrent && q->count() >= q->maxCount()) { #if QT_DEPRECATED_SINCE(5, 15) || QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") +QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") emit q->activated(text); +QT_WARNING_POP #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) emit q->textActivated(text); #endif } } #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66) KPixmapProvider *KHistoryComboBox::pixmapProvider() const { Q_D(const KHistoryComboBox); return d->pixmapProvider; } #endif void KHistoryComboBox::reset() { Q_D(KHistoryComboBox); d->currentIndex = -1; d->rotated = false; } #include "moc_khistorycombobox.cpp"