/* * Copyright (C) 2012 - Juan Ferrer Toribio * * 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 3 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, see . */ #include "vn-column-image.h" #include "../vn-list-model.h" #define LOAD_IMAGE _IMAGE_DIR"/load.gif" G_DEFINE_TYPE (VnColumnImage, vn_column_image, VN_TYPE_COLUMN); typedef struct { VnColumnImage * self; GtkTreeModel * model; GtkTreeIter * iter; GtkCellRenderer * cell; gchar * name; } DownloadData; typedef struct { gchar * path; gboolean error; gint tooltip_size; GtkWidget * image; } TooltipData; VnColumn * vn_column_image_new () { return g_object_new (VN_TYPE_COLUMN_IMAGE, NULL); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Private static void vn_column_image_download_error (VnColumnImage * self, const GError * error) { g_object_set (VN_COLUMN (self)->cell, "icon-name", "image-missing", NULL); } static void free_object (gpointer object) { if (object != NULL) g_object_unref (object); } static void free_tooltip_data (TooltipData * data) { if (data) { g_free (data->path); if (data->image) g_object_unref (data->image); } g_free (data); } static void vn_column_image_on_tooltip_size (GdkPixbufLoader * loader, gint width, gint height, gpointer tsize) { gint h, w, size = GPOINTER_TO_INT (tsize); if (width > size || height > size) { if (width >= height) { w = size; h = height * w / width; } else { h = size; w = width * h / height; } } else { w = width; h = height; } gdk_pixbuf_loader_set_size (loader, w, h); } static void vn_column_image_on_download_tooltip (DbFileLoader * fl, GBytes * bytes, const GError * error, TooltipData * data) { if (error) data->image = g_object_ref_sink (gtk_image_new_from_icon_name ("image-missing", GTK_ICON_SIZE_MENU)); else if (bytes && data) { gsize size; GError * err = NULL; const guchar * raw_data = g_bytes_get_data (bytes, &size); GdkPixbufLoader * loader = gdk_pixbuf_loader_new (); if (data->tooltip_size > 0) g_signal_connect (loader, "size-prepared", G_CALLBACK (vn_column_image_on_tooltip_size), GINT_TO_POINTER (data->tooltip_size)); if (gdk_pixbuf_loader_write (loader, raw_data, size, &err) && gdk_pixbuf_loader_close (loader, &err)) { GdkPixbuf * pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); if (data->image && G_IS_OBJECT (data->image)) g_object_unref (data->image); data->image = g_object_ref_sink (gtk_image_new_from_pixbuf (pixbuf)); } else { gdk_pixbuf_loader_close (loader, NULL); g_error_free (err); } g_object_unref (loader); } gtk_tooltip_trigger_tooltip_query (gdk_display_get_default()); } static gboolean vn_column_image_on_query_tooltip (GtkTreeView * view, gint x, gint y, gboolean k, GtkTooltip * tip, VnColumnImage * self) { gboolean ret = FALSE; TooltipData * data; GtkTreeIter iter; GtkTreePath * path = NULL; if (gtk_tree_view_get_tooltip_context (view, &x, &y, k, NULL, &path, &iter)) { gint wx, wy; GdkRectangle rect; gtk_tree_view_convert_bin_window_to_widget_coords (view, x, y, &wx, &wy); gtk_tree_view_get_background_area (view, path, GTK_TREE_VIEW_COLUMN (self), &rect); if (!(rect.x < wx && wx < rect.x + rect.width)) { gtk_tree_path_free (path); return FALSE; } } else return FALSE; if (self->tooltips && (data = g_hash_table_lookup (self->tooltips, iter.user_data)) && !data->error && data->path) { gtk_tree_view_set_tooltip_cell (view, tip, path, GTK_TREE_VIEW_COLUMN (self), VN_COLUMN (self)->cell); if (!data->image) { data->image = g_object_ref_sink (gtk_image_new_from_file (LOAD_IMAGE)); gtk_tooltip_set_custom (tip, data->image); db_file_loader_download (self->loader, data->path, (DbFileLoaderCallbackFunc) vn_column_image_on_download_tooltip, data); } else gtk_tooltip_set_custom (tip, data->image); ret = TRUE; } gtk_tree_path_free (path); return ret; } static GdkPixbuf * vn_column_image_set_image (VnColumnImage * self, GtkCellRenderer * cell, GBytes * bytes, gboolean pix) { gsize size; GdkPixbuf * pixbuf = NULL; GError * error = NULL; const guchar * raw_data = g_bytes_get_data (bytes, &size); if (raw_data) { GdkPixbufLoader * loader = gdk_pixbuf_loader_new (); if (gdk_pixbuf_loader_write (loader, raw_data, size, &error) && gdk_pixbuf_loader_close (loader, &error)) { pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); g_object_set (cell, "pixbuf", pixbuf, NULL); } else { gdk_pixbuf_loader_close (loader, NULL); vn_column_image_download_error (self, error); g_error_free (error); } g_object_unref (loader); } return pix && pixbuf ? g_object_ref (pixbuf) : NULL; } static void vn_column_image_on_download (DbFileLoader * self, GBytes * bytes, const GError * error, DownloadData * data) { GtkTreePath * path; if (error) vn_column_image_download_error (data->self, error); else if (bytes && vn_list_model_iter_is_valid (data->iter, data->model)) { if (data->self->loaded) g_hash_table_replace (data->self->loaded, g_strdup (data->name), vn_column_image_set_image (data->self, data->cell, bytes, TRUE)); path = gtk_tree_model_get_path (data->model, data->iter); gtk_tree_model_row_changed (data->model, path, data->iter); gtk_tree_path_free (path); } g_object_unref (data->self); g_object_unref (data->model); gtk_tree_iter_free (data->iter); g_free (data->name); g_free (data); } static void vn_column_image_set_value (VnColumnImage * self, GtkTreeModel * model, GtkTreeIter * iter, GtkCellRenderer * cell, const GValue * value) { GType type = G_VALUE_TYPE (value); if (type == GVN_TYPE_NULL) g_object_set (cell, "pixbuf", NULL, "icon-name", NULL, NULL); else if (type == G_TYPE_BYTES) { GBytes * bytes = g_value_get_boxed (value); if (bytes) vn_column_image_set_image (self, cell, bytes, FALSE); else vn_column_image_download_error (self, NULL); } else if (type == G_TYPE_STRING) { DownloadData * data; gchar * cell_name; const gchar * name = g_value_get_string (value); GtkTreeView * view = GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (GTK_TREE_VIEW_COLUMN (self))); if (view != self->tree_view) { if (GTK_IS_TREE_VIEW (self->tree_view)) g_signal_handlers_disconnect_by_func (self->tree_view, vn_column_image_on_query_tooltip, self); g_signal_connect (view, "query-tooltip", G_CALLBACK (vn_column_image_on_query_tooltip), self); g_object_set (view, "has-tooltip", TRUE, NULL); self->tree_view = view; } if (self->loaded) { if (g_hash_table_contains (self->loaded, name)) { GdkPixbuf * pixbuf = g_hash_table_lookup (self->loaded, name); if (pixbuf) g_object_set (cell, "pixbuf", pixbuf, NULL); else vn_column_image_download_error (self, NULL); if (self->tooltips && !g_hash_table_contains (self->tooltips, iter->user_data)) { TooltipData * data = g_new (TooltipData, 1); data->path = g_strconcat ("/", self->tooltip_path, "/", name, NULL); data->error = FALSE; data->tooltip_size = self->tooltip_size; data->image = NULL; g_hash_table_insert (self->tooltips, iter->user_data, data); } return; } else { gint view_x, view_y; GdkRectangle view_rect, cell_rect; GtkTreePath * path = gtk_tree_model_get_path (model, iter); gtk_tree_view_get_cell_area (view, path, GTK_TREE_VIEW_COLUMN (self), &cell_rect); gtk_tree_path_free (path); gtk_tree_view_get_visible_rect (view, &view_rect); gtk_tree_view_convert_tree_to_bin_window_coords (view, view_rect.x, view_rect.y, &view_x, &view_y); if (!(view_x <= cell_rect.x && cell_rect.x <= view_x + view_rect.width && view_y <= cell_rect.y && cell_rect.y <= view_y + view_rect.height)) return; } } else self->loaded = g_hash_table_new_full ((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) free_object); cell_name = /*self->external_loader ? g_strconcat ("/", self->path, "/", name, NULL):*/ g_strdup (name); g_hash_table_insert (self->loaded, g_strdup (cell_name), NULL); if (!self->loader) self->loader = db_file_loader_new (self->host, self->path); if (self->tooltip_path && !self->tooltips) self->tooltips = g_hash_table_new_full ((GHashFunc) g_direct_hash, (GEqualFunc) g_direct_equal, (GDestroyNotify) NULL, (GDestroyNotify) free_tooltip_data); data = g_new (DownloadData, 1); data->self = g_object_ref (self); data->model = g_object_ref (model); data->iter = gtk_tree_iter_copy (iter); data->cell = cell; data->name = cell_name; db_file_loader_download (self->loader, cell_name, (DbFileLoaderCallbackFunc) vn_column_image_on_download, data); } } static void vn_column_image_on_model_changed (VnColumnImage * self) { if (self->loader) db_file_loader_cancel_all (self->loader); if (self->loaded) g_hash_table_destroy (self->loaded); if (self->tooltips) g_hash_table_destroy (self->tooltips); self->loaded = NULL; self->tooltips = NULL; } static void vn_column_image_set_editable (VnColumn * self, gboolean editable){} //+++++++++++++++++++++++++++++++++++++++++++++++++++ Property enum { PROP_HOST = 1 ,PROP_PATH ,PROP_TOOLTIP_PATH ,PROP_TOOLTIP_SIZE ,PROP_FILE_LOADER }; static void vn_column_image_set_property (VnColumnImage * self, guint id, const GValue * value, GParamSpec * pspec) { switch (id) { case PROP_HOST: g_free (self->host); self->host = g_value_dup_string (value); break; case PROP_PATH: g_free (self->path); self->path = g_value_dup_string (value); break; case PROP_TOOLTIP_PATH: g_free (self->tooltip_path); self->tooltip_path = g_value_dup_string (value); break; case PROP_TOOLTIP_SIZE: self->tooltip_size = g_value_get_int (value); break; case PROP_FILE_LOADER: self->loader = g_value_dup_object (value); if (self->loader) self->external_loader = TRUE; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } static void vn_column_image_get_property (VnColumnImage * self, guint id, GValue * value, GParamSpec * pspec) { switch (id) { case PROP_HOST: g_value_set_string (value, self->host); break; case PROP_PATH: g_value_set_string (value, self->path); break; case PROP_TOOLTIP_PATH: g_value_set_string (value, self->tooltip_path); break; case PROP_TOOLTIP_SIZE: g_value_set_int (value, self->tooltip_size); break; case PROP_FILE_LOADER: g_value_set_object (value, self->loader); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Class static void vn_column_image_init (VnColumnImage * self) { GtkCellRenderer * cell = gtk_cell_renderer_pixbuf_new (); VN_COLUMN_GET_CLASS (self)->set_renderer (VN_COLUMN (self), cell); self->host = NULL; self->path = NULL; self->tooltip_path = NULL; self->loader = NULL; self->external_loader = FALSE; self->loaded = NULL; self->tooltips = NULL; self->tree_view = NULL; } static void vn_column_image_finalize (VnColumnImage * self) { g_free (self->host); g_free (self->path); g_free (self->tooltip_path); if (self->loader) { db_file_loader_cancel_all (self->loader); g_object_unref (self->loader); } if (self->loaded) g_hash_table_destroy (self->loaded); if (self->tooltips) g_hash_table_destroy (self->tooltips); if (GTK_IS_TREE_VIEW (self->tree_view)) g_signal_handlers_disconnect_by_func (self->tree_view, vn_column_image_on_query_tooltip, self); G_OBJECT_CLASS (vn_column_image_parent_class)->finalize (G_OBJECT (self)); } static void vn_column_image_class_init (VnColumnImageClass * klass) { GObjectClass * k = G_OBJECT_CLASS (klass); VnColumnClass * col_k = VN_COLUMN_CLASS (klass); k->finalize = (GObjectFinalizeFunc) vn_column_image_finalize; k->set_property = (GObjectSetPropertyFunc) vn_column_image_set_property; k->get_property = (GObjectGetPropertyFunc) vn_column_image_get_property; col_k->set_value = (VnColumnSetValueFunc) vn_column_image_set_value; col_k->set_editable = (VnColumnSetEditableFunc) vn_column_image_set_editable; col_k->model_changed = (VnColumnModelChangedFunc) vn_column_image_on_model_changed; g_object_class_install_property (k, PROP_HOST, g_param_spec_string ("host" ,_("Host") ,_("The host web server name to get the images") ,NULL ,G_PARAM_CONSTRUCT | G_PARAM_READWRITE )); g_object_class_install_property (k, PROP_PATH, g_param_spec_string ("path" ,_("Path") ,_("Base path from the host where the images will be downloaded") ,NULL ,G_PARAM_CONSTRUCT | G_PARAM_READWRITE )); g_object_class_install_property (k, PROP_TOOLTIP_PATH, g_param_spec_string ("tooltip-path" ,_("Tooltip path") ,_("Prefix for the path of the images to be shown in the tooltip. " "Starting after the path of the column and appending the name " "on each cell") ,NULL ,G_PARAM_CONSTRUCT | G_PARAM_READWRITE )); g_object_class_install_property (k, PROP_TOOLTIP_SIZE, g_param_spec_int ("tooltip-size" ,_("Tooltip size") ,_("Size of the bigger side of the tooltip images, the another " "side will be scaled accordingly and smaller images won't be " "scaled") ,-1 ,G_MAXINT ,300 ,G_PARAM_CONSTRUCT | G_PARAM_READWRITE )); g_object_class_install_property (k, PROP_FILE_LOADER, g_param_spec_object ("file-loader" ,_("File loader") ,_("An optional file loader, if it's NULL the column will create one") ,DB_TYPE_FILE_LOADER ,G_PARAM_CONSTRUCT | G_PARAM_READWRITE )); }