Merge lp:~tombeckmann/switchboard/restructure into lp:~elementary-pantheon/switchboard/switchboard
- restructure
- Merge into switchboard
Status: | Rejected |
---|---|
Rejected by: | David Gomes |
Proposed branch: | lp:~tombeckmann/switchboard/restructure |
Merge into: | lp:~elementary-pantheon/switchboard/switchboard |
Diff against target: |
716 lines (+219/-261) 3 files modified
CMakeLists.txt (+2/-1) Switchboard/switchboard-app.vala (+132/-126) Switchboard/switchboard-categoryview.vala (+85/-134) |
To merge this branch: | bzr merge lp:~tombeckmann/switchboard/restructure |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Danielle Foré | Disapprove | ||
Review via email: mp+127597@code.launchpad.net |
Commit message
Description of the change
Restructure the UI quite a bit to fit to Harvey's mockup http://
Adjust the coding style in quite some places
Readd clutter based animations (this time stable ones as far as tested)
Tom Beckmann (tombeckmann) wrote : | # |
Danielle Foré (danrabbit) wrote : | # |
* The vertical layout doesn't really make sense to me.
* Displays are typically landscape, not portrait. This layout is going to cause unecessary amounts of scrolling and doesn't take advantage of the width of the display.
* Filtering just looks really unclear. Not a fan.
* The animation here isn't meaningful to me. It says "I'm opening a new window" and then it doesn't. The swipe animation we had before made more sense because it says "I'm changing context" and it gives the user a path to follow back. Additionally, the swipe animation implies a multi-touch gesture and I think it would make sense to have a consistent back/forward swipe gesture in elementary future.
* At this point in the development cycle, we need to not introduce such drastic changes that could introduce new problems. The focus should be on stability, the time for new features and experimental was supposed to end with feature freeze.
Harvey Cabaguio (harveycabaguio) wrote : | # |
> * The vertical layout doesn't really make sense to me.
> * Displays are typically landscape, not portrait. This layout is going to
> cause unecessary amounts of scrolling and doesn't take advantage of the width
> of the display.
There's no need to take advantage of the width of the display, the items displayed on Switchboard are essentially lists and a vertical list is more efficient than a horizontal one.
And there is no unnecessary amounts of scrolling, I've listed all the plugs we're using right now and it even fits a small 800x600 screen resolution.
Unmerged revisions
- 318. By Tom Beckmann
-
Restructure the UI, rewrite big parts of code, adjust style, readd clutter based animations
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2012-09-22 12:29:22 +0000 |
3 | +++ CMakeLists.txt 2012-10-02 21:23:23 +0000 |
4 | @@ -61,7 +61,7 @@ |
5 | # |
6 | |
7 | find_package(PkgConfig) |
8 | -pkg_check_modules(DEPS REQUIRED granite gio-2.0 gee-1.0 unity) |
9 | +pkg_check_modules(DEPS REQUIRED granite gio-2.0 gee-1.0 unity clutter-gtk-1.0) |
10 | add_definitions(${DEPS_CFLAGS}) |
11 | link_directories(${DEPS_LIBRARY_DIRS}) |
12 | find_package(Vala REQUIRED) |
13 | @@ -79,6 +79,7 @@ |
14 | gee-1.0 |
15 | gio-2.0 |
16 | unity |
17 | + clutter-gtk-1.0 |
18 | CUSTOM_VAPIS |
19 | vapi/config.vapi |
20 | ) |
21 | |
22 | === modified file 'Switchboard/switchboard-app.vala' |
23 | --- Switchboard/switchboard-app.vala 2012-09-23 22:43:12 +0000 |
24 | +++ Switchboard/switchboard-app.vala 2012-10-02 21:23:23 +0000 |
25 | @@ -63,10 +63,11 @@ |
26 | Gtk.ScrolledWindow scrollable_view; |
27 | Switchboard.CategoryView category_view; |
28 | |
29 | + GtkClutter.Embed clutter; |
30 | + GtkClutter.Actor actor; |
31 | + |
32 | // Plug data |
33 | - bool socket_shown; |
34 | - Gee.HashMap<string, string> current_plug = new Gee.HashMap<string, string>(); |
35 | - Gee.HashMap<string, string>[] plugs; |
36 | + Plug current_plug; |
37 | |
38 | string[] plug_places = {"/usr/share/plugs/", |
39 | "/usr/lib/plugs/", |
40 | @@ -78,7 +79,7 @@ |
41 | |
42 | public SwitchboardApp () { |
43 | |
44 | - main_window = new Gtk.Window(); |
45 | + main_window = new Gtk.Window (); |
46 | |
47 | // Set up defaults |
48 | main_window.title = APP_TITLE; |
49 | @@ -108,12 +109,8 @@ |
50 | }); |
51 | main_window.add_accel_group (accel_group); |
52 | |
53 | - // ??? Why? |
54 | - current_plug["title"] = ""; |
55 | - current_plug["executable"] = ""; |
56 | - |
57 | category_view = new Switchboard.CategoryView (); |
58 | - category_view.plug_selected.connect ((title, executable, @extern) => load_plug (title, executable, @extern)); |
59 | + category_view.plug_selected.connect ((title, executable, external) => load_plug (title, executable, external)); |
60 | category_view.margin_top = 12; |
61 | |
62 | scrollable_view = new Gtk.ScrolledWindow (null, null); |
63 | @@ -129,27 +126,30 @@ |
64 | grid.attach (alert_view, 0, 2, 1, 1); |
65 | |
66 | main_window.add (grid); |
67 | + |
68 | scrollable_view.add_with_viewport (category_view); |
69 | scrollable_view.set_vexpand (true); |
70 | - grid.attach (scrollable_view, 0, 1, 1, 1); |
71 | + |
72 | + var bg = main_window.get_style_context ().get_background_color (Gtk.StateFlags.NORMAL); |
73 | + |
74 | + clutter = new GtkClutter.Embed (); |
75 | + actor = new GtkClutter.Actor (); |
76 | + actor.scale_gravity = Clutter.Gravity.CENTER; |
77 | + actor.add_constraint (new Clutter.BindConstraint (clutter.get_stage (), |
78 | + Clutter.BindCoordinate.WIDTH, 0)); |
79 | + actor.add_constraint (new Clutter.BindConstraint (clutter.get_stage (), |
80 | + Clutter.BindCoordinate.HEIGHT, 0)); |
81 | + (actor.get_widget () as Gtk.Container).add (scrollable_view); |
82 | + |
83 | + clutter.get_stage ().add_child (actor); |
84 | + clutter.get_stage ().background_color = {(uint8)(bg.red * 255), (uint8)(bg.green * 255), (uint8)(bg.blue * 255)}; |
85 | + |
86 | + grid.attach (clutter, 0, 1, 1, 1); |
87 | |
88 | main_window.set_application (this); |
89 | main_window.show_all (); |
90 | |
91 | - foreach (var label in category_view.category_labels.values) |
92 | - label.hide (); |
93 | - foreach (var view in category_view.category_views.values) |
94 | - view.hide (); |
95 | - |
96 | - alert_view.hide(); |
97 | - |
98 | - loading = new Gtk.Spinner (); |
99 | - loading.set_vexpand(true); |
100 | - loading.halign = Gtk.Align.CENTER; |
101 | - loading.valign = Gtk.Align.CENTER; |
102 | - loading.width_request = 72; |
103 | - loading.height_request = 72; |
104 | - loading.start (); |
105 | + alert_view.hide (); |
106 | |
107 | grid.attach (socket, 0, 1, 1, 1); |
108 | socket.hide (); |
109 | @@ -158,13 +158,6 @@ |
110 | |
111 | var any_plugs = false; |
112 | |
113 | - socket.plug_added.connect (() => { |
114 | - if (loading.visible) { |
115 | - loading.hide (); |
116 | - socket.show_all (); |
117 | - } |
118 | - }); |
119 | - |
120 | foreach (string place in plug_places) |
121 | if (enumerate_plugs (place)) |
122 | any_plugs = true; |
123 | @@ -174,27 +167,24 @@ |
124 | search_box.sensitive = false; |
125 | } |
126 | |
127 | + actor.get_widget ().show_all (); |
128 | + |
129 | bool found = false; |
130 | if (plug_to_open != null) { |
131 | - foreach (var plug in plugs) { |
132 | - if (plug["id"] == plug_to_open) { |
133 | - load_plug (plug["title"], plug["exec"], plug["extern"] == "1"); |
134 | + foreach (var child in category_view.get_children ()) { |
135 | + if (child is Plug && (child as Plug).id == plug_to_open) { |
136 | + var plug = child as Plug; |
137 | + load_plug (plug.title, plug.exec, plug.external); |
138 | found = true; |
139 | } |
140 | } |
141 | + |
142 | if (!found) { |
143 | critical ("Couldn't find %s among the loaded settings.", plug_to_open); |
144 | } |
145 | } |
146 | - |
147 | - foreach (var store in category_view.category_store.values) { |
148 | - store.foreach ((model, path, iter) => { |
149 | - store.set_value (iter, 3, true); |
150 | - return false; |
151 | - }); |
152 | - } |
153 | } |
154 | - |
155 | + |
156 | void shut_down () { |
157 | plug_closed (); |
158 | } |
159 | @@ -210,36 +200,42 @@ |
160 | category_view.hide (); |
161 | } |
162 | |
163 | - public void load_plug (string title, string executable, bool @extern) { |
164 | - debug ("Selected plug: title %s | executable %s", title, executable); |
165 | + // we cant pass a Plug to this method as its serialization via dbus is not supported |
166 | + public void load_plug (string title, string executable, bool external) { |
167 | + |
168 | + Plug plug = null; |
169 | + foreach (var child in category_view.get_children ()) { |
170 | + if (child is Plug && (child as Plug).exec == executable) |
171 | + plug = child as Plug; |
172 | + } |
173 | + |
174 | + if (plug == null) |
175 | + error ("Could not load plug %s, not loaded", title); |
176 | + |
177 | + debug ("Selected plug: title %s | executable %s", plug.title, plug.exec); |
178 | |
179 | - // Launch plug's executable |
180 | - if (current_plug["title"] != title || !socket.visible) { |
181 | - try { |
182 | - // The plug is already selected |
183 | - debug(_("Closing plug \"%s\" in Switchboard controller..."), current_plug["title"]); |
184 | + try { |
185 | + // The plug is already selected |
186 | + if (current_plug != null) { |
187 | + debug(_("Closing plug \"%s\" in Switchboard controller"), current_plug.title); |
188 | plug_closed (); |
189 | - |
190 | - string[] cmd_exploded = (executable!=null)?executable.split (" "):null; |
191 | - GLib.Process.spawn_async (File.new_for_path (cmd_exploded[0]).get_parent (). |
192 | - get_path (), cmd_exploded, null, SpawnFlags.SEARCH_PATH, null, null); |
193 | - |
194 | - // ensure the button is sensitive; it might be the first plug loaded |
195 | - if (!@extern) { |
196 | - navigation_button.set_sensitive(true); |
197 | - navigation_button.stock_id = Gtk.Stock.HOME; |
198 | - current_plug["title"] = title; |
199 | - current_plug["executable"] = executable; |
200 | - } |
201 | - } catch { warning(_("Failed to launch plug: title %s | executable %s"), title, executable); } |
202 | - } else { |
203 | - navigation_button.set_sensitive(true); |
204 | - navigation_button.stock_id = Gtk.Stock.HOME; |
205 | - } |
206 | + } |
207 | + |
208 | + string[] cmd_exploded = (plug.exec != null) ? plug.exec.split (" ") : null; |
209 | + GLib.Process.spawn_async (File.new_for_path (cmd_exploded[0]).get_parent (). |
210 | + get_path (), cmd_exploded, null, SpawnFlags.SEARCH_PATH, null, null); |
211 | + |
212 | + // ensure the button is sensitive; it might be the first plug loaded |
213 | + if (!plug.external) { |
214 | + navigation_button.set_sensitive (true); |
215 | + navigation_button.stock_id = Gtk.Stock.HOME; |
216 | + current_plug = plug; |
217 | + } |
218 | + } catch { warning(_("Failed to launch plug: title %s | executable %s"), plug.title, plug.exec); } |
219 | |
220 | - if (!@extern) { |
221 | + if (!plug.external) { |
222 | switch_to_socket (); |
223 | - main_window.title = @"$APP_TITLE - $title"; |
224 | + main_window.title = @"$APP_TITLE - " + plug.title; |
225 | } |
226 | } |
227 | |
228 | @@ -254,35 +250,34 @@ |
229 | switch_to_icons(); |
230 | navigation_button.stock_id = Gtk.Stock.GO_BACK; |
231 | } |
232 | - else { |
233 | - load_plug (current_plug["title"], current_plug["executable"], current_plug["extern"] == "1"); |
234 | + else if (current_plug != null) { |
235 | + load_plug (current_plug.title, current_plug.exec, current_plug.external); |
236 | navigation_button.stock_id = Gtk.Stock.HOME; |
237 | } |
238 | } |
239 | |
240 | // Switches to the socket view |
241 | void switch_to_socket () { |
242 | - |
243 | - socket_shown = true; |
244 | search_box.sensitive = false; |
245 | |
246 | - category_view.hide (); |
247 | - loading.show_all (); |
248 | + actor.animate (Clutter.AnimationMode.EASE_IN_CUBIC, 300, scale_x : 2.0f, scale_y : 2.0f, opacity : 0). |
249 | + completed.connect (() => { |
250 | + clutter.hide (); |
251 | + socket.show_all (); |
252 | + }); |
253 | } |
254 | |
255 | // Switches back to the icons |
256 | bool switch_to_icons () { |
257 | socket.hide (); |
258 | - loading.hide (); |
259 | - category_view.show (); |
260 | - |
261 | - socket_shown = false; |
262 | + clutter.show (); |
263 | + actor.animate (Clutter.AnimationMode.EASE_IN_CUBIC, 250, scale_x : 1.0f, scale_y : 1.0f, opacity : 255); |
264 | |
265 | // Reset state |
266 | reset_title (); |
267 | - search_box.set_text(""); |
268 | - search_box.sensitive = count_plugs() > 0; |
269 | - progress_label.set_text(""); |
270 | + search_box.set_text (""); |
271 | + search_box.sensitive = count_plugs () > 0; |
272 | + progress_label.set_text (""); |
273 | progress_bar.fraction = 0.0; |
274 | progress_toolitem.visible = false; |
275 | |
276 | @@ -298,36 +293,42 @@ |
277 | |
278 | // <keyfile's absolute path, keyfile's directory> |
279 | List<string> keyfiles = find_plugs (plug_root_dir); |
280 | - if (keyfiles.length() == 0) { |
281 | + if (keyfiles.length () == 0) { |
282 | return false; |
283 | } else { |
284 | foreach (string keyfile in keyfiles) { |
285 | KeyFile kf = new KeyFile(); |
286 | |
287 | - string head = File.new_for_path(keyfile).get_basename(); |
288 | - string parent = File.new_for_path(keyfile).get_parent().get_path(); |
289 | + string head = File.new_for_path (keyfile).get_basename (); |
290 | + string parent = File.new_for_path (keyfile).get_parent ().get_path (); |
291 | |
292 | - Gee.HashMap<string, string> plug = new Gee.HashMap<string, string> (); |
293 | - try { kf.load_from_file(keyfile, KeyFileFlags.NONE); |
294 | - } catch (Error e) { warning("Couldn't load this keyfile, %s (path: %s)", e.message, keyfile); } |
295 | - plug["id"] = kf.get_start_group(); |
296 | - try { |
297 | - var exec = kf.get_string (head, "exec"); |
298 | - //if a path starts with a double slash, we take it as an absolute path |
299 | + string title="", icon="", category="", exec="", id=""; |
300 | + bool external=false; |
301 | + |
302 | + try { |
303 | + kf.load_from_file (keyfile, KeyFileFlags.NONE); |
304 | + } catch (Error e) { warning ("Couldn't load this keyfile, %s (path: %s)", e.message, keyfile); } |
305 | + |
306 | + id = kf.get_start_group (); |
307 | + |
308 | + try { |
309 | + exec = kf.get_string (head, "exec"); |
310 | + //if a path starts with a double slash, we take it as an absolute path to an extern plug |
311 | if (exec.substring (0, 2) == "//") { |
312 | exec = exec.substring (1); |
313 | - plug["extern"] = "1"; |
314 | + external = true; |
315 | } else { |
316 | - exec = Path.build_filename(parent, exec); |
317 | - plug["extern"] = "0"; |
318 | + exec = Path.build_filename (parent, exec); |
319 | + external = false; |
320 | } |
321 | - |
322 | - plug["exec"] = exec; |
323 | } catch (Error e) { warning("Couldn't read exec field in file %s, %s", keyfile, e.message); } |
324 | - try { plug["icon"] = kf.get_string (head, "icon"); |
325 | + |
326 | + try { |
327 | + icon = kf.get_string (head, "icon"); |
328 | } catch (Error e) { warning("Couldn't read icon field in file %s, %s", keyfile, e.message); } |
329 | + |
330 | try { |
331 | - plug["title"] = kf.get_locale_string (head, "title"); |
332 | + title = kf.get_locale_string (head, "title"); |
333 | string? textdomain = null; |
334 | foreach (var domain_key in SUPPORTED_GETTEXT_DOMAINS_KEYS) { |
335 | if (kf.has_key (head, domain_key)) { |
336 | @@ -336,32 +337,34 @@ |
337 | } |
338 | } |
339 | if (textdomain != null) |
340 | - plug["title"] = GLib.dgettext (textdomain, plug["title"]).dup (); |
341 | + title = GLib.dgettext (textdomain, title).dup (); |
342 | } catch (Error e) { warning("Couldn't read title field in file %s, %s", keyfile, e.message); } |
343 | - try { plug["category"] = kf.get_string (head, "category"); |
344 | - } catch { |
345 | - plug["category"] = "other"; |
346 | - } |
347 | - category_view.add_plug (plug); |
348 | - plugs += plug; |
349 | + |
350 | + try { |
351 | + category = kf.get_string (head, "category"); |
352 | + } catch { category = "other"; } |
353 | + |
354 | + category_view.add_plug (new Plug (title, exec, external, icon, category, id)); |
355 | } |
356 | + |
357 | return true; |
358 | } |
359 | } |
360 | |
361 | // Checks if the file is a .plug file |
362 | bool is_plug_file (string filename) { |
363 | - return (filename.down().has_suffix(".plug")); |
364 | + return filename.down ().has_suffix (".plug"); |
365 | } |
366 | |
367 | // Find all .plug files |
368 | - List<string> find_plugs (string path, List<string>? keyfiles_list = null) { |
369 | + List<string>? find_plugs (string path, List<string>? keyfiles_list = null) { |
370 | + |
371 | List<string>? keyfiles; |
372 | if(keyfiles_list == null) { |
373 | keyfiles = new List<string> (); |
374 | } else { |
375 | keyfiles = new List<string> (); |
376 | - foreach(var keyfile in keyfiles_list) { |
377 | + foreach (var keyfile in keyfiles_list) { |
378 | keyfiles.append(keyfile); |
379 | } |
380 | } |
381 | @@ -370,18 +373,19 @@ |
382 | if (!directory.query_exists ()) { |
383 | return null; |
384 | } |
385 | + |
386 | try { |
387 | var enumerator = directory.enumerate_children ( |
388 | FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_TYPE, 0); |
389 | FileInfo file_info; |
390 | + |
391 | while ((file_info = enumerator.next_file ()) != null) { |
392 | - string file_path = Path.build_filename(path, file_info.get_name()); |
393 | - if (file_info.get_file_type() == GLib.FileType.REGULAR && |
394 | - is_plug_file(file_info.get_name())) { |
395 | - keyfiles.append(file_path); |
396 | - } else if(file_info.get_file_type() == GLib.FileType.DIRECTORY) { |
397 | - keyfiles = find_plugs(file_path, keyfiles); |
398 | - } |
399 | + string file_path = Path.build_filename (path, file_info.get_name ()); |
400 | + |
401 | + if (file_info.get_file_type() == GLib.FileType.REGULAR && is_plug_file (file_info.get_name())) |
402 | + keyfiles.append (file_path); |
403 | + else if(file_info.get_file_type () == GLib.FileType.DIRECTORY) |
404 | + keyfiles = find_plugs (file_path, keyfiles); |
405 | } |
406 | } catch { warning(_(@"Unable to iterate over enumerated plug directory \"$path\"'s contents")); } |
407 | |
408 | @@ -391,10 +395,12 @@ |
409 | // Counts how many plugs exist at the moment |
410 | int count_plugs () { |
411 | |
412 | - uint count = 0; |
413 | - foreach (string place in plug_places) |
414 | - count += find_plugs (place).length (); |
415 | - return (int) count; |
416 | + int count = 0; |
417 | + foreach (var child in category_view.get_children ()) |
418 | + if (child is Plug) |
419 | + count ++; |
420 | + |
421 | + return count; |
422 | } |
423 | |
424 | /* |
425 | @@ -463,7 +469,7 @@ |
426 | rspace.set_expand(true); |
427 | |
428 | // Progressbar |
429 | - var progress_vbox = new Gtk.VBox (true, 0); |
430 | + var progress_vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); |
431 | progress_label = new Gtk.Label (""); |
432 | progress_label.set_use_markup(true); |
433 | progress_bar = new Gtk.ProgressBar (); |
434 | @@ -477,18 +483,19 @@ |
435 | search_box = new Granite.Widgets.SearchBar (_("Search Settings")); |
436 | search_box.primary_icon_stock = "gtk-find"; |
437 | search_box.activate.connect(() => search_box_activated()); |
438 | + search_box.margin_right = 6; |
439 | search_box.changed.connect(() => { |
440 | category_view.filter_plugs(search_box.get_text (), this); |
441 | - search_box_text_changed(); |
442 | + search_box_text_changed (); |
443 | }); |
444 | - search_box.sensitive = (count_plugs () > 0); |
445 | + |
446 | var find_toolitem = new Gtk.ToolItem (); |
447 | - find_toolitem.add(search_box); |
448 | + find_toolitem.add (search_box); |
449 | |
450 | // Nav button |
451 | navigation_button = new Gtk.ToolButton.from_stock(Gtk.Stock.GO_BACK); |
452 | navigation_button.clicked.connect (handle_navigation_button_clicked); |
453 | - navigation_button.set_sensitive(false); |
454 | + navigation_button.set_sensitive (false); |
455 | |
456 | // Add everything to the toolbar |
457 | toolbar.insert (navigation_button, -1); |
458 | @@ -496,7 +503,6 @@ |
459 | toolbar.insert (progress_toolitem, -1); |
460 | toolbar.insert (rspace, -1); |
461 | toolbar.insert (find_toolitem, -1); |
462 | - toolbar.insert (create_appmenu (new Gtk.Menu ()), -1); |
463 | } |
464 | |
465 | public override void activate () {} |
466 | @@ -516,7 +522,7 @@ |
467 | message(_(@"Version: $VERSION")); |
468 | message(_("Report any issues/bugs you mind find to lp:switchboard")); |
469 | |
470 | - Gtk.init (ref args); |
471 | + GtkClutter.init (ref args); |
472 | |
473 | var context = new OptionContext(""); |
474 | context.add_main_entries(entries, "switchboard "); |
475 | |
476 | === modified file 'Switchboard/switchboard-categoryview.vala' |
477 | --- Switchboard/switchboard-categoryview.vala 2012-09-22 17:33:26 +0000 |
478 | +++ Switchboard/switchboard-categoryview.vala 2012-10-02 21:23:23 +0000 |
479 | @@ -17,152 +17,103 @@ |
480 | |
481 | namespace Switchboard { |
482 | |
483 | + public class Plug : Gtk.EventBox { |
484 | + |
485 | + public string title; |
486 | + public string exec; |
487 | + public bool external; |
488 | + public string category; |
489 | + public string id; |
490 | + |
491 | + public signal void selected (); |
492 | + |
493 | + Gtk.Box box; |
494 | + |
495 | + public Plug (string _title, string _exec, bool _extern, string _icon, string _category, string _id) |
496 | + { |
497 | + title = _title; |
498 | + exec = _exec; |
499 | + external = _extern; |
500 | + id = _id; |
501 | + category = _category; |
502 | + |
503 | + margin = 6; |
504 | + |
505 | + Gdk.Pixbuf icon_pixbuf = null; |
506 | + try { |
507 | + icon_pixbuf = Gtk.IconTheme.get_default ().load_icon (_icon, 32, Gtk.IconLookupFlags.GENERIC_FALLBACK); |
508 | + } catch { warning (_("Unable to load plug %s's icon: %s"), title, _icon); } |
509 | + |
510 | + var lbl = new Gtk.Label (title); |
511 | + lbl.halign = Gtk.Align.START; |
512 | + lbl.ellipsize = Pango.EllipsizeMode.END; |
513 | + box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); |
514 | + |
515 | + box.pack_start (new Gtk.Image.from_pixbuf (icon_pixbuf), false); |
516 | + box.pack_start (lbl); |
517 | + |
518 | + add (box); |
519 | + } |
520 | + |
521 | + public override bool button_press_event (Gdk.EventButton event) { |
522 | + selected (); |
523 | + |
524 | + return false; |
525 | + } |
526 | + } |
527 | + |
528 | public class CategoryView : Gtk.Grid { |
529 | |
530 | - public Gee.HashMap<string, Gtk.Grid> category_labels = new Gee.HashMap<string, Gtk.Grid> (); |
531 | - public Gee.HashMap<string, Gtk.ListStore> category_store = new Gee.HashMap<string, Gtk.ListStore> (); |
532 | - public Gee.HashMap<string, Gtk.IconView> category_views = new Gee.HashMap<string, Gtk.IconView> (); |
533 | - Gtk.IconTheme theme = Gtk.IconTheme.get_default (); |
534 | - |
535 | - public signal void plug_selected (string title, string executable, bool @extern); |
536 | + public signal void plug_selected (string title, string executable, bool external); |
537 | + |
538 | string [] category_ids = { "personal", "hardware", "network", "system" }; |
539 | string [] category_names = { N_("Personal"), N_("Hardware"), N_("Network and Wireless"), N_("System") }; |
540 | + int [] current_indices = {1, 1, 1, 1}; |
541 | |
542 | public CategoryView () { |
543 | - for (int i = 0; i < category_ids.length; i++) { |
544 | - var store = new Gtk.ListStore (5, typeof (Gdk.Pixbuf), typeof (string), |
545 | - typeof(string), typeof(bool), typeof(string)); |
546 | - store.set_sort_column_id (1, Gtk.SortType.ASCENDING); |
547 | - |
548 | - var category_label = new Gtk.Label ("<big><b>" + _(category_names[i]) + "</b></big>"); |
549 | - category_label.margin_left = 12; |
550 | - var filtered = new Gtk.TreeModelFilter(store, null); |
551 | - filtered.set_visible_column(3); |
552 | - filtered.refilter(); |
553 | - |
554 | - var category_plugs = new Gtk.IconView.with_model (filtered); |
555 | - category_plugs.set_item_width(96); |
556 | - category_plugs.set_text_column (1); |
557 | - category_plugs.set_pixbuf_column (0); |
558 | - category_plugs.set_hexpand (true); |
559 | - category_plugs.selection_changed.connect(() => on_selection_changed(category_plugs, filtered)); |
560 | - |
561 | - (category_plugs.get_cells ().nth_data (0) as Gtk.CellRendererText).wrap_mode = Pango.WrapMode.WORD; |
562 | - (category_plugs.get_cells ().nth_data (0) as Gtk.CellRendererText).ellipsize_set = true; |
563 | - |
564 | - var bg_css = new Gtk.CssProvider (); |
565 | - try { |
566 | - bg_css.load_from_data ("*{background-color:@background_color;}", -1); |
567 | - } catch (Error e) { warning (e.message); } |
568 | - category_plugs.get_style_context ().add_provider (bg_css, 20000); |
569 | - category_label.xalign = (float) 0.02; |
570 | - |
571 | - var grid = new Gtk.Grid (); |
572 | - category_label.use_markup = true; |
573 | - |
574 | - // Always add a Seperator |
575 | - var h_separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); |
576 | - h_separator.set_hexpand (true); |
577 | - grid.attach (category_label, 0, 0, 1, 1); |
578 | - grid.attach (h_separator, 1, 0, 1, 1); // expand, fill, padding´ |
579 | - |
580 | - grid.attach(category_plugs, 0, 1, 2, 1); |
581 | - |
582 | - category_labels[category_ids[i]] = grid; |
583 | - category_store[category_ids[i]] = store; |
584 | - category_views[category_ids[i]] = category_plugs; |
585 | - |
586 | - attach (grid, 0, i, 1, 1); |
587 | + |
588 | + column_homogeneous = true; |
589 | + margin = 12; |
590 | + |
591 | + for (var i = 0;i < category_names.length; i++) { |
592 | + var lbl = new Gtk.Label ("<b><big>"+category_names[i]+"</big></b>"); |
593 | + lbl.halign = Gtk.Align.START; |
594 | + lbl.use_markup = true; |
595 | + attach (lbl, i, 0, 1, 1); |
596 | } |
597 | - } |
598 | - |
599 | - public void add_plug (Gee.HashMap<string, string> plug) { |
600 | - |
601 | - Gtk.TreeIter root; |
602 | - string plug_down = plug["category"].down(); |
603 | - |
604 | - if (!(plug_down in category_ids)) { |
605 | - warning (_("Keyfile \"%s\" contains an invalid category: \"%s\", and will not be added"), |
606 | - plug["title"], plug["category"]); |
607 | - return; |
608 | - } |
609 | - |
610 | - Gdk.Pixbuf icon_pixbuf = null; |
611 | - try { |
612 | - icon_pixbuf = theme.load_icon (plug["icon"], 32, Gtk.IconLookupFlags.GENERIC_FALLBACK); |
613 | - } catch { |
614 | - warning(_("Unable to load plug %s's icon: %s"), plug["title"], plug["icon"]); |
615 | - return; // FIXME: if we get no icon, we probably dont want that one.. |
616 | - } |
617 | - category_store[plug_down].append(out root); |
618 | - |
619 | - category_store[plug_down].set(root, 0, icon_pixbuf, 1, plug["title"], 2, plug["exec"], |
620 | - 3, true, 4, plug["extern"]); |
621 | - category_labels[plug_down].show_all (); |
622 | - category_views[plug_down].show_all (); |
623 | - |
624 | - } |
625 | - |
626 | - public void filter_plugs (string filter, SwitchboardApp switchboard) { |
627 | |
628 | - var any_found = false; |
629 | - foreach (string category in category_ids) { |
630 | - |
631 | - var store = category_store[category]; |
632 | - var container = category_labels[category]; |
633 | - |
634 | - int shown = 0; |
635 | - |
636 | - store.foreach((model, path, iter) => { |
637 | - string title; |
638 | - |
639 | - store.get (iter, 1, out title); |
640 | - |
641 | - if (filter.down () in title.down ()) { |
642 | - store.set_value (iter, 3, true); |
643 | - shown ++; |
644 | - } else { |
645 | - store.set_value (iter, 3, false); |
646 | + } |
647 | + |
648 | + public void add_plug (Plug plug) { |
649 | + string plug_down = plug.category.down (); |
650 | + |
651 | + int column = -1; |
652 | + for (var i=0;i<category_ids.length; i++) { |
653 | + if (plug_down == category_ids[i]) { |
654 | + column = i; |
655 | + break; |
656 | } |
657 | - |
658 | - return false; |
659 | - }); |
660 | - |
661 | - if (shown == 0) { |
662 | - container.hide (); |
663 | - } else { |
664 | - any_found = true; |
665 | - container.show (); |
666 | } |
667 | - } |
668 | - if (!any_found) { |
669 | - switchboard.show_alert(_("No settings found"), _("Try changing your search terms"), Gtk.MessageType.INFO); |
670 | - } else { |
671 | - switchboard.hide_alert(); |
672 | - } |
673 | + |
674 | + if (column == -1) |
675 | + error (_("Keyfile \"%s\" contains an invalid category: \"%s\", and will not be added"), |
676 | + plug.title, plug.category); |
677 | + |
678 | + attach (plug, column, current_indices[column], 1, 1); |
679 | + current_indices[column]++; |
680 | + |
681 | + plug.selected.connect (() => plug_selected (plug.title, plug.exec, plug.external)); |
682 | } |
683 | |
684 | - private void on_selection_changed (Gtk.IconView view, Gtk.TreeModelFilter store) { |
685 | - |
686 | - GLib.Value title; |
687 | - GLib.Value executable; |
688 | - GLib.Value @extern; |
689 | - Gtk.TreeIter selected_plug; |
690 | - |
691 | - var selected = view.get_selected_items (); |
692 | - var item = selected.nth_data(0); |
693 | - |
694 | - if (item == null) |
695 | - return; |
696 | - |
697 | - store.get_iter (out selected_plug, item); |
698 | - store.get_value (selected_plug, 1, out title); |
699 | - store.get_value (selected_plug, 2, out executable); |
700 | - store.get_value (selected_plug, 4, out @extern); |
701 | - |
702 | - plug_selected (title.get_string(), executable.get_string(), @extern.get_string () == "1"); |
703 | - |
704 | - view.unselect_path (item); |
705 | + public void filter_plugs (string filter, SwitchboardApp switchboard) { |
706 | + var searching = filter.down (); |
707 | + |
708 | + foreach (var child in get_children ()) { |
709 | + if (!(child is Plug)) |
710 | + continue; |
711 | + |
712 | + child.visible = searching in (child as Plug).title.down (); |
713 | + } |
714 | } |
715 | } |
716 | } |
Also see this video https:/ /www.youtube. com/watch? v=Sf6XwTPNZO4 of the animations