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
=== modified file 'lib/Makefile.am'
--- lib/Makefile.am 2013-11-22 16:44:45 +0000
+++ lib/Makefile.am 2014-02-20 04:00:27 +0000
@@ -2,12 +2,14 @@
22
3AM_CFLAGS = -w -DGNOME_DESKTOP_USE_UNSTABLE_API3AM_CFLAGS = -w -DGNOME_DESKTOP_USE_UNSTABLE_API
4AM_LDFLAGS = -lm4AM_LDFLAGS = -lm
5AM_VALAFLAGS = --metadatadir $(top_srcdir)/deps \5AM_VALAFLAGS = --enable-experimental-non-null \
6 --metadatadir $(top_srcdir)/deps \
6 --vapidir $(top_srcdir)/deps7 --vapidir $(top_srcdir)/deps
78
8indicator_keyboard_service_SOURCES = main.vala \9indicator_keyboard_service_SOURCES = main.vala \
9 source.vala \10 source.vala \
10 common.vala \11 common.vala \
12 ibus-menu.vala \
11 window-stack.vala \13 window-stack.vala \
12 unity-greeter.vala14 unity-greeter.vala
13indicator_keyboard_service_VALAFLAGS = $(AM_VALAFLAGS) \15indicator_keyboard_service_VALAFLAGS = $(AM_VALAFLAGS) \
1416
=== added file 'lib/ibus-menu.vala'
--- lib/ibus-menu.vala 1970-01-01 00:00:00 +0000
+++ lib/ibus-menu.vala 2014-02-20 04:00:27 +0000
@@ -0,0 +1,318 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: William Hua <william.hua@canonical.com>
17 */
18
19public class Indicator.Keyboard.IBusMenu : MenuModel {
20
21 private static uint radio_counter = 0;
22
23 private IBus.PropList? properties;
24
25 private Menu menu;
26 private SimpleActionGroup? action_group;
27
28 private string? radio_name;
29 private SimpleAction? radio_action;
30 private Gee.HashMap<string, IBus.Property> radio_properties;
31
32 // A list of the action names this menu registers
33 private Gee.LinkedList<string> names;
34
35 public IBusMenu (SimpleActionGroup? action_group = null, IBus.PropList? properties = null) {
36 menu = new Menu ();
37
38 menu.items_changed.connect ((position, removed, added) => {
39 items_changed (position, removed, added);
40 });
41
42 names = new Gee.LinkedList<string> ();
43 set_action_group (action_group);
44 set_properties (properties);
45 }
46
47 ~IBusMenu () {
48 remove_actions ();
49 }
50
51 public signal void activate (IBus.Property property, IBus.PropState state);
52
53 private string get_action_name (string key) {
54 string name;
55
56 if (!action_name_is_valid (key)) {
57 var builder = new StringBuilder.sized (key.length + 1);
58
59 unichar letter = 0;
60 int index = 0;
61
62 while (key.get_next_char (ref index, out letter)) {
63 if (letter == '-' || letter == '.' || letter.isalnum ()) {
64 builder.append_unichar (letter);
65 } else {
66 builder.append_c ('-');
67 }
68 }
69
70 name = @"ibus-$(builder.str)";
71 } else {
72 name = @"ibus-$key";
73 }
74
75 // Find an unused action name using a counter
76 if (action_group != null && (Action?) ((!) action_group).lookup_action (name) != null) {
77 var i = 0;
78 var unique_name = @"$name-$i";
79
80 while ((Action?) ((!) action_group).lookup_action (unique_name) != null) {
81 i++;
82 unique_name = @"$name-$i";
83 }
84
85 name = unique_name;
86 }
87
88 return name;
89 }
90
91 private string? get_label (IBus.Property property) {
92 string? label = null;
93
94 if ((IBus.Text?) property.label != null) {
95 label = property.label.text;
96 }
97
98 if (label == null && (IBus.Text?) property.symbol != null) {
99 label = property.symbol.text;
100 }
101
102 return label;
103 }
104
105 private void append_normal_property (IBus.Property property) {
106 if (property.prop_type == IBus.PropType.NORMAL) {
107 if ((string?) property.key != null) {
108 var name = get_action_name (property.key);
109
110 if (action_group != null) {
111 var action = new SimpleAction (name, null);
112 action.activate.connect ((parameter) => { activate (property, property.state); });
113 ((!) action_group).add_action (action);
114 names.add (name);
115 }
116
117 menu.append (get_label (property), property.sensitive ? @"indicator.$name" : "-private-disabled");
118 }
119 }
120 }
121
122 private void append_toggle_property (IBus.Property property) {
123 if (property.prop_type == IBus.PropType.TOGGLE) {
124 if ((string?) property.key != null) {
125 var name = get_action_name (property.key);
126
127 if (action_group != null) {
128 var state = new Variant.boolean (property.state == IBus.PropState.CHECKED);
129 var action = new SimpleAction.stateful (name, null, state);
130
131 action.activate.connect ((parameter) => {
132 action.change_state (new Variant.boolean (!action.get_state ().get_boolean ()));
133 });
134
135 action.change_state.connect ((value) => {
136 if (value != null) {
137 action.set_state ((!) value);
138 activate (property, ((!) value).get_boolean () ? IBus.PropState.CHECKED : IBus.PropState.UNCHECKED);
139 }
140 });
141
142 ((!) action_group).add_action (action);
143 names.add (name);
144 }
145
146 menu.append (get_label (property), property.sensitive ? @"indicator.$name" : "-private-disabled");
147 }
148 }
149 }
150
151 private void append_radio_property (IBus.Property property) {
152 if (property.prop_type == IBus.PropType.RADIO) {
153 if ((string?) property.key != null) {
154 // Create a single action for all radio properties.
155 if (action_group != null && radio_name == null) {
156 radio_counter++;
157 radio_name = @"-private-radio-$radio_counter";
158 radio_action = new SimpleAction.stateful ((!) radio_name, VariantType.STRING, new Variant.string (""));
159
160 ((!) radio_action).activate.connect ((parameter) => {
161 ((!) radio_action).change_state (parameter);
162 });
163
164 ((!) radio_action).change_state.connect ((value) => {
165 if (value != null) {
166 var key = ((!) value).get_string ();
167
168 if (radio_properties.has_key (key)) {
169 ((!) radio_action).set_state ((!) value);
170 activate (radio_properties[key], IBus.PropState.CHECKED);
171 }
172 }
173 });
174
175 ((!) action_group).add_action ((!) radio_action);
176 names.add ((!) radio_name);
177 }
178
179 radio_properties[property.key] = property;
180
181 if (property.state == IBus.PropState.CHECKED) {
182 ((!) radio_action).change_state (new Variant.string (property.key));
183 }
184
185 var item = new MenuItem (get_label (property), "-private-disabled");
186
187 if (property.sensitive) {
188 item.set_action_and_target_value (@"indicator.$((!) radio_name)", new Variant.string (property.key));
189 }
190
191 menu.append_item (item);
192 }
193 }
194 }
195
196 private void append_menu_property (IBus.Property property) {
197 if (property.prop_type == IBus.PropType.MENU) {
198 var submenu = new IBusMenu (action_group, ((!) property).sub_props);
199 submenu.activate.connect ((property, state) => { activate (property, state); });
200 menu.append_submenu (get_label (property), submenu);
201 }
202 }
203
204 private void append_property (IBus.Property? property) {
205 if (property != null && ((!) property).visible) {
206 switch (((!) property).prop_type) {
207 case IBus.PropType.NORMAL:
208 append_normal_property ((!) property);
209 break;
210
211 case IBus.PropType.TOGGLE:
212 append_toggle_property ((!) property);
213 break;
214
215 case IBus.PropType.RADIO:
216 append_radio_property ((!) property);
217 break;
218
219 case IBus.PropType.MENU:
220 append_menu_property ((!) property);
221 break;
222
223 case IBus.PropType.SEPARATOR:
224 break;
225 }
226 }
227 }
228
229 private void update_menu () {
230 // There's a reference cycle between the action group and the submenus.
231 // We need to break it here so that those submenus aren't hanging around.
232 for (var i = 0; i < menu.get_n_items (); i++) {
233 var submenu = menu.get_item_link (i, Menu.LINK_SUBMENU) as IBusMenu;
234
235 if (submenu != null) {
236 ((!) submenu).remove_actions ();
237 }
238 }
239
240 menu.remove_all ();
241
242 if (properties != null) {
243 for (var i = 0; i < ((!) properties).properties.length; i++) {
244 append_property (((!) properties).get (i));
245 }
246 }
247 }
248
249 private void remove_actions () {
250 radio_action = null;
251 radio_name = null;
252
253 if (action_group != null) {
254 foreach (var name in names) {
255 ((!) action_group).remove_action (name);
256 }
257 }
258
259 names.clear ();
260 }
261
262 public void set_action_group (SimpleActionGroup? action_group) {
263 if (action_group != this.action_group) {
264 remove_actions ();
265 this.action_group = action_group;
266 update_menu ();
267 }
268 }
269
270 public void set_properties (IBus.PropList? properties) {
271 if (properties != this.properties) {
272 remove_actions ();
273 radio_properties = new Gee.HashMap<string, IBus.Property> ();
274 this.properties = properties;
275 update_menu ();
276 }
277 }
278
279 public void update_property (IBus.Property property) {
280 remove_actions ();
281 radio_properties = new Gee.HashMap<string, IBus.Property> ();
282 update_menu ();
283 }
284
285 // Forward all menu model calls to our internal menu
286
287 public override Variant get_item_attribute_value (int item_index, string attribute, VariantType? expected_type) {
288 return menu.get_item_attribute_value (item_index, attribute, expected_type);
289 }
290
291 public override void get_item_attributes (int item_index, out HashTable<string, Variant>? attributes) {
292 menu.get_item_attributes (item_index, out attributes);
293 }
294
295 public override MenuModel get_item_link (int item_index, string link) {
296 return menu.get_item_link (item_index, link);
297 }
298
299 public override void get_item_links (int item_index, out HashTable<string, MenuModel>? links) {
300 menu.get_item_links (item_index, out links);
301 }
302
303 public override int get_n_items () {
304 return menu.get_n_items ();
305 }
306
307 public override bool is_mutable () {
308 return menu.is_mutable ();
309 }
310
311 public override MenuAttributeIter iterate_item_attributes (int item_index) {
312 return menu.iterate_item_attributes (item_index);
313 }
314
315 public override MenuLinkIter iterate_item_links (int item_index) {
316 return menu.iterate_item_links (item_index);
317 }
318}
0319
=== modified file 'lib/main.vala'
--- lib/main.vala 2014-02-19 15:09:16 +0000
+++ lib/main.vala 2014-02-20 04:00:27 +0000
@@ -19,8 +19,9 @@
19[DBus (name = "com.canonical.indicator.keyboard")]19[DBus (name = "com.canonical.indicator.keyboard")]
20public class Indicator.Keyboard.Service : Object {20public class Indicator.Keyboard.Service : Object {
2121
22 private static const uint PROPERTIES_DELAY = 250;
23
22 private static Service service;24 private static Service service;
23 private static IBus.Bus? ibus;
2425
25 private bool force;26 private bool force;
26 private bool use_gtk;27 private bool use_gtk;
@@ -35,12 +36,17 @@
35 private Gee.HashMap<uint, uint>? window_sources;36 private Gee.HashMap<uint, uint>? window_sources;
36 private uint focused_window_id;37 private uint focused_window_id;
3738
39 private IBus.Bus? ibus;
40 private IBus.PanelService? panel_service;
41 private uint panel_timeout;
42
38 private Source[]? sources;43 private Source[]? sources;
3944
40 private SimpleActionGroup? action_group;45 private SimpleActionGroup? action_group;
41 private SimpleAction? indicator_action;46 private SimpleAction? indicator_action;
42 private MenuModel? menu_model;47 private MenuModel? menu_model;
43 private Menu? sources_menu;48 private Menu? sources_menu;
49 private IBusMenu? ibus_menu;
4450
45 private UnityGreeter? unity_greeter;51 private UnityGreeter? unity_greeter;
46 private string? greeter_user;52 private string? greeter_user;
@@ -96,7 +102,7 @@
96 }102 }
97103
98 [DBus (visible = false)]104 [DBus (visible = false)]
99 private static IBus.Bus get_ibus () {105 private IBus.Bus get_ibus () {
100 if (ibus == null) {106 if (ibus == null) {
101 IBus.init ();107 IBus.init ();
102 ibus = new IBus.Bus ();108 ibus = new IBus.Bus ();
@@ -210,7 +216,7 @@
210 if (source != null) {216 if (source != null) {
211 var array = source_settings.get_value ("sources");217 var array = source_settings.get_value ("sources");
212218
213 for (int i = 0; i < array.n_children (); i++) {219 for (var i = 0; i < array.n_children (); i++) {
214 unowned string type;220 unowned string type;
215 unowned string name;221 unowned string name;
216222
@@ -543,6 +549,14 @@
543 break;549 break;
544 }550 }
545 }551 }
552
553 if (panel_service == null && sources[i].is_ibus) {
554 if (get_ibus ().request_name (IBus.SERVICE_PANEL, IBus.BusNameFlag.REPLACE_EXISTING) > 0) {
555 panel_service = new IBus.PanelService (get_ibus ().get_connection ());
556 ((!) panel_service).register_properties.connect (handle_registered_properties);
557 ((!) panel_service).update_property.connect (handle_updated_property);
558 }
559 }
546 }560 }
547 }561 }
548562
@@ -550,6 +564,25 @@
550 }564 }
551565
552 [DBus (visible = false)]566 [DBus (visible = false)]
567 private void handle_registered_properties (IBus.PropList list) {
568 if (panel_timeout > 0) {
569 GLib.Source.remove (panel_timeout);
570 panel_timeout = 0;
571 }
572
573 panel_timeout = Timeout.add (PROPERTIES_DELAY, () => {
574 update_ibus_menu (list);
575 panel_timeout = 0;
576 return false;
577 });
578 }
579
580 [DBus (visible = false)]
581 private void handle_updated_property (IBus.Property property) {
582 get_ibus_menu ().update_property (property);
583 }
584
585 [DBus (visible = false)]
553 private void update_indicator_action () {586 private void update_indicator_action () {
554 var visible = indicator_settings.get_boolean ("visible");587 var visible = indicator_settings.get_boolean ("visible");
555 var current = source_settings.get_uint ("current");588 var current = source_settings.get_uint ("current");
@@ -600,7 +633,7 @@
600 var length = (int) sources.n_children ();633 var length = (int) sources.n_children ();
601634
602 if (length > 0) {635 if (length > 0) {
603 var offset = parameter.get_int32 () % length;636 var offset = ((!) parameter).get_int32 () % length;
604 source_settings.set_uint ("current", (current + (length - offset)) % length);637 source_settings.set_uint ("current", (current + (length - offset)) % length);
605 }638 }
606 }639 }
@@ -610,28 +643,28 @@
610 protected virtual SimpleActionGroup create_action_group (Action root_action) {643 protected virtual SimpleActionGroup create_action_group (Action root_action) {
611 var group = new SimpleActionGroup ();644 var group = new SimpleActionGroup ();
612645
613 group.insert (root_action);646 group.add_action (root_action);
614 group.insert (source_settings.create_action ("current"));647 group.add_action (source_settings.create_action ("current"));
615648
616 var action = new SimpleAction ("next", null);649 var action = new SimpleAction ("next", null);
617 action.activate.connect (handle_middle_click);650 action.activate.connect (handle_middle_click);
618 group.insert (action);651 group.add_action (action);
619652
620 action = new SimpleAction ("scroll", VariantType.INT32);653 action = new SimpleAction ("scroll", VariantType.INT32);
621 action.activate.connect (handle_scroll_wheel);654 action.activate.connect (handle_scroll_wheel);
622 group.insert (action);655 group.add_action (action);
623656
624 action = new SimpleAction ("map", null);657 action = new SimpleAction ("map", null);
625 action.activate.connect (handle_activate_map);658 action.activate.connect (handle_activate_map);
626 group.insert (action);659 group.add_action (action);
627660
628 action = new SimpleAction ("chart", null);661 action = new SimpleAction ("chart", null);
629 action.activate.connect (handle_activate_chart);662 action.activate.connect (handle_activate_chart);
630 group.insert (action);663 group.add_action (action);
631664
632 action = new SimpleAction ("settings", null);665 action = new SimpleAction ("settings", null);
633 action.activate.connect (handle_activate_settings);666 action.activate.connect (handle_activate_settings);
634 group.insert (action);667 group.add_action (action);
635668
636 return group;669 return group;
637 }670 }
@@ -649,12 +682,9 @@
649 private void update_sources_menu () {682 private void update_sources_menu () {
650 if (sources_menu != null) {683 if (sources_menu != null) {
651 var menu = get_sources_menu ();684 var menu = get_sources_menu ();
652685 menu.remove_all ();
653 while (menu.get_n_items () > 0)
654 menu.remove (0);
655686
656 var sources = get_sources ();687 var sources = get_sources ();
657
658 for (var i = 0; i < sources.length; i++) {688 for (var i = 0; i < sources.length; i++) {
659 var item = new MenuItem (sources[i].name, "indicator.current");689 var item = new MenuItem (sources[i].name, "indicator.current");
660 item.set_attribute (Menu.ATTRIBUTE_TARGET, "u", i);690 item.set_attribute (Menu.ATTRIBUTE_TARGET, "u", i);
@@ -682,12 +712,32 @@
682 }712 }
683713
684 [DBus (visible = false)]714 [DBus (visible = false)]
685 protected virtual MenuModel create_menu_model (MenuModel section_menu) {715 private void update_ibus_menu (IBus.PropList list) {
716 get_ibus_menu ().set_properties (list);
717 }
718
719 [DBus (visible = false)]
720 private IBusMenu get_ibus_menu () {
721 if (ibus_menu == null) {
722 ibus_menu = new IBusMenu (get_action_group ());
723 ((!) ibus_menu).activate.connect ((property, state) => {
724 if (panel_service != null) {
725 ((!) panel_service).property_activate (property.key, state);
726 }
727 });
728 }
729
730 return (!) ibus_menu;
731 }
732
733 [DBus (visible = false)]
734 protected virtual MenuModel create_menu_model (MenuModel sources_menu, MenuModel ibus_menu) {
686 var menu = new Menu ();735 var menu = new Menu ();
687736
688 var submenu = new Menu ();737 var submenu = new Menu ();
689738
690 submenu.append_section (null, section_menu);739 submenu.append_section (null, sources_menu);
740 submenu.append_section (null, ibus_menu);
691741
692 if (!is_login_user ()) {742 if (!is_login_user ()) {
693 var section = new Menu ();743 var section = new Menu ();
@@ -710,7 +760,7 @@
710 [DBus (visible = false)]760 [DBus (visible = false)]
711 public MenuModel get_menu_model () {761 public MenuModel get_menu_model () {
712 if (menu_model == null) {762 if (menu_model == null) {
713 menu_model = create_menu_model (get_sources_menu ());763 menu_model = create_menu_model (get_sources_menu (), get_ibus_menu ());
714 }764 }
715765
716 return (!) menu_model;766 return (!) menu_model;
717767
=== modified file 'lib/source.vala'
--- lib/source.vala 2013-08-07 19:11:32 +0000
+++ lib/source.vala 2014-02-20 04:00:27 +0000
@@ -69,6 +69,14 @@
69 construct set { _use_gtk = value; icon = null; }69 construct set { _use_gtk = value; icon = null; }
70 }70 }
7171
72 public bool is_xkb {
73 get { return xkb != null; }
74 }
75
76 public bool is_ibus {
77 get { return ibus != null; }
78 }
79
72 public Source (Variant variant, bool use_gtk = false) {80 public Source (Variant variant, bool use_gtk = false) {
73 Object (use_gtk: use_gtk);81 Object (use_gtk: use_gtk);
7482

Subscribers

People subscribed via source and target branches

to all changes: