/* 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include "gdk/gdkkeysyms.h" #include "appenv.h" #include "draw_core.h" #include "drawable.h" #include "tools.h" #include "edit_selection.h" #include "floating_sel.h" #include "gimage_mask.h" #include "gdisplay.h" #include "undo.h" #define EDIT_SELECT_SCROLL_LOCK 0 #define ARROW_VELOCITY 25 typedef struct _edit_selection EditSelection; struct _edit_selection { int origx, origy; /* original x and y coords */ int x, y; /* current x and y coords */ int x1, y1; /* bounding box of selection mask */ int x2, y2; EditType edit_type; /* translate the mask or layer? */ DrawCore * core; /* selection core for drawing bounds */ ButtonReleaseFunc old_button_release; /* old button press member func */ MotionFunc old_motion; /* old motion member function */ ToolCtlFunc old_control; /* old control member function */ CursorUpdateFunc old_cursor_update; /* old cursor update function */ int old_scroll_lock; /* old value of scroll lock */ int old_auto_snap_to; /* old value of auto snap to */ }; /* static EditSelection structure--there is ever only one present */ static EditSelection edit_select; static void edit_selection_snap (GDisplay *gdisp, int x, int y) { int x1, y1; int x2, y2; int dx, dy; gdisplay_untransform_coords (gdisp, x, y, &x, &y, FALSE, TRUE); dx = x - edit_select.origx; dy = y - edit_select.origy; x1 = edit_select.x1 + dx; y1 = edit_select.y1 + dy; x2 = edit_select.x2 + dx; y2 = edit_select.y2 + dy; gdisplay_transform_coords (gdisp, x1, y1, &x1, &y1, TRUE); gdisplay_transform_coords (gdisp, x2, y2, &x2, &y2, TRUE); gdisplay_snap_rectangle (gdisp, x1, y1, x2, y2, &x1, &y1); gdisplay_untransform_coords (gdisp, x1, y1, &x1, &y1, FALSE, TRUE); edit_select.x = x1 - (edit_select.x1 - edit_select.origx); edit_select.y = y1 - (edit_select.y1 - edit_select.origy); } void init_edit_selection (Tool *tool, gpointer gdisp_ptr, GdkEventButton *bevent, EditType edit_type) { GDisplay *gdisp; Layer *layer; int x, y; gdisp = (GDisplay *) gdisp_ptr; /* Move the (x, y) point from screen to image space */ gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &x, &y, FALSE, TRUE); edit_select.x = edit_select.origx = x; edit_select.y = edit_select.origy = y; /* Make a check to see if it should be a floating selection translation */ if (edit_type == LayerTranslate) { layer = gimage_get_active_layer (gdisp->gimage); if (layer_is_floating_sel (layer)) edit_type = FloatingSelTranslate; } edit_select.edit_type = edit_type; edit_select.old_button_release = tool->button_release_func; edit_select.old_motion = tool->motion_func; edit_select.old_control = tool->control_func; edit_select.old_cursor_update = tool->cursor_update_func; edit_select.old_scroll_lock = tool->scroll_lock; edit_select.old_auto_snap_to = tool->auto_snap_to; /* find the bounding box of the selection mask-- * this is used for the case of a MaskToLayerTranslate, * where the translation will result in floating the selection * mask and translating the resulting layer */ drawable_mask_bounds (gimage_active_drawable (gdisp->gimage), &edit_select.x1, &edit_select.y1, &edit_select.x2, &edit_select.y2); edit_selection_snap (gdisp, bevent->x, bevent->y); /* reset the function pointers on the selection tool */ tool->button_release_func = edit_selection_button_release; tool->motion_func = edit_selection_motion; tool->control_func = edit_selection_control; tool->cursor_update_func = edit_selection_cursor_update; tool->scroll_lock = EDIT_SELECT_SCROLL_LOCK; tool->auto_snap_to = FALSE; /* pause the current selection */ selection_pause (gdisp->select); /* Create and start the selection core */ edit_select.core = draw_core_new (edit_selection_draw); draw_core_start (edit_select.core, gdisp->canvas->window, tool); } void edit_selection_button_release (Tool *tool, GdkEventButton *bevent, gpointer gdisp_ptr) { int x, y; GDisplay * gdisp; Layer *layer; Layer *floating_layer; GSList *layer_list; gdisp = (GDisplay *) gdisp_ptr; /* resume the current selection and ungrab the pointer */ selection_resume (gdisp->select); gdk_pointer_ungrab (bevent->time); gdk_flush (); /* Stop and free the selection core */ draw_core_stop (edit_select.core, tool); draw_core_free (edit_select.core); edit_select.core = NULL; tool->state = INACTIVE; tool->button_release_func = edit_select.old_button_release; tool->motion_func = edit_select.old_motion; tool->control_func = edit_select.old_control; tool->cursor_update_func = edit_select.old_cursor_update; tool->scroll_lock = edit_select.old_scroll_lock; tool->auto_snap_to = edit_select.old_auto_snap_to; /* If the cancel button is down...Do nothing */ if (! (bevent->state & GDK_BUTTON3_MASK)) { edit_selection_snap (gdisp, bevent->x, bevent->y); x = edit_select.x; y = edit_select.y; /* if there has been movement, move the selection */ if (edit_select.origx != x || edit_select.origy != y) { switch (edit_select.edit_type) { case MaskTranslate: /* translate the selection */ gimage_mask_translate (gdisp->gimage, (x - edit_select.origx), (y - edit_select.origy)); break; case MaskToLayerTranslate: gimage_mask_float (gdisp->gimage, gimage_active_drawable (gdisp->gimage), (x - edit_select.origx), (y - edit_select.origy)); break; case LayerTranslate: /* Push a linked undo group */ undo_push_group_start (gdisp->gimage, LINKED_LAYER_UNDO); if ((floating_layer = gimage_floating_sel (gdisp->gimage))) floating_sel_relax (floating_layer, TRUE); /* translate the layer--and any "linked" layers as well */ layer_list = gdisp->gimage->layers; while (layer_list) { layer = (Layer *) layer_list->data; if (layer == gdisp->gimage->active_layer || layer_linked (layer)) layer_translate (layer, (x - edit_select.origx), (y - edit_select.origy)); layer_list = g_slist_next (layer_list); } if (floating_layer) floating_sel_rigor (floating_layer, TRUE); /* End the linked undo group */ undo_push_group_end (gdisp->gimage); break; case FloatingSelTranslate: layer = gimage_get_active_layer (gdisp->gimage); undo_push_group_start (gdisp->gimage, LINKED_LAYER_UNDO); floating_sel_relax (layer, TRUE); layer_translate (layer, (x - edit_select.origx), (y - edit_select.origy)); floating_sel_rigor (layer, TRUE); undo_push_group_end (gdisp->gimage); break; } } /* if no movement has occured, clear the current selection */ else if ((edit_select.edit_type == MaskTranslate) || (edit_select.edit_type == MaskToLayerTranslate)) gimage_mask_clear (gdisp->gimage); /* if no movement occured and the type is LayerTranslate, * check if the layer is a floating selection. If so, anchor. */ else if (edit_select.edit_type == FloatingSelTranslate) { layer = gimage_get_active_layer (gdisp->gimage); if (layer_is_floating_sel (layer)) floating_sel_anchor (layer); } } gdisplays_flush (); } void edit_selection_motion (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr) { GDisplay * gdisp; if (tool->state != ACTIVE) return; gdisp = (GDisplay *) gdisp_ptr; draw_core_pause (edit_select.core, tool); edit_selection_snap (gdisp, mevent->x, mevent->y); draw_core_resume (edit_select.core, tool); } void edit_selection_draw (Tool *tool) { int i; int diff_x, diff_y; GDisplay * gdisp; GdkSegment * seg; Selection * select; Layer *layer; GSList *layer_list; int floating_sel; int x1, y1, x2, y2; int x3, y3, x4, y4; int off_x, off_y; gdisp = (GDisplay *) tool->gdisp_ptr; select = gdisp->select; diff_x = SCALE (gdisp, (edit_select.x - edit_select.origx)); diff_y = SCALE (gdisp, (edit_select.y - edit_select.origy)); switch (edit_select.edit_type) { case MaskTranslate: layer = gimage_get_active_layer (gdisp->gimage); floating_sel = layer_is_floating_sel (layer); /* offset the current selection */ seg = select->segs_in; for (i = 0; i < select->num_segs_in; i++) { seg->x1 += diff_x; seg->x2 += diff_x; seg->y1 += diff_y; seg->y2 += diff_y; seg++; } seg = select->segs_out; for (i = 0; i < select->num_segs_out; i++) { seg->x1 += diff_x; seg->x2 += diff_x; seg->y1 += diff_y; seg->y2 += diff_y; seg++; } if (! floating_sel) gdk_draw_segments (edit_select.core->win, edit_select.core->gc, select->segs_in, select->num_segs_in); gdk_draw_segments (edit_select.core->win, edit_select.core->gc, select->segs_out, select->num_segs_out); /* reset the the current selection */ seg = select->segs_in; for (i = 0; i < select->num_segs_in; i++) { seg->x1 -= diff_x; seg->x2 -= diff_x; seg->y1 -= diff_y; seg->y2 -= diff_y; seg++; } seg = select->segs_out; for (i = 0; i < select->num_segs_out; i++) { seg->x1 -= diff_x; seg->x2 -= diff_x; seg->y1 -= diff_y; seg->y2 -= diff_y; seg++; } break; case MaskToLayerTranslate: if (diff_x < 0) { x1 = x1 + 1; x1 --; } gdisplay_transform_coords (gdisp, edit_select.x1, edit_select.y1, &x1, &y1, TRUE); gdisplay_transform_coords (gdisp, edit_select.x2, edit_select.y2, &x2, &y2, TRUE); gdk_draw_rectangle (edit_select.core->win, edit_select.core->gc, 0, x1 + diff_x, y1 + diff_y, (x2 - x1) - 1, (y2 - y1) - 1); break; case LayerTranslate: gdisplay_transform_coords (gdisp, 0, 0, &x1, &y1, TRUE); gdisplay_transform_coords (gdisp, drawable_width ( GIMP_DRAWABLE (gdisp->gimage->active_layer)), drawable_height ( GIMP_DRAWABLE (gdisp->gimage->active_layer)), &x2, &y2, TRUE); /* Now, expand the rectangle to include all linked layers as well */ layer_list = gdisp->gimage->layers; while (layer_list) { layer = (Layer *) layer_list->data; if (((layer) != gdisp->gimage->active_layer) && layer_linked (layer)) { drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); gdisplay_transform_coords (gdisp, off_x, off_y, &x3, &y3, FALSE); gdisplay_transform_coords (gdisp, off_x + drawable_width (GIMP_DRAWABLE (layer)), off_y + drawable_height (GIMP_DRAWABLE (layer)), &x4, &y4, FALSE); if (x3 < x1) x1 = x3; if (y3 < y1) y1 = y3; if (x4 > x2) x2 = x4; if (y4 > y2) y2 = y4; } layer_list = g_slist_next (layer_list); } gdk_draw_rectangle (edit_select.core->win, edit_select.core->gc, 0, x1 + diff_x, y1 + diff_y, (x2 - x1) - 1, (y2 - y1) - 1); break; case FloatingSelTranslate: seg = select->segs_in; for (i = 0; i < select->num_segs_in; i++) { seg->x1 += diff_x; seg->x2 += diff_x; seg->y1 += diff_y; seg->y2 += diff_y; seg++; } /* Draw the items */ gdk_draw_segments (edit_select.core->win, edit_select.core->gc, select->segs_in, select->num_segs_in); /* reset the the current selection */ seg = select->segs_in; for (i = 0; i < select->num_segs_in; i++) { seg->x1 -= diff_x; seg->x2 -= diff_x; seg->y1 -= diff_y; seg->y2 -= diff_y; seg++; } break; } } void edit_selection_control (Tool *tool, int action, gpointer gdisp_ptr) { switch (action) { case PAUSE : draw_core_pause (edit_select.core, tool); break; case RESUME : draw_core_resume (edit_select.core, tool); break; case HALT : draw_core_stop (edit_select.core, tool); draw_core_free (edit_select.core); break; } } void edit_selection_cursor_update (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr) { GDisplay *gdisp; gdisp = (GDisplay *) gdisp_ptr; gdisplay_install_tool_cursor (gdisp, GDK_FLEUR); } void edit_sel_arrow_keys_func (Tool *tool, GdkEventKey *kevent, gpointer gdisp_ptr) { int inc_x, inc_y; GDisplay *gdisp; Layer *layer; Layer *floating_layer; GSList *layer_list; EditType edit_type; layer = NULL; gdisp = (GDisplay *) gdisp_ptr; inc_x = inc_y = 0; switch (kevent->keyval) { case GDK_Up : inc_y = -1; break; case GDK_Left : inc_x = -1; break; case GDK_Right : inc_x = 1; break; case GDK_Down : inc_y = 1; break; } /* If the shift key is down, move by an accelerated increment */ if (kevent->state & GDK_SHIFT_MASK) { inc_y *= ARROW_VELOCITY; inc_x *= ARROW_VELOCITY; } if (kevent->state & GDK_MOD1_MASK) edit_type = MaskTranslate; else { layer = gimage_get_active_layer (gdisp->gimage); if (layer_is_floating_sel (layer)) edit_type = FloatingSelTranslate; else edit_type = LayerTranslate; } switch (edit_type) { case MaskTranslate: /* translate the selection */ gimage_mask_translate (gdisp->gimage, inc_x, inc_y); break; case MaskToLayerTranslate: gimage_mask_float (gdisp->gimage, gimage_active_drawable (gdisp->gimage), inc_x, inc_y); break; case LayerTranslate: /* Push a linked undo group */ undo_push_group_start (gdisp->gimage, LINKED_LAYER_UNDO); if ((floating_layer = gimage_floating_sel (gdisp->gimage))) floating_sel_relax (floating_layer, TRUE); /* translate the layer--and any "linked" layers as well */ layer_list = gdisp->gimage->layers; while (layer_list) { layer = (Layer *) layer_list->data; if (((layer) == gdisp->gimage->active_layer) || layer_linked (layer)) layer_translate (layer, inc_x, inc_y); layer_list = g_slist_next (layer_list); } if (floating_layer) floating_sel_rigor (floating_layer, TRUE); /* End the linked undo group */ undo_push_group_end (gdisp->gimage); break; case FloatingSelTranslate: undo_push_group_start (gdisp->gimage, LINKED_LAYER_UNDO); floating_sel_relax (layer, TRUE); layer_translate (layer, inc_x, inc_y); floating_sel_rigor (layer, TRUE); undo_push_group_end (gdisp->gimage); break; default: /* this won't occur */ break; } gdisplays_flush (); }