on ad55782 help sign in
unity8/Components/PageHeader.qml
377 lines 16 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.1
23Item {
24 id: root
25 property bool searchEntryEnabled: false
26 property alias searchQuery: searchField.text
27 property ListModel searchHistory: SearchHistoryModel {}
28 property Scope scope
29 property alias childItem: itemContainer.children
31 height: units.gu(8.5)
32 implicitHeight: units.gu(8.5)
34 function triggerSearch() {
35 if (searchEntryEnabled) searchField.forceActiveFocus()
36 }
38 function resetSearch() {
39 if (!searchHistory) return;
41 searchHistory.addQuery(searchField.text);
42 searchField.text = "";
43 searchField.focus = false;
44 }
46 function unfocus() {
47 searchField.focus = false;
48 }
50 Connections {
51 target: greeter
52 onShownChanged: if (shown) resetSearch()
53 }
55 Flickable {
56 id: header
57 anchors {
58 left: parent.left
59 right: parent.right
60 top: parent.top
61 }
62 height: units.gu(6.5)
64 interactive: false
65 contentHeight: headerContainer.height
66 clip: true
68 contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
70 Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
72 // FIXME this could potentially be simplified to avoid all the containers
73 Item {
74 id: headerContainer
76 width: parent.width
77 height: childrenRect.height
79 Item {
80 id: itemContainer
82 width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
83 height: header.height
84 }
86 Item {
87 id: searchContainer
88 objectName: "searchContainer"
90 visible: searchEntryEnabled
92 property bool narrowMode: {
93 if (root.childItem.length == 0)
94 return false;
95 if (root.childItem.length > 1) {
96 console.log("ERROR: PageHeader childItem must be a single item");
97 return false;
98 }
99 var item = root.childItem[0];
100 return parent.width < item.implicitWidth + units.gu(50)
101 }
103 property bool active: searchField.text != "" || searchField.activeFocus
104 property var popover: null
106 anchors.right: headerContainer.right
107 height: header.height
109 state:
110 if (active && narrowMode) "narrowActive"
111 else if (!active && narrowMode) "narrowInactive"
112 else if (active && !narrowMode) "active"
113 else if (!active && !narrowMode) "inactive"
115 function openPopover() {
116 if (searchHistory.count > 0) {
117 searchContainer.popover = PopupUtils.open(popoverComponent, searchField,
118 {
119 "pointerTarget": pointerPositioner,
120 "contentWidth": searchField.width,
121 "edgeMargins": units.gu(1)
122 }
123 )
124 }
125 }
127 function closePopover() {
128 if (searchContainer.popover) PopupUtils.close(searchContainer.popover)
129 }
131 onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
133 TextField {
134 id: searchField
136 anchors.fill: parent
137 anchors.margins: units.gu(1)
139 hasClearButton: false
141 primaryItem: AbstractButton {
142 enabled: searchField.text != "" && !searchIndicator.running
143 onClicked: {
144 if (searchField.text != "") {
145 searchHistory.addQuery(searchField.text)
146 searchField.text = ""
147 }
148 }
149 height: parent.height
150 width: height
152 ActivityIndicator {
153 id: searchIndicator
154 objectName: "searchIndicator"
156 anchors {
157 verticalCenter: parent.verticalCenter
158 left: parent.left
159 leftMargin: units.gu(0.5)
160 }
162 running: opacity > 0
163 }
165 Image {
166 id: primaryImage
167 objectName: "primaryImage"
168 anchors {
169 verticalCenter: parent.verticalCenter
170 left: parent.left
171 leftMargin: units.gu(0.5)
172 }
173 width: units.gu(3)
174 height: units.gu(3)
175 visible: opacity > 0
176 }
178 Item {
179 id: pointerPositioner
180 anchors.left: parent.right
181 anchors.leftMargin: units.gu(0.5)
182 anchors.top: parent.bottom
183 }
184 }
186 onTextChanged: {
187 if (text != "") searchContainer.closePopover()
188 else if (text == "" && activeFocus) searchContainer.openPopover()
189 }
191 onActiveFocusChanged: {
192 if (!activeFocus) searchContainer.closePopover()
193 }
195 states: [
196 State {
197 name: "searching"
198 when: scope && scope.searchInProgress
199 PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
200 PropertyChanges { target: primaryImage; opacity: 0 }
201 },
202 State {
203 name: "idle"
204 when: !scope || !scope.searchInProgress
205 PropertyChanges { target: searchIndicator; opacity: 0 }
206 PropertyChanges { target: primaryImage; opacity: 1 }
207 }
208 ]
210 transitions: [
211 Transition {
212 to: "searching"
213 reversible: true
214 SequentialAnimation {
215 NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
216 NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
217 }
218 }
219 ]
220 }
222 states: [
223 State {
224 name: "wide"
225 AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
226 AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
227 },
228 State {
229 name: "narrow"
230 PropertyChanges { target: searchField; highlighted: true }
231 AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
232 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
233 },
234 State {
235 name: "active"
236 extend: "wide"
237 PropertyChanges { target: searchContainer; width: units.gu(40) }
238 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
239 PropertyChanges { target: searchField; highlighted: true }
240 },
241 State {
242 name: "inactive"
243 extend: "wide"
244 PropertyChanges { target: searchContainer; width: units.gu(25) }
245 PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
246 PropertyChanges { target: searchField; highlighted: false }
247 },
248 State {
249 name: "narrowActive"
250 extend: "narrow"
251 PropertyChanges { target: header; contentY: 0 }
252 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
253 },
254 State {
255 name: "narrowInactive"
256 extend: "narrow"
257 PropertyChanges { target: header; contentY: header.height }
258 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
259 }
260 ]
262 transitions: [
263 Transition {
264 to: "active"
265 SequentialAnimation {
266 ParallelAnimation {
267 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
268 PropertyAction { target: primaryImage; property: "source" }
269 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
270 }
271 ScriptAction { script: searchContainer.openPopover() }
272 }
273 },
274 Transition {
275 to: "inactive"
276 ScriptAction { script: searchContainer.closePopover() }
277 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
278 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
279 },
280 Transition {
281 to: "narrowActive"
282 SequentialAnimation {
283 ParallelAnimation {
284 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
285 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
286 }
287 ScriptAction { script: searchContainer.openPopover() }
288 }
289 },
290 Transition {
291 to: "narrowInactive"
292 ScriptAction { script: searchContainer.closePopover() }
293 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
294 AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
295 }
296 ]
298 Component {
299 id: popoverComponent
300 Popover {
301 id: popover
303 // FIXME: this should go into the first item below, but enable: false
304 // prevents mouse events propagation
305 AbstractButton {
306 anchors {
307 top: parent.top
308 right: parent.right
309 }
310 height: units.gu(6)
311 width: height
313 onClicked: searchContainer.closePopover()
315 Image {
316 anchors.centerIn: parent
317 width: units.gu(2)
318 height: units.gu(2)
319 source: "../Dash/graphics/icon_listview_clear.png"
320 }
321 }
323 Column {
324 anchors {
325 top: parent.top
326 left: parent.left
327 right: parent.right
328 }
330 ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
332 Repeater {
333 id: recentSearches
334 model: searchHistory
336 delegate: ListItem.Standard {
337 showDivider: index < recentSearches.count - 1
338 text: query
339 onClicked: {
340 searchHistory.addQuery(text)
341 searchField.text = text
342 }
343 }
344 }
345 }
346 }
347 }
349 InverseMouseArea {
350 enabled: searchField.activeFocus
352 anchors {
353 top: parent.top
354 left: parent.left
355 right: parent.right
356 }
358 height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
360 onPressed: searchField.focus = false
361 }
362 }
363 }
364 }
366 BorderImage {
367 id: bottomBorder
368 anchors {
369 top: header.bottom
370 left: parent.left
371 right: parent.right
372 bottom: parent.bottom
373 }
375 source: "graphics/PageHeaderBaseDivider.sci"
376 }