Mercurial > pidgin.yaz
view pidgin/gtkutils.c @ 21329:a04a0d3f9b4f
Add tooltips when hovering over rooms in the roomlist so the full channel
topics can be seen more easily. It's mostly copy & paste from gtkblist.c,
so could probably be cleaned up as these tips don't need all the complexity
of the blist tips.
author | Stu Tomlinson <stu@nosnilmot.com> |
---|---|
date | Sun, 11 Nov 2007 18:08:19 +0000 |
parents | 6de09629f091 |
children | abbf74f4732b 38cc722159ff |
line wrap: on
line source
/** * @file gtkutils.c GTK+ utility functions * @ingroup pidgin */ /* pidgin * * 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 * source distribution. * * 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 */ #include "internal.h" #include "pidgin.h" #ifndef _WIN32 # include <X11/Xlib.h> #else # ifdef small # undef small # endif #endif /*_WIN32*/ #ifdef USE_GTKSPELL # include <gtkspell/gtkspell.h> # ifdef _WIN32 # include "wspell.h" # endif #endif #include <gdk/gdkkeysyms.h> #include "conversation.h" #include "debug.h" #include "desktopitem.h" #include "imgstore.h" #include "notify.h" #include "prefs.h" #include "prpl.h" #include "request.h" #include "signals.h" #include "util.h" #include "gtkconv.h" #include "gtkdialogs.h" #include "gtkimhtml.h" #include "gtkimhtmltoolbar.h" #include "pidginstock.h" #include "gtkthemes.h" #include "gtkutils.h" typedef struct { GtkWidget *menu; gint default_item; } AopMenu; static guint accels_save_timer = 0; static gboolean url_clicked_idle_cb(gpointer data) { purple_notify_uri(NULL, data); g_free(data); return FALSE; } static void url_clicked_cb(GtkWidget *w, const char *uri) { 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, }; void pidgin_setup_imhtml(GtkWidget *imhtml) { PangoFontDescription *desc = NULL; g_return_if_fail(imhtml != NULL); g_return_if_fail(GTK_IS_IMHTML(imhtml)); g_signal_connect(G_OBJECT(imhtml), "url_clicked", G_CALLBACK(url_clicked_cb), NULL); 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")) { const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font"); desc = pango_font_description_from_string(font); } else if (purple_running_gnome()) { /* Use the GNOME "document" font, if applicable */ char *path; if ((path = g_find_program_in_path("gconftool-2"))) { char *font = NULL; g_free(path); if (g_spawn_command_line_sync( "gconftool-2 -g /desktop/gnome/interface/document_font_name", &font, NULL, NULL, NULL)) { desc = pango_font_description_from_string(font); } g_free(font); } } if (desc) { gtk_widget_modify_font(imhtml, desc); pango_font_description_free(desc); } } GtkWidget * pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) { GtkWindow *wnd = NULL; wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); if (title) gtk_window_set_title(wnd, title); #ifdef _WIN32 else gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE); #endif gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width); if (role) gtk_window_set_role(wnd, role); gtk_window_set_resizable(wnd, resizable); return GTK_WIDGET(wnd); } GtkWidget * pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret) { GtkWidget *frame; GtkWidget *imhtml; GtkWidget *sep; GtkWidget *sw; GtkWidget *toolbar = NULL; GtkWidget *vbox; 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); gtk_widget_show(vbox); if (editable) { 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); gtk_widget_show(sep); } sw = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); gtk_widget_show(sw); 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); #ifdef USE_GTKSPELL if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck")) pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml)); #endif gtk_widget_show(imhtml); if (editable) { gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml); gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default"); } pidgin_setup_imhtml(imhtml); gtk_container_add(GTK_CONTAINER(sw), imhtml); if (imhtml_ret != NULL) *imhtml_ret = imhtml; if (editable && (toolbar_ret != NULL)) *toolbar_ret = toolbar; if (sw_ret != NULL) *sw_ret = sw; return frame; } void 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, (*text != '\0')); } void pidgin_toggle_sensitive(GtkWidget *widget, GtkWidget *to_toggle) { gboolean sensitivity; if (to_toggle == NULL) return; sensitivity = GTK_WIDGET_IS_SENSITIVE(to_toggle); gtk_widget_set_sensitive(to_toggle, !sensitivity); } void pidgin_toggle_sensitive_array(GtkWidget *w, GPtrArray *data) { gboolean sensitivity; gpointer element; int i; for (i=0; i < data->len; i++) { element = g_ptr_array_index(data,i); if (element == NULL) continue; sensitivity = GTK_WIDGET_IS_SENSITIVE(element); gtk_widget_set_sensitive(element, !sensitivity); } } void pidgin_toggle_showhide(GtkWidget *widget, GtkWidget *to_toggle) { if (to_toggle == NULL) return; if (GTK_WIDGET_VISIBLE(to_toggle)) gtk_widget_hide(to_toggle); else gtk_widget_show(to_toggle); } GtkWidget *pidgin_separator(GtkWidget *menu) { GtkWidget *menuitem; menuitem = gtk_separator_menu_item_new(); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); return menuitem; } GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str) { GtkWidget *menuitem; GtkWidget *label; menuitem = gtk_menu_item_new(); if (menu) 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); gtk_widget_show(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); return menuitem; } GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str, GtkSignalFunc sf, gpointer data, gboolean checked) { GtkWidget *menuitem; menuitem = gtk_check_menu_item_new_with_mnemonic(str); if (menu) gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked); if (sf) g_signal_connect(G_OBJECT(menuitem), "activate", sf, data); gtk_widget_show_all(menuitem); return menuitem; } GtkWidget * 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); return button; } GtkWidget * 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); if (text) lbox = gtk_hbox_new(FALSE, 0); } else { bbox = gtk_vbox_new(FALSE, 0); ibox = gtk_vbox_new(FALSE, 0); if (text) lbox = gtk_vbox_new(FALSE, 0); } gtk_container_add(GTK_CONTAINER(button), bbox); if (icon) { gtk_box_pack_start_defaults(GTK_BOX(bbox), ibox); image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON); gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0); } if (text) { gtk_box_pack_start_defaults(GTK_BOX(bbox), lbox); 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); return button; } GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const char *icon, GtkSignalFunc sf, gpointer data, guint accel_key, guint accel_mods, char *mod) { GtkWidget *menuitem; /* GtkWidget *hbox; GtkWidget *label; */ GtkWidget *image; if (icon == NULL) menuitem = gtk_menu_item_new_with_mnemonic(str); else menuitem = gtk_image_menu_item_new_with_mnemonic(str); if (menu) gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); if (sf) g_signal_connect(G_OBJECT(menuitem), "activate", sf, data); if (icon != NULL) { 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 if (mod) { label = gtk_label_new(mod); gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2); gtk_widget_show(label); } */ /* if (accel_key) { gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key, accel_mods, GTK_ACCEL_LOCKED); } */ gtk_widget_show_all(menuitem); return menuitem; } GtkWidget * pidgin_make_frame(GtkWidget *parent, const char *title) { GtkWidget *vbox, *label, *hbox; char *labeltitle; vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0); gtk_widget_show(vbox); label = gtk_label_new(NULL); labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title); gtk_label_set_markup(GTK_LABEL(label), labeltitle); g_free(labeltitle); gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); gtk_widget_show(label); 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); gtk_widget_show(hbox); label = gtk_label_new(" "); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); gtk_widget_show(label); vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); gtk_widget_show(vbox); return vbox; } static gpointer 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)); if (p_item) (*p_item) = item; return g_object_get_data(G_OBJECT(item), "aop_per_item_data"); } static void aop_menu_cb(GtkWidget *optmenu, GCallback cb) { GtkWidget *item; gpointer per_item_data; per_item_data = aop_option_menu_get_selected(optmenu, &item); if (cb != NULL) { ((void (*)(GtkWidget *, gpointer, gpointer))cb)(item, per_item_data, g_object_get_data(G_OBJECT(optmenu), "user_data")); } } static GtkWidget * aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data) { GtkWidget *item; GtkWidget *hbox; GtkWidget *image; GtkWidget *label; item = gtk_menu_item_new(); gtk_widget_show(item); hbox = gtk_hbox_new(FALSE, 4); gtk_widget_show(hbox); /* Create the image */ if (pixbuf == NULL) image = gtk_image_new(); else image = gtk_image_new_from_pixbuf(pixbuf); gtk_widget_show(image); if (sg) gtk_size_group_add_widget(sg, image); /* Create the label */ label = gtk_label_new (lbl); gtk_widget_show (label); 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); return item; } static GdkPixbuf * pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account) { PurplePluginProtocolInfo *prpl_info; const char *protoname = NULL; char *tmp; char *filename = NULL; GdkPixbuf *pixbuf; prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); if (prpl_info->list_icon == NULL) return NULL; protoname = prpl_info->list_icon(account, NULL); if (protoname == NULL) return 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", tmp, NULL); g_free(tmp); pixbuf = gdk_pixbuf_new_from_file(filename, NULL); g_free(filename); return pixbuf; } static GtkWidget * aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data) { GtkWidget *optmenu; 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); return optmenu; } static void 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); } static void aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data) { guint idx; GList *llItr = NULL; for (idx = 0, llItr = GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))->children; llItr != NULL; 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); break; } } } static AopMenu * create_protocols_menu(const char *default_proto_id) { AopMenu *aop_menu = NULL; PurplePluginProtocolInfo *prpl_info; PurplePlugin *plugin; GdkPixbuf *pixbuf = NULL; GtkSizeGroup *sg; GList *p; const char *gtalk_name = NULL; int i; 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"); for (p = purple_plugins_get_protocols(), i = 0; p != NULL; p = p->next, i++) { plugin = (PurplePlugin *)p->data; prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) { char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", "google-talk.png", NULL); GtkWidget *item; pixbuf = gdk_pixbuf_new_from_file(filename, NULL); g_free(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), "fake", GINT_TO_POINTER(1)); if (pixbuf) g_object_unref(pixbuf); gtalk_name = NULL; i++; } 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 (pixbuf) g_object_unref(pixbuf); if (default_proto_id != NULL && !strcmp(plugin->info->id, default_proto_id)) aop_menu->default_item = i; } g_object_unref(sg); return aop_menu; } GtkWidget * pidgin_protocol_option_menu_new(const char *id, GCallback cb, gpointer user_data) { return aop_option_menu_new(create_protocols_menu(id), cb, user_data); } const char * pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu) { return (const char *)aop_option_menu_get_selected(optmenu, NULL); } PurpleAccount * pidgin_account_option_menu_get_selected(GtkWidget *optmenu) { return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL); } static AopMenu * create_account_menu(PurpleAccount *default_account, PurpleFilterAccountFunc filter_func, gboolean show_all) { AopMenu *aop_menu = NULL; PurpleAccount *account; GdkPixbuf *pixbuf = NULL; GList *list; GList *p; GtkSizeGroup *sg; int i; char buf[256]; if (show_all) list = purple_accounts_get_all(); else 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++) { PurplePlugin *plugin; if (show_all) account = (PurpleAccount *)p->data; else { PurpleConnection *gc = (PurpleConnection *)p->data; account = purple_connection_get_account(gc); } if (filter_func && !filter_func(account)) { i--; continue; } plugin = purple_find_prpl(purple_account_get_protocol_id(account)); pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); if (pixbuf) { 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)); } else { 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 (pixbuf) g_object_unref(pixbuf); if (default_account && account == default_account) aop_menu->default_item = i; } g_object_unref(sg); return aop_menu; } static void regenerate_account_menu(GtkWidget *optmenu) { gboolean show_all; PurpleAccount *account; 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)); } static void account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu) { regenerate_account_menu(optmenu); } static void account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu) { regenerate_account_menu(optmenu); } static gboolean account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event, void *user_data) { purple_signals_disconnect_by_handle(optmenu); return FALSE; } void pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account) { aop_option_menu_select_by_data(optmenu, account); } GtkWidget * pidgin_account_option_menu_new(PurpleAccount *default_account, gboolean show_all, GCallback cb, PurpleFilterAccountFunc filter_func, gpointer user_data) { GtkWidget *optmenu; /* 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), optmenu); purple_signal_connect(purple_connections_get_handle(), "signed-off", optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb), optmenu); purple_signal_connect(purple_accounts_get_handle(), "account-added", optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb), optmenu); purple_signal_connect(purple_accounts_get_handle(), "account-removed", optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb), optmenu); /* Set some data. */ 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); return optmenu; } gboolean pidgin_check_if_dir(const char *path, GtkFileSelection *filesel) { char *dirname = NULL; 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); g_free(dirname); return TRUE; } return FALSE; } void pidgin_setup_gtkspell(GtkTextView *textview) { #ifdef USE_GTKSPELL GError *error = NULL; char *locale = NULL; 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", error->message); g_error_free(error); } #endif /* USE_GTKSPELL */ } void pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1, GdkModifierType arg2, GClosure *arg3, gpointer data) { purple_debug(PURPLE_DEBUG_MISC, "accels", "accel changed, scheduling save.\n"); if (!accels_save_timer) accels_save_timer = g_timeout_add(5000, pidgin_save_accels, NULL); } gboolean pidgin_save_accels(gpointer data) { char *filename = NULL; filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S, "accels", NULL); purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename); gtk_accel_map_save(filename); g_free(filename); accels_save_timer = 0; return FALSE; } void pidgin_load_accels() { char *filename = NULL; filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S, "accels", NULL); gtk_accel_map_load(filename); g_free(filename); } static void 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) { char *who = NULL; PurplePluginProtocolInfo *prpl_info = NULL; if (chat < 0) { pidgin_retrieve_user_info(conn, name); return; } prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl); if (prpl_info == NULL || prpl_info->get_cb_info == NULL) { pidgin_retrieve_user_info(conn, name); return; } if (prpl_info->get_cb_real_name) who = prpl_info->get_cb_real_name(conn, chat, name); show_retrieveing_info(conn, who ? who : name); prpl_info->get_cb_info(conn, chat, name); g_free(who); } gboolean pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts, PurpleAccount **ret_account, char **ret_protocol, char **ret_username, char **ret_alias) { char *protocol = NULL; char *username = NULL; char *alias = NULL; char *str; char *c, *s; gboolean valid; 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); s = str = g_strdup(msg); while (*s != '\r' && *s != '\n' && *s != '\0') { char *key, *value; key = s; /* Grab the key */ while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ') s++; if (*s == '\r') s++; if (*s == '\n') { s++; continue; } if (*s != '\0') *s++ = '\0'; /* Clear past any whitespace */ while (*s != '\0' && *s == ' ') s++; /* Now let's grab until the end of the line. */ value = s; while (*s != '\r' && *s != '\n' && *s != '\0') s++; if (*s == '\r') *s++ = '\0'; if (*s == '\n') *s++ = '\0'; if ((c = 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:")) alias = g_strdup(value); } } if (username != NULL && protocol != NULL) { valid = TRUE; *ret_username = username; *ret_protocol = protocol; if (ret_alias != NULL) *ret_alias = alias; /* Check for a compatible account. */ if (ret_account != NULL) { GList *list; PurpleAccount *account = NULL; GList *l; const char *protoname; if (all_accounts) list = purple_accounts_get_all(); else list = purple_connections_get_all(); for (l = list; l != NULL; l = l->next) { PurpleConnection *gc; PurplePluginProtocolInfo *prpl_info = NULL; PurplePlugin *plugin; if (all_accounts) { account = (PurpleAccount *)l->data; plugin = purple_plugins_find_with_id( purple_account_get_protocol_id(account)); if (plugin == NULL) { account = NULL; continue; } prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); } else { 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)) break; account = NULL; } /* Special case for AIM and ICQ */ if (account == NULL && (!strcmp(protocol, "aim") || !strcmp(protocol, "icq"))) { for (l = list; l != NULL; l = l->next) { PurpleConnection *gc; PurplePluginProtocolInfo *prpl_info = NULL; PurplePlugin *plugin; if (all_accounts) { account = (PurpleAccount *)l->data; plugin = purple_plugins_find_with_id( purple_account_get_protocol_id(account)); if (plugin == NULL) { account = NULL; continue; } prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); } else { 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")) break; account = NULL; } } *ret_account = account; } } else { valid = FALSE; g_free(username); g_free(protocol); g_free(alias); } g_free(str); return valid; } void pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l) { AtkObject *acc; const gchar *label_text; 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); if (!existing_name) { label_text = gtk_label_get_text (GTK_LABEL(l)); if (label_text) atk_object_set_name (acc, label_text); } pidgin_set_accessible_relations(w, l); } void pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l) { AtkObject *acc, *label; AtkObject *rel_obj[1]; AtkRelationSet *set; AtkRelation *relation; 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); rel_obj[0] = label; relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY); atk_relation_set_add (set, relation); g_object_unref (relation); g_object_unref(set); /* Create the label-for relation */ set = atk_object_ref_relation_set (label); rel_obj[0] = acc; relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR); atk_relation_set_add (set, relation); g_object_unref (relation); g_object_unref(set); } void pidgin_menu_position_func_helper(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data) { #if GTK_CHECK_VERSION(2,2,0) GtkWidget *widget; GtkRequisition requisition; GdkScreen *screen; GdkRectangle monitor; gint monitor_num; gint space_left, space_right, space_above, space_below; gint needed_width; gint needed_height; gint xthickness; gint ythickness; gboolean rtl; 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); push_in = FALSE; /* * The placement of popup menus horizontally works like this (with * RTL in parentheses) * * - 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)) { /* position left */ *x = *x + xthickness - requisition.width + 1; } else { /* position right */ *x = *x - xthickness; } /* 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) { /* left justify */ *x = monitor.x; } else { /* right justify */ *x = monitor.x + monitor.width - requisition.width; } } else /* menu is simply too big for the monitor */ { if (rtl) { /* right justify */ *x = monitor.x + monitor.width - requisition.width; } else { /* left justify */ *x = monitor.x; } } /* 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; else *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; else *y = monitor.y; } else { *y = monitor.y; } #endif } void pidgin_treeview_popup_menu_position_func(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data) { GtkWidget *widget = GTK_WIDGET(data); GtkTreeView *tv = GTK_TREE_VIEW(data); GtkTreePath *path; GtkTreeViewColumn *col; GdkRectangle rect; 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); *x += rect.x+rect.width; *y += rect.y+rect.height+ythickness; pidgin_menu_position_func_helper(menu, x, y, push_in, data); } enum { DND_FILE_TRANSFER, DND_IM_IMAGE, DND_BUDDY_ICON }; typedef struct { char *filename; PurpleAccount *account; char *who; } _DndData; static void dnd_image_ok_callback(_DndData *data, int choice) { char *filedata; size_t size; struct stat st; GError *err = NULL; PurpleConversation *conv; PidginConversation *gtkconv; GtkTextIter iter; int id; switch (choice) { case DND_BUDDY_ICON: if (g_stat(data->filename, &st)) { char *str; str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, strerror(errno)); purple_notify_error(NULL, NULL, _("Failed to load image"), str); g_free(str); return; } pidgin_set_custom_buddy_icon(data->account, data->who, data->filename); break; case DND_FILE_TRANSFER: serv_send_file(purple_account_get_connection(data->account), data->who, data->filename); break; case DND_IM_IMAGE: 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, &err)) { char *str; 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"), str); g_error_free(err); g_free(str); return; } id = purple_imgstore_add_with_id(filedata, size, data->filename); 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); break; } free(data->filename); free(data->who); free(data); } static void dnd_image_cancel_callback(_DndData *data, int choice) { free(data->filename); free(data->who); free(data); } 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) { free(data->filename); free(data->who); free(data); } void pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who) { GList *tmp; GdkPixbuf *pb; GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data); PurpleConnection *gc = purple_account_get_connection(account); PurplePluginProtocolInfo *prpl_info = NULL; gboolean file_send_ok = FALSE; #ifndef _WIN32 PurpleDesktopItem *item; #endif g_return_if_fail(account != NULL); g_return_if_fail(who != NULL); for(tmp = files; tmp != NULL ; tmp = g_list_next(tmp)) { gchar *filename = tmp->data; gchar *basename = g_path_get_basename(filename); /* Set the default action: don't send anything */ file_send_ok = FALSE; /* XXX - Make ft API support creating a transfer with more than one file */ if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { continue; } /* XXX - make ft api suupport sending a directory */ /* Are we dealing with a directory? */ if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { char *str, *str2; 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, str, str2); g_free(str); g_free(str2); continue; } /* Are we dealing with an image? */ pb = gdk_pixbuf_new_from_file(filename, NULL); if (pb) { _DndData *data = g_malloc(sizeof(_DndData)); gboolean ft = FALSE, im = FALSE; data->who = g_strdup(who); data->filename = g_strdup(filename); data->account = account; if (gc) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE) im = TRUE; if (prpl_info && prpl_info->can_receive_file) ft = prpl_info->can_receive_file(gc, who); else if (prpl_info && prpl_info->send_file) ft = TRUE; if (im && ft) purple_request_choice_with_hint(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, account, who, NULL, PURPLE_REQUEST_UI_HINT_CONV, data, _("Set as buddy icon"), DND_BUDDY_ICON, _("Send image file"), DND_FILE_TRANSFER, _("Insert in message"), DND_IM_IMAGE, NULL); else if (!(im || ft)) purple_request_yes_no_with_hint(NULL, NULL, _("You have dragged an image"), _("Would you like to set it as the buddy icon for this user?"), 0, account, who, NULL, PURPLE_REQUEST_UI_HINT_CONV, data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb); else purple_request_choice_with_hint(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, account, who, NULL, PURPLE_REQUEST_UI_HINT_CONV, data, _("Set as buddy icon"), DND_BUDDY_ICON, (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE), NULL); gdk_pixbuf_unref(pb); return; } #ifndef _WIN32 /* 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; char key[64]; const char *itemname = NULL; #if GTK_CHECK_VERSION(2,6,0) const char * const *langs; int i; langs = g_get_language_names(); for (i = 0; langs[i]; i++) { g_snprintf(key, sizeof(key), "Name[%s]", langs[i]); itemname = purple_desktop_item_get_string(item, key); break; } #else const char *lang = g_getenv("LANG"); char *dot; dot = strchr(lang, '.'); if (dot) *dot = '\0'; g_snprintf(key, sizeof(key), "Name[%s]", lang); itemname = purple_desktop_item_get_string(item, key); #endif if (!itemname) itemname = purple_desktop_item_get_string(item, "Name"); dtype = purple_desktop_item_get_entry_type(item); switch (dtype) { 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); break; default: /* 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 return. */ /* 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 whatever this launcher points to instead of this launcher" " itself.")); break; } purple_desktop_item_unref(item); return; } #endif /* _WIN32 */ /* Everything is fine, let's send */ serv_send_file(gc, who, filename); g_free(filename); } g_list_free(files); } 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)) return; purple_buddy_icon_get_scale_size(spec, width, height); /* and now for some arbitrary sanity checks */ if(*width > 100) *width = 100; if(*height > 100) *height = 100; } GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size) { GtkIconSize icon_size = gtk_icon_size_from_name(size); GdkPixbuf *pixbuf = NULL; if (prim == PURPLE_STATUS_UNAVAILABLE) pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_BUSY, icon_size, "GtkWidget"); else if (prim == PURPLE_STATUS_AWAY) pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_AWAY, icon_size, "GtkWidget"); else if (prim == PURPLE_STATUS_EXTENDED_AWAY) pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_XA, icon_size, "GtkWidget"); else if (prim == PURPLE_STATUS_INVISIBLE) pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_INVISIBLE, icon_size, "GtkWidget"); else if (prim == PURPLE_STATUS_OFFLINE) pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_OFFLINE, icon_size, "GtkWidget"); else pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_AVAILABLE, icon_size, "GtkWidget"); return pixbuf; } GdkPixbuf * pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size) { PurplePlugin *prpl; g_return_val_if_fail(account != NULL, NULL); prpl = purple_find_prpl(purple_account_get_protocol_id(account)); if (prpl == NULL) return NULL; return pidgin_create_prpl_icon_from_prpl(prpl, size, account); } static void menu_action_cb(GtkMenuItem *item, gpointer object) { gpointer data; void (*callback)(gpointer, gpointer); callback = g_object_get_data(G_OBJECT(item), "purplecallback"); data = g_object_get_data(G_OBJECT(item), "purplecallbackdata"); if (callback) callback(object, data); } GtkWidget * pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act, gpointer object) { GtkWidget *menuitem; if (act == NULL) { 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), "purplecallback", act->callback); g_object_set_data(G_OBJECT(menuitem), "purplecallbackdata", act->data); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_action_cb), object); } else { gtk_widget_set_sensitive(menuitem, FALSE); } gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); } else { GList *l = NULL; GtkWidget *submenu = NULL; GtkAccelGroup *group; 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)); if (group) { char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label); gtk_menu_set_accel_path(GTK_MENU(submenu), path); g_free(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); act->children = NULL; } purple_menu_action_free(act); return menuitem; } #if GTK_CHECK_VERSION(2,3,0) # define NEW_STYLE_COMPLETION #endif typedef struct { GtkWidget *entry; GtkWidget *accountopt; PidginFilterBuddyCompletionEntryFunc filter_func; gpointer filter_func_user_data; #ifdef NEW_STYLE_COMPLETION GtkListStore *store; #else GCompletion *completion; gboolean completion_started; GList *log_items; #endif /* NEW_STYLE_COMPLETION */ } PidginCompletionData; #ifndef NEW_STYLE_COMPLETION static gboolean completion_entry_event(GtkEditable *entry, GdkEventKey *event, PidginCompletionData *data) { int pos, end_pos; if (event->type == GDK_KEY_PRESS && event->keyval == GDK_Tab) { gtk_editable_get_selection_bounds(entry, &pos, &end_pos); if (data->completion_started && pos != end_pos && pos > 1 && end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry)))) { gtk_editable_select_region(entry, 0, 0); gtk_editable_set_position(entry, -1); return TRUE; } } else if (event->type == GDK_KEY_PRESS && event->length > 0) { char *prefix, *nprefix; gtk_editable_get_selection_bounds(entry, &pos, &end_pos); if (data->completion_started && pos != end_pos && pos > 1 && end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry)))) { char *temp; temp = gtk_editable_get_chars(entry, 0, pos); prefix = g_strconcat(temp, event->string, NULL); g_free(temp); } else if (pos == end_pos && pos > 1 && end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry)))) { prefix = g_strconcat(gtk_entry_get_text(GTK_ENTRY(entry)), event->string, NULL); } else return FALSE; pos = strlen(prefix); nprefix = NULL; g_completion_complete(data->completion, prefix, &nprefix); if (nprefix != NULL) { gtk_entry_set_text(GTK_ENTRY(entry), nprefix); gtk_editable_set_position(entry, pos); gtk_editable_select_region(entry, pos, -1); data->completion_started = TRUE; g_free(nprefix); g_free(prefix); return TRUE; } g_free(prefix); } return FALSE; } static void destroy_completion_data(GtkWidget *w, PidginCompletionData *data) { g_list_foreach(data->completion->items, (GFunc)g_free, NULL); g_completion_free(data->completion); g_free(data); } #endif /* !NEW_STYLE_COMPLETION */ #ifdef NEW_STYLE_COMPLETION static gboolean screenname_completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data) { GtkTreeModel *model; GValue val1; GValue val2; const char *tmp; model = gtk_entry_completion_get_model (completion); val1.g_type = 0; gtk_tree_model_get_value(model, iter, 2, &val1); tmp = g_value_get_string(&val1); if (tmp != NULL && purple_str_has_prefix(tmp, key)) { g_value_unset(&val1); return TRUE; } g_value_unset(&val1); val2.g_type = 0; gtk_tree_model_get_value(model, iter, 3, &val2); tmp = g_value_get_string(&val2); if (tmp != NULL && purple_str_has_prefix(tmp, key)) { g_value_unset(&val2); return TRUE; } g_value_unset(&val2); return FALSE; } static gboolean screenname_completion_match_selected_cb(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data) { GValue val; GtkWidget *optmenu = data->accountopt; PurpleAccount *account; val.g_type = 0; gtk_tree_model_get_value(model, iter, 1, &val); gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val)); g_value_unset(&val); gtk_tree_model_get_value(model, iter, 4, &val); account = g_value_get_pointer(&val); g_value_unset(&val); if (account == NULL) return TRUE; if (optmenu != NULL) aop_option_menu_select_by_data(optmenu, account); return TRUE; } static void add_screenname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias, const PurpleAccount *account, const char *screenname) { GtkTreeIter iter; gboolean completion_added = FALSE; gchar *normalized_screenname; gchar *tmp; tmp = g_utf8_normalize(screenname, -1, G_NORMALIZE_DEFAULT); normalized_screenname = g_utf8_casefold(tmp, -1); g_free(tmp); /* There's no sense listing things like: 'xxx "xxx"' when the screenname and buddy alias match. */ if (buddy_alias && strcmp(buddy_alias, screenname)) { char *completion_entry = g_strdup_printf("%s \"%s\"", screenname, buddy_alias); char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT); tmp = g_utf8_casefold(tmp2, -1); g_free(tmp2); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, completion_entry, 1, screenname, 2, normalized_screenname, 3, tmp, 4, account, -1); g_free(completion_entry); g_free(tmp); completion_added = TRUE; } /* There's no sense listing things like: 'xxx "xxx"' when the screenname and contact alias match. */ if (contact_alias && strcmp(contact_alias, screenname)) { /* 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\"", screenname, contact_alias); char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT); tmp = g_utf8_casefold(tmp2, -1); g_free(tmp2); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, completion_entry, 1, screenname, 2, normalized_screenname, 3, tmp, 4, account, -1); g_free(completion_entry); g_free(tmp); completion_added = TRUE; } } if (completion_added == FALSE) { /* Add the buddy's screenname. */ gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, screenname, 1, screenname, 2, normalized_screenname, 3, NULL, 4, account, -1); } g_free(normalized_screenname); } #endif /* NEW_STYLE_COMPLETION */ 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.is_buddy = FALSE; entry.entry.logged_buddy = set; if (filter_func(&entry, user_data)) { #ifdef NEW_STYLE_COMPLETION add_screenname_autocomplete_entry(data->store, NULL, NULL, set->account, set->name); #else /* Steal the name for the GCompletion. */ data->log_items = g_list_append(data->log_items, set->name); set->name = set->normalized_name = NULL; #endif /* NEW_STYLE_COMPLETION */ } } } static void add_completion_list(PidginCompletionData *data) { PurpleBlistNode *gnode, *cnode, *bnode; PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func; gpointer user_data = data->filter_func_user_data; GHashTable *sets; #ifdef NEW_STYLE_COMPLETION gtk_list_store_clear(data->store); #else GList *item = g_list_append(NULL, NULL); g_list_foreach(data->completion->items, (GFunc)g_free, NULL); g_completion_clear_items(data->completion); #endif /* NEW_STYLE_COMPLETION */ for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next) { if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue; for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) { if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) continue; for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) { PidginBuddyCompletionEntry entry; entry.is_buddy = TRUE; entry.entry.buddy = (PurpleBuddy *) bnode; if (filter_func(&entry, user_data)) { #ifdef NEW_STYLE_COMPLETION add_screenname_autocomplete_entry(data->store, ((PurpleContact *)cnode)->alias, purple_buddy_get_contact_alias(entry.entry.buddy), entry.entry.buddy->account, entry.entry.buddy->name ); #else item->data = g_strdup(entry.entry.buddy->name); g_completion_add_items(data->completion, item); #endif /* NEW_STYLE_COMPLETION */ } } } } #ifndef NEW_STYLE_COMPLETION g_list_free(item); data->log_items = NULL; #endif /* NEW_STYLE_COMPLETION */ sets = purple_log_get_log_sets(); g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data); g_hash_table_destroy(sets); #ifndef NEW_STYLE_COMPLETION g_completion_add_items(data->completion, data->log_items); g_list_free(data->log_items); #endif /* NEW_STYLE_COMPLETION */ } static void screenname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data) { g_free(data); purple_signals_disconnect_by_handle(widget); } static void repopulate_autocomplete(gpointer something, gpointer data) { add_completion_list(data); } void pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data) { PidginCompletionData *data; #ifdef NEW_STYLE_COMPLETION /* Store the displayed completion value, the screenname, the UTF-8 normalized & casefolded screenname, * the UTF-8 normalized & casefolded value for comparison, and the account. */ GtkListStore *store; 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->entry = entry; data->accountopt = accountopt; if (filter_func == NULL) { data->filter_func = pidgin_screenname_autocomplete_default_filter; data->filter_func_user_data = NULL; } else { data->filter_func = filter_func; data->filter_func_user_data = user_data; } data->store = store; add_completion_list(data); /* Sort the completion list by screenname. */ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), 1, GTK_SORT_ASCENDING); completion = gtk_entry_completion_new(); gtk_entry_completion_set_match_func(completion, screenname_completion_match_func, NULL, NULL); g_signal_connect(G_OBJECT(completion), "match-selected", G_CALLBACK(screenname_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)); g_object_unref(store); gtk_entry_completion_set_text_column(completion, 0); #else /* !NEW_STYLE_COMPLETION */ data = g_new0(PidginCompletionData, 1); data->entry = entry; data->accountopt = accountopt; if (filter_func == NULL) { data->filter_func = pidgin_screenname_autocomplete_default_filter; data->filter_func_user_data = NULL; } else { data->filter_func = filter_func; data->filter_func_user_data = user_data; } data->completion = g_completion_new(NULL); data->completion_started = FALSE; add_completion_list(data); g_completion_set_compare(data->completion, g_ascii_strncasecmp); g_signal_connect(G_OBJECT(entry), "event", G_CALLBACK(completion_entry_event), data); g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(destroy_completion_data), data); #endif /* !NEW_STYLE_COMPLETION */ 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(screenname_autocomplete_destroyed_cb), data); } gboolean 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); } else { return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account)); } } void 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) { GdkCursor *cursor; g_return_if_fail(widget != NULL); if (widget->window == NULL) return; cursor = gdk_cursor_new(GDK_WATCH); gdk_window_set_cursor(widget->window, cursor); gdk_cursor_unref(cursor); #if GTK_CHECK_VERSION(2,4,0) gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window))); #else gdk_flush(); #endif } void pidgin_clear_cursor(GtkWidget *widget) { g_return_if_fail(widget != NULL); if (widget->window == NULL) return; gdk_window_set_cursor(widget->window, NULL); } struct _icon_chooser { GtkWidget *icon_filesel; GtkWidget *icon_preview; GtkWidget *icon_text; void (*callback)(const char*,gpointer); gpointer data; }; #if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ static void icon_filesel_delete_cb(GtkWidget *w, struct _icon_chooser *dialog) { if (dialog->icon_filesel != NULL) gtk_widget_destroy(dialog->icon_filesel); if (dialog->callback) dialog->callback(NULL, dialog->data); g_free(dialog); } #endif /* FILECHOOSER */ #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ static void 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; if (dialog->callback) dialog->callback(NULL, dialog->data); g_free(dialog); return; } 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); g_free(current_folder); } #else /* FILECHOOSER */ static void icon_filesel_choose_cb(GtkWidget *w, struct _icon_chooser *dialog) { char *filename, *current_folder; filename = g_strdup(gtk_file_selection_get_filename( GTK_FILE_SELECTION(dialog->icon_filesel))); /* If they typed in a directory, change there */ if (pidgin_check_if_dir(filename, GTK_FILE_SELECTION(dialog->icon_filesel))) { g_free(filename); return; } current_folder = g_path_get_dirname(filename); if (current_folder != NULL) { purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder); g_free(current_folder); } #endif /* FILECHOOSER */ if (dialog->callback) dialog->callback(filename, dialog->data); gtk_widget_destroy(dialog->icon_filesel); g_free(filename); g_free(dialog); } static void #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog) #else /* FILECHOOSER */ icon_preview_change_cb(GtkTreeSelection *sel, struct _icon_chooser *dialog) #endif /* FILECHOOSER */ { GdkPixbuf *pixbuf, *scale; int height, width; char *basename, *markup, *size; struct stat st; char *filename; #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ filename = gtk_file_chooser_get_preview_filename( GTK_FILE_CHOOSER(dialog->icon_filesel)); #else /* FILECHOOSER */ filename = g_strdup(gtk_file_selection_get_filename( GTK_FILE_SELECTION(dialog->icon_filesel))); #endif /* FILECHOOSER */ if (!filename || g_stat(filename, &st) || !(pixbuf = gdk_pixbuf_new_from_file(filename, NULL))) { gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL); gtk_label_set_markup(GTK_LABEL(dialog->icon_text), ""); g_free(filename); return; } width = gdk_pixbuf_get_width(pixbuf); height = gdk_pixbuf_get_height(pixbuf); 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>File size:</b> %s\n" "<b>Image size:</b> %dx%d"), basename, size, width, height); scale = gdk_pixbuf_scale_simple(pixbuf, width * 50 / height, 50, GDK_INTERP_BILINEAR); gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), scale); gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup); g_object_unref(G_OBJECT(pixbuf)); g_object_unref(G_OBJECT(scale)); g_free(filename); g_free(basename); g_free(size); g_free(markup); } 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); #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ GtkWidget *vbox; #else GtkWidget *hbox; GtkWidget *tv; GtkTreeSelection *sel; #endif /* FILECHOOSER */ const char *current_folder; dialog->callback = callback; dialog->data = data; if (dialog->icon_filesel != NULL) { gtk_window_present(GTK_WINDOW(dialog->icon_filesel)); return NULL; } current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder"); #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"), parent, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); 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), current_folder); 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); #else /* FILECHOOSER */ dialog->icon_filesel = gtk_file_selection_new(_("Buddy Icon")); dialog->icon_preview = gtk_image_new(); dialog->icon_text = gtk_label_new(NULL); if ((current_folder != NULL) && (*current_folder != '\0')) gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog->icon_filesel), current_folder); gtk_widget_set_size_request(GTK_WIDGET(dialog->icon_preview),-1, 50); hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start( GTK_BOX(GTK_FILE_SELECTION(dialog->icon_filesel)->main_vbox), hbox, FALSE, FALSE, 0); gtk_box_pack_end(GTK_BOX(hbox), dialog->icon_preview, FALSE, FALSE, 0); gtk_box_pack_end(GTK_BOX(hbox), dialog->icon_text, FALSE, FALSE, 0); tv = GTK_FILE_SELECTION(dialog->icon_filesel)->file_list; sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)); g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(icon_preview_change_cb), dialog); g_signal_connect( G_OBJECT(GTK_FILE_SELECTION(dialog->icon_filesel)->ok_button), "clicked", G_CALLBACK(icon_filesel_choose_cb), dialog); g_signal_connect( G_OBJECT(GTK_FILE_SELECTION(dialog->icon_filesel)->cancel_button), "clicked", G_CALLBACK(icon_filesel_delete_cb), dialog); g_signal_connect(G_OBJECT(dialog->icon_filesel), "destroy", G_CALLBACK(icon_filesel_delete_cb), dialog); #endif /* FILECHOOSER */ return dialog->icon_filesel; } #if GTK_CHECK_VERSION(2,2,0) static gboolean str_array_match(char **a, char **b) { int i, j; if (!a || !b) return FALSE; for (i = 0; a[i] != NULL; i++) for (j = 0; b[j] != NULL; j++) if (!g_ascii_strcasecmp(a[i], b[j])) return TRUE; return FALSE; } #endif gpointer pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len) { PurplePluginProtocolInfo *prpl_info; #if GTK_CHECK_VERSION(2,2,0) char **prpl_formats; int width, height; char **pixbuf_formats = NULL; GdkPixbufFormat *format; GdkPixbuf *pixbuf; #if !GTK_CHECK_VERSION(2,4,0) GdkPixbufLoader *loader; #endif #endif gchar *contents; gsize length; prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); g_return_val_if_fail(prpl_info->icon_spec.format != NULL, NULL); #if GTK_CHECK_VERSION(2,2,0) #if GTK_CHECK_VERSION(2,4,0) format = gdk_pixbuf_get_file_info(path, &width, &height); #else loader = gdk_pixbuf_loader_new(); if (g_file_get_contents(path, &contents, &length, NULL)) { gdk_pixbuf_loader_write(loader, contents, length, NULL); g_free(contents); } gdk_pixbuf_loader_close(loader, NULL); pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); width = gdk_pixbuf_get_width(pixbuf); height = gdk_pixbuf_get_height(pixbuf); format = gdk_pixbuf_loader_get_format(loader); g_object_unref(G_OBJECT(loader)); #endif if (format == NULL) return NULL; pixbuf_formats = gdk_pixbuf_format_get_extensions(format); prpl_formats = g_strsplit(prpl_info->icon_spec.format,",",0); if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */ (!(prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */ (prpl_info->icon_spec.min_width <= width && prpl_info->icon_spec.max_width >= width && prpl_info->icon_spec.min_height <= height && prpl_info->icon_spec.max_height >= height))) /* The icon is the correct size */ #endif { #if GTK_CHECK_VERSION(2,2,0) g_strfreev(prpl_formats); g_strfreev(pixbuf_formats); #endif /* We don't need to scale the image. */ contents = NULL; if (!g_file_get_contents(path, &contents, &length, NULL)) { g_free(contents); #if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0) g_object_unref(G_OBJECT(pixbuf)); #endif return NULL; } } #if GTK_CHECK_VERSION(2,2,0) else { int i; GError *error = NULL; GdkPixbuf *scale; gboolean success = FALSE; char *filename = NULL; g_strfreev(pixbuf_formats); pixbuf = gdk_pixbuf_new_from_file(path, &error); if (error) { purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message); g_error_free(error); g_strfreev(prpl_formats); return NULL; } if ((prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) && (width < prpl_info->icon_spec.min_width || width > prpl_info->icon_spec.max_width || height < prpl_info->icon_spec.min_height || height > prpl_info->icon_spec.max_height)) { int new_width = width; int new_height = height; purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &new_width, &new_height); scale = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height, GDK_INTERP_HYPER); g_object_unref(G_OBJECT(pixbuf)); pixbuf = scale; } for (i = 0; prpl_formats[i]; i++) { FILE *fp; g_free(filename); fp = purple_mkstemp(&filename, TRUE); if (!fp) { g_free(filename); return NULL; } fclose(fp); purple_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats[i], filename); /* The "compression" param wasn't supported until gdk-pixbuf 2.8. * Using it in previous versions causes the save to fail (and an assert message). */ if ((gdk_pixbuf_major_version > 2 || (gdk_pixbuf_major_version == 2 && gdk_pixbuf_minor_version >= 8)) && strcmp(prpl_formats[i], "png") == 0) { if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i], &error, "compression", "9", NULL)) { success = TRUE; break; } } else if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i], &error, NULL)) { success = TRUE; break; } /* The NULL checking 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"); g_error_free(error); error = NULL; } g_strfreev(prpl_formats); g_object_unref(G_OBJECT(pixbuf)); if (!success) { purple_debug_error("buddyicon", "Could not convert icon to usable format.\n"); return NULL; } contents = NULL; if (!g_file_get_contents(filename, &contents, &length, NULL)) { purple_debug_error("buddyicon", "Could not read '%s', which we just wrote to disk.\n", filename); g_free(contents); g_free(filename); return NULL; } g_unlink(filename); g_free(filename); } /* Check the image size */ /* * TODO: If the file is too big, it would be cool if we checked if * the prpl supported jpeg, and then we could convert to that * and use a lower quality setting. */ if ((prpl_info->icon_spec.max_filesize != 0) && (length > prpl_info->icon_spec.max_filesize)) { gchar *tmp; 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); purple_debug_info("buddyicon", "'%s' was converted to an image which is %" G_GSIZE_FORMAT " bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT " bytes\n", path, length, plugin->info->name, prpl_info->icon_spec.max_filesize); g_free(tmp); return NULL; } if (len) *len = length; return contents; #else /* * The chosen icon wasn't the right size, and we're using * GTK+ 2.0 so we can't scale it. */ return NULL; #endif } #if !GTK_CHECK_VERSION(2,6,0) static void _gdk_file_scale_size_prepared_cb (GdkPixbufLoader *loader, int width, int height, gpointer data) { struct { gint width; gint height; gboolean preserve_aspect_ratio; } *info = data; g_return_if_fail (width > 0 && height > 0); if (info->preserve_aspect_ratio && (info->width > 0 || info->height > 0)) { if (info->width < 0) { width = width * (double)info->height/(double)height; height = info->height; } else if (info->height < 0) { height = height * (double)info->width/(double)width; width = info->width; } else if ((double)height * (double)info->width > (double)width * (double)info->height) { width = 0.5 + (double)width * (double)info->height / (double)height; height = info->height; } else { height = 0.5 + (double)height * (double)info->width / (double)width; width = info->width; } } else { if (info->width > 0) width = info->width; if (info->height > 0) height = info->height; } #if GTK_CHECK_VERSION(2,2,0) /* 2.0 users are going to have very strangely sized things */ gdk_pixbuf_loader_set_size (loader, width, height); #else #warning nosnilmot could not be bothered to fix this properly for you #warning ... good luck ... your images may end up strange sizes #endif } GdkPixbuf * gdk_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio, GError **error) { GdkPixbufLoader *loader; GdkPixbuf *pixbuf; guchar buffer [4096]; int length; FILE *f; struct { gint width; gint height; gboolean preserve_aspect_ratio; } info; GdkPixbufAnimation *animation; GdkPixbufAnimationIter *iter; gboolean has_frame; g_return_val_if_fail (filename != NULL, NULL); g_return_val_if_fail (width > 0 || width == -1, NULL); g_return_val_if_fail (height > 0 || height == -1, NULL); f = g_fopen (filename, "rb"); if (!f) { gint save_errno = errno; gchar *display_name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (save_errno), _("Failed to open file '%s': %s"), display_name ? display_name : "(unknown)", g_strerror (save_errno)); g_free (display_name); return NULL; } loader = gdk_pixbuf_loader_new (); info.width = width; info.height = height; info.preserve_aspect_ratio = preserve_aspect_ratio; g_signal_connect (loader, "size-prepared", G_CALLBACK (_gdk_file_scale_size_prepared_cb), &info); has_frame = FALSE; while (!has_frame && !feof (f) && !ferror (f)) { length = fread (buffer, 1, sizeof (buffer), f); if (length > 0) if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) { gdk_pixbuf_loader_close (loader, NULL); fclose (f); g_object_unref (loader); return NULL; } animation = gdk_pixbuf_loader_get_animation (loader); if (animation) { iter = gdk_pixbuf_animation_get_iter (animation, 0); if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) { has_frame = TRUE; } g_object_unref (iter); } } fclose (f); if (!gdk_pixbuf_loader_close (loader, error) && !has_frame) { g_object_unref (loader); return NULL; } pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); if (!pixbuf) { gchar *display_name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); g_object_unref (loader); g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Failed to load image '%s': reason not known, probably a corrupt image file"), display_name ? display_name : "(unknown)"); g_free (display_name); return NULL; } g_object_ref (pixbuf); g_object_unref (loader); return pixbuf; } #endif /* ! Gtk 2.6.0 */ void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename) { PurpleBuddy *buddy; PurpleContact *contact; gpointer data = NULL; size_t len = 0; buddy = purple_find_buddy(account, who); if (!buddy) { purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n"); return; } contact = purple_buddy_get_contact(buddy); if (filename) { const char *prpl_id = purple_account_get_protocol_id(account); PurplePlugin *prpl = purple_find_prpl(prpl_id); data = pidgin_convert_buddy_icon(prpl, filename, &len); /* We don't want to delete the old icon if the new one didn't load. */ if (data == NULL) return; } purple_buddy_icons_set_custom_icon(contact, data, len); } char *pidgin_make_pretty_arrows(const char *str) { char *ret; char **split = g_strsplit(str, "->", -1); ret = g_strjoinv("\342\207\250", split); g_strfreev(split); split = g_strsplit(ret, "<-", -1); g_free(ret); ret = g_strjoinv("\342\207\246", split); g_strfreev(split); return ret; } void pidgin_set_urgent(GtkWindow *window, gboolean urgent) { #if GTK_CHECK_VERSION(2,8,0) gtk_window_set_urgency_hint(window, urgent); #elif defined _WIN32 winpidgin_window_flash(window, urgent); #elif defined GDK_WINDOWING_X11 GdkWindow *gdkwin; XWMHints *hints; g_return_if_fail(window != NULL); gdkwin = GTK_WIDGET(window)->window; g_return_if_fail(gdkwin != NULL); hints = XGetWMHints(GDK_WINDOW_XDISPLAY(gdkwin), GDK_WINDOW_XWINDOW(gdkwin)); if(!hints) hints = XAllocWMHints(); if (urgent) hints->flags |= XUrgencyHint; else hints->flags &= ~XUrgencyHint; XSetWMHints(GDK_WINDOW_XDISPLAY(gdkwin), GDK_WINDOW_XWINDOW(gdkwin), hints); XFree(hints); #else /* do something else? */ #endif } GSList *minidialogs = NULL; static void * pidgin_utils_get_handle() { static int handle; return &handle; } static void connection_signed_off_cb(PurpleConnection *gc) { GSList *list; for (list = minidialogs; list; list = list->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); } void *pidgin_make_mini_dialog(PurpleConnection *gc, const char *icon_name, const char *primary, const char *secondary, void *user_data, ...) { GtkWidget *vbox; GtkWidget *hbox; GtkWidget *hbox2; GtkWidget *label; GtkWidget *button; GtkWidget *img = NULL; GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH); char label_text[2048]; const char *button_text; GCallback callback; char *primary_esc, *secondary_esc = NULL; va_list args; static gboolean first_call = TRUE; img = gtk_image_new_from_stock(icon_name, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); gtk_misc_set_alignment(GTK_MISC(img), 0, 0); vbox = gtk_vbox_new(FALSE,0); gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BOX_SPACE); g_object_set_data(G_OBJECT(vbox), "gc" ,gc); minidialogs = g_slist_prepend(minidialogs, vbox); g_signal_connect(G_OBJECT(vbox), "destroy", G_CALLBACK(alert_killed_cb), NULL); if (first_call) { first_call = FALSE; purple_signal_connect(purple_connections_get_handle(), "signed-off", pidgin_utils_get_handle(), PURPLE_CALLBACK(connection_signed_off_cb), NULL); } hbox = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(vbox), hbox); if (img != NULL) gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); primary_esc = g_markup_escape_text(primary, -1); if (secondary) secondary_esc = g_markup_escape_text(secondary, -1); g_snprintf(label_text, sizeof(label_text), "<span weight=\"bold\" size=\"smaller\">%s</span>%s<span size=\"smaller\">%s</span>", primary_esc, secondary ? "\n" : "", secondary_esc ? secondary_esc : ""); g_free(primary_esc); g_free(secondary_esc); label = gtk_label_new(NULL); gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1); gtk_label_set_markup(GTK_LABEL(label), label_text); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); hbox2 = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 0); va_start(args, user_data); while ((button_text = va_arg(args, char*))) { callback = va_arg(args, GCallback); button = gtk_button_new(); if (callback) g_signal_connect_swapped(G_OBJECT(button), "clicked", callback, user_data); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), vbox); hbox = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(button), hbox); gtk_container_set_border_width(GTK_CONTAINER(hbox), 3); g_snprintf(label_text, sizeof(label_text), "<span size=\"smaller\">%s</span>", button_text); label = gtk_label_new(NULL); gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), label_text); gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); gtk_box_pack_end(GTK_BOX(hbox2), button, FALSE, FALSE, 0); gtk_size_group_add_widget(sg, button); } va_end(args); g_object_unref(sg); return vbox; } /* * "This is so dead sexy." * "Two thumbs up." * "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 * entered string. * * 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) { gchar *enteredstring; gchar *tmp; gchar *withmarkup; gchar *nomarkup; gchar *normalized; gboolean result; size_t i; size_t len; PangoLogAttr *log_attrs; gchar *word; if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0) { purple_notify_info(NULL, "WOPR", "Wouldn't you prefer a nice game of chess?", NULL); return FALSE; } gtk_tree_model_get(model, iter, column, &withmarkup, -1); if (withmarkup == NULL) /* This is probably a separator */ return TRUE; tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT); enteredstring = g_utf8_casefold(tmp, -1); g_free(tmp); nomarkup = purple_markup_strip_html(withmarkup); tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT); g_free(nomarkup); normalized = g_utf8_casefold(tmp, -1); g_free(tmp); if (purple_str_has_prefix(normalized, enteredstring)) { g_free(withmarkup); g_free(enteredstring); g_free(normalized); return FALSE; } /* 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); word = normalized; result = TRUE; for (i = 0; i < (len - 1) ; i++) { if (log_attrs[i].is_word_start && purple_str_has_prefix(word, enteredstring)) { result = FALSE; break; } word = g_utf8_next_char(word); } g_free(log_attrs); /* The non-Pango version. */ #if 0 word = normalized; result = TRUE; while (word[0] != '\0') { 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)) { result = FALSE; break; } } else word = g_utf8_find_next_char(word, NULL); } #endif g_free(withmarkup); g_free(enteredstring); g_free(normalized); return result; } gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) { int width, height, rowstride, i; unsigned char *pixels; unsigned char *row; if (!gdk_pixbuf_get_has_alpha(pixbuf)) return TRUE; width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); pixels = gdk_pixbuf_get_pixels (pixbuf); row = pixels; for (i = 3; i < rowstride; i+=4) { if (row[i] < 0xfe) return FALSE; } for (i = 1; i < height - 1; i++) { row = pixels + (i*rowstride); if (row[3] < 0xfe || row[rowstride-1] < 0xfe) { return FALSE; } } row = pixels + ((height-1) * rowstride); for (i = 3; i < rowstride; i+=4) { if (row[i] < 0xfe) return FALSE; } return TRUE; } void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) { int width, height, rowstride; guchar *pixels; if (!gdk_pixbuf_get_has_alpha(pixbuf)) return; 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) return; /* Top left */ pixels[3] = 0; pixels[7] = 0x80; pixels[11] = 0xC0; pixels[rowstride + 3] = 0x80; pixels[rowstride * 2 + 3] = 0xC0; /* Top right */ 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; /* Bottom left */ 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; /* Bottom right */ 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] = ""; GtkStyle *style; if (!widget) return "dim grey"; style = gtk_widget_get_style(widget); if (!style) return "dim grey"; 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); return dim_grey_string; } #if !GTK_CHECK_VERSION(2,2,0) GtkTreePath * gtk_tree_path_new_from_indices (gint first_index, ...) { int arg; va_list args; GtkTreePath *path; path = gtk_tree_path_new (); va_start (args, first_index); arg = first_index; while (arg != -1) { gtk_tree_path_append_index (path, arg); arg = va_arg (args, gint); } va_end (args); return path; } #endif static void 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 : ""); g_free(text); } static gboolean entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo) { if (key->keyval == GDK_Down || key->keyval == GDK_Up) { gtk_combo_box_popup(combo); return TRUE; } return FALSE; } GtkWidget * pidgin_text_combo_box_entry_new(const char *default_item, GList *items) { GtkComboBox *ret = NULL; GtkWidget *the_entry = NULL; ret = GTK_COMBO_BOX(gtk_combo_box_new_text()); the_entry = gtk_entry_new(); gtk_container_add(GTK_CONTAINER(ret), the_entry); if (default_item) gtk_entry_set_text(GTK_ENTRY(the_entry), default_item); for (; items != NULL ; items = items->next) { char *text = items->data; if (text && *text) 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); return GTK_WIDGET(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)); }