Merge lp:~kamstrup/unity/home-lenses into lp:unity

Proposed by Mikkel Kamstrup Erlandsen on 2012-01-23
Status: Merged
Approved by: Michal Hruby on 2012-01-26
Approved revision: 1886
Merged at revision: 1868
Proposed branch: lp:~kamstrup/unity/home-lenses
Merge into: lp:unity
Diff against target: 3591 lines (+1781/-1033)
35 files modified
HACKING (+7/-0)
UnityCore/CMakeLists.txt (+2/-0)
UnityCore/Categories.cpp (+8/-0)
UnityCore/Categories.h (+1/-0)
UnityCore/Filters.cpp (+8/-0)
UnityCore/Filters.h (+1/-0)
UnityCore/GLibDBusProxy.cpp (+11/-0)
UnityCore/GLibDBusProxy.h (+1/-0)
UnityCore/GLibWrapper.cpp (+5/-0)
UnityCore/GLibWrapper.h (+1/-0)
UnityCore/HomeLens.cpp (+957/-0)
UnityCore/HomeLens.h (+79/-0)
UnityCore/Lens.cpp (+128/-55)
UnityCore/Lens.h (+17/-6)
UnityCore/Model-inl.h (+47/-9)
UnityCore/Model.h (+11/-0)
UnityCore/Results.cpp (+8/-0)
UnityCore/Results.h (+1/-0)
com.canonical.Unity.gschema.xml (+10/-3)
plugins/unityshell/src/DashView.cpp (+41/-9)
plugins/unityshell/src/DashView.h (+4/-2)
plugins/unityshell/src/HomeView.cpp (+0/-252)
plugins/unityshell/src/HomeView.h (+0/-93)
plugins/unityshell/src/LensBar.cpp (+13/-3)
plugins/unityshell/src/LensView.cpp (+11/-2)
plugins/unityshell/src/PlacesHomeView.cpp (+0/-378)
plugins/unityshell/src/PlacesHomeView.h (+0/-72)
plugins/unityshell/src/ResultViewGrid.cpp (+13/-2)
po/POTFILES.in (+0/-1)
po/unity.pot (+0/-120)
standalone-clients/CMakeLists.txt (+0/-4)
standalone-clients/standalone_dash.cpp (+0/-16)
tests/CMakeLists.txt (+7/-6)
tests/test_home_lens.cpp (+362/-0)
tests/test_utils.h (+27/-0)
To merge this branch: bzr merge lp:~kamstrup/unity/home-lenses
Reviewer Review Type Date Requested Status
Michal Hruby (community) Approve on 2012-01-25
Andrea Azzarone (community) 2012-01-23 Needs Information on 2012-01-23
Review via email: mp+89669@code.launchpad.net

Description of the change

Replace the Unity home screen tiles with a "merged lens view" where lenses can contribute any categories they see fit.

settings: introduced a new gsettings key in the com.canonical.Unity.Dash namespace that controls the order of the results on the home screen. We should consider in a later branch having the sorting logic in FilesystemLenses also use this key (still falling back to alphabetical sorting when nothing is set). It empowers users and OEMs to override the default view on the homescreen by putting custom stuff first.

libunity-core: Added a HomeLens class that implements Lens and Lenses interfaces. The typical use case is to add a FilesystemLenses to the HomeLens which will make it automatically merge all lenses found on the fs. In testing we implement custom Lenses instances and add those to the HomeLens in stead.

unityshell: Removed the tiled homescreen and use a bog standard LensView to render the HomeLens in stead. Fixed a bunch of places where we assumed that Lenses' categories were always only appended to. This is no longer true with the HomeLens. Also important: Fix the SetViewType() calling semantics to the lenses when showing/hiding the dash and when switching between the lenses.

Testing it out:
For this branch to work you need also: lp:~kamstrup/unity-lens-applications/home-lenses, lp:~kamstrup/unity-lens-files/home-lenses, and lp:~mhr3/unity-lens-music/home-lenses. Add to that latest bamf. Without these you will not see the correct results.

To validate the results: Make sure that bringing up the dash resets the search and shows you a default view with exactly 3 categories in this order: Recent Apps, Recent Files, and Downloads. Then do a search and validate that you have exactly 3 categories in this order Applications, Files & Folders, and Music.

Finally; launch an app that is not a favorite, then bring up the bring up the dash home screen and assert that the running app is *not* in the recent apps category (but showing in the launcher). Then close the app and bring up the dash again. The app should now show under Recent Apps.

Design review:
Home screen without search: https://bugs.launchpad.net/unity/+bug/885738/+attachment/2690636/+files/unity-home-lens-design-review-1.png
Home screen with search: https://bugs.launchpad.net/unity/+bug/885738/+attachment/2690639/+files/unity-home-lens-design-review-2.png

To post a comment you must log in.

Meh. Sorry for the i18n noise. No idea how that got here. Will try to remove.

Sigh. bzr insists that I don't have any diff with trunk wrt the translations. Fun and games.

Found it. Amended in r1875.

lp:~kamstrup/unity/home-lenses updated on 2012-01-23
1875. By Mikkel Kamstrup Erlandsen on 2012-01-23

Revert changes to .po files

Michal Hruby (mhr3) wrote :

Just a note: all the lens branches are now merged in respective trunks.

Andrea Azzarone (azzar1) wrote :

Is PlacesHomeView.cpp/h still needed?

Andrea Azzarone (azzar1) :
review: Needs Information
Michal Hruby (mhr3) wrote :

1519 ~Lens();

I believe this should be virtual now.

1538 template<class RowAdaptor>
1539 Model<RowAdaptor>::Model()

The missing model.SetGetterFunction(...) could be a very nasty surprise in the future.

One of weird things I noticed is that when using keynav in the home view (specifically the down array) sometimes the "See xx more results" doesn't get highlighted and can't be expanded by pressing enter.

review: Needs Fixing
Andrea Azzarone (azzar1) wrote :

> One of weird things I noticed is that when using keynav in the home view
> (specifically the down array) sometimes the "See xx more results" doesn't get
> highlighted and can't be expanded by pressing enter.

Fixed here https://code.launchpad.net/~andyrock/unity/key-nav

> Is PlacesHomeView.cpp/h still needed

Strictly, no. I didn't delete it because technically the home-lens concept is on a trial run. If design ditches it we'll have to revert to the old view and I didn't want to dig it out of the rev history. Although, I probably should delete it, to have a better looking diffstat :-)

> 1519 ~Lens();
>
> I believe this should be virtual now.

Right. I wonder why the compiler didn't complain. It usually does in these cases. Fixed.

> 1538 template<class RowAdaptor>
> 1539 Model<RowAdaptor>::Model()
>
> The missing model.SetGetterFunction(...) could be a very nasty surprise in the
> future.

Ah, good catch. I created a new Init() method that gets called from the constructors so they can share the setup code. (my favorite hate-pattern. why oh why can't we chain constructors in C++?!?!?!11). Anyway; fixed.

lp:~kamstrup/unity/home-lenses updated on 2012-01-24
1876. By Mikkel Kamstrup Erlandsen on 2012-01-24

From the review: Make the Model<RowAdaptor> constructors call into a shared Init() method to make sure we don't forget to initialize some of the properties (in particular one variant of the constructor wasn't setting a getter on the 'model' property)

1877. By Mikkel Kamstrup Erlandsen on 2012-01-24

From the review: Make the destructor for the Lens class virtual

1878. By Mikkel Kamstrup Erlandsen on 2012-01-24

Remove HomeView.{cpp,h} and PlacesHomeView.{cpp,h} now that they have been obsoleted by the HomeLens class. Check them diffstats, ma'!

1879. By Mikkel Kamstrup Erlandsen on 2012-01-24

Remove PlacesHomeView.cpp from POTFILES.in

WTF. The diffs seems to be showing that I removed po/unity.pot which I swear I didn't! Let me look into it.

Ok, the diff should be clean in r1881. Please re-review

lp:~kamstrup/unity/home-lenses updated on 2012-01-24
1880. By Mikkel Kamstrup Erlandsen on 2012-01-24

Sync with trunk

1881. By Mikkel Kamstrup Erlandsen on 2012-01-24

Re-add unity.pot. It seems to have been removed by some freak accident in r1832 of this branch

Michal Hruby (mhr3) wrote :

One thing that's still missing is resetting the search entry when dash is hidden (or not-resetting the search sent to lenses as https://bugs.launchpad.net/ayatana-design/+bug/914759 requires).

Either would be fine, but right now the search entry isn't reset, while the search is...

review: Needs Fixing

Implemented search state retention as requested by design. Please re-review.

lp:~kamstrup/unity/home-lenses updated on 2012-01-24
1882. By Mikkel Kamstrup Erlandsen on 2012-01-24

Implement search state retention policy for the home lens as specified in https://bugs.launchpad.net/unity/+bug/914759

John acked the design review on IRC

Michal Hruby (mhr3) wrote :

Sorry, but Esc now no longer clears the search entry... I don't think that's desired.

review: Needs Fixing
Andrea Azzarone (azzar1) wrote :

> Sorry, but Esc now no longer clears the search entry... I don't think that's
> desired.

1866 - else
1867 - search_bar_->search_string = "";

> > Sorry, but Esc now no longer clears the search entry... I don't think that's
> > desired.
>
> 1866 - else
> 1867 - search_bar_->search_string = "";

I am not sure if you mean to back Michal up, or refute his claim Andrea? :-)

In any case, I think I have the right behavior here... When clicking Esc I see:

a) if there is text in the entry the text is cleared, and the dash shows results for the empty search
b) if there is no search, the dash is hidden

As a Corollary, c) hitting Esc twice clears and hides the dash.

Michal Hruby (mhr3) wrote :

Unfortunately, that's not the behaviour I see (particularly a) doesn't work). Afaict the code Andrea pasted is what handled the "esc and search entry not empty" case, and you removed it...

lp:~kamstrup/unity/home-lenses updated on 2012-01-25
1883. By Mikkel Kamstrup Erlandsen on 2012-01-25

Clear search entry when Esc is pressed - and close it if we have no search string.

Hmmm, I can see you are correct looking at the code... I wonder how it could work on my box. I must have been running some almost, but not entirely, identical to what I pushed. Nonetheless; pushed a fix.

Michal Hruby (mhr3) wrote :

Yep, it's fine now. Approved from me, but I suppose more people want to go through this...

review: Approve
Neil J. Patel (njpatel) wrote :

237 (and other places): Instead of handling the gchar* yourself, use glib::String from UnityCore/GLibWrapper.h, it autofree's it when it's popped from the stack, so you don't need to worry about g_free in multiple places.

330: Evil, evil kamstrup.

Apart from that, excellent work :)

lp:~kamstrup/unity/home-lenses updated on 2012-01-26
1884. By Mikkel Kamstrup Erlandsen on 2012-01-26

Add automatic std::string conversion to glib::String

1885. By Mikkel Kamstrup Erlandsen on 2012-01-26

Use glib::String to simplify mem management in HomeLens

> 237 (and other places): Instead of handling the gchar* yourself, use
> glib::String from UnityCore/GLibWrapper.h, it autofree's it when it's popped
> from the stack, so you don't need to worry about g_free in multiple places.

Ok, now using glib::String. It required that I add automatic std::string conversion to glib::String in order not to complicate the code - I was wondering if there was a specific reason it wasn't in glib::String already. Seems like a strange omission?

lp:~kamstrup/unity/home-lenses updated on 2012-01-26
1886. By Mikkel Kamstrup Erlandsen on 2012-01-26

Sync with trunk

Michal Hruby (mhr3) wrote :

