on 5bc8421 help sign in
unity8/Components/PageHeader.qml
345 lines 14 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
22Item {
23 /*!
24 \preliminary
25 The text that is shown inside the Page Header
26 \qmlproperty string text
27 */
28 property alias text: label.text
30 property bool searchEntryEnabled: false
31 property alias searchQuery: searchField.text
32 property ListModel searchHistory: SearchHistoryModel {}
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 searchField.text = "";
46 searchField.focus = false;
47 }
49 Connections {
50 target: greeter
51 onShownChanged: if (shown) resetSearch()
52 }
54 Flickable {
55 id: header
56 anchors {
57 left: parent.left
58 right: parent.right
59 top: parent.top
60 }
61 height: units.gu(6.5)
63 interactive: false
64 contentHeight: headerContainer.height
65 clip: true
67 contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
69 Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
71 // FIXME this could potentially be simplified to avoid all the containers
72 Item {
73 id: headerContainer
75 width: parent.width
76 height: childrenRect.height
78 Item {
79 id: textContainer
81 width: header.width
82 height: header.height
84 Label {
85 id: label
86 anchors {
87 left: parent.left
88 leftMargin: units.gu(2)
89 right: parent.right
90 verticalCenter: parent.verticalCenter
91 }
93 color: Theme.palette.selected.backgroundText
94 opacity: 0.8
95 font.family: "Ubuntu"
96 font.weight: Font.Light
97 fontSize: "x-large"
98 elide: Text.ElideRight
99 style: Text.Raised
100 styleColor: "black"
101 }
102 }
104 Item {
105 id: searchContainer
106 objectName: "searchContainer"
108 visible: searchEntryEnabled
110 property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
112 property bool active: searchField.text != "" || searchField.activeFocus
113 property var popover: null
115 anchors.right: textContainer.right
116 height: header.height
118 state:
119 if (active && narrowMode) "narrowActive"
120 else if (!active && narrowMode) "narrowInactive"
121 else if (active && !narrowMode) "active"
122 else if (!active && !narrowMode) "inactive"
124 function openPopover() {
125 if (searchHistory.count > 0) {
126 searchContainer.popover = PopupUtils.open(popoverComponent, searchField,
127 {
128 "pointerTarget": pointerPositioner,
129 "contentWidth": searchField.width,
130 "edgeMargins": units.gu(1)
131 }
132 )
133 }
134 }
136 function closePopover() {
137 if (searchContainer.popover) PopupUtils.close(searchContainer.popover)
138 }
140 onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
142 TextField {
143 id: searchField
145 anchors.fill: parent
146 anchors.margins: units.gu(1)
148 hasClearButton: false
150 primaryItem: AbstractButton {
151 enabled: searchField.text != ""
152 onClicked: {
153 if (searchField.text != "") {
154 searchHistory.addQuery(searchField.text)
155 searchField.text = ""
156 }
157 }
158 height: parent.height
159 width: height
161 Image {
162 id: primaryImage
163 anchors {
164 verticalCenter: parent.verticalCenter
165 left: parent.left
166 leftMargin: units.gu(0.5)
167 }
168 width: units.gu(3)
169 height: units.gu(3)
170 }
172 Item {
173 id: pointerPositioner
174 anchors.left: parent.right
175 anchors.leftMargin: units.gu(0.5)
176 anchors.top: parent.bottom
177 }
178 }
180 onTextChanged: {
181 if (text != "") searchContainer.closePopover()
182 else if (text == "" && activeFocus) searchContainer.openPopover()
183 }
185 onActiveFocusChanged: {
186 if (!activeFocus) searchContainer.closePopover()
187 }
188 }
190 states: [
191 State {
192 name: "wide"
193 AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
194 AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
195 },
196 State {
197 name: "narrow"
198 PropertyChanges { target: searchField; highlighted: true }
199 AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
200 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
201 },
202 State {
203 name: "active"
204 extend: "wide"
205 PropertyChanges { target: searchContainer; width: units.gu(40) }
206 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
207 PropertyChanges { target: searchField; highlighted: true }
208 },
209 State {
210 name: "inactive"
211 extend: "wide"
212 PropertyChanges { target: searchContainer; width: units.gu(25) }
213 PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
214 PropertyChanges { target: searchField; highlighted: false }
215 },
216 State {
217 name: "narrowActive"
218 extend: "narrow"
219 PropertyChanges { target: header; contentY: 0 }
220 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
221 },
222 State {
223 name: "narrowInactive"
224 extend: "narrow"
225 PropertyChanges { target: header; contentY: header.height }
226 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
227 }
228 ]
230 transitions: [
231 Transition {
232 to: "active"
233 SequentialAnimation {
234 ParallelAnimation {
235 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
236 PropertyAction { target: primaryImage; property: "source" }
237 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
238 }
239 ScriptAction { script: searchContainer.openPopover() }
240 }
241 },
242 Transition {
243 to: "inactive"
244 ScriptAction { script: searchContainer.closePopover() }
245 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
246 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
247 },
248 Transition {
249 to: "narrowActive"
250 SequentialAnimation {
251 ParallelAnimation {
252 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
253 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
254 }
255 ScriptAction { script: searchContainer.openPopover() }
256 }
257 },
258 Transition {
259 to: "narrowInactive"
260 ScriptAction { script: searchContainer.closePopover() }
261 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
262 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
263 }
264 ]
266 Component {
267 id: popoverComponent
268 Popover {
269 id: popover
271 // FIXME: this should go into the first item below, but enable: false
272 // prevents mouse events propagation
273 AbstractButton {
274 anchors {
275 top: parent.top
276 right: parent.right
277 }
278 height: units.gu(6)
279 width: height
281 onClicked: searchContainer.closePopover()
283 Image {
284 anchors.centerIn: parent
285 width: units.gu(2)
286 height: units.gu(2)
287 source: "../Dash/graphics/icon_listview_clear.png"
288 }
289 }
291 Column {
292 anchors {
293 top: parent.top
294 left: parent.left
295 right: parent.right
296 }
298 ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
300 Repeater {
301 id: recentSearches
302 model: searchHistory
304 delegate: ListItem.Standard {
305 showDivider: index < recentSearches.count - 1
306 text: query
307 onClicked: {
308 searchHistory.addQuery(text)
309 searchField.text = text
310 }
311 }
312 }
313 }
314 }
315 }
317 InverseMouseArea {
318 enabled: searchField.activeFocus
320 anchors {
321 top: parent.top
322 left: parent.left
323 right: parent.right
324 }
326 height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
328 onPressed: searchField.focus = false
329 }
330 }
331 }
332 }
334 BorderImage {
335 id: bottomBorder
336 anchors {
337 top: header.bottom
338 left: parent.left
339 right: parent.right
340 bottom: parent.bottom
341 }
343 source: "graphics/PageHeaderBaseDivider.sci"
344 }