1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtWidgets module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qlineedit.h" |
41 | #include "qlineedit_p.h" |
42 | |
43 | #include "qvariant.h" |
44 | #if QT_CONFIG(itemviews) |
45 | #include "qabstractitemview.h" |
46 | #endif |
47 | #if QT_CONFIG(draganddrop) |
48 | #include "qdrag.h" |
49 | #endif |
50 | #include "qwidgetaction.h" |
51 | #include "qclipboard.h" |
52 | #ifndef QT_NO_ACCESSIBILITY |
53 | #include "qaccessible.h" |
54 | #endif |
55 | #ifndef QT_NO_IM |
56 | #include "qinputmethod.h" |
57 | #include "qlist.h" |
58 | #endif |
59 | #include <qpainter.h> |
60 | #if QT_CONFIG(animation) |
61 | #include <qpropertyanimation.h> |
62 | #endif |
63 | #include <qstylehints.h> |
64 | #include <qvalidator.h> |
65 | |
66 | QT_BEGIN_NAMESPACE |
67 | |
68 | const int QLineEditPrivate::verticalMargin(1); |
69 | const int QLineEditPrivate::horizontalMargin(2); |
70 | |
71 | // Needs to be kept in sync with QLineEdit::paintEvent |
72 | QRect QLineEditPrivate::adjustedControlRect(const QRect &rect) const |
73 | { |
74 | QRect widgetRect = !rect.isEmpty() ? rect : q_func()->rect(); |
75 | QRect cr = adjustedContentsRect(); |
76 | int cix = cr.x() - hscroll + horizontalMargin; |
77 | return widgetRect.translated(p: QPoint(cix, vscroll - control->ascent() + q_func()->fontMetrics().ascent())); |
78 | } |
79 | |
80 | int QLineEditPrivate::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const |
81 | { |
82 | QRect cr = adjustedContentsRect(); |
83 | x-= cr.x() - hscroll + horizontalMargin; |
84 | return control->xToPos(x, betweenOrOn); |
85 | } |
86 | |
87 | bool QLineEditPrivate::inSelection(int x) const |
88 | { |
89 | x -= adjustedContentsRect().x() - hscroll + horizontalMargin; |
90 | return control->inSelection(x); |
91 | } |
92 | |
93 | QRect QLineEditPrivate::cursorRect() const |
94 | { |
95 | return adjustedControlRect(rect: control->cursorRect()); |
96 | } |
97 | |
98 | #if QT_CONFIG(completer) |
99 | |
100 | void QLineEditPrivate::_q_completionHighlighted(const QString &newText) |
101 | { |
102 | Q_Q(QLineEdit); |
103 | if (control->completer()->completionMode() != QCompleter::InlineCompletion) { |
104 | q->setText(newText); |
105 | } else { |
106 | int c = control->cursor(); |
107 | QString text = control->text(); |
108 | q->setText(text.leftRef(n: c) + newText.midRef(position: c)); |
109 | control->moveCursor(pos: control->end(), mark: false); |
110 | #ifndef Q_OS_ANDROID |
111 | const bool mark = true; |
112 | #else |
113 | const bool mark = (imHints & Qt::ImhNoPredictiveText); |
114 | #endif |
115 | control->moveCursor(pos: c, mark); |
116 | } |
117 | } |
118 | |
119 | #endif // QT_CONFIG(completer) |
120 | |
121 | void QLineEditPrivate::_q_handleWindowActivate() |
122 | { |
123 | Q_Q(QLineEdit); |
124 | if (!q->hasFocus() && control->hasSelectedText()) |
125 | control->deselect(); |
126 | } |
127 | |
128 | void QLineEditPrivate::_q_textEdited(const QString &text) |
129 | { |
130 | Q_Q(QLineEdit); |
131 | edited = true; |
132 | emit q->textEdited(text); |
133 | #if QT_CONFIG(completer) |
134 | if (control->completer() |
135 | && control->completer()->completionMode() != QCompleter::InlineCompletion) |
136 | control->complete(key: -1); // update the popup on cut/paste/del |
137 | #endif |
138 | } |
139 | |
140 | void QLineEditPrivate::_q_cursorPositionChanged(int from, int to) |
141 | { |
142 | Q_Q(QLineEdit); |
143 | q->update(); |
144 | emit q->cursorPositionChanged(from, to); |
145 | } |
146 | |
147 | #ifdef QT_KEYPAD_NAVIGATION |
148 | void QLineEditPrivate::_q_editFocusChange(bool e) |
149 | { |
150 | Q_Q(QLineEdit); |
151 | q->setEditFocus(e); |
152 | } |
153 | #endif |
154 | |
155 | void QLineEditPrivate::_q_selectionChanged() |
156 | { |
157 | Q_Q(QLineEdit); |
158 | if (control->preeditAreaText().isEmpty()) { |
159 | QStyleOptionFrame opt; |
160 | q->initStyleOption(option: &opt); |
161 | bool showCursor = control->hasSelectedText() ? |
162 | q->style()->styleHint(stylehint: QStyle::SH_BlinkCursorWhenTextSelected, opt: &opt, widget: q): |
163 | q->hasFocus(); |
164 | setCursorVisible(showCursor); |
165 | } |
166 | |
167 | emit q->selectionChanged(); |
168 | #ifndef QT_NO_ACCESSIBILITY |
169 | QAccessibleTextSelectionEvent ev(q, control->selectionStart(), control->selectionEnd()); |
170 | ev.setCursorPosition(control->cursorPosition()); |
171 | QAccessible::updateAccessibility(event: &ev); |
172 | #endif |
173 | } |
174 | |
175 | void QLineEditPrivate::_q_updateNeeded(const QRect &rect) |
176 | { |
177 | q_func()->update(adjustedControlRect(rect)); |
178 | } |
179 | |
180 | void QLineEditPrivate::init(const QString& txt) |
181 | { |
182 | Q_Q(QLineEdit); |
183 | control = new QWidgetLineControl(txt); |
184 | control->setParent(q); |
185 | control->setFont(q->font()); |
186 | QObject::connect(sender: control, SIGNAL(textChanged(QString)), |
187 | receiver: q, SIGNAL(textChanged(QString))); |
188 | QObject::connect(sender: control, SIGNAL(textEdited(QString)), |
189 | receiver: q, SLOT(_q_textEdited(QString))); |
190 | QObject::connect(sender: control, SIGNAL(cursorPositionChanged(int,int)), |
191 | receiver: q, SLOT(_q_cursorPositionChanged(int,int))); |
192 | QObject::connect(sender: control, SIGNAL(selectionChanged()), |
193 | receiver: q, SLOT(_q_selectionChanged())); |
194 | QObject::connect(sender: control, SIGNAL(accepted()), |
195 | receiver: q, SIGNAL(returnPressed())); |
196 | QObject::connect(sender: control, SIGNAL(editingFinished()), |
197 | receiver: q, SIGNAL(editingFinished())); |
198 | #ifdef QT_KEYPAD_NAVIGATION |
199 | QObject::connect(control, SIGNAL(editFocusChange(bool)), |
200 | q, SLOT(_q_editFocusChange(bool))); |
201 | #endif |
202 | QObject::connect(sender: control, SIGNAL(cursorPositionChanged(int,int)), |
203 | receiver: q, SLOT(updateMicroFocus())); |
204 | |
205 | QObject::connect(sender: control, SIGNAL(textChanged(QString)), |
206 | receiver: q, SLOT(updateMicroFocus())); |
207 | |
208 | QObject::connect(sender: control, SIGNAL(updateMicroFocus()), |
209 | receiver: q, SLOT(updateMicroFocus())); |
210 | |
211 | // for now, going completely overboard with updates. |
212 | QObject::connect(sender: control, SIGNAL(selectionChanged()), |
213 | receiver: q, SLOT(update())); |
214 | |
215 | QObject::connect(sender: control, SIGNAL(selectionChanged()), |
216 | receiver: q, SLOT(updateMicroFocus())); |
217 | |
218 | QObject::connect(sender: control, SIGNAL(displayTextChanged(QString)), |
219 | receiver: q, SLOT(update())); |
220 | |
221 | QObject::connect(sender: control, SIGNAL(updateNeeded(QRect)), |
222 | receiver: q, SLOT(_q_updateNeeded(QRect))); |
223 | QObject::connect(sender: control, SIGNAL(inputRejected()), receiver: q, SIGNAL(inputRejected())); |
224 | |
225 | QStyleOptionFrame opt; |
226 | q->initStyleOption(option: &opt); |
227 | control->setPasswordCharacter(q->style()->styleHint(stylehint: QStyle::SH_LineEdit_PasswordCharacter, opt: &opt, widget: q)); |
228 | control->setPasswordMaskDelay(q->style()->styleHint(stylehint: QStyle::SH_LineEdit_PasswordMaskDelay, opt: &opt, widget: q)); |
229 | #ifndef QT_NO_CURSOR |
230 | q->setCursor(Qt::IBeamCursor); |
231 | #endif |
232 | q->setFocusPolicy(Qt::StrongFocus); |
233 | q->setAttribute(Qt::WA_InputMethodEnabled); |
234 | // Specifies that this widget can use more, but is able to survive on |
235 | // less, horizontal space; and is fixed vertically. |
236 | q->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed, QSizePolicy::LineEdit)); |
237 | q->setBackgroundRole(QPalette::Base); |
238 | q->setAttribute(Qt::WA_KeyCompression); |
239 | q->setMouseTracking(true); |
240 | q->setAcceptDrops(true); |
241 | |
242 | q->setAttribute(Qt::WA_MacShowFocusRect); |
243 | |
244 | initMouseYThreshold(); |
245 | } |
246 | |
247 | void QLineEditPrivate::initMouseYThreshold() |
248 | { |
249 | mouseYThreshold = QGuiApplication::styleHints()->mouseQuickSelectionThreshold(); |
250 | } |
251 | |
252 | QRect QLineEditPrivate::adjustedContentsRect() const |
253 | { |
254 | Q_Q(const QLineEdit); |
255 | QStyleOptionFrame opt; |
256 | q->initStyleOption(option: &opt); |
257 | QRect r = q->style()->subElementRect(subElement: QStyle::SE_LineEditContents, option: &opt, widget: q); |
258 | r = r.marginsRemoved(margins: effectiveTextMargins()); |
259 | return r; |
260 | } |
261 | |
262 | void QLineEditPrivate::setCursorVisible(bool visible) |
263 | { |
264 | Q_Q(QLineEdit); |
265 | if ((bool)cursorVisible == visible) |
266 | return; |
267 | cursorVisible = visible; |
268 | if (control->inputMask().isEmpty()) |
269 | q->update(cursorRect()); |
270 | else |
271 | q->update(); |
272 | } |
273 | |
274 | void QLineEditPrivate::setText(const QString& text) |
275 | { |
276 | edited = true; |
277 | control->setText(text); |
278 | } |
279 | |
280 | void QLineEditPrivate::updatePasswordEchoEditing(bool editing) |
281 | { |
282 | Q_Q(QLineEdit); |
283 | control->updatePasswordEchoEditing(editing); |
284 | q->setAttribute(Qt::WA_InputMethodEnabled, on: shouldEnableInputMethod()); |
285 | } |
286 | |
287 | void QLineEditPrivate::resetInputMethod() |
288 | { |
289 | Q_Q(QLineEdit); |
290 | if (q->hasFocus() && qApp) { |
291 | QGuiApplication::inputMethod()->reset(); |
292 | } |
293 | } |
294 | |
295 | /*! |
296 | This function is not intended as polymorphic usage. Just a shared code |
297 | fragment that calls QInputMethod::invokeAction for this |
298 | class. |
299 | */ |
300 | bool QLineEditPrivate::sendMouseEventToInputContext( QMouseEvent *e ) |
301 | { |
302 | #if !defined QT_NO_IM |
303 | if ( control->composeMode() ) { |
304 | int tmp_cursor = xToPos(x: e->pos().x()); |
305 | int mousePos = tmp_cursor - control->cursor(); |
306 | if ( mousePos < 0 || mousePos > control->preeditAreaText().length() ) |
307 | mousePos = -1; |
308 | |
309 | if (mousePos >= 0) { |
310 | if (e->type() == QEvent::MouseButtonRelease) |
311 | QGuiApplication::inputMethod()->invokeAction(a: QInputMethod::Click, cursorPosition: mousePos); |
312 | |
313 | return true; |
314 | } |
315 | } |
316 | #else |
317 | Q_UNUSED(e); |
318 | #endif |
319 | |
320 | return false; |
321 | } |
322 | |
323 | #if QT_CONFIG(draganddrop) |
324 | void QLineEditPrivate::drag() |
325 | { |
326 | Q_Q(QLineEdit); |
327 | dndTimer.stop(); |
328 | QMimeData *data = new QMimeData; |
329 | data->setText(control->selectedText()); |
330 | QDrag *drag = new QDrag(q); |
331 | drag->setMimeData(data); |
332 | Qt::DropAction action = drag->exec(supportedActions: Qt::CopyAction); |
333 | if (action == Qt::MoveAction && !control->isReadOnly() && drag->target() != q) |
334 | control->removeSelection(); |
335 | } |
336 | #endif // QT_CONFIG(draganddrop) |
337 | |
338 | |
339 | #if QT_CONFIG(toolbutton) |
340 | QLineEditIconButton::QLineEditIconButton(QWidget *parent) |
341 | : QToolButton(parent) |
342 | , m_opacity(0) |
343 | { |
344 | setFocusPolicy(Qt::NoFocus); |
345 | } |
346 | |
347 | QLineEditPrivate *QLineEditIconButton::lineEditPrivate() const |
348 | { |
349 | QLineEdit *le = qobject_cast<QLineEdit *>(object: parentWidget()); |
350 | return le ? static_cast<QLineEditPrivate *>(qt_widget_private(widget: le)) : nullptr; |
351 | } |
352 | |
353 | void QLineEditIconButton::paintEvent(QPaintEvent *) |
354 | { |
355 | QPainter painter(this); |
356 | QWindow *window = qt_widget_private(widget: this)->windowHandle(mode: QWidgetPrivate::WindowHandleMode::Closest); |
357 | QIcon::Mode state = QIcon::Disabled; |
358 | if (isEnabled()) |
359 | state = isDown() ? QIcon::Active : QIcon::Normal; |
360 | const QLineEditPrivate *lep = lineEditPrivate(); |
361 | const int iconWidth = lep ? lep->sideWidgetParameters().iconSize : 16; |
362 | const QSize iconSize(iconWidth, iconWidth); |
363 | const QPixmap iconPixmap = icon().pixmap(window, size: iconSize, mode: state, state: QIcon::Off); |
364 | QRect pixmapRect = QRect(QPoint(0, 0), iconSize); |
365 | pixmapRect.moveCenter(p: rect().center()); |
366 | painter.setOpacity(m_opacity); |
367 | painter.drawPixmap(r: pixmapRect, pm: iconPixmap); |
368 | } |
369 | |
370 | void QLineEditIconButton::actionEvent(QActionEvent *e) |
371 | { |
372 | switch (e->type()) { |
373 | case QEvent::ActionChanged: { |
374 | const QAction *action = e->action(); |
375 | if (isVisibleTo(parentWidget()) != action->isVisible()) { |
376 | setVisible(action->isVisible()); |
377 | if (QLineEditPrivate *lep = lineEditPrivate()) |
378 | lep->positionSideWidgets(); |
379 | } |
380 | } |
381 | break; |
382 | default: |
383 | break; |
384 | } |
385 | QToolButton::actionEvent(e); |
386 | } |
387 | |
388 | void QLineEditIconButton::setOpacity(qreal value) |
389 | { |
390 | if (!qFuzzyCompare(p1: m_opacity, p2: value)) { |
391 | m_opacity = value; |
392 | updateCursor(); |
393 | update(); |
394 | } |
395 | } |
396 | |
397 | #if QT_CONFIG(animation) |
398 | bool QLineEditIconButton::shouldHideWithText() const |
399 | { |
400 | return m_hideWithText; |
401 | } |
402 | |
403 | void QLineEditIconButton::setHideWithText(bool hide) |
404 | { |
405 | m_hideWithText = hide; |
406 | } |
407 | |
408 | void QLineEditIconButton::onAnimationFinished() |
409 | { |
410 | if (shouldHideWithText() && isVisible() && m_fadingOut) { |
411 | hide(); |
412 | m_fadingOut = false; |
413 | |
414 | // Invalidate previous geometry to take into account new size of side widgets |
415 | if (auto le = lineEditPrivate()) |
416 | le->updateGeometry_helper(forceUpdate: true); |
417 | } |
418 | } |
419 | |
420 | void QLineEditIconButton::animateShow(bool visible) |
421 | { |
422 | m_fadingOut = !visible; |
423 | |
424 | if (shouldHideWithText() && !isVisible()) { |
425 | show(); |
426 | |
427 | // Invalidate previous geometry to take into account new size of side widgets |
428 | if (auto le = lineEditPrivate()) |
429 | le->updateGeometry_helper(forceUpdate: true); |
430 | } |
431 | |
432 | startOpacityAnimation(endValue: visible ? 1.0 : 0.0); |
433 | } |
434 | |
435 | void QLineEditIconButton::startOpacityAnimation(qreal endValue) |
436 | { |
437 | QPropertyAnimation *animation = new QPropertyAnimation(this, QByteArrayLiteral("opacity" )); |
438 | connect(sender: animation, signal: &QPropertyAnimation::finished, receiver: this, slot: &QLineEditIconButton::onAnimationFinished); |
439 | |
440 | animation->setDuration(160); |
441 | animation->setEndValue(endValue); |
442 | animation->start(policy: QAbstractAnimation::DeleteWhenStopped); |
443 | } |
444 | #endif |
445 | |
446 | void QLineEditIconButton::updateCursor() |
447 | { |
448 | #ifndef QT_NO_CURSOR |
449 | setCursor(qFuzzyCompare(p1: m_opacity, p2: qreal(1.0)) || !parentWidget() ? QCursor(Qt::ArrowCursor) : parentWidget()->cursor()); |
450 | #endif |
451 | } |
452 | #endif // QT_CONFIG(toolbutton) |
453 | |
454 | #if QT_CONFIG(animation) && QT_CONFIG(toolbutton) |
455 | static void displayWidgets(const QLineEditPrivate::SideWidgetEntryList &widgets, bool display) |
456 | { |
457 | for (const auto &e : widgets) { |
458 | if (e.flags & QLineEditPrivate::SideWidgetFadeInWithText) |
459 | static_cast<QLineEditIconButton *>(e.widget)->animateShow(visible: display); |
460 | } |
461 | } |
462 | #endif |
463 | |
464 | void QLineEditPrivate::_q_textChanged(const QString &text) |
465 | { |
466 | if (hasSideWidgets()) { |
467 | const int newTextSize = text.size(); |
468 | if (!newTextSize || !lastTextSize) { |
469 | lastTextSize = newTextSize; |
470 | #if QT_CONFIG(animation) && QT_CONFIG(toolbutton) |
471 | const bool display = newTextSize > 0; |
472 | displayWidgets(widgets: leadingSideWidgets, display); |
473 | displayWidgets(widgets: trailingSideWidgets, display); |
474 | #endif |
475 | } |
476 | } |
477 | } |
478 | |
479 | void QLineEditPrivate::_q_clearButtonClicked() |
480 | { |
481 | Q_Q(QLineEdit); |
482 | if (!q->text().isEmpty()) { |
483 | q->clear(); |
484 | _q_textEdited(text: QString()); |
485 | } |
486 | } |
487 | |
488 | QLineEditPrivate::SideWidgetParameters QLineEditPrivate::sideWidgetParameters() const |
489 | { |
490 | Q_Q(const QLineEdit); |
491 | SideWidgetParameters result; |
492 | result.iconSize = q->style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: nullptr, widget: q); |
493 | result.margin = result.iconSize / 4; |
494 | result.widgetWidth = result.iconSize + 6; |
495 | result.widgetHeight = result.iconSize + 2; |
496 | return result; |
497 | } |
498 | |
499 | QIcon QLineEditPrivate::clearButtonIcon() const |
500 | { |
501 | Q_Q(const QLineEdit); |
502 | QStyleOptionFrame styleOption; |
503 | q->initStyleOption(option: &styleOption); |
504 | return q->style()->standardIcon(standardIcon: QStyle::SP_LineEditClearButton, option: &styleOption, widget: q); |
505 | } |
506 | |
507 | void QLineEditPrivate::setClearButtonEnabled(bool enabled) |
508 | { |
509 | #if QT_CONFIG(action) |
510 | for (const SideWidgetEntry &e : trailingSideWidgets) { |
511 | if (e.flags & SideWidgetClearButton) { |
512 | e.action->setEnabled(enabled); |
513 | break; |
514 | } |
515 | } |
516 | #else |
517 | Q_UNUSED(enabled); |
518 | #endif |
519 | } |
520 | |
521 | void QLineEditPrivate::positionSideWidgets() |
522 | { |
523 | Q_Q(QLineEdit); |
524 | if (hasSideWidgets()) { |
525 | const QRect contentRect = q->rect(); |
526 | const SideWidgetParameters p = sideWidgetParameters(); |
527 | const int delta = p.margin + p.widgetWidth; |
528 | QRect widgetGeometry(QPoint(p.margin, (contentRect.height() - p.widgetHeight) / 2), |
529 | QSize(p.widgetWidth, p.widgetHeight)); |
530 | for (const SideWidgetEntry &e : leftSideWidgetList()) { |
531 | e.widget->setGeometry(widgetGeometry); |
532 | #if QT_CONFIG(action) |
533 | if (e.action->isVisible()) |
534 | widgetGeometry.moveLeft(pos: widgetGeometry.left() + delta); |
535 | #else |
536 | Q_UNUSED(delta); |
537 | #endif |
538 | } |
539 | widgetGeometry.moveLeft(pos: contentRect.width() - p.widgetWidth - p.margin); |
540 | for (const SideWidgetEntry &e : rightSideWidgetList()) { |
541 | e.widget->setGeometry(widgetGeometry); |
542 | #if QT_CONFIG(action) |
543 | if (e.action->isVisible()) |
544 | widgetGeometry.moveLeft(pos: widgetGeometry.left() - delta); |
545 | #endif |
546 | } |
547 | } |
548 | } |
549 | |
550 | QLineEditPrivate::SideWidgetLocation QLineEditPrivate::findSideWidget(const QAction *a) const |
551 | { |
552 | int i = 0; |
553 | for (const auto &e : leadingSideWidgets) { |
554 | if (a == e.action) |
555 | return {.position: QLineEdit::LeadingPosition, .index: i}; |
556 | ++i; |
557 | } |
558 | i = 0; |
559 | for (const auto &e : trailingSideWidgets) { |
560 | if (a == e.action) |
561 | return {.position: QLineEdit::TrailingPosition, .index: i}; |
562 | ++i; |
563 | } |
564 | return {.position: QLineEdit::LeadingPosition, .index: -1}; |
565 | } |
566 | |
567 | QWidget *QLineEditPrivate::addAction(QAction *newAction, QAction *before, QLineEdit::ActionPosition position, int flags) |
568 | { |
569 | Q_Q(QLineEdit); |
570 | if (!newAction) |
571 | return nullptr; |
572 | if (!hasSideWidgets()) { // initial setup. |
573 | QObject::connect(sender: q, SIGNAL(textChanged(QString)), receiver: q, SLOT(_q_textChanged(QString))); |
574 | lastTextSize = q->text().size(); |
575 | } |
576 | QWidget *w = nullptr; |
577 | // Store flags about QWidgetAction here since removeAction() may be called from ~QAction, |
578 | // in which a qobject_cast<> no longer works. |
579 | #if QT_CONFIG(action) |
580 | if (QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(object: newAction)) { |
581 | if ((w = widgetAction->requestWidget(parent: q))) |
582 | flags |= SideWidgetCreatedByWidgetAction; |
583 | } |
584 | #endif |
585 | if (!w) { |
586 | #if QT_CONFIG(toolbutton) |
587 | QLineEditIconButton *toolButton = new QLineEditIconButton(q); |
588 | toolButton->setIcon(newAction->icon()); |
589 | toolButton->setOpacity(lastTextSize > 0 || !(flags & SideWidgetFadeInWithText) ? 1 : 0); |
590 | if (flags & SideWidgetClearButton) { |
591 | QObject::connect(sender: toolButton, SIGNAL(clicked()), receiver: q, SLOT(_q_clearButtonClicked())); |
592 | |
593 | #if QT_CONFIG(animation) |
594 | // The clear button is handled only by this widget. The button should be really |
595 | // shown/hidden in order to calculate size hints correctly. |
596 | toolButton->setHideWithText(true); |
597 | #endif |
598 | } |
599 | toolButton->setDefaultAction(newAction); |
600 | w = toolButton; |
601 | #else |
602 | return nullptr; |
603 | #endif |
604 | } |
605 | |
606 | // QTBUG-59957: clear button should be the leftmost action. |
607 | if (!before && !(flags & SideWidgetClearButton) && position == QLineEdit::TrailingPosition) { |
608 | for (const SideWidgetEntry &e : trailingSideWidgets) { |
609 | if (e.flags & SideWidgetClearButton) { |
610 | before = e.action; |
611 | break; |
612 | } |
613 | } |
614 | } |
615 | |
616 | // If there is a 'before' action, it takes preference |
617 | |
618 | // There's a bug in GHS compiler that causes internal error on the following code. |
619 | // The affected GHS compiler versions are 2016.5.4 and 2017.1. GHS internal reference |
620 | // to track the progress of this issue is TOOLS-26637. |
621 | // This temporary workaround allows to compile with GHS toolchain and should be |
622 | // removed when GHS provides a patch to fix the compiler issue. |
623 | |
624 | #if defined(Q_CC_GHS) |
625 | const SideWidgetLocation loc = {position, -1}; |
626 | const auto location = before ? findSideWidget(before) : loc; |
627 | #else |
628 | const auto location = before ? findSideWidget(a: before) : SideWidgetLocation{.position: position, .index: -1}; |
629 | #endif |
630 | |
631 | SideWidgetEntryList &list = location.position == QLineEdit::TrailingPosition ? trailingSideWidgets : leadingSideWidgets; |
632 | list.insert(position: location.isValid() ? list.begin() + location.index : list.end(), |
633 | x: SideWidgetEntry(w, newAction, flags)); |
634 | positionSideWidgets(); |
635 | w->show(); |
636 | return w; |
637 | } |
638 | |
639 | void QLineEditPrivate::removeAction(QAction *action) |
640 | { |
641 | #if QT_CONFIG(action) |
642 | Q_Q(QLineEdit); |
643 | const auto location = findSideWidget(a: action); |
644 | if (!location.isValid()) |
645 | return; |
646 | SideWidgetEntryList &list = location.position == QLineEdit::TrailingPosition ? trailingSideWidgets : leadingSideWidgets; |
647 | SideWidgetEntry entry = list[location.index]; |
648 | list.erase(position: list.begin() + location.index); |
649 | if (entry.flags & SideWidgetCreatedByWidgetAction) |
650 | static_cast<QWidgetAction *>(entry.action)->releaseWidget(widget: entry.widget); |
651 | else |
652 | delete entry.widget; |
653 | positionSideWidgets(); |
654 | if (!hasSideWidgets()) // Last widget, remove connection |
655 | QObject::disconnect(sender: q, SIGNAL(textChanged(QString)), receiver: q, SLOT(_q_textChanged(QString))); |
656 | q->update(); |
657 | #else |
658 | Q_UNUSED(action); |
659 | #endif // QT_CONFIG(action) |
660 | } |
661 | |
662 | static int effectiveTextMargin(int defaultMargin, const QLineEditPrivate::SideWidgetEntryList &widgets, |
663 | const QLineEditPrivate::SideWidgetParameters ¶meters) |
664 | { |
665 | if (widgets.empty()) |
666 | return defaultMargin; |
667 | |
668 | const auto visibleSideWidgetCount = std::count_if(first: widgets.begin(), last: widgets.end(), |
669 | pred: [](const QLineEditPrivate::SideWidgetEntry &e) { |
670 | #if QT_CONFIG(animation) |
671 | // a button that's fading out doesn't get any space |
672 | if (auto* iconButton = qobject_cast<QLineEditIconButton*>(object: e.widget)) |
673 | return iconButton->needsSpace(); |
674 | |
675 | #endif |
676 | return e.widget->isVisibleTo(e.widget->parentWidget()); |
677 | }); |
678 | |
679 | return defaultMargin + (parameters.margin + parameters.widgetWidth) * visibleSideWidgetCount; |
680 | } |
681 | |
682 | QMargins QLineEditPrivate::effectiveTextMargins() const |
683 | { |
684 | return {effectiveTextMargin(defaultMargin: textMargins.left(), widgets: leftSideWidgetList(), parameters: sideWidgetParameters()), |
685 | textMargins.top(), |
686 | effectiveTextMargin(defaultMargin: textMargins.right(), widgets: rightSideWidgetList(), parameters: sideWidgetParameters()), |
687 | textMargins.bottom()}; |
688 | } |
689 | |
690 | |
691 | QT_END_NAMESPACE |
692 | |
693 | #include "moc_qlineedit_p.cpp" |
694 | |