Logo Search packages:      
Sourcecode: vertex version File versions

imgview.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include "guiutils.h"
#include "imgview.h"
#include "imgviewcrop.h"

#include "images/icon_zoom_in_16x16.xpm"
#include "images/icon_zoom_out_16x16.xpm"
#include "images/icon_zoom_onetoone_16x16.xpm"
#include "images/icon_zoom_tofit_16x16.xpm"

/*
#include "images/icon_zoom_in_20x20.xpm"
#include "images/icon_zoom_out_20x20.xpm"
 */
#include "images/icon_zoom_onetoone_20x20.xpm"
#include "images/icon_zoom_tofit_20x20.xpm"

#include "images/iv.xpm"


/* Image IO. */
imgview_image_struct *ImgViewImageNew(
      gint width, gint height, gint depth
);
void ImgViewImageClear(
        imgview_image_struct *img, guint32 v
);
void ImgViewImageSendRectangle(
      imgview_image_struct *image, GdkDrawable *d, GdkGC *gc,     gint quality,
      const GdkRectangle *rect
);
void ImgViewImageSend(
      imgview_image_struct *image, GdkDrawable *d, GdkGC *gc, gint quality
);
void ImgViewImageDelete(imgview_image_struct *image);

/* Callbacks. */
static gint ImgViewCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data);
static void ImgViewDestroyCB(GtkObject *object, gpointer data);
static void ImgViewZoomInPressedCB(GtkWidget *widget, gpointer data);
static gint ImgViewZoomInTOCB(gpointer data);
static void ImgViewZoomOutPressedCB(GtkWidget *widget, gpointer data);
static gint ImgViewZoomOutTOCB(gpointer data);
static void ImgViewZoomReleasedCB(GtkWidget *widget, gpointer data);
static void ImgViewZoomOneToOneCB(GtkWidget *widget, gpointer data);
static void ImgViewZoomToFitCB(GtkWidget *widget, gpointer data);
static void ImgViewToolBarToggleCB(GtkWidget *widget, gpointer data);
static void ImgViewValuesToggleCB(GtkWidget *widget, gpointer data);
static void ImgViewStatusBarToggleCB(GtkWidget *widget, gpointer data);
static void ImgViewQualityPoorCB(GtkWidget *widget, gpointer data);
static void ImgViewQualityOptimalCB(GtkWidget *widget, gpointer data);
static void ImgViewQualityBestCB(GtkWidget *widget, gpointer data);
static gint ImgViewInfoLabelCrossingCB(
        GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);
static gint ImgViewInfoLabelExposeCB(
      GtkWidget *widget, GdkEventExpose *expose, gpointer data
);
static void ImgViewAdjustmentValueChangedCB(
        GtkAdjustment *adjustment, gpointer data
);
static gint ImgViewViewEventCB(
      GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint ImgViewExposeCB(
      GtkWidget *widget, GdkEventExpose *expose, gpointer data
);

/* Utilities and operations. */
static gint ImgViewRInt(gdouble x);
static gint ImgViewConvertUnitViewToOrigX(
      imgview_struct *iv, gint x
);
static gint ImgViewConvertUnitViewToOrigY(
      imgview_struct *iv, gint y
);
static gint ImgViewConvertUnitOrigToViewX(
        imgview_struct *iv, gint x
);
static gint ImgViewConvertUnitOrigToViewY(
        imgview_struct *iv, gint y
);
static void ImgViewInfoLabelDraw(imgview_struct *iv);
static void ImgViewRealizeChange(imgview_struct *iv);
static void ImgViewZoomIterate(imgview_struct *iv, gint dz);
static void ImgViewZoomRectangular(
      imgview_struct *iv,
      gint x0, gint x1,
      gint y0, gint y1
);

static void ImgViewBlitView(imgview_struct *iv);
static void ImgViewWMIconUpdate(imgview_struct *iv);
static void ImgViewBufferRecreate(imgview_struct *iv);

/* Public. */
GtkAccelGroup *ImgViewGetAccelGroup(imgview_struct *iv);
gboolean ImgViewToplevelIsWindow(imgview_struct *iv);
GtkWidget *ImgViewGetToplevelWidget(imgview_struct *iv);
GtkDrawingArea *ImgViewGetViewWidget(imgview_struct *iv);
GtkMenu *ImgViewGetMenuWidget(imgview_struct *iv);
gboolean ImgViewIsLoaded(imgview_struct *iv);
imgview_image_struct *ImgViewGetImage(imgview_struct *iv);
guint8 *ImgViewGetImageData(
        imgview_struct *iv,
        gint *width, gint *height, gint *bpl, gint *bpp,
        gint *format
);

void ImgViewClear(imgview_struct *iv);

static gint ImgViewLoadNexus(
        imgview_struct *iv,
        gint width, gint height,
        gint bytes_per_line,    /* Can be 0 to auto calculate. */
        gint format,            /* One of IMGVIEW_FORMAT_*. */
        const guint8 *data,
        gboolean zoom_to_fit
);
gint ImgViewLoad(
      imgview_struct *iv,
      gint width, gint height,
      gint bytes_per_line,    /* Can be 0 to auto calculate. */
      gint format,            /* One of IMGVIEW_FORMAT_*. */
      const guint8 *data
);
gint ImgViewLoadToFit(
        imgview_struct *iv,
        gint width, gint height,
        gint bytes_per_line,    /* Can be 0 to auto calculate. */
        gint format,            /* One of IMGVIEW_FORMAT_*. */
        const guint8 *data
);

imgview_struct *ImgViewNew(
      gboolean show_toolbar,
      gboolean show_values,
      gboolean show_statusbar,
      gboolean show_image_on_wm_icon,
      gint quality,           /* From 0 to 2 (2 being best/slowest). */
      gboolean toplevel_is_window,
      GtkWidget **toplevel_rtn
);
void ImgViewSetChangedCB(
        imgview_struct *iv,
        void (*changed_cb)(gpointer, imgview_image_struct *, gpointer),
        gpointer data
);
void ImgViewReset(imgview_struct *iv, gboolean need_unmap);
void ImgViewDraw(imgview_struct *iv);
void ImgViewUpdateMenus(imgview_struct *iv);
void ImgViewShowToolBar(imgview_struct *iv, gboolean show);
void ImgViewShowStatusBar(imgview_struct *iv, gboolean show);
void ImgViewSetViewBG(
      imgview_struct *iv,
      GdkColor *c       /* 5 colors. */
);

void ImgViewZoomIn(imgview_struct *iv);
void ImgViewZoomOut(imgview_struct *iv);
void ImgViewZoomOneToOne(imgview_struct *iv);
void ImgViewZoomToFit(imgview_struct *iv);

void ImgViewAllowCrop(imgview_struct *iv, gboolean allow_crop);
void ImgViewSetBusy(imgview_struct *iv, gboolean is_busy);
void ImgViewProgressUpdate(
        imgview_struct *iv, gdouble position, gboolean allow_gtk_iteration
);
void ImgViewMap(imgview_struct *iv);
void ImgViewUnmap(imgview_struct *iv);
void ImgViewDelete(imgview_struct *iv);


#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define ABSOLUTE(x)           (((x) < 0) ? ((x) * -1) : (x))

#ifndef GDK_BUTTON1
# define GDK_BUTTON1    1
#endif

#ifndef GDK_BUTTON2
# define GDK_BUTTON2    2
#endif

#ifndef GDK_BUTTON3
# define GDK_BUTTON3    3
#endif


/* Title. */
#define IMGVIEW_TITLE         "Image Viewer"

/* Minimum width. */
#define IMGVIEW_MIN_WIDTH     200
#define     IMGVIEW_MIN_HEIGHT      (IMGVIEW_MIN_WIDTH * 0.75)

/* WM icon size in pixels. */
#define IMGVIEW_WM_ICON_WIDTH 48
#define IMGVIEW_WM_ICON_HEIGHT      48

/* Zoom out maximum. */
#define IMGVIEW_ZOOM_MIN      0.01

/* Zoom in maximum. */
#define IMGVIEW_ZOOM_MAX      50.0

/* Zoom rate (in coeff per cycle). */
#define IMGVIEW_ZOOM_RATE     0.0025


/* Tool bar height in pixels. */
#define IMGVIEW_TOOLBAR_HEIGHT            (16 + (2 * 2))

/* Status bar height in pixels. */
/*
Since the status bar is a simple one with only a progress bar then we
really don't need the status bar to be big.
#define IMGVIEW_STATUSBAR_HEIGHT    26
 */
#define IMGVIEW_STATUSBAR_HEIGHT    16

/* Zoom repeat timeout interval in ms. */
#define IMGVIEW_ZOOM_TIMEOUT_INT          100
#define IMGVIEW_ZOOM_INITIAL_TIMEOUT_INT  320

/* Zoom itteration pixels for button or keypresses. */
#define IMGVIEW_ZOOM_ITTERATION_PIXELS          50


/*
 *    Creates a new image of the specified pixel width and height.
 *
 *    The actual depth of the associated GdkDrawable's Visual needs to
 *    be given (this is not the depth of the image to be created).
 */
imgview_image_struct *ImgViewImageNew(
        gint width, gint height, gint depth
)
{
      gint bpl;

      imgview_image_struct *img = IMGVIEW_IMAGE(g_malloc0(
          sizeof(imgview_image_struct)
      ));
      if(img == NULL)
          return(NULL);

      if((width < 1) || (height < 1))
      {
          g_free(img);
          return(NULL);
      }

      /* Record associated GdkDrawable's Visual depth. */
      img->visual_depth = depth;


      /* Begin recording image values. */
      img->depth = 24;  /* 24 bits per pixel. */
      img->bpp = 4;           /* 4 bytes per pixel allocation. */

      /* Calculate bytes per line. */
      bpl = img->bpp * width;
      /* Align bytes per line to 4 bytes by incrementing it untill it
       * matches the next 4 byte align value
       */
      if(bpl > 4)
          while((bpl % 4) != 0)
            bpl++;
      else
          bpl = 4;
      img->bpl = bpl;

      /* Set width and height in units of pixels. */
      img->width = width;
      img->height = height;

      /* Allocate image buffer. */
      img->mem = (guint8 *)g_malloc(img->bpl * img->height);
      if(img->mem == NULL)
      {
          ImgViewImageDelete(img);
          return(NULL);
      }

      return(img);
}

/*
 *    Clears the given image with the given pixel value v.
 *
 *    The pixel value v must be 32 bits in size and in the format
 *    RGBA.
 */
void ImgViewImageClear(
      imgview_image_struct *img, guint32 v
)
{
      gint x, y, bc;
      gint width, height;
      gint bpl, bpp, index_offset;
      guint8 *buf, *buf_ptr;
      guint32 vbuf = v;
      const guint8 *vptr;


      if(img == NULL)
          return;

      buf = img->mem;
      if(buf == NULL)
          return;

      width = img->width,
      height = img->height;
      bpp = img->bpp;
      bpl = img->bpl;

      /* Iterate through rows. */
      for(y = 0; y < height; y++)
      {
          index_offset = y * bpl;   /* Cur line index offset in bytes. */
          for(x = 0; x < width; x++)
          {
            /* Get pointer to target buffer and start of given pixel
             * value.
             */
            buf_ptr = &buf[index_offset + (x * bpp)];
            vptr = (const guint8 *)&vbuf;

            for(bc = 0; bc < bpp; bc++)
                *buf_ptr++ = *vptr++;
          }
      }
}

/*
 *    Sends the given rectangular area of the given image to the
 *    drawable.
 */
void ImgViewImageSendRectangle(
        imgview_image_struct *img, GdkDrawable *d, GdkGC *gc, gint quality,
        const GdkRectangle *rect
)
{
      gint visual_depth;
        guchar *rgb_buf;
      GdkRgbDither dither_level = GDK_RGB_DITHER_NORMAL;
      GdkRectangle lrect;


        if((img == NULL) || (d == NULL) || (gc == NULL) ||
           (rect == NULL)
      )
            return;

        rgb_buf = img->mem;
        if(rgb_buf == NULL)
            return;

      /* Get associated GdkDrawable's Visual depth. */
      visual_depth = img->visual_depth;

      /* Handle by associated GdkDrawable's Visual depth. */
      if(visual_depth <= 8)
      {
          switch(quality)
          {
            case 2:     /* Best/slowest. */
              dither_level = GDK_RGB_DITHER_MAX;
              break;
              case 1:   /* Optimal. */
                dither_level = GDK_RGB_DITHER_NORMAL;
                break;
            default:    /* Poor/fastest. */
              dither_level = GDK_RGB_DITHER_NONE;
                break;
          }
      }
        else if(visual_depth <= 16)
        {
/* printf("Quality = %i\n", quality); */
            switch(quality)
            {
              case 2:   /* Best/slowest. */
                dither_level = GDK_RGB_DITHER_MAX;
                break;
              case 1:   /* Optimal. */
                dither_level = GDK_RGB_DITHER_NORMAL;
                break;
              default:  /* Poor/fastest. */
                dither_level = GDK_RGB_DITHER_NONE;
                break;
            }
        }
        else if(visual_depth <= 32)
        {
          /* Never dither at 17 to 32 bits. */
          dither_level = GDK_RGB_DITHER_NONE;
        }


/* Rectangles don't work too well, need to be upper left 0 0 oriented.
 * So fix the rectangle values so they're upper left 0 0 oriented.
 */
      memcpy(&lrect, rect, sizeof(GdkRectangle));
      lrect.width += lrect.x;
      lrect.height += lrect.y;
      lrect.x = 0;
      lrect.y = 0;

      switch(img->bpp)
      {
        case 4:
          gdk_draw_rgb_32_image(
            d, gc,
            lrect.x, lrect.y,
            lrect.width, lrect.height,
            dither_level,
            rgb_buf,
            img->bpl
          );
          break;

          case 3:
            gdk_draw_rgb_image(
                d, gc,
                lrect.x, lrect.y,
                lrect.width, lrect.height,
                dither_level,
                rgb_buf,
                img->bpl
            );
            break;
      }
}

/*
 *    Sends the given image to the drawable.
 */
void ImgViewImageSend(
        imgview_image_struct *img, GdkDrawable *d, GdkGC *gc, gint quality
)
{
      GdkRectangle rect;

      if((img == NULL) || (d == NULL) || (gc == NULL))
          return;

      rect.x = 0;
      rect.y = 0;
      rect.width = img->width;
      rect.height = img->height;

      ImgViewImageSendRectangle(img, d, gc, quality, &rect);
}

/*
 *    Deallocates the given image and all its resources.
 */
void ImgViewImageDelete(imgview_image_struct *img)
{
      if(img == NULL)
          return;

      /* Deallocate image buffer. */
      if(img->mem != NULL)
      {
          guint8 *tm = img->mem;
          img->mem = NULL;
          g_free(tm);
      }

      /* Deallocate structure itself. */
      g_free(img);
}


/*
 *    Close "delete_event" callback.
 */
static gint ImgViewCloseCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
      imgview_struct *iv = IMGVIEW(data);
      if(iv == NULL)
          return(FALSE);

/* Let calling function determine what close does, do not unmap or
 * do anything here!
 */
/*    ImgViewUnmap(iv); */
      return(TRUE);
}

/*
 *    Destroy event callback.
 */
static void ImgViewDestroyCB(GtkObject *object, gpointer data)
{
      return;
}

/*
 *    Zoom in "pressed" callback.
 */
static void ImgViewZoomInPressedCB(GtkWidget *widget, gpointer data)
{
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

        /* Call timeout callback which will iterate once and set up
         * timeout callback to call itself again.
         */
      ImgViewZoomInTOCB(iv);
}

/*
 *    Zoom in timeout callback.
 */
static gint ImgViewZoomInTOCB(gpointer data)
{
      gboolean is_initial = FALSE;
      imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return(FALSE);

        /* Zoom in. */
        ImgViewZoomIterate(iv, -IMGVIEW_ZOOM_ITTERATION_PIXELS);

        /* Clip bounds, update adjustments and redraw. */
        ImgViewRealizeChange(iv);
        ImgViewExposeCB(iv->view_da, NULL, iv);

        /* Flush output and reset timeout for next itteration. */
        gdk_flush();
        if(iv->view_zoom_toid != (guint)-1)
            gtk_timeout_remove(iv->view_zoom_toid);
      else
          is_initial = TRUE;
        iv->view_zoom_toid = gtk_timeout_add(
            (is_initial) ?
                IMGVIEW_ZOOM_INITIAL_TIMEOUT_INT : IMGVIEW_ZOOM_TIMEOUT_INT,
            (GtkFunction)ImgViewZoomInTOCB,
            iv
        );

      return(FALSE);
}

/*
 *    Zoom out "pressed" callback.
 */
static void ImgViewZoomOutPressedCB(GtkWidget *widget, gpointer data)
{
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

      /* Call timeout callback which will iterate once and set up
       * timeout callback to call itself again.
       */
      ImgViewZoomOutTOCB(iv);
}

/*
 *    Zoom out timeout callback.
 */
static gint ImgViewZoomOutTOCB(gpointer data)
{
      gboolean is_initial = FALSE;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return(FALSE);

      /* Zoom out. */
      ImgViewZoomIterate(iv, IMGVIEW_ZOOM_ITTERATION_PIXELS);

      /* Clip bounds, update adjustments and redraw. */
      ImgViewRealizeChange(iv);
      ImgViewExposeCB(iv->view_da, NULL, iv);

      /* Flush output and reset timeout for next itteration. */
      gdk_flush();
        if(iv->view_zoom_toid != (guint)-1)
          gtk_timeout_remove(iv->view_zoom_toid);
      else
          is_initial = TRUE;
      iv->view_zoom_toid = gtk_timeout_add(
          (is_initial) ? 
            IMGVIEW_ZOOM_INITIAL_TIMEOUT_INT : IMGVIEW_ZOOM_TIMEOUT_INT,
          (GtkFunction)ImgViewZoomOutTOCB,
          iv
      );

        return(FALSE);
}

/*
 *    Zoom in or zoom out button "released" callback.
 */
static void ImgViewZoomReleasedCB(GtkWidget *widget, gpointer data)
{
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

        if(iv->view_zoom_toid != (guint)-1)
        {
            gtk_timeout_remove(iv->view_zoom_toid);
          iv->view_zoom_toid = (guint)-1;
        }
}


/*
 *    Zoom one to one callback.
 */
static void ImgViewZoomOneToOneCB(GtkWidget *widget, gpointer data)
{
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

        if(iv->orig_img == NULL)
            return;

      /* Reset zoom to 1.0. */
      iv->view_zoom = 1.0;

        /* Clip bounds, update adjustments and redraw. */
        ImgViewRealizeChange(iv);
        ImgViewExposeCB(iv->view_da, NULL, iv);
}

