File indexing completed on 2024-04-28 03:53:08

0001 /*
0002     This file is part of the KDE libraries
0003 
0004     SPDX-FileCopyrightText: 2000, 2001 Dawit Alemayehu <adawit@kde.org>
0005     SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
0006     SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
0007 
0008     SPDX-License-Identifier: LGPL-2.1-or-later
0009 */
0010 
0011 #include "kcombobox.h"
0012 #include "kcombobox_p.h"
0013 
0014 #include <kcompletion_debug.h>
0015 #include <kcompletionbox.h>
0016 
0017 #include <QUrl>
0018 
0019 void KComboBoxPrivate::init()
0020 {
0021     Q_Q(KComboBox);
0022 }
0023 
0024 void KComboBoxPrivate::slotLineEditDeleted(QLineEdit *sender)
0025 {
0026     Q_Q(KComboBox);
0027     // yes, we need those ugly casts due to the multiple inheritance
0028     // "sender" is guaranteed to be a KLineEdit (see the connect() to the
0029     // destroyed() signal
0030     const KCompletionBase *base = static_cast<const KCompletionBase *>(static_cast<const KLineEdit *>(sender));
0031 
0032     // is it our delegate, that is destroyed?
0033     if (base == q->delegate()) {
0034         q->setDelegate(nullptr);
0035     }
0036 }
0037 
0038 KComboBox::KComboBox(QWidget *parent)
0039     : KComboBox(*new KComboBoxPrivate(this), parent)
0040 {
0041 }
0042 
0043 KComboBox::KComboBox(KComboBoxPrivate &dd, QWidget *parent)
0044     : QComboBox(parent)
0045     , d_ptr(&dd)
0046 {
0047     Q_D(KComboBox);
0048 
0049     d->init();
0050 }
0051 
0052 KComboBox::KComboBox(bool rw, QWidget *parent)
0053     : KComboBox(*new KComboBoxPrivate(this), parent)
0054 {
0055     setEditable(rw);
0056 }
0057 
0058 KComboBox::~KComboBox()
0059 {
0060     Q_D(KComboBox);
0061     disconnect(d->m_klineEditConnection);
0062 }
0063 
0064 bool KComboBox::contains(const QString &text) const
0065 {
0066     if (text.isEmpty()) {
0067         return false;
0068     }
0069 
0070     const int itemCount = count();
0071     for (int i = 0; i < itemCount; ++i) {
0072         if (itemText(i) == text) {
0073             return true;
0074         }
0075     }
0076     return false;
0077 }
0078 
0079 int KComboBox::cursorPosition() const
0080 {
0081     return (isEditable()) ? lineEdit()->cursorPosition() : -1;
0082 }
0083 
0084 void KComboBox::setAutoCompletion(bool autocomplete)
0085 {
0086     Q_D(KComboBox);
0087     if (d->klineEdit) {
0088         if (autocomplete) {
0089             d->klineEdit->setCompletionMode(KCompletion::CompletionAuto);
0090             setCompletionMode(KCompletion::CompletionAuto);
0091         } else {
0092             d->klineEdit->setCompletionMode(KCompletion::CompletionPopup);
0093             setCompletionMode(KCompletion::CompletionPopup);
0094         }
0095     }
0096 }
0097 
0098 bool KComboBox::autoCompletion() const
0099 {
0100     return completionMode() == KCompletion::CompletionAuto;
0101 }
0102 
0103 bool KComboBox::urlDropsEnabled() const
0104 {
0105     Q_D(const KComboBox);
0106     return d->klineEdit && d->klineEdit->urlDropsEnabled();
0107 }
0108 
0109 void KComboBox::setCompletedText(const QString &text, bool marked)
0110 {
0111     Q_D(KComboBox);
0112     if (d->klineEdit) {
0113         d->klineEdit->setCompletedText(text, marked);
0114     }
0115 }
0116 
0117 void KComboBox::setCompletedText(const QString &text)
0118 {
0119     Q_D(KComboBox);
0120     if (d->klineEdit) {
0121         d->klineEdit->setCompletedText(text);
0122     }
0123 }
0124 
0125 void KComboBox::makeCompletion(const QString &text)
0126 {
0127     Q_D(KComboBox);
0128     if (d->klineEdit) {
0129         d->klineEdit->makeCompletion(text);
0130     }
0131 
0132     else { // read-only combo completion
0133         if (text.isNull() || !view()) {
0134             return;
0135         }
0136 
0137         view()->keyboardSearch(text);
0138     }
0139 }
0140 
0141 void KComboBox::rotateText(KCompletionBase::KeyBindingType type)
0142 {
0143     Q_D(KComboBox);
0144     if (d->klineEdit) {
0145         d->klineEdit->rotateText(type);
0146     }
0147 }
0148 
0149 void KComboBox::setTrapReturnKey(bool trap)
0150 {
0151     Q_D(KComboBox);
0152     d->trapReturnKey = trap;
0153 
0154     if (d->klineEdit) {
0155         d->klineEdit->setTrapReturnKey(trap);
0156     } else {
0157         qCWarning(KCOMPLETION_LOG) << "KComboBox::setTrapReturnKey not supported with a non-KLineEdit.";
0158     }
0159 }
0160 
0161 bool KComboBox::trapReturnKey() const
0162 {
0163     Q_D(const KComboBox);
0164     return d->trapReturnKey;
0165 }
0166 
0167 void KComboBox::setEditUrl(const QUrl &url)
0168 {
0169     QComboBox::setEditText(url.toDisplayString());
0170 }
0171 
0172 void KComboBox::addUrl(const QUrl &url)
0173 {
0174     QComboBox::addItem(url.toDisplayString());
0175 }
0176 
0177 void KComboBox::addUrl(const QIcon &icon, const QUrl &url)
0178 {
0179     QComboBox::addItem(icon, url.toDisplayString());
0180 }
0181 
0182 void KComboBox::insertUrl(int index, const QUrl &url)
0183 {
0184     QComboBox::insertItem(index, url.toDisplayString());
0185 }
0186 
0187 void KComboBox::insertUrl(int index, const QIcon &icon, const QUrl &url)
0188 {
0189     QComboBox::insertItem(index, icon, url.toDisplayString());
0190 }
0191 
0192 void KComboBox::changeUrl(int index, const QUrl &url)
0193 {
0194     QComboBox::setItemText(index, url.toDisplayString());
0195 }
0196 
0197 void KComboBox::changeUrl(int index, const QIcon &icon, const QUrl &url)
0198 {
0199     QComboBox::setItemIcon(index, icon);
0200     QComboBox::setItemText(index, url.toDisplayString());
0201 }
0202 
0203 void KComboBox::setCompletedItems(const QStringList &items, bool autoSuggest)
0204 {
0205     Q_D(KComboBox);
0206     if (d->klineEdit) {
0207         d->klineEdit->setCompletedItems(items, autoSuggest);
0208     }
0209 }
0210 
0211 KCompletionBox *KComboBox::completionBox(bool create)
0212 {
0213     Q_D(KComboBox);
0214     if (d->klineEdit) {
0215         return d->klineEdit->completionBox(create);
0216     }
0217     return nullptr;
0218 }
0219 
0220 QSize KComboBox::minimumSizeHint() const
0221 {
0222     Q_D(const KComboBox);
0223     QSize size = QComboBox::minimumSizeHint();
0224     if (isEditable() && d->klineEdit) {
0225         // if it's a KLineEdit and it's editable add the clear button size
0226         // to the minimum size hint, otherwise looks ugly because the
0227         // clear button will cover the last 2/3 letters of the biggest entry
0228         QSize bs = d->klineEdit->clearButtonUsedSize();
0229         if (bs.isValid()) {
0230             size.rwidth() += bs.width();
0231             size.rheight() = qMax(size.height(), bs.height());
0232         }
0233     }
0234     return size;
0235 }
0236 
0237 void KComboBox::setLineEdit(QLineEdit *edit)
0238 {
0239     Q_D(KComboBox);
0240     if (!isEditable() && edit && !qstrcmp(edit->metaObject()->className(), "QLineEdit")) {
0241         // uic generates code that creates a read-only KComboBox and then
0242         // calls combo->setEditable(true), which causes QComboBox to set up
0243         // a dumb QLineEdit instead of our nice KLineEdit.
0244         // As some KComboBox features rely on the KLineEdit, we reject
0245         // this order here.
0246         delete edit;
0247         KLineEdit *kedit = new KLineEdit(this);
0248 
0249         if (isEditable()) {
0250             kedit->setClearButtonEnabled(true);
0251         }
0252 
0253         edit = kedit;
0254     }
0255 
0256     // reuse an existing completion object, if it does not belong to the previous
0257     // line edit and gets destroyed with it
0258     QPointer<KCompletion> completion = compObj();
0259 
0260     QComboBox::setLineEdit(edit);
0261     edit->setCompleter(nullptr); // remove Qt's builtin completer (set by setLineEdit), we have our own
0262     d->klineEdit = qobject_cast<KLineEdit *>(edit);
0263     setDelegate(d->klineEdit);
0264 
0265     if (completion && d->klineEdit) {
0266         d->klineEdit->setCompletionObject(completion);
0267     }
0268 
0269     if (d->klineEdit) {
0270         // someone calling KComboBox::setEditable(false) destroys our
0271         // line edit without us noticing. And KCompletionBase::delegate would
0272         // be a dangling pointer then, so prevent that. Note: only do this
0273         // when it is a KLineEdit!
0274         d->m_klineEditConnection = connect(edit, &QObject::destroyed, this, [d, edit]() {
0275             d->slotLineEditDeleted(edit);
0276         });
0277 
0278         connect(d->klineEdit, &KLineEdit::returnKeyPressed, this, qOverload<const QString &>(&KComboBox::returnPressed));
0279 
0280         connect(d->klineEdit, &KLineEdit::completion, this, &KComboBox::completion);
0281 
0282         connect(d->klineEdit, &KLineEdit::substringCompletion, this, &KComboBox::substringCompletion);
0283 
0284         connect(d->klineEdit, &KLineEdit::textRotation, this, &KComboBox::textRotation);
0285 
0286         connect(d->klineEdit, &KLineEdit::completionModeChanged, this, &KComboBox::completionModeChanged);
0287 
0288         connect(d->klineEdit, &KLineEdit::aboutToShowContextMenu, [this](QMenu *menu) {
0289             Q_D(KComboBox);
0290             d->contextMenu = menu;
0291             Q_EMIT aboutToShowContextMenu(menu);
0292         });
0293 
0294         connect(d->klineEdit, &KLineEdit::completionBoxActivated, this, &QComboBox::textActivated);
0295 
0296         d->klineEdit->setTrapReturnKey(d->trapReturnKey);
0297     }
0298 }
0299 
0300 QMenu *KComboBox::contextMenu() const
0301 {
0302     return d_ptr->contextMenu;
0303 }
0304 
0305 void KComboBox::setCurrentItem(const QString &item, bool insert, int index)
0306 {
0307     int sel = -1;
0308 
0309     const int itemCount = count();
0310     for (int i = 0; i < itemCount; ++i) {
0311         if (itemText(i) == item) {
0312             sel = i;
0313             break;
0314         }
0315     }
0316 
0317     if (sel == -1 && insert) {
0318         if (index >= 0) {
0319             insertItem(index, item);
0320             sel = index;
0321         } else {
0322             addItem(item);
0323             sel = count() - 1;
0324         }
0325     }
0326     setCurrentIndex(sel);
0327 }
0328 
0329 void KComboBox::setEditable(bool editable)
0330 {
0331     if (editable == isEditable()) {
0332         return;
0333     }
0334 
0335     if (editable) {
0336         // Create a KLineEdit instead of a QLineEdit
0337         // Compared to QComboBox::setEditable, we might be missing the SH_ComboBox_Popup code though...
0338         // If a style needs this, then we'll need to call QComboBox::setEditable and then setLineEdit again
0339         KLineEdit *edit = new KLineEdit(this);
0340         edit->setClearButtonEnabled(true);
0341         setLineEdit(edit);
0342     } else {
0343         if (d_ptr->contextMenu) {
0344             d_ptr->contextMenu->close();
0345         }
0346         QComboBox::setEditable(editable);
0347     }
0348 }
0349 
0350 #include "moc_kcombobox.cpp"