seltrans.cpp revision 21c27920218c0cee0a6231c9fff353dfd5ad74d4
#define __SELTRANS_C__
/*
* Helper object for transforming selected items
*
* Author:
* Lauris Kaplinski <lauris@kaplinski.com>
* bulia byak <buliabyak@users.sf.net>
* Carl Hetherington <inkscape@carlh.net>
*
* Copyright (C) 1999-2002 Lauris Kaplinski
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <libnr/nr-matrix-ops.h>
#include <libnr/nr-matrix-translate-ops.h>
#include <libnr/nr-rotate-ops.h>
#include <libnr/nr-scale-ops.h>
#include <libnr/nr-translate-matrix-ops.h>
#include <libnr/nr-translate-ops.h>
#include <gdk/gdkkeysyms.h>
#include "document.h"
#include "sp-namedview.h"
#include "desktop.h"
#include "desktop-handles.h"
#include "desktop-style.h"
#include "knot.h"
#include "snap.h"
#include "selection.h"
#include "select-context.h"
#include "sp-item.h"
#include "sp-item-transform.h"
#include "seltrans-handles.h"
#include "seltrans.h"
#include "selection-chemistry.h"
#include "sp-metrics.h"
#include "verbs.h"
#include "display/sp-ctrlline.h"
#include "prefs-utils.h"
#include "isnan.h" //temp fix. make sure included last
static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint32 state, gpointer data);
static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *p, guint state, gboolean *data);
{
case GDK_MOTION_NOTIFY:
break;
case GDK_KEY_PRESS:
/* stamping mode: both mode(show content and outline) operation with knot */
if (!SP_KNOT_IS_GRABBED(knot)) {
return FALSE;
}
return TRUE;
}
break;
default:
break;
}
return FALSE;
}
_grabbed(false),
_show_handles(true),
{
for (int i = 0; i < 8; i++) {
}
_center_is_set = false; // reread _center from items, or set to bbox midpoint
"anchor", GTK_ANCHOR_CENTER,
"mode", SP_CTRL_MODE_COLOR,
"shape", SP_CTRL_SHAPE_BITMAP,
"size", 13.0,
"filled", TRUE,
"fill_color", 0x00000000,
"stroked", TRUE,
"stroke_color", 0x000000a0,
NULL);
"anchor", GTK_ANCHOR_CENTER,
"mode", SP_CTRL_MODE_XOR,
"shape", SP_CTRL_SHAPE_CROSS,
"size", 7.0,
"filled", TRUE,
"fill_color", 0xffffff7f,
"stroked", TRUE,
"stroke_color", 0xffffffff,
NULL);
for (int i = 0; i < 4; i++) {
sp_canvas_item_hide(_l[i]);
}
);
);
}
{
for (unsigned int i = 0; i < 8; i++) {
if (_shandle[i]) {
}
if (_rhandle[i]) {
}
}
if (_chandle) {
}
if (_norm) {
}
if (_grip) {
}
for (int i = 0; i < 4; i++) {
if (_l[i]) {
}
}
}
}
{
}
{
if (_state == STATE_SCALE) {
} else {
}
_center_is_set = true; // no need to reread center
}
{
_center = p;
_center_is_set = true;
// Write the new center position into all selected items
// only set the value; updating repr and document_done will be done once, on ungrab
}
}
{
_grabbed = true;
_changed = false;
if (_empty) {
return;
}
_items_centers.push_back(std::pair<SPItem *, NR::Point>(it, it->getCenter())); // for content-dragging, we need to remember original centers
}
_point = p;
/* Snapping a huge number of nodes will take way too long, so limit the number of snappable nodes
An average user would rarely ever try to snap such a large number of nodes anyway, because
(s)he could hardly discern which node would be snapping */
// Unfortunately, by now we will have lost the font-baseline snappoints :-(
}
if (_box) {
for ( unsigned i = 0 ; i < 4 ; i++ ) {
}
}
/*Snapping will be to either nodes or to boundingbox-cornes; each will require its own origin, which
is only slightly different from the other. When we would use an origin at one of the nodes while
trying to snap the boundingbox, all four points of the boundingbox would be moving (e.g. during stretching),
and would therefore also be snapping (which is bad). This leads to bugs similar to #1540195, in which
a box is caught between to guides. To solve this, we need two different points: _opposite_for_snappoints and
_opposite_for_boundingbox
*/
if (_box) {
// FIXME: should be using ConvexHull here
if ( _snap_points.empty() == false) {
i++;
while (i != _snap_points.end()) {
i++;
}
}
//until we can kick out the old _opposite, and use _opposite_for_bboxpoints or _opposite_for_snappoints everywhere
//keep the old behavior for _opposite:
//FIXME: get rid of _opposite. Requires different handling of preferences, see Bulia Byak's comment for bug #1540195
}
if ((x != -1) && (y != -1)) {
}
if (_show == SHOW_OUTLINE) {
for (int i = 0; i < 4; i++)
sp_canvas_item_show(_l[i]);
}
}
{
if (_show == SHOW_CONTENT) {
// update the content
}
} else {
if (_box) {
/* update the outline */
for (unsigned i = 0 ; i < 4 ; i++) {
}
for (unsigned i = 0 ; i < 4 ; i++) {
}
}
}
_changed = true;
}
{
_grabbed = false;
_show_handles = true;
}
if (_show == SHOW_OUTLINE) {
for (int i = 0; i < 4; i++)
sp_canvas_item_hide(_l[i]);
}
if (_stamp_cache) {
_stamp_cache = NULL;
}
if (_center) {
_center_is_set = true;
}
// If dragging showed content live, sp_selection_apply_affine cannot change the centers
// appropriately - it does not know the original positions of the centers (all objects already have
// the new bboxes). So we need to reset the centers from our saved array.
for (unsigned i = 0; i < _items_centers.size(); i++) {
}
}
}
if (_current.is_translation()) {
_("Move"));
_("Scale"));
} else if (_current.is_rotation()) {
_("Rotate"));
} else {
_("Skew"));
}
} else {
if (_center_is_set) {
// we were dragging center; update reprs and commit undoable action
}
_("Set center"));
}
}
}
/* fixme: This is really bad, as we compare positions for each stamp (Lauris) */
/* fixme: IMHO the best way to keep sort cache would be to implement timestamping at last */
{
if ( fixup && _stamp_cache ) {
// TODO - give a proper fix. Simple temproary work-around for the grab() issue
_stamp_cache = NULL;
}
/* stamping mode */
if (!_empty) {
GSList *l;
if (_stamp_cache) {
l = _stamp_cache;
} else {
/* Build cache */
_stamp_cache = l;
}
while (l) {
// remember the position of the item
// remember parent
// add the new repr to the parent
// move to the saved position
if (_show == SHOW_OUTLINE) {
} else {
}
}
l = l->next;
}
_("Stamp"));
}
if ( fixup && _stamp_cache ) {
// TODO - give a proper fix. Simple temproary work-around for the grab() issue
_stamp_cache = NULL;
}
}
{
if ( !_show_handles || _empty )
{
return;
}
// center handle
_chandle = sp_knot_new(_desktop, _("<b>Center</b> of rotation and skewing: drag to reposition; scaling with Shift also uses this center"));
}
if ( _state == STATE_SCALE ) {
_("<b>Squeeze or stretch</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"),
_("<b>Scale</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"));
} else {
_("<b>Skew</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to skew around the opposite side"),
_("<b>Rotate</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to rotate around the opposite corner"));
}
if (!_center_is_set) {
_center_is_set = true;
}
} else {
}
}
{
if (_empty) {
return;
}
if (!_box) {
_empty = true;
return;
}
}
{
for (int i = 0; i < num; i++) {
sp_knot_hide(knot[i]);
}
}
}
{
g_return_if_fail( !_empty );
for (int i = 0; i < num; i++) {
sp_knot_update_ctrl(knot[i]);
g_signal_connect(G_OBJECT(knot[i]), "event", G_CALLBACK(sp_seltrans_handle_event), (gpointer) &handle[i]);
}
sp_knot_show(knot[i]);
// shouldn't have nullary bbox, but knots
+ ( _box->dimensions()
sp_knot_moveto(knot[i], &p);
}
}
{
);
}
{
}
static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint state, gpointer data)
{
);
}
static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *position, guint state, gboolean *data)
{
);
}
{
);
}
{
case GTK_ANCHOR_CENTER:
if (state & GDK_SHIFT_MASK) {
// Unset the center position for all selected items
it->unsetCenter();
_center_is_set = false; // center has changed
}
_("Reset center"));
}
break;
default:
break;
}
}
{
case GTK_ANCHOR_CENTER:
"shape", SP_CTRL_SHAPE_BITMAP,
"size", 13.0,
NULL);
break;
default:
"shape", SP_CTRL_SHAPE_CROSS,
"size", 7.0,
NULL);
break;
}
}
void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
{
if (!SP_KNOT_IS_GRABBED(knot)) {
return;
}
// in case items have been unhooked from the document, don't
// try to continue processing events for them.
return;
}
}
}
gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
{
if (!SP_KNOT_IS_GRABBED(knot)) {
return TRUE;
}
if (state & GDK_MOD1_MASK) {
}
} else if (_center) {
} else {
// FIXME
return TRUE;
}
}
return TRUE;
}
{
if (!_grabbed) {
_center_is_set = false; // center(s) may have changed
}
}
{
if (!_grabbed) {
// reset internal flag
_changed = false;
_center_is_set = false; // center(s) may have changed
}
}
/*
* handlers for handle move-request
*/
/** Returns -1 or 1 according to the sign of x. Returns 1 for 0 and NaN. */
static double sign(double const x)
{
return ( x < 0
? -1
: 1 );
}
{
}
{
}
{
}
{
}
{
}
{
using NR::X;
using NR::Y;
/* Work out the new scale factors `s' */
for ( unsigned int i = 0 ; i < 2 ; i++ ) {
if ( fabs(d[i]) > 0.001 ) {
if ( fabs(s[i]) < 1e-9 ) {
s[i] = 1e-9;
}
}
}
/* Get a STL list of the selected items.
** FIXME: this should probably be done by Inkscape::Selection.
*/
}
/* Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y].
** To do this, we snap along a suitable constraint vector from the origin.
*/
// The inclination of the constraint vector is calculated from the aspect ratio
// Determine direction of the constraint vector
);
it,
s,
it,
s,
/* We didn't snap, so just lock aspect ratio */
} else {
}
} else {
/* Choose the smaller difference in scale. Since s[X] == s[Y] we can
** just compare difference in s[X].
*/
}
} else {
/* Scale aspect ratio is unlocked */
it,
s,
it,
s,
/* Pick the snap that puts us closest to the original scale */
: NR_HUGE;
: NR_HUGE;
}
/* Update the knot position */
/* Status text */
_("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
return TRUE;
}
gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
{
using NR::X;
using NR::Y;
case GDK_TOP_SIDE:
case GDK_BOTTOM_SIDE:
break;
case GDK_LEFT_SIDE:
case GDK_RIGHT_SIDE:
break;
default:
return TRUE;
};
return FALSE;
}
s[axis] = 1e-15;
}
/* Get a STL list of the selected items.
** FIXME: this should probably be done by Inkscape::Selection.
*/
}
if ( state & GDK_CONTROL_MASK ) {
it,
s[axis],
axis,
true);
it,
s[axis],
axis,
true);
} else {
it,
s[axis],
axis,
false);
it,
s[axis],
axis,
false);
/* Choose the smaller difference in scale */
s[perp] = 1;
}
g_warning("point=(%g, %g), norm=(%g, %g), s=(%g, %g)\n",
}
// status text
_("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
return TRUE;
}
gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
{
using NR::X;
using NR::Y;
return FALSE;
}
dim_a = X;
dim_b = Y;
} else {
dim_a = Y;
dim_b = X;
}
double skew[2];
double s[2] = { 1.0, 1.0 };
return FALSE;
}
} else {
}
if (state & GDK_CONTROL_MASK) {
if (snaps) {
}
} else {
dim_b);
dim_b);
/* We snapped something, so change the skew to reflect it */
}
}
/* status text */
// TRANSLATORS: don't modify the first ";"
// (it will NOT be displayed as ";" - only the second one will be)
_("<b>Skew</b>: %0.2f°; with <b>Ctrl</b> to snap angle"),
degrees);
return TRUE;
}
{
// rotate affine in rotate
double radians;
if (state & GDK_CONTROL_MASK) {
/* Have to restrict movement. */
if (snaps) {
}
} else {
}
/* status text */
// TRANSLATORS: don't modify the first ";"
// (it will NOT be displayed as ";" - only the second one will be)
_("<b>Rotate</b>: %0.2f°; with <b>Ctrl</b> to snap angle"), degrees);
return TRUE;
}
{
using NR::X;
using NR::Y;
if (state & GDK_CONTROL_MASK) {
} else {
}
}
// screen pixels to snap center to bbox
#define SNAP_DIST 5
// FIXME: take from prefs
for (int i = 0; i < 2; i++) {
}
}
}
}
}
// status text
_message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move <b>center</b> to %s, %s"), xs->str, ys->str);
return TRUE;
}
/*
* handlers for handle movement
*
*/
void sp_sel_trans_stretch(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
{
}
void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
{
}
void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
{
}
void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
{
}
{
using NR::X;
using NR::Y;
case GDK_LEFT_SIDE:
case GDK_RIGHT_SIDE:
dim = X;
break;
case GDK_TOP_SIDE:
case GDK_BOTTOM_SIDE:
dim = Y;
break;
default:
abort();
break;
}
return;
}
g_warning("s[dim]=%g, pt[dim]=%g, scale_origin[dim]=%g, point[dim]=%g\n",
}
s[dim] = 1e-15;
}
if (state & GDK_CONTROL_MASK) {
/* Preserve aspect ratio, but never flip in the dimension not being edited. */
}
if (!_box) {
return;
}
NR::Point new_bbox_min = _box->min() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin));
NR::Point new_bbox_max = _box->max() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin));
}
{
if (!_box) {
return;
}
if (fabs(s[i]) < 1e-9)
s[i] = 1e-9;
}
NR::Point new_bbox_min = _box->min() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin));
NR::Point new_bbox_max = _box->max() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin));
}
{
unsigned dim;
case GDK_SB_H_DOUBLE_ARROW:
break;
case GDK_SB_V_DOUBLE_ARROW:
break;
default:
abort();
break;
}
return;
}
for (int i = 0; i < 2; i++) {
}
}
}
{
if (h1 < 1e-15) {
return;
}
if (h2 < 1e-15) {
return;
}
}
void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
{
}
{
/* The amount that we've moved by during this drag */
/* Get a STL list of the selected items.
** FIXME: this should probably be done by Inkscape::Selection.
*/
}
if (alt) {
/* Alt pressed means keep offset: snap the moved distance to the grid.
** FIXME: this will snap to more than just the grid, nowadays.
*/
} else if (!shift) {
/* We're snapping to things, possibly with a constraint to horizontal or
** vertical movement. Obtain a list of possible translations and then
** pick the smallest.
*/
/* This will be our list of possible translations */
if (control) {
/* Snap to things, and also constrain to horizontal or vertical movement */
it,
dxy));
it,
dxy));
}
} else {
/* Snap to things with no constraint */
}
/* Pick one */
if (i->second) {
if (m < best) {
best = m;
}
}
}
}
if (control) {
/* Ensure that the horizontal and vertical constraint has been applied */
} else {
}
}
// status text
_message_context.setF(Inkscape::NORMAL_MESSAGE, _("<b>Move</b> by %s, %s; with <b>Ctrl</b> to restrict to horizontal/vertical; with <b>Shift</b> to disable snapping"), xs->str, ys->str);
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :