/* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include "apptypes.h" #include "appenv.h" #include "cursorutil.h" #include "dialog_handler.h" #include "info_dialog.h" #include "info_window.h" #include "gdisplay.h" #include "gimpcontext.h" #include "gimage.h" #include "gimppreviewcache.h" #include "gimpset.h" #include "gimprc.h" #include "gimpui.h" #include "gximage.h" #include "nav_window.h" #include "scroll.h" #include "scale.h" #include "pdb/procedural_db.h" #include "libgimp/gimpunit.h" #include "libgimp/gimpintl.h" #include "pixmaps/zoom_in.xpm" #include "pixmaps/zoom_out.xpm" #define MAX_BUF 256 #define PREVIEW_MASK GDK_EXPOSURE_MASK | \ GDK_BUTTON_PRESS_MASK | \ GDK_KEY_PRESS_MASK | \ GDK_KEY_RELEASE_MASK | \ GDK_POINTER_MOTION_MASK /* Navigation preview sizes */ #define NAV_PREVIEW_WIDTH 112 #define NAV_PREVIEW_HEIGHT 112 #define BORDER_PEN_WIDTH 3 #define MAX_SCALE_BUF 20 typedef enum { NAV_WINDOW, NAV_POPUP } NavWinType; /* Timeout before preview is updated */ #define PREVIEW_UPDATE_TIMEOUT 1100 typedef struct _NavWinData NavWinData; struct _NavWinData { NavWinType ptype; gboolean showingPreview; gboolean installedDirtyTimer; InfoDialog *info_win; GtkWidget *previewBox; GtkWidget *previewAlign; GtkWidget *zoom_label; GtkObject *zoom_adjustment; GtkWidget *preview; GDisplay *gdisp; /* I'm not happy 'bout this one */ GdkGC *gc; gint dispx; /* x pos of top left corner of display area */ gint dispy; /* y pos of top left corner of display area */ gint dispwidth; /* width of display area */ gint dispheight; /* height of display area */ gint sig_hand_id; gboolean sq_grabbed; /* In the process of moving the preview square */ gint motion_offsetx; gint motion_offsety; gint pwidth; /* real preview width */ gint pheight; /* real preview height */ gint imagewidth; /* width of the real image */ gint imageheight; /* height of real image */ gdouble ratio; gboolean block_window_marker; /* Block redraws of window marker */ gint nav_preview_width; gint nav_preview_height; gboolean block_adj_sig; gboolean frozen; /* Has the dialog been frozen ? */ guint timer_id; }; static gint nav_window_preview_events (GtkWidget *widget, GdkEvent *event, gpointer data); static gint nav_window_expose_events (GtkWidget *widget, GdkEvent *event, gpointer data); static void nav_window_update_preview (NavWinData *iwd); static void nav_window_update_preview_blank (NavWinData *iwd); static void destroy_preview_widget (NavWinData *iwd); static void create_preview_widget (NavWinData *iwd); static void nav_window_draw_sqr (NavWinData *iwd, gboolean undraw, gint x, gint y, gint w, gint h); static void set_size_data (NavWinData *iwd); static gint nav_preview_update_do_timer (NavWinData *iwd); static void nav_window_destroy_callback (GtkWidget *widget, gpointer client_data) { InfoDialog *info_win; info_win = (InfoDialog *)client_data; dialog_unregister(info_win->shell); } static void nav_window_close_callback (GtkWidget *widget, gpointer client_data) { InfoDialog *info_win; NavWinData *iwd; info_win = (InfoDialog *)client_data; iwd = (NavWinData *)info_win->user_data; iwd->showingPreview = FALSE; info_dialog_popdown ((InfoDialog *) client_data); } static void nav_window_disp_area (NavWinData *iwd, GDisplay *gdisp) { GimpImage *gimage; gint newwidth; gint newheight; gdouble xratio; gdouble yratio; /* Screen res ratio */ gboolean need_update = FALSE; /* Calculate preview size */ gimage = gdisp->gimage; xratio = SCALEFACTOR_X (gdisp); yratio = SCALEFACTOR_Y (gdisp); iwd->dispx = gdisp->offset_x * iwd->ratio / xratio + 0.5; iwd->dispy = gdisp->offset_y * iwd->ratio/yratio + 0.5; iwd->dispwidth = (gdisp->disp_width * iwd->ratio) / xratio + 0.5; iwd->dispheight = (gdisp->disp_height * iwd->ratio) / yratio + 0.5; newwidth = gimage->width; newheight = gimage->height; if (!gdisp->dot_for_dot) { newwidth = (newwidth * gdisp->gimage->yresolution) / gdisp->gimage->xresolution; iwd->dispx = ((gdisp->offset_x * gdisp->gimage->yresolution * iwd->ratio) / (gdisp->gimage->xresolution * xratio)) + 0.5; /*here*/ iwd->dispwidth = ((gdisp->disp_width * gdisp->gimage->yresolution * iwd->ratio) / (gdisp->gimage->xresolution * xratio)) + 0.5; /*here*/ } if ((iwd->imagewidth > 0 && newwidth != iwd->imagewidth) || (iwd->imageheight > 0 && newheight != iwd->imageheight)) { /* Must change the preview size */ if (iwd->ptype != NAV_POPUP) { gtk_window_set_focus (GTK_WINDOW (iwd->info_win->shell), NULL); } destroy_preview_widget (iwd); create_preview_widget (iwd); need_update = TRUE; } iwd->imagewidth = newwidth; iwd->imageheight = newheight; /* Normalise */ iwd->dispwidth = MAX (iwd->dispwidth, 2); iwd->dispheight = MAX (iwd->dispheight, 2); iwd->dispwidth = MIN (iwd->dispwidth, iwd->pwidth /* - BORDER_PEN_WIDTH */); iwd->dispheight = MIN (iwd->dispheight, iwd->pheight /* - BORDER_PEN_WIDTH */); if (need_update) { gtk_widget_hide (iwd->previewAlign); nav_window_update_preview_blank (iwd); gtk_widget_show (iwd->preview); gtk_widget_draw (iwd->preview, NULL); gtk_widget_show (iwd->previewAlign); nav_window_draw_sqr (iwd, FALSE, iwd->dispx, iwd->dispy, iwd->dispwidth, iwd->dispheight); if (iwd->ptype != NAV_POPUP) { gtk_window_set_focus (GTK_WINDOW (iwd->info_win->shell), iwd->preview); iwd->timer_id = gtk_timeout_add (PREVIEW_UPDATE_TIMEOUT, (GtkFunction) nav_preview_update_do_timer, (gpointer) iwd); } else { nav_window_update_preview (iwd); gtk_widget_draw (iwd->preview, NULL); } } } static void nav_window_draw_sqr (NavWinData *iwd, gboolean undraw, gint x, gint y, gint w, gint h) { gdk_gc_set_function (iwd->gc, GDK_INVERT); if (undraw) { if (iwd->dispx != 0 || iwd->dispy != 0 || iwd->pwidth != iwd->dispwidth || iwd->pheight != iwd->dispheight) { /* first undraw from last co-ords */ gdk_draw_rectangle (iwd->preview->window, iwd->gc, FALSE, iwd->dispx, iwd->dispy, iwd->dispwidth - BORDER_PEN_WIDTH + 1, iwd->dispheight - BORDER_PEN_WIDTH + 1); } } if (x != 0 || y != 0 || w != iwd->pwidth || h != iwd->pheight) { gdk_draw_rectangle (iwd->preview->window, iwd->gc, FALSE, x, y, w - BORDER_PEN_WIDTH + 1, h - BORDER_PEN_WIDTH + 1); } iwd->dispx = x; iwd->dispy = y; iwd->dispwidth = w; iwd->dispheight = h; } static void destroy_preview_widget (NavWinData *iwd) { if(!iwd->preview) return; gtk_widget_hide(iwd->previewBox); gtk_widget_destroy(iwd->previewBox); iwd->previewBox = NULL; iwd->preview = NULL; } static void set_size_data (NavWinData *iwd) { gint sel_width; gint sel_height; gint pwidth; gint pheight; GDisplay *gdisp; GimpImage *gimage; gdisp = iwd->gdisp; gimage = gdisp->gimage; sel_width = gimage->width; sel_height = gimage->height; if (!gdisp->dot_for_dot) sel_width = (sel_width * gdisp->gimage->yresolution) / gdisp->gimage->xresolution; if (sel_width > sel_height) { pwidth = iwd->nav_preview_width; /* pheight = sel_height * pwidth / sel_width; */ /*iwd->ratio = MIN (1.0, (gdouble) pwidth / (gdouble) sel_width);*/ iwd->ratio = (gdouble) pwidth / (gdouble) sel_width; } else { pheight = iwd->nav_preview_height; /* pwidth = sel_width * pheight / sel_height; */ /*iwd->ratio = MIN (1.0, (gdouble) pheight / (gdouble) sel_height);*/ iwd->ratio = (gdouble) pheight / (gdouble) sel_height; } pwidth = sel_width * iwd->ratio + 0.5; pheight = sel_height * iwd->ratio + 0.5; iwd->pwidth = pwidth; iwd->pheight = pheight; } static void create_preview_widget (NavWinData *iwd) { GtkWidget *hbox; GtkWidget *image; GtkWidget *frame; hbox = gtk_hbox_new (FALSE,0); iwd->previewBox = hbox; gtk_widget_show (hbox); gtk_container_add (GTK_CONTAINER (iwd->previewAlign), hbox); image = gtk_preview_new (GTK_PREVIEW_COLOR); gtk_widget_set_events (GTK_WIDGET(image), PREVIEW_MASK); iwd->preview = image; gtk_widget_show (image); gtk_preview_set_dither (GTK_PREVIEW (image), GDK_RGB_DITHER_MAX); set_size_data (iwd); gtk_preview_size (GTK_PREVIEW (iwd->preview), iwd->pwidth, iwd->pheight); gtk_widget_set_usize (iwd->preview, iwd->pwidth, iwd->pheight); frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type (GTK_FRAME(frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (iwd->previewBox), frame); gtk_container_add (GTK_CONTAINER (frame), iwd->preview); gtk_widget_show (frame); iwd->sig_hand_id = gtk_signal_connect_after (GTK_OBJECT (image), "expose_event", (GtkSignalFunc) nav_window_expose_events, iwd); gtk_signal_connect (GTK_OBJECT (image), "event", (GtkSignalFunc) nav_window_preview_events, iwd); /* gtk_signal_connect (GTK_OBJECT (image), "size_allocate", */ /* (GtkSignalFunc) nav_window_preview_resized, */ /* iwd); */ GTK_WIDGET_SET_FLAGS (image, GTK_CAN_FOCUS); } static void update_real_view (NavWinData *iwd, gint tx, gint ty) { GDisplay *gdisp; gdouble xratio; gdouble yratio; gint xoffset; gint yoffset; gint xpnt; gint ypnt; gdisp = iwd->gdisp; xratio = SCALEFACTOR_X (gdisp); yratio = SCALEFACTOR_Y (gdisp); if ((tx + iwd->dispwidth) >= iwd->pwidth) { tx = iwd->pwidth; /* Actually should be less... * but bound check will save us. */ } xpnt = (gint) (((gdouble) (tx) * xratio) / iwd->ratio + 0.5); if ((ty + iwd->dispheight) >= iwd->pheight) ty = iwd->pheight; /* Same comment as for xpnt above. */ ypnt = (gint) (((gdouble) (ty) * yratio) / iwd->ratio + 0.5); if (!gdisp->dot_for_dot) /* here */ xpnt = ((gdouble) xpnt * gdisp->gimage->xresolution) / gdisp->gimage->yresolution + 0.5; xoffset = xpnt - gdisp->offset_x; yoffset = ypnt - gdisp->offset_y; iwd->block_window_marker = TRUE; scroll_display (iwd->gdisp, xoffset, yoffset); iwd->block_window_marker = FALSE; } static void nav_window_update_preview (NavWinData *iwd) { GDisplay *gdisp; TempBuf *preview_buf; TempBuf *preview_buf_ptr; TempBuf *preview_buf_notdot = NULL; guchar *src, *buf, *dest; gint x, y; gint pwidth, pheight; GimpImage *gimage; gdouble r, g, b, a, chk; gint xoff = 0; gint yoff = 0; gimp_add_busy_cursors (); gdisp = iwd->gdisp; gimage = gdisp->gimage; /* Min size is 2 */ pwidth = iwd->pwidth; pheight = iwd->pheight; /* we need a large normal preview which we will cut down later. * gimp_image_construct_composite_preview() can't cope with * dot_for_dot not been set. */ if (!gdisp->dot_for_dot) /* ALT */ { gint sel_width = gimage->width; gint sel_height = gimage->height; gdouble tratio; if (sel_width > sel_height) tratio = (gdouble) iwd->nav_preview_width / ((gdouble) sel_width); else tratio = (gdouble) iwd->nav_preview_height / ((gdouble) sel_height); pwidth = sel_width * tratio + 0.5; pheight = sel_height * tratio + 0.5; } if (iwd->ratio > 1.0) /* Preview is scaling up! */ { TempBuf *tmp; tmp = gimp_image_construct_composite_preview (gimage, gimage->width, gimage->height); preview_buf = gimp_preview_scale (tmp, pwidth, pheight); temp_buf_free (tmp); } else { preview_buf = gimp_image_construct_composite_preview (gimage, MAX (pwidth, 2), MAX (pheight, 2)); } /* reset & get new preview */ /* FIXME: should use gimp_preview_scale() */ if (!gdisp->dot_for_dot) { gint loop1, loop2; gdouble x_ratio, y_ratio; guchar *src_data; guchar *dest_data; preview_buf_notdot = temp_buf_new (iwd->pwidth, iwd->pheight, preview_buf->bytes, 0, 0, NULL); x_ratio = (gdouble) pwidth / (gdouble) iwd->pwidth; y_ratio = (gdouble) pheight / (gdouble) iwd->pheight; src_data = temp_buf_data (preview_buf); dest_data = temp_buf_data (preview_buf_notdot); for (loop1 = 0 ; loop1 < iwd->pheight ; loop1++) for (loop2 = 0 ; loop2 < iwd->pwidth ; loop2++) { gint i; guchar *src_pixel; guchar *dest_pixel; src_pixel = src_data + ((gint) (loop2 * x_ratio)) * preview_buf->bytes + ((gint) (loop1 * y_ratio)) * pwidth * preview_buf->bytes; dest_pixel = dest_data + (loop2 + loop1 * iwd->pwidth) * preview_buf->bytes; for (i = 0 ; i < preview_buf->bytes; i++) *dest_pixel++ = *src_pixel++; } pwidth = iwd->pwidth; pheight = iwd->pheight; src = temp_buf_data (preview_buf_notdot); preview_buf_ptr = preview_buf_notdot; } else { src = temp_buf_data (preview_buf); preview_buf_ptr = preview_buf; } buf = g_new (gchar, preview_buf_ptr->width * 3); for (y = 0; y < preview_buf_ptr->height ; y++) { dest = buf; switch (preview_buf_ptr->bytes) { case 4: for (x = 0; x < preview_buf_ptr->width; x++) { r = ((gdouble) (*(src++))) / 255.0; g = ((gdouble) (*(src++))) / 255.0; b = ((gdouble) (*(src++))) / 255.0; a = ((gdouble) (*(src++))) / 255.0; chk = ((gdouble) ((( (x^y) & 4 ) << 4) | 128)) / 255.0; *(dest++) = (guchar) ((chk + (r - chk) * a) * 255.0); *(dest++) = (guchar) ((chk + (g - chk) * a) * 255.0); *(dest++) = (guchar) ((chk + (b - chk) * a) * 255.0); } break; case 2: for (x = 0; x < preview_buf_ptr->width; x++) { r = ((gdouble) (*(src++))) / 255.0; a = ((gdouble) (*(src++))) / 255.0; chk = ((gdouble) ((( (x^y) & 4 ) << 4) | 128)) / 255.0; *(dest++) = (guchar) ((chk + (r - chk) * a) * 255.0); *(dest++) = (guchar) ((chk + (r - chk) * a) * 255.0); *(dest++) = (guchar) ((chk + (r - chk) * a) * 255.0); } break; default: g_warning ("UNKNOWN TempBuf bpp in nav_window_update_preview()"); } gtk_preview_draw_row (GTK_PREVIEW (iwd->preview), (guchar *) buf, xoff, yoff + y, preview_buf_ptr->width); } g_free (buf); temp_buf_free (preview_buf); if (preview_buf_notdot) temp_buf_free (preview_buf_notdot); gimp_remove_busy_cursors (NULL); } static void nav_window_update_preview_blank (NavWinData *iwd) { guchar *buf; guchar *dest; gint x, y; gdouble chk; #if 0 GimpImage *gimage; GDisplay *gdisp; gdisp = iwd->gdisp; gimage = gdisp->gimage; #endif /* 0 */ buf = g_new (gchar, iwd->pwidth * 3); for (y = 0; y < iwd->pheight ; y++) { dest = buf; for (x = 0; x < iwd->pwidth; x++) { chk = ((gdouble) ((( (x^y) & 4 ) << 4) | 128))/255.0; chk *= 128.0; *(dest++) = (guchar) chk; *(dest++) = (guchar) chk; *(dest++) = (guchar) chk; } gtk_preview_draw_row (GTK_PREVIEW (iwd->preview), (guchar *) buf, 0, y, iwd->pwidth); } g_free (buf); gdk_flush (); } static gint inside_preview_square (NavWinData *iwd, gint x, gint y) { if (x > iwd->dispx && x < (iwd->dispx + iwd->dispwidth) && y > iwd->dispy && y < iwd->dispy + iwd->dispheight) return TRUE; return FALSE; } static void update_zoom_label (NavWinData *iwd) { gchar scale_str[MAX_SCALE_BUF]; if(!iwd->zoom_label) return; /* Update the zoom scale string */ g_snprintf (scale_str, MAX_SCALE_BUF, "%d:%d", SCALEDEST (iwd->gdisp), SCALESRC (iwd->gdisp)); gtk_label_set_text (GTK_LABEL (iwd->zoom_label), scale_str); } static void update_zoom_adjustment (NavWinData *iwd) { GtkAdjustment *adj; gdouble f; gdouble val; if (! iwd->zoom_adjustment) return; adj = GTK_ADJUSTMENT (iwd->zoom_adjustment); f = ((gdouble) SCALEDEST (iwd->gdisp)) / ((gdouble) SCALESRC (iwd->gdisp)); if (f < 1.0) { val = -1.0 / f; } else { val = f; } if (abs ((gint) adj->value) != (gint)(val - 1) && iwd->block_adj_sig != TRUE) { adj->value = val; gtk_signal_emit_by_name (GTK_OBJECT (iwd->zoom_adjustment), "changed"); } } static void move_to_point (NavWinData *iwd, gint tx, gint ty) { tx = CLAMP (tx, 0, iwd->pwidth); ty = CLAMP (ty, 0, iwd->pheight); if ((tx + iwd->dispwidth) >= iwd->pwidth) { tx = iwd->pwidth - iwd->dispwidth; } if ((ty + iwd->dispheight) >= iwd->pheight) { ty = iwd->pheight - iwd->dispheight; } if (iwd->dispx == tx && iwd->dispy == ty) return; /* Update the real display */ update_real_view (iwd, tx, ty); nav_window_draw_sqr (iwd, TRUE, tx, ty, iwd->dispwidth, iwd->dispheight); } static void nav_window_grab_pointer (NavWinData *iwd, GtkWidget *widget) { GdkCursor *cursor; iwd->sq_grabbed = TRUE; gtk_grab_add (widget); cursor = gdk_cursor_new (GDK_CROSSHAIR); gdk_pointer_grab (widget->window, TRUE, GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_EXTENSION_EVENTS_ALL, widget->window, cursor, 0); gdk_cursor_destroy (cursor); } static gint nav_window_preview_events (GtkWidget *widget, GdkEvent *event, gpointer data) { NavWinData *iwd; GDisplay *gdisp; GdkEventButton *bevent; GdkEventMotion *mevent; GdkEventKey *kevent; GdkModifierType mask; gint tx = 0; gint ty = 0; gint mx; gint my; gboolean arrowKey = FALSE; iwd = (NavWinData *)data; if(!iwd || iwd->frozen == TRUE) return FALSE; gdisp = iwd->gdisp; switch (event->type) { case GDK_EXPOSE: break; case GDK_MAP: if (iwd->ptype == NAV_POPUP) { nav_window_update_preview (iwd); /* First time displayed.... get the pointer! */ nav_window_grab_pointer (iwd, iwd->preview); } else { nav_window_update_preview_blank (iwd); iwd->timer_id = gtk_timeout_add (PREVIEW_UPDATE_TIMEOUT, (GtkFunction) nav_preview_update_do_timer, (gpointer) iwd); } break; case GDK_BUTTON_PRESS: bevent = (GdkEventButton *) event; tx = bevent->x; ty = bevent->y; /* Must start the move */ switch (bevent->button) { case 1: if (! inside_preview_square (iwd, tx, ty)) { /* Direct scroll to the location */ /* view scrolled to the center or nearest possible point */ tx -= iwd->dispwidth / 2; ty -= iwd->dispheight / 2; if(tx < 0) tx = 0; if((tx + iwd->dispwidth) > iwd->pwidth) tx = iwd->pwidth - iwd->dispwidth; if(ty < 0) ty = 0; if((ty + iwd->dispheight) > iwd->pheight) ty = iwd->pheight - iwd->dispheight; update_real_view(iwd,tx,ty); nav_window_draw_sqr (iwd, TRUE, tx, ty, iwd->dispwidth, iwd->dispheight); iwd->motion_offsetx = iwd->dispwidth / 2; iwd->motion_offsety = iwd->dispheight / 2; } else { iwd->motion_offsetx = tx - iwd->dispx; iwd->motion_offsety = ty - iwd->dispy; } nav_window_grab_pointer (iwd, widget); break; /* wheelmouse support */ case 4: if (bevent->state & GDK_SHIFT_MASK) { change_scale (gdisp, ZOOMIN); } else { GtkAdjustment *adj = (bevent->state & GDK_CONTROL_MASK) ? gdisp->hsbdata : gdisp->vsbdata; gfloat new_value = adj->value - adj->page_increment / 2; new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size); gtk_adjustment_set_value (adj, new_value); } break; case 5: if (bevent->state & GDK_SHIFT_MASK) { change_scale (gdisp, ZOOMOUT); } else { GtkAdjustment *adj = (bevent->state & GDK_CONTROL_MASK) ? gdisp->hsbdata : gdisp->vsbdata; gfloat new_value = adj->value + adj->page_increment / 2; new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size); gtk_adjustment_set_value (adj, new_value); } break; default: break; } break; case GDK_BUTTON_RELEASE: bevent = (GdkEventButton *) event; tx = bevent->x; ty = bevent->y; switch (bevent->button) { case 1: iwd->sq_grabbed = FALSE; gtk_grab_remove (widget); gdk_pointer_ungrab (0); if(iwd->ptype == NAV_POPUP) { gtk_widget_hide (gdisp->nav_popup); } gdk_flush (); break; default: break; } break; case GDK_KEY_PRESS: /* hack for the update preview... needs to be fixed */ kevent = (GdkEventKey *) event; switch (kevent->keyval) { case GDK_space: gdk_window_raise(gdisp->shell->window); break; case GDK_Up: arrowKey = TRUE; tx = iwd->dispx; ty = iwd->dispy - 1; break; case GDK_Left: arrowKey = TRUE; tx = iwd->dispx - 1; ty = iwd->dispy; break; case GDK_Right: arrowKey = TRUE; tx = iwd->dispx + 1; ty = iwd->dispy; break; case GDK_Down: arrowKey = TRUE; tx = iwd->dispx; ty = iwd->dispy + 1; break; case GDK_equal: change_scale (gdisp, ZOOMIN); break; case GDK_minus: change_scale (gdisp, ZOOMOUT); break; default: break; } if (arrowKey) { move_to_point (iwd, tx, ty); return TRUE; } break; case GDK_MOTION_NOTIFY: mevent = (GdkEventMotion *) event; if (!iwd->sq_grabbed) break; gdk_window_get_pointer (widget->window, &mx, &my, &mask); tx = mx; ty = my; tx = tx - iwd->motion_offsetx; ty = ty - iwd->motion_offsety; move_to_point (iwd, tx, ty); break; default: break; } return FALSE; } static gint nav_window_expose_events (GtkWidget *widget, GdkEvent *event, gpointer data) { NavWinData *iwd; GDisplay *gdisp; iwd = (NavWinData *) data; if(!iwd || iwd->frozen == TRUE) return FALSE; gdisp = iwd->gdisp; switch (event->type) { case GDK_EXPOSE: /* we must have the grab if in nav_popup*/ gtk_signal_handler_block (GTK_OBJECT(widget), iwd->sig_hand_id); gtk_widget_draw (iwd->preview, NULL); gtk_signal_handler_unblock (GTK_OBJECT(widget), iwd->sig_hand_id); nav_window_draw_sqr (iwd,FALSE, iwd->dispx, iwd->dispy, iwd->dispwidth, iwd->dispheight); if (! gtk_grab_get_current() && iwd->ptype == NAV_POPUP) nav_window_grab_pointer (iwd, iwd->preview); break; default: break; } return FALSE; } static gint nav_preview_update_do (NavWinData *iwd) { /* If the gdisp has gone then don't do anything in this timer */ if (!iwd->gdisp) return FALSE; nav_window_update_preview (iwd); nav_window_disp_area (iwd, iwd->gdisp); gtk_widget_queue_draw (iwd->preview); iwd->installedDirtyTimer = FALSE; return FALSE; } static gint nav_preview_update_do_timer (NavWinData *iwd) { iwd->timer_id = 0; gtk_idle_add ((GtkFunction) nav_preview_update_do, (gpointer) iwd); return FALSE; } static void nav_image_need_update (GtkObject *obj, gpointer client_data) { NavWinData *iwd; iwd = (NavWinData *)client_data; if(!iwd || !iwd->showingPreview || iwd->installedDirtyTimer == TRUE || iwd->frozen == TRUE) return; iwd->installedDirtyTimer = TRUE; /* Update preview at a less busy time */ nav_window_update_preview_blank (iwd); gtk_widget_draw (iwd->preview, NULL); iwd->timer_id = gtk_timeout_add (PREVIEW_UPDATE_TIMEOUT, (GtkFunction) nav_preview_update_do_timer, (gpointer) iwd); } static void navwindow_zoomin (GtkWidget *widget, gpointer data) { NavWinData *iwd; iwd = (NavWinData *) data; if(!iwd || iwd->frozen == TRUE) return; change_scale (iwd->gdisp, ZOOMIN); } static void navwindow_zoomout (GtkWidget *widget, gpointer data) { NavWinData *iwd; iwd = (NavWinData *)data; if (!iwd || iwd->frozen == TRUE) return; change_scale (iwd->gdisp, ZOOMOUT); } static void zoom_adj_changed (GtkAdjustment *adj, gpointer data) { NavWinData *iwd; gint scalesrc; gint scaledest; iwd = (NavWinData *)data; if (!iwd || iwd->frozen == TRUE) return; if (adj->value < 0.0) { scalesrc = abs ((gint) adj->value - 1); scaledest = 1; } else { scaledest = abs ((gint) adj->value + 1); scalesrc = 1; } iwd->block_adj_sig = TRUE; change_scale (iwd->gdisp, (scaledest * 100) + scalesrc); iwd->block_adj_sig = FALSE; } static GtkWidget * nav_create_button_area (InfoDialog *info_win) { GtkWidget *hbox1; GtkWidget *vbox1; GtkWidget *button; GtkWidget *hscale1; GtkWidget *label1; GtkObject *adjustment; NavWinData *iwd; gchar scale_str[MAX_SCALE_BUF]; iwd = (NavWinData *) info_win->user_data; hbox1 = gtk_hbox_new (FALSE, 0); gtk_widget_show (hbox1); button = gimp_pixmap_button_new (zoom_out_xpm, NULL); GTK_WIDGET_UNSET_FLAGS (button, GTK_RECEIVES_DEFAULT); gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (navwindow_zoomout), (gpointer) info_win->user_data); gtk_box_pack_start (GTK_BOX (hbox1), button, FALSE, FALSE, 0); gtk_widget_show (button); vbox1 = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox1); gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0); /* user zoom ratio */ g_snprintf (scale_str, MAX_SCALE_BUF, "%d:%d", SCALEDEST (iwd->gdisp), SCALESRC (iwd->gdisp)); label1 = gtk_label_new (scale_str); gtk_widget_show (label1); iwd->zoom_label = label1; gtk_box_pack_start (GTK_BOX (hbox1), label1, TRUE, TRUE, 0); adjustment = gtk_adjustment_new (0.0, -15.0, 16.0, 1.0, 1.0, 1.0); hscale1 = gtk_hscale_new (GTK_ADJUSTMENT (adjustment)); gtk_scale_set_digits (GTK_SCALE (hscale1), 0); iwd->zoom_adjustment = adjustment; gtk_widget_show (hscale1); gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", GTK_SIGNAL_FUNC (zoom_adj_changed), iwd); gtk_box_pack_start (GTK_BOX (vbox1), hscale1, TRUE, TRUE, 0); gtk_scale_set_draw_value (GTK_SCALE (hscale1), FALSE); button = gimp_pixmap_button_new (zoom_in_xpm, NULL); GTK_WIDGET_UNSET_FLAGS (button, GTK_RECEIVES_DEFAULT); gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (navwindow_zoomin), (gpointer) info_win->user_data); gtk_box_pack_start (GTK_BOX (hbox1), button, FALSE, FALSE, 0); gtk_widget_show (button); return vbox1; } static GtkWidget * create_preview_containers (NavWinData *iwd, GtkWidget *shell) { GtkWidget *hbox1; GtkWidget *vbox1; GtkWidget *alignment; hbox1 = gtk_hbox_new (FALSE, 0); gtk_widget_show (hbox1); vbox1 = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox1); gtk_widget_realize (shell); /* need gc to draw the preview sqr with */ iwd->gc = gdk_gc_new (shell->window); gdk_gc_set_function (iwd->gc, GDK_INVERT); gdk_gc_set_line_attributes (iwd->gc, BORDER_PEN_WIDTH, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_ROUND); alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); iwd->previewAlign = alignment; gtk_widget_show (alignment); /* Create the preview in which to draw the thumbnail image */ create_preview_widget (iwd); gtk_box_pack_start (GTK_BOX (hbox1), alignment, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0); return vbox1; } static GtkWidget * info_window_image_preview_new (InfoDialog *info_win) { GtkWidget *button_area; GtkWidget *vbox1; NavWinData *iwd; iwd = (NavWinData *) info_win->user_data; vbox1 = create_preview_containers (iwd, info_win->shell); /* This one has (may) have the buttons in */ button_area = nav_create_button_area (info_win); gtk_box_pack_start (GTK_BOX (vbox1), button_area, TRUE, TRUE, 0); return vbox1; } NavWinData * create_dummy_iwd (GDisplay *gdisp, NavWinType ptype) { NavWinData *iwd; iwd = g_new (NavWinData, 1); iwd->ptype = ptype; iwd->info_win = NULL; iwd->showingPreview = TRUE; iwd->installedDirtyTimer = FALSE; iwd->preview = NULL; iwd->zoom_label = NULL; iwd->zoom_adjustment = NULL; iwd->gdisp = gdisp; iwd->dispx = -1; iwd->dispy = -1; iwd->dispwidth = -1; iwd->dispheight = -1; iwd->imagewidth = -1; iwd->imageheight = -1; iwd->sq_grabbed = FALSE; iwd->ratio = 1.0; iwd->block_window_marker = FALSE; iwd->nav_preview_width = (nav_preview_size < 0 || nav_preview_size > 256) ? NAV_PREVIEW_WIDTH : nav_preview_size; iwd->nav_preview_height = (nav_preview_size < 0 || nav_preview_size > 256) ? NAV_PREVIEW_HEIGHT : nav_preview_size; iwd->block_adj_sig = FALSE; iwd->frozen = FALSE; iwd->timer_id = 0; return(iwd); } /* Create a version of the nav window that follows the active * image. */ static InfoDialog *nav_window_auto = NULL; static gchar * nav_window_title (GDisplay *gdisp) { gchar *title; gchar *title_buf; title = g_basename (gimp_image_filename (gdisp->gimage)); /* create the info dialog */ title_buf = g_strdup_printf (_("Navigation: %s-%d.%d"), title, pdb_image_to_id (gdisp->gimage), gdisp->instance); return title_buf; } static void nav_window_change_display (GimpContext *context, /* NOT USED */ GDisplay *newdisp, gpointer data /* Not used */) { GDisplay *gdisp = newdisp; GDisplay *old_gdisp; GimpImage *gimage; NavWinData *iwd; gchar *title_buf; iwd = (NavWinData *) nav_window_auto->user_data; old_gdisp = iwd->gdisp; if (!nav_window_auto || gdisp == old_gdisp || !gdisp) { return; } gtk_widget_set_sensitive (nav_window_auto->vbox, TRUE); iwd->frozen = FALSE; title_buf = nav_window_title (gdisp); gtk_window_set_title (GTK_WINDOW (nav_window_auto->shell), title_buf); g_free (title_buf); gimage = gdisp->gimage; if (gimage && gimp_set_have (image_context, gimage)) { iwd->gdisp = gdisp; /* Update preview to new display */ nav_window_preview_resized (nav_window_auto); /* Tie into the dirty signal so we can update the preview */ /* provided we haven't already */ if (!gtk_object_get_data (GTK_OBJECT (gdisp->gimage), "nav_handlers_installed")) { gtk_signal_connect_after (GTK_OBJECT (gdisp->gimage), "dirty", GTK_SIGNAL_FUNC (nav_image_need_update), iwd); /* Also clean signal so undos get caught as well..*/ gtk_signal_connect_after (GTK_OBJECT (gdisp->gimage), "clean", GTK_SIGNAL_FUNC (nav_image_need_update), iwd); gtk_object_set_data (GTK_OBJECT (gdisp->gimage), "nav_handlers_installed",iwd); } } } void nav_window_follow_auto (void) { GDisplay *gdisp; NavWinData *iwd; gdisp = gdisplay_active (); if (!gdisp) return; if (!nav_window_auto) { nav_window_auto = nav_window_create (gdisp); gtk_signal_connect (GTK_OBJECT (gimp_context_get_user ()), "display_changed", GTK_SIGNAL_FUNC (nav_window_change_display), NULL); } nav_dialog_popup (nav_window_auto); iwd = (NavWinData *) nav_window_auto->user_data; gtk_widget_set_sensitive (nav_window_auto->vbox, TRUE); iwd->frozen = FALSE; } InfoDialog * nav_window_create (GDisplay *gdisp) { InfoDialog *info_win; NavWinData *iwd; GtkWidget *container; gchar *title_buf; GimpImageBaseType type; type = gimp_image_base_type (gdisp->gimage); title_buf = nav_window_title (gdisp); info_win = info_dialog_new (title_buf, gimp_standard_help_func, "dialogs/navigation_window.html"); g_free (title_buf); dialog_register (info_win->shell); /* create the action area */ gimp_dialog_create_action_area (GTK_DIALOG (info_win->shell), _("Close"), nav_window_close_callback, info_win, NULL, NULL, TRUE, FALSE, NULL); gtk_signal_connect (GTK_OBJECT (info_win->shell), "destroy", (GtkSignalFunc) nav_window_destroy_callback, info_win); iwd = create_dummy_iwd (gdisp, NAV_WINDOW); info_win->user_data = iwd; iwd->info_win = info_win; /* Add preview */ container = info_window_image_preview_new (info_win); gtk_table_attach_defaults (GTK_TABLE (info_win->info_table), container, 0, 2, 0, 1); /* Tie into the dirty signal so we can update the preview */ gtk_signal_connect_after (GTK_OBJECT (gdisp->gimage), "dirty", GTK_SIGNAL_FUNC(nav_image_need_update),iwd); /* Also clean signal so undos get caught as well..*/ gtk_signal_connect_after (GTK_OBJECT (gdisp->gimage), "clean", GTK_SIGNAL_FUNC(nav_image_need_update),iwd); /* Mark the image as having the handlers installed... */ gtk_object_set_data (GTK_OBJECT (gdisp->gimage), "nav_handlers_installed", iwd); return info_win; } void nav_window_update_window_marker (InfoDialog *info_win) { NavWinData *iwd; /* So this functions works both ways.. * it will come in here with info_win == null * if the auto mode is on... */ if (!info_win && nav_window_auto) { iwd = (NavWinData *) nav_window_auto->user_data; if(iwd->frozen) return; nav_window_update_window_marker (nav_window_auto); return; } if (!info_win) return; iwd = (NavWinData *) info_win->user_data; if (!iwd || iwd->showingPreview == FALSE || iwd->block_window_marker == TRUE) return; update_zoom_label (iwd); update_zoom_adjustment (iwd); /* Undraw old size */ nav_window_draw_sqr (iwd, FALSE, iwd->dispx, iwd->dispy, iwd->dispwidth, iwd->dispheight); /* Update to new size */ nav_window_disp_area (iwd, iwd->gdisp); /* and redraw */ nav_window_draw_sqr (iwd, FALSE, iwd->dispx, iwd->dispy, iwd->dispwidth, iwd->dispheight); } void nav_dialog_popup (InfoDialog *idialog) { NavWinData *iwd; if (!idialog) return; iwd = (NavWinData *) idialog->user_data; iwd->showingPreview = TRUE; if (! GTK_WIDGET_VISIBLE (idialog->shell)) { gtk_widget_show (idialog->shell); nav_window_update_preview_blank (iwd); nav_window_update_window_marker (idialog); iwd->timer_id = gtk_timeout_add (PREVIEW_UPDATE_TIMEOUT, (GtkFunction) nav_preview_update_do_timer, (gpointer) iwd); } gdk_window_raise (GTK_WIDGET (idialog->shell)->window); } /* Chose the first image.. */ static void gimlist_cb (gpointer im, gpointer data) { GSList **list_ptr; list_ptr = (GSList **) data; *list_ptr = g_slist_prepend (*list_ptr, im); } static GDisplay * nav_window_get_gdisp (void) { GSList *list = NULL; GSList *listPtr = NULL; GDisplay *gdisp = NULL; GimpImage *gimage; gimage_foreach (gimlist_cb, &list); if (!list) return NULL; /* Find first valid image */ for (listPtr = list; listPtr; listPtr = g_slist_next (listPtr)) { gimage = GIMP_IMAGE (listPtr->data); gdisp = gdisplays_check_valid (NULL, gimage); if (gdisp) break; } g_slist_free (list); return gdisp; } void nav_window_free (GDisplay *del_gdisp, InfoDialog *info_win) { NavWinData *iwd; /* So this functions works both ways.. * it will come in here with info_win == null * if the auto mode is on... */ if (!info_win) { if (nav_window_auto != NULL) { GDisplay *gdisp; iwd = (NavWinData *) nav_window_auto->user_data; /* Only freeze if we are displaying the image we have deleted */ if (iwd->gdisp != del_gdisp) return; if (iwd->timer_id) gtk_timeout_remove (iwd->timer_id); iwd->timer_id = 0; gdisp = nav_window_get_gdisp (); if (gdisp) { nav_window_change_display (NULL, gdisp, NULL); } else { /* Clear window and freeze */ iwd->frozen = TRUE; nav_window_update_preview_blank (iwd); gtk_window_set_title (GTK_WINDOW (nav_window_auto->shell), _("Navigation: No Image")); gtk_widget_set_sensitive (nav_window_auto->vbox, FALSE); iwd->gdisp = NULL; gtk_widget_hide (GTK_WIDGET (nav_window_auto->shell)); } } return; } /* We are actually freeing a window here so remove any timers left. */ iwd = (NavWinData *)info_win->user_data; if (iwd->timer_id) gtk_timeout_remove (iwd->timer_id); g_free (info_win->user_data); info_dialog_free (info_win); } /* nav popup ... * should share code with the nav_window dialog since they are * essentially the same. */ void nav_popup_click_handler (GtkWidget *widget, GdkEventButton *event, gpointer data) { GdkEventButton *bevent; GDisplay *gdisp = data; NavWinData *iwp; /* dummy shorter version for the popups */ gint x, y; gint x_org, y_org; gint scr_w, scr_h; bevent = (GdkEventButton *)event; if (! gdisp->nav_popup) { /* popup a simplfied window with the nav box in it */ GtkWidget *frame; GtkWidget *vbox; iwp = create_dummy_iwd (gdisp, NAV_POPUP); gdisp->nav_popup = gtk_window_new (GTK_WINDOW_POPUP); gtk_widget_set_events (gdisp->nav_popup, PREVIEW_MASK); gtk_window_set_policy (GTK_WINDOW (gdisp->nav_popup), FALSE, FALSE, TRUE); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (gdisp->nav_popup), frame); gtk_widget_show (frame); vbox = create_preview_containers (iwp,gdisp->nav_popup); gtk_widget_set_extension_events (iwp->preview, GDK_EXTENSION_EVENTS_ALL); gtk_container_add (GTK_CONTAINER (frame), vbox); gtk_object_set_data (GTK_OBJECT (gdisp->nav_popup),"navpop_prt", (gpointer) iwp); nav_window_disp_area (iwp, iwp->gdisp); } else { gtk_widget_hide (gdisp->nav_popup); iwp = (NavWinData *) gtk_object_get_data (GTK_OBJECT (gdisp->nav_popup), "navpop_prt"); nav_window_disp_area (iwp, iwp->gdisp); nav_window_update_preview (iwp); } /* decide where to put the popup */ gdk_window_get_origin (widget->window, &x_org, &y_org); scr_w = gdk_screen_width (); scr_h = gdk_screen_height (); x = x_org + bevent->x - iwp->dispx - ((iwp->dispwidth - BORDER_PEN_WIDTH + 1) * 0.5) - 2; y = y_org + bevent->y - iwp->dispy - ((iwp->dispheight - BORDER_PEN_WIDTH + 1)* 0.5) - 2; /* If the popup doesn't fit into the screen, we have a problem. * We move the popup onscreen and risk that the pointer is not * in the square representing the viewable area anymore. Moving * the pointer will make the image scroll by a large amount, * but then it works as usual. Probably better than a popup that * is completely unusable in the lower right of the screen. * * Warping the pointer would be another solution ... */ x = (x < 0) ? 0 : x; y = (y < 0) ? 0 : y; x = (x + iwp->pwidth > scr_w) ? scr_w - iwp->pwidth - 2: x; y = (y + iwp->pheight > scr_h) ? scr_h - iwp->pheight - 2: y; gtk_widget_popup (gdisp->nav_popup, x, y); gdk_flush(); /* fill in then set up handlers for mouse motion etc */ iwp->motion_offsetx = (iwp->dispwidth - BORDER_PEN_WIDTH + 1) * 0.5; iwp->motion_offsety = (iwp->dispheight - BORDER_PEN_WIDTH + 1) * 0.5; if(GTK_WIDGET_VISIBLE(iwp->preview)) nav_window_grab_pointer (iwp, iwp->preview); } void nav_popup_free (GtkWidget *nav_popup) { gtk_widget_destroy (nav_popup); } void nav_window_preview_resized (InfoDialog *idialog) { NavWinData *iwd; if (!idialog) return; iwd = (NavWinData *)idialog->user_data; /* force regeneration of the widgets */ /* bit of a fiddle... could cause if the image really is 1x1 * but the preview would not really matter in that case. */ iwd->imagewidth = 1; iwd->imageheight = 1; iwd->nav_preview_width = (nav_preview_size < 0 || nav_preview_size > 256) ? NAV_PREVIEW_WIDTH : nav_preview_size; iwd->nav_preview_height = (nav_preview_size < 0 || nav_preview_size > 256) ? NAV_PREVIEW_HEIGHT : nav_preview_size; nav_window_update_window_marker (idialog); } void nav_window_popup_preview_resized (GtkWidget **widget) { NavWinData *iwp; /* dummy shorter version for the popups */ iwp = (NavWinData *) gtk_object_get_data (GTK_OBJECT (*widget), "navpop_prt"); g_free (iwp); gtk_widget_destroy (*widget); *widget = NULL; }