diff src/gtkutils.c @ 14133:31d33e7bc0e6

[gaim-migrate @ 16775] A global buddy icon selector in the statusbox. This is done totally in the GTK+ UI; the core still sees a buddy icon as belonging to an account. Per-account icons can override the global one in Modify Account. There are some caching issues to work out, still. committer: Tailor Script <tailor@pidgin.im>
author Sean Egan <seanegan@gmail.com>
date Tue, 15 Aug 2006 23:25:29 +0000
parents 8bda65b88e49
children 4006d3dc2871
line wrap: on
line diff
--- a/src/gtkutils.c	Tue Aug 15 20:23:58 2006 +0000
+++ b/src/gtkutils.c	Tue Aug 15 23:25:29 2006 +0000
@@ -2211,3 +2211,429 @@
 	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, 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) {
+		gaim_prefs_set_string("/gaim/gtk/filelocations/last_icon_folder", current_folder);
+		g_free(current_folder);
+	}
+
+#else /* FILECHOOSER */
+static void
+icon_filesel_choose_cb(GtkWidget *w, AccountPrefsDialog *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 (gaim_gtk_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) {
+		gaim_prefs_set_string("/gaim/gtk/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))
+	{
+		g_free(filename);
+		return;
+	}
+
+	pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+	if (!pixbuf) {
+		gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
+		gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
+#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
+		gtk_file_chooser_set_preview_widget_active(
+					GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
+#endif /* FILECHOOSER */
+		g_free(filename);
+		return;
+	}
+
+	width = gdk_pixbuf_get_width(pixbuf);
+	height = gdk_pixbuf_get_height(pixbuf);
+	basename = g_path_get_basename(filename);
+	size = gaim_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);
+#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
+	gtk_file_chooser_set_preview_widget_active(
+					GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
+#endif /* FILECHOOSER */
+	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 *gaim_gtk_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 *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 = gaim_prefs_get_string("/gaim/gtk/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);
+	gtk_widget_set_size_request(GTK_WIDGET(dialog->icon_preview), -1, 50);
+	gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel),
+					    GTK_WIDGET(dialog->icon_preview));
+	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, GAIM_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
+
+char*
+gaim_gtk_convert_buddy_icon(GaimPlugin *plugin, const char *path)
+{
+#if GTK_CHECK_VERSION(2,2,0)
+	int width, height;
+	char **pixbuf_formats = NULL;
+	GdkPixbufFormat *format;
+	GdkPixbuf *pixbuf;
+	GaimPluginProtocolInfo *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin);
+	char **prpl_formats =  g_strsplit (prpl_info->icon_spec.format,",",0);
+#if !GTK_CHECK_VERSION(2,4,0)
+	GdkPixbufLoader *loader;
+	FILE *file;
+	struct stat st;
+	void *data = NULL;
+#endif
+#endif
+	const char *dirname = gaim_buddy_icons_get_cache_dir();
+	char *random   = g_strdup_printf("%x", g_random_int());
+	char *filename = g_build_filename(dirname, random, NULL);
+
+	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
+		gaim_debug_info("buddyicon", "Creating icon cache directory.\n");
+
+		if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
+			gaim_debug_error("buddyicon",
+							 "Unable to create directory %s: %s\n",
+							 dirname, strerror(errno));
+#if GTK_CHECK_VERSION(2,2,0)
+			g_strfreev(prpl_formats);
+#endif
+			g_free(random);
+			g_free(filename);
+			return 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_stat(path, &st) && (file = g_fopen(path, "rb")) != NULL) {
+		data = g_malloc(st.st_size);
+		fread(data, 1, st.st_size, file);
+		fclose(file);
+		gdk_pixbuf_loader_write(loader, data, st.st_size, NULL);
+		g_free(data);
+	}
+	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
+	pixbuf_formats =  gdk_pixbuf_format_get_extensions(format);
+
+	if (str_array_match(pixbuf_formats, prpl_formats) &&                  /* This is an acceptable format AND */
+		 (!(prpl_info->icon_spec.scale_rules & GAIM_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
+	{
+		gchar *contents;
+		gsize length;
+		FILE *image;
+
+#if GTK_CHECK_VERSION(2,2,0)
+		g_strfreev(prpl_formats);
+		g_strfreev(pixbuf_formats);
+#endif
+
+		/* Copy the image to the cache folder as "filename". */
+
+		if (!g_file_get_contents(path, &contents, &length, NULL) ||
+		    (image = g_fopen(filename, "wb")) == NULL)
+		{
+			g_free(random);
+			g_free(filename);
+#if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
+			g_object_unref(G_OBJECT(pixbuf));
+#endif
+			return NULL;
+		}
+
+		if (fwrite(contents, 1, length, image) != length)
+		{
+			fclose(image);
+			g_unlink(filename);
+
+			g_free(random);
+			g_free(filename);
+#if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
+			g_object_unref(G_OBJECT(pixbuf));
+#endif
+			return NULL;
+		}
+		fclose(image);
+
+#if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
+		g_object_unref(G_OBJECT(pixbuf));
+#endif
+
+		g_free(filename);
+		return random;
+	}
+#if GTK_CHECK_VERSION(2,2,0)
+	else
+	{
+		int i;
+		GError *error = NULL;
+		GdkPixbuf *scale;
+		pixbuf = gdk_pixbuf_new_from_file(path, &error);
+		g_strfreev(pixbuf_formats);
+		if (!error && (prpl_info->icon_spec.scale_rules & GAIM_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;
+
+			if(new_width > prpl_info->icon_spec.max_width)
+				new_width = prpl_info->icon_spec.max_width;
+			else if(new_width < prpl_info->icon_spec.min_width)
+				new_width = prpl_info->icon_spec.min_width;
+			if(new_height > prpl_info->icon_spec.max_height)
+				new_height = prpl_info->icon_spec.max_height;
+			else if(new_height < prpl_info->icon_spec.min_height)
+				new_height = prpl_info->icon_spec.min_height;
+
+			/* preserve aspect ratio */
+			if ((double)height * (double)new_width >
+				(double)width * (double)new_height) {
+					new_width = 0.5 + (double)width * (double)new_height / (double)height;
+			} else {
+					new_height = 0.5 + (double)height * (double)new_width / (double)width;
+			}
+
+			scale = gdk_pixbuf_scale_simple (pixbuf, new_width, new_height,
+					GDK_INTERP_HYPER);
+			g_object_unref(G_OBJECT(pixbuf));
+			pixbuf = scale;
+		}
+		if (error) {
+			g_free(random);
+			g_free(filename);
+			gaim_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message);
+			g_error_free(error);
+			g_strfreev(prpl_formats);
+			return NULL;
+		}
+
+		for (i = 0; prpl_formats[i]; i++) {
+			gaim_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats[i], filename);
+			/* The gdk-pixbuf documentation is wrong. gdk_pixbuf_save returns TRUE if it was successful,
+			 * FALSE if an error was set. */
+			if (gdk_pixbuf_save (pixbuf, filename, prpl_formats[i], &error, NULL) == TRUE)
+					break;
+			gaim_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats[i], error->message);
+			g_error_free(error);
+			error = NULL;
+		}
+		g_strfreev(prpl_formats);
+		if (!error) {
+			g_object_unref(G_OBJECT(pixbuf));
+			g_free(filename);
+			return random;
+		} else {
+			gaim_debug_error("buddyicon", "Could not convert icon to usable format: %s\n", error->message);
+			g_error_free(error);
+		}
+		g_free(random);
+		g_free(filename);
+		g_object_unref(G_OBJECT(pixbuf));
+	}
+	return NULL;
+#endif
+}