gimp/app/undo_history.c
Sven Neumann 073e533a8a Finally landed the new GimpConfig based gimprc parser. It's not finished
2002-11-18  Sven Neumann  <sven@gimp.org>

	Finally landed the new GimpConfig based gimprc parser. It's not
	finished yet but we need to start somewhere. This release removes
	the old gimprc.[ch] files. The gimprc format changes slightly, but
	the changes are minimal. The Preferences dialog is temporarily
	disabled since it still needs to be ported. If you are are afraid,
	stay away from CVS for a few days ;-)

	* app/Makefile.am
	* app/gimprc.[ch]: removed the old gimprc system.

	* app/base/Makefile.am
	* app/base/base-config.[ch]: removed these files in favor of
	config/gimpbaseconfig.[ch].

	* app/core/Makefile.am
	* app/core/gimpcoreconfig.[ch]: removed these files in favor of
	config/gimpcoreconfig.[ch].

	* app/config/Makefile.am
	* app/config/config-types.h: moved typedefs into this new file.

	* app/config/gimpbaseconfig.[ch]
	* app/config/gimpcoreconfig.[ch]
	* app/config/gimpdisplayconfig.[ch]
	* app/config/gimpguiconfig.[ch]
	* app/config/gimprc.[ch]
	* app/config/test-config.c: brought into shape for real use.

	* app/base/base-types.h: include config/config-types.h here. Added
	a global GimpBaseConfig *base_config variable to ease migration.

	* app/gui/Makefile.am: temporarily disabled the preferences dialog.

	* app/app_procs.c
	* app/undo.c
	* app/undo_history.c
	* app/base/base.[ch]
	* app/base/gimphistogram.c
	* app/base/pixel-processor.c
	* app/base/temp-buf.c
	* app/base/tile-cache.c
	* app/core/core-types.h
	* app/core/gimp-documents.c
	* app/core/gimp.c
	* app/core/gimpbrush.c
	* app/core/gimpbrushgenerated.c
	* app/core/gimpcontext.c
	* app/core/gimpdrawable-transform.c
	* app/core/gimpimage-new.c
	* app/core/gimpimage.c
	* app/core/gimpimagefile.c
	* app/core/gimpmodules.c
	* app/core/gimppattern.c
	* app/display/Makefile.am
	* app/display/gimpdisplay-handlers.c
	* app/display/gimpdisplay.[ch]
	* app/display/gimpdisplayshell-callbacks.c
	* app/display/gimpdisplayshell-handlers.c
	* app/display/gimpdisplayshell-layer-select.c
	* app/display/gimpdisplayshell-render.c
	* app/display/gimpdisplayshell-scale.c
	* app/display/gimpdisplayshell-scroll.c
	* app/display/gimpdisplayshell-selection.c
	* app/display/gimpdisplayshell.[ch]
	* app/display/gimpnavigationview.c
	* app/file/file-save.c
	* app/gui/device-status-dialog.c
	* app/gui/dialogs-constructors.c
	* app/gui/file-commands.c
	* app/gui/file-new-dialog.c
	* app/gui/file-open-dialog.c
	* app/gui/file-save-dialog.c
	* app/gui/gui.c
	* app/gui/menus.c
	* app/gui/paths-dialog.c
	* app/gui/resize-dialog.c
	* app/gui/session.c
	* app/gui/test-commands.c
	* app/gui/tips-dialog.c
	* app/gui/tips-dialog.h
	* app/gui/user-install-dialog.c
	* app/gui/view-commands.c
	* app/paint/gimppaintcore.c
	* app/plug-in/plug-in.c
	* app/plug-in/plug-ins.c
	* app/tools/gimpbezierselecttool.c
	* app/tools/gimpbucketfilltool.c
	* app/tools/gimpcolorpickertool.c
	* app/tools/gimpcroptool.c
	* app/tools/gimpeditselectiontool.c
	* app/tools/gimpfuzzyselecttool.c
	* app/tools/gimpinktool.c
	* app/tools/gimpmagnifytool.c
	* app/tools/gimpmeasuretool.c
	* app/tools/gimppainttool.c
	* app/tools/gimppathtool.c
	* app/tools/gimptexttool.[ch]
	* app/tools/selection_options.c
	* app/tools/tools.c
	* app/tools/transform_options.c
	* app/widgets/gimphelp.c
	* app/widgets/gimpitemfactory.c
	* app/widgets/gimpselectioneditor.c
	* app/xcf/xcf-load.c
	* tools/pdbgen/pdb/fileops.pdb
	* tools/pdbgen/pdb/gimprc.pdb
	* tools/pdbgen/pdb/image.pdb
	* tools/pdbgen/pdb/layer.pdb
	* tools/pdbgen/pdb/transform_tools.pdb: use the new config system
	instead of the old gimprc stuff.

	* etc/gimprc.in
	* etc/gimprc_user.in: adapted to the new gimprc format. Will update
	the man-page later...

	* app/pdb/fileops_cmds.c
	* app/pdb/gimprc_cmds.c
	* app/pdb/image_cmds.c
	* app/pdb/layer_cmds.c
	* app/pdb/transform_tools_cmds.c
	* libgimp/gimpgimprc_pdb.c: regenerated.
