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
35KLineEditPrivate::~KLineEditPrivate()
36{
37 // causes a weird crash in KWord at least, so let Qt delete it for us.
38 // delete completionBox;
39}
40
41void 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".
52void 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
63bool KLineEditPrivate::s_backspacePerformsCompletion = false;
64bool KLineEditPrivate::s_initialized = false;
65
66void 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
128KLineEdit::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
136KLineEdit::KLineEdit(QWidget *parent)
137 : QLineEdit(parent)
138 , d_ptr(new KLineEditPrivate(this))
139{
140 Q_D(KLineEdit);
141 d->init();
142}
143
144KLineEdit::~KLineEdit()
145{
146}
147
148QSize 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
165void 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
195void KLineEdit::setCompletionModeDisabled(KCompletion::CompletionMode mode, bool disable)
196{
197 Q_D(KLineEdit);
198 d->disableCompletionMap[mode] = disable;
199}
200
201void 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
221void 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
231void 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
253void 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
291void 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
318void KLineEdit::setSqueezedText(const QString &text)
319{
320 setSqueezedTextEnabled(true);
321 setText(text);
322}
323
324void KLineEdit::setSqueezedTextEnabled(bool enable)
325{
326 Q_D(KLineEdit);
327 d->enableSqueezedText = enable;
328}
329
330bool KLineEdit::isSqueezedTextEnabled() const
331{
332 Q_D(const KLineEdit);
333 return d->enableSqueezedText;
334}
335
336void 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
348void 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
416void KLineEdit::copy() const
417{
418 Q_D(const KLineEdit);
419 if (!d->copySqueezedText(copy: true)) {
420 QLineEdit::copy();
421 }
422}
423
424bool 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
455void 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
465void 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
830void 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
842void 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
864void 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
877void KLineEditPrivate::_k_tripleClickTimeout()
878{
879 possibleTripleClick = false;
880}
881
882QMenu *KLineEdit::createStandardContextMenu()
883{
884 Q_D(KLineEdit);
885 QMenu *popup = 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 *subMenu = 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
974void KLineEdit::contextMenuEvent(QContextMenuEvent *e)
975{
976 if (QLineEdit::contextMenuPolicy() != Qt::DefaultContextMenu) {
977 return;
978 }
979 QMenu *popup = 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
990void KLineEditPrivate::_k_completionMenuActivated(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
1023bool 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
1053bool KLineEdit::urlDropsEnabled() const
1054{
1055 Q_D(const KLineEdit);
1056 return d->handleURLDrops;
1057}
1058
1059void KLineEdit::setTrapReturnKey(bool trap)
1060{
1061 Q_D(KLineEdit);
1062 d->trapReturnKeyEvents = trap;
1063}
1064
1065bool KLineEdit::trapReturnKey() const
1066{
1067 Q_D(const KLineEdit);
1068 return d->trapReturnKeyEvents;
1069}
1070
1071void KLineEdit::setUrl(const QUrl &url)
1072{
1073 setText(url.toDisplayString());
1074}
1075
1076void 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 */
1100static 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
1111void 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
1130bool 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
1216void 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
1274KCompletionBox *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
1286void 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
1304void 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
1330void KLineEditPrivate::_k_restoreSelectionColors()
1331{
1332 Q_Q(KLineEdit);
1333 if (disableRestoreSelection) {
1334 return;
1335 }
1336
1337 q->setUserSelection(true);
1338}
1339
1340void 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
1350QString 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
1360QString KLineEdit::userText() const
1361{
1362 Q_D(const KLineEdit);
1363 return d->userText;
1364}
1365
1366bool KLineEdit::autoSuggest() const
1367{
1368 Q_D(const KLineEdit);
1369 return d->autoSuggest;
1370}
1371
1372void 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
1399void 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

source code of kcompletion/src/klineedit.cpp