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