From eba5b547e7f1c3817d172936293e03535225f8ee Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 1 Oct 2016 21:08:09 +0200 Subject: Add ComboBox::editable [ChangeLog][Controls][ComboBox] Added editable property Task-number: QTBUG-53876 Change-Id: I1cb035b3bb4c63f7935f08298814005fad51b5eb Reviewed-by: Mitch Curtis --- src/imports/controls/ComboBox.qml | 40 +- .../doc/src/includes/inputmethodhints.qdocinc | 38 ++ src/imports/controls/material/ComboBox.qml | 39 +- src/imports/controls/universal/ComboBox.qml | 44 ++- src/quicktemplates2/qquickcombobox.cpp | 430 ++++++++++++++++++++- src/quicktemplates2/qquickcombobox_p.h | 36 ++ 6 files changed, 588 insertions(+), 39 deletions(-) create mode 100644 src/imports/controls/doc/src/includes/inputmethodhints.qdocinc (limited to 'src') diff --git a/src/imports/controls/ComboBox.qml b/src/imports/controls/ComboBox.qml index 4944906a..b1497a12 100644 --- a/src/imports/controls/ComboBox.qml +++ b/src/imports/controls/ComboBox.qml @@ -64,40 +64,54 @@ T.ComboBox { indicator: Image { x: control.mirrored ? control.padding : control.width - width - control.padding y: control.topPadding + (control.availableHeight - height) / 2 - source: "image://default/double-arrow/" + (control.visualFocus ? Default.focusColor : Default.textColor) + source: "image://default/double-arrow/" + (!control.editable && control.visualFocus ? Default.focusColor : Default.textColor) sourceSize.width: width sourceSize.height: height opacity: enabled ? 1 : 0.3 } - contentItem: Text { - leftPadding: control.mirrored ? 0 : 12 - rightPadding: control.mirrored ? 12 : 0 + contentItem: T.TextField { + leftPadding: !control.mirrored ? 12 : control.editable && activeFocus ? 3 : 1 + rightPadding: control.mirrored ? 12 : control.editable && activeFocus ? 3 : 1 topPadding: 6 - control.padding bottomPadding: 6 - control.padding - text: control.displayText + text: control.editable ? control.editText : control.displayText + + enabled: control.editable + autoScroll: control.editable + readOnly: control.popup.visible + inputMethodHints: control.inputMethodHints + validator: control.validator + font: control.font - color: control.visualFocus ? Default.focusColor : Default.textColor + color: !control.editable && control.visualFocus ? Default.focusColor : Default.textColor + selectionColor: Default.focusColor + selectedTextColor: Default.textLightColor horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - opacity: enabled ? 1 : 0.3 + opacity: control.enabled ? 1 : 0.3 + + background: Rectangle { + visible: control.editable && !control.flat + border.width: parent && parent.activeFocus ? 2 : 1 + border.color: parent && parent.activeFocus ? Default.focusColor : Default.buttonColor + } } background: Rectangle { implicitWidth: 120 implicitHeight: 40 - color: control.visualFocus ? (control.pressed ? Default.focusPressedColor : Default.focusLightColor) : - (control.down ? Default.buttonPressedColor : Default.buttonColor) + color: !control.editable && control.visualFocus ? (control.pressed ? Default.focusPressedColor : Default.focusLightColor) : + (control.down || popup.visible ? Default.buttonPressedColor : Default.buttonColor) border.color: Default.focusColor - border.width: control.visualFocus ? 2 : 0 - visible: !control.flat || control.pressed + border.width: !control.editable && control.visualFocus ? 2 : 0 + visible: !control.flat || control.down } popup: T.Popup { - y: control.height - (control.visualFocus ? 0 : 1) + y: control.height width: control.width implicitHeight: contentItem.implicitHeight topMargin: 6 diff --git a/src/imports/controls/doc/src/includes/inputmethodhints.qdocinc b/src/imports/controls/doc/src/includes/inputmethodhints.qdocinc new file mode 100644 index 00000000..73710e1e --- /dev/null +++ b/src/imports/controls/doc/src/includes/inputmethodhints.qdocinc @@ -0,0 +1,38 @@ +//! [flags] +The value is a bit-wise combination of flags or \c Qt.ImhNone if no hints are set. + +Flags that alter behavior are: + +\list +\li Qt.ImhHiddenText - Characters should be hidden, as is typically used when entering passwords. +\li Qt.ImhSensitiveData - Typed text should not be stored by the active input method + in any persistent storage like predictive user dictionary. +\li Qt.ImhNoAutoUppercase - The input method should not try to automatically switch to upper case + when a sentence ends. +\li Qt.ImhPreferNumbers - Numbers are preferred (but not required). +\li Qt.ImhPreferUppercase - Upper case letters are preferred (but not required). +\li Qt.ImhPreferLowercase - Lower case letters are preferred (but not required). +\li Qt.ImhNoPredictiveText - Do not use predictive text (i.e. dictionary lookup) while typing. + +\li Qt.ImhDate - The text editor functions as a date field. +\li Qt.ImhTime - The text editor functions as a time field. +\endlist + +Flags that restrict input (exclusive flags) are: + +\list +\li Qt.ImhDigitsOnly - Only digits are allowed. +\li Qt.ImhFormattedNumbersOnly - Only number input is allowed. This includes decimal point and minus sign. +\li Qt.ImhUppercaseOnly - Only upper case letter input is allowed. +\li Qt.ImhLowercaseOnly - Only lower case letter input is allowed. +\li Qt.ImhDialableCharactersOnly - Only characters suitable for phone dialing are allowed. +\li Qt.ImhEmailCharactersOnly - Only characters suitable for email addresses are allowed. +\li Qt.ImhUrlCharactersOnly - Only characters suitable for URLs are allowed. +\endlist + +Masks: + +\list +\li Qt.ImhExclusiveInputMask - This mask yields nonzero if any of the exclusive flags are used. +\endlist +//! [flags] diff --git a/src/imports/controls/material/ComboBox.qml b/src/imports/controls/material/ComboBox.qml index 50175cbe..8f41a481 100644 --- a/src/imports/controls/material/ComboBox.qml +++ b/src/imports/controls/material/ComboBox.qml @@ -73,17 +73,27 @@ T.ComboBox { source: "image://material/drop-indicator/" + (control.enabled ? control.Material.foreground : control.Material.hintTextColor) } - contentItem: Text { + contentItem: T.TextField { padding: 6 - leftPadding: control.mirrored ? 0 : 12 - rightPadding: control.mirrored ? 12 : 0 + leftPadding: control.editable ? 2 : control.mirrored ? 0 : 12 + rightPadding: control.editable ? 2 : control.mirrored ? 12 : 0 + + text: control.editable ? control.editText : control.displayText + + enabled: control.editable + autoScroll: control.editable + readOnly: control.popup.visible + inputMethodHints: control.inputMethodHints + validator: control.validator - text: control.displayText font: control.font color: control.enabled ? control.Material.foreground : control.Material.hintTextColor + selectionColor: control.Material.accentColor + selectedTextColor: control.Material.primaryHighlightedTextColor horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + + cursorDelegate: CursorDelegate { } } background: Rectangle { @@ -94,32 +104,43 @@ T.ComboBox { y: 6 height: parent.height - 12 radius: control.flat ? 0 : 2 - color: control.Material.dialogColor + color: !control.editable ? control.Material.dialogColor : "transparent" Behavior on color { + enabled: !control.editable ColorAnimation { duration: 400 } } - layer.enabled: control.enabled && control.Material.background.a > 0 + layer.enabled: control.enabled && !control.editable && control.Material.background.a > 0 layer.effect: ElevationEffect { elevation: control.Material.elevation } + Rectangle { + visible: control.editable + y: parent.y + control.baselineOffset + width: parent.width + height: control.activeFocus ? 2 : 1 + color: control.editable && control.activeFocus ? control.Material.accentColor : control.Material.hintTextColor + } + Ripple { clip: control.flat clipRadius: control.flat ? 0 : 2 - width: parent.width + x: control.editable && control.indicator ? control.indicator.x : 0 + width: control.editable && control.indicator ? control.indicator.width : parent.width height: parent.height pressed: control.pressed - anchor: control + anchor: control.editable && control.indicator ? control.indicator : control active: control.pressed || control.visualFocus || control.hovered color: control.Material.rippleColor } } popup: T.Popup { + y: control.editable ? control.height - 5 : 0 width: control.width implicitHeight: contentItem.implicitHeight transformOrigin: Item.Top diff --git a/src/imports/controls/universal/ComboBox.qml b/src/imports/controls/universal/ComboBox.qml index 7552c164..4c5ecf74 100644 --- a/src/imports/controls/universal/ComboBox.qml +++ b/src/imports/controls/universal/ComboBox.qml @@ -53,6 +53,8 @@ T.ComboBox { leftPadding: padding + (!control.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) rightPadding: padding + (control.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) + Universal.theme: editable && activeFocus ? Universal.Light : undefined + delegate: ItemDelegate { width: parent.width text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData @@ -66,22 +68,40 @@ T.ComboBox { source: "image://universal/downarrow/" + (!control.enabled ? control.Universal.baseLowColor : control.Universal.baseMediumHighColor) sourceSize.width: width sourceSize.height: height + + Rectangle { + z: -1 + width: parent.width + height: parent.height + color: control.activeFocus ? control.Universal.accent : + control.pressed ? control.Universal.baseMediumLowColor : + control.hovered ? control.Universal.baseLowColor : "transparent" + visible: control.editable && !contentItem.hovered && (control.pressed || control.hovered) + opacity: control.activeFocus && !control.pressed ? 0.4 : 1.0 + } } - contentItem: Text { - leftPadding: control.mirrored ? 0 : 12 - rightPadding: control.mirrored ? 10 : 0 + contentItem: T.TextField { + leftPadding: control.mirrored ? 1 : 12 + rightPadding: control.mirrored ? 10 : 1 topPadding: 5 - control.topPadding bottomPadding: 7 - control.bottomPadding - text: control.displayText + text: control.editable ? control.editText : control.displayText + + enabled: control.editable + autoScroll: control.editable + readOnly: control.popup.visible + inputMethodHints: control.inputMethodHints + validator: control.validator + font: control.font + color: !control.enabled ? control.Universal.chromeDisabledLowColor : + control.editable && control.activeFocus ? control.Universal.chromeBlackHighColor : control.Universal.foreground + selectionColor: control.Universal.accent + selectedTextColor: control.Universal.chromeWhiteColor horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - - opacity: enabled ? 1.0 : 0.2 - color: control.Universal.foreground } background: Rectangle { @@ -90,11 +110,13 @@ T.ComboBox { border.width: control.flat ? 0 : 2 // ComboBoxBorderThemeThickness border.color: !control.enabled ? control.Universal.baseLowColor : - control.down ? control.Universal.baseMediumLowColor : + control.editable && control.activeFocus ? control.Universal.accent : + control.down || popup.visible ? control.Universal.baseMediumLowColor : control.hovered ? control.Universal.baseMediumColor : control.Universal.baseMediumLowColor color: !control.enabled ? control.Universal.baseLowColor : control.down ? control.Universal.listMediumColor : - control.flat && control.hovered ? control.Universal.listLowColor : control.Universal.altMediumLowColor + control.flat && control.hovered ? control.Universal.listLowColor : + control.editable && control.activeFocus ? control.Universal.background : control.Universal.altMediumLowColor visible: !control.flat || control.pressed || control.hovered || control.visualFocus Rectangle { @@ -103,7 +125,7 @@ T.ComboBox { width: parent.width - 4 height: parent.height - 4 - visible: control.visualFocus + visible: control.visualFocus && !control.editable color: control.Universal.accent opacity: control.Universal.theme === Universal.Light ? 0.4 : 0.6 } diff --git a/src/quicktemplates2/qquickcombobox.cpp b/src/quicktemplates2/qquickcombobox.cpp index 5f18fb40..0ced47d1 100644 --- a/src/quicktemplates2/qquickcombobox.cpp +++ b/src/quicktemplates2/qquickcombobox.cpp @@ -41,11 +41,15 @@ #include #include +#include +#include #include #include #include +#include #include #include +#include QT_BEGIN_NAMESPACE @@ -74,6 +78,30 @@ QT_BEGIN_NAMESPACE } \endcode + \section1 Editable ComboBox + + ComboBox can be made \l editable. An editable combo box auto-completes + its text based on what is available in the model. + + The following example demonstrates appending content to an editable + combo box by reacting to the \l accepted signal. + + \code + ComboBox { + editable: true + model: ListModel { + id: model + ListElement { text: "Banana" } + ListElement { text: "Apple" } + ListElement { text: "Coconut" } + } + onAccepted: { + if (find(editText) === -1) + model.append({text: editText}) + } + } + \endcode + \section1 ComboBox Model Roles ComboBox is able to visualize standard \l {qml-data-models}{data models} @@ -130,6 +158,19 @@ QT_BEGIN_NAMESPACE \sa highlightedIndex */ +/*! + \since QtQuick.Controls 2.2 + \qmlsignal void QtQuick.Controls::ComboBox::accepted() + + This signal is emitted when the \uicontrol Return or \uicontrol Enter key is pressed + on an \l editable combo box. If the confirmed string is not currently in the model, + the \l currentIndex will be set to \c -1 and the \c currentText will be updated + accordingly. + + \note If there is a \l validator set on the combo box, the signal will only be + emitted if the input is in an acceptable state. +*/ + namespace { enum Activation { NoActivate, Activate }; enum Highlighting { NoHighlight, Highlight }; @@ -181,8 +222,15 @@ public: void itemClicked(); void createdItem(int index, QObject *object); + void modelUpdated(); void countChanged(); + + void updateEditText(); void updateCurrentText(); + + void acceptInput(); + QString tryComplete(const QString &inputText); + void incrementCurrentIndex(); void decrementCurrentIndex(); void setCurrentIndex(int index, Activation activate); @@ -212,6 +260,23 @@ public: QQmlComponent *delegate; QQuickItem *indicator; QQuickPopup *popup; + + struct ExtraData { + ExtraData() + : editable(false), + accepting(false), + allowComplete(false), + inputMethodHints(Qt::ImhNone), + validator(nullptr) { } + + bool editable; + bool accepting; + bool allowComplete; + Qt::InputMethodHints inputMethodHints; + QString editText; + QValidator *validator; + }; + QLazilyAllocated extra; }; QQuickComboBoxPrivate::QQuickComboBoxPrivate() @@ -267,6 +332,9 @@ void QQuickComboBoxPrivate::togglePopup(bool accept) void QQuickComboBoxPrivate::popupVisibleChanged() { Q_Q(QQuickComboBox); + if (isPopupVisible()) + QGuiApplication::inputMethod()->reset(); + updateHighlightedIndex(); if (!hasDown) { q->setDown(pressed || isPopupVisible()); @@ -286,6 +354,7 @@ void QQuickComboBoxPrivate::itemClicked() void QQuickComboBoxPrivate::createdItem(int index, QObject *object) { + Q_Q(QQuickComboBox); QQuickItem *item = qobject_cast(object); if (popup && item && !item->parentItem()) { item->setParentItem(popup->contentItem()); @@ -298,7 +367,13 @@ void QQuickComboBoxPrivate::createdItem(int index, QObject *object) connect(button, &QQuickAbstractButton::clicked, this, &QQuickComboBoxPrivate::itemClicked); } - if (index == currentIndex) + if (index == currentIndex && !q->isEditable()) + updateCurrentText(); +} + +void QQuickComboBoxPrivate::modelUpdated() +{ + if (!extra.isAllocated() || !extra->accepting) updateCurrentText(); } @@ -310,6 +385,26 @@ void QQuickComboBoxPrivate::countChanged() emit q->countChanged(); } +void QQuickComboBoxPrivate::updateEditText() +{ + Q_Q(QQuickComboBox); + QQuickTextInput *input = qobject_cast(contentItem); + if (!input) + return; + + const QString text = input->text(); + + if (extra.isAllocated() && extra->allowComplete && !text.isEmpty()) { + const QString completed = tryComplete(text); + if (completed.length() > text.length()) { + input->setText(completed); + input->select(completed.length(), text.length()); + return; + } + } + q->setEditText(text); +} + void QQuickComboBoxPrivate::updateCurrentText() { Q_Q(QQuickComboBox); @@ -324,6 +419,45 @@ void QQuickComboBoxPrivate::updateCurrentText() displayText = text; emit q->displayTextChanged(); } + if (!extra.isAllocated() || !extra->accepting) + q->setEditText(currentText); +} + +void QQuickComboBoxPrivate::acceptInput() +{ + Q_Q(QQuickComboBox); + int idx = q->find(extra.value().editText, Qt::MatchFixedString); + if (idx > -1) + q->setCurrentIndex(idx); + + extra.value().accepting = true; + emit q->accepted(); + + if (idx == -1) + q->setCurrentIndex(q->find(extra.value().editText, Qt::MatchFixedString)); + extra.value().accepting = false; +} + +QString QQuickComboBoxPrivate::tryComplete(const QString &input) +{ + Q_Q(QQuickComboBox); + QString match; + + const int itemCount = q->count(); + for (int idx = 0; idx < itemCount; ++idx) { + const QString text = q->textAt(idx); + if (!text.startsWith(input, Qt::CaseInsensitive)) + continue; + + // either the first or the shortest match + if (match.isEmpty() || text.length() < match.length()) + match = text; + } + + if (match.isEmpty()) + return input; + + return input + match.mid(input.length()); } void QQuickComboBoxPrivate::setCurrentIndex(int index, Activation activate) @@ -345,6 +479,8 @@ void QQuickComboBoxPrivate::setCurrentIndex(int index, Activation activate) void QQuickComboBoxPrivate::incrementCurrentIndex() { Q_Q(QQuickComboBox); + if (extra.isAllocated()) + extra->allowComplete = false; if (isPopupVisible()) { if (highlightedIndex < q->count() - 1) setHighlightedIndex(highlightedIndex + 1, Highlight); @@ -352,10 +488,14 @@ void QQuickComboBoxPrivate::incrementCurrentIndex() if (currentIndex < q->count() - 1) setCurrentIndex(currentIndex + 1, Activate); } + if (extra.isAllocated()) + extra->allowComplete = true; } void QQuickComboBoxPrivate::decrementCurrentIndex() { + if (extra.isAllocated()) + extra->allowComplete = false; if (isPopupVisible()) { if (highlightedIndex > 0) setHighlightedIndex(highlightedIndex - 1, Highlight); @@ -363,6 +503,8 @@ void QQuickComboBoxPrivate::decrementCurrentIndex() if (currentIndex > 0) setCurrentIndex(currentIndex - 1, Activate); } + if (extra.isAllocated()) + extra->allowComplete = true; } void QQuickComboBoxPrivate::updateHighlightedIndex() @@ -449,7 +591,7 @@ void QQuickComboBoxPrivate::createDelegateModel() QQmlInstanceModel* oldModel = delegateModel; if (oldModel) { disconnect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged); - disconnect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::updateCurrentText); + disconnect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated); disconnect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem); } @@ -469,7 +611,7 @@ void QQuickComboBoxPrivate::createDelegateModel() if (delegateModel) { connect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged); - connect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::updateCurrentText); + connect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated); connect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem); } @@ -485,6 +627,7 @@ QQuickComboBox::QQuickComboBox(QQuickItem *parent) : setFocusPolicy(Qt::StrongFocus); setFlag(QQuickItem::ItemIsFocusScope); setAcceptedMouseButtons(Qt::LeftButton); + setInputMethodHints(Qt::ImhNoPredictiveText); } QQuickComboBox::~QQuickComboBox() @@ -566,6 +709,48 @@ QQmlInstanceModel *QQuickComboBox::delegateModel() const return d->delegateModel; } +/*! + \since QtQuick.Controls 2.2 + \qmlproperty bool QtQuick.Controls::ComboBox::editable + + This property holds whether the combo box is editable. + + The default value is \c false. + + \sa validator +*/ +bool QQuickComboBox::isEditable() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() && d->extra->editable; +} + +void QQuickComboBox::setEditable(bool editable) +{ + Q_D(QQuickComboBox); + if (editable == isEditable()) + return; + + if (d->contentItem) { + if (editable) { + d->contentItem->installEventFilter(this); + if (QQuickTextInput *input = qobject_cast(d->contentItem)) { + QObjectPrivate::connect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + QObjectPrivate::connect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + } + } else { + d->contentItem->removeEventFilter(this); + if (QQuickTextInput *input = qobject_cast(d->contentItem)) { + QObjectPrivate::disconnect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + QObjectPrivate::disconnect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + } + } + } + + d->extra.value().editable = editable; + emit editableChanged(); +} + /*! \since QtQuick.Controls 2.1 \qmlproperty bool QtQuick.Controls::ComboBox::flat @@ -764,6 +949,35 @@ void QQuickComboBox::resetDisplayText() d->updateCurrentText(); } +/*! + \since QtQuick.Controls 2.2 + \qmlproperty string QtQuick.Controls::ComboBox::editText + + This property holds the text in the text field of an editable combo box. + + \sa editable +*/ +QString QQuickComboBox::editText() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() ? d->extra->editText : QString(); +} + +void QQuickComboBox::setEditText(const QString &text) +{ + Q_D(QQuickComboBox); + if (text == editText()) + return; + + d->extra.value().editText = text; + emit editTextChanged(); +} + +void QQuickComboBox::resetEditText() +{ + setEditText(QString()); +} + /*! \qmlproperty string QtQuick.Controls::ComboBox::textRole @@ -900,6 +1114,115 @@ void QQuickComboBox::setPopup(QQuickPopup *popup) emit popupChanged(); } +/*! + \since QtQuick.Controls 2.2 + \qmlproperty Validator QtQuick.Controls::ComboBox::validator + + This property holds an input text validator for an editable combo box. + + When a validator is set, the text field will only accept input which + leaves the text property in an intermediate state. The \l accepted signal + will only be emitted if the text is in an acceptable state when the + \uicontrol Return or \uicontrol Enter key is pressed. + + The currently supported validators are \l[QtQuick]{IntValidator}, + \l[QtQuick]{DoubleValidator}, and \l[QtQuick]{RegExpValidator}. An + example of using validators is shown below, which allows input of + integers between \c 0 and \c 10 into the text field: + + \code + ComboBox { + model: 10 + editable: true + validator: IntValidator { + top: 9 + bottom: 0 + } + } + \endcode + + \sa acceptableInput, accepted, editable +*/ +QValidator *QQuickComboBox::validator() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() ? d->extra->validator : nullptr; +} + +void QQuickComboBox::setValidator(QValidator *validator) +{ + Q_D(QQuickComboBox); + if (validator == QQuickComboBox::validator()) + return; + + d->extra.value().validator = validator; + if (validator) + validator->setLocale(d->locale); + emit validatorChanged(); +} + +/*! + \since QtQuick.Controls 2.2 + \qmlproperty flags QtQuick.Controls::ComboBox::inputMethodHints + + Provides hints to the input method about the expected content of the combo box and how it + should operate. + + The default value is \c Qt.ImhNoPredictiveText. + + \include inputmethodhints.qdocinc +*/ +Qt::InputMethodHints QQuickComboBox::inputMethodHints() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() ? d->extra->inputMethodHints : Qt::ImhNoPredictiveText; +} + +void QQuickComboBox::setInputMethodHints(Qt::InputMethodHints hints) +{ + Q_D(QQuickComboBox); + if (hints == inputMethodHints()) + return; + + d->extra.value().inputMethodHints = hints; + emit inputMethodHintsChanged(); +} + +/*! + \since QtQuick.Controls 2.2 + \qmlproperty bool QtQuick.Controls::ComboBox::inputMethodComposing + \readonly + + This property holds whether an editable combo box has partial text input from an input method. + + While it is composing, an input method may rely on mouse or key events from the combo box to + edit or commit the partial text. This property can be used to determine when to disable event + handlers that may interfere with the correct operation of an input method. +*/ +bool QQuickComboBox::isInputMethodComposing() const +{ + Q_D(const QQuickComboBox); + return d->contentItem && d->contentItem->property("inputMethodComposing").toBool(); +} + +/*! + \since QtQuick.Controls 2.2 + \qmlproperty bool QtQuick.Controls::ComboBox::acceptableInput + \readonly + + This property holds whether the combo box contains acceptable text in the editable text field. + + If a validator has been set, the value is \c true only if the current text is acceptable + to the validator as a final string (not as an intermediate string). + + \sa validator, accepted +*/ +bool QQuickComboBox::hasAcceptableInput() const +{ + Q_D(const QQuickComboBox); + return d->contentItem && d->contentItem->property("acceptableInput").toBool(); +} + /*! \qmlmethod string QtQuick.Controls::ComboBox::textAt(int index) @@ -977,6 +1300,59 @@ void QQuickComboBox::decrementCurrentIndex() d->decrementCurrentIndex(); } +/*! + \since QtQuick.Controls 2.2 + \qmlmethod void QtQuick.Controls::ComboBox::selectAll() + + Selects all the text in the editable text field of the combo box. + + \sa editText +*/ +void QQuickComboBox::selectAll() +{ + Q_D(QQuickComboBox); + QQuickTextInput *input = qobject_cast(d->contentItem); + if (!input) + return; + input->selectAll(); +} + +bool QQuickComboBox::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QQuickComboBox); + switch (event->type()) { + case QEvent::MouseButtonRelease: + if (d->isPopupVisible()) + d->hidePopup(false); + break; + case QEvent::KeyPress: { + const int key = static_cast(event)->key(); + if (d->extra.isAllocated()) + d->extra->allowComplete = key != Qt::Key_Backspace && key != Qt::Key_Delete; + break; + } + case QEvent::FocusOut: + d->hidePopup(false); + setPressed(false); + break; + case QEvent::InputMethod: + if (d->extra.isAllocated()) + d->extra->allowComplete = !static_cast(event)->commitString().isEmpty(); + break; + default: + break; + } + return QQuickControl::eventFilter(object, event); +} + +void QQuickComboBox::focusInEvent(QFocusEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::focusInEvent(event); + if (d->contentItem && isEditable()) + d->contentItem->forceActiveFocus(event->reason()); +} + void QQuickComboBox::focusOutEvent(QFocusEvent *event) { Q_D(QQuickComboBox); @@ -985,6 +1361,16 @@ void QQuickComboBox::focusOutEvent(QFocusEvent *event) setPressed(false); } +void QQuickComboBox::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::inputMethodEvent(event); + if (!isEditable() && !event->commitString().isEmpty()) + d->keySearch(event->commitString()); + else + event->ignore(); +} + void QQuickComboBox::keyPressEvent(QKeyEvent *event) { Q_D(QQuickComboBox); @@ -1030,7 +1416,7 @@ void QQuickComboBox::keyPressEvent(QKeyEvent *event) event->accept(); break; default: - if (!event->text().isEmpty()) + if (!isEditable() && !event->text().isEmpty()) d->keySearch(event->text()); else event->ignore(); @@ -1047,13 +1433,15 @@ void QQuickComboBox::keyReleaseEvent(QKeyEvent *event) switch (event->key()) { case Qt::Key_Space: - d->togglePopup(true); + if (!isEditable()) + d->togglePopup(true); setPressed(false); event->accept(); break; case Qt::Key_Enter: case Qt::Key_Return: - d->hidePopup(d->isPopupVisible()); + if (!isEditable() || d->isPopupVisible()) + d->hidePopup(d->isPopupVisible()); setPressed(false); event->accept(); break; @@ -1126,6 +1514,36 @@ void QQuickComboBox::componentComplete() } } +void QQuickComboBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickComboBox); + if (oldItem) { + oldItem->removeEventFilter(this); + if (QQuickTextInput *oldInput = qobject_cast(oldItem)) { + QObjectPrivate::disconnect(oldInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + disconnect(oldInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged); + disconnect(oldInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged); + } + } + if (newItem && isEditable()) { + newItem->installEventFilter(this); + if (QQuickTextInput *newInput = qobject_cast(newItem)) { + QObjectPrivate::connect(newInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + connect(newInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged); + connect(newInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged); + } + } +} + +void QQuickComboBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale) +{ + QQuickControl::localeChange(newLocale, oldLocale); + if (QValidator *v = validator()) + v->setLocale(newLocale); +} + QFont QQuickComboBox::defaultFont() const { return QQuickControlPrivate::themeFont(QPlatformTheme::ComboMenuItemFont); diff --git a/src/quicktemplates2/qquickcombobox_p.h b/src/quicktemplates2/qquickcombobox_p.h index b1491ac5..6bbbe3b7 100644 --- a/src/quicktemplates2/qquickcombobox_p.h +++ b/src/quicktemplates2/qquickcombobox_p.h @@ -52,6 +52,7 @@ QT_BEGIN_NAMESPACE +class QValidator; class QQuickPopup; class QQmlInstanceModel; class QQuickComboBoxPrivate; @@ -62,6 +63,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickComboBox : public QQuickControl Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged FINAL) Q_PROPERTY(QQmlInstanceModel *delegateModel READ delegateModel NOTIFY delegateModelChanged FINAL) + Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged FINAL REVISION 2) Q_PROPERTY(bool flat READ isFlat WRITE setFlat NOTIFY flatChanged FINAL REVISION 1) Q_PROPERTY(bool down READ isDown WRITE setDown RESET resetDown NOTIFY downChanged FINAL REVISION 2) Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) // ### Qt 6: should not be writable @@ -69,10 +71,15 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickComboBox : public QQuickControl Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL) Q_PROPERTY(QString currentText READ currentText NOTIFY currentTextChanged FINAL) Q_PROPERTY(QString displayText READ displayText WRITE setDisplayText RESET resetDisplayText NOTIFY displayTextChanged FINAL) + Q_PROPERTY(QString editText READ editText WRITE setEditText RESET resetEditText NOTIFY editTextChanged FINAL REVISION 2) Q_PROPERTY(QString textRole READ textRole WRITE setTextRole NOTIFY textRoleChanged FINAL) Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) Q_PROPERTY(QQuickItem *indicator READ indicator WRITE setIndicator NOTIFY indicatorChanged FINAL) Q_PROPERTY(QQuickPopup *popup READ popup WRITE setPopup NOTIFY popupChanged FINAL) + Q_PROPERTY(QValidator *validator READ validator WRITE setValidator NOTIFY validatorChanged FINAL REVISION 2) + Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ inputMethodHints WRITE setInputMethodHints NOTIFY inputMethodHintsChanged FINAL REVISION 2) + Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged FINAL REVISION 2) + Q_PROPERTY(bool acceptableInput READ hasAcceptableInput NOTIFY acceptableInputChanged FINAL REVISION 2) public: explicit QQuickComboBox(QQuickItem *parent = nullptr); @@ -84,6 +91,9 @@ public: void setModel(const QVariant &model); QQmlInstanceModel *delegateModel() const; + bool isEditable() const; + void setEditable(bool editable); + bool isFlat() const; void setFlat(bool flat); @@ -105,6 +115,10 @@ public: void setDisplayText(const QString &text); void resetDisplayText(); + QString editText() const; + void setEditText(const QString &text); + void resetEditText(); + QString textRole() const; void setTextRole(const QString &role); @@ -117,17 +131,28 @@ public: QQuickPopup *popup() const; void setPopup(QQuickPopup *popup); + QValidator *validator() const; + void setValidator(QValidator *validator); + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints(Qt::InputMethodHints hints); + + bool isInputMethodComposing() const; + bool hasAcceptableInput() const; + Q_INVOKABLE QString textAt(int index) const; Q_INVOKABLE int find(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly) const; public Q_SLOTS: void incrementCurrentIndex(); void decrementCurrentIndex(); + Q_REVISION(2) void selectAll(); Q_SIGNALS: void countChanged(); void modelChanged(); void delegateModelChanged(); + Q_REVISION(2) void editableChanged(); Q_REVISION(1) void flatChanged(); Q_REVISION(2) void downChanged(); void pressedChanged(); @@ -135,16 +160,25 @@ Q_SIGNALS: void currentIndexChanged(); void currentTextChanged(); void displayTextChanged(); + Q_REVISION(2) void editTextChanged(); void textRoleChanged(); void delegateChanged(); void indicatorChanged(); void popupChanged(); + Q_REVISION(2) void validatorChanged(); + Q_REVISION(2) void inputMethodHintsChanged(); + Q_REVISION(2) void inputMethodComposingChanged(); + Q_REVISION(2) void acceptableInputChanged(); void activated(int index); void highlighted(int index); + Q_REVISION(2) void accepted(); protected: + bool eventFilter(QObject *object, QEvent *event) override; + void focusInEvent(QFocusEvent *event) override; void focusOutEvent(QFocusEvent *event) override; + void inputMethodEvent(QInputMethodEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void mousePressEvent(QMouseEvent *event) override; @@ -154,6 +188,8 @@ protected: void wheelEvent(QWheelEvent *event) override; void componentComplete() override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + void localeChange(const QLocale &newLocale, const QLocale &oldLocale) override; QFont defaultFont() const override; -- cgit v1.2.3