Merge lp:~attente/indicator-keyboard/ibus-panel into lp:indicator-keyboard

Proposed by William Hua
Status: Merged
Approved by: Sebastien Bacher
Approved revision: 268
Merged at revision: 275
Proposed branch: lp:~attente/indicator-keyboard/ibus-panel
Merge into: lp:indicator-keyboard
Diff against target: 555 lines (+397/-19)
4 files modified
lib/Makefile.am (+3/-1)
lib/ibus-menu.vala (+318/-0)
lib/main.vala (+68/-18)
lib/source.vala (+8/-0)
To merge this branch: bzr merge lp:~attente/indicator-keyboard/ibus-panel
Reviewer Review Type Date Requested Status
Sebastien Bacher user testing Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+207240@code.launchpad.net

Commit message

Implement the IBus panel within the indicator.

Description of the change

Implement the IBus panel within the indicator.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Sebastien Bacher (seb128) wrote :

Testing building/running the new version, it seems to work fine. I would welcome testing from somebody who use IMs regularly though since I only use layouts.

I'm going to code review tomorrow (looks fine from a first glance) and try to land that before ff (if somebody else wants to help and do a code review that would be welcome though)

review: Approve (user testing)
Revision history for this message
Ted Gould (ted) wrote :

Sad to see that there isn't any tests and not a single comment.

Looking through the code I don't see any specific issues.

I'm curious about the UI thought, I would have expected these options to be radio items: http://ubuntuone.com/21XhYlv015IJKciNuU0DTN

Revision history for this message
Ted Gould (ted) wrote :

Oh, looks like I lost "Japanese (Anthy)" as a label as well.

268. By William Hua

