Warning, /libraries/kirigami-addons/src/components/SearchPopupField.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-FileCopyrightText: 2021 Jonah BrĂ¼chert <jbb@kaidan.im>
0002 // SPDX-FileCopyrightText: 2023 Mathis BrĂ¼chert <mbb@kaidan.im>
0003 // SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
0004 // SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
0005 //
0006 // SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 
0008 import QtQuick 2.15
0009 import QtQuick.Controls 2.15 as QQC2
0010 import QtQuick.Templates 2.15 as T
0011 import QtQuick.Layouts 1.15
0012 import Qt.labs.qmlmodels 1.0
0013 import org.kde.kirigami 2.20 as Kirigami
0014 
0015 /**
0016  * SearchField with a Popup to show autocompletion entries or search results
0017  *
0018  * @since KirigamiAddons.labs.components 1.0
0019  */
0020 QQC2.Control {
0021     id: root
0022 
0023     /**
0024      * This property holds the content item of the popup.
0025      *
0026      * Overflow will be automatically be handled as popupContentItem is
0027      * contained inside a ScrollView.
0028      *
0029      * This is the default element of SearchPopupField.
0030      *
0031      * ```qml
0032      * SearchPopupField {
0033      *     ListView {
0034      *         model: SearchModel {}
0035      *         delegate: QQC2.ItemDelegate {}
0036      *
0037      *         Kirigami.PlaceholderMessage {
0038      *             id: loadingPlaceholder
0039      *             anchors.centerIn: parent
0040      *             width: parent.width - Kirigami.Units.gridUnit * 4
0041      *             // ...
0042      *         }
0043      *     }
0044      * }
0045      * ```
0046      *
0047      * @since KirigamiAddons.labs.components 1.0
0048      */
0049     default property alias popupContentItem: scrollView.contentItem
0050 
0051     /**
0052      * This property holds the text of the search field.
0053      *
0054      * @since KirigamiAddons.labs.components 1.0
0055      */
0056     property alias text: root.searchField.text
0057 
0058     /**
0059      * @brief This property sets whether to delay automatic acceptance of the search input.
0060      *
0061      * Set this to true if your search is expensive (such as for online
0062      * operations or in exceptionally slow data sets) and want to delay it
0063      * for 2.5 seconds.
0064      *
0065      * @note If you must have immediate feedback (filter-style), use the
0066      * text property directly instead of accepted()
0067      *
0068      * default: ``false``
0069      *
0070      * @since KirigamiAddons.labs.components 1.0
0071      */
0072     property alias delaySearch: root.searchField.delaySearch
0073 
0074     /**
0075      * @brief This property sets whether the accepted signal is fired automatically
0076      * when the text is changed.
0077      *
0078      * Setting this to false will require that the user presses return or enter
0079      * (the same way a QtQuick.Controls.TextInput works).
0080      *
0081      * default: ``false``
0082      *
0083      * @since KirigamiAddons.labs.components 1.0
0084      */
0085     property alias autoAccept: root.searchField.autoAccept
0086 
0087     /**
0088      * This property holds whether there is space available on the left.
0089      *
0090      * This is used by the left shadow.
0091      *
0092      * @since KirigamiAddons.labs.components 1.0
0093      * @deprecated Was not really used by anything.
0094      */
0095     property bool spaceAvailableLeft: true
0096 
0097     /**
0098      * This property holds whether there is space available on the left.
0099      *
0100      * This is used by the right shadow.
0101      *
0102      * @since KirigamiAddons.labs.components 1.0
0103      * @deprecated Was not really used by anything.
0104      */
0105     property bool spaceAvailableRight: true
0106 
0107     /**
0108      * @brief This hold the focus state of the internal SearchField.
0109     */
0110     property alias fieldFocus: searchField.focus
0111 
0112     /**
0113      * This signal is triggered when the user trigger a search.
0114      */
0115     signal accepted()
0116 
0117     property alias popup: popup
0118 
0119     property Kirigami.SearchField searchField: Kirigami.SearchField {
0120         id: searchField
0121         anchors.left: parent ? parent.left : undefined
0122         anchors.right: parent ? parent.right : undefined
0123         selectByMouse: true
0124 
0125         KeyNavigation.tab: scrollView.contentItem
0126         KeyNavigation.down: scrollView.contentItem
0127 
0128         onActiveFocusChanged: if (activeFocus) {
0129             // Don't mess with popups and reparenting inside focus change handler.
0130             // Especially nested popups hate that: it may break all focus management
0131             // on a scene until restart.
0132             Qt.callLater(() => {
0133                 // TODO: Kirigami.OverlayZStacking fails to find and bind to
0134                 // parent logical popup when parent item is itself reparented
0135                 // on the fly.
0136                 if (typeof Kirigami.OverlayZStacking !== "undefined") {
0137                     root.popup.z = Qt.binding(() => root.popup.Kirigami.OverlayZStacking.z);
0138                 }
0139                 root.popup.open();
0140             });
0141         }
0142 
0143         onAccepted: {
0144             if (root.autoAccept) {
0145                 if (text.length > 2) {
0146                     root.accepted()
0147                 }
0148             } else if (text.length === 0) {
0149                 root.popup.close();
0150             } else {
0151                 root.accepted();
0152             }
0153         }
0154     }
0155 
0156     contentItem: Item {
0157         implicitHeight: searchField.implicitHeight
0158         implicitWidth: searchField.implicitWidth
0159         children: searchField
0160     }
0161 
0162     padding: 0
0163     topPadding: undefined
0164     leftPadding: undefined
0165     rightPadding: undefined
0166     bottomPadding: undefined
0167     verticalPadding: undefined
0168     horizontalPadding: undefined
0169 
0170     focusPolicy: Qt.NoFocus
0171     activeFocusOnTab: true
0172 
0173     onActiveFocusChanged: {
0174         if (activeFocus) {
0175             searchField.forceActiveFocus();
0176         }
0177     }
0178 
0179     onVisibleChanged: {
0180         if (!visible) {
0181             popup.close();
0182         }
0183     }
0184 
0185     function __handoverChild(child: Item, oldParent: Item, newParent: Item) {
0186         // It used to be more complicated with QQC2.Control::contentItem
0187         // handover. But plain Items are very simple to deal with, and they
0188         // don't attempt to hide old contentItem by setting their visible=false.
0189         child.parent = newParent;
0190     }
0191 
0192     T.Popup {
0193         id: popup
0194 
0195         Component.onCompleted: {
0196             // TODO KF6: port to declarative bindings.
0197             if (typeof Kirigami.OverlayZStacking !== "undefined") {
0198                 Kirigami.OverlayZStacking.layer = Kirigami.OverlayZStacking.Dialog;
0199                 z = Qt.binding(() => Kirigami.OverlayZStacking.z);
0200             }
0201         }
0202 
0203         readonly property real collapsedHeight: searchField.implicitHeight
0204             + topMargin + bottomMargin + topPadding + bottomPadding
0205 
0206         // How much vertical space this popup is actually going to take,
0207         // considering that margins will push it inside and shrink if needed.
0208         readonly property real realisticHeight: {
0209             const wantedHeight = searchField.implicitHeight + Kirigami.Units.gridUnit * 20;
0210             const overlay = root.QQC2.Overlay.overlay;
0211             if (!overlay) {
0212                 return 0;
0213             }
0214             return Math.min(wantedHeight, overlay.height - topMargin - bottomMargin);
0215         }
0216 
0217         readonly property real realisticContentHeight: realisticHeight - topPadding - bottomPadding
0218 
0219         // y offset from parent/root control if there's not enough space on
0220         // the bottom, so popup is being pushed upward.
0221         readonly property real yOffset: {
0222             const overlay = root.QQC2.Overlay.overlay;
0223             if (!overlay) {
0224                 return 0;
0225             }
0226             return Math.max(-root.Kirigami.ScenePosition.y, Math.min(0, overlay.height - root.Kirigami.ScenePosition.y - realisticHeight));
0227         }
0228 
0229         clip: false
0230         parent: root
0231 
0232         // make sure popup is being pushed in-bounds if it is too large or root control is (partially) out of bounds
0233         margins: 0
0234 
0235         leftPadding: dialogRoundedBackground.border.width
0236         rightPadding: dialogRoundedBackground.border.width
0237         bottomPadding: dialogRoundedBackground.border.width
0238         x: -leftPadding
0239         y: 0 // initial value, will be managed by enter/exit transitions
0240 
0241         implicitWidth: root.width + leftPadding + rightPadding
0242         height: popup.collapsedHeight // initial binding, will be managed by enter/exit transitions
0243 
0244         onVisibleChanged: {
0245             searchField.QQC2.ToolTip.hide();
0246             if (visible) {
0247                 root.__handoverChild(searchField, root.contentItem, fieldContainer);
0248                 searchField.forceActiveFocus();
0249             } else {
0250                 root.__handoverChild(searchField, fieldContainer, root.contentItem);
0251             }
0252         }
0253 
0254         onAboutToHide: {
0255             searchField.focus = false;
0256         }
0257 
0258         enter: Transition {
0259             SequentialAnimation {
0260                 // cross-fade search field's background with popup's bigger rounded background
0261                 ParallelAnimation {
0262                     NumberAnimation {
0263                         target: searchField.background
0264                         property: "opacity"
0265                         to: 0
0266                         easing.type: Easing.OutCubic
0267                         duration: Kirigami.Units.shortDuration
0268                     }
0269                     NumberAnimation {
0270                         target: dialogRoundedBackground
0271                         property: "opacity"
0272                         to: 1
0273                         easing.type: Easing.OutCubic
0274                         duration: Kirigami.Units.shortDuration
0275                     }
0276                 }
0277                 // push Y upward (if needed) and expand at the same time
0278                 ParallelAnimation {
0279                     NumberAnimation {
0280                         property: "y"
0281                         easing.type: Easing.OutCubic
0282                         duration: Kirigami.Units.longDuration
0283                         to: popup.yOffset
0284                     }
0285                     NumberAnimation {
0286                         property: "height"
0287                         easing.type: Easing.OutCubic
0288                         duration: Kirigami.Units.longDuration
0289                         to: popup.realisticHeight
0290                     }
0291                 }
0292             }
0293         }
0294 
0295         // Rebind animated properties in case enter/exit transition was skipped.
0296         onOpened: {
0297             searchField.background.opacity = 0;
0298             dialogRoundedBackground.opacity = 1;
0299             // Make sure height stays sensible if window is resized while popup is open.
0300             popup.y = Qt.binding(() => popup.yOffset);
0301             popup.height = Qt.binding(() => popup.realisticHeight);
0302         }
0303 
0304         exit: Transition {
0305             SequentialAnimation {
0306                 // return Y back to root control's position (if needed) and collapse at the same time
0307                 ParallelAnimation {
0308                     NumberAnimation {
0309                         property: "y"
0310                         easing.type: Easing.OutCubic
0311                         duration: Kirigami.Units.longDuration
0312                         to: 0
0313                     }
0314                     NumberAnimation {
0315                         property: "height"
0316                         easing.type: Easing.OutCubic
0317                         duration: Kirigami.Units.longDuration
0318                         to: popup.collapsedHeight
0319                     }
0320                 }
0321                 // cross-fade search field's background with popup's bigger rounded background
0322                 ParallelAnimation {
0323                     NumberAnimation {
0324                         target: searchField.background
0325                         property: "opacity"
0326                         to: 1
0327                         easing.type: Easing.OutCubic
0328                         duration: Kirigami.Units.shortDuration
0329                     }
0330                     NumberAnimation {
0331                         target: dialogRoundedBackground
0332                         property: "opacity"
0333                         to: 0
0334                         easing.type: Easing.OutCubic
0335                         duration: Kirigami.Units.shortDuration
0336                     }
0337                 }
0338             }
0339         }
0340 
0341         // Rebind animated properties in case enter/exit transition was skipped.
0342         onClosed: {
0343             searchField.background.opacity = 1;
0344             dialogRoundedBackground.opacity = 0;
0345             // Make sure height stays sensible if search field is resized while popup is closed.
0346             popup.y = 0;
0347             popup.height = Qt.binding(() => popup.collapsedHeight);
0348         }
0349 
0350         background: DialogRoundedBackground {
0351             id: dialogRoundedBackground
0352 
0353             // initial value, will be managed by enter/exit transitions
0354             opacity: 0
0355         }
0356 
0357         contentItem: Item {
0358             // clip with rounded corners
0359             layer.enabled: popup.enter.running || popup.exit.running
0360             layer.effect: Kirigami.ShadowedTexture {
0361                 // color is not needed, we are here for the clipping only.
0362                 color: "transparent"
0363                 // border is not needed, as is is already accounted by
0364                 // padding. But radius has to be adjusted for that padding.
0365                 radius: dialogRoundedBackground.radius - popup.leftPadding - popup.bottomPadding
0366             }
0367 
0368             ColumnLayout {
0369                 anchors {
0370                     top: parent.top
0371                     left: parent.left
0372                     right: parent.right
0373                 }
0374                 height: popup.realisticContentHeight
0375                 spacing: 0
0376 
0377                 Item {
0378                     id: fieldContainer
0379                     implicitWidth: searchField.implicitWidth
0380                     implicitHeight: searchField.implicitHeight
0381                     Layout.fillWidth: true
0382                 }
0383 
0384                 Kirigami.Separator {
0385                     Layout.fillWidth: true
0386                 }
0387 
0388                 QQC2.ScrollView {
0389                     id: scrollView
0390 
0391                     Kirigami.Theme.colorSet: Kirigami.Theme.View
0392                     Kirigami.Theme.inherit: false
0393 
0394                     Layout.fillHeight: true
0395                     Layout.fillWidth: true
0396                 }
0397             }
0398         }
0399     }
0400 }