on 6c03f35 help sign in
unity8/qml/Components/PageHeader.qml
333 lines 11 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.2
18import Ubuntu.Components 1.1
19import Ubuntu.Components.Themes.Ambiance 1.1
20import Ubuntu.Components.Popups 1.0
21import Ubuntu.Components.ListItems 1.0
22import "SearchHistoryModel"
24Item {
25 id: root
26 implicitHeight: headerContainer.height + units.gu(2) + bottomContainer.height
28 property bool showBackButton: false
29 property string title
31 property bool searchEntryEnabled: false
32 property ListModel searchHistory: SearchHistoryModel
33 property alias searchQuery: searchTextField.text
34 property bool searchInProgress: false
36 property alias bottomItem: bottomContainer.children
38 // TODO We should use foreground for the icons
39 // of the toolbar but unfortunately Action does not have
40 // the keyColor property as Icon does :-/
41 property var styleTool: QtObject {
42 readonly property color foreground: "grey"
43 readonly property url headerLogo: ""
44 }
46 signal backClicked()
48 onStyleToolChanged: refreshLogo()
50 function triggerSearch() {
51 if (searchEntryEnabled) {
52 headerContainer.showSearch = true;
53 searchTextField.forceActiveFocus();
54 }
55 }
57 function resetSearch(keepFocus) {
58 if (searchHistory) {
59 searchHistory.addQuery(searchTextField.text);
60 }
61 if (!keepFocus) {
62 unfocus();
63 }
64 searchTextField.text = "";
65 if (headerContainer.popover != null) {
66 PopupUtils.close(headerContainer.popover);
67 }
68 }
70 function unfocus() {
71 searchTextField.focus = false;
72 if (!searchTextField.text) {
73 headerContainer.showSearch = false;
74 }
75 }
77 function openSearchHistory() {
78 if (openSearchAnimation.running) {
79 openSearchAnimation.openSearchHistory = true;
80 } else if (root.searchHistory.count > 0 && headerContainer.popover == null) {
81 headerContainer.popover = PopupUtils.open(popoverComponent, searchTextField,
82 {
83 "contentWidth": searchTextField.width,
84 "edgeMargins": units.gu(1)
85 }
86 );
87 }
88 }
90 function refreshLogo() {
91 if (styleTool.headerLogo != "") {
92 header.contents = imageComponent.createObject();
93 } else if (header.contents) {
94 header.contents.destroy();
95 header.contents = null;
96 }
97 }
99 Connections {
100 target: styleTool
101 onHeaderLogoChanged: root.refreshLogo()
102 }
104 InverseMouseArea {
105 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
106 visible: headerContainer.showSearch
107 onPressed: {
108 if (headerContainer.popover) {
109 PopupUtils.close(headerContainer.popover);
110 }
111 if (!searchTextField.text) {
112 headerContainer.showSearch = false;
113 }
114 searchTextField.focus = false;
115 mouse.accepted = false;
116 }
117 }
119 Flickable {
120 id: headerContainer
121 objectName: "headerContainer"
122 clip: true
123 anchors { left: parent.left; top: parent.top; right: parent.right }
124 height: units.gu(6.5)
125 contentHeight: headersColumn.height
126 interactive: false
127 contentY: showSearch ? 0 : height
129 property bool showSearch: false
130 property var popover: null
132 Behavior on contentY {
133 UbuntuNumberAnimation {
134 id: openSearchAnimation
135 property bool openSearchHistory: false
137 onRunningChanged: {
138 if (!running && openSearchAnimation.openSearchHistory) {
139 openSearchAnimation.openSearchHistory = false;
140 root.openSearchHistory();
141 }
142 }
143 }
144 }
146 Column {
147 id: headersColumn
148 anchors { left: parent.left; right: parent.right }
150 PageHeadStyle {
151 id: searchHeader
152 anchors { left: parent.left; right: parent.right }
153 height: headerContainer.height
154 contentHeight: height
155 separatorSource: ""
156 // Required to keep PageHeadStyle noise down as it expects the Page's properties around.
157 property var styledItem: searchHeader
158 property string title
159 property var config: PageHeadConfiguration {
160 backAction: Action {
161 iconName: "back"
162 onTriggered: {
163 root.resetSearch();
164 headerContainer.showSearch = false;
165 }
166 }
167 }
168 property var contents: TextField {
169 id: searchTextField
170 hasClearButton: false
171 anchors {
172 fill: parent
173 leftMargin: units.gu(1)
174 topMargin: units.gu(1)
175 bottomMargin: units.gu(1)
176 rightMargin: root.width > units.gu(60) ? root.width - units.gu(40) : units.gu(1)
177 }
179 secondaryItem: AbstractButton {
180 height: searchTextField.height
181 width: height
182 enabled: searchTextField.text.length > 0
184 Image {
185 objectName: "clearIcon"
186 anchors.fill: parent
187 anchors.margins: units.gu(.75)
188 source: "image://theme/clear"
189 opacity: searchTextField.text.length > 0 && !searchActivityIndicator.running
190 visible: opacity > 0
191 Behavior on opacity {
192 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
193 }
194 }
196 ActivityIndicator {
197 id: searchActivityIndicator
198 objectName: "searchIndicator"
199 anchors.fill: parent
200 anchors.margins: units.gu(.75)
201 running: root.searchInProgress
202 opacity: running ? 1 : 0
203 Behavior on opacity {
204 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
205 }
206 }
208 onClicked: {
209 root.resetSearch(true);
210 root.openSearchHistory();
211 }
212 }
214 onActiveFocusChanged: {
215 if (activeFocus) {
216 root.openSearchHistory();
217 }
218 }
219 }
220 }
222 PageHeadStyle {
223 id: header
224 objectName: "pageHeader"
225 anchors { left: parent.left; right: parent.right }
226 height: headerContainer.height
227 contentHeight: height
228 separatorSource: ""
229 textColor: root.styleTool.foreground
230 property var styledItem: header
231 property string title: root.title
232 property var config: PageHeadConfiguration {
233 backAction: Action {
234 iconName: "back"
235 visible: root.showBackButton
236 onTriggered: {
237 root.backClicked();
238 }
239 }
241 actions: [
242 Action {
243 iconName: "search"
244 visible: root.searchEntryEnabled
245 onTriggered: {
246 headerContainer.showSearch = true;
247 searchTextField.forceActiveFocus();
248 }
249 }
250 ]
251 }
253 property var contents: null
254 Component.onCompleted: root.refreshLogo()
256 Component {
257 id: imageComponent
259 Item {
260 anchors { fill: parent; topMargin: units.gu(1); bottomMargin: units.gu(1) }
261 clip: true
262 Image {
263 objectName: "titleImage"
264 anchors.fill: parent
265 source: root.styleTool.headerLogo
266 fillMode: Image.PreserveAspectFit
267 horizontalAlignment: Image.AlignLeft
268 sourceSize.height: height
269 }
270 }
271 }
272 }
273 }
274 }
276 Component {
277 id: popoverComponent
278 Popover {
279 id: popover
280 autoClose: false
282 Component.onDestruction: {
283 headerContainer.popover = null;
284 }
286 Column {
287 anchors {
288 top: parent.top
289 left: parent.left
290 right: parent.right
291 }
293 Repeater {
294 id: recentSearches
295 model: searchHistory
297 delegate: Standard {
298 showDivider: index < recentSearches.count - 1
299 text: query
300 onClicked: {
301 searchHistory.addQuery(text);
302 searchTextField.text = text;
303 PopupUtils.close(popover);
304 }
305 }
306 }
307 }
308 }
309 }
311 BorderImage {
312 id: bottomBorder
313 anchors {
314 top: headerContainer.bottom
315 left: parent.left
316 right: parent.right
317 bottom: bottomContainer.top
318 }
320 source: "graphics/PageHeaderBaseDivider.sci"
321 }
323 Item {
324 id: bottomContainer
326 anchors {
327 left: parent.left
328 right: parent.right
329 bottom: parent.bottom
330 }
331 height: childrenRect.height
332 }