on xenial help sign in
Added an indicator which is displayed in the search bar whenever a search is in progress.

Added accompanying test in tst_PageHeader. Approved by PS Jenkins bot, Michał Sawicz, Michal Hruby.

commit1e440dac91fa647c179ab6715604e4da2e2343d4 authorNicolas d'Offay <nicolas.doffay@canonical.com> dateThu 3 Oct 2013, 02:21 (13 years ago) parente5d5e7135f340a
7 files changed+128−9
Components/PageHeader.qml+44−1 Dash/GenericScopeView.qml+1 plugins/Unity/scope.cpp+30−5 plugins/Unity/scope.h+7−2 tests/mocks/Unity/fake_scope.cpp+13 tests/mocks/Unity/fake_scope.h+5−1 tests/qmltests/Components/tst_PageHeader.qml+28
Components/PageHeader.qml+44−1
@@ -18,6 +18,7 @@ import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Ubuntu.Components.Popups 0.1
2020import Ubuntu.Components.ListItems 0.1 as ListItem
21+import Unity 0.1
2122
2223Item {
2324 /*!
@@ -30,6 +31,7 @@ Item {
3031 property bool searchEntryEnabled: false
3132 property alias searchQuery: searchField.text
3233 property ListModel searchHistory: SearchHistoryModel {}
34+ property Scope scope
3335
3436 height: units.gu(8.5)
3537 implicitHeight: units.gu(8.5)
@@ -148,7 +150,7 @@ Item {
148150 hasClearButton: false
149151
150152 primaryItem: AbstractButton {
151− enabled: searchField.text != ""
153+ enabled: searchField.text != "" && !searchIndicator.running
152154 onClicked: {
153155 if (searchField.text != "") {
154156 searchHistory.addQuery(searchField.text)
@@ -158,8 +160,22 @@ Item {
158160 height: parent.height
159161 width: height
160162
163+ ActivityIndicator {
164+ id: searchIndicator
165+ objectName: "searchIndicator"
166+
167+ anchors {
168+ verticalCenter: parent.verticalCenter
169+ left: parent.left
170+ leftMargin: units.gu(0.5)
171+ }
172+
173+ running: opacity > 0
174+ }
175+
161176 Image {
162177 id: primaryImage
178+ objectName: "primaryImage"
163179 anchors {
164180 verticalCenter: parent.verticalCenter
165181 left: parent.left
@@ -167,6 +183,7 @@ Item {
167183 }
168184 width: units.gu(3)
169185 height: units.gu(3)
186+ visible: opacity > 0
170187 }
171188
172189 Item {
@@ -185,6 +202,32 @@ Item {
185202 onActiveFocusChanged: {
186203 if (!activeFocus) searchContainer.closePopover()
187204 }
205+
206+ states: [
207+ State {
208+ name: "searching"
209+ when: scope.searchInProgress && searchField.text !== ""
210+ PropertyChanges { target: searchIndicator; running: true; opacity: 1 }
211+ PropertyChanges { target: primaryImage; opacity: 0 }
212+ },
213+ State {
214+ name: "idle"
215+ when: !scope.searchInProgress || searchField.text === ""
216+ PropertyChanges { target: searchIndicator; opacity: 0 }
217+ PropertyChanges { target: primaryImage; opacity: 1 }
218+ }
219+ ]
220+
221+ transitions: [
222+ Transition {
223+ to: "searching"
224+ reversible: true
225+ SequentialAnimation {
226+ NumberAnimation { target: primaryImage; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
227+ NumberAnimation { target: searchIndicator; property: "opacity"; duration: UbuntuAnimation.FastDuration; easing.type: Easing.Linear }
228+ }
229+ }
230+ ]
188231 }
189232
190233 states: [
Dash/GenericScopeView.qml+1
@@ -179,6 +179,7 @@ ScopeView {
179179 width: categoryView.width
180180 text: scopeView.scope.name
181181 searchEntryEnabled: true
182+ scope: scopeView.scope
182183 }
183184 }
184185
plugins/Unity/scope.cpp+30−5
@@ -33,12 +33,14 @@
3333#include <QQmlEngine>
3434
3535#include <UnityCore/Variant.h>
36+#include <UnityCore/GLibWrapper.h>
3637
3738#include <libintl.h>
3839#include <glib.h>
3940
4041Scope::Scope(QObject *parent) : QObject(parent)
4142 , m_formFactor("phone")
43+ , m_searchInProgress(false)
4244{
4345 m_categories.reset(new Categories(this));
4446}
@@ -68,6 +70,11 @@ QString Scope::searchHint() const
6870 return QString::fromStdString(m_unityScope->search_hint());
6971}
7072
73+bool Scope::searchInProgress() const
74+{
75+ return m_searchInProgress;
76+}
77+
7178bool Scope::visible() const
7279{
7380 return m_unityScope->visible();
@@ -115,10 +122,15 @@ void Scope::setSearchQuery(const QString& search_query)
115122 string ("") and m_searchQuery is the null string,
116123 search_query != m_searchQuery is still true.
117124 */
125+ using namespace std::placeholders;
126+
118127 if (m_searchQuery.isNull() || search_query != m_searchQuery) {
119128 m_searchQuery = search_query;
120− m_unityScope->Search(search_query.toStdString(), sigc::mem_fun(this, &Scope::searchFinished));
129+ m_cancellable.Renew();
130+ m_searchInProgress = true;
131+ m_unityScope->Search(search_query.toStdString(), std::bind(&Scope::onSearchFinished, this, _1, _2, _3), m_cancellable);
121132 Q_EMIT searchQueryChanged();
133+ Q_EMIT searchInProgressChanged();
122134 }
123135}
124136
@@ -294,8 +306,6 @@ void Scope::setUnityScope(const unity::dash::Scope::Ptr& scope)
294306 m_unityScope->visible.changed.connect(sigc::mem_fun(this, &Scope::visibleChanged));
295307 m_unityScope->shortcut.changed.connect(sigc::mem_fun(this, &Scope::shortcutChanged));
296308 m_unityScope->connected.changed.connect(sigc::mem_fun(this, &Scope::connectedChanged));
297− /* Signals forwarding */
298− connect(this, SIGNAL(searchFinished(const std::string &, unity::glib::HintsMap const &, unity::glib::Error const &)), SLOT(onSearchFinished(const std::string &, unity::glib::HintsMap const &)));
299309
300310 /* FIXME: signal should be forwarded instead of calling the handler directly */
301311 m_unityScope->activated.connect(sigc::mem_fun(this, &Scope::onActivated));
@@ -316,18 +326,33 @@ unity::dash::Scope::Ptr Scope::unityScope() const
316326
317327void Scope::synchronizeStates()
318328{
329+ using namespace std::placeholders;
330+
319331 if (connected()) {
320332 /* Forward local states to m_unityScope */
321333 if (!m_searchQuery.isNull()) {
322− m_unityScope->Search(m_searchQuery.toStdString());
334+ m_cancellable.Renew();
335+ m_searchInProgress = true;
336+ m_unityScope->Search(m_searchQuery.toStdString(), std::bind(&Scope::onSearchFinished, this, _1, _2, _3), m_cancellable);
337+ Q_EMIT searchInProgressChanged();
323338 }
324339 }
325340}
326341
327−void Scope::onSearchFinished(const std::string& /* query */, unity::glib::HintsMap const &hints)
342+void Scope::onSearchFinished(std::string const& /* query */, unity::glib::HintsMap const& hints, unity::glib::Error const& err)
328343{
329344 QString hint;
330345
346+ GError* error = const_cast<unity::glib::Error&>(err);
347+
348+ if (!err || !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
349+ m_searchInProgress = false;
350+ Q_EMIT searchInProgressChanged();
351+ } else {
352+ // no need to check the results hint, we're still searching
353+ return;
354+ }
355+
331356 if (!m_unityScope->results()->count()) {
332357 unity::glib::HintsMap::const_iterator it = hints.find("no-results-hint");
333358 if (it != hints.end()) {
plugins/Unity/scope.h+7−2
@@ -28,6 +28,7 @@
2828// libunity-core
2929#include <UnityCore/Scope.h>
3030#include <UnityCore/Results.h>
31+#include <UnityCore/GLibWrapper.h>
3132
3233#include "categories.h"
3334#include "filters.h"
@@ -43,6 +44,7 @@ class Scope : public QObject
4344 Q_PROPERTY(QString iconHint READ iconHint NOTIFY iconHintChanged)
4445 Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
4546 Q_PROPERTY(QString searchHint READ searchHint NOTIFY searchHintChanged)
47+ Q_PROPERTY(bool searchInProgress READ searchInProgress NOTIFY searchInProgressChanged)
4648 Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged)
4749 Q_PROPERTY(QString shortcut READ shortcut NOTIFY shortcutChanged)
4850 Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
@@ -65,6 +67,7 @@ public:
6567 bool visible() const;
6668 QString shortcut() const;
6769 bool connected() const;
70+ bool searchInProgress() const;
6871 Categories* categories() const;
6972 Filters* filters() const;
7073 QString searchQuery() const;
@@ -95,11 +98,11 @@ Q_SIGNALS:
9598 void iconHintChanged(const std::string&);
9699 void descriptionChanged(const std::string&);
97100 void searchHintChanged(const std::string&);
101+ void searchInProgressChanged();
98102 void visibleChanged(bool);
99103 void shortcutChanged(const std::string&);
100104 void connectedChanged(bool);
101105 void categoriesChanged();
102− void searchFinished(const std::string&, unity::glib::HintsMap const&, unity::glib::Error const&);
103106 void searchQueryChanged();
104107 void noResultsHintChanged();
105108 void formFactorChanged();
@@ -115,7 +118,7 @@ Q_SIGNALS:
115118
116119private Q_SLOTS:
117120 void synchronizeStates();
118− void onSearchFinished(const std::string &, unity::glib::HintsMap const &);
121+ void onSearchFinished(std::string const &, unity::glib::HintsMap const &, unity::glib::Error const&);
119122
120123private:
121124 unity::dash::LocalResult createLocalResult(const QVariant &uri, const QVariant &icon_hint,
@@ -133,6 +136,8 @@ private:
133136 QString m_searchQuery;
134137 QString m_noResultsHint;
135138 QString m_formFactor;
139+ bool m_searchInProgress;
140+ unity::glib::Cancellable m_cancellable;
136141 unity::glib::Cancellable m_previewCancellable;
137142};
138143
tests/mocks/Unity/fake_scope.cpp+13
@@ -30,6 +30,7 @@ static DeeModel* create_results_model(unsigned category_count, unsigned result_c
3030Scope::Scope(QObject* parent)
3131 : QObject(parent)
3232 , m_visible(false)
33+ , m_searching(false)
3334 , m_categories(new Categories(this))
3435 , m_results(new DeeListModel(this))
3536{
@@ -44,6 +45,7 @@ Scope::Scope(QString const& id, QString const& name, bool visible, QObject* pare
4445 , m_id(id)
4546 , m_name(name)
4647 , m_visible(visible)
48+ , m_searching(false)
4749 , m_categories(new Categories(this))
4850 , m_results(new DeeListModel(this))
4951{
@@ -85,6 +87,10 @@ bool Scope::connected() const {
8587 return true;
8688}
8789
90+bool Scope::searchInProgress() const {
91+ return m_searching;
92+}
93+
8894Categories* Scope::categories() const {
8995 return m_categories;
9096}
@@ -122,6 +128,13 @@ void Scope::setFormFactor(const QString &str) {
122128 }
123129}
124130
131+void Scope::setSearchInProgress(const bool inProg) {
132+ if (inProg != m_searching) {
133+ m_searching = inProg;
134+ Q_EMIT searchInProgressChanged();
135+ }
136+}
137+
125138void Scope::setNoResultsHint(const QString& str) {
126139 if (str != m_noResultsHint) {
127140 m_noResultsHint = str;
tests/mocks/Unity/fake_scope.h+5−1
@@ -37,6 +37,7 @@ class Scope : public QObject
3737 Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
3838 Q_PROPERTY(Categories* categories READ categories NOTIFY categoriesChanged)
3939
40+ Q_PROPERTY(bool searchInProgress READ searchInProgress WRITE setSearchInProgress NOTIFY searchInProgressChanged)
4041 Q_PROPERTY(QString searchQuery READ searchQuery WRITE setSearchQuery NOTIFY searchQueryChanged)
4142 Q_PROPERTY(QString noResultsHint READ noResultsHint WRITE setNoResultsHint NOTIFY noResultsHintChanged)
4243 Q_PROPERTY(QString formFactor READ formFactor WRITE setFormFactor NOTIFY formFactorChanged)
@@ -54,6 +55,7 @@ public:
5455 bool visible() const;
5556 QString shortcut() const;
5657 bool connected() const;
58+ bool searchInProgress() const;
5759 Categories* categories() const;
5860 QString searchQuery() const;
5961 QString noResultsHint() const;
@@ -64,6 +66,7 @@ public:
6466 void setSearchQuery(const QString& search_query);
6567 void setNoResultsHint(const QString& hint);
6668 void setFormFactor(const QString& form_factor);
69+ void setSearchInProgress(const bool inProg);
6770
6871 Q_INVOKABLE void activate(const QVariant &uri, const QVariant &icon_hint, const QVariant &category,
6972 const QVariant &result_type, const QVariant &mimetype, const QVariant &title,
@@ -78,11 +81,11 @@ Q_SIGNALS:
7881 void iconHintChanged(const QString&);
7982 void descriptionChanged(const QString&);
8083 void searchHintChanged(const QString&);
84+ void searchInProgressChanged();
8185 void visibleChanged(bool);
8286 void shortcutChanged(const QString&);
8387 void connectedChanged(bool);
8488 void categoriesChanged();
85− void searchFinished(const QString&);
8689 void searchQueryChanged();
8790 void noResultsHintChanged();
8891 void formFactorChanged();
@@ -103,6 +106,7 @@ protected:
103106 QString m_noResultsHint;
104107 QString m_formFactor;
105108 bool m_visible;
109+ bool m_searching;
106110
107111 Categories* m_categories;
108112 DeeListModel* m_results;
tests/qmltests/Components/tst_PageHeader.qml+28
@@ -19,12 +19,17 @@ import QtTest 1.0
1919import ".."
2020import "../../../Components"
2121import Ubuntu.Components 0.1
22+import Unity 0.1
2223import Unity.Test 0.1 as UT
2324
2425Item {
2526 width: units.gu(110)
2627 height: units.gu(30)
2728
29+ Scope {
30+ id: scopeMock
31+ }
32+
2833 UT.UnityTestCase {
2934 name: "PageHeaderTest"
3035 when: windowShown
@@ -132,6 +137,27 @@ Item {
132137 compare(pageHeader.searchHistory.count, 3)
133138 compare(pageHeader.searchHistory.get(0).query, "humppa4")
134139 }
140+
141+ function test_search_indicator() {
142+ var searchIndicator = findChild(pageHeader, "searchIndicator")
143+ var primaryImage = findChild(pageHeader, "primaryImage")
144+
145+ pageHeader.triggerSearch()
146+
147+ scopeMock.searchInProgress = false
148+ compare(searchIndicator.running, false, "Search indicator is running.")
149+ tryCompare(primaryImage, "visible", true)
150+
151+ scopeMock.searchInProgress = true
152+ compare(searchIndicator.running, true, "Search indicator isn't running.")
153+ tryCompare(primaryImage, "visible", false)
154+
155+ pageHeader.resetSearch()
156+ }
157+
158+ function cleanup() {
159+ scopeMock.searchInProgress = false
160+ }
135161 }
136162
137163 Column {
@@ -145,6 +171,8 @@ Item {
145171 right: parent.right
146172 }
147173
174+ scope: scopeMock
175+
148176 searchEntryEnabled: true
149177 text: "%^$%^%^&%^&%^$%GHR%"
150178 }