/*
 *    Zoom to fit callback.
 */
static void ImgViewZoomToFitCB(GtkWidget *widget, gpointer data)
{
      GtkAdjustment *xadj, *yadj;
      imgview_image_struct *tar_img;
      gint src_width, src_height;
      gdouble zoom;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

      if(iv->orig_img == NULL)
          return;

        xadj = iv->view_x_adj;
        yadj = iv->view_y_adj;
        tar_img = iv->view_img;

      if((xadj == NULL) || (yadj == NULL) || (tar_img == NULL))
          return;

      if((tar_img->width < 1) || (tar_img->height < 1))
          return;

      src_width = (gint)(xadj->upper - xadj->lower);
      src_height = (gint)(yadj->upper - yadj->lower);
      if((src_width < 1) || (src_height < 1))
          return;

      zoom = (gdouble)tar_img->width / (gdouble)src_width;
      if(zoom <= 0.0)
          return;

      if((tar_img->height / zoom) < src_height)
          zoom = (gdouble)tar_img->height / (gdouble)src_height;

      iv->view_zoom = zoom;

        /* Clip bounds, update adjustments and redraw. */
        ImgViewRealizeChange(iv);
        ImgViewExposeCB(iv->view_da, NULL, iv);
}

/*
 *      Show toolbar toggle callback.
 */
static void ImgViewToolBarToggleCB(GtkWidget *widget, gpointer data)
{
      static gboolean reenterant = FALSE;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

        ImgViewShowToolBar(iv, !iv->toolbar_map_state);

      reenterant = FALSE;
}

/*
 *      Show values toggle callback.
 */
static void ImgViewValuesToggleCB(GtkWidget *widget, gpointer data)
{
        static gboolean reenterant = FALSE;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

        if(reenterant)
            return;
        else
            reenterant = TRUE;

      iv->show_values = !iv->show_values;
      ImgViewInfoLabelDraw(iv);     /* Redraw info_label. */
      ImgViewUpdateMenus(iv);
      ImgViewExposeCB(iv->view_da, NULL, iv);   /* Redraw view. */

        reenterant = FALSE;
}

/*
 *      Show status bar toggle callback.
 */
static void ImgViewStatusBarToggleCB(GtkWidget *widget, gpointer data)
{
        static gboolean reenterant = FALSE;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

        if(reenterant)
            return;
        else
            reenterant = TRUE;

        ImgViewShowStatusBar(iv, !iv->statusbar_map_state);

        reenterant = FALSE;
}

/*
 *    Set quality to poor/fastest callback.
 */
static void ImgViewQualityPoorCB(GtkWidget *widget, gpointer data)
{
      gint new_quality = 0;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

      if(iv->quality != new_quality)
      {
          iv->quality = new_quality;

          /* Update menus and redraw view to reflect new quality. */
          ImgViewUpdateMenus(iv);
          ImgViewExposeCB(iv->view_da, NULL, iv);
      }
}

/*
 *    Set quality to optimal callback.
 */
static void ImgViewQualityOptimalCB(GtkWidget *widget, gpointer data)
{
        gint new_quality = 1;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

        if(iv->quality != new_quality)
        {
            iv->quality = new_quality;

            /* Update menus and redraw view to reflect new quality. */
            ImgViewUpdateMenus(iv);
            ImgViewExposeCB(iv->view_da, NULL, iv);
        }
}

/*
 *    Set quality to best/slowest callback.
 */
static void ImgViewQualityBestCB(GtkWidget *widget, gpointer data)
{
        gint new_quality = 2;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

        if(iv->quality != new_quality)
        {
            iv->quality = new_quality;

            /* Update menus and redraw view to reflect new quality. */
            ImgViewUpdateMenus(iv);
            ImgViewExposeCB(iv->view_da, NULL, iv);
        }
}

/*
 *    Info label "enter_notify_event" and "leave_notify_event"
 *    signal callbacks.
 */
static gint ImgViewInfoLabelCrossingCB(
        GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
      static gboolean reenterent = FALSE;
      gchar text[256];
      GdkVisual *vis;
      const gchar *vis_name;
      gint depth;
      GdkWindow *window;
      GtkAdjustment *xadj, *yadj;
      GtkWidget *w;
      imgview_image_struct *src_img;
        imgview_struct *iv = IMGVIEW(data);
        if((widget == NULL) || (crossing == NULL) || (iv == NULL))
            return(FALSE);

        /* Skip "leave_notify_event" events. */
        if(crossing->type == GDK_LEAVE_NOTIFY)
            return(TRUE);

      /* Is this event sent synthemtically (when we called 
       * GUIShowTipsNow() below? If it is then skip it.
       */
      if(crossing->send_event)
            return(TRUE);


      /* Get values from image viewer. */
      xadj = iv->view_x_adj;
      yadj = iv->view_y_adj;
      w = iv->view_da;
      if((xadj == NULL) || (yadj == NULL) || (w == NULL))
          return(TRUE);

      if(GTK_WIDGET_NO_WINDOW(w))
          return(TRUE);

      window = w->window;
      if(window == NULL)
          return(TRUE);


      if(reenterent)
          return(TRUE);
      else
          reenterent = TRUE;

      /* Get visual. */
      vis_name = "";
      depth = 0;
      vis = gdk_window_get_visual(window);
      if(vis != NULL)
      {
          switch((gint)vis->type)
          {
            case GDK_VISUAL_STATIC_GRAY:
            vis_name = "StaticGray";
            break;
            case GDK_VISUAL_GRAYSCALE:
                vis_name = "GrayScale";
                break;
            case GDK_VISUAL_STATIC_COLOR:
                vis_name = "StaticColor";
                break;
              case GDK_VISUAL_PSEUDO_COLOR:
                vis_name = "PseudoColor";
                break;
              case GDK_VISUAL_TRUE_COLOR:
                vis_name = "TrueColor";
                break;
              case GDK_VISUAL_DIRECT_COLOR:
                vis_name = "DirectColor";
                break;
          }
          depth = vis->depth;
      }

      /* Check if original image is loaded. */
      src_img = iv->orig_img;
      if((src_img == NULL) || !iv->show_values)
      {
          GUISetWidgetTip(widget, NULL);
          reenterent = FALSE;
          return(TRUE);
      }

      sprintf(
          text,
          "Translate:%i,%i Size:%ix%i Zoom:%.0f%% Visual:%s Depth:%i",
            (gint)xadj->value,
            (gint)yadj->value,
          (gint)(xadj->upper - xadj->lower),
          (gint)(yadj->upper - yadj->lower),
          iv->view_zoom * 100.0,
          vis_name,
          depth
      );

      /* Update tip. */
      GUISetWidgetTip(widget, text);
      GUIShowTipsNow(widget);

      reenterent = FALSE;
      return(TRUE);
}

/*
 *    Info label GtkDrawingArea "expose_event" signal callback.
 */
static gint ImgViewInfoLabelExposeCB(
        GtkWidget *widget, GdkEventExpose *expose, gpointer data
)
{
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return(FALSE);

      ImgViewInfoLabelDraw(iv);

      return(TRUE);
}

/*
 *    Adjustment "value_changed" signal callback.
 *
 *    This does not watch for the "changed" signal which is emitted
 *    when one of the image viewer's adjustments is updated by the 
 *    program (if from a pointer drag). This only checks if one of the
 *    scroll bar widgets was moved by the user.
 */
static void ImgViewAdjustmentValueChangedCB(
      GtkAdjustment *adjustment, gpointer data
)
{
      static gboolean reenterent = FALSE;
        imgview_struct *iv = IMGVIEW(data);
        if(iv == NULL)
            return;

      if(reenterent)
          return;
      else
          reenterent = TRUE;

      /* Redraw info label. */
      ImgViewInfoLabelDraw(iv);

      /* Redraw view drawing area when an adjustment has changed. */
      ImgViewExposeCB(iv->view_da, NULL, iv);

      reenterent = FALSE;
}

/*
 *    View widget event handler (except for event "expose_event", see
 *    ImgViewExposeCB() for that handler).
 */
