on fa40195 help sign in
unity8/qml/Components/PageHeader.qml
412 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 showBackButton: backButton.visible
32 signal backClicked
34 height: units.gu(8.5)
35 implicitHeight: units.gu(8.5)
37 function triggerSearch() {
38 if (searchEntryEnabled) searchField.forceActiveFocus()
39 }
41 function resetSearch() {
42 if (!searchHistory) return;
44 searchHistory.addQuery(searchField.text);
45 unfocus();
46 searchField.text = "";
47 }
49 function unfocus() {
50 searchField.focus = false;
51 }
53 Connections {
54 target: greeter
55 onShownChanged: if (shown) resetSearch()
56 }
58 Flickable {
59 id: header
60 anchors {
61 left: parent.left
62 right: parent.right
63 top: parent.top
64 }
65 height: units.gu(6.5)
67 interactive: false
68 contentHeight: headerContainer.height
69 clip: true
71 contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
73 Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
75 AbstractButton {
76 id: backButton
77 objectName: root.objectName + "_backButton"
78 visible: false
79 height: header.height
80 y: header.contentY
81 anchors {
82 left: parent.left
83 leftMargin: visible ? units.gu(2) : 0
84 }
85 width: visible ? image.width + units.gu(2) : 0
86 onClicked: root.backClicked();
87 Image {
88 id: image
89 anchors.centerIn: parent
90 source: "graphics/headerback.png"
91 }
92 }
94 // FIXME this could potentially be simplified to avoid all the containers
95 Item {
96 id: headerContainer
98 anchors {
99 left: backButton.right
100 right: parent.right
101 }
102 height: childrenRect.height
104 Item {
105 id: itemContainer
107 width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
108 height: header.height
109 }
111 Item {
112 id: searchContainer
113 objectName: "searchContainer"
115 visible: searchEntryEnabled
116 property bool popoverShouldOpen: false
117 property bool popoverShouldClose: false
119 property bool narrowMode: parent.width < units.gu(80)
121 property bool active: searchField.text != "" || searchField.activeFocus
122 property var popover: null
124 anchors.right: headerContainer.right
125 height: header.height
127 state:
128 if (active && narrowMode) "narrowActive"
129 else if (!active && narrowMode) "narrowInactive"
130 else if (active && !narrowMode) "active"
131 else if (!active && !narrowMode) "inactive"
133 onStateChanged: {
134 if (state == "active" || state == "narrowActive") {
135 popoverShouldOpen = true;
136 popoverShouldClose = false;
137 } else {
138 popoverShouldOpen = false;
139 popoverShouldClose = true;
140 }
141 }
143 function openPopover() {
144 if (searchHistory.count > 0) {
145 searchContainer.popover = PopupUtils.open(popoverComponent, pointerPositioner,
146 {
147 "contentWidth": searchField.width,
148 "edgeMargins": units.gu(1)
149 }
150 )
151 }
152 popoverShouldOpen = false;
153 popoverShouldClose = false;
154 }
156 function closePopover() {
157 if (searchContainer.popover) {
158 PopupUtils.close(searchContainer.popover);
159 searchContainer.popover = null;
160 }
161 popoverShouldOpen = false;
162 popoverShouldClose = false;
163 }
165 onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
167 TextField {
168 id: searchField
170 anchors.fill: parent
171 anchors.margins: units.gu(1)
173 inputMethodHints: Qt.ImhNoPredictiveText
174 hasClearButton: false
176 primaryItem: AbstractButton {
177 enabled: searchField.text != "" && !searchIndicator.running
178 onClicked: {
179 if (searchField.text != "") {
180 searchHistory.addQuery(searchField.text)
181 searchField.text = ""
182 }
183 }
184 height: parent.height
185 width: height
187 ActivityIndicator {
188 id: searchIndicator
189 objectName: "searchIndicator"
191 anchors {
192 verticalCenter: parent.verticalCenter
193 left: parent.left
194 leftMargin: units.gu(0.5)
195 }
197 running: opacity > 0
198 }
200 Image {
201 id: primaryImage
202 objectName: "primaryImage"
203 anchors {
204 verticalCenter: parent.verticalCenter
205 left: parent.left
206 leftMargin: units.gu(0.5)
207 }
208 width: units.gu(3)
209 height: units.gu(3)
210 visible: opacity > 0
211 }
213 Item {
214 id: pointerPositioner
215 anchors.left: parent.right
216 anchors.leftMargin: units.gu(0.5)
217 anchors.top: parent.bottom
218 }
219 }
221 onTextChanged: {
222 if (text != "") searchContainer.closePopover()
223 else if (text == "" && activeFocus) searchContainer.openPopover()
224 }
226 onActiveFocusChanged: {
227 if (!activeFocus) searchContainer.closePopover()
228 }
230 states: [
231 State {
232 name: "searching"
233 when: scope && scope.searchInProgress
234 PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
235 PropertyChanges { target: primaryImage; opacity: 0 }
236 },
237 State {
238 name: "idle"
239 when: !scope || !scope.searchInProgress
240 PropertyChanges { target: searchIndicator; opacity: 0 }
241 PropertyChanges { target: primaryImage; opacity: 1 }
242 }
243 ]
245 transitions: [
246 Transition {
247 to: "searching"
248 reversible: true
249 SequentialAnimation {
250 NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
251 NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
252 }
253 }
254 ]
255 }
257 states: [
258 State {
259 name: "wide"
260 AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
261 AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
262 },
263 State {
264 name: "narrow"
265 PropertyChanges { target: searchField; highlighted: true }
266 AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
267 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
268 },
269 State {
270 name: "active"
271 extend: "wide"
272 PropertyChanges { target: searchContainer; width: units.gu(40) }
273 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
274 PropertyChanges { target: searchField; highlighted: true }
275 },
276 State {
277 name: "inactive"
278 extend: "wide"
279 PropertyChanges { target: searchContainer; width: units.gu(25) }
280 PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
281 PropertyChanges { target: searchField; highlighted: false }
282 },
283 State {
284 name: "narrowActive"
285 extend: "narrow"
286 PropertyChanges { target: header; contentY: 0 }
287 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
288 },
289 State {
290 name: "narrowInactive"
291 extend: "narrow"
292 PropertyChanges { target: header; contentY: header.height }
293 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
294 }
295 ]
297 transitions: [
298 Transition {
299 to: "active"
300 SequentialAnimation {
301 ParallelAnimation {
302 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
303 PropertyAction { target: primaryImage; property: "source" }
304 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
305 }
306 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
307 }
308 },
309 Transition {
310 to: "inactive"
311 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
312 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
313 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
314 },
315 Transition {
316 to: "narrowActive"
317 SequentialAnimation {
318 ParallelAnimation {
319 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
320 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
321 }
322 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
323 }
324 },
325 Transition {
326 to: "narrowInactive"
327 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
328 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
329 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
330 }
331 ]
333 Component {
334 id: popoverComponent
335 Popover {
336 id: popover
338 // FIXME: this should go into the first item below, but enable: false
339 // prevents mouse events propagation
340 AbstractButton {
341 anchors {
342 top: parent.top
343 right: parent.right
344 }
345 height: units.gu(6)
346 width: height
348 onClicked: searchContainer.closePopover()
350 Image {
351 anchors.centerIn: parent
352 width: units.gu(2)
353 height: units.gu(2)
354 source: "../Dash/graphics/icon_listview_clear.png"
355 }
356 }
358 Column {
359 anchors {
360 top: parent.top
361 left: parent.left
362 right: parent.right
363 }
365 ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
367 Repeater {
368 id: recentSearches
369 model: searchHistory
371 delegate: ListItem.Standard {
372 showDivider: index < recentSearches.count - 1
373 text: query
374 onClicked: {
375 searchHistory.addQuery(text)
376 searchField.text = text
377 }
378 }
379 }
380 }
381 }
382 }
384 InverseMouseArea {
385 enabled: searchField.activeFocus
387 anchors {
388 top: parent.top
389 left: parent.left
390 right: parent.right
391 }
393 height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
395 onPressed: searchField.focus = false
396 }
397 }
398 }
399 }
401 BorderImage {
402 id: bottomBorder
403 anchors {
404 top: header.bottom
405 left: parent.left
406 right: parent.right
407 bottom: parent.bottom
408 }
410 source: "graphics/PageHeaderBaseDivider.sci"
411 }