on 4089111 help sign in
unity8/qml/Components/PageHeader.qml
424 lines 17 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19import Ubuntu.Components.Popups 0.1
20import Ubuntu.Components.ListItems 0.1 as ListItem
21import Unity 0.2
23Item {
24 id: root
25 property bool searchEntryEnabled: false
26 property alias searchQuery: searchField.text
27 property ListModel searchHistory
28 property var scope: null
29 property alias childItem: itemContainer.children
30 property alias bottomItem: bottomContainer.children
31 property alias showBackButton: backButton.visible
33 signal backClicked
35 height: header.height + units.gu(2) + bottomContainer.height
36 implicitHeight: header.height + units.gu(2) + bottomContainer.height
38 function triggerSearch() {
39 if (searchEntryEnabled) searchField.forceActiveFocus()
40 }
42 function resetSearch() {
43 if (!searchHistory) return;
45 searchHistory.addQuery(searchField.text);
46 unfocus();
47 searchField.text = "";
48 }
50 function unfocus() {
51 searchField.focus = false;
52 }
54 Connections {
55 target: greeter
56 onShownChanged: if (shown) resetSearch()
57 }
59 Flickable {
60 id: header
61 anchors {
62 left: parent.left
63 right: parent.right
64 top: parent.top
65 }
66 height: units.gu(6.5)
68 interactive: false
69 contentHeight: headerContainer.height
70 clip: true
72 contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
74 Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
76 AbstractButton {
77 id: backButton
78 objectName: root.objectName + "_backButton"
79 visible: false
80 height: header.height
81 y: header.contentY
82 anchors {
83 left: parent.left
84 leftMargin: visible ? units.gu(2) : 0
85 }
86 width: visible ? image.width + units.gu(2) : 0
87 onClicked: root.backClicked();
88 Image {
89 id: image
90 anchors.centerIn: parent
91 source: "graphics/headerback.png"
92 }
93 }
95 // FIXME this could potentially be simplified to avoid all the containers
96 Item {
97 id: headerContainer
99 anchors {
100 left: backButton.right
101 right: parent.right
102 }
103 height: childrenRect.height
105 Item {
106 id: itemContainer
108 width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
109 height: header.height
110 }
112 Item {
113 id: searchContainer
114 objectName: "searchContainer"
116 visible: searchEntryEnabled
117 property bool popoverShouldOpen: false
118 property bool popoverShouldClose: false
120 property bool narrowMode: parent.width < units.gu(80)
122 property bool active: searchField.text != "" || searchField.activeFocus
123 property var popover: null
125 anchors.right: headerContainer.right
126 height: header.height
128 state:
129 if (active && narrowMode) "narrowActive"
130 else if (!active && narrowMode) "narrowInactive"
131 else if (active && !narrowMode) "active"
132 else if (!active && !narrowMode) "inactive"
134 onStateChanged: {
135 if (state == "active" || state == "narrowActive") {
136 popoverShouldOpen = true;
137 popoverShouldClose = false;
138 } else {
139 popoverShouldOpen = false;
140 popoverShouldClose = true;
141 }
142 }
144 function openPopover() {
145 if (searchHistory.count > 0) {
146 searchContainer.popover = PopupUtils.open(popoverComponent, pointerPositioner,
147 {
148 "contentWidth": searchField.width,
149 "edgeMargins": units.gu(1)
150 }
151 )
152 }
153 popoverShouldOpen = false;
154 popoverShouldClose = false;
155 }
157 function closePopover() {
158 if (searchContainer.popover) {
159 PopupUtils.close(searchContainer.popover);
160 searchContainer.popover = null;
161 }
162 popoverShouldOpen = false;
163 popoverShouldClose = false;
164 }
166 onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
168 TextField {
169 id: searchField
171 anchors.fill: parent
172 anchors.margins: units.gu(1)
174 inputMethodHints: Qt.ImhNoPredictiveText
175 hasClearButton: false
177 primaryItem: AbstractButton {
178 enabled: searchField.text != "" && !searchIndicator.running
179 onClicked: {
180 if (searchField.text != "") {
181 searchHistory.addQuery(searchField.text)
182 searchField.text = ""
183 }
184 }
185 height: parent.height
186 width: height
188 ActivityIndicator {
189 id: searchIndicator
190 objectName: "searchIndicator"
192 anchors {
193 verticalCenter: parent.verticalCenter
194 left: parent.left
195 leftMargin: units.gu(0.5)
196 }
198 running: opacity > 0
199 }
201 Image {
202 id: primaryImage
203 objectName: "primaryImage"
204 anchors {
205 verticalCenter: parent.verticalCenter
206 left: parent.left
207 leftMargin: units.gu(0.5)
208 }
209 width: units.gu(3)
210 height: units.gu(3)
211 visible: opacity > 0
212 }
214 Item {
215 id: pointerPositioner
216 anchors.left: parent.right
217 anchors.leftMargin: units.gu(0.5)
218 anchors.top: parent.bottom
219 }
220 }
222 onTextChanged: {
223 if (text != "") searchContainer.closePopover()
224 else if (text == "" && activeFocus) searchContainer.openPopover()
225 }
227 onActiveFocusChanged: {
228 if (!activeFocus) searchContainer.closePopover()
229 }
231 states: [
232 State {
233 name: "searching"
234 when: scope && scope.searchInProgress
235 PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
236 PropertyChanges { target: primaryImage; opacity: 0 }
237 },
238 State {
239 name: "idle"
240 when: !scope || !scope.searchInProgress
241 PropertyChanges { target: searchIndicator; opacity: 0 }
242 PropertyChanges { target: primaryImage; opacity: 1 }
243 }
244 ]
246 transitions: [
247 Transition {
248 to: "searching"
249 reversible: true
250 SequentialAnimation {
251 NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
252 NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
253 }
254 }
255 ]
256 }
258 states: [
259 State {
260 name: "wide"
261 AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
262 AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
263 },
264 State {
265 name: "narrow"
266 PropertyChanges { target: searchField; highlighted: true }
267 AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
268 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
269 },
270 State {
271 name: "active"
272 extend: "wide"
273 PropertyChanges { target: searchContainer; width: units.gu(40) }
274 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
275 PropertyChanges { target: searchField; highlighted: true }
276 },
277 State {
278 name: "inactive"
279 extend: "wide"
280 PropertyChanges { target: searchContainer; width: units.gu(25) }
281 PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
282 PropertyChanges { target: searchField; highlighted: false }
283 },
284 State {
285 name: "narrowActive"
286 extend: "narrow"
287 PropertyChanges { target: header; contentY: 0 }
288 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
289 },
290 State {
291 name: "narrowInactive"
292 extend: "narrow"
293 PropertyChanges { target: header; contentY: header.height }
294 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
295 }
296 ]
298 transitions: [
299 Transition {
300 to: "active"
301 SequentialAnimation {
302 ParallelAnimation {
303 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
304 PropertyAction { target: primaryImage; property: "source" }
305 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
306 }
307 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
308 }
309 },
310 Transition {
311 to: "inactive"
312 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
313 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
314 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
315 },
316 Transition {
317 to: "narrowActive"
318 SequentialAnimation {
319 ParallelAnimation {
320 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
321 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
322 }
323 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
324 }
325 },
326 Transition {
327 to: "narrowInactive"
328 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
329 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
330 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
331 }
332 ]
334 Component {
335 id: popoverComponent
336 Popover {
337 id: popover
339 // FIXME: this should go into the first item below, but enable: false
340 // prevents mouse events propagation
341 AbstractButton {
342 anchors {
343 top: parent.top
344 right: parent.right
345 }
346 height: units.gu(6)
347 width: height
349 onClicked: searchContainer.closePopover()
351 Image {
352 anchors.centerIn: parent
353 width: units.gu(2)
354 height: units.gu(2)
355 source: "../Dash/graphics/icon_listview_clear.png"
356 }
357 }
359 Column {
360 anchors {
361 top: parent.top
362 left: parent.left
363 right: parent.right
364 }
366 ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
368 Repeater {
369 id: recentSearches
370 model: searchHistory
372 delegate: ListItem.Standard {
373 showDivider: index < recentSearches.count - 1
374 text: query
375 onClicked: {
376 searchHistory.addQuery(text)
377 searchField.text = text
378 }
379 }
380 }
381 }
382 }
383 }
385 InverseMouseArea {
386 enabled: searchField.activeFocus
388 anchors {
389 top: parent.top
390 left: parent.left
391 right: parent.right
392 }
394 height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
396 onPressed: searchField.focus = false
397 }
398 }
399 }
400 }
402 BorderImage {
403 id: bottomBorder
404 anchors {
405 top: header.bottom
406 left: parent.left
407 right: parent.right
408 bottom: bottomContainer.top
409 }
411 source: "graphics/PageHeaderBaseDivider.sci"
412 }
414 Item {
415 id: bottomContainer
417 anchors {
418 left: parent.left
419 right: parent.right
420 bottom: parent.bottom
421 }
422 height: childrenRect.height
423 }