@@ -14,386 +14,294 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
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
17+import QtQuick 2.2
18+import Ubuntu.Components 1.1
19+import Ubuntu.Components.Themes.Ambiance 1.1
20+import Ubuntu.Components.Popups 1.0
21+import Ubuntu.Components.ListItems 1.0
22+import "SearchHistoryModel"
2223
2324Item {
2425 id: root
26+ objectName: "pageHeader"
27+ implicitHeight: headerContainer.height + units.gu(2) + bottomContainer.height
28+
29+ property bool showBackButton: false
30+ property string title
31+
2532 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
33+ property ListModel searchHistory: SearchHistoryModel
34+ property alias searchQuery: searchTextField.text
35+ property bool searchInProgress: false
36+
3037 property alias bottomItem: bottomContainer.children
31− property alias showBackButton: backButton.visible
3238
33− signal backClicked
39+ // TODO We should use foreground for the icons
40+ // of the toolbar but unfortunately Action does not have
41+ // the keyColor property as Icon does :-/
42+ property var scopeStyle: null
3443
35− height: header.height + units.gu(2) + bottomContainer.height
36− implicitHeight: header.height + units.gu(2) + bottomContainer.height
44+ signal backClicked()
45+
46+ onScopeStyleChanged: refreshLogo()
3747
3848 function triggerSearch() {
39− if (searchEntryEnabled) searchField.forceActiveFocus()
49+ if (searchEntryEnabled) {
50+ headerContainer.showSearch = true;
51+ searchTextField.forceActiveFocus();
52+ }
4053 }
4154
42− function resetSearch() {
43− if (!searchHistory) return;
44−
45− searchHistory.addQuery(searchField.text);
46− unfocus();
47− searchField.text = "";
55+ function resetSearch(keepFocus) {
56+ if (searchHistory) {
57+ searchHistory.addQuery(searchTextField.text);
58+ }
59+ if (!keepFocus) {
60+ unfocus();
61+ }
62+ searchTextField.text = "";
63+ if (headerContainer.popover != null) {
64+ PopupUtils.close(headerContainer.popover);
65+ }
4866 }
4967
5068 function unfocus() {
51− searchField.focus = false;
69+ searchTextField.focus = false;
70+ if (!searchTextField.text) {
71+ headerContainer.showSearch = false;
72+ }
5273 }
5374
54− Connections {
55− target: greeter
56− onShownChanged: if (shown) resetSearch()
75+ function openSearchHistory() {
76+ if (openSearchAnimation.running) {
77+ openSearchAnimation.openSearchHistory = true;
78+ } else if (root.searchHistory.count > 0 && headerContainer.popover == null) {
79+ headerContainer.popover = PopupUtils.open(popoverComponent, searchTextField,
80+ {
81+ "contentWidth": searchTextField.width,
82+ "edgeMargins": units.gu(1)
83+ }
84+ );
85+ }
5786 }
5887
59− Flickable {
60− id: header
61− anchors {
62− left: parent.left
63− right: parent.right
64− top: parent.top
88+ function refreshLogo() {
89+ if (scopeStyle ? scopeStyle.headerLogo != "" : false) {
90+ header.contents = imageComponent.createObject();
91+ } else if (header.contents) {
92+ header.contents.destroy();
93+ header.contents = null;
6594 }
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
95+ }
7396
74− Behavior on contentY { NumberAnimation { duration: 200; easing.type: Easing.OutQuad } }
97+ Connections {
98+ target: root.scopeStyle
99+ onHeaderLogoChanged: root.refreshLogo()
100+ }
75101
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
102+ InverseMouseArea {
103+ anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
104+ visible: headerContainer.showSearch
105+ onPressed: {
106+ if (headerContainer.popover) {
107+ PopupUtils.close(headerContainer.popover);
85108 }
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"
109+ if (!searchTextField.text) {
110+ headerContainer.showSearch = false;
92111 }
112+ searchTextField.focus = false;
113+ mouse.accepted = false;
93114 }
115+ }
94116
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
117+ Flickable {
118+ id: headerContainer
119+ objectName: "headerContainer"
120+ clip: true
121+ anchors { left: parent.left; top: parent.top; right: parent.right }
122+ height: units.gu(6.5)
123+ contentHeight: headersColumn.height
124+ interactive: false
125+ contentY: showSearch ? 0 : height
124126
125− anchors.right: headerContainer.right
126− height: header.height
127+ property bool showSearch: false
128+ property var popover: null
127129
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"
130+ Behavior on contentY {
131+ UbuntuNumberAnimation {
132+ id: openSearchAnimation
133+ property bool openSearchHistory: false
133134
134− onStateChanged: {
135− if (state == "active" || state == "narrowActive") {
136− popoverShouldOpen = true;
137− popoverShouldClose = false;
138− } else {
139− popoverShouldOpen = false;
140− popoverShouldClose = true;
135+ onRunningChanged: {
136+ if (!running && openSearchAnimation.openSearchHistory) {
137+ openSearchAnimation.openSearchHistory = false;
138+ root.openSearchHistory();
141139 }
142140 }
141+ }
142+ }
143143
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− )
144+ Column {
145+ id: headersColumn
146+ anchors { left: parent.left; right: parent.right }
147+
148+ PageHeadStyle {
149+ id: searchHeader
150+ anchors { left: parent.left; right: parent.right }
151+ height: headerContainer.height
152+ contentHeight: height
153+ separatorSource: ""
154+ // Required to keep PageHeadStyle noise down as it expects the Page's properties around.
155+ property var styledItem: searchHeader
156+ property string title
157+ property var config: PageHeadConfiguration {
158+ backAction: Action {
159+ iconName: "back"
160+ onTriggered: {
161+ root.resetSearch();
162+ headerContainer.showSearch = false;
163+ }
152164 }
153− popoverShouldOpen = false;
154− popoverShouldClose = false;
155165 }
156−
157− function closePopover() {
158− if (searchContainer.popover) {
159− PopupUtils.close(searchContainer.popover);
160− searchContainer.popover = null;
166+ property var contents: TextField {
167+ id: searchTextField
168+ objectName: "searchTextField"
169+ hasClearButton: false
170+ anchors {
171+ fill: parent
172+ leftMargin: units.gu(1)
173+ topMargin: units.gu(1)
174+ bottomMargin: units.gu(1)
175+ rightMargin: root.width > units.gu(60) ? root.width - units.gu(40) : units.gu(1)
161176 }
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)
173177
174− inputMethodHints: Qt.ImhNoPredictiveText
175− hasClearButton: false
178+ secondaryItem: AbstractButton {
179+ height: searchTextField.height
180+ width: height
181+ enabled: searchTextField.text.length > 0
176182
177− primaryItem: AbstractButton {
178− enabled: searchField.text != "" && !searchIndicator.running
179− onClicked: {
180− if (searchField.text != "") {
181− searchHistory.addQuery(searchField.text)
182− searchField.text = ""
183+ Image {
184+ objectName: "clearIcon"
185+ anchors.fill: parent
186+ anchors.margins: units.gu(.75)
187+ source: "image://theme/clear"
188+ opacity: searchTextField.text.length > 0 && !searchActivityIndicator.running
189+ visible: opacity > 0
190+ Behavior on opacity {
191+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
183192 }
184193 }
185− height: parent.height
186− width: height
187194
188195 ActivityIndicator {
189− id: searchIndicator
196+ id: searchActivityIndicator
190197 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)
198+ anchors.fill: parent
199+ anchors.margins: units.gu(.75)
200+ running: root.searchInProgress
201+ opacity: running ? 1 : 0
202+ Behavior on opacity {
203+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
208204 }
209− width: units.gu(3)
210− height: units.gu(3)
211− visible: opacity > 0
212205 }
213206
214− Item {
215− id: pointerPositioner
216− anchors.left: parent.right
217− anchors.leftMargin: units.gu(0.5)
218− anchors.top: parent.bottom
207+ onClicked: {
208+ root.resetSearch(true);
209+ root.openSearchHistory();
219210 }
220211 }
221212
222− onTextChanged: {
223− if (text != "") searchContainer.closePopover()
224− else if (text == "" && activeFocus) searchContainer.openPopover()
225− }
226−
227213 onActiveFocusChanged: {
228− if (!activeFocus) searchContainer.closePopover()
214+ if (activeFocus) {
215+ root.openSearchHistory();
216+ }
229217 }
218+ }
219+ }
230220
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 }
221+ PageHeadStyle {
222+ id: header
223+ objectName: "innerPageHeader"
224+ anchors { left: parent.left; right: parent.right }
225+ height: headerContainer.height
226+ contentHeight: height
227+ separatorSource: ""
228+ textColor: root.scopeStyle ? root.scopeStyle.foreground : "grey"
229+ property var styledItem: header
230+ property string title: root.title
231+ property var config: PageHeadConfiguration {
232+ backAction: Action {
233+ iconName: "back"
234+ visible: root.showBackButton
235+ onTriggered: {
236+ root.backClicked();
243237 }
244− ]
238+ }
245239
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 }
240+ actions: [
241+ Action {
242+ iconName: "search"
243+ visible: root.searchEntryEnabled
244+ onTriggered: {
245+ headerContainer.showSearch = true;
246+ searchTextField.forceActiveFocus();
253247 }
254248 }
255249 ]
256250 }
257251
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− ]
252+ property var contents: null
253+ Component.onCompleted: root.refreshLogo()
333254
334255 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()
256+ id: imageComponent
350257
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− }
258+ Item {
259+ anchors { fill: parent; topMargin: units.gu(1); bottomMargin: units.gu(1) }
260+ clip: true
261+ Image {
262+ objectName: "titleImage"
263+ anchors.fill: parent
264+ source: root.scopeStyle ? root.scopeStyle.headerLogo : ""
265+ fillMode: Image.PreserveAspectFit
266+ horizontalAlignment: Image.AlignLeft
267+ sourceSize.height: height
357268 }
269+ }
270+ }
271+ }
272+ }
273+ }
358274
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") }
275+ Component {
276+ id: popoverComponent
277+ Popover {
278+ id: popover
279+ autoClose: false
367280
368− Repeater {
369− id: recentSearches
370− model: searchHistory
281+ Component.onDestruction: {
282+ headerContainer.popover = null;
283+ }
371284
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− }
285+ Column {
286+ anchors {
287+ top: parent.top
288+ left: parent.left
289+ right: parent.right
383290 }
384291
385− InverseMouseArea {
386− enabled: searchField.activeFocus
292+ Repeater {
293+ id: recentSearches
294+ model: searchHistory
387295
388− anchors {
389− top: parent.top
390− left: parent.left
391− right: parent.right
296+ delegate: Standard {
297+ showDivider: index < recentSearches.count - 1
298+ text: query
299+ onClicked: {
300+ searchHistory.addQuery(text);
301+ searchTextField.text = text;
302+ PopupUtils.close(popover);
303+ }
392304 }
393−
394− height: searchContainer.popover ? parent.height + searchContainer.popover.contentHeight + units.gu(2) : parent.height
395−
396− onPressed: searchField.focus = false
397305 }
398306 }
399307 }
@@ -402,7 +310,7 @@ Item {
402310 BorderImage {
403311 id: bottomBorder
404312 anchors {
405− top: header.bottom
313+ top: headerContainer.bottom
406314 left: parent.left
407315 right: parent.right
408316 bottom: bottomContainer.top