Merge lp:~3v1n0/unity/spread-filter into lp:unity
- spread-filter
- Merge into trunk
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 |
Related bugs: |
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://
Actual implementation: http://
Tests added, it has a soft-dependency on lp:~3v1n0/compiz/scale-improvements to properly work.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:3685
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Brandon Schaefer (brandontschaefer) wrote : | # |
LGTM
Preview Diff
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 |
Looks very nice :).