1 /*
2     This file is part of the KDE libraries
3 
4     SPDX-FileCopyrightText: 2000, 2001 Dawit Alemayehu <adawit@kde.org>
5     SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
6     SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
7 
8     SPDX-License-Identifier: LGPL-2.1-or-later
9 */
10 
11 #include "kcombobox.h"
12 #include "kcombobox_p.h"
13 
14 #include <kcompletion_debug.h>
15 #include <kcompletionbox.h>
16 
17 #include <QUrl>
18 
init()19 void KComboBoxPrivate::init()
20 {
21     Q_Q(KComboBox);
22 }
23 
_k_lineEditDeleted()24 void KComboBoxPrivate::_k_lineEditDeleted()
25 {
26     Q_Q(KComboBox);
27     // yes, we need those ugly casts due to the multiple inheritance
28     // sender() is guaranteed to be a KLineEdit (see the connect() to the
29     // destroyed() signal
30     const KCompletionBase *base = static_cast<const KCompletionBase *>(static_cast<const KLineEdit *>(q->sender()));
31 
32     // is it our delegate, that is destroyed?
33     if (base == q->delegate()) {
34         q->setDelegate(nullptr);
35     }
36 }
37 
KComboBox(QWidget * parent)38 KComboBox::KComboBox(QWidget *parent)
39     : KComboBox(*new KComboBoxPrivate(this), parent)
40 {
41 }
42 
KComboBox(KComboBoxPrivate & dd,QWidget * parent)43 KComboBox::KComboBox(KComboBoxPrivate &dd, QWidget *parent)
44     : QComboBox(parent)
45     , d_ptr(&dd)
46 {
47     Q_D(KComboBox);
48 
49     d->init();
50 }
51 
KComboBox(bool rw,QWidget * parent)52 KComboBox::KComboBox(bool rw, QWidget *parent)
53     : KComboBox(*new KComboBoxPrivate(this), parent)
54 {
55     setEditable(rw);
56 }
57 
~KComboBox()58 KComboBox::~KComboBox()
59 {
60 }
61 
contains(const QString & text) const62 bool KComboBox::contains(const QString &text) const
63 {
64     if (text.isEmpty()) {
65         return false;
66     }
67 
68     const int itemCount = count();
69     for (int i = 0; i < itemCount; ++i) {
70         if (itemText(i) == text) {
71             return true;
72         }
73     }
74     return false;
75 }
76 
cursorPosition() const77 int KComboBox::cursorPosition() const
78 {
79     return (isEditable()) ? lineEdit()->cursorPosition() : -1;
80 }
81 
setAutoCompletion(bool autocomplete)82 void KComboBox::setAutoCompletion(bool autocomplete)
83 {
84     Q_D(KComboBox);
85     if (d->klineEdit) {
86         if (autocomplete) {
87             d->klineEdit->setCompletionMode(KCompletion::CompletionAuto);
88             setCompletionMode(KCompletion::CompletionAuto);
89         } else {
90             d->klineEdit->setCompletionMode(KCompletion::CompletionPopup);
91             setCompletionMode(KCompletion::CompletionPopup);
92         }
93     }
94 }
95 
autoCompletion() const96 bool KComboBox::autoCompletion() const
97 {
98     return completionMode() == KCompletion::CompletionAuto;
99 }
100 
101 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(4, 5)
setContextMenuEnabled(bool showMenu)102 void KComboBox::setContextMenuEnabled(bool showMenu)
103 {
104     Q_D(KComboBox);
105     if (d->klineEdit) {
106         d->klineEdit->setContextMenuPolicy(showMenu ? Qt::DefaultContextMenu : Qt::NoContextMenu);
107     }
108 }
109 #endif
110 
111 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 0)
setUrlDropsEnabled(bool enable)112 void KComboBox::setUrlDropsEnabled(bool enable)
113 {
114     Q_D(KComboBox);
115     if (d->klineEdit) {
116         d->klineEdit->setUrlDropsEnabled(enable);
117     }
118 }
119 #endif
120 
urlDropsEnabled() const121 bool KComboBox::urlDropsEnabled() const
122 {
123     Q_D(const KComboBox);
124     return d->klineEdit && d->klineEdit->urlDropsEnabled();
125 }
126 
setCompletedText(const QString & text,bool marked)127 void KComboBox::setCompletedText(const QString &text, bool marked)
128 {
129     Q_D(KComboBox);
130     if (d->klineEdit) {
131         d->klineEdit->setCompletedText(text, marked);
132     }
133 }
134 
setCompletedText(const QString & text)135 void KComboBox::setCompletedText(const QString &text)
136 {
137     Q_D(KComboBox);
138     if (d->klineEdit) {
139         d->klineEdit->setCompletedText(text);
140     }
141 }
142 
makeCompletion(const QString & text)143 void KComboBox::makeCompletion(const QString &text)
144 {
145     Q_D(KComboBox);
146     if (d->klineEdit) {
147         d->klineEdit->makeCompletion(text);
148     }
149 
150     else { // read-only combo completion
151         if (text.isNull() || !view()) {
152             return;
153         }
154 
155         view()->keyboardSearch(text);
156     }
157 }
158 
rotateText(KCompletionBase::KeyBindingType type)159 void KComboBox::rotateText(KCompletionBase::KeyBindingType type)
160 {
161     Q_D(KComboBox);
162     if (d->klineEdit) {
163         d->klineEdit->rotateText(type);
164     }
165 }
166 
setTrapReturnKey(bool trap)167 void KComboBox::setTrapReturnKey(bool trap)
168 {
169     Q_D(KComboBox);
170     d->trapReturnKey = trap;
171 
172     if (d->klineEdit) {
173         d->klineEdit->setTrapReturnKey(trap);
174     } else {
175         qCWarning(KCOMPLETION_LOG) << "KComboBox::setTrapReturnKey not supported with a non-KLineEdit.";
176     }
177 }
178 
trapReturnKey() const179 bool KComboBox::trapReturnKey() const
180 {
181     Q_D(const KComboBox);
182     return d->trapReturnKey;
183 }
184 
setEditUrl(const QUrl & url)185 void KComboBox::setEditUrl(const QUrl &url)
186 {
187     QComboBox::setEditText(url.toDisplayString());
188 }
189 
addUrl(const QUrl & url)190 void KComboBox::addUrl(const QUrl &url)
191 {
192     QComboBox::addItem(url.toDisplayString());
193 }
194 
addUrl(const QIcon & icon,const QUrl & url)195 void KComboBox::addUrl(const QIcon &icon, const QUrl &url)
196 {
197     QComboBox::addItem(icon, url.toDisplayString());
198 }
199 
insertUrl(int index,const QUrl & url)200 void KComboBox::insertUrl(int index, const QUrl &url)
201 {
202     QComboBox::insertItem(index, url.toDisplayString());
203 }
204 
insertUrl(int index,const QIcon & icon,const QUrl & url)205 void KComboBox::insertUrl(int index, const QIcon &icon, const QUrl &url)
206 {
207     QComboBox::insertItem(index, icon, url.toDisplayString());
208 }
209 
changeUrl(int index,const QUrl & url)210 void KComboBox::changeUrl(int index, const QUrl &url)
211 {
212     QComboBox::setItemText(index, url.toDisplayString());
213 }
214 
changeUrl(int index,const QIcon & icon,const QUrl & url)215 void KComboBox::changeUrl(int index, const QIcon &icon, const QUrl &url)
216 {
217     QComboBox::setItemIcon(index, icon);
218     QComboBox::setItemText(index, url.toDisplayString());
219 }
220 
setCompletedItems(const QStringList & items,bool autoSuggest)221 void KComboBox::setCompletedItems(const QStringList &items, bool autoSuggest)
222 {
223     Q_D(KComboBox);
224     if (d->klineEdit) {
225         d->klineEdit->setCompletedItems(items, autoSuggest);
226     }
227 }
228 
completionBox(bool create)229 KCompletionBox *KComboBox::completionBox(bool create)
230 {
231     Q_D(KComboBox);
232     if (d->klineEdit) {
233         return d->klineEdit->completionBox(create);
234     }
235     return nullptr;
236 }
237 
minimumSizeHint() const238 QSize KComboBox::minimumSizeHint() const
239 {
240     Q_D(const KComboBox);
241     QSize size = QComboBox::minimumSizeHint();
242     if (isEditable() && d->klineEdit) {
243         // if it's a KLineEdit and it's editable add the clear button size
244         // to the minimum size hint, otherwise looks ugly because the
245         // clear button will cover the last 2/3 letters of the biggest entry
246         QSize bs = d->klineEdit->clearButtonUsedSize();
247         if (bs.isValid()) {
248             size.rwidth() += bs.width();
249             size.rheight() = qMax(size.height(), bs.height());
250         }
251     }
252     return size;
253 }
254 
setLineEdit(QLineEdit * edit)255 void KComboBox::setLineEdit(QLineEdit *edit)
256 {
257     Q_D(KComboBox);
258     if (!isEditable() && edit && !qstrcmp(edit->metaObject()->className(), "QLineEdit")) {
259         // uic generates code that creates a read-only KComboBox and then
260         // calls combo->setEditable(true), which causes QComboBox to set up
261         // a dumb QLineEdit instead of our nice KLineEdit.
262         // As some KComboBox features rely on the KLineEdit, we reject
263         // this order here.
264         delete edit;
265         KLineEdit *kedit = new KLineEdit(this);
266 
267         if (isEditable()) {
268             kedit->setClearButtonEnabled(true);
269         }
270 
271         edit = kedit;
272     }
273 
274     // reuse an existing completion object, if it does not belong to the previous
275     // line edit and gets destroyed with it
276     QPointer<KCompletion> completion = compObj();
277 
278     QComboBox::setLineEdit(edit);
279     edit->setCompleter(nullptr); // remove Qt's builtin completer (set by setLineEdit), we have our own
280     d->klineEdit = qobject_cast<KLineEdit *>(edit);
281     setDelegate(d->klineEdit);
282 
283     if (completion && d->klineEdit) {
284         d->klineEdit->setCompletionObject(completion);
285     }
286 
287 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 81)
288     // Connect the returnPressed signal for both Q[K]LineEdits'
289     if (edit) {
290         connect(edit, qOverload<>(&QLineEdit::returnPressed), this, qOverload<>(&KComboBox::returnPressed));
291     }
292 #endif
293 
294     if (d->klineEdit) {
295         // someone calling KComboBox::setEditable(false) destroys our
296         // line edit without us noticing. And KCompletionBase::delegate would
297         // be a dangling pointer then, so prevent that. Note: only do this
298         // when it is a KLineEdit!
299         connect(edit, SIGNAL(destroyed()), SLOT(_k_lineEditDeleted()));
300 
301         connect(d->klineEdit, &KLineEdit::returnKeyPressed, this, qOverload<const QString &>(&KComboBox::returnPressed));
302 
303         connect(d->klineEdit, &KLineEdit::completion, this, &KComboBox::completion);
304 
305         connect(d->klineEdit, &KLineEdit::substringCompletion, this, &KComboBox::substringCompletion);
306 
307         connect(d->klineEdit, &KLineEdit::textRotation, this, &KComboBox::textRotation);
308 
309         connect(d->klineEdit, &KLineEdit::completionModeChanged, this, &KComboBox::completionModeChanged);
310 
311         connect(d->klineEdit, &KLineEdit::aboutToShowContextMenu, [this](QMenu *menu) {
312             Q_D(KComboBox);
313             d->contextMenu = menu;
314             Q_EMIT aboutToShowContextMenu(menu);
315         });
316 
317         // match the declaration of the deprecated signal
318 #if QT_DEPRECATED_SINCE(5, 15)
319         QT_WARNING_PUSH
320         QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
321         QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
322         connect(d->klineEdit, &KLineEdit::completionBoxActivated, this, qOverload<const QString &>(&QComboBox::activated));
323         QT_WARNING_POP
324 #endif
325         connect(d->klineEdit, &KLineEdit::completionBoxActivated, this, &QComboBox::textActivated);
326 
327         d->klineEdit->setTrapReturnKey(d->trapReturnKey);
328     }
329 }
330 
contextMenu() const331 QMenu *KComboBox::contextMenu() const
332 {
333     return d_ptr->contextMenu;
334 }
335 
setCurrentItem(const QString & item,bool insert,int index)336 void KComboBox::setCurrentItem(const QString &item, bool insert, int index)
337 {
338     int sel = -1;
339 
340     const int itemCount = count();
341     for (int i = 0; i < itemCount; ++i) {
342         if (itemText(i) == item) {
343             sel = i;
344             break;
345         }
346     }
347 
348     if (sel == -1 && insert) {
349         if (index >= 0) {
350             insertItem(index, item);
351             sel = index;
352         } else {
353             addItem(item);
354             sel = count() - 1;
355         }
356     }
357     setCurrentIndex(sel);
358 }
359 
setEditable(bool editable)360 void KComboBox::setEditable(bool editable)
361 {
362     if (editable == isEditable()) {
363         return;
364     }
365 
366     if (editable) {
367         // Create a KLineEdit instead of a QLineEdit
368         // Compared to QComboBox::setEditable, we might be missing the SH_ComboBox_Popup code though...
369         // If a style needs this, then we'll need to call QComboBox::setEditable and then setLineEdit again
370         KLineEdit *edit = new KLineEdit(this);
371         edit->setClearButtonEnabled(true);
372         setLineEdit(edit);
373     } else {
374         if (d_ptr->contextMenu) {
375             d_ptr->contextMenu->close();
376         }
377         QComboBox::setEditable(editable);
378     }
379 }
380 
381 #include "moc_kcombobox.cpp"
382