static gint ImgViewViewEventCB(GtkWidget *widget, GdkEvent *event, gpointer data)
{
      gint etype, status = FALSE;
      gboolean need_grab_pointer = FALSE;
      GdkCursor *cur = NULL;
      imgview_image_struct *src_img, *tar_img;
      GdkWindow *window;
      GtkAdjustment *xadj, *yadj;
      GdkEventConfigure *configure;
      GdkEventCrossing *crossing;
      GdkEventKey *key;
      GdkEventButton *button;
      GdkEventMotion *motion;
      gboolean keystate;
      guint keyval, keymods, motion_timmer;
      gint x, y;
        GdkModifierType mask;
        imgview_struct *iv = IMGVIEW(data);
        if((widget == NULL) || (event == NULL) || (iv == NULL))
            return(status);


      /* Get view adjustments (may be NULL). */
      xadj = iv->view_x_adj;
      yadj = iv->view_y_adj;

      /* Get source and target images (may be NULL). */
      src_img = iv->orig_img;
      tar_img = iv->view_img;

      /* Get view widget GdkWindow. */
      if(GTK_WIDGET_NO_WINDOW(widget))
          window = NULL;
      else
          window = widget->window;

      /* Get event type. */
      etype = *(gint *)event;

      /* Handle by event type. */
      switch(etype)
      {
          case GDK_CONFIGURE:
            configure = (GdkEventConfigure *)event;
          /* Destroy old view image buffer and create a new one based on
           * the new size of the view_da.
           */
            ImgViewBufferRecreate(iv);
          /* Need to update scrollbar about the new view_da (target image)
           * size.
           */
          ImgViewRealizeChange(iv);
          /* Redraw to update original image to new view image. */
          ImgViewExposeCB(iv->view_da, NULL, iv);
            status = TRUE;
            break;

        case GDK_ENTER_NOTIFY:
          crossing = (GdkEventCrossing *)event;
          /* Handle this event only if it is not program generated. */
          if(!crossing->send_event)
          {
            gint prev_keymods;


            /* Update keyboard modifiers state if (and only if) a key
             * modifier has been released (ignoring newly pressed 
             * modifiers).
             *
             * This is to correct a problem when pressing CTRL + <key>
             * and related short cuts that map other windows and thus
             * do not report a key modifier release to the view 
             * widget. So when the pointer enters back to the view
             * widget, it needs to check if the key modifiers were
             * released.
             */
            prev_keymods = iv->modifiers;
            keymods = crossing->state;
            if(!(keymods & GDK_SHIFT_MASK))
                iv->modifiers &= ~IMGVIEW_MODIFIER_SHIFT;
                if(!(keymods & GDK_CONTROL_MASK))
                    iv->modifiers &= ~IMGVIEW_MODIFIER_CTRL;
                if(!(keymods & GDK_MOD1_MASK))
                    iv->modifiers &= ~IMGVIEW_MODIFIER_ALT;

            /* If no modifiers left, then reset view window cursor. */
                if(prev_keymods && !iv->modifiers && (window != NULL))
                    gdk_window_set_cursor(window, NULL);


            /* Set up widget to grab focus so key_press_event and
             * and key_release_event events will be sent to it.
             */
            gtk_widget_grab_focus(widget);
          }
          status = TRUE;
          break;

        case GDK_LEAVE_NOTIFY:
          crossing = (GdkEventCrossing *)event;
          status = TRUE;
          break;

          case GDK_KEY_PRESS: case GDK_KEY_RELEASE:
            key = (GdkEventKey *)event;
          keyval = key->keyval;
          keystate = ((etype == GDK_KEY_PRESS) ? TRUE : FALSE);
          /* Handle by keyval. */
          /* ALT? */
            if((keyval == GDK_Alt_L) || (keyval == GDK_Alt_R))
            {
                if(keystate)
                    iv->modifiers |= IMGVIEW_MODIFIER_ALT;
                else
                    iv->modifiers &= ~IMGVIEW_MODIFIER_ALT;
                status = TRUE;
            }
          /* CTRL? */
            else if((keyval == GDK_Control_L) || (keyval == GDK_Control_R))
            {
                if(keystate)
                    iv->modifiers |= IMGVIEW_MODIFIER_CTRL;
                else
                    iv->modifiers &= ~IMGVIEW_MODIFIER_CTRL;
                status = TRUE;
            }
          /* SHIFT? */
            else if((keyval == GDK_Shift_L) || (keyval == GDK_Shift_R))
            {
                if(keystate)
                    iv->modifiers |= IMGVIEW_MODIFIER_SHIFT;
                else
                    iv->modifiers &= ~IMGVIEW_MODIFIER_SHIFT;
            status = TRUE;
          }
          /* Zoom in? */
          else if((keyval == GDK_plus) || (keyval == GDK_equal) ||
                    (keyval == GDK_KP_Add)
          )
          {
                if(keystate)
            {
                    ImgViewZoomIterate(
                  iv, -IMGVIEW_ZOOM_ITTERATION_PIXELS
                );
                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
            }
                status = TRUE;
          }
            /* Zoom in fast? */
            else if((keyval == GDK_Page_Up) ||
                    (keyval == GDK_KP_Page_Up)
            )
            {
                if(keystate)
                {
                    ImgViewZoomIterate(
                        iv, -IMGVIEW_ZOOM_ITTERATION_PIXELS * 4
                    );
                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                }
                status = TRUE;
            }
            /* Zoom out? */
            else if((keyval == GDK_minus) || (keyval == GDK_underscore) ||
                    (keyval == GDK_KP_Subtract)
          )
            {
                if(keystate)
                {
                    ImgViewZoomIterate(
                  iv, IMGVIEW_ZOOM_ITTERATION_PIXELS
                );
                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                }
                status = TRUE;
            }
            /* Zoom out fast? */
            else if((keyval == GDK_Page_Down) ||
                    (keyval == GDK_KP_Page_Down)
          )
            {
                if(keystate)
                {
                    ImgViewZoomIterate(
                        iv, IMGVIEW_ZOOM_ITTERATION_PIXELS * 4
                    );
                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                }
                status = TRUE;
            }
          /* Translate up. */
          else if((keyval == GDK_Up) || (keyval == GDK_KP_Up))
          {
            if(keystate && (xadj != NULL) && (yadj != NULL))
            {
                gdouble zoom = iv->view_zoom;
                const gdouble zoom_min = IMGVIEW_ZOOM_MIN;

                    /* Sanitize current zoom. */
                    if(zoom < zoom_min)
                        zoom = zoom_min;

                    /* Translate. */
                    yadj->value -= (gfloat)yadj->step_increment / zoom;

                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
            }
            /* Stop signal so GTK+ does not later handle it and change
             * the focus widget, since this is also a focus change key.
             */
            gtk_signal_emit_stop_by_name(
                GTK_OBJECT(widget),
                keystate ? "key_press_event" : "key_release_event"
            );
                status = TRUE;
          }
            /* Translate down. */
            else if((keyval == GDK_Down) || (keyval == GDK_KP_Down))
            {
                if(keystate && (xadj != NULL) && (yadj != NULL))
                {
                    gdouble zoom = iv->view_zoom;
                    const gdouble zoom_min = IMGVIEW_ZOOM_MIN;

                    /* Sanitize current zoom. */
                    if(zoom < zoom_min)
                        zoom = zoom_min;

                    /* Translate. */
                    yadj->value += (gfloat)yadj->step_increment / zoom;

                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                }
                /* Stop signal so GTK+ does not later handle it and change
                 * the focus widget, since this is also a focus change key.
                 */
                gtk_signal_emit_stop_by_name(
                    GTK_OBJECT(widget),
                    keystate ? "key_press_event" : "key_release_event"
                );
                status = TRUE;
            }
            /* Translate left. */
            else if((keyval == GDK_Left) || (keyval == GDK_KP_Left))
            {
                if(keystate && (xadj != NULL) && (yadj != NULL))
                {
                    gdouble zoom = iv->view_zoom;
                    const gdouble zoom_min = IMGVIEW_ZOOM_MIN;

                    /* Sanitize current zoom. */
                    if(zoom < zoom_min)
                        zoom = zoom_min;

                    /* Translate. */
                    xadj->value -= (gfloat)xadj->step_increment / zoom;

                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                }
                /* Stop signal so GTK+ does not later handle it and change
                 * the focus widget, since this is also a focus change key.
                 */
                gtk_signal_emit_stop_by_name(
                    GTK_OBJECT(widget),
                    keystate ? "key_press_event" : "key_release_event"
                );
                status = TRUE;
            }
            /* Translate right. */
            else if((keyval == GDK_Right) || (keyval == GDK_KP_Right))
            {
                if(keystate && (xadj != NULL) && (yadj != NULL))
                {
                    gdouble zoom = iv->view_zoom;
                    const gdouble zoom_min = IMGVIEW_ZOOM_MIN;

                    /* Sanitize current zoom. */
                    if(zoom < zoom_min)
                        zoom = zoom_min;

                    /* Translate. */
                    xadj->value += (gfloat)xadj->step_increment / zoom;

                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                }
            /* Stop "key_press_event" or "key_release_event" signal
             * for cursor keys so GTK+ does not later handle it and
             * change the focus widget.
             *
             * Note that the tab key is ignored so tab can still be
             * used to change focus.
                 */
                gtk_signal_emit_stop_by_name(
                    GTK_OBJECT(widget),
                    keystate ? "key_press_event" : "key_release_event"
                );
                status = TRUE;
            }
          /* Zoom to fit? */
          else if((keyval == GDK_End) || (keyval == GDK_KP_End))
          {
            if(keystate)
            {
                ImgViewZoomToFitCB(widget, iv);
            }
            status = TRUE;
          }
            /* Zoom one to one? */
            else if((keyval == GDK_Home) || (keyval == GDK_KP_Home))
            {
            if(keystate)
            {
                ImgViewZoomOneToOneCB(widget, iv);
            }
            status = TRUE;
            }
          /* Zoom in to units of 100%'s with respect to number keys,
           * where 2 = 200%, 4 = 400%, etc... keys 1-9 are supported.
           */
            else if((keyval >= '1') && (keyval <= '9'))
            {
            if(keystate)
            {
                if(iv->modifiers & IMGVIEW_MODIFIER_SHIFT)
                  iv->view_zoom = 1.0 /
                      MAX((gdouble)(keyval - '1') + 1.0, 1.0);
                else
                  iv->view_zoom =
                      MAX((gdouble)(keyval - '1') + 1.0, 1.0);
                    ImgViewRealizeChange(iv);
                    ImgViewExposeCB(iv->view_da, NULL, iv);
            }
                status = TRUE;
            }

          /* Event handled? */
          if(status)
          {
            if(keystate)
            {
                if(iv->modifiers & IMGVIEW_MODIFIER_ALT)
                  cur = iv->zoom_cur;
                    else if(iv->modifiers & IMGVIEW_MODIFIER_SHIFT)
                        cur = iv->zoom_rectangle_cur;
                else if(iv->modifiers & IMGVIEW_MODIFIER_CTRL)
                  cur = iv->crop_cur;
            }
                /* Update cursor. */
                if(window != NULL)
                    gdk_window_set_cursor(window, cur);
          }
          break;

        case GDK_BUTTON_PRESS:
          button = (GdkEventButton *)event;
          cur = NULL;
            switch(button->button)
            {
              case GDK_BUTTON1:
                if(iv->modifiers & IMGVIEW_MODIFIER_ALT)
                {
                    iv->drag_mode = IMGVIEW_DRAG_MODE_ZOOM;
                    cur = iv->zoom_cur;
                need_grab_pointer = TRUE;
                /* Need to redraw view and info label. */
                ImgViewExposeCB(iv->view_da, NULL, iv);
                ImgViewInfoLabelDraw(iv);
                }
            else if(iv->modifiers & IMGVIEW_MODIFIER_CTRL)
            {
                if(iv->crop_flags & IMGVIEW_CROP_ALLOWED)
                {
                  iv->drag_mode = IMGVIEW_DRAG_MODE_CROP_RECTANGLE;
                  cur = iv->crop_cur;
                  need_grab_pointer = TRUE;

                  /* Crop rectangle is defined right from the start
                   * of a crop rectangle defination.
                   */
                  iv->crop_flags |= IMGVIEW_CROP_DEFINED;

                  /* Reset crop rectangle positions. */
                  iv->crop_rectangle_start_x =
                      iv->crop_rectangle_cur_x = ImgViewConvertUnitViewToOrigX(
                        iv, (gint)button->x
                      );
                  iv->crop_rectangle_start_y =
                      iv->crop_rectangle_cur_y = ImgViewConvertUnitViewToOrigY(
                        iv, (gint)button->y
                      );

                        /* Need to redraw view and info label. */
                        ImgViewExposeCB(iv->view_da, NULL, iv);
                        ImgViewInfoLabelDraw(iv);
                }
            }
                else if(iv->modifiers & IMGVIEW_MODIFIER_SHIFT)
                {
                    iv->drag_mode = IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE;
                    cur = iv->zoom_rectangle_cur;
                need_grab_pointer = TRUE;

                /* Reset drag rectangle position. */
                iv->drag_zoom_rectangle_start_x =
                  iv->drag_zoom_rectangle_cur_x = (gint)button->x;
                iv->drag_zoom_rectangle_start_y =
                  iv->drag_zoom_rectangle_cur_y = (gint)button->y;

                    /* Need to redraw view and info label. */
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                    ImgViewInfoLabelDraw(iv);
                }
            else
            {
                iv->drag_mode = IMGVIEW_DRAG_MODE_TRANSLATE;
                cur = iv->translate_cur;
                need_grab_pointer = TRUE;
            }
                status = TRUE;
                break;

              case GDK_BUTTON2:
                if(iv->modifiers & IMGVIEW_MODIFIER_ALT)
                {
                    /* Zoom 1:1. */
                    ImgViewZoomOneToOneCB(widget, data);
                /* Still need to set cursor to zoom. */
                    cur = iv->zoom_cur;
                }
            else if(iv->modifiers & IMGVIEW_MODIFIER_SHIFT)
            {
                /* Zoom to fit. */
                ImgViewZoomToFitCB(widget, data);
                /* Still need to set cursor to zoom rectangular. */
                    cur = iv->zoom_rectangle_cur;
            }
            else
            {
                /* Drag zoom. */
                iv->drag_mode = IMGVIEW_DRAG_MODE_ZOOM;
                cur = iv->zoom_cur;
                need_grab_pointer = TRUE;

                    /* Need to redraw view and info label. */
                    ImgViewExposeCB(iv->view_da, NULL, iv);
                    ImgViewInfoLabelDraw(iv);
            }
                status = TRUE;
                break;

              case GDK_BUTTON3:
            if(iv->menu != NULL)
            {
                GtkMenu *menu = GTK_MENU(iv->menu);
                    gtk_menu_popup(
                        menu,
                        NULL, NULL, NULL, NULL,
                        button->button, button->time
                    );
            }
                status = TRUE;
                break;
          }
          /* Set cursor. */
          if(window != NULL)
            gdk_window_set_cursor(window, cur);

          /* Reset last drag positions on any button press. */
          iv->drag_last_x = (gint)button->x;
            iv->drag_last_y = (gint)button->y;

          /* Reset last motion event handled time. */
            iv->last_motion_time = 0;

          /* Grab pointer? */
          if(need_grab_pointer && (window != NULL))
          {
            gdk_flush();
                gdk_pointer_grab(
                    window,
                    FALSE,
                    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
                        GDK_POINTER_MOTION_MASK,
                    NULL,
                    GDK_NONE,
                    button->time
                );
            gdk_flush();
          }
            break;

          case GDK_BUTTON_RELEASE:
            button = (GdkEventButton *)event;

            /* Ungrab pointer. */
            gdk_flush();
            gdk_pointer_ungrab(button->time);
            gdk_flush();
            while(gdk_pointer_is_grabbed())
                { gdk_pointer_ungrab(GDK_CURRENT_TIME); gdk_flush(); }

          /* Post drag handling. */
          switch(iv->drag_mode)
          {
            case IMGVIEW_DRAG_MODE_ZOOM:
                /* Need to reset drag mode before redraw. */
                iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;

                /* Redraw. */
                ImgViewExposeCB(iv->view_da, NULL, iv);
            break;

              case IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE:
                /* Need to reset drag mode before processing. */
                iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;

            /* Zoom to rectangle. */
            ImgViewZoomRectangular(
                iv,
                (gint)button->x, iv->drag_zoom_rectangle_start_x,
                (gint)button->y, iv->drag_zoom_rectangle_start_y
            );

            /* Clip bounds, update adjustments and redraw. */
            ImgViewRealizeChange(iv);
            ImgViewExposeCB(iv->view_da, NULL, iv);
            break;

              case IMGVIEW_DRAG_MODE_CROP_RECTANGLE:
                /* Need to reset drag mode before processing. */
                iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;

                /* Crop rectangle should now be defined and in its
             * final state. Now map the crop dialog.
             */
            if(iv->crop_flags & IMGVIEW_CROP_ALLOWED)
            {
                /* Need to reset keyboard modifiers since the mapping
                 * of the crop dialog might catch any key releases.
                 */
                iv->modifiers = 0;

                /* Update current rectangle bounds. */
                    iv->crop_rectangle_cur_x = ImgViewConvertUnitViewToOrigX(
                        iv, (gint)button->x
                    );
                    iv->crop_rectangle_cur_y = ImgViewConvertUnitViewToOrigY(
                        iv, (gint)button->y
                    );

                /* Create crop dialog as needed. */
                if(iv->crop_dialog == NULL)
                  iv->crop_dialog = ImgViewCropDialogNew(iv);
                /* Map crop dialog with the new crop values. */
                    ImgViewCropDialogMapValues(
                        (imgview_crop_dialog_struct *)iv->crop_dialog,
                        iv->crop_rectangle_cur_x, iv->crop_rectangle_start_x,
                        iv->crop_rectangle_cur_y, iv->crop_rectangle_start_y
                    );
            }

                /* Redraw. */
                ImgViewExposeCB(iv->view_da, NULL, iv);
                break;
          }

          /* Cancel any drag modes whenever a button is released. */
          iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;

            /* Reset last drag positions on any button release. */
            iv->drag_last_x = (gint)button->x;
            iv->drag_last_y = (gint)button->y;

            /* Reset last drag rectangle position. */
          iv->drag_zoom_rectangle_start_x =
                iv->drag_zoom_rectangle_cur_x = (gint)button->x;
            iv->drag_zoom_rectangle_start_y =
                iv->drag_zoom_rectangle_cur_y = (gint)button->y;

          /* Do not reset last crop rectangle position. */

            /* Reset last motion event handled time. */
            iv->last_motion_time = 0;

            /* Reset cursor only if no key modifiers are held. */
            if((iv->modifiers == 0) && (window != NULL))
                gdk_window_set_cursor(window, NULL);

            status = TRUE;
            break;

          case GDK_MOTION_NOTIFY:
            motion = (GdkEventMotion *)event;
            if(motion->is_hint)
            {
                if(iv->drag_mode != IMGVIEW_DRAG_MODE_NONE)
                    gdk_window_get_pointer(
                        motion->window, &x, &y, &mask
                    );
            }
            else
            {
                x = motion->x;
                y = motion->y;
                mask = motion->state;
            }

/*
printf("Motion last:%i ev:%i ms\n",
 iv->last_motion_time, motion->time
);
 */
          /* Too soon to process motion event? */
          if(iv->last_motion_time > 0)
          {
/*
            if(iv->last_motion_time > motion->time)
                break;
 */
          }

          /* Initialize motion timmer. */
          motion_timmer = gdk_time_get();

          /* Handle by drag mode. */
          switch(iv->drag_mode)
          {
              case IMGVIEW_DRAG_MODE_TRANSLATE:
            if((xadj != NULL) && (yadj != NULL))
            {
                gint    dx = iv->drag_last_x - x,
                        dy = iv->drag_last_y - y;
                gdouble zoom = iv->view_zoom;
                const gdouble zoom_min = IMGVIEW_ZOOM_MIN;


                /* Sanitize current zoom. */
                if(zoom < zoom_min)
                  zoom = zoom_min;

                /* Translate. */
                xadj->value += (gfloat)dx / zoom;
                    yadj->value += (gfloat)dy / zoom;

                    /* Clip bounds and update adjustments. */
                ImgViewRealizeChange(iv);
                ImgViewExposeCB(iv->view_da, NULL, iv);
            }
            status = TRUE;
            break;

              case IMGVIEW_DRAG_MODE_ZOOM:
                if((xadj != NULL) && (yadj != NULL) &&
                   (tar_img != NULL) && (src_img != NULL)
            )
                {
                /* Zoom. */
                ImgViewZoomIterate(iv, iv->drag_last_y - y);

                /* Clip bounds, update adjustments and redraw. */
                    ImgViewRealizeChange(iv);
                ImgViewExposeCB(iv->view_da, NULL, iv);
                }
                status = TRUE;
                break;

            case IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE:
            if((tar_img != NULL) && (src_img != NULL))
            {
                GdkEventExpose ev;
                GdkRectangle *rect;


                /* Update current rectangle bounds. */
                iv->drag_zoom_rectangle_cur_x = x;
                iv->drag_zoom_rectangle_cur_y = y;

                /* Set up expose event for redraw of only the
                 * rectangular area.
                 */
                ev.type = GDK_EXPOSE;
                ev.window = motion->window;
                ev.count = 0;
                rect = &ev.area;

                rect->x = 0;
                rect->y = 0;
                    rect->width = tar_img->width;
                    rect->height = tar_img->height;

                /* Redraw. */
                ImgViewExposeCB(iv->view_da, &ev, iv);
            }
            status = TRUE;
            break;

              case IMGVIEW_DRAG_MODE_CROP_RECTANGLE:
                if((tar_img != NULL) && (src_img != NULL) &&
                   (iv->crop_flags & IMGVIEW_CROP_ALLOWED)
            )
                {
                    GdkEventExpose ev;
                    GdkRectangle *rect;

                /* Update current rectangle bounds. */
                iv->crop_rectangle_cur_x = ImgViewConvertUnitViewToOrigX(
                  iv, (gint)motion->x
                );
                    iv->crop_rectangle_cur_y = ImgViewConvertUnitViewToOrigY(
                        iv, (gint)motion->y
                    );

                    /* Set up expose event for redraw of only the
                     * rectangular area.
                     */
                    ev.type = GDK_EXPOSE;
                    ev.window = motion->window;
                    ev.count = 0;
                    rect = &ev.area;

                    rect->x = 0;
                    rect->y = 0;
                    rect->width = tar_img->width;
                    rect->height = tar_img->height;

                    /* Redraw. */
                    ImgViewExposeCB(iv->view_da, &ev, iv);
                }
                status = TRUE;
                break;
          }
            /* Reset last drag positions on any motion. */
            iv->drag_last_x = x;
            iv->drag_last_y = y;

          /* Update last motion event handled time. */
          iv->last_motion_time = motion->time +
            (gdk_time_get() - motion_timmer);
          break;
      }

      /* Return event handled status. */
      return(status);
}

/*
 *    View widget "expose_event" callback.
 *
 *    The given event expose can be NULL, if it is NULL then the image
 *    from original to view image will be blitted. Otherwise if expose is
 *    not NULL then the original to view image will not be updated.
 */
static gint ImgViewExposeCB(
      GtkWidget *widget, GdkEventExpose *expose, gpointer data
)
{
      GdkWindow *window;
      imgview_image_struct *img;
      GtkStyle *style;
      GtkWidget *w = widget;
        imgview_struct *iv = IMGVIEW(data);
        if((w == NULL) || (iv == NULL))
            return(FALSE);


      if(GTK_WIDGET_NO_WINDOW(w))
          return(TRUE);
      else
          window = w->window;
      if(window == NULL)
          return(TRUE);

      style = gtk_widget_get_style(w);
      if(style == NULL)
          return(TRUE);

      img = iv->view_img;
      if(img == NULL)
          return(TRUE);


      /* Begin updating offscreen original image to view image. */

      /* Blit original image data to target image data only if the event
       * expose is NULL.
       *
       * Because if expose is not NULL it implies this function was 
       * called as a signal handler and we can assume the target image 
       * was already blitted to represent the current data accuratly and
       * ready to be drawn to the view GdkWindow.
       *
       * Otherwise if expose is NULL it implies this function was called 
       * internally by another function and requires that the view's
       * image be updated (possibly that the position or zoom as changed
       * or needs to be reinitialized).
       */
      if(expose == NULL)
          ImgViewBlitView(iv);


      /* Begin updating view image to onscreen window. */

      /* At this point, if the on screen window is not mapped then there
       * is no reason to update the offscreen view image to the view's
       * on screen GdkWindow.
       *
       * Note that if expose is not NULL it implies the signal handler
       * called this function for a reason so we ignore the map state.
       */
      if(!iv->map_state && (expose == NULL))
          return(TRUE);

        /* Put blitted view image to view drawing area widget's window. */
      if(expose != NULL)
          ImgViewImageSendRectangle(
                img, (GdkDrawable *)window, style->fg_gc[GTK_STATE_NORMAL],
            iv->quality, &expose->area
            );
      else
          ImgViewImageSend(
            img, (GdkDrawable *)window, style->fg_gc[GTK_STATE_NORMAL],
            iv->quality
          );

      /* Check drag mode and draw superimposed graphics as needed. */
        /* Drag mode in zoom? */
      if(iv->drag_mode == IMGVIEW_DRAG_MODE_ZOOM)
        {
          GdkFont *font = iv->view_font;
            GdkGC       *gc = iv->view_selection_gc;
          imgview_image_struct *tar_img = iv->view_img;
          gdouble zoom = iv->view_zoom;

          /* Draw zoom value? */
          if(iv->show_values && (font != NULL) && (gc != NULL) &&
               (tar_img != NULL) && (zoom > 0.0)
          )
          {
            gchar s[256];
            gint lbearing, rbearing, width, ascent, descent;

            sprintf(
                s,
                "%.0f%%",
                zoom * 100
            );
            gdk_string_extents(
                font, s,
                &lbearing, &rbearing, &width,
                &ascent, &descent
            );
            gdk_draw_string(
                (GdkDrawable *)window, font, gc,
                (tar_img->width / 2) - (width / 2) + lbearing,
                (tar_img->height / 2) - ((ascent + descent) / 2) + ascent,
                s
            );
          }
      }
      /* Drag mode in zoom rectangle? */
      else if(iv->drag_mode == IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE)
      {
          GdkGC   *gc = iv->view_selection_gc;
          gint    x0 = iv->drag_zoom_rectangle_start_x,
                  x1 = iv->drag_zoom_rectangle_cur_x,
                  y0 = iv->drag_zoom_rectangle_start_y,
                  y1 = iv->drag_zoom_rectangle_cur_y,
                  rw, rh;
            gdouble     zoom = iv->view_zoom;
          GtkAdjustment *xadj = iv->view_x_adj,
                        *yadj = iv->view_y_adj;


          if(x0 > x1)
          {
            gint t = x1;
            x1 = x0;
            x0 = t;
          }
            if(y0 > y1)
            {
                gint t = y1;
                y1 = y0;
                y0 = t;
            }

          rw = x1 - x0;
          rh = y1 - y0;

          /* Enough data to draw rectangle? */
          if((rw > 0) && (rh > 0) && (gc != NULL) && (zoom > 0.0) &&
             (xadj != NULL) && (yadj != NULL)
          )
          {
            GdkFont *font = iv->view_font;
/* Note tx and ty are not entirly correct, need to work on them.
            gint  tx = xadj->value + (x0 / zoom),
                  ty = yadj->value + (y0 / zoom);
 */

            /* Draw `rectangular rubber band'. */
            gdk_draw_rectangle(
                (GdkDrawable *)window, gc,
                FALSE,
                x0, y0, rw, rh
            );
            /* Draw rectangle size? */
            if(iv->show_values && (font != NULL))
            {
                gchar s[256];
                gint lbearing, rbearing, width, ascent, descent;

                sprintf(
                  s,
                  "%ix%i",
                  (gint)((x1 - x0) / zoom),
                  (gint)((y1 - y0) / zoom)
                );
                gdk_string_extents(
                  font, s,
                  &lbearing, &rbearing, &width,
                  &ascent, &descent
                );
                gdk_draw_string(
                  (GdkDrawable *)window, font, gc,
                  x0 + ((x1 - x0) / 2) - (width / 2) + lbearing,
                  y0 + 2 + ascent,
                        s
                );
            }
          }
      }
        /* Crop rectangle defined? */
        else if(iv->crop_flags & IMGVIEW_CROP_DEFINED)
      {
            GdkGC *gc = iv->view_selection_gc;
            gint x0, x1, y0, y1, rw, rh;
          gint orig_x, orig_y, orig_width, orig_height;
            imgview_image_struct *tar_img = iv->view_img;


          /* Calculate crop rectangle bounds. */
          x0 = ImgViewConvertUnitOrigToViewX(
            iv, iv->crop_rectangle_start_x
          );
            x1 = ImgViewConvertUnitOrigToViewX(
                iv, iv->crop_rectangle_cur_x
            );
            y0 = ImgViewConvertUnitOrigToViewY(
                iv, iv->crop_rectangle_start_y
            );
            y1 = ImgViewConvertUnitOrigToViewY(
                iv, iv->crop_rectangle_cur_y
            );

            if(x0 > x1)
            {
                gint t = x1;
                x1 = x0;
                x0 = t;
            }
            if(y0 > y1)
            {
                gint t = y1;
                y1 = y0;
                y0 = t;
            }

          /* Get original (actual) geometry of crop rectangle. */
          orig_x = MIN(iv->crop_rectangle_start_x, iv->crop_rectangle_cur_x);
          orig_y = MIN(iv->crop_rectangle_start_y, iv->crop_rectangle_cur_y);
          orig_width = ABSOLUTE(
            iv->crop_rectangle_cur_x - iv->crop_rectangle_start_x
          );
            orig_height = ABSOLUTE(
                iv->crop_rectangle_cur_y - iv->crop_rectangle_start_y
            );

          if((gc != NULL) && (tar_img != NULL))
          {
            /* Sanitize crop bounds. */
            if(x0 < -1)
               x0 = -1;
            if(x1 < -1)
                   x1 = -1;
            if(y0 < -1)
                   y0 = -1;
            if(y1 < -1)
                   y1 = -1;

            if(x0 > (tar_img->width + 1))
                x0 = tar_img->width + 1;
                if(x1 > (tar_img->width + 1))
                    x1 = tar_img->width + 1;
                if(y0 > (tar_img->height + 1))
                    y0 = tar_img->height + 1;
                if(y1 > (tar_img->height + 1))
                    y1 = tar_img->height + 1;

            rw = x1 - x0;
            rh = y1 - y0;

                /* Crop rectangle has positive size? */
            if((rw > 0) && (rh > 0))
            {
                    GdkFont *font = iv->view_font;


                /* Draw crop rectangle `rubber band'. */
                gdk_draw_rectangle(
                  (GdkDrawable *)window, gc,
                  FALSE,
                  x0, y0, rw, rh
                );

                    /* Draw label? */
                    if(iv->show_values && (font != NULL))
                    {
                        gchar s[256];
                        gint lbearing, rbearing, width, ascent, descent;

                        sprintf(
                            s,
                            "%ix%i%s%i%s%i",
                            orig_width, orig_height,
                      (orig_x >= 0) ? "+" : "", orig_x,
                      (orig_y >= 0) ? "+" : "", orig_y
                        );
                        gdk_string_extents(
                            font, s,
                            &lbearing, &rbearing, &width,
                            &ascent, &descent
                         );
                        gdk_draw_string(
                            (GdkDrawable *)window, font, gc,
                            x0 + ((rw) / 2) - (width / 2) + lbearing,
                            y0 + 2 + ascent,
                            s
                        );
                    }
              }   /* Crop rectangle has positive size? */

          }
      }

      return(TRUE);
}

