1import QtQuick 2.5
2import QtQuick.Controls 1.0
3import QtGraphicalEffects 1.0
4import "themes.js" as Themes
5
6Item {
7
8    id: root
9
10    property bool ctrl: false
11
12    width: frame.width+2*preferences.shadow_size
13    height: frame.height+2*preferences.shadow_size
14
15    layer.enabled: true
16    layer.effect: DropShadow {
17        transparentBorder: true
18        verticalOffset: preferences.shadow_size/3
19        radius: preferences.shadow_size
20        samples: preferences.shadow_size*2
21        color: preferences.shadow_color
22    }
23
24    Preferences {
25        id: preferences
26        objectName: "preferences"
27    }
28
29    FontLoader {
30        source: "fonts/Roboto-Thin.ttf"
31        onStatusChanged: if (loader.status === FontLoader.Ready) preferences.font_name = fontname
32    }
33
34    Rectangle {
35
36        id: frame
37        objectName: "frame" // for C++
38        x: preferences.shadow_size;
39        y: preferences.shadow_size
40        width: preferences.window_width
41        height: content.height+2*content.anchors.margins
42        radius: preferences.radius
43        color: preferences.background_color
44        Behavior on color { ColorAnimation { duration: preferences.animation_duration; easing.type: Easing.OutCubic } }
45        Behavior on border.color { ColorAnimation { duration: preferences.animation_duration; easing.type: Easing.OutCubic } }
46        border.color: preferences.border_color
47        border.width: preferences.border_size
48
49        Column {
50
51            id: content
52            anchors {
53                top: parent.top
54                left: parent.left
55                right: parent.right
56                margins: preferences.border_size+preferences.padding
57            }
58            spacing: preferences.spacing
59
60            HistoryTextInput {
61                id: historyTextInput
62                anchors {
63                    left: parent.left;
64                    right: parent.right;
65                }
66                clip: true
67                color: preferences.input_color
68                focus: true
69                font.pixelSize: preferences.input_fontsize
70                font.family: preferences.font_name
71                selectByMouse: true
72                selectedTextColor: preferences.background_color
73                selectionColor: preferences.selection_color
74                Keys.forwardTo: [root, resultsList]
75                cursorDelegate : Item {
76                    Rectangle {
77                        width: 1
78                        height: parent.height
79                        color: preferences.cursor_color
80                    }
81                    SequentialAnimation on opacity {
82                        id: animation
83                        loops: Animation.Infinite;
84                        NumberAnimation { to: 0; duration: 750; easing.type: Easing.InOutExpo }
85                        NumberAnimation { to: 1; duration: 750; easing.type: Easing.InOutExpo }
86                    }
87                    Connections {
88                        target: historyTextInput
89                        onTextChanged: { opacity=1; animation.restart() }
90                        onCursorPositionChanged: { opacity=1; animation.restart() }
91                    }
92                }
93                onTextChanged: { root.state="" }
94            } // historyTextInput
95
96            DesktopListView {
97                id: resultsList
98                width: parent.width
99                model: resultsModel
100                itemCount: preferences.max_items
101                delegate: Component {
102                    ItemViewDelegate{
103                        iconSize: preferences.icon_size
104                        spacing: preferences.spacing
105                        textSize: preferences.item_title_fontsize
106                        descriptionSize: preferences.item_description_fontsize
107                        textColor: preferences.foreground_color
108                        highlightColor: preferences.highlight_color
109                        fontName: preferences.font_name
110                        animationDuration: preferences.animation_duration
111                    }
112                }
113                Keys.onEnterPressed: (event.modifiers===Qt.KeypadModifier) ? root.activate(resultsList.currentIndex) : root.activate(resultsList.currentIndex,-event.modifiers)
114                Keys.onReturnPressed: (event.modifiers===Qt.NoModifier) ? root.activate(resultsList.currentIndex) : root.activate(resultsList.currentIndex,-event.modifiers)
115                onCurrentIndexChanged: if (root.state==="detailsView") root.state=""
116            }  // resultsList (ListView)
117
118            DesktopListView {
119                id: actionsListView
120                width: parent.width
121                model: ListModel { id: actionsModel }
122                itemCount: actionsModel.count
123                spacing: preferences.spacing
124                Behavior on visible {
125                    SequentialAnimation {
126                        PropertyAction  { }
127                        NumberAnimation { target: actionsListView; property: "opacity"; from:0; to: 1; duration: preferences.animation_duration }
128                    }
129                }
130                delegate: Text {
131                    horizontalAlignment: Text.AlignHCenter
132                    width: parent.width
133                    text: name
134                    textFormat: Text.PlainText
135                    font.family: preferences.font_name
136                    elide: Text.ElideRight
137                    font.pixelSize: (preferences.item_description_fontsize+preferences.item_title_fontsize)/2
138                    color: ListView.isCurrentItem ? preferences.highlight_color : preferences.foreground_color
139                    Behavior on color { ColorAnimation{ duration: preferences.animation_duration } }
140                    MouseArea {
141                        anchors.fill: parent
142                        onClicked: actionsListView.currentIndex = index
143                        onDoubleClicked: root.activate(resultsList.currentIndex, actionsListView.currentIndex)
144                    }
145                }
146                visible: false
147                Keys.onEnterPressed: root.activate(resultsList.currentIndex, actionsListView.currentIndex)
148                Keys.onReturnPressed: root.activate(resultsList.currentIndex, actionsListView.currentIndex)
149            }  // actionsListView (ListView)
150        }  // content (Column)
151
152
153        SettingsButton {
154            id: settingsButton
155            size: preferences.settingsbutton_size
156            color: preferences.settingsbutton_color
157            hoverColor: preferences.settingsbutton_hover_color
158            onLeftClicked: settingsWidgetRequested()
159            onRightClicked: menu.popup()
160            anchors {
161                top: parent.top
162                right: parent.right
163                topMargin: preferences.padding+preferences.border_size
164                rightMargin: preferences.padding+preferences.border_size
165            }
166
167            Menu {
168                id: menu
169                MenuItem {
170                    text: "Settings"
171                    shortcut: "Alt+,"
172                    onTriggered: settingsWidgetRequested()
173                }
174                MenuItem {
175                    text: "Quit"
176                    shortcut: "Alt+F4"
177                    onTriggered: Qt.quit()
178
179                }
180            }
181        }
182    }  // frame (Rectangle)
183
184    onActiveFocusChanged: state=""
185
186    // Key handling
187    Keys.onPressed: {
188        event.accepted = true
189        if (state === ""
190                && ((event.key === Qt.Key_Up && resultsList.currentIndex === 0 && !event.isAutoRepeat)  // top item selected
191                        || (event.modifiers === Qt.ControlModifier && (event.key === Qt.Key_P || event.key === Qt.Key_Up)))){ // whatever item, using control modifier
192            state == ""
193            historyTextInput.forwardSearchHistory()
194        }
195        else if (event.modifiers === Qt.ControlModifier && (event.key === Qt.Key_Down || event.key === Qt.Key_N)) {
196            state == ""
197            historyTextInput.backwardSearchHistory()
198        }
199        else if (event.key === Qt.Key_Meta) {
200            if (resultsList.currentIndex === -1)
201                resultsList.currentIndex = 0
202            state="fallback"
203        }
204        else if (event.key === Qt.Key_Comma && (event.modifiers === Qt.AltModifier || event.modifiers === Qt.ControlModifier)) {
205            settingsWidgetRequested()
206        }
207        else if (event.key === Qt.Key_Alt && resultsList.count > 0) {
208            if (resultsList.currentIndex === -1)
209                resultsList.currentIndex = 0
210            state = "detailsView"
211        }
212        else if (48 <= event.key && event.key <= 57 && event.modifiers === Qt.ControlModifier){
213            var num = 9
214            if (event.key !== Qt.Key_0)
215                num = event.key - 49
216            if (num < preferences.max_items && num < resultsList.count) {
217                var index = resultsList.indexAt(0, resultsList.contentY)
218                index += num
219                resultsList.currentIndex = index
220                root.activate(resultsList.currentIndex)
221            }
222        }
223        else if ( event.key === Qt.Key_Control )
224            ctrl = true
225        else if ( event.key === Qt.Key_Tab && resultsList.count > 0 ) {
226            if ( resultsList.currentIndex === -1 )
227                resultsList.currentIndex = 0
228            historyTextInput.text = resultsList.currentItem.attachedModel.itemCompletionStringRole
229        } else event.accepted = false
230    }
231    Keys.onReleased: {
232        event.accepted = true
233        if ( event.key === Qt.Key_Meta )
234            state=""
235        else if ( event.key === Qt.Key_Alt )
236            state=""
237        else if ( event.key === Qt.Key_Control )
238            ctrl = false
239        else event.accepted = false
240
241    }
242
243    states : [
244        State {
245            name: ""
246            StateChangeScript {
247                name: "defaultStateScript"
248                script: {
249                    actionsModel.clear()
250                }
251            }
252        },
253        State {
254            name: "fallback"
255        },
256        State {
257            name: "detailsView"
258            PropertyChanges { target: actionsListView; visible: true  }
259            PropertyChanges { target: historyTextInput; Keys.forwardTo: [root, actionsListView] }
260            StateChangeScript {
261                name: "detailsViewStateScript"
262                script: {
263                    var actionTexts = resultsList.currentItem.actionsList();
264                    for ( var i = 0; i < actionTexts.length; i++ )
265                        actionsModel.append({"name": actionTexts[i]});
266                }
267            }
268        }
269    ]
270
271    Connections {
272        target: mainWindow
273        onVisibleChanged: {
274            if (!arg) {
275
276                // Save the text if the text displayed has been entered by the user
277                if (historyTextInput.userText === historyTextInput.text)
278                    historyTextInput.pushTextToHistory()
279
280                // Reset state
281                state=""
282                ctrl=false
283                historyTextInput.selectAll()
284                historyTextInput.userText = null
285                historyTextInput.resetHistoryMode()
286            }
287        }
288    }
289
290    Component.onCompleted: setTheme("Bright")
291
292
293    // ▼ ▼ ▼ ▼ ▼ DO NOT CHANGE THIS UNLESS YOU KNOW WHAT YOU ARE DOING ▼ ▼ ▼ ▼ ▼
294
295    /*
296     * Currently the interface with the program logic comprises the following:
297     *
298     * These context properties are set:
299     *   - mainWindow
300     *   - resultsModel
301     *   - history
302     *
303     * These properties must exist in root:
304     *   - inputText (string, including the implicitly genreated signal)
305     *
306     * These functions must extist in root:
307     *   - availableThemes()
308     *   - setTheme(str)
309     *
310     * These signals must exist in root:
311     *   - settingsWidgetRequested()
312     *
313     * These object names with must exist somewhere:
314     *   - frame (the visual root frame, i.e. withouth shadow)
315     *   - preferences (QtObject containing only preference propterties)
316     */
317    property string interfaceVersion: "1.0-alpha" // Will not change until beta
318
319    property alias inputText: historyTextInput.text
320    signal settingsWidgetRequested()
321
322    function activate(index, action) {
323        if ( resultsList.count > 0 ) {
324            resultsList.currentIndex = index
325            if ( resultsList.currentIndex === -1 )
326                resultsList.currentIndex = 0
327            resultsList.currentItem.activate(action)
328
329            // Order important! (hide triggers root.onVisibleChanged())
330            historyTextInput.pushTextToHistory()
331            mainWindow.hide()
332            historyTextInput.text = ""
333        }
334    }
335
336    function availableThemes() { return Object.keys(Themes.themes()) }
337    function setTheme(themeName) {
338        var themeObject = Themes.themes()[themeName]
339        for (var property in themeObject)
340            if (themeObject.hasOwnProperty(property))
341                preferences[property] = themeObject[property]
342    }
343}
344