/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2002-2007 Imendio AB
- * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2010 Collabora Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* Martyn Russell <martyn@imendio.com>
* Xavier Claessens <xclaesse@gmail.com>
* Jonny Lamb <jonny.lamb@collabora.co.uk>
+ * Travis Reitter <travis.reitter@collabora.co.uk>
*
* Part of this file is copied from GtkSourceView (gtksourceiter.c):
* Paolo Maggi
#include <gtk/gtk.h>
#include <gio/gio.h>
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+
#include "empathy-ui-utils.h"
#include "empathy-images.h"
+#include "empathy-live-search.h"
#include "empathy-smiley-manager.h"
-#include "empathy-conf.h"
#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
#include <libempathy/empathy-debug.h>
#include <libempathy/empathy-utils.h>
-#include <libempathy/empathy-dispatcher.h>
-#include <libempathy/empathy-idle.h>
#include <libempathy/empathy-ft-factory.h>
void
DEBUG ("Loading file %s", filename);
gui = gtk_builder_new ();
+ gtk_builder_set_translation_domain (gui, GETTEXT_PACKAGE);
if (!gtk_builder_add_from_file (gui, filename, &error)) {
g_critical ("GtkBuilder Error (%s): %s",
filename, error->message);
}
void
-empathy_builder_connect (GtkBuilder *gui,
- gpointer user_data,
- gchar *first_object,
+empathy_builder_connect (GtkBuilder *gui,
+ gpointer user_data,
+ const gchar *first_object,
...)
{
va_list args;
case TP_CONNECTION_PRESENCE_TYPE_AWAY:
return EMPATHY_IMAGE_AWAY;
case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
- return EMPATHY_IMAGE_EXT_AWAY;
+ if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+ EMPATHY_IMAGE_EXT_AWAY))
+ return EMPATHY_IMAGE_EXT_AWAY;
+
+ /* The 'extended-away' icon is not an official one so we fallback to idle if
+ * it's not implemented */
+ return EMPATHY_IMAGE_IDLE;
case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
- return EMPATHY_IMAGE_HIDDEN;
+ if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+ EMPATHY_IMAGE_HIDDEN))
+ return EMPATHY_IMAGE_HIDDEN;
+
+ /* The 'hidden' icon is not an official one so we fallback to offline if
+ * it's not implemented */
+ return EMPATHY_IMAGE_OFFLINE;
case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
case TP_CONNECTION_PRESENCE_TYPE_ERROR:
- case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
return EMPATHY_IMAGE_OFFLINE;
+ case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+ return EMPATHY_IMAGE_PENDING;
case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+ default:
return NULL;
}
return empathy_icon_name_for_presence (presence);
}
+const gchar *
+empathy_icon_name_for_individual (FolksIndividual *individual)
+{
+ FolksPresenceType folks_presence;
+ TpConnectionPresenceType presence;
+
+ folks_presence =
+ folks_presence_details_get_presence_type (
+ FOLKS_PRESENCE_DETAILS (individual));
+ presence = empathy_folks_presence_type_to_tp (folks_presence);
+
+ return empathy_icon_name_for_presence (presence);
+}
+
const gchar *
empathy_protocol_name_for_contact (EmpathyContact *contact)
{
static gboolean
empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
{
- gint width, height, rowstride, i;
+ gint height, rowstride, i;
guchar *pixels;
guchar *row;
- width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
pixels = gdk_pixbuf_get_pixels (pixbuf);
return TRUE;
}
+static GdkPixbuf *
+avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
+ GdkPixbuf *rounded_pixbuf;
+
+ rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+ gdk_pixbuf_copy_area (pixbuf, 0, 0,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ rounded_pixbuf,
+ 0, 0);
+ pixbuf = rounded_pixbuf;
+ } else {
+ g_object_ref (pixbuf);
+ }
+
+ if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
+ empathy_avatar_pixbuf_roundify (pixbuf);
+ }
+
+ return pixbuf;
+}
+
GdkPixbuf *
empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
gint width,
}
gdk_pixbuf_loader_close (loader, NULL);
-
- pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
- if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
- GdkPixbuf *rounded_pixbuf;
-
- rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
- gdk_pixbuf_get_width (pixbuf),
- gdk_pixbuf_get_height (pixbuf));
- gdk_pixbuf_copy_area (pixbuf, 0, 0,
- gdk_pixbuf_get_width (pixbuf),
- gdk_pixbuf_get_height (pixbuf),
- rounded_pixbuf,
- 0, 0);
- pixbuf = rounded_pixbuf;
- } else {
- g_object_ref (pixbuf);
- }
-
- if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
- empathy_avatar_pixbuf_roundify (pixbuf);
- }
+ pixbuf = avatar_pixbuf_from_loader (loader);
g_object_unref (loader);
return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
}
+typedef struct {
+ FolksIndividual *individual;
+ GSimpleAsyncResult *result;
+ guint width;
+ guint height;
+ struct SizeData size_data;
+ GdkPixbufLoader *loader;
+ GCancellable *cancellable;
+ guint8 data[512];
+} PixbufAvatarFromIndividualClosure;
+
+static PixbufAvatarFromIndividualClosure *
+pixbuf_avatar_from_individual_closure_new (FolksIndividual *individual,
+ GSimpleAsyncResult *result,
+ gint width,
+ gint height,
+ GCancellable *cancellable)
+{
+ PixbufAvatarFromIndividualClosure *closure;
+
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+ closure = g_new0 (PixbufAvatarFromIndividualClosure, 1);
+ closure->individual = g_object_ref (individual);
+ closure->result = g_object_ref (result);
+ closure->width = width;
+ closure->height = height;
+ closure->cancellable = g_object_ref (cancellable);
+
+ return closure;
+}
+
+static void
+pixbuf_avatar_from_individual_closure_free (
+ PixbufAvatarFromIndividualClosure *closure)
+{
+ g_object_unref (closure->cancellable);
+ tp_clear_object (&closure->loader);
+ g_object_unref (closure->individual);
+ g_object_unref (closure->result);
+ g_free (closure);
+}
+
+static void
+avatar_icon_load_close_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GError *error = NULL;
+
+ g_input_stream_close_finish (G_INPUT_STREAM (object), result, &error);
+
+ if (error != NULL) {
+ DEBUG ("Failed to close pixbuf stream: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+avatar_icon_load_read_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GInputStream *stream = G_INPUT_STREAM (object);
+ PixbufAvatarFromIndividualClosure *closure = user_data;
+ gssize n_read;
+ GError *error = NULL;
+
+ /* Finish reading this chunk from the stream */
+ n_read = g_input_stream_read_finish (stream, result, &error);
+ if (error != NULL) {
+ DEBUG ("Failed to finish read from pixbuf stream: %s",
+ error->message);
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out_close;
+ }
+
+ /* Write the chunk to the pixbuf loader */
+ if (!gdk_pixbuf_loader_write (closure->loader, (guchar *) closure->data,
+ n_read, &error)) {
+ DEBUG ("Failed to write to pixbuf loader: %s",
+ error ? error->message : "No error given");
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out_close;
+ }
+
+ if (n_read == 0) {
+ /* EOF? */
+ if (!gdk_pixbuf_loader_close (closure->loader, &error)) {
+ DEBUG ("Failed to close pixbuf loader: %s",
+ error ? error->message : "No error given");
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out;
+ }
+
+ /* We're done. */
+ g_simple_async_result_set_op_res_gpointer (closure->result,
+ avatar_pixbuf_from_loader (closure->loader),
+ g_object_unref);
+
+ goto out;
+ } else {
+ /* Loop round and read another chunk. */
+ g_input_stream_read_async (stream, closure->data,
+ G_N_ELEMENTS (closure->data),
+ G_PRIORITY_DEFAULT, closure->cancellable,
+ avatar_icon_load_read_cb, closure);
+
+ return;
+ }
+
+out_close:
+ /* We must close the pixbuf loader before unreffing it. */
+ gdk_pixbuf_loader_close (closure->loader, NULL);
+
+out:
+ /* Close the file for safety (even though it should be
+ * automatically closed when the stream is finalised). */
+ g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) avatar_icon_load_close_cb, NULL);
+
+ g_simple_async_result_complete (closure->result);
+
+ g_clear_error (&error);
+ pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+static void
+avatar_icon_load_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GLoadableIcon *icon = G_LOADABLE_ICON (object);
+ PixbufAvatarFromIndividualClosure *closure = user_data;
+ GInputStream *stream;
+ GError *error = NULL;
+
+ stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
+ if (error != NULL) {
+ DEBUG ("Failed to open avatar stream: %s", error->message);
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out;
+ }
+
+ closure->size_data.width = closure->width;
+ closure->size_data.height = closure->height;
+ closure->size_data.preserve_aspect_ratio = TRUE;
+
+ /* Load the data into a pixbuf loader in chunks. */
+ closure->loader = gdk_pixbuf_loader_new ();
+
+ g_signal_connect (closure->loader, "size-prepared",
+ G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+ &(closure->size_data));
+
+ /* Begin to read the first chunk. */
+ g_input_stream_read_async (stream, closure->data,
+ G_N_ELEMENTS (closure->data),
+ G_PRIORITY_DEFAULT, closure->cancellable,
+ avatar_icon_load_read_cb, closure);
+
+ g_object_unref (stream);
+
+ return;
+
+out:
+ g_simple_async_result_complete (closure->result);
+
+ g_clear_error (&error);
+ tp_clear_object (&stream);
+ pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+void
+empathy_pixbuf_avatar_from_individual_scaled_async (
+ FolksIndividual *individual,
+ gint width,
+ gint height,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GLoadableIcon *avatar_icon;
+ GSimpleAsyncResult *result;
+ PixbufAvatarFromIndividualClosure *closure;
+
+ result = g_simple_async_result_new (G_OBJECT (individual),
+ callback, user_data,
+ empathy_pixbuf_avatar_from_individual_scaled_async);
+
+ avatar_icon =
+ folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual));
+ if (avatar_icon == NULL)
+ goto out;
+
+ closure = pixbuf_avatar_from_individual_closure_new (individual, result,
+ width, height,
+ cancellable);
+ if (closure == NULL)
+ goto out;
+
+ g_loadable_icon_load_async (avatar_icon, width, cancellable,
+ avatar_icon_load_cb, closure);
+
+ g_object_unref (result);
+
+ return;
+
+out:
+ g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
+ g_simple_async_result_complete (result);
+ g_object_unref (result);
+}
+
+/* Return a ref on the GdkPixbuf */
+GdkPixbuf *
+empathy_pixbuf_avatar_from_individual_scaled_finish (
+ FolksIndividual *individual,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ gboolean result_valid;
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ result_valid = g_simple_async_result_is_valid (result,
+ G_OBJECT (individual),
+ empathy_pixbuf_avatar_from_individual_scaled_async);
+ g_return_val_if_fail (result_valid, NULL);
+
+ pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
+ return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
+}
+
GdkPixbuf *
empathy_pixbuf_contact_status_icon (EmpathyContact *contact,
gboolean show_protocol)
gint height, width;
gint numerator, denominator;
- g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
+ (show_protocol == FALSE), NULL);
g_return_val_if_fail (icon_name != NULL, NULL);
numerator = 3;
pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
- g_free (icon_filename);
-
if (pix_status == NULL) {
DEBUG ("Could not open icon %s\n", icon_filename);
+ g_free (icon_filename);
return NULL;
}
+ g_free (icon_filename);
+
if (!show_protocol)
return pix_status;
}
icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
+ if (icon_info == NULL)
+ return NULL;
+
ret = g_strdup (gtk_icon_info_get_filename (icon_info));
gtk_icon_info_free (icon_info);
return retval;
}
-gboolean
-empathy_window_get_is_visible (GtkWindow *window)
-{
- GdkWindowState state;
- GdkWindow *gdk_window;
-
- g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
-
- gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
- if (!gdk_window) {
- return FALSE;
- }
-
- state = gdk_window_get_state (gdk_window);
- if (state & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) {
- return FALSE;
- }
-
- return TRUE;
-}
-
-void
-empathy_window_iconify (GtkWindow *window, GtkStatusIcon *status_icon)
-{
- GdkRectangle icon_location;
- gulong data[4];
- Display *dpy;
- GdkWindow *gdk_window;
-
- gtk_status_icon_get_geometry (status_icon, NULL, &icon_location, NULL);
- gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
- dpy = gdk_x11_drawable_get_xdisplay (gdk_window);
-
- data[0] = icon_location.x;
- data[1] = icon_location.y;
- data[2] = icon_location.width;
- data[3] = icon_location.height;
-
- XChangeProperty (dpy,
- GDK_WINDOW_XID (gdk_window),
- gdk_x11_get_xatom_by_name_for_display (
- gdk_drawable_get_display (gdk_window),
- "_NET_WM_ICON_GEOMETRY"),
- XA_CARDINAL, 32, PropModeReplace,
- (guchar *)&data, 4);
-
- gtk_window_set_skip_taskbar_hint (window, TRUE);
- gtk_window_iconify (window);
-}
-
/* Takes care of moving the window to the current workspace. */
void
-empathy_window_present (GtkWindow *window)
+empathy_window_present_with_time (GtkWindow *window,
+ guint32 timestamp)
{
GdkWindow *gdk_window;
- guint32 timestamp;
g_return_if_fail (GTK_IS_WINDOW (window));
gtk_widget_hide (GTK_WIDGET (window));
}
- timestamp = gtk_get_current_event_time ();
- if (timestamp == 0)
- /* No event, fallback to _NET_WM_USER_TIME */
- timestamp = gdk_x11_display_get_user_time (gdk_display_get_default ());
+ if (timestamp == GDK_CURRENT_TIME)
+ gtk_window_present (window);
+ else
+ gtk_window_present_with_time (window, timestamp);
+}
- gtk_window_present_with_time (window, timestamp);
- gtk_window_set_skip_taskbar_hint (window, FALSE);
- gtk_window_deiconify (window);
+void
+empathy_window_present (GtkWindow *window)
+{
+ empathy_window_present_with_time (window, gtk_get_current_event_time ());
}
GtkWindow *
g_free (real_url);
}
-static void
-link_button_hook (GtkLinkButton *button,
- const gchar *link,
- gpointer user_data)
-{
- empathy_url_show (GTK_WIDGET (button), link);
-}
-
-GtkWidget *
-empathy_link_button_new (const gchar *url,
- const gchar *title)
-{
- static gboolean hook = FALSE;
-
- if (!hook) {
- hook = TRUE;
- gtk_link_button_set_uri_hook (link_button_hook, NULL, NULL);
- }
-
- return gtk_link_button_new_with_label (url, title);
-}
-
void
empathy_send_file (EmpathyContact *contact, GFile *file)
{
factory = empathy_ft_factory_dup_singleton ();
- empathy_ft_factory_new_transfer_outgoing (factory, contact, file);
+ empathy_ft_factory_new_transfer_outgoing (factory, contact, file,
+ empathy_get_current_action_time ());
uri = g_file_get_uri (file);
manager = gtk_recent_manager_get_default ();
g_object_unref (file);
}
+ g_object_unref (contact);
gtk_widget_destroy (GTK_WIDGET (widget));
}
g_signal_connect (widget, "response",
G_CALLBACK (file_manager_send_file_response_cb),
- contact);
+ g_object_ref (contact));
gtk_widget_show (widget);
}
GFile *file;
if (response == GTK_RESPONSE_OK) {
- factory = empathy_ft_factory_dup_singleton ();
+ GFile *parent;
+ GFileInfo *info;
+ guint64 free_space, file_size;
+ GError *error = NULL;
+
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ parent = g_file_get_parent (file);
+ info = g_file_query_filesystem_info (parent,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+ NULL, &error);
+
+ g_object_unref (parent);
+
+ if (error != NULL) {
+ g_warning ("Error: %s", error->message);
+
+ g_object_unref (file);
+ return;
+ }
- empathy_ft_factory_set_destination_for_incoming_handler
- (factory, handler, file);
+ free_space = g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+ file_size = empathy_ft_handler_get_total_bytes (handler);
+
+ g_object_unref (info);
+
+ if (file_size > free_space) {
+ GtkWidget *message = gtk_message_dialog_new (
+ GTK_WINDOW (dialog),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Insufficient free space to save file"));
+ char *file_size_str, *free_space_str;
+
+ file_size_str = g_format_size_for_display (file_size);
+ free_space_str = g_format_size_for_display (free_space);
+
+ gtk_message_dialog_format_secondary_text (
+ GTK_MESSAGE_DIALOG (message),
+ _("%s of free space are required to save this "
+ "file, but only %s is available. Please "
+ "choose another location."),
+ file_size_str, free_space_str);
+
+ gtk_dialog_run (GTK_DIALOG (message));
+
+ g_free (file_size_str);
+ g_free (free_space_str);
+ gtk_widget_destroy (message);
+
+ g_object_unref (file);
+
+ return;
+ }
+
+ factory = empathy_ft_factory_dup_singleton ();
+
+ empathy_ft_factory_set_destination_for_incoming_handler (
+ factory, handler, file);
g_object_unref (factory);
g_object_unref (file);
{
GtkWidget *widget;
const gchar *dir;
+ EmpathyContact *contact;
+ gchar *title;
+
+ contact = empathy_ft_handler_get_contact (handler);
+ g_assert (contact != NULL);
- widget = gtk_file_chooser_dialog_new (_("Select a destination"),
+ title = g_strdup_printf (_("Incoming file from %s"),
+ empathy_contact_get_alias (contact));
+
+ widget = gtk_file_chooser_dialog_new (title,
NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL,
G_CALLBACK (file_manager_receive_file_response_cb), handler);
gtk_widget_show (widget);
+ g_free (title);
+}
+
+void
+empathy_make_color_whiter (GdkRGBA *color)
+{
+ const GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
+
+ color->red = (color->red + white.red) / 2;
+ color->green = (color->green + white.green) / 2;
+ color->blue = (color->blue + white.blue) / 2;
}
+static void
+menu_deactivate_cb (GtkMenu *menu,
+ gpointer user_data)
+{
+ /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
+ g_signal_handlers_disconnect_by_func (menu,
+ menu_deactivate_cb, user_data);
+
+ gtk_menu_detach (menu);
+}
+
+/* Convenient function to create a GtkMenu attached to @attach_to and detach
+ * it when the menu is not displayed any more. This is useful when creating a
+ * context menu that we want to get rid as soon as it as been displayed. */
+GtkWidget *
+empathy_context_menu_new (GtkWidget *attach_to)
+{
+ GtkWidget *menu;
+
+ menu = gtk_menu_new ();
+
+ gtk_menu_attach_to_widget (GTK_MENU (menu), attach_to, NULL);
+
+ /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
+ * floating ref. We can either wait that @attach_to releases its ref when
+ * it will be destroyed (when leaving Empathy most of the time) or explicitely
+ * detach the menu when it's not displayed any more.
+ * We go for the latter as we don't want to keep useless menus in memory
+ * during the whole lifetime of Empathy. */
+ g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb), NULL);
+
+ return menu;
+}
+
+gint64
+empathy_get_current_action_time (void)
+{
+ return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
+}
+
+/* @words = empathy_live_search_strip_utf8_string (@text);
+ *
+ * User has to pass both so we don't have to compute @words ourself each time
+ * this function is called. */
+gboolean
+empathy_individual_match_string (FolksIndividual *individual,
+ const char *text,
+ GPtrArray *words)
+{
+ const gchar *str;
+ GeeSet *personas;
+ GeeIterator *iter;
+ gboolean retval = FALSE;
+
+ /* check alias name */
+ str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
+
+ if (empathy_live_search_match_words (str, words))
+ return TRUE;
+
+ personas = folks_individual_get_personas (individual);
+
+ /* check contact id, remove the @server.com part */
+ iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+ while (retval == FALSE && gee_iterator_next (iter))
+ {
+ FolksPersona *persona = gee_iterator_get (iter);
+ const gchar *p;
+
+ if (empathy_folks_persona_is_interesting (persona))
+ {
+ str = folks_persona_get_display_id (persona);
+
+ /* Accept the persona if @text is a full prefix of his ID; that allows
+ * user to find, say, a jabber contact by typing his JID. */
+ if (g_str_has_prefix (str, text))
+ {
+ retval = TRUE;
+ }
+ else
+ {
+ gchar *dup_str = NULL;
+ gboolean visible;
+
+ p = strstr (str, "@");
+ if (p != NULL)
+ str = dup_str = g_strndup (str, p - str);
+
+ visible = empathy_live_search_match_words (str, words);
+ g_free (dup_str);
+ if (visible)
+ retval = TRUE;
+ }
+ }
+ g_clear_object (&persona);
+ }
+ g_clear_object (&iter);
+
+ /* FIXME: Add more rules here, we could check phone numbers in
+ * contact's vCard for example. */
+ return retval;
+}
+
+static gboolean
+dtmf_dialpad_button_pressed_cb (GObject *button,
+ GtkEntry *entry)
+{
+ GtkEntryBuffer *buffer = gtk_entry_get_buffer (entry);
+ const gchar *label;
+
+ label = g_object_get_data (button, "label");
+ gtk_entry_buffer_insert_text (buffer, -1, label, -1);
+
+ return FALSE;
+}
+
+GtkWidget *
+empathy_create_dtmf_dialpad (GObject *self,
+ GCallback dtmf_button_pressed_cb,
+ GCallback dtmf_button_released_cb)
+{
+ GtkWidget *box, *entry, *table;
+ int i;
+ GQuark button_quark;
+ struct {
+ const gchar *label;
+ const gchar *sublabel;
+ TpDTMFEvent event;
+ } dtmfbuttons[] = { { "1", "", TP_DTMF_EVENT_DIGIT_1 },
+ { "2", "abc", TP_DTMF_EVENT_DIGIT_2 },
+ { "3", "def", TP_DTMF_EVENT_DIGIT_3 },
+ { "4", "ghi", TP_DTMF_EVENT_DIGIT_4 },
+ { "5", "jkl", TP_DTMF_EVENT_DIGIT_5 },
+ { "6", "mno", TP_DTMF_EVENT_DIGIT_6 },
+ { "7", "pqrs", TP_DTMF_EVENT_DIGIT_7 },
+ { "8", "tuv", TP_DTMF_EVENT_DIGIT_8 },
+ { "9", "wxyz", TP_DTMF_EVENT_DIGIT_9 },
+ { "#", "", TP_DTMF_EVENT_HASH },
+ { "0", "", TP_DTMF_EVENT_DIGIT_0 },
+ { "*", "", TP_DTMF_EVENT_ASTERISK },
+ { NULL, } };
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
+
+ entry = gtk_entry_new ();
+ gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
+
+ gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 3);
+
+ button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID);
+
+ table = gtk_table_new (4, 3, TRUE);
+
+ for (i = 0; dtmfbuttons[i].label != NULL; i++)
+ {
+ GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
+ GtkWidget *button = gtk_button_new ();
+ GtkWidget *label;
+ gchar *str;
+
+ gtk_container_add (GTK_CONTAINER (button), vbox);
+
+ /* main label */
+ label = gtk_label_new ("");
+ str = g_strdup_printf ("<span size='x-large'>%s</span>",
+ dtmfbuttons[i].label);
+ gtk_label_set_markup (GTK_LABEL (label), str);
+ g_free (str);
+
+ g_object_set_data (G_OBJECT (button), "label",
+ (gpointer) dtmfbuttons[i].label);
+
+ gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 3);
+
+ /* sub label */
+ label = gtk_label_new ("");
+ str = g_strdup_printf (
+ "<span foreground='#555555'>%s</span>",
+ dtmfbuttons[i].sublabel);
+ gtk_label_set_markup (GTK_LABEL (label), str);
+ g_free (str);
+
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0);
+
+ gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
+ i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
+
+ g_object_set_qdata (G_OBJECT (button), button_quark,
+ GUINT_TO_POINTER (dtmfbuttons[i].event));
+
+ /* To update the GtkEntry */
+ g_signal_connect (G_OBJECT (button), "pressed",
+ G_CALLBACK (dtmf_dialpad_button_pressed_cb), entry);
+
+ g_signal_connect (G_OBJECT (button), "pressed",
+ dtmf_button_pressed_cb, self);
+ g_signal_connect (G_OBJECT (button), "released",
+ dtmf_button_released_cb, self);
+ }
+
+ gtk_box_pack_start (GTK_BOX (box), table, FALSE, FALSE, 3);
+
+ return box;
+}
+
+void
+empathy_launch_program (const gchar *dir,
+ const gchar *name,
+ const gchar *args)
+{
+ GdkDisplay *display;
+ GError *error = NULL;
+ gchar *path, *cmd;
+ GAppInfo *app_info;
+ GdkAppLaunchContext *context = NULL;
+
+ /* Try to run from source directory if possible */
+ path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
+ name, NULL);
+
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ {
+ g_free (path);
+ path = g_build_filename (dir, name, NULL);
+ }
+
+ if (args != NULL)
+ cmd = g_strconcat (path, " ", args, NULL);
+ else
+ cmd = g_strdup (path);
+
+ app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
+ if (app_info == NULL)
+ {
+ DEBUG ("Failed to create app info: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ display = gdk_display_get_default ();
+ context = gdk_display_get_app_launch_context (display);
+
+ if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
+ &error))
+ {
+ g_warning ("Failed to launch %s: %s", name, error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+out:
+ tp_clear_object (&app_info);
+ tp_clear_object (&context);
+ g_free (path);
+ g_free (cmd);
+}