spiral-context.cpp revision ac7344fa2f2335d9c3bcdb2a63db828d55073a73
951N/A/*
951N/A * Spiral drawing context
951N/A *
951N/A * Authors:
951N/A * Mitsuru Oka
951N/A * Lauris Kaplinski <lauris@kaplinski.com>
951N/A * bulia byak <buliabyak@users.sf.net>
951N/A * Jon A. Cruz <jon@joncruz.org>
951N/A * Abhishek Sharma
951N/A *
951N/A * Copyright (C) 1999-2001 Lauris Kaplinski
951N/A * Copyright (C) 2001-2002 Mitsuru Oka
951N/A *
951N/A * Released under GNU GPL
951N/A */
951N/A
951N/A#include "config.h"
951N/A
951N/A#include <gdk/gdkkeysyms.h>
951N/A#include <cstring>
951N/A#include <string>
951N/A
951N/A#include "macros.h"
951N/A#include "display/sp-canvas.h"
951N/A#include "sp-spiral.h"
951N/A#include "document.h"
951N/A#include "document-undo.h"
951N/A#include "sp-namedview.h"
951N/A#include "selection.h"
951N/A#include "desktop-handles.h"
951N/A#include "snap.h"
951N/A#include "desktop.h"
951N/A#include "desktop-style.h"
951N/A#include "message-context.h"
951N/A#include "pixmaps/cursor-spiral.xpm"
951N/A#include "spiral-context.h"
951N/A#include "sp-metrics.h"
951N/A#include <glibmm/i18n.h>
951N/A#include "xml/repr.h"
951N/A#include "xml/node-event-vector.h"
951N/A#include "preferences.h"
951N/A#include "context-fns.h"
951N/A#include "shape-editor.h"
951N/A#include "verbs.h"
951N/A#include "display/sp-canvas-item.h"
951N/A
951N/Ausing Inkscape::DocumentUndo;
951N/A
951N/Astatic void sp_spiral_context_class_init(SPSpiralContextClass * klass);
951N/Astatic void sp_spiral_context_init(SPSpiralContext *spiral_context);
951N/Astatic void sp_spiral_context_dispose(GObject *object);
951N/Astatic void sp_spiral_context_setup(SPEventContext *ec);
958N/Astatic void sp_spiral_context_finish(SPEventContext *ec);
951N/Astatic void sp_spiral_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val);
951N/A
968N/Astatic gint sp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event);
951N/A
951N/Astatic void sp_spiral_drag(SPSpiralContext *sc, Geom::Point const &p, guint state);
951N/Astatic void sp_spiral_finish(SPSpiralContext *sc);
951N/Astatic void sp_spiral_cancel(SPSpiralContext *sc);
951N/A
951N/Astatic SPEventContextClass *parent_class;
951N/A
951N/AGType
951N/Asp_spiral_context_get_type()
951N/A{
951N/A static GType type = 0;
951N/A if (!type) {
951N/A GTypeInfo info = {
951N/A sizeof(SPSpiralContextClass),
951N/A NULL, NULL,
951N/A (GClassInitFunc) sp_spiral_context_class_init,
951N/A NULL, NULL,
951N/A sizeof(SPSpiralContext),
951N/A 4,
951N/A (GInstanceInitFunc) sp_spiral_context_init,
951N/A NULL, /* value_table */
951N/A };
951N/A type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPSpiralContext", &info, (GTypeFlags)0);
951N/A }
951N/A return type;
951N/A}
951N/A
951N/Astatic void
951N/Asp_spiral_context_class_init(SPSpiralContextClass *klass)
951N/A{
951N/A GObjectClass *object_class = (GObjectClass *) klass;
951N/A SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
951N/A
951N/A parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
951N/A
951N/A object_class->dispose = sp_spiral_context_dispose;
951N/A
951N/A event_context_class->setup = sp_spiral_context_setup;
951N/A event_context_class->finish = sp_spiral_context_finish;
951N/A event_context_class->set = sp_spiral_context_set;
951N/A event_context_class->root_handler = sp_spiral_context_root_handler;
951N/A}
951N/A
951N/Astatic void
951N/Asp_spiral_context_init(SPSpiralContext *spiral_context)
951N/A{
951N/A SPEventContext *event_context = SP_EVENT_CONTEXT(spiral_context);
951N/A
951N/A event_context->cursor_shape = cursor_spiral_xpm;
951N/A event_context->hot_x = 4;
951N/A event_context->hot_y = 4;
951N/A event_context->xp = 0;
951N/A event_context->yp = 0;
951N/A event_context->tolerance = 0;
951N/A event_context->within_tolerance = false;
951N/A event_context->item_to_select = NULL;
951N/A
951N/A spiral_context->item = NULL;
951N/A
951N/A spiral_context->revo = 3.0;
951N/A spiral_context->exp = 1.0;
951N/A spiral_context->t0 = 0.0;
951N/A
951N/A new (&spiral_context->sel_changed_connection) sigc::connection();
951N/A}
951N/A
951N/Astatic void sp_spiral_context_finish(SPEventContext *ec)
951N/A{
951N/A SPSpiralContext *sc = SP_SPIRAL_CONTEXT(ec);
951N/A SPDesktop *desktop = ec->desktop;
951N/A
951N/A sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME);
951N/A sp_spiral_finish(sc);
951N/A sc->sel_changed_connection.disconnect();
951N/A
951N/A if (((SPEventContextClass *) parent_class)->finish) {
951N/A ((SPEventContextClass *) parent_class)->finish(ec);
951N/A }
951N/A}
951N/A
951N/Astatic void
951N/Asp_spiral_context_dispose(GObject *object)
951N/A{
951N/A SPSpiralContext *sc = SP_SPIRAL_CONTEXT(object);
951N/A SPEventContext *ec = SP_EVENT_CONTEXT(object);
951N/A
951N/A ec->enableGrDrag(false);
951N/A
951N/A sc->sel_changed_connection.disconnect();
951N/A sc->sel_changed_connection.~connection();
951N/A
951N/A delete ec->shape_editor;
951N/A ec->shape_editor = NULL;
951N/A
951N/A /* fixme: This is necessary because we do not grab */
951N/A if (sc->item) sp_spiral_finish(sc);
951N/A
951N/A if (sc->_message_context) {
951N/A delete sc->_message_context;
951N/A }
951N/A
951N/A G_OBJECT_CLASS(parent_class)->dispose(object);
951N/A}
951N/A
951N/A/**
951N/A * Callback that processes the "changed" signal on the selection;
951N/A * destroys old and creates new knotholder.
951N/A */
951N/Avoid sp_spiral_context_selection_changed(Inkscape::Selection *selection, gpointer data)
951N/A{
951N/A SPSpiralContext *sc = SP_SPIRAL_CONTEXT(data);
951N/A SPEventContext *ec = SP_EVENT_CONTEXT(sc);
951N/A
951N/A ec->shape_editor->unset_item(SH_KNOTHOLDER);
951N/A SPItem *item = selection->singleItem();
1274N/A ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1274N/A}
1274N/A
1274N/Astatic void
951N/Asp_spiral_context_setup(SPEventContext *ec)
1274N/A{
1274N/A SPSpiralContext *sc = SP_SPIRAL_CONTEXT(ec);
1274N/A
1274N/A if (((SPEventContextClass *) parent_class)->setup)
1274N/A ((SPEventContextClass *) parent_class)->setup(ec);
1274N/A
1274N/A sp_event_context_read(ec, "expansion");
1274N/A sp_event_context_read(ec, "revolution");
1274N/A sp_event_context_read(ec, "t0");
951N/A
951N/A ec->shape_editor = new ShapeEditor(ec->desktop);
951N/A
1274N/A SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
1274N/A if (item) {
1274N/A ec->shape_editor->set_item(item, SH_KNOTHOLDER);
1274N/A }
951N/A
951N/A Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
951N/A sc->sel_changed_connection.disconnect();
951N/A sc->sel_changed_connection = selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_spiral_context_selection_changed), (gpointer)sc));
951N/A
951N/A Inkscape::Preferences *prefs = Inkscape::Preferences::get();
951N/A if (prefs->getBool("/tools/shapes/selcue")) {
951N/A ec->enableSelectionCue();
951N/A }
951N/A if (prefs->getBool("/tools/shapes/gradientdrag")) {
951N/A ec->enableGrDrag();
951N/A }
951N/A
951N/A sc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
951N/A}
951N/A
951N/Astatic void
1028N/Asp_spiral_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val)
1028N/A{
1028N/A SPSpiralContext *sc = SP_SPIRAL_CONTEXT(ec);
1028N/A Glib::ustring name = val->getEntryName();
1028N/A
1028N/A if (name == "expansion") {
1028N/A sc->exp = CLAMP(val->getDouble(), 0.0, 1000.0);
1028N/A } else if (name == "revolution") {
1028N/A sc->revo = CLAMP(val->getDouble(3.0), 0.05, 40.0);
1028N/A } else if (name == "t0") {
1028N/A sc->t0 = CLAMP(val->getDouble(), 0.0, 0.999);
1028N/A }
1028N/A}
1028N/A
951N/Astatic gint
951N/Asp_spiral_context_root_handler(SPEventContext *event_context, GdkEvent *event)
951N/A{
951N/A static gboolean dragging;
951N/A
951N/A SPDesktop *desktop = event_context->desktop;
951N/A Inkscape::Selection *selection = sp_desktop_selection (desktop);
951N/A SPSpiralContext *sc = SP_SPIRAL_CONTEXT(event_context);
951N/A
951N/A Inkscape::Preferences *prefs = Inkscape::Preferences::get();
951N/A event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
951N/A
951N/A gint ret = FALSE;
951N/A
951N/A switch (event->type) {
951N/A case GDK_BUTTON_PRESS:
951N/A if (event->button.button == 1 && !event_context->space_panning) {
1028N/A
951N/A dragging = TRUE;
968N/A sc->center = Inkscape::setup_for_drag_start(desktop, event_context, event);
951N/A
951N/A SnapManager &m = desktop->namedview->snap_manager;
1028N/A m.setup(desktop);
1028N/A m.freeSnapReturnByRef(sc->center, Inkscape::SNAPSOURCE_NODE_HANDLE);
1028N/A m.unSetup();
1028N/A sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
1028N/A ( GDK_KEY_PRESS_MASK |
1028N/A GDK_BUTTON_RELEASE_MASK |
1028N/A GDK_POINTER_MOTION_MASK |
1028N/A GDK_POINTER_MOTION_HINT_MASK |
1028N/A GDK_BUTTON_PRESS_MASK ),
1028N/A NULL, event->button.time);
1028N/A ret = TRUE;
1274N/A }
1028N/A break;
1028N/A case GDK_MOTION_NOTIFY:
1028N/A if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !event_context->space_panning) {
1028N/A
1028N/A if ( event_context->within_tolerance
1028N/A && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
1028N/A && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
1028N/A break; // do not drag if we're within tolerance from origin
1028N/A }
1028N/A // Once the user has moved farther than tolerance from the original location
1028N/A // (indicating they intend to draw, not click), then always process the
1028N/A // motion notify coordinates as given (no snapping back to origin)
1028N/A event_context->within_tolerance = false;
1028N/A
1028N/A Geom::Point const motion_w(event->motion.x, event->motion.y);
1028N/A Geom::Point motion_dt(event_context->desktop->w2d(motion_w));
1028N/A
1028N/A SnapManager &m = desktop->namedview->snap_manager;
1028N/A m.setup(desktop, true, sc->item);
1028N/A m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
1028N/A m.unSetup();
1028N/A sp_spiral_drag(sc, motion_dt, event->motion.state);
1028N/A
1028N/A gobble_motion_events(GDK_BUTTON1_MASK);
1028N/A
1028N/A ret = TRUE;
1028N/A } else if (!sp_event_context_knot_mouseover(sc)) {
1028N/A SnapManager &m = desktop->namedview->snap_manager;
951N/A m.setup(desktop);
951N/A Geom::Point const motion_w(event->motion.x, event->motion.y);
951N/A Geom::Point motion_dt(desktop->w2d(motion_w));
951N/A m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
951N/A m.unSetup();
951N/A }
951N/A break;
951N/A case GDK_BUTTON_RELEASE:
951N/A event_context->xp = event_context->yp = 0;
951N/A if (event->button.button == 1 && !event_context->space_panning) {
951N/A dragging = FALSE;
951N/A sp_event_context_discard_delayed_snap_event(event_context);
951N/A if (!event_context->within_tolerance) {
951N/A // we've been dragging, finish the spiral
951N/A sp_spiral_finish(sc);
951N/A } else if (event_context->item_to_select) {
951N/A // no dragging, select clicked item if any
951N/A if (event->button.state & GDK_SHIFT_MASK) {
951N/A selection->toggle(event_context->item_to_select);
951N/A } else {
951N/A selection->set(event_context->item_to_select);
951N/A }
951N/A } else {
951N/A // click in an empty space
1028N/A selection->clear();
1028N/A }
1028N/A
1028N/A event_context->item_to_select = NULL;
1028N/A ret = TRUE;
1028N/A sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
1028N/A }
1028N/A break;
1028N/A case GDK_KEY_PRESS:
1028N/A switch (get_group0_keyval(&event->key)) {
1028N/A case GDK_KEY_Alt_R:
1028N/A case GDK_KEY_Control_L:
1028N/A case GDK_KEY_Control_R:
1028N/A case GDK_KEY_Shift_L:
1028N/A case GDK_KEY_Shift_R:
1028N/A case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
1028N/A case GDK_KEY_Meta_R:
1274N/A sp_event_show_modifier_tip(event_context->defaultMessageContext(), event,
1028N/A _("<b>Ctrl</b>: snap angle"),
1028N/A NULL,
1028N/A _("<b>Alt</b>: lock spiral radius"));
1028N/A break;
1028N/A case GDK_KEY_Up:
1274N/A case GDK_KEY_Down:
1274N/A case GDK_KEY_KP_Up:
1028N/A case GDK_KEY_KP_Down:
1274N/A // prevent the zoom field from activation
1028N/A if (!MOD__CTRL_ONLY)
1274N/A ret = TRUE;
1028N/A break;
1028N/A case GDK_KEY_x:
1028N/A case GDK_KEY_X:
951N/A if (MOD__ALT_ONLY) {
951N/A desktop->setToolboxFocusTo ("altx-spiral");
951N/A ret = TRUE;
951N/A }
951N/A break;
951N/A case GDK_KEY_Escape:
951N/A if (dragging) {
958N/A dragging = false;
951N/A sp_event_context_discard_delayed_snap_event(event_context);
951N/A // if drawing, cancel, otherwise pass it up for deselecting
951N/A sp_spiral_cancel(sc);
951N/A ret = TRUE;
951N/A }
951N/A break;
951N/A
951N/A case GDK_KEY_space:
951N/A if (dragging) {
951N/A sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
1274N/A event->button.time);
951N/A dragging = false;
951N/A sp_event_context_discard_delayed_snap_event(event_context);
951N/A if (!event_context->within_tolerance) {
951N/A // we've been dragging, finish the spiral
951N/A sp_spiral_finish(sc);
951N/A }
951N/A // do not return true, so that space would work switching to selector
951N/A }
951N/A break;
951N/A case GDK_KEY_Delete:
951N/A case GDK_KEY_KP_Delete:
951N/A case GDK_KEY_BackSpace:
951N/A ret = event_context->deleteSelectedDrag(MOD__CTRL_ONLY);
951N/A break;
951N/A
951N/A default:
951N/A break;
951N/A }
951N/A break;
951N/A case GDK_KEY_RELEASE:
951N/A switch (get_group0_keyval(&event->key)) {
951N/A case GDK_KEY_Alt_L:
951N/A case GDK_KEY_Alt_R:
951N/A case GDK_KEY_Control_L:
951N/A case GDK_KEY_Control_R:
951N/A case GDK_KEY_Shift_L:
951N/A case GDK_KEY_Shift_R:
951N/A case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
951N/A case GDK_KEY_Meta_R:
951N/A event_context->defaultMessageContext()->clear();
951N/A break;
951N/A default:
951N/A break;
951N/A }
951N/A break;
951N/A default:
951N/A break;
951N/A }
951N/A
951N/A if (!ret) {
951N/A if (((SPEventContextClass *) parent_class)->root_handler)
951N/A ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
951N/A }
951N/A
951N/A return ret;
951N/A}
951N/A
951N/Astatic void sp_spiral_drag(SPSpiralContext *sc, Geom::Point const &p, guint state)
951N/A{
951N/A SPDesktop *desktop = SP_EVENT_CONTEXT(sc)->desktop;
951N/A
951N/A Inkscape::Preferences *prefs = Inkscape::Preferences::get();
951N/A int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
951N/A
951N/A if (!sc->item) {
951N/A
951N/A if (Inkscape::have_viable_layer(desktop, sc->_message_context) == false) {
951N/A return;
951N/A }
951N/A
951N/A // Create object
951N/A Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DOCUMENT(sc)->getReprDoc();
951N/A Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
951N/A repr->setAttribute("sodipodi:type", "spiral");
951N/A
951N/A // Set style
951N/A sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/spiral", false);
951N/A
951N/A sc->item = (SPItem *) desktop->currentLayer()->appendChildRepr(repr);
951N/A Inkscape::GC::release(repr);
951N/A sc->item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
951N/A sc->item->updateRepr();
951N/A
951N/A desktop->canvas->forceFullRedrawAfterInterruptions(5);
951N/A }
951N/A
951N/A SnapManager &m = desktop->namedview->snap_manager;
951N/A m.setup(desktop, true, sc->item);
951N/A Geom::Point pt2g = p;
951N/A m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE);
951N/A m.unSetup();
951N/A Geom::Point const p0 = desktop->dt2doc(sc->center);
951N/A Geom::Point const p1 = desktop->dt2doc(pt2g);
951N/A
951N/A SPSpiral *spiral = SP_SPIRAL(sc->item);
951N/A
951N/A Geom::Point const delta = p1 - p0;
951N/A gdouble const rad = Geom::L2(delta);
951N/A
951N/A gdouble arg = Geom::atan2(delta) - 2.0*M_PI*spiral->revo;
951N/A
951N/A if (state & GDK_CONTROL_MASK) {
951N/A arg = sp_round(arg, M_PI/snaps);
951N/A }
951N/A
951N/A /* Fixme: these parameters should be got from dialog box */
951N/A sp_spiral_position_set(spiral, p0[Geom::X], p0[Geom::Y],
951N/A /*expansion*/ sc->exp,
951N/A /*revolution*/ sc->revo,
951N/A rad, arg,
951N/A /*t0*/ sc->t0);
951N/A
951N/A /* status text */
951N/A GString *rads = SP_PX_TO_METRIC_STRING(rad, desktop->namedview->getDefaultMetric());
951N/A sc->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE,
951N/A _("<b>Spiral</b>: radius %s, angle %5g&#176;; with <b>Ctrl</b> to snap angle"),
951N/A rads->str, sp_round((arg + 2.0*M_PI*spiral->revo)*180/M_PI, 0.0001));
951N/A g_string_free(rads, FALSE);
951N/A}
951N/A
951N/Astatic void
951N/Asp_spiral_finish(SPSpiralContext *sc)
951N/A{
951N/A sc->_message_context->clear();
951N/A
951N/A if (sc->item != NULL) {
951N/A SPSpiral *spiral = SP_SPIRAL(sc->item);
951N/A if (spiral->rad == 0) {
951N/A sp_spiral_cancel(sc); // Don't allow the creating of zero sized spiral, for example when the start and and point snap to the snap grid point
951N/A return;
1028N/A }
1028N/A
1028N/A SPDesktop *desktop = SP_EVENT_CONTEXT(sc)->desktop;
1028N/A
1028N/A SP_SHAPE(spiral)->setShape();
1028N/A SP_OBJECT(spiral)->updateRepr(SP_OBJECT_WRITE_EXT);
1028N/A
1028N/A desktop->canvas->endForcedFullRedraws();
1028N/A
1028N/A sp_desktop_selection(desktop)->set(sc->item);
1028N/A DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_SPIRAL,
1028N/A _("Create spiral"));
1028N/A
1028N/A sc->item = NULL;
1028N/A }
1028N/A}
1028N/A
1028N/Astatic void sp_spiral_cancel(SPSpiralContext *sc)
1028N/A{
1028N/A SPDesktop *desktop = SP_EVENT_CONTEXT(sc)->desktop;
1028N/A
1028N/A sp_desktop_selection(desktop)->clear();
1028N/A sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0);
1274N/A
1274N/A if (sc->item != NULL) {
1274N/A SP_OBJECT(sc->item)->deleteObject();
1274N/A sc->item = NULL;
1274N/A }
1274N/A
1274N/A sc->within_tolerance = false;
1274N/A sc->xp = 0;
1274N/A sc->yp = 0;
1274N/A sc->item_to_select = NULL;
1274N/A
1274N/A desktop->canvas->endForcedFullRedraws();
1274N/A
1028N/A DocumentUndo::cancel(sp_desktop_document(desktop));
951N/A}
951N/A
951N/A/*
968N/A Local Variables:
968N/A mode:c++
968N/A c-file-style:"stroustrup"
968N/A c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
968N/A indent-tabs-mode:nil
968N/A fill-column:99
968N/A End:
968N/A*/
968N/A// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
968N/A