Merge lp:~3v1n0/unity/spread-filter into lp:unity

Proposed by Marco Trevisan (Treviño)
Status: Merged
Approved by: Brandon Schaefer
Approved revision: no longer in the source branch.
Merged at revision: 3668
Proposed branch: lp:~3v1n0/unity/spread-filter
Merge into: lp:unity
Prerequisite: lp:~3v1n0/unity/spreadish-scale
Diff against target: 964 lines (+484/-93)
14 files modified
plugins/unityshell/src/unityshell.cpp (+60/-30)
plugins/unityshell/src/unityshell.h (+4/-0)
plugins/unityshell/unityshell.xml.in (+1/-0)
shutdown/SessionController.h (+0/-1)
tests/CMakeLists.txt (+1/-0)
tests/autopilot/unity/emulators/screen.py (+23/-0)
tests/autopilot/unity/tests/test_spread.py (+49/-8)
tests/test_spread_filter.cpp (+116/-0)
unity-shared/CMakeLists.txt (+1/-0)
unity-shared/PluginAdapter.cpp (+3/-7)
unity-shared/SearchBar.cpp (+40/-45)
unity-shared/SearchBar.h (+1/-2)
unity-shared/SpreadFilter.cpp (+121/-0)
unity-shared/SpreadFilter.h (+64/-0)
To merge this branch: bzr merge lp:~3v1n0/unity/spread-filter
Reviewer Review Type Date Requested Status
Brandon Schaefer (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+206802@code.launchpad.net

Commit message

UnityScreen: add a SpreadFilter when in Scale mode, when updated it filters the scale results

The SpreadFilter is a BaseWindow with a SearchBar shown on the top-left corner of the
active workspace that is hidden by default monitoring key-presses; when some content is
written, the bar is shown, while is hidden when empty.

Thanks to this we can finally filter the windows by name in the unity spread!

Description of the change

As defined in the Spread design doc [1]:

Filtering Windows by Title Captions
The user can start typing to filter the windows shown to include only those which contain the search text in their title bar.

So, added a new SpreadFilter class that basically just shows a SearchBar that is hidden unless the user won't type anything useful to filter the spreaded windows.

Design: http://go.3v1n0.net/1eJfQCF
Actual implementation: http://people.ubuntu.com/~3v1n0/Spread-Filter.webm

Tests added, it has a soft-dependency on lp:~3v1n0/compiz/scale-improvements to properly work.

[1] http://go.3v1n0.net/1bFTXd2

To post a comment you must log in.
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

Looks very nice :).

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/unityshell/src/unityshell.cpp'
2--- plugins/unityshell/src/unityshell.cpp 2014-02-18 00:59:55 +0000
3+++ plugins/unityshell/src/unityshell.cpp 2014-02-18 00:59:55 +0000
4@@ -137,10 +137,8 @@
5 const unsigned int SCROLL_UP_BUTTON = 7;
6 const int MAX_BUFFER_AGE = 11;
7 const int FRAMES_TO_REDRAW_ON_RESUME = 10;
8-
9 const std::string RELAYOUT_TIMEOUT = "relayout-timeout";
10 } // namespace local
11-
12 } // anon namespace
13
14 UnityScreen::UnityScreen(CompScreen* screen)
15@@ -149,6 +147,7 @@
16 , screen(screen)
17 , cScreen(CompositeScreen::get(screen))
18 , gScreen(GLScreen::get(screen))
19+ , sScreen(ScaleScreen::get(screen))
20 , menus_(std::make_shared<menu::Manager>(std::make_shared<indicator::DBusIndicators>(), std::make_shared<key::GnomeGrabber>()))
21 , deco_manager_(std::make_shared<decoration::Manager>())
22 , debugger_(this)
23@@ -511,13 +510,28 @@
24
25 void UnityScreen::OnInitiateSpread()
26 {
27- for (auto const& swin : ScaleScreen::get(screen)->getWindows())
28+ spread_filter_ = std::make_shared<spread::Filter>();
29+ spread_filter_->text.changed.connect([this] (std::string const& filter) {
30+ if (filter.empty())
31+ {
32+ sScreen->relayoutSlots(CompMatch::emptyMatch);
33+ }
34+ else
35+ {
36+ auto match = sScreen->getCustomMatch();
37+ sScreen->relayoutSlots(match & ("ititle="+filter));
38+ }
39+ });
40+
41+ for (auto const& swin : sScreen->getWindows())
42 UnityWindow::get(swin->window)->OnInitiateSpread();
43 }
44
45 void UnityScreen::OnTerminateSpread()
46 {
47- for (auto const& swin : ScaleScreen::get(screen)->getWindows())
48+ spread_filter_.reset();
49+
50+ for (auto const& swin : sScreen->getWindows())
51 UnityWindow::get(swin->window)->OnTerminateSpread();
52 }
53
54@@ -1680,8 +1694,7 @@
55 case MotionNotify:
56 if (wm.IsScaleActive())
57 {
58- ScaleScreen* ss = ScaleScreen::get(screen);
59- if (CompWindow *w = screen->findWindow(ss->getSelectedWindow()))
60+ if (CompWindow *w = screen->findWindow(sScreen->getSelectedWindow()))
61 skip_other_plugins = UnityWindow::get(w)->handleEvent(event);
62 }
63 else if (switcher_controller_->IsDetailViewShown())
64@@ -1706,9 +1719,14 @@
65 }
66 if (wm.IsScaleActive())
67 {
68- ScaleScreen* ss = ScaleScreen::get(screen);
69- if (CompWindow *w = screen->findWindow(ss->getSelectedWindow()))
70- skip_other_plugins = UnityWindow::get(w)->handleEvent(event);
71+ if (spread_filter_ && spread_filter_->Visible())
72+ skip_other_plugins = spread_filter_->GetAbsoluteGeometry().IsPointInside(event->xbutton.x_root, event->xbutton.y_root);
73+
74+ if (!skip_other_plugins)
75+ {
76+ if (CompWindow *w = screen->findWindow(sScreen->getSelectedWindow()))
77+ skip_other_plugins = UnityWindow::get(w)->handleEvent(event);
78+ }
79 }
80 else if (switcher_controller_->IsDetailViewShown())
81 {
82@@ -1782,9 +1800,14 @@
83 }
84 else if (wm.IsScaleActive())
85 {
86- ScaleScreen* ss = ScaleScreen::get(screen);
87- if (CompWindow *w = screen->findWindow(ss->getSelectedWindow()))
88- skip_other_plugins = UnityWindow::get(w)->handleEvent(event);
89+ if (spread_filter_ && spread_filter_->Visible())
90+ skip_other_plugins = spread_filter_->GetAbsoluteGeometry().IsPointInside(event->xbutton.x_root, event->xbutton.y_root);
91+
92+ if (!skip_other_plugins)
93+ {
94+ if (CompWindow *w = screen->findWindow(sScreen->getSelectedWindow()))
95+ skip_other_plugins = skip_other_plugins || UnityWindow::get(w)->handleEvent(event);
96+ }
97 }
98 break;
99 case KeyPress:
100@@ -1853,6 +1876,16 @@
101 EnableCancelAction(CancelActionTarget::LAUNCHER_SWITCHER, false);
102 }
103 }
104+
105+ if (spread_filter_ && spread_filter_->Visible())
106+ {
107+ if (key_sym == XK_Escape)
108+ {
109+ skip_other_plugins = true;
110+ spread_filter_->text = "";
111+ }
112+ }
113+
114 break;
115 }
116 case MapRequest:
117@@ -1866,22 +1899,21 @@
118 }
119 break;
120 default:
121- if (screen->shapeEvent () + ShapeNotify == event->type)
122+ if (screen->shapeEvent() + ShapeNotify == event->type)
123 {
124 Window xid = event->xany.window;
125 CompWindow *w = screen->findWindow(xid);
126
127 if (w)
128 {
129- UnityWindow *uw = UnityWindow::get (w);
130-
131+ UnityWindow *uw = UnityWindow::get(w);
132 uw->handleEvent(event);
133 }
134 }
135 break;
136 }
137
138- compiz::CompizMinimizedWindowHandler<UnityScreen, UnityWindow>::handleEvent (event);
139+ compiz::CompizMinimizedWindowHandler<UnityScreen, UnityWindow>::handleEvent(event);
140
141 // avoid further propagation (key conflict for instance)
142 if (!skip_other_plugins)
143@@ -1890,19 +1922,18 @@
144 if (deco_manager_->HandleEventAfter(event))
145 return;
146
147- switch (event->type)
148- {
149- case MapRequest:
150- ShowdesktopHandler::AllowLeaveShowdesktopMode(event->xmaprequest.window);
151- break;
152- }
153+ if (event->type == MapRequest)
154+ ShowdesktopHandler::AllowLeaveShowdesktopMode(event->xmaprequest.window);
155
156- if ((event->type == MotionNotify || event->type == ButtonPress || event->type == ButtonRelease) &&
157- switcher_controller_->IsMouseDisabled() && switcher_controller_->Visible())
158+ if (switcher_controller_->IsMouseDisabled() && switcher_controller_->Visible() &&
159+ (event->type == MotionNotify || event->type == ButtonPress || event->type == ButtonRelease))
160 {
161 skip_other_plugins = true;
162 }
163
164+ if (spread_filter_ && spread_filter_->Visible())
165+ skip_other_plugins = false;
166+
167 if (!skip_other_plugins &&
168 screen->otherGrabExist("deco", "move", "switcher", "resize", nullptr))
169 {
170@@ -2786,7 +2817,7 @@
171 }
172
173 if (WindowManager::Default().IsScaleActive() &&
174- ScaleScreen::get(screen)->getSelectedWindow() == window->id())
175+ uScreen->sScreen->getSelectedWindow() == window->id())
176 {
177 nux::Geometry const& scaled_geo = GetScaledGeometry();
178 paintInnerGlow(scaled_geo, matrix, attrib, mask);
179@@ -3737,7 +3768,7 @@
180 void UnityWindow::AddProperties(debug::IntrospectionData& introspection)
181 {
182 Window xid = window->id();
183- auto const& swins = ScaleScreen::get(screen)->getWindows();
184+ auto const& swins = uScreen->sScreen->getWindows();
185 bool scaled = std::find(swins.begin(), swins.end(), ScaleWindow::get(window)) != swins.end();
186 WindowManager& wm = WindowManager::Default();
187
188@@ -3942,8 +3973,7 @@
189 if (!scale_win->hasSlot()) // animation not finished
190 return;
191
192- ScaleScreen* ss = ScaleScreen::get(screen);
193- auto state = ss->getState();
194+ auto state = uScreen->sScreen->getState();
195
196 if (state != ScaleScreen::Wait && state != ScaleScreen::Out)
197 return;
198@@ -3953,7 +3983,7 @@
199 auto deco_attrib = attrib;
200 deco_attrib.opacity = COMPIZ_COMPOSITE_OPAQUE;
201
202- bool highlighted = (ss->getSelectedWindow() == window->id());
203+ bool highlighted = (uScreen->sScreen->getSelectedWindow() == window->id());
204 paintFakeDecoration(scale_geo, deco_attrib, transform, mask, highlighted, pos.scale);
205 }
206
207@@ -4130,7 +4160,7 @@
208
209 Introspectable::IntrospectableList ScreenIntrospection::GetIntrospectableChildren()
210 {
211- IntrospectableList children;
212+ IntrospectableList children({uScreen->spread_filter_.get()});
213
214 for (auto const& win : screen_->windows())
215 children.push_back(UnityWindow::get(win));
216
217=== modified file 'plugins/unityshell/src/unityshell.h'
218--- plugins/unityshell/src/unityshell.h 2014-02-12 07:13:01 +0000
219+++ plugins/unityshell/src/unityshell.h 2014-02-18 00:59:55 +0000
220@@ -65,6 +65,7 @@
221 #include "ScreenIntrospection.h"
222 #include "SwitcherController.h"
223 #include "SessionController.h"
224+#include "SpreadFilter.h"
225 #include "UBusWrapper.h"
226 #include "UnityshellPrivate.h"
227 #include "UnityShowdesktopHandler.h"
228@@ -118,6 +119,7 @@
229 CompScreen* screen;
230 CompositeScreen* cScreen;
231 GLScreen* gScreen;
232+ ScaleScreen* sScreen;
233
234 /* prepares nux for drawing */
235 void nuxPrologue();
236@@ -327,6 +329,7 @@
237 session::Controller::Ptr session_controller_;
238 debug::DebugDBusInterface debugger_;
239 std::unique_ptr<BGHash> bghash_;
240+ spread::Filter::Ptr spread_filter_;
241
242 /* Subscription for gestures that manipulate Unity launcher */
243 std::unique_ptr<nux::GesturesSubscription> gestures_sub_launcher_;
244@@ -407,6 +410,7 @@
245 bool is_desktop_active_;
246
247 friend class UnityWindow;
248+ friend class debug::ScreenIntrospection;
249 friend class decoration::Manager;
250 };
251
252
253=== modified file 'plugins/unityshell/unityshell.xml.in'
254--- plugins/unityshell/unityshell.xml.in 2014-02-04 16:38:45 +0000
255+++ plugins/unityshell/unityshell.xml.in 2014-02-18 00:59:55 +0000
256@@ -43,6 +43,7 @@
257 </requirement>
258 <conflict>
259 <plugin>decor</plugin>
260+ <plugin>scalefilter</plugin>
261 </conflict>
262 </deps>
263
264
265=== modified file 'shutdown/SessionController.h'
266--- shutdown/SessionController.h 2014-01-06 19:50:53 +0000
267+++ shutdown/SessionController.h 2014-02-18 00:59:55 +0000
268@@ -25,7 +25,6 @@
269 #include <Nux/Nux.h>
270 #include <Nux/BaseWindow.h>
271 #include <Nux/HLayout.h>
272-#include <NuxCore/Color.h>
273 #include <NuxCore/Animation.h>
274 #include <UnityCore/SessionManager.h>
275
276
277=== modified file 'tests/CMakeLists.txt'
278--- tests/CMakeLists.txt 2014-02-07 21:50:15 +0000
279+++ tests/CMakeLists.txt 2014-02-18 00:59:55 +0000
280@@ -278,6 +278,7 @@
281 test_single_monitor_launcher_icon.cpp
282 test_showdesktop_handler.cpp
283 test_software_center_launcher_icon.cpp
284+ test_spread_filter.cpp
285 test_static_cairo_text.cpp
286 test_switcher_controller.cpp
287 test_switcher_controller_class.cpp
288
289=== modified file 'tests/autopilot/unity/emulators/screen.py'
290--- tests/autopilot/unity/emulators/screen.py 2013-10-08 14:08:52 +0000
291+++ tests/autopilot/unity/emulators/screen.py 2014-02-18 00:59:55 +0000
292@@ -28,6 +28,15 @@
293 """Return the available scaled windows, or None."""
294 return self.get_children_by_type(Window, scaled=True)
295
296+ @property
297+ def spread_filter(self):
298+ """Return the spread filter, or None."""
299+ filter = self.get_children_by_type(SpreadFilter)
300+ if len(filter):
301+ return filter[0]
302+
303+ return None
304+
305 def window(self, xid):
306 """Return the window with given xid."""
307 windows = self.get_children_by_type(Window, xid=xid)
308@@ -51,3 +60,17 @@
309 self.scaled_close_width.wait_for(GreaterThan(0))
310 self.scaled_close_height.wait_for(GreaterThan(0))
311 return (self.scaled_close_x, self.scaled_close_y, self.scaled_close_width, self.scaled_close_height)
312+
313+
314+class SpreadFilter(UnityIntrospectionObject):
315+ """The spread filter."""
316+
317+ @property
318+ def search_bar(self):
319+ """Return the search bar."""
320+ [search_bar] = self.get_children_by_type(SearchBar)
321+ return search_bar
322+
323+
324+class SearchBar(UnityIntrospectionObject):
325+ """The search bar for the spread filter."""
326\ No newline at end of file
327
328=== modified file 'tests/autopilot/unity/tests/test_spread.py'
329--- tests/autopilot/unity/tests/test_spread.py 2014-02-14 16:40:28 +0000
330+++ tests/autopilot/unity/tests/test_spread.py 2014-02-18 00:59:55 +0000
331@@ -12,7 +12,6 @@
332 from autopilot.matchers import Eventually
333 from testtools.matchers import Equals, NotEquals
334 from time import sleep
335-from unity.emulators.icons import BFBLauncherIcon
336
337 from unity.tests import UnityTestCase
338
339@@ -56,10 +55,14 @@
340 self.launcher.click_launcher_icon(icon, move_mouse_after=False)
341 self.assertThat(self.unity.window_manager.scale_active_for_group, Eventually(Equals(True)))
342
343- def assertWindowIsNotScaled(self, window):
344- """Assert that a window is not scaled"""
345- refresh_fn = lambda: window.id in [w.id for w in self.unity.screen.scaled_windows]
346- self.assertThat(refresh_fn, Eventually(Equals(False)))
347+ def get_spread_filter(self):
348+ self.assertThat(lambda: self.unity.screen.spread_filter, Eventually(NotEquals(None)))
349+ return self.unity.screen.spread_filter
350+
351+ def assertWindowIsScaledEquals(self, xid, scaled):
352+ """Assert weather a window is scaled"""
353+ refresh_fn = lambda: xid in [w.xid for w in self.unity.screen.scaled_windows]
354+ self.assertThat(refresh_fn, Eventually(Equals(scaled)))
355
356 def assertWindowIsClosed(self, xid):
357 """Assert that a window is not in the list of the open windows"""
358@@ -72,7 +75,7 @@
359
360 def assertLauncherIconsDesaturated(self, also_active=True):
361 for icon in self.unity.launcher.model.get_launcher_icons():
362- if isinstance(icon, BFBLauncherIcon) or (not also_active and icon.active):
363+ if not also_active and icon.active:
364 self.assertFalse(icon.monitors_desaturated[self.monitor])
365 else:
366 self.assertTrue(icon.monitors_desaturated[self.monitor])
367@@ -119,7 +122,7 @@
368 sleep(.5)
369 self.mouse.click(button=2)
370
371- self.assertWindowIsNotScaled(target_win)
372+ self.assertWindowIsScaledEquals(target_xid, False)
373 self.assertWindowIsClosed(target_xid)
374
375 def test_scaled_window_closes_on_close_button_click(self):
376@@ -135,7 +138,7 @@
377 sleep(.5)
378 self.mouse.click()
379
380- self.assertWindowIsNotScaled(target_win)
381+ self.assertWindowIsScaledEquals(target_xid, False)
382 self.assertWindowIsClosed(target_xid)
383
384 def test_spread_desaturate_launcher_icons(self):
385@@ -191,3 +194,41 @@
386
387 self.initiate_spread_for_screen()
388 self.assertThat(icon.get_tooltip().active, Eventually(Equals(False)))
389+
390+ def test_spread_puts_panel_in_overlay_mode(self):
391+ """Test that the panel is in overlay mode when in spread"""
392+ self.start_test_application_windows("Calculator", 1)
393+ self.initiate_spread_for_screen()
394+ self.assertThat(self.unity.panels.get_active_panel().in_overlay_mode, Eventually(Equals(True)))
395+ self.unity.window_manager.terminate_spread()
396+ self.assertThat(self.unity.panels.get_active_panel().in_overlay_mode, Eventually(Equals(False)))
397+
398+ def test_panel_close_window_button_terminates_spread(self):
399+ """Test that the panel close window button terminates the spread"""
400+ self.start_test_application_windows("Calculator", 1)
401+ self.initiate_spread_for_screen()
402+ self.unity.panels.get_active_panel().window_buttons.close.mouse_click();
403+ self.assertThat(self.unity.window_manager.scale_active, Eventually(Equals(False)))
404+
405+ def test_spread_filter(self):
406+ """Test spread filter"""
407+ cal_wins = self.start_test_application_windows("Calculator", 2)
408+ char_wins = self.start_test_application_windows("Character Map", 2)
409+ self.initiate_spread_for_screen()
410+ spread_filter = self.get_spread_filter()
411+ self.assertThat(spread_filter.visible, Eventually(Equals(False)))
412+
413+ self.addCleanup(self.keyboard.press_and_release, "Escape")
414+ self.keyboard.type(cal_wins[0].title)
415+ self.assertThat(spread_filter.visible, Eventually(Equals(True)))
416+ self.assertThat(spread_filter.search_bar.search_string, Eventually(Equals(cal_wins[0].title)))
417+
418+ for w in cal_wins + char_wins:
419+ self.assertWindowIsScaledEquals(w.x_id, (w in cal_wins))
420+
421+ self.keyboard.press_and_release("Escape")
422+ self.assertThat(spread_filter.visible, Eventually(Equals(False)))
423+ self.assertThat(spread_filter.search_bar.search_string, Eventually(Equals("")))
424+
425+ for w in cal_wins + char_wins:
426+ self.assertWindowIsScaledEquals(w.x_id, True)
427
428=== added file 'tests/test_spread_filter.cpp'
429--- tests/test_spread_filter.cpp 1970-01-01 00:00:00 +0000
430+++ tests/test_spread_filter.cpp 2014-02-18 00:59:55 +0000
431@@ -0,0 +1,116 @@
432+/*
433+ * Copyright 2014 Canonical Ltd.
434+ *
435+ * This program is free software: you can redistribute it and/or modify it
436+ * under the terms of the GNU General Public License version 3, as published
437+ * by the Free Software Foundation.
438+ *
439+ * This program is distributed in the hope that it will be useful, but
440+ * WITHOUT ANY WARRANTY; without even the implied warranties of
441+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
442+ * PURPOSE. See the GNU General Public License for more details.
443+ *
444+ * You should have received a copy of the GNU General Public License
445+ * version 3 along with this program. If not, see
446+ * <http://www.gnu.org/licenses/>
447+ *
448+ * Authored by: Marco Trevisan <marco.trevisan@canonical.com>
449+ *
450+ */
451+
452+#include <gmock/gmock.h>
453+#include <Nux/NuxTimerTickSource.h>
454+#include <NuxCore/AnimationController.h>
455+
456+#include "SpreadFilter.h"
457+#include "UnitySettings.h"
458+#include "DashStyle.h"
459+#include "test_utils.h"
460+
461+namespace unity
462+{
463+namespace spread
464+{
465+namespace
466+{
467+using namespace testing;
468+
469+const unsigned ANIMATION_DURATION = 100 * 1000; // in microseconds
470+
471+struct SigReceiver : sigc::trackable
472+{
473+ typedef NiceMock<SigReceiver> Nice;
474+
475+ SigReceiver(Filter const& const_filter)
476+ {
477+ auto& filter = const_cast<Filter&>(const_filter);
478+ filter.text.changed.connect(sigc::mem_fun(this, &SigReceiver::TextChanged));
479+ }
480+
481+ MOCK_CONST_METHOD1(TextChanged, void(std::string const&));
482+};
483+
484+struct TestSpreadFilter : Test
485+{
486+ TestSpreadFilter()
487+ : animation_controller(tick_source)
488+ , big_tick_(0)
489+ , sig_receiver(filter)
490+ {}
491+
492+ void Tick()
493+ {
494+ big_tick_ += ANIMATION_DURATION;
495+ tick_source.tick(big_tick_);
496+ }
497+
498+ Settings settings_;
499+ dash::Style style_;
500+ nux::NuxTimerTickSource tick_source;
501+ nux::animation::AnimationController animation_controller;
502+ uint64_t big_tick_;
503+ Filter filter;
504+ SigReceiver::Nice sig_receiver;
505+};
506+
507+TEST_F(TestSpreadFilter, Construction)
508+{
509+ EXPECT_FALSE(filter.Visible());
510+ EXPECT_TRUE(filter.text().empty());
511+}
512+
513+TEST_F(TestSpreadFilter, VisibleWithText)
514+{
515+ std::string filter_string = "Unity is cool!";
516+ EXPECT_CALL(sig_receiver, TextChanged(_)).Times(0);
517+
518+ filter.text = filter_string;
519+ Tick();
520+
521+ EXPECT_TRUE(filter.Visible());
522+ EXPECT_FALSE(filter.text().empty());
523+
524+ EXPECT_CALL(sig_receiver, TextChanged(filter_string));
525+ Utils::WaitForTimeoutMSec();
526+
527+ EXPECT_EQ(filter_string, filter.text());
528+}
529+
530+TEST_F(TestSpreadFilter, InVisibleWithoutText)
531+{
532+ filter.text = "Really, Unity is cool!";
533+ Utils::WaitForTimeoutMSec();
534+ Tick();
535+
536+ ASSERT_TRUE(filter.Visible());
537+
538+ EXPECT_CALL(sig_receiver, TextChanged(""));
539+ filter.text = "";
540+ EXPECT_TRUE(filter.text().empty());
541+ Tick();
542+ EXPECT_FALSE(filter.Visible());
543+}
544+
545+} // anonymous namespace
546+} // spread namespace
547+} // unity namespace
548
549=== modified file 'unity-shared/CMakeLists.txt'
550--- unity-shared/CMakeLists.txt 2014-02-12 07:09:43 +0000
551+++ unity-shared/CMakeLists.txt 2014-02-18 00:59:55 +0000
552@@ -55,6 +55,7 @@
553 ResizingBaseWindow.cpp
554 SearchBar.cpp
555 SearchBarSpinner.cpp
556+ SpreadFilter.cpp
557 StaticCairoText.cpp
558 TextureCache.cpp
559 TextInput.cpp
560
561=== modified file 'unity-shared/PluginAdapter.cpp'
562--- unity-shared/PluginAdapter.cpp 2014-02-18 00:59:55 +0000
563+++ unity-shared/PluginAdapter.cpp 2014-02-18 00:59:55 +0000
564@@ -24,6 +24,7 @@
565 #include "PluginAdapter.h"
566 #include "CompizUtils.h"
567
568+#include <scale/scale.h>
569 #include <NuxCore/Logger.h>
570
571 namespace unity
572@@ -288,7 +289,7 @@
573
574 if (primary_action_)
575 {
576- primary_action_->terminate()(primary_action_, 0, argument);
577+ primary_action_->terminate()(primary_action_, CompAction::StateCancel, argument);
578 return;
579 }
580
581@@ -337,13 +338,8 @@
582 {
583 std::ostringstream sout;
584
585- sout << "any & (";
586-
587 for (auto const& window : windows)
588- {
589- sout << "| xid=" << window << " ";
590- }
591- sout << ")";
592+ sout << "xid=" << window << " | ";
593
594 return sout.str();
595 }
596
597=== modified file 'unity-shared/SearchBar.cpp'
598--- unity-shared/SearchBar.cpp 2013-11-19 18:48:35 +0000
599+++ unity-shared/SearchBar.cpp 2014-02-18 00:59:55 +0000
600@@ -33,8 +33,8 @@
601
602 namespace
603 {
604-const float kExpandDefaultIconOpacity = 1.0f;
605-const int LIVE_SEARCH_TIMEOUT = 40;
606+const float DEFAULT_ICON_OPACITY = 1.0f;
607+const int DEFAULT_LIVE_SEARCH_TIMEOUT = 40;
608 const int SPINNER_TIMEOUT = 100;
609
610 const int SPACE_BETWEEN_SPINNER_AND_TEXT = 5;
611@@ -105,34 +105,19 @@
612 NUX_IMPLEMENT_OBJECT_TYPE(SearchBar);
613
614 SearchBar::SearchBar(NUX_FILE_LINE_DECL)
615- : View(NUX_FILE_LINE_PARAM)
616- , search_hint("")
617- , showing_filters(false)
618- , can_refine_search(false)
619- , show_filter_hint_(true)
620- , expander_view_(nullptr)
621- , show_filters_(nullptr)
622- , last_width_(-1)
623- , last_height_(-1)
624-{
625- Init();
626-}
627-
628-SearchBar::SearchBar(bool show_filter_hint_, NUX_FILE_LINE_DECL)
629- : View(NUX_FILE_LINE_PARAM)
630- , search_hint("")
631- , showing_filters(false)
632- , can_refine_search(false)
633- , show_filter_hint_(show_filter_hint_)
634- , expander_view_(nullptr)
635- , show_filters_(nullptr)
636- , last_width_(-1)
637- , last_height_(-1)
638-{
639- Init();
640-}
641-
642-void SearchBar::Init()
643+ : SearchBar(false)
644+{}
645+
646+SearchBar::SearchBar(bool show_filter_hint, NUX_FILE_LINE_DECL)
647+ : View(NUX_FILE_LINE_PARAM)
648+ , showing_filters(false)
649+ , can_refine_search(false)
650+ , live_search_wait(DEFAULT_LIVE_SEARCH_TIMEOUT)
651+ , show_filter_hint_(show_filter_hint)
652+ , expander_view_(nullptr)
653+ , show_filters_(nullptr)
654+ , last_width_(-1)
655+ , last_height_(-1)
656 {
657 dash::Style& style = dash::Style::Instance();
658 nux::BaseTexture* icon = style.GetSearchMagnifyIcon();
659@@ -199,7 +184,7 @@
660 expand_icon_ = new IconTexture(arrow,
661 arrow->GetWidth(),
662 arrow->GetHeight());
663- expand_icon_->SetOpacity(kExpandDefaultIconOpacity);
664+ expand_icon_->SetOpacity(DEFAULT_ICON_OPACITY);
665 expand_icon_->SetMinimumSize(arrow->GetWidth(), arrow->GetHeight());
666 expand_icon_->SetVisible(false);
667
668@@ -294,10 +279,13 @@
669 font_desc << pango_font_description_get_family(desc) << " " << HINT_LABEL_FONT_STYLE << " " << HINT_LABEL_FONT_SIZE;
670 hint_->SetFont(font_desc.str().c_str());
671
672- font_desc.str("");
673- font_desc.clear();
674- font_desc << pango_font_description_get_family(desc) << " " << SHOW_FILTERS_LABEL_FONT_STYLE << " " << SHOW_FILTERS_LABEL_FONT_SIZE;
675- show_filters_->SetFont(font_desc.str().c_str());
676+ if (show_filter_hint_)
677+ {
678+ font_desc.str("");
679+ font_desc.clear();
680+ font_desc << pango_font_description_get_family(desc) << " " << SHOW_FILTERS_LABEL_FONT_STYLE << " " << SHOW_FILTERS_LABEL_FONT_SIZE;
681+ show_filters_->SetFont(font_desc.str().c_str());
682+ }
683
684 pango_font_description_free(desc);
685 }
686@@ -317,7 +305,7 @@
687 // We don't want to set a new search string on every new character, so we add a sma
688 // timeout to see if the user is typing a sentence. If more characters are added, we
689 // keep restarting the timeout unti the user has actuay paused.
690- live_search_timeout_.reset(new glib::Timeout(LIVE_SEARCH_TIMEOUT));
691+ live_search_timeout_.reset(new glib::Timeout(live_search_wait()));
692 live_search_timeout_->Run(sigc::mem_fun(this, &SearchBar::OnLiveSearchTimeout));
693
694 // Don't animate the spinner immediately, the searches are fast and
695@@ -428,6 +416,9 @@
696 nux::GetPainter().PushPaintLayerStack();
697 }
698
699+ if (!IsFullRedraw())
700+ graphics::ClearGeometry(pango_entry_->GetGeometry());
701+
702 layout_->ProcessDraw(graphics_engine, force_draw);
703
704 if (IsFullRedraw())
705@@ -455,7 +446,7 @@
706
707 void SearchBar::ForceLiveSearch()
708 {
709- live_search_timeout_.reset(new glib::Timeout(LIVE_SEARCH_TIMEOUT));
710+ live_search_timeout_.reset(new glib::Timeout(live_search_wait()));
711 live_search_timeout_->Run(sigc::mem_fun(this, &SearchBar::OnLiveSearchTimeout));
712
713 start_spinner_timeout_.reset(new glib::Timeout(SPINNER_TIMEOUT));
714@@ -466,8 +457,7 @@
715 {
716 start_spinner_timeout_.reset();
717
718- bool is_empty = pango_entry_->im_active() ?
719- false : pango_entry_->GetText() == "";
720+ bool is_empty = pango_entry_->im_active() ? false : pango_entry_->GetText().empty();
721 spinner_->SetState(is_empty ? STATE_READY : STATE_CLEAR);
722 }
723
724@@ -609,14 +599,19 @@
725 .add(GetAbsoluteGeometry())
726 .add("has_focus", pango_entry_->HasKeyFocus())
727 .add("search_string", pango_entry_->GetText())
728- .add("expander-has-focus", expander_view_->HasKeyFocus())
729 .add("showing-filters", showing_filters)
730- .add("filter-label-x", show_filters_->GetAbsoluteX())
731- .add("filter-label-y", show_filters_->GetAbsoluteY())
732- .add("filter-label-width", show_filters_->GetAbsoluteWidth())
733- .add("filter-label-height", show_filters_->GetAbsoluteHeight())
734- .add("filter-label-geo", show_filters_->GetAbsoluteGeometry())
735 .add("im_active", pango_entry_->im_active());
736+
737+ if (show_filter_hint_)
738+ {
739+ introspection
740+ .add("expander-has-focus", expander_view_->HasKeyFocus())
741+ .add("filter-label-x", show_filters_->GetAbsoluteX())
742+ .add("filter-label-y", show_filters_->GetAbsoluteY())
743+ .add("filter-label-width", show_filters_->GetAbsoluteWidth())
744+ .add("filter-label-height", show_filters_->GetAbsoluteHeight())
745+ .add("filter-label-geo", show_filters_->GetAbsoluteGeometry());
746+ }
747 }
748
749 } // namespace unity
750
751=== modified file 'unity-shared/SearchBar.h'
752--- unity-shared/SearchBar.h 2013-09-19 16:44:03 +0000
753+++ unity-shared/SearchBar.h 2014-02-18 00:59:55 +0000
754@@ -63,14 +63,13 @@
755 nux::Property<bool> can_refine_search;
756 nux::ROProperty<bool> im_active;
757 nux::ROProperty<bool> im_preedit;
758+ nux::Property<unsigned> live_search_wait;
759
760 sigc::signal<void> activated;
761 sigc::signal<void, std::string const&> search_changed;
762 sigc::signal<void, std::string const&> live_search_reached;
763
764 private:
765- void Init();
766-
767 void OnFontChanged(GtkSettings* settings, GParamSpec* pspec=NULL);
768 void OnSearchHintChanged();
769
770
771=== added file 'unity-shared/SpreadFilter.cpp'
772--- unity-shared/SpreadFilter.cpp 1970-01-01 00:00:00 +0000
773+++ unity-shared/SpreadFilter.cpp 2014-02-18 00:59:55 +0000
774@@ -0,0 +1,121 @@
775+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
776+/*
777+* Copyright (C) 2014 Canonical Ltd
778+*
779+* This program is free software: you can redistribute it and/or modify
780+* it under the terms of the GNU General Public License version 3 as
781+* published by the Free Software Foundation.
782+*
783+* This program is distributed in the hope that it will be useful,
784+* but WITHOUT ANY WARRANTY; without even the implied warranty of
785+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
786+* GNU General Public License for more details.
787+*
788+* You should have received a copy of the GNU General Public License
789+* along with this program. If not, see <http://www.gnu.org/licenses/>.
790+*
791+* Authored by: Marco Trevisan <marco@ubuntu.com>
792+*/
793+
794+#include "SpreadFilter.h"
795+
796+#include <Nux/HLayout.h>
797+#include "AnimationUtils.h"
798+#include "SearchBar.h"
799+#include "WindowManager.h"
800+
801+namespace unity
802+{
803+namespace spread
804+{
805+namespace
806+{
807+const unsigned FADE_DURATION = 100;
808+const unsigned DEFAULT_SEARCH_WAIT = 300;
809+const nux::Point OFFSET(10, 15);
810+const nux::Size SIZE(620, 42);
811+}
812+
813+Filter::Filter()
814+ : fade_animator_(FADE_DURATION)
815+{
816+ search_bar_ = SearchBar::Ptr(new SearchBar());
817+ search_bar_->SetMinMaxSize(SIZE.width, SIZE.height);
818+ search_bar_->live_search_wait = DEFAULT_SEARCH_WAIT;
819+ text.SetGetterFunction([this] { return search_bar_->search_string(); });
820+ text.SetSetterFunction([this] (std::string const& t) { search_bar_->search_string = t; return false; });
821+ debug::Introspectable::AddChild(search_bar_.GetPointer());
822+
823+ auto layout = new nux::HLayout(NUX_TRACKER_LOCATION);
824+ layout->SetVerticalExternalMargin(0);
825+ layout->SetHorizontalExternalMargin(0);
826+ layout->AddView(search_bar_.GetPointer());
827+
828+ auto const& work_area = WindowManager::Default().GetWorkAreaGeometry(0);
829+ view_window_ = new nux::BaseWindow(GetName().c_str());
830+ view_window_->SetLayout(layout);
831+ view_window_->SetBackgroundColor(nux::color::Transparent);
832+ view_window_->SetWindowSizeMatchLayout(true);
833+ view_window_->ShowWindow(true);
834+ view_window_->PushToFront();
835+ view_window_->SetOpacity(0.0f);
836+ view_window_->SetEnterFocusInputArea(search_bar_.GetPointer());
837+ view_window_->SetInputFocus();
838+ view_window_->SetXY(OFFSET.x + work_area.x, OFFSET.y + work_area.y);
839+ fade_animator_.updated.connect([this] (double opacity) { view_window_->SetOpacity(opacity); });
840+
841+ nux::GetWindowCompositor().SetKeyFocusArea(search_bar_->text_entry());
842+
843+ search_bar_->search_changed.connect([this] (std::string const& search) {
844+ if (!Visible())
845+ animation::StartOrReverse(fade_animator_, animation::Direction::FORWARD);
846+
847+ if (search.empty())
848+ {
849+ text.changed.emit(search);
850+ animation::StartOrReverse(fade_animator_, animation::Direction::BACKWARD);
851+ }
852+ });
853+
854+ search_bar_->live_search_reached.connect([this] (std::string const& search) {
855+ if (!search.empty())
856+ {
857+ text.changed.emit(search);
858+ search_bar_->SetSearchFinished();
859+ }
860+ });
861+}
862+
863+Filter::~Filter()
864+{
865+ nux::GetWindowCompositor().SetKeyFocusArea(nullptr);
866+ nux::GetWindowThread()->RemoveObjectFromLayoutQueue(view_window_.GetPointer());
867+}
868+
869+bool Filter::Visible() const
870+{
871+ return (view_window_->GetOpacity() != 0.0f);
872+}
873+
874+nux::Geometry const& Filter::GetAbsoluteGeometry() const
875+{
876+ return view_window_->GetGeometry();
877+}
878+
879+//
880+// Introspection
881+//
882+std::string Filter::GetName() const
883+{
884+ return "SpreadFilter";
885+}
886+
887+void Filter::AddProperties(debug::IntrospectionData& introspection)
888+{
889+ introspection
890+ .add(GetAbsoluteGeometry())
891+ .add("visible", Visible());
892+}
893+
894+} // namespace spread
895+} // namespace unity
896
897=== added file 'unity-shared/SpreadFilter.h'
898--- unity-shared/SpreadFilter.h 1970-01-01 00:00:00 +0000
899+++ unity-shared/SpreadFilter.h 2014-02-18 00:59:55 +0000
900@@ -0,0 +1,64 @@
901+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
902+/*
903+* Copyright (C) 2014 Canonical Ltd
904+*
905+* This program is free software: you can redistribute it and/or modify
906+* it under the terms of the GNU General Public License version 3 as
907+* published by the Free Software Foundation.
908+*
909+* This program is distributed in the hope that it will be useful,
910+* but WITHOUT ANY WARRANTY; without even the implied warranty of
911+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
912+* GNU General Public License for more details.
913+*
914+* You should have received a copy of the GNU General Public License
915+* along with this program. If not, see <http://www.gnu.org/licenses/>.
916+*
917+* Authored by: Marco Trevisan <marco@ubuntu.com>
918+*/
919+
920+#ifndef UNITYSHELL_SPREAD_FILTER_H
921+#define UNITYSHELL_SPREAD_FILTER_H
922+
923+#include <memory>
924+
925+#include <Nux/Nux.h>
926+#include <Nux/BaseWindow.h>
927+#include <NuxCore/Animation.h>
928+#include "Introspectable.h"
929+
930+namespace unity
931+{
932+class SearchBar;
933+
934+namespace spread
935+{
936+
937+class Filter : public debug::Introspectable, public sigc::trackable
938+{
939+public:
940+ typedef std::shared_ptr<Filter> Ptr;
941+
942+ Filter();
943+ virtual ~Filter();
944+
945+ nux::RWProperty<std::string> text;
946+
947+ bool Visible() const;
948+ nux::Geometry const& GetAbsoluteGeometry() const;
949+
950+protected:
951+ // Introspectable
952+ std::string GetName() const;
953+ void AddProperties(debug::IntrospectionData&);
954+
955+private:
956+ nux::ObjectPtr<SearchBar> search_bar_;
957+ nux::ObjectPtr<nux::BaseWindow> view_window_;
958+ nux::animation::AnimateValue<double> fade_animator_;
959+};
960+
961+} // namespace spread
962+} // namespace unity
963+
964+#endif