* @file gtkutils.c GTK+ utility functions * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #define _PIDGIN_GTKUTILS_C_ # include <gtkspell/gtkspell.h> #include <gdk/gdkkeysyms.h> #include "conversation.h" #include "gtkimhtmltoolbar.h" #include "pidgin/minidialog.h" static guint accels_save_timer = 0; static GSList *registered_url_handlers = NULL; url_clicked_idle_cb(gpointer data) purple_notify_uri(NULL, data); url_clicked_cb(GtkIMHtml *unused, GtkIMHtmlLink *link) const char *uri = gtk_imhtml_link_get_url(link); g_idle_add(url_clicked_idle_cb, g_strdup(uri)); static GtkIMHtmlFuncs gtkimhtml_cbs = { (GtkIMHtmlGetImageFunc)purple_imgstore_find_by_id, (GtkIMHtmlGetImageDataFunc)purple_imgstore_get_data, (GtkIMHtmlGetImageSizeFunc)purple_imgstore_get_size, (GtkIMHtmlGetImageFilenameFunc)purple_imgstore_get_filename, purple_imgstore_ref_by_id, purple_imgstore_unref_by_id, pidgin_setup_imhtml(GtkWidget *imhtml) g_return_if_fail(imhtml != NULL); g_return_if_fail(GTK_IS_IMHTML(imhtml)); pidgin_themes_smiley_themeize(imhtml); gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), >kimhtml_cbs); if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font")) { PangoFontDescription *desc; const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font"); desc = pango_font_description_from_string(font); gtk_widget_modify_font(imhtml, desc); pango_font_description_free(desc); void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable) gtk_window_set_title(wnd, title); gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE); gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width); gtk_window_set_role(wnd, role); gtk_window_set_resizable(wnd, resizable); pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); pidgin_window_init(wnd, title, border_width, role, resizable); pidgin_create_small_button(GtkWidget *image) button = gtk_button_new(); gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); /* don't allow focus on the close button */ gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE); /* set style to make it as small as possible */ gtk_widget_set_name(button, "pidgin-small-close-button"); gtk_container_add(GTK_CONTAINER(button), image); pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable) wnd = GTK_WINDOW(gtk_dialog_new()); pidgin_window_init(wnd, title, border_width, role, resizable); g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL); pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing) GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox); gtk_box_set_homogeneous(vbox, homogeneous); gtk_box_set_spacing(vbox, spacing); GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog) return GTK_DIALOG(dialog)->vbox; GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog) return GTK_DIALOG(dialog)->action_area; GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label, GCallback callback, gpointer callbackdata) GtkWidget *button = gtk_button_new_from_stock(label); GtkWidget *bbox = pidgin_dialog_get_action_area(dialog); gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata); pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret) GtkWidget *toolbar = NULL; frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(frame), vbox); toolbar = gtk_imhtmltoolbar_new(); gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); gtk_widget_show(toolbar); sep = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0); g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep); g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep); imhtml = gtk_imhtml_new(NULL, NULL); gtk_imhtml_set_editable(GTK_IMHTML(imhtml), editable); gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_IMAGE); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR); if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck")) pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml)); gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml); gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default"); pidgin_setup_imhtml(imhtml); sw = pidgin_make_scrollable(imhtml, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1); gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); if (editable && (toolbar_ret != NULL)) pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog) const char *text = gtk_entry_get_text(GTK_ENTRY(entry)); gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, pidgin_toggle_sensitive(GtkWidget *widget, GtkWidget *to_toggle) sensitivity = GTK_WIDGET_IS_SENSITIVE(to_toggle); gtk_widget_set_sensitive(to_toggle, !sensitivity); pidgin_toggle_sensitive_array(GtkWidget *w, GPtrArray *data) for (i=0; i < data->len; i++) { element = g_ptr_array_index(data,i); sensitivity = GTK_WIDGET_IS_SENSITIVE(element); gtk_widget_set_sensitive(element, !sensitivity); pidgin_toggle_showhide(GtkWidget *widget, GtkWidget *to_toggle) if (GTK_WIDGET_VISIBLE(to_toggle)) gtk_widget_hide(to_toggle); gtk_widget_show(to_toggle); GtkWidget *pidgin_separator(GtkWidget *menu) menuitem = gtk_separator_menu_item_new(); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str) menuitem = gtk_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_widget_show(menuitem); label = gtk_label_new(str); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_label_set_pattern(GTK_LABEL(label), "_"); gtk_container_add(GTK_CONTAINER(menuitem), label); /* FIXME: Go back and fix this gtk_widget_add_accelerator(menuitem, "activate", accel, str[0], GDK_MOD1_MASK, GTK_ACCEL_LOCKED); pidgin_set_accessible_label (menuitem, label); GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str, GCallback cb, gpointer data, gboolean checked) menuitem = gtk_check_menu_item_new_with_mnemonic(str); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked); g_signal_connect(G_OBJECT(menuitem), "activate", cb, data); gtk_widget_show_all(menuitem); pidgin_pixbuf_toolbar_button_from_stock(const char *icon) GtkWidget *button, *image, *bbox; button = gtk_toggle_button_new(); gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); bbox = gtk_vbox_new(FALSE, 0); gtk_container_add (GTK_CONTAINER(button), bbox); image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0); gtk_widget_show_all(bbox); pidgin_pixbuf_button_from_stock(const char *text, const char *icon, PidginButtonOrientation style) GtkWidget *button, *image, *label, *bbox, *ibox, *lbox = NULL; button = gtk_button_new(); if (style == PIDGIN_BUTTON_HORIZONTAL) { bbox = gtk_hbox_new(FALSE, 0); ibox = gtk_hbox_new(FALSE, 0); lbox = gtk_hbox_new(FALSE, 0); bbox = gtk_vbox_new(FALSE, 0); ibox = gtk_vbox_new(FALSE, 0); lbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(button), bbox); gtk_box_pack_start(GTK_BOX(bbox), ibox, TRUE, TRUE, 0); image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON); gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(bbox), lbox, TRUE, TRUE, 0); label = gtk_label_new(NULL); gtk_label_set_text_with_mnemonic(GTK_LABEL(label), text); gtk_label_set_mnemonic_widget(GTK_LABEL(label), button); gtk_box_pack_start(GTK_BOX(lbox), label, FALSE, TRUE, 0); pidgin_set_accessible_label (button, label); gtk_widget_show_all(bbox); GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const char *icon, GCallback cb, gpointer data, guint accel_key, guint accel_mods, char *mod) menuitem = gtk_menu_item_new_with_mnemonic(str); menuitem = gtk_image_menu_item_new_with_mnemonic(str); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); g_signal_connect(G_OBJECT(menuitem), "activate", cb, data); image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); /* FIXME: this isn't right label = gtk_label_new(mod); gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2); gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key, accel_mods, GTK_ACCEL_LOCKED); gtk_widget_show_all(menuitem); pidgin_make_frame(GtkWidget *parent, const char *title) GtkWidget *vbox, *label, *hbox; vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0); label = gtk_label_new(NULL); labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title); gtk_label_set_markup(GTK_LABEL(label), labeltitle); gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); pidgin_set_accessible_label (vbox, label); hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); label = gtk_label_new(" "); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); aop_option_menu_get_selected(GtkWidget *optmenu, GtkWidget **p_item) GtkWidget *menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)); GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu)); return item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL; aop_menu_cb(GtkWidget *optmenu, GCallback cb) per_item_data = aop_option_menu_get_selected(optmenu, &item); ((void (*)(GtkWidget *, gpointer, gpointer))cb)(item, per_item_data, g_object_get_data(G_OBJECT(optmenu), "user_data")); aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data) item = gtk_menu_item_new(); hbox = gtk_hbox_new(FALSE, 4); image = gtk_image_new_from_pixbuf(pixbuf); gtk_size_group_add_widget(sg, image); label = gtk_label_new (lbl); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_container_add(GTK_CONTAINER(item), hbox); gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); g_object_set_data(G_OBJECT (item), data, per_item_data); g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data); pidgin_set_accessible_label(item, label); pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account) PurplePluginProtocolInfo *prpl_info; const char *protoname = NULL; prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); if (prpl_info->list_icon == NULL) protoname = prpl_info->list_icon(account, NULL); * Status icons will be themeable too, and then it will look up * protoname from the theme tmp = g_strconcat(protoname, ".png", NULL); filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", size == PIDGIN_PRPL_ICON_SMALL ? "16" : size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48", pixbuf = pidgin_pixbuf_new_from_file(filename); aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data) optmenu = gtk_option_menu_new(); gtk_widget_show(optmenu); gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), aop_menu->menu); if (aop_menu->default_item != -1) gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), aop_menu->default_item); g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", aop_menu, (GDestroyNotify)g_free); g_object_set_data(G_OBJECT(optmenu), "user_data", user_data); g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb); aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu) if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu))) gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu)); gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), new_aop_menu->menu); if (new_aop_menu->default_item != -1) gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), new_aop_menu->default_item); g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", new_aop_menu, (GDestroyNotify)g_free); aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data) for (idx = 0, llItr = GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))->children; llItr = llItr->next, idx++) { if (data == g_object_get_data(G_OBJECT(llItr->data), "aop_per_item_data")) { gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), idx); create_protocols_menu(const char *default_proto_id) AopMenu *aop_menu = NULL; GdkPixbuf *pixbuf = NULL; const char *gtalk_name = NULL, *facebook_name = NULL; aop_menu = g_malloc0(sizeof(AopMenu)); aop_menu->default_item = -1; aop_menu->menu = gtk_menu_new(); gtk_widget_show(aop_menu->menu); sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); if (purple_find_prpl("prpl-jabber")) { gtalk_name = _("Google Talk"); facebook_name = _("Facebook (XMPP)"); for (p = purple_plugins_get_protocols(), i = 0; plugin = (PurplePlugin *)p->data; if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) { char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", "google-talk.png", NULL); pixbuf = pidgin_pixbuf_new_from_file(filename); gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu), item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol")); g_object_set_data(G_OBJECT(item), "fakegoogle", GINT_TO_POINTER(1)); if (facebook_name && strcmp(facebook_name, plugin->info->name) < 0) { char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", "facebook.png", NULL); pixbuf = pidgin_pixbuf_new_from_file(filename); gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu), item = aop_menu_item_new(sg, pixbuf, facebook_name, "prpl-jabber", "protocol")); g_object_set_data(G_OBJECT(item), "fakefacebook", GINT_TO_POINTER(1)); pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL); gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu), aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol")); if (default_proto_id != NULL && !strcmp(plugin->info->id, default_proto_id)) aop_menu->default_item = i; pidgin_protocol_option_menu_new(const char *id, GCallback cb, return aop_option_menu_new(create_protocols_menu(id), cb, user_data); pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu) return (const char *)aop_option_menu_get_selected(optmenu, NULL); pidgin_account_option_menu_get_selected(GtkWidget *optmenu) return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL); create_account_menu(PurpleAccount *default_account, PurpleFilterAccountFunc filter_func, gboolean show_all) AopMenu *aop_menu = NULL; GdkPixbuf *pixbuf = NULL; list = purple_accounts_get_all(); list = purple_connections_get_all(); aop_menu = g_malloc0(sizeof(AopMenu)); aop_menu->default_item = -1; aop_menu->menu = gtk_menu_new(); gtk_widget_show(aop_menu->menu); sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); for (p = list, i = 0; p != NULL; p = p->next, i++) { account = (PurpleAccount *)p->data; PurpleConnection *gc = (PurpleConnection *)p->data; account = purple_connection_get_account(gc); if (filter_func && !filter_func(account)) { pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); if (purple_account_is_disconnected(account) && show_all && purple_connections_get_all()) gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE); if (purple_account_get_alias(account)) { g_snprintf(buf, sizeof(buf), "%s (%s) (%s)", purple_account_get_username(account), purple_account_get_alias(account), purple_account_get_protocol_name(account)); g_snprintf(buf, sizeof(buf), "%s (%s)", purple_account_get_username(account), purple_account_get_protocol_name(account)); gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu), aop_menu_item_new(sg, pixbuf, buf, account, "account")); if (default_account && account == default_account) aop_menu->default_item = i; regenerate_account_menu(GtkWidget *optmenu) PurpleFilterAccountFunc filter_func; account = (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL); show_all = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu), "show_all")); filter_func = g_object_get_data(G_OBJECT(optmenu), "filter_func"); aop_option_menu_replace_menu(optmenu, create_account_menu(account, filter_func, show_all)); account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu) regenerate_account_menu(optmenu); account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu) regenerate_account_menu(optmenu); account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event, purple_signals_disconnect_by_handle(optmenu); pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account) aop_option_menu_select_by_data(optmenu, account); pidgin_account_option_menu_new(PurpleAccount *default_account, gboolean show_all, GCallback cb, PurpleFilterAccountFunc filter_func, /* Create the option menu */ optmenu = aop_option_menu_new(create_account_menu(default_account, filter_func, show_all), cb, user_data); g_signal_connect(G_OBJECT(optmenu), "destroy", G_CALLBACK(account_menu_destroyed_cb), NULL); /* Register the purple sign on/off event callbacks. */ purple_signal_connect(purple_connections_get_handle(), "signed-on", optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb), purple_signal_connect(purple_connections_get_handle(), "signed-off", optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb), purple_signal_connect(purple_accounts_get_handle(), "account-added", optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb), purple_signal_connect(purple_accounts_get_handle(), "account-removed", optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb), g_object_set_data(G_OBJECT(optmenu), "user_data", user_data); g_object_set_data(G_OBJECT(optmenu), "show_all", GINT_TO_POINTER(show_all)); g_object_set_data(G_OBJECT(optmenu), "filter_func", filter_func); pidgin_check_if_dir(const char *path, GtkFileSelection *filesel) if (g_file_test(path, G_FILE_TEST_IS_DIR)) { /* append a / if needed */ if (path[strlen(path) - 1] != G_DIR_SEPARATOR) { dirname = g_strconcat(path, G_DIR_SEPARATOR_S, NULL); gtk_file_selection_set_filename(filesel, (dirname != NULL) ? dirname : path); pidgin_setup_gtkspell(GtkTextView *textview) g_return_if_fail(textview != NULL); g_return_if_fail(GTK_IS_TEXT_VIEW(textview)); if (gtkspell_new_attach(textview, locale, &error) == NULL && error) purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n", #endif /* USE_GTKSPELL */ pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1, GdkModifierType arg2, GClosure *arg3, purple_debug(PURPLE_DEBUG_MISC, "accels", "accel changed, scheduling save.\n"); accels_save_timer = purple_timeout_add_seconds(5, pidgin_save_accels, pidgin_save_accels(gpointer data) filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S, purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename); gtk_accel_map_save(filename); filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S, gtk_accel_map_load(filename); show_retrieveing_info(PurpleConnection *conn, const char *name) PurpleNotifyUserInfo *info = purple_notify_user_info_new(); purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving...")); purple_notify_userinfo(conn, name, info, NULL, NULL); purple_notify_user_info_destroy(info); void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name) show_retrieveing_info(conn, name); serv_get_info(conn, name); void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat) PurplePluginProtocolInfo *prpl_info = NULL; pidgin_retrieve_user_info(conn, name); prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl); if (prpl_info != NULL && prpl_info->get_cb_real_name) who = prpl_info->get_cb_real_name(conn, chat, name); if (prpl_info == NULL || prpl_info->get_cb_info == NULL) { pidgin_retrieve_user_info(conn, who ? who : name); show_retrieveing_info(conn, who ? who : name); prpl_info->get_cb_info(conn, chat, name); pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts, PurpleAccount **ret_account, char **ret_protocol, char **ret_username, char **ret_alias) g_return_val_if_fail(msg != NULL, FALSE); g_return_val_if_fail(ret_protocol != NULL, FALSE); g_return_val_if_fail(ret_username != NULL, FALSE); while (*s != '\r' && *s != '\n' && *s != '\0') while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ') if (*s != '\0') *s++ = '\0'; /* Clear past any whitespace */ while (*s != '\0' && *s == ' ') /* Now let's grab until the end of the line. */ while (*s != '\r' && *s != '\n' && *s != '\0') if (*s == '\r') *s++ = '\0'; if (*s == '\n') *s++ = '\0'; if (strchr(key, ':') != NULL) if (!g_ascii_strcasecmp(key, "X-IM-Username:")) username = g_strdup(value); else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:")) protocol = g_strdup(value); else if (!g_ascii_strcasecmp(key, "X-IM-Alias:")) if (username != NULL && protocol != NULL) *ret_username = username; *ret_protocol = protocol; /* Check for a compatible account. */ PurpleAccount *account = NULL; list = purple_accounts_get_all(); list = purple_connections_get_all(); for (l = list; l != NULL; l = l->next) PurplePluginProtocolInfo *prpl_info = NULL; account = (PurpleAccount *)l->data; plugin = purple_plugins_find_with_id( purple_account_get_protocol_id(account)); prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); gc = (PurpleConnection *)l->data; account = purple_connection_get_account(gc); prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); protoname = prpl_info->list_icon(account, NULL); if (!strcmp(protoname, protocol)) /* Special case for AIM and ICQ */ if (account == NULL && (!strcmp(protocol, "aim") || !strcmp(protocol, "icq"))) for (l = list; l != NULL; l = l->next) PurplePluginProtocolInfo *prpl_info = NULL; account = (PurpleAccount *)l->data; plugin = purple_plugins_find_with_id( purple_account_get_protocol_id(account)); prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); gc = (PurpleConnection *)l->data; account = purple_connection_get_account(gc); prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); protoname = prpl_info->list_icon(account, NULL); if (!strcmp(protoname, "aim") || !strcmp(protoname, "icq")) pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l) const gchar *existing_name; acc = gtk_widget_get_accessible (w); /* If this object has no name, set it's name with the label text */ existing_name = atk_object_get_name (acc); label_text = gtk_label_get_text (GTK_LABEL(l)); atk_object_set_name (acc, label_text); pidgin_set_accessible_relations(w, l); pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l) acc = gtk_widget_get_accessible (w); label = gtk_widget_get_accessible (l); /* Make sure mnemonics work */ gtk_label_set_mnemonic_widget(GTK_LABEL(l), w); /* Create the labeled-by relation */ set = atk_object_ref_relation_set (acc); relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY); atk_relation_set_add (set, relation); g_object_unref (relation); /* Create the label-for relation */ set = atk_object_ref_relation_set (label); relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR); atk_relation_set_add (set, relation); g_object_unref (relation); pidgin_menu_position_func_helper(GtkMenu *menu, GtkRequisition requisition; gint space_left, space_right, space_above, space_below; g_return_if_fail(GTK_IS_MENU(menu)); widget = GTK_WIDGET(menu); screen = gtk_widget_get_screen(widget); xthickness = widget->style->xthickness; ythickness = widget->style->ythickness; rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL); * We need the requisition to figure out the right place to * popup the menu. In fact, we always need to ask here, since * if a size_request was queued while we weren't popped up, * the requisition won't have been recomputed yet. gtk_widget_size_request (widget, &requisition); monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y); * The placement of popup menus horizontally works like this (with * - If there is enough room to the right (left) of the mouse cursor, * position the menu there. * - Otherwise, if if there is enough room to the left (right) of the * mouse cursor, position the menu there. * - Otherwise if the menu is smaller than the monitor, position it * on the side of the mouse cursor that has the most space available * - Otherwise (if there is simply not enough room for the menu on the * monitor), position it as far left (right) as possible. * Positioning in the vertical direction is similar: first try below * mouse cursor, then above. gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); space_left = *x - monitor.x; space_right = monitor.x + monitor.width - *x - 1; space_above = *y - monitor.y; space_below = monitor.y + monitor.height - *y - 1; /* position horizontally */ /* the amount of space we need to position the menu. Note the * menu is offset "xthickness" pixels needed_width = requisition.width - xthickness; if (needed_width <= space_left || needed_width <= space_right) if ((rtl && needed_width <= space_left) || (!rtl && needed_width > space_right)) *x = *x + xthickness - requisition.width + 1; /* x is clamped on-screen further down */ else if (requisition.width <= monitor.width) /* the menu is too big to fit on either side of the mouse * cursor, but smaller than the monitor. Position it on * the side that has the most space if (space_left > space_right) *x = monitor.x + monitor.width - requisition.width; else /* menu is simply too big for the monitor */ *x = monitor.x + monitor.width - requisition.width; /* Position vertically. The algorithm is the same as above, but * simpler because we don't have to take RTL into account. needed_height = requisition.height - ythickness; if (needed_height <= space_above || needed_height <= space_below) if (needed_height <= space_below) *y = *y + ythickness - requisition.height + 1; *y = CLAMP (*y, monitor.y, monitor.y + monitor.height - requisition.height); else if (needed_height > space_below && needed_height > space_above) if (space_below >= space_above) *y = monitor.y + monitor.height - requisition.height; pidgin_treeview_popup_menu_position_func(GtkMenu *menu, GtkWidget *widget = GTK_WIDGET(data); GtkTreeView *tv = GTK_TREE_VIEW(data); gint ythickness = GTK_WIDGET(menu)->style->ythickness; gdk_window_get_origin (widget->window, x, y); gtk_tree_view_get_cursor (tv, &path, &col); gtk_tree_view_get_cell_area (tv, path, col, &rect); *y += rect.y+rect.height+ythickness; pidgin_menu_position_func_helper(menu, x, y, push_in, data); static void dnd_image_ok_callback(_DndData *data, int choice) PurpleConversation *conv; PidginConversation *gtkconv; if (g_stat(data->filename, &st)) { str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, g_strerror(errno)); purple_notify_error(NULL, NULL, _("Failed to load image"), buddy = purple_find_buddy(data->account, data->who); purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n"); contact = purple_buddy_get_contact(buddy); purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename); serv_send_file(purple_account_get_connection(data->account), data->who, data->filename); conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, data->account, data->who); gtkconv = PIDGIN_CONVERSATION(conv); if (!g_file_get_contents(data->filename, &filedata, &size, str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message); purple_notify_error(NULL, NULL, _("Failed to load image"), shortname = strrchr(data->filename, G_DIR_SEPARATOR); shortname = shortname ? shortname + 1 : data->filename; id = purple_imgstore_add_with_id(filedata, size, shortname); gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter, gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer)); gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter); purple_imgstore_unref_by_id(id); static void dnd_image_cancel_callback(_DndData *data, int choice) static void dnd_set_icon_ok_cb(_DndData *data) dnd_image_ok_callback(data, DND_BUDDY_ICON); static void dnd_set_icon_cancel_cb(_DndData *data) pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who) GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data); PurpleConnection *gc = purple_account_get_connection(account); PurplePluginProtocolInfo *prpl_info = NULL; g_return_if_fail(account != NULL); g_return_if_fail(who != NULL); for ( ; files; files = g_list_delete_link(files, files)) { basename = g_path_get_basename(filename); /* XXX - Make ft API support creating a transfer with more than one file */ if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { /* XXX - make ft api suupport sending a directory */ /* Are we dealing with a directory? */ if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { str = g_strdup_printf(_("Cannot send folder %s."), basename); str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME); purple_notify_error(NULL, NULL, /* Are we dealing with an image? */ pb = pidgin_pixbuf_new_from_file(filename); _DndData *data = g_malloc(sizeof(_DndData)); gboolean ft = FALSE, im = FALSE; data->who = g_strdup(who); data->filename = g_strdup(filename); prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE) if (prpl_info && prpl_info->can_receive_file) ft = prpl_info->can_receive_file(gc, who); else if (prpl_info && prpl_info->send_file) purple_request_choice(NULL, NULL, _("You have dragged an image"), _("You can send this image as a file transfer, " "embed it into this message, or use it as the buddy icon for this user."), DND_FILE_TRANSFER, _("OK"), (GCallback)dnd_image_ok_callback, _("Cancel"), (GCallback)dnd_image_cancel_callback, _("Set as buddy icon"), DND_BUDDY_ICON, _("Send image file"), DND_FILE_TRANSFER, _("Insert in message"), DND_IM_IMAGE, purple_request_yes_no(NULL, NULL, _("You have dragged an image"), _("Would you like to set it as the buddy icon for this user?"), PURPLE_DEFAULT_ACTION_NONE, data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb); purple_request_choice(NULL, NULL, _("You have dragged an image"), (ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") : _("You can insert this image into this message, or use it as the buddy icon for this user")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE), _("OK"), (GCallback)dnd_image_ok_callback, _("Cancel"), (GCallback)dnd_image_cancel_callback, _("Set as buddy icon"), DND_BUDDY_ICON, (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE), g_object_unref(G_OBJECT(pb)); files = g_list_delete_link(files, files); /* Are we trying to send a .desktop file? */ else if (purple_str_has_suffix(basename, ".desktop") && (item = purple_desktop_item_new_from_file(filename))) { PurpleDesktopItemType dtype; const char *itemname = NULL; const char * const *langs; langs = g_get_language_names(); g_snprintf(key, sizeof(key), "Name[%s]", langs[0]); itemname = purple_desktop_item_get_string(item, key); itemname = purple_desktop_item_get_string(item, "Name"); dtype = purple_desktop_item_get_entry_type(item); PurpleConversation *conv; PidginConversation *gtkconv; case PURPLE_DESKTOP_ITEM_TYPE_LINK: conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who); gtkconv = PIDGIN_CONVERSATION(conv); gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry), gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer), purple_desktop_item_get_string(item, "URL"), itemname); /* I don't know if we really want to do anything here. Most of * the desktop item types are crap like "MIME Type" (I have no * clue how that would be a desktop item) and "Comment"... * nothing we can really send. The only logical one is * "Application," but do we really want to send a binary and * nothing else? Probably not. I'll just give an error and /* The original patch sent the icon used by the launcher. That's probably wrong */ purple_notify_error(NULL, NULL, _("Cannot send launcher"), _("You dragged a desktop launcher. Most " "likely you wanted to send the target " "of this launcher instead of this " purple_desktop_item_unref(item); files = g_list_delete_link(files, files); /* Everything is fine, let's send */ serv_send_file(gc, who, filename); void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleIconScaleRules rules, int *width, int *height) *width = gdk_pixbuf_get_width(buf); *height = gdk_pixbuf_get_height(buf); if ((spec == NULL) || !(spec->scale_rules & rules)) purple_buddy_icon_get_scale_size(spec, width, height); /* and now for some arbitrary sanity checks */ GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size) GtkIconSize icon_size = gtk_icon_size_from_name(size); GdkPixbuf *pixbuf = NULL; const char *stock = pidgin_stock_id_from_status_primitive(prim); pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE, stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle) const char *stock = NULL; case PURPLE_STATUS_UNSET: case PURPLE_STATUS_UNAVAILABLE: stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY; stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY; case PURPLE_STATUS_EXTENDED_AWAY: stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA; case PURPLE_STATUS_INVISIBLE: stock = PIDGIN_STOCK_STATUS_INVISIBLE; case PURPLE_STATUS_OFFLINE: stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE; stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE; pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim) return stock_id_from_status_primitive_idle(prim, FALSE); pidgin_stock_id_from_presence(PurplePresence *presence) PurpleStatusPrimitive prim; g_return_val_if_fail(presence, NULL); status = purple_presence_get_active_status(presence); type = purple_status_get_type(status); prim = purple_status_type_get_primitive(type); idle = purple_presence_is_idle(presence); return stock_id_from_status_primitive_idle(prim, idle); pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size) g_return_val_if_fail(account != NULL, NULL); prpl = purple_find_prpl(purple_account_get_protocol_id(account)); return pidgin_create_prpl_icon_from_prpl(prpl, size, account); menu_action_cb(GtkMenuItem *item, gpointer object) void (*callback)(gpointer, gpointer); callback = g_object_get_data(G_OBJECT(item), "purplecallback"); data = g_object_get_data(G_OBJECT(item), "purplecallbackdata"); pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act, return pidgin_separator(menu); if (act->children == NULL) { menuitem = gtk_menu_item_new_with_mnemonic(act->label); if (act->callback != NULL) { g_object_set_data(G_OBJECT(menuitem), g_object_set_data(G_OBJECT(menuitem), g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_action_cb), gtk_widget_set_sensitive(menuitem, FALSE); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); GtkWidget *submenu = NULL; menuitem = gtk_menu_item_new_with_mnemonic(act->label); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); submenu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); group = gtk_menu_get_accel_group(GTK_MENU(menu)); char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label); gtk_menu_set_accel_path(GTK_MENU(submenu), path); gtk_menu_set_accel_group(GTK_MENU(submenu), group); for (l = act->children; l; l = l->next) { PurpleMenuAction *act = (PurpleMenuAction *)l->data; pidgin_append_menu_action(submenu, act, object); g_list_free(act->children); purple_menu_action_free(act); PidginFilterBuddyCompletionEntryFunc filter_func; gpointer filter_func_user_data; static gboolean buddyname_completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data) model = gtk_entry_completion_get_model (completion); gtk_tree_model_get_value(model, iter, 2, &val1); tmp = g_value_get_string(&val1); if (tmp != NULL && purple_str_has_prefix(tmp, key)) gtk_tree_model_get_value(model, iter, 3, &val2); tmp = g_value_get_string(&val2); if (tmp != NULL && purple_str_has_prefix(tmp, key)) static gboolean buddyname_completion_match_selected_cb(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data) GtkWidget *optmenu = data->accountopt; gtk_tree_model_get_value(model, iter, 1, &val); gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val)); gtk_tree_model_get_value(model, iter, 4, &val); account = g_value_get_pointer(&val); aop_option_menu_select_by_data(optmenu, account); add_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias, const PurpleAccount *account, const char *buddyname) gboolean completion_added = FALSE; gchar *normalized_buddyname; tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT); normalized_buddyname = g_utf8_casefold(tmp, -1); /* There's no sense listing things like: 'xxx "xxx"' when the name and buddy alias match. */ if (buddy_alias && strcmp(buddy_alias, buddyname)) { char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, buddy_alias); char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT); tmp = g_utf8_casefold(tmp2, -1); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, g_free(completion_entry); /* There's no sense listing things like: 'xxx "xxx"' when the name and contact alias match. */ if (contact_alias && strcmp(contact_alias, buddyname)) { /* We don't want duplicates when the contact and buddy alias match. */ if (!buddy_alias || strcmp(contact_alias, buddy_alias)) { char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, contact_alias); char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT); tmp = g_utf8_casefold(tmp2, -1); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, g_free(completion_entry); if (completion_added == FALSE) { /* Add the buddy's name. */ gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, g_free(normalized_buddyname); static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data) PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func; gpointer user_data = data->filter_func_user_data; /* 1. Don't show buddies because we will have gotten them already. * 2. The boxes that use this autocomplete code handle only IMs. */ if (!set->buddy && set->type == PURPLE_LOG_IM) { PidginBuddyCompletionEntry entry; entry.entry.logged_buddy = set; if (filter_func(&entry, user_data)) { add_buddyname_autocomplete_entry(data->store, NULL, NULL, set->account, set->name); add_completion_list(PidginCompletionData *data) PurpleBlistNode *gnode, *cnode, *bnode; PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func; gpointer user_data = data->filter_func_user_data; gtk_list_store_clear(data->store); for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next) if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) PidginBuddyCompletionEntry entry; entry.entry.buddy = (PurpleBuddy *) bnode; if (filter_func(&entry, user_data)) { add_buddyname_autocomplete_entry(data->store, ((PurpleContact *)cnode)->alias, purple_buddy_get_contact_alias(entry.entry.buddy), entry.entry.buddy->account, sets = purple_log_get_log_sets(); g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data); g_hash_table_destroy(sets); buddyname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data) purple_signals_disconnect_by_handle(widget); repopulate_autocomplete(gpointer something, gpointer data) add_completion_list(data); pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data) PidginCompletionData *data; * Store the displayed completion value, the buddy name, the UTF-8 * normalized & casefolded buddy name, the UTF-8 normalized & * casefolded value for comparison, and the account. GtkEntryCompletion *completion; data = g_new0(PidginCompletionData, 1); store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER); data->accountopt = accountopt; if (filter_func == NULL) { data->filter_func = pidgin_screenname_autocomplete_default_filter; data->filter_func_user_data = NULL; data->filter_func = filter_func; data->filter_func_user_data = user_data; add_completion_list(data); /* Sort the completion list by buddy name */ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), completion = gtk_entry_completion_new(); gtk_entry_completion_set_match_func(completion, buddyname_completion_match_func, NULL, NULL); g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(buddyname_completion_match_selected_cb), data); gtk_entry_set_completion(GTK_ENTRY(entry), completion); g_object_unref(completion); gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store)); gtk_entry_completion_set_text_column(completion, 0); purple_signal_connect(purple_connections_get_handle(), "signed-on", entry, PURPLE_CALLBACK(repopulate_autocomplete), data); purple_signal_connect(purple_connections_get_handle(), "signed-off", entry, PURPLE_CALLBACK(repopulate_autocomplete), data); purple_signal_connect(purple_accounts_get_handle(), "account-added", entry, PURPLE_CALLBACK(repopulate_autocomplete), data); purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry, PURPLE_CALLBACK(repopulate_autocomplete), data); g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(buddyname_autocomplete_destroyed_cb), data); pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) { gboolean all = GPOINTER_TO_INT(all_accounts); if (completion_entry->is_buddy) { return all || purple_account_is_connected(completion_entry->entry.buddy->account); return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account)); pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *accountopt, gboolean all) { pidgin_setup_screenname_autocomplete_with_filter(entry, accountopt, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all)); void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type) g_return_if_fail(widget != NULL); if (widget->window == NULL) cursor = gdk_cursor_new(cursor_type); gdk_window_set_cursor(widget->window, cursor); gdk_cursor_unref(cursor); gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window))); void pidgin_clear_cursor(GtkWidget *widget) g_return_if_fail(widget != NULL); if (widget->window == NULL) gdk_window_set_cursor(widget->window, NULL); void (*callback)(const char*,gpointer); icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog) char *filename, *current_folder; if (response != GTK_RESPONSE_ACCEPT) { if (response == GTK_RESPONSE_CANCEL) { gtk_widget_destroy(dialog->icon_filesel); dialog->icon_filesel = NULL; dialog->callback(NULL, dialog->data); filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel)); current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel)); if (current_folder != NULL) { purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder); dialog->callback(filename, dialog->data); gtk_widget_destroy(dialog->icon_filesel); icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog) char *basename, *markup, *size; filename = gtk_file_chooser_get_preview_filename( GTK_FILE_CHOOSER(dialog->icon_filesel)); if (!filename || g_stat(filename, &st) || !(pixbuf = pidgin_pixbuf_new_from_file_at_size(filename, 128, 128))) gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL); gtk_label_set_markup(GTK_LABEL(dialog->icon_text), ""); gdk_pixbuf_get_file_info(filename, &width, &height); basename = g_path_get_basename(filename); size = purple_str_size_to_units(st.st_size); markup = g_strdup_printf(_("<b>File:</b> %s\n" "<b>Image size:</b> %dx%d"), basename, size, width, height); gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), pixbuf); gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup); g_object_unref(G_OBJECT(pixbuf)); GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) { struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1); const char *current_folder; dialog->callback = callback; current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder"); dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT); if ((current_folder != NULL) && (*current_folder != '\0')) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel), dialog->icon_preview = gtk_image_new(); dialog->icon_text = gtk_label_new(NULL); vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0); gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0); gtk_widget_show_all(vbox); gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox); gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE); gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE); g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview", G_CALLBACK(icon_preview_change_cb), dialog); g_signal_connect(G_OBJECT(dialog->icon_filesel), "response", G_CALLBACK(icon_filesel_choose_cb), dialog); icon_preview_change_cb(NULL, dialog); g_signal_connect(G_OBJECT(dialog->icon_filesel), "show", G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel); return dialog->icon_filesel; * @return True if any string from array a exists in array b. str_array_match(char **a, char **b) for (i = 0; a[i] != NULL; i++) for (j = 0; b[j] != NULL; j++) if (!g_ascii_strcasecmp(a[i], b[j])) pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len) PurplePluginProtocolInfo *prpl_info; PurpleBuddyIconSpec *spec; int orig_width, orig_height, new_width, new_height; GdkPixbuf *pixbuf, *original; prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); spec = &prpl_info->icon_spec; g_return_val_if_fail(spec->format != NULL, NULL); format = gdk_pixbuf_get_file_info(path, &orig_width, &orig_height); purple_debug_warning("buddyicon", "Could not get file info of %s\n", path); pixbuf_formats = gdk_pixbuf_format_get_extensions(format); prpl_formats = g_strsplit(spec->format, ",", 0); if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */ (!(spec->scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */ (spec->min_width <= orig_width && spec->max_width >= orig_width && spec->min_height <= orig_height && spec->max_height >= orig_height))) /* The icon is the correct size */ g_strfreev(pixbuf_formats); if (!g_file_get_contents(path, &contents, &length, &error)) { purple_debug_warning("buddyicon", "Could not get file contents " "of %s: %s\n", path, error->message); g_strfreev(prpl_formats); if (spec->max_filesize == 0 || length < spec->max_filesize) { /* The supplied image fits the file size, dimensions and type constraints. Great! Return it without making any changes. */ g_strfreev(prpl_formats); /* The image was too big. Fall-through and try scaling it down. */ g_strfreev(pixbuf_formats); /* The original image wasn't compatible. Scale it or convert file type. */ pixbuf = gdk_pixbuf_new_from_file(path, &error); purple_debug_warning("buddyicon", "Could not open icon '%s' for " "conversion: %s\n", path, error->message); g_strfreev(prpl_formats); original = g_object_ref(G_OBJECT(pixbuf)); new_height = orig_height; /* Make sure the image is the correct dimensions */ if (spec->scale_rules & PURPLE_ICON_SCALE_SEND && (orig_width < spec->min_width || orig_width > spec->max_width || orig_height < spec->min_height || orig_height > spec->max_height)) purple_buddy_icon_get_scale_size(spec, &new_width, &new_height); g_object_unref(G_OBJECT(pixbuf)); pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER); for (i = 0; prpl_formats[i]; i++) { const char *value = NULL; purple_debug_info("buddyicon", "Converting buddy icon to %s\n", prpl_formats[i]); if (g_str_equal(prpl_formats[i], "png")) { } else if (g_str_equal(prpl_formats[i], "jpeg")) { sprintf(tmp_buf, "%u", quality); if (!gdk_pixbuf_save_to_buffer(pixbuf, &contents, &length, prpl_formats[i], &error, key, value, NULL)) /* The NULL checking of error is necessary due to this bug: * http://bugzilla.gnome.org/show_bug.cgi?id=405539 */ purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats[i], (error && error->message) ? error->message : "Unknown error"); /* We couldn't convert to this image type. Try the next if (spec->max_filesize == 0 || length <= spec->max_filesize) { /* We were able to save the image as this image type and have it be within the size constraints. Great! Return purple_debug_info("buddyicon", "Converted image from " "%dx%d to %dx%d, format=%s, quality=%u, " "filesize=%zu\n", orig_width, orig_height, new_width, new_height, prpl_formats[i], quality, g_strfreev(prpl_formats); g_object_unref(G_OBJECT(pixbuf)); g_object_unref(G_OBJECT(original)); if (!g_str_equal(prpl_formats[i], "jpeg")) { /* File size was too big and we can't lower the quality, so skip to the next image type. */ /* File size was too big, but we're dealing with jpeg so try /* We couldn't save the image in any format that was below the max file size. Maybe we can reduce the image dimensions? */ new_width = orig_width * scale_factor; new_height = orig_height * scale_factor; g_object_unref(G_OBJECT(pixbuf)); pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER); } while ((new_width > 10 || new_height > 10) && new_width > spec->min_width && new_height > spec->min_height); g_strfreev(prpl_formats); g_object_unref(G_OBJECT(pixbuf)); g_object_unref(G_OBJECT(original)); tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"), path, plugin->info->name); purple_notify_error(NULL, _("Icon Error"), _("Could not set icon"), tmp); void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename) buddy = purple_find_buddy(account, who); purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n"); contact = purple_buddy_get_contact(buddy); purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename); char *pidgin_make_pretty_arrows(const char *str) char **split = g_strsplit(str, "->", -1); ret = g_strjoinv("\342\207\250", split); split = g_strsplit(ret, "<-", -1); ret = g_strjoinv("\342\207\246", split); void pidgin_set_urgent(GtkWindow *window, gboolean urgent) winpidgin_window_flash(window, urgent); gtk_window_set_urgency_hint(window, urgent); static GSList *minidialogs = NULL; pidgin_utils_get_handle(void) static void connection_signed_off_cb(PurpleConnection *gc) for (list = minidialogs; list; list = l_next) { if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) { gtk_widget_destroy(GTK_WIDGET(list->data)); static void alert_killed_cb(GtkWidget *widget) minidialogs = g_slist_remove(minidialogs, widget); struct _old_button_clicked_cb_data PidginUtilMiniDialogCallback cb; old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog, struct _old_button_clicked_cb_data *data = user_data; data->cb(data->data, button); old_mini_dialog_destroy_cb(GtkWidget *dialog, cb_datas = g_list_delete_link(cb_datas, cb_datas); mini_dialog_init(PidginMiniDialog *mini_dialog, PurpleConnection *gc, void *user_data, va_list args) static gboolean first_call = TRUE; purple_signal_connect(purple_connections_get_handle(), "signed-off", pidgin_utils_get_handle(), PURPLE_CALLBACK(connection_signed_off_cb), NULL); g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc); g_signal_connect(G_OBJECT(mini_dialog), "destroy", G_CALLBACK(alert_killed_cb), NULL); while ((button_text = va_arg(args, char*))) { struct _old_button_clicked_cb_data *data = NULL; PidginMiniDialogCallback wrapper_cb = NULL; PidginUtilMiniDialogCallback callback = va_arg(args, PidginUtilMiniDialogCallback); data = g_new0(struct _old_button_clicked_cb_data, 1); wrapper_cb = old_mini_dialog_button_clicked_cb; pidgin_mini_dialog_add_button(mini_dialog, button_text, cb_datas = g_list_append(cb_datas, data); g_signal_connect(G_OBJECT(mini_dialog), "destroy", G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas); #define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \ va_start(args, user_data); \ mini_dialog_init(mini_dialog, gc, user_data, args); \ return GTK_WIDGET(mini_dialog); pidgin_make_mini_dialog(PurpleConnection *gc, PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name); INIT_AND_RETURN_MINI_DIALOG(mini_dialog); pidgin_make_mini_dialog_with_custom_icon(PurpleConnection *gc, PidginMiniDialog *mini_dialog = pidgin_mini_dialog_new_with_custom_icon(primary, secondary, custom_icon); INIT_AND_RETURN_MINI_DIALOG(mini_dialog); * "This is so dead sexy." * "Best movie of the year." * This is the function that handles CTRL+F searching in the buddy list. * It finds the top-most buddy/group/chat/whatever containing the * It's somewhat ineffecient, because we strip all the HTML from the * "name" column of the buddy list (because the GtkTreeModel does not * contain the screen name in a non-markedup format). But the alternative * is to add an extra column to the GtkTreeModel. And this function is * used rarely, so it shouldn't matter TOO much. gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data) if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0) purple_notify_info(NULL, "WOPR", "Wouldn't you prefer a nice game of chess?", NULL); gtk_tree_model_get(model, iter, column, &withmarkup, -1); if (withmarkup == NULL) /* This is probably a separator */ tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT); enteredstring = g_utf8_casefold(tmp, -1); nomarkup = purple_markup_strip_html(withmarkup); tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT); normalized = g_utf8_casefold(tmp, -1); if (purple_str_has_prefix(normalized, enteredstring)) /* Use Pango to separate by words. */ len = g_utf8_strlen(normalized, -1); log_attrs = g_new(PangoLogAttr, len + 1); pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1); for (i = 0; i < (len - 1) ; i++) if (log_attrs[i].is_word_start && purple_str_has_prefix(word, enteredstring)) word = g_utf8_next_char(word); /* The non-Pango version. */ gunichar c = g_utf8_get_char(word); if (!g_unichar_isalnum(c)) word = g_utf8_find_next_char(word, NULL); if (purple_str_has_prefix(word, enteredstring)) word = g_utf8_find_next_char(word, NULL); gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) { int height, rowstride, i; if (!gdk_pixbuf_get_has_alpha(pixbuf)) height = gdk_pixbuf_get_height (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); pixels = gdk_pixbuf_get_pixels (pixbuf); for (i = 3; i < rowstride; i+=4) { for (i = 1; i < height - 1; i++) { row = pixels + (i * rowstride); if (row[3] < 0xfe || row[rowstride - 1] < 0xfe) { row = pixels + ((height - 1) * rowstride); for (i = 3; i < rowstride; i += 4) { void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) { int width, height, rowstride; if (!gdk_pixbuf_get_has_alpha(pixbuf)) width = gdk_pixbuf_get_width(pixbuf); height = gdk_pixbuf_get_height(pixbuf); rowstride = gdk_pixbuf_get_rowstride(pixbuf); pixels = gdk_pixbuf_get_pixels(pixbuf); if (width < 6 || height < 6) pixels[rowstride + 3] = 0x80; pixels[rowstride * 2 + 3] = 0xC0; pixels[width * 4 - 1] = 0; pixels[width * 4 - 5] = 0x80; pixels[width * 4 - 9] = 0xC0; pixels[rowstride + (width * 4) - 1] = 0x80; pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0; pixels[(height - 1) * rowstride + 3] = 0; pixels[(height - 1) * rowstride + 7] = 0x80; pixels[(height - 1) * rowstride + 11] = 0xC0; pixels[(height - 2) * rowstride + 3] = 0x80; pixels[(height - 3) * rowstride + 3] = 0xC0; pixels[height * rowstride - 1] = 0; pixels[(height - 1) * rowstride - 1] = 0x80; pixels[(height - 2) * rowstride - 1] = 0xC0; pixels[height * rowstride - 5] = 0x80; pixels[height * rowstride - 9] = 0xC0; const char *pidgin_get_dim_grey_string(GtkWidget *widget) { static char dim_grey_string[8] = ""; style = gtk_widget_get_style(widget); snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x", style->text_aa[GTK_STATE_NORMAL].red >> 8, style->text_aa[GTK_STATE_NORMAL].green >> 8, style->text_aa[GTK_STATE_NORMAL].blue >> 8); combo_box_changed_cb(GtkComboBox *combo_box, GtkEntry *entry) char *text = gtk_combo_box_get_active_text(combo_box); gtk_entry_set_text(entry, text ? text : ""); entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo) if (key->keyval == GDK_Down || key->keyval == GDK_Up) { gtk_combo_box_popup(combo); pidgin_text_combo_box_entry_new(const char *default_item, GList *items) GtkWidget *the_entry = NULL; ret = GTK_COMBO_BOX(gtk_combo_box_entry_new_text()); the_entry = gtk_entry_new(); gtk_container_add(GTK_CONTAINER(ret), the_entry); gtk_entry_set_text(GTK_ENTRY(the_entry), default_item); for (; items != NULL ; items = items->next) { char *text = items->data; gtk_combo_box_append_text(ret, text); g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry); g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret); const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget) return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget))->child)); void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text) gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text)); pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label) hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0); label = gtk_label_new_with_mnemonic(widget_label); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_size_group_add_widget(sg, label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0); gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget); pidgin_set_accessible_label (widget, label); gboolean pidgin_auto_parent_window(GtkWidget *widget) /* This looks at the most recent window that received focus, and makes * that the parent window. */ static GdkAtom _WindowTime = GDK_NONE; static GdkAtom _Cardinal = GDK_NONE; GtkWidget *parent = NULL; windows = gtk_window_list_toplevels(); if (_WindowTime == GDK_NONE) { _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME")); if (_Cardinal == GDK_NONE) { _Cardinal = gdk_atom_intern("CARDINAL", FALSE); GtkWidget *window = windows->data; windows = g_list_delete_link(windows, windows); !GTK_WIDGET_VISIBLE(window)) if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE, if (window_time < value) { if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) { /* The window is in focus, and the new window was not triggered by a keypress/click * event. So do not set it transient, to avoid focus stealing and all that. gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent)); /* This finds the currently active window and makes that the parent window. */ GtkWidget *parent = NULL; GdkEvent *event = gtk_get_current_event(); /* The window was not triggered by a user action. */ /* We need to special case events from a popup menu. */ if (event->type == GDK_BUTTON_RELEASE) { /* XXX: Neither of the following works: menu = event->button.window; menu = gdk_window_get_parent(event->button.window); menu = gdk_window_get_toplevel(event->button.window); } else if (event->type == GDK_KEY_PRESS) menu = event->key.window; windows = gtk_window_list_toplevels(); GtkWidget *window = windows->data; windows = g_list_delete_link(windows, windows); !GTK_WIDGET_VISIBLE(window)) { if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) || (menu && menu == window->window)) { gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent)); static GObject *pidgin_pixbuf_from_data_helper(const guchar *buf, gsize count, gboolean animated) loader = gdk_pixbuf_loader_new(); if (!gdk_pixbuf_loader_write(loader, buf, count, &error) || error) { purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() " "failed with size=%zu: %s\n", count, error ? error->message : "(no error message)"); g_object_unref(G_OBJECT(loader)); if (!gdk_pixbuf_loader_close(loader, &error) || error) { purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() " "failed for image of size %zu: %s\n", count, error ? error->message : "(no error message)"); g_object_unref(G_OBJECT(loader)); pixbuf = G_OBJECT(gdk_pixbuf_loader_get_animation(loader)); pixbuf = G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader)); purple_debug_warning("gtkutils", "%s() returned NULL for image " animated ? "gdk_pixbuf_loader_get_animation" : "gdk_pixbuf_loader_get_pixbuf", count); g_object_unref(G_OBJECT(loader)); g_object_unref(G_OBJECT(loader)); GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count) return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf, count, FALSE)); GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count) return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf, count, TRUE)); GdkPixbuf *pidgin_pixbuf_from_imgstore(PurpleStoredImage *image) return pidgin_pixbuf_from_data(purple_imgstore_get_data(image), purple_imgstore_get_size(image)); GdkPixbuf *pidgin_pixbuf_new_from_file(const gchar *filename) pixbuf = gdk_pixbuf_new_from_file(filename, &error); purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() " "returned %s for file %s: %s\n", pixbuf ? "something" : "nothing", error ? error->message : "(no error message)"); g_object_unref(G_OBJECT(pixbuf)); GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height) pixbuf = gdk_pixbuf_new_from_file_at_size(filename, purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() " "returned %s for file %s: %s\n", pixbuf ? "something" : "nothing", error ? error->message : "(no error message)"); g_object_unref(G_OBJECT(pixbuf)); GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio) pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, width, height, preserve_aspect_ratio, &error); purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() " "returned %s for file %s: %s\n", pixbuf ? "something" : "nothing", error ? error->message : "(no error message)"); g_object_unref(G_OBJECT(pixbuf)); static void url_copy(GtkWidget *w, gchar *url) clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY); gtk_clipboard_set_text(clipboard, url, -1); clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text(clipboard, url, -1); link_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu) url = gtk_imhtml_link_get_url(link); img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU); item = gtk_image_menu_item_new_with_mnemonic(_("_Open Link")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU); item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), (gpointer)url); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); copy_email_address(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu) #define MAILTOSIZE (sizeof("mailto:") - 1) text = gtk_imhtml_link_get_url(link); g_return_val_if_fail(text && strlen(text) > MAILTOSIZE, FALSE); address = (char*)text + MAILTOSIZE; img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU); item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), address); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); * @param filename The path to a file. Specifically this is the link target * from a link in an IM window with the leading "file://" removed. open_file(GtkIMHtml *imhtml, const char *filename) /* Copied from gtkft.c:open_button_cb */ /* Escape URI by replacing double-quote with 2 double-quotes. */ gchar *escaped = purple_strreplace(filename, "\"", "\"\""); gchar *param = g_strconcat("/select,\"", escaped, "\"", NULL); wchar_t *wc_param = g_utf8_to_utf16(param, -1, NULL, NULL, NULL); /* TODO: Better to use SHOpenFolderAndSelectItems()? */ code = (int)ShellExecuteW(NULL, L"OPEN", L"explorer.exe", wc_param, NULL, SW_NORMAL); if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC) purple_notify_error(imhtml, NULL, _("There is no application configured to open this type of file."), NULL); purple_notify_error(imhtml, NULL, _("An error occurred while opening the file."), NULL); purple_debug_warning("gtkutils", "filename: %s; code: %d\n", if (purple_running_gnome()) char *escaped = g_shell_quote(filename); command = g_strdup_printf("gnome-open %s", escaped); else if (purple_running_kde()) char *escaped = g_shell_quote(filename); if (purple_str_has_suffix(filename, ".desktop")) command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped); command = g_strdup_printf("kfmclient openURL %s", escaped); purple_notify_uri(NULL, filename); if (purple_program_is_valid(command)) if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error)) tmp = g_strdup_printf(_("Error launching %s: %s"), filename, error->message); purple_notify_error(imhtml, NULL, _("Unable to open file."), tmp); char *primary = g_strdup_printf(_("Error running %s"), command); char *secondary = g_strdup_printf(_("Process returned error code %d"), purple_notify_error(imhtml, NULL, primary, secondary); #define FILELINKSIZE (sizeof("file://") - 1) file_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link) /* Strip "file://" from the URI. */ const char *filename = gtk_imhtml_link_get_url(link) + FILELINKSIZE; open_file(imhtml, filename); open_containing_cb(GtkIMHtml *imhtml, const char *url) char *dir = g_path_get_dirname(url + FILELINKSIZE); file_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu) url = gtk_imhtml_link_get_url(link); img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU); item = gtk_image_menu_item_new_with_mnemonic(_("_Open File")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); /* Open Containing Directory */ img = gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU); item = gtk_image_menu_item_new_with_mnemonic(_("Open _Containing Directory")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_containing_cb), (gpointer)url); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); #define AUDIOLINKSIZE (sizeof("audio://") - 1) audio_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link) PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv"); if (!conv) /* no playback in debug window */ uri = gtk_imhtml_link_get_url(link) + AUDIOLINKSIZE; purple_sound_play_file(uri, NULL); savefile_write_cb(gpointer user_data, char *file) char *temp_file = user_data; if (!g_file_get_contents(temp_file, &contents, &length, &error)) { purple_debug_error("gtkutils", "Unable to read contents of %s: %s\n", temp_file, error->message); if (!purple_util_write_data_to_file_absolute(file, contents, length)) { purple_debug_error("gtkutils", "Unable to write contents to %s\n", save_file_cb(GtkWidget *item, const char *url) PidginConversation *conv = g_object_get_data(G_OBJECT(item), "gtkconv"); purple_request_file(conv->active_conv, _("Save File"), NULL, TRUE, G_CALLBACK(savefile_write_cb), NULL, conv->active_conv->account, NULL, conv->active_conv, audio_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu) PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv"); if (!conv) /* No menu in debug window */ url = gtk_imhtml_link_get_url(link); img = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_MENU); item = gtk_image_menu_item_new_with_mnemonic(_("_Play Sound")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU); item = gtk_image_menu_item_new_with_mnemonic(_("_Save File")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_file_cb), (gpointer)(url+AUDIOLINKSIZE)); g_object_set_data(G_OBJECT(item), "gtkconv", conv); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); /* XXX: The following two functions are for demonstration purposes only! */ open_dialog(GtkIMHtml *imhtml, GtkIMHtmlLink *link) url = gtk_imhtml_link_get_url(link); if (!url || strlen(url) < sizeof("open://")) str = url + sizeof("open://") - 1; if (strcmp(str, "accounts") == 0) pidgin_accounts_window_show(); else if (strcmp(str, "prefs") == 0) dummy(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu) register_gnome_url_handlers(void) tmp = g_find_program_in_path("gconftool-2"); if (!g_spawn_command_line_sync("gconftool-2 --all-dirs /desktop/gnome/url-handlers", g_return_val_if_reached(FALSE); for (c = start = tmp ; *c ; c++) /* Skip leading spaces. */ if (c == start && *c == ' ') if (g_str_has_prefix(start, "/desktop/gnome/url-handlers/")) /* If there is an enabled boolean, honor it. */ cmd = g_strdup_printf("gconftool-2 -g %s/enabled", start); if (g_spawn_command_line_sync(cmd, &tmp2, &err, NULL, NULL)) if (!strcmp(tmp2, "false\n")) start += sizeof("/desktop/gnome/url-handlers/") - 1; protocol = g_strdup_printf("%s:", start); registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol); gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu); return (registered_url_handlers != NULL); winpidgin_register_win32_url_handlers(void) LONG ret = ERROR_SUCCESS; ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, idx++, start, &nameSize, if (ret == ERROR_SUCCESS) { ret = RegOpenKeyExW(HKEY_CLASSES_ROOT, start, 0, KEY_READ, ®_key); if (ret == ERROR_SUCCESS) { ret = RegQueryValueExW(reg_key, L"URL Protocol", NULL, NULL, NULL, NULL); if (ret == ERROR_SUCCESS) { gchar *utf8 = g_utf16_to_utf8(start, -1, NULL, NULL, NULL); gchar *protocol = g_strdup_printf("%s:", utf8); registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol); /* We still pass everything to the "http" "open" handler for security reasons */ gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu); } while (ret == ERROR_SUCCESS); if (ret != ERROR_NO_MORE_ITEMS) purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n", pidgin_make_scrollable(GtkWidget *child, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, GtkShadowType shadow_type, int width, int height) GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), hscrollbar_policy, vscrollbar_policy); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), shadow_type); if (width != -1 || height != -1) gtk_widget_set_size_request(sw, width, height); if (GTK_WIDGET_GET_CLASS(child)->set_scroll_adjustments_signal) gtk_container_add(GTK_CONTAINER(sw), child); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child); void pidgin_utils_init(void) gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu); gtk_imhtml_class_register_protocol("https://", url_clicked_cb, link_context_menu); gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, link_context_menu); gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb, link_context_menu); gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb, copy_email_address); gtk_imhtml_class_register_protocol("file://", file_clicked_cb, file_context_menu); gtk_imhtml_class_register_protocol("audio://", audio_clicked_cb, audio_context_menu); /* Example custom URL handler. */ gtk_imhtml_class_register_protocol("open://", open_dialog, dummy); /* If we're under GNOME, try registering the system URL handlers. */ if (purple_running_gnome()) register_gnome_url_handlers(); /* Used to make small buttons */ gtk_rc_parse_string("style \"pidgin-small-close-button\"\n" "GtkWidget::focus-padding = 0\n" "GtkWidget::focus-line-width = 0\n" "GtkContainer::border-width = 0\n" "GtkButton::inner-border = {0, 0, 0, 0}\n" "GtkButton::default-border = {0, 0, 0, 0}\n" "widget \"*.pidgin-small-close-button\" style \"pidgin-small-close-button\""); winpidgin_register_win32_url_handlers(); void pidgin_utils_uninit(void) gtk_imhtml_class_register_protocol("open://", NULL, NULL); /* If we have GNOME handlers registered, unregister them. */ if (registered_url_handlers) for (l = registered_url_handlers; l; l = l->next) gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL); g_slist_free(registered_url_handlers); registered_url_handlers = NULL; gtk_imhtml_class_register_protocol("audio://", NULL, NULL); gtk_imhtml_class_register_protocol("file://", NULL, NULL); gtk_imhtml_class_register_protocol("http://", NULL, NULL); gtk_imhtml_class_register_protocol("https://", NULL, NULL); gtk_imhtml_class_register_protocol("ftp://", NULL, NULL); gtk_imhtml_class_register_protocol("mailto:", NULL, NULL); gtk_imhtml_class_register_protocol("gopher://", NULL, NULL);