1
/* vim:set et sts=4 sw=4:
5
* Copyright(c) 2013 Red Hat, Inc.
6
* Copyright(c) 2013 Peng Huang <shawn.p.huang@gmail.com>
7
* Copyright(c) 2013 Takao Fujiwara <takao.fujiwara1@gmail.com>
9
* This library is free software; you can redistribute it and/or
10
* modify it under the terms of the GNU Lesser General Public
11
* License as published by the Free Software Foundation; either
12
* version 2.1 of the License, or (at your option) any later version.
14
* This library is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
* Lesser General Public License for more details.
19
* You should have received a copy of the GNU Lesser General Public
20
* License along with this library; if not, write to the Free Software
21
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
31
public class PropertyPanel : Gtk.Box {
32
private Gtk.Window m_toplevel;
33
private IBus.PropList m_props;
34
private IPropToolItem[] m_items;
35
private Gdk.Rectangle m_cursor_location = Gdk.Rectangle(){
36
x = -1, y = -1, width = 0, height = 0 };
37
private int m_show = PanelShow.AUTO_HIDE;
38
private uint m_auto_hide_timeout = 10000;
39
private uint m_auto_hide_timeout_id = 0;
41
public PropertyPanel() {
42
/* Chain up base class constructor */
43
GLib.Object(orientation: Gtk.Orientation.HORIZONTAL,
48
m_toplevel = new Gtk.Window(Gtk.WindowType.POPUP);
49
m_toplevel.add_events(Gdk.EventMask.BUTTON_PRESS_MASK);
51
Handle handle = new Handle();
52
handle.set_visible(true);
53
pack_start(handle, false, false, 0);
58
public void set_properties(IBus.PropList props) {
59
foreach (var item in m_items)
60
(item as Gtk.Widget).destroy();
68
public void update_property(IBus.Property prop) {
69
GLib.assert(prop != null);
71
debug("update_property(prop.key = %s)\n", prop.get_key());
74
m_props.update_property(prop);
76
/* Need to update GUI since panel buttons are not redrawn. */
77
foreach (var item in m_items)
78
item.update_property(prop);
80
show_with_auto_hide_timer();
83
public void set_cursor_location(int x, int y, int width, int height) {
84
/* FIXME: set_cursor_location() has a different behavior
85
* in embedded preedit by applications.
86
* GtkTextView applications, e.g. gedit, always call
87
* set_cursor_location() with and without preedit
88
* but VTE applications, e.g. gnome-terminal, and xterm
89
* do not call set_cursor_location() with preedit.
90
* firefox and thunderbird do not call set_cursor_location()
92
* This may treat GtkIMContext and XIM with different ways.
93
* Maybe get_preedit_string() class method.
96
/* FIXME: When the cursor is at the bottom of the screen,
97
* gedit returns the right cursor position but terminal applications
98
* such as gnome-terminal, xfce4-terminal and etc, the position is
99
* not accurate and the cursor and panel could be overlapped slightly.
100
* Maybe it's a bug in vte.
102
Gdk.Rectangle location = Gdk.Rectangle(){
103
x = x, y = y, width = width, height = height };
105
if (m_cursor_location == location)
108
debug("set_cursor_location(x = %d, y = %d, width = %d, height = %d)\n",
109
x, y, width, height);
111
/* Hide the panel in AUTO_HIDE mode when the cursor position is
112
* chagned on the same input context by typing keyboard or
113
* clicking mouse. (But not focus change or property change)
115
if (m_show == PanelShow.AUTO_HIDE)
116
if (m_cursor_location.x != -1 || m_cursor_location.y != -1) {
117
m_cursor_location = location;
119
adjust_window_position();
123
m_cursor_location = location;
124
adjust_window_position();
125
show_with_auto_hide_timer();
128
public void set_preedit_text(IBus.Text? text, uint cursor) {
129
if (text == null && cursor == 0)
132
debug("set_preedit_text(text, cursor = %u)\n", cursor);
134
/* Hide the panel in AUTO_HIDE mode when embed-preedit-text value
135
* is disabled and the preedit is changed on the same input context.
140
public void set_auxiliary_text(IBus.Text? text) {
144
debug("set_auxiliary_text(text)\n");
149
public void set_lookup_table(IBus.LookupTable? table) {
153
debug("set_lookup_table(table)\n");
158
public new void show() {
159
if (m_show == PanelShow.DO_NOT_SHOW || m_items.length == 0) {
163
else if (m_show == PanelShow.ALWAYS) {
164
m_toplevel.show_all();
168
/* Do not change the state here if m_show == AUTO_HIDE. */
171
public new void hide() {
175
public void focus_in() {
176
debug("focus_in()\n");
178
/* Reset m_auto_hide_timeout_id in previous focus-in */
181
/* Invalidate m_cursor_location before set_cursor_location()
182
* is called because the position can be same even if the input
184
* E.g. Two tabs on gnome-terminal can keep the cursor position.
186
m_cursor_location = { -1, -1, 0, 0 };
188
/* set_cursor_location() will be called later. */
191
public void set_show(int _show) {
196
public void set_auto_hide_timeout(uint timeout) {
197
m_auto_hide_timeout = timeout;
200
public override void get_preferred_width(out int minimum_width,
201
out int natural_width) {
202
base.get_preferred_width(out minimum_width, out natural_width);
203
m_toplevel.resize(1, 1);
206
public override void get_preferred_height(out int minimum_width,
207
out int natural_width) {
208
base.get_preferred_height(out minimum_width, out natural_width);
209
m_toplevel.resize(1, 1);
212
private void create_menu_items() {
215
IBus.Property prop = m_props.get(i);
220
IPropToolItem item = null;
221
switch(prop.get_prop_type()) {
222
case IBus.PropType.NORMAL:
223
item = new PropToolButton(prop);
225
case IBus.PropType.TOGGLE:
226
item = new PropToggleToolButton(prop);
228
case IBus.PropType.MENU:
229
item = new PropMenuToolButton(prop);
231
case IBus.PropType.SEPARATOR:
232
item = new PropSeparatorToolItem(prop);
235
warning("unknown property type %d",
236
(int) prop.get_prop_type());
240
pack_start(item as Gtk.Widget, false, false, 0);
242
item.property_activate.connect((w, k, s) =>
243
property_activate(k, s));
248
private void move(int x, int y) {
249
m_toplevel.move(x, y);
252
private void adjust_window_position() {
253
Gdk.Point cursor_right_bottom = {
254
m_cursor_location.x + m_cursor_location.width,
255
m_cursor_location.y + m_cursor_location.height
258
Gtk.Allocation allocation;
259
m_toplevel.get_allocation(out allocation);
260
Gdk.Point window_right_bottom = {
261
cursor_right_bottom.x + allocation.width,
262
cursor_right_bottom.y + allocation.height
265
Gdk.Window root = Gdk.get_default_root_window();
266
int root_width = root.get_width();
267
int root_height = root.get_height();
270
if (window_right_bottom.x > root_width)
271
x = root_width - allocation.width;
273
x = cursor_right_bottom.x;
275
if (window_right_bottom.y > root_height)
276
y = m_cursor_location.y - allocation.height;
278
y = cursor_right_bottom.y;
283
private void show_with_auto_hide_timer() {
284
if (m_show != PanelShow.AUTO_HIDE || m_items.length == 0)
287
if (m_auto_hide_timeout_id != 0)
288
GLib.Source.remove(m_auto_hide_timeout_id);
290
m_toplevel.show_all();
292
/* Change the priority because IME typing sometimes freezes. */
293
m_auto_hide_timeout_id = GLib.Timeout.add(m_auto_hide_timeout, () => {
295
m_auto_hide_timeout_id = 0;
298
GLib.Priority.DEFAULT_IDLE);
301
private void hide_if_necessary() {
302
if (m_show == PanelShow.AUTO_HIDE && m_auto_hide_timeout_id != 0) {
303
GLib.Source.remove(m_auto_hide_timeout_id);
304
m_auto_hide_timeout_id = 0;
309
public signal void property_activate(string key, int state);
312
public interface IPropToolItem : GLib.Object {
313
public abstract void update_property(IBus.Property prop);
314
public signal void property_activate(string key, int state);
317
public class PropMenu : Gtk.Menu, IPropToolItem {
318
private Gtk.Widget m_parent_button;
319
private IPropItem[] m_items;
321
public PropMenu(IBus.Property prop) {
322
/* Chain up base class constructor */
325
set_take_focus(false);
326
create_items(prop.get_sub_props());
328
set_sensitive(prop.get_sensitive());
331
public void update_property(IBus.Property prop) {
332
foreach (var item in m_items)
333
item.update_property(prop);
336
public new void popup(uint button,
337
uint32 activate_time,
339
m_parent_button = widget;
340
base.popup(null, null, menu_position, button, activate_time);
343
public override void destroy() {
344
m_parent_button = null;
349
private void create_items(IBus.PropList props) {
351
PropRadioMenuItem last_radio = null;
354
IBus.Property prop = props.get(i);
359
IPropItem item = null;
360
switch(prop.get_prop_type()) {
361
case IBus.PropType.NORMAL:
362
item = new PropImageMenuItem(prop);
364
case IBus.PropType.TOGGLE:
365
item = new PropCheckMenuItem(prop);
367
case IBus.PropType.RADIO:
368
last_radio = new PropRadioMenuItem(prop, last_radio);
371
case IBus.PropType.MENU:
373
var menuitem = new PropImageMenuItem(prop);
374
menuitem.set_submenu(new PropMenu(prop));
378
case IBus.PropType.SEPARATOR:
379
item = new PropSeparatorMenuItem(prop);
382
warning("Unknown property type: %d",
383
(int) prop.get_prop_type());
386
if (prop.get_prop_type() != IBus.PropType.RADIO)
389
append(item as Gtk.MenuItem);
390
item.property_activate.connect((w, k, s) =>
391
property_activate(k, s));
397
private void menu_position(Gtk.Menu menu,
401
var button = m_parent_button;
402
var screen = button.get_screen();
403
var monitor = screen.get_monitor_at_window(button.get_window());
405
Gdk.Rectangle monitor_location;
406
screen.get_monitor_geometry(monitor, out monitor_location);
408
button.get_window().get_origin(out x, out y);
410
Gtk.Allocation button_allocation;
411
button.get_allocation(out button_allocation);
413
x += button_allocation.x;
414
y += button_allocation.y;
418
menu.get_size_request(out menu_width, out menu_height);
420
if (x + menu_width >= monitor_location.width)
421
x -= menu_width - button_allocation.width;
422
else if (x - menu_width <= 0)
425
if (x <= monitor_location.width * 3 / 4)
428
x -= menu_width - button_allocation.width;
431
if (y + button_allocation.height + menu_width
432
>= monitor_location.height)
434
else if (y - menu_height <= 0)
435
y += button_allocation.height;
437
if (y <= monitor_location.height * 3 / 4)
438
y += button_allocation.height;
447
public class PropToolButton : Gtk.ToolButton, IPropToolItem {
448
private IBus.Property m_prop = null;
450
public PropToolButton(IBus.Property prop) {
451
string label = prop.get_symbol().get_text();
453
/* Chain up base class constructor */
454
GLib.Object(label: label);
458
set_homogeneous(false);
462
public void update_property(IBus.Property prop) {
463
if (m_prop.get_key() != prop.get_key())
465
m_prop.set_symbol(prop.get_symbol());
466
m_prop.set_tooltip(prop.get_tooltip());
467
m_prop.set_sensitive(prop.get_sensitive());
468
m_prop.set_icon(prop.get_icon());
469
m_prop.set_state(prop.get_state());
470
m_prop.set_visible(prop.get_visible());
474
private void sync() {
475
set_label(m_prop.get_symbol().get_text());
476
set_tooltip_text(m_prop.get_tooltip().get_text());
477
set_sensitive(m_prop.get_sensitive());
478
set_icon_name(m_prop.get_icon());
480
if (m_prop.get_visible())
486
public override void clicked() {
487
property_activate(m_prop.get_key(), m_prop.get_state());
490
public new void set_icon_name(string icon_name) {
491
string label = m_prop.get_symbol().get_text();
492
IconWidget icon_widget = null;
496
icon_widget = new IconWidget(icon_name, Gtk.IconSize.BUTTON);
497
set_is_important(false);
499
set_is_important(true);
502
set_icon_widget(icon_widget);
506
public class PropToggleToolButton : Gtk.ToggleToolButton, IPropToolItem {
507
private IBus.Property m_prop = null;
509
public PropToggleToolButton(IBus.Property prop) {
510
/* Chain up base class constructor */
515
set_homogeneous(false);
519
public new void set_property(IBus.Property prop) {
524
public void update_property(IBus.Property prop) {
525
if (m_prop.get_key() != prop.get_key())
527
m_prop.set_symbol(prop.get_symbol());
528
m_prop.set_tooltip(prop.get_tooltip());
529
m_prop.set_sensitive(prop.get_sensitive());
530
m_prop.set_icon(prop.get_icon());
531
m_prop.set_state(prop.get_state());
532
m_prop.set_visible(prop.get_visible());
536
private void sync() {
537
set_label(m_prop.get_symbol().get_text());
538
set_tooltip_text(m_prop.get_tooltip().get_text());
539
set_sensitive(m_prop.get_sensitive());
540
set_icon_name(m_prop.get_icon());
541
set_active(m_prop.get_state() == IBus.PropState.CHECKED);
543
if (m_prop.get_visible())
549
public override void toggled() {
550
/* Do not send property-activate to engine in case the event is
551
* sent from engine. */
553
bool do_emit = false;
556
if (m_prop.get_state() != IBus.PropState.CHECKED)
558
m_prop.set_state(IBus.PropState.CHECKED);
560
if (m_prop.get_state() != IBus.PropState.UNCHECKED)
562
m_prop.set_state(IBus.PropState.UNCHECKED);
566
property_activate(m_prop.get_key(), m_prop.get_state());
569
public new void set_icon_name(string icon_name) {
570
string label = m_prop.get_symbol().get_text();
571
IconWidget icon_widget = null;
575
icon_widget = new IconWidget(icon_name, Gtk.IconSize.BUTTON);
576
set_is_important(false);
578
set_is_important(true);
581
set_icon_widget(icon_widget);
585
public class PropMenuToolButton : PropToggleToolButton, IPropToolItem {
586
private PropMenu m_menu = null;
588
public PropMenuToolButton(IBus.Property prop) {
589
/* Chain up base class constructor */
592
m_menu = new PropMenu(prop);
593
m_menu.deactivate.connect((m) =>
595
m_menu.property_activate.connect((w, k, s) =>
596
property_activate(k, s));
598
base.set_property(prop);
601
public new void update_property(IBus.Property prop) {
602
base.update_property(prop);
603
m_menu.update_property(prop);
606
public override void toggled() {
608
m_menu.popup(0, Gtk.get_current_event_time(), this);
611
public override void destroy() {
617
public class PropSeparatorToolItem : Gtk.SeparatorToolItem, IPropToolItem {
618
public PropSeparatorToolItem(IBus.Property prop) {
619
/* Chain up base class constructor */
622
set_homogeneous(false);
625
public void update_property(IBus.Property prop) {