aboutsummaryrefslogtreecommitdiffstats
path: root/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExpressionTextField.qml
blob: bc6763abbeb2473788ac53fdd57ffdf094b7cb7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuickDesignerTheme 1.0
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme

StudioControls.TextField {
    id: textField

    translationIndicator.visible: false
    actionIndicator.visible: false

    property bool completeOnlyTypes: false
    property bool completionActive: listView.model !== null
    property bool dotCompletion: false
    property int dotCursorPos: 0
    property string prefix
    property bool fixedSize: false
    property bool replaceCurrentTextByCompletion: false

    property alias completionList: listView

    function commitCompletion() {
        if (replaceCurrentTextByCompletion) {
            textField.text = listView.currentItem.text
        } else {
            var cursorPos = textField.cursorPosition
            var string = textField.text
            var before = string.slice(0, cursorPos - textField.prefix.length)
            var after = string.slice(cursorPos)

            textField.text = before + listView.currentItem.text + after
            textField.cursorPosition = cursorPos + listView.currentItem.text.length - prefix.length
        }
        listView.model = null
    }

    Popup {
        id: textFieldPopup
        x: StudioTheme.Values.border
        y: textField.height
        width: textField.width - (StudioTheme.Values.border * 2)
        // TODO Setting the height on the popup solved the problem with the popup of height 0,
        // but it has the problem that it sometimes extend over the border of the actual window
        // and is then cut off.
        height: Math.min(contentItem.implicitHeight + textFieldPopup.topPadding + textFieldPopup.bottomPadding,
                         textField.Window.height - topMargin - bottomMargin,
                         StudioTheme.Values.maxComboBoxPopupHeight)
        padding: StudioTheme.Values.border
        margins: 0 // If not defined margin will be -1
        closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnPressOutsideParent
                     | Popup.CloseOnEscape | Popup.CloseOnReleaseOutside
                     | Popup.CloseOnReleaseOutsideParent

        visible: textField.completionActive

        onClosed: listView.model = null

        contentItem: ListView {
            id: listView
            clip: true
            implicitHeight: contentHeight
            boundsBehavior: Flickable.StopAtBounds
            ScrollBar.vertical: StudioControls.ScrollBar {
                id: popupScrollBar
            }

            model: null

            delegate: ItemDelegate {
                id: myItemDelegate

                width: textFieldPopup.width - textFieldPopup.leftPadding - textFieldPopup.rightPadding
                       - (popupScrollBar.visible ? popupScrollBar.contentItem.implicitWidth
                                                           + 2 : 0) // TODO Magic number
                height: StudioTheme.Values.height - 2 * StudioTheme.Values.border
                padding: 0
                text: itemDelegateText.text

                highlighted: listView.currentIndex === index

                contentItem: Text {
                    id: itemDelegateText
                    leftPadding: 8
                    text: modelData
                    color: highlighted ? StudioTheme.Values.themeTextSelectedTextColor
                                       : StudioTheme.Values.themeTextColor
                    font: textField.font
                    elide: Text.ElideRight
                    verticalAlignment: Text.AlignVCenter
                }
                background: Rectangle {
                    color: "transparent"
                }

                hoverEnabled: true
                onHoveredChanged: {
                    if (hovered)
                        listView.currentIndex = index
                }
                onClicked: {
                    listView.currentIndex = index
                    if (textField.completionActive)
                        textField.commitCompletion()
                }
            }

            highlight: Rectangle {
                id: listViewHighlight
                width: textFieldPopup.width - textFieldPopup.leftPadding - textFieldPopup.rightPadding
                       - (popupScrollBar.visible ? popupScrollBar.contentItem.implicitWidth
                                                           + 2 : 0)
                height: StudioTheme.Values.height - 2 * StudioTheme.Values.border
                color: StudioTheme.Values.themeInteraction
                y: listView.currentItem?.y ?? 0
            }
            highlightFollowsCurrentItem: false
        }

        background: Rectangle {
            color: StudioTheme.Values.themePopupBackground
            border.width: 0
        }

        enter: Transition {}
        exit: Transition {}
    }

    verticalAlignment: Text.AlignVCenter

    onPressed: listView.model = null

    onRejected: {
        if (textField.completionActive)
            listView.model = null
    }

    Keys.priority: Keys.BeforeItem
    Keys.onPressed: function(event) {
        var text = textField.text
        var pos = textField.cursorPosition
        var explicitComplete = true

        switch (event.key) {

        case Qt.Key_Period:
            textField.dotCursorPos = textField.cursorPosition + 1
            text = textField.text + "."
            pos = textField.dotCursorPos
            explicitComplete = false
            textField.dotCompletion = true
            break

        case Qt.Key_Right:
            if (!textField.completionActive)
                return

            pos = Math.min(textField.cursorPosition + 1, textField.text.length)
            break

        case Qt.Key_Left:
            if (!textField.completionActive)
                return

            pos = Math.max(0, textField.cursorPosition - 1)
            break

        case Qt.Key_Backspace:
            if (!textField.completionActive)
                return

            pos = textField.cursorPosition - 1
            if (pos < 0)
                return

            text = textField.text.substring(0, pos) + textField.text.substring(textField.cursorPosition)
            break

        case Qt.Key_Delete:
            return

        default:
            if (!textField.completionActive)
                return

            var tmp = textField.text
            text = tmp.substring(0, textField.cursorPosition) + event.text + tmp.substring(textField.cursorPosition)
            pos = textField.cursorPosition + event.text.length
        }

        var list = autoComplete(text.trim(), pos, explicitComplete, textField.completeOnlyTypes)
        textField.prefix = text.substring(0, pos)

        if (list.length && list[list.length - 1] === textField.prefix)
            list.pop()

        listView.model = list
    }

    Keys.onSpacePressed: function(event) {
        if (event.modifiers & Qt.ControlModifier) {
            var list = autoComplete(textField.text, textField.cursorPosition, true, textField.completeOnlyTypes)
            textField.prefix = textField.text.substring(0, textField.cursorPosition)
            if (list.length && list[list.length - 1] === textField.prefix)
                list.pop()

            listView.model = list
            textField.dotCompletion = false

            event.accepted = true;
        } else {
            event.accepted = false
        }
    }

    Keys.onReturnPressed: function(event) {
        event.accepted = false
        if (textField.completionActive) {
            textField.commitCompletion()
            event.accepted = true
        }
    }

    Keys.onUpPressed: function(event) {
        listView.decrementCurrentIndex()
        event.accepted = false
    }

    Keys.onDownPressed: function(event) {
        listView.incrementCurrentIndex()
        event.accepted = false
    }
}