gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) { static bool moved = false; gint ret = FALSE; SPGuide *guide = SP_GUIDE(data); SPDesktop *desktop = static_cast<SPDesktop*>(g_object_get_data(G_OBJECT(item->canvas), "SPDesktop")); switch (event->type) { case GDK_2BUTTON_PRESS: if (event->button.button == 1) { drag_type = SP_DRAG_NONE; sp_event_context_discard_delayed_snap_event(desktop->event_context); sp_canvas_item_ungrab(item, event->button.time); Inkscape::UI::Dialogs::GuidelinePropertiesDialog::showDialog(guide, desktop); ret = TRUE; } break; case GDK_BUTTON_PRESS: if (event->button.button == 1) { Geom::Point const event_w(event->button.x, event->button.y); Geom::Point const event_dt(desktop->w2d(event_w)); // Due to the tolerance allowed when grabbing a guide, event_dt will generally // be close to the guide but not just exactly on it. The drag origin calculated // here must be exactly on the guide line though, otherwise // small errors will occur once we snap, see // https://bugs.launchpad.net/inkscape/+bug/333762 drag_origin = Geom::projection(event_dt, Geom::Line(guide->getPoint(), guide->angle())); if (event->button.state & GDK_SHIFT_MASK) { // with shift we rotate the guide drag_type = SP_DRAG_ROTATE; } else { if (event->button.state & GDK_CONTROL_MASK) { drag_type = SP_DRAG_MOVE_ORIGIN; } else { drag_type = SP_DRAG_TRANSLATE; } } if (drag_type == SP_DRAG_ROTATE || drag_type == SP_DRAG_TRANSLATE) { sp_canvas_item_grab(item, ( GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK ), NULL, event->button.time); } ret = TRUE; } break; case GDK_MOTION_NOTIFY: if (drag_type != SP_DRAG_NONE) { Geom::Point const motion_w(event->motion.x, event->motion.y); Geom::Point motion_dt(desktop->w2d(motion_w)); sp_event_context_snap_delay_handler(desktop->event_context, (gpointer) item, data, (GdkEventMotion *)event, Inkscape::UI::Tools::DelayedSnapEvent::GUIDE_HANDLER); // This is for snapping while dragging existing guidelines. New guidelines, // which are dragged off the ruler, are being snapped in sp_dt_ruler_event SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, NULL, NULL, guide); if (drag_type == SP_DRAG_MOVE_ORIGIN) { // If we snap in guideConstrainedSnap() below, then motion_dt will // be forced to be on the guide. If we don't snap however, then // the origin should still be constrained to the guide. So let's do // that explicitly first: Geom::Line line(guide->getPoint(), guide->angle()); Geom::Coord t = line.nearestTime(motion_dt); motion_dt = line.pointAt(t); if (!(event->motion.state & GDK_SHIFT_MASK)) { m.guideConstrainedSnap(motion_dt, *guide); } } else if (!((drag_type == SP_DRAG_ROTATE) && (event->motion.state & GDK_CONTROL_MASK))) { // cannot use shift here to disable snapping, because we already use it for rotating the guide Geom::Point temp; if (drag_type == SP_DRAG_ROTATE) { temp = guide->getPoint(); m.guideFreeSnap(motion_dt, temp, true, false); guide->moveto(temp, false); } else { temp = guide->getNormal(); m.guideFreeSnap(motion_dt, temp, false, true); guide->set_normal(temp, false); } } m.unSetup(); switch (drag_type) { case SP_DRAG_TRANSLATE: { guide->moveto(motion_dt, false); break; } case SP_DRAG_ROTATE: { Geom::Point pt = motion_dt - guide->getPoint(); Geom::Angle angle(pt); if (event->motion.state & GDK_CONTROL_MASK) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); bool const relative_snaps = prefs->getBool("/options/relativeguiderotationsnap/value", false); if (snaps) { if (relative_snaps) { Geom::Angle orig_angle(guide->getNormal()); Geom::Angle snap_angle = angle - orig_angle; double sections = floor(snap_angle.radians0() * snaps / M_PI + .5); angle = (M_PI / snaps) * sections + orig_angle.radians0(); } else { double sections = floor(angle.radians0() * snaps / M_PI + .5); angle = (M_PI / snaps) * sections; } } } guide->set_normal(Geom::Point::polar(angle).cw(), false); break; } case SP_DRAG_MOVE_ORIGIN: { guide->moveto(motion_dt, false); break; } case SP_DRAG_NONE: assert(false); break; } moved = true; desktop->set_coordinate_status(motion_dt); ret = TRUE; } break; case GDK_BUTTON_RELEASE: if (drag_type != SP_DRAG_NONE && event->button.button == 1) { sp_event_context_discard_delayed_snap_event(desktop->event_context); if (moved) { Geom::Point const event_w(event->button.x, event->button.y); Geom::Point event_dt(desktop->w2d(event_w)); SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, NULL, NULL, guide); if (drag_type == SP_DRAG_MOVE_ORIGIN) { // If we snap in guideConstrainedSnap() below, then motion_dt will // be forced to be on the guide. If we don't snap however, then // the origin should still be constrained to the guide. So let's // do that explicitly first: Geom::Line line(guide->getPoint(), guide->angle()); Geom::Coord t = line.nearestTime(event_dt); event_dt = line.pointAt(t); if (!(event->button.state & GDK_SHIFT_MASK)) { m.guideConstrainedSnap(event_dt, *guide); } } else if (!((drag_type == SP_DRAG_ROTATE) && (event->motion.state & GDK_CONTROL_MASK))) { // cannot use shift here to disable snapping, because we already use it for rotating the guide Geom::Point temp; if (drag_type == SP_DRAG_ROTATE) { temp = guide->getPoint(); m.guideFreeSnap(event_dt, temp, true, false); guide->moveto(temp, false); } else { temp = guide->getNormal(); m.guideFreeSnap(event_dt, temp, false, true); guide->set_normal(temp, false); } } m.unSetup(); if (sp_canvas_world_pt_inside_window(item->canvas, event_w)) { switch (drag_type) { case SP_DRAG_TRANSLATE: { guide->moveto(event_dt, true); break; } case SP_DRAG_ROTATE: { Geom::Point pt = event_dt - guide->getPoint(); Geom::Angle angle(pt); if (event->motion.state & GDK_CONTROL_MASK) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); bool const relative_snaps = prefs->getBool("/options/relativeguiderotationsnap/value", false); if (snaps) { if (relative_snaps) { Geom::Angle orig_angle(guide->getNormal()); Geom::Angle snap_angle = angle - orig_angle; double sections = floor(snap_angle.radians0() * snaps / M_PI + .5); angle = (M_PI / snaps) * sections + orig_angle.radians0(); } else { double sections = floor(angle.radians0() * snaps / M_PI + .5); angle = (M_PI / snaps) * sections; } } } guide->set_normal(Geom::Point::polar(angle).cw(), true); break; } case SP_DRAG_MOVE_ORIGIN: { guide->moveto(event_dt, true); break; } case SP_DRAG_NONE: assert(false); break; } DocumentUndo::done(desktop->getDocument(), SP_VERB_NONE, _("Move guide")); } else { /* Undo movement of any attached shapes. */ guide->moveto(guide->getPoint(), false); guide->set_normal(guide->getNormal(), false); sp_guide_remove(guide); DocumentUndo::done(desktop->getDocument(), SP_VERB_NONE, _("Delete guide")); } moved = false; desktop->set_coordinate_status(event_dt); } drag_type = SP_DRAG_NONE; sp_canvas_item_ungrab(item, event->button.time); ret=TRUE; } break; case GDK_ENTER_NOTIFY: { sp_guideline_set_color(SP_GUIDELINE(item), guide->getHiColor()); // set move or rotate cursor Geom::Point const event_w(event->crossing.x, event->crossing.y); if ((event->crossing.state & GDK_SHIFT_MASK) && (drag_type != SP_DRAG_MOVE_ORIGIN)) { GdkCursor *guide_cursor; guide_cursor = gdk_cursor_new (GDK_EXCHANGE); gdk_window_set_cursor(gtk_widget_get_window (GTK_WIDGET(desktop->getCanvas())), guide_cursor); #if GTK_CHECK_VERSION(3,0,0) g_object_unref(guide_cursor); #else gdk_cursor_unref(guide_cursor); #endif } else { GdkCursor *guide_cursor; guide_cursor = gdk_cursor_new (GDK_HAND1); gdk_window_set_cursor(gtk_widget_get_window (GTK_WIDGET(desktop->getCanvas())), guide_cursor); #if GTK_CHECK_VERSION(3,0,0) g_object_unref(guide_cursor); #else gdk_cursor_unref(guide_cursor); #endif } char *guide_description = guide->description(); desktop->guidesMessageContext()->setF(Inkscape::NORMAL_MESSAGE, _("<b>Guideline</b>: %s"), guide_description); g_free(guide_description); break; } case GDK_LEAVE_NOTIFY: sp_guideline_set_color(SP_GUIDELINE(item), guide->getColor()); // restore event context's cursor gdk_window_set_cursor(gtk_widget_get_window (GTK_WIDGET(desktop->getCanvas())), desktop->event_context->cursor); desktop->guidesMessageContext()->clear(); break; case GDK_KEY_PRESS: switch (Inkscape::UI::Tools::get_group0_keyval (&event->key)) { case GDK_KEY_Delete: case GDK_KEY_KP_Delete: case GDK_KEY_BackSpace: { SPDocument *doc = guide->document; sp_guide_remove(guide); DocumentUndo::done(doc, SP_VERB_NONE, _("Delete guide")); ret = TRUE; sp_event_context_discard_delayed_snap_event(desktop->event_context); break; } case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: if (drag_type != SP_DRAG_MOVE_ORIGIN) { GdkCursor *guide_cursor; guide_cursor = gdk_cursor_new (GDK_EXCHANGE); gdk_window_set_cursor(gtk_widget_get_window (GTK_WIDGET(desktop->getCanvas())), guide_cursor); #if GTK_CHECK_VERSION(3,0,0) g_object_unref(guide_cursor); #else gdk_cursor_unref(guide_cursor); #endif ret = TRUE; break; } default: // do nothing; break; } break; case GDK_KEY_RELEASE: switch (Inkscape::UI::Tools::get_group0_keyval (&event->key)) { case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: GdkCursor *guide_cursor; guide_cursor = gdk_cursor_new (GDK_EXCHANGE); gdk_window_set_cursor(gtk_widget_get_window (GTK_WIDGET(desktop->getCanvas())), guide_cursor); #if GTK_CHECK_VERSION(3,0,0) g_object_unref(guide_cursor); #else gdk_cursor_unref(guide_cursor); #endif break; default: // do nothing; break; } break; default: break; } return ret; }
static void sp_object_layout_any_value_changed(GtkAdjustment *adj, SPWidget *spw) { if (g_object_get_data(G_OBJECT(spw), "update")) { return; } UnitTracker *tracker = reinterpret_cast<UnitTracker*>(g_object_get_data(G_OBJECT(spw), "tracker")); if ( !tracker || tracker->isUpdating() ) { /* * When only units are being changed, don't treat changes * to adjuster values as object changes. */ return; } g_object_set_data(G_OBJECT(spw), "update", GINT_TO_POINTER(TRUE)); SPDesktop *desktop = SP_ACTIVE_DESKTOP; Inkscape::Selection *selection = desktop->getSelection(); SPDocument *document = desktop->getDocument(); document->ensureUpToDate (); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Geom::OptRect bbox_vis = selection->visualBounds(); Geom::OptRect bbox_geom = selection->geometricBounds(); int prefs_bbox = prefs->getInt("/tools/bounding_box"); SPItem::BBoxType bbox_type = (prefs_bbox == 0)? SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX; Geom::OptRect bbox_user = selection->bounds(bbox_type); if ( !bbox_user ) { g_object_set_data(G_OBJECT(spw), "update", GINT_TO_POINTER(FALSE)); return; } gdouble x0 = 0; gdouble y0 = 0; gdouble x1 = 0; gdouble y1 = 0; gdouble xrel = 0; gdouble yrel = 0; Unit const *unit = tracker->getActiveUnit(); g_return_if_fail(unit != NULL); GtkAdjustment* a_x = GTK_ADJUSTMENT( g_object_get_data( G_OBJECT(spw), "X" ) ); GtkAdjustment* a_y = GTK_ADJUSTMENT( g_object_get_data( G_OBJECT(spw), "Y" ) ); GtkAdjustment* a_w = GTK_ADJUSTMENT( g_object_get_data( G_OBJECT(spw), "width" ) ); GtkAdjustment* a_h = GTK_ADJUSTMENT( g_object_get_data( G_OBJECT(spw), "height" ) ); if (unit->type == Inkscape::Util::UNIT_TYPE_LINEAR) { x0 = Quantity::convert(gtk_adjustment_get_value(a_x), unit, "px"); y0 = Quantity::convert(gtk_adjustment_get_value(a_y), unit, "px"); x1 = x0 + Quantity::convert(gtk_adjustment_get_value(a_w), unit, "px"); xrel = Quantity::convert(gtk_adjustment_get_value(a_w), unit, "px") / bbox_user->dimensions()[Geom::X]; y1 = y0 + Quantity::convert(gtk_adjustment_get_value(a_h), unit, "px");; yrel = Quantity::convert(gtk_adjustment_get_value(a_h), unit, "px") / bbox_user->dimensions()[Geom::Y]; } else { double const x0_propn = gtk_adjustment_get_value (a_x) / 100 / unit->factor; x0 = bbox_user->min()[Geom::X] * x0_propn; double const y0_propn = gtk_adjustment_get_value (a_y) / 100 / unit->factor; y0 = y0_propn * bbox_user->min()[Geom::Y]; xrel = gtk_adjustment_get_value (a_w) / (100 / unit->factor); x1 = x0 + xrel * bbox_user->dimensions()[Geom::X]; yrel = gtk_adjustment_get_value (a_h) / (100 / unit->factor); y1 = y0 + yrel * bbox_user->dimensions()[Geom::Y]; } // Keep proportions if lock is on GtkToggleAction *lock = GTK_TOGGLE_ACTION( g_object_get_data(G_OBJECT(spw), "lock") ); if ( gtk_toggle_action_get_active(lock) ) { if (adj == a_h) { x1 = x0 + yrel * bbox_user->dimensions()[Geom::X]; } else if (adj == a_w) { y1 = y0 + xrel * bbox_user->dimensions()[Geom::Y]; } } // scales and moves, in px double mh = fabs(x0 - bbox_user->min()[Geom::X]); double sh = fabs(x1 - bbox_user->max()[Geom::X]); double mv = fabs(y0 - bbox_user->min()[Geom::Y]); double sv = fabs(y1 - bbox_user->max()[Geom::Y]); // unless the unit is %, convert the scales and moves to the unit if (unit->type == Inkscape::Util::UNIT_TYPE_LINEAR) { mh = Quantity::convert(mh, "px", unit); sh = Quantity::convert(sh, "px", unit); mv = Quantity::convert(mv, "px", unit); sv = Quantity::convert(sv, "px", unit); } // do the action only if one of the scales/moves is greater than half the last significant // digit in the spinbox (currently spinboxes have 3 fractional digits, so that makes 0.0005). If // the value was changed by the user, the difference will be at least that much; otherwise it's // just rounding difference between the spinbox value and actual value, so no action is // performed char const * const actionkey = ( mh > 5e-4 ? "selector:toolbar:move:horizontal" : sh > 5e-4 ? "selector:toolbar:scale:horizontal" : mv > 5e-4 ? "selector:toolbar:move:vertical" : sv > 5e-4 ? "selector:toolbar:scale:vertical" : NULL ); if (actionkey != NULL) { // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed desktop->getCanvas()->forceFullRedrawAfterInterruptions(0); bool transform_stroke = prefs->getBool("/options/transform/stroke", true); bool preserve = prefs->getBool("/options/preservetransform/value", false); Geom::Affine scaler; if (bbox_type == SPItem::VISUAL_BBOX) { scaler = get_scale_transform_for_variable_stroke (*bbox_vis, *bbox_geom, transform_stroke, preserve, x0, y0, x1, y1); } else { // 1) We could have use the newer get_scale_transform_for_variable_stroke() here, but to avoid regressions // we'll just use the old get_scale_transform_for_uniform_stroke() for now. // 2) get_scale_transform_for_uniform_stroke() is intended for visual bounding boxes, not geometrical ones! // we'll trick it into using a geometric bounding box though, by setting the stroke width to zero scaler = get_scale_transform_for_uniform_stroke (*bbox_geom, 0, 0, false, false, x0, y0, x1, y1); } sp_selection_apply_affine(selection, scaler); DocumentUndo::maybeDone(document, actionkey, SP_VERB_CONTEXT_SELECT, _("Transform by toolbar")); // resume interruptibility desktop->getCanvas()->endForcedFullRedraws(); } g_object_set_data(G_OBJECT(spw), "update", GINT_TO_POINTER(FALSE)); }