/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include "display-types.h" #include "config/gimpdisplayconfig.h" #include "base/boundary.h" #include "core/gimp.h" #include "core/gimpchannel.h" #include "core/gimplayermask.h" #include "core/gimpimage.h" #include "gimpcanvas.h" #include "gimpdisplay.h" #include "gimpdisplayshell.h" #include "gimpdisplayshell-appearance.h" #include "gimpdisplayshell-selection.h" #include "gimpdisplayshell-transform.h" #undef VERBOSE #define MAX_POINTS_INC 2048 #define USE_DRAWPOINTS TRUE struct _Selection { GimpDisplayShell *shell; /* shell that owns the selection */ GdkSegment *segs_in; /* gdk segments of area boundary */ GdkSegment *segs_out; /* gdk segments of area boundary */ GdkSegment *segs_layer; /* gdk segments of layer boundary */ gint num_segs_in; /* number of segments in segs1 */ gint num_segs_out; /* number of segments in segs2 */ gint num_segs_layer; /* number of segments in segs3 */ guint index; /* index of current stipple pattern */ gint paused; /* count of pause requests */ gboolean visible; /* visility of the display shell */ gboolean hidden; /* is the selection hidden? */ gboolean layer_hidden; /* is the layer boundary hidden? */ guint timeout; /* timer for successive draws */ GdkPoint *points_in[8]; /* points of segs_in for fast ants */ gint num_points_in[8]; /* number of points in points_in */ }; /* local function prototypes */ static void selection_start (Selection *selection); static void selection_stop (Selection *selection); static void selection_pause (Selection *selection); static void selection_resume (Selection *selection); static void selection_draw (Selection *selection); static void selection_undraw (Selection *selection); static void selection_layer_undraw (Selection *selection); static void selection_layer_draw (Selection *selection); static void selection_add_point (GdkPoint *points[8], gint max_npoints[8], gint npoints[8], gint x, gint y); static void selection_render_points (Selection *selection); static void selection_transform_segs (Selection *selection, const BoundSeg *src_segs, GdkSegment *dest_segs, gint num_segs); static void selection_generate_segs (Selection *selection); static void selection_free_segs (Selection *selection); static gboolean selection_start_timeout (Selection *selection); static gboolean selection_timeout (Selection *selection); static gboolean selection_window_state_event (GtkWidget *shell, GdkEventWindowState *event, Selection *selection); static gboolean selection_visibility_notify_event (GtkWidget *shell, GdkEventVisibility *event, Selection *selection); /* public functions */ void gimp_display_shell_selection_init (GimpDisplayShell *shell) { Selection *selection; gint i; g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); g_return_if_fail (shell->selection == NULL); selection = g_slice_new0 (Selection); selection->shell = shell; selection->visible = TRUE; selection->hidden = ! gimp_display_shell_get_show_selection (shell); selection->layer_hidden = ! gimp_display_shell_get_show_layer (shell); for (i = 0; i < 8; i++) selection->points_in[i] = NULL; shell->selection = selection; g_signal_connect (shell, "window-state-event", G_CALLBACK (selection_window_state_event), selection); g_signal_connect (shell, "visibility-notify-event", G_CALLBACK (selection_visibility_notify_event), selection); } void gimp_display_shell_selection_free (GimpDisplayShell *shell) { g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); if (shell->selection) { Selection *selection = shell->selection; selection_stop (selection); g_signal_handlers_disconnect_by_func (shell, selection_window_state_event, selection); g_signal_handlers_disconnect_by_func (shell, selection_visibility_notify_event, selection); selection_free_segs (selection); g_slice_free (Selection, selection); shell->selection = NULL; } } void gimp_display_shell_selection_control (GimpDisplayShell *shell, GimpSelectionControl control) { g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); if (shell->selection && shell->display->image) { Selection *selection = shell->selection; switch (control) { case GIMP_SELECTION_OFF: selection_undraw (selection); break; case GIMP_SELECTION_LAYER_OFF: selection_layer_undraw (selection); break; case GIMP_SELECTION_LAYER_ON: selection_layer_draw (selection); break; case GIMP_SELECTION_ON: selection_start (selection); break; case GIMP_SELECTION_PAUSE: selection_pause (selection); break; case GIMP_SELECTION_RESUME: selection_resume (selection); break; } } else if (shell->selection) { selection_stop (shell->selection); selection_free_segs (shell->selection); } } void gimp_display_shell_selection_set_hidden (GimpDisplayShell *shell, gboolean hidden) { g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); if (shell->selection && shell->display->image) { Selection *selection = shell->selection; if (hidden != selection->hidden) { selection_undraw (selection); selection_layer_undraw (selection); selection->hidden = hidden; selection_start (selection); } } } void gimp_display_shell_selection_layer_set_hidden (GimpDisplayShell *shell, gboolean hidden) { g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); if (shell->selection && shell->display->image) { Selection *selection = shell->selection; if (hidden != selection->layer_hidden) { selection_undraw (selection); selection_layer_undraw (selection); selection->layer_hidden = hidden; selection_start (selection); } } } /* private functions */ static void selection_start (Selection *selection) { selection_stop (selection); /* If this selection is paused, do not start it */ if (selection->paused == 0) { selection->timeout = g_idle_add ((GSourceFunc) selection_start_timeout, selection); } } static void selection_stop (Selection *selection) { if (selection->timeout) { g_source_remove (selection->timeout); selection->timeout = 0; } } static void selection_pause (Selection *selection) { if (selection->paused == 0) selection_stop (selection); selection->paused++; } static void selection_resume (Selection *selection) { selection->paused--; if (selection->paused == 0) selection_start (selection); } static void selection_draw (Selection *selection) { GimpCanvas *canvas = GIMP_CANVAS (selection->shell->canvas); #ifdef USE_DRAWPOINTS #ifdef VERBOSE { gint j, sum; sum = 0; for (j = 0; j < 8; j++) sum += selection->num_points_in[j]; g_print ("%d segments, %d points\n", selection->num_segs_in, sum); } #endif if (selection->segs_in) { gint i; if (selection->index == 0) { for (i = 0; i < 4; i++) if (selection->num_points_in[i]) gimp_canvas_draw_points (canvas, GIMP_CANVAS_STYLE_WHITE, selection->points_in[i], selection->num_points_in[i]); for (i = 4; i < 8; i++) if (selection->num_points_in[i]) gimp_canvas_draw_points (canvas, GIMP_CANVAS_STYLE_BLACK, selection->points_in[i], selection->num_points_in[i]); } else { i = ((selection->index + 3) & 7); if (selection->num_points_in[i]) gimp_canvas_draw_points (canvas, GIMP_CANVAS_STYLE_WHITE, selection->points_in[i], selection->num_points_in[i]); i = ((selection->index + 7) & 7); if (selection->num_points_in[i]) gimp_canvas_draw_points (canvas, GIMP_CANVAS_STYLE_BLACK, selection->points_in[i], selection->num_points_in[i]); } } #else /* ! USE_DRAWPOINTS */ gimp_canvas_set_stipple_index (canvas, GIMP_CANVAS_STYLE_SELECTION_IN, selection->index); if (selection->segs_in) gimp_canvas_draw_segments (canvas, GIMP_CANVAS_STYLE_SELECTION_IN, selection->segs_in, selection->num_segs_in); #endif } static void selection_undraw (Selection *selection) { gint x1, y1, x2, y2; selection_stop (selection); if (gimp_display_shell_mask_bounds (selection->shell, &x1, &y1, &x2, &y2)) { /* expose will restart the selection */ gimp_display_shell_expose_area (selection->shell, x1, y1, (x2 - x1), (y2 - y1)); } else { selection_start (selection); } } static void selection_layer_draw (Selection *selection) { GimpCanvas *canvas = GIMP_CANVAS (selection->shell->canvas); GimpImage *image = selection->shell->display->image; GimpDrawable *drawable = gimp_image_get_active_drawable (image); if (selection->segs_layer) gimp_canvas_draw_segments (canvas, GIMP_IS_LAYER_MASK (drawable) ? GIMP_CANVAS_STYLE_LAYER_MASK_ACTIVE: GIMP_CANVAS_STYLE_LAYER_BOUNDARY, selection->segs_layer, selection->num_segs_layer); } static void selection_layer_undraw (Selection *selection) { selection_stop (selection); if (selection->segs_layer != NULL && selection->num_segs_layer == 4) { gint x1 = selection->segs_layer[0].x1 - 1; gint y1 = selection->segs_layer[0].y1 - 1; gint x2 = selection->segs_layer[3].x2 + 1; gint y2 = selection->segs_layer[3].y2 + 1; gint x3 = selection->segs_layer[0].x1 + 1; gint y3 = selection->segs_layer[0].y1 + 1; gint x4 = selection->segs_layer[3].x2 - 1; gint y4 = selection->segs_layer[3].y2 - 1; /* expose the region, this will restart the selection */ gimp_display_shell_expose_area (selection->shell, x1, y1, (x2 - x1) + 1, (y3 - y1) + 1); gimp_display_shell_expose_area (selection->shell, x1, y3, (x3 - x1) + 1, (y4 - y3) + 1); gimp_display_shell_expose_area (selection->shell, x1, y4, (x2 - x1) + 1, (y2 - y4) + 1); gimp_display_shell_expose_area (selection->shell, x4, y3, (x2 - x4) + 1, (y4 - y3) + 1); } else { selection_start (selection); } } static void selection_add_point (GdkPoint *points[8], gint max_npoints[8], gint npoints[8], gint x, gint y) { gint i, j; j = (x - y) & 7; i = npoints[j]++; if (i == max_npoints[j]) { max_npoints[j] += 2048; points[j] = g_realloc (points[j], sizeof (GdkPoint) * max_npoints[j]); } points[j][i].x = x; points[j][i].y = y; } /* Render the segs_in array into points_in */ static void selection_render_points (Selection *selection) { gint i, j; gint max_npoints[8]; gint x, y; gint dx, dy; gint dxa, dya; gint r; if (selection->segs_in == NULL) return; for (j = 0; j < 8; j++) { max_npoints[j] = MAX_POINTS_INC; selection->points_in[j] = g_new (GdkPoint, max_npoints[j]); selection->num_points_in[j] = 0; } for (i = 0; i < selection->num_segs_in; i++) { #ifdef VERBOSE g_print ("%2d: (%d, %d) - (%d, %d)\n", i, selection->segs_in[i].x1, selection->segs_in[i].y1, selection->segs_in[i].x2, selection->segs_in[i].y2); #endif x = selection->segs_in[i].x1; dxa = selection->segs_in[i].x2 - x; if (dxa > 0) { dx = 1; } else { dxa = -dxa; dx = -1; } y = selection->segs_in[i].y1; dya = selection->segs_in[i].y2 - y; if (dya > 0) { dy = 1; } else { dya = -dya; dy = -1; } if (dxa > dya) { r = dya; do { selection_add_point (selection->points_in, max_npoints, selection->num_points_in, x, y); x += dx; r += dya; if (r >= (dxa << 1)) { y += dy; r -= (dxa << 1); } } while (x != selection->segs_in[i].x2); } else if (dxa < dya) { r = dxa; do { selection_add_point (selection->points_in, max_npoints, selection->num_points_in, x, y); y += dy; r += dxa; if (r >= (dya << 1)) { x += dx; r -= (dya << 1); } } while (y != selection->segs_in[i].y2); } else { selection_add_point (selection->points_in, max_npoints, selection->num_points_in, x, y); } } } static void selection_transform_segs (Selection *selection, const BoundSeg *src_segs, GdkSegment *dest_segs, gint num_segs) { gint xclamp = selection->shell->disp_width + 1; gint yclamp = selection->shell->disp_height + 1; gint i; gimp_display_shell_transform_segments (selection->shell, src_segs, dest_segs, num_segs, FALSE); for (i = 0; i < num_segs; i++) { dest_segs[i].x1 = CLAMP (dest_segs[i].x1, -1, xclamp); dest_segs[i].y1 = CLAMP (dest_segs[i].y1, -1, yclamp); dest_segs[i].x2 = CLAMP (dest_segs[i].x2, -1, xclamp); dest_segs[i].y2 = CLAMP (dest_segs[i].y2, -1, yclamp); /* If this segment is a closing segment && the segments lie inside * the region, OR if this is an opening segment and the segments * lie outside the region... * we need to transform it by one display pixel */ if (!src_segs[i].open) { /* If it is vertical */ if (dest_segs[i].x1 == dest_segs[i].x2) { dest_segs[i].x1 -= 1; dest_segs[i].x2 -= 1; } else { dest_segs[i].y1 -= 1; dest_segs[i].y2 -= 1; } } } } static void selection_generate_segs (Selection *selection) { const BoundSeg *segs_in; const BoundSeg *segs_out; BoundSeg *segs_layer; /* Ask the image for the boundary of its selected region... * Then transform that information into a new buffer of GdkSegments */ gimp_channel_boundary (gimp_image_get_mask (selection->shell->display->image), &segs_in, &segs_out, &selection->num_segs_in, &selection->num_segs_out, 0, 0, 0, 0); if (selection->num_segs_in) { selection->segs_in = g_new (GdkSegment, selection->num_segs_in); selection_transform_segs (selection, segs_in, selection->segs_in, selection->num_segs_in); #ifdef USE_DRAWPOINTS selection_render_points (selection); #endif } else { selection->segs_in = NULL; } /* Possible secondary boundary representation */ if (selection->num_segs_out) { selection->segs_out = g_new (GdkSegment, selection->num_segs_out); selection_transform_segs (selection, segs_out, selection->segs_out, selection->num_segs_out); } else { selection->segs_out = NULL; } /* The active layer's boundary */ gimp_image_layer_boundary (selection->shell->display->image, &segs_layer, &selection->num_segs_layer); if (selection->num_segs_layer) { selection->segs_layer = g_new (GdkSegment, selection->num_segs_layer); selection_transform_segs (selection, segs_layer, selection->segs_layer, selection->num_segs_layer); } else { selection->segs_layer = NULL; } g_free (segs_layer); } static void selection_free_segs (Selection *selection) { gint j; if (selection->segs_in) g_free (selection->segs_in); if (selection->segs_out) g_free (selection->segs_out); if (selection->segs_layer) g_free (selection->segs_layer); selection->segs_in = NULL; selection->num_segs_in = 0; selection->segs_out = NULL; selection->num_segs_out = 0; selection->segs_layer = NULL; selection->num_segs_layer = 0; for (j = 0; j < 8; j++) { if (selection->points_in[j]) g_free (selection->points_in[j]); selection->points_in[j] = NULL; selection->num_points_in[j] = 0; } } static gboolean selection_start_timeout (Selection *selection) { selection_free_segs (selection); selection->timeout = 0; if (! selection->shell->display->image) return FALSE; selection_generate_segs (selection); selection->index = 0; if (! selection->layer_hidden) selection_layer_draw (selection); /* Draw the ants */ if (! selection->hidden) { GimpCanvas *canvas = GIMP_CANVAS (selection->shell->canvas); GimpDisplayConfig *config = selection->shell->display->config; selection_draw (selection); if (selection->segs_out) gimp_canvas_draw_segments (canvas, GIMP_CANVAS_STYLE_SELECTION_OUT, selection->segs_out, selection->num_segs_out); if (selection->segs_in && selection->visible) selection->timeout = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, config->marching_ants_speed, (GSourceFunc) selection_timeout, selection, NULL); } return FALSE; } static gboolean selection_timeout (Selection *selection) { selection->index++; selection_draw (selection); return TRUE; } static void selection_set_visible (Selection *selection, gboolean visible) { if (selection->visible != visible) { selection->visible = visible; if (visible) selection_start (selection); else selection_stop (selection); } } static gboolean selection_window_state_event (GtkWidget *shell, GdkEventWindowState *event, Selection *selection) { selection_set_visible (selection, (event->new_window_state & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED)) == 0); return FALSE; } static gboolean selection_visibility_notify_event (GtkWidget *shell, GdkEventVisibility *event, Selection *selection) { selection_set_visible (selection, event->state != GDK_VISIBILITY_FULLY_OBSCURED); return FALSE; }