on bc0a410 help sign in
unity8/Components/PageHeader.qml
392 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 function unfocus() {
52 searchField.focus = false;
53 }
55 Connections {
56 target: greeter
57 onShownChanged: if (shown) resetSearch()
58 }
60 Flickable {
61 id: header
62 anchors {
63 left: parent.left
64 right: parent.right
65 top: parent.top
66 }
67 height: units.gu(6.5)
69 interactive: false
70 contentHeight: headerContainer.height
71 clip: true
73 contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
75 Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
77 // FIXME this could potentially be simplified to avoid all the containers
78 Item {
79 id: headerContainer
81 width: parent.width
82 height: childrenRect.height
84 Item {
85 id: textContainer
87 width: header.width
88 height: header.height
90 Label {
91 id: label
92 anchors {
93 left: parent.left
94 leftMargin: units.gu(2)
95 right: parent.right
96 verticalCenter: parent.verticalCenter
97 }
99 color: Theme.palette.selected.backgroundText
100 opacity: 0.8
101 font.family: "Ubuntu"
102 font.weight: Font.Light
103 fontSize: "x-large"
104 elide: Text.ElideRight
105 style: Text.Raised
106 styleColor: "black"
107 }
108 }
110 Item {
111 id: searchContainer
112 objectName: "searchContainer"
114 visible: searchEntryEnabled
116 property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
118 property bool active: searchField.text != "" || searchField.activeFocus
119 property var popover: null
121 anchors.right: textContainer.right
122 height: header.height
124 state:
125 if (active && narrowMode) "narrowActive"
126 else if (!active && narrowMode) "narrowInactive"
127 else if (active && !narrowMode) "active"
128 else if (!active && !narrowMode) "inactive"
130 function openPopover() {
131 if (searchHistory.count > 0) {
132 searchContainer.popover = PopupUtils.open(popoverComponent, searchField,
133 {
134 "pointerTarget": pointerPositioner,
135 "contentWidth": searchField.width,
136 "edgeMargins": units.gu(1)
137 }
138 )
139 }
140 }
142 function closePopover() {
143 if (searchContainer.popover) PopupUtils.close(searchContainer.popover)
144 }
146 onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
148 TextField {
149 id: searchField
151 anchors.fill: parent
152 anchors.margins: units.gu(1)
154 hasClearButton: false
156 primaryItem: AbstractButton {
157 enabled: searchField.text != "" && !searchIndicator.running
158 onClicked: {
159 if (searchField.text != "") {
160 searchHistory.addQuery(searchField.text)
161 searchField.text = ""
162 }
163 }
164 height: parent.height
165 width: height
167 ActivityIndicator {
168 id: searchIndicator
169 objectName: "searchIndicator"
171 anchors {
172 verticalCenter: parent.verticalCenter
173 left: parent.left
174 leftMargin: units.gu(0.5)
175 }
177 running: opacity > 0
178 }
180 Image {
181 id: primaryImage
182 objectName: "primaryImage"
183 anchors {
184 verticalCenter: parent.verticalCenter
185 left: parent.left
186 leftMargin: units.gu(0.5)
187 }
188 width: units.gu(3)
189 height: units.gu(3)
190 visible: opacity > 0
191 }
193 Item {
194 id: pointerPositioner
195 anchors.left: parent.right
196 anchors.leftMargin: units.gu(0.5)
197 anchors.top: parent.bottom
198 }
199 }
201 onTextChanged: {
202 if (text != "") searchContainer.closePopover()
203 else if (text == "" && activeFocus) searchContainer.openPopover()
204 }
206 onActiveFocusChanged: {
207 if (!activeFocus) searchContainer.closePopover()
208 }
210 states: [
211 State {
212 name: "searching"
213 when: scope && scope.searchInProgress
214 PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
215 PropertyChanges { target: primaryImage; opacity: 0 }
216 },
217 State {
218 name: "idle"
219 when: !scope || !scope.searchInProgress
220 PropertyChanges { target: searchIndicator; opacity: 0 }
221 PropertyChanges { target: primaryImage; opacity: 1 }
222 }
223 ]
225 transitions: [
226 Transition {
227 to: "searching"
228 reversible: true
229 SequentialAnimation {
230 NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
231 NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
232 }
233 }
234 ]
235 }
237 states: [
238 State {
239 name: "wide"
240 AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
241 AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
242 },
243 State {
244 name: "narrow"
245 PropertyChanges { target: searchField; highlighted: true }
246 AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
247 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
248 },
249 State {
250 name: "active"
251 extend: "wide"
252 PropertyChanges { target: searchContainer; width: units.gu(40) }
253 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
254 PropertyChanges { target: searchField; highlighted: true }
255 },
256 State {
257 name: "inactive"
258 extend: "wide"
259 PropertyChanges { target: searchContainer; width: units.gu(25) }
260 PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
261 PropertyChanges { target: searchField; highlighted: false }
262 },
263 State {
264 name: "narrowActive"
265 extend: "narrow"
266 PropertyChanges { target: header; contentY: 0 }
267 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
268 },
269 State {
270 name: "narrowInactive"
271 extend: "narrow"
272 PropertyChanges { target: header; contentY: header.height }
273 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
274 }
275 ]
277 transitions: [
278 Transition {
279 to: "active"
280 SequentialAnimation {
281 ParallelAnimation {
282 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
283 PropertyAction { target: primaryImage; property: "source" }
284 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
285 }
286 ScriptAction { script: searchContainer.openPopover() }
287 }
288 },
289 Transition {
290 to: "inactive"
291 ScriptAction { script: searchContainer.closePopover() }
292 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
293 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
294 },
295 Transition {
296 to: "narrowActive"
297 SequentialAnimation {
298 ParallelAnimation {
299 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
300 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
301 }
302 ScriptAction { script: searchContainer.openPopover() }
303 }
304 },
305 Transition {
306 to: "narrowInactive"
307 ScriptAction { script: searchContainer.closePopover() }
308 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
309 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
310 }
311 ]
313 Component {
314 id: popoverComponent
315 Popover {
316 id: popover
318 // FIXME: this should go into the first item below, but enable: false
319 // prevents mouse events propagation
320 AbstractButton {
321 anchors {
322 top: parent.top
323 right: parent.right
324 }
325 height: units.gu(6)
326 width: height
328 onClicked: searchContainer.closePopover()
330 Image {
331 anchors.centerIn: parent
332 width: units.gu(2)
333 height: units.gu(2)
334 source: "../Dash/graphics/icon_listview_clear.png"
335 }
336 }
338 Column {
339 anchors {
340 top: parent.top
341 left: parent.left
342 right: parent.right
343 }
345 ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
347 Repeater {
348 id: recentSearches
349 model: searchHistory
351 delegate: ListItem.Standard {
352 showDivider: index < recentSearches.count - 1
353 text: query
354 onClicked: {
355 searchHistory.addQuery(text)
356 searchField.text = text
357 }
358 }
359 }
360 }
361 }
362 }
364 InverseMouseArea {
365 enabled: searchField.activeFocus
367 anchors {
368 top: parent.top
369 left: parent.left
370 right: parent.right
371 }
373 height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
375 onPressed: searchField.focus = false
376 }
377 }
378 }
379 }
381 BorderImage {
382 id: bottomBorder
383 anchors {
384 top: header.bottom
385 left: parent.left
386 right: parent.right
387 bottom: parent.bottom
388 }
390 source: "graphics/PageHeaderBaseDivider.sci"
391 }