home *** CD-ROM | disk | FTP | other *** search
/ PC Pro 2002 April / pcpro0402.iso / essentials / graphics / Gimp / gimp-src-20001226.exe / src / gimp / app / undo_history.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-08-24  |  25.4 KB  |  868 lines

  1. /* The GIMP -- an image manipulation program
  2.  * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
  3.  *
  4.  * This program is free software; you can redistribute it and/or modify
  5.  * it under the terms of the GNU General Public License as published by
  6.  * the Free Software Foundation; either version 2 of the License, or
  7.  * (at your option) any later version.
  8.  *
  9.  * This program is distributed in the hope that it will be useful,
  10.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.  * GNU General Public License for more details.
  13.  *
  14.  * You should have received a copy of the GNU General Public License
  15.  * along with this program; if not, write to the Free Software
  16.  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  17.  *
  18.  * Undo history browser by Austin Donnelly <austin@gimp.org>
  19.  */
  20.  
  21.  
  22. /* TODO:
  23.  *
  24.  *  - reuse the L&C previews?
  25.  *         Currently we use gimp_image_construct_composite_preview ()
  26.  *       which makes use of the preview_cache on a per layer basis.
  27.  *
  28.  *  - work out which (if any) is the clean image, and mark it as such.
  29.  *         Currently, it's on the wrong line.
  30.  *
  31.  *  - undo names are less than useful.  This isn't a problem with
  32.  *         undo_history.c itself, more with the rather chaotic way
  33.  *         people have of picking an undo type when pushing undos, and
  34.  *         inconsistent use of undo groups.  Maybe rather than
  35.  *         specifying an (enum) type, it should be a const char * ?
  36.  *
  37.  * BUGS:
  38.  *  - clean pixmap in wrong place
  39.  *
  40.  *  Initial rev 0.01, (c) 19 Sept 1999 Austin Donnelly <austin@gimp.org>
  41.  *
  42.  */
  43.  
  44. #include <gtk/gtk.h>
  45. #include "gimprc.h"
  46. #include "gimpui.h"
  47. #include "temp_buf.h"
  48. #include "undo.h"
  49. #include "gimage_mask.h"
  50. #include "dialog_handler.h"
  51.  
  52. #include "config.h"
  53. #include "libgimp/gimpintl.h"
  54. #include "libgimp/gimplimits.h"
  55.  
  56. #include "pixmaps/raise.xpm"
  57. #include "pixmaps/lower.xpm"
  58. #include "pixmaps/yes.xpm"
  59. #include "pixmaps/question.xpm"
  60.  
  61. typedef struct
  62. {
  63.   GImage    *gimage;          /* image we're tracking undo info for */
  64.   GtkWidget *shell;          /* dialog window */
  65.   GtkWidget *clist;          /* list of undo actions */
  66.   GtkWidget *undo_button;     /* button to undo an operation */
  67.   GtkWidget *redo_button;     /* button to redo an operation */
  68.   int        old_selection;   /* previous selection in the clist */
  69.   int        preview_size;    /* size of the previews (from preferences) */
  70. } undo_history_st;
  71.  
  72. typedef struct 
  73. {
  74.   GtkCList *clist;
  75.   gint      row;
  76.   gint      size;
  77.   GImage   *gimage;
  78. } idle_preview_args;
  79.  
  80. /*
  81.  * Theory of operation.
  82.  *
  83.  * Keep a clist.  Each row of the clist corresponds to an image as it
  84.  * was at some time in the past, present or future.  The selected row
  85.  * is the present image.  Rows below the selected one are in the
  86.  * future - as redo operations are performed, they become the current
  87.  * image.  Rows above the selected one are in the past - undo
  88.  * operations move the highlight up.
  89.  *
  90.  * The slight fly in the ointment is that if rows are images, then how
  91.  * should they be labelled?  An undo or redo operation goes _between_
  92.  * two image states - it isn't an image state.  It's a pretty
  93.  * arbitrary decision, but I've chosen to label a row with the name of
  94.  * the action that brought the image into the state represented by
  95.  * that row.  Thus, there is a special first row without a meaningful
  96.  * label, which represents the image state before the first action has
  97.  * been done to it.  The choice is between a special first row or a
  98.  * special last row.  Since people mostly work near the leading edge,
  99.  * not often going all the way back, I've chosen to put the special
  100.  * case out of common sight.
  101.  *
  102.  * So, the undo stack contents appear above the selected row, and the
  103.  * redo stack below it.
  104.  *
  105.  * The clist is initialised by mapping over the undo and redo stack.
  106.  *
  107.  * Once initialised, the dialog listens to undo_event signals from the
  108.  * gimage.  These undo events allow us to track changes to the undo
  109.  * and redo stacks.  We follow the events, making parallel changes to
  110.  * the clist.  If we ever get out of sync, there is no mechanism to
  111.  * notice or re-sync.  A few g_return_if_fails should catch some of
  112.  * these cases.
  113.  *
  114.  * User clicks changing the selected row in the clist turn into
  115.  * multiple calls to undo_pop or undo_redo, with appropriate signals
  116.  * blocked so we don't get our own events back.
  117.  *
  118.  * The "Close" button hides the dialog, rather than destroying it.
  119.  * This may well need to be changed, since the dialog will continue to
  120.  * track updates, and if it's generating previews this might take too
  121.  * long for large images. 
  122.  *
  123.  * The dialog is destroyed when the gimage it is tracking is
  124.  * destroyed.  Note that a File/Revert destroys the current gimage and
  125.  * so blows the undo/redo stacks.
  126.  *
  127.  * --austin, 19/9/1999
  128.  */
  129.  
  130. /**************************************************************/
  131. /* Static Data */
  132.  
  133. static GdkPixmap *clean_pixmap = NULL;
  134. static GdkBitmap *clean_mask   = NULL;
  135.  
  136. static GdkPixmap *clear_pixmap = NULL;
  137. static GdkBitmap *clear_mask   = NULL;
  138.  
  139. /**************************************************************/
  140. /* Local functions */
  141.  
  142.  
  143. static MaskBuf *
  144. mask_render_preview (GImage        *gimage,
  145.              gint          *pwidth,
  146.              gint          *pheight)
  147. {
  148.   Channel * mask;
  149.   MaskBuf * scaled_buf = NULL;
  150.   PixelRegion srcPR, destPR;
  151.   gint subsample;
  152.   gint width, height;
  153.   gint scale;
  154.  
  155.   mask = gimage_get_mask (gimage);
  156.   if ((drawable_width (GIMP_DRAWABLE(mask)) > *pwidth) ||
  157.       (drawable_height (GIMP_DRAWABLE(mask)) > *pheight))
  158.     {
  159.       if (((gfloat) drawable_width (GIMP_DRAWABLE (mask)) / (gfloat) *pwidth) >
  160.       ((gfloat) drawable_height (GIMP_DRAWABLE (mask)) / (gfloat) *pheight))
  161.     {
  162.       width = *pwidth;
  163.       height = (drawable_height (GIMP_DRAWABLE (mask)) * (*pwidth)) / drawable_width (GIMP_DRAWABLE (mask));
  164.     }
  165.       else
  166.     {
  167.       width = (drawable_width (GIMP_DRAWABLE (mask)) * (*pheight)) / drawable_height (GIMP_DRAWABLE (mask));
  168.       height = *pheight;
  169.     }
  170.  
  171.       scale = TRUE;
  172.     }
  173.   else
  174.     {
  175.       width = drawable_width (GIMP_DRAWABLE (mask));
  176.       height = drawable_height (GIMP_DRAWABLE (mask));
  177.  
  178.       scale = FALSE;
  179.     }
  180.  
  181.   /*  if the mask is empty, no need to scale and update again  */
  182.   if (gimage_mask_is_empty (gimage))
  183.     return NULL;
  184.  
  185.   if (scale)
  186.     {
  187.       /*  calculate 'acceptable' subsample  */
  188.       subsample = 1;
  189.       while ((width * (subsample + 1) * 2 < drawable_width (GIMP_DRAWABLE (mask))) &&
  190.          (height * (subsample + 1) * 2 < drawable_height (GIMP_DRAWABLE (mask))))
  191.     subsample = subsample + 1;
  192.  
  193.       pixel_region_init (&srcPR, drawable_data (GIMP_DRAWABLE (mask)), 
  194.              0, 0, 
  195.              drawable_width (GIMP_DRAWABLE (mask)), 
  196.              drawable_height (GIMP_DRAWABLE (mask)), FALSE);
  197.  
  198.       scaled_buf = mask_buf_new (width, height);
  199.       destPR.bytes = 1;
  200.       destPR.x = 0;
  201.       destPR.y = 0;
  202.       destPR.w = width;
  203.       destPR.h = height;
  204.       destPR.rowstride = srcPR.bytes * width;
  205.       destPR.data = mask_buf_data (scaled_buf);
  206.       destPR.tiles = NULL;
  207.  
  208.       subsample_region (&srcPR, &destPR, subsample);
  209.     }
  210.   else
  211.     {
  212.       pixel_region_init (&srcPR, drawable_data (GIMP_DRAWABLE (mask)), 
  213.              0, 0, 
  214.              drawable_width (GIMP_DRAWABLE (mask)), 
  215.              drawable_height (GIMP_DRAWABLE (mask)), FALSE);
  216.  
  217.       scaled_buf = mask_buf_new (width, height);
  218.       destPR.bytes = 1;
  219.       destPR.x = 0;
  220.       destPR.y = 0;
  221.       destPR.w = width;
  222.       destPR.h = height;
  223.       destPR.rowstride = srcPR.bytes * width;
  224.       destPR.data = mask_buf_data (scaled_buf);
  225.       destPR.tiles = NULL;
  226.  
  227.       copy_region (&srcPR, &destPR);
  228.     }
  229.  
  230.   *pheight = height;
  231.   *pwidth = width;
  232.    return scaled_buf;
  233. }
  234.  
  235.  
  236. static gint
  237. undo_history_set_pixmap_idle (gpointer data)
  238. {
  239.   idle_preview_args *idle = data;
  240.   static GdkGC *gc = NULL;
  241.   TempBuf   *buf = NULL;
  242.   GdkPixmap *pixmap;
  243.   UndoType   utype;
  244.   MaskBuf   *mbuf = NULL;
  245.   guchar    *src;
  246.   gdouble    r, g, b, a;
  247.   gdouble    c0, c1;
  248.   guchar    *p0, *p1, *even, *odd;
  249.   gint       width, height, bpp;
  250.   gint       x, y;
  251.  
  252.   if (!gc)
  253.     gc = gdk_gc_new (GTK_WIDGET (idle->clist)->window);
  254.  
  255.   width  = idle->gimage->width;
  256.   height = idle->gimage->height;
  257.  
  258.   /* Get right aspect ratio */  
  259.   if (width > height)
  260.     { 
  261.       height = (gint)(((gdouble)idle->size * (gdouble)height) / (gdouble)width + 0.5);
  262.       width  = (gint)(((gdouble)width * (gdouble)height)/ (gdouble)idle->gimage->height + 0.5);
  263.     }
  264.   else
  265.     {
  266.       width  = (gint)(((gdouble)idle->size * (gdouble)width) / (gdouble)height + 0.5);
  267.       height = (gint)(((gdouble)height * (gdouble)width ) /(gdouble) idle->gimage->width + 0.5);
  268.     }
  269.  
  270.   utype = undo_get_undo_top_type (idle->gimage);
  271.  
  272.   if((utype != MASK_UNDO && utype != QMASK_UNDO) || 
  273.      (mbuf = mask_render_preview (idle->gimage,&width,&height)) == NULL)
  274.     {
  275.       buf = gimp_image_construct_composite_preview (idle->gimage, width, height);
  276.       bpp = buf->bytes;
  277.       src = temp_buf_data (buf);
  278.     }
  279.   else
  280.     {
  281.       src = mask_buf_data (mbuf); 
  282.       bpp = 1; /* Always the case for masks */
  283.     } 
  284.  
  285.   pixmap = gdk_pixmap_new (GTK_WIDGET (idle->clist)->window, width+2, height+2, -1);
  286.   
  287.   gdk_draw_rectangle (pixmap, 
  288.               GTK_WIDGET (idle->clist)->style->black_gc,
  289.               TRUE,
  290.               0, 0,
  291.               width + 2, height + 2);
  292.  
  293.   even = g_malloc (width * 3);
  294.   odd  = g_malloc (width * 3);
  295.  
  296.   for (y = 0; y < height; y++)
  297.     {
  298.       p0 = even;
  299.       p1 = odd;
  300.  
  301.       for (x = 0; x < width; x++)
  302.     {
  303.       if (bpp == 4)
  304.         {
  305.           r = ((gdouble) src[x*4+0]) / 255.0;
  306.           g = ((gdouble) src[x*4+1]) / 255.0;
  307.           b = ((gdouble) src[x*4+2]) / 255.0;
  308.           a = ((gdouble) src[x*4+3]) / 255.0;
  309.         }
  310.       else if (bpp == 3)
  311.         {
  312.           r = ((gdouble) src[x*3+0]) / 255.0;
  313.           g = ((gdouble) src[x*3+1]) / 255.0;
  314.           b = ((gdouble) src[x*3+2]) / 255.0;
  315.           a = 1.0;
  316.         }
  317.       else
  318.         {
  319.           r = ((gdouble) src[x*bpp+0]) / 255.0;
  320.           g = b = r;
  321.           if (bpp == 2)
  322.         a = ((gdouble) src[x*bpp+1]) / 255.0;
  323.           else
  324.         a = 1.0;
  325.         }
  326.  
  327.       if ((x / GIMP_CHECK_SIZE_SM) & 1)
  328.         {
  329.           c0 = GIMP_CHECK_LIGHT;
  330.           c1 = GIMP_CHECK_DARK;
  331.         }
  332.       else
  333.         {
  334.           c0 = GIMP_CHECK_DARK;
  335.           c1 = GIMP_CHECK_LIGHT;
  336.         }
  337.  
  338.       *p0++ = (c0 + (r - c0) * a) * 255.0;
  339.       *p0++ = (c0 + (g - c0) * a) * 255.0;
  340.       *p0++ = (c0 + (b - c0) * a) * 255.0;
  341.  
  342.       *p1++ = (c1 + (r - c1) * a) * 255.0;
  343.       *p1++ = (c1 + (g - c1) * a) * 255.0;
  344.       *p1++ = (c1 + (b - c1) * a) * 255.0;
  345.  
  346.     }
  347.       
  348.       if ((y / GIMP_CHECK_SIZE_SM) & 1)
  349.     {
  350.       gdk_draw_rgb_image (pixmap, gc,
  351.                   1, y + 1,
  352.                   width, 1,
  353.                   GDK_RGB_DITHER_NORMAL,
  354.                   (guchar *) odd, 3);
  355.     }
  356.       else
  357.     {
  358.       gdk_draw_rgb_image (pixmap, gc,
  359.                   1, y + 1,
  360.                   width, 1,
  361.                   GDK_RGB_DITHER_NORMAL,
  362.                   (guchar *) even, 3);
  363.     }
  364.       src += width * bpp;
  365.     }
  366.  
  367.   g_free (even);
  368.   g_free (odd);
  369.  
  370.   if (buf)
  371.     temp_buf_free (buf);
  372.   if (mbuf)
  373.     mask_buf_free (mbuf);
  374.  
  375.   gtk_clist_set_row_data (idle->clist, idle->row, (gpointer)2);
  376.   gtk_clist_set_pixmap (idle->clist, idle->row, 0, pixmap, NULL);
  377.   gdk_pixmap_unref (pixmap);
  378.   
  379.   return (FALSE);
  380. }
  381.  
  382. /* check if a preview is already made, otherwise gtk_idle_add the pixmap func */ 
  383. static void
  384. undo_history_set_pixmap (GtkCList *clist,
  385.              gint      row,
  386.              gint      size,
  387.              GImage   *gimage)
  388. {
  389.   static idle_preview_args idle;
  390.   
  391.   if (!size || ((gint)gtk_clist_get_row_data (clist, row)) == 2)
  392.     return;
  393.     
  394.   idle.clist  = clist;
  395.   idle.row    = row;
  396.   idle.size   = size;
  397.   idle.gimage = gimage;
  398.  
  399.   gtk_idle_add ((GtkFunction)undo_history_set_pixmap_idle, &idle);
  400. }
  401.  
  402.  
  403. /* close button clicked */
  404. static void
  405. undo_history_close_callback (GtkWidget *widget,
  406.                  gpointer   data)
  407. {
  408.   undo_history_st *st = data;
  409.   gtk_widget_hide (GTK_WIDGET (st->shell));
  410. }
  411.  
  412. /* The gimage and shell destroy callbacks are split so we can:
  413.  *   a) blow the shell when the image dissappears
  414.  *   b) disconnect from the image if the shell dissappears (we don't
  415.  *        want signals from the image to carry on using "st" once it's
  416.  *        been freed.
  417.  */
  418.  
  419. /* gimage renamed */
  420. static void
  421. undo_history_gimage_rename_callback (GimpImage *gimage,
  422.                      gpointer   data)
  423. {
  424.   undo_history_st *st = data;
  425.   gchar *title;
  426.  
  427.   title = g_strdup_printf (_("Undo History: %s"),
  428.                g_basename (gimage_filename (gimage)));
  429.   gtk_window_set_title (GTK_WINDOW (st->shell), title);
  430.   g_free (title);
  431. }
  432.  
  433. /* gimage destroyed */
  434. static void
  435. undo_history_gimage_destroy_callback (GimpImage *gimage,
  436.                       gpointer   data)
  437. {
  438.   undo_history_st *st = data;
  439.  
  440.   st->gimage = NULL;  /* not allowed to use this any more */
  441.   dialog_unregister (st->shell);
  442.   gtk_widget_destroy (GTK_WIDGET (st->shell));
  443.   /* which continues in the function below: */
  444. }
  445.  
  446. static void
  447. undo_history_shell_destroy_callback (GtkWidget *widget,
  448.                      gpointer   data)
  449. {
  450.   undo_history_st *st = data;
  451.  
  452.   if (st->gimage)
  453.     gtk_signal_disconnect_by_data (GTK_OBJECT (st->gimage), st);
  454.   g_free (st);
  455. }
  456.  
  457. /* undo button clicked */
  458. static void
  459. undo_history_undo_callback (GtkWidget *widget,
  460.                 gpointer   data)
  461. {
  462.   undo_history_st *st = data;
  463.  
  464.   undo_pop (st->gimage);
  465. }
  466.  
  467. /* redo button clicked */
  468. static void
  469. undo_history_redo_callback (GtkWidget *widget,
  470.                 gpointer   data)
  471. {
  472.   undo_history_st *st = data;
  473.  
  474.   undo_redo (st->gimage);
  475. }
  476.  
  477.  
  478. /* Always start clist with dummy entry for image state before
  479.  * the first action on the undo stack */
  480. static void
  481. undo_history_prepend_special (GtkCList *clist)
  482. {
  483.   gchar *name = _("[ base image ]");
  484.   gchar *namelist[3];
  485.   gint   row;
  486.  
  487.   namelist[0] = NULL;
  488.   namelist[1] = NULL;
  489.   namelist[2] = name;
  490.  
  491.   row = gtk_clist_prepend (clist, namelist);
  492. }
  493.  
  494.  
  495. /* Recalculate which of the undo and redo buttons are meant to be sensitive */
  496. static void
  497. undo_history_set_sensitive (undo_history_st *st,
  498.                 gint             rows)
  499. {
  500.   gtk_widget_set_sensitive (st->undo_button, (st->old_selection != 0));
  501.   gtk_widget_set_sensitive (st->redo_button, (st->old_selection != rows-1));
  502. }
  503.  
  504.  
  505. /* Track undo_event signals, telling us of changes to the undo and
  506.  * redo stacks. */
  507. static void
  508. undo_history_undo_event (GtkWidget *widget,
  509.              gint       ev,
  510.              gpointer   data)
  511. {
  512.   undo_history_st *st = data;
  513.   undo_event_t event = ev;
  514.   const gchar *name;
  515.   gchar *namelist[3];
  516.   GList *list;
  517.   gint cur_selection;
  518.   GtkCList *clist;
  519.   gint row;
  520.   GdkPixmap *pixmap;
  521.   GdkBitmap *mask;
  522.  
  523.   list = GTK_CLIST (st->clist)->selection;
  524.   g_return_if_fail (list != NULL);
  525.   cur_selection = GPOINTER_TO_INT (list->data);
  526.  
  527.   clist = GTK_CLIST (st->clist); 
  528.  
  529.   /* block select events */
  530.   gtk_signal_handler_block_by_data (GTK_OBJECT (st->clist), st);
  531.  
  532.   switch (event)
  533.     {
  534.     case UNDO_PUSHED:
  535.       /* clip everything after the current selection (ie, the
  536.        * actions that are from the redo stack) */
  537.       gtk_clist_freeze (clist);
  538.       while (clist->rows > cur_selection + 1)
  539.     gtk_clist_remove (clist, cur_selection + 1);
  540.  
  541.       /* find out what's new */
  542.       name = undo_get_undo_name (st->gimage);
  543.       namelist[0] = NULL;
  544.       namelist[1] = NULL;
  545.       namelist[2] = (char *) name;
  546.       row = gtk_clist_append (clist, namelist);
  547.       g_assert (clist->rows == cur_selection + 2);
  548.  
  549.       undo_history_set_pixmap (clist, row, st->preview_size, st->gimage);
  550.  
  551.       /* always force selection to bottom, and scroll to it */
  552.       gtk_clist_select_row (clist, clist->rows - 1, -1);
  553.       gtk_clist_thaw (clist);
  554.       gtk_clist_moveto (clist, clist->rows - 1, 0, 1.0, 0.0);
  555.       cur_selection = clist->rows - 1;
  556.       break;
  557.  
  558.     case UNDO_EXPIRED:
  559.       /* remove earliest row, but not our special first one */
  560.       if (gtk_clist_get_pixmap (clist, 1, 0, &pixmap, &mask))
  561.     gtk_clist_set_pixmap (clist, 0, 0, pixmap, mask);
  562.       gtk_clist_remove (clist, 1);
  563.       break;
  564.  
  565.     case UNDO_POPPED:
  566.       /* move hilight up one */
  567.       g_return_if_fail (cur_selection >= 1);
  568.       gtk_clist_select_row (clist, cur_selection - 1, -1);
  569.       cur_selection--;
  570.       undo_history_set_pixmap (clist, cur_selection, st->preview_size, st->gimage);
  571.       if ( !(gtk_clist_row_is_visible (clist, cur_selection) & GTK_VISIBILITY_FULL))
  572.     gtk_clist_moveto (clist, cur_selection, 0, 0.0, 0.0);
  573.       break;
  574.  
  575.      case UNDO_REDO:
  576.        /* move hilight down one */
  577.        g_return_if_fail (cur_selection+1 < clist->rows);
  578.        gtk_clist_select_row (clist, cur_selection+1, -1);
  579.        cur_selection++;
  580.        undo_history_set_pixmap (clist, cur_selection, st->preview_size, st->gimage);
  581.        if ( !(gtk_clist_row_is_visible (clist, cur_selection) & GTK_VISIBILITY_FULL))
  582.      gtk_clist_moveto (clist, cur_selection, 0, 1.0, 0.0);
  583.        break;
  584.  
  585.     case UNDO_FREE:
  586.       /* clear all info other that the special first line */
  587.       gtk_clist_freeze (clist);
  588.       gtk_clist_clear (clist);
  589.       undo_history_prepend_special (clist);
  590.       gtk_clist_thaw (clist);
  591.       cur_selection = 0;
  592.       break;
  593.     }
  594.  
  595.   /*  if the image is clean, set the clean pixmap  */ 
  596.   if (st->gimage->dirty == 0)
  597.     gtk_clist_set_pixmap (clist, cur_selection, 1, clean_pixmap, clean_mask);
  598.  
  599.   gtk_signal_handler_unblock_by_data (GTK_OBJECT (st->clist), st);
  600.  
  601.   st->old_selection = cur_selection;
  602.   undo_history_set_sensitive (st, clist->rows);
  603. }
  604.  
  605.  
  606. static void
  607. undo_history_select_row_callback (GtkWidget *widget,
  608.                   gint       row,
  609.                   gint       column,
  610.                   gpointer   event,
  611.                   gpointer   data)
  612. {
  613.   undo_history_st *st = data;
  614.   gint cur_selection;
  615.  
  616.   cur_selection = row;
  617.  
  618.   if (cur_selection == st->old_selection)
  619.     return;
  620.  
  621.   /* Disable undo_event signals while we do these multiple undo or
  622.    * redo actions. */
  623.   gtk_signal_handler_block_by_func (GTK_OBJECT (st->gimage),
  624.                     undo_history_undo_event, st);
  625.  
  626.   while (cur_selection < st->old_selection)
  627.     {
  628.       undo_pop (st->gimage);
  629.       st->old_selection--;
  630.     }
  631.   while (cur_selection > st->old_selection)
  632.     {
  633.       undo_redo (st->gimage);
  634.       st->old_selection++;
  635.     }
  636.  
  637.   undo_history_set_pixmap (GTK_CLIST (widget), cur_selection, st->preview_size, st->gimage);
  638.  
  639.   /*  if the image is clean, set the clean pixmap  */ 
  640.   if (st->gimage->dirty == 0)
  641.     gtk_clist_set_pixmap (GTK_CLIST (widget), cur_selection, 1, clean_pixmap, clean_mask);
  642.  
  643.   gtk_signal_handler_unblock_by_func (GTK_OBJECT (st->gimage),
  644.                       undo_history_undo_event, st);    
  645.  
  646.   undo_history_set_sensitive (st, GTK_CLIST(st->clist)->rows);
  647. }
  648.  
  649.  
  650. static void
  651. undo_history_clean_callback (GtkWidget *widget,
  652.                  gpointer   data)
  653. {
  654.   undo_history_st *st = data;
  655.   gint i;
  656.   gint nrows;
  657.   GtkCList *clist;
  658.  
  659.   if (st->gimage->dirty != 0)
  660.     return;
  661.  
  662.   /* 
  663.    * The image has become clean. Remove the clean_pixmap from 
  664.    * all entries. It will be set in the undo_event or select_row
  665.    * callbacks. 
  666.    * Ugly, but works better than before. The actual problem is 
  667.    * that the "clean" signal is emitted before UNDO_POPPED event,
  668.    * so we can not simply set the clean pixmap here.
  669.    */ 
  670.  
  671.   clist = GTK_CLIST (st->clist);
  672.   nrows = clist->rows;
  673.  
  674.   gtk_clist_freeze (clist);
  675.   for (i=0; i < nrows; i++)
  676.     gtk_clist_set_text (clist, i, 1, NULL);
  677.   gtk_clist_thaw (clist);
  678. }
  679.  
  680.  
  681. /* Used to build up initial contents of clist */
  682. static gboolean
  683. undo_history_init_undo (const gchar *undoitemname,
  684.             void        *data)
  685. {
  686.   undo_history_st *st = data;
  687.   gchar *namelist[3];
  688.   gint row;
  689.  
  690.   namelist[0] = NULL;
  691.   namelist[1] = NULL;
  692.   namelist[2] = (gchar *) undoitemname;
  693.   row = gtk_clist_prepend (GTK_CLIST (st->clist), namelist);
  694.   gtk_clist_set_pixmap (GTK_CLIST (st->clist), row, 0,
  695.             clear_pixmap, clear_mask);
  696.  
  697.   return FALSE;
  698. }
  699.  
  700. /* Ditto */
  701. static gboolean
  702. undo_history_init_redo (const char *undoitemname,
  703.             void       *data)
  704. {
  705.   undo_history_st *st = data;
  706.   gchar *namelist[3];
  707.   gint row;
  708.  
  709.   namelist[0] = NULL;  namelist[1] = NULL;
  710.   namelist[2] = (gchar *) undoitemname;
  711.   row = gtk_clist_append (GTK_CLIST (st->clist), namelist);
  712.   gtk_clist_set_pixmap (GTK_CLIST (st->clist), row, 0,
  713.             clear_pixmap, clear_mask);
  714.  
  715.   return FALSE;
  716. }
  717.  
  718.  
  719. /*************************************************************/
  720. /* Publicly exported function */
  721.  
  722. GtkWidget *
  723. undo_history_new (GImage *gimage)
  724. {
  725.   undo_history_st *st;
  726.   GtkWidget *vbox;
  727.   GtkWidget *hbox;
  728.   GtkWidget *button;
  729.   GtkWidget *scrolled_win;
  730.  
  731.   st = g_new0 (undo_history_st, 1);
  732.   st->gimage = gimage;
  733.   st->preview_size = preview_size;
  734.  
  735.   /*  gimage signals  */
  736.   gtk_signal_connect (GTK_OBJECT (gimage), "undo_event",
  737.               GTK_SIGNAL_FUNC (undo_history_undo_event),
  738.               st);
  739.   gtk_signal_connect (GTK_OBJECT (gimage), "rename",
  740.               GTK_SIGNAL_FUNC (undo_history_gimage_rename_callback),
  741.               st);
  742.   gtk_signal_connect (GTK_OBJECT (gimage), "destroy",
  743.               GTK_SIGNAL_FUNC (undo_history_gimage_destroy_callback),
  744.               st);
  745.   gtk_signal_connect (GTK_OBJECT (gimage), "clean",
  746.               GTK_SIGNAL_FUNC (undo_history_clean_callback),
  747.               st);
  748.  
  749.   /*  The shell and main vbox  */
  750.   {
  751.     gchar *title = g_strdup_printf (_("Undo History: %s"),
  752.                     g_basename (gimage_filename (gimage)));
  753.     st->shell = gimp_dialog_new (title, "undo_history",
  754.                  gimp_standard_help_func,
  755.                  "dialogs/undo_history.html",
  756.                  GTK_WIN_POS_NONE,
  757.                  FALSE, TRUE, FALSE,
  758.  
  759.                  _("Close"), undo_history_close_callback,
  760.                  st, NULL, NULL, TRUE, TRUE,
  761.  
  762.                  NULL);
  763.     dialog_register (st->shell);
  764.  
  765.     g_free (title);
  766.   }
  767.  
  768.   vbox = gtk_vbox_new (FALSE, 2);
  769.   gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
  770.   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (st->shell)->vbox), vbox);
  771.   gtk_widget_show (vbox);
  772.  
  773.   gtk_signal_connect (GTK_OBJECT (st->shell), "destroy",
  774.               GTK_SIGNAL_FUNC (undo_history_shell_destroy_callback),
  775.               st);
  776.  
  777.   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
  778.   gtk_widget_set_usize (GTK_WIDGET (scrolled_win), 
  779.             160 + st->preview_size,
  780.             4 * (MAX (st->preview_size, 16) + 6));
  781.  
  782.   /* clist of undo actions */
  783.   st->clist = gtk_clist_new (3);
  784.   gtk_clist_set_selection_mode (GTK_CLIST (st->clist), GTK_SELECTION_BROWSE);
  785.   gtk_clist_set_reorderable (GTK_CLIST (st->clist), FALSE);
  786.   gtk_clist_set_row_height (GTK_CLIST (st->clist), MAX (st->preview_size, 16) + 4);
  787.   gtk_clist_set_column_width (GTK_CLIST (st->clist), 0, st->preview_size + 2);
  788.   gtk_clist_set_column_width (GTK_CLIST (st->clist), 1, 18);
  789.   gtk_clist_set_column_min_width (GTK_CLIST (st->clist), 2, 64);
  790.  
  791.   /* allocate the pixmaps if not already done */
  792.   if (!clean_pixmap)
  793.     {
  794.       GtkStyle *style;
  795.  
  796.       gtk_widget_realize (st->shell);
  797.       style = gtk_widget_get_style (st->shell);
  798.  
  799.       clean_pixmap =
  800.     gdk_pixmap_create_from_xpm_d (st->shell->window,
  801.                       &clean_mask,
  802.                       &style->bg[GTK_STATE_NORMAL],
  803.                       yes_xpm);
  804.  
  805.       clear_pixmap =
  806.     gdk_pixmap_create_from_xpm_d (st->shell->window,
  807.                       &clear_mask,
  808.                       &style->bg[GTK_STATE_NORMAL],
  809.                       question_xpm);
  810.    }
  811.  
  812.   /* work out the initial contents */
  813.   undo_map_over_undo_stack (st->gimage, undo_history_init_undo, st);
  814.   /* force selection to bottom */
  815.   gtk_clist_select_row (GTK_CLIST (st->clist),
  816.             GTK_CLIST (st->clist)->rows - 1, -1);
  817.   undo_map_over_redo_stack (st->gimage, undo_history_init_redo, st);
  818.   undo_history_prepend_special (GTK_CLIST (st->clist));
  819.   st->old_selection = GPOINTER_TO_INT(GTK_CLIST(st->clist)->selection->data);
  820.  
  821.   /* draw the preview of the current state */
  822.   undo_history_set_pixmap (GTK_CLIST (st->clist),
  823.                st->old_selection, st->preview_size, st->gimage);
  824.  
  825.   gtk_signal_connect (GTK_OBJECT (st->clist), "select_row",
  826.               GTK_SIGNAL_FUNC (undo_history_select_row_callback),
  827.               st);
  828.  
  829.   /*  if the image is clean, set the clean pixmap  */ 
  830.   if (st->gimage->dirty == 0)
  831.     gtk_clist_set_pixmap (GTK_CLIST (st->clist), st->old_selection, 1, clean_pixmap, clean_mask);
  832.  
  833.   gtk_widget_show (GTK_WIDGET (st->clist));
  834.  
  835.   gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0);
  836.   gtk_widget_show (GTK_WIDGET (scrolled_win));
  837.   gtk_container_add (GTK_CONTAINER (scrolled_win), st->clist);
  838.   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
  839.                   GTK_POLICY_NEVER,
  840.                   GTK_POLICY_ALWAYS);
  841.  
  842.   hbox = gtk_hbox_new (FALSE, 6);
  843.   gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
  844.   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  845.   gtk_widget_show (hbox);
  846.  
  847.   st->undo_button = button = gimp_pixmap_button_new (raise_xpm, _("Undo"));
  848.   gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  849.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  850.               GTK_SIGNAL_FUNC (undo_history_undo_callback),
  851.               st);
  852.   gtk_widget_show (GTK_WIDGET (button));
  853.  
  854.   st->redo_button = button = gimp_pixmap_button_new (lower_xpm, _("Redo"));
  855.   gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  856.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  857.               GTK_SIGNAL_FUNC (undo_history_redo_callback),
  858.               st);
  859.   gtk_widget_show (GTK_WIDGET (button));
  860.  
  861.   undo_history_set_sensitive (st, GTK_CLIST (st->clist)->rows);
  862.  
  863.   gtk_widget_show (GTK_WIDGET (st->shell));
  864.   gtk_clist_moveto (GTK_CLIST (st->clist), st->old_selection, 0, 0.5, 0.0);
  865.  
  866.   return st->shell;
  867. }
  868.