From 50275345b5d581eeb6d93f94fd36afa4fb04ea9c Mon Sep 17 00:00:00 2001 From: buliabyak Date: Wed, 8 Aug 2007 07:33:15 +0000 Subject: [PATCH] tweak tool --- src/Makefile_insert | 1 + src/preferences-skeleton.h | 3 +- src/tools-switch.cpp | 9 + src/tools-switch.h | 1 + src/tweak-context.cpp | 751 +++++++++++++++++++++++++++ src/tweak-context.h | 88 ++++ src/ui/dialog/inkscape-preferences.h | 1 + src/verbs.cpp | 11 + src/verbs.h | 2 + src/widgets/toolbox.cpp | 158 +++++- 10 files changed, 1023 insertions(+), 2 deletions(-) create mode 100644 src/tweak-context.cpp create mode 100644 src/tweak-context.h diff --git a/src/Makefile_insert b/src/Makefile_insert index d57dd5774..946e3b1ff 100644 --- a/src/Makefile_insert +++ b/src/Makefile_insert @@ -270,6 +270,7 @@ libinkpre_a_SOURCES = \ text-editing.cpp text-editing.h \ text-tag-attributes.h \ tools-switch.cpp tools-switch.h\ + tweak-context.h tweak-context.cpp \ uri-references.cpp uri-references.h \ vanishing-point.cpp vanishing-point.h \ verbs.cpp verbs.h \ diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 0ad7bc157..8a04640e1 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -70,11 +70,12 @@ static char const preferences_skeleton[] = " \n" " \n" +" tracebackground=\"0\" usepressure=\"1\" usetilt=\"0\" keep_selected=\"1\"/>\n" " \n" " \n" +" \n" " \n" " \n" " \n" diff --git a/src/tools-switch.cpp b/src/tools-switch.cpp index fd8acad23..1bc83d7a2 100644 --- a/src/tools-switch.cpp +++ b/src/tools-switch.cpp @@ -24,6 +24,7 @@ #include "select-context.h" #include "node-context.h" +#include "tweak-context.h" #include "sp-path.h" #include "rect-context.h" #include "sp-rect.h" @@ -55,6 +56,7 @@ static char const *const tool_names[] = { NULL, "tools.select", "tools.nodes", + "tools.tweak", "tools.shapes.rect", "tools.shapes.3dbox", "tools.shapes.arc", @@ -76,6 +78,7 @@ static char const *const tool_ids[] = { NULL, "select", "nodes", + "tweak", "rect", "3dbox", "arc", @@ -141,6 +144,12 @@ tools_switch(SPDesktop *dt, int num) inkscape_eventcontext_set(sp_desktop_event_context(dt)); dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("To edit a path, click, Shift+click, or drag around nodes to select them, then drag nodes and handles. Click on an object to select.")); break; + case TOOLS_TWEAK: + dt->set_event_context(SP_TYPE_TWEAK_CONTEXT, tool_names[num]); + dt->activate_guides(true); + inkscape_eventcontext_set(sp_desktop_event_context(dt)); + dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("To tweak a path by pushing, select it and drag over it.")); + break; case TOOLS_SHAPES_RECT: dt->set_event_context(SP_TYPE_RECT_CONTEXT, tool_names[num]); dt->activate_guides(false); diff --git a/src/tools-switch.h b/src/tools-switch.h index aac48d834..8107984a8 100644 --- a/src/tools-switch.h +++ b/src/tools-switch.h @@ -18,6 +18,7 @@ enum { TOOLS_INVALID, TOOLS_SELECT, TOOLS_NODES, + TOOLS_TWEAK, TOOLS_SHAPES_RECT, TOOLS_SHAPES_3DBOX, TOOLS_SHAPES_ARC, diff --git a/src/tweak-context.cpp b/src/tweak-context.cpp new file mode 100644 index 000000000..6a951901e --- /dev/null +++ b/src/tweak-context.cpp @@ -0,0 +1,751 @@ +#define __SP_TWEAK_CONTEXT_C__ + +/* + * tweaking paths without node editing + * + * Authors: + * bulia byak + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noTWEAK_VERBOSE + +#include "config.h" + +#include +#include +#include + +#include + +#include "svg/svg.h" +#include "display/canvas-bpath.h" +#include "display/bezier-utils.h" + +#include +#include "macros.h" +#include "document.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "desktop-affine.h" +#include "desktop-style.h" +#include "message-context.h" +#include "pixmaps/cursor-thin.xpm" +#include "pixmaps/cursor-thicken.xpm" +#include "pixmaps/cursor-push.xpm" +#include "pixmaps/cursor-roughen.xpm" +#include "libnr/n-art-bpath.h" +#include "libnr/nr-path.h" +#include "libnr/nr-matrix-ops.h" +#include "libnr/nr-scale-translate-ops.h" +#include "xml/repr.h" +#include "context-fns.h" +#include "sp-item.h" +#include "inkscape.h" +#include "color.h" +#include "splivarot.h" +#include "sp-item-group.h" +#include "sp-shape.h" +#include "sp-path.h" +#include "path-chemistry.h" +#include "sp-text.h" +#include "sp-flowtext.h" +#include "display/canvas-bpath.h" +#include "display/canvas-arena.h" +#include "livarot/Shape.h" +#include "isnan.h" +#include "prefs-utils.h" + +#include "tweak-context.h" + +#define DDC_RED_RGBA 0xff0000ff + +#define DYNA_MIN_WIDTH 1.0e-6 + +// FIXME: move it to some shared file to be reused by both calligraphy and dropper +#define C1 0.552 +static NArtBpath const hatch_area_circle[] = { + { NR_MOVETO, 0, 0, 0, 0, -1, 0 }, + { NR_CURVETO, -1, C1, -C1, 1, 0, 1 }, + { NR_CURVETO, C1, 1, 1, C1, 1, 0 }, + { NR_CURVETO, 1, -C1, C1, -1, 0, -1 }, + { NR_CURVETO, -C1, -1, -1, -C1, -1, 0 }, + { NR_END, 0, 0, 0, 0, 0, 0 } +}; +#undef C1 + + +static void sp_tweak_context_class_init(SPTweakContextClass *klass); +static void sp_tweak_context_init(SPTweakContext *ddc); +static void sp_tweak_context_dispose(GObject *object); + +static void sp_tweak_context_setup(SPEventContext *ec); +static void sp_tweak_context_set(SPEventContext *ec, gchar const *key, gchar const *val); +static gint sp_tweak_context_root_handler(SPEventContext *ec, GdkEvent *event); + +static SPEventContextClass *parent_class; + +GtkType +sp_tweak_context_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPTweakContextClass), + NULL, NULL, + (GClassInitFunc) sp_tweak_context_class_init, + NULL, NULL, + sizeof(SPTweakContext), + 4, + (GInstanceInitFunc) sp_tweak_context_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPTweakContext", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_tweak_context_class_init(SPTweakContextClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + SPEventContextClass *event_context_class = (SPEventContextClass *) klass; + + parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass); + + object_class->dispose = sp_tweak_context_dispose; + + event_context_class->setup = sp_tweak_context_setup; + event_context_class->set = sp_tweak_context_set; + event_context_class->root_handler = sp_tweak_context_root_handler; +} + +static void +sp_tweak_context_init(SPTweakContext *tc) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(tc); + + event_context->cursor_shape = cursor_push_xpm; + event_context->hot_x = 4; + event_context->hot_y = 4; + + /* attributes */ + tc->dragging = FALSE; + + tc->width = 0.2; + tc->force = 0.2; + tc->pressure = TC_DEFAULT_PRESSURE; + + tc->is_dilating = false; + tc->has_dilated = false; +} + +static void +sp_tweak_context_dispose(GObject *object) +{ + SPTweakContext *tc = SP_TWEAK_CONTEXT(object); + + if (tc->dilate_area) { + gtk_object_destroy(GTK_OBJECT(tc->dilate_area)); + tc->dilate_area = NULL; + } + + if (tc->_message_context) { + delete tc->_message_context; + } + + G_OBJECT_CLASS(parent_class)->dispose(object); +} + +void +sp_tweak_update_cursor (SPTweakContext *tc, gint mode) +{ + SPEventContext *event_context = SP_EVENT_CONTEXT(tc); + switch (mode) { + case TWEAK_MODE_PUSH: + event_context->cursor_shape = cursor_push_xpm; + break; + case TWEAK_MODE_SUCK: + event_context->cursor_shape = cursor_thin_xpm; + break; + case TWEAK_MODE_BLOW: + event_context->cursor_shape = cursor_thicken_xpm; + break; + case TWEAK_MODE_ROUGHEN: + event_context->cursor_shape = cursor_roughen_xpm; + break; + } + sp_event_context_update_cursor(event_context); +} + +static void +sp_tweak_context_setup(SPEventContext *ec) +{ + SPTweakContext *tc = SP_TWEAK_CONTEXT(ec); + + if (((SPEventContextClass *) parent_class)->setup) + ((SPEventContextClass *) parent_class)->setup(ec); + + { + SPCurve *c = sp_curve_new_from_foreign_bpath(hatch_area_circle); + tc->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(ec->desktop), c); + sp_curve_unref(c); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(tc->dilate_area), 0x00000000,(SPWindRule)0); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(tc->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_hide(tc->dilate_area); + } + + sp_event_context_read(ec, "width"); + sp_event_context_read(ec, "mode"); + sp_event_context_read(ec, "fidelity"); + sp_event_context_read(ec, "force"); + sp_event_context_read(ec, "usepressure"); + + tc->is_drawing = false; + + tc->_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); +} + +static void +sp_tweak_context_set(SPEventContext *ec, gchar const *key, gchar const *val) +{ + SPTweakContext *tc = SP_TWEAK_CONTEXT(ec); + + if (!strcmp(key, "width")) { + double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.1 ); + tc->width = CLAMP(dval, -1000.0, 1000.0); + } else if (!strcmp(key, "mode")) { + gint64 const dval = ( val ? g_ascii_strtoll (val, NULL, 10) : 0 ); + tc->mode = dval; + sp_tweak_update_cursor(tc, tc->mode); + } else if (!strcmp(key, "fidelity")) { + double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0 ); + tc->fidelity = CLAMP(dval, 0.0, 1.0); + } else if (!strcmp(key, "force")) { + double const dval = ( val ? g_ascii_strtod (val, NULL) : 1.0 ); + tc->force = CLAMP(dval, 0, 1.0); + } else if (!strcmp(key, "usepressure")) { + tc->usepressure = (val && strcmp(val, "0")); + } +} + +static void +sp_tweak_extinput(SPTweakContext *tc, GdkEvent *event) +{ + if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &tc->pressure)) + tc->pressure = CLAMP (tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE); + else + tc->pressure = TC_DEFAULT_PRESSURE; +} + +double +get_dilate_radius (SPTweakContext *tc) +{ + // 10 times the pen width: + return 500 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom(); +} + +double +get_dilate_force (SPTweakContext *tc) +{ + double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE) + /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom()); + if (force > 3) { + force += 4 * (force - 3); + } + return force * tc->force; +} + +bool +sp_tweak_dilate_recursive (Inkscape::Selection *selection, SPItem *item, NR::Point p, NR::Point vector, gint mode, double radius, double force, double fidelity) +{ + bool did = false; + + if (SP_IS_GROUP(item)) { + for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (SP_IS_ITEM(child)) { + if (sp_tweak_dilate_recursive (selection, SP_ITEM(child), p, vector, mode, radius, force, fidelity)) + did = true; + } + } + + } else if (SP_IS_PATH(item) || SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + + Inkscape::XML::Node *newrepr = NULL; + gint pos = 0; + Inkscape::XML::Node *parent = NULL; + char const *id = NULL; + if (!SP_IS_PATH(item)) { + newrepr = sp_selected_item_to_curved_repr(item, 0); + if (!newrepr) + return false; + + // remember the position of the item + pos = SP_OBJECT_REPR(item)->position(); + // remember parent + parent = SP_OBJECT_REPR(item)->parent(); + // remember id + id = SP_OBJECT_REPR(item)->attribute("id"); + } + + + // skip those paths whose bboxes are entirely out of reach with our radius + NR::Maybe bbox = item->getBounds(sp_item_i2doc_affine(item)); + if (bbox) { + bbox->growBy(radius); + if (!bbox->contains(p)) { + return false; + } + } + + Path *orig = Path_for_item(item, false); + if (orig == NULL) { + return false; + } + Path *res = new Path; + res->SetBackData(false); + + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + orig->ConvertWithBackData(0.08 - (0.07 * fidelity)); // default 0.059 + orig->Fill(theShape, 0); + + SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(item), "style"); + gchar const *val = sp_repr_css_property(css, "fill-rule", NULL); + if (val && strcmp(val, "nonzero") == 0) + { + theRes->ConvertToShape(theShape, fill_nonZero); + } + else if (val && strcmp(val, "evenodd") == 0) + { + theRes->ConvertToShape(theShape, fill_oddEven); + } + else + { + theRes->ConvertToShape(theShape, fill_nonZero); + } + + if (NR::L2(vector) != 0) + vector = 1/NR::L2(vector) * vector; + + bool did_this = false; + NR::Matrix i2doc(sp_item_i2doc_affine(item)); + if (mode == TWEAK_MODE_SUCK || mode == TWEAK_MODE_BLOW) { + if (theShape->MakeOffset(theRes, + mode == TWEAK_MODE_BLOW? force : -force, + join_straight, 4.0, + true, p[NR::X], p[NR::Y], radius, &i2doc) == 0) // 0 means the shape was actually changed + did_this = true; + } else if (mode == TWEAK_MODE_PUSH) { + if (theShape->MakePush(theRes, + join_straight, 4.0, + true, p, force*2*vector, radius, &i2doc) == 0) + did_this = true; + } else if (mode == TWEAK_MODE_ROUGHEN) { + if (theShape->MakeJitter(theRes, + join_straight, 4.0, + true, p, force, radius, &i2doc) == 0) + did_this = true; + } + + // the rest only makes sense if we actually changed the path + if (did_this) { + theRes->ConvertToShape(theShape, fill_positive); + + res->Reset(); + theRes->ConvertToForme(res); + + double th_max = 0.6 - 0.59*sqrt(fidelity); + double threshold = MAX(th_max, th_max*force); + res->ConvertEvenLines(threshold); + res->Simplify(threshold); + + if (newrepr) { // converting to path, need to replace the repr + bool is_selected = selection->includes(item); + selection->remove(item); + + // It's going to resurrect, so we delete without notifying listeners. + SP_OBJECT(item)->deleteObject(false); + + // restore id + newrepr->setAttribute("id", id); + // add the new repr to the parent + parent->appendChild(newrepr); + // move to the saved position + newrepr->setPosition(pos > 0 ? pos : 0); + + if (is_selected) + selection->add(newrepr); + } + + if (res->descr_cmd.size() > 1) { + gchar *str = res->svg_dump_path(); + if (newrepr) + newrepr->setAttribute("d", str); + else + SP_OBJECT_REPR(item)->setAttribute("d", str); + g_free(str); + } else { + // TODO: if there's 0 or 1 node left, delete this path altogether + } + + if (newrepr) { + Inkscape::GC::release(newrepr); + newrepr = NULL; + } + } + + delete theShape; + delete theRes; + delete orig; + delete res; + + if (did_this) + did = true; + } + + return did; +} + + +bool +sp_tweak_dilate (SPTweakContext *tc, NR::Point p, NR::Point vector) +{ + Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop); + + if (selection->isEmpty()) { + return false; + } + + bool did = false; + double radius = get_dilate_radius(tc); + double force = get_dilate_force(tc); + if (radius == 0 || force == 0) { + return false; + } + + for (GSList *items = g_slist_copy((GSList *) selection->itemList()); + items != NULL; + items = items->next) { + + SPItem *item = (SPItem *) items->data; + + if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, force, tc->fidelity)) + did = true; + + } + + return did; +} + +void +sp_tweak_update_area (SPTweakContext *tc) +{ + double radius = get_dilate_radius(tc); + NR::Matrix const sm (NR::scale(radius, radius) * NR::translate(SP_EVENT_CONTEXT(tc)->desktop->point())); + sp_canvas_item_affine_absolute(tc->dilate_area, sm); + sp_canvas_item_show(tc->dilate_area); +} + +void +sp_tweak_switch_mode (SPTweakContext *tc, gint mode) +{ + SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode); + // need to set explicitly, because the prefs may not have changed by the previous + tc->mode = mode; + sp_tweak_update_cursor (tc, mode); +} + +void +sp_tweak_switch_mode_temporarily (SPTweakContext *tc, gint mode) +{ + // Juggling about so that prefs have the old value but tc->mode and the button show new mode: + gint now_mode = prefs_get_int_attribute("tools.tweak", "mode", 0); + SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode); + sp_tweak_update_cursor (tc, mode); + // button has changed prefs, restore + prefs_set_int_attribute("tools.tweak", "mode", now_mode); + // changing prefs changed tc->mode, restore back :) + tc->mode = mode; +} + +gint +sp_tweak_context_root_handler(SPEventContext *event_context, + GdkEvent *event) +{ + SPTweakContext *tc = SP_TWEAK_CONTEXT(event_context); + SPDesktop *desktop = event_context->desktop; + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !event_context->space_panning) { + + if (Inkscape::have_viable_layer(desktop, tc->_message_context) == false) { + return TRUE; + } + + NR::Point const button_w(event->button.x, + event->button.y); + NR::Point const button_dt(desktop->w2d(button_w)); + tc->last_push = desktop->dt2doc(button_dt); + + sp_tweak_extinput(tc, event); + + sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3); + tc->is_drawing = true; + tc->is_dilating = true; + tc->has_dilated = false; + + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + { + NR::Point const motion_w(event->motion.x, + event->motion.y); + NR::Point motion_dt(desktop->w2d(motion_w)); + NR::Point motion_doc(desktop->dt2doc(motion_dt)); + sp_tweak_extinput(tc, event); + + // draw the dilating cursor + double radius = get_dilate_radius(tc); + NR::Matrix const sm (NR::scale(radius, radius) * NR::translate(desktop->w2d(motion_w))); + sp_canvas_item_affine_absolute(tc->dilate_area, sm); + sp_canvas_item_show(tc->dilate_area); + + guint num = 0; + if (!desktop->selection->isEmpty()) { + num = g_slist_length((GSList *) desktop->selection->itemList()); + } + if (num == 0) { + tc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Select paths to tweak")); + } else { + switch (tc->mode) { + case TWEAK_MODE_PUSH: + tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, + _("Pushing %d selected path(s)"), num); + break; + case TWEAK_MODE_SUCK: + tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, + _("Melting %d selected path(s)"), num); + break; + case TWEAK_MODE_BLOW: + tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, + _("Blowing %d selected path(s)"), num); + break; + case TWEAK_MODE_ROUGHEN: + tc->_message_context->setF(Inkscape::NORMAL_MESSAGE, + _("Roughening %d selected path(s)"), num); + break; + } + } + + + // dilating: + if (tc->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) { + sp_tweak_dilate (tc, motion_doc, motion_doc - tc->last_push); + //tc->last_push = motion_doc; + tc->has_dilated = true; + // it's slow, so prevent clogging up with events + gobble_motion_events(GDK_BUTTON1_MASK); + return TRUE; + } + + } + break; + + + case GDK_BUTTON_RELEASE: + { + NR::Point const motion_w(event->button.x, event->button.y); + NR::Point const motion_dt(desktop->w2d(motion_w)); + + sp_canvas_end_forced_full_redraws(desktop->canvas); + tc->is_drawing = false; + + if (tc->is_dilating && event->button.button == 1 && !event_context->space_panning) { + if (!tc->has_dilated) { + // if we did not rub, do a light tap + tc->pressure = 0.03; + sp_tweak_dilate (tc, desktop->dt2doc(motion_dt), NR::Point(0,0)); + } + tc->is_dilating = false; + tc->has_dilated = false; + sp_document_done(sp_desktop_document(SP_EVENT_CONTEXT(tc)->desktop), + SP_VERB_CONTEXT_TWEAK, + (tc->mode==TWEAK_MODE_BLOW ? _("Blow tweak") : (tc->mode==TWEAK_MODE_SUCK ? _("Melt tweak") : (tc->mode==TWEAK_MODE_PUSH ? _("Push tweak") : _("Roughen tweak"))))); + ret = TRUE; + + } + break; + } + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_p: + case GDK_P: + case GDK_1: + if (MOD__SHIFT_ONLY) { + sp_tweak_switch_mode(tc, TWEAK_MODE_PUSH); + ret = TRUE; + } + break; + case GDK_m: + case GDK_M: + case GDK_2: + if (MOD__SHIFT_ONLY) { + sp_tweak_switch_mode(tc, TWEAK_MODE_SUCK); + ret = TRUE; + } + break; + case GDK_b: + case GDK_B: + case GDK_3: + if (MOD__SHIFT_ONLY) { + sp_tweak_switch_mode(tc, TWEAK_MODE_BLOW); + ret = TRUE; + } + break; + case GDK_r: + case GDK_R: + case GDK_4: + if (MOD__SHIFT_ONLY) { + sp_tweak_switch_mode(tc, TWEAK_MODE_ROUGHEN); + ret = TRUE; + } + break; + + case GDK_Up: + case GDK_KP_Up: + if (!MOD__CTRL_ONLY) { + tc->force += 0.05; + if (tc->force > 1.0) + tc->force = 1.0; + desktop->setToolboxAdjustmentValue ("tweak-force", tc->force * 100); + ret = TRUE; + } + break; + case GDK_Down: + case GDK_KP_Down: + if (!MOD__CTRL_ONLY) { + tc->force -= 0.05; + if (tc->force < 0.0) + tc->force = 0.0; + desktop->setToolboxAdjustmentValue ("tweak-force", tc->force * 100); + ret = TRUE; + } + break; + case GDK_Right: + case GDK_KP_Right: + if (!MOD__CTRL_ONLY) { + tc->width += 0.01; + if (tc->width > 1.0) + tc->width = 1.0; + desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100); // the same spinbutton is for alt+x + sp_tweak_update_area(tc); + ret = TRUE; + } + break; + case GDK_Left: + case GDK_KP_Left: + if (!MOD__CTRL_ONLY) { + tc->width -= 0.01; + if (tc->width < 0.01) + tc->width = 0.01; + desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100); + sp_tweak_update_area(tc); + ret = TRUE; + } + break; + case GDK_Home: + case GDK_KP_Home: + tc->width = 0.01; + desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100); + sp_tweak_update_area(tc); + ret = TRUE; + break; + case GDK_End: + case GDK_KP_End: + tc->width = 1.0; + desktop->setToolboxAdjustmentValue ("altx-tweak", tc->width * 100); + sp_tweak_update_area(tc); + ret = TRUE; + break; + case GDK_x: + case GDK_X: + if (MOD__ALT_ONLY) { + desktop->setToolboxFocusTo ("altx-tweak"); + ret = TRUE; + } + break; + + case GDK_Control_L: + case GDK_Control_R: + if (MOD__SHIFT) { + sp_tweak_switch_mode_temporarily(tc, TWEAK_MODE_BLOW); + } else { + sp_tweak_switch_mode_temporarily(tc, TWEAK_MODE_SUCK); + } + break; + case GDK_Shift_L: + case GDK_Shift_R: + if (MOD__CTRL) { + sp_tweak_switch_mode_temporarily(tc, TWEAK_MODE_BLOW); + } + break; + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (get_group0_keyval(&event->key)) { + case GDK_Control_L: + case GDK_Control_R: + sp_tweak_switch_mode (tc, prefs_get_int_attribute("tools.tweak", "mode", 0)); + tc->_message_context->clear(); + break; + case GDK_Shift_L: + case GDK_Shift_R: + if (MOD__CTRL) { + sp_tweak_switch_mode_temporarily(tc, TWEAK_MODE_SUCK); + } + break; + break; + default: + sp_tweak_switch_mode (tc, prefs_get_int_attribute("tools.tweak", "mode", 0)); + break; + } + + default: + break; + } + + if (!ret) { + if (((SPEventContextClass *) parent_class)->root_handler) { + ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); + } + } + + return ret; +} + + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/tweak-context.h b/src/tweak-context.h new file mode 100644 index 000000000..14297b5a2 --- /dev/null +++ b/src/tweak-context.h @@ -0,0 +1,88 @@ +#ifndef __SP_TWEAK_CONTEXT_H__ +#define __SP_TWEAK_CONTEXT_H__ + +/* + * tweaking paths without node editing + * + * Authors: + * bulia byak + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display/curve.h" +#include "event-context.h" +#include +#include + +#define SP_TYPE_TWEAK_CONTEXT (sp_tweak_context_get_type()) +#define SP_TWEAK_CONTEXT(o) (GTK_CHECK_CAST((o), SP_TYPE_TWEAK_CONTEXT, SPTweakContext)) +#define SP_TWEAK_CONTEXT_CLASS(k) (GTK_CHECK_CLASS_CAST((k), SP_TYPE_TWEAK_CONTEXT, SPTweakContextClass)) +#define SP_IS_TWEAK_CONTEXT(o) (GTK_CHECK_TYPE((o), SP_TYPE_TWEAK_CONTEXT)) +#define SP_IS_TWEAK_CONTEXT_CLASS(k) (GTK_CHECK_CLASS_TYPE((k), SP_TYPE_TWEAK_CONTEXT)) + +class SPTweakContext; +class SPTweakContextClass; + +#define SAMPLING_SIZE 8 /* fixme: ?? */ + +#define TC_MIN_PRESSURE 0.0 +#define TC_MAX_PRESSURE 1.0 +#define TC_DEFAULT_PRESSURE 0.4 + +enum { + TWEAK_MODE_PUSH, + TWEAK_MODE_SUCK, + TWEAK_MODE_BLOW, + TWEAK_MODE_ROUGHEN +}; + +struct SPTweakContext +{ + SPEventContext event_context; + + /* extended input data */ + gdouble pressure; + + /* attributes */ + guint dragging : 1; /* mouse state: mouse is dragging */ + guint usepressure : 1; + guint usetilt : 1; + + double width; + double force; + double fidelity; + + gint mode; + + Inkscape::MessageContext *_message_context; + + bool is_drawing; + + bool is_dilating; + bool has_dilated; + NR::Point last_push; + SPCanvasItem *dilate_area; +}; + +struct SPTweakContextClass +{ + SPEventContextClass parent_class; +}; + +GtkType sp_tweak_context_get_type(void); + +#endif + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index c1999f946..ea699e98f 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -40,6 +40,7 @@ enum { PREFS_PAGE_TOOLS, PREFS_PAGE_TOOLS_SELECTOR, PREFS_PAGE_TOOLS_NODE, + PREFS_PAGE_TOOLS_TWEAK, PREFS_PAGE_TOOLS_ZOOM, PREFS_PAGE_TOOLS_SHAPES, PREFS_PAGE_TOOLS_SHAPES_RECT, diff --git a/src/verbs.cpp b/src/verbs.cpp index 897f11369..dc9ebc38e 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -1398,6 +1398,9 @@ ContextVerb::perform(SPAction *action, void *data, void *pdata) case SP_VERB_CONTEXT_NODE: tools_switch_current(TOOLS_NODES); break; + case SP_VERB_CONTEXT_TWEAK: + tools_switch_current(TOOLS_TWEAK); + break; case SP_VERB_CONTEXT_RECT: tools_switch_current(TOOLS_SHAPES_RECT); break; @@ -1449,6 +1452,10 @@ ContextVerb::perform(SPAction *action, void *data, void *pdata) prefs_set_int_attribute("dialogs.preferences", "page", PREFS_PAGE_TOOLS_NODE); dt->_dlg_mgr->showDialog("InkscapePreferences"); break; + case SP_VERB_CONTEXT_TWEAK_PREFS: + prefs_set_int_attribute("dialogs.preferences", "page", PREFS_PAGE_TOOLS_TWEAK); + dt->_dlg_mgr->showDialog("InkscapePreferences"); + break; case SP_VERB_CONTEXT_RECT_PREFS: prefs_set_int_attribute("dialogs.preferences", "page", PREFS_PAGE_TOOLS_SHAPES_RECT); dt->_dlg_mgr->showDialog("InkscapePreferences"); @@ -2367,6 +2374,8 @@ Verb *Verb::_base_verbs[] = { N_("Select and transform objects"), "draw_select"), new ContextVerb(SP_VERB_CONTEXT_NODE, "ToolNode", N_("Node Edit"), N_("Edit path nodes or control handles"), "draw_node"), + new ContextVerb(SP_VERB_CONTEXT_TWEAK, "ToolTweak", N_("Tweak"), + N_("Tweak paths by pushing, melting, blowing, or roughening"), "draw_tweak"), new ContextVerb(SP_VERB_CONTEXT_RECT, "ToolRect", N_("Rectangle"), N_("Create rectangles and squares"), "draw_rect"), new ContextVerb(SP_VERB_CONTEXT_3DBOX, "Tool3DBox", N_("3D Box"), @@ -2401,6 +2410,8 @@ Verb *Verb::_base_verbs[] = { N_("Open Preferences for the Selector tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_NODE_PREFS, "NodePrefs", N_("Node Tool Preferences"), N_("Open Preferences for the Node tool"), NULL), + new ContextVerb(SP_VERB_CONTEXT_TWEAK_PREFS, "TweakPrefs", N_("Tweak Tool Preferences"), + N_("Open Preferences for the Tweak tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_RECT_PREFS, "RectPrefs", N_("Rectangle Preferences"), N_("Open Preferences for the Rectangle tool"), NULL), new ContextVerb(SP_VERB_CONTEXT_3DBOX_PREFS, "3DBoxPrefs", N_("3D Box Preferences"), diff --git a/src/verbs.h b/src/verbs.h index ec21ac068..ebe5e2fed 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -137,6 +137,7 @@ enum { /* Tools */ SP_VERB_CONTEXT_SELECT, SP_VERB_CONTEXT_NODE, + SP_VERB_CONTEXT_TWEAK, SP_VERB_CONTEXT_RECT, SP_VERB_CONTEXT_3DBOX, SP_VERB_CONTEXT_ARC, @@ -154,6 +155,7 @@ enum { /* Tool preferences */ SP_VERB_CONTEXT_SELECT_PREFS, SP_VERB_CONTEXT_NODE_PREFS, + SP_VERB_CONTEXT_TWEAK_PREFS, SP_VERB_CONTEXT_RECT_PREFS, SP_VERB_CONTEXT_3DBOX_PREFS, SP_VERB_CONTEXT_ARC_PREFS, diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index b0c8a9b7b..95decde95 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -105,6 +105,7 @@ typedef void (*SetupFunction)(GtkWidget *toolbox, SPDesktop *desktop); typedef void (*UpdateFunction)(SPDesktop *desktop, SPEventContext *eventcontext, GtkWidget *toolbox); static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); +static void sp_tweak_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_zoom_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_star_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); static void sp_arc_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder); @@ -130,6 +131,7 @@ static struct { } const tools[] = { { "SPSelectContext", "select_tool", SP_VERB_CONTEXT_SELECT, SP_VERB_CONTEXT_SELECT_PREFS}, { "SPNodeContext", "node_tool", SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_NODE_PREFS }, + { "SPTweakContext", "tweak_tool", SP_VERB_CONTEXT_TWEAK, SP_VERB_CONTEXT_TWEAK_PREFS }, { "SPZoomContext", "zoom_tool", SP_VERB_CONTEXT_ZOOM, SP_VERB_CONTEXT_ZOOM_PREFS }, { "SPRectContext", "rect_tool", SP_VERB_CONTEXT_RECT, SP_VERB_CONTEXT_RECT_PREFS }, // { "SP3DBoxContext", "3dbox_tool", SP_VERB_CONTEXT_3DBOX, SP_VERB_CONTEXT_3DBOX_PREFS }, @@ -161,6 +163,8 @@ static struct { SP_VERB_INVALID, 0, 0}, { "SPNodeContext", "node_toolbox", 0, sp_node_toolbox_prep, "NodeToolbar", SP_VERB_INVALID, 0, 0}, + { "SPTweakContext", "tweak_toolbox", 0, sp_tweak_toolbox_prep, "TweakToolbar", + SP_VERB_INVALID, 0, 0}, { "SPZoomContext", "zoom_toolbox", 0, sp_zoom_toolbox_prep, "ZoomToolbar", SP_VERB_INVALID, 0, 0}, { "SPStarContext", "star_toolbox", 0, sp_star_toolbox_prep, "StarToolbar", @@ -241,6 +245,16 @@ static gchar const * ui_descr = " " " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " " " " " @@ -2513,6 +2527,148 @@ static void sp_pencil_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActio // Put stuff here } +//######################## +//## Tweak ## +//######################## + +static void sp_tweak_width_value_changed( GtkAdjustment *adj, GObject *tbl ) +{ + prefs_set_double_attribute( "tools.tweak", "width", adj->value * 0.01 ); +} + +static void sp_tweak_force_value_changed( GtkAdjustment *adj, GObject *tbl ) +{ + prefs_set_double_attribute( "tools.tweak", "force", adj->value * 0.01 ); +} + +static void sp_tweak_pressure_state_changed( GtkToggleAction *act, gpointer data ) +{ + prefs_set_int_attribute( "tools.tweak", "usepressure", gtk_toggle_action_get_active( act ) ? 1 : 0); +} + +static void sp_tweak_mode_changed( EgeSelectOneAction *act, GObject *tbl ) +{ + prefs_set_int_attribute("tools.tweak", "mode", ege_select_one_action_get_active( act )); +} + +static void sp_tweak_fidelity_value_changed( GtkAdjustment *adj, GObject *tbl ) +{ + prefs_set_double_attribute( "tools.tweak", "fidelity", adj->value * 0.01 ); +} + + +static void sp_tweak_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder) +{ + { + /* Width */ + gchar const* labels[] = {_("(pinch tweak)"), 0, 0, 0, _("(default)"), 0, 0, 0, 0, _("(broad tweak)")}; + gdouble values[] = {1, 3, 5, 10, 15, 20, 30, 50, 75, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "TweakWidthAction", + _("Width:"), _("The width of the tweak area (relative to the visible canvas area)"), + "tools.tweak", "width", 15, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "altx-tweak", + 1, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_tweak_width_value_changed, 0.01, 0, 100 ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + + { + /* Force */ + gchar const* labels[] = {_("(minimum force)"), 0, 0, _("(default)"), 0, 0, 0, _("(maximum force)")}; + gdouble values[] = {1, 5, 10, 20, 30, 50, 70, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "TweakForceAction", + _("Force:"), _("The force of the tweak action"), + "tools.tweak", "force", 20, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "tweak-force", + 1, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_tweak_force_value_changed, 0.01, 0, 100 ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + /* Mode */ + { + GtkListStore* model = gtk_list_store_new( 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING ); + + GtkTreeIter iter; + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Push mode"), + 1, _("Switch to Push mode"), + 2, "tweak_push_mode", + -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Melt mode"), + 1, _("Switch to Melt mode"), + 2, "tweak_suck_mode", + -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Blow mode"), + 1, _("Switch to Blow mode"), + 2, "tweak_blow_mode", + -1 ); + + gtk_list_store_append( model, &iter ); + gtk_list_store_set( model, &iter, + 0, _("Roughen mode"), + 1, _("Switch to Roughen mode"), + 2, "tweak_roughen_mode", + -1 ); + + EgeSelectOneAction* act = ege_select_one_action_new( "TweakModeAction", _(""), _(""), NULL, GTK_TREE_MODEL(model) ); + gtk_action_group_add_action( mainActions, GTK_ACTION(act) ); + g_object_set_data( holder, "mode_action", act ); + + ege_select_one_action_set_appearance( act, "full" ); + ege_select_one_action_set_radio_action_type( act, INK_RADIO_ACTION_TYPE ); + g_object_set( G_OBJECT(act), "icon-property", "iconId", NULL ); + ege_select_one_action_set_icon_column( act, 2 ); + ege_select_one_action_set_tooltip_column( act, 1 ); + + gint mode = prefs_get_int_attribute("tools.tweak", "mode", 0); + ege_select_one_action_set_active( act, mode ); + g_signal_connect_after( G_OBJECT(act), "changed", G_CALLBACK(sp_tweak_mode_changed), holder ); + + g_object_set_data( G_OBJECT(holder), "tweak_tool_mode", act); + } + + { /* Fidelity */ + gchar const* labels[] = {_("(rough, simplified)"), 0, 0, _("(default)"), 0, 0, _("(fine, but many nodes)")}; + gdouble values[] = {10, 25, 35, 50, 60, 80, 100}; + EgeAdjustmentAction *eact = create_adjustment_action( "TweakFidelityAction", + _("Fidelity:"), _("Low fidelity simplifies paths; high fidelity preserves path features but may generate a lot of new nodes"), + "tools.tweak", "fidelity", 50, + GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "tweak-fidelity", + 1, 100, 1.0, 10.0, + labels, values, G_N_ELEMENTS(labels), + sp_tweak_fidelity_value_changed, 0.01, 0, 100 ); + gtk_action_group_add_action( mainActions, GTK_ACTION(eact) ); + gtk_action_set_sensitive( GTK_ACTION(eact), TRUE ); + } + + + /* Use Pressure button */ + { + InkToggleAction* act = ink_toggle_action_new( "TweakPressureAction", + _("Pressure"), + _("Use the pressure of the input device to alter the width of the area"), + "use_pressure", + Inkscape::ICON_SIZE_DECORATION ); + gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); + g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_tweak_pressure_state_changed), NULL); + gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs_get_int_attribute( "tools.tweak", "usepressure", 1 ) ); + } + +} + //######################## //## Calligraphy ## @@ -2772,7 +2928,7 @@ static void sp_calligraphy_toolbox_prep(SPDesktop *desktop, GtkActionGroup* main { GtkAction* act = gtk_action_new( "CalligraphyResetAction", _("Defaults"), - _("Reset shape parameters to defaults (use Inkscape Preferences > Tools to change defaults)"), + _("Reset all parameters to defaults"), GTK_STOCK_CLEAR ); g_signal_connect_after( G_OBJECT(act), "activate", G_CALLBACK(sp_ddc_defaults), holder ); gtk_action_group_add_action( mainActions, act ); -- 2.30.2