1 | /* |
2 | This file is part of the KDE libraries |
3 | |
4 | SPDX-FileCopyrightText: 1997 Sven Radej <sven.radej@iname.com> |
5 | SPDX-FileCopyrightText: 1999 Patrick Ward <PAT_WARD@HP-USA-om5.om.hp.com> |
6 | SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org> |
7 | |
8 | Re-designed for KDE 2.x by |
9 | SPDX-FileCopyrightText: 2000, 2001 Dawit Alemayehu <adawit@kde.org> |
10 | SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org> |
11 | |
12 | SPDX-License-Identifier: LGPL-2.0-or-later |
13 | */ |
14 | |
15 | #include "klineedit.h" |
16 | #include "klineedit_p.h" |
17 | |
18 | #include <KAuthorized> |
19 | #include <KConfigGroup> |
20 | #include <KCursor> |
21 | #include <KLineEditUrlDropEventFilter> |
22 | #include <KSharedConfig> |
23 | #include <KStandardShortcut> |
24 | |
25 | #include <kcompletionbox.h> |
26 | |
27 | #include <QActionGroup> |
28 | #include <QApplication> |
29 | #include <QClipboard> |
30 | #include <QKeyEvent> |
31 | #include <QMenu> |
32 | #include <QTimer> |
33 | #include <QToolTip> |
34 | |
35 | KLineEditPrivate::~KLineEditPrivate() |
36 | { |
37 | // causes a weird crash in KWord at least, so let Qt delete it for us. |
38 | // delete completionBox; |
39 | } |
40 | |
41 | void KLineEditPrivate::_k_textChanged(const QString &text) |
42 | { |
43 | Q_Q(KLineEdit); |
44 | // COMPAT (as documented): emit userTextChanged whenever textChanged is emitted |
45 | if (!completionRunning && (text != userText)) { |
46 | userText = text; |
47 | } |
48 | } |
49 | |
50 | // Call this when a completion operation changes the lineedit text |
51 | // "as if it had been edited by the user". |
52 | void KLineEditPrivate::updateUserText(const QString &text) |
53 | { |
54 | Q_Q(KLineEdit); |
55 | if (!completionRunning && (text != userText)) { |
56 | userText = text; |
57 | q->setModified(true); |
58 | Q_EMIT q->textEdited(text); |
59 | Q_EMIT q->textChanged(text); |
60 | } |
61 | } |
62 | |
63 | bool KLineEditPrivate::s_backspacePerformsCompletion = false; |
64 | bool KLineEditPrivate::s_initialized = false; |
65 | |
66 | void KLineEditPrivate::init() |
67 | { |
68 | Q_Q(KLineEdit); |
69 | //--- |
70 | completionBox = nullptr; |
71 | handleURLDrops = true; |
72 | trapReturnKeyEvents = false; |
73 | |
74 | userSelection = true; |
75 | autoSuggest = false; |
76 | disableRestoreSelection = false; |
77 | enableSqueezedText = false; |
78 | |
79 | completionRunning = false; |
80 | if (!s_initialized) { |
81 | KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("General" )); |
82 | s_backspacePerformsCompletion = config.readEntry(key: "Backspace performs completion" , defaultValue: false); |
83 | s_initialized = true; |
84 | } |
85 | |
86 | urlDropEventFilter = new KLineEditUrlDropEventFilter(q); |
87 | |
88 | // i18n: Placeholder text in line edit widgets is the text appearing |
89 | // before any user input, briefly explaining to the user what to type |
90 | // (e.g. "Enter search pattern"). |
91 | // By default the text is set in italic, which may not be appropriate |
92 | // for some languages and scripts (e.g. for CJK ideographs). |
93 | QString metaMsg = KLineEdit::tr(s: "1" , c: "Italic placeholder text in line edits: 0 no, 1 yes" ); |
94 | italicizePlaceholder = (metaMsg.trimmed() != QLatin1Char('0')); |
95 | //--- |
96 | possibleTripleClick = false; |
97 | bgRole = q->backgroundRole(); |
98 | |
99 | // Enable the context menu by default. |
100 | q->QLineEdit::setContextMenuPolicy(Qt::DefaultContextMenu); |
101 | KCursor::setAutoHideCursor(w: q, enable: true, customEventFilter: true); |
102 | |
103 | KCompletion::CompletionMode mode = q->completionMode(); |
104 | autoSuggest = (mode == KCompletion::CompletionMan // |
105 | || mode == KCompletion::CompletionPopupAuto // |
106 | || mode == KCompletion::CompletionAuto); |
107 | q->connect(sender: q, signal: &KLineEdit::selectionChanged, context: q, slot: [this]() { |
108 | _k_restoreSelectionColors(); |
109 | }); |
110 | |
111 | if (handleURLDrops) { |
112 | q->installEventFilter(filterObj: urlDropEventFilter); |
113 | } |
114 | |
115 | const QPalette p = q->palette(); |
116 | if (!previousHighlightedTextColor.isValid()) { |
117 | previousHighlightedTextColor = p.color(cg: QPalette::Normal, cr: QPalette::HighlightedText); |
118 | } |
119 | if (!previousHighlightColor.isValid()) { |
120 | previousHighlightColor = p.color(cg: QPalette::Normal, cr: QPalette::Highlight); |
121 | } |
122 | |
123 | q->connect(sender: q, signal: &KLineEdit::textChanged, context: q, slot: [this](const QString &text) { |
124 | _k_textChanged(text); |
125 | }); |
126 | } |
127 | |
128 | KLineEdit::KLineEdit(const QString &string, QWidget *parent) |
129 | : QLineEdit(string, parent) |
130 | , d_ptr(new KLineEditPrivate(this)) |
131 | { |
132 | Q_D(KLineEdit); |
133 | d->init(); |
134 | } |
135 | |
136 | KLineEdit::KLineEdit(QWidget *parent) |
137 | : QLineEdit(parent) |
138 | , d_ptr(new KLineEditPrivate(this)) |
139 | { |
140 | Q_D(KLineEdit); |
141 | d->init(); |
142 | } |
143 | |
144 | KLineEdit::~KLineEdit() |
145 | { |
146 | } |
147 | |
148 | QSize KLineEdit::clearButtonUsedSize() const |
149 | { |
150 | QSize s; |
151 | |
152 | if (isClearButtonEnabled()) { |
153 | // from qlineedit_p.cpp |
154 | |
155 | const int iconSize = height() < 34 ? 16 : 32; |
156 | const int buttonWidth = iconSize + 6; |
157 | const int buttonHeight = iconSize + 2; |
158 | |
159 | s = QSize(buttonWidth, buttonHeight); |
160 | } |
161 | |
162 | return s; |
163 | } |
164 | |
165 | void KLineEdit::setCompletionMode(KCompletion::CompletionMode mode) |
166 | { |
167 | Q_D(KLineEdit); |
168 | KCompletion::CompletionMode oldMode = completionMode(); |
169 | |
170 | if (oldMode != mode // |
171 | && (oldMode == KCompletion::CompletionPopup || oldMode == KCompletion::CompletionPopupAuto) // |
172 | && d->completionBox && d->completionBox->isVisible()) { |
173 | d->completionBox->hide(); |
174 | } |
175 | |
176 | // If the widgets echo mode is not Normal, no completion |
177 | // feature will be enabled even if one is requested. |
178 | if (echoMode() != QLineEdit::Normal) { |
179 | mode = KCompletion::CompletionNone; // Override the request. |
180 | } |
181 | |
182 | if (!KAuthorized::authorize(QStringLiteral("lineedit_text_completion" ))) { |
183 | mode = KCompletion::CompletionNone; |
184 | } |
185 | |
186 | if (mode == KCompletion::CompletionPopupAuto || mode == KCompletion::CompletionAuto || mode == KCompletion::CompletionMan) { |
187 | d->autoSuggest = true; |
188 | } else { |
189 | d->autoSuggest = false; |
190 | } |
191 | |
192 | KCompletionBase::setCompletionMode(mode); |
193 | } |
194 | |
195 | void KLineEdit::setCompletionModeDisabled(KCompletion::CompletionMode mode, bool disable) |
196 | { |
197 | Q_D(KLineEdit); |
198 | d->disableCompletionMap[mode] = disable; |
199 | } |
200 | |
201 | void KLineEdit::setCompletedText(const QString &t, bool marked) |
202 | { |
203 | Q_D(KLineEdit); |
204 | if (!d->autoSuggest) { |
205 | return; |
206 | } |
207 | |
208 | const QString txt = text(); |
209 | |
210 | if (t != txt) { |
211 | setText(t); |
212 | if (marked) { |
213 | setSelection(t.length(), txt.length() - t.length()); |
214 | } |
215 | setUserSelection(false); |
216 | } else { |
217 | setUserSelection(true); |
218 | } |
219 | } |
220 | |
221 | void KLineEdit::setCompletedText(const QString &text) |
222 | { |
223 | KCompletion::CompletionMode mode = completionMode(); |
224 | const bool marked = (mode == KCompletion::CompletionAuto // |
225 | || mode == KCompletion::CompletionMan // |
226 | || mode == KCompletion::CompletionPopup // |
227 | || mode == KCompletion::CompletionPopupAuto); |
228 | setCompletedText(t: text, marked); |
229 | } |
230 | |
231 | void KLineEdit::rotateText(KCompletionBase::KeyBindingType type) |
232 | { |
233 | KCompletion *comp = compObj(); |
234 | if (comp && // |
235 | (type == KCompletionBase::PrevCompletionMatch // |
236 | || type == KCompletionBase::NextCompletionMatch)) { |
237 | QString input; |
238 | |
239 | if (type == KCompletionBase::PrevCompletionMatch) { |
240 | input = comp->previousMatch(); |
241 | } else { |
242 | input = comp->nextMatch(); |
243 | } |
244 | |
245 | // Skip rotation if previous/next match is null or the same text |
246 | if (input.isEmpty() || input == displayText()) { |
247 | return; |
248 | } |
249 | setCompletedText(t: input, marked: hasSelectedText()); |
250 | } |
251 | } |
252 | |
253 | void KLineEdit::makeCompletion(const QString &text) |
254 | { |
255 | Q_D(KLineEdit); |
256 | KCompletion *comp = compObj(); |
257 | KCompletion::CompletionMode mode = completionMode(); |
258 | |
259 | if (!comp || mode == KCompletion::CompletionNone) { |
260 | return; // No completion object... |
261 | } |
262 | |
263 | const QString match = comp->makeCompletion(string: text); |
264 | |
265 | if (mode == KCompletion::CompletionPopup || mode == KCompletion::CompletionPopupAuto) { |
266 | if (match.isEmpty()) { |
267 | if (d->completionBox) { |
268 | d->completionBox->hide(); |
269 | d->completionBox->clear(); |
270 | } |
271 | } else { |
272 | setCompletedItems(items: comp->allMatches(), autoSuggest: comp->shouldAutoSuggest()); |
273 | } |
274 | } else { // Auto, ShortAuto (Man) and Shell |
275 | // all other completion modes |
276 | // If no match or the same match, simply return without completing. |
277 | if (match.isEmpty() || match == text) { |
278 | return; |
279 | } |
280 | |
281 | if (mode != KCompletion::CompletionShell) { |
282 | setUserSelection(false); |
283 | } |
284 | |
285 | if (d->autoSuggest) { |
286 | setCompletedText(match); |
287 | } |
288 | } |
289 | } |
290 | |
291 | void KLineEdit::setReadOnly(bool readOnly) |
292 | { |
293 | Q_D(KLineEdit); |
294 | // Do not do anything if nothing changed... |
295 | if (readOnly == isReadOnly()) { |
296 | return; |
297 | } |
298 | |
299 | QLineEdit::setReadOnly(readOnly); |
300 | |
301 | if (readOnly) { |
302 | d->bgRole = backgroundRole(); |
303 | setBackgroundRole(QPalette::Window); |
304 | if (d->enableSqueezedText && d->squeezedText.isEmpty()) { |
305 | d->squeezedText = text(); |
306 | d->setSqueezedText(); |
307 | } |
308 | } else { |
309 | if (!d->squeezedText.isEmpty()) { |
310 | setText(d->squeezedText); |
311 | d->squeezedText.clear(); |
312 | } |
313 | |
314 | setBackgroundRole(d->bgRole); |
315 | } |
316 | } |
317 | |
318 | void KLineEdit::setSqueezedText(const QString &text) |
319 | { |
320 | setSqueezedTextEnabled(true); |
321 | setText(text); |
322 | } |
323 | |
324 | void KLineEdit::setSqueezedTextEnabled(bool enable) |
325 | { |
326 | Q_D(KLineEdit); |
327 | d->enableSqueezedText = enable; |
328 | } |
329 | |
330 | bool KLineEdit::isSqueezedTextEnabled() const |
331 | { |
332 | Q_D(const KLineEdit); |
333 | return d->enableSqueezedText; |
334 | } |
335 | |
336 | void KLineEdit::setText(const QString &text) |
337 | { |
338 | Q_D(KLineEdit); |
339 | if (d->enableSqueezedText && isReadOnly()) { |
340 | d->squeezedText = text; |
341 | d->setSqueezedText(); |
342 | return; |
343 | } |
344 | |
345 | QLineEdit::setText(text); |
346 | } |
347 | |
348 | void KLineEditPrivate::setSqueezedText() |
349 | { |
350 | Q_Q(KLineEdit); |
351 | squeezedStart = 0; |
352 | squeezedEnd = 0; |
353 | const QString fullText = squeezedText; |
354 | const int fullLength = fullText.length(); |
355 | const QFontMetrics fm(q->fontMetrics()); |
356 | const int labelWidth = q->size().width() - 2 * q->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth) - 2; |
357 | const int textWidth = fm.boundingRect(text: fullText).width(); |
358 | |
359 | // TODO: investigate use of QFontMetrics::elidedText for this |
360 | if (textWidth > labelWidth) { |
361 | const QStringView sview{fullText}; |
362 | // TODO: better would be "…" char (0x2026), but for that one would need to ensure it's from the main font, |
363 | // otherwise if resulting in use of a new fallback font this can affect the metrics of the complete text, |
364 | // resulting in shifted characters |
365 | const QString ellipsisText = QStringLiteral("..." ); |
366 | // start with the dots only |
367 | QString squeezedText = ellipsisText; |
368 | int squeezedWidth = fm.boundingRect(text: squeezedText).width(); |
369 | |
370 | // estimate how many letters we can add to the dots on both sides |
371 | int letters = fullText.length() * (labelWidth - squeezedWidth) / textWidth / 2; |
372 | squeezedText = sview.left(n: letters) + ellipsisText + sview.right(n: letters); |
373 | squeezedWidth = fm.boundingRect(text: squeezedText).width(); |
374 | |
375 | if (squeezedWidth < labelWidth) { |
376 | // we estimated too short |
377 | // add letters while text < label |
378 | do { |
379 | letters++; |
380 | squeezedText = sview.left(n: letters) + ellipsisText + sview.right(n: letters); |
381 | squeezedWidth = fm.boundingRect(text: squeezedText).width(); |
382 | } while (squeezedWidth < labelWidth && letters <= fullLength / 2); |
383 | letters--; |
384 | squeezedText = sview.left(n: letters) + ellipsisText + sview.right(n: letters); |
385 | } else if (squeezedWidth > labelWidth) { |
386 | // we estimated too long |
387 | // remove letters while text > label |
388 | do { |
389 | letters--; |
390 | squeezedText = sview.left(n: letters) + ellipsisText + sview.right(n: letters); |
391 | squeezedWidth = fm.boundingRect(text: squeezedText).width(); |
392 | } while (squeezedWidth > labelWidth && letters >= 5); |
393 | } |
394 | |
395 | if (letters < 5) { |
396 | // too few letters added -> we give up squeezing |
397 | q->QLineEdit::setText(fullText); |
398 | } else { |
399 | q->QLineEdit::setText(squeezedText); |
400 | squeezedStart = letters; |
401 | squeezedEnd = fullText.length() - letters; |
402 | } |
403 | |
404 | q->setToolTip(fullText); |
405 | |
406 | } else { |
407 | q->QLineEdit::setText(fullText); |
408 | |
409 | q->setToolTip(QString()); |
410 | QToolTip::showText(pos: q->pos(), text: QString()); // hide |
411 | } |
412 | |
413 | q->setCursorPosition(0); |
414 | } |
415 | |
416 | void KLineEdit::copy() const |
417 | { |
418 | Q_D(const KLineEdit); |
419 | if (!d->copySqueezedText(copy: true)) { |
420 | QLineEdit::copy(); |
421 | } |
422 | } |
423 | |
424 | bool KLineEditPrivate::copySqueezedText(bool copy) const |
425 | { |
426 | Q_Q(const KLineEdit); |
427 | if (!squeezedText.isEmpty() && squeezedStart) { |
428 | KLineEdit *that = const_cast<KLineEdit *>(q); |
429 | if (!that->hasSelectedText()) { |
430 | return false; |
431 | } |
432 | int start = q->selectionStart(); |
433 | int end = start + q->selectedText().length(); |
434 | if (start >= squeezedStart + 3) { |
435 | start = start - 3 - squeezedStart + squeezedEnd; |
436 | } else if (start > squeezedStart) { |
437 | start = squeezedStart; |
438 | } |
439 | if (end >= squeezedStart + 3) { |
440 | end = end - 3 - squeezedStart + squeezedEnd; |
441 | } else if (end > squeezedStart) { |
442 | end = squeezedEnd; |
443 | } |
444 | if (start == end) { |
445 | return false; |
446 | } |
447 | QString t = squeezedText; |
448 | t = t.mid(position: start, n: end - start); |
449 | QApplication::clipboard()->setText(t, mode: copy ? QClipboard::Clipboard : QClipboard::Selection); |
450 | return true; |
451 | } |
452 | return false; |
453 | } |
454 | |
455 | void KLineEdit::resizeEvent(QResizeEvent *ev) |
456 | { |
457 | Q_D(KLineEdit); |
458 | if (!d->squeezedText.isEmpty()) { |
459 | d->setSqueezedText(); |
460 | } |
461 | |
462 | QLineEdit::resizeEvent(event: ev); |
463 | } |
464 | |
465 | void KLineEdit::keyPressEvent(QKeyEvent *e) |
466 | { |
467 | Q_D(KLineEdit); |
468 | const int key = e->key() | e->modifiers(); |
469 | |
470 | if (KStandardShortcut::copy().contains(t: key)) { |
471 | copy(); |
472 | return; |
473 | } else if (KStandardShortcut::paste().contains(t: key)) { |
474 | // TODO: |
475 | // we should restore the original text (not autocompleted), otherwise the paste |
476 | // will get into troubles Bug: 134691 |
477 | if (!isReadOnly()) { |
478 | paste(); |
479 | } |
480 | return; |
481 | } else if (KStandardShortcut::pasteSelection().contains(t: key)) { |
482 | QString text = QApplication::clipboard()->text(mode: QClipboard::Selection); |
483 | insert(text); |
484 | deselect(); |
485 | return; |
486 | } else if (KStandardShortcut::cut().contains(t: key)) { |
487 | if (!isReadOnly()) { |
488 | cut(); |
489 | } |
490 | return; |
491 | } else if (KStandardShortcut::undo().contains(t: key)) { |
492 | if (!isReadOnly()) { |
493 | undo(); |
494 | } |
495 | return; |
496 | } else if (KStandardShortcut::redo().contains(t: key)) { |
497 | if (!isReadOnly()) { |
498 | redo(); |
499 | } |
500 | return; |
501 | } else if (KStandardShortcut::deleteWordBack().contains(t: key)) { |
502 | cursorWordBackward(mark: true); |
503 | if (hasSelectedText() && !isReadOnly()) { |
504 | del(); |
505 | } |
506 | |
507 | e->accept(); |
508 | return; |
509 | } else if (KStandardShortcut::deleteWordForward().contains(t: key)) { |
510 | // Workaround for QT bug where |
511 | cursorWordForward(mark: true); |
512 | if (hasSelectedText() && !isReadOnly()) { |
513 | del(); |
514 | } |
515 | |
516 | e->accept(); |
517 | return; |
518 | } else if (KStandardShortcut::backwardWord().contains(t: key)) { |
519 | cursorWordBackward(mark: false); |
520 | e->accept(); |
521 | return; |
522 | } else if (KStandardShortcut::forwardWord().contains(t: key)) { |
523 | cursorWordForward(mark: false); |
524 | e->accept(); |
525 | return; |
526 | } else if (KStandardShortcut::beginningOfLine().contains(t: key)) { |
527 | home(mark: false); |
528 | e->accept(); |
529 | return; |
530 | } else if (KStandardShortcut::endOfLine().contains(t: key)) { |
531 | end(mark: false); |
532 | e->accept(); |
533 | return; |
534 | } |
535 | |
536 | // Filter key-events if EchoMode is normal and |
537 | // completion mode is not set to CompletionNone |
538 | if (echoMode() == QLineEdit::Normal && completionMode() != KCompletion::CompletionNone) { |
539 | if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { |
540 | const bool trap = (d->completionBox && d->completionBox->isVisible()); |
541 | const bool stopEvent = (trap |
542 | || (d->trapReturnKeyEvents // |
543 | && (e->modifiers() == Qt::NoButton || // |
544 | e->modifiers() == Qt::KeypadModifier))); |
545 | |
546 | if (stopEvent) { |
547 | Q_EMIT QLineEdit::returnPressed(); |
548 | e->accept(); |
549 | } |
550 | Q_EMIT returnKeyPressed(text: displayText()); |
551 | if (trap) { |
552 | d->completionBox->hide(); |
553 | deselect(); |
554 | setCursorPosition(text().length()); |
555 | } |
556 | |
557 | // Eat the event if the user asked for it, or if a completionbox was visible |
558 | if (stopEvent) { |
559 | return; |
560 | } |
561 | } |
562 | |
563 | const KeyBindingMap keys = keyBindingMap(); |
564 | const KCompletion::CompletionMode mode = completionMode(); |
565 | const bool noModifier = (e->modifiers() == Qt::NoButton // |
566 | || e->modifiers() == Qt::ShiftModifier // |
567 | || e->modifiers() == Qt::KeypadModifier); |
568 | |
569 | if ((mode == KCompletion::CompletionAuto // |
570 | || mode == KCompletion::CompletionPopupAuto // |
571 | || mode == KCompletion::CompletionMan) // |
572 | && noModifier) { |
573 | if (!d->userSelection && hasSelectedText() // |
574 | && (e->key() == Qt::Key_Right || e->key() == Qt::Key_Left) // |
575 | && e->modifiers() == Qt::NoButton) { |
576 | const QString old_txt = text(); |
577 | d->disableRestoreSelection = true; |
578 | const int start = selectionStart(); |
579 | |
580 | deselect(); |
581 | QLineEdit::keyPressEvent(e); |
582 | const int cPosition = cursorPosition(); |
583 | setText(old_txt); |
584 | |
585 | // keep cursor at cPosition |
586 | setSelection(old_txt.length(), cPosition - old_txt.length()); |
587 | if (e->key() == Qt::Key_Right && cPosition > start) { |
588 | // the user explicitly accepted the autocompletion |
589 | d->updateUserText(text: text()); |
590 | } |
591 | |
592 | d->disableRestoreSelection = false; |
593 | return; |
594 | } |
595 | |
596 | if (e->key() == Qt::Key_Escape) { |
597 | if (hasSelectedText() && !d->userSelection) { |
598 | del(); |
599 | setUserSelection(true); |
600 | } |
601 | |
602 | // Don't swallow the Escape press event for the case |
603 | // of dialogs, which have Escape associated to Cancel |
604 | e->ignore(); |
605 | return; |
606 | } |
607 | } |
608 | |
609 | if ((mode == KCompletion::CompletionAuto // |
610 | || mode == KCompletion::CompletionMan) |
611 | && noModifier) { |
612 | const QString keycode = e->text(); |
613 | if (!keycode.isEmpty() |
614 | && (keycode.unicode()->isPrint() // |
615 | || e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete)) { |
616 | const bool hasUserSelection = d->userSelection; |
617 | const bool hadSelection = hasSelectedText(); |
618 | |
619 | bool cursorNotAtEnd = false; |
620 | |
621 | const int start = selectionStart(); |
622 | const int cPos = cursorPosition(); |
623 | |
624 | // When moving the cursor, we want to keep the autocompletion as an |
625 | // autocompletion, so we want to process events at the cursor position |
626 | // as if there was no selection. After processing the key event, we |
627 | // can set the new autocompletion again. |
628 | if (hadSelection && !hasUserSelection && start > cPos) { |
629 | del(); |
630 | setCursorPosition(cPos); |
631 | cursorNotAtEnd = true; |
632 | } |
633 | |
634 | d->disableRestoreSelection = true; |
635 | QLineEdit::keyPressEvent(e); |
636 | d->disableRestoreSelection = false; |
637 | |
638 | QString txt = text(); |
639 | int len = txt.length(); |
640 | if (!hasSelectedText() && len /*&& cursorPosition() == len */) { |
641 | if (e->key() == Qt::Key_Backspace) { |
642 | if (hadSelection && !hasUserSelection && !cursorNotAtEnd) { |
643 | backspace(); |
644 | txt = text(); |
645 | len = txt.length(); |
646 | } |
647 | |
648 | if (!d->s_backspacePerformsCompletion || !len) { |
649 | d->autoSuggest = false; |
650 | } |
651 | } |
652 | |
653 | if (e->key() == Qt::Key_Delete) { |
654 | d->autoSuggest = false; |
655 | } |
656 | |
657 | doCompletion(text: txt); |
658 | |
659 | if ((e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete)) { |
660 | d->autoSuggest = true; |
661 | } |
662 | |
663 | e->accept(); |
664 | } |
665 | |
666 | return; |
667 | } |
668 | |
669 | } else if ((mode == KCompletion::CompletionPopup || mode == KCompletion::CompletionPopupAuto) // |
670 | && noModifier && !e->text().isEmpty()) { |
671 | const QString old_txt = text(); |
672 | const bool hasUserSelection = d->userSelection; |
673 | const bool hadSelection = hasSelectedText(); |
674 | bool cursorNotAtEnd = false; |
675 | |
676 | const int start = selectionStart(); |
677 | const int cPos = cursorPosition(); |
678 | const QString keycode = e->text(); |
679 | |
680 | // When moving the cursor, we want to keep the autocompletion as an |
681 | // autocompletion, so we want to process events at the cursor position |
682 | // as if there was no selection. After processing the key event, we |
683 | // can set the new autocompletion again. |
684 | if (hadSelection && !hasUserSelection && start > cPos |
685 | && ((!keycode.isEmpty() && keycode.unicode()->isPrint()) // |
686 | || e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete)) { |
687 | del(); |
688 | setCursorPosition(cPos); |
689 | cursorNotAtEnd = true; |
690 | } |
691 | |
692 | const int selectedLength = selectedText().length(); |
693 | |
694 | d->disableRestoreSelection = true; |
695 | QLineEdit::keyPressEvent(e); |
696 | d->disableRestoreSelection = false; |
697 | |
698 | if ((selectedLength != selectedText().length()) && !hasUserSelection) { |
699 | d->_k_restoreSelectionColors(); // and set userSelection to true |
700 | } |
701 | |
702 | QString txt = text(); |
703 | int len = txt.length(); |
704 | if ((txt != old_txt || txt != e->text()) && len /* && ( cursorPosition() == len || force )*/ |
705 | && ((!keycode.isEmpty() && keycode.unicode()->isPrint()) // |
706 | || e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete)) { |
707 | if (e->key() == Qt::Key_Backspace) { |
708 | if (hadSelection && !hasUserSelection && !cursorNotAtEnd) { |
709 | backspace(); |
710 | txt = text(); |
711 | len = txt.length(); |
712 | } |
713 | |
714 | if (!d->s_backspacePerformsCompletion) { |
715 | d->autoSuggest = false; |
716 | } |
717 | } |
718 | |
719 | if (e->key() == Qt::Key_Delete) { |
720 | d->autoSuggest = false; |
721 | } |
722 | |
723 | if (d->completionBox) { |
724 | d->completionBox->setCancelledText(txt); |
725 | } |
726 | |
727 | doCompletion(text: txt); |
728 | |
729 | if ((e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) // |
730 | && mode == KCompletion::CompletionPopupAuto) { |
731 | d->autoSuggest = true; |
732 | } |
733 | |
734 | e->accept(); |
735 | } else if (!len && d->completionBox && d->completionBox->isVisible()) { |
736 | d->completionBox->hide(); |
737 | } |
738 | |
739 | return; |
740 | } else if (mode == KCompletion::CompletionShell) { |
741 | // Handles completion. |
742 | QList<QKeySequence> cut; |
743 | if (keys[TextCompletion].isEmpty()) { |
744 | cut = KStandardShortcut::shortcut(id: KStandardShortcut::TextCompletion); |
745 | } else { |
746 | cut = keys[TextCompletion]; |
747 | } |
748 | |
749 | if (cut.contains(t: key)) { |
750 | // Emit completion if the completion mode is CompletionShell |
751 | // and the cursor is at the end of the string. |
752 | const QString txt = text(); |
753 | const int len = txt.length(); |
754 | if (cursorPosition() == len && len != 0) { |
755 | doCompletion(text: txt); |
756 | return; |
757 | } |
758 | } else if (d->completionBox) { |
759 | d->completionBox->hide(); |
760 | } |
761 | } |
762 | |
763 | // handle rotation |
764 | // Handles previous match |
765 | QList<QKeySequence> cut; |
766 | if (keys[PrevCompletionMatch].isEmpty()) { |
767 | cut = KStandardShortcut::shortcut(id: KStandardShortcut::PrevCompletion); |
768 | } else { |
769 | cut = keys[PrevCompletionMatch]; |
770 | } |
771 | |
772 | if (cut.contains(t: key)) { |
773 | if (emitSignals()) { |
774 | Q_EMIT textRotation(KCompletionBase::PrevCompletionMatch); |
775 | } |
776 | if (handleSignals()) { |
777 | rotateText(type: KCompletionBase::PrevCompletionMatch); |
778 | } |
779 | return; |
780 | } |
781 | |
782 | // Handles next match |
783 | if (keys[NextCompletionMatch].isEmpty()) { |
784 | cut = KStandardShortcut::shortcut(id: KStandardShortcut::NextCompletion); |
785 | } else { |
786 | cut = keys[NextCompletionMatch]; |
787 | } |
788 | |
789 | if (cut.contains(t: key)) { |
790 | if (emitSignals()) { |
791 | Q_EMIT textRotation(KCompletionBase::NextCompletionMatch); |
792 | } |
793 | if (handleSignals()) { |
794 | rotateText(type: KCompletionBase::NextCompletionMatch); |
795 | } |
796 | return; |
797 | } |
798 | |
799 | // substring completion |
800 | if (compObj()) { |
801 | QList<QKeySequence> cut; |
802 | if (keys[SubstringCompletion].isEmpty()) { |
803 | cut = KStandardShortcut::shortcut(id: KStandardShortcut::SubstringCompletion); |
804 | } else { |
805 | cut = keys[SubstringCompletion]; |
806 | } |
807 | |
808 | if (cut.contains(t: key)) { |
809 | if (emitSignals()) { |
810 | Q_EMIT substringCompletion(text()); |
811 | } |
812 | if (handleSignals()) { |
813 | setCompletedItems(items: compObj()->substringCompletion(string: text())); |
814 | e->accept(); |
815 | } |
816 | return; |
817 | } |
818 | } |
819 | } |
820 | const int selectedLength = selectedText().length(); |
821 | |
822 | // Let QLineEdit handle any other keys events. |
823 | QLineEdit::keyPressEvent(e); |
824 | |
825 | if (selectedLength != selectedText().length()) { |
826 | d->_k_restoreSelectionColors(); // and set userSelection to true |
827 | } |
828 | } |
829 | |
830 | void KLineEdit::mouseDoubleClickEvent(QMouseEvent *e) |
831 | { |
832 | Q_D(KLineEdit); |
833 | if (e->button() == Qt::LeftButton) { |
834 | d->possibleTripleClick = true; |
835 | QTimer::singleShot(interval: QApplication::doubleClickInterval(), receiver: this, slot: [d]() { |
836 | d->_k_tripleClickTimeout(); |
837 | }); |
838 | } |
839 | QLineEdit::mouseDoubleClickEvent(e); |
840 | } |
841 | |
842 | void KLineEdit::mousePressEvent(QMouseEvent *e) |
843 | { |
844 | Q_D(KLineEdit); |
845 | if (e->button() == Qt::LeftButton && d->possibleTripleClick) { |
846 | selectAll(); |
847 | e->accept(); |
848 | return; |
849 | } |
850 | |
851 | // if middle clicking and if text is present in the clipboard then clear the selection |
852 | // to prepare paste operation |
853 | if (e->button() == Qt::MiddleButton) { |
854 | if (hasSelectedText() && !isReadOnly()) { |
855 | if (QApplication::clipboard()->text(mode: QClipboard::Selection).length() > 0) { |
856 | backspace(); |
857 | } |
858 | } |
859 | } |
860 | |
861 | QLineEdit::mousePressEvent(e); |
862 | } |
863 | |
864 | void KLineEdit::mouseReleaseEvent(QMouseEvent *e) |
865 | { |
866 | Q_D(KLineEdit); |
867 | QLineEdit::mouseReleaseEvent(e); |
868 | |
869 | if (QApplication::clipboard()->supportsSelection()) { |
870 | if (e->button() == Qt::LeftButton) { |
871 | // Fix copying of squeezed text if needed |
872 | d->copySqueezedText(copy: false); |
873 | } |
874 | } |
875 | } |
876 | |
877 | void KLineEditPrivate::_k_tripleClickTimeout() |
878 | { |
879 | possibleTripleClick = false; |
880 | } |
881 | |
882 | QMenu *KLineEdit::createStandardContextMenu() |
883 | { |
884 | Q_D(KLineEdit); |
885 | QMenu * = QLineEdit::createStandardContextMenu(); |
886 | |
887 | if (!isReadOnly()) { |
888 | // FIXME: This code depends on Qt's action ordering. |
889 | const QList<QAction *> actionList = popup->actions(); |
890 | enum { |
891 | UndoAct, |
892 | RedoAct, |
893 | Separator1, |
894 | CutAct, |
895 | CopyAct, |
896 | PasteAct, |
897 | DeleteAct, |
898 | ClearAct, |
899 | Separator2, |
900 | SelectAllAct, |
901 | NCountActs, |
902 | }; |
903 | QAction *separatorAction = nullptr; |
904 | // separator we want is right after Delete right now. |
905 | const int idx = actionList.indexOf(t: actionList[DeleteAct]) + 1; |
906 | if (idx < actionList.count()) { |
907 | separatorAction = actionList.at(i: idx); |
908 | } |
909 | if (separatorAction) { |
910 | QAction *clearAllAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear" )), tr(s: "C&lear" , c: "@action:inmenu" ), this); |
911 | clearAllAction->setShortcuts(QKeySequence::keyBindings(key: QKeySequence::DeleteCompleteLine)); |
912 | connect(sender: clearAllAction, signal: &QAction::triggered, context: this, slot: &QLineEdit::clear); |
913 | if (text().isEmpty()) { |
914 | clearAllAction->setEnabled(false); |
915 | } |
916 | popup->insertAction(before: separatorAction, action: clearAllAction); |
917 | } |
918 | } |
919 | |
920 | // If a completion object is present and the input |
921 | // widget is not read-only, show the Text Completion |
922 | // menu item. |
923 | if (compObj() && !isReadOnly() && KAuthorized::authorize(QStringLiteral("lineedit_text_completion" ))) { |
924 | QMenu * = popup->addMenu(icon: QIcon::fromTheme(QStringLiteral("text-completion" )), title: tr(s: "Text Completion" , c: "@title:menu" )); |
925 | connect(sender: subMenu, signal: &QMenu::triggered, context: this, slot: [d](QAction *action) { |
926 | d->_k_completionMenuActivated(act: action); |
927 | }); |
928 | |
929 | popup->addSeparator(); |
930 | |
931 | QActionGroup *ag = new QActionGroup(this); |
932 | d->noCompletionAction = ag->addAction(text: tr(s: "None" , c: "@item:inmenu Text Completion" )); |
933 | d->shellCompletionAction = ag->addAction(text: tr(s: "Manual" , c: "@item:inmenu Text Completion" )); |
934 | d->autoCompletionAction = ag->addAction(text: tr(s: "Automatic" , c: "@item:inmenu Text Completion" )); |
935 | d->popupCompletionAction = ag->addAction(text: tr(s: "Dropdown List" , c: "@item:inmenu Text Completion" )); |
936 | d->shortAutoCompletionAction = ag->addAction(text: tr(s: "Short Automatic" , c: "@item:inmenu Text Completion" )); |
937 | d->popupAutoCompletionAction = ag->addAction(text: tr(s: "Dropdown List && Automatic" , c: "@item:inmenu Text Completion" )); |
938 | subMenu->addActions(actions: ag->actions()); |
939 | |
940 | // subMenu->setAccel( KStandardShortcut::completion(), ShellCompletion ); |
941 | |
942 | d->shellCompletionAction->setCheckable(true); |
943 | d->noCompletionAction->setCheckable(true); |
944 | d->popupCompletionAction->setCheckable(true); |
945 | d->autoCompletionAction->setCheckable(true); |
946 | d->shortAutoCompletionAction->setCheckable(true); |
947 | d->popupAutoCompletionAction->setCheckable(true); |
948 | |
949 | d->shellCompletionAction->setEnabled(!d->disableCompletionMap[KCompletion::CompletionShell]); |
950 | d->noCompletionAction->setEnabled(!d->disableCompletionMap[KCompletion::CompletionNone]); |
951 | d->popupCompletionAction->setEnabled(!d->disableCompletionMap[KCompletion::CompletionPopup]); |
952 | d->autoCompletionAction->setEnabled(!d->disableCompletionMap[KCompletion::CompletionAuto]); |
953 | d->shortAutoCompletionAction->setEnabled(!d->disableCompletionMap[KCompletion::CompletionMan]); |
954 | d->popupAutoCompletionAction->setEnabled(!d->disableCompletionMap[KCompletion::CompletionPopupAuto]); |
955 | |
956 | const KCompletion::CompletionMode mode = completionMode(); |
957 | d->noCompletionAction->setChecked(mode == KCompletion::CompletionNone); |
958 | d->shellCompletionAction->setChecked(mode == KCompletion::CompletionShell); |
959 | d->popupCompletionAction->setChecked(mode == KCompletion::CompletionPopup); |
960 | d->autoCompletionAction->setChecked(mode == KCompletion::CompletionAuto); |
961 | d->shortAutoCompletionAction->setChecked(mode == KCompletion::CompletionMan); |
962 | d->popupAutoCompletionAction->setChecked(mode == KCompletion::CompletionPopupAuto); |
963 | |
964 | const KCompletion::CompletionMode defaultMode = KCompletion::CompletionPopup; |
965 | if (mode != defaultMode && !d->disableCompletionMap[defaultMode]) { |
966 | subMenu->addSeparator(); |
967 | d->defaultAction = subMenu->addAction(text: tr(s: "Default" , c: "@item:inmenu Text Completion" )); |
968 | } |
969 | } |
970 | |
971 | return popup; |
972 | } |
973 | |
974 | void KLineEdit::(QContextMenuEvent *e) |
975 | { |
976 | if (QLineEdit::contextMenuPolicy() != Qt::DefaultContextMenu) { |
977 | return; |
978 | } |
979 | QMenu * = createStandardContextMenu(); |
980 | |
981 | // ### do we really need this? Yes, Please do not remove! This |
982 | // allows applications to extend the popup menu without having to |
983 | // inherit from this class! (DA) |
984 | Q_EMIT aboutToShowContextMenu(contextMenu: popup); |
985 | |
986 | popup->exec(pos: e->globalPos()); |
987 | delete popup; |
988 | } |
989 | |
990 | void KLineEditPrivate::(QAction *act) |
991 | { |
992 | Q_Q(KLineEdit); |
993 | KCompletion::CompletionMode oldMode = q->completionMode(); |
994 | |
995 | if (act == noCompletionAction) { |
996 | q->setCompletionMode(KCompletion::CompletionNone); |
997 | } else if (act == shellCompletionAction) { |
998 | q->setCompletionMode(KCompletion::CompletionShell); |
999 | } else if (act == autoCompletionAction) { |
1000 | q->setCompletionMode(KCompletion::CompletionAuto); |
1001 | } else if (act == popupCompletionAction) { |
1002 | q->setCompletionMode(KCompletion::CompletionPopup); |
1003 | } else if (act == shortAutoCompletionAction) { |
1004 | q->setCompletionMode(KCompletion::CompletionMan); |
1005 | } else if (act == popupAutoCompletionAction) { |
1006 | q->setCompletionMode(KCompletion::CompletionPopupAuto); |
1007 | } else if (act == defaultAction) { |
1008 | q->setCompletionMode(KCompletion::CompletionPopup); |
1009 | } else { |
1010 | return; |
1011 | } |
1012 | |
1013 | if (oldMode != q->completionMode()) { |
1014 | if ((oldMode == KCompletion::CompletionPopup || oldMode == KCompletion::CompletionPopupAuto) // |
1015 | && completionBox // |
1016 | && completionBox->isVisible()) { |
1017 | completionBox->hide(); |
1018 | } |
1019 | Q_EMIT q->completionModeChanged(q->completionMode()); |
1020 | } |
1021 | } |
1022 | |
1023 | bool KLineEdit::event(QEvent *ev) |
1024 | { |
1025 | Q_D(KLineEdit); |
1026 | KCursor::autoHideEventFilter(this, ev); |
1027 | if (ev->type() == QEvent::ShortcutOverride) { |
1028 | QKeyEvent *e = static_cast<QKeyEvent *>(ev); |
1029 | if (d->overrideShortcut(e)) { |
1030 | ev->accept(); |
1031 | } |
1032 | } else if (ev->type() == QEvent::ApplicationPaletteChange || ev->type() == QEvent::PaletteChange) { |
1033 | // Assume the widget uses the application's palette |
1034 | QPalette p = QApplication::palette(); |
1035 | d->previousHighlightedTextColor = p.color(cg: QPalette::Normal, cr: QPalette::HighlightedText); |
1036 | d->previousHighlightColor = p.color(cg: QPalette::Normal, cr: QPalette::Highlight); |
1037 | setUserSelection(d->userSelection); |
1038 | } else if (ev->type() == QEvent::ChildAdded) { |
1039 | QObject *obj = static_cast<QChildEvent *>(ev)->child(); |
1040 | if (obj) { |
1041 | connect(sender: obj, signal: &QObject::objectNameChanged, context: this, slot: [this, obj] { |
1042 | if (obj->objectName() == QLatin1String("_q_qlineeditclearaction" )) { |
1043 | QAction *action = qobject_cast<QAction *>(object: obj); |
1044 | connect(sender: action, signal: &QAction::triggered, context: this, slot: &KLineEdit::clearButtonClicked); |
1045 | } |
1046 | }); |
1047 | } |
1048 | } |
1049 | |
1050 | return QLineEdit::event(ev); |
1051 | } |
1052 | |
1053 | bool KLineEdit::urlDropsEnabled() const |
1054 | { |
1055 | Q_D(const KLineEdit); |
1056 | return d->handleURLDrops; |
1057 | } |
1058 | |
1059 | void KLineEdit::setTrapReturnKey(bool trap) |
1060 | { |
1061 | Q_D(KLineEdit); |
1062 | d->trapReturnKeyEvents = trap; |
1063 | } |
1064 | |
1065 | bool KLineEdit::trapReturnKey() const |
1066 | { |
1067 | Q_D(const KLineEdit); |
1068 | return d->trapReturnKeyEvents; |
1069 | } |
1070 | |
1071 | void KLineEdit::setUrl(const QUrl &url) |
1072 | { |
1073 | setText(url.toDisplayString()); |
1074 | } |
1075 | |
1076 | void KLineEdit::setCompletionBox(KCompletionBox *box) |
1077 | { |
1078 | Q_D(KLineEdit); |
1079 | if (d->completionBox) { |
1080 | return; |
1081 | } |
1082 | |
1083 | d->completionBox = box; |
1084 | if (handleSignals()) { |
1085 | connect(sender: d->completionBox, signal: &KCompletionBox::currentTextChanged, context: this, slot: [d](const QString &text) { |
1086 | d->_k_completionBoxTextChanged(text); |
1087 | }); |
1088 | |
1089 | connect(sender: d->completionBox, signal: &KCompletionBox::userCancelled, context: this, slot: &KLineEdit::userCancelled); |
1090 | |
1091 | connect(sender: d->completionBox, signal: &KCompletionBox::textActivated, context: this, slot: &KLineEdit::completionBoxActivated); |
1092 | connect(sender: d->completionBox, signal: &KCompletionBox::textActivated, context: this, slot: &KLineEdit::textEdited); |
1093 | } |
1094 | } |
1095 | |
1096 | /* |
1097 | * Set the line edit text without changing the modified flag. By default |
1098 | * calling setText resets the modified flag to false. |
1099 | */ |
1100 | static void setEditText(KLineEdit *edit, const QString &text) |
1101 | { |
1102 | if (!edit) { |
1103 | return; |
1104 | } |
1105 | |
1106 | const bool wasModified = edit->isModified(); |
1107 | edit->setText(text); |
1108 | edit->setModified(wasModified); |
1109 | } |
1110 | |
1111 | void KLineEdit::userCancelled(const QString &cancelText) |
1112 | { |
1113 | Q_D(KLineEdit); |
1114 | if (completionMode() != KCompletion::CompletionPopupAuto) { |
1115 | setEditText(edit: this, text: cancelText); |
1116 | } else if (hasSelectedText()) { |
1117 | if (d->userSelection) { |
1118 | deselect(); |
1119 | } else { |
1120 | d->autoSuggest = false; |
1121 | const int start = selectionStart(); |
1122 | const QString s = text().remove(i: selectionStart(), len: selectedText().length()); |
1123 | setEditText(edit: this, text: s); |
1124 | setCursorPosition(start); |
1125 | d->autoSuggest = true; |
1126 | } |
1127 | } |
1128 | } |
1129 | |
1130 | bool KLineEditPrivate::overrideShortcut(const QKeyEvent *e) |
1131 | { |
1132 | Q_Q(KLineEdit); |
1133 | QList<QKeySequence> scKey; |
1134 | |
1135 | const int key = e->key() | e->modifiers(); |
1136 | const KLineEdit::KeyBindingMap keys = q->keyBindingMap(); |
1137 | |
1138 | if (keys[KLineEdit::TextCompletion].isEmpty()) { |
1139 | scKey = KStandardShortcut::shortcut(id: KStandardShortcut::TextCompletion); |
1140 | } else { |
1141 | scKey = keys[KLineEdit::TextCompletion]; |
1142 | } |
1143 | |
1144 | if (scKey.contains(t: key)) { |
1145 | return true; |
1146 | } |
1147 | |
1148 | if (keys[KLineEdit::NextCompletionMatch].isEmpty()) { |
1149 | scKey = KStandardShortcut::shortcut(id: KStandardShortcut::NextCompletion); |
1150 | } else { |
1151 | scKey = keys[KLineEdit::NextCompletionMatch]; |
1152 | } |
1153 | |
1154 | if (scKey.contains(t: key)) { |
1155 | return true; |
1156 | } |
1157 | |
1158 | if (keys[KLineEdit::PrevCompletionMatch].isEmpty()) { |
1159 | scKey = KStandardShortcut::shortcut(id: KStandardShortcut::PrevCompletion); |
1160 | } else { |
1161 | scKey = keys[KLineEdit::PrevCompletionMatch]; |
1162 | } |
1163 | |
1164 | if (scKey.contains(t: key)) { |
1165 | return true; |
1166 | } |
1167 | |
1168 | constexpr int ctrlE = QKeyCombination(Qt::CTRL | Qt::Key_E).toCombined(); |
1169 | constexpr int ctrlU = QKeyCombination(Qt::CTRL | Qt::Key_U).toCombined(); |
1170 | |
1171 | // Override all the text manupilation accelerators... |
1172 | if (KStandardShortcut::copy().contains(t: key)) { |
1173 | return true; |
1174 | } else if (KStandardShortcut::paste().contains(t: key)) { |
1175 | return true; |
1176 | } else if (KStandardShortcut::cut().contains(t: key)) { |
1177 | return true; |
1178 | } else if (KStandardShortcut::undo().contains(t: key)) { |
1179 | return true; |
1180 | } else if (KStandardShortcut::redo().contains(t: key)) { |
1181 | return true; |
1182 | } else if (KStandardShortcut::deleteWordBack().contains(t: key)) { |
1183 | return true; |
1184 | } else if (KStandardShortcut::deleteWordForward().contains(t: key)) { |
1185 | return true; |
1186 | } else if (KStandardShortcut::forwardWord().contains(t: key)) { |
1187 | return true; |
1188 | } else if (KStandardShortcut::backwardWord().contains(t: key)) { |
1189 | return true; |
1190 | } else if (KStandardShortcut::beginningOfLine().contains(t: key)) { |
1191 | return true; |
1192 | } else if (KStandardShortcut::endOfLine().contains(t: key)) { |
1193 | return true; |
1194 | } |
1195 | |
1196 | // Shortcut overrides for shortcuts that QLineEdit handles |
1197 | // but doesn't dare force as "stronger than kaction shortcuts"... |
1198 | else if (e->matches(key: QKeySequence::SelectAll)) { |
1199 | return true; |
1200 | } else if (qApp->platformName() == QLatin1String("xcb" ) && (key == ctrlE || key == ctrlU)) { |
1201 | return true; |
1202 | } |
1203 | |
1204 | if (completionBox && completionBox->isVisible()) { |
1205 | const int key = e->key(); |
1206 | const Qt::KeyboardModifiers modifiers = e->modifiers(); |
1207 | if ((key == Qt::Key_Backtab || key == Qt::Key_Tab) // |
1208 | && (modifiers == Qt::NoModifier || (modifiers & Qt::ShiftModifier))) { |
1209 | return true; |
1210 | } |
1211 | } |
1212 | |
1213 | return false; |
1214 | } |
1215 | |
1216 | void KLineEdit::setCompletedItems(const QStringList &items, bool autoSuggest) |
1217 | { |
1218 | Q_D(KLineEdit); |
1219 | QString txt; |
1220 | if (d->completionBox && d->completionBox->isVisible()) { |
1221 | // The popup is visible already - do the matching on the initial string, |
1222 | // not on the currently selected one. |
1223 | txt = completionBox()->cancelledText(); |
1224 | } else { |
1225 | txt = text(); |
1226 | } |
1227 | |
1228 | if (!items.isEmpty() // |
1229 | && !(items.count() == 1 && txt == items.first())) { |
1230 | // create completion box if non-existent |
1231 | completionBox(); |
1232 | |
1233 | if (d->completionBox->isVisible()) { |
1234 | QListWidgetItem *currentItem = d->completionBox->currentItem(); |
1235 | |
1236 | QString currentSelection; |
1237 | if (currentItem != nullptr) { |
1238 | currentSelection = currentItem->text(); |
1239 | } |
1240 | |
1241 | d->completionBox->setItems(items); |
1242 | |
1243 | const QList<QListWidgetItem *> matchedItems = d->completionBox->findItems(text: currentSelection, flags: Qt::MatchExactly); |
1244 | QListWidgetItem *matchedItem = matchedItems.isEmpty() ? nullptr : matchedItems.first(); |
1245 | |
1246 | if (matchedItem) { |
1247 | const bool blocked = d->completionBox->blockSignals(b: true); |
1248 | d->completionBox->setCurrentItem(matchedItem); |
1249 | d->completionBox->blockSignals(b: blocked); |
1250 | } else { |
1251 | d->completionBox->setCurrentRow(-1); |
1252 | } |
1253 | } else { // completion box not visible yet -> show it |
1254 | if (!txt.isEmpty()) { |
1255 | d->completionBox->setCancelledText(txt); |
1256 | } |
1257 | d->completionBox->setItems(items); |
1258 | d->completionBox->popup(); |
1259 | } |
1260 | |
1261 | if (d->autoSuggest && autoSuggest) { |
1262 | const int index = items.first().indexOf(s: txt); |
1263 | const QString newText = items.first().mid(position: index); |
1264 | setUserSelection(false); // can be removed? setCompletedText sets it anyway |
1265 | setCompletedText(t: newText, marked: true); |
1266 | } |
1267 | } else { |
1268 | if (d->completionBox && d->completionBox->isVisible()) { |
1269 | d->completionBox->hide(); |
1270 | } |
1271 | } |
1272 | } |
1273 | |
1274 | KCompletionBox *KLineEdit::completionBox(bool create) |
1275 | { |
1276 | Q_D(KLineEdit); |
1277 | if (create && !d->completionBox) { |
1278 | setCompletionBox(new KCompletionBox(this)); |
1279 | d->completionBox->setObjectName(QStringLiteral("completion box" )); |
1280 | d->completionBox->setFont(font()); |
1281 | } |
1282 | |
1283 | return d->completionBox; |
1284 | } |
1285 | |
1286 | void KLineEdit::setCompletionObject(KCompletion *comp, bool handle) |
1287 | { |
1288 | Q_D(class KLineEdit); |
1289 | |
1290 | KCompletion *oldComp = compObj(); |
1291 | if (oldComp && handleSignals()) { |
1292 | disconnect(d->m_matchesConnection); |
1293 | } |
1294 | |
1295 | if (comp && handle) { |
1296 | d->m_matchesConnection = connect(sender: comp, signal: &KCompletion::matches, context: this, slot: [this](const QStringList &list) { |
1297 | setCompletedItems(items: list); |
1298 | }); |
1299 | } |
1300 | |
1301 | KCompletionBase::setCompletionObject(completionObject: comp, handleSignals: handle); |
1302 | } |
1303 | |
1304 | void KLineEdit::setUserSelection(bool userSelection) |
1305 | { |
1306 | Q_D(KLineEdit); |
1307 | // if !d->userSelection && userSelection we are accepting a completion, |
1308 | // so trigger an update |
1309 | |
1310 | if (!d->userSelection && userSelection) { |
1311 | d->updateUserText(text: text()); |
1312 | } |
1313 | |
1314 | QPalette p = palette(); |
1315 | |
1316 | if (userSelection) { |
1317 | p.setColor(acr: QPalette::Highlight, acolor: d->previousHighlightColor); |
1318 | p.setColor(acr: QPalette::HighlightedText, acolor: d->previousHighlightedTextColor); |
1319 | } else { |
1320 | QColor color = p.color(cg: QPalette::Disabled, cr: QPalette::Text); |
1321 | p.setColor(acr: QPalette::HighlightedText, acolor: color); |
1322 | color = p.color(cg: QPalette::Active, cr: QPalette::Base); |
1323 | p.setColor(acr: QPalette::Highlight, acolor: color); |
1324 | } |
1325 | |
1326 | d->userSelection = userSelection; |
1327 | setPalette(p); |
1328 | } |
1329 | |
1330 | void KLineEditPrivate::_k_restoreSelectionColors() |
1331 | { |
1332 | Q_Q(KLineEdit); |
1333 | if (disableRestoreSelection) { |
1334 | return; |
1335 | } |
1336 | |
1337 | q->setUserSelection(true); |
1338 | } |
1339 | |
1340 | void KLineEditPrivate::_k_completionBoxTextChanged(const QString &text) |
1341 | { |
1342 | Q_Q(KLineEdit); |
1343 | if (!text.isEmpty()) { |
1344 | q->setText(text); |
1345 | q->setModified(true); |
1346 | q->end(mark: false); // force cursor at end |
1347 | } |
1348 | } |
1349 | |
1350 | QString KLineEdit::originalText() const |
1351 | { |
1352 | Q_D(const KLineEdit); |
1353 | if (d->enableSqueezedText && isReadOnly()) { |
1354 | return d->squeezedText; |
1355 | } |
1356 | |
1357 | return text(); |
1358 | } |
1359 | |
1360 | QString KLineEdit::userText() const |
1361 | { |
1362 | Q_D(const KLineEdit); |
1363 | return d->userText; |
1364 | } |
1365 | |
1366 | bool KLineEdit::autoSuggest() const |
1367 | { |
1368 | Q_D(const KLineEdit); |
1369 | return d->autoSuggest; |
1370 | } |
1371 | |
1372 | void KLineEdit::paintEvent(QPaintEvent *ev) |
1373 | { |
1374 | Q_D(KLineEdit); |
1375 | if (echoMode() == Password && d->threeStars) { |
1376 | // ### hack alert! |
1377 | // QLineEdit has currently no hooks to modify the displayed string. |
1378 | // When we call setText(), an update() is triggered and we get |
1379 | // into an infinite recursion. |
1380 | // Qt offers the setUpdatesEnabled() method, but when we re-enable |
1381 | // them, update() is triggered, and we get into the same recursion. |
1382 | // To work around this problem, we set/clear the internal Qt flag which |
1383 | // marks the updatesDisabled state manually. |
1384 | setAttribute(Qt::WA_UpdatesDisabled, on: true); |
1385 | blockSignals(b: true); |
1386 | const QString oldText = text(); |
1387 | const bool isModifiedState = isModified(); // save modified state because setText resets it |
1388 | setText(oldText + oldText + oldText); |
1389 | QLineEdit::paintEvent(ev); |
1390 | setText(oldText); |
1391 | setModified(isModifiedState); |
1392 | blockSignals(b: false); |
1393 | setAttribute(Qt::WA_UpdatesDisabled, on: false); |
1394 | } else { |
1395 | QLineEdit::paintEvent(ev); |
1396 | } |
1397 | } |
1398 | |
1399 | void KLineEdit::doCompletion(const QString &text) |
1400 | { |
1401 | Q_D(KLineEdit); |
1402 | if (emitSignals()) { |
1403 | Q_EMIT completion(text); // emit when requested... |
1404 | } |
1405 | d->completionRunning = true; |
1406 | if (handleSignals()) { |
1407 | makeCompletion(text); // handle when requested... |
1408 | } |
1409 | d->completionRunning = false; |
1410 | } |
1411 | |
1412 | #include "moc_klineedit.cpp" |
1413 | |