2002-11-18 20:50:31 +00:00

914 lines
26 KiB
C

/* The GIMP -- an image manipulation program
* Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Undo history browser by Austin Donnelly <austin@gimp.org>
*/
/* TODO:
*
* - reuse the L&C previews?
* Currently we use gimp_image_construct_composite_preview ()
* which makes use of the preview_cache on a per layer basis.
*
* - work out which (if any) is the clean image, and mark it as such.
* Currently, it's on the wrong line.
*
* - undo names are less than useful. This isn't a problem with
* undo_history.c itself, more with the rather chaotic way
* people have of picking an undo type when pushing undos, and
* inconsistent use of undo groups. Maybe rather than
* specifying an (enum) type, it should be a const char * ?
*
* BUGS:
* - clean pixmap in wrong place
*
* Initial rev 0.01, (c) 19 Sept 1999 Austin Donnelly <austin@gimp.org>
*
*/
#include "config.h"
#ifdef __GNUC__
#warning GTK_DISABLE_DEPRECATED
#endif
#undef GTK_DISABLE_DEPRECATED
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "display/display-types.h"
#include "config/gimpcoreconfig.h"
#include "base/pixel-region.h"
#include "base/temp-buf.h"
#include "paint-funcs/paint-funcs.h"
#include "core/gimp.h"
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimpimage-mask.h"
#include "file/file-utils.h"
#include "widgets/gimpviewabledialog.h"
#include "undo.h"
#include "undo_types.h"
#include "libgimp/gimpintl.h"
#include "pixmaps/yes.xpm"
#include "pixmaps/question.xpm"
typedef struct
{
GimpImage *gimage; /* image we're tracking undo info for */
GtkWidget *shell; /* dialog window */
GtkWidget *clist; /* list of undo actions */
GtkWidget *undo_button; /* button to undo an operation */
GtkWidget *redo_button; /* button to redo an operation */
int old_selection; /* previous selection in the clist */
int preview_size; /* size of the previews (from preferences) */
} undo_history_st;
typedef struct
{
GtkCList *clist;
gint row;
gint size;
GimpImage *gimage;
} idle_preview_args;
/*
* Theory of operation.
*
* Keep a clist. Each row of the clist corresponds to an image as it
* was at some time in the past, present or future. The selected row
* is the present image. Rows below the selected one are in the
* future - as redo operations are performed, they become the current
* image. Rows above the selected one are in the past - undo
* operations move the highlight up.
*
* The slight fly in the ointment is that if rows are images, then how
* should they be labelled? An undo or redo operation goes _between_
* two image states - it isn't an image state. It's a pretty
* arbitrary decision, but I've chosen to label a row with the name of
* the action that brought the image into the state represented by
* that row. Thus, there is a special first row without a meaningful
* label, which represents the image state before the first action has
* been done to it. The choice is between a special first row or a
* special last row. Since people mostly work near the leading edge,
* not often going all the way back, I've chosen to put the special
* case out of common sight.
*
* So, the undo stack contents appear above the selected row, and the
* redo stack below it.
*
* The clist is initialised by mapping over the undo and redo stack.
*
* Once initialised, the dialog listens to undo_event signals from the
* gimage. These undo events allow us to track changes to the undo
* and redo stacks. We follow the events, making parallel changes to
* the clist. If we ever get out of sync, there is no mechanism to
* notice or re-sync. A few g_return_if_fails should catch some of
* these cases.
*
* User clicks changing the selected row in the clist turn into
* multiple calls to undo_pop or undo_redo, with appropriate signals
* blocked so we don't get our own events back.
*
* The "Close" button hides the dialog, rather than destroying it.
* This may well need to be changed, since the dialog will continue to
* track updates, and if it's generating previews this might take too
* long for large images.
*
* The dialog is destroyed when the gimage it is tracking is
* destroyed. Note that a File/Revert destroys the current gimage and
* so blows the undo/redo stacks.
*
* --austin, 19/9/1999
*/
/**************************************************************/
/* Static Data */
static GdkPixmap *clean_pixmap = NULL;
static GdkBitmap *clean_mask = NULL;
static GdkPixmap *clear_pixmap = NULL;
static GdkBitmap *clear_mask = NULL;
static void undo_history_undo_event (GtkWidget *widget,
gint ev,
gpointer data);
static void undo_history_clean_callback (GtkWidget *widget,
gpointer data);
static void undo_history_select_row_callback (GtkWidget *widget,
gint row,
gint column,
gpointer event,
gpointer data);
/**************************************************************/
/* Local functions */
static MaskBuf *
mask_render_preview (GimpImage *gimage,
gint *pwidth,
gint *pheight)
{
GimpChannel *mask;
MaskBuf *scaled_buf = NULL;
PixelRegion srcPR, destPR;
gint subsample;
gint width, height;
gint scale;
mask = gimp_image_get_mask (gimage);
if ((gimp_drawable_width (GIMP_DRAWABLE(mask)) > *pwidth) ||
(gimp_drawable_height (GIMP_DRAWABLE(mask)) > *pheight))
{
if (((gfloat) gimp_drawable_width (GIMP_DRAWABLE (mask)) / (gfloat) *pwidth) >
((gfloat) gimp_drawable_height (GIMP_DRAWABLE (mask)) / (gfloat) *pheight))
{
width = *pwidth;
height = (gimp_drawable_height (GIMP_DRAWABLE (mask)) * (*pwidth)) / gimp_drawable_width (GIMP_DRAWABLE (mask));
}
else
{
width = (gimp_drawable_width (GIMP_DRAWABLE (mask)) * (*pheight)) / gimp_drawable_height (GIMP_DRAWABLE (mask));
height = *pheight;
}
scale = TRUE;
}
else
{
width = gimp_drawable_width (GIMP_DRAWABLE (mask));
height = gimp_drawable_height (GIMP_DRAWABLE (mask));
scale = FALSE;
}
/* if the mask is empty, no need to scale and update again */
if (gimp_image_mask_is_empty (gimage))
return NULL;
if (scale)
{
/* calculate 'acceptable' subsample */
subsample = 1;
while ((width * (subsample + 1) * 2 < gimp_drawable_width (GIMP_DRAWABLE (mask))) &&
(height * (subsample + 1) * 2 < gimp_drawable_height (GIMP_DRAWABLE (mask))))
subsample = subsample + 1;
pixel_region_init (&srcPR, gimp_drawable_data (GIMP_DRAWABLE (mask)),
0, 0,
gimp_drawable_width (GIMP_DRAWABLE (mask)),
gimp_drawable_height (GIMP_DRAWABLE (mask)), FALSE);
scaled_buf = mask_buf_new (width, height);
destPR.bytes = 1;
destPR.x = 0;
destPR.y = 0;
destPR.w = width;
destPR.h = height;
destPR.rowstride = srcPR.bytes * width;
destPR.data = mask_buf_data (scaled_buf);
destPR.tiles = NULL;
subsample_region (&srcPR, &destPR, subsample);
}
else
{
pixel_region_init (&srcPR, gimp_drawable_data (GIMP_DRAWABLE (mask)),
0, 0,
gimp_drawable_width (GIMP_DRAWABLE (mask)),
gimp_drawable_height (GIMP_DRAWABLE (mask)), FALSE);
scaled_buf = mask_buf_new (width, height);
destPR.bytes = 1;
destPR.x = 0;
destPR.y = 0;
destPR.w = width;
destPR.h = height;
destPR.rowstride = srcPR.bytes * width;
destPR.data = mask_buf_data (scaled_buf);
destPR.tiles = NULL;
copy_region (&srcPR, &destPR);
}
*pheight = height;
*pwidth = width;
return scaled_buf;
}
static gint
undo_history_set_pixmap_idle (gpointer data)
{
idle_preview_args *idle = data;
static GdkGC *gc = NULL;
TempBuf *buf = NULL;
GdkPixmap *pixmap;
UndoType utype;
MaskBuf *mbuf = NULL;
guchar *src;
gdouble r, g, b, a;
gdouble c0, c1;
guchar *p0, *p1, *even, *odd;
gint width, height, bpp;
gint x, y;
if (!gc)
gc = gdk_gc_new (GTK_WIDGET (idle->clist)->window);
width = idle->gimage->width;
height = idle->gimage->height;
/* Get right aspect ratio */
if (width > height)
{
height = (gint)(((gdouble)idle->size * (gdouble)height) / (gdouble)width + 0.5);
width = (gint)(((gdouble)width * (gdouble)height)/ (gdouble)idle->gimage->height + 0.5);
}
else
{
width = (gint)(((gdouble)idle->size * (gdouble)width) / (gdouble)height + 0.5);
height = (gint)(((gdouble)height * (gdouble)width ) /(gdouble) idle->gimage->width + 0.5);
}
utype = undo_get_undo_top_type (idle->gimage);
if ((utype != MASK_UNDO && utype != IMAGE_QMASK_UNDO) ||
(mbuf = mask_render_preview (idle->gimage, &width, &height)) == NULL)
{
buf = gimp_viewable_get_new_preview (GIMP_VIEWABLE (idle->gimage),
width,
height);
bpp = buf->bytes;
src = temp_buf_data (buf);
}
else
{
src = mask_buf_data (mbuf);
bpp = 1; /* Always the case for masks */
}
pixmap = gdk_pixmap_new (GTK_WIDGET (idle->clist)->window,
width + 2, height + 2,
-1);
gdk_draw_rectangle (pixmap,
GTK_WIDGET (idle->clist)->style->black_gc,
TRUE,
0, 0,
width + 2, height + 2);
even = g_malloc (width * 3);
odd = g_malloc (width * 3);
for (y = 0; y < height; y++)
{
p0 = even;
p1 = odd;
for (x = 0; x < width; x++)
{
if (bpp == 4)
{
r = ((gdouble) src[x*4+0]) / 255.0;
g = ((gdouble) src[x*4+1]) / 255.0;
b = ((gdouble) src[x*4+2]) / 255.0;
a = ((gdouble) src[x*4+3]) / 255.0;
}
else if (bpp == 3)
{
r = ((gdouble) src[x*3+0]) / 255.0;
g = ((gdouble) src[x*3+1]) / 255.0;
b = ((gdouble) src[x*3+2]) / 255.0;
a = 1.0;
}
else
{
r = ((gdouble) src[x*bpp+0]) / 255.0;
g = b = r;
if (bpp == 2)
a = ((gdouble) src[x*bpp+1]) / 255.0;
else
a = 1.0;
}
if ((x / GIMP_CHECK_SIZE_SM) & 1)
{
c0 = GIMP_CHECK_LIGHT;
c1 = GIMP_CHECK_DARK;
}
else
{
c0 = GIMP_CHECK_DARK;
c1 = GIMP_CHECK_LIGHT;
}
*p0++ = (c0 + (r - c0) * a) * 255.0;
*p0++ = (c0 + (g - c0) * a) * 255.0;
*p0++ = (c0 + (b - c0) * a) * 255.0;
*p1++ = (c1 + (r - c1) * a) * 255.0;
*p1++ = (c1 + (g - c1) * a) * 255.0;
*p1++ = (c1 + (b - c1) * a) * 255.0;
}
if ((y / GIMP_CHECK_SIZE_SM) & 1)
{
gdk_draw_rgb_image (pixmap, gc,
1, y + 1,
width, 1,
GDK_RGB_DITHER_NORMAL,
(guchar *) odd, 3);
}
else
{
gdk_draw_rgb_image (pixmap, gc,
1, y + 1,
width, 1,
GDK_RGB_DITHER_NORMAL,
(guchar *) even, 3);
}
src += width * bpp;
}
g_free (even);
g_free (odd);
if (buf)
temp_buf_free (buf);
if (mbuf)
mask_buf_free (mbuf);
gtk_clist_set_row_data (idle->clist, idle->row, (gpointer)2);
gtk_clist_set_pixmap (idle->clist, idle->row, 0, pixmap, NULL);
g_object_unref (pixmap);
return (FALSE);
}
/* check if a preview is already made, otherwise gtk_idle_add the pixmap func */
static void
undo_history_set_pixmap (GtkCList *clist,
gint row,
gint size,
GimpImage *gimage)
{
static idle_preview_args idle;
if (!size || ((gint)gtk_clist_get_row_data (clist, row)) == 2)
return;
idle.clist = clist;
idle.row = row;
idle.size = size;
idle.gimage = gimage;
gtk_idle_add ((GtkFunction)undo_history_set_pixmap_idle, &idle);
}
/* close button clicked */
static void
undo_history_close_callback (GtkWidget *widget,
gpointer data)
{
undo_history_st *st = data;
gtk_widget_hide (GTK_WIDGET (st->shell));
}
/* The gimage and shell destroy callbacks are split so we can:
* a) blow the shell when the image dissappears
* b) disconnect from the image if the shell dissappears (we don't
* want signals from the image to carry on using "st" once it's
* been freed.
*/
/* gimage renamed */
static void
undo_history_gimage_rename_callback (GimpImage *gimage,
gpointer data)
{
undo_history_st *st = data;
gchar *basename;
gchar *title;
basename = g_path_get_basename (gimp_image_get_uri (gimage));
title = g_strdup_printf (_("Undo History: %s"), basename);
g_free (basename);
gtk_window_set_title (GTK_WINDOW (st->shell), title);
g_free (title);
}
static void
undo_history_shell_destroy_callback (GtkWidget *widget,
gpointer data)
{
undo_history_st *st = data;
if (st->gimage)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (st->gimage),
undo_history_undo_event,
st);
g_signal_handlers_disconnect_by_func (G_OBJECT (st->gimage),
undo_history_gimage_rename_callback,
st);
g_signal_handlers_disconnect_by_func (G_OBJECT (st->gimage),
undo_history_clean_callback,
st);
}
g_free (st);
}
/* undo button clicked */
static void
undo_history_undo_callback (GtkWidget *widget,
gpointer data)
{
undo_history_st *st = data;
if (undo_pop (st->gimage))
gimp_image_flush (st->gimage);
}
/* redo button clicked */
static void
undo_history_redo_callback (GtkWidget *widget,
gpointer data)
{
undo_history_st *st = data;
if (undo_redo (st->gimage))
gimp_image_flush (st->gimage);
}
/* Always start clist with dummy entry for image state before
* the first action on the undo stack */
static void
undo_history_prepend_special (GtkCList *clist)
{
gchar *name = _("[ base image ]");
gchar *namelist[3];
gint row;
namelist[0] = NULL;
namelist[1] = NULL;
namelist[2] = name;
row = gtk_clist_prepend (clist, namelist);
}
/* Recalculate which of the undo and redo buttons are meant to be sensitive */
static void
undo_history_set_sensitive (undo_history_st *st,
gint rows)
{
gtk_widget_set_sensitive (st->undo_button, (st->old_selection != 0));
gtk_widget_set_sensitive (st->redo_button, (st->old_selection != rows-1));
}
/* Track undo_event signals, telling us of changes to the undo and
* redo stacks. */
static void
undo_history_undo_event (GtkWidget *widget,
gint ev,
gpointer data)
{
undo_history_st *st = data;
undo_event_t event = ev;
const gchar *name;
gchar *namelist[3];
GList *list;
gint cur_selection;
GtkCList *clist;
gint row;
GdkPixmap *pixmap;
GdkBitmap *mask;
list = GTK_CLIST (st->clist)->selection;
g_return_if_fail (list != NULL);
cur_selection = GPOINTER_TO_INT (list->data);
clist = GTK_CLIST (st->clist);
/* block select events */
g_signal_handlers_block_by_func (G_OBJECT (st->clist),
undo_history_select_row_callback,
st);
switch (event)
{
case UNDO_PUSHED:
/* clip everything after the current selection (ie, the
* actions that are from the redo stack) */
gtk_clist_freeze (clist);
while (clist->rows > cur_selection + 1)
gtk_clist_remove (clist, cur_selection + 1);
/* find out what's new */
name = undo_get_undo_name (st->gimage);
namelist[0] = NULL;
namelist[1] = NULL;
namelist[2] = (char *) name;
row = gtk_clist_append (clist, namelist);
g_assert (clist->rows == cur_selection + 2);
undo_history_set_pixmap (clist, row, st->preview_size, st->gimage);
/* always force selection to bottom, and scroll to it */
gtk_clist_select_row (clist, clist->rows - 1, -1);
gtk_clist_thaw (clist);
gtk_clist_moveto (clist, clist->rows - 1, 0, 1.0, 0.0);
cur_selection = clist->rows - 1;
break;
case UNDO_EXPIRED:
/* remove earliest row, but not our special first one */
if (gtk_clist_get_pixmap (clist, 1, 0, &pixmap, &mask))
gtk_clist_set_pixmap (clist, 0, 0, pixmap, mask);
gtk_clist_remove (clist, 1);
break;
case UNDO_POPPED:
/* move hilight up one */
g_return_if_fail (cur_selection >= 1);
gtk_clist_select_row (clist, cur_selection - 1, -1);
cur_selection--;
undo_history_set_pixmap (clist, cur_selection, st->preview_size, st->gimage);
if ( !(gtk_clist_row_is_visible (clist, cur_selection) & GTK_VISIBILITY_FULL))
gtk_clist_moveto (clist, cur_selection, 0, 0.0, 0.0);
break;
case UNDO_REDO:
/* move hilight down one */
g_return_if_fail (cur_selection+1 < clist->rows);
gtk_clist_select_row (clist, cur_selection+1, -1);
cur_selection++;
undo_history_set_pixmap (clist, cur_selection, st->preview_size, st->gimage);
if ( !(gtk_clist_row_is_visible (clist, cur_selection) & GTK_VISIBILITY_FULL))
gtk_clist_moveto (clist, cur_selection, 0, 1.0, 0.0);
break;
case UNDO_FREE:
/* clear all info other that the special first line */
gtk_clist_freeze (clist);
gtk_clist_clear (clist);
undo_history_prepend_special (clist);
gtk_clist_thaw (clist);
cur_selection = 0;
break;
}
/* if the image is clean, set the clean pixmap */
if (st->gimage->dirty == 0)
gtk_clist_set_pixmap (clist, cur_selection, 1, clean_pixmap, clean_mask);
g_signal_handlers_unblock_by_func (G_OBJECT (st->clist),
undo_history_select_row_callback,
st);
st->old_selection = cur_selection;
undo_history_set_sensitive (st, clist->rows);
}
static void
undo_history_select_row_callback (GtkWidget *widget,
gint row,
gint column,
gpointer event,
gpointer data)
{
undo_history_st *st = data;
gint cur_selection;
cur_selection = row;
if (cur_selection == st->old_selection)
return;
/* Disable undo_event signals while we do these multiple undo or
* redo actions. */
g_signal_handlers_block_by_func (G_OBJECT (st->gimage),
undo_history_undo_event, st);
while (cur_selection < st->old_selection)
{
undo_pop (st->gimage);
st->old_selection--;
}
while (cur_selection > st->old_selection)
{
undo_redo (st->gimage);
st->old_selection++;
}
gimp_image_flush (st->gimage);
undo_history_set_pixmap (GTK_CLIST (widget),
cur_selection, st->preview_size, st->gimage);
/* if the image is clean, set the clean pixmap */
if (st->gimage->dirty == 0)
gtk_clist_set_pixmap (GTK_CLIST (widget),
cur_selection, 1, clean_pixmap, clean_mask);
g_signal_handlers_unblock_by_func (G_OBJECT (st->gimage),
undo_history_undo_event, st);
undo_history_set_sensitive (st, GTK_CLIST(st->clist)->rows);
}
static void
undo_history_clean_callback (GtkWidget *widget,
gpointer data)
{
undo_history_st *st = data;
gint i;
gint nrows;
GtkCList *clist;
if (st->gimage->dirty != 0)
return;
/*
* The image has become clean. Remove the clean_pixmap from
* all entries. It will be set in the undo_event or select_row
* callbacks.
* Ugly, but works better than before. The actual problem is
* that the "clean" signal is emitted before UNDO_POPPED event,
* so we can not simply set the clean pixmap here.
*/
clist = GTK_CLIST (st->clist);
nrows = clist->rows;
gtk_clist_freeze (clist);
for (i=0; i < nrows; i++)
gtk_clist_set_text (clist, i, 1, NULL);
gtk_clist_thaw (clist);
}
/* Used to build up initial contents of clist */
static gboolean
undo_history_init_undo (const gchar *undoitemname,
void *data)
{
undo_history_st *st = data;
gchar *namelist[3];
gint row;
namelist[0] = NULL;
namelist[1] = NULL;
namelist[2] = (gchar *) undoitemname;
row = gtk_clist_prepend (GTK_CLIST (st->clist), namelist);
gtk_clist_set_pixmap (GTK_CLIST (st->clist), row, 0,
clear_pixmap, clear_mask);
return FALSE;
}
/* Ditto */
static gboolean
undo_history_init_redo (const char *undoitemname,
void *data)
{
undo_history_st *st = data;
gchar *namelist[3];
gint row;
namelist[0] = NULL; namelist[1] = NULL;
namelist[2] = (gchar *) undoitemname;
row = gtk_clist_append (GTK_CLIST (st->clist), namelist);
gtk_clist_set_pixmap (GTK_CLIST (st->clist), row, 0,
clear_pixmap, clear_mask);
return FALSE;
}
/*************************************************************/
/* Publicly exported function */
GtkWidget *
undo_history_new (GimpImage *gimage)
{
undo_history_st *st;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *button;
GtkWidget *scrolled_win;
st = g_new0 (undo_history_st, 1);
st->gimage = gimage;
st->preview_size = gimage->gimp->config->preview_size;
/* gimage signals */
g_signal_connect (G_OBJECT (gimage), "undo_event",
G_CALLBACK (undo_history_undo_event),
st);
g_signal_connect (G_OBJECT (gimage), "name_changed",
G_CALLBACK (undo_history_gimage_rename_callback),
st);
g_signal_connect (G_OBJECT (gimage), "clean",
G_CALLBACK (undo_history_clean_callback),
st);
/* The shell and main vbox */
st->shell =
gimp_viewable_dialog_new (GIMP_VIEWABLE (gimage),
_("Undo History"), "undo_history",
GTK_STOCK_UNDO,
_("Image Undo History"),
gimp_standard_help_func,
"dialogs/undo_history.html",
GTK_STOCK_CLOSE, undo_history_close_callback,
st, NULL, NULL, TRUE, TRUE,
NULL);
vbox = gtk_vbox_new (FALSE, 2);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
gtk_container_add (GTK_CONTAINER (GTK_DIALOG (st->shell)->vbox), vbox);
gtk_widget_show (vbox);
g_signal_connect (G_OBJECT (st->shell), "destroy",
G_CALLBACK (undo_history_shell_destroy_callback),
st);
scrolled_win = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_usize (GTK_WIDGET (scrolled_win),
160 + st->preview_size,
4 * (MAX (st->preview_size, 16) + 6));
/* clist of undo actions */
st->clist = gtk_clist_new (3);
gtk_clist_set_selection_mode (GTK_CLIST (st->clist), GTK_SELECTION_BROWSE);
gtk_clist_set_reorderable (GTK_CLIST (st->clist), FALSE);
gtk_clist_set_row_height (GTK_CLIST (st->clist), MAX (st->preview_size, 16) + 4);
gtk_clist_set_column_width (GTK_CLIST (st->clist), 0, st->preview_size + 2);
gtk_clist_set_column_width (GTK_CLIST (st->clist), 1, 18);
gtk_clist_set_column_min_width (GTK_CLIST (st->clist), 2, 64);
/* allocate the pixmaps if not already done */
if (!clean_pixmap)
{
GtkStyle *style;
gtk_widget_realize (st->shell);
style = gtk_widget_get_style (st->shell);
clean_pixmap =
gdk_pixmap_create_from_xpm_d (st->shell->window,
&clean_mask,
&style->bg[GTK_STATE_NORMAL],
yes_xpm);
clear_pixmap =
gdk_pixmap_create_from_xpm_d (st->shell->window,
&clear_mask,
&style->bg[GTK_STATE_NORMAL],
question_xpm);
}
/* work out the initial contents */
undo_map_over_undo_stack (st->gimage, undo_history_init_undo, st);
/* force selection to bottom */
gtk_clist_select_row (GTK_CLIST (st->clist),
GTK_CLIST (st->clist)->rows - 1, -1);
undo_map_over_redo_stack (st->gimage, undo_history_init_redo, st);
undo_history_prepend_special (GTK_CLIST (st->clist));
st->old_selection = GPOINTER_TO_INT(GTK_CLIST(st->clist)->selection->data);
/* draw the preview of the current state */
undo_history_set_pixmap (GTK_CLIST (st->clist),
st->old_selection, st->preview_size, st->gimage);
g_signal_connect (G_OBJECT (st->clist), "select_row",
G_CALLBACK (undo_history_select_row_callback),
st);
/* if the image is clean, set the clean pixmap */
if (st->gimage->dirty == 0)
gtk_clist_set_pixmap (GTK_CLIST (st->clist), st->old_selection, 1, clean_pixmap, clean_mask);
gtk_widget_show (GTK_WIDGET (st->clist));
gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0);
gtk_widget_show (GTK_WIDGET (scrolled_win));
gtk_container_add (GTK_CONTAINER (scrolled_win), st->clist);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
GTK_POLICY_NEVER,
GTK_POLICY_ALWAYS);
hbox = gtk_hbox_new (FALSE, 6);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
st->undo_button = button = gtk_button_new_from_stock (GTK_STOCK_UNDO);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
g_signal_connect (G_OBJECT (button), "clicked",
G_CALLBACK (undo_history_undo_callback),
st);
gtk_widget_show (GTK_WIDGET (button));
st->redo_button = button = gtk_button_new_from_stock (GTK_STOCK_REDO);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
g_signal_connect (G_OBJECT (button), "clicked",
G_CALLBACK (undo_history_redo_callback),
st);
gtk_widget_show (GTK_WIDGET (button));
undo_history_set_sensitive (st, GTK_CLIST (st->clist)->rows);
gtk_widget_show (GTK_WIDGET (st->shell));
gtk_clist_moveto (GTK_CLIST (st->clist), st->old_selection, 0, 0.5, 0.0);
return st->shell;
}