Merge lp:~julien-spautz/switchboard-plug-startup-applications/new-design into lp:switchboard-plug-startup-applications

Proposed by Julien Spautz
Status: Merged
Merged at revision: 50
Proposed branch: lp:~julien-spautz/switchboard-plug-startup-applications/new-design
Merge into: lp:switchboard-plug-startup-applications
Diff against target: 1312 lines (+430/-627)
8 files modified
src/Backend/KeyFile.vala (+83/-32)
src/Backend/KeyFileFactory.vala (+4/-23)
src/CMakeLists.txt (+7/-9)
src/Dialogs/AppChooser.vala (+142/-188)
src/Plug.vala (+14/-37)
src/Widgets/Editor.vala (+0/-102)
src/Widgets/List.vala (+180/-175)
src/Widgets/Toolbar.vala (+0/-61)
To merge this branch: bzr merge lp:~julien-spautz/switchboard-plug-startup-applications/new-design
Reviewer Review Type Date Requested Status
Julien Spautz Pending
Review via email: mp+214391@code.launchpad.net

Description of the change

Implemented new design, now using Gtk.Popover and Gtk.ListBox requiring Gtk+ 3.12

Simplified interface by removing the editor pane because it's kind of useless, apps can still be added and removed, custom commands also still work.

To post a comment you must log in.
47. By Julien Spautz

make label insensitive when app is inactive

48. By Julien Spautz

rewrote AppChooser dialog using gtk.listbox

49. By Julien Spautz

remove gobject-style construction

50. By Julien Spautz