Break reference cycle which caused some inconsistent action handling.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/Makefile.am'
2--- lib/Makefile.am 2013-11-22 16:44:45 +0000
3+++ lib/Makefile.am 2014-02-20 04:00:27 +0000
4@@ -2,12 +2,14 @@
5
6 AM_CFLAGS = -w -DGNOME_DESKTOP_USE_UNSTABLE_API
7 AM_LDFLAGS = -lm
8-AM_VALAFLAGS = --metadatadir $(top_srcdir)/deps \
9+AM_VALAFLAGS = --enable-experimental-non-null \
10+ --metadatadir $(top_srcdir)/deps \
11 --vapidir $(top_srcdir)/deps
12
13 indicator_keyboard_service_SOURCES = main.vala \
14 source.vala \
15 common.vala \
16+ ibus-menu.vala \
17 window-stack.vala \
18 unity-greeter.vala
19 indicator_keyboard_service_VALAFLAGS = $(AM_VALAFLAGS) \
20
21=== added file 'lib/ibus-menu.vala'
22--- lib/ibus-menu.vala 1970-01-01 00:00:00 +0000
23+++ lib/ibus-menu.vala 2014-02-20 04:00:27 +0000
24@@ -0,0 +1,318 @@
25+/*
26+ * Copyright 2014 Canonical Ltd.
27+ *
28+ * This program is free software: you can redistribute it and/or modify
29+ * it under the terms of the GNU General Public License as published by
30+ * the Free Software Foundation, version 3 of the License.
31+ *
32+ * This program is distributed in the hope that it will be useful,
33+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
34+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35+ * GNU General Public License for more details.
36+ *
37+ * You should have received a copy of the GNU General Public License
38+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
39+ *
40+ * Authors: William Hua <william.hua@canonical.com>
41+ */
42+
43+public class Indicator.Keyboard.IBusMenu : MenuModel {
44+
45+ private static uint radio_counter = 0;
46+
47+ private IBus.PropList? properties;
48+
49+ private Menu menu;
50+ private SimpleActionGroup? action_group;
51+
52+ private string? radio_name;
53+ private SimpleAction? radio_action;
54+ private Gee.HashMap<string, IBus.Property> radio_properties;
55+
56+ // A list of the action names this menu registers
57+ private Gee.LinkedList<string> names;
58+
59+ public IBusMenu (SimpleActionGroup? action_group = null, IBus.PropList? properties = null) {
60+ menu = new Menu ();
61+
62+ menu.items_changed.connect ((position, removed, added) => {
63+ items_changed (position, removed, added);
64+ });
65+
66+ names = new Gee.LinkedList<string> ();
67+ set_action_group (action_group);
68+ set_properties (properties);
69+ }
70+
71+ ~IBusMenu () {
72+ remove_actions ();
73+ }
74+
75+ public signal void activate (IBus.Property property, IBus.PropState state);
76+
77+ private string get_action_name (string key) {
78+ string name;
79+
80+ if (!action_name_is_valid (key)) {
81+ var builder = new StringBuilder.sized (key.length + 1);
82+
83+ unichar letter = 0;
84+ int index = 0;
85+
86+ while (key.get_next_char (ref index, out letter)) {
87+ if (letter == '-' || letter == '.' || letter.isalnum ()) {
88+ builder.append_unichar (letter);
89+ } else {
90+ builder.append_c ('-');
91+ }
92+ }
93+
94+ name = @"ibus-$(builder.str)";
95+ } else {
96+ name = @"ibus-$key";
97+ }
98+
99+ // Find an unused action name using a counter
100+ if (action_group != null && (Action?) ((!) action_group).lookup_action (name) != null) {
101+ var i = 0;
102+ var unique_name = @"$name-$i";
103+
104+ while ((Action?) ((!) action_group).lookup_action (unique_name) != null) {
105+ i++;
106+ unique_name = @"$name-$i";
107+ }
108+
109+ name = unique_name;
110+ }
111+
112+ return name;
113+ }
114+
115+ private string? get_label (IBus.Property property) {
116+ string? label = null;
117+
118+ if ((IBus.Text?) property.label != null) {
119+ label = property.label.text;
120+ }
121+
122+ if (label == null && (IBus.Text?) property.symbol != null) {
123+ label = property.symbol.text;
124+ }
125+
126+ return label;
127+ }
128+
129+ private void append_normal_property (IBus.Property property) {
130+ if (property.prop_type == IBus.PropType.NORMAL) {
131+ if ((string?) property.key != null) {
132+ var name = get_action_name (property.key);
133+
134+ if (action_group != null) {
135+ var action = new SimpleAction (name, null);
136+ action.activate.connect ((parameter) => { activate (property, property.state); });
137+ ((!) action_group).add_action (action);
138+ names.add (name);
139+ }
140+
141+ menu.append (get_label (property), property.sensitive ? @"indicator.$name" : "-private-disabled");
142+ }
143+ }
144+ }
145+
146+ private void append_toggle_property (IBus.Property property) {
147+ if (property.prop_type == IBus.PropType.TOGGLE) {
148+ if ((string?) property.key != null) {
149+ var name = get_action_name (property.key);
150+
151+ if (action_group != null) {
152+ var state = new Variant.boolean (property.state == IBus.PropState.CHECKED);
153+ var action = new SimpleAction.stateful (name, null, state);
154+
155+ action.activate.connect ((parameter) => {
156+ action.change_state (new Variant.boolean (!action.get_state ().get_boolean ()));
157+ });
158+
159+ action.change_state.connect ((value) => {
160+ if (value != null) {
161+ action.set_state ((!) value);
162+ activate (property, ((!) value).get_boolean () ? IBus.PropState.CHECKED : IBus.PropState.UNCHECKED);
163+ }
164+ });
165+
166+ ((!) action_group).add_action (action);
167+ names.add (name);
168+ }
169+
170+ menu.append (get_label (property), property.sensitive ? @"indicator.$name" : "-private-disabled");
171+ }
172+ }
173+ }
174+
175+ private void append_radio_property (IBus.Property property) {
176+ if (property.prop_type == IBus.PropType.RADIO) {
177+ if ((string?) property.key != null) {
178+ // Create a single action for all radio properties.
179+ if (action_group != null && radio_name == null) {
180+ radio_counter++;
181+ radio_name = @"-private-radio-$radio_counter";
182+ radio_action = new SimpleAction.stateful ((!) radio_name, VariantType.STRING, new Variant.string (""));
183+
184+ ((!) radio_action).activate.connect ((parameter) => {
185+ ((!) radio_action).change_state (parameter);
186+ });
187+
188+ ((!) radio_action).change_state.connect ((value) => {
189+ if (value != null) {
190+ var key = ((!) value).get_string ();
191+
192+ if (radio_properties.has_key (key)) {
193+ ((!) radio_action).set_state ((!) value);
194+ activate (radio_properties[key], IBus.PropState.CHECKED);
195+ }
196+ }
197+ });
198+
199+ ((!) action_group).add_action ((!) radio_action);
200+ names.add ((!) radio_name);
201+ }
202+
203+ radio_properties[property.key] = property;
204+
205+ if (property.state == IBus.PropState.CHECKED) {
206+ ((!) radio_action).change_state (new Variant.string (property.key));
207+ }
208+
209+ var item = new MenuItem (get_label (property), "-private-disabled");
210+
211+ if (property.sensitive) {
212+ item.set_action_and_target_value (@"indicator.$((!) radio_name)", new Variant.string (property.key));
213+ }
214+
215+ menu.append_item (item);
216+ }
217+ }
218+ }
219+
220+ private void append_menu_property (IBus.Property property) {
221+ if (property.prop_type == IBus.PropType.MENU) {
222+ var submenu = new IBusMenu (action_group, ((!) property).sub_props);
223+ submenu.activate.connect ((property, state) => { activate (property, state); });
224+ menu.append_submenu (get_label (property), submenu);
225+ }
226+ }
227+
228+ private void append_property (IBus.Property? property) {
229+ if (property != null && ((!) property).visible) {
230+ switch (((!) property).prop_type) {
231+ case IBus.PropType.NORMAL:
232+ append_normal_property ((!) property);
233+ break;
234+
235+ case IBus.PropType.TOGGLE:
236+ append_toggle_property ((!) property);
237+ break;
238+
239+ case IBus.PropType.RADIO:
240+ append_radio_property ((!) property);
241+ break;
242+
243+ case IBus.PropType.MENU:
244+ append_menu_property ((!) property);
245+ break;
246+
247+ case IBus.PropType.SEPARATOR:
248+ break;
249+ }
250+ }
251+ }
252+
253+ private void update_menu () {
254+ // There's a reference cycle between the action group and the submenus.
255+ // We need to break it here so that those submenus aren't hanging around.
256+ for (var i = 0; i < menu.get_n_items (); i++) {
257+ var submenu = menu.get_item_link (i, Menu.LINK_SUBMENU) as IBusMenu;
258+
259+ if (submenu != null) {
260+ ((!) submenu).remove_actions ();
261+ }
262+ }
263+
264+ menu.remove_all ();
265+
266+ if (properties != null) {
267+ for (var i = 0; i < ((!) properties).properties.length; i++) {
268+ append_property (((!) properties).get (i));
269+ }
270+ }
271+ }
272+
273+ private void remove_actions () {
274+ radio_action = null;
275+ radio_name = null;
276+
277+ if (action_group != null) {
278+ foreach (var name in names) {
279+ ((!) action_group).remove_action (name);
280+ }
281+ }
282+
283+ names.clear ();
284+ }
285+
286+ public void set_action_group (SimpleActionGroup? action_group) {
287+ if (action_group != this.action_group) {
288+ remove_actions ();
289+ this.action_group = action_group;
290+ update_menu ();
291+ }
292+ }
293+
294+ public void set_properties (IBus.PropList? properties) {
295+ if (properties != this.properties) {
296+ remove_actions ();
297+ radio_properties = new Gee.HashMap<string, IBus.Property> ();
298+ this.properties = properties;
299+ update_menu ();
300+ }
301+ }
302+
303+ public void update_property (IBus.Property property) {
304+ remove_actions ();
305+ radio_properties = new Gee.HashMap<string, IBus.Property> ();
306+ update_menu ();
307+ }
308+
309+ // Forward all menu model calls to our internal menu
310+
311+ public override Variant get_item_attribute_value (int item_index, string attribute, VariantType? expected_type) {
312+ return menu.get_item_attribute_value (item_index, attribute, expected_type);
313+ }
314+
315+ public override void get_item_attributes (int item_index, out HashTable<string, Variant>? attributes) {
316+ menu.get_item_attributes (item_index, out attributes);
317+ }
318+
319+ public override MenuModel get_item_link (int item_index, string link) {
320+ return menu.get_item_link (item_index, link);
321+ }
322+
323+ public override void get_item_links (int item_index, out HashTable<string, MenuModel>? links) {
324+ menu.get_item_links (item_index, out links);
325+ }
326+
327+ public override int get_n_items () {
328+ return menu.get_n_items ();
329+ }
330+
331+ public override bool is_mutable () {
332+ return menu.is_mutable ();
333+ }
334+
335+ public override MenuAttributeIter iterate_item_attributes (int item_index) {
336+ return menu.iterate_item_attributes (item_index);
337+ }
338+
339+ public override MenuLinkIter iterate_item_links (int item_index) {
340+ return menu.iterate_item_links (item_index);
341+ }
342+}
343
344=== modified file 'lib/main.vala'
345--- lib/main.vala 2014-02-19 15:09:16 +0000
346+++ lib/main.vala 2014-02-20 04:00:27 +0000
347@@ -19,8 +19,9 @@
348 [DBus (name = "com.canonical.indicator.keyboard")]
349 public class Indicator.Keyboard.Service : Object {
350
351+ private static const uint PROPERTIES_DELAY = 250;
352+
353 private static Service service;
354- private static IBus.Bus? ibus;
355
356 private bool force;
357 private bool use_gtk;
358@@ -35,12 +36,17 @@
359 private Gee.HashMap<uint, uint>? window_sources;
360 private uint focused_window_id;
361
362+ private IBus.Bus? ibus;
363+ private IBus.PanelService? panel_service;
364+ private uint panel_timeout;
365+
366 private Source[]? sources;
367
368 private SimpleActionGroup? action_group;
369 private SimpleAction? indicator_action;
370 private MenuModel? menu_model;
371 private Menu? sources_menu;
372+ private IBusMenu? ibus_menu;
373
374 private UnityGreeter? unity_greeter;
375 private string? greeter_user;
376@@ -96,7 +102,7 @@
377 }
378
379 [DBus (visible = false)]
380- private static IBus.Bus get_ibus () {
381+ private IBus.Bus get_ibus () {
382 if (ibus == null) {
383 IBus.init ();
384 ibus = new IBus.Bus ();
385@@ -210,7 +216,7 @@
386 if (source != null) {
387 var array = source_settings.get_value ("sources");
388
389- for (int i = 0; i < array.n_children (); i++) {
390+ for (var i = 0; i < array.n_children (); i++) {
391 unowned string type;
392 unowned string name;
393
394@@ -543,6 +549,14 @@
395 break;
396 }
397 }
398+
399+ if (panel_service == null && sources[i].is_ibus) {
400+ if (get_ibus ().request_name (IBus.SERVICE_PANEL, IBus.BusNameFlag.REPLACE_EXISTING) > 0) {
401+ panel_service = new IBus.PanelService (get_ibus ().get_connection ());
402+ ((!) panel_service).register_properties.connect (handle_registered_properties);
403+ ((!) panel_service).update_property.connect (handle_updated_property);
404+ }
405+ }
406 }
407 }
408
409@@ -550,6 +564,25 @@
410 }
411
412 [DBus (visible = false)]
413+ private void handle_registered_properties (IBus.PropList list) {
414+ if (panel_timeout > 0) {
415+ GLib.Source.remove (panel_timeout);
416+ panel_timeout = 0;
417+ }
418+
419+ panel_timeout = Timeout.add (PROPERTIES_DELAY, () => {
420+ update_ibus_menu (list);
421+ panel_timeout = 0;
422+ return false;
423+ });
424+ }
425+
426+ [DBus (visible = false)]
427+ private void handle_updated_property (IBus.Property property) {
428+ get_ibus_menu ().update_property (property);
429+ }
430+
431+ [DBus (visible = false)]
432 private void update_indicator_action () {
433 var visible = indicator_settings.get_boolean ("visible");
434 var current = source_settings.get_uint ("current");
435@@ -600,7 +633,7 @@
436 var length = (int) sources.n_children ();
437
438 if (length > 0) {
439- var offset = parameter.get_int32 () % length;
440+ var offset = ((!) parameter).get_int32 () % length;
441 source_settings.set_uint ("current", (current + (length - offset)) % length);
442 }
443 }
444@@ -610,28 +643,28 @@
445 protected virtual SimpleActionGroup create_action_group (Action root_action) {
446 var group = new SimpleActionGroup ();
447
448- group.insert (root_action);
449- group.insert (source_settings.create_action ("current"));
450+ group.add_action (root_action);
451+ group.add_action (source_settings.create_action ("current"));
452
453 var action = new SimpleAction ("next", null);
454 action.activate.connect (handle_middle_click);
455- group.insert (action);
456+ group.add_action (action);
457
458 action = new SimpleAction ("scroll", VariantType.INT32);
459 action.activate.connect (handle_scroll_wheel);
460- group.insert (action);
461+ group.add_action (action);
462
463 action = new SimpleAction ("map", null);
464 action.activate.connect (handle_activate_map);
465- group.insert (action);
466+ group.add_action (action);
467
468 action = new SimpleAction ("chart", null);
469 action.activate.connect (handle_activate_chart);
470- group.insert (action);
471+ group.add_action (action);
472
473 action = new SimpleAction ("settings", null);
474 action.activate.connect (handle_activate_settings);
475- group.insert (action);
476+ group.add_action (action);
477
478 return group;
479 }
480@@ -649,12 +682,9 @@
481 private void update_sources_menu () {
482 if (sources_menu != null) {
483 var menu = get_sources_menu ();
484-
485- while (menu.get_n_items () > 0)
486- menu.remove (0);
487+ menu.remove_all ();
488
489 var sources = get_sources ();
490-
491 for (var i = 0; i < sources.length; i++) {
492 var item = new MenuItem (sources[i].name, "indicator.current");
493 item.set_attribute (Menu.ATTRIBUTE_TARGET, "u", i);
494@@ -682,12 +712,32 @@
495 }
496
497 [DBus (visible = false)]
498- protected virtual MenuModel create_menu_model (MenuModel section_menu) {
499+ private void update_ibus_menu (IBus.PropList list) {
500+ get_ibus_menu ().set_properties (list);
501+ }
502+
503+ [DBus (visible = false)]
504+ private IBusMenu get_ibus_menu () {
505+ if (ibus_menu == null) {
506+ ibus_menu = new IBusMenu (get_action_group ());
507+ ((!) ibus_menu).activate.connect ((property, state) => {
508+ if (panel_service != null) {
509+ ((!) panel_service).property_activate (property.key, state);
510+ }
511+ });
512+ }
513+
514+ return (!) ibus_menu;
515+ }
516+
517+ [DBus (visible = false)]
518+ protected virtual MenuModel create_menu_model (MenuModel sources_menu, MenuModel ibus_menu) {
519 var menu = new Menu ();
520
521 var submenu = new Menu ();
522
523- submenu.append_section (null, section_menu);
524+ submenu.append_section (null, sources_menu);
525+ submenu.append_section (null, ibus_menu);
526
527 if (!is_login_user ()) {
528 var section = new Menu ();
529@@ -710,7 +760,7 @@
530 [DBus (visible = false)]
531 public MenuModel get_menu_model () {
532 if (menu_model == null) {
533- menu_model = create_menu_model (get_sources_menu ());
534+ menu_model = create_menu_model (get_sources_menu (), get_ibus_menu ());
535 }
536
537 return (!) menu_model;
538
539=== modified file 'lib/source.vala'
540--- lib/source.vala 2013-08-07 19:11:32 +0000
541+++ lib/source.vala 2014-02-20 04:00:27 +0000
542@@ -69,6 +69,14 @@
543 construct set { _use_gtk = value; icon = null; }
544 }
545
546+ public bool is_xkb {
547+ get { return xkb != null; }
548+ }
549+
550+ public bool is_ibus {
551+ get { return ibus != null; }
552+ }
553+
554 public Source (Variant variant, bool use_gtk = false) {
555 Object (use_gtk: use_gtk);
556

Subscribers

People subscribed via source and target branches

to all changes: