Merge lp:~attente/indicator-keyboard/ibus-panel into lp:indicator-keyboard
- ibus-panel
- Merge into trunk
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 |
Related bugs: |
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
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)
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://
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
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 |
PASSED: Continuous integration, rev:267 jenkins. qa.ubuntu. com/job/ indicator- keyboard- ci/45/ jenkins. qa.ubuntu. com/job/ indicator- keyboard- trusty- amd64-ci/ 12 jenkins. qa.ubuntu. com/job/ indicator- keyboard- trusty- armhf-ci/ 12 jenkins. qa.ubuntu. com/job/ indicator- keyboard- trusty- armhf-ci/ 12/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/indicator- keyboard- ci/45/rebuild
http://