gimp/app/core/gimpprojection.c
Michael Natterer d162376d47 app/display/Makefile.am app/display/gimpdisplay-callbacks.[ch]
2001-11-01  Michael Natterer  <mitch@gimp.org>

	* app/display/Makefile.am
	* app/display/gimpdisplay-callbacks.[ch]
	* app/display/gimpdisplay-render.[ch]
	* app/display/gimpdisplay-scale.[ch]
	* app/display/gimpdisplay-scroll.[ch]: removed and added as
	gimpdisplayshell-foo.[ch] because they are all methods of the
	shell.

	* app/display/gimpdisplay.[ch]
	* app/display/gimpdisplayshell.[ch]: moved the "offset" and "size"
	variables from GimpDisplay to GimpDisplayShell. GimpDisplay
	should know nothing about screen coordinates.

	The gdisplay_[un]transform_foo() methods are still part of
	GimpDisplay but will be moved to GimpDisplayShell as soon as the
	tools' vitrual functions speak in image coordinates instead of
	GdkEvents.

	* app/display/gimpdisplayshell-callbacks.[ch]: prefixed all
	functions with gimp_display_shell_*. Moved some stuff to a
	"realize" callback File still has to be renamed.

	* app/display/gimpdisplay-foreach.[ch]: removed
	gdisplays_shrink_wrap().

	* app/gui/menus.c
	* app/gui/view-commands.[ch]
	* app/display/gimpdisplayshell-scale.[ch]: implemented "Zoom to
	Fit Window" function (#57670).

	* app/nav_window.c
	* app/display/gimpdisplay-handlers.c
	* app/display/gimpdisplayshell-render.[ch]
	* app/display/gimpdisplayshell-scale.[ch]
	* app/display/gimpdisplayshell-scroll.[ch]
	* app/gui/colormap-dialog.c
	* app/gui/gui.c
	* app/gui/preferences-dialog.c
	* app/tools/gimpmagnifytool.c
	* app/tools/gimpmovetool.c
	* app/widgets/gimppreview.c: changed according to variable
	and filename changes.

	* app/tools/tool_manager.c: tool_manager_select_tool(): send the
	active tool a "HALT" command before selecting the new one. Fixes
	stale tool dialogs which were there because some other hack was
	removed (This is IMHO the right place to shut down the active
	tool).

	* app/tools/gimpcroptool.c: don't shrink wrap after cropping but
	let gimprc.allow_resize_windows decide.

	* app/tools/gimpselectiontool.c: gimage_mask_value() takes image,
	not screen coordinates. A good example of how braindead it is to
	pass GdkEvents to tools :-) Fixes incorrect cursor and oper
	update of the selection tools.

	* app/tools/gimptransformtool.c
	* app/undo.c: removed (#if 0 for now) some strange code which did
	manual exposing of GimpDisplayShell areas. This was definitely a
	hack and should not be there given the image emits correct
	"update" signals.
2001-11-02 09:31:21 +00:00

791 lines
20 KiB
C

/* The GIMP -- an image manipulation program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <gtk/gtk.h>
#include "display-types.h"
#include "gui/gui-types.h" /* FIXME */
#include "core/gimp.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "tools/gimptool.h"
#include "tools/tool_manager.h"
#include "gui/info-window.h"
#include "gimpdisplay.h"
#include "gimpdisplay-area.h"
#include "gimpdisplay-handlers.h"
#include "gimpdisplay-selection.h"
#include "gimpdisplayshell.h"
#include "gximage.h"
#include "gimprc.h"
#include "nav_window.h"
#include "plug_in.h"
#include "qmask.h"
#include "libgimp/gimpintl.h"
/* local function prototypes */
static void gimp_display_class_init (GimpDisplayClass *klass);
static void gimp_display_init (GimpDisplay *gdisp);
static void gimp_display_finalize (GObject *object);
static void gimp_display_flush_whenever (GimpDisplay *gdisp,
gboolean now);
static void gimp_display_paint_area (GimpDisplay *gdisp,
gint x,
gint y,
gint w,
gint h);
static GimpObjectClass *parent_class = NULL;
GSList *display_list = NULL;
static gint display_num = 1;
GType
gimp_display_get_type (void)
{
static GType display_type = 0;
if (! display_type)
{
static const GTypeInfo display_info =
{
sizeof (GimpDisplayClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gimp_display_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GimpDisplay),
0, /* n_preallocs */
(GInstanceInitFunc) gimp_display_init,
};
display_type = g_type_register_static (GIMP_TYPE_OBJECT,
"GimpDisplay",
&display_info, 0);
}
return display_type;
}
static void
gimp_display_class_init (GimpDisplayClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
object_class->finalize = gimp_display_finalize;
}
static void
gimp_display_init (GimpDisplay *gdisp)
{
gdisp->ID = display_num++;
gdisp->gimage = NULL;
gdisp->instance = 0;
gdisp->shell = NULL;
gdisp->scale = 0;
gdisp->dot_for_dot = gimprc.default_dot_for_dot;
gdisp->draw_guides = TRUE;
gdisp->snap_to_guides = TRUE;
gdisp->select = NULL;
gdisp->update_areas = NULL;
gdisp->idle_render.idleid = -1;
gdisp->idle_render.update_areas = NULL;
gdisp->idle_render.active = FALSE;
}
static void
gimp_display_finalize (GObject *object)
{
GimpDisplay *gdisp;
g_return_if_fail (GIMP_IS_DISPLAY (object));
gdisp = GIMP_DISPLAY (object);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
GimpDisplay *
gdisplay_new (GimpImage *gimage,
guint scale)
{
GimpDisplay *gdisp;
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
/* If there isn't an interface, never create a gdisplay */
if (gimage->gimp->no_interface)
return NULL;
/*
* Set all GimpDisplay parameters...
*/
gdisp = g_object_new (GIMP_TYPE_DISPLAY, NULL);
/* add the new display to the list so that it isn't lost */
display_list = g_slist_append (display_list, gdisp);
gimp_display_connect (gdisp, gimage);
gdisp->scale = scale;
/* create the shell for the image */
gdisp->shell = gimp_display_shell_new (gdisp);
return gdisp;
}
void
gdisplay_delete (GimpDisplay *gdisp)
{
GimpTool *active_tool;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
/* remove the display from the list */
display_list = g_slist_remove (display_list, gdisp);
/* stop any active tool */
tool_manager_control_active (gdisp->gimage->gimp, HALT, gdisp);
active_tool = tool_manager_get_active (gdisp->gimage->gimp);
/* clear out the pointer to this gdisp from the active tool */
if (active_tool && active_tool->gdisp == gdisp)
{
active_tool->drawable = NULL;
active_tool->gdisp = NULL;
}
if (gdisp->select)
{
selection_free (gdisp->select);
gdisp->select = NULL;
}
/* If this gdisplay was idlerendering at the time when it was deleted,
deactivate the idlerendering thread before deletion! */
if (gdisp->idle_render.active)
{
g_source_remove (gdisp->idle_render.idleid);
gdisp->idle_render.active = FALSE;
}
/* free the update area lists */
gimp_display_area_list_free (gdisp->update_areas);
gimp_display_area_list_free (gdisp->idle_render.update_areas);
if (gdisp->shell)
{
gtk_widget_destroy (gdisp->shell);
gdisp->shell = NULL;
}
/* free the gimage */
gimp_display_disconnect (gdisp);
g_object_unref (G_OBJECT (gdisp));
}
GimpDisplay *
gdisplay_get_by_ID (Gimp *gimp,
gint ID)
{
GimpDisplay *gdisp;
GSList *list;
/* Traverse the list of displays, returning the one that matches the ID
* If no display in the list is a match, return NULL.
*/
for (list = display_list; list; list = g_slist_next (list))
{
gdisp = (GimpDisplay *) list->data;
if (gdisp->ID == ID)
return gdisp;
}
return NULL;
}
void
gdisplay_reconnect (GimpDisplay *gdisp,
GimpImage *gimage)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
g_return_if_fail (GIMP_IS_IMAGE (gimage));
if (gdisp->idle_render.active)
{
g_source_remove (gdisp->idle_render.idleid);
gdisp->idle_render.idleid = 0;
gdisp->idle_render.active = FALSE;
}
gimp_display_disconnect (gdisp);
gimp_display_connect (gdisp, gimage);
gdisplay_add_update_area (gdisp,
0, 0,
gdisp->gimage->width,
gdisp->gimage->height);
gimp_display_shell_resize_cursor_label (GIMP_DISPLAY_SHELL (gdisp->shell));
gimp_display_shell_shrink_wrap (GIMP_DISPLAY_SHELL (gdisp->shell));
}
static gint
idle_render_next_area (GimpDisplay *gdisp)
{
GimpArea *ga;
GSList *list;
list = gdisp->idle_render.update_areas;
if (list == NULL)
{
return -1;
}
ga = (GimpArea *) list->data;
gdisp->idle_render.update_areas =
g_slist_remove (gdisp->idle_render.update_areas, ga);
gdisp->idle_render.x = gdisp->idle_render.basex = ga->x1;
gdisp->idle_render.y = gdisp->idle_render.basey = ga->y1;
gdisp->idle_render.width = ga->x2 - ga->x1;
gdisp->idle_render.height = ga->y2 - ga->y1;
g_free (ga);
return 0;
}
/* Unless specified otherwise, display re-rendering is organised by
* IdleRender, which amalgamates areas to be re-rendered and breaks
* them into bite-sized chunks which are chewed on in a low- priority
* idle thread. This greatly improves responsiveness for many GIMP
* operations. -- Adam
*/
static gint
idlerender_callback (gpointer data)
{
const gint CHUNK_WIDTH = 256;
const gint CHUNK_HEIGHT = 128;
gint workx, worky, workw, workh;
GimpDisplay *gdisp = data;
workw = CHUNK_WIDTH;
workh = CHUNK_HEIGHT;
workx = gdisp->idle_render.x;
worky = gdisp->idle_render.y;
if (workx + workw > gdisp->idle_render.basex + gdisp->idle_render.width)
{
workw = gdisp->idle_render.basex + gdisp->idle_render.width - workx;
}
if (worky+workh > gdisp->idle_render.basey+gdisp->idle_render.height)
{
workh = gdisp->idle_render.basey + gdisp->idle_render.height - worky;
}
gimp_display_paint_area (gdisp, workx, worky, workw, workh);
gimp_display_shell_flush (GIMP_DISPLAY_SHELL (gdisp->shell));
gdisp->idle_render.x += CHUNK_WIDTH;
if (gdisp->idle_render.x >=
gdisp->idle_render.basex + gdisp->idle_render.width)
{
gdisp->idle_render.x = gdisp->idle_render.basex;
gdisp->idle_render.y += CHUNK_HEIGHT;
if (gdisp->idle_render.y >=
gdisp->idle_render.basey + gdisp->idle_render.height)
{
if (idle_render_next_area(gdisp) != 0)
{
/* FINISHED */
gdisp->idle_render.active = FALSE;
return 0;
}
}
}
/* Still work to do. */
return 1;
}
static void
gimp_display_idlerender_init (GimpDisplay *gdisp)
{
GSList *list;
GimpArea *ga, *new_ga;
/* We need to merge the IdleRender's and the GimpDisplay's update_areas list
* to keep track of which of the updates have been flushed and hence need
* to be drawn.
*/
list = gdisp->update_areas;
while (list)
{
ga = (GimpArea *) list->data;
new_ga = g_malloc (sizeof(GimpArea));
memcpy (new_ga, ga, sizeof(GimpArea));
gdisp->idle_render.update_areas =
gimp_display_area_list_process (gdisp->idle_render.update_areas, new_ga);
list = g_slist_next (list);
}
/* If an idlerender was already running, merge the remainder of its
unrendered area with the update_areas list, and make it start work
on the next unrendered area in the list. */
if (gdisp->idle_render.active)
{
new_ga = g_malloc (sizeof (GimpArea));
new_ga->x1 = gdisp->idle_render.basex;
new_ga->y1 = gdisp->idle_render.y;
new_ga->x2 = gdisp->idle_render.basex + gdisp->idle_render.width;
new_ga->y2 = gdisp->idle_render.y +
(gdisp->idle_render.height -
(gdisp->idle_render.y - gdisp->idle_render.basey)
);
gdisp->idle_render.update_areas =
gimp_display_area_list_process (gdisp->idle_render.update_areas, new_ga);
idle_render_next_area (gdisp);
}
else
{
if (gdisp->idle_render.update_areas == NULL)
{
g_warning ("Wanted to start idlerender thread with no update_areas. (+memleak)");
return;
}
idle_render_next_area (gdisp);
gdisp->idle_render.active = TRUE;
gdisp->idle_render.idleid = gtk_idle_add_priority (GTK_PRIORITY_LOW,
idlerender_callback,
gdisp);
}
/* Caller frees gdisp->update_areas */
}
static void
gimp_display_flush_whenever (GimpDisplay *gdisp,
gboolean now)
{
GSList *list;
GimpArea *ga;
/* Flush the items in the displays and updates lists -
* but only if gdisplay has been mapped and exposed
*/
if (!gdisp->select)
return;
/* First the updates... */
if (now)
{
/* Synchronous */
list = gdisp->update_areas;
while (list)
{
/* Paint the area specified by the GimpArea */
ga = (GimpArea *) list->data;
if ((ga->x1 != ga->x2) && (ga->y1 != ga->y2))
{
gimp_display_paint_area (gdisp, ga->x1, ga->y1,
(ga->x2 - ga->x1), (ga->y2 - ga->y1));
}
list = g_slist_next (list);
}
}
else
{
/* Asynchronous */
if (gdisp->update_areas)
gimp_display_idlerender_init (gdisp);
}
/* Free the update lists */
gdisp->update_areas = gimp_display_area_list_free (gdisp->update_areas);
/* Next the displays... */
gimp_display_shell_flush (GIMP_DISPLAY_SHELL (gdisp->shell));
/* update the gdisplay's info dialog */
info_window_update (gdisp);
/* update the gdisplay's qmask buttons */
qmask_buttons_update (gdisp);
/* ensure the consistency of the tear-off menus */
if (! now && gimp_context_get_display (gimp_get_user_context
(gdisp->gimage->gimp)) == gdisp)
{
gimp_display_shell_set_menu_sensitivity (GIMP_DISPLAY_SHELL (gdisp->shell));
}
}
void
gdisplay_flush (GimpDisplay *gdisp)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
/* Redraw on idle time */
gimp_display_flush_whenever (gdisp, FALSE);
}
void
gdisplay_flush_now (GimpDisplay *gdisp)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
/* Redraw NOW */
gimp_display_flush_whenever (gdisp, TRUE);
}
/* Force all gdisplays to finish their idlerender projection */
void
gdisplays_finish_draw (void)
{
GSList *list;
GimpDisplay *gdisp;
for (list = display_list; list; list = g_slist_next (list))
{
gdisp = (GimpDisplay *) list->data;
if (gdisp->idle_render.active)
{
g_source_remove (gdisp->idle_render.idleid);
gdisp->idle_render.idleid = 0;
while (idlerender_callback (gdisp));
}
}
}
void
gdisplay_add_update_area (GimpDisplay *gdisp,
gint x,
gint y,
gint w,
gint h)
{
GimpArea *ga;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
ga = g_new (GimpArea, 1);
ga->x1 = CLAMP (x, 0, gdisp->gimage->width);
ga->y1 = CLAMP (y, 0, gdisp->gimage->height);
ga->x2 = CLAMP (x + w, 0, gdisp->gimage->width);
ga->y2 = CLAMP (y + h, 0, gdisp->gimage->height);
gdisp->update_areas = gimp_display_area_list_process (gdisp->update_areas, ga);
}
static void
gimp_display_paint_area (GimpDisplay *gdisp,
gint x,
gint y,
gint w,
gint h)
{
GimpDisplayShell *shell;
gint x1, y1, x2, y2;
shell = GIMP_DISPLAY_SHELL (gdisp->shell);
/* Bounds check */
x1 = CLAMP (x, 0, gdisp->gimage->width);
y1 = CLAMP (y, 0, gdisp->gimage->height);
x2 = CLAMP (x + w, 0, gdisp->gimage->width);
y2 = CLAMP (y + h, 0, gdisp->gimage->height);
x = x1;
y = y1;
w = (x2 - x1);
h = (y2 - y1);
/* calculate the extents of the update as limited by what's visible */
gdisplay_untransform_coords (gdisp,
0, 0,
&x1, &y1,
FALSE, FALSE);
gdisplay_untransform_coords (gdisp,
shell->disp_width, shell->disp_height,
&x2, &y2,
FALSE, FALSE);
gimp_image_invalidate (gdisp->gimage, x, y, w, h, x1, y1, x2, y2);
/* display the area */
gdisplay_transform_coords (gdisp, x, y, &x1, &y1, FALSE);
gdisplay_transform_coords (gdisp, x + w, y + h, &x2, &y2, FALSE);
gimp_display_shell_add_expose_area (shell,
x1, y1, (x2 - x1), (y2 - y1));
}
void
gdisplay_transform_coords (GimpDisplay *gdisp,
gint x,
gint y,
gint *nx,
gint *ny,
gboolean use_offsets)
{
GimpDisplayShell *shell;
gdouble scalex;
gdouble scaley;
gint offset_x;
gint offset_y;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
g_return_if_fail (nx != NULL);
g_return_if_fail (ny != NULL);
shell = GIMP_DISPLAY_SHELL (gdisp->shell);
/* transform from image coordinates to screen coordinates */
scalex = SCALEFACTOR_X (gdisp);
scaley = SCALEFACTOR_Y (gdisp);
if (use_offsets)
{
gimp_drawable_offsets (gimp_image_active_drawable (gdisp->gimage),
&offset_x, &offset_y);
}
else
{
offset_x = offset_y = 0;
}
*nx = (gint) (scalex * (x + offset_x) - shell->offset_x);
*ny = (gint) (scaley * (y + offset_y) - shell->offset_y);
*nx += shell->disp_xoffset;
*ny += shell->disp_yoffset;
}
void
gdisplay_untransform_coords (GimpDisplay *gdisp,
gint x,
gint y,
gint *nx,
gint *ny,
gboolean round,
gboolean use_offsets)
{
GimpDisplayShell *shell;
gdouble scalex;
gdouble scaley;
gint offset_x;
gint offset_y;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
g_return_if_fail (nx != NULL);
g_return_if_fail (ny != NULL);
shell = GIMP_DISPLAY_SHELL (gdisp->shell);
x -= shell->disp_xoffset;
y -= shell->disp_yoffset;
/* transform from screen coordinates to image coordinates */
scalex = SCALEFACTOR_X (gdisp);
scaley = SCALEFACTOR_Y (gdisp);
if (use_offsets)
{
gimp_drawable_offsets (gimp_image_active_drawable (gdisp->gimage),
&offset_x, &offset_y);
}
else
{
offset_x = offset_y = 0;
}
if (round)
{
*nx = ROUND ((x + shell->offset_x) / scalex - offset_x);
*ny = ROUND ((y + shell->offset_y) / scaley - offset_y);
}
else
{
*nx = (int) ((x + shell->offset_x) / scalex - offset_x);
*ny = (int) ((y + shell->offset_y) / scaley - offset_y);
}
}
void
gdisplay_transform_coords_f (GimpDisplay *gdisp,
gdouble x,
gdouble y,
gdouble *nx,
gdouble *ny,
gboolean use_offsets)
{
GimpDisplayShell *shell;
gdouble scalex;
gdouble scaley;
gint offset_x;
gint offset_y;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
g_return_if_fail (nx != NULL);
g_return_if_fail (ny != NULL);
shell = GIMP_DISPLAY_SHELL (gdisp->shell);
/* transform from gimp coordinates to screen coordinates */
scalex = SCALEFACTOR_X (gdisp);
scaley = SCALEFACTOR_Y (gdisp);
if (use_offsets)
{
gimp_drawable_offsets (gimp_image_active_drawable (gdisp->gimage),
&offset_x, &offset_y);
}
else
{
offset_x = offset_y = 0;
}
*nx = scalex * (x + offset_x) - shell->offset_x;
*ny = scaley * (y + offset_y) - shell->offset_y;
*nx += shell->disp_xoffset;
*ny += shell->disp_yoffset;
}
void
gdisplay_untransform_coords_f (GimpDisplay *gdisp,
gdouble x,
gdouble y,
gdouble *nx,
gdouble *ny,
gboolean use_offsets)
{
GimpDisplayShell *shell;
gdouble scalex;
gdouble scaley;
gint offset_x;
gint offset_y;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
g_return_if_fail (nx != NULL);
g_return_if_fail (ny != NULL);
shell = GIMP_DISPLAY_SHELL (gdisp->shell);
x -= shell->disp_xoffset;
y -= shell->disp_yoffset;
/* transform from screen coordinates to gimp coordinates */
scalex = SCALEFACTOR_X (gdisp);
scaley = SCALEFACTOR_Y (gdisp);
if (use_offsets)
{
gimp_drawable_offsets (gimp_image_active_drawable (gdisp->gimage),
&offset_x, &offset_y);
}
else
{
offset_x = offset_y = 0;
}
*nx = (x + shell->offset_x) / scalex - offset_x;
*ny = (y + shell->offset_y) / scaley - offset_y;
}
void
gdisplay_selection_visibility (GimpDisplay *gdisp,
GimpSelectionControl control)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
if (gdisp->select)
{
switch (control)
{
case GIMP_SELECTION_OFF:
selection_invis (gdisp->select);
break;
case GIMP_SELECTION_LAYER_OFF:
selection_layer_invis (gdisp->select);
break;
case GIMP_SELECTION_ON:
selection_start (gdisp->select, TRUE);
break;
case GIMP_SELECTION_PAUSE:
selection_pause (gdisp->select);
break;
case GIMP_SELECTION_RESUME:
selection_resume (gdisp->select);
break;
}
}
}