@@ -14,386 +14,287 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17−import QtQuick 2.0
17+import QtQuick 2.2
18+import Ubuntu.Components 1.1
18−import Ubuntu.Components 0.1
19+import Ubuntu.Components.Themes.Ambiance 1.1
19−import Ubuntu.Components.Popups 0.1
20+import Ubuntu.Components.Popups 1.0
20−import Ubuntu.Components.ListItems 0.1 as ListItem
21+import Ubuntu.Components.ListItems 1.0
21−import Unity 0.2
22+import "SearchHistoryModel"
26+ objectName: "pageHeader"
27+ implicitHeight: headerContainer.height + units.gu(2) + bottomContainer.height
29+ property bool showBackButton: false
30+ property string title
31+ property string imageSource
25 property bool searchEntryEnabled: false
33 property bool searchEntryEnabled: false
34+ property ListModel searchHistory: SearchHistoryModel
26− property alias searchQuery: searchField.text
35+ property alias searchQuery: searchTextField.text
27− property ListModel searchHistory
36+ property bool searchInProgress: false
28− property var scope: null
29− property alias childItem: itemContainer.children
30− property alias bottomItem: bottomContainer.children
31− property alias showBackButton: backButton.visible
33− signal backClicked
38+ property alias bottomItem: bottomContainer.children
35− height: header.height + units.gu(2) + bottomContainer.height
40+ signal backClicked()
36− implicitHeight: header.height + units.gu(2) + bottomContainer.height
38 function triggerSearch() {
42 function triggerSearch() {
39− if (searchEntryEnabled) searchField.forceActiveFocus()
43+ if (searchEntryEnabled) {
44+ headerContainer.showSearch = true;
45+ searchTextField.forceActiveFocus();
42− function resetSearch() {
49+ function resetSearch(keepFocus) {
43− if (!searchHistory) return;
50+ if (searchHistory) {
45− searchHistory.addQuery(searchField.text);
51+ searchHistory.addQuery(searchTextField.text);
46− unfocus();
54+ unfocus();
47− searchField.text = "";
55+ }
56+ searchTextField.text = "";
57+ if (headerContainer.popover != null) {
58+ PopupUtils.close(headerContainer.popover);
50 function unfocus() {
62 function unfocus() {
51− searchField.focus = false;
63+ searchTextField.focus = false;
64+ if (!searchTextField.text) {
65+ headerContainer.showSearch = false;
54− Connections {
69+ function openSearchHistory() {
55− target: greeter
70+ if (openSearchAnimation.running) {
56− onShownChanged: if (shown) resetSearch()
71+ openSearchAnimation.openSearchHistory = true;
72+ } else if (root.searchHistory.count > 0 && headerContainer.popover == null) {
73+ headerContainer.popover = PopupUtils.open(popoverComponent, searchTextField,
75+ "contentWidth": searchTextField.width,
76+ "edgeMargins": units.gu(1)
59− Flickable {
82+ onImageSourceChanged: {
60− id: header
83+ if (imageSource) {
61− anchors {
84+ header.contents = imageComponent.createObject();
62− left: parent.left
85+ } else {
63− right: parent.right
86+ header.contents.destroy();
64− top: parent.top
87+ header.contents = null;
66− height: units.gu(6.5)
89+ }
69− contentHeight: headerContainer.height
72− contentY: searchField.activeFocus || searchField.text != "" ? searchContainer.y : headerContainer.y
74− Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
76− AbstractButton {
91+ InverseMouseArea {
77− id: backButton
92+ anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
78− objectName: root.objectName + "_backButton"
93+ visible: headerContainer.showSearch
79− visible: false
94+ onPressed: {
80− height: header.height
95+ if (headerContainer.popover) {
81− y: header.contentY
96+ PopupUtils.close(headerContainer.popover);
84− leftMargin: visible ? units.gu(2) : 0
86− width: visible ? image.width + units.gu(2) : 0
98+ if (!searchTextField.text) {
87− onClicked: root.backClicked();
99+ headerContainer.showSearch = false;
90− anchors.centerIn: parent
91− source: "graphics/headerback.png"
101+ searchTextField.focus = false;
102+ mouse.accepted = false;
95− // FIXME this could potentially be simplified to avoid all the containers
106+ Flickable {
97− id: headerContainer
107+ id: headerContainer
100− left: backButton.right
103− height: childrenRect.height
108− width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
109− height: header.height
114− objectName: "searchContainer"
108+ objectName: "headerContainer"
116− visible: searchEntryEnabled
110+ anchors { left: parent.left; top: parent.top; right: parent.right }
117− property bool popoverShouldOpen: false
111+ height: units.gu(6.5)
118− property bool popoverShouldClose: false
112+ contentHeight: headersColumn.height
119−
113+ interactive: false
120− property bool narrowMode: parent.width < units.gu(80)
114+ contentY: showSearch ? 0 : height
122− property bool active: searchField.text != "" || searchField.activeFocus
123− property var popover: null
125− anchors.right: headerContainer.right
116+ property bool showSearch: false
126− height: header.height
117+ property var popover: null
128− state:
119+ Behavior on contentY {
129− if (active && narrowMode) "narrowActive"
120+ UbuntuNumberAnimation {
130− else if (!active && narrowMode) "narrowInactive"
121+ id: openSearchAnimation
131− else if (active && !narrowMode) "active"
122+ property bool openSearchHistory: false
132− else if (!active && !narrowMode) "inactive"
134− onStateChanged: {
124+ onRunningChanged: {
135− if (state == "active" || state == "narrowActive") {
125+ if (!running && openSearchAnimation.openSearchHistory) {
136− popoverShouldOpen = true;
126+ openSearchAnimation.openSearchHistory = false;
137− popoverShouldClose = false;
127+ root.openSearchHistory();
139− popoverShouldOpen = false;
140− popoverShouldClose = true;
144− function openPopover() {
133+ Column {
145− if (searchHistory.count > 0) {
134+ id: headersColumn
146− searchContainer.popover = PopupUtils.open(popoverComponent, pointerPositioner,
135+ anchors { left: parent.left; right: parent.right }
148− "contentWidth": searchField.width,
137+ PageHeadStyle {
149− "edgeMargins": units.gu(1)
138+ id: searchHeader
139+ anchors { left: parent.left; right: parent.right }
140+ height: headerContainer.height
141+ contentHeight: height
143+ // Required to keep PageHeadStyle noise down as it expects the Page's properties around.
144+ property var styledItem: searchHeader
145+ property string title
146+ property var config: PageHeadConfiguration {
147+ backAction: Action {
151+ headerContainer.showSearch = false;
153− popoverShouldOpen = false;
154− popoverShouldClose = false;
156−
155+ property var contents: TextField {
157− function closePopover() {
156+ id: searchTextField
158− if (searchContainer.popover) {
157+ objectName: "searchTextField"
159− PopupUtils.close(searchContainer.popover);
158+ hasClearButton: false
160− searchContainer.popover = null;
159+ anchors {
161+ leftMargin: units.gu(1)
162+ topMargin: units.gu(1)
163+ bottomMargin: units.gu(1)
164+ rightMargin: root.width > units.gu(60) ? root.width - units.gu(40) : units.gu(1)
162− popoverShouldOpen = false;
163− popoverShouldClose = false;
166− onActiveFocusChanged: if (!activeFocus) { searchHistory.addQuery(searchField.text) }
171− anchors.fill: parent
167+ secondaryItem: AbstractButton {
172− anchors.margins: units.gu(1)
168+ height: searchTextField.height
174− inputMethodHints: Qt.ImhNoPredictiveText
170+ enabled: searchTextField.text.length > 0
175− hasClearButton: false
177− primaryItem: AbstractButton {
172+ Image {
178− enabled: searchField.text != "" && !searchIndicator.running
173+ objectName: "clearIcon"
179− onClicked: {
174+ anchors.fill: parent
180− if (searchField.text != "") {
175+ anchors.margins: units.gu(.75)
181− searchHistory.addQuery(searchField.text)
176+ source: "image://theme/clear"
182− searchField.text = ""
177+ opacity: searchTextField.text.length > 0 && !searchActivityIndicator.running
178+ visible: opacity > 0
179+ Behavior on opacity {
180+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
185− height: parent.height
188 ActivityIndicator {
184 ActivityIndicator {
189− id: searchIndicator
185+ id: searchActivityIndicator
190 objectName: "searchIndicator"
186 objectName: "searchIndicator"
192− anchors {
187+ anchors.fill: parent
193− verticalCenter: parent.verticalCenter
188+ anchors.margins: units.gu(.75)
194− left: parent.left
189+ running: root.searchInProgress
195− leftMargin: units.gu(0.5)
190+ opacity: running ? 1 : 0
191+ Behavior on opacity {
192+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
198− running: opacity > 0
203− objectName: "primaryImage"
205− verticalCenter: parent.verticalCenter
207− leftMargin: units.gu(0.5)
211− visible: opacity > 0
214− Item {
196+ onClicked: {
215− id: pointerPositioner
197+ root.resetSearch(true);
216− anchors.left: parent.right
198+ root.openSearchHistory();
217− anchors.leftMargin: units.gu(0.5)
218− anchors.top: parent.bottom
223− if (text != "") searchContainer.closePopover()
224− else if (text == "" && activeFocus) searchContainer.openPopover()
227 onActiveFocusChanged: {
202 onActiveFocusChanged: {
228− if (!activeFocus) searchContainer.closePopover()
203+ if (activeFocus) {
204+ root.openSearchHistory();
231− states: [
210+ PageHeadStyle {
232− State {
211+ id: header
233− name: "searching"
212+ objectName: "innerPageHeader"
234− when: scope && scope.searchInProgress
213+ anchors { left: parent.left; right: parent.right }
235− PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
214+ height: headerContainer.height
236− PropertyChanges { target: primaryImage; opacity: 0 }
215+ contentHeight: height
237− },
216+ separatorSource: ""
238− State {
217+ // FIXME: Use the theme's color once we're split from the shell
239− name: "idle"
218+ textColor: "grey"
240− when: !scope || !scope.searchInProgress
219+ property var styledItem: header
241− PropertyChanges { target: searchIndicator; opacity: 0 }
220+ property string title: root.title
242− PropertyChanges { target: primaryImage; opacity: 1 }
221+ property var config: PageHeadConfiguration {
222+ backAction: Action {
224+ visible: root.showBackButton
246− transitions: [
230+ actions: [
247− Transition {
231+ Action {
248− to: "searching"
232+ iconName: "search"
249− reversible: true
233+ visible: root.searchEntryEnabled
250− SequentialAnimation {
234+ onTriggered: {
251− NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
235+ headerContainer.showSearch = true;
252− NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
236+ searchTextField.forceActiveFocus();
258− states: [
242+ property var contents: null
259− State {
243+ Component.onCompleted: {
260− name: "wide"
244+ if (root.imageSource) {
261− AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
245+ header.contents = imageComponent.createObject();
262− AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
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 }
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 }
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 }
285− name: "narrowActive"
287− PropertyChanges { target: header; contentY: 0 }
288− PropertyChanges { target: primaryImage; source: searchField.text ? "../Dash/graphics/icon_clear.png" : "../Dash/graphics/icon_search_active.png" }
291− name: "narrowInactive"
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" }
301− SequentialAnimation {
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 }
307− ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
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 }
318− SequentialAnimation {
320− NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
321− AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
323− ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
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 }
334 Component {
248 Component {
335− id: popoverComponent
249+ id: imageComponent
339− // FIXME: this should go into the first item below, but enable: false
340− // prevents mouse events propagation
349− onClicked: searchContainer.closePopover()
352− anchors.centerIn: parent
252+ anchors { fill: parent; topMargin: units.gu(1); bottomMargin: units.gu(1) }
353− width: units.gu(2)
253+ clip: true
354− height: units.gu(2)
254+ Image {
355− source: "../Dash/graphics/icon_listview_clear.png"
255+ objectName: "titleImage"
356− }
256+ anchors.fill: parent
257+ source: root.imageSource
258+ fillMode: Image.PreserveAspectFit
259+ horizontalAlignment: Image.AlignLeft
260+ sourceSize.height: height
359− Column {
268+ Component {
360− anchors {
269+ id: popoverComponent
361− top: parent.top
270+ Popover {
362− left: parent.left
271+ id: popover
363− right: parent.right
272+ autoClose: false
366− ListItem.Standard { enabled: false; text: i18n.tr("Recent searches") }
368− Repeater {
274+ Component.onDestruction: {
369− id: recentSearches
275+ headerContainer.popover = null;
370− model: searchHistory
276+ }
372− delegate: ListItem.Standard {
278+ Column {
373− showDivider: index < recentSearches.count - 1
279+ anchors {
374− text: query
280+ top: parent.top
375− onClicked: {
281+ left: parent.left
376− searchHistory.addQuery(text)
282+ right: parent.right
377− searchField.text = text
385− InverseMouseArea {
285+ Repeater {
386− enabled: searchField.activeFocus
286+ id: recentSearches
287+ model: searchHistory
388− anchors {
289+ delegate: Standard {
389− top: parent.top
290+ showDivider: index < recentSearches.count - 1
390− left: parent.left
291+ text: query
391− right: parent.right
292+ onClicked: {
293+ searchHistory.addQuery(text);
294+ searchTextField.text = text;
295+ PopupUtils.close(popover);
394− height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
396− onPressed: searchField.focus = false
@@ -402,7 +303,7 @@ Item {
402 BorderImage {
303 BorderImage {
403 id: bottomBorder
304 id: bottomBorder
404 anchors {
305 anchors {
405− top: header.bottom
306+ top: headerContainer.bottom
406 left: parent.left
307 left: parent.left
407 right: parent.right
308 right: parent.right
408 bottom: bottomContainer.top
309 bottom: bottomContainer.top