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