simplify KeyFileFactory and hide popover instead of destroying and recreating every time

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Backend/KeyFile.vala'
2--- src/Backend/KeyFile.vala 2014-03-22 11:07:44 +0000
3+++ src/Backend/KeyFile.vala 2014-04-07 13:03:25 +0000
4@@ -18,13 +18,15 @@
5 ***/
6
7 /**
8- * Stores information about an app found in it's .desktop file
9+ * Stores information about an app found in it's .desktop file
10 * and allows us to modify it.
11+ * http://standards.freedesktop.org/desktop-entry-spec/latest/index.html
12 */
13 public class Pantheon.Startup.Backend.KeyFile : GLib.Object {
14
15 const int ICON_SIZE = 48;
16 const string FALLBACK_ICON = "application-default-icon";
17+ const string CUSTOM_COMMAND_NAME = _("Custom Command");
18
19 const string KEY_NAME = KeyFileDesktop.KEY_NAME;
20 const string KEY_COMMAND = KeyFileDesktop.KEY_EXEC;
21@@ -33,7 +35,10 @@
22 const string KEY_ACTIVE = "X-GNOME-Autostart-enabled";
23 const string KEY_TYPE = KeyFileDesktop.KEY_TYPE;
24 const string KEY_NO_DISPLAY = KeyFileDesktop.KEY_NO_DISPLAY;
25-
26+ const string KEY_HIDDEN = KeyFileDesktop.KEY_HIDDEN;
27+ const string KEY_NOT_SHOW_IN = KeyFileDesktop.KEY_NOT_SHOW_IN;
28+ const string KEY_ONLY_SHOW_IN = KeyFileDesktop.KEY_ONLY_SHOW_IN;
29+
30 public string name {
31 owned get { return get_key (KEY_NAME); }
32 set { set_key (KEY_NAME, value); }
33@@ -55,44 +60,54 @@
34 }
35
36 public bool active {
37- get { return get_key (KEY_ACTIVE) != "false"; }
38- set { set_key (KEY_ACTIVE, value.to_string ()); }
39+ get { return get_bool_key (KEY_ACTIVE); }
40+ set { set_bool_key (KEY_ACTIVE, value); }
41 }
42-
43- public bool dont_show {
44- get { return bool.parse (get_key (KEY_NO_DISPLAY)); }
45+
46+ public bool show {
47+ get {
48+ if (get_bool_key (KEY_NO_DISPLAY))
49+ return false;
50+ if (get_bool_key (KEY_HIDDEN))
51+ return false;
52+ return show_in_environment ();
53+ }
54 }
55
56 public string path { get; set; }
57
58+ public bool is_custom_command {
59+ get { return name == CUSTOM_COMMAND_NAME; }
60+ }
61+
62 GLib.KeyFile keyfile;
63 static string[] languages;
64 static string preferred_language;
65-
66+
67 static construct {
68 languages = Intl.get_language_names ();
69 preferred_language = languages [0];
70 }
71-
72+
73 public KeyFile (string path) {
74- GLib.Object (path: path);
75+ Object (path: path);
76
77 keyfile = new GLib.KeyFile ();
78 load_from_file ();
79 }
80-
81+
82 public KeyFile.from_command (string command) {
83 keyfile = new GLib.KeyFile ();
84-
85+
86 this.path = Utils.get_user_startup_dir () + command.split (" ")[0] + ".desktop";
87- this.name = _("Custom Command");
88+ this.name = CUSTOM_COMMAND_NAME;
89 this.comment = command;
90 this.command = command;
91 this.icon = FALLBACK_ICON;
92 this.active = true;
93-
94+
95 set_key (KEY_TYPE, "Application");
96-
97+
98 write_to_file ();
99 }
100
101@@ -106,7 +121,7 @@
102 } catch (Error e) {
103 warning (e.message);
104 }
105-
106+
107 message ("-- Saving to %s --", path);
108 message ("Name: %s", name);
109 message ("Comment: %s", comment);
110@@ -124,31 +139,46 @@
111 }
112 }
113
114+ void set_bool_key (string key, bool value) {
115+ var as_string = value ? "true" : "false";
116+ keyfile_set_string (key, as_string);
117+ }
118+
119+ bool get_bool_key (string key) {
120+ var as_string = keyfile_get_string (key);
121+ return as_string == "true";
122+ }
123+
124 void set_key (string key, string value) {
125 if (key_is_localized (key))
126 keyfile_set_locale_string (key, value);
127 else
128 keyfile_set_string (key, value);
129 }
130-
131+
132 string get_key (string key) {
133 if (key_is_localized (key))
134 return keyfile_get_locale_string (key);
135 else
136 return keyfile_get_string (key);
137 }
138-
139+
140 bool key_is_localized (string key) {
141 switch (key) {
142 case KEY_NAME:
143 case KEY_COMMENT:
144 return true;
145-
146+
147 case KEY_COMMAND:
148 case KEY_ICON:
149 case KEY_ACTIVE:
150+ case KEY_NO_DISPLAY:
151+ case KEY_TYPE:
152+ case KEY_ONLY_SHOW_IN:
153+ case KEY_NOT_SHOW_IN:
154+ case KEY_HIDDEN:
155 return false;
156-
157+
158 default:
159 warn_if_reached ();
160 return false;
161@@ -166,9 +196,7 @@
162 string keyfile_get_string (string key) {
163 try {
164 return keyfile.get_string (KeyFileDesktop.GROUP, key);
165- } catch (KeyFileError e) {
166- warning (e.message);
167- }
168+ } catch (KeyFileError e) { }
169
170 return "";
171 }
172@@ -177,28 +205,51 @@
173 foreach (string lang in languages) {
174 try {
175 return keyfile.get_locale_string (KeyFileDesktop.GROUP, key, lang);
176- } catch (KeyFileError e) {
177- warning (e.message);
178- }
179+ } catch (KeyFileError e) { }
180 }
181
182 return "";
183 }
184-
185+
186+ bool show_in_environment () {
187+ var only_show_in = get_key (KEY_ONLY_SHOW_IN);
188+ var not_show_in = get_key (KEY_NOT_SHOW_IN);
189+
190+ var session = Environment.get_variable ("DESKTOP_SESSION");
191+
192+ if (session in only_show_in)
193+ return true;
194+ if (session in not_show_in)
195+ return false;
196+
197+ if (only_show_in == "")
198+ return true;
199+ return false;
200+ }
201+
202 public void copy_to_local () {
203 path = Utils.get_user_startup_dir () + name.down ().split (" ")[0] + ".desktop";
204 write_to_file ();
205 }
206-
207+
208 public string create_markup () {
209- var markup = @"<span font_weight=\"bold\" size=\"large\">$name</span>\n$comment";
210+ var escaped_name = Markup.escape_text (name);
211+ var escaped_comment = Markup.escape_text (comment);
212+ var escaped_command = Markup.escape_text (command);
213+ string markup;
214+
215+ if (is_custom_command)
216+ markup = escaped_command;
217+ else
218+ markup = @"<span font_weight=\"bold\" size=\"large\">$escaped_name</span>\n$escaped_comment";
219+
220 return markup;
221 }
222-
223+
224 public Gdk.Pixbuf create_icon (int size = ICON_SIZE) {
225 var icon_theme = Gtk.IconTheme.get_default ();
226 var lookup_flags = Gtk.IconLookupFlags.GENERIC_FALLBACK;
227-
228+
229 try {
230 if (icon_theme.has_icon (icon))
231 return icon_theme.load_icon (icon, size, lookup_flags);
232@@ -207,7 +258,7 @@
233 } catch (Error e) {
234 warning (e.message);
235 }
236-
237+
238 return (Gdk.Pixbuf) null;
239 }
240 }
241\ No newline at end of file
242
243=== modified file 'src/Backend/KeyFileFactory.vala'
244--- src/Backend/KeyFileFactory.vala 2014-03-22 11:07:44 +0000
245+++ src/Backend/KeyFileFactory.vala 2014-04-07 13:03:25 +0000
246@@ -24,29 +24,10 @@
247 public static void init () {
248 cache = new Gee.HashMap <string, KeyFile> ();
249 }
250-
251+
252 public static KeyFile get_or_create (string path) {
253- if (key_file_is_cached (path))
254- return get_key_file_from_path (path);
255- else
256- return create_key_file_from_path (path);
257- }
258-
259- static KeyFile get_key_file_from_path (string path)
260- requires (key_file_is_cached (path)) {
261- return cache[path];
262- }
263-
264- static KeyFile create_key_file_from_path (string path)
265- requires (key_file_is_cached (path) == false)
266- ensures (key_file_is_cached (path)) {
267-
268- var key_file = new KeyFile (path);
269- cache.set (path, key_file);
270- return key_file;
271- }
272-
273- static bool key_file_is_cached (string path) {
274- return cache.has_key (path);
275+ if (cache [path] == null) {
276+ cache [path] = new KeyFile (path);
277+ return cache [path];
278 }
279 }
280\ No newline at end of file
281
282=== modified file 'src/CMakeLists.txt'
283--- src/CMakeLists.txt 2014-03-22 11:07:44 +0000
284+++ src/CMakeLists.txt 2014-04-07 13:03:25 +0000
285@@ -1,7 +1,12 @@
286 find_package (PkgConfig)
287
288 # Add all your dependencies to the list below
289-pkg_check_modules (DEPS REQUIRED gthread-2.0 gtk+-3.0 switchboard-2.0 granite)
290+pkg_check_modules (DEPS REQUIRED
291+ gthread-2.0
292+ gtk+-3.0>=3.12
293+ switchboard-2.0
294+ granite
295+)
296
297 add_definitions (${DEPS_CFLAGS})
298 link_libraries (${DEPS_LIBRARIES})
299@@ -16,25 +21,18 @@
300 vala_precompile (VALA_C
301 Plug.vala
302 Utils.vala
303-
304 Backend/KeyFile.vala
305 Backend/KeyFileFactory.vala
306 Backend/DesktopFileEnumerator.vala
307-
308 Widgets/List.vala
309- Widgets/Editor.vala
310- Widgets/Toolbar.vala
311-
312 Dialogs/AppChooser.vala
313-
314 ${CMAKE_CURRENT_BINARY_DIR}/config.vala
315 PACKAGES
316 switchboard-2.0
317 granite
318 OPTIONS
319 --thread
320- --enable-experimental
321- #--fatal-warnings
322+ --fatal-warnings
323 --verbose
324 )
325
326
327=== modified file 'src/Dialogs/AppChooser.vala'
328--- src/Dialogs/AppChooser.vala 2014-03-22 11:07:44 +0000
329+++ src/Dialogs/AppChooser.vala 2014-04-07 13:03:25 +0000
330@@ -1,204 +1,158 @@
331 /***
332- Copyright (C) 2013 Michael Langfermann
333- 2013 Julien Spautz <spautz.julien@gmail.com>
334-
335+ Copyright (C) 2013 Julien Spautz <spautz.julien@gmail.com>
336+
337 This program is free software: you can redistribute it and/or modify
338 it under the terms of the GNU General Public License as published by
339 the Free Software Foundation, either version 3 of the License, or
340 (at your option) any later version.
341-
342+
343 This program is distributed in the hope that it will be useful,
344 but WITHOUT ANY WARRANTY; without even the implied warranty of
345 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
346 GNU General Public License for more details.
347-
348+
349 You should have received a copy of the GNU General Public License
350 along with this program. If not, see <http://www.gnu.org/licenses/>.
351 ***/
352
353-public class Pantheon.Startup.Dialogs.AppChooser : Granite.Widgets.PopOver {
354-
355- private Gtk.ListStore list_store;
356- private Gtk.TreeModelSort list_sort;
357- private Gtk.TreeModelFilter list_filter;
358- private Gtk.TreeView list;
359- private Gtk.Widget apply_button;
360- private Granite.Widgets.SearchBar search;
361- private Gtk.ScrolledWindow scroll;
362- private Gtk.CellRendererText cell;
363- private Gtk.Entry command;
364-
365- private string search_text = "";
366-
367- public AppInfo selected_app;
368-
369- public signal void app_chosen (Backend.KeyFile key_file);
370- public signal void app_selected ();
371-
372- public AppChooser () {
373- setup_gui ();
374- list_update ();
375- connect_signals ();
376- }
377-
378- private void setup_gui () {
379- title = _("Choose an Application...");
380- border_width = 5;
381-
382- list_store = new Gtk.ListStore (3,
383- typeof (string),
384- typeof (Gdk.Pixbuf),
385- typeof (Backend.KeyFile));
386-
387- list_filter = new Gtk.TreeModelFilter (list_store, null);
388- list_filter.set_visible_func (filter_func);
389-
390- list_sort = new Gtk.TreeModelSort.with_model (list_filter);
391- list_sort.set_sort_column_id (0, Gtk.SortType.ASCENDING);
392-
393- list = new Gtk.TreeView.with_model (list_sort);
394- list.expand = true;
395- list.enable_search = false;
396- list.headers_visible = false;
397-
398- Gtk.CellRendererPixbuf pixbuf = new Gtk.CellRendererPixbuf ();
399- list.insert_column_with_attributes (-1, null, pixbuf, "pixbuf", 1);
400-
401- cell = new Gtk.CellRendererText ();
402- cell.wrap_mode = Pango.WrapMode.WORD_CHAR;
403- list.insert_column_with_attributes (-1, null, cell, "markup", 0);
404-
405- search = new Granite.Widgets.SearchBar (_("Search Applications..."));
406-
407- scroll = new Gtk.ScrolledWindow (null, null);
408- scroll.hscrollbar_policy = Gtk.PolicyType.NEVER;
409- scroll.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
410- scroll.shadow_type = Gtk.ShadowType.IN;
411- scroll.expand = true;
412- scroll.height_request = 250;
413- scroll.width_request = 150;
414- scroll.add (list);
415-
416- Gtk.Box content = get_content_area () as Gtk.Box;
417- content.pack_start (search, false, true, 0);
418- content.pack_start (scroll, false, true, 0);
419-
420- command = new Gtk.Entry();
421- command.placeholder_text = _("Type in a custom command");
422- command.primary_icon_name = "document-properties-symbolic";
423- command.primary_icon_activatable = false;
424- content.pack_start (command, false, true, 0);
425-
426- content.spacing = 10;
427-
428- add_button (Gtk.Stock.CANCEL, Gtk.ResponseType.CLOSE);
429- apply_button = add_button (Gtk.Stock.ADD, Gtk.ResponseType.APPLY);
430- apply_button.sensitive = false;
431- }
432-
433- void list_update () {
434- var enumerator = new Backend.DesktopFileEnumerator ("/usr/share/applications/");
435- var names = enumerator.get_desktop_files ();
436-
437- foreach (var file in names) {
438- var key_file = new Backend.KeyFile (file);
439- append_item_from_keyfile (key_file);
440- }
441- }
442-
443- void append_item_from_keyfile (Backend.KeyFile key_file) {
444- if (key_file.dont_show)
445- return;
446-
447- Gtk.TreeIter iter;
448- list_store.append (out iter);
449- list_store.set (iter, 0, key_file.name,
450- 1, key_file.create_icon (32),
451- 2, key_file);
452- }
453-
454- void connect_signals () {
455- command.icon_press.connect (() => command.text = "");
456-
457- command.changed.connect (() => {
458- var is_empty = (command.text == "");
459-
460- scroll.sensitive = is_empty;
461- search.sensitive = is_empty;
462- command.secondary_icon_sensitive = !is_empty;
463- apply_button.sensitive = !is_empty;
464-
465- if (is_empty) {
466- command.secondary_icon_name = null;
467- list_update ();
468- } else {
469- list_store.clear ();
470- command.secondary_icon_name = "edit-clear-symbolic";
471+namespace Pantheon.Startup.Dialogs {
472+
473+ public class AppRow : Gtk.Box {
474+
475+ public Backend.KeyFile app { get; construct; }
476+
477+ public signal void deleted ();
478+
479+ public AppRow (Backend.KeyFile app) {
480+ Object (app: app);
481+ setup ();
482+ }
483+
484+ void setup () {
485+ orientation = Gtk.Orientation.HORIZONTAL;
486+
487+ var markup = app.create_markup ();
488+ var icon = app.create_icon (32);
489+
490+ margin = 6;
491+ spacing = 12;
492+
493+ var image = new Gtk.Image.from_pixbuf (icon);
494+ add (image);
495+
496+ var label = new Gtk.Label (markup);
497+ label.use_markup = true;
498+ label.halign = Gtk.Align.START;
499+ label.ellipsize = Pango.EllipsizeMode.END;
500+ add (label);
501+
502+ show_all ();
503+ }
504+ }
505+
506+ public class AppChooser : Gtk.Popover {
507+
508+ Gtk.ListBox list;
509+ Granite.Widgets.SearchBar search_bar;
510+ Gtk.Entry custom_bar;
511+
512+ public signal void app_chosen (Backend.KeyFile key_file);
513+
514+ public AppChooser (Gtk.Widget widget) {
515+ Object (relative_to: widget);
516+ setup_gui ();
517+ connect_signals ();
518+ }
519+
520+ void setup_gui () {
521+ var grid = new Gtk.Grid ();
522+ grid.margin = 12;
523+ grid.row_spacing = 6;
524+
525+ search_bar = new Granite.Widgets.SearchBar (_("Search Applications"));
526+
527+ var scrolled = new Gtk.ScrolledWindow (null, null);
528+ scrolled.height_request = 200;
529+ scrolled.width_request = 250;
530+ scrolled.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
531+ scrolled.shadow_type = Gtk.ShadowType.IN;
532+
533+ list = new Gtk.ListBox ();
534+ list.expand = true;
535+ list.height_request = 200;
536+ list.width_request = 150;
537+ list.set_sort_func (sort_function);
538+ list.set_filter_func (filter_function);
539+ scrolled.add (list);
540+
541+ custom_bar = new Gtk.Entry();
542+ custom_bar.placeholder_text = _("Type in a custom command");
543+ custom_bar.primary_icon_name = "document-properties-symbolic";
544+ custom_bar.primary_icon_activatable = false;
545+
546+ grid.attach (search_bar, 0, 0, 1, 1);
547+ grid.attach (scrolled, 0, 1, 1, 1);
548+ grid.attach (custom_bar, 0, 2, 1, 1);
549+
550+ add (grid);
551+
552+ list_update ();
553+ }
554+
555+ void list_update () {
556+ var enumerator = new Backend.DesktopFileEnumerator ("/usr/share/applications/");
557+ var paths = enumerator.get_desktop_files ();
558+
559+ foreach (var path in paths) {
560+ var key_file = Backend.KeyFileFactory.get_or_create (path);
561+ if (key_file.show)
562+ append_item_from_keyfile (key_file);
563 }
564- });
565-
566- list.cursor_changed.connect (() => command.activate ());
567- response.connect (on_response);
568-
569- command.activate.connect (() => {
570- app_chosen(get_app());
571- destroy ();
572- });
573-
574- search.text_changed_pause.connect ((text) => {
575- search_text = text;
576- list_filter.refilter ();
577- });
578-
579- size_allocate.connect ((allocation) =>
580- cell.wrap_width = allocation.width - (allocation.width / 3));
581- }
582-
583- private void on_response (Gtk.Dialog source, int response_id) {
584- if (response_id == Gtk.ResponseType.APPLY) {
585- app_chosen (get_app());
586- }
587-
588- destroy ();
589- }
590-
591- private Backend.KeyFile get_app () {
592- Gtk.TreeIter iter;
593- Gtk.TreePath path;
594- Backend.KeyFile app = null;
595-
596- list.get_cursor (out path, null);
597- if (path != null) {
598- path = list_sort.convert_path_to_child_path (path);
599- path = list_filter.convert_path_to_child_path (path);
600- list_store.get_iter (out iter, path);
601- list_store.get (iter, 2, out app);
602- app.copy_to_local ();
603- app.active = true;
604- return app;
605- } else if (command.text != "" && command.text != null) {
606- return create_command ();
607- }
608-
609- return (Backend.KeyFile) null;
610- }
611-
612- private bool filter_func (Gtk.TreeModel model, Gtk.TreeIter iter) {
613- Backend.KeyFile key_file;
614- string name;
615-
616- list_store.get (iter, 2, out key_file);
617- name = key_file.name + key_file.comment;
618-
619- if (search_text == "")
620- return true;
621- else if (name != null)
622- return name.up ().contains (search_text.up ());
623- else
624- return false;
625- }
626-
627- private Backend.KeyFile create_command () {
628- return new Backend.KeyFile.from_command (command.text);
629+ }
630+
631+ void append_item_from_keyfile (Backend.KeyFile key_file) {
632+ var app_row = new AppRow (key_file);
633+ list.prepend (app_row);
634+ }
635+
636+ int sort_function (Gtk.ListBoxRow list_box_row_1,
637+ Gtk.ListBoxRow list_box_row_2) {
638+ var row_1 = list_box_row_1.get_child () as AppRow;
639+ var row_2 = list_box_row_2.get_child () as AppRow;
640+
641+ var name_1 = row_1.app.name;
642+ var name_2 = row_2.app.name;
643+
644+ return name_1.collate (name_2);
645+ }
646+
647+ bool filter_function (Gtk.ListBoxRow list_box_row) {
648+ var app_row = list_box_row.get_child () as AppRow;
649+ return search_bar.text.down () in app_row.app.name.down ();
650+ }
651+
652+ void connect_signals () {
653+ list.row_activated.connect (on_app_selected);
654+ search_bar.text_changed_pause.connect (apply_filter);
655+ custom_bar.activate.connect (on_custom_command_entered);
656+ }
657+
658+ void on_app_selected (Gtk.ListBoxRow list_box_row) {
659+ var app_row = list_box_row.get_child () as AppRow;
660+ app_row.app.copy_to_local ();
661+ app_row.app.active = true;
662+ app_chosen (app_row.app);
663+ hide ();
664+ }
665+
666+ void apply_filter () {
667+ list.set_filter_func (filter_function);
668+ }
669+
670+ void on_custom_command_entered () {
671+ var app = new Backend.KeyFile.from_command (custom_bar.text);
672+ app_chosen (app);
673+ hide ();
674+ }
675 }
676 }
677\ No newline at end of file
678
679=== modified file 'src/Plug.vala'
680--- src/Plug.vala 2014-03-22 11:07:44 +0000
681+++ src/Plug.vala 2014-04-07 13:03:25 +0000
682@@ -19,67 +19,44 @@
683
684 public Switchboard.Plug get_plug (Module module) {
685 debug ("Activating Startup Apps plug");
686- var plug = new Pantheon.Startup.Plug ();
687- return plug;
688+ return new Pantheon.Startup.Plug ();
689 }
690
691 public class Pantheon.Startup.Plug : Switchboard.Plug {
692
693- Widgets.List list;
694- Widgets.Editor editor;
695- Granite.Widgets.ThinPaned paned = null;
696-
697+ Gtk.ScrolledWindow scrolled;
698+
699 public Plug () {
700 Object (category: Category.PERSONAL,
701 code_name: "personal-pantheon-startup",
702 display_name: _("Startup Apps"),
703 description: _("Shows Startup Applications Settings…"),
704 icon: "preferences-system-session");
705-
706+
707 Backend.KeyFileFactory.init ();
708 }
709
710 public override Gtk.Widget get_widget () {
711- if (paned == null) {
712- setup_gui ();
713- connect_signals ();
714- initialize_state ();
715+ if (scrolled == null) {
716+ scrolled = new Gtk.ScrolledWindow (null, null);
717+ var list = new Widgets.List ();
718+ scrolled.add (list);
719 }
720
721- return paned;
722+ return scrolled;
723 }
724-
725+
726 public override void shown () {
727+ scrolled.show_all ();
728 }
729-
730+
731 public override void hidden () {
732 }
733
734 public override void search_callback (string location) {
735 }
736
737- public override async Gee.TreeMap<string, string> search (string search) {
738- return new Gee.TreeMap<string, string> (null, null);
739- }
740-
741- void setup_gui () {
742- this.list = new Widgets.List ();
743- this.editor = new Widgets.Editor ();
744-
745- paned = new Granite.Widgets.ThinPaned ();
746- paned.pack1 (list, false, false);
747- paned.pack2 (editor, false, false);
748-
749- paned.show_all ();
750- }
751-
752- void connect_signals () {
753- list.selected.connect ((key_file) => {
754- editor.load_key_file (key_file);
755- });
756- }
757-
758- void initialize_state () {
759- list.select_first_item ();
760+ public override async Gee.TreeMap <string, string> search (string search) {
761+ return new Gee.TreeMap <string, string> (null, null);
762 }
763 }
764\ No newline at end of file
765
766=== removed file 'src/Widgets/Editor.vala'
767--- src/Widgets/Editor.vala 2014-03-22 11:07:44 +0000
768+++ src/Widgets/Editor.vala 1970-01-01 00:00:00 +0000
769@@ -1,102 +0,0 @@
770-/***
771- Copyright (C) 2013 Julien Spautz <spautz.julien@gmail.com>
772-
773- This program or library is free software; you can redistribute it
774- and/or modify it under the terms of the GNU Lesser General Public
775- License as published by the Free Software Foundation; either
776- version 3 of the License, or (at your option) any later version.
777-
778- This library is distributed in the hope that it will be useful,
779- but WITHOUT ANY WARRANTY; without even the implied warranty of
780- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
781- Lesser General Public License for more details.
782-
783- You should have received a copy of the GNU Lesser General
784- Public License along with this library; if not, write to the
785- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
786- Boston, MA 02110-1301 USA.
787-***/
788-
789-public class Pantheon.Startup.Widgets.Editor : Gtk.Grid {
790-
791- public string icon { get; set; }
792-
793- public new string name {
794- get { return name_entry.text; }
795- set { name_entry.text = value; }
796- }
797-
798- public string comment {
799- get { return comment_entry.text; }
800- set { comment_entry.text = value; }
801- }
802-
803- public string command {
804- get { return command_entry.text; }
805- set { command_entry.text = value; }
806- }
807-
808- public bool revive {
809- get; set;
810- }
811-
812- Backend.KeyFile key_file;
813-
814- Gtk.Entry name_entry;
815- Gtk.Entry comment_entry;
816- Gtk.Entry command_entry;
817-
818- public Editor () {
819- var name_label = new Gtk.Label (_("Name:"));
820- name_label.halign = Gtk.Align.END;
821- name_entry = new Gtk.Entry ();
822- name_entry.placeholder_text = _("Application name");
823- name_entry.width_request = 300;
824- name_entry.valign = Gtk.Align.END;
825-
826- var comment_label = new Gtk.Label (_("Description:"));
827- comment_label.halign = Gtk.Align.END;
828- comment_entry = new Gtk.Entry ();
829- comment_entry.placeholder_text = _("Short description");
830-
831- var command_label = new Gtk.Label (_("Command:"));
832- command_label.halign = Gtk.Align.END;
833- command_entry = new Gtk.Entry ();
834- command_entry.placeholder_text = _("Command to execute");
835-
836- this.attach (name_label, 0, 0, 1, 1);
837- this.attach (name_entry, 1, 0, 1, 1);
838- this.attach (comment_label, 0, 1, 1, 1);
839- this.attach (comment_entry, 1, 1, 1, 1);
840- this.attach (command_label, 0, 2, 1, 1);
841- this.attach (command_entry, 1, 2, 1, 1);
842-
843- row_spacing = 6;
844- column_spacing = 6;
845- margin = 12;
846- halign = Gtk.Align.CENTER;
847-
848- name_entry.notify["text"].connect (() => {
849- key_file.name = name_entry.text;
850- key_file.write_to_file ();
851- });
852-
853- comment_entry.notify["text"].connect (() => {
854- key_file.comment = comment_entry.text;
855- key_file.write_to_file ();
856- });
857-
858- command_entry.notify["text"].connect (() => {
859- key_file.command = command_entry.text;
860- key_file.write_to_file ();
861- });
862- }
863-
864- public void load_key_file (Backend.KeyFile key_file) {
865- this.key_file = key_file;
866-
867- name_entry.text = key_file.name;
868- comment_entry.text = key_file.comment;
869- command_entry.text = key_file.command;
870- }
871-}
872\ No newline at end of file
873
874=== modified file 'src/Widgets/List.vala'
875--- src/Widgets/List.vala 2014-03-22 11:07:44 +0000
876+++ src/Widgets/List.vala 2014-04-07 13:03:25 +0000
877@@ -17,187 +17,192 @@
878 Boston, MA 02110-1301 USA.
879 ***/
880
881-class Pantheon.Startup.Widgets.List : Gtk.Grid {
882-
883- private Gtk.ListStore list_store;
884- private Gtk.TreeModelSort sorted_list;
885- private Gtk.TreeView list;
886- private Widgets.Toolbar toolbar;
887- private Gtk.ScrolledWindow scroll;
888- private Gtk.CellRendererToggle toggle;
889-
890+class Pantheon.Startup.Widgets.AppRow : Gtk.Box {
891+
892+ Gtk.Button delete_button;
893+ Gtk.Label label;
894+ Gtk.Switch active_switch;
895+ Gtk.Image image;
896+
897+ public Backend.KeyFile app { get; construct; }
898+
899+ public signal void deleted ();
900+
901+ public AppRow (Backend.KeyFile app) {
902+ Object (app: app);
903+ setup ();
904+ connect_signals ();
905+ on_active_changed ();
906+ }
907+
908+ void setup () {
909+ orientation = Gtk.Orientation.HORIZONTAL;
910+
911+ var markup = app.create_markup ();
912+ var icon = app.create_icon ();
913+
914+ margin = 6;
915+ spacing = 12;
916+
917+ active_switch = new Gtk.Switch ();
918+ active_switch.active = app.active;
919+ add (active_switch);
920+
921+ image = new Gtk.Image.from_pixbuf (icon);
922+ add (image);
923+
924+ label = new Gtk.Label (markup);
925+ label.expand = true;
926+ label.use_markup = true;
927+ label.halign = Gtk.Align.START;
928+ label.ellipsize = Pango.EllipsizeMode.END;
929+ label.sensitive = app.active;
930+ add (label);
931+
932+ delete_button = new Gtk.Button.with_label (_("Delete"));
933+ delete_button.get_style_context ().add_class ("destructive-action");
934+ delete_button.no_show_all = true;
935+ delete_button.vexpand = false;
936+ var button_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL);
937+ button_box.add (delete_button);
938+ add (button_box);
939+
940+ show_all ();
941+ }
942+
943+ void connect_signals () {
944+ delete_button.clicked.connect (on_delete_clicked);
945+ active_switch.notify["active"].connect (on_active_changed);
946+ }
947+
948+ void on_delete_clicked () {
949+ app.delete_file ();
950+ deleted ();
951+ }
952+
953+ void on_active_changed () {
954+ var active = active_switch.active;
955+ label.sensitive = active;
956+ app.active = active;
957+ app.write_to_file ();
958+ }
959+
960+ public void show_delete (bool show) {
961+ delete_button.no_show_all = !show;
962+ delete_button.visible = show;
963+ }
964+}
965+
966+class Pantheon.Startup.Widgets.NewAppRow : Gtk.Box {
967+
968+ public signal void app_added (Backend.KeyFile app);
969+
970+ public NewAppRow () {
971+ orientation = Gtk.Orientation.HORIZONTAL;
972+
973+ margin = 6;
974+ spacing = 12;
975+
976+ var sw = new Gtk.Switch ();
977+ sw.opacity = 0.0;
978+ add (sw);
979+
980+ var add_button = new Gtk.Button.from_icon_name ("add", Gtk.IconSize.DIALOG);
981+ add_button.relief = Gtk.ReliefStyle.NONE;
982+ add (add_button);
983+
984+ var label = new Gtk.Label ("<i>" + _("Add Startup App") + "</i>");
985+ label.expand = true;
986+ label.use_markup = true;
987+ label.halign = Gtk.Align.START;
988+ label.ellipsize = Pango.EllipsizeMode.END;
989+ add (label);
990+
991+ var app_chooser = new Dialogs.AppChooser (add_button);
992+ app_chooser.modal = true;
993+
994+ app_chooser.app_chosen.connect ((key_file) => {
995+ app_added (key_file);
996+ });
997+
998+ add_button.clicked.connect (app_chooser.show_all);
999+ }
1000+}
1001+
1002+class Pantheon.Startup.Widgets.List : Gtk.ListBox {
1003+
1004+ NewAppRow new_app_row;
1005+
1006+ public string search_string { get; set; }
1007+
1008 public signal void selected (Backend.KeyFile app);
1009
1010- enum Column {
1011- ACTIVE,
1012- ICON,
1013- TEXT,
1014- APP,
1015- COUNT
1016- }
1017-
1018 public List () {
1019- setup_scrolled ();
1020- setup_toolbar ();
1021-
1022- attach (scroll, 0, 0, 1, 1);
1023- attach (toolbar, 0, 1, 1, 1);
1024-
1025- setup_signals ();
1026-
1027- load_apps ();
1028- }
1029-
1030- void setup_scrolled () {
1031- list_store = new Gtk.ListStore (Column.COUNT,
1032- typeof (bool),
1033- typeof (Gdk.Pixbuf),
1034- typeof (string),
1035- typeof (Backend.KeyFile)
1036- );
1037-
1038- sorted_list = new Gtk.TreeModelSort.with_model (list_store);
1039- sorted_list.set_sort_column_id (Column.TEXT, Gtk.SortType.ASCENDING);
1040-
1041- list = new Gtk.TreeView.with_model (sorted_list);
1042-
1043- toggle = new Gtk.CellRendererToggle ();
1044- list.insert_column_with_attributes (-1, null, toggle, "active", Column.ACTIVE);
1045-
1046- var pixbuf = new Gtk.CellRendererPixbuf ();
1047- list.insert_column_with_attributes (-1, null, pixbuf, "pixbuf", Column.ICON);
1048-
1049- var cell = new Gtk.CellRendererText ();
1050- list.insert_column_with_attributes (-1, null, cell, "markup", Column.TEXT);
1051-
1052- list.expand = true;
1053- list.enable_search = false;
1054- list.headers_visible = false;
1055-
1056- scroll = new Gtk.ScrolledWindow (null, null);
1057- scroll.width_request = 300;
1058- scroll.add (list);
1059- }
1060-
1061- void setup_toolbar () {
1062- toolbar = new Widgets.Toolbar ();
1063- }
1064-
1065- void setup_signals () {
1066- list.cursor_changed.connect (() => {
1067- Gtk.TreeIter iter;
1068- Gtk.TreePath tree_path;
1069- GLib.Value app;
1070-
1071- list.get_cursor (out tree_path, null);
1072- list.model.get_iter (out iter, tree_path);
1073- list.model.get_value (iter, Column.APP, out app);
1074-
1075- selected (app as Backend.KeyFile);
1076- });
1077-
1078- toggle.toggled.connect ((toggle, tree_path_string) => {
1079- GLib.Value app;
1080-
1081- Gtk.TreePath tree_path = new Gtk.TreePath.from_string (tree_path_string);
1082- list.set_cursor (tree_path, null, false);
1083- Gtk.TreeIter iter;
1084- tree_path = sorted_list.convert_path_to_child_path (tree_path);
1085-
1086- list_store.get_iter (out iter, tree_path);
1087- list_store.set (iter, Column.ACTIVE, !toggle.active);
1088- list_store.get_value (iter, Column.APP, out app);
1089-
1090- (app as Backend.KeyFile).active = toggle.active;
1091- (app as Backend.KeyFile).write_to_file ();
1092- });
1093-
1094- toolbar.clicked_remove_button.connect (() => {
1095- remove_current_app ();
1096- });
1097-
1098- toolbar.clicked_add_button.connect ((key_file) => {
1099- add_app (key_file.path);
1100- });
1101- }
1102-
1103- void load_apps () {
1104- foreach (var file in get_auto_start_files ())
1105- add_app (file);
1106- }
1107-
1108+ setup_gui ();
1109+ connect_signals ();
1110+ }
1111+
1112+ void setup_gui () {
1113+ load_startup_apps ();
1114+ add_new_app_row ();
1115+ }
1116+
1117+ void connect_signals () {
1118+ row_selected.connect (show_delete_button_on_select);
1119+ set_sort_func (sort_function);
1120+ new_app_row.app_added.connect (add_app);
1121+ }
1122+
1123+ void load_startup_apps () {
1124+ foreach (var path in get_auto_start_files ()) {
1125+ var app = Backend.KeyFileFactory.get_or_create (path);
1126+ add_app (app);
1127+ }
1128+ }
1129+
1130+ void add_new_app_row () {
1131+ new_app_row = new NewAppRow ();
1132+ prepend (new_app_row);
1133+ }
1134+
1135+ void show_delete_button_on_select (Gtk.ListBoxRow? selected_list_box_row) {
1136+ this.foreach ((row) => {
1137+ var list_box_row = row as Gtk.ListBoxRow;
1138+ var app_row = list_box_row.get_child () as AppRow;
1139+ if (app_row != null)
1140+ app_row.show_delete (false);
1141+ });
1142+
1143+ if (selected_list_box_row != null) {
1144+ var selected_app_row = selected_list_box_row.get_child () as AppRow;
1145+ selected_app_row.show_delete (true);
1146+ }
1147+ }
1148+
1149+ int sort_function (Gtk.ListBoxRow list_box_row_1,
1150+ Gtk.ListBoxRow list_box_row_2) {
1151+ var row_1 = list_box_row_1.get_child ();
1152+ var row_2 = list_box_row_2.get_child ();
1153+
1154+ if (row_1 is NewAppRow)
1155+ return 1;
1156+ if (row_2 is NewAppRow)
1157+ return -1;
1158+
1159+ var name_1 = (row_1 as AppRow).app.name;
1160+ var name_2 = (row_2 as AppRow).app.name;
1161+ return name_1.collate (name_2);
1162+ }
1163+
1164 string[] get_auto_start_files () {
1165 var startup_dir = Utils.get_user_startup_dir ();
1166 var enumerator = new Backend.DesktopFileEnumerator (startup_dir);
1167 return enumerator.get_desktop_files ();
1168 }
1169-
1170-
1171- public void select_first_item () {
1172- var path = new Gtk.TreePath.from_indices (0);
1173- list.set_cursor (path, null, false);
1174- }
1175-
1176- public void remove_current_app () {
1177- Gtk.TreeIter iter;
1178- Gtk.TreePath path;
1179- GLib.Value app;
1180-
1181- list.get_cursor (out path, null);
1182- path = sorted_list.convert_path_to_child_path (path);
1183-
1184- list_store.get_iter (out iter, path);
1185- list_store.get_value (iter, Column.APP, out app);
1186- (app as Backend.KeyFile).delete_file ();
1187-
1188- list_store.remove (iter);
1189- }
1190-
1191- public void add_app (string path) {
1192- Gtk.TreeIter iter;
1193-
1194- var app = Backend.KeyFileFactory.get_or_create (path);
1195- var markup = app.create_markup ();
1196- var icon = app.create_icon ();
1197-
1198- list_store.append (out iter);
1199- list_store.set (iter,
1200- Column.ACTIVE, app.active,
1201- Column.ICON, icon,
1202- Column.TEXT, markup,
1203- Column.APP, app
1204- );
1205-
1206- app.notify["name"].connect (() => {
1207- update_list_item (iter, path);
1208- });
1209-
1210- app.notify["comment"].connect (() => {
1211- update_list_item (iter, path);
1212- });
1213-
1214- app.notify["command"].connect (() => {
1215- update_list_item (iter, path);
1216- });
1217- }
1218-
1219- void update_list_item (Gtk.TreeIter iter, string path) {
1220- var app = Backend.KeyFileFactory.get_or_create (path);
1221- var markup = app.create_markup ();
1222- var icon = app.create_icon ();
1223-
1224- list_store.set (iter,
1225- Column.ACTIVE, app.active,
1226- Column.ICON, icon,
1227- Column.TEXT, markup,
1228- Column.APP, app
1229- );
1230- }
1231-
1232- /*Gtk.TreeIter get_iter_from_index (int index) {
1233- Gtk.TreeIter iter;
1234- var path = new Gtk.TreePath.from_indices (0);
1235- list_store.get_iter (out iter, path);
1236- return iter;
1237- }*/
1238+
1239+ public void add_app (Backend.KeyFile app) {
1240+ var row = new AppRow (app);
1241+ prepend (row);
1242+ row.deleted.connect (() => remove (row.parent));
1243+ }
1244 }
1245\ No newline at end of file
1246
1247=== removed file 'src/Widgets/Toolbar.vala'
1248--- src/Widgets/Toolbar.vala 2014-03-22 11:07:44 +0000
1249+++ src/Widgets/Toolbar.vala 1970-01-01 00:00:00 +0000
1250@@ -1,61 +0,0 @@
1251-/***
1252- Copyright (C) 2013 Julien Spautz <spautz.julien@gmail.com>
1253-
1254- This program or library is free software; you can redistribute it
1255- and/or modify it under the terms of the GNU Lesser General Public
1256- License as published by the Free Software Foundation; either
1257- version 3 of the License, or (at your option) any later version.
1258-
1259- This library is distributed in the hope that it will be useful,
1260- but WITHOUT ANY WARRANTY; without even the implied warranty of
1261- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1262- Lesser General Public License for more details.
1263-
1264- You should have received a copy of the GNU Lesser General
1265- Public License along with this library; if not, write to the
1266- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1267- Boston, MA 02110-1301 USA.
1268-***/
1269-
1270-class Pantheon.Startup.Widgets.Toolbar : Gtk.Toolbar {
1271-
1272- public signal void clicked_add_button (Backend.KeyFile key_file);
1273- public signal void clicked_remove_button ();
1274-
1275- public Toolbar () {
1276- set_style (Gtk.ToolbarStyle.ICONS);
1277- set_icon_size (Gtk.IconSize.SMALL_TOOLBAR);
1278- set_show_arrow (false);
1279- get_style_context ().add_class (Gtk.STYLE_CLASS_INLINE_TOOLBAR);
1280- get_style_context ().set_junction_sides (Gtk.JunctionSides.TOP);
1281-
1282- var add_button = new Gtk.ToolButton (null, _("Add..."));
1283- var remove_button = new Gtk.ToolButton (null, _("Remove"));
1284-
1285- add_button.set_icon_name ("list-add-symbolic");
1286- remove_button.set_icon_name ("list-remove-symbolic");
1287-
1288- add_button.set_tooltip_text (_("Add..."));
1289- remove_button.set_tooltip_text (_("Remove"));
1290-
1291- insert (add_button, -1);
1292- insert (remove_button, -1);
1293-
1294- add_button.clicked.connect (() => {
1295- var app_chooser = new Dialogs.AppChooser ();
1296- app_chooser.modal = true;
1297-
1298- app_chooser.app_chosen.connect ((key_file) => {
1299- clicked_add_button (key_file);
1300- });
1301-
1302- app_chooser.move_to_widget (add_button);
1303- app_chooser.show_all ();
1304- app_chooser.present ();
1305- app_chooser.run ();
1306- app_chooser.destroy ();
1307- });
1308-
1309- remove_button.clicked.connect (() => clicked_remove_button ());
1310- }
1311-}
1312\ No newline at end of file

Subscribers

People subscribed via source and target branches

to all changes: