/* Get a new thumbnail region of GtkTreeView widget in which event falls. It is an error to call this function if !in_thumbnail (widget, event). */ static GdkRegion * thumbnail_region (GtkWidget *widget, GdkEventMotion *event) { g_assert (GTK_IS_TREE_VIEW (widget)); g_assert (in_thumbnail (widget, event)); GtkTreePath *tp = thumbnail_path (widget, event); g_assert (tp != NULL); /* Rectangle of region to be returned. */ GdkRectangle tn_rect; /* If over the input image thumbnail, return the input thumbnail region, */ GtkTreeViewColumn *tc = NULL; /* Thumbnail column we are over. */ if ( in_input_thumbnail (widget, event) ) { tc = input_thumbnail_column (widget, event); } gtk_tree_view_get_cell_area (GTK_TREE_VIEW (widget), tp, tc, &tn_rect); /* Here we depend on the fact that the thumbnails are packed at the beginning of the cell horizontally, and centered in the cell vertically (FIXME: find a way to verify this with assertions). */ GdkRectangle itn_rect; /* Image thumbnail rectangle. */ /* FIXME: fix this border hackery to be precise somehow. */ itn_rect.x = tn_rect.x + 1; /* There is probably a small border so +1. */ itn_rect.y = tn_rect.y + 1; itn_rect.width = THUMB_SIZE; itn_rect.height = THUMB_SIZE; return gdk_region_rectangle (&itn_rect); }
static gboolean in_input_thumbnail (GtkWidget *widget, GdkEventMotion *event) { g_assert (GTK_IS_TREE_VIEW (widget)); gboolean result = FALSE; /* Result to be returned. */ GtkTreePath *tp = thumbnail_path (widget, event); if ( tp == NULL ) { return FALSE; /* Pointer is not over a filled in row. */ } /* Check if we are over the input image thumbnail. */ GtkTreeViewColumn *itc = input_thumbnail_column (widget, event); GdkRectangle itnc_rect; /* Input thumbnail cell rectangle. */ gtk_tree_view_get_cell_area (GTK_TREE_VIEW (widget), tp, itc, &itnc_rect); /* Here we depend on the fact that the input thumbnail is packed at the beginning of the cell horizontally, and centered in the cell vertically (FIXME: find a way to verify this with assertions). */ GdkRectangle itn_rect; /* Input thumbnail rectangle. */ /* FIXME: fix this border hackery to be precise somehow. */ itn_rect.x = itnc_rect.x + 1; /* There is probably a small border so +1. */ itn_rect.y = itnc_rect.y + 1; itn_rect.width = THUMB_SIZE; itn_rect.height = THUMB_SIZE; GdkRegion *itn_region = gdk_region_rectangle (&itn_rect); if ( gdk_region_point_in (itn_region, (int) event->x, (int) event->y) ) { result = TRUE; // g_message ("Over input thumbnail!"); } gdk_region_destroy (itn_region); return result; }
static void update_thumbnail_popup_process (GtkWidget *widget, GdkEventMotion *event) { //g_message ("Doing %s, event = %p", __func__, event); static gboolean witn = FALSE; /* "Was in thumbnail". */ static GtkTreePath *otp = NULL; static GtkTreeViewColumn *otc = NULL; static GTimer *hover_timer = NULL; if ( hover_timer == NULL ) { hover_timer = g_timer_new (); } /* Fos some crazy reason, drawing the popup image changes the result of in_thumbnail (I suspect because gdk is reusing event structures in some strange way). So memorize in_thumbnail up front. */ // Also, this assertion should pass, but doesn't... synthesizing // motion events is apparently a bit harder than I realized. // g_assert (event->type == GDK_MOTION_NOTIFY); //g_message ("event x: %lf, event y: %lf", event->x, event->y); gboolean event_in_thumbnail = in_thumbnail (widget, event); /* Hover time in milliseconds required before a popup image is displayed. */ const gint hover_time = 70; // FIXME: It would be nice to allow popup images of both the input // and output thumbnails, but it requires more hassle keeping track // of where we were, where we are now, etc., so its not going to // happen until someone asks. if ( !witn && in_input_thumbnail (widget, event) ) { witn = TRUE; otp = thumbnail_path (widget, event); g_assert (gtk_tree_path_get_depth (otp) == 1); otc = thumbnail_column (widget, event); g_timer_start (hover_timer); //g_message ("Adding timeout from !with && in_input_thumbnail"); fake_motion_signal_args_t *fmsa = g_new (fake_motion_signal_args_t, 1); fmsa->widget = widget; fmsa->event = event; fmsa->is_valid = TRUE; g_timeout_add (hover_time, (GSourceFunc) emit_fake_motion_signal, fmsa); } if ( witn && in_input_thumbnail (widget, event) ) { //g_message ("in_thumbnail: %d", in_thumbnail (widget, event)); GtkTreePath *ctp = thumbnail_path (widget, event); g_assert (gtk_tree_path_get_depth (ctp) == 1); GtkTreeViewColumn *ctc = thumbnail_column (widget, event); if ( gtk_tree_path_compare (ctp, otp) == 0 && ctc == otc ) { /* Sometimes the timeout handler seems to go off a bit before timer has measured the correct amount of time, so we add a small margin here. */ const gint slop_time = 5; const gint seconds_to_mseconds_factor = 1000; /* If we hover for this long or longer, we should show the popup. */ if ( g_timer_elapsed (hover_timer, NULL) * seconds_to_mseconds_factor >= hover_time - slop_time) { //g_message ("elapsed time: %lf\n", g_timer_elapsed (hover_timer, NULL)); //g_message ("Do popup!!!"); //g_message ("in_thumbnail: %d", in_thumbnail (widget, event)); GdkRegion *tr = thumbnail_region (widget, event); //g_message ("bpw in_thumbnail: %d", in_thumbnail (widget, event)); //g_message ("widget: %p", widget); GdkWindow *popup_window = draw_popup_image (widget, ctp, ctc, tr); //g_message ("apw in_thumbnail: %d", in_thumbnail (widget, event)); //g_message ("widget: %p", widget); /* We don't want continuous redrawing of the popup, so we disable the handler that triggers it until the popup is removed. */ if (popup_window) { guint signal_id = g_signal_lookup ("motion-notify-event", GTK_WIDGET_TYPE (widget)); gulong handler_id = g_signal_handler_find (widget, G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, signal_id, (GQuark) 0, NULL, files_list_motion_notify_event_handler, NULL); g_assert (handler_id != 0); g_signal_handler_block (widget, handler_id); /* We want to get rid of the popup window as soon as the user moves the mouse outside of the original thumbnail space. */ maybe_clear_popup_image_args.popup = popup_window; maybe_clear_popup_image_args.tree_view = GTK_TREE_VIEW (widget); if ( maybe_clear_popup_image_args.thumbnail_region != NULL ) { gdk_region_destroy (maybe_clear_popup_image_args.thumbnail_region); } //g_message ("in_thumbnail: %d", in_thumbnail (widget, event)); maybe_clear_popup_image_args.thumbnail_region = tr; g_signal_connect (widget, "motion-notify-event", G_CALLBACK (maybe_clear_popup_image), &maybe_clear_popup_image_args); //g_message ("in_thumbnail: %d", in_thumbnail (widget, event)); } } else { gtk_tree_path_free (ctp); } } else { gtk_tree_path_free (otp); otp = ctp; otc = ctc; g_timer_start (hover_timer); //g_message ("Adding timeout from 'different thumbnails'"); fake_motion_signal_args_t *fmsa = g_new (fake_motion_signal_args_t, 1); fmsa->widget = widget; fmsa->event = event; fmsa->is_valid = TRUE; g_timeout_add (hover_time, (GSourceFunc) emit_fake_motion_signal, fmsa); } } if ( witn && !event_in_thumbnail ) { //g_message ("in_thumbnail: %d", in_thumbnail (widget, event)); //g_message ("Setting witn false"); witn = FALSE; } }
/* Load image 'path' in the background and insert into pixmap_cache. * Call callback(data, path) when done (path is NULL => error). * If the image is already uptodate, or being created already, calls the * callback right away. */ void pixmap_background_thumb(const gchar *path, GFunc callback, gpointer data) { gboolean found = FALSE; MaskedPixmap* image = g_fscache_lookup_full(pixmap_cache, path, FSCACHE_LOOKUP_ONLY_NEW, &found); if (found) { dbg(0, "found"); // Thumbnail is known, or being created if (image) g_object_unref(image); callback(data, NULL); return; } dbg(0, "FIXME not found"); #if 0 pid_t child; ChildThumbnail *info; g_return_if_fail(image == NULL); GdkPixbuf* pixbuf = get_thumbnail_for(path); if (!pixbuf) { struct stat info1, info2; char *dir; dir = g_path_get_dirname(path); // If the image itself is in ~/.thumbnails, load it now (ie, don't create thumbnails for thumbnails!). if (stat(dir, &info1) != 0) { callback(data, NULL); g_free(dir); return; } g_free(dir); if (stat(make_path(home_dir, ".thumbnails/normal"), &info2) == 0 && info1.st_dev == info2.st_dev && info1.st_ino == info2.st_ino) { pixbuf = rox_pixbuf_new_from_file_at_scale(path, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL); if (!pixbuf) { g_fscache_insert(pixmap_cache, path, NULL, TRUE); callback(data, NULL); return; } } } if (pixbuf) { MaskedPixmap *image; image = masked_pixmap_new(pixbuf); gdk_pixbuf_unref(pixbuf); g_fscache_insert(pixmap_cache, path, image, TRUE); callback(data, (gchar *) path); g_object_unref(G_OBJECT(image)); return; } MIME_type* type = type_from_path(path); if (!type) type = text_plain; // Add an entry, set to NULL, so no-one else tries to load this image. g_fscache_insert(pixmap_cache, path, NULL, TRUE); gchar* thumb_prog = thumbnail_program(type); // Only attempt to load 'images' types ourselves if (thumb_prog == NULL && strcmp(type->media_type, "image") != 0) { callback(data, NULL); return; // Don't know how to handle this type } child = fork(); if (child == -1) { g_free(thumb_prog); delayed_error("fork(): %s", g_strerror(errno)); callback(data, NULL); return; } if (child == 0) { // We are the child process. (We are sloppy with freeing memory, but since we go away very quickly, that's ok.) if (thumb_prog) { DirItem *item; item = diritem_new(g_basename(thumb_prog)); diritem_restat(thumb_prog, item, NULL); if (item->flags & ITEM_FLAG_APPDIR) thumb_prog = g_strconcat(thumb_prog, "/AppRun", NULL); execl(thumb_prog, thumb_prog, path, thumbnail_path(path), g_strdup_printf("%d", PIXMAP_THUMB_SIZE), NULL); _exit(1); } child_create_thumbnail(path); _exit(0); } g_free(thumb_prog); info = g_new(ChildThumbnail, 1); info->path = g_strdup(path); info->callback = callback; info->data = data; on_child_death(child, (CallbackFn) thumbnail_child_done, info); #endif }