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 | |
19 | void KComboBoxPrivate::init() |
20 | { |
21 | Q_Q(KComboBox); |
22 | } |
23 | |
24 | void 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 | |
38 | KComboBox::KComboBox(QWidget *parent) |
39 | : KComboBox(*new KComboBoxPrivate(this), parent) |
40 | { |
41 | } |
42 | |
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 | |
52 | KComboBox::KComboBox(bool rw, QWidget *parent) |
53 | : KComboBox(*new KComboBoxPrivate(this), parent) |
54 | { |
55 | setEditable(rw); |
56 | } |
57 | |
58 | KComboBox::~KComboBox() |
59 | { |
60 | Q_D(KComboBox); |
61 | disconnect(d->m_klineEditConnection); |
62 | } |
63 | |
64 | bool 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 | |
79 | int KComboBox::cursorPosition() const |
80 | { |
81 | return (isEditable()) ? lineEdit()->cursorPosition() : -1; |
82 | } |
83 | |
84 | void 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 | |
98 | bool KComboBox::autoCompletion() const |
99 | { |
100 | return completionMode() == KCompletion::CompletionAuto; |
101 | } |
102 | |
103 | bool KComboBox::urlDropsEnabled() const |
104 | { |
105 | Q_D(const KComboBox); |
106 | return d->klineEdit && d->klineEdit->urlDropsEnabled(); |
107 | } |
108 | |
109 | void 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 | |
117 | void KComboBox::setCompletedText(const QString &text) |
118 | { |
119 | Q_D(KComboBox); |
120 | if (d->klineEdit) { |
121 | d->klineEdit->setCompletedText(text); |
122 | } |
123 | } |
124 | |
125 | void 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 | |
141 | void KComboBox::rotateText(KCompletionBase::KeyBindingType type) |
142 | { |
143 | Q_D(KComboBox); |
144 | if (d->klineEdit) { |
145 | d->klineEdit->rotateText(type); |
146 | } |
147 | } |
148 | |
149 | void 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 | |
161 | bool KComboBox::trapReturnKey() const |
162 | { |
163 | Q_D(const KComboBox); |
164 | return d->trapReturnKey; |
165 | } |
166 | |
167 | void KComboBox::setEditUrl(const QUrl &url) |
168 | { |
169 | QComboBox::setEditText(url.toDisplayString()); |
170 | } |
171 | |
172 | void KComboBox::addUrl(const QUrl &url) |
173 | { |
174 | QComboBox::addItem(atext: url.toDisplayString()); |
175 | } |
176 | |
177 | void KComboBox::addUrl(const QIcon &icon, const QUrl &url) |
178 | { |
179 | QComboBox::addItem(aicon: icon, atext: url.toDisplayString()); |
180 | } |
181 | |
182 | void KComboBox::insertUrl(int index, const QUrl &url) |
183 | { |
184 | QComboBox::insertItem(aindex: index, atext: url.toDisplayString()); |
185 | } |
186 | |
187 | void KComboBox::insertUrl(int index, const QIcon &icon, const QUrl &url) |
188 | { |
189 | QComboBox::insertItem(index, icon, text: url.toDisplayString()); |
190 | } |
191 | |
192 | void KComboBox::changeUrl(int index, const QUrl &url) |
193 | { |
194 | QComboBox::setItemText(index, text: url.toDisplayString()); |
195 | } |
196 | |
197 | void 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 | |
203 | void 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 | |
211 | KCompletionBox *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 | |
220 | QSize 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 | |
237 | void 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 *) { |
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 | |
300 | QMenu *KComboBox::() const |
301 | { |
302 | return d_ptr->contextMenu; |
303 | } |
304 | |
305 | void 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 | |
329 | void 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 | |