+
+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);
+}