/*
 *    Rounds off the given value to the nearest int.
 */
static gint ImgViewRInt(gdouble x)
{
      return((gint)(x + 0.5));
}

/*
 *    Converts the given window coordinate image to the zoomed
 *    original image coordinate.
 */
static gint ImgViewConvertUnitViewToOrigX(
      imgview_struct *iv, gint x
)
{
      gdouble view_zoom;
      GtkAdjustment *adj;
      imgview_image_struct *src_img, *tar_img;
      gint sw;


      if(iv == NULL)
          return(x);

      src_img = iv->orig_img;
      tar_img = iv->view_img;
      adj = iv->view_x_adj;
      if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
          return(x);

      view_zoom = iv->view_zoom;
      if(view_zoom <= 0.0)
          return(x);

      sw = src_img->width * view_zoom;
      if(sw < tar_img->width)
          x -= (tar_img->width / 2) - (sw / 2);

      return((gint)(adj->value + (x / view_zoom)));
}

static gint ImgViewConvertUnitViewToOrigY(
        imgview_struct *iv, gint y
)
{
        gdouble view_zoom;
        GtkAdjustment *adj;
        imgview_image_struct *src_img, *tar_img;
      gint sh;

        if(iv == NULL)
            return(y);

        src_img = iv->orig_img;
        tar_img = iv->view_img;
        adj = iv->view_y_adj;
        if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
            return(y);

        view_zoom = iv->view_zoom;
        if(view_zoom <= 0.0)
            return(y);

        sh = src_img->height * view_zoom;
        if(sh < tar_img->height)
            y -= (tar_img->height / 2) - (sh / 2);

        return((gint)(adj->value + (y / view_zoom)));
}

/*
 *      Converts the given original image coordinate value to be relative
 *    to the view window.
 */
static gint ImgViewConvertUnitOrigToViewX(
        imgview_struct *iv, gint x
)
{
      gdouble view_zoom;
        GtkAdjustment *adj;
        imgview_image_struct *src_img, *tar_img;
      gint x_offset, sw;


        if(iv == NULL)
            return(x);

        src_img = iv->orig_img;
        tar_img = iv->view_img;
        adj = iv->view_x_adj;
        if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
            return(x);

        view_zoom = iv->view_zoom;
        if(view_zoom <= 0.0)
            return(x);

        sw = src_img->width * view_zoom;
        if(sw < tar_img->width)
            x_offset = (tar_img->width / 2) - (sw / 2);
      else
          x_offset = 0;

        return(
            (gint)((x - adj->value) * view_zoom) + x_offset
        );
}

static gint ImgViewConvertUnitOrigToViewY(
        imgview_struct *iv, gint y
)
{
        gdouble view_zoom;
        GtkAdjustment *adj;
        imgview_image_struct *src_img, *tar_img;
        gint y_offset, sh;


        if(iv == NULL)
            return(y);

        src_img = iv->orig_img;
        tar_img = iv->view_img;
        adj = iv->view_y_adj;
        if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
            return(y);

        view_zoom = iv->view_zoom;
        if(view_zoom <= 0.0)
            return(y);

        sh = src_img->height * view_zoom;
        if(sh < tar_img->height)
            y_offset = (tar_img->height / 2) - (sh / 2);
        else
            y_offset = 0;

        return(
            (gint)((y - adj->value) * iv->view_zoom) + y_offset
        );
}

/*
 *    Redraws the info_label GtkDrawingArea on the given image viewer.
 *
 *    This function should be called whenever the adjustments change
 *    or when the info_label is exposed.
 */
static void ImgViewInfoLabelDraw(imgview_struct *iv)
{
      gint border_minor = 2;
        GtkAdjustment *xadj, *yadj;
      GtkWidget *w;
      imgview_image_struct *src_img;


        if(iv == NULL)
            return;

      /* Do not draw if image viewer is not mapped. */
      if(!iv->map_state)
          return;

        xadj = iv->view_x_adj;
        yadj = iv->view_y_adj;
      src_img = iv->orig_img;
      if((xadj == NULL) || (yadj == NULL) || (src_img == NULL))
      {
          /* No adjustments or original image data, so just clear
           * the info label's GdkWindow.
           */
            w = iv->info_label;
            if((w != NULL) ? !GTK_WIDGET_NO_WINDOW(w) : 0)
          {
            GdkWindow *window = w->window;
            if(window != NULL)
                gdk_window_clear(window);
          }
          return;
      }

        /* Begin redrawing info label. */
        w = iv->info_label;
        if((w != NULL) ? !GTK_WIDGET_NO_WINDOW(w) : 0)
        {
          GtkStyle *style = gtk_widget_get_style(w);
          GdkWindow *window = w->window;
          GdkFont *font = iv->font;
          gchar text[256];

          /* Enough resources to draw to info label's GdkWindow? */
          if((style != NULL) && (window != NULL) && (font != NULL))
          {
                gint lbearing, rbearing, width, ascent, descent;
            gint state = GTK_STATE_NORMAL;
            GdkGC *gc;


            gc = style->text_gc[state];
            if(gc == NULL)
                gc = style->fg_gc[state];
            if(gc == NULL)
                gc = style->black_gc;

            if((src_img == NULL) || !iv->show_values)
                (*text) = '\0';
            else
                sprintf(
                  text, "%ix%i%s%i%s%i",
                  (gint)(xadj->upper - xadj->lower),
                  (gint)(yadj->upper - yadj->lower),
                  ((gint)xadj->value < 0) ? "" : "+",
                  (gint)xadj->value,
                  ((gint)yadj->value < 0) ? "" : "+",
                  (gint)yadj->value
                );

            if(gc != NULL)
            {
                    gdk_string_extents(
                        font, text,
                        &lbearing, &rbearing, &width,
                        &ascent, &descent
                    );
                gdk_window_clear(window);
                    gdk_draw_string(
                        (GdkDrawable *)window, font, gc,
                        border_minor + lbearing,
                        (w->allocation.height / 2) -
                      ((ascent + descent) / 2) + ascent,
                  text
                );
            }
          }
        }   /* Redraw info label. */
}

/*
 *    Clips the given image viewer's translate bounds, updates the
 *    adjustments, and emits a "changed" signal for the adjustments.
 *
 *    This function should be called whenever changes are made to
 *    the view, ie when the view's translation or zoom has changed or
 *    when a new image has been loaded.
 */
static void ImgViewRealizeChange(imgview_struct *iv)
{
      gdouble zoom;
      gint src_vw, src_vh;
      imgview_image_struct *tar_img, *src_img;
      GtkAdjustment *xadj, *yadj;


      if(iv == NULL)
          return;

      tar_img = iv->view_img;
      src_img = iv->orig_img;
      zoom = iv->view_zoom;
      xadj = iv->view_x_adj;
      yadj = iv->view_y_adj;

      /* Must have adjustments. */
      if((xadj == NULL) || (yadj == NULL))
          return;

      if((tar_img == NULL) || (zoom <= 0.0))
      {
          xadj->value = 0.0;
          yadj->value = 0.0;
          return;
      }

      /* If no source image, then set adjustments to size of
       * target image.
       */
      if(src_img == NULL)
      {
          xadj->value = 0.0;
          xadj->lower = 0.0;
          xadj->upper = (gfloat)tar_img->width;
          xadj->page_increment = 0.0;
          xadj->page_size = (gfloat)tar_img->width;

            yadj->value = 0.0;
            yadj->lower = 0.0;
            yadj->upper = (gfloat)tar_img->height;
          yadj->page_increment = 0.0;
            yadj->page_size = (gfloat)tar_img->height;
      }
      else
      {
          /* Calculate the size of the viewed area on the source image. */
          src_vw = tar_img->width / zoom;
          src_vh = tar_img->height / zoom;

          /* Set bounds for adjustments to match source image size. */
          xadj->lower = 0.0;
          xadj->upper = src_img->width;
          yadj->lower = 0.0;
          yadj->upper = src_img->height;

          /* Width of viewed source smaller than target width? */
          if((src_img->width * zoom) < tar_img->width)
          {
            xadj->value = 0.0;
            xadj->page_size = (gfloat)src_img->width;
          }
          else
          {
            if(xadj->value > (gfloat)(src_img->width - src_vw))
                xadj->value = (gfloat)(src_img->width - src_vw);
            if(xadj->value < 0.0)
                xadj->value = 0.0;
            xadj->page_size = (gfloat)src_vw;
            xadj->page_increment = (gfloat)src_vw * xadj->page_size;
          }

          /* Height of viewed source smaller than target height? */
          if((src_img->height * zoom) < tar_img->height)
          {
            yadj->value = 0.0;
            yadj->page_size = (gfloat)src_img->height;
          }
          else
          {
            if(yadj->value > (gfloat)(src_img->height - src_vh))
                yadj->value = (gfloat)(src_img->height - src_vh);
            if(yadj->value < 0.0)
                yadj->value = 0.0;
            yadj->page_size = (gfloat)src_vh;
                yadj->page_increment = (gfloat)src_vh * yadj->page_size;
          }
        }

      /* Update info label. */
      ImgViewInfoLabelDraw(iv);

      /* Send "changed" signal to adjustments so the scrollbars are
       * updated.
       */
      gtk_signal_emit_by_name(GTK_OBJECT(xadj), "changed");
      gtk_signal_emit_by_name(GTK_OBJECT(yadj), "changed");
}

/*
 *    Zoom itteration.
 *
 *    Zooms in or out depending on the value of dz in units
 *    of pixels. Positive dz will zoom out while negative dz will
 *    zoom in.
 *
 *    No bounds will be cliped and view is not redrawn, the calling 
 *    function is responsible for that.
 */
static void ImgViewZoomIterate(
      imgview_struct *iv, gint dz
)
{
      const gdouble     zoom_min = IMGVIEW_ZOOM_MIN,
                  zoom_max = IMGVIEW_ZOOM_MAX,
                  zoom_rate = IMGVIEW_ZOOM_RATE;
      imgview_image_struct *tar_img;
      GtkAdjustment *xadj, *yadj;
      gint p_vw, p_vh, n_vw, n_vh;
      gdouble     new_zoom,
            prev_zoom = iv->view_zoom;


      if(iv == NULL)
          return;

      if(dz == 0)
          return;

      xadj = iv->view_x_adj;
      yadj = iv->view_y_adj;
      tar_img = iv->view_img;
      if((xadj == NULL) || (yadj == NULL) || (tar_img == NULL))
          return;

      /* Update zoom. */
      iv->view_zoom -= ((gfloat)dz * zoom_rate);
      if(iv->view_zoom < zoom_min)
          iv->view_zoom = zoom_min;
      new_zoom = iv->view_zoom;

      /* Sanitize previous zoom. */
      if(prev_zoom > zoom_max)
          prev_zoom = zoom_max;
      else if(prev_zoom < zoom_min)
          prev_zoom = zoom_min;

      /* Calculate previous and new viewable dimensions on the source
       * image.
       */
      p_vw = tar_img->width / prev_zoom;
      p_vh = tar_img->height / prev_zoom;
      n_vw = tar_img->width / new_zoom;
      n_vh = tar_img->height / new_zoom;

      /* Adjust translation due to zooming. */
      xadj->value += (p_vw / 2) - (n_vw / 2);
      yadj->value += (p_vh / 2) - (n_vh / 2);
}

/*
 *    Rectangular zoom.
 *
 *    Zooms into the defined rectangle from the given inputs.
 *
 *    No bounds will be cliped and view is not redrawn, the calling
 *      function is responsible for that.
 */
static void ImgViewZoomRectangular(
        imgview_struct *iv,
        gint x0, gint x1,
        gint y0, gint y1
)
{
        gint view_width, view_height;
      gint src_width, src_height;
      gint rw, rh, rcx, rcy;
        imgview_image_struct *tar_img, *src_img;
        GtkAdjustment *xadj, *yadj;
      const gdouble     zoom_min = IMGVIEW_ZOOM_MIN,
                  zoom_max = IMGVIEW_ZOOM_MAX;
        gdouble new_zoom,
                prev_zoom = iv->view_zoom;


        if(iv == NULL)
            return;

        if((x0 == x1) || (y0 == y1))
            return;

      if(prev_zoom <= 0.0)
          return;

        xadj = iv->view_x_adj;
        yadj = iv->view_y_adj;
        tar_img = iv->view_img;
      src_img = iv->orig_img;
        if((xadj == NULL) || (yadj == NULL) ||
           (tar_img == NULL) || (src_img == NULL)
      )
            return;

      if((tar_img->width < 1) || (tar_img->height < 1))
          return;


      /* Flip rectangle coordinates as needed. */
      if(x0 > x1)
      {
          gint t = x1;
          x1 = x0;
          x0 = t;
      }
        if(y0 > y1)
        {
            gint t = y1;
            y1 = y0;
            y0 = t;
        }

      /* Calculate zoomed rectangle size. */
      rw = (x1 - x0) / prev_zoom;
      rh = (y1 - y0) / prev_zoom;

      if((rw < 1) || (rh < 1))
          return;

        /* Get size of view in window coordinates. */
        view_width = tar_img->width;
        view_height = tar_img->height;

      /* Get size of original image in window coordinates. */
      src_width = src_img->width;
      src_height = src_img->height;

      /* Remove centering offset from rectangle coordinates. */
      x0 += (gint)MIN(((src_width * prev_zoom) - view_width) / 2, 0);
        x1 += (gint)MIN(((src_width * prev_zoom) - view_width) / 2, 0);
        y0 += (gint)MIN(((src_height * prev_zoom) - view_height) / 2, 0);
        y1 += (gint)MIN(((src_height * prev_zoom) - view_height) / 2, 0);

      /* Calculate zoomed rectangle center (be sure to discard offset
       * used to center image on view if source image is smaller).
       */
      rcx = (gint)(xadj->value + ((gfloat)x0 / prev_zoom) + ((gfloat)rw / 2));
        rcy = (gint)(yadj->value + ((gfloat)y0 / prev_zoom) + ((gfloat)rh / 2));

      /* Calculate new zoom coefficient. */
      new_zoom = (gdouble)view_width / (gdouble)rw;
      if(((gfloat)view_height / new_zoom) < rh)
          new_zoom = (gdouble)view_height / (gdouble)rh;

      /* Check if new zoom is out of bounds, if it is then skip. */
      if((new_zoom < zoom_min) || (new_zoom > zoom_max))
          return;

      /* Update zoom. */
      iv->view_zoom = new_zoom;

      /* Update translation. */
      xadj->value = (gfloat)(rcx - ((gfloat)view_width / new_zoom / 2));
        yadj->value = (gfloat)(rcy - ((gfloat)view_height / new_zoom / 2));
}


/*
 *    Blits the image viewer's orig_img to view_img using the given image
 *    viewer's translation and zoom.
 */
