| 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 | |