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.Themes.Ambiance 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) {
82  if (extraPanel.visible) {
83  extraPanel.visible = false;
84  } else if (!keepFocus) {
85  unfocus();
86  }
87  if (!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() {
101  searchTextField.focus = false;
102  if (!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.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  PageHeadStyle {
177  // FIXME: Replace PageHeadStyle from the Ambiance theme by the new PageHeader from Ubuntu.Components 1.3.
178  id: searchHeader
179  anchors { left: parent.left; right: parent.right }
180  opacity: headerContainer.clip || headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
181  __separator_visible: false
182  // Required to keep PageHeadStyle noise down as it expects the Page's properties around.
183  property var styledItem: searchHeader
184  property color dividerColor: "transparent" // Doesn't matter as we don't have PageHeadSections
185  property color panelColor: background.topColor
186  panelForegroundColor: config.foregroundColor
187  backgroundColor: "transparent"
188  config: PageHeadConfiguration {
189  foregroundColor: root.scopeStyle ? root.scopeStyle.headerForeground : theme.palette.normal.baseText
190  }
191  property var contents: Item {
192  anchors.fill: parent
193 
194  TextField {
195  id: searchTextField
196  objectName: "searchTextField"
197  inputMethodHints: Qt.ImhNoPredictiveText
198  hasClearButton: false
199  anchors {
200  top: parent.top
201  topMargin: units.gu(1)
202  left: parent.left
203  bottom: parent.bottom
204  bottomMargin: units.gu(1)
205  right: settingsButton.left
206  rightMargin: settingsButton.visible ? 0 : units.gu(2)
207  }
208 
209  primaryItem: Rectangle {
210  color: "#F5F4F5"
211  width: root.navigationTag != "" ? tagLabel.width + units.gu(2) : 0
212  height: root.navigationTag != "" ? tagLabel.height + units.gu(1) : 0
213  radius: units.gu(0.5)
214  Label {
215  id: tagLabel
216  text: root.navigationTag
217  anchors.centerIn: parent
218  color: "#333333"
219  }
220  }
221 
222  secondaryItem: AbstractButton {
223  id: clearButton
224  height: searchTextField.height
225  width: height
226  enabled: searchTextField.text.length > 0 || root.navigationTag != ""
227 
228  Image {
229  objectName: "clearIcon"
230  anchors.fill: parent
231  anchors.margins: units.gu(1)
232  source: "image://theme/clear"
233  opacity: parent.enabled
234  visible: opacity > 0
235  Behavior on opacity {
236  UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
237  }
238  }
239 
240  onClicked: {
241  root.clearSearch(true);
242  }
243  }
244 
245  onActiveFocusChanged: {
246  if (activeFocus) {
247  root.searchTextFieldFocused();
248  root.openPopup();
249  }
250  }
251 
252  onTextChanged: {
253  if (text != "") {
254  closePopup(/* keepFocus */true);
255  }
256  }
257  }
258 
259  AbstractButton {
260  id: settingsButton
261  objectName: "settingsButton"
262 
263  width: root.scopeHasFilters ? height : 0
264  visible: width > 0
265  anchors {
266  top: parent.top
267  right: cancelButton.left
268  bottom: parent.bottom
269  rightMargin: units.gu(-1)
270  }
271 
272  Icon {
273  anchors.fill: parent
274  anchors.margins: units.gu(2)
275  name: "filters"
276  color: root.activeFiltersCount > 0 ? UbuntuColors.orange : Qt.rgba(0.0, 0.0, 0.0, 0.0)
277  }
278 
279  onClicked: {
280  root.showFiltersPopup(settingsButton);
281  }
282  }
283 
284  AbstractButton {
285  id: cancelButton
286  width: cancelLabel.width + cancelLabel.anchors.rightMargin + cancelLabel.anchors.leftMargin
287  anchors {
288  top: parent.top
289  right: parent.right
290  bottom: parent.bottom
291  }
292  onClicked: {
293  root.clearSearch(false);
294  headerContainer.showSearch = false;
295  }
296  Label {
297  id: cancelLabel
298  text: i18n.tr("Cancel")
299  color: header.panelForegroundColor
300  verticalAlignment: Text.AlignVCenter
301  anchors {
302  verticalCenter: parent.verticalCenter
303  right: parent.right
304  rightMargin: units.gu(2)
305  leftMargin: units.gu(1)
306  }
307  }
308  }
309  }
310  }
311 
312  PageHeadStyle {
313  // FIXME: Replace PageHeadStyle from the Ambiance theme by the new PageHeader from Ubuntu.Components 1.3.
314  id: header
315  objectName: "innerPageHeader"
316  anchors { left: parent.left; right: parent.right }
317  height: headerContainer.height
318  opacity: headerContainer.clip || !headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
319  __separator_visible: false
320  property var styledItem: header
321  property color dividerColor: "transparent" // Doesn't matter as we don't have PageHeadSections
322  property color panelColor: background.topColor
323  panelForegroundColor: config.foregroundColor
324  backgroundColor: "transparent"
325  config: PageHeadConfiguration {
326  title: root.title
327  foregroundColor: root.scopeStyle ? root.scopeStyle.headerForeground : theme.palette.normal.baseText
328  backAction: Action {
329  iconName: backIsClose ? "close" : "back"
330  visible: root.showBackButton
331  onTriggered: root.backClicked()
332  }
333 
334  actions: [
335  Action {
336  objectName: "store"
337  text: i18n.ctr("Button: Open the Ubuntu Store", "Store")
338  iconName: "ubuntu-store-symbolic"
339  visible: root.storeEntryEnabled
340  onTriggered: root.storeClicked();
341  },
342  Action {
343  objectName: "search"
344  text: i18n.ctr("Button: Start a search in the current dash scope", "Search")
345  iconName: "search"
346  visible: root.searchEntryEnabled
347  onTriggered: {
348  headerContainer.showSearch = true;
349  searchTextField.forceActiveFocus();
350  }
351  },
352  Action {
353  objectName: "settings"
354  text: i18n.ctr("Button: Show the current dash scope settings", "Settings")
355  iconName: "settings"
356  visible: root.settingsEnabled
357  onTriggered: root.settingsClicked()
358  },
359  Action {
360  objectName: "favorite"
361  text: root.favorite ? i18n.tr("Remove from Favorites") : i18n.tr("Add to Favorites")
362  iconName: root.favorite ? "starred" : "non-starred"
363  visible: root.favoriteEnabled
364  onTriggered: root.favoriteClicked()
365  }
366  ]
367  }
368 
369  property var contents: null
370  Component.onCompleted: root.refreshLogo()
371 
372  Component {
373  id: imageComponent
374 
375  Item {
376  anchors { fill: parent; topMargin: units.gu(1.5); bottomMargin: units.gu(1.5) }
377  clip: true
378  Image {
379  objectName: "titleImage"
380  anchors.fill: parent
381  source: root.scopeStyle ? root.scopeStyle.headerLogo : ""
382  fillMode: Image.PreserveAspectFit
383  horizontalAlignment: Image.AlignLeft
384  sourceSize.height: height
385  }
386  }
387  }
388  }
389  }
390  }
391 
392  Rectangle {
393  id: bottomBorder
394  visible: showSignatureLine
395  anchors {
396  top: headerContainer.bottom
397  left: parent.left
398  right: parent.right
399  bottom: parent.bottom
400  }
401 
402  color: root.scopeStyle ? root.scopeStyle.headerDividerColor : "#e0e0e0"
403 
404  Rectangle {
405  anchors {
406  top: parent.top
407  left: parent.left
408  right: parent.right
409  }
410  height: units.dp(1)
411  color: Qt.darker(parent.color, 1.1)
412  }
413  }
414 
415  Row {
416  visible: bottomBorder.visible
417  spacing: units.gu(.5)
418  Repeater {
419  objectName: "paginationRepeater"
420  model: root.paginationCount
421  Image {
422  objectName: "paginationDots_" + index
423  height: units.gu(1)
424  width: height
425  source: (index == root.paginationIndex) ? "graphics/pagination_dot_on.png" : "graphics/pagination_dot_off.png"
426  }
427  }
428  anchors {
429  top: headerContainer.bottom
430  horizontalCenter: headerContainer.horizontalCenter
431  topMargin: units.gu(.5)
432  }
433  }
434 
435  // FIXME this doesn't work with solid scope backgrounds due to z-ordering
436  Item {
437  id: bottomHighlight
438  visible: bottomBorder.visible
439  anchors {
440  top: parent.bottom
441  left: parent.left
442  right: parent.right
443  }
444  z: 1
445  height: units.dp(1)
446  opacity: 0.6
447 
448  Rectangle {
449  anchors.fill: parent
450  color: if (root.scopeStyle) {
451  Qt.lighter(Qt.rgba(root.scopeStyle.background.r,
452  root.scopeStyle.background.g,
453  root.scopeStyle.background.b, 1.0), 1.2);
454  } else "#CCFFFFFF"
455  }
456  }
457 }