static void ImgViewBlitView(imgview_struct *iv)
{
      GtkAdjustment *xadj, *yadj;
      GtkStyle *style;
      GtkWidget *w;
      imgview_image_struct *src_img, *tar_img;
      guint alpha_channel_flags;
      guint32 bg_pix32 = 0xffffffff;
      guint depth;                  /* Actual depth. */

      gint  src_sk_col,       /* Skips, in * 256. */
            src_sk_row;
      gint  src_cx, src_cy,         /* Cur pos, in * 256. */
            src_x, src_y,           /* Restart pos, in * 256. */
            src_w, src_h,
            src_tw, src_th;
      gint  tar_cx, tar_cy,         /* Cur pos, in * 256. */
            tar_x, tar_y,           /* Restart pos, in * 256. */
            tar_w, tar_h,
            tar_tw, tar_th;
      gdouble zoom;


      if(iv == NULL)
          return;

      w = iv->view_da;
      if(w == NULL)
          return;

      xadj = iv->view_x_adj;
      yadj = iv->view_y_adj;
      if((xadj == NULL) || (yadj == NULL))
          return;

      alpha_channel_flags = iv->alpha_channel_flags;

      /* Get background pixel. */
      style = gtk_widget_get_style(w);
      if(style != NULL)
      {
          GdkColor *c = &style->base[GTK_STATE_NORMAL];
          bg_pix32 = ((guint32)0xff000000) +
            (((guint32)c->blue & 0x0000ff00) << 8) +
            (((guint32)c->green & 0x0000ff00)) +
            (((guint32)c->red & 0x0000ff00) >> 8)
          ;
      }

      /* Get pointers to source and target images. */
      src_img = iv->orig_img;
      tar_img = iv->view_img;
      if(tar_img == NULL)
          return;

        /* Get depth, use target image. */
        depth = ((guint)tar_img->bpp << 3);
        if(depth < 1)
            return;

      /* If no source image then just clear target image. */
      if(src_img == NULL)
      {
          ImgViewImageClear(tar_img, bg_pix32);
          return;
      }


      /* Actual depth of both images must be the same! */
      if(src_img->bpp != tar_img->bpp)
          return;

      /* Make sure both images have allocated image buffers. */
      if((src_img->mem == NULL) || (tar_img->mem == NULL))
          return;

      /* Get zoom. */
      zoom = iv->view_zoom;
      if(zoom <= 0.0)
          return;

      /* Column and row skips in units of 256. */
      src_sk_col = (gint)ImgViewRInt(256.0 / zoom);
      src_sk_row = (gint)ImgViewRInt(256.0 / zoom);

      /* Calculate source and target allocation bounds, in units of
       * 256.
       */
      src_tw = (gint)src_img->width * 256;
      src_th = (gint)src_img->height * 256;
      tar_tw = (gint)tar_img->width * 256;
      tar_th = (gint)tar_img->height * 256;

      /* Calculate visible source bounds based on the target bounds
       * with zoom applied, in units of 256.
       */
      src_w = (gint)(tar_tw / zoom);
        src_h = (gint)(tar_th / zoom);
      /* Visible source bounds cannot exceed allocation size in units
       * of 256.
       */
      if(src_w > src_tw)
          src_w = src_tw;
      if(src_h > src_th)
          src_h = src_th;

      /* Prepare target starting points and bounds. */
      tar_w = (gint)(((src_tw * zoom) < tar_tw) ? src_tw * zoom : tar_tw);
      tar_h = (gint)(((src_th * zoom) < tar_th) ? src_th * zoom : tar_th);

      tar_x = (tar_w < tar_tw) ? (tar_tw / 2) - (tar_w / 2) : 0;
      if(tar_x < 0)
          tar_x = 0;
        tar_y = (tar_h < tar_th) ? (tar_th / 2) - (tar_h / 2) : 0;
      if(tar_y < 0)
          tar_y = 0;

      tar_w += tar_x;         /* Offset target itteration bounds. */
      tar_h += tar_y;


      /* Prepare source starting points and bounds. */
      src_x = (gint)(xadj->value * 256.0);
      src_x = ((src_x + src_w) > src_tw) ? src_tw - src_w : src_x;
      if(src_x < 0)
          src_x = 0;
        src_y = (gint)(yadj->value * 256.0);
        src_y = ((src_y + src_h) > src_th) ? src_th - src_h : src_y;
        if(src_y < 0)
          src_y = 0;

      src_w += src_x;         /* Offset source itteration bounds. */
      src_h += src_y;


      /* Set source and target starting positions in units of 256. */
      src_cx = src_x;
      src_cy = src_y;
      tar_cx = tar_x;
      tar_cy = tar_y;


        /* Need to clear target image if zoom causes source image to be
       * displayed smaller than the target image.
       */
        if(((src_tw * zoom) < tar_tw) || ((src_th * zoom) < tar_th))
          ImgViewImageClear(tar_img, bg_pix32);

#define DEBUG_CHECK_BOUNDS    \
{ \
 if((src_cx < 0) || ((src_cx >> 8) >= src_img->width)) \
  printf("Source segfault X %i(%i)\n", (src_cx >> 8), src_img->width); \
 if((src_cy < 0) || ((src_cy >> 8) >= src_img->height)) \
  printf("Source segfault Y %i(%i)\n", (src_cy >> 8), src_img->height); \
 if((tar_cx < 0) || ((tar_cx >> 8) >= tar_img->width)) \
  printf("Target segfault X %i(%i)\n", (tar_cx >> 8), tar_img->width); \
 if((tar_cy < 0) || ((tar_cy >> 8) >= tar_img->height)) \
  printf("Target segfault Y %i(%i)\n", (tar_cy >> 8), src_img->height); \
}

      /* 8 bits. */
      if(depth == 8)
      {
          gint          src_bpp = src_img->bpp,
                        tar_bpp = tar_img->bpp;
          gint          src_bpl = src_img->bpl,
                        tar_bpl = tar_img->bpl;
          const guint8  *src_ptr,
                        *src_buf = (const guint8 *)src_img->mem;
            guint8            *tar_ptr,
                        *tar_buf = (guint8 *)tar_img->mem;


            while((src_cy < src_h) &&
                  (tar_cy < tar_h)
            )
            {
                src_ptr = &(src_buf[
                    ((src_cy >> 8) * src_bpl) +
                    ((src_cx >> 8) * src_bpp)
                ]);
                tar_ptr = &(tar_buf[
                    ((tar_cy >> 8) * tar_bpl) +
                    ((tar_cx >> 8) * tar_bpp)
                ]);
                *(guint8 *)tar_ptr = *(guint8 *)src_ptr;

                /* Increment target x colum. */
                tar_cx += 256;
                /* Go to next target line? */
                if(tar_cx >= tar_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;
        
                    src_cy += src_sk_row;
                    src_cx = src_x;
                continue;
                }

                /* Increment source x column. */
                src_cx += src_sk_col;
                /* Go to next source line? */
                if(src_cx >= src_w)  
                {
                    tar_cy += 256;
                    tar_cx = tar_x;
         
                    src_cy += src_sk_row;
                    src_cx = src_x;
                continue;
                }
            }
      }
      /* 15 or 16 bits. */
      else if((depth == 15) || (depth == 16))
      {
          gint          src_bpp = src_img->bpp,
                        tar_bpp = tar_img->bpp;
          gint          src_bpl = src_img->bpl,
                        tar_bpl = tar_img->bpl;
            const guint8      *src_ptr,
                                *src_buf = (const guint8 *)src_img->mem;
          guint8              *tar_ptr,
                                *tar_buf = (guint8 *)tar_img->mem;


          while((src_cy < src_h) &&
                  (tar_cy < tar_h)
          )
          {
            src_ptr = &(src_buf[
                ((src_cy >> 8) * src_bpl) +
                ((src_cx >> 8) * src_bpp)
            ]);
            tar_ptr = &(tar_buf[
                    ((tar_cy >> 8) * tar_bpl) +
                    ((tar_cx >> 8) * tar_bpp)
                ]);
            *(guint16 *)tar_ptr = *(guint16 *)src_ptr;

                /* Increment target x column. */
                tar_cx += 256;
                /* Go to next target line? */
                if(tar_cx >= tar_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;

                src_cy += src_sk_row;
                src_cx = src_x;
                continue;
                }

            /* Increment source x column. */
            src_cx += src_sk_col;
                /* Go to next source line? */
            if(src_cx >= src_w)
            {
                    tar_cy += 256;
                    tar_cx = tar_x;

                src_cy += src_sk_row;
                src_cx = src_x;
                continue;
            }
          }
      }
      /* 24 bits. */
      else if(depth == 24)
      {
            gint                src_bpp = src_img->bpp,
                                tar_bpp = tar_img->bpp;
            gint                src_bpl = src_img->bpl,
                                tar_bpl = tar_img->bpl;
            const guint8      *src_ptr,
                                *src_buf = (const guint8 *)src_img->mem;
            guint8            *tar_ptr,
                                *tar_buf = (guint8 *)tar_img->mem;

            while((src_cy < src_h) &&
                  (tar_cy < tar_h)
            )
            {
                src_ptr = &(src_buf[
                    ((src_cy >> 8) * src_bpl) +
                    ((src_cx >> 8) * src_bpp)
                ]);
                tar_ptr = &(tar_buf[
                    ((tar_cy >> 8) * tar_bpl) +
                    ((tar_cx >> 8) * tar_bpp)
                ]);
                *tar_ptr++ = *src_ptr++;
                *tar_ptr++ = *src_ptr++;
                *tar_ptr++ = *src_ptr++;

                /* Increment target x column. */
                tar_cx += 256;
                /* Go to next target line? */
                if(tar_cx >= tar_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;

                    src_cy += src_sk_row;
                    src_cx = src_x;
                    continue;
                }

                /* Increment source x column. */
                src_cx += src_sk_col;
                /* Go to next source line? */
                if(src_cx >= src_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;

                    src_cy += src_sk_row;
                    src_cx = src_x;
                    continue;
                }
            }
        }
      /* 32 bits. */
      else if(depth == 32)
      {
          gint          src_bpp = src_img->bpp,
                        tar_bpp = tar_img->bpp;
            gint                src_bpl = src_img->bpl,
                                tar_bpl = tar_img->bpl;
            const guint8      *src_ptr,
                                *src_buf = (const guint8 *)src_img->mem;
            guint8            *tar_ptr,
                                *tar_buf = (guint8 *)tar_img->mem;

          /* Has alpha channel with non-uniform values? */
          if(alpha_channel_flags & IMGVIEW_ALPHA_DEFINED)
          {
            gfloat src_alpha_coeff;

              while((src_cy < src_h) &&
                    (tar_cy < tar_h)
              )
              {
                src_ptr = &(src_buf[
                    ((src_cy >> 8) * src_bpl) +
                    ((src_cx >> 8) * src_bpp)
                ]);
                tar_ptr = &(tar_buf[
                    ((tar_cy >> 8) * tar_bpl) +
                    ((tar_cx >> 8) * tar_bpp)
                ]);

#ifdef USE_BIG_ENDIAN
#define SWAB16(a) ( (((guint16)(a) << 8) & 0xFF00) |  \
                    (((guint16)(a) >> 8) & 0x00FF)    \
                  )
#define SWAB32(a) ( (SWAB16(((a) >> 16) & 0xFFFF) << 0) |   \
                    (SWAB16((a) & 0xFFFF) << 16)            \
                  )

            *(guint32 *)src_ptr = SWAB32(*(guint32 *)src_ptr);
#endif      /* USE_BIG_ENDIAN */

            /* Calculate alpha channel value coefficient. */
            if(alpha_channel_flags & IMGVIEW_ALPHA_INVERTED)
                src_alpha_coeff = (gfloat)1.0 - (gfloat)(
                  ((*(guint32 *)src_ptr) & 0xff000000) >> 24
                ) / (gfloat)0x000000ff;
            else
                    src_alpha_coeff = (gfloat)(
                        ((*(guint32 *)src_ptr) & 0xff000000) >> 24
                    ) / (gfloat)0x000000ff;

#ifdef USE_BIG_ENDIAN
            *(guint32 *)src_ptr = SWAB32(*(guint32 *)src_ptr);
#endif      /* USE_BIG_ENDIAN */

            if(src_alpha_coeff >= 1.0)
            {
                *(guint32 *)tar_ptr = *(guint32 *)src_ptr;
            }
            else if(src_alpha_coeff <= 0.0)
            {
                *(guint32 *)tar_ptr = bg_pix32;
            }
            else
            {
                const gfloat tar_alpha_coeff = 1.0 - src_alpha_coeff;
                const guint8 *bg_ptr = (const guint8 *)&bg_pix32;

                *tar_ptr++ = (*bg_ptr++ * tar_alpha_coeff) +
                  (*src_ptr++ * src_alpha_coeff);
                    *tar_ptr++ = (*bg_ptr++ * tar_alpha_coeff) +
                        (*src_ptr++ * src_alpha_coeff);
                    *tar_ptr++ = (*bg_ptr++ * tar_alpha_coeff) +
                        (*src_ptr++ * src_alpha_coeff);
                    *tar_ptr = (*bg_ptr * tar_alpha_coeff) +
                        (*src_ptr * src_alpha_coeff);
            }

                /* Increment target x column. */
                tar_cx += 256;
                /* Go to next target line? */
                if(tar_cx >= tar_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;

                    src_cy += src_sk_row;
                    src_cx = src_x;
                continue;
                }

                /* Increment source x column. */
                src_cx += src_sk_col;
                /* Go to next source line? */
                if(src_cx >= src_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;

                    src_cy += src_sk_row;
                    src_cx = src_x;
                continue;
                }
            }
            }
          else
          {
              while((src_cy < src_h) &&
                    (tar_cy < tar_h)
              )
              {
                src_ptr = &(src_buf[
                    ((src_cy >> 8) * src_bpl) +
                    ((src_cx >> 8) * src_bpp)
                ]);
                tar_ptr = &(tar_buf[
                    ((tar_cy >> 8) * tar_bpl) +
                    ((tar_cx >> 8) * tar_bpp)
                ]);
                *(guint32 *)tar_ptr = *(guint32 *)src_ptr;


                /* Increment target x column. */
                tar_cx += 256;
                /* Go to next target line? */
                if(tar_cx >= tar_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;

                    src_cy += src_sk_row;
                    src_cx = src_x;
                    continue;
                }

                /* Increment source x column. */
                src_cx += src_sk_col;
                /* Go to next source line? */
                if(src_cx >= src_w)
                {
                    tar_cy += 256;
                    tar_cx = tar_x;

                    src_cy += src_sk_row;
                    src_cx = src_x;
                    continue;
                }
            }
          } /* Has alpha channel? */
      }

#undef DEBUG_CHECK_BOUNDS
}

/*
 *    Updates the contents of the WM icon (if any) on the given
 *    image viewer. If the image viewer's toplevel_is_window is FALSE
 *    then there will be no WM icon to update.
 *
 *    If iv->show_image_on_wm_icon is FALSE then this call has no
 *    affect.
 */
static void ImgViewWMIconUpdate(imgview_struct *iv)
{
      const gint  width = IMGVIEW_WM_ICON_WIDTH,
                  height = IMGVIEW_WM_ICON_HEIGHT;
      gint x, y, depth;
      GdkVisual *vis;
      GdkWindow *window;
      GdkBitmap *mask = NULL;
      GdkPixmap *pixmap = NULL;
        GtkWidget *w;
      GtkStyle *style;
      gchar *bm_data, *bm_ptr;
        imgview_image_struct *src_img;


        if(iv == NULL)
            return;

      /* Skip if image viewer's toplevel widget is not a GtkWindow or
       * we do not want to show image on wm icon.
       */
      if(!iv->toplevel_is_window || !iv->show_image_on_wm_icon)
          return;

      /* Icon size must be positive. */
        if((width < 1) || (height < 1))
            return;

      /* Get toplevel widget's GdkWindow. */
      w = iv->toplevel;
      if((w == NULL) ? 1 : GTK_WIDGET_NO_WINDOW(w))
          return;
      else
          window = w->window;
      if(window == NULL)
          return;

      /* Get depth of window. */
      vis = gdk_window_get_visual(window);
      depth = ((vis != NULL) ? vis->depth : -1);


      /* Calculate size of bitmap in bits. */
/*
      if(width % 8)
          bm_width = (gint)(width / 8) + 1;
      else
          bm_width = (gint)(width / 8) + 1;
      bm_height = height;
 */

      /* Allocate bitmap data. */
      bm_data = (gchar *)g_malloc(width * height);
      if(bm_data != NULL)
      {
          /* Set bitmap data. */
          for(y = 0; y < height; y++)
          {
            for(x = 0; x < width; x++)
            {
                bm_ptr = &(bm_data[
                  (y * width) + x
                ]);
                *bm_ptr = 0xff;
            }
          }

          /* Create new WM icon mask. */
          mask = gdk_bitmap_create_from_data(
            window, bm_data, width, height
          );

          /* Deallocate bitmap data. */
          g_free(bm_data);
          bm_data = NULL;
      }




      /* Create new WM icon pixmap. */
      pixmap = gdk_pixmap_new(
          window, width, height, depth
      );
      src_img = iv->orig_img;
      style = gtk_widget_get_style(w);
      if(style != NULL)
      {
          /* Create tempory target image. */
          imgview_image_struct *tar_img = ImgViewImageNew(
            width, height, depth
          );

          /* Clear pixmap if source image is NULL. */
          if((src_img == NULL) && (tar_img != NULL))
          {
            /* No source image, so just clear it. */
            ImgViewImageClear(tar_img, 0xffffffff);
          }
          else if((src_img != NULL) && (tar_img != NULL))
          {
            /* Got source image, now copy and resize the source image
             * buffer to the target image.
             */
            GUIResizeBuffer(
                tar_img->bpp,       /* Tar/src bpp should be same. */
                src_img->mem,       /* Source buffer. */
                src_img->width, src_img->height, src_img->bpl,
                tar_img->mem,       /* Target buffer. */
                tar_img->width, tar_img->height, tar_img->bpl
            );
          }

          /* Put target image to the WM icon pixmap. */
          ImgViewImageSend(
            tar_img,
            (GdkDrawable *)pixmap,
            style->fg_gc[GTK_STATE_NORMAL],
            iv->quality
          );

          /* Delete tempory target image. */
          gdk_flush();
          ImgViewImageDelete(tar_img);
      }


        /* Update WM icon for our toplevel window's GdkWindow. */
      if((mask != NULL) && (pixmap != NULL))
          gdk_window_set_icon(
            window,
            NULL,       /* WM icon GdkWindow. */
            pixmap,           /* WM icon GdkPixmap. */
            mask        /* WM icon GdkBitmap. */
          );
      /* Flush output to make sure icon is changed. */
      gdk_flush();

        /* Unref old icon mask and pixmap. */
        if(iv->wm_icon_mask != NULL)
            gdk_bitmap_unref(iv->wm_icon_mask);
        if(iv->wm_icon_pixmap != NULL)
            gdk_pixmap_unref(iv->wm_icon_pixmap);

      /* Record new mask and pixmap. */
      iv->wm_icon_mask = mask;
      iv->wm_icon_pixmap = pixmap;

}


/*
 *    Recreates the image view buffer by checking the visual and the size of
 *    the view translation and geometry.
 *
 *    This function should be called on resizes and zoom changes on the view
 *    drawing area widget.
 *
 *    On failure, the iv's view_img will be NULL.
 */
