Unity 8
DashPageHeader.qml
1 /*
2  * Copyright (C) 2013,2015,2016 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.4
18 import Ubuntu.Components 1.3
19 import Ubuntu.Components.Popups 1.3
20 import Ubuntu.Components.ListItems 1.3
21 import Utils 0.1
22 import "../Components"
23 
24 Item {
25  id: root
26  objectName: "pageHeader"
27  implicitHeight: headerContainer.height + signatureLineHeight
28  readonly property real signatureLineHeight: showSignatureLine ? units.gu(2.5) : headerBottomLine.height
29 
30  property int activeFiltersCount: 0
31  property bool scopeHasFilters: false
32  property bool showBackButton: false
33  property bool backIsClose: false
34  property string title
35  property var extraPanel
36  property string navigationTag
37 
38  property bool storeEntryEnabled: false
39  property bool searchEntryEnabled: false
40  property bool settingsEnabled: false
41  property bool favoriteEnabled: false
42  property bool favorite: false
43  property ListModel searchHistory
44  property alias searchQuery: searchTextField.text
45  property alias searchHint: searchTextField.placeholderText
46  property bool showSignatureLine: false
47 
48  property int paginationCount: 0
49  property int paginationIndex: -1
50 
51  property var scopeStyle: null
52 
53  signal clearSearch(bool keepPanelOpen)
54  signal backClicked()
55  signal storeClicked()
56  signal settingsClicked()
57  signal favoriteClicked()
58  signal searchTextFieldFocused()
59  signal showFiltersPopup(var item)
60 
61  onScopeStyleChanged: refreshLogo()
62  onSearchQueryChanged: {
63  // Make sure we are at the search page if the search query changes behind our feet
64  if (searchQuery) {
65  headerContainer.showSearch = true;
66  }
67  }
68  onNavigationTagChanged: {
69  // Make sure we are at the search page if the navigation tag changes behind our feet
70  if (navigationTag) {
71  headerContainer.showSearch = true;
72  }
73  }
74 
75  function triggerSearch() {
76  if (searchEntryEnabled) {
77  headerContainer.showSearch = true;
78  searchTextField.forceActiveFocus();
79  }
80  }
81 
82  function closePopup(keepFocus, keepSearch) {
83  if (extraPanel.visible) {
84  extraPanel.visible = false;
85  }
86  if (!keepFocus) {
87  unfocus(keepSearch);
88  }
89  if (!keepSearch && !searchTextField.text && !root.navigationTag && searchHistory.count == 0) {
90  headerContainer.showSearch = false;
91  }
92  }
93 
94  function resetSearch(keepFocus) {
95  if (searchHistory) {
96  searchHistory.addQuery(searchTextField.text);
97  }
98  searchTextField.text = "";
99  closePopup(keepFocus);
100  }
101 
102  function unfocus(keepSearch) {
103  searchTextField.focus = false;
104  if (!keepSearch && !searchTextField.text && !root.navigationTag) {
105  headerContainer.showSearch = false;
106  }
107  }
108 
109  function openPopup() {
110  if (openSearchAnimation.running) {
111  openSearchAnimation.openPopup = true;
112  } else if (extraPanel.hasContents) {
113  // Show extraPanel
114  extraPanel.visible = true;
115  }
116  }
117 
118  function refreshLogo() {
119  if (root.scopeStyle ? root.scopeStyle.headerLogo != "" : false) {
120  header.contents = imageComponent.createObject();
121  } else if (header.contents) {
122  header.contents.destroy();
123  header.contents = null;
124  }
125  }
126 
127  Connections {
128  target: root.scopeStyle
129  onHeaderLogoChanged: root.refreshLogo()
130  }
131 
132  InverseMouseArea {
133  anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + (extraPanel ? extraPanel.height : 0) }
134  visible: headerContainer.showSearch
135  onPressed: {
136  closePopup(/* keepFocus */false);
137  mouse.accepted = false;
138  }
139  }
140 
141  Item {
142  id: headerContainer
143  objectName: "headerContainer"
144  anchors { left: parent.left; top: parent.top; right: parent.right }
145  height: header.__styleInstance.contentHeight
146 
147  property bool showSearch: false
148 
149  state: headerContainer.showSearch ? "search" : ""
150 
151  states: State {
152  name: "search"
153 
154  AnchorChanges {
155  target: headersColumn
156  anchors.top: parent.top
157  anchors.bottom: undefined
158  }
159  }
160 
161  transitions: Transition {
162  id: openSearchAnimation
163  AnchorAnimation {
164  duration: UbuntuAnimation.FastDuration
165  easing: UbuntuAnimation.StandardEasing
166  }
167 
168  property bool openPopup: false
169 
170  onRunningChanged: {
171  headerContainer.clip = running;
172  if (!running && openSearchAnimation.openPopup) {
173  openSearchAnimation.openPopup = false;
174  root.openPopup();
175  }
176  }
177  }
178 
179  Background {
180  id: background
181  objectName: "headerBackground"
182  style: scopeStyle.headerBackground
183  }
184 
185  Column {
186  id: headersColumn
187  objectName: "headersColumn"
188  anchors {
189  left: parent.left
190  right: parent.right
191  bottom: parent.bottom
192  }
193 
194  PageHeader {
195  id: searchHeader
196  anchors { left: parent.left; right: parent.right }
197  opacity: headerContainer.clip || headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
198 
199  StyleHints {
200  foregroundColor: root.scopeStyle ? root.scopeStyle.headerForeground : theme.palette.normal.baseText
201  backgroundColor: "transparent"
202  dividerColor: "transparent"
203  }
204 
205  contents: Item {
206  anchors.fill: parent
207 
208  Keys.onEscapePressed: { // clear the search text, dismiss the search in the second step
209  if (searchTextField.text != "") {
210  root.clearSearch(true);
211  } else {
212  root.clearSearch(false);
213  headerContainer.showSearch = false;
214  }
215  }
216 
217  TextField {
218  id: searchTextField
219  objectName: "searchTextField"
220  inputMethodHints: Qt.ImhNoPredictiveText
221  hasClearButton: false
222  anchors {
223  top: parent.top
224  topMargin: units.gu(1)
225  left: parent.left
226  bottom: parent.bottom
227  bottomMargin: units.gu(1)
228  right: settingsButton.left
229  rightMargin: settingsButton.visible ? 0 : units.gu(2)
230  }
231 
232  primaryItem: Rectangle {
233  color: "#F5F4F5"
234  width: root.navigationTag != "" ? tagLabel.width + units.gu(2) : 0
235  height: root.navigationTag != "" ? tagLabel.height + units.gu(1) : 0
236  radius: units.gu(0.5)
237  Label {
238  id: tagLabel
239  text: root.navigationTag
240  anchors.centerIn: parent
241  color: "#333333"
242  }
243  }
244 
245  secondaryItem: AbstractButton {
246  id: clearButton
247  height: searchTextField.height
248  width: height
249  enabled: searchTextField.text.length > 0 || root.navigationTag != ""
250 
251  Image {
252  objectName: "clearIcon"
253  anchors.fill: parent
254  anchors.margins: units.gu(1)
255  source: "image://theme/clear"
256  sourceSize.width: width
257  sourceSize.height: height
258  opacity: parent.enabled
259  visible: opacity > 0
260  Behavior on opacity {
261  UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
262  }
263  }
264 
265  onClicked: {
266  root.clearSearch(true);
267  }
268  }
269 
270  onActiveFocusChanged: {
271  if (activeFocus) {
272  root.searchTextFieldFocused();
273  root.openPopup();
274  }
275  }
276 
277  onTextChanged: {
278  if (text != "") {
279  closePopup(/* keepFocus */true);
280  }
281  }
282  }
283 
284  AbstractButton {
285  id: settingsButton
286  objectName: "settingsButton"
287 
288  width: root.scopeHasFilters ? height : 0
289  visible: width > 0
290  anchors {
291  top: parent.top
292  right: cancelButton.left
293  bottom: parent.bottom
294  rightMargin: units.gu(-1)
295  }
296 
297  Icon {
298  anchors.fill: parent
299  anchors.margins: units.gu(2)
300  name: "filters"
301  color: root.activeFiltersCount > 0 ? theme.palette.normal.positive : header.__styleInstance.foregroundColor
302  }
303 
304  onClicked: {
305  root.showFiltersPopup(settingsButton);
306  }
307  }
308 
309  AbstractButton {
310  id: cancelButton
311  objectName: "cancelButton"
312  width: cancelLabel.width + cancelLabel.anchors.rightMargin + cancelLabel.anchors.leftMargin
313  anchors {
314  top: parent.top
315  right: parent.right
316  bottom: parent.bottom
317  }
318  onClicked: {
319  root.clearSearch(false);
320  headerContainer.showSearch = false;
321  }
322  Label {
323  id: cancelLabel
324  text: i18n.tr("Cancel")
325  color: header.__styleInstance.foregroundColor
326  verticalAlignment: Text.AlignVCenter
327  anchors {
328  verticalCenter: parent.verticalCenter
329  right: parent.right
330  leftMargin: units.gu(1)
331  }
332  }
333  }
334  }
335  }
336 
337  PageHeader {
338  id: header
339  objectName: "innerPageHeader"
340  anchors { left: parent.left; right: parent.right }
341  height: headerContainer.height
342  opacity: headerContainer.clip || !headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
343  title: root.title
344 
345  StyleHints {
346  foregroundColor: root.scopeStyle ? root.scopeStyle.headerForeground : theme.palette.normal.baseText
347  backgroundColor: "transparent"
348  dividerColor: "transparent"
349  }
350 
351  leadingActionBar.actions: Action {
352  iconName: backIsClose ? "close" : "back"
353  visible: root.showBackButton
354  onTriggered: root.backClicked()
355  }
356 
357  trailingActionBar {
358  actions: [
359  Action {
360  objectName: "store"
361  text: i18n.ctr("Button: Open the Ubuntu Store", "Store")
362  iconName: "ubuntu-store-symbolic"
363  visible: root.storeEntryEnabled
364  onTriggered: root.storeClicked();
365  },
366  Action {
367  objectName: "search"
368  text: i18n.ctr("Button: Start a search in the current dash scope", "Search")
369  iconName: "search"
370  visible: root.searchEntryEnabled
371  onTriggered: {
372  headerContainer.showSearch = true;
373  searchTextField.forceActiveFocus();
374  }
375  },
376  Action {
377  objectName: "settings"
378  text: i18n.ctr("Button: Show the current dash scope settings", "Settings")
379  iconName: "settings"
380  visible: root.settingsEnabled
381  onTriggered: root.settingsClicked()
382  },
383  Action {
384  objectName: "favorite"
385  text: root.favorite ? i18n.tr("Remove from Favorites") : i18n.tr("Add to Favorites")
386  iconName: root.favorite ? "starred" : "non-starred"
387  visible: root.favoriteEnabled
388  onTriggered: root.favoriteClicked()
389  }
390  ]
391  }
392 
393  Component.onCompleted: root.refreshLogo()
394 
395  Component {
396  id: imageComponent
397 
398  Item {
399  anchors { fill: parent; topMargin: units.gu(1.5); bottomMargin: units.gu(1.5) }
400  clip: true
401  Image {
402  objectName: "titleImage"
403  anchors.fill: parent
404  source: root.scopeStyle ? root.scopeStyle.headerLogo : ""
405  fillMode: Image.PreserveAspectFit
406  horizontalAlignment: Image.AlignLeft
407  sourceSize.height: height
408  }
409  }
410  }
411  }
412  }
413  }
414 
415  Rectangle {
416  id: headerBottomLine
417  anchors {
418  top: headerContainer.bottom
419  left: parent.left
420  right: parent.right
421  }
422  height: units.dp(1)
423  color: theme.palette.normal.base
424  }
425 
426  Row {
427  anchors {
428  top: headerContainer.bottom
429  horizontalCenter: headerContainer.horizontalCenter
430  topMargin: units.gu(1)
431  }
432  visible: showSignatureLine
433  spacing: units.gu(.5)
434  Repeater {
435  objectName: "paginationRepeater"
436  model: root.paginationCount
437  Rectangle {
438  objectName: "paginationDots_" + index
439  height: units.gu(1)
440  width: height
441  radius: height / 2
442  color: index == root.paginationIndex ? UbuntuColors.blue : "transparent"
443  border.width: index == root.paginationIndex ? 0 : 1 // yes, one pixel and not 1dp
444  border.color: theme.palette.normal.baseText
445  }
446  }
447  }
448 }