Neil ACKed this as well, so approving...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'HACKING'
2--- HACKING 2012-01-10 16:35:26 +0000
3+++ HACKING 2012-01-26 08:37:26 +0000
4@@ -39,5 +39,12 @@
5
6 Testing is handled through GTest, googles test system, we currently have three test targets; test-gtest, test-gtest-xless, test-gtest-dbus. tests that require Nux should be in test-gtest as they require X, tests that can run headless should be in test-gtest-xless and tests that require dbus should be run with test-gtest-dbus, which tests run with our test service.
7
8+To enable debug logging you must set the environment variable UNITY_LOG_SEVERITY to the log domain you
9+want to watch and the log level for that domain. For example:
10+
11+ UNITY_LOG_SEVERITY="unity.dash.lens=DEBUG" standalone-clients/dash
12+
13+
14+
15 for more information see the README file
16
17
18=== modified file 'UnityCore/CMakeLists.txt'
19--- UnityCore/CMakeLists.txt 2012-01-17 16:02:39 +0000
20+++ UnityCore/CMakeLists.txt 2012-01-26 08:37:26 +0000
21@@ -21,6 +21,7 @@
22 GLibSignal-inl.h
23 GLibWrapper.h
24 GLibWrapper-inl.h
25+ HomeLens.h
26 IndicatorEntry.h
27 Indicator.h
28 Indicators.h
29@@ -53,6 +54,7 @@
30 GLibDBusProxy.cpp
31 GLibSignal.cpp
32 GLibWrapper.cpp
33+ HomeLens.cpp
34 Indicator.cpp
35 IndicatorEntry.cpp
36 Indicators.cpp
37
38=== modified file 'UnityCore/Categories.cpp'
39--- UnityCore/Categories.cpp 2011-07-25 10:54:25 +0000
40+++ UnityCore/Categories.cpp 2012-01-26 08:37:26 +0000
41@@ -31,6 +31,14 @@
42 row_removed.connect(sigc::mem_fun(this, &Categories::OnRowRemoved));
43 }
44
45+Categories::Categories(ModelType model_type)
46+ : Model<Category>::Model(model_type)
47+{
48+ row_added.connect(sigc::mem_fun(this, &Categories::OnRowAdded));
49+ row_changed.connect(sigc::mem_fun(this, &Categories::OnRowChanged));
50+ row_removed.connect(sigc::mem_fun(this, &Categories::OnRowRemoved));
51+}
52+
53 void Categories::OnRowAdded(Category& category)
54 {
55 category_added.emit(category);
56
57=== modified file 'UnityCore/Categories.h'
58--- UnityCore/Categories.h 2011-07-28 13:34:38 +0000
59+++ UnityCore/Categories.h 2012-01-26 08:37:26 +0000
60@@ -36,6 +36,7 @@
61 typedef std::shared_ptr<Categories> Ptr;
62
63 Categories();
64+ Categories(ModelType model_type);
65
66 sigc::signal<void, Category const&> category_added;
67 sigc::signal<void, Category const&> category_changed;
68
69=== modified file 'UnityCore/Filters.cpp'
70--- UnityCore/Filters.cpp 2011-08-05 09:55:33 +0000
71+++ UnityCore/Filters.cpp 2012-01-26 08:37:26 +0000
72@@ -56,6 +56,14 @@
73 row_removed.connect(sigc::mem_fun(this, &Filters::OnRowRemoved));
74 }
75
76+Filters::Filters(ModelType model_type)
77+ : Model<FilterAdaptor>::Model(model_type)
78+{
79+ row_added.connect(sigc::mem_fun(this, &Filters::OnRowAdded));
80+ row_changed.connect(sigc::mem_fun(this, &Filters::OnRowChanged));
81+ row_removed.connect(sigc::mem_fun(this, &Filters::OnRowRemoved));
82+}
83+
84 Filters::~Filters()
85 {}
86
87
88=== modified file 'UnityCore/Filters.h'
89--- UnityCore/Filters.h 2011-08-05 09:55:33 +0000
90+++ UnityCore/Filters.h 2012-01-26 08:37:26 +0000
91@@ -51,6 +51,7 @@
92 typedef std::map<DeeModelIter*, Filter::Ptr> FilterMap;
93
94 Filters();
95+ Filters(ModelType model_type);
96 ~Filters();
97
98 Filter::Ptr FilterAtIndex(std::size_t index);
99
100=== modified file 'UnityCore/GLibDBusProxy.cpp'
101--- UnityCore/GLibDBusProxy.cpp 2012-01-16 15:19:47 +0000
102+++ UnityCore/GLibDBusProxy.cpp 2012-01-26 08:37:26 +0000
103@@ -72,6 +72,7 @@
104 int timeout_msec);
105
106 void Connect(string const& signal_name, ReplyCallback callback);
107+ bool IsConnected();
108
109 static void OnNameAppeared(GDBusConnection* connection, const char* name,
110 const char* name_owner, gpointer impl);
111@@ -192,6 +193,11 @@
112 this);
113 }
114
115+bool DBusProxy::Impl::IsConnected()
116+{
117+ return connected_;
118+}
119+
120 void DBusProxy::Impl::OnProxyConnectCallback(GObject* source,
121 GAsyncResult* res,
122 gpointer impl)
123@@ -320,5 +326,10 @@
124 pimpl->Connect(signal_name, callback);
125 }
126
127+bool DBusProxy::IsConnected()
128+{
129+ return pimpl->IsConnected();
130+}
131+
132 }
133 }
134
135=== modified file 'UnityCore/GLibDBusProxy.h'
136--- UnityCore/GLibDBusProxy.h 2011-12-06 10:39:36 +0000
137+++ UnityCore/GLibDBusProxy.h 2012-01-26 08:37:26 +0000
138@@ -54,6 +54,7 @@
139 int timeout_msec = -1);
140
141 void Connect(std::string const& signal_name, ReplyCallback callback);
142+ bool IsConnected();
143
144 sigc::signal<void> connected;
145 sigc::signal<void> disconnected;
146
147=== modified file 'UnityCore/GLibWrapper.cpp'
148--- UnityCore/GLibWrapper.cpp 2011-12-06 10:39:36 +0000
149+++ UnityCore/GLibWrapper.cpp 2012-01-26 08:37:26 +0000
150@@ -104,6 +104,11 @@
151 return string_;
152 }
153
154+String::operator std::string()
155+{
156+ return Str();
157+}
158+
159 String::operator bool() const
160 {
161 return bool(string_);
162
163=== modified file 'UnityCore/GLibWrapper.h'
164--- UnityCore/GLibWrapper.h 2011-12-14 18:22:23 +0000
165+++ UnityCore/GLibWrapper.h 2012-01-26 08:37:26 +0000
166@@ -115,6 +115,7 @@
167
168 operator bool() const;
169 operator char*();
170+ operator std::string();
171 gchar* Value();
172 std::string Str() const;
173
174
175=== added file 'UnityCore/HomeLens.cpp'
176--- UnityCore/HomeLens.cpp 1970-01-01 00:00:00 +0000
177+++ UnityCore/HomeLens.cpp 2012-01-26 08:37:26 +0000
178@@ -0,0 +1,957 @@
179+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
180+/*
181+ * Copyright (C) 2012 Canonical Ltd
182+ *
183+ * This program is free software: you can redistribute it and/or modify
184+ * it under the terms of the GNU General Public License version 3 as
185+ * published by the Free Software Foundation.
186+ *
187+ * This program is distributed in the hope that it will be useful,
188+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
189+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
190+ * GNU General Public License for more details.
191+ *
192+ * You should have received a copy of the GNU General Public License
193+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
194+ *
195+ * Authored by: Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com>
196+ */
197+
198+#include <glib.h>
199+#include <string>
200+#include <stdexcept>
201+#include <map>
202+
203+#include "GLibSignal.h"
204+#include "HomeLens.h"
205+#include "Lens.h"
206+#include "Model.h"
207+
208+#include "config.h"
209+
210+namespace unity
211+{
212+namespace dash
213+{
214+
215+namespace
216+{
217+
218+nux::logging::Logger logger("unity.dash.homelens");
219+
220+}
221+
222+/*
223+ * Helper class that maps category offsets between the merged lens and
224+ * source lenses. We also use it to merge categories from different lenses
225+ * with the same display name into the same category.
226+ *
227+ * NOTE: The model pointers passed in are expected to be pointers to the
228+ * result source models - and not the category source models!
229+ */
230+class HomeLens::CategoryRegistry
231+{
232+public:
233+ CategoryRegistry(HomeLens* owner)
234+ : is_dirty_(false)
235+ , owner_(owner) {}
236+
237+ int FindCategoryOffset(DeeModel* model, unsigned int source_cat_offset)
238+ {
239+ glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model));
240+ std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id);
241+
242+ if (i != reg_by_id_.end())
243+ return i->second;
244+
245+ return -1;
246+ }
247+
248+ int FindCategoryOffset(const gchar* display_name)
249+ {
250+ std::map<std::string,unsigned int>::iterator i =
251+ reg_by_display_name_.find(display_name);
252+ if (i != reg_by_display_name_.end())
253+ return i->second;
254+
255+ return -1;
256+ }
257+
258+ /* Register a new category */
259+ void RegisterCategoryOffset(DeeModel* model,
260+ unsigned int source_cat_offset,
261+ const gchar* display_name,
262+ unsigned int target_cat_offset)
263+ {
264+ glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model));
265+
266+ std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id);
267+ if (i != reg_by_id_.end())
268+ {
269+ LOG_ERROR(logger) << "Category '" << c_id << "' already registered!";
270+ return;
271+ }
272+
273+ if (display_name != NULL)
274+ {
275+ i = reg_by_display_name_.find(display_name);
276+ if (i != reg_by_display_name_.end())
277+ {
278+ LOG_ERROR(logger) << "Category '" << display_name << "' already registered!";
279+ return;
280+ }
281+ }
282+
283+ /* Any existing categories with offsets >= target_cat_offset must be
284+ * pushed up. Update both maps by id and display name */
285+ std::map<std::string,unsigned int>::iterator end = reg_by_id_.end();
286+ for (i = reg_by_id_.begin(); i != end; i++)
287+ {
288+ if (i->second >= target_cat_offset)
289+ {
290+ i->second = i->second + 1;
291+ is_dirty_ = true;
292+ }
293+ }
294+
295+ for (i = reg_by_display_name_.begin(), end = reg_by_display_name_.end(); i != end; i++)
296+ {
297+ if (i->second >= target_cat_offset)
298+ {
299+ i->second = i->second + 1;
300+ is_dirty_ = true;
301+ }
302+ }
303+
304+ reg_by_id_[c_id] = target_cat_offset;
305+
306+ /* Callers pass a NULL display_name when they already have a category
307+ * with the right display registered */
308+ if (display_name != NULL)
309+ {
310+ reg_by_display_name_[display_name] = target_cat_offset;
311+ LOG_DEBUG(logger) << "Registered category '" << display_name
312+ << "' with source offset " << source_cat_offset
313+ << " and target offset " << target_cat_offset
314+ << ". Id " << c_id;
315+ }
316+ else
317+ {
318+ LOG_DEBUG(logger) << "Registered category with source offset "
319+ << source_cat_offset << " and target offset "
320+ << target_cat_offset << ". Id " << c_id;
321+ }
322+ }
323+
324+ /* Associate a source results model and category offset with an existing
325+ * target category offset */
326+ void AssociateCategoryOffset(DeeModel* model,
327+ unsigned int source_cat_offset,
328+ unsigned int target_cat_offset)
329+ {
330+ glib::String c_id(g_strdup_printf("%u+%p", source_cat_offset, model));
331+
332+ std::map<std::string,unsigned int>::iterator i = reg_by_id_.find(c_id);
333+ if (i != reg_by_id_.end())
334+ {
335+ LOG_ERROR(logger) << "Category '" << c_id << "' already registered!";
336+ return;
337+ }
338+
339+ reg_by_id_[c_id] = target_cat_offset;
340+ }
341+
342+ /**
343+ * Returns true and resets the dirty state if the registry was dirty.
344+ * When you've checked a dirty registry you must either clear the
345+ * merged results model or recalibrate all category offset in it
346+ * (and Unity probably wont support the latter?).
347+ */
348+ bool CheckDirty()
349+ {
350+ return is_dirty_ ? (is_dirty_ = false, true) : false;
351+ }
352+
353+private:
354+ std::map<std::string,unsigned int> reg_by_id_;
355+ std::map<std::string,unsigned int> reg_by_display_name_;
356+ bool is_dirty_;
357+ HomeLens* owner_;
358+};
359+
360+/*
361+ * Helper class that merges a set of DeeModels into one super model
362+ */
363+class HomeLens::ModelMerger : public sigc::trackable
364+{
365+public:
366+ ModelMerger(glib::Object<DeeModel> target);
367+ virtual ~ModelMerger();
368+
369+ void AddSource(glib::Object<DeeModel> source);
370+
371+protected:
372+ virtual void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter);
373+ virtual void OnSourceRowRemoved(DeeModel* model, DeeModelIter* iter);
374+ virtual void OnSourceRowChanged(DeeModel* model, DeeModelIter* iter);
375+ void EnsureRowBuf(DeeModel *source);
376+
377+ /* The merge tag lives on the source models, pointing to the mapped
378+ * row in the target model */
379+ DeeModelTag* FindSourceToTargetTag(DeeModel *model);
380+
381+protected:
382+ glib::SignalManager sig_manager_;
383+ GVariant** row_buf_;
384+ unsigned int n_cols_;
385+ glib::Object<DeeModel> target_;
386+ std::map<DeeModel*,DeeModelTag*> source_to_target_tags_;
387+};
388+
389+/*
390+ * Specialized ModelMerger that takes care merging results models.
391+ * We need special handling here because rows in each lens' results model
392+ * specifies an offset into the lens' categories model where the display
393+ * name of the category is defined.
394+ *
395+ * This class converts the offset of the source lens' categories into
396+ * offsets into the merged category model.
397+ *
398+ * Each row added to the target is tagged with a pointer to the Lens instance
399+ * from which it came
400+ */
401+class HomeLens::ResultsMerger : public ModelMerger
402+{
403+public:
404+ ResultsMerger(glib::Object<DeeModel> target,
405+ HomeLens::CategoryRegistry* cat_registry);
406+
407+protected:
408+ void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter);
409+ void OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter);
410+ void OnSourceRowChanged(DeeModel *model, DeeModelIter *iter);
411+ void CheckCategoryRegistryDirty();
412+
413+private:
414+ HomeLens::CategoryRegistry* cat_registry_;
415+};
416+
417+/*
418+ * Specialized ModelMerger that takes care merging category models.
419+ * We need special handling here because rows in each lens' results model
420+ * specifies an offset into the lens' categories model where the display
421+ * name of the category is defined.
422+ *
423+ * This class records a map of the offsets from the original source category
424+ * models to the offsets in the combined categories model.
425+ */
426+class HomeLens::CategoryMerger : public ModelMerger
427+{
428+public:
429+ CategoryMerger(glib::Object<DeeModel> target,
430+ HomeLens::CategoryRegistry* cat_registry);
431+
432+ void OnSourceRowAdded(DeeModel *model, DeeModelIter *iter);
433+ void OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter);
434+
435+private:
436+ HomeLens::CategoryRegistry* cat_registry_;
437+ DeeModelTag* priority_tag_;
438+};
439+
440+/*
441+ * Pimpl for HomeLens
442+ */
443+class HomeLens::Impl : public sigc::trackable
444+{
445+public:
446+ Impl(HomeLens* owner);
447+ ~Impl();
448+
449+ void OnLensAdded(Lens::Ptr& lens);
450+ gsize FindLensPriority (Lens::Ptr& lens);
451+ void EnsureCategoryAnnotation(Lens::Ptr& lens, DeeModel* results, DeeModel* categories);
452+ Lens::Ptr FindLensForUri(std::string const& uri);
453+
454+ HomeLens* owner_;
455+ Lenses::LensList lenses_;
456+ HomeLens::CategoryRegistry cat_registry_;
457+ HomeLens::ResultsMerger results_merger_;
458+ HomeLens::CategoryMerger categories_merger_;
459+ HomeLens::ModelMerger filters_merger_;
460+ int running_searches_;
461+ glib::Object<GSettings> settings_;
462+};
463+
464+/*
465+ * IMPLEMENTATION
466+ */
467+
468+HomeLens::ModelMerger::ModelMerger(glib::Object<DeeModel> target)
469+ : row_buf_(NULL)
470+ , n_cols_(0)
471+ , target_(target)
472+{}
473+
474+HomeLens::ResultsMerger::ResultsMerger(glib::Object<DeeModel> target,
475+ CategoryRegistry *cat_registry)
476+ : HomeLens::ModelMerger::ModelMerger(target)
477+ , cat_registry_(cat_registry)
478+{}
479+
480+HomeLens::CategoryMerger::CategoryMerger(glib::Object<DeeModel> target,
481+ CategoryRegistry *cat_registry)
482+ : HomeLens::ModelMerger::ModelMerger(target)
483+ , cat_registry_(cat_registry)
484+ , priority_tag_(dee_model_register_tag(target, NULL))
485+{}
486+
487+HomeLens::ModelMerger::~ModelMerger()
488+{
489+ if (row_buf_)
490+ delete row_buf_;
491+}
492+
493+void HomeLens::ModelMerger::AddSource(glib::Object<DeeModel> source)
494+{
495+ typedef glib::Signal<void, DeeModel*, DeeModelIter*> RowSignalType;
496+
497+ if (!source)
498+ {
499+ LOG_ERROR(logger) << "Trying to add NULL source to ModelMerger";
500+ return;
501+ }
502+
503+ DeeModelTag* merger_tag = dee_model_register_tag(source, NULL);
504+ source_to_target_tags_[source.RawPtr()] = merger_tag;
505+
506+ sig_manager_.Add(new RowSignalType(source.RawPtr(),
507+ "row-added",
508+ sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowAdded)));
509+
510+ sig_manager_.Add(new RowSignalType(source.RawPtr(),
511+ "row-removed",
512+ sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowRemoved)));
513+
514+ sig_manager_.Add(new RowSignalType(source.RawPtr(),
515+ "row-changed",
516+ sigc::mem_fun(this, &HomeLens::ModelMerger::OnSourceRowChanged)));
517+}
518+
519+void HomeLens::ModelMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter)
520+{
521+ // Default impl. does nothing.
522+ // Note that the filters_merger_ relies on this behavior. Supporting
523+ // filters on the home screen is possible, but *quite* tricky.
524+ // So...
525+ // Discard ALL the rows!
526+}
527+
528+void HomeLens::ResultsMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter)
529+{
530+ DeeModelIter* target_iter;
531+ DeeModelTag* target_tag;
532+ int target_cat_offset, source_cat_offset;
533+ const unsigned int CATEGORY_COLUMN = 2;
534+
535+ EnsureRowBuf(model);
536+ CheckCategoryRegistryDirty();
537+
538+ dee_model_get_row (model, iter, row_buf_);
539+ target_tag = FindSourceToTargetTag(model);
540+
541+ /* Update the row with the corrected category offset */
542+ source_cat_offset = dee_model_get_uint32(model, iter, CATEGORY_COLUMN);
543+ target_cat_offset = cat_registry_->FindCategoryOffset(model, source_cat_offset);
544+
545+ if (target_cat_offset >= 0)
546+ {
547+ g_variant_unref (row_buf_[CATEGORY_COLUMN]);
548+ row_buf_[CATEGORY_COLUMN] = g_variant_new_uint32(target_cat_offset);
549+
550+ /* Sink the ref on the new row member. By Dee API contract they must all
551+ * be strong refs, not floating */
552+ g_variant_ref_sink(row_buf_[CATEGORY_COLUMN]);
553+
554+ target_iter = dee_model_append_row (target_, row_buf_);
555+ dee_model_set_tag(model, iter, target_tag, target_iter);
556+
557+ LOG_DEBUG(logger) << "Found " << dee_model_get_string(model, iter, 0)
558+ << " (source cat " << source_cat_offset << ", target cat "
559+ << target_cat_offset << ")";
560+ }
561+ else
562+ {
563+ LOG_ERROR(logger) << "No category registered for model "
564+ << model << ", source offset " << source_cat_offset
565+ << ": " << dee_model_get_string(model, iter, 0);
566+ }
567+
568+ for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]);
569+}
570+
571+void HomeLens::CategoryMerger::OnSourceRowAdded(DeeModel *model, DeeModelIter *iter)
572+{
573+ DeeModel* results_model;
574+ DeeModelIter* target_iter;
575+ DeeModelIter* target_end;
576+ DeeModelTag* target_tag;
577+ int target_cat_offset, source_cat_offset;
578+ const gchar* display_name;
579+ const unsigned int DISPLAY_NAME_COLUMN = 0;
580+ gsize lens_priority, prio;
581+
582+ EnsureRowBuf(model);
583+
584+ results_model = static_cast<DeeModel*>(g_object_get_data(
585+ G_OBJECT(model), "unity-homelens-results-model"));
586+ if (results_model == NULL)
587+ {
588+ LOG_DEBUG(logger) << "Category model " << model
589+ << " does not have a results model yet";
590+ return;
591+ }
592+
593+ dee_model_get_row (model, iter, row_buf_);
594+ target_tag = FindSourceToTargetTag(model);
595+ source_cat_offset = dee_model_get_position(model, iter);
596+
597+ /* If we already have a category registered with the same display name
598+ * then we just use that. Otherwise register a new category for it */
599+ display_name = dee_model_get_string(model, iter, DISPLAY_NAME_COLUMN);
600+ target_cat_offset = cat_registry_->FindCategoryOffset(display_name);
601+ if (target_cat_offset >= 0)
602+ {
603+ cat_registry_->AssociateCategoryOffset(results_model, source_cat_offset,
604+ target_cat_offset);
605+ goto cleanup;
606+ }
607+
608+ /*
609+ * Below we can assume that we have a genuinely new category.
610+ *
611+ * Our goal is to insert the category at a position suitable for its
612+ * priority. We insert it as the last item in the set of items which
613+ * have equal priority.
614+ *
615+ * We allow our selves to do linear inserts as we wont expect a lot
616+ * of categories.
617+ */
618+
619+ lens_priority = GPOINTER_TO_SIZE(g_object_get_data(
620+ G_OBJECT(model), "unity-homelens-priority"));
621+
622+ /* Seek correct position in the merged category model */
623+ target_iter = dee_model_get_first_iter(target_);
624+ target_end = dee_model_get_last_iter(target_);
625+ while (target_iter != target_end)
626+ {
627+ prio = GPOINTER_TO_SIZE(dee_model_get_tag(target_, target_iter, priority_tag_));
628+ if (lens_priority > prio)
629+ break;
630+ target_iter = dee_model_next(target_, target_iter);
631+ }
632+
633+ /* Add the row to the merged categories model and store required metadata */
634+ target_iter = dee_model_insert_row_before(target_, target_iter, row_buf_);
635+ dee_model_set_tag(model, iter, target_tag, target_iter);
636+ dee_model_set_tag(target_, target_iter, priority_tag_, GSIZE_TO_POINTER(lens_priority));
637+ target_cat_offset = dee_model_get_position(target_, target_iter);
638+ cat_registry_->RegisterCategoryOffset(results_model, source_cat_offset,
639+ display_name, target_cat_offset);
640+
641+ cleanup:
642+ for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]);
643+}
644+
645+void HomeLens::CategoryMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter)
646+{
647+ /* We don't support removals of categories.
648+ * You can check out any time you like, but you can never leave
649+ *
650+ * The category registry code is spaghettified enough already.
651+ * No more please.
652+ */
653+ LOG_DEBUG(logger) << "Removal of categories not supported.";
654+}
655+
656+void HomeLens::ModelMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter)
657+{
658+ DeeModelIter* target_iter;
659+ DeeModelTag* target_tag;
660+
661+ EnsureRowBuf(model);
662+
663+ target_tag = FindSourceToTargetTag(model);
664+ target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(model,
665+ iter,
666+ target_tag));
667+
668+ /* We might not have registered a target iter for the row.
669+ * This fx. happens if we re-used a category based on display_name */
670+ if (target_iter != NULL)
671+ dee_model_remove(target_, target_iter);
672+}
673+
674+void HomeLens::ResultsMerger::OnSourceRowRemoved(DeeModel *model, DeeModelIter *iter)
675+{
676+ CheckCategoryRegistryDirty();
677+ ModelMerger::OnSourceRowRemoved(model, iter);
678+}
679+
680+void HomeLens::ModelMerger::OnSourceRowChanged(DeeModel *model, DeeModelIter *iter)
681+{
682+ DeeModelIter* target_iter;
683+ DeeModelTag* target_tag;
684+
685+ EnsureRowBuf(model);
686+
687+ dee_model_get_row (model, iter, row_buf_);
688+ target_tag = FindSourceToTargetTag(model);
689+ target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(model,
690+ iter,
691+ target_tag));
692+
693+ dee_model_set_row (target_, target_iter, row_buf_);
694+
695+ for (unsigned int i = 0; i < n_cols_; i++) g_variant_unref(row_buf_[i]);
696+}
697+
698+void HomeLens::ResultsMerger::OnSourceRowChanged(DeeModel *model, DeeModelIter *iter)
699+{
700+ // FIXME: We can support this, but we need to re-calculate the category offset
701+ LOG_WARN(logger) << "In-line changing of results not supported in the home lens. Sorry.";
702+}
703+
704+void HomeLens::ModelMerger::EnsureRowBuf(DeeModel *model)
705+{
706+ if (G_UNLIKELY (n_cols_ == 0))
707+ {
708+ /* We have two things to accomplish here.
709+ * 1) Allocate the row_buf_, and
710+ * 2) Make sure that the target model has the correct schema set.
711+ *
712+ * INVARIANT: n_cols_ == 0 iff row_buf_ == NULL.
713+ */
714+
715+ n_cols_ = dee_model_get_n_columns(model);
716+
717+ if (n_cols_ == 0)
718+ {
719+ LOG_ERROR(logger) << "Source model has not provided a schema for the model merger!";
720+ return;
721+ }
722+
723+ /* Lazily adopt schema from source if we don't have one.
724+ * If we do have a schema let's validate that they match the source */
725+ if (dee_model_get_n_columns(target_) == 0)
726+ {
727+ dee_model_set_schema_full(target_,
728+ dee_model_get_schema(model, NULL),
729+ n_cols_);
730+ }
731+ else
732+ {
733+ unsigned int n_cols1;
734+ const gchar* const *schema1 = dee_model_get_schema(target_, &n_cols1);
735+ const gchar* const *schema2 = dee_model_get_schema(model, NULL);
736+
737+ /* At the very least we should have an equal number of rows */
738+ if (n_cols_ != n_cols1)
739+ {
740+ LOG_ERROR(logger) << "Schema mismatch between source and target model. Expected "
741+ << n_cols1 << " columns, but found "
742+ << n_cols_ << ".";
743+ n_cols_ = 0;
744+ return;
745+ }
746+
747+ /* Compare schemas */
748+ for (unsigned int i = 0; i < n_cols_; i++)
749+ {
750+ if (g_strcmp0(schema1[i], schema2[i]) != 0)
751+ {
752+ LOG_ERROR(logger) << "Schema mismatch between source and target model. Expected column "
753+ << i << " to be '" << schema1[i] << "', but found '"
754+ << schema2[i] << "'.";
755+ n_cols_ = 0;
756+ return;
757+ }
758+ }
759+ }
760+
761+ row_buf_ = g_new0 (GVariant*, n_cols_);
762+ }
763+}
764+
765+DeeModelTag* HomeLens::ModelMerger::FindSourceToTargetTag(DeeModel *model)
766+{
767+ return source_to_target_tags_[model];
768+}
769+
770+void HomeLens::ResultsMerger::CheckCategoryRegistryDirty()
771+{
772+ DeeModel* source;
773+ DeeModelTag* target_tag;
774+ const unsigned int CATEGORY_COLUMN = 2;
775+ std::map<DeeModel*,DeeModelTag*>::iterator i, end;
776+
777+ if (G_LIKELY(!cat_registry_->CheckDirty()))
778+ return;
779+
780+ LOG_DEBUG(logger) << "Category registry marked dirty. Fixing category offsets.";
781+
782+ /*
783+ * Iterate over all results in each source model and re-calculate the
784+ * the category offset in the corresponding rows in the target model
785+ */
786+ for (i = source_to_target_tags_.begin(), end = source_to_target_tags_.end();
787+ i != end; i++)
788+ {
789+ source = i->first;
790+ target_tag = i->second;
791+
792+ DeeModelIter* source_iter = dee_model_get_first_iter(source);
793+ DeeModelIter* source_end = dee_model_get_last_iter(source);
794+
795+ for (source_iter = dee_model_get_first_iter(source), source_end = dee_model_get_last_iter(source);
796+ source_iter != source_end;
797+ source_iter = dee_model_next(source, source_iter))
798+ {
799+ DeeModelIter* target_iter = static_cast<DeeModelIter*>(dee_model_get_tag(source, source_iter, target_tag));
800+
801+ /* No guarantee that rows in the source are mapped to the target */
802+ if (target_iter == NULL)
803+ continue;
804+
805+ unsigned int source_cat_offset = dee_model_get_uint32(source, source_iter, CATEGORY_COLUMN);
806+ int cat_offset = cat_registry_->FindCategoryOffset(source, source_cat_offset);
807+
808+ if (G_LIKELY(cat_offset >= 0))
809+ {
810+ dee_model_set_value(target_, target_iter, CATEGORY_COLUMN,
811+ g_variant_new_uint32(cat_offset));
812+ }
813+ else
814+ {
815+ LOG_ERROR(logger) << "No registered category id for category "
816+ << source_cat_offset << " on result source model "
817+ << source << ".";
818+ /* We can't really recover from this :-( */
819+ }
820+ }
821+ }
822+}
823+
824+HomeLens::Impl::Impl(HomeLens *owner)
825+ : owner_(owner)
826+ , cat_registry_(owner)
827+ , results_merger_(owner->results()->model(), &cat_registry_)
828+ , categories_merger_(owner->categories()->model(), &cat_registry_)
829+ , filters_merger_(owner->filters()->model())
830+ , running_searches_(0)
831+ , settings_(g_settings_new("com.canonical.Unity.Dash"))
832+{
833+ DeeModel* results = owner->results()->model();
834+ if (dee_model_get_n_columns(results) == 0)
835+ {
836+ dee_model_set_schema(results, "s", "s", "u", "s", "s", "s", "s", NULL);
837+ }
838+
839+ DeeModel* categories = owner->categories()->model();
840+ if (dee_model_get_n_columns(categories) == 0)
841+ {
842+ dee_model_set_schema(categories, "s", "s", "s", "a{sv}", NULL);
843+ }
844+
845+ DeeModel* filters = owner->filters()->model();
846+ if (dee_model_get_n_columns(filters) == 0)
847+ {
848+ dee_model_set_schema(filters, "s", "s", "s", "s", "a{sv}", "b", "b", "b", NULL);
849+ }
850+}
851+
852+HomeLens::Impl::~Impl()
853+{
854+
855+}
856+
857+/*void HomeLens::Impl::CheckCategories()
858+{
859+
860+}*/
861+
862+gsize HomeLens::Impl::FindLensPriority (Lens::Ptr& lens)
863+{
864+ gchar** lenses = g_settings_get_strv(settings_, "home-lens-ordering");
865+ gsize pos = 0, len = g_strv_length(lenses);
866+
867+ for (pos = 0; pos < len; pos++)
868+ {
869+ if (g_strcmp0(lenses[pos], lens->id().c_str()) == 0)
870+ break;
871+ }
872+
873+ g_strfreev(lenses);
874+
875+ return len - pos;
876+}
877+
878+void HomeLens::Impl::EnsureCategoryAnnotation (Lens::Ptr& lens,
879+ DeeModel* categories,
880+ DeeModel* results)
881+{
882+ if (categories && results)
883+ {
884+ if (!(DEE_IS_MODEL(results) && DEE_IS_MODEL(categories)))
885+ {
886+ LOG_ERROR(logger) << "The "
887+ << std::string(DEE_IS_MODEL(results) ? "categories" : "results")
888+ << " model is not a valid DeeModel. ("
889+ << lens->id() << ")";
890+ return;
891+ }
892+
893+ g_object_set_data(G_OBJECT(categories),
894+ "unity-homelens-results-model",
895+ results);
896+
897+ gsize lens_priority = FindLensPriority(lens);
898+ g_object_set_data(G_OBJECT(categories),
899+ "unity-homelens-priority",
900+ GSIZE_TO_POINTER(lens_priority));
901+
902+ LOG_DEBUG(logger) << "Registering results model " << results
903+ << " and lens priority " << lens_priority
904+ << " on category model " << categories << ". ("
905+ << lens->id() << ")";
906+ }
907+}
908+
909+Lens::Ptr HomeLens::Impl::FindLensForUri(std::string const& uri)
910+{
911+ /* We iterate over all lenses looking for the given uri in their
912+ * global results. This might seem like a sucky approach, but it
913+ * saves us from a ship load of book keeping */
914+
915+ for (auto lens : lenses_)
916+ {
917+ DeeModel* results = lens->global_results()->model();
918+ DeeModelIter* iter = dee_model_get_first_iter(results);
919+ DeeModelIter* end = dee_model_get_last_iter(results);
920+ const int URI_COLUMN = 0;
921+
922+ while (iter != end)
923+ {
924+ if (g_strcmp0(uri.c_str(), dee_model_get_string(results, iter, URI_COLUMN)) == 0)
925+ {
926+ return lens;
927+ }
928+ iter = dee_model_next(results, iter);
929+ }
930+ }
931+
932+ return Lens::Ptr();
933+}
934+
935+// FIXME: Coordinated sorting between the lens bar and home screen categories. Make void FilesystemLenses::Impl::DecrementAndCheckChildrenWaiting() use the gsettings key
936+// FIXME: on no results https://bugs.launchpad.net/unity/+bug/711199
937+
938+void HomeLens::Impl::OnLensAdded (Lens::Ptr& lens)
939+{
940+ lenses_.push_back (lens);
941+ owner_->lens_added.emit(lens);
942+
943+ /* When we dispatch a search we inc the search count and when we finish
944+ * one we decrease it. When we reach 0 we'll emit search_finished. */
945+ lens->global_search_finished.connect([&] (Hints const& hints) {
946+ running_searches_--;
947+
948+ if (running_searches_ <= 0)
949+ {
950+ owner_->search_finished.emit(Hints());
951+ LOG_DEBUG(logger) << "Search finished";
952+ }
953+ });
954+
955+ nux::ROProperty<glib::Object<DeeModel>>& results_prop = lens->global_results()->model;
956+ nux::ROProperty<glib::Object<DeeModel>>& categories_prop = lens->categories()->model;
957+ nux::ROProperty<glib::Object<DeeModel>>& filters_prop = lens->filters()->model;
958+
959+ /*
960+ * Important: We must ensure that the categories model is annotated
961+ * with the results model in the "unity-homelens-results-model"
962+ * data slot. We need it later to compute the transfermed offsets
963+ * of the categories in the merged category model.
964+ */
965+
966+ /* Most lenses add models lazily, but we can't know that;
967+ * so try to see if we can add them up front */
968+ if (results_prop().RawPtr())
969+ {
970+ EnsureCategoryAnnotation(lens, categories_prop(), results_prop());
971+ results_merger_.AddSource(results_prop());
972+ }
973+
974+ if (categories_prop().RawPtr())
975+ {
976+ EnsureCategoryAnnotation(lens, categories_prop(), results_prop());
977+ categories_merger_.AddSource(categories_prop());
978+ }
979+
980+ if (filters_prop().RawPtr())
981+ filters_merger_.AddSource(filters_prop());
982+
983+ /*
984+ * Pick it up when the lens set models lazily.
985+ */
986+ results_prop.changed.connect([&] (glib::Object<DeeModel> model)
987+ {
988+ EnsureCategoryAnnotation(lens, lens->categories()->model(), model);
989+ results_merger_.AddSource(model);
990+ });
991+
992+ categories_prop.changed.connect([&] (glib::Object<DeeModel> model)
993+ {
994+ EnsureCategoryAnnotation(lens, model, lens->global_results()->model());
995+ categories_merger_.AddSource(model);
996+ });
997+
998+ filters_prop.changed.connect([&] (glib::Object<DeeModel> model)
999+ {
1000+ filters_merger_.AddSource(model);
1001+ });
1002+
1003+ /*
1004+ * Register pre-existing categories up front
1005+ * FIXME: Do the same for results?
1006+ */
1007+ DeeModel* cats = categories_prop();
1008+ DeeModelIter* cats_iter;
1009+ DeeModelIter* cats_end;
1010+ for (cats_iter = dee_model_get_first_iter(cats), cats_end = dee_model_get_last_iter(cats);
1011+ cats_iter != cats_end;
1012+ cats_iter = dee_model_next(cats, cats_iter))
1013+ {
1014+ categories_merger_.OnSourceRowAdded(cats, cats_iter);
1015+ }
1016+}
1017+
1018+HomeLens::HomeLens(std::string const& name, std::string const& description, std::string const& search_hint)
1019+ : Lens("home.lens", "", "", name, PKGDATADIR"/lens-nav-home.svg",
1020+ description, search_hint, true, "",
1021+ ModelType::LOCAL)
1022+ , pimpl(new Impl(this))
1023+{
1024+ count.SetGetterFunction(sigc::mem_fun(&pimpl->lenses_, &Lenses::LensList::size));
1025+ search_in_global = false;
1026+}
1027+
1028+HomeLens::~HomeLens()
1029+{
1030+ delete pimpl;
1031+}
1032+
1033+void HomeLens::AddLenses(Lenses& lenses)
1034+{
1035+ for (auto lens : lenses.GetLenses())
1036+ {
1037+ pimpl->OnLensAdded(lens);
1038+ }
1039+
1040+ lenses.lens_added.connect(sigc::mem_fun(pimpl, &HomeLens::Impl::OnLensAdded));
1041+}
1042+
1043+Lenses::LensList HomeLens::GetLenses() const
1044+{
1045+ return pimpl->lenses_;
1046+}
1047+
1048+Lens::Ptr HomeLens::GetLens(std::string const& lens_id) const
1049+{
1050+ for (auto lens: pimpl->lenses_)
1051+ {
1052+ if (lens->id == lens_id)
1053+ {
1054+ return lens;
1055+ }
1056+ }
1057+
1058+ return Lens::Ptr();
1059+}
1060+
1061+Lens::Ptr HomeLens::GetLensAtIndex(std::size_t index) const
1062+{
1063+ try
1064+ {
1065+ return pimpl->lenses_.at(index);
1066+ }
1067+ catch (std::out_of_range& error)
1068+ {
1069+ LOG_WARN(logger) << error.what();
1070+ }
1071+
1072+ return Lens::Ptr();
1073+}
1074+
1075+void HomeLens::GlobalSearch(std::string const& search_string)
1076+{
1077+ LOG_WARN(logger) << "Global search not enabled for HomeLens class."
1078+ << " Ignoring query '" << search_string << "'";
1079+}
1080+
1081+void HomeLens::Search(std::string const& search_string)
1082+{
1083+ LOG_DEBUG(logger) << "Search '" << search_string << "'";
1084+
1085+ /* Reset running search counter */
1086+ pimpl->running_searches_ = 0;
1087+
1088+ for (auto lens: pimpl->lenses_)
1089+ {
1090+ if (lens->search_in_global())
1091+ {
1092+ LOG_DEBUG(logger) << " - Global search on '" << lens->id() << "' for '"
1093+ << search_string << "'";
1094+ lens->view_type = ViewType::HOME_VIEW;
1095+ lens->GlobalSearch(search_string);
1096+ pimpl->running_searches_++;
1097+ }
1098+ }
1099+}
1100+
1101+void HomeLens::Activate(std::string const& uri)
1102+{
1103+ LOG_DEBUG(logger) << "Activate '" << uri << "'";
1104+
1105+ Lens::Ptr lens = pimpl->FindLensForUri(uri);
1106+
1107+ /* Fall back to default handling of URIs if no lens is found.
1108+ * - Although, this shouldn't really happen */
1109+ if (lens)
1110+ {
1111+ LOG_DEBUG(logger) << "Activation request passed to '" << lens->id() << "'";
1112+ lens->Activate(uri);
1113+ }
1114+ else
1115+ {
1116+ LOG_WARN(logger) << "Unable to find a lens for activating '" << uri
1117+ << "'. Using fallback activation.";
1118+ activated.emit(uri, HandledType::NOT_HANDLED, Hints());
1119+ }
1120+}
1121+
1122+void HomeLens::Preview(std::string const& uri)
1123+{
1124+ LOG_DEBUG(logger) << "Preview '" << uri << "'";
1125+
1126+ Lens::Ptr lens = pimpl->FindLensForUri(uri);
1127+
1128+ if (lens)
1129+ lens->Preview(uri);
1130+ else
1131+ LOG_WARN(logger) << "Unable to find a lens for previewing '" << uri << "'";
1132+}
1133+
1134+}
1135+}
1136
1137=== added file 'UnityCore/HomeLens.h'
1138--- UnityCore/HomeLens.h 1970-01-01 00:00:00 +0000
1139+++ UnityCore/HomeLens.h 2012-01-26 08:37:26 +0000
1140@@ -0,0 +1,79 @@
1141+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
1142+/*
1143+ * Copyright (C) 2012 Canonical Ltd
1144+ *
1145+ * This program is free software: you can redistribute it and/or modify
1146+ * it under the terms of the GNU General Public License version 3 as
1147+ * published by the Free Software Foundation.
1148+ *
1149+ * This program is distributed in the hope that it will be useful,
1150+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1151+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1152+ * GNU General Public License for more details.
1153+ *
1154+ * You should have received a copy of the GNU General Public License
1155+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1156+ *
1157+ * Authored by: Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com>
1158+ */
1159+
1160+#ifndef UNITY_HOME_LENS_H
1161+#define UNITY_HOME_LENS_H
1162+
1163+#include <vector>
1164+#include <memory>
1165+#include <sigc++/signal.h>
1166+#include <sigc++/trackable.h>
1167+
1168+#include "Lenses.h"
1169+#include "Lens.h"
1170+
1171+namespace unity
1172+{
1173+namespace dash
1174+{
1175+
1176+/**
1177+ * A special Lens implementation that merges together a set of source Lens
1178+ * instances.
1179+ *
1180+ * NOTE: Changes in the filter models are currently not propagated back to the
1181+ * the source lenses. If we want to support filters on the dash home
1182+ * screen this needs to be addressed.
1183+ */
1184+class HomeLens : public Lens, public Lenses
1185+{
1186+public:
1187+ typedef std::shared_ptr<HomeLens> Ptr;
1188+
1189+ /**
1190+ * Should be constructed with i18n arguments:
1191+ * _("Home"), _("Home screen"), _("Search")
1192+ */
1193+ HomeLens(std::string const& name, std::string const& description, std::string const& search_hint);
1194+ virtual ~HomeLens();
1195+
1196+ void AddLenses(Lenses& lenses);
1197+
1198+ Lenses::LensList GetLenses() const;
1199+ Lens::Ptr GetLens(std::string const& lens_id) const;
1200+ Lens::Ptr GetLensAtIndex(std::size_t index) const;
1201+
1202+ void GlobalSearch(std::string const& search_string);
1203+ void Search(std::string const& search_string);
1204+ void Activate(std::string const& uri);
1205+ void Preview(std::string const& uri);
1206+
1207+private:
1208+ class Impl;
1209+ class ModelMerger;
1210+ class ResultsMerger;
1211+ class CategoryMerger;
1212+ class CategoryRegistry;
1213+ Impl* pimpl;
1214+};
1215+
1216+}
1217+}
1218+
1219+#endif
1220
1221=== modified file 'UnityCore/Lens.cpp'
1222--- UnityCore/Lens.cpp 2012-01-17 16:02:39 +0000
1223+++ UnityCore/Lens.cpp 2012-01-26 08:37:26 +0000
1224@@ -51,11 +51,12 @@
1225 string const& description,
1226 string const& search_hint,
1227 bool visible,
1228- string const& shortcut);
1229+ string const& shortcut,
1230+ ModelType model_type);
1231
1232 ~Impl();
1233
1234- void OnProxyConnected();
1235+ void OnProxyConnectionChanged();
1236 void OnProxyDisconnected();
1237
1238 void ResultsModelUpdated(unsigned long long begin_seqnum,
1239@@ -94,6 +95,7 @@
1240 string const& search_hint() const;
1241 bool visible() const;
1242 bool search_in_global() const;
1243+ bool set_search_in_global(bool val);
1244 string const& shortcut() const;
1245 Results::Ptr const& results() const;
1246 Results::Ptr const& global_results() const;
1247@@ -121,7 +123,7 @@
1248
1249 string private_connection_name_;
1250
1251- glib::DBusProxy proxy_;
1252+ glib::DBusProxy* proxy_;
1253 glib::Object<GCancellable> search_cancellable_;
1254 glib::Object<GCancellable> global_search_cancellable_;
1255
1256@@ -138,7 +140,8 @@
1257 string const& description,
1258 string const& search_hint,
1259 bool visible,
1260- string const& shortcut)
1261+ string const& shortcut,
1262+ ModelType model_type)
1263 : owner_(owner)
1264 , id_(id)
1265 , dbus_name_(dbus_name)
1266@@ -150,20 +153,46 @@
1267 , visible_(visible)
1268 , search_in_global_(false)
1269 , shortcut_(shortcut)
1270- , results_(new Results())
1271- , global_results_(new Results())
1272- , categories_(new Categories())
1273- , filters_(new Filters())
1274+ , results_(new Results(model_type))
1275+ , global_results_(new Results(model_type))
1276+ , categories_(new Categories(model_type))
1277+ , filters_(new Filters(model_type))
1278 , connected_(false)
1279- , proxy_(dbus_name, dbus_path, "com.canonical.Unity.Lens")
1280+ , proxy_(NULL)
1281 , results_variant_(NULL)
1282 , global_results_variant_(NULL)
1283 {
1284- proxy_.connected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyConnected));
1285- proxy_.disconnected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyDisconnected));
1286- proxy_.Connect("Changed", sigc::mem_fun(this, &Lens::Impl::OnChanged));
1287+ if (model_type == ModelType::REMOTE)
1288+ {
1289+ proxy_ = new glib::DBusProxy(dbus_name, dbus_path, "com.canonical.Unity.Lens");
1290+ proxy_->connected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyConnectionChanged));
1291+ proxy_->disconnected.connect(sigc::mem_fun(this, &Lens::Impl::OnProxyDisconnected));
1292+ proxy_->Connect("Changed", sigc::mem_fun(this, &Lens::Impl::OnChanged));
1293+ }
1294+
1295+ /* Technically these signals will only be fired by remote models, but we
1296+ * connect them no matter the ModelType. Dee may grow support in the future.
1297+ */
1298 results_->end_transaction.connect(sigc::mem_fun(this, &Lens::Impl::ResultsModelUpdated));
1299 global_results_->end_transaction.connect(sigc::mem_fun(this, &Lens::Impl::GlobalResultsModelUpdated));
1300+
1301+ owner_->id.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::id));
1302+ owner_->dbus_name.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::dbus_name));
1303+ owner_->dbus_path.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::dbus_path));
1304+ owner_->name.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::name));
1305+ owner_->icon_hint.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::icon_hint));
1306+ owner_->description.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::description));
1307+ owner_->search_hint.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::search_hint));
1308+ owner_->visible.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::visible));
1309+ owner_->search_in_global.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::search_in_global));
1310+ owner_->search_in_global.SetSetterFunction(sigc::mem_fun(this, &Lens::Impl::set_search_in_global));
1311+ owner_->shortcut.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::shortcut));
1312+ owner_->results.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::results));
1313+ owner_->global_results.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::global_results));
1314+ owner_->categories.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::categories));
1315+ owner_->filters.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::filters));
1316+ owner_->connected.SetGetterFunction(sigc::mem_fun(this, &Lens::Impl::connected));
1317+ owner_->view_type.changed.connect(sigc::mem_fun(this, &Lens::Impl::OnViewTypeChanged));
1318 }
1319
1320 Lens::Impl::~Impl()
1321@@ -176,13 +205,19 @@
1322 {
1323 g_cancellable_cancel (global_search_cancellable_);
1324 }
1325+
1326+ if (proxy_)
1327+ delete proxy_;
1328 }
1329
1330-void Lens::Impl::OnProxyConnected()
1331+void Lens::Impl::OnProxyConnectionChanged()
1332 {
1333- proxy_.Call("InfoRequest");
1334- ViewType current_view_type = owner_->view_type;
1335- proxy_.Call("SetViewType", g_variant_new("(u)", current_view_type));
1336+ if (proxy_->IsConnected())
1337+ {
1338+ proxy_->Call("InfoRequest");
1339+ ViewType current_view_type = owner_->view_type;
1340+ proxy_->Call("SetViewType", g_variant_new("(u)", current_view_type));
1341+ }
1342 }
1343
1344 void Lens::Impl::OnProxyDisconnected()
1345@@ -387,12 +422,13 @@
1346
1347 void Lens::Impl::OnViewTypeChanged(ViewType view_type)
1348 {
1349- proxy_.Call("SetViewType", g_variant_new("(u)", view_type));
1350+ if (proxy_ && proxy_->IsConnected())
1351+ proxy_->Call("SetViewType", g_variant_new("(u)", view_type));
1352 }
1353
1354 void Lens::Impl::GlobalSearch(std::string const& search_string)
1355 {
1356- LOG_DEBUG(logger) << "Global Searching " << id_ << " for " << search_string;
1357+ LOG_DEBUG(logger) << "Global Searching '" << id_ << "' for '" << search_string << "'";
1358
1359 GVariantBuilder b;
1360 g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}"));
1361@@ -407,7 +443,7 @@
1362 global_results_variant_ = NULL;
1363 }
1364
1365- proxy_.Call("GlobalSearch",
1366+ proxy_->Call("GlobalSearch",
1367 g_variant_new("(sa{sv})",
1368 search_string.c_str(),
1369 &b),
1370@@ -418,7 +454,13 @@
1371
1372 void Lens::Impl::Search(std::string const& search_string)
1373 {
1374- LOG_DEBUG(logger) << "Searching " << id_ << " for " << search_string;
1375+ LOG_DEBUG(logger) << "Searching '" << id_ << "' for '" << search_string << "'";
1376+
1377+ if (!proxy_->IsConnected())
1378+ {
1379+ LOG_DEBUG(logger) << "Skipping search. Proxy not connected. ('" << id_ << "')";
1380+ return;
1381+ }
1382
1383 GVariantBuilder b;
1384 g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}"));
1385@@ -432,23 +474,29 @@
1386 results_variant_ = NULL;
1387 }
1388
1389- proxy_.Call("Search",
1390- g_variant_new("(sa{sv})",
1391- search_string.c_str(),
1392- &b),
1393- sigc::mem_fun(this, &Lens::Impl::OnSearchFinished),
1394- search_cancellable_);
1395+ proxy_->Call("Search",
1396+ g_variant_new("(sa{sv})",
1397+ search_string.c_str(),
1398+ &b),
1399+ sigc::mem_fun(this, &Lens::Impl::OnSearchFinished),
1400+ search_cancellable_);
1401
1402 g_variant_builder_clear(&b);
1403 }
1404
1405 void Lens::Impl::Activate(std::string const& uri)
1406 {
1407- LOG_DEBUG(logger) << "Activating " << uri << " on " << id_;
1408-
1409- proxy_.Call("Activate",
1410- g_variant_new("(su)", uri.c_str(), 0),
1411- sigc::mem_fun(this, &Lens::Impl::ActivationReply));
1412+ LOG_DEBUG(logger) << "Activating '" << uri << "' on '" << id_ << "'";
1413+
1414+ if (!proxy_->IsConnected())
1415+ {
1416+ LOG_DEBUG(logger) << "Skipping activation. Proxy not connected. ('" << id_ << "')";
1417+ return;
1418+ }
1419+
1420+ proxy_->Call("Activate",
1421+ g_variant_new("(su)", uri.c_str(), 0),
1422+ sigc::mem_fun(this, &Lens::Impl::ActivationReply));
1423 }
1424
1425 void Lens::Impl::ActivationReply(GVariant* parameters)
1426@@ -468,11 +516,17 @@
1427
1428 void Lens::Impl::Preview(std::string const& uri)
1429 {
1430- LOG_DEBUG(logger) << "Previewing " << uri << " on " << id_;
1431-
1432- proxy_.Call("Preview",
1433- g_variant_new("(s)", uri.c_str()),
1434- sigc::mem_fun(this, &Lens::Impl::PreviewReply));
1435+ LOG_DEBUG(logger) << "Previewing '" << uri << "' on '" << id_ << "'";
1436+
1437+ if (!proxy_->IsConnected())
1438+ {
1439+ LOG_DEBUG(logger) << "Skipping preview. Proxy not connected. ('" << id_ << "')";
1440+ return;
1441+ }
1442+
1443+ proxy_->Call("Preview",
1444+ g_variant_new("(s)", uri.c_str()),
1445+ sigc::mem_fun(this, &Lens::Impl::PreviewReply));
1446 }
1447
1448 void Lens::Impl::PreviewReply(GVariant* parameters)
1449@@ -535,6 +589,17 @@
1450 return search_in_global_;
1451 }
1452
1453+bool Lens::Impl::set_search_in_global(bool val)
1454+{
1455+ if (search_in_global_ != val)
1456+ {
1457+ search_in_global_ = val;
1458+ owner_->search_in_global.EmitChanged(val);
1459+ }
1460+
1461+ return search_in_global_;
1462+}
1463+
1464 string const& Lens::Impl::shortcut() const
1465 {
1466 return shortcut_;
1467@@ -584,25 +649,33 @@
1468 description_,
1469 search_hint_,
1470 visible_,
1471- shortcut_))
1472-{
1473- id.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::id));
1474- dbus_name.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::dbus_name));
1475- dbus_path.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::dbus_path));
1476- name.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::name));
1477- icon_hint.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::icon_hint));
1478- description.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::description));
1479- search_hint.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::search_hint));
1480- visible.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::visible));
1481- search_in_global.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::search_in_global));
1482- shortcut.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::shortcut));
1483- results.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::results));
1484- global_results.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::global_results));
1485- categories.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::categories));
1486- filters.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::filters));
1487- connected.SetGetterFunction(sigc::mem_fun(pimpl, &Lens::Impl::connected));
1488- view_type.changed.connect(sigc::mem_fun(pimpl, &Lens::Impl::OnViewTypeChanged));
1489-}
1490+ shortcut_,
1491+ ModelType::REMOTE))
1492+{}
1493+
1494+Lens::Lens(string const& id_,
1495+ string const& dbus_name_,
1496+ string const& dbus_path_,
1497+ string const& name_,
1498+ string const& icon_hint_,
1499+ string const& description_,
1500+ string const& search_hint_,
1501+ bool visible_,
1502+ string const& shortcut_,
1503+ ModelType model_type)
1504+
1505+ : pimpl(new Impl(this,
1506+ id_,
1507+ dbus_name_,
1508+ dbus_path_,
1509+ name_,
1510+ icon_hint_,
1511+ description_,
1512+ search_hint_,
1513+ visible_,
1514+ shortcut_,
1515+ model_type))
1516+{}
1517
1518 Lens::~Lens()
1519 {
1520
1521=== modified file 'UnityCore/Lens.h'
1522--- UnityCore/Lens.h 2012-01-17 13:31:52 +0000
1523+++ UnityCore/Lens.h 2012-01-26 08:37:26 +0000
1524@@ -67,12 +67,23 @@
1525 bool visible = true,
1526 std::string const& shortcut = "");
1527
1528- ~Lens();
1529-
1530- void GlobalSearch(std::string const& search_string);
1531- void Search(std::string const& search_string);
1532- void Activate(std::string const& uri);
1533- void Preview(std::string const& uri);
1534+ Lens(std::string const& id,
1535+ std::string const& dbus_name,
1536+ std::string const& dbus_path,
1537+ std::string const& name,
1538+ std::string const& icon,
1539+ std::string const& description,
1540+ std::string const& search_hint,
1541+ bool visible,
1542+ std::string const& shortcut,
1543+ ModelType model_type);
1544+
1545+ virtual ~Lens();
1546+
1547+ virtual void GlobalSearch(std::string const& search_string);
1548+ virtual void Search(std::string const& search_string);
1549+ virtual void Activate(std::string const& uri);
1550+ virtual void Preview(std::string const& uri);
1551
1552 nux::RWProperty<std::string> id;
1553 nux::RWProperty<std::string> dbus_name;
1554
1555=== modified file 'UnityCore/Model-inl.h'
1556--- UnityCore/Model-inl.h 2012-01-10 14:14:17 +0000
1557+++ UnityCore/Model-inl.h 2012-01-26 08:37:26 +0000
1558@@ -34,10 +34,28 @@
1559
1560 template<class RowAdaptor>
1561 Model<RowAdaptor>::Model()
1562+ : model_type_(ModelType::REMOTE)
1563+{
1564+ Init();
1565+}
1566+
1567+template<class RowAdaptor>
1568+Model<RowAdaptor>::Model (ModelType model_type)
1569+ : model_type_(model_type)
1570+{
1571+ Init();
1572+
1573+ if (model_type == ModelType::LOCAL)
1574+ swarm_name = ":local";
1575+}
1576+
1577+template<class RowAdaptor>
1578+void Model<RowAdaptor>::Init ()
1579 {
1580 swarm_name.changed.connect(sigc::mem_fun(this, &Model<RowAdaptor>::OnSwarmNameChanged));
1581 count.SetGetterFunction(sigc::mem_fun(this, &Model<RowAdaptor>::get_count));
1582 seqnum.SetGetterFunction(sigc::mem_fun(this, &Model<RowAdaptor>::get_seqnum));
1583+ model.SetGetterFunction(sigc::mem_fun(this, &Model<RowAdaptor>::get_model));
1584 }
1585
1586 template<class RowAdaptor>
1587@@ -52,7 +70,29 @@
1588 if (model_)
1589 dee_model_clear(model_);
1590
1591- model_ = dee_shared_model_new(swarm_name.c_str());
1592+ switch(model_type_)
1593+ {
1594+ case ModelType::LOCAL:
1595+ model_ = dee_sequence_model_new();
1596+ break;
1597+ case ModelType::REMOTE:
1598+ model_ = dee_shared_model_new(swarm_name.c_str());
1599+ sig_manager_.Add(new TransactionSignalType(model_,
1600+ "begin-transaction",
1601+ sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionBegin)));
1602+
1603+ sig_manager_.Add(new TransactionSignalType(model_,
1604+ "end-transaction",
1605+ sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionEnd)));
1606+ break;
1607+ default:
1608+ LOG_ERROR(_model_inl_logger) << "Unexpected ModelType " << model_type_;
1609+ break;
1610+ }
1611+
1612+ model.EmitChanged(model_);
1613+
1614+
1615 renderer_tag_ = dee_model_register_tag(model_, NULL);
1616
1617 sig_manager_.Add(new RowSignalType(model_,
1618@@ -66,14 +106,6 @@
1619 sig_manager_.Add(new RowSignalType(model_,
1620 "row-removed",
1621 sigc::mem_fun(this, &Model<RowAdaptor>::OnRowRemoved)));
1622-
1623- sig_manager_.Add(new TransactionSignalType(model_,
1624- "begin-transaction",
1625- sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionBegin)));
1626-
1627- sig_manager_.Add(new TransactionSignalType(model_,
1628- "end-transaction",
1629- sigc::mem_fun(this, &Model<RowAdaptor>::OnTransactionEnd)));
1630 }
1631
1632 template<class RowAdaptor>
1633@@ -148,6 +180,12 @@
1634 return 0;
1635 }
1636
1637+template<class RowAdaptor>
1638+glib::Object<DeeModel> Model<RowAdaptor>::get_model()
1639+{
1640+ return model_;
1641+}
1642+
1643 }
1644 }
1645
1646
1647=== modified file 'UnityCore/Model.h'
1648--- UnityCore/Model.h 2012-01-10 14:14:17 +0000
1649+++ UnityCore/Model.h 2012-01-26 08:37:26 +0000
1650@@ -35,6 +35,12 @@
1651 namespace dash
1652 {
1653
1654+enum ModelType
1655+{
1656+ REMOTE,
1657+ LOCAL
1658+};
1659+
1660 /* This template class encapsulates the basics of talking to a DeeSharedModel,
1661 * however it is a template as you can choose your own RowAdaptor (see
1662 * ResultsRowAdaptor.h for an example) which then presents the data in the rows
1663@@ -47,6 +53,7 @@
1664 typedef std::shared_ptr<Model> Ptr;
1665
1666 Model();
1667+ Model (ModelType model_type);
1668 virtual ~Model();
1669
1670 const RowAdaptor RowAtIndex(std::size_t index);
1671@@ -54,6 +61,7 @@
1672 nux::Property<std::string> swarm_name;
1673 nux::ROProperty<std::size_t> count;
1674 nux::ROProperty<unsigned long long> seqnum;
1675+ nux::ROProperty<glib::Object<DeeModel>> model;
1676
1677 sigc::signal<void, RowAdaptor&> row_added;
1678 sigc::signal<void, RowAdaptor&> row_changed;
1679@@ -63,6 +71,7 @@
1680 sigc::signal<void, unsigned long long, unsigned long long> end_transaction;
1681
1682 private:
1683+ void Init();
1684 void OnRowAdded(DeeModel* model, DeeModelIter* iter);
1685 void OnRowChanged(DeeModel* model, DeeModelIter* iter);
1686 void OnRowRemoved(DeeModel* model, DeeModelIter* iter);
1687@@ -71,11 +80,13 @@
1688 void OnSwarmNameChanged(std::string const& swarm_name);
1689 std::size_t get_count();
1690 unsigned long long get_seqnum();
1691+ glib::Object<DeeModel> get_model();
1692
1693 private:
1694 glib::Object<DeeModel> model_;
1695 glib::SignalManager sig_manager_;
1696 DeeModelTag* renderer_tag_;
1697+ ModelType model_type_;
1698 };
1699
1700 }
1701
1702=== modified file 'UnityCore/Results.cpp'
1703--- UnityCore/Results.cpp 2011-07-25 10:54:25 +0000
1704+++ UnityCore/Results.cpp 2012-01-26 08:37:26 +0000
1705@@ -31,6 +31,14 @@
1706 row_removed.connect(sigc::mem_fun(this, &Results::OnRowRemoved));
1707 }
1708
1709+Results::Results(ModelType model_type)
1710+ : Model<Result>::Model(model_type)
1711+{
1712+ row_added.connect(sigc::mem_fun(this, &Results::OnRowAdded));
1713+ row_changed.connect(sigc::mem_fun(this, &Results::OnRowChanged));
1714+ row_removed.connect(sigc::mem_fun(this, &Results::OnRowRemoved));
1715+}
1716+
1717 void Results::OnRowAdded(Result& result)
1718 {
1719 result_added.emit(result);
1720
1721=== modified file 'UnityCore/Results.h'
1722--- UnityCore/Results.h 2011-07-28 13:35:05 +0000
1723+++ UnityCore/Results.h 2012-01-26 08:37:26 +0000
1724@@ -36,6 +36,7 @@
1725 typedef std::shared_ptr<Results> Ptr;
1726
1727 Results();
1728+ Results(ModelType model_type);
1729
1730 sigc::signal<void, Result const&> result_added;
1731 sigc::signal<void, Result const&> result_changed;
1732
1733=== modified file 'com.canonical.Unity.gschema.xml'
1734--- com.canonical.Unity.gschema.xml 2011-12-12 18:18:38 +0000
1735+++ com.canonical.Unity.gschema.xml 2012-01-26 08:37:26 +0000
1736@@ -21,7 +21,7 @@
1737 <description>Whether the home screen should be expanded.</description>
1738 </key>
1739 </schema>
1740- <schema path="/desktop/unity/launcher/" id="com.canonical.Unity.Launcher" gettext-domain="unity">
1741+ <schema path="/desktop/unity/launcher/" id="com.canonical.Unity.Launcher" gettext-domain="unity">
1742 <key type="as" name="favorites">
1743 <default>[ 'ubiquity-gtkui.desktop', 'nautilus-home.desktop', 'firefox.desktop', 'libreoffice-writer.desktop', 'libreoffice-calc.desktop', 'libreoffice-impress.desktop', 'ubuntu-software-center.desktop', 'ubuntuone-installer.desktop', 'gnome-control-center.desktop' ]</default>
1744 <summary>List of desktop file ids for favorites on the launcher.</summary>
1745@@ -33,7 +33,7 @@
1746 <description>This is a detection key for the favorite migration script to know whether the needed migration is done or not.</description>
1747 </key>
1748 </schema>
1749- <schema path="/desktop/unity/panel/" id="com.canonical.Unity.Panel" gettext-domain="unity">
1750+ <schema path="/desktop/unity/panel/" id="com.canonical.Unity.Panel" gettext-domain="unity">
1751 <key type="as" name="systray-whitelist">
1752 <default>[ 'JavaEmbeddedFrame', 'Wine', 'scp-dbus-service', 'Update-notifier' ]</default>
1753 <summary>List of client names, resource classes or wm classes to allow in the Panel's systray implementation.</summary>
1754@@ -46,5 +46,12 @@
1755 <summary>List of device uuid for favorites on the launcher.</summary>
1756 <description>These devices are shown in the Launcher by default.</description>
1757 </key>
1758- </schema>
1759+ </schema>
1760+ <schema path="/desktop/unity/dash/" id="com.canonical.Unity.Dash" gettext-domain="unity">
1761+ <key type="as" name="home-lens-ordering">
1762+ <default>[ 'applications.lens', 'files.lens', 'music.lens' ]</default>
1763+ <summary>List of lens ids specifying how lenses should be ordered in the Dash home screen.</summary>
1764+ <description>The categories listed on the Dash home screen will be ordered according to this list. Lenses not appearing in this list will not have any particular ordering and will always sort after lenses specified in this list.</description>
1765+ </key>
1766+ </schema>
1767 </schemalist>
1768
1769=== modified file 'plugins/unityshell/src/DashView.cpp'
1770--- plugins/unityshell/src/DashView.cpp 2012-01-17 13:31:52 +0000
1771+++ plugins/unityshell/src/DashView.cpp 2012-01-26 08:37:26 +0000
1772@@ -47,6 +47,7 @@
1773
1774 DashView::DashView()
1775 : nux::View(NUX_TRACKER_LOCATION)
1776+ , home_lens_(new HomeLens(_("Home"), _("Home screen"), _("Search")))
1777 , active_lens_view_(0)
1778 , last_activated_uri_("")
1779 , searching_timeout_id_(0)
1780@@ -67,6 +68,8 @@
1781 mouse_down.connect(sigc::mem_fun(this, &DashView::OnMouseButtonDown));
1782
1783 Relayout();
1784+
1785+ home_lens_->AddLenses(lenses_);
1786 lens_bar_->Activate("home.lens");
1787 }
1788
1789@@ -81,6 +84,23 @@
1790 ubus_manager_.SendMessage(UBUS_BACKGROUND_REQUEST_COLOUR_EMIT);
1791 visible_ = true;
1792 search_bar_->text_entry()->SelectAll();
1793+
1794+ /* Give the lenses a chance to prep data before we map them */
1795+ lens_bar_->Activate(active_lens_view_->lens()->id());
1796+ if (active_lens_view_->lens()->id() == "home.lens")
1797+ {
1798+ for (auto lens : lenses_.GetLenses())
1799+ {
1800+ lens->view_type = ViewType::HOME_VIEW;
1801+ LOG_DEBUG(logger) << "Setting ViewType " << ViewType::HOME_VIEW
1802+ << " on '" << lens->id() << "'";
1803+ }
1804+
1805+ home_lens_->view_type = ViewType::LENS_VIEW;
1806+ LOG_DEBUG(logger) << "Setting ViewType " << ViewType::LENS_VIEW
1807+ << " on '" << home_lens_->id() << "'";
1808+ }
1809+
1810 renderer_.AboutToShow();
1811 }
1812
1813@@ -88,6 +108,17 @@
1814 {
1815 visible_ = false;
1816 renderer_.AboutToHide();
1817+
1818+ for (auto lens : lenses_.GetLenses())
1819+ {
1820+ lens->view_type = ViewType::HIDDEN;
1821+ LOG_DEBUG(logger) << "Setting ViewType " << ViewType::HIDDEN
1822+ << " on '" << lens->id() << "'";
1823+ }
1824+
1825+ home_lens_->view_type = ViewType::HIDDEN;
1826+ LOG_DEBUG(logger) << "Setting ViewType " << ViewType::HIDDEN
1827+ << " on '" << home_lens_->id() << "'";
1828 }
1829
1830 void DashView::SetupViews()
1831@@ -111,9 +142,9 @@
1832 lenses_layout_ = new nux::VLayout();
1833 content_layout_->AddView(lenses_layout_, 1, nux::MINOR_POSITION_LEFT);
1834
1835- home_view_ = new HomeView();
1836+ home_view_ = new LensView(home_lens_);
1837 active_lens_view_ = home_view_;
1838- lens_views_["home.lens"] = home_view_;
1839+ lens_views_[home_lens_->id] = home_view_;
1840 lenses_layout_->AddView(home_view_);
1841
1842 lens_bar_ = new LensBar();
1843@@ -239,7 +270,6 @@
1844
1845 std::string id = AnalyseLensURI(uri.Str());
1846
1847- home_view_->search_string = "";
1848 lens_bar_->Activate(id);
1849
1850 if ((id == "home.lens" && handled_type != GOTO_DASH_URI ) || !visible_)
1851@@ -336,7 +366,6 @@
1852 {
1853 std::string id = lens->id;
1854 lens_bar_->AddLens(lens);
1855- home_view_->AddLens(lens);
1856
1857 LensView* view = new LensView(lens);
1858 view->SetVisible(false);
1859@@ -362,15 +391,17 @@
1860 for (auto it: lens_views_)
1861 {
1862 bool id_matches = it.first == id;
1863+ ViewType view_type = id_matches ? LENS_VIEW : (view == home_view_ ? HOME_VIEW : HIDDEN);
1864 it.second->SetVisible(id_matches);
1865- it.second->view_type = id_matches ? LENS_VIEW : (view == home_view_ ? HOME_VIEW : HIDDEN);
1866+ it.second->view_type = view_type;
1867+
1868+ LOG_DEBUG(logger) << "Setting ViewType " << view_type
1869+ << " on '" << it.first << "'";
1870 }
1871
1872 search_bar_->search_string = view->search_string;
1873- if (view != home_view_)
1874- search_bar_->search_hint = view->lens()->search_hint;
1875- else
1876- search_bar_->search_hint = _("Search");
1877+ search_bar_->search_hint = view->lens()->search_hint;
1878+
1879 bool expanded = view->filters_expanded;
1880 search_bar_->showing_filters = expanded;
1881
1882@@ -553,6 +584,7 @@
1883 ubus_manager_.SendMessage(UBUS_PLACE_VIEW_CLOSE_REQUEST);
1884 else
1885 search_bar_->search_string = "";
1886+
1887 return true;
1888 }
1889 return false;
1890
1891=== modified file 'plugins/unityshell/src/DashView.h'
1892--- plugins/unityshell/src/DashView.h 2012-01-16 15:31:59 +0000
1893+++ plugins/unityshell/src/DashView.h 2012-01-26 08:37:26 +0000
1894@@ -27,10 +27,10 @@
1895 #include <Nux/View.h>
1896 #include <Nux/VLayout.h>
1897 #include <UnityCore/FilesystemLenses.h>
1898+#include <UnityCore/HomeLens.h>
1899
1900 #include "BackgroundEffectHelper.h"
1901 #include "DashSearchBar.h"
1902-#include "HomeView.h"
1903 #include "Introspectable.h"
1904 #include "LensBar.h"
1905 #include "LensView.h"
1906@@ -95,6 +95,7 @@
1907 std::string AnalyseLensURI(std::string const& uri);
1908 void UpdateLensFilter(std::string lens, std::string filter, std::string value);
1909 void UpdateLensFilterValue(Filter::Ptr filter, std::string value);
1910+ void EnsureLensesInitialized();
1911
1912 bool AcceptKeyNavFocus();
1913 bool InspectKeyEvent(unsigned int eventType, unsigned int key_sym, const char* character);
1914@@ -108,6 +109,7 @@
1915 private:
1916 UBusManager ubus_manager_;
1917 FilesystemLenses lenses_;
1918+ HomeLens::Ptr home_lens_;
1919 LensViews lens_views_;
1920
1921
1922@@ -118,7 +120,7 @@
1923 nux::VLayout* lenses_layout_;
1924 LensBar* lens_bar_;
1925
1926- HomeView* home_view_;
1927+ LensView* home_view_;
1928 LensView* active_lens_view_;
1929
1930 // Drawing related
1931
1932=== removed file 'plugins/unityshell/src/HomeView.cpp'
1933--- plugins/unityshell/src/HomeView.cpp 2011-12-21 16:49:59 +0000
1934+++ plugins/unityshell/src/HomeView.cpp 1970-01-01 00:00:00 +0000
1935@@ -1,252 +0,0 @@
1936-/*
1937- * Copyright (C) 2010 Canonical Ltd
1938- *
1939- * This program is free software: you can redistribute it and/or modify
1940- * it under the terms of the GNU General Public License version 3 as
1941- * published by the Free Software Foundation.
1942- *
1943- * This program is distributed in the hope that it will be useful,
1944- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1945- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1946- * GNU General Public License for more details.
1947- *
1948- * You should have received a copy of the GNU General Public License
1949- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1950- *
1951- * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
1952- */
1953-
1954-#include "HomeView.h"
1955-
1956-#include <boost/lexical_cast.hpp>
1957-
1958-#include <NuxCore/Logger.h>
1959-
1960-#include "DashStyle.h"
1961-#include "ResultRendererTile.h"
1962-#include "UBusMessages.h"
1963-
1964-namespace unity
1965-{
1966-namespace dash
1967-{
1968-
1969-namespace
1970-{
1971-nux::logging::Logger logger("unity.dash.homeview");
1972-}
1973-
1974-// This is so we can override the scroll bar for the view.
1975-class HomeScrollView: public nux::ScrollView
1976-{
1977-public:
1978- HomeScrollView(nux::VScrollBar* scroll_bar, NUX_FILE_LINE_DECL)
1979- : nux::ScrollView(NUX_FILE_LINE_PARAM)
1980- {
1981- SetVScrollBar(scroll_bar);
1982- }
1983-};
1984-
1985-
1986-
1987-NUX_IMPLEMENT_OBJECT_TYPE(HomeView);
1988-
1989-HomeView::HomeView()
1990- : fix_renderering_id_(0)
1991-{
1992- SetupViews();
1993-
1994- search_string.changed.connect([&](std::string const& search)
1995- {
1996- for (auto lens : lenses_)
1997- lens->GlobalSearch(search);
1998-
1999- for (auto group: categories_)
2000- {
2001- group->SetVisible(search != "" && counts_[group]);
2002- }
2003- home_view_->SetVisible(search == "");
2004- scroll_view_->SetVisible(search != "");
2005-
2006- QueueDraw();
2007- });
2008-}
2009-
2010-HomeView::~HomeView()
2011-{
2012- if (fix_renderering_id_)
2013- g_source_remove(fix_renderering_id_);
2014-}
2015-
2016-void HomeView::SetupViews()
2017-{
2018- layout_ = new nux::HLayout(NUX_TRACKER_LOCATION);
2019- layout_->SetHorizontalExternalMargin(7);
2020-
2021- scroll_view_ = new HomeScrollView(new PlacesVScrollBar(NUX_TRACKER_LOCATION),
2022- NUX_TRACKER_LOCATION);
2023- scroll_view_->EnableVerticalScrollBar(true);
2024- scroll_view_->EnableHorizontalScrollBar(false);
2025- scroll_view_->SetVisible(false);
2026- layout_->AddView(scroll_view_);
2027-
2028- scroll_layout_ = new nux::VLayout();
2029- scroll_view_->SetLayout(scroll_layout_);
2030-
2031- home_view_ = new PlacesHomeView();
2032- layout_->AddView(home_view_);
2033-
2034- SetLayout(layout_);
2035-}
2036-
2037-void HomeView::AddLens(Lens::Ptr lens)
2038-{
2039- lenses_.push_back(lens);
2040-
2041- std::string name = lens->name;
2042- std::string icon_hint = lens->icon_hint;
2043-
2044- LOG_DEBUG(logger) << "Lens added " << name;
2045-
2046- PlacesGroup* group = new PlacesGroup();
2047- group->SetName(name.c_str());
2048- group->SetIcon(icon_hint.c_str());
2049- group->SetExpanded(false);
2050- group->SetVisible(false);
2051- group->expanded.connect(sigc::mem_fun(this, &HomeView::OnGroupExpanded));
2052- categories_.push_back(group);
2053- counts_[group] = 0;
2054-
2055- ResultViewGrid* grid = new ResultViewGrid(NUX_TRACKER_LOCATION);
2056- grid->expanded = false;
2057- grid->SetModelRenderer(new ResultRendererTile(NUX_TRACKER_LOCATION));
2058- grid->UriActivated.connect([&, lens] (std::string const& uri) { uri_activated.emit(uri); lens->Activate(uri); });
2059- group->SetChildView(grid);
2060-
2061- Results::Ptr results = lens->global_results;
2062- results->result_added.connect([&, group, grid] (Result const& result)
2063- {
2064- grid->AddResult(const_cast<Result&>(result));
2065- counts_[group]++;
2066- UpdateCounts(group);
2067- });
2068-
2069- results->result_removed.connect([&, group, grid] (Result const& result)
2070- {
2071- grid->RemoveResult(const_cast<Result&>(result));
2072- counts_[group]--;
2073- UpdateCounts(group);
2074- });
2075-
2076-
2077- scroll_layout_->AddView(group, 0);
2078-}
2079-
2080-void HomeView::UpdateCounts(PlacesGroup* group)
2081-{
2082- group->SetCounts(dash::Style::Instance().GetDefaultNColumns(), counts_[group]);
2083- group->SetVisible(counts_[group]);
2084-
2085- QueueFixRenderering();
2086-}
2087-
2088-void HomeView::OnGroupExpanded(PlacesGroup* group)
2089-{
2090- ResultViewGrid* grid = static_cast<ResultViewGrid*>(group->GetChildView());
2091- grid->expanded = group->GetExpanded();
2092- ubus_manager_.SendMessage(UBUS_PLACE_VIEW_QUEUE_DRAW);
2093-}
2094-
2095-void HomeView::OnColumnsChanged()
2096-{
2097- unsigned int columns = dash::Style::Instance().GetDefaultNColumns();
2098-
2099- for (auto group: categories_)
2100- {
2101- group->SetCounts(columns, counts_[group]);
2102- }
2103-}
2104-
2105-void HomeView::QueueFixRenderering()
2106-{
2107- if (fix_renderering_id_)
2108- return;
2109-
2110- fix_renderering_id_ = g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc)FixRenderering, this, NULL);
2111-}
2112-
2113-gboolean HomeView::FixRenderering(HomeView* self)
2114-{
2115- std::list<Area*> children = self->scroll_layout_->GetChildren();
2116- std::list<Area*>::reverse_iterator rit;
2117- bool found_one = false;
2118-
2119- for (rit = children.rbegin(); rit != children.rend(); ++rit)
2120- {
2121- PlacesGroup* group = static_cast<PlacesGroup*>(*rit);
2122-
2123- if (group->IsVisible())
2124- group->SetDrawSeparator(found_one);
2125-
2126- found_one = group->IsVisible();
2127- }
2128-
2129- self->fix_renderering_id_ = 0;
2130- return FALSE;
2131-}
2132-
2133-void HomeView::Draw(nux::GraphicsEngine& gfx_context, bool force_draw)
2134-{
2135- nux::Geometry geo = GetGeometry();
2136-
2137- gfx_context.PushClippingRectangle(geo);
2138- nux::GetPainter().PaintBackground(gfx_context, geo);
2139- gfx_context.PopClippingRectangle();
2140-}
2141-
2142-void HomeView::DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw)
2143-{
2144- gfx_context.PushClippingRectangle(GetGeometry());
2145-
2146- layout_->ProcessDraw(gfx_context, force_draw);
2147-
2148- gfx_context.PopClippingRectangle();
2149-}
2150-
2151-void HomeView::ActivateFirst()
2152-{
2153- for (auto lens: lenses_)
2154- {
2155- Results::Ptr results = lens->global_results;
2156- if (results->count())
2157- {
2158- Result result = results->RowAtIndex(0);
2159- if (result.uri != "")
2160- {
2161- uri_activated(result.uri);
2162- lens->Activate(result.uri);
2163- return;
2164- }
2165- }
2166- }
2167-}
2168-
2169-
2170-// Keyboard navigation
2171-bool HomeView::AcceptKeyNavFocus()
2172-{
2173- return false;
2174-}
2175-
2176-// Introspectable
2177-std::string HomeView::GetName() const
2178-{
2179- return "HomeView";
2180-}
2181-
2182-void HomeView::AddProperties(GVariantBuilder* builder)
2183-{}
2184-
2185-
2186-}
2187-}
2188
2189=== removed file 'plugins/unityshell/src/HomeView.h'
2190--- plugins/unityshell/src/HomeView.h 2011-12-16 01:55:42 +0000
2191+++ plugins/unityshell/src/HomeView.h 1970-01-01 00:00:00 +0000
2192@@ -1,93 +0,0 @@
2193-/*
2194- * Copyright (C) 2010 Canonical Ltd
2195- *
2196- * This program is free software: you can redistribute it and/or modify
2197- * it under the terms of the GNU General Public License version 3 as
2198- * published by the Free Software Foundation.
2199- *
2200- * This program is distributed in the hope that it will be useful,
2201- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2202- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2203- * GNU General Public License for more details.
2204- *
2205- * You should have received a copy of the GNU General Public License
2206- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2207- *
2208- * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
2209- */
2210-
2211-#ifndef UNITY_HOME_VIEW_H_
2212-#define UNITY_HOME_VIEW_H_
2213-
2214-#include <string>
2215-
2216-#include <NuxGraphics/GraphicsEngine.h>
2217-#include <Nux/Nux.h>
2218-#include <Nux/HLayout.h>
2219-#include <Nux/View.h>
2220-#include <Nux/VLayout.h>
2221-#include <UnityCore/Lens.h>
2222-
2223-#include "LensView.h"
2224-#include "PlacesGroup.h"
2225-#include "PlacesHomeView.h"
2226-#include "ResultViewGrid.h"
2227-#include "UBusWrapper.h"
2228-
2229-namespace unity
2230-{
2231-namespace dash
2232-{
2233-
2234-class HomeView : public LensView
2235-{
2236- NUX_DECLARE_OBJECT_TYPE(HomeView, LensView);
2237- typedef std::vector<PlacesGroup*> CategoryGroups;
2238- typedef std::map<PlacesGroup*, unsigned int> ResultCounts;
2239- typedef std::vector<Lens::Ptr> Lenses;
2240-
2241-public:
2242- HomeView();
2243- ~HomeView();
2244-
2245- void AddLens(Lens::Ptr lens);
2246- void ActivateFirst();
2247-
2248-private:
2249- void SetupViews();
2250-
2251- void OnResultAdded(Result const& result);
2252- void OnResultRemoved(Result const& result);
2253- void UpdateCounts(PlacesGroup* group);
2254- void OnGroupExpanded(PlacesGroup* group);
2255- void OnColumnsChanged();
2256- void QueueFixRenderering();
2257-
2258- static gboolean FixRenderering(HomeView* self);
2259-
2260- void Draw(nux::GraphicsEngine& gfx_context, bool force_draw);
2261- void DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw);
2262-
2263- bool AcceptKeyNavFocus();
2264- std::string GetName() const;
2265- void AddProperties(GVariantBuilder* builder);
2266-
2267-private:
2268- UBusManager ubus_manager_;
2269- CategoryGroups categories_;
2270- ResultCounts counts_;
2271- Lenses lenses_;
2272-
2273- nux::HLayout* layout_;
2274- nux::ScrollView* scroll_view_;
2275- nux::VLayout* scroll_layout_;
2276-
2277- PlacesHomeView* home_view_;
2278-
2279- guint fix_renderering_id_;
2280-};
2281-
2282-
2283-}
2284-}
2285-#endif
2286
2287=== modified file 'plugins/unityshell/src/LensBar.cpp'
2288--- plugins/unityshell/src/LensBar.cpp 2011-12-08 01:23:11 +0000
2289+++ plugins/unityshell/src/LensBar.cpp 2012-01-26 08:37:26 +0000
2290@@ -148,10 +148,20 @@
2291
2292 void LensBar::SetActive(LensBarIcon* activated)
2293 {
2294+ bool state_changed = false;
2295+
2296 for (auto icon: icons_)
2297- icon->active = icon == activated;
2298-
2299- lens_activated.emit(activated->id);
2300+ {
2301+ bool state = icon == activated;
2302+
2303+ if (icon->active != state)
2304+ state_changed = true;
2305+
2306+ icon->active = state;
2307+ }
2308+
2309+ if (state_changed)
2310+ lens_activated.emit(activated->id);
2311 }
2312
2313 void LensBar::ActivateNext()
2314
2315=== modified file 'plugins/unityshell/src/LensView.cpp'
2316--- plugins/unityshell/src/LensView.cpp 2011-12-14 16:18:41 +0000
2317+++ plugins/unityshell/src/LensView.cpp 2012-01-26 08:37:26 +0000
2318@@ -219,7 +219,12 @@
2319 group->SetExpanded(false);
2320 group->SetVisible(false);
2321 group->expanded.connect(sigc::mem_fun(this, &LensView::OnGroupExpanded));
2322- categories_.push_back(group);
2323+
2324+
2325+ /* Add the group at the correct offset into the categories vector */
2326+ categories_.insert(categories_.begin() + index, group);
2327+
2328+ /* Reset result count */
2329 counts_[group] = 0;
2330
2331 ResultViewGrid* grid = new ResultViewGrid(NUX_TRACKER_LOCATION);
2332@@ -232,7 +237,11 @@
2333 grid->UriActivated.connect([&] (std::string const& uri) { uri_activated.emit(uri); lens_->Activate(uri); });
2334 group->SetChildView(grid);
2335
2336- scroll_layout_->AddView(group, 0);
2337+ /* We need the full range of method args so we can specify the offset
2338+ * of the group into the layout */
2339+ scroll_layout_->AddView(group, 0, nux::MinorDimensionPosition::eAbove,
2340+ nux::MinorDimensionSize::eFull, 100.0f,
2341+ (nux::LayoutPosition)index);
2342 }
2343
2344 void LensView::OnResultAdded(Result const& result)
2345
2346=== removed file 'plugins/unityshell/src/PlacesHomeView.cpp'
2347--- plugins/unityshell/src/PlacesHomeView.cpp 2011-12-14 20:04:23 +0000
2348+++ plugins/unityshell/src/PlacesHomeView.cpp 1970-01-01 00:00:00 +0000
2349@@ -1,378 +0,0 @@
2350-// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
2351-/*
2352- * Copyright (C) 2010 Canonical Ltd
2353- *
2354- * This program is free software: you can redistribute it and/or modify
2355- * it under the terms of the GNU General Public License version 3 as
2356- * published by the Free Software Foundation.
2357- *
2358- * This program is distributed in the hope that it will be useful,
2359- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2360- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2361- * GNU General Public License for more details.
2362- *
2363- * You should have received a copy of the GNU General Public License
2364- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2365- *
2366- * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
2367- */
2368-
2369-#include "config.h"
2370-
2371-#include <Nux/Nux.h>
2372-#include <Nux/BaseWindow.h>
2373-#include <Nux/HLayout.h>
2374-#include <Nux/Layout.h>
2375-#include <Nux/WindowCompositor.h>
2376-
2377-#include <NuxImage/CairoGraphics.h>
2378-#include <NuxImage/ImageSurface.h>
2379-
2380-#include <NuxGraphics/GLThread.h>
2381-#include <NuxGraphics/RenderingPipe.h>
2382-
2383-#include <glib.h>
2384-#include <glib/gi18n-lib.h>
2385-#include <gio/gdesktopappinfo.h>
2386-#include "ubus-server.h"
2387-#include "UBusMessages.h"
2388-
2389-#include "PlacesHomeView.h"
2390-#include "PlacesSimpleTile.h"
2391-
2392-#include "DashStyle.h"
2393-#include <UnityCore/GLibWrapper.h>
2394-#include <UnityCore/Variant.h>
2395-
2396-#include <string>
2397-#include <vector>
2398-
2399-#define DELTA_DOUBLE_REQUEST 500000000
2400-
2401-namespace unity
2402-{
2403-
2404-enum
2405-{
2406- TYPE_PLACE = 0,
2407- TYPE_EXEC
2408-};
2409-
2410-class Shortcut : public PlacesSimpleTile
2411-{
2412-public:
2413- Shortcut(const char* icon, const char* name, int size)
2414- : PlacesSimpleTile(icon, name, size),
2415- _id(0),
2416- _place_id(NULL),
2417- _place_section(0),
2418- _exec(NULL)
2419- {
2420- SetDndEnabled(false, false);
2421- }
2422-
2423- ~Shortcut()
2424- {
2425- g_free(_place_id);
2426- g_free(_exec);
2427- }
2428-
2429- int _id;
2430- gchar* _place_id;
2431- guint32 _place_section;
2432- char* _exec;
2433-};
2434-
2435-PlacesHomeView::PlacesHomeView()
2436- : _ubus_handle(0)
2437-{
2438- dash::Style& style = dash::Style::Instance();
2439-
2440- SetName(_("Shortcuts"));
2441- SetIcon(PKGDATADIR"/shortcuts_group_icon.png");
2442- SetDrawSeparator(false);
2443-
2444- _layout = new nux::GridHLayout(NUX_TRACKER_LOCATION);
2445- _layout->SetReconfigureParentLayoutOnGeometryChange(true);
2446- SetChildLayout(_layout);
2447-
2448- _layout->ForceChildrenSize(true);
2449- _layout->SetChildrenSize(style.GetHomeTileWidth(), style.GetHomeTileHeight());
2450- _layout->EnablePartialVisibility(false);
2451- _layout->MatchContentSize(true);
2452- _layout->SetLeftAndRightPadding(32);
2453- _layout->SetSpaceBetweenChildren(32, 32);
2454- _layout->SetMinMaxSize((style.GetHomeTileWidth() * 4) + (32 * 5),
2455- (style.GetHomeTileHeight() * 2) + 32);
2456-
2457- _ubus_handle = ubus_server_register_interest(ubus_server_get_default(),
2458- UBUS_PLACE_VIEW_SHOWN,
2459- (UBusCallback) &PlacesHomeView::DashVisible,
2460- this);
2461-
2462- //In case the GConf key is invalid (e.g. when an app was uninstalled), we
2463- //rely on a fallback "whitelist" mechanism instead of showing nothing at all
2464- _browser_alternatives.push_back("firefox");
2465- _browser_alternatives.push_back("chromium-browser");
2466- _browser_alternatives.push_back("epiphany-browser");
2467- _browser_alternatives.push_back("midori");
2468-
2469- _photo_alternatives.push_back("shotwell");
2470- _photo_alternatives.push_back("f-spot");
2471- _photo_alternatives.push_back("gthumb");
2472- _photo_alternatives.push_back("gwenview");
2473- _photo_alternatives.push_back("eog");
2474-
2475- _email_alternatives.push_back("evolution");
2476- _email_alternatives.push_back("thunderbird");
2477- _email_alternatives.push_back("claws-mail");
2478- _email_alternatives.push_back("kmail");
2479-
2480- _music_alternatives.push_back("banshee-1");
2481- _music_alternatives.push_back("rhythmbox");
2482- _music_alternatives.push_back("totem");
2483- _music_alternatives.push_back("vlc");
2484-
2485- expanded.connect(sigc::mem_fun(this, &PlacesHomeView::Refresh));
2486-
2487- Refresh();
2488-}
2489-
2490-PlacesHomeView::~PlacesHomeView()
2491-{
2492- if (_ubus_handle != 0)
2493- ubus_server_unregister_interest(ubus_server_get_default(), _ubus_handle);
2494-}
2495-
2496-void
2497-PlacesHomeView::DashVisible(GVariant* data, void* val)
2498-{
2499- PlacesHomeView* self = (PlacesHomeView*)val;
2500- self->Refresh();
2501-}
2502-
2503-void
2504-PlacesHomeView::Refresh(PlacesGroup*foo)
2505-{
2506- Shortcut* shortcut = NULL;
2507- gchar* markup = NULL;
2508- const char* temp = "<big>%s</big>";
2509- int icon_size = dash::Style::Instance().GetHomeTileIconSize();
2510-
2511- _layout->Clear();
2512-
2513- // Media Apps
2514- markup = g_strdup_printf(temp, _("Media Apps"));
2515- shortcut = new Shortcut(PKGDATADIR"/find_media_apps.png",
2516- markup,
2517- icon_size);
2518- shortcut->_id = TYPE_PLACE;
2519- shortcut->_place_id = g_strdup("applications.lens?filter_type=media");
2520- shortcut->_place_section = 9;
2521- _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull);
2522- shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked));
2523- g_free(markup);
2524-
2525- // Internet Apps
2526- markup = g_strdup_printf(temp, _("Internet Apps"));
2527- shortcut = new Shortcut(PKGDATADIR"/find_internet_apps.png",
2528- markup,
2529- icon_size);
2530- shortcut->_id = TYPE_PLACE;
2531- shortcut->_place_id = g_strdup("applications.lens?filter_type=internet");
2532- shortcut->_place_section = 8;
2533- _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull);
2534- shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked));
2535- g_free(markup);
2536-
2537- // More Apps
2538- markup = g_strdup_printf(temp, _("More Apps"));
2539- shortcut = new Shortcut(PKGDATADIR"/find_more_apps.png",
2540- markup,
2541- icon_size);
2542- shortcut->_id = TYPE_PLACE;
2543- shortcut->_place_id = g_strdup("applications.lens");
2544- shortcut->_place_section = 0;
2545- _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull);
2546- shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked));
2547- g_free(markup);
2548-
2549- // Find Files
2550- markup = g_strdup_printf(temp, _("Find Files"));
2551- shortcut = new Shortcut(PKGDATADIR"/find_files.png",
2552- markup,
2553- icon_size);
2554- shortcut->_id = TYPE_PLACE;
2555- shortcut->_place_id = g_strdup("files.lens");
2556- shortcut->_place_section = 0;
2557- _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull);
2558- shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked));
2559- g_free(markup);
2560-
2561- // Browser
2562- CreateShortcutFromMime("x-scheme-handler/http", _("Browse the Web"), _browser_alternatives);
2563-
2564- // Photos
2565- // FIXME: Need to figure out the default
2566- CreateShortcutFromExec("shotwell", _("View Photos"), _photo_alternatives);
2567-
2568- CreateShortcutFromMime("x-scheme-handler/mailto", _("Check Email"), _email_alternatives);
2569-
2570- CreateShortcutFromMime("audio/x-vorbis+ogg", _("Listen to Music"), _music_alternatives);
2571-
2572- SetExpanded(true);
2573- SetCounts(8, 8);
2574-
2575- QueueDraw();
2576- _layout->QueueDraw();
2577- QueueRelayout();
2578-}
2579-
2580-void
2581-PlacesHomeView::CreateShortcutFromExec(const char* exec,
2582- const char* name,
2583- std::vector<std::string>& alternatives)
2584-{
2585- dash::Style& style = dash::Style::Instance();
2586- Shortcut* shortcut = NULL;
2587- gchar* id;
2588- gchar* markup;
2589- gchar* icon;
2590- gchar* real_exec;
2591- GDesktopAppInfo* info;
2592-
2593- markup = g_strdup_printf("<big>%s</big>", name);
2594-
2595- // We're going to try and create a desktop id from a exec string. Now, this is hairy at the
2596- // best of times but the following is the closest best-guess without having to do D-Bus
2597- // roundtrips to BAMF.
2598- if (exec)
2599- {
2600- char* basename;
2601-
2602- if (exec[0] == '/')
2603- basename = g_path_get_basename(exec);
2604- else
2605- basename = g_strdup(exec);
2606-
2607- id = g_strdup_printf("%s.desktop", basename);
2608-
2609- g_free(basename);
2610- }
2611- else
2612- {
2613- id = g_strdup_printf("%s.desktop", alternatives[0].c_str());
2614- }
2615-
2616- info = g_desktop_app_info_new(id);
2617- std::vector<std::string>::iterator iter = alternatives.begin();
2618- while (iter != alternatives.end())
2619- {
2620- if (!G_IS_DESKTOP_APP_INFO(info))
2621- {
2622- id = g_strdup_printf("%s.desktop", (*iter).c_str());
2623- info = g_desktop_app_info_new(id);
2624- iter++;
2625- }
2626-
2627- if (G_IS_DESKTOP_APP_INFO(info))
2628- {
2629- icon = g_icon_to_string(g_app_info_get_icon(G_APP_INFO(info)));
2630- real_exec = g_strdup(g_app_info_get_executable(G_APP_INFO(info)));
2631-
2632- shortcut = new Shortcut(icon, markup, style.GetHomeTileIconSize());
2633- shortcut->_id = TYPE_EXEC;
2634- shortcut->_exec = real_exec;
2635- _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull);
2636- shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked));
2637-
2638- g_free(icon);
2639-
2640- break;
2641- }
2642- }
2643-
2644- g_free(id);
2645- g_free(markup);
2646-}
2647-
2648-void PlacesHomeView::CreateShortcutFromMime(const char* mime,
2649- const char* name,
2650- std::vector<std::string>& alternatives)
2651-{
2652- dash::Style& style = dash::Style::Instance();
2653- GAppInfo* info = g_app_info_get_default_for_type(mime, FALSE);
2654-
2655- // If it was invalid check alternatives for backup
2656- if (!G_IS_DESKTOP_APP_INFO(info))
2657- {
2658- for (auto alt: alternatives)
2659- {
2660- std::string id = alt + ".desktop";
2661- info = G_APP_INFO(g_desktop_app_info_new(id.c_str()));
2662-
2663- if (G_IS_DESKTOP_APP_INFO(info))
2664- break;
2665- }
2666- }
2667-
2668- if (G_IS_DESKTOP_APP_INFO(info))
2669- {
2670- glib::String icon(g_icon_to_string(g_app_info_get_icon(G_APP_INFO(info))));
2671- glib::String markup(g_strdup_printf("<big>%s</big>", name));
2672-
2673- Shortcut* shortcut = new Shortcut(icon.Value(), markup.Value(), style.GetHomeTileIconSize());
2674- shortcut->_id = TYPE_EXEC;
2675- shortcut->_exec = g_strdup (g_app_info_get_executable(G_APP_INFO(info)));;
2676- shortcut->sigClick.connect(sigc::mem_fun(this, &PlacesHomeView::OnShortcutClicked));
2677- _layout->AddView(shortcut, 1, nux::eLeft, nux::eFull);
2678-
2679- g_object_unref(info);
2680- }
2681-}
2682-
2683-void
2684-PlacesHomeView::OnShortcutClicked(PlacesTile* tile)
2685-{
2686- Shortcut* shortcut = static_cast<Shortcut*>(tile);
2687- int id = shortcut->_id;
2688-
2689- if (id == TYPE_PLACE)
2690- {
2691- ubus_server_send_message(ubus_server_get_default(),
2692- UBUS_PLACE_ENTRY_ACTIVATE_REQUEST,
2693- g_variant_new("(sus)",
2694- shortcut->_place_id,
2695- shortcut->_place_section,
2696- ""));
2697- }
2698- else if (id == TYPE_EXEC)
2699- {
2700- GError* error = NULL;
2701-
2702- if (!g_spawn_command_line_async(shortcut->_exec, &error))
2703- {
2704- g_warning("%s: Unable to launch %s: %s",
2705- G_STRFUNC,
2706- shortcut->_exec,
2707- error->message);
2708- g_error_free(error);
2709- }
2710-
2711- ubus_server_send_message(ubus_server_get_default(),
2712- UBUS_PLACE_VIEW_CLOSE_REQUEST,
2713- NULL);
2714- }
2715-}
2716-
2717-std::string PlacesHomeView::GetName() const
2718-{
2719- return "PlacesHomeView";
2720-}
2721-
2722-void PlacesHomeView::AddProperties(GVariantBuilder* builder)
2723-{
2724- unity::variant::BuilderWrapper(builder).add(GetGeometry());
2725-}
2726-
2727-} // namespace unity
2728
2729=== removed file 'plugins/unityshell/src/PlacesHomeView.h'
2730--- plugins/unityshell/src/PlacesHomeView.h 2011-12-14 20:04:23 +0000
2731+++ plugins/unityshell/src/PlacesHomeView.h 1970-01-01 00:00:00 +0000
2732@@ -1,72 +0,0 @@
2733-// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
2734-/*
2735- * Copyright (C) 2010 Canonical Ltd
2736- *
2737- * This program is free software: you can redistribute it and/or modify
2738- * it under the terms of the GNU General Public License version 3 as
2739- * published by the Free Software Foundation.
2740- *
2741- * This program is distributed in the hope that it will be useful,
2742- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2743- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2744- * GNU General Public License for more details.
2745- *
2746- * You should have received a copy of the GNU General Public License
2747- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2748- *
2749- * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
2750- */
2751-
2752-#ifndef PLACES_HOME_VIEW_H
2753-#define PLACES_HOME_VIEW_H
2754-
2755-#include <Nux/View.h>
2756-#include <Nux/Layout.h>
2757-#include <Nux/TextureArea.h>
2758-#include <NuxGraphics/GraphicsEngine.h>
2759-
2760-#include "Introspectable.h"
2761-
2762-#include <Nux/GridHLayout.h>
2763-
2764-#include "PlacesTile.h"
2765-#include "PlacesGroup.h"
2766-
2767-namespace unity
2768-{
2769-
2770-class PlacesHomeView : public unity::debug::Introspectable, public PlacesGroup
2771-{
2772-public:
2773- PlacesHomeView();
2774- ~PlacesHomeView();
2775-
2776- void Refresh(PlacesGroup* foo =NULL);
2777-
2778-protected:
2779- // Introspectable methods
2780- std::string GetName() const;
2781- void AddProperties(GVariantBuilder* builder);
2782-
2783-private:
2784- static void DashVisible(GVariant* data, void* val);
2785- void OnShortcutClicked(PlacesTile* _tile);
2786- void CreateShortcutFromExec(const char* exec,
2787- const char* name,
2788- std::vector<std::string>& alternatives);
2789- void CreateShortcutFromMime(const char* mime,
2790- const char* name,
2791- std::vector<std::string>& alternatives);
2792-
2793-private:
2794- nux::GridHLayout* _layout;
2795- std::vector<std::string> _browser_alternatives;
2796- std::vector<std::string> _photo_alternatives;
2797- std::vector<std::string> _email_alternatives;
2798- std::vector<std::string> _music_alternatives;
2799-
2800- guint _ubus_handle;
2801-};
2802-
2803-}
2804-#endif
2805
2806=== modified file 'plugins/unityshell/src/ResultViewGrid.cpp'
2807--- plugins/unityshell/src/ResultViewGrid.cpp 2012-01-02 21:39:28 +0000
2808+++ plugins/unityshell/src/ResultViewGrid.cpp 2012-01-26 08:37:26 +0000
2809@@ -626,8 +626,19 @@
2810 int half_width = recorded_dash_width_ / 2;
2811 int half_height = recorded_dash_height_;
2812
2813- int offset_x = MAX(MIN((x_position - half_width) / (half_width / 10), 5), -5);
2814- int offset_y = MAX(MIN(((y_position + absolute_y) - half_height) / (half_height / 10), 5), -5);
2815+ int offset_x, offset_y;
2816+
2817+ /* Guard against divide-by-zero. SIGFPEs are not mythological
2818+ * contrary to popular belief */
2819+ if (half_width >= 10)
2820+ offset_x = MAX(MIN((x_position - half_width) / (half_width / 10), 5), -5);
2821+ else
2822+ offset_x = 0;
2823+
2824+ if (half_height >= 10)
2825+ offset_y = MAX(MIN(((y_position + absolute_y) - half_height) / (half_height / 10), 5), -5);
2826+ else
2827+ offset_y = 0;
2828
2829 if (recorded_dash_width_ < 1 || recorded_dash_height_ < 1)
2830 {
2831
2832=== modified file 'po/POTFILES.in'
2833--- po/POTFILES.in 2012-01-16 16:03:32 +0000
2834+++ po/POTFILES.in 2012-01-26 08:37:26 +0000
2835@@ -3,7 +3,6 @@
2836 plugins/unityshell/src/LauncherController.cpp
2837 plugins/unityshell/src/PanelMenuView.cpp
2838 plugins/unityshell/src/PlacesGroup.cpp
2839-plugins/unityshell/src/PlacesHomeView.cpp
2840 plugins/unityshell/src/SpacerLauncherIcon.cpp
2841 plugins/unityshell/src/TrashLauncherIcon.cpp
2842 plugins/unityshell/src/BFBLauncherIcon.cpp
2843
2844=== added file 'po/unity.pot'
2845--- po/unity.pot 1970-01-01 00:00:00 +0000
2846+++ po/unity.pot 2012-01-26 08:37:26 +0000
2847@@ -0,0 +1,120 @@
2848+# SOME DESCRIPTIVE TITLE.
2849+# Copyright (C) YEAR Canonical\ Ltd
2850+# This file is distributed under the same license as the PACKAGE package.
2851+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
2852+#
2853+#, fuzzy
2854+msgid ""
2855+msgstr ""
2856+"Project-Id-Version: PACKAGE VERSION\n"
2857+"Report-Msgid-Bugs-To: ayatana-dev@lists.launchpad.net\n"
2858+"POT-Creation-Date: 2011-08-23 20:58-0400\n"
2859+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
2860+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
2861+"Language-Team: LANGUAGE <LL@li.org>\n"
2862+"Language: \n"
2863+"MIME-Version: 1.0\n"
2864+"Content-Type: text/plain; charset=CHARSET\n"
2865+"Content-Transfer-Encoding: 8bit\n"
2866+
2867+msgid "Keep in launcher"
2868+msgstr ""
2869+
2870+msgid "Quit"
2871+msgstr ""
2872+
2873+msgid "Open"
2874+msgstr ""
2875+
2876+msgid "Eject"
2877+msgstr ""
2878+
2879+msgid "Eject parent drive"
2880+msgstr ""
2881+
2882+msgid "Safely remove"
2883+msgstr ""
2884+
2885+msgid "Safely remove parent drive"
2886+msgstr ""
2887+
2888+msgid "Unmount"
2889+msgstr ""
2890+
2891+msgid "The drive has been successfully ejected"
2892+msgstr ""
2893+
2894+msgid "Workspace Switcher"
2895+msgstr ""
2896+
2897+msgid "Search"
2898+msgstr ""
2899+
2900+msgid "Search across all places"
2901+msgstr ""
2902+
2903+msgid "See fewer results"
2904+msgstr ""
2905+
2906+msgid "Shortcuts"
2907+msgstr ""
2908+
2909+#. Media Apps
2910+msgid "Media Apps"
2911+msgstr ""
2912+
2913+#. Internet Apps
2914+msgid "Internet Apps"
2915+msgstr ""
2916+
2917+#. More Apps
2918+msgid "More Apps"
2919+msgstr ""
2920+
2921+#. Find Files
2922+msgid "Find Files"
2923+msgstr ""
2924+
2925+msgid "Browse the Web"
2926+msgstr ""
2927+
2928+#. Photos
2929+#. FIXME: Need to figure out the default
2930+msgid "View Photos"
2931+msgstr ""
2932+
2933+msgid "Check Email"
2934+msgstr ""
2935+
2936+msgid "Listen to Music"
2937+msgstr ""
2938+
2939+msgid "Drop To Add Application"
2940+msgstr ""
2941+
2942+msgid "Trash"
2943+msgstr ""
2944+
2945+msgid "Empty Trash..."
2946+msgstr ""
2947+
2948+msgid "Empty all items from Trash?"
2949+msgstr ""
2950+
2951+msgid "All items in the Trash will be permanently deleted."
2952+msgstr ""
2953+
2954+msgid "Empty Trash"
2955+msgstr ""
2956+
2957+msgid "Launcher & Menus"
2958+msgstr ""
2959+
2960+msgid "<b>Show the launcher when the pointer:</b>"
2961+msgstr ""
2962+
2963+msgid "Pushes the left edge of the screen"
2964+msgstr ""
2965+
2966+msgid "Touches the top left corner of the screen"
2967+msgstr ""
2968
2969=== removed file 'po/unity.pot'
2970--- po/unity.pot 2012-01-15 14:03:49 +0000
2971+++ po/unity.pot 1970-01-01 00:00:00 +0000
2972@@ -1,120 +0,0 @@
2973-# SOME DESCRIPTIVE TITLE.
2974-# Copyright (C) YEAR Canonical\ Ltd
2975-# This file is distributed under the same license as the PACKAGE package.
2976-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
2977-#
2978-#, fuzzy
2979-msgid ""
2980-msgstr ""
2981-"Project-Id-Version: PACKAGE VERSION\n"
2982-"Report-Msgid-Bugs-To: ayatana-dev@lists.launchpad.net\n"
2983-"POT-Creation-Date: 2011-08-23 20:58-0400\n"
2984-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
2985-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
2986-"Language-Team: LANGUAGE <LL@li.org>\n"
2987-"Language: \n"
2988-"MIME-Version: 1.0\n"
2989-"Content-Type: text/plain; charset=CHARSET\n"
2990-"Content-Transfer-Encoding: 8bit\n"
2991-
2992-msgid "Keep in launcher"
2993-msgstr ""
2994-
2995-msgid "Quit"
2996-msgstr ""
2997-
2998-msgid "Open"
2999-msgstr ""
3000-
3001-msgid "Eject"
3002-msgstr ""
3003-
3004-msgid "Eject parent drive"
3005-msgstr ""
3006-
3007-msgid "Safely remove"
3008-msgstr ""
3009-
3010-msgid "Safely remove parent drive"
3011-msgstr ""
3012-
3013-msgid "Unmount"
3014-msgstr ""
3015-
3016-msgid "The drive has been successfully ejected"
3017-msgstr ""
3018-
3019-msgid "Workspace Switcher"
3020-msgstr ""
3021-
3022-msgid "Search"
3023-msgstr ""
3024-
3025-msgid "Search across all places"
3026-msgstr ""
3027-
3028-msgid "See fewer results"
3029-msgstr ""
3030-
3031-msgid "Shortcuts"
3032-msgstr ""
3033-
3034-#. Media Apps
3035-msgid "Media Apps"
3036-msgstr ""
3037-
3038-#. Internet Apps
3039-msgid "Internet Apps"
3040-msgstr ""
3041-
3042-#. More Apps
3043-msgid "More Apps"
3044-msgstr ""
3045-
3046-#. Find Files
3047-msgid "Find Files"
3048-msgstr ""
3049-
3050-msgid "Browse the Web"
3051-msgstr ""
3052-
3053-#. Photos
3054-#. FIXME: Need to figure out the default
3055-msgid "View Photos"
3056-msgstr ""
3057-
3058-msgid "Check Email"
3059-msgstr ""
3060-
3061-msgid "Listen to Music"
3062-msgstr ""
3063-
3064-msgid "Drop To Add Application"
3065-msgstr ""
3066-
3067-msgid "Trash"
3068-msgstr ""
3069-
3070-msgid "Empty Trash..."
3071-msgstr ""
3072-
3073-msgid "Empty all items from Trash?"
3074-msgstr ""
3075-
3076-msgid "All items in the Trash will be permanently deleted."
3077-msgstr ""
3078-
3079-msgid "Empty Trash"
3080-msgstr ""
3081-
3082-msgid "Launcher & Menus"
3083-msgstr ""
3084-
3085-msgid "<b>Show the launcher when the pointer:</b>"
3086-msgstr ""
3087-
3088-msgid "Pushes the left edge of the screen"
3089-msgstr ""
3090-
3091-msgid "Touches the top left corner of the screen"
3092-msgstr ""
3093
3094=== modified file 'standalone-clients/CMakeLists.txt'
3095--- standalone-clients/CMakeLists.txt 2012-01-17 11:30:30 +0000
3096+++ standalone-clients/CMakeLists.txt 2012-01-26 08:37:26 +0000
3097@@ -76,8 +76,6 @@
3098 ${UNITY_SRC}/FontSettings.h
3099 ${UNITY_SRC}/IMTextEntry.cpp
3100 ${UNITY_SRC}/IMTextEntry.h
3101- ${UNITY_SRC}/PlacesHomeView.cpp
3102- ${UNITY_SRC}/PlacesHomeView.h
3103 ${UNITY_SRC}/PlacesGroup.cpp
3104 ${UNITY_SRC}/PlacesGroup.h
3105 ${UNITY_SRC}/PlacesTile.cpp
3106@@ -86,8 +84,6 @@
3107 ${UNITY_SRC}/PlacesSimpleTile.h
3108 ${UNITY_SRC}/PlacesVScrollBar.cpp
3109 ${UNITY_SRC}/PlacesVScrollBar.h
3110- ${UNITY_SRC}/HomeView.cpp
3111- ${UNITY_SRC}/HomeView.h
3112 ${UNITY_SRC}/DashStyle.cpp
3113 ${UNITY_SRC}/IconLoader.cpp
3114 ${UNITY_SRC}/IconLoader.h
3115
3116=== modified file 'standalone-clients/standalone_dash.cpp'
3117--- standalone-clients/standalone_dash.cpp 2012-01-15 22:14:35 +0000
3118+++ standalone-clients/standalone_dash.cpp 2012-01-26 08:37:26 +0000
3119@@ -77,18 +77,8 @@
3120 self->Init ();
3121 }
3122
3123-void
3124-ControlThread (nux::NThread* thread,
3125- void* data)
3126-{
3127- // sleep for 3 seconds
3128- nux::SleepForMilliseconds (3000);
3129- printf ("ControlThread successfully started\n");
3130-}
3131-
3132 int main(int argc, char **argv)
3133 {
3134- nux::SystemThread* st = NULL;
3135 nux::WindowThread* wt = NULL;
3136
3137 gtk_init (&argc, &argv);
3138@@ -109,13 +99,7 @@
3139 &TestRunner::InitWindowThread,
3140 test_runner);
3141
3142- st = nux::CreateSystemThread (NULL, ControlThread, wt);
3143-
3144- if (st)
3145- st->Start (NULL);
3146-
3147 wt->Run (NULL);
3148- delete st;
3149 delete wt;
3150 return 0;
3151 }
3152
3153=== modified file 'tests/CMakeLists.txt'
3154--- tests/CMakeLists.txt 2012-01-25 17:26:09 +0000
3155+++ tests/CMakeLists.txt 2012-01-26 08:37:26 +0000
3156@@ -119,11 +119,12 @@
3157 test_glib_variant.cpp
3158 ${CMAKE_CURRENT_BINARY_DIR}/test_glib_signals_utils_marshal.cpp
3159 test_favorite_store_gsettings.cpp
3160+ test_home_lens.cpp
3161 test_shortcut_model.cpp
3162 test_shortcut_private.cpp
3163 test_introspection.cpp
3164 test_main_xless.cpp
3165- test_grabhandle.cpp
3166+ test_grabhandle.cpp
3167 ${UNITY_SRC}/AbstractLauncherIcon.h
3168 ${UNITY_SRC}/AbstractShortcutHint.h
3169 ${UNITY_SRC}/Animator.cpp
3170@@ -152,11 +153,11 @@
3171 ${UNITY_SRC}/Timer.h
3172 ${UNITY_SRC}/WindowManager.cpp
3173 ${UNITY_SRC}/WindowManager.h
3174- ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle.cpp
3175- ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-group.cpp
3176- ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-impl-factory.cpp
3177- ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-layout.cpp
3178- ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-texture.cpp
3179+ ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle.cpp
3180+ ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-group.cpp
3181+ ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-impl-factory.cpp
3182+ ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-grab-handle-layout.cpp
3183+ ${CMAKE_SOURCE_DIR}/plugins/unity-mt-grab-handles/src/unity-mt-texture.cpp
3184 )
3185 target_link_libraries(test-gtest-xless ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIB} ${GMOCK_MAIN_LIB})
3186 add_test(UnityGTestXless test-gtest-xless)
3187
3188=== added file 'tests/test_home_lens.cpp'
3189--- tests/test_home_lens.cpp 1970-01-01 00:00:00 +0000
3190+++ tests/test_home_lens.cpp 2012-01-26 08:37:26 +0000
3191@@ -0,0 +1,362 @@
3192+#include <gtest/gtest.h>
3193+#include <glib-object.h>
3194+#include <dee.h>
3195+#include <string>
3196+#include <iostream>
3197+#include <stdexcept>
3198+#include <map>
3199+#include <memory>
3200+#include <sigc++/signal.h>
3201+#include <sigc++/trackable.h>
3202+
3203+#include <UnityCore/GLibWrapper.h>
3204+#include <UnityCore/Variant.h>
3205+#include <UnityCore/HomeLens.h>
3206+#include <UnityCore/Lens.h>
3207+#include <UnityCore/Lenses.h>
3208+
3209+#include "test_utils.h"
3210+
3211+using namespace std;
3212+using namespace unity::dash;
3213+
3214+namespace
3215+{
3216+
3217+/*
3218+ * FORWARDS
3219+ */
3220+
3221+class StaticTestLens;
3222+
3223+typedef struct {
3224+ StaticTestLens* lens;
3225+ gchar* search_string;
3226+} LensSearchClosure;
3227+
3228+static gboolean dispatch_global_search(gpointer userdata);
3229+
3230+
3231+/*
3232+ * Mock Lens instance that does not use DBus. The default search does like this:
3233+ * For input "bar" output:
3234+ *
3235+ * i = 0
3236+ * for letter in "bar":
3237+ * put result row [ "uri+$letter+$lens_id", "icon+$letter+$lens_id", i % 3, "mime+$letter+$lens_id", ...]
3238+ * i++
3239+ *
3240+ * The mock lens has 3 categories:
3241+ *
3242+ * 0) "cat0+$lens_id"
3243+ * 1) "cat1+$lens_id"
3244+ * 2) "Shared cat"
3245+ */
3246+class StaticTestLens : public Lens
3247+{
3248+public:
3249+ typedef std::shared_ptr<StaticTestLens> Ptr;
3250+
3251+ StaticTestLens(string const& id, string const& name, string const& description, string const& search_hint)
3252+ : Lens(id, "", "", name, "lens-icon.png",
3253+ description, search_hint, true, "",
3254+ ModelType::LOCAL)
3255+ {
3256+ search_in_global(true);
3257+
3258+ DeeModel* cats = categories()->model();
3259+ DeeModel* results = global_results()->model();
3260+ DeeModel* flters = filters()->model();
3261+
3262+ // Set model schemas
3263+ dee_model_set_schema(cats, "s", "s", "s", "a{sv}", NULL);
3264+ dee_model_set_schema(results, "s", "s", "u", "s", "s", "s", "s", NULL);
3265+ dee_model_set_schema(flters, "s", "s", "s", "s", "a{sv}", "b", "b", "b", NULL);
3266+
3267+ // Populate categories model
3268+ ostringstream cat0, cat1;
3269+ cat0 << "cat0+" << id;
3270+ cat1 << "cat1+" << id;
3271+ GVariantBuilder b;
3272+ g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
3273+ GVariant *asv = g_variant_builder_end(&b);
3274+
3275+ dee_model_append(cats, cat0.str().c_str(), "icon.png", "tile-vertical", asv);
3276+ dee_model_append(cats, cat1.str().c_str(), "icon.png", "tile-vertical", asv);
3277+ dee_model_append(cats, "Shared cat", "icon.png", "tile-vertical", asv);
3278+ }
3279+
3280+ virtual ~StaticTestLens() {}
3281+
3282+ virtual void DoGlobalSearch(string const& search_string)
3283+ {
3284+ DeeModel* model = global_results()->model();
3285+ GVariant** row_buf = g_new(GVariant*, 8);
3286+
3287+ row_buf[1] = g_variant_new_string("");
3288+ row_buf[3] = g_variant_new_string("");
3289+ row_buf[4] = g_variant_new_string("");
3290+ row_buf[5] = g_variant_new_string("");
3291+ row_buf[6] = g_variant_new_string("");
3292+ row_buf[7] = NULL;
3293+
3294+ unsigned int i;
3295+ for (i = 0; i < search_string.size(); i++)
3296+ {
3297+ ostringstream uri;
3298+ uri << "uri+" << search_string.at(i) << "+" << id();
3299+ row_buf[0] = g_variant_new_string(uri.str().c_str());
3300+ row_buf[2] = g_variant_new_uint32(i % 3);
3301+
3302+ dee_model_append_row(model, row_buf);
3303+ }
3304+
3305+ g_free(row_buf);
3306+ }
3307+
3308+ void GlobalSearch(string const& search_string)
3309+ {
3310+ /* Dispatch search async, because that's */
3311+ LensSearchClosure* closure = g_new0(LensSearchClosure, 1);
3312+ closure->lens = this;
3313+ closure->search_string = g_strdup(search_string.c_str());
3314+ g_idle_add(dispatch_global_search, closure);
3315+ }
3316+
3317+ void Search(string const& search_string)
3318+ {
3319+
3320+ }
3321+
3322+ void Activate(string const& uri)
3323+ {
3324+
3325+ }
3326+
3327+ void Preview(string const& uri)
3328+ {
3329+
3330+ }
3331+
3332+};
3333+
3334+static gboolean dispatch_global_search(gpointer userdata)
3335+{
3336+ LensSearchClosure* closure = (LensSearchClosure*) userdata;
3337+
3338+ closure->lens->DoGlobalSearch(closure->search_string);
3339+
3340+ g_free(closure->search_string);
3341+ g_free(closure);
3342+
3343+ return FALSE;
3344+}
3345+
3346+/*
3347+ * Mock Lenses class
3348+ */
3349+class StaticTestLenses : public Lenses
3350+{
3351+public:
3352+ typedef std::shared_ptr<StaticTestLenses> Ptr;
3353+
3354+ StaticTestLenses()
3355+ {
3356+ count.SetGetterFunction(sigc::mem_fun(&list_, &Lenses::LensList::size));
3357+ }
3358+
3359+ virtual ~StaticTestLenses() {}
3360+
3361+ Lenses::LensList GetLenses() const
3362+ {
3363+ return list_;
3364+ }
3365+
3366+ Lens::Ptr GetLens(std::string const& lens_id) const
3367+ {
3368+ for (auto lens : list_)
3369+ {
3370+ if (lens->id() == lens_id)
3371+ return lens;
3372+ }
3373+ return Lens::Ptr();
3374+ }
3375+
3376+ Lens::Ptr GetLensAtIndex(std::size_t index) const
3377+ {
3378+ return list_.at(index);
3379+ }
3380+
3381+protected:
3382+ Lenses::LensList list_;
3383+};
3384+
3385+class TwoStaticTestLenses : public StaticTestLenses
3386+{
3387+public:
3388+ TwoStaticTestLenses()
3389+ : lens_1_(new StaticTestLens("first.lens", "First Lens", "The very first lens", "First search hint"))
3390+ , lens_2_(new StaticTestLens("second.lens", "Second Lens", "The second lens", "Second search hint"))
3391+ {
3392+ list_.push_back(lens_1_);
3393+ list_.push_back(lens_2_);
3394+ }
3395+
3396+private:
3397+ Lens::Ptr lens_1_;
3398+ Lens::Ptr lens_2_;
3399+};
3400+
3401+TEST(TestHomeLens, TestConstruction)
3402+{
3403+ HomeLens home_lens_("name", "description", "searchhint");
3404+
3405+ EXPECT_EQ(home_lens_.id(), "home.lens");
3406+ EXPECT_EQ(home_lens_.connected, false);
3407+ EXPECT_EQ(home_lens_.search_in_global, false);
3408+ EXPECT_EQ(home_lens_.name, "name");
3409+ EXPECT_EQ(home_lens_.description, "description");
3410+ EXPECT_EQ(home_lens_.search_hint, "searchhint");
3411+}
3412+
3413+TEST(TestHomeLens, TestInitiallyEmpty)
3414+{
3415+ HomeLens home_lens_("name", "description", "searchhint");
3416+ DeeModel* results = home_lens_.results()->model();
3417+ DeeModel* categories = home_lens_.categories()->model();;
3418+ DeeModel* filters = home_lens_.filters()->model();;
3419+
3420+ EXPECT_EQ(dee_model_get_n_rows(results), 0);
3421+ EXPECT_EQ(dee_model_get_n_rows(categories), 0);
3422+ EXPECT_EQ(dee_model_get_n_rows(filters), 0);
3423+
3424+ EXPECT_EQ(home_lens_.count(), 0);
3425+}
3426+
3427+TEST(TestHomeLens, TestTwoStaticLenses)
3428+{
3429+ HomeLens home_lens_("name", "description", "searchhint");
3430+ TwoStaticTestLenses lenses_;
3431+
3432+ home_lens_.AddLenses(lenses_);
3433+
3434+ EXPECT_EQ(home_lens_.count, (size_t) 2);
3435+
3436+ /* Test iteration of registered lensess */
3437+ map<string,string> remaining;
3438+ remaining["first.lens"] = "";
3439+ remaining["second.lens"] = "";
3440+ for (auto lens : home_lens_.GetLenses())
3441+ {
3442+ remaining.erase(lens->id());
3443+ }
3444+
3445+ EXPECT_EQ(remaining.size(), 0);
3446+
3447+ /* Test sorting and GetAtIndex */
3448+ EXPECT_EQ(home_lens_.GetLensAtIndex(0)->id(), "first.lens");
3449+ EXPECT_EQ(home_lens_.GetLensAtIndex(1)->id(), "second.lens");
3450+}
3451+
3452+TEST(TestHomeLens, TestCategoryMerging)
3453+{
3454+ HomeLens home_lens_("name", "description", "searchhint");
3455+ TwoStaticTestLenses lenses_;
3456+ DeeModel* cats = home_lens_.categories()->model();
3457+ DeeModelIter* iter;
3458+ unsigned int cat0_first = 0,
3459+ cat1_first = 1,
3460+ cat_shared = 2,
3461+ cat0_second = 3,
3462+ cat1_second = 4;
3463+ const unsigned int NAME_COLUMN = 0;
3464+
3465+ home_lens_.AddLenses(lenses_);
3466+
3467+ EXPECT_EQ(dee_model_get_n_rows(cats), 5); // 5 because each lens has 3 cats, but 1 is shared between them
3468+
3469+ /* Validate the merged categories */
3470+ iter = dee_model_get_iter_at_row(cats, cat0_first);
3471+ EXPECT_EQ("cat0+first.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN)));
3472+
3473+ iter = dee_model_get_iter_at_row(cats, cat1_first);
3474+ EXPECT_EQ("cat1+first.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN)));
3475+
3476+ iter = dee_model_get_iter_at_row(cats, cat_shared);
3477+ EXPECT_EQ("Shared cat", string(dee_model_get_string(cats, iter, NAME_COLUMN)));
3478+
3479+ iter = dee_model_get_iter_at_row(cats, cat0_second);
3480+ EXPECT_EQ("cat0+second.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN)));
3481+
3482+ iter = dee_model_get_iter_at_row(cats, cat1_second);
3483+ EXPECT_EQ("cat1+second.lens", string(dee_model_get_string(cats, iter, NAME_COLUMN)));
3484+}
3485+
3486+// It's not that we must not support filters. It is just not implemented yet.
3487+// But we actively test against it to make sure we don't end up with broken
3488+// filters in the UI. When/if we land support for filters on the home screen
3489+// this test should obviously be removed
3490+TEST(TestHomeLens, TestIgnoreFilters)
3491+{
3492+ HomeLens home_lens_("name", "description", "searchhint");
3493+ TwoStaticTestLenses lenses_;
3494+ DeeModel* filters = home_lens_.filters()->model();
3495+
3496+ EXPECT_EQ(dee_model_get_n_rows(filters), 0);
3497+}
3498+
3499+TEST(TestHomeLens, TestOneSearch)
3500+{
3501+ HomeLens home_lens_("name", "description", "searchhint");
3502+ TwoStaticTestLenses lenses_;
3503+ DeeModel* results = home_lens_.results()->model();
3504+ DeeModel* cats = home_lens_.categories()->model();
3505+ DeeModel* filters = home_lens_.filters()->model();
3506+ DeeModelIter* iter;
3507+ unsigned int cat0_first = 0,
3508+ cat1_first = 1,
3509+ cat_shared = 2,
3510+ cat0_second = 3,
3511+ cat1_second = 4;
3512+ const unsigned int URI_COLUMN = 0;
3513+ const unsigned int CAT_COLUMN = 2;
3514+
3515+ home_lens_.AddLenses(lenses_);
3516+
3517+ home_lens_.Search("ape");
3518+
3519+ Utils::WaitForTimeoutMSec();
3520+
3521+ /* Validate counts */
3522+ EXPECT_EQ(dee_model_get_n_rows(results), 6); // 3 hits from each lens
3523+ EXPECT_EQ(dee_model_get_n_rows(cats), 5); // 5 because each lens has 3 cats, but 1 is shared between them
3524+ EXPECT_EQ(dee_model_get_n_rows(filters), 0); // We ignore filters deliberately currently
3525+
3526+ /* Validate results. In particular that we get the correct merged
3527+ * category offsets assigned */
3528+ iter = dee_model_get_iter_at_row(results, 0);
3529+ EXPECT_EQ(string("uri+a+first.lens"), string(dee_model_get_string(results, iter, URI_COLUMN)));
3530+ EXPECT_EQ(cat0_first, dee_model_get_uint32(results, iter, CAT_COLUMN));
3531+
3532+ iter = dee_model_get_iter_at_row(results, 1);
3533+ EXPECT_EQ(string("uri+p+first.lens"), string(dee_model_get_string(results, iter, URI_COLUMN)));
3534+ EXPECT_EQ(cat1_first, dee_model_get_uint32(results, iter, CAT_COLUMN));
3535+
3536+ iter = dee_model_get_iter_at_row(results, 2);
3537+ EXPECT_EQ(string("uri+e+first.lens"), string(dee_model_get_string(results, iter, URI_COLUMN)));
3538+ EXPECT_EQ(cat_shared, dee_model_get_uint32(results, iter, CAT_COLUMN));
3539+
3540+ iter = dee_model_get_iter_at_row(results, 3);
3541+ EXPECT_EQ(string("uri+a+second.lens"), string(dee_model_get_string(results, iter, URI_COLUMN)));
3542+ EXPECT_EQ(cat0_second, dee_model_get_uint32(results, iter, CAT_COLUMN));
3543+
3544+ iter = dee_model_get_iter_at_row(results, 4);
3545+ EXPECT_EQ(string("uri+p+second.lens"), string(dee_model_get_string(results, iter, URI_COLUMN)));
3546+ EXPECT_EQ(cat1_second, dee_model_get_uint32(results, iter, CAT_COLUMN));
3547+
3548+ iter = dee_model_get_iter_at_row(results, 5);
3549+ EXPECT_EQ(string("uri+e+second.lens"), string(dee_model_get_string(results, iter, URI_COLUMN)));
3550+ EXPECT_EQ(cat_shared, dee_model_get_uint32(results, iter, CAT_COLUMN));
3551+}
3552+
3553+}
3554
3555=== modified file 'tests/test_utils.h'
3556--- tests/test_utils.h 2011-12-02 12:03:27 +0000
3557+++ tests/test_utils.h 2012-01-26 08:37:26 +0000
3558@@ -51,6 +51,33 @@
3559 return g_timeout_add_seconds(timeout_duration, TimeoutCallback, timeout_reached);
3560 }
3561
3562+ static guint32 ScheduleTimeoutMSec(bool* timeout_reached, unsigned int timeout_duration = 10)
3563+ {
3564+ return g_timeout_add(timeout_duration, TimeoutCallback, timeout_reached);
3565+ }
3566+
3567+ static void WaitForTimeout(unsigned int timeout_duration = 10)
3568+ {
3569+ bool timeout_reached = false;
3570+ guint32 timeout_id = ScheduleTimeout(&timeout_reached, timeout_duration);
3571+
3572+ while (!timeout_reached)
3573+ g_main_context_iteration(g_main_context_get_thread_default(), TRUE);
3574+
3575+ g_source_remove(timeout_id);
3576+ }
3577+
3578+ static void WaitForTimeoutMSec(unsigned int timeout_duration = 10)
3579+ {
3580+ bool timeout_reached = false;
3581+ guint32 timeout_id = ScheduleTimeoutMSec(&timeout_reached, timeout_duration);
3582+
3583+ while (!timeout_reached)
3584+ g_main_context_iteration(g_main_context_get_thread_default(), TRUE);
3585+
3586+ g_source_remove(timeout_id);
3587+ }
3588+
3589 private:
3590 static gboolean TimeoutCallback(gpointer data)
3591 {