static void ImgViewBufferRecreate(imgview_struct *iv)
{
      gint width, height, depth;
      GdkVisual *vis;
      GdkWindow *window;
      GtkWidget *w;
      imgview_image_struct *img;


        if(iv == NULL)
            return;

      /* Destroy old image. */
      if(iv->view_img != NULL)
      {
          gdk_flush();
          ImgViewImageDelete(iv->view_img);
          iv->view_img = NULL;
      }

      /* Get view drawing area and visual (both must be valid). */
      w = iv->view_da;
      if(w == NULL)
          return;

      /* Get drawing area widget's GdkWindow. */
      if(GTK_WIDGET_NO_WINDOW(w))
          return;
      else
          window = w->window;
      if(window == NULL)
          return;

      /* Get depth of GdkWindow. */
      vis = gdk_window_get_visual(window);
      depth = ((vis != NULL) ? vis->depth : -1);

      /* Get size that we need to recreate the view buffer image as. */
      width = w->allocation.width;
      height = w->allocation.height;
      if((width < 1) || (height < 1))
          return;

      /* Create new view buffer image. */
      iv->view_img = img = ImgViewImageNew(
          width, height, depth
      );
      if(img == NULL)
          return;
}

/*
 *    Returns the GtkAccelGroup for the image viewer if available.
 */
GtkAccelGroup *ImgViewGetAccelGroup(imgview_struct *iv)
{
        return((iv != NULL) ? iv->accelgrp : NULL);
}

/*
 *    Returns TRUE if the image viewer's toplevel widget is a
 *    GtkWindow otherwise it is a GtkVBox.
 */
gboolean ImgViewToplevelIsWindow(imgview_struct *iv)
{
      return((iv != NULL) ? iv->toplevel_is_window : FALSE);
}

/*
 *    Returns the toplevel widget, this may be either a GtkVBox or
 *    GtkWindow depending on what iv->toplevel_is_window says.
 */
GtkWidget *ImgViewGetToplevelWidget(imgview_struct *iv)
{
        return((iv != NULL) ? iv->toplevel : NULL);
}

/*
 *    Returns the view drawing area widget.
 */
GtkDrawingArea *ImgViewGetViewWidget(imgview_struct *iv)
{
      return((GtkDrawingArea *)((iv != NULL) ? iv->view_da : NULL));
}

/*
 *    Returns the menu widget.
 */
GtkMenu *ImgViewGetMenuWidget(imgview_struct *iv)
{
        return((GtkMenu *)((iv != NULL) ? iv->menu : NULL));
}

/*
 *    Returns TRUE if there is an image loaded on the given image
 *    viewer.
 */
gboolean ImgViewIsLoaded(imgview_struct *iv)
{
      if(iv == NULL)
          return(FALSE);

        return((iv->orig_img != NULL) ? TRUE : FALSE);
}

/*
 *      Returns the loaded image, can return NULL if there is no
 *    image loaded.
 */
imgview_image_struct *ImgViewGetImage(imgview_struct *iv)
{
        return((iv != NULL) ? iv->orig_img : NULL);
}

/*
 *    Returns the pointer to the loaded image data.
 *
 *    May return NULL if no image is currently loaded.
 *    The format is always returned as IMGVIEW_FORMAT_RGBA.
 */
guint8 *ImgViewGetImageData(
        imgview_struct *iv,
        gint *width, gint *height, gint *bpl, gint *bpp,
        gint *format
)
{
      imgview_image_struct *img;


      if(width != NULL)
          (*width) = 0;
        if(height != NULL)
            (*height) = 0;
        if(bpl != NULL)
            (*bpl) = 0;
        if(bpp != NULL)
            (*bpp) = 0;
        if(format != NULL)
            (*format) = IMGVIEW_FORMAT_RGBA;;

        if(iv == NULL)
            return(NULL);

      img = iv->orig_img;
      if(img == NULL)
          return(NULL);

      if(width != NULL)
            (*width) = img->width;
        if(height != NULL)
            (*height) = img->height;
        if(bpl != NULL)
            (*bpl) = img->bpl;
        if(bpp != NULL)
            (*bpp) = img->bpp;

      return(img->mem);
}



/*
 *    Deallocates the orig_img on the image viewer and redraws.
 *
 *    This basically flushes GDK, unloads the image, resets zoom and 
 *    translations, and resets alpha channel flags.
 *
 *    WM icon will be updated, menus will be updated, and view will
 *    be redrawn.
 */
void ImgViewClear(imgview_struct *iv)
{
      gboolean image_unloaded = FALSE;
      GtkAdjustment *adj;


      if(iv == NULL)
            return;

      /* Is there an image currently loaded? */
      if(iv->orig_img != NULL)
      {
          /* Flush GDK output (incase image is shared) and unload
           * the image.
           */
          gdk_flush();
          ImgViewImageDelete(iv->orig_img);
          iv->orig_img = NULL;

          /* Note that the image was actually unloaded. */
          image_unloaded = TRUE;
      }


      /* If an image was actually unloaded, then reset values. */
      if(image_unloaded)
      {
          /* Reset alpha channel flags. */
          iv->alpha_channel_flags = 0;
          iv->alpha_threshold = 0x80;

          /* Reset zoom. */
          iv->view_zoom = 1.0;

          /* Reset translations. */
          adj = iv->view_x_adj;
          if(adj != NULL)
          {
            adj->value = 0.0;
          }
          adj = iv->view_y_adj;
          {
            adj->value = 0.0;
          }

          /* Update WM icon as needed. */
          ImgViewWMIconUpdate(iv);

          /* Clip bounds, update adjustments, and redraw. */
          ImgViewRealizeChange(iv);
          ImgViewUpdateMenus(iv);
          ImgViewExposeCB(iv->view_da, NULL, iv);
      }
}

/*
 *    Loads the given image data to the image viewer. The given image data
 *    buffer format must match the format specified by format.
 *
 *    If an image is already loaded then it will be unloaded.
 *
 *    Returns non-zero on error.
 */
static gint ImgViewLoadNexus(
      imgview_struct *iv,
      gint width, gint height,
      gint bytes_per_line,    /* Can be 0 to auto calculate. */
      gint format,            /* One of IMGVIEW_FORMAT_*. */
      const guint8 *data,
      gboolean zoom_to_fit
)
{
      gint depth;
      GdkColormap *colormap;
      imgview_image_struct *img;
      GdkVisual *vis;
      GdkWindow *window;
      GtkWidget *w;


      if(iv == NULL)
          return(-1);

      /* Unload image first incase it was not unloaded yet, if (and only
       * if) an  image was actually unloaded then translations will be
       * reset, view redrawn, and menus updated.
       */
      ImgViewClear(iv);

      /* Image was not unloaded? */
      if(iv->orig_img != NULL)
      {
          fprintf(
            stderr,
"ImgViewLoad(): Internal error, image was not unloaded after calling ImgViewClear().\n"
          );
          return(-3);
      }

      /* If given data is NULL, then give up. */
      if((data == NULL) || (width < 1) || (height < 1))
          return(-1);

      /* Get view drawing area widget. */
      w = iv->view_da;
      if(w == NULL)
          return(-1);

      /* Get drawing area widget's GdkWindow. */
      if(GTK_WIDGET_NO_WINDOW(w))
          return(-1);
      else
          window = w->window;
      if(window == NULL)
          return(-1);

      /* Get colormap, visual, and depth of the GdkWindow. */
      colormap = gdk_window_get_colormap(window);
      vis = gdk_window_get_visual(window);
      if((colormap == NULL) || (vis == NULL))
          return(-3);

      depth = vis->depth;

      /* Create new image. */
        iv->orig_img = img = ImgViewImageNew(
          width, height, depth
      );
      if(img == NULL)
          return(-1);

      /* Copy given data to target image if target image's buffer
       * is allocated. Both source image data and target image data
       * must be of the same width and height but may differ in
       * bytes per line and/or bytes per pixel.
       */
      if(img->mem != NULL)
        {
          gboolean is_color, has_alpha = FALSE;
            gint x, y, bc, bc_min;
            gint src_bpp, src_bpl;
          gint    tar_bpp = img->bpp,
                  tar_bpl = img->bpl;
          const guint8  *src_ptr;
          guint8        *tar_ptr, *tar_base_ptr,
                        *tar_buf = (guint8 *)img->mem;

          /* Check source image data format, to calculate source image
           * data bytes per line and bytes per pixel units. This will
           * determine the correct src_bpp and is_color value.
           */
          switch(format)
          {
            case IMGVIEW_FORMAT_RGBA:
            src_bpp = 4;
            is_color = TRUE;
            break;
              case IMGVIEW_FORMAT_RGB:
                src_bpp = 3;
            is_color = TRUE;
                break;
              case IMGVIEW_FORMAT_GREYSCALEA32:
                src_bpp = 4;
                is_color = FALSE;
                break;
              case IMGVIEW_FORMAT_GREYSCALE32:
                src_bpp = 4;
            is_color = FALSE;
                break;
              case IMGVIEW_FORMAT_GREYSCALE16:
                src_bpp = 2;
            is_color = FALSE;
                break;
              case IMGVIEW_FORMAT_GREYSCALE8:
                src_bpp = 1;
            is_color = FALSE;
                break;
            default:
            /* Unsupported source image format, safest is to assume
             * lowest bytes per pixel unit.
             */
            src_bpp = 1;
            is_color = FALSE;
            break;
          }
          /* Calculate source image bytes per line unit, if given
           * bytes_per_line is 0 then that means they want us to 
           * calculate it.
           */
          src_bpl = ((bytes_per_line <= 0) ?
            width * src_bpp : bytes_per_line
          );

          /* Calculate byte count minimum, the smaller of the two
           * bytes per pixel units.
           */
          bc_min = MIN(src_bpp, tar_bpp);

          /* Color blitting? */
          if(is_color)
          {
            /* Color blitting. */
            guint8 default_alpha_byte = ((iv->alpha_channel_flags & IMGVIEW_ALPHA_INVERTED) ?
                0x00 : 0xff
            );

            /* Iterate through each row. */
            for(y = 0; y < height; y++)
            {
                for(x = 0; x < width; x++)
                {
                  /* Get source and target 8 bit pointers at start
                   * of current pixel.
                   */
                  src_ptr = &(data[
                      (y * src_bpl) + (x * src_bpp)
                  ]);
                  tar_base_ptr = tar_ptr = &(tar_buf[
                      (y * tar_bpl) + (x * tar_bpp)
                  ]);

                  /* Copy bytes from current source pixel to current
                   * target pixel, incrementing each pointer one byte
                   * after each byte is copied.
                   */
                  for(bc = 0; bc < bc_min; bc++)
                      *tar_ptr++ = *src_ptr++;

                  /* Clear any remaining bytes on the current
                   * target pixel. Remaining bytes should be
                   * the alpha channel.
                   */
                  for(; bc < tar_bpp; bc++)
                      *tar_ptr++ = default_alpha_byte;

                  /* Check if alpha value dosen't match
                   * default_alpha_byte which would hint that there
                   * is an alpha channel.
                   */
                  if((tar_bpp >= 4) && !has_alpha)
                  {
                      if(
 (guint8)((*(guint32 *)tar_base_ptr & 0xff000000) >> 24) != default_alpha_byte
                      )
                        has_alpha = TRUE;
                  }
                }
            }     /* Iterate through each row. */
          }
          else
          {
            /* Greyscale blitting. */
            guint8 first_grey_byte;

                /* Iterate through each row. */
                for(y = 0; y < height; y++)
                {
                    for(x = 0; x < width; x++)
                    {
                        /* Get source and target 8 bit pointers at start
                         * of current pixel.
                         */
                        src_ptr = &(data[
                            (y * src_bpl) + (x * src_bpp)
                        ]);
                        tar_ptr = &(tar_buf[
                            (y * tar_bpl) + (x * tar_bpp)
                        ]);

                  /* Copy first byte. */
                  *tar_ptr++ = first_grey_byte = *src_ptr++;

                        /* Copy bytes from current source pixel to current
                         * target pixel, incrementing each pointer one byte
                         * after each byte is copied. Start at 1 since
                   * first byte is already coppied.
                         */
                        for(bc = 1; bc < bc_min; bc++)
                            *tar_ptr++ = *src_ptr++;

                        /* Clear any remaining bytes on the current
                         * target pixel.
                         */
                        for(; bc < tar_bpp; bc++)
                            *tar_ptr++ = first_grey_byte;
                }
            }
          }

            /* Update alpha channel flag on image viewer structure. */
            if(has_alpha)
                iv->alpha_channel_flags |= IMGVIEW_ALPHA_DEFINED;
            else
                iv->alpha_channel_flags &= ~IMGVIEW_ALPHA_DEFINED;
        }

        /* Update WM icon as needed. */
        ImgViewWMIconUpdate(iv);

      /* Zoom to fit after loading? */
      if(zoom_to_fit)
      {
          /* Zoom the image to fit, this will also realize changes and
           * redraw.
           */
          gint    wwidth = w->allocation.width,
                  wheight = w->allocation.height;
          gdouble zoom;


          zoom = (gdouble)wwidth / (gdouble)width;
          if(zoom > 0.0)
          {
            if((wheight / zoom) < height)
                zoom = (gdouble)wheight / (gdouble)height;
          }
          if(zoom > 0.0)
            iv->view_zoom = zoom;
      }

      /* Clip bounds, update adjustments, and redraw. */
      ImgViewRealizeChange(iv);
      ImgViewUpdateMenus(iv);
      ImgViewExposeCB(iv->view_da, NULL, iv);

      return(0);
}

gint ImgViewLoad(
        imgview_struct *iv,
        gint width, gint height,
        gint bytes_per_line,    /* Can be 0 to auto calculate. */
        gint format,            /* One of IMGVIEW_FORMAT_*. */
        const guint8 *data
)
{
      return(ImgViewLoadNexus(
          iv, width, height, bytes_per_line, format, data, FALSE
      ));
}

gint ImgViewLoadToFit(
        imgview_struct *iv,
        gint width, gint height,
        gint bytes_per_line,    /* Can be 0 to auto calculate. */
        gint format,            /* One of IMGVIEW_FORMAT_*. */
        const guint8 *data
)
{
        return(ImgViewLoadNexus(
            iv, width, height, bytes_per_line, format, data, TRUE
        ));
}


/*
 *    Creates a new image viewer.
 */
