on b599083 help sign in
unity8/Components/PageHeader.qml
388 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 /*!
25 \preliminary
26 The text that is shown inside the Page Header
27 \qmlproperty string text
28 */
29 property alias text: label.text
31 property bool searchEntryEnabled: false
32 property alias searchQuery: searchField.text
33 property ListModel searchHistory: SearchHistoryModel {}
34 property Scope scope
36 height: units.gu(8.5)
37 implicitHeight: units.gu(8.5)
39 function triggerSearch() {
40 if (searchEntryEnabled) searchField.forceActiveFocus()
41 }
43 function resetSearch() {
44 if (!searchHistory) return;
46 searchHistory.addQuery(searchField.text);
47 searchField.text = "";
48 searchField.focus = false;
49 }
51 Connections {
52 target: greeter
53 onShownChanged: if (shown) resetSearch()
54 }
56 Flickable {
57 id: header
58 anchors {
59 left: parent.left
60 right: parent.right
61 top: parent.top
62 }
63 height: units.gu(6.5)
65 interactive: false
66 contentHeight: headerContainer.height
67 clip: true
69 contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
71 Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
73 // FIXME this could potentially be simplified to avoid all the containers
74 Item {
75 id: headerContainer
77 width: parent.width
78 height: childrenRect.height
80 Item {
81 id: textContainer
83 width: header.width
84 height: header.height
86 Label {
87 id: label
88 anchors {
89 left: parent.left
90 leftMargin: units.gu(2)
91 right: parent.right
92 verticalCenter: parent.verticalCenter
93 }
95 color: Theme.palette.selected.backgroundText
96 opacity: 0.8
97 font.family: "Ubuntu"
98 font.weight: Font.Light
99 fontSize: "x-large"
100 elide: Text.ElideRight
101 style: Text.Raised
102 styleColor: "black"
103 }
104 }
106 Item {
107 id: searchContainer
108 objectName: "searchContainer"
110 visible: searchEntryEnabled
112 property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
114 property bool active: searchField.text != "" || searchField.activeFocus
115 property var popover: null
117 anchors.right: textContainer.right
118 height: header.height
120 state:
121 if (active && narrowMode) "narrowActive"
122 else if (!active && narrowMode) "narrowInactive"
123 else if (active && !narrowMode) "active"
124 else if (!active && !narrowMode) "inactive"
126 function openPopover() {
127 if (searchHistory.count > 0) {
128 searchContainer.popover = PopupUtils.open(popoverComponent, searchField,
129 {
130 "pointerTarget": pointerPositioner,
131 "contentWidth": searchField.width,
132 "edgeMargins": units.gu(1)
133 }
134 )
135 }
136 }
138 function closePopover() {
139 if (searchContainer.popover) PopupUtils.close(searchContainer.popover)
140 }
142 onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
144 TextField {
145 id: searchField
147 anchors.fill: parent
148 anchors.margins: units.gu(1)
150 hasClearButton: false
152 primaryItem: AbstractButton {
153 enabled: searchField.text != "" && !searchIndicator.running
154 onClicked: {
155 if (searchField.text != "") {
156 searchHistory.addQuery(searchField.text)
157 searchField.text = ""
158 }
159 }
160 height: parent.height
161 width: height
163 ActivityIndicator {
164 id: searchIndicator
165 objectName: "searchIndicator"
167 anchors {
168 verticalCenter: parent.verticalCenter
169 left: parent.left
170 leftMargin: units.gu(0.5)
171 }
173 running: opacity > 0
174 }
176 Image {
177 id: primaryImage
178 objectName: "primaryImage"
179 anchors {
180 verticalCenter: parent.verticalCenter
181 left: parent.left
182 leftMargin: units.gu(0.5)
183 }
184 width: units.gu(3)
185 height: units.gu(3)
186 visible: opacity > 0
187 }
189 Item {
190 id: pointerPositioner
191 anchors.left: parent.right
192 anchors.leftMargin: units.gu(0.5)
193 anchors.top: parent.bottom
194 }
195 }
197 onTextChanged: {
198 if (text != "") searchContainer.closePopover()
199 else if (text == "" && activeFocus) searchContainer.openPopover()
200 }
202 onActiveFocusChanged: {
203 if (!activeFocus) searchContainer.closePopover()
204 }
206 states: [
207 State {
208 name: "searching"
209 when: scope.searchInProgress && searchField.text !== ""
210 PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
211 PropertyChanges { target: primaryImage; opacity: 0 }
212 },
213 State {
214 name: "idle"
215 when: !scope.searchInProgress || searchField.text === ""
216 PropertyChanges { target: searchIndicator; opacity: 0 }
217 PropertyChanges { target: primaryImage; opacity: 1 }
218 }
219 ]
221 transitions: [
222 Transition {
223 to: "searching"
224 reversible: true
225 SequentialAnimation {
226 NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
227 NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
228 }
229 }
230 ]
231 }
233 states: [
234 State {
235 name: "wide"
236 AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
237 AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
238 },
239 State {
240 name: "narrow"
241 PropertyChanges { target: searchField; highlighted: true }
242 AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
243 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
244 },
245 State {
246 name: "active"
247 extend: "wide"
248 PropertyChanges { target: searchContainer; width: units.gu(40) }
249 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
250 PropertyChanges { target: searchField; highlighted: true }
251 },
252 State {
253 name: "inactive"
254 extend: "wide"
255 PropertyChanges { target: searchContainer; width: units.gu(25) }
256 PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
257 PropertyChanges { target: searchField; highlighted: false }
258 },
259 State {
260 name: "narrowActive"
261 extend: "narrow"
262 PropertyChanges { target: header; contentY: 0 }
263 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
264 },
265 State {
266 name: "narrowInactive"
267 extend: "narrow"
268 PropertyChanges { target: header; contentY: header.height }
269 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
270 }
271 ]
273 transitions: [
274 Transition {
275 to: "active"
276 SequentialAnimation {
277 ParallelAnimation {
278 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
279 PropertyAction { target: primaryImage; property: "source" }
280 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
281 }
282 ScriptAction { script: searchContainer.openPopover() }
283 }
284 },
285 Transition {
286 to: "inactive"
287 ScriptAction { script: searchContainer.closePopover() }
288 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
289 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
290 },
291 Transition {
292 to: "narrowActive"
293 SequentialAnimation {
294 ParallelAnimation {
295 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
296 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
297 }
298 ScriptAction { script: searchContainer.openPopover() }
299 }
300 },
301 Transition {
302 to: "narrowInactive"
303 ScriptAction { script: searchContainer.closePopover() }
304 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
305 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
306 }
307 ]
309 Component {
310 id: popoverComponent
311 Popover {
312 id: popover
314 // FIXME: this should go into the first item below, but enable: false
315 // prevents mouse events propagation
316 AbstractButton {
317 anchors {
318 top: parent.top
319 right: parent.right
320 }
321 height: units.gu(6)
322 width: height
324 onClicked: searchContainer.closePopover()
326 Image {
327 anchors.centerIn: parent
328 width: units.gu(2)
329 height: units.gu(2)
330 source: "../Dash/graphics/icon_listview_clear.png"
331 }
332 }
334 Column {
335 anchors {
336 top: parent.top
337 left: parent.left
338 right: parent.right
339 }
341 ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
343 Repeater {
344 id: recentSearches
345 model: searchHistory
347 delegate: ListItem.Standard {
348 showDivider: index < recentSearches.count - 1
349 text: query
350 onClicked: {
351 searchHistory.addQuery(text)
352 searchField.text = text
353 }
354 }
355 }
356 }
357 }
358 }
360 InverseMouseArea {
361 enabled: searchField.activeFocus
363 anchors {
364 top: parent.top
365 left: parent.left
366 right: parent.right
367 }
369 height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
371 onPressed: searchField.focus = false
372 }
373 }
374 }
375 }
377 BorderImage {
378 id: bottomBorder
379 anchors {
380 top: header.bottom
381 left: parent.left
382 right: parent.right
383 bottom: parent.bottom
384 }
386 source: "graphics/PageHeaderBaseDivider.sci"
387 }