on 5f72b85 help sign in
unity8/Components/PageHeader.qml
346 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
21import SessionManager 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 {}
35 height: units.gu(8.5)
36 implicitHeight: units.gu(8.5)
38 function triggerSearch() {
39 if (searchEntryEnabled) searchField.forceActiveFocus()
40 }
42 function resetSearch() {
43 if (!searchHistory) return;
45 searchHistory.addQuery(searchField.text);
46 searchField.text = "";
47 searchField.focus = false;
48 }
50 Connections {
51 target: SessionManager
52 onActiveChanged: if (!SessionManager.active) 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: textContainer
82 width: header.width
83 height: header.height
85 Label {
86 id: label
87 anchors {
88 left: parent.left
89 leftMargin: units.gu(2)
90 right: parent.right
91 verticalCenter: parent.verticalCenter
92 }
94 color: Theme.palette.selected.backgroundText
95 opacity: 0.8
96 font.family: "Ubuntu"
97 font.weight: Font.Light
98 fontSize: "x-large"
99 elide: Text.ElideRight
100 style: Text.Raised
101 styleColor: "black"
102 }
103 }
105 Item {
106 id: searchContainer
107 objectName: "searchContainer"
109 visible: searchEntryEnabled
111 property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
113 property bool active: searchField.text != "" || searchField.activeFocus
114 property var popover: null
116 anchors.right: textContainer.right
117 height: header.height
119 state:
120 if (active && narrowMode) "narrowActive"
121 else if (!active && narrowMode) "narrowInactive"
122 else if (active && !narrowMode) "active"
123 else if (!active && !narrowMode) "inactive"
125 function openPopover() {
126 if (searchHistory.count > 0) {
127 searchContainer.popover = PopupUtils.open(popoverComponent, searchField,
128 {
129 "pointerTarget": pointerPositioner,
130 "contentWidth": searchField.width,
131 "edgeMargins": units.gu(1)
132 }
133 )
134 }
135 }
137 function closePopover() {
138 if (searchContainer.popover) PopupUtils.close(searchContainer.popover)
139 }
141 onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
143 TextField {
144 id: searchField
146 anchors.fill: parent
147 anchors.margins: units.gu(1)
149 hasClearButton: false
151 primaryItem: AbstractButton {
152 enabled: searchField.text != ""
153 onClicked: {
154 if (searchField.text != "") {
155 searchHistory.addQuery(searchField.text)
156 searchField.text = ""
157 }
158 }
159 height: parent.height
160 width: height
162 Image {
163 id: primaryImage
164 anchors {
165 verticalCenter: parent.verticalCenter
166 left: parent.left
167 leftMargin: units.gu(0.5)
168 }
169 width: units.gu(3)
170 height: units.gu(3)
171 }
173 Item {
174 id: pointerPositioner
175 anchors.left: parent.right
176 anchors.leftMargin: units.gu(0.5)
177 anchors.top: parent.bottom
178 }
179 }
181 onTextChanged: {
182 if (text != "") searchContainer.closePopover()
183 else if (text == "" && activeFocus) searchContainer.openPopover()
184 }
186 onActiveFocusChanged: {
187 if (!activeFocus) searchContainer.closePopover()
188 }
189 }
191 states: [
192 State {
193 name: "wide"
194 AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
195 AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
196 },
197 State {
198 name: "narrow"
199 PropertyChanges { target: searchField; highlighted: true }
200 AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
201 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
202 },
203 State {
204 name: "active"
205 extend: "wide"
206 PropertyChanges { target: searchContainer; width: units.gu(40) }
207 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
208 PropertyChanges { target: searchField; highlighted: true }
209 },
210 State {
211 name: "inactive"
212 extend: "wide"
213 PropertyChanges { target: searchContainer; width: units.gu(25) }
214 PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
215 PropertyChanges { target: searchField; highlighted: false }
216 },
217 State {
218 name: "narrowActive"
219 extend: "narrow"
220 PropertyChanges { target: header; contentY: 0 }
221 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
222 },
223 State {
224 name: "narrowInactive"
225 extend: "narrow"
226 PropertyChanges { target: header; contentY: header.height }
227 PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
228 }
229 ]
231 transitions: [
232 Transition {
233 to: "active"
234 SequentialAnimation {
235 ParallelAnimation {
236 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
237 PropertyAction { target: primaryImage; property: "source" }
238 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
239 }
240 ScriptAction { script: searchContainer.openPopover() }
241 }
242 },
243 Transition {
244 to: "inactive"
245 ScriptAction { script: searchContainer.closePopover() }
246 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
247 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
248 },
249 Transition {
250 to: "narrowActive"
251 SequentialAnimation {
252 ParallelAnimation {
253 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
254 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
255 }
256 ScriptAction { script: searchContainer.openPopover() }
257 }
258 },
259 Transition {
260 to: "narrowInactive"
261 ScriptAction { script: searchContainer.closePopover() }
262 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
263 AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
264 }
265 ]
267 Component {
268 id: popoverComponent
269 Popover {
270 id: popover
272 // FIXME: this should go into the first item below, but enable: false
273 // prevents mouse events propagation
274 AbstractButton {
275 anchors {
276 top: parent.top
277 right: parent.right
278 }
279 height: units.gu(6)
280 width: height
282 onClicked: searchContainer.closePopover()
284 Image {
285 anchors.centerIn: parent
286 width: units.gu(2)
287 height: units.gu(2)
288 source: "../Dash/graphics/icon_listview_clear.png"
289 }
290 }
292 Column {
293 anchors {
294 top: parent.top
295 left: parent.left
296 right: parent.right
297 }
299 ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
301 Repeater {
302 id: recentSearches
303 model: searchHistory
305 delegate: ListItem.Standard {
306 showDivider: index < recentSearches.count - 1
307 text: query
308 onClicked: {
309 searchHistory.addQuery(text)
310 searchField.text = text
311 }
312 }
313 }
314 }
315 }
316 }
318 InverseMouseArea {
319 enabled: searchField.activeFocus
321 anchors {
322 top: parent.top
323 left: parent.left
324 right: parent.right
325 }
327 height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
329 onPressed: searchField.focus = false
330 }
331 }
332 }
333 }
335 BorderImage {
336 id: bottomBorder
337 anchors {
338 top: header.bottom
339 left: parent.left
340 right: parent.right
341 bottom: parent.bottom
342 }
344 source: "graphics/PageHeaderBaseDivider.sci"
345 }