imgview_struct *ImgViewNew(
      gboolean show_toolbar,
      gboolean show_values,
      gboolean show_statusbar,
      gboolean show_image_on_wm_icon,
      gint quality,           /* From 0 to 2 (2 being best/slowest). */
      gboolean toplevel_is_window,
      GtkWidget **toplevel_rtn
)
{
      gint  border_minor = 2;
      GdkColormap *colormap;
      GtkAdjustment *adj;
      GdkFont *font;
      GdkColor *c;
      GtkRcStyle *rcstyle;
      GtkStyle *style;
      GtkWidget *w, *parent, *parent2, *parent3, *parent4;
      GtkAccelGroup *accelgrp;
      imgview_struct *iv = IMGVIEW(g_malloc0(
          sizeof(imgview_struct)
      ));


        if(toplevel_rtn != NULL)
            (*toplevel_rtn) = NULL;

      if(iv == NULL)
           return(NULL);

      /* Reset values. */
      iv->map_state = FALSE;
      iv->toplevel_is_window = toplevel_is_window;
      iv->show_values = show_values;
      iv->show_image_on_wm_icon = show_image_on_wm_icon;
      iv->quality = quality;
      iv->view_zoom_toid = (guint)-1;
      iv->view_zoom = 1.0;
      iv->alpha_channel_flags = 0;
      iv->alpha_threshold = 0x80;
      iv->crop_dialog = NULL;

      /* Cursors. */
      iv->busy_cur = gdk_cursor_new(GDK_WATCH);
      iv->translate_cur = gdk_cursor_new(GDK_FLEUR);
      iv->zoom_cur = gdk_cursor_new(GDK_SIZING);
      iv->zoom_rectangle_cur = gdk_cursor_new(GDK_TCROSS);
      iv->crop_cur = gdk_cursor_new(GDK_TCROSS);

      /* Get some default resources from the default GtkStyle. */
      style = gtk_widget_get_default_style();
      if(style != NULL)
      {
          font = style->font;
          if(font != NULL)
          {
            gdk_font_ref(font);
            iv->font = font;
          }
      }

        /* Keyboard accelerator group. */
      iv->accelgrp = accelgrp = gtk_accel_group_new();


      /* View adjustments. */
      iv->view_x_adj = adj = (GtkAdjustment *)gtk_adjustment_new(
            0.0,                /* Current value. */
            0.0, 0.0,           /* Lower, upper. */
            10.0, 50.0,         /* Step inc, page inc. */
            0.0                 /* Page size. */
        );
        gtk_signal_connect(
            GTK_OBJECT(adj), "value_changed",
            GTK_SIGNAL_FUNC(ImgViewAdjustmentValueChangedCB),
            (gpointer)iv
        );

        iv->view_y_adj = adj = (GtkAdjustment *)gtk_adjustment_new(
            0.0,        /* Current value. */
            0.0, 0.0,         /* Lower, upper. */
            10.0, 50.0,       /* Step inc, page inc. */
          0.0                 /* Page size. */
        );
        gtk_signal_connect(
            GTK_OBJECT(adj), "value_changed",
            GTK_SIGNAL_FUNC(ImgViewAdjustmentValueChangedCB),
            (gpointer)iv
        );


        /* Check if toplevel is to be created as a GtkWindow. */
      if(toplevel_is_window)
      {
          GdkWindow *window = NULL;

          /* Create a toplevel GtkWindow with RGB buffers. */
            gtk_widget_push_visual(gdk_rgb_get_visual());
            gtk_widget_push_colormap(gdk_rgb_get_cmap());
          iv->toplevel = w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
            gtk_widget_pop_visual();
            gtk_widget_pop_colormap();
            gtk_window_set_policy(
                GTK_WINDOW(w), TRUE, TRUE, TRUE
            );
/* Do not set usize, so calling function can set view_da's usize
 * for better image fitting.
 */
/*        gtk_widget_set_usize(w, 320, 240); */
          gtk_widget_realize(w);
          gtk_window_set_title(GTK_WINDOW(w), IMGVIEW_TITLE);
          if(!GTK_WIDGET_NO_WINDOW(w))
          {
            GdkGeometry geo;


            window = w->window;     /* Update toplevel GdkWindow pointer. */

            geo.min_width = IMGVIEW_MIN_WIDTH;
            geo.min_height = IMGVIEW_MIN_HEIGHT;
            geo.base_width = 0;
            geo.base_height = 0;
            geo.width_inc = 1;
            geo.height_inc = 1;
/*
            geo.min_aspect = 1.3;
            geo.max_aspect = 1.3;
 */
            gdk_window_set_geometry_hints(
                window,
                &geo,
                GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
                /* GDK_HINT_ASPECT | */
                GDK_HINT_RESIZE_INC
            );

            if(!show_image_on_wm_icon)
                GUISetWMIcon(window, (u_int8_t **)iv_xpm);
          }
            gtk_signal_connect(
                GTK_OBJECT(w), "delete_event",
                GTK_SIGNAL_FUNC(ImgViewCloseCB),
                (gpointer)iv
            );
            gtk_signal_connect(
                GTK_OBJECT(w), "destroy",
                GTK_SIGNAL_FUNC(ImgViewDestroyCB),
                (gpointer)iv
            );
          gtk_accel_group_attach(accelgrp, GTK_OBJECT(w));
          /* Update toplevel return. */
            if(toplevel_rtn != NULL)
                (*toplevel_rtn) = w;
          parent = w;

          /* Main vbox in window. */
            iv->main_vbox = w = gtk_vbox_new(FALSE, border_minor);
          gtk_container_add(GTK_CONTAINER(parent), w);
          gtk_widget_show(w);
          parent = w;
      }
      else
      {
          /* Create a vbox as the toplevel, which will be parented by
           * the calling function.
           */
          iv->toplevel = w = gtk_vbox_new(FALSE, border_minor);
            gtk_signal_connect(
                GTK_OBJECT(w), "destroy",
                GTK_SIGNAL_FUNC(ImgViewDestroyCB),
                (gpointer)iv
            );
            gtk_accel_group_attach(accelgrp, GTK_OBJECT(w));
          parent = w;

          iv->main_vbox = NULL;

            /* Update toplevel return. */
            if(toplevel_rtn != NULL)
                (*toplevel_rtn) = w;
      }

      /* New parent is set to either a GtkWindow or a GtkVBox depending
       * on toplevel_is_window.
       */

      /* Toolbar. */
      iv->toolbar_map_state = show_toolbar;
      /* Toolbar toplevel hbox. */
      iv->toolbar_toplevel = w = gtk_hbox_new(FALSE, border_minor);
      gtk_widget_set_usize(w, -1, IMGVIEW_TOOLBAR_HEIGHT);
        gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
      if(show_toolbar)
          gtk_widget_show(w);
      parent3 = w;

      /* Toolbar depressed frame. */
      w = gtk_frame_new(NULL);
        gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
        gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, 0);
      gtk_widget_show(w);
        parent4 = w;
      /* Hbox inside toolbar's depressed frame. */
      w = gtk_hbox_new(FALSE, border_minor);
      gtk_container_add(GTK_CONTAINER(parent4), w);
      gtk_widget_show(w);
      parent4 = w;

      /* Info label GtkDrawingArea, the info label is really a 
       * GtkDrawingArea because otherwise it would resize the image
       * viewer annoyingly each time the label is changed (which is
       * quite often).
       */
        iv->info_label = w = gtk_drawing_area_new();
        gtk_widget_add_events(
            w,
            GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK |
            GDK_LEAVE_NOTIFY_MASK
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "expose_event",
            GTK_SIGNAL_FUNC(ImgViewInfoLabelExposeCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "enter_notify_event",
            GTK_SIGNAL_FUNC(ImgViewInfoLabelCrossingCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "leave_notify_event",
            GTK_SIGNAL_FUNC(ImgViewInfoLabelCrossingCB),
            iv
        );
      gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, TRUE, 0);
      gtk_widget_show(w);

      /* Toolbar buttons. */
      iv->zoom_in_btn = w = GUIButtonPixmap(
          (u_int8_t **)icon_zoom_in_16x16_xpm
      );
        gtk_widget_set_usize(w, 16, 16);
        gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
        gtk_widget_set_name(w, IMGVIEW_WNAME_BUTTON_ZOOM_IN);
        gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
        gtk_signal_connect(
            GTK_OBJECT(w), "pressed",
            GTK_SIGNAL_FUNC(ImgViewZoomInPressedCB),
            (gpointer)iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "released",
            GTK_SIGNAL_FUNC(ImgViewZoomReleasedCB),
            (gpointer)iv
        );
        gtk_widget_show(w);

        iv->zoom_out_btn = w = GUIButtonPixmap(
            (u_int8_t **)icon_zoom_out_16x16_xpm
        );
        gtk_widget_set_usize(w, 16, 16);
        gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
        gtk_widget_set_name(w, IMGVIEW_WNAME_BUTTON_ZOOM_OUT);
        gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
        gtk_signal_connect(
            GTK_OBJECT(w), "pressed",
            GTK_SIGNAL_FUNC(ImgViewZoomOutPressedCB),
            (gpointer)iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "released",
            GTK_SIGNAL_FUNC(ImgViewZoomReleasedCB),
            (gpointer)iv
        );
        gtk_widget_show(w);

        iv->zoom_onetoone_btn = w = GUIButtonPixmap(
            (u_int8_t **)icon_zoom_onetoone_16x16_xpm
        );
      gtk_widget_set_usize(w, 16, 16);
      gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
        gtk_widget_set_name(w, IMGVIEW_WNAME_BUTTON_ZOOM_ONETOONE);
        gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
        gtk_signal_connect(
            GTK_OBJECT(w), "clicked",
            GTK_SIGNAL_FUNC(ImgViewZoomOneToOneCB),
            (gpointer)iv
        );
        gtk_widget_show(w);

        iv->zoom_tofit_btn = w = GUIButtonPixmap(
            (u_int8_t **)icon_zoom_tofit_16x16_xpm
        );
        gtk_widget_set_usize(w, 16, 16);
        gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
        gtk_widget_set_name(w, IMGVIEW_WNAME_BUTTON_ZOOM_TOFIT);
        gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
        gtk_signal_connect(
            GTK_OBJECT(w), "clicked",
            GTK_SIGNAL_FUNC(ImgViewZoomToFitCB),
            (gpointer)iv
        );
        gtk_widget_show(w);

      /* Show toolbar? */
      if(show_toolbar)
          gtk_widget_show(iv->toolbar_toplevel);

        /* Table to hold drawing area and scrollbars. */
        w = gtk_table_new(2, 2, FALSE);
        gtk_table_set_row_spacing(GTK_TABLE(w), 0, border_minor);
        gtk_table_set_col_spacing(GTK_TABLE(w), 0, border_minor);
        gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
        gtk_widget_show(w);
        parent2 = w;

      /* Frame for drawing area. */
        w = gtk_frame_new(NULL);
        gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
        gtk_table_attach(
            GTK_TABLE(parent2), w,
            0, 1, 0, 1,
            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
            0, 0
        );
        gtk_widget_show(w);
      parent3 = w;

      /* View drawing area (with RGB buffers if toplevel is not a
       * GtkWindow).
       */
      if(!toplevel_is_window)
      {
          gtk_widget_push_visual(gdk_rgb_get_visual());
          gtk_widget_push_colormap(gdk_rgb_get_cmap());
      }
      iv->view_da = w = gtk_drawing_area_new();
      if(!toplevel_is_window)
      {
          gtk_widget_pop_visual();
          gtk_widget_pop_colormap();
      }
        gtk_widget_set_name(w, IMGVIEW_WNAME_VIEW);
      /* Set view style. */
      rcstyle = gtk_rc_style_new();
        rcstyle->color_flags[GTK_STATE_NORMAL] = GTK_RC_BASE;
      rcstyle->color_flags[GTK_STATE_SELECTED] = GTK_RC_BASE;
        rcstyle->color_flags[GTK_STATE_INSENSITIVE] = GTK_RC_BASE;
      c = &rcstyle->base[GTK_STATE_NORMAL];
      c->red = 1.0 * (guint16)-1;
        c->green = 1.0 * (guint16)-1;
        c->blue = 1.0 * (guint16)-1;
        c = &rcstyle->base[GTK_STATE_SELECTED];
        c->red = 0.0 * (guint16)-1;
        c->green = 0.0 * (guint16)-1;
        c->blue = 0.61 * (guint16)-1;
        c = &rcstyle->base[GTK_STATE_INSENSITIVE];
        c->red = 1.0 * (guint16)-1;
        c->green = 1.0 * (guint16)-1;
        c->blue = 1.0 * (guint16)-1;
      gtk_widget_modify_style(w, rcstyle);
      GUIRCStyleDeallocUnref(rcstyle);
      /* Needs to accept focus for keyboard modifier events. */
      GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
        gtk_widget_add_events(
            w,
            GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK |
          GDK_LEAVE_NOTIFY_MASK | GDK_KEY_PRESS_MASK |
            GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
            GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
            GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "enter_notify_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "leave_notify_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "key_press_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
      );
        gtk_signal_connect(
            GTK_OBJECT(w), "key_release_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "button_press_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "button_release_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "motion_notify_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "expose_event",
            GTK_SIGNAL_FUNC(ImgViewExposeCB),
            iv
        );
        gtk_signal_connect(
            GTK_OBJECT(w), "configure_event",
            GTK_SIGNAL_FUNC(ImgViewViewEventCB),
            iv
        );
      gtk_container_add(GTK_CONTAINER(parent3), w);
      gtk_widget_show(w);

      /* Reset view image buffer to NULL, it will be created when
       * ImgViewBufferRecreate() is called for the first time.
       */
      iv->view_img = NULL;


      /* Use default font for view_font and the view_selection_gc. */
      font = iv->font;
        /* Create view selection cursory GC. */
      colormap = gtk_widget_get_colormap(w);
      if(colormap == NULL)
          colormap = gdk_colormap_get_system();
      if(colormap != NULL)
      {
          GdkColor *cf, *cb;
            GdkGCValues gcv;
          GdkWindow *window = (GdkWindow *)GDK_ROOT_PARENT();
            GdkGCValuesMask gcv_mask = (
            GDK_GC_FOREGROUND | GDK_GC_BACKGROUND | GDK_GC_FONT |
            GDK_GC_FUNCTION | GDK_GC_FILL | GDK_GC_LINE_WIDTH |
            GDK_GC_LINE_STYLE | GDK_GC_CAP_STYLE | GDK_GC_JOIN_STYLE
          );
            cf = &gcv.foreground;
            cf->red = (guint16)(0.0 * (guint16)-1);
            cf->green = (guint16)(0.0 * (guint16)-1);
            cf->blue = (guint16)(0.0 * (guint16)-1);
            gdk_colormap_alloc_color(colormap, cf, TRUE, TRUE);
          cb = &gcv.background;
            cb->red = (guint16)(1.0 * (guint16)-1);
            cb->green = (guint16)(1.0 * (guint16)-1);
            cb->blue = (guint16)(1.0 * (guint16)-1);
            gdk_colormap_alloc_color(colormap, cb, TRUE, TRUE);
          if(font != NULL)
            gdk_font_ref(font);
          gcv.font = font;
            gcv.function = GDK_INVERT;
            gcv.fill = GDK_SOLID;
            gcv.line_width = 1;
            gcv.line_style = GDK_LINE_SOLID;
            gcv.cap_style = GDK_CAP_NOT_LAST;
            gcv.join_style = GDK_JOIN_MITER;
            iv->view_selection_gc = gdk_gc_new_with_values(
            window, &gcv, gcv_mask
          );
            gdk_colormap_free_colors(colormap, cf, 1);
          gdk_colormap_free_colors(colormap, cb, 1);
            if(font != NULL)
                gdk_font_unref(font);
      }
      /* Record view font if available, be sure to add a ref count
       * to it since we want to keep it around.
       */
      if(font != NULL)
      {
          gdk_font_ref(font);
          iv->view_font = font;
      }


      /* Create scroll bars, make sure that we increase the
       * ref count for the adjustment since it will be unref'ed
       * when the scroll bar is destroyed.
       */
      gtk_object_ref(GTK_OBJECT(iv->view_x_adj));
        w = gtk_hscrollbar_new(iv->view_x_adj);
        gtk_table_attach(
            GTK_TABLE(parent2), w,
            0, 1, 1, 2,
          GTK_EXPAND | GTK_SHRINK | GTK_FILL,
            GTK_FILL,
            0, 0
        );
        gtk_widget_show(w);

      gtk_object_ref(GTK_OBJECT(iv->view_y_adj));
        w = gtk_vscrollbar_new(iv->view_y_adj);
        gtk_table_attach(
            GTK_TABLE(parent2), w,
            1, 2, 0, 1,
            GTK_FILL,
            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
            0, 0
        );
        gtk_widget_show(w);


        /* Status bar. */
        iv->statusbar_map_state = show_statusbar;
        /* Status bar toplevel hbox. */
/* Naw, frame dosen't look very pretty for a simple status bar with
 * only a progress bar.

        iv->statusbar_toplevel = w = gtk_frame_new(NULL);
        gtk_widget_set_usize(w, -1, IMGVIEW_STATUSBAR_HEIGHT);
        gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
        gtk_container_border_width(GTK_CONTAINER(w), 0);
        gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
        if(show_statusbar)
            gtk_widget_show(w);
        parent2 = w;
 */
      /* Hbox to stretch progress bar. */
        iv->statusbar_toplevel = w = gtk_hbox_new(FALSE, 0);
        gtk_widget_set_usize(w, -1, IMGVIEW_STATUSBAR_HEIGHT);
        gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
        if(show_statusbar)
            gtk_widget_show(w);
      parent2 = w;
/* We need to use a GtkTable here instead if we want a label or other
 * widgets on the status bar.
 */
      /* Status bar progress bar. */
        adj = (GtkAdjustment *)gtk_adjustment_new(0, 1, 100, 0, 0, 0);
        iv->progress_bar = w = gtk_progress_bar_new_with_adjustment(adj);
/*    gtk_widget_set_usize(w, 100, -1); */
        gtk_progress_bar_set_orientation(
            GTK_PROGRESS_BAR(w), GTK_PROGRESS_LEFT_TO_RIGHT
        );
        gtk_progress_bar_set_bar_style(
            GTK_PROGRESS_BAR(w), GTK_PROGRESS_CONTINUOUS
        );
        gtk_progress_set_activity_mode(
            GTK_PROGRESS(w), FALSE
        );
        gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
        gtk_widget_show(w);






      /* Right-click menu. */
      iv->menu = w = (GtkWidget *)GUIMenuCreate();
      if(w != NULL)
      {
          GtkWidget *submenu, *menu = w;
          GtkWidget *fw;
          gint accel_key;
          gpointer accel_group = NULL;
          guint accel_mods;
          u_int8_t **icon;
          const gchar *label = NULL;
          gpointer mclient_data = iv;
          void (*func_cb)(GtkWidget *w, gpointer) = NULL;

#define DO_ADD_MENU_ITEM_LABEL            \
{ \
 w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_LABEL, accel_group, \
  icon, label, accel_key, accel_mods, (void **)&fw, \
  mclient_data, func_cb \
 ); \
}
#define DO_ADD_MENU_ITEM_CHECK            \
{ \
 w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_CHECK, accel_group, \
  icon, label, accel_key, accel_mods, (void **)&fw, \
  mclient_data, func_cb \
 ); \
}
#define DO_ADD_MENU_ITEM_SUBMENU    \
{ \
 w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_SUBMENU, accel_group, \
  icon, label, accel_key, accel_mods, (gpointer *)&fw, \
  mclient_data, func_cb \
 ); \
 if(w != NULL) \
  GUIMenuItemSetSubMenu(w, submenu); \
}
#define DO_ADD_MENU_SEP             \
{ \
 w = GUIMenuItemCreate( \
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL, \
  NULL, NULL, 0, 0, NULL, \
  NULL, NULL \
 ); \
}
/*
            icon = (u_int8_t **)icon_zoom_in_20x20_xpm;
            label = "Zoom In";
            accel_key = 0;
            accel_mods = 0;
          func_cb = ImgViewZoomInCB;
            DO_ADD_MENU_ITEM_LABEL
            iv->zoom_in_mi = w;

          icon = (u_int8_t **)icon_zoom_out_20x20_xpm;
          label = "Zoom Out";
          accel_key = 0;
          accel_mods = 0;
          func_cb = ImgViewZoomOutCB;
          DO_ADD_MENU_ITEM_LABEL
          iv->zoom_out_mi = w;
 */
            icon = (u_int8_t **)icon_zoom_onetoone_20x20_xpm;
            label = "Zoom 1:1";
            accel_key = 0;
            accel_mods = 0;
          func_cb = ImgViewZoomOneToOneCB;
            DO_ADD_MENU_ITEM_LABEL
            iv->zoom_onetoone_mi = w;

            icon = (u_int8_t **)icon_zoom_tofit_20x20_xpm;
            label = "Zoom To Fit";
            accel_key = 0;
            accel_mods = 0;
          func_cb = ImgViewZoomToFitCB;
            DO_ADD_MENU_ITEM_LABEL
            iv->zoom_tofit_mi = w;

          DO_ADD_MENU_SEP

            icon = NULL;
            label = "Tool Bar";
            accel_key = 0;
            accel_mods = 0;
            func_cb = ImgViewToolBarToggleCB;
            DO_ADD_MENU_ITEM_CHECK
            iv->show_toolbar_micheck = w;
          GTK_CHECK_MENU_ITEM(w)->active = iv->toolbar_map_state;

            icon = NULL;
            label = "Values";
            accel_key = 0;
            accel_mods = 0;
            func_cb = ImgViewValuesToggleCB;
            DO_ADD_MENU_ITEM_CHECK
            iv->show_values_micheck = w;
            GTK_CHECK_MENU_ITEM(w)->active = iv->show_values;

            icon = NULL;
            label = "Status Bar";
            accel_key = 0;
            accel_mods = 0;
            func_cb = ImgViewStatusBarToggleCB;
            DO_ADD_MENU_ITEM_CHECK
            iv->show_statusbar_micheck = w;
            GTK_CHECK_MENU_ITEM(w)->active = iv->statusbar_map_state;

          DO_ADD_MENU_SEP

            /* Create quality submenu. */
            iv->quality_menu = submenu = GUIMenuCreate();
            if(submenu != NULL)
            {
                GtkWidget *menu;        /* Overload. */

                menu = submenu;

                icon = NULL;
                label = "Poor/Fastest";
                accel_key = 0;
                accel_mods = 0;
                func_cb = ImgViewQualityPoorCB;
                DO_ADD_MENU_ITEM_CHECK
            iv->quality_poor_mi = w;
                GTK_CHECK_MENU_ITEM(w)->active = ((quality == 0) ?
                TRUE : FALSE
            );

                icon = NULL;
                label = "Optimal";
                accel_key = 0;
                accel_mods = 0;
                func_cb = ImgViewQualityOptimalCB;
                DO_ADD_MENU_ITEM_CHECK
                iv->quality_optimal_mi = w;
                GTK_CHECK_MENU_ITEM(w)->active = ((quality == 1) ?
                    TRUE : FALSE
                );

                icon = NULL;
                label = "Best/Slowest";
                accel_key = 0;
                accel_mods = 0;
                func_cb = ImgViewQualityBestCB;
                DO_ADD_MENU_ITEM_CHECK
                iv->quality_best_mi = w;
                GTK_CHECK_MENU_ITEM(w)->active = ((quality == 2) ?
                    TRUE : FALSE
                );
            }
            icon = NULL;
            label = "Quality";
            accel_key = 0;
            accel_mods = 0;
            func_cb = NULL;
            DO_ADD_MENU_ITEM_SUBMENU


