diff options
-rw-r--r-- | data/org.gnome.Empathy.gschema.xml.in | 5 | ||||
-rw-r--r-- | libempathy/empathy-utils.c | 75 | ||||
-rw-r--r-- | libempathy/empathy-utils.h | 2 | ||||
-rw-r--r-- | src/empathy-main-window.c | 325 | ||||
-rw-r--r-- | src/empathy-main-window.ui | 29 |
5 files changed, 433 insertions, 3 deletions
diff --git a/data/org.gnome.Empathy.gschema.xml.in b/data/org.gnome.Empathy.gschema.xml.in index 27a8cb375..10001507d 100644 --- a/data/org.gnome.Empathy.gschema.xml.in +++ b/data/org.gnome.Empathy.gschema.xml.in @@ -55,6 +55,11 @@ <_summary>Show protocols</_summary> <_description>Whether to show protocols for contacts in the contact list.</_description> </key> + <key name="show-balance-in-roster" type="b"> + <default>false</default> + <_summary>Show Balance in roster</_summary> + <_description>Whether to show account balances for in the contact roster.</_description> + </key> <key name="compact-contact-list" type="b"> <default>false</default> <_summary>Compact contact list</_summary> diff --git a/libempathy/empathy-utils.c b/libempathy/empathy-utils.c index 24d7b544b..8d4cd8d68 100644 --- a/libempathy/empathy-utils.c +++ b/libempathy/empathy-utils.c @@ -29,6 +29,7 @@ #include "config.h" #include <string.h> +#include <math.h> #include <time.h> #include <sys/types.h> @@ -796,3 +797,77 @@ empathy_get_x509_certificate_hostname (gnutls_x509_crt_t cert) return NULL; } + +gchar * +empathy_format_currency (gint amount, + guint scale, + const gchar *currency) +{ +#define MINUS "\342\210\222" +#define EURO "\342\202\254" +#define YEN "\302\245" +#define POUND "\302\243" + + /* localised representations of currency */ + /* FIXME: check these, especially negatives and decimals */ + static const struct { + const char *currency; + const char *positive; + const char *negative; + const char *decimal; + } currencies[] = { + /* sym positive negative decimal */ + { "EUR", EURO "%s", MINUS EURO "%s", "." }, + { "USD", "$%s", MINUS "$%s", "." }, + { "JPY", YEN "%s" MINUS YEN "%s", "." }, + { "GBP", POUND "%s", MINUS POUND "%s", "." }, + { "PLN", "%s zl", MINUS "%s zl", "." }, + { "BRL", "R$%s", MINUS "R$%s", "." }, + { "SEK", "%s kr", MINUS "%s kr", "." }, + { "DKK", "kr %s", "kr " MINUS "%s", "." }, + { "HKD", "$%s", MINUS "$%s", "." }, + { "CHF", "%s Fr.", MINUS "%s Fr.", "." }, + { "NOK", "kr %s", "kr" MINUS "%s", "," }, + { "CAD", "$%s", MINUS "$%s", "." }, + { "TWD", "$%s", MINUS "$%s", "." }, + { "AUD", "$%s", MINUS "$%s", "." }, + }; + + const char *positive = "%s"; + const char *negative = MINUS "%s"; + const char *decimal = "."; + char *fmt_amount, *money; + guint i; + + /* get the localised currency format */ + for (i = 0; i < G_N_ELEMENTS (currencies); i++) { + if (!tp_strdiff (currency, currencies[i].currency)) { + positive = currencies[i].positive; + negative = currencies[i].negative; + decimal = currencies[i].decimal; + break; + } + } + + /* format the amount using the scale */ + if (scale == 0) { + /* no decimal point required */ + fmt_amount = g_strdup_printf ("%d", amount); + } else { + /* don't use floating point arithmatic, it's noisy; + * we take the absolute values, because we want the minus + * sign to appear before the $ */ + int divisor = pow (10, scale); + int dollars = abs (amount / divisor); + int cents = abs (amount % divisor); + + fmt_amount = g_strdup_printf ("%d%s%0*d", + dollars, decimal, scale, cents); + } + + money = g_strdup_printf (amount < 0 ? negative : positive, fmt_amount); + g_free (fmt_amount); + + return money; +} + diff --git a/libempathy/empathy-utils.h b/libempathy/empathy-utils.h index 2ddfb4c53..7ec96be13 100644 --- a/libempathy/empathy-utils.h +++ b/libempathy/empathy-utils.h @@ -102,6 +102,8 @@ gboolean empathy_folks_persona_is_interesting (FolksPersona *persona); gchar * empathy_get_x509_certificate_hostname (gnutls_x509_crt_t cert); +gchar *empathy_format_currency (gint amount, guint scale, const gchar *currency); + G_END_DECLS #endif /* __EMPATHY_UTILS_H__ */ diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c index 864ef474e..4c1c788ed 100644 --- a/src/empathy-main-window.c +++ b/src/empathy-main-window.c @@ -144,6 +144,10 @@ struct _EmpathyMainWindowPriv { GtkWidget *edit_context; GtkWidget *edit_context_separator; + GtkActionGroup *balance_action_group; + GtkAction *view_balance_show_in_roster; + GtkWidget *balance_vbox; + guint size_timeout_id; /* reffed TpAccount* => visible GtkInfoBar* */ @@ -904,6 +908,274 @@ main_window_update_status (EmpathyMainWindow *window) } } +static char * +main_window_account_to_action_name (TpAccount *account) +{ + char *r; + + /* action names can't have '/' in them, replace it with '.' */ + r = g_strdup (tp_account_get_path_suffix (account)); + r = g_strdelimit (r, "/", '.'); + + return r; +} + +static void +main_window_balance_activate_cb (GtkAction *action, + EmpathyMainWindow *window) +{ + TpAccount *account = g_object_get_data (G_OBJECT (action), "account"); + const char *protocol = tp_account_get_protocol (account); + + /* FIXME: need a generic way to find out how to top-up an + * account that also works with arbitrary SIP and XMPP gateways -- + * https://bugs.freedesktop.org/show_bug.cgi?id=36254 */ + if (!tp_strdiff (protocol, "skype")) { + empathy_url_show (GTK_WIDGET (window), + "http://go.skype.com/store.buy.skypecredit"); + } else { + DEBUG ("unknown protocol for top-up"); + } +} + +static void +main_window_balance_update_balance (GtkAction *action, + GValueArray *balance) +{ + TpAccount *account = g_object_get_data (G_OBJECT (action), "account"); + int amount = 0; + guint scale = G_MAXINT32; + const char *currency = ""; + char *str; + + if (balance != NULL) + tp_value_array_unpack (balance, 3, + &amount, + &scale, + ¤cy); + + if (amount == 0 && + scale == G_MAXINT32 && + tp_str_empty (currency)) { + /* unknown balance */ + str = g_strdup_printf ("%s (--)", + tp_account_get_display_name (account)); + } else { + char *money = empathy_format_currency (amount, scale, currency); + + str = g_strdup_printf ("%s (%s %s)", + tp_account_get_display_name (account), + currency, money); + g_free (money); + } + + gtk_action_set_label (action, str); + g_free (str); +} + +static void +main_window_setup_balance_got_balance (TpProxy *conn, + const GValue *value, + const GError *in_error, + gpointer user_data, + GObject *action) +{ + GValueArray *balance = NULL; + + if (in_error != NULL) { + DEBUG ("Failed to get account balance: %s", + in_error->message); + } else if (!G_VALUE_HOLDS (value, TP_STRUCT_TYPE_CURRENCY_AMOUNT)) { + DEBUG ("Type mismatch"); + } else { + balance = g_value_get_boxed (value); + } + + main_window_balance_update_balance (GTK_ACTION (action), balance); +} + +static void +main_window_balance_changed_cb (TpConnection *conn, + const GValueArray *balance, + gpointer user_data, + GObject *action) +{ + main_window_balance_update_balance (GTK_ACTION (action), + (GValueArray *) balance); +} + +static GtkAction * +main_window_setup_balance_create_action (EmpathyMainWindow *window, + TpAccount *account) +{ + EmpathyMainWindowPriv *priv = GET_PRIV (window); + GtkAction *action; + char *name, *ui; + guint merge_id; + GError *error = NULL; + + /* create the action group if required */ + if (priv->balance_action_group == NULL) { + priv->balance_action_group = + gtk_action_group_new ("balance-action-group"); + + gtk_ui_manager_insert_action_group (priv->ui_manager, + priv->balance_action_group, -1); + } + + /* create the action */ + name = main_window_account_to_action_name (account); + action = gtk_action_new (name, + tp_account_get_display_name (account), + _("Top up account credit"), + NULL); + g_object_bind_property (account, "icon-name", action, "icon-name", + G_BINDING_SYNC_CREATE); + + g_object_set_data (G_OBJECT (action), "account", account); + g_signal_connect (action, "activate", + G_CALLBACK (main_window_balance_activate_cb), window); + + gtk_action_group_add_action (priv->balance_action_group, action); + g_object_unref (action); + + ui = g_strdup_printf ( + "<ui>" + " <menubar name='menubar'>" + " <menu action='view'>" + " <placeholder name='view_balance_placeholder'>" + " <menuitem action='%s'/>" + " </placeholder>" + " </menu>" + " </menubar>" + "</ui>", + name); + + merge_id = gtk_ui_manager_add_ui_from_string (priv->ui_manager, + ui, -1, &error); + if (error != NULL) { + DEBUG ("Failed to add balance UI for %s: %s", + tp_account_get_display_name (account), + error->message); + g_error_free (error); + } + + g_object_set_data (G_OBJECT (action), + "merge-id", GUINT_TO_POINTER (merge_id)); + + g_free (name); + g_free (ui); + + return action; +} + +static GtkWidget * +main_window_setup_balance_create_widget (EmpathyMainWindow *window, + GtkAction *action) +{ + EmpathyMainWindowPriv *priv = GET_PRIV (window); + GtkWidget *hbox, *image, *label, *button; + + hbox = gtk_hbox_new (FALSE, 6); + + image = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0); + gtk_widget_show (image); + + label = gtk_label_new (""); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + button = gtk_button_new_with_label (_("Top Up...")); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0); + gtk_widget_show (button); + + gtk_box_pack_start (GTK_BOX (priv->balance_vbox), hbox, FALSE, TRUE, 0); + gtk_widget_show_all (hbox); + + /* bind the properties from the action to the widgets -- I could have + * written a widget that implemented GtkActivatable, but effort */ + g_object_bind_property (action, "label", label, "label", + G_BINDING_SYNC_CREATE); + g_object_bind_property (action, "icon-name", image, "icon-name", + G_BINDING_SYNC_CREATE); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gtk_action_activate), action); + + /* tie the lifetime of the widget to the lifetime of the action */ + g_object_weak_ref (G_OBJECT (action), + (GWeakNotify) gtk_widget_destroy, hbox); + + return hbox; +} + +static void +main_window_setup_balance_conn_ready (GObject *conn, + GAsyncResult *result, + gpointer user_data) +{ + EmpathyMainWindow *window = user_data; + EmpathyMainWindowPriv *priv = GET_PRIV (window); + TpAccount *account = g_object_get_data (conn, "account"); + GtkAction *action; + GtkWidget *widget; + GError *error = NULL; + + if (!tp_proxy_prepare_finish (conn, result, &error)) { + DEBUG ("Failed to prepare connection: %s", error->message); + + g_error_free (error); + return; + } + + if (!tp_proxy_has_interface_by_id (conn, + TP_IFACE_QUARK_CONNECTION_INTERFACE_BALANCE)) { + return; + } + + DEBUG ("Setting up balance for acct: %s", + tp_account_get_display_name (account)); + + /* create the action */ + action = main_window_setup_balance_create_action (window, account); + + if (action == NULL) + return; + + gtk_action_set_visible (priv->view_balance_show_in_roster, TRUE); + + /* create the display widget */ + widget = main_window_setup_balance_create_widget (window, action); + + /* request the current balance and monitor for any changes */ + tp_cli_dbus_properties_call_get (conn, -1, + TP_IFACE_CONNECTION_INTERFACE_BALANCE, + "AccountBalance", + main_window_setup_balance_got_balance, + window, NULL, G_OBJECT (action)); + + tp_cli_connection_interface_balance_connect_to_balance_changed ( + TP_CONNECTION (conn), main_window_balance_changed_cb, + window, NULL, G_OBJECT (action), NULL); +} + +static void +main_window_setup_balance (EmpathyMainWindow *window, + TpAccount *account) +{ + TpConnection *conn = tp_account_get_connection (account); + + if (conn == NULL) + return; + + /* need to prepare the connection: + * store the account on the connection */ + g_object_set_data (G_OBJECT (conn), "account", account); + tp_proxy_prepare_async (conn, NULL, + main_window_setup_balance_conn_ready, window); +} + static void main_window_connection_changed_cb (TpAccount *account, guint old_status, @@ -913,6 +1185,8 @@ main_window_connection_changed_cb (TpAccount *account, GHashTable *details, EmpathyMainWindow *window) { + EmpathyMainWindowPriv *priv = GET_PRIV (window); + main_window_update_status (window); if (current == TP_CONNECTION_STATUS_DISCONNECTED && @@ -923,6 +1197,44 @@ main_window_connection_changed_cb (TpAccount *account, if (current == TP_CONNECTION_STATUS_DISCONNECTED) { empathy_sound_play (GTK_WIDGET (window), EMPATHY_SOUND_ACCOUNT_DISCONNECTED); + + /* remove balance action if required */ + if (priv->balance_action_group != NULL) { + GtkAction *action; + char *name; + GList *a; + + name = main_window_account_to_action_name (account); + + action = gtk_action_group_get_action ( + priv->balance_action_group, name); + + if (action != NULL) { + guint merge_id; + + DEBUG ("Removing action"); + + merge_id = GPOINTER_TO_UINT (g_object_get_data ( + G_OBJECT (action), + "merge-id")); + + gtk_ui_manager_remove_ui (priv->ui_manager, + merge_id); + gtk_action_group_remove_action ( + priv->balance_action_group, action); + } + + g_free (name); + + a = gtk_action_group_list_actions ( + priv->balance_action_group); + + gtk_action_set_visible ( + priv->view_balance_show_in_roster, + g_list_length (a) > 0); + + g_list_free (a); + } } if (current == TP_CONNECTION_STATUS_CONNECTED) { @@ -931,6 +1243,7 @@ main_window_connection_changed_cb (TpAccount *account, /* Account connected without error, remove error message if any */ main_window_remove_error (window, account); + main_window_setup_balance (window, account); } } @@ -1775,6 +2088,8 @@ account_manager_prepared_cb (GObject *source_object, window); g_hash_table_insert (priv->status_changed_handlers, account, GUINT_TO_POINTER (handler_id)); + + main_window_setup_balance (window, account); } g_signal_connect (manager, "account-validity-changed", @@ -1870,6 +2185,7 @@ empathy_main_window_init (EmpathyMainWindow *window) filename = empathy_file_lookup ("empathy-main-window.ui", "src"); gui = empathy_builder_get_file (filename, "main_vbox", &priv->main_vbox, + "balance_vbox", &priv->balance_vbox, "errors_vbox", &priv->errors_vbox, "auth_vbox", &priv->auth_vbox, "ui_manager", &priv->ui_manager, @@ -1887,6 +2203,7 @@ empathy_main_window_init (EmpathyMainWindow *window) "notebook", &priv->notebook, "no_entry_label", &priv->no_entry_label, "roster_scrolledwindow", &sw, + "view_balance_show_in_roster", &priv->view_balance_show_in_roster, NULL); g_free (filename); @@ -2044,6 +2361,14 @@ empathy_main_window_init (EmpathyMainWindow *window) /* Set window size. */ empathy_geometry_bind (GTK_WINDOW (window), GEOMETRY_NAME); + /* bind view_balance_show_in_roster */ + g_settings_bind (priv->gsettings_ui, "show-balance-in-roster", + priv->view_balance_show_in_roster, "active", + G_SETTINGS_BIND_DEFAULT); + g_object_bind_property (priv->view_balance_show_in_roster, "active", + priv->balance_vbox, "visible", + G_BINDING_SYNC_CREATE); + /* Enable event handling */ priv->call_observer = empathy_call_observer_dup_singleton (); priv->event_manager = empathy_event_manager_dup_singleton (); diff --git a/src/empathy-main-window.ui b/src/empathy-main-window.ui index a538e0120..95a6ec8dd 100644 --- a/src/empathy-main-window.ui +++ b/src/empathy-main-window.ui @@ -69,6 +69,13 @@ </object> </child> <child> + <object class="GtkToggleAction" id="view_balance_show_in_roster"> + <property name="name">view_balance_show_in_roster</property> + <property name="label" translatable="yes">Show Account _Balances in Roster</property> + <property name="visible">False</property> + </object> + </child> + <child> <object class="GtkAction" id="view_show_map"> <property name="name">view_show_map</property> <property name="label" translatable="yes">Contacts on a _Map</property> @@ -256,6 +263,9 @@ <menuitem action="view_show_offline"/> <menuitem action="view_show_protocols"/> <separator/> + <menuitem action="view_balance_show_in_roster"/> + <placeholder name="view_balance_placeholder"/> + <separator/> <menuitem action="view_sort_by_name"/> <menuitem action="view_sort_by_status"/> <separator/> @@ -305,7 +315,8 @@ </packing> </child> <child> - <object class="GtkVBox" id="errors_vbox"> + <object class="GtkVBox" id="balance_vbox"> + <property name="spacing">3</property> <child> <placeholder/> </child> @@ -317,7 +328,7 @@ </packing> </child> <child> - <object class="GtkVBox" id="auth_vbox"> + <object class="GtkVBox" id="errors_vbox"> <child> <placeholder/> </child> @@ -329,6 +340,18 @@ </packing> </child> <child> + <object class="GtkVBox" id="auth_vbox"> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">4</property> + </packing> + </child> + <child> <object class="GtkNotebook" id="notebook"> <property name="visible">True</property> <property name="can_focus">True</property> @@ -360,7 +383,7 @@ </child> </object> <packing> - <property name="position">4</property> + <property name="position">5</property> </packing> </child> </object> |