Unity 8
 All Classes Functions Properties
PageHeader.qml
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  */
16 
17 import QtQuick 2.0
18 import Ubuntu.Components 0.1
19 import Ubuntu.Components.Popups 0.1
20 import Ubuntu.Components.ListItems 0.1 as ListItem
21 import Unity 0.2
22 
23 Item {
24  id: root
25  property bool searchEntryEnabled: false
26  property alias searchQuery: searchField.text
27  property ListModel searchHistory
28  property var scope: null
29  property alias childItem: itemContainer.children
30  property alias bottomItem: bottomContainer.children
31  property alias showBackButton: backButton.visible
32 
33  signal backClicked
34 
35  height: header.height + units.gu(2) + bottomContainer.height
36  implicitHeight: header.height + units.gu(2) + bottomContainer.height
37 
38  function triggerSearch() {
39  if (searchEntryEnabled) searchField.forceActiveFocus()
40  }
41 
42  function resetSearch() {
43  if (!searchHistory) return;
44 
45  searchHistory.addQuery(searchField.text);
46  unfocus();
47  searchField.text = "";
48  }
49 
50  function unfocus() {
51  searchField.focus = false;
52  }
53 
54  Connections {
55  target: greeter
56  onShownChanged: if (shown) resetSearch()
57  }
58 
59  Flickable {
60  id: header
61  anchors {
62  left: parent.left
63  right: parent.right
64  top: parent.top
65  }
66  height: units.gu(6.5)
67 
68  interactive: false
69  contentHeight: headerContainer.height
70  clip: true
71 
72  contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
73 
74  Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
75 
76  AbstractButton {
77  id: backButton
78  objectName: root.objectName + "_backButton"
79  visible: false
80  height: header.height
81  y: header.contentY
82  anchors {
83  left: parent.left
84  leftMargin: visible ? units.gu(2) : 0
85  }
86  width: visible ? image.width + units.gu(2) : 0
87  onClicked: root.backClicked();
88  Image {
89  id: image
90  anchors.centerIn: parent
91  source: "graphics/headerback.png"
92  }
93  }
94 
95  // FIXME this could potentially be simplified to avoid all the containers
96  Item {
97  id: headerContainer
98 
99  anchors {
100  left: backButton.right
101  right: parent.right
102  }
103  height: childrenRect.height
104 
105  Item {
106  id: itemContainer
107 
108  width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
109  height: header.height
110  }
111 
112  Item {
113  id: searchContainer
114  objectName: "searchContainer"
115 
116  visible: searchEntryEnabled
117  property bool popoverShouldOpen: false
118  property bool popoverShouldClose: false
119 
120  property bool narrowMode: parent.width < units.gu(80)
121 
122  property bool active: searchField.text != "" || searchField.activeFocus
123  property var popover: null
124 
125  anchors.right: headerContainer.right
126  height: header.height
127 
128  state:
129  if (active && narrowMode) "narrowActive"
130  else if (!active && narrowMode) "narrowInactive"
131  else if (active && !narrowMode) "active"
132  else if (!active && !narrowMode) "inactive"
133 
134  onStateChanged: {
135  if (state == "active" || state == "narrowActive") {
136  popoverShouldOpen = true;
137  popoverShouldClose = false;
138  } else {
139  popoverShouldOpen = false;
140  popoverShouldClose = true;
141  }
142  }
143 
144  function openPopover() {
145  if (searchHistory.count > 0) {
146  searchContainer.popover = PopupUtils.open(popoverComponent, pointerPositioner,
147  {
148  "contentWidth": searchField.width,
149  "edgeMargins": units.gu(1)
150  }
151  )
152  }
153  popoverShouldOpen = false;
154  popoverShouldClose = false;
155  }
156 
157  function closePopover() {
158  if (searchContainer.popover) {
159  PopupUtils.close(searchContainer.popover);
160  searchContainer.popover = null;
161  }
162  popoverShouldOpen = false;
163  popoverShouldClose = false;
164  }
165 
166  onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
167 
168  TextField {
169  id: searchField
170 
171  anchors.fill: parent
172  anchors.margins: units.gu(1)
173 
174  inputMethodHints: Qt.ImhNoPredictiveText
175  hasClearButton: false
176 
177  primaryItem: AbstractButton {
178  enabled: searchField.text != "" && !searchIndicator.running
179  onClicked: {
180  if (searchField.text != "") {
181  searchHistory.addQuery(searchField.text)
182  searchField.text = ""
183  }
184  }
185  height: parent.height
186  width: height
187 
188  ActivityIndicator {
189  id: searchIndicator
190  objectName: "searchIndicator"
191 
192  anchors {
193  verticalCenter: parent.verticalCenter
194  left: parent.left
195  leftMargin: units.gu(0.5)
196  }
197 
198  running: opacity > 0
199  }
200 
201  Image {
202  id: primaryImage
203  objectName: "primaryImage"
204  anchors {
205  verticalCenter: parent.verticalCenter
206  left: parent.left
207  leftMargin: units.gu(0.5)
208  }
209  width: units.gu(3)
210  height: units.gu(3)
211  visible: opacity > 0
212  }
213 
214  Item {
215  id: pointerPositioner
216  anchors.left: parent.right
217  anchors.leftMargin: units.gu(0.5)
218  anchors.top: parent.bottom
219  }
220  }
221 
222  onTextChanged: {
223  if (text != "") searchContainer.closePopover()
224  else if (text == "" && activeFocus) searchContainer.openPopover()
225  }
226 
227  onActiveFocusChanged: {
228  if (!activeFocus) searchContainer.closePopover()
229  }
230 
231  states: [
232  State {
233  name: "searching"
234  when: scope && scope.searchInProgress
235  PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
236  PropertyChanges { target: primaryImage; opacity: 0 }
237  },
238  State {
239  name: "idle"
240  when: !scope || !scope.searchInProgress
241  PropertyChanges { target: searchIndicator; opacity: 0 }
242  PropertyChanges { target: primaryImage; opacity: 1 }
243  }
244  ]
245 
246  transitions: [
247  Transition {
248  to: "searching"
249  reversible: true
250  SequentialAnimation {
251  NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
252  NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
253  }
254  }
255  ]
256  }
257 
258  states: [
259  State {
260  name: "wide"
261  AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
262  AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
263  },
264  State {
265  name: "narrow"
266  PropertyChanges { target: searchField; highlighted: true }
267  AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
268  AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
269  },
270  State {
271  name: "active"
272  extend: "wide"
273  PropertyChanges { target: searchContainer; width: units.gu(40) }
274  PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
275  PropertyChanges { target: searchField; highlighted: true }
276  },
277  State {
278  name: "inactive"
279  extend: "wide"
280  PropertyChanges { target: searchContainer; width: units.gu(25) }
281  PropertyChanges { target: primaryImage; source: "../Dash/graphics/icon_search_inactive.png" }
282  PropertyChanges { target: searchField; highlighted: false }
283  },
284  State {
285  name: "narrowActive"
286  extend: "narrow"
287  PropertyChanges { target: header; contentY: 0 }
288  PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
289  },
290  State {
291  name: "narrowInactive"
292  extend: "narrow"
293  PropertyChanges { target: header; contentY: header.height }
294  PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
295  }
296  ]
297 
298  transitions: [
299  Transition {
300  to: "active"
301  SequentialAnimation {
302  ParallelAnimation {
303  NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
304  PropertyAction { target: primaryImage; property: "source" }
305  AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
306  }
307  ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
308  }
309  },
310  Transition {
311  to: "inactive"
312  ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
313  NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
314  AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
315  },
316  Transition {
317  to: "narrowActive"
318  SequentialAnimation {
319  ParallelAnimation {
320  NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
321  AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
322  }
323  ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
324  }
325  },
326  Transition {
327  to: "narrowInactive"
328  ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
329  NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
330  AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
331  }
332  ]
333 
334  Component {
335  id: popoverComponent
336  Popover {
337  id: popover
338 
339  // FIXME: this should go into the first item below, but enable: false
340  // prevents mouse events propagation
341  AbstractButton {
342  anchors {
343  top: parent.top
344  right: parent.right
345  }
346  height: units.gu(6)
347  width: height
348 
349  onClicked: searchContainer.closePopover()
350 
351  Image {
352  anchors.centerIn: parent
353  width: units.gu(2)
354  height: units.gu(2)
355  source: "../Dash/graphics/icon_listview_clear.png"
356  }
357  }
358 
359  Column {
360  anchors {
361  top: parent.top
362  left: parent.left
363  right: parent.right
364  }
365 
366  ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
367 
368  Repeater {
369  id: recentSearches
370  model: searchHistory
371 
372  delegate: ListItem.Standard {
373  showDivider: index < recentSearches.count - 1
374  text: query
375  onClicked: {
376  searchHistory.addQuery(text)
377  searchField.text = text
378  }
379  }
380  }
381  }
382  }
383  }
384 
385  InverseMouseArea {
386  enabled: searchField.activeFocus
387 
388  anchors {
389  top: parent.top
390  left: parent.left
391  right: parent.right
392  }
393 
394  height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
395 
396  onPressed: searchField.focus = false
397  }
398  }
399  }
400  }
401 
402  BorderImage {
403  id: bottomBorder
404  anchors {
405  top: header.bottom
406  left: parent.left
407  right: parent.right
408  bottom: bottomContainer.top
409  }
410 
411  source: "graphics/PageHeaderBaseDivider.sci"
412  }
413 
414  Item {
415  id: bottomContainer
416 
417  anchors {
418  left: parent.left
419  right: parent.right
420  bottom: parent.bottom
421  }
422  height: childrenRect.height
423  }
424 }