2
An indicator to show SyncMenuApps' states and menus
4
Copyright 2012 Canonical Ltd.
7
Charles Kerr <charles.kerr@canonical.com>
9
This program is free software: you can redistribute it and/or modify it
10
under the terms of the GNU General Public License version 3,
11
as published by the Free Software Foundation.
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranties of
15
MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.
16
See the GNU General Public License for more details.
18
You should have received a copy of the GNU General Public License along
19
with this program. If not, see <http://www.gnu.org/licenses/>.
27
#include <glib-object.h>
28
#include <glib/gi18n-lib.h>
31
#include <libdbusmenu-gtk/menu.h>
32
#include <libdbusmenu-gtk/menuitem.h>
34
#include <libindicator/indicator.h>
35
#include <libindicator/indicator-object.h>
36
#include <libindicator/indicator-service-manager.h>
38
#include <libido/idoswitchmenuitem.h>
40
#include "dbus-shared.h"
41
#include "sync-menu/sync-app.h"
42
#include "sync-menu/sync-enum.h"
43
#include "sync-service-dbus.h"
45
#define INDICATOR_SYNC_TYPE (indicator_sync_get_type ())
46
#define INDICATOR_SYNC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_SYNC_TYPE, IndicatorSync))
47
#define INDICATOR_SYNC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_SYNC_TYPE, IndicatorSyncClass))
48
#define IS_INDICATOR_SYNC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_SYNC_TYPE))
49
#define IS_INDICATOR_SYNC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_SYNC_TYPE))
50
#define INDICATOR_SYNC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_SYNC_TYPE, IndicatorSyncClass))
52
typedef struct _IndicatorSync IndicatorSync;
53
typedef struct _IndicatorSyncClass IndicatorSyncClass;
55
struct _IndicatorSyncClass
57
IndicatorObjectClass parent_class;
62
IndicatorObject parent;
63
IndicatorObjectEntry entry;
64
IndicatorServiceManager * service_manager;
65
DbusSyncService * sync_service_proxy;
66
DbusmenuGtkClient * menu_client;
69
GType indicator_sync_get_type (void);
71
/* Indicator Module Config */
73
INDICATOR_SET_TYPE(INDICATOR_SYNC_TYPE)
75
static void indicator_sync_class_init (IndicatorSyncClass *klass);
76
static void indicator_sync_init (IndicatorSync *self);
77
static void indicator_sync_dispose (GObject *object);
78
static void indicator_sync_finalize (GObject *object);
80
static gboolean new_item_app (DbusmenuMenuitem * newitem,
81
DbusmenuMenuitem * parent,
82
DbusmenuClient * client,
85
static gboolean new_item_prog (DbusmenuMenuitem * newitem,
86
DbusmenuMenuitem * parent,
87
DbusmenuClient * client,
90
static const gchar * calculate_icon_name (IndicatorSync * self);
92
static void on_service_manager_connection_changed (IndicatorServiceManager * sm,
101
get_entries (IndicatorObject * io)
103
return g_list_prepend (NULL, &INDICATOR_SYNC(io)->entry);
106
G_DEFINE_TYPE (IndicatorSync, indicator_sync, INDICATOR_OBJECT_TYPE);
109
indicator_sync_class_init (IndicatorSyncClass *klass)
111
GObjectClass * object_class = G_OBJECT_CLASS (klass);
112
object_class->dispose = indicator_sync_dispose;
113
object_class->finalize = indicator_sync_finalize;
115
IndicatorObjectClass * io_class = INDICATOR_OBJECT_CLASS(klass);
116
io_class->get_entries = get_entries;
120
indicator_sync_init (IndicatorSync *self)
122
g_object_set (self, INDICATOR_OBJECT_DEFAULT_VISIBILITY, FALSE, NULL);
125
DbusmenuGtkMenu * menu = dbusmenu_gtkmenu_new (SYNC_SERVICE_DBUS_NAME,
126
SYNC_SERVICE_DBUS_MENU_OBJECT);
127
self->menu_client = dbusmenu_gtkmenu_get_client (menu);
128
dbusmenu_client_add_type_handler (DBUSMENU_CLIENT(self->menu_client),
129
APPLICATION_MENUITEM_TYPE,
131
dbusmenu_client_add_type_handler (DBUSMENU_CLIENT(self->menu_client),
132
SYNC_MENU_PROGRESS_MENUITEM_TYPE,
136
self->entry.label = NULL; /* no label */
137
self->entry.image = g_object_ref_sink (gtk_image_new_from_icon_name (calculate_icon_name (self), GTK_ICON_SIZE_BUTTON));
138
self->entry.menu = g_object_ref_sink (menu);
139
self->entry.name_hint = PACKAGE;
140
self->entry.accessible_desc = NULL;
141
gtk_widget_show (GTK_WIDGET(self->entry.image));
143
/* init the service manager */
144
self->service_manager = indicator_service_manager_new_version (
145
SYNC_SERVICE_DBUS_NAME, 1);
146
g_signal_connect (self->service_manager,
147
INDICATOR_SERVICE_MANAGER_SIGNAL_CONNECTION_CHANGE,
148
G_CALLBACK(on_service_manager_connection_changed), self);
152
indicator_sync_dispose (GObject *object)
154
IndicatorSync * self = INDICATOR_SYNC(object);
155
g_return_if_fail (self != NULL);
157
g_clear_object (&self->sync_service_proxy);
158
g_clear_object (&self->service_manager);
159
g_clear_object (&self->entry.image);
161
if (self->entry.menu)
163
self->menu_client = NULL;
164
g_clear_object (&self->entry.menu);
167
G_OBJECT_CLASS (indicator_sync_parent_class)->dispose (object);
171
indicator_sync_finalize (GObject *object)
173
G_OBJECT_CLASS (indicator_sync_parent_class)->finalize (object);
181
get_service_state (IndicatorSync * self)
183
if (self->sync_service_proxy == NULL)
185
return SYNC_MENU_STATE_IDLE;
188
return dbus_sync_service_get_state (self->sync_service_proxy);
192
get_service_paused (IndicatorSync * self)
194
return (self->sync_service_proxy != NULL)
195
&& (dbus_sync_service_get_paused (self->sync_service_proxy));
199
get_service_count (IndicatorSync * self)
203
if (self->sync_service_proxy != NULL)
205
count = dbus_sync_service_get_client_count (self->sync_service_proxy);
211
/* show the indicator only if the service has SyncMenuApps */
213
update_visibility (IndicatorSync * self)
215
const gboolean new_visible = get_service_count (self) > 0;
216
g_debug (G_STRLOC" setting visibility flag to %d", (int)new_visible);
217
indicator_object_set_visible (INDICATOR_OBJECT(self), new_visible);
220
/* update our a11y text based on the sync service's state */
222
update_accessible_title (IndicatorSync * self)
224
const SyncMenuState state = get_service_state (self);
225
const gboolean paused = get_service_paused (self);
227
const gchar * desc = NULL;
229
if (state == SYNC_MENU_STATE_ERROR)
231
desc = _("Sync (error)");
233
else if (state == SYNC_MENU_STATE_SYNCING)
235
desc = _("Sync (syncing)");
239
desc = _("Sync (paused)");
246
if (g_strcmp0 (self->entry.accessible_desc, desc))
248
g_debug (G_STRLOC" setting accessible_desc to '%s'", desc);
249
self->entry.accessible_desc = desc;
251
INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE_ID,
257
/* figure out what icon to use */
259
calculate_icon_name (IndicatorSync * self)
261
const gchar * icon_name;
262
const SyncMenuState state = get_service_state (self);
263
const gboolean paused = get_service_paused (self);
265
if (state == SYNC_MENU_STATE_ERROR)
267
icon_name = "sync-error";
269
else if (state == SYNC_MENU_STATE_SYNCING)
271
icon_name = "sync-syncing";
275
icon_name = "sync-paused";
279
icon_name = "sync-idle";
285
/* update our icon based on the service's state */
287
update_icon (IndicatorSync * self)
289
g_return_if_fail (IS_INDICATOR_SYNC(self));
291
const gchar * const icon_name = calculate_icon_name (self);
292
g_debug (G_STRLOC" setting icon to '%s'", icon_name);
293
gtk_image_set_from_icon_name (GTK_IMAGE(self->entry.image), icon_name, GTK_ICON_SIZE_MENU);
297
on_service_client_count_changed (GObject * o G_GNUC_UNUSED,
298
GParamSpec * pspec G_GNUC_UNUSED,
301
IndicatorSync * self = INDICATOR_SYNC(user_data);
302
g_return_if_fail (self != NULL);
304
update_visibility (self);
308
on_service_state_changed (GObject * o G_GNUC_UNUSED,
309
GParamSpec * pspec G_GNUC_UNUSED,
312
IndicatorSync * self = INDICATOR_SYNC(user_data);
313
g_return_if_fail (self != NULL);
316
update_accessible_title (self);
320
on_service_paused_changed (GObject * o G_GNUC_UNUSED,
321
GParamSpec * pspec G_GNUC_UNUSED,
324
IndicatorSync * self = INDICATOR_SYNC(user_data);
325
g_return_if_fail (self != NULL);
328
update_accessible_title (self);
332
/* manage our service proxy based on whether or not the manager's connected */
334
on_service_manager_connection_changed (IndicatorServiceManager * sm,
338
IndicatorSync * self = INDICATOR_SYNC(user_data);
339
g_return_if_fail (self != NULL);
343
g_clear_object (&self->sync_service_proxy);
344
update_visibility (self);
346
else if (self->sync_service_proxy == NULL)
349
self->sync_service_proxy = dbus_sync_service_proxy_new_for_bus_sync (
351
G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
352
SYNC_SERVICE_DBUS_NAME,
353
SYNC_SERVICE_DBUS_OBJECT,
358
g_warning ("Indicator couldn't create SyncService proxy: %s", err->message);
359
g_clear_error (&err);
363
GObject * o = G_OBJECT (self->sync_service_proxy);
364
g_signal_connect (o, "notify::state",
365
G_CALLBACK(on_service_state_changed), self);
366
g_signal_connect (o, "notify::paused",
367
G_CALLBACK(on_service_paused_changed), self);
368
g_signal_connect (o, "notify::client-count",
369
G_CALLBACK(on_service_client_count_changed), self);
370
on_service_state_changed (o, NULL, self);
371
on_service_paused_changed (o, NULL, self);
372
on_service_client_count_changed (o, NULL, self);
379
**** APPLICATION_MENUITEM_TYPE handler
383
#define PROP_TYPE DBUSMENU_MENUITEM_PROP_TYPE
384
#define PROP_TOGGLE_STATE DBUSMENU_MENUITEM_PROP_TOGGLE_STATE
385
#define TOGGLE_STATE_CHECKED DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED
386
#define SIGNAL_PROPERTY_CHANGED DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED
388
static int aligned_left_margin = 0;
391
* This ugly little hack aligns the SyncMenuApp's exported GtkMenuItems'
392
* text with the AppMenuItem's GtkMenuItem text.
394
* Ideally we'd do this by setting the widgets' left margins when
395
* they are created, but that happens deep inside dbusmenu-gtk.
396
* So we need a way to get the widgets after they're created but
397
* before the users see them.
399
* The approach used here is to listen for the "map" signal from the
400
* widgets we /do/ create (in new_item_app()) so that we know when
401
* the menu is being popped up. When mapping happens, we walk through
402
* the menuitems and align them.
405
on_app_widget_shown (GtkWidget * unused, DbusmenuClient * client)
407
/* init the tweak key */
408
static GQuark tweak_quark = 0;
409
if (G_UNLIKELY(tweak_quark == 0))
411
tweak_quark = g_quark_from_static_string ("margin tweaked by i-sync");
414
DbusmenuMenuitem * root = dbusmenu_client_get_root (client);
421
GList * children = dbusmenu_menuitem_get_children (root);
422
DbusmenuGtkClient * gtk_client = DBUSMENU_GTKCLIENT (client);
423
for (l=children; l!=NULL; l=l->next)
425
DbusmenuMenuitem * child = DBUSMENU_MENUITEM(l->data);
427
/* AppMenuItems are already indented by their icons */
428
const gchar * type = dbusmenu_menuitem_property_get (child, PROP_TYPE);
429
if (!g_strcmp0 (type, APPLICATION_MENUITEM_TYPE))
434
GtkMenuItem * gmi = dbusmenu_gtkclient_menuitem_get (gtk_client, child);
436
GObject * o = G_OBJECT(gmi);
437
if (g_object_get_qdata (o, tweak_quark) == NULL)
439
g_object_set_qdata (o, tweak_quark, GINT_TO_POINTER(TRUE));
440
gtk_widget_set_margin_left (GTK_WIDGET(o), aligned_left_margin);
453
app_update_check (DbusmenuMenuitem * mi, struct AppWidgets * widgets)
455
const gint checked = dbusmenu_menuitem_property_get_int (mi, PROP_TOGGLE_STATE);
457
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM(widgets->gmi),
458
checked == TOGGLE_STATE_CHECKED);
462
app_update_icon (DbusmenuMenuitem * mi, struct AppWidgets * w)
464
const gchar * icon_name = dbusmenu_menuitem_property_get (mi, APPLICATION_MENUITEM_PROP_ICON);
466
gtk_image_set_from_icon_name (GTK_IMAGE(w->icon), icon_name, GTK_ICON_SIZE_MENU);
470
app_update_label (DbusmenuMenuitem * mi, struct AppWidgets * w)
472
const gchar * name = dbusmenu_menuitem_property_get (mi, APPLICATION_MENUITEM_PROP_NAME);
473
const SyncMenuState state = dbusmenu_menuitem_property_get_int (mi, APPLICATION_MENUITEM_PROP_STATE);
475
if (state != SYNC_MENU_STATE_ERROR)
477
gtk_label_set_text (GTK_LABEL(w->label), name);
481
/* FIXME: it would be nice if the error color played nicely with themes */
482
gchar * escaped = g_markup_escape_text (name, -1);
483
gchar * markup = g_strdup_printf ("<span foreground=\"red\">%s</span>", escaped);
484
gtk_label_set_markup (GTK_LABEL(w->label), markup);
491
on_app_property_changed (DbusmenuMenuitem * mi,
494
struct AppWidgets * w)
496
if (!g_strcmp0 (prop, APPLICATION_MENUITEM_PROP_STATE) ||
497
!g_strcmp0 (prop, APPLICATION_MENUITEM_PROP_NAME))
499
app_update_label (mi, w);
501
else if (!g_strcmp0(prop, APPLICATION_MENUITEM_PROP_ICON))
503
app_update_icon (mi, w);
505
else if (!g_strcmp0(prop, PROP_TOGGLE_STATE))
507
app_update_check (mi, w);
511
/* build a gtkmenuitem to represent an AppMenuItem */
513
new_item_app (DbusmenuMenuitem * newitem,
514
DbusmenuMenuitem * parent,
515
DbusmenuClient * client,
518
struct AppWidgets * w;
520
/* build the display widgets and initialize them */
521
w = g_new0 (struct AppWidgets, 1);
522
w->gmi = ido_switch_menu_item_new ();
523
w->icon = gtk_image_new ();
524
w->label = gtk_label_new (NULL);
525
app_update_icon (newitem, w);
526
app_update_label (newitem, w);
527
app_update_check (newitem, w);
529
/* layout & styling */
530
g_signal_connect (w->gmi, "map", G_CALLBACK(on_app_widget_shown), client);
532
gtk_widget_style_get(GTK_WIDGET(w->gmi), "toggle-spacing", &padding, NULL);
533
GtkWidget * hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, padding);
535
gtk_widget_get_preferred_width (w->icon, NULL, &icon_width);
536
aligned_left_margin = padding + icon_width;
537
gtk_misc_set_alignment(GTK_MISC(w->icon), 1.0 /* right aligned */, 0.5);
538
gtk_box_pack_start (GTK_BOX (hbox), w->icon, FALSE, FALSE, 0);
539
gtk_misc_set_alignment (GTK_MISC(w->label), 0.0, 0.5);
540
gtk_box_pack_start (GTK_BOX(hbox), w->label, TRUE, TRUE, 0);
541
gtk_container_add (ido_switch_menu_item_get_content_area(IDO_SWITCH_MENU_ITEM(w->gmi)), hbox);
542
gtk_widget_show_all (w->gmi);
544
/* let dbusmenu attach its standard handlers */
545
dbusmenu_gtkclient_newitem_base (DBUSMENU_GTKCLIENT(client),
547
GTK_MENU_ITEM(w->gmi),
550
/* listen for property changes that would affect how we draw this item */
551
g_signal_connect_data (newitem, SIGNAL_PROPERTY_CHANGED,
552
G_CALLBACK(on_app_property_changed),
553
w, (GClosureNotify)g_free, 0);
555
return TRUE; /* success */
560
**** SYNC_MENU_PROGRESS_MENUITEM_TYPE handler
562
**** FIXME: this would be a good candiate for promotion into IDO
563
**** if any other indicators need this in the future.
571
GtkWidget * progress_bar;
574
#define PROP_PERCENT SYNC_MENU_PROGRESS_MENUITEM_PROP_PERCENT_DONE
575
#define PROP_LABEL DBUSMENU_MENUITEM_PROP_LABEL
578
get_percent_done (DbusmenuMenuitem * mi)
580
return CLAMP (dbusmenu_menuitem_property_get_int (mi, PROP_PERCENT), 0, 100);
584
prog_update_name (DbusmenuMenuitem * mi, struct ProgWidgets * w)
586
const int percent_done = get_percent_done (mi);
587
const gchar * name = dbusmenu_menuitem_property_get (mi, PROP_LABEL);
589
char * text = g_strdup_printf ("%s ... %d%%", name, percent_done);
590
gtk_label_set_text (GTK_LABEL(w->label), text);
595
prog_update_percent (DbusmenuMenuitem * mi, struct ProgWidgets * w)
597
const int percent_done = get_percent_done (mi);
599
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(w->progress_bar),
603
/* update our display when some of the menuitem's properties change */
605
on_prog_property_changed (DbusmenuMenuitem * mi,
608
struct ProgWidgets * w)
610
if (!g_strcmp0 (prop, PROP_PERCENT))
612
prog_update_name (mi, w);
613
prog_update_percent (mi, w);
615
else if (!g_strcmp0 (prop, PROP_LABEL))
617
prog_update_name (mi, w);
621
/* Calculate a uniform width for the progmenus' progress bars & labels.
622
This is kind of a fuzzy guesswork of wanting to ellipsize filenames
623
after they get about 40 characters or so wide. */
625
calculate_prog_menuitem_preferred_width (GtkWidget * w)
628
const gchar * const test_case = "THIS STRING HAS FORTY CHARACTERS IN IT..";
631
pl = gtk_widget_create_pango_layout (w, test_case);
632
pango_layout_get_pixel_size (pl, &width, NULL);
633
g_clear_object (&pl);
639
new_item_prog (DbusmenuMenuitem * newitem,
640
DbusmenuMenuitem * parent,
641
DbusmenuClient * client,
644
struct ProgWidgets * w;
646
/* build the display widgets and initialize them */
647
w = g_new0 (struct ProgWidgets, 1);
648
w->label = gtk_label_new (NULL);
649
w->progress_bar = gtk_progress_bar_new ();
650
w->gmi = gtk_menu_item_new ();
651
prog_update_name (newitem, w);
652
prog_update_percent (newitem, w);
654
/* layout & styling */
655
GtkWidget * grid = gtk_grid_new ();
656
const int width = calculate_prog_menuitem_preferred_width (w->label);
657
gtk_widget_set_size_request (w->label, width, -1);
658
gtk_label_set_ellipsize (GTK_LABEL(w->label), PANGO_ELLIPSIZE_MIDDLE);
659
gtk_misc_set_alignment(GTK_MISC(w->label), 0.0, 0.5);
660
gtk_grid_attach (GTK_GRID(grid), w->label, 0, 0, 1, 1);
661
gtk_widget_set_size_request (w->progress_bar, width, -1);
662
gtk_grid_attach (GTK_GRID(grid), w->progress_bar, 0, 1, 1, 1);
663
gtk_container_add (GTK_CONTAINER(w->gmi), grid);
664
gtk_container_set_border_width (GTK_CONTAINER(w->gmi), 4);
665
gtk_widget_show_all (w->gmi);
667
/* let dbusmenu attach its standard handlers */
668
dbusmenu_gtkclient_newitem_base (DBUSMENU_GTKCLIENT(client),
670
GTK_MENU_ITEM(w->gmi),
673
/* listen for property changes that would affect how we draw this item */
674
g_signal_connect_data (newitem, SIGNAL_PROPERTY_CHANGED,
675
G_CALLBACK(on_prog_property_changed),
676
w, (GClosureNotify)g_free, 0);
679
return TRUE; /* success */