#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_ITEM_SUBMENU
#undef DO_ADD_MENU_SEP

      }

      /* Reset and update menus. */
      ImgViewReset(iv, FALSE);

      return(iv);
}

/*
 *    Sets the image viewer's image changed callback function.
 */
void ImgViewSetChangedCB(
      imgview_struct *iv,
      void (*changed_cb)(gpointer, imgview_image_struct *, gpointer),
      gpointer data
)
{
        if(iv == NULL)
            return;

      iv->changed_cb = changed_cb;
      iv->changed_data = data;
}

/*
 *    Deallocates all resources on the given image viewer.
 *
 *    If repeating view zoom is active then it will be removed.
 *    Any loaded image indicated by orig_img will be unloaded and
 *    orig_img reset. Menus will be updated.
 *
 *    If need_unmap is TRUE then the image viewer will be unmapped.
 */
void ImgViewReset(imgview_struct *iv, gboolean need_unmap)
{
        if(iv == NULL)
            return;

      /* Reset zoom timeout as needed. */
      if(iv->view_zoom_toid != (guint)-1)
      {
          gtk_timeout_remove(iv->view_zoom_toid);
          iv->view_zoom_toid = (guint)-1;
      }

      /* Reset alpha channel flags. */
        iv->alpha_channel_flags = 0;
        iv->alpha_threshold = 0x80;

      /* Reset zoom. */
      iv->view_zoom = 1.0;

      /* Reset quality. */
/* Leave it as is, only let quality change on creation or when
 * user modifies it.
      iv->quality = 0;
 */

      /* Clear drag mode. */
      iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;

      /* Reset last drag position. */
      iv->drag_last_x = 0;
      iv->drag_last_y = 0;

      /* Reset last drag rectangular position. */
        iv->drag_zoom_rectangle_start_x =
            iv->drag_zoom_rectangle_cur_x = 0;
        iv->drag_zoom_rectangle_start_y =
            iv->drag_zoom_rectangle_cur_y = 0;

      /* Reset last motion event time. */
      iv->last_motion_time = 0;

      /* Reset crop position. */
      iv->crop_flags &= ~IMGVIEW_CROP_DEFINED;
      iv->crop_rectangle_start_x =
          iv->crop_rectangle_start_y = 0;
      iv->crop_rectangle_cur_x =
          iv->crop_rectangle_cur_y = 0;

      /* Delete crop dialog when resetting image viewer. */
      ImgViewCropDialogDelete((imgview_crop_dialog_struct *)iv->crop_dialog);
      iv->crop_dialog = NULL;


      /* Unload current image (if any). */
      ImgViewClear(iv);

      /* Realize changes and update menus. */
      ImgViewRealizeChange(iv);
      ImgViewUpdateMenus(iv);

      if(need_unmap)
          ImgViewUnmap(iv);
}

/*
 *    Redraws the image viewer's view. This is called by public 
 *    functions since local functions can call ImgViewExposeCB()
 *    to redraw directly.
 */
void ImgViewDraw(imgview_struct *iv)
{
        if(iv == NULL)
            return;

      ImgViewExposeCB(iv->view_da, NULL, iv);
}

/*
 *    Updates menus on the image viewer to reflect its current states.
 */
void ImgViewUpdateMenus(imgview_struct *iv)
{
      static gboolean reenterant = FALSE;
      gboolean sensitivity, state;
      GtkWidget *w;
      gint quality;
      imgview_image_struct *src_img;
        if(iv == NULL)
            return;

      if(reenterant)
          return;
      else
          reenterant = TRUE;

      quality = iv->quality;
      src_img = iv->orig_img;

#define DO_SET_SENSITIVITY    \
{ \
 if(w != NULL) \
  gtk_widget_set_sensitive(w, sensitivity); \
}

#define DO_SET_CHECK_MENU_ITEM      \
{ \
 if(w != NULL) \
  GTK_CHECK_MENU_ITEM(w)->active = state; \
}

      /* Update toolbar widgets. */
      w = iv->zoom_in_btn;
      sensitivity = ((src_img != NULL) ? TRUE : FALSE);
      DO_SET_SENSITIVITY
        w = iv->zoom_out_btn;
        sensitivity = ((src_img != NULL) ? TRUE : FALSE);
        DO_SET_SENSITIVITY
        w = iv->zoom_onetoone_btn;
        sensitivity = ((src_img != NULL) ? TRUE : FALSE);
        DO_SET_SENSITIVITY
        w = iv->zoom_tofit_btn;
        sensitivity = ((src_img != NULL) ? TRUE : FALSE);
        DO_SET_SENSITIVITY

        /* Update menu items. */
        w = iv->zoom_in_mi;
        sensitivity = ((src_img != NULL) ? TRUE : FALSE);
        DO_SET_SENSITIVITY
        w = iv->zoom_out_mi;
        sensitivity = ((src_img != NULL) ? TRUE : FALSE);
        DO_SET_SENSITIVITY
        w = iv->zoom_onetoone_mi;
        sensitivity = ((src_img != NULL) ? TRUE : FALSE);
        DO_SET_SENSITIVITY
        w = iv->zoom_tofit_mi;
        sensitivity = ((src_img != NULL) ? TRUE : FALSE);
        DO_SET_SENSITIVITY

      w = iv->show_toolbar_micheck; 
      state = iv->toolbar_map_state;
      DO_SET_CHECK_MENU_ITEM
        w = iv->show_values_micheck;
        state = iv->show_values;
        DO_SET_CHECK_MENU_ITEM
        w = iv->show_statusbar_micheck;
        state = iv->statusbar_map_state;
        DO_SET_CHECK_MENU_ITEM

      /* Update quality menu items. */
        w = iv->quality_poor_mi;
        state = ((quality == 0) ? TRUE : FALSE);
        DO_SET_CHECK_MENU_ITEM
        w = iv->quality_optimal_mi;
        state = ((quality == 1) ? TRUE : FALSE);
        DO_SET_CHECK_MENU_ITEM
      w = iv->quality_best_mi;
      state = ((quality == 2) ? TRUE : FALSE);
      DO_SET_CHECK_MENU_ITEM


#undef DO_SET_SENSITIVITY
#undef DO_SET_CHECK_MENU_ITEM

      reenterant = FALSE;
}


/*
 *    Shows or hides the tool bar.
 */
void ImgViewShowToolBar(imgview_struct *iv, gboolean show)
{
        if(iv == NULL)
            return;

      if(show)
      {
          /* Show toolbar. */
          if(!iv->toolbar_map_state)
          {
            GtkWidget *w = iv->toolbar_toplevel;
            if(w != NULL)
                gtk_widget_show(w);
                iv->toolbar_map_state = TRUE;
                ImgViewUpdateMenus(iv);
          }
        }
      else
      {
            /* Hide toolbar. */
            if(iv->toolbar_map_state)
            {
                GtkWidget *w = iv->toolbar_toplevel;
                if(w != NULL)
                    gtk_widget_hide(w);
                iv->toolbar_map_state = FALSE;
            ImgViewUpdateMenus(iv);
            }
      }
}

/*
 *      Shows or hides the status bar.
 */
void ImgViewShowStatusBar(imgview_struct *iv, gboolean show)
{
        if(iv == NULL)
            return;

        if(show)
        {
            /* Show status bar. */
            if(!iv->statusbar_map_state)
            {
                GtkWidget *w = iv->statusbar_toplevel;
                if(w != NULL)
                    gtk_widget_show(w);
                iv->statusbar_map_state = TRUE;
                ImgViewUpdateMenus(iv);
            }
        }
        else
        {
            /* Hide status bar. */
            if(iv->statusbar_map_state)
            {
                GtkWidget *w = iv->statusbar_toplevel;
                if(w != NULL)
                    gtk_widget_hide(w);
                iv->statusbar_map_state = FALSE;
                ImgViewUpdateMenus(iv);
            }
        }
}

/*
 *    Sets the background color of the view, the color
 *    does not need to be allocated with a colormap.
 *
 *    The colors in the array must be 5 to match with each corresponding
 *    GTK_STATE_* index.
 */
void ImgViewSetViewBG(
      imgview_struct *iv,
      GdkColor *c       /* 5 colors. */
)
{
      gint i;
      GtkWidget *w;
      GtkRcStyle *rcstyle;
      GdkColor *ct, *cs;


        if(iv == NULL)
            return;

      if(c == NULL)
          return;

      w = (GtkWidget *)ImgViewGetViewWidget(iv);
      if(w == NULL)
          return;

        rcstyle = gtk_rc_style_new();
      for(i = 0; i < 5; i++)
      {
          rcstyle->color_flags[i] = GTK_RC_BASE;

          ct = &rcstyle->base[i];
          cs = &c[i];

          ct->red = cs->red;
          ct->green = cs->green;
            ct->blue = cs->blue;
      }
      gtk_widget_modify_style(w, rcstyle);
      GUIRCStyleDeallocUnref(rcstyle);

      /* Update menus and redraw view to reflect new background color. */
      ImgViewUpdateMenus(iv);
      ImgViewExposeCB(iv->view_da, NULL, iv);
}


/*
 *    Zoom in one step.
 */
void ImgViewZoomIn(imgview_struct *iv)
{
      if(iv == NULL)
          return;

      /* Zoom in. */
        ImgViewZoomIterate(iv, -IMGVIEW_ZOOM_ITTERATION_PIXELS);

        /* Clip bounds, update adjustments and redraw. */
        ImgViewRealizeChange(iv);
        ImgViewExposeCB(iv->view_da, NULL, iv);
}

/*
 *    Zoom out one step.
 */
void ImgViewZoomOut(imgview_struct *iv)
{
        if(iv == NULL)
            return;

        /* Zoom out. */
        ImgViewZoomIterate(iv, IMGVIEW_ZOOM_ITTERATION_PIXELS);

        /* Clip bounds, update adjustments and redraw. */
        ImgViewRealizeChange(iv);
        ImgViewExposeCB(iv->view_da, NULL, iv);
}

/*
 *      Zoom one to one.
 */
void ImgViewZoomOneToOne(imgview_struct *iv)
{
      ImgViewZoomOneToOneCB(NULL, iv);
}

/*
 *    Zoom to fit.
 */
void ImgViewZoomToFit(imgview_struct *iv)
{
      ImgViewZoomToFitCB(NULL, iv);
}

/*
 *      Sets the given image viewer to allow user cropping or not.
 */
void ImgViewAllowCrop(imgview_struct *iv, gboolean allow_crop)
{
        if(iv == NULL)
            return;

        if(allow_crop)
            iv->crop_flags |= IMGVIEW_CROP_ALLOWED;
        else
            iv->crop_flags &= ~IMGVIEW_CROP_ALLOWED;

      ImgViewUpdateMenus(iv);
}

/*
 *    Marks the image viewer as busy or ready.
 */
void ImgViewSetBusy(imgview_struct *iv, gboolean is_busy)
{
      GtkWidget *w;

        if(iv == NULL)
            return;

      w = iv->toplevel;
      if(!GTK_WIDGET_NO_WINDOW(w))
      {
          GdkCursor *cur = ((is_busy) ? iv->busy_cur : NULL);
          GdkWindow *window = w->window;

          if(window != NULL)
            gdk_window_set_cursor(window, cur);
          gdk_flush();
      }
}

/*
 *    Updates the progress bar.
 */
void ImgViewProgressUpdate(
        imgview_struct *iv, gdouble position, gboolean allow_gtk_iteration
)
{
        GtkWidget *w;


        if(iv == NULL)
            return;

        /* Begin updating progress bar position */
        w = iv->progress_bar;
        if(w != NULL)
        {
/*        GtkProgress *pr = GTK_PROGRESS(w); */
            GtkProgressBar *pb = GTK_PROGRESS_BAR(w);


            if(position > 1.0)
                position = 1.0;
            else if(position < 0.0)
                position = 0.0;

            gtk_progress_bar_update(pb, position);
        }

        /* Allow gtk main iteration to be called so that updated
         * values get enacted within this call?
         */
        if(allow_gtk_iteration)
        {
            while(gtk_events_pending() > 0)
                gtk_main_iteration();
        }
}

/*
 *    Maps the image viewer.
 */
void ImgViewMap(imgview_struct *iv)
{
      GtkWidget *w;

        if(iv == NULL)
            return;

      w = iv->toplevel;
      gtk_widget_show_raise(w);
      iv->map_state = TRUE;
}

/*
 *    Unmaps the image viewer.
 */
void ImgViewUnmap(imgview_struct *iv)
{
        if(iv == NULL)
            return;

        if(iv->map_state)
        {
            GtkWidget *w = iv->toplevel;
            if(w != NULL)
                gtk_widget_hide(w);
            iv->map_state = FALSE;
        }
}


/*
 *    Destroys the image viewer's resources and deallocates its
 *    structure.
 */
void ImgViewDelete(imgview_struct *iv)
{
      GdkFont **font;
      GdkGC **gc;
      GdkBitmap **bitmap;
      GdkPixmap **pixmap;
      GdkCursor **cur;
      GtkWidget **w;
      GtkAdjustment **adj;


        if(iv == NULL)
            return;

      if(TRUE)
      {
#define DO_UNREF_BITMAP       \
{ if(*bitmap != NULL) { gdk_bitmap_unref(*bitmap); *bitmap = NULL; } }

#define DO_UNREF_PIXMAP       \
{ if(*pixmap != NULL) { gdk_pixmap_unref(*pixmap); *pixmap = NULL; } }

#define DO_UNREF_FONT         \
{ if(*font != NULL) { gdk_font_unref(*font); *font = NULL; } }

#define DO_UNREF_GC           \
{ if(*gc != NULL) { gdk_gc_unref(*gc); *gc = NULL; } }

#define DO_DESTROY_CURSOR       \
{ if(*cur != NULL) { gdk_cursor_destroy(*cur); *cur = NULL; } }

#define DO_DESTROY_WIDGET     \
{ if(*w != NULL) { gtk_widget_destroy(*w); *w = NULL; } }

#define DO_UNREF_ADJUSTMENT   \
{ if(*adj != NULL) { gtk_object_unref(GTK_OBJECT(*adj)); *adj = NULL; } }

          /* Reset the image viewer, this will unload the original image
           * and deallocate other resources on the image viewer.
           */
          ImgViewReset(iv, iv->map_state);

          /* Flush GDK before destroying widgets, incase any shared
           * image puts are still happening.
           */
          gdk_flush();

          /* Delete the crop dialog. */
          ImgViewCropDialogDelete((imgview_crop_dialog_struct *)iv->crop_dialog);
          iv->crop_dialog = NULL;


          /* Begin destroying widgets. */

          w = &iv->quality_menu;
          iv->quality_poor_mi = NULL;
          iv->quality_optimal_mi = NULL;
          iv->quality_best_mi = NULL;
          DO_DESTROY_WIDGET

          w = &iv->menu;
          iv->zoom_in_mi = NULL;
          iv->zoom_out_mi = NULL;
          iv->zoom_tofit_mi = NULL;
          iv->zoom_onetoone_mi = NULL;
          iv->show_toolbar_micheck = NULL;
          iv->show_values_micheck = NULL;
          iv->show_statusbar_micheck = NULL;
          iv->quality_submenu_mi = NULL;
          DO_DESTROY_WIDGET

            w = &iv->toplevel;
            iv->main_vbox = NULL;
            iv->toolbar_toplevel = NULL;
            iv->info_label = NULL;
            iv->zoom_in_btn = NULL;
            iv->zoom_out_btn = NULL;
            iv->zoom_onetoone_btn = NULL;
            iv->zoom_tofit_btn = NULL;
            DO_DESTROY_WIDGET

          pixmap = &iv->wm_icon_pixmap;
          DO_UNREF_PIXMAP
          bitmap = &iv->wm_icon_mask;
          DO_UNREF_BITMAP

            if(iv->accelgrp != NULL)
            {
                gtk_accel_group_unref(iv->accelgrp);
                iv->accelgrp = NULL;
            }

          if(iv->view_img != NULL)
          {
            ImgViewImageDelete(iv->view_img);
            iv->view_img = NULL;
          }

          /* The orig_img should already be destroyed when we
           * resetted, but we'll check to deallocate again just
           * in case.
           */
          if(iv->orig_img != NULL)
            {
                ImgViewImageDelete(iv->orig_img);
                iv->orig_img = NULL;
            }


          adj = &iv->view_x_adj;
          DO_UNREF_ADJUSTMENT
            adj = &iv->view_y_adj;
            DO_UNREF_ADJUSTMENT

          cur = &iv->busy_cur;
          DO_DESTROY_CURSOR
          cur = &iv->translate_cur;
          DO_DESTROY_CURSOR
          cur = &iv->zoom_cur;
          DO_DESTROY_CURSOR
          cur = &iv->zoom_rectangle_cur;
          DO_DESTROY_CURSOR
          cur = &iv->crop_cur;
          DO_DESTROY_CURSOR

          gc = &iv->view_selection_gc;
          DO_UNREF_GC

          font = &iv->view_font;
          DO_UNREF_FONT
          font = &iv->font;
          DO_UNREF_FONT

#undef DO_UNREF_BITMAP
#undef DO_UNREF_PIXMAP
#undef DO_UNREF_FONT
#undef DO_UNREF_GC
#undef DO_DESTROY_CURSOR
#undef DO_DESTROY_WIDGET
#undef DO_UNREF_ADJUSTMENT
      }

      /* Deallocate structure itself. */
      g_free(iv);
}

Generated by  Doxygen 1.6.0   Back to index