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
19void KComboBoxPrivate::init()
20{
21 Q_Q(KComboBox);
22}
23
24void KComboBoxPrivate::slotLineEditDeleted(QLineEdit *sender)
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 *>(sender));
31
32 // is it our delegate, that is destroyed?
33 if (base == q->delegate()) {
34 q->setDelegate(nullptr);
35 }
36}
37
38KComboBox::KComboBox(QWidget *parent)
39 : KComboBox(*new KComboBoxPrivate(this), parent)
40{
41}
42
43KComboBox::KComboBox(KComboBoxPrivate &dd, QWidget *parent)
44 : QComboBox(parent)
45 , d_ptr(&dd)
46{
47 Q_D(KComboBox);
48
49 d->init();
50}
51
52KComboBox::KComboBox(bool rw, QWidget *parent)
53 : KComboBox(*new KComboBoxPrivate(this), parent)
54{
55 setEditable(rw);
56}
57
58KComboBox::~KComboBox()
59{
60 Q_D(KComboBox);
61 disconnect(d->m_klineEditConnection);
62}
63
64bool KComboBox::contains(const QString &text) const
65{
66 if (text.isEmpty()) {
67 return false;
68 }
69
70 const int itemCount = count();
71 for (int i = 0; i < itemCount; ++i) {
72 if (itemText(index: i) == text) {
73 return true;
74 }
75 }
76 return false;
77}
78
79int KComboBox::cursorPosition() const
80{
81 return (isEditable()) ? lineEdit()->cursorPosition() : -1;
82}
83
84void KComboBox::setAutoCompletion(bool autocomplete)
85{
86 Q_D(KComboBox);
87 if (d->klineEdit) {
88 if (autocomplete) {
89 d->klineEdit->setCompletionMode(KCompletion::CompletionAuto);
90 setCompletionMode(KCompletion::CompletionAuto);
91 } else {
92 d->klineEdit->setCompletionMode(KCompletion::CompletionPopup);
93 setCompletionMode(KCompletion::CompletionPopup);
94 }
95 }
96}
97
98bool KComboBox::autoCompletion() const
99{
100 return completionMode() == KCompletion::CompletionAuto;
101}
102
103bool KComboBox::urlDropsEnabled() const
104{
105 Q_D(const KComboBox);
106 return d->klineEdit && d->klineEdit->urlDropsEnabled();
107}
108
109void KComboBox::setCompletedText(const QString &text, bool marked)
110{
111 Q_D(KComboBox);
112 if (d->klineEdit) {
113 d->klineEdit->setCompletedText(text, marked);
114 }
115}
116
117void KComboBox::setCompletedText(const QString &text)
118{
119 Q_D(KComboBox);
120 if (d->klineEdit) {
121 d->klineEdit->setCompletedText(text);
122 }
123}
124
125void KComboBox::makeCompletion(const QString &text)
126{
127 Q_D(KComboBox);
128 if (d->klineEdit) {
129 d->klineEdit->makeCompletion(text);
130 }
131
132 else { // read-only combo completion
133 if (text.isNull() || !view()) {
134 return;
135 }
136
137 view()->keyboardSearch(search: text);
138 }
139}
140
141void KComboBox::rotateText(KCompletionBase::KeyBindingType type)
142{
143 Q_D(KComboBox);
144 if (d->klineEdit) {
145 d->klineEdit->rotateText(type);
146 }
147}
148
149void KComboBox::setTrapReturnKey(bool trap)
150{
151 Q_D(KComboBox);
152 d->trapReturnKey = trap;
153
154 if (d->klineEdit) {
155 d->klineEdit->setTrapReturnKey(trap);
156 } else {
157 qCWarning(KCOMPLETION_LOG) << "KComboBox::setTrapReturnKey not supported with a non-KLineEdit.";
158 }
159}
160
161bool KComboBox::trapReturnKey() const
162{
163 Q_D(const KComboBox);
164 return d->trapReturnKey;
165}
166
167void KComboBox::setEditUrl(const QUrl &url)
168{
169 QComboBox::setEditText(url.toDisplayString());
170}
171
172void KComboBox::addUrl(const QUrl &url)
173{
174 QComboBox::addItem(atext: url.toDisplayString());
175}
176
177void KComboBox::addUrl(const QIcon &icon, const QUrl &url)
178{
179 QComboBox::addItem(aicon: icon, atext: url.toDisplayString());
180}
181
182void KComboBox::insertUrl(int index, const QUrl &url)
183{
184 QComboBox::insertItem(aindex: index, atext: url.toDisplayString());
185}
186
187void KComboBox::insertUrl(int index, const QIcon &icon, const QUrl &url)
188{
189 QComboBox::insertItem(index, icon, text: url.toDisplayString());
190}
191
192void KComboBox::changeUrl(int index, const QUrl &url)
193{
194 QComboBox::setItemText(index, text: url.toDisplayString());
195}
196
197void KComboBox::changeUrl(int index, const QIcon &icon, const QUrl &url)
198{
199 QComboBox::setItemIcon(index, icon);
200 QComboBox::setItemText(index, text: url.toDisplayString());
201}
202
203void KComboBox::setCompletedItems(const QStringList &items, bool autoSuggest)
204{
205 Q_D(KComboBox);
206 if (d->klineEdit) {
207 d->klineEdit->setCompletedItems(items, autoSuggest);
208 }
209}
210
211KCompletionBox *KComboBox::completionBox(bool create)
212{
213 Q_D(KComboBox);
214 if (d->klineEdit) {
215 return d->klineEdit->completionBox(create);
216 }
217 return nullptr;
218}
219
220QSize KComboBox::minimumSizeHint() const
221{
222 Q_D(const KComboBox);
223 QSize size = QComboBox::minimumSizeHint();
224 if (isEditable() && d->klineEdit) {
225 // if it's a KLineEdit and it's editable add the clear button size
226 // to the minimum size hint, otherwise looks ugly because the
227 // clear button will cover the last 2/3 letters of the biggest entry
228 QSize bs = d->klineEdit->clearButtonUsedSize();
229 if (bs.isValid()) {
230 size.rwidth() += bs.width();
231 size.rheight() = qMax(a: size.height(), b: bs.height());
232 }
233 }
234 return size;
235}
236
237void KComboBox::setLineEdit(QLineEdit *edit)
238{
239 Q_D(KComboBox);
240 if (!isEditable() && edit && !qstrcmp(str1: edit->metaObject()->className(), str2: "QLineEdit")) {
241 // uic generates code that creates a read-only KComboBox and then
242 // calls combo->setEditable(true), which causes QComboBox to set up
243 // a dumb QLineEdit instead of our nice KLineEdit.
244 // As some KComboBox features rely on the KLineEdit, we reject
245 // this order here.
246 delete edit;
247 KLineEdit *kedit = new KLineEdit(this);
248
249 if (isEditable()) {
250 kedit->setClearButtonEnabled(true);
251 }
252
253 edit = kedit;
254 }
255
256 // reuse an existing completion object, if it does not belong to the previous
257 // line edit and gets destroyed with it
258 QPointer<KCompletion> completion = compObj();
259
260 QComboBox::setLineEdit(edit);
261 edit->setCompleter(nullptr); // remove Qt's builtin completer (set by setLineEdit), we have our own
262 d->klineEdit = qobject_cast<KLineEdit *>(object: edit);
263 setDelegate(d->klineEdit);
264
265 if (completion && d->klineEdit) {
266 d->klineEdit->setCompletionObject(completion);
267 }
268
269 if (d->klineEdit) {
270 // someone calling KComboBox::setEditable(false) destroys our
271 // line edit without us noticing. And KCompletionBase::delegate would
272 // be a dangling pointer then, so prevent that. Note: only do this
273 // when it is a KLineEdit!
274 d->m_klineEditConnection = connect(sender: edit, signal: &QObject::destroyed, context: this, slot: [d, edit]() {
275 d->slotLineEditDeleted(sender: edit);
276 });
277
278 connect(sender: d->klineEdit, signal: &KLineEdit::returnKeyPressed, context: this, slot: qOverload<const QString &>(&KComboBox::returnPressed));
279
280 connect(sender: d->klineEdit, signal: &KLineEdit::completion, context: this, slot: &KComboBox::completion);
281
282 connect(sender: d->klineEdit, signal: &KLineEdit::substringCompletion, context: this, slot: &KComboBox::substringCompletion);
283
284 connect(sender: d->klineEdit, signal: &KLineEdit::textRotation, context: this, slot: &KComboBox::textRotation);
285
286 connect(sender: d->klineEdit, signal: &KLineEdit::completionModeChanged, context: this, slot: &KComboBox::completionModeChanged);
287
288 connect(sender: d->klineEdit, signal: &KLineEdit::aboutToShowContextMenu, slot: [this](QMenu *menu) {
289 Q_D(KComboBox);
290 d->contextMenu = menu;
291 Q_EMIT aboutToShowContextMenu(contextMenu: menu);
292 });
293
294 connect(sender: d->klineEdit, signal: &KLineEdit::completionBoxActivated, context: this, slot: &QComboBox::textActivated);
295
296 d->klineEdit->setTrapReturnKey(d->trapReturnKey);
297 }
298}
299
300QMenu *KComboBox::contextMenu() const
301{
302 return d_ptr->contextMenu;
303}
304
305void KComboBox::setCurrentItem(const QString &item, bool insert, int index)
306{
307 int sel = -1;
308
309 const int itemCount = count();
310 for (int i = 0; i < itemCount; ++i) {
311 if (itemText(index: i) == item) {
312 sel = i;
313 break;
314 }
315 }
316
317 if (sel == -1 && insert) {
318 if (index >= 0) {
319 insertItem(aindex: index, atext: item);
320 sel = index;
321 } else {
322 addItem(atext: item);
323 sel = count() - 1;
324 }
325 }
326 setCurrentIndex(sel);
327}
328
329void KComboBox::setEditable(bool editable)
330{
331 if (editable == isEditable()) {
332 return;
333 }
334
335 if (editable) {
336 // Create a KLineEdit instead of a QLineEdit
337 // Compared to QComboBox::setEditable, we might be missing the SH_ComboBox_Popup code though...
338 // If a style needs this, then we'll need to call QComboBox::setEditable and then setLineEdit again
339 KLineEdit *edit = new KLineEdit(this);
340 edit->setClearButtonEnabled(true);
341 setLineEdit(edit);
342 } else {
343 if (d_ptr->contextMenu) {
344 d_ptr->contextMenu->close();
345 }
346 QComboBox::setEditable(editable);
347 }
348}
349
350#include "moc_kcombobox.cpp"
351

source code of kcompletion/src/kcombobox.cpp