freehand-base.cpp revision 523c67aa88378b56e7d995ce820cf9a49d3c4c3c
/*
* Generic drawing context
*
* Author:
* Lauris Kaplinski <lauris@kaplinski.com>
* Abhishek Sharma
* Jon A. Cruz <jon@joncruz.org>
*
* Copyright (C) 2000 Lauris Kaplinski
* Copyright (C) 2000-2001 Ximian, Inc.
* Copyright (C) 2002 Lauris Kaplinski
* Copyright (C) 2012 Johan Engelen
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#define DRAW_VERBOSE
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "live_effects/lpe-patternalongpath.h"
#include "display/canvas-bpath.h"
#include "xml/repr.h"
#include "svg/svg.h"
#include <glibmm/i18n.h>
#include "display/curve.h"
#include "desktop.h"
#include "desktop-handles.h"
#include "desktop-style.h"
#include "document.h"
#include "draw-anchor.h"
#include "macros.h"
#include "message-stack.h"
#include "ui/tools/pen-tool.h"
#include "ui/tools/lpe-tool.h"
#include "preferences.h"
#include "selection.h"
#include "selection-chemistry.h"
#include "snap.h"
#include "sp-path.h"
#include "sp-namedview.h"
#include "live_effects/lpe-powerstroke.h"
#include "style.h"
#include "ui/control-manager.h"
#include "ui/tools/freehand-base.h"
#include <gdk/gdkkeysyms.h>
using Inkscape::DocumentUndo;
namespace Inkscape {
namespace UI {
namespace Tools {
static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc);
static void spdc_selection_modified(Inkscape::Selection *sel, guint flags, FreehandBase *dc);
static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection *sel);
/**
* Flushes white curve(s) and additional curve into object.
*
* No cleaning of colored curves - this has to be done by caller
* No rereading of white data, so if you cannot rely on ::modified, do it in caller
*/
static void spdc_flush_white(FreehandBase *dc, SPCurve *gc);
static void spdc_reset_white(FreehandBase *dc);
static void spdc_free_colors(FreehandBase *dc);
FreehandBase::FreehandBase(gchar const *const *cursor_shape, gint hot_x, gint hot_y)
: ToolBase(cursor_shape, hot_x, hot_y)
, selection(NULL)
, grab(NULL)
, attach(false)
, red_color(0xff00007f)
, blue_color(0x0000ff7f)
, green_color(0x00ff007f)
, red_bpath(NULL)
, red_curve(NULL)
, blue_bpath(NULL)
, blue_curve(NULL)
, blue2_bpath(NULL)
, blue2_curve(NULL)
, green_bpaths(NULL)
, green_curve(NULL)
, green_anchor(NULL)
, green_closed(false)
, white_item(NULL)
, white_curves(NULL)
, white_anchors(NULL)
, sa(NULL)
, ea(NULL)
, waiting_LPE_type(Inkscape::LivePathEffect::INVALID_LPE)
, red_curve_is_valid(false)
, anchor_statusbar(false)
{
}
FreehandBase::~FreehandBase() {
if (this->grab) {
sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME);
this->grab = NULL;
}
if (this->selection) {
this->selection = NULL;
}
spdc_free_colors(this);
}
void FreehandBase::setup() {
ToolBase::setup();
this->selection = sp_desktop_selection(desktop);
// Connect signals to track selection changes
this->sel_changed_connection = this->selection->connectChanged(
sigc::bind(sigc::ptr_fun(&spdc_selection_changed), this)
);
this->sel_modified_connection = this->selection->connectModified(
sigc::bind(sigc::ptr_fun(&spdc_selection_modified), this)
);
// Create red bpath
this->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL);
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
// Create red curve
this->red_curve = new SPCurve();
// Create blue bpath
this->blue_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL);
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->blue_bpath), this->blue_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
// Create blue curve
this->blue_curve = new SPCurve();
// Create blue2 bpath
this->blue2_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL);
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->blue2_bpath), this->blue_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
// Create blue2 curve
this->blue2_curve = new SPCurve();
// Create green curve
this->green_curve = new SPCurve();
// No green anchor by default
this->green_anchor = NULL;
this->green_closed = FALSE;
this->attach = TRUE;
spdc_attach_selection(this, this->selection);
}
void FreehandBase::finish() {
this->sel_changed_connection.disconnect();
this->sel_modified_connection.disconnect();
if (this->grab) {
sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME);
}
if (this->selection) {
this->selection = NULL;
}
spdc_free_colors(this);
ToolBase::finish();
}
void FreehandBase::set(const Inkscape::Preferences::Entry& /*value*/) {
}
bool FreehandBase::root_handler(GdkEvent* event) {
gint ret = FALSE;
switch (event->type) {
case GDK_KEY_PRESS:
switch (get_group0_keyval (&event->key)) {
case GDK_KEY_Up:
case GDK_KEY_Down:
case GDK_KEY_KP_Up:
case GDK_KEY_KP_Down:
// prevent the zoom field from activation
if (!MOD__CTRL_ONLY(event)) {
ret = TRUE;
}
break;
default:
break;
}
break;
default:
break;
}
if (!ret) {
ret = ToolBase::root_handler(event);
}
return ret;
}
static Glib::ustring const tool_name(FreehandBase *dc)
{
return ( SP_IS_PEN_CONTEXT(dc)
? "/tools/freehand/pen"
: "/tools/freehand/pencil" );
}
static void spdc_paste_curve_as_freehand_shape(const SPCurve *c, FreehandBase *dc, SPItem *item)
{
using namespace Inkscape::LivePathEffect;
// TODO: Don't paste path if nothing is on the clipboard
Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item);
Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
gchar *svgd = sp_svg_write_path(c->get_pathvector());
static_cast<LPEPatternAlongPath*>(lpe)->pattern.paste_param_path(svgd);
}
static void spdc_apply_powerstroke_shape(const std::vector<Geom::Point> & points, FreehandBase *dc, SPItem *item)
{
using namespace Inkscape::LivePathEffect;
Effect::createAndApply(POWERSTROKE, dc->desktop->doc(), item);
Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
static_cast<LPEPowerStroke*>(lpe)->offset_points.param_set_and_write_new_value(points);
// write powerstroke parameters:
lpe->getRepr()->setAttribute("start_linecap_type", "zerowidth");
lpe->getRepr()->setAttribute("end_linecap_type", "zerowidth");
lpe->getRepr()->setAttribute("cusp_linecap_type", "round");
lpe->getRepr()->setAttribute("sort_points", "true");
lpe->getRepr()->setAttribute("interpolator_type", "CubicBezierJohan");
lpe->getRepr()->setAttribute("interpolator_beta", "0.2");
}
static void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item, SPCurve *curve)
{
using namespace Inkscape::LivePathEffect;
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
if (item && SP_IS_LPE_ITEM(item)) {
if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1) {
Effect::createAndApply(SPIRO, dc->desktop->doc(), item);
}
//add the bspline node in the waiting effects
if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2) {
Effect::createAndApply(BSPLINE, dc->desktop->doc(), item);
}
int shape = prefs->getInt(tool_name(dc) + "/shape", 0);
bool shape_applied = false;
SPCSSAttr *css_item = sp_css_attr_from_object(item, SP_STYLE_FLAG_ALWAYS);
const char *cstroke = sp_repr_css_property(css_item, "stroke", "none");
#define SHAPE_LENGTH 10
#define SHAPE_HEIGHT 10
switch (shape) {
case 0:
// don't apply any shape
break;
case 1:
{
// "triangle in"
std::vector<Geom::Point> points(1);
points[0] = Geom::Point(0., SHAPE_HEIGHT/2);
spdc_apply_powerstroke_shape(points, dc, item);
shape_applied = true;
break;
}
case 2:
{
// "triangle out"
guint curve_length = curve->get_segment_count();
std::vector<Geom::Point> points(1);
points[0] = Geom::Point((double)curve_length, SHAPE_HEIGHT/2);
spdc_apply_powerstroke_shape(points, dc, item);
shape_applied = true;
break;
}
case 3:
{
// "ellipse"
SPCurve *c = new SPCurve();
const double C1 = 0.552;
c->moveto(0, SHAPE_HEIGHT/2);
c->curveto(0, (1 - C1) * SHAPE_HEIGHT/2, (1 - C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH/2, 0);
c->curveto((1 + C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH, (1 - C1) * SHAPE_HEIGHT/2, SHAPE_LENGTH, SHAPE_HEIGHT/2);
c->curveto(SHAPE_LENGTH, (1 + C1) * SHAPE_HEIGHT/2, (1 + C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, SHAPE_LENGTH/2, SHAPE_HEIGHT);
c->curveto((1 - C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, 0, (1 + C1) * SHAPE_HEIGHT/2, 0, SHAPE_HEIGHT/2);
c->closepath();
spdc_paste_curve_as_freehand_shape(c, dc, item);
c->unref();
shape_applied = true;
break;
}
case 4:
{
// take shape from clipboard; TODO: catch the case where clipboard is empty
Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item);
Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
static_cast<LPEPatternAlongPath*>(lpe)->pattern.on_paste_button_click();
shape_applied = true;
break;
}
default:
break;
}
if (shape_applied) {
// apply original stroke color as fill and unset stroke; then return
SPCSSAttr *css = sp_repr_css_attr_new();
if (!strcmp(cstroke, "none")){
sp_repr_css_set_property (css, "fill", "black");
} else {
sp_repr_css_set_property (css, "fill", cstroke);
}
sp_repr_css_set_property (css, "stroke", "none");
sp_desktop_apply_css_recursive(item, css, true);
sp_repr_css_attr_unref(css);
return;
}
if (dc->waiting_LPE_type != INVALID_LPE) {
Effect::createAndApply(dc->waiting_LPE_type, dc->desktop->doc(), item);
dc->waiting_LPE_type = INVALID_LPE;
if (SP_IS_LPETOOL_CONTEXT(dc)) {
// since a geometric LPE was applied, we switch back to "inactive" mode
lpetool_context_switch_mode(SP_LPETOOL_CONTEXT(dc), INVALID_LPE);
}
}
if (SP_IS_PEN_CONTEXT(dc)) {
SP_PEN_CONTEXT(dc)->setPolylineMode();
}
}
}
/*
* Selection handlers
*/
static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc)
{
if (dc->attach) {
spdc_attach_selection(dc, sel);
}
}
/* fixme: We have to ensure this is not delayed (Lauris) */
static void spdc_selection_modified(Inkscape::Selection *sel, guint /*flags*/, FreehandBase *dc)
{
if (dc->attach) {
spdc_attach_selection(dc, sel);
}
}
static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection */*sel*/)
{
// We reset white and forget white/start/end anchors
spdc_reset_white(dc);
dc->sa = NULL;
dc->ea = NULL;
SPItem *item = dc->selection ? dc->selection->singleItem() : NULL;
if ( item && SP_IS_PATH(item) ) {
// Create new white data
// Item
dc->white_item = item;
// Curve list
// We keep it in desktop coordinates to eliminate calculation errors
SPCurve *norm = SP_PATH(item)->get_curve_for_edit();
norm->transform((dc->white_item)->i2dt_affine());
g_return_if_fail( norm != NULL );
dc->white_curves = g_slist_reverse(norm->split());
norm->unref();
// Anchor list
for (GSList *l = dc->white_curves; l != NULL; l = l->next) {
SPCurve *c;
c = static_cast<SPCurve*>(l->data);
g_return_if_fail( c->get_segment_count() > 0 );
if ( !c->is_closed() ) {
SPDrawAnchor *a;
a = sp_draw_anchor_new(dc, c, TRUE, *(c->first_point()));
if (a)
dc->white_anchors = g_slist_prepend(dc->white_anchors, a);
a = sp_draw_anchor_new(dc, c, FALSE, *(c->last_point()));
if (a)
dc->white_anchors = g_slist_prepend(dc->white_anchors, a);
}
}
// fixme: recalculate active anchor?
}
}
void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o,
guint state)
{
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12));
SnapManager &m = ec->desktop->namedview->snap_manager;
m.setup(ec->desktop);
bool snap_enabled = m.snapprefs.getSnapEnabledGlobally();
if (state & GDK_SHIFT_MASK) {
// SHIFT disables all snapping, except the angular snapping. After all, the user explicitly asked for angular
// snapping by pressing CTRL, otherwise we wouldn't have arrived here. But although we temporarily disable
// the snapping here, we must still call for a constrained snap in order to apply the constraints (i.e. round
// to the nearest angle increment)
m.snapprefs.setSnapEnabledGlobally(false);
}
Inkscape::SnappedPoint dummy = m.constrainedAngularSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE), boost::optional<Geom::Point>(), o, snaps);
p = dummy.getPoint();
if (state & GDK_SHIFT_MASK) {
m.snapprefs.setSnapEnabledGlobally(snap_enabled); // restore the original setting
}
m.unSetup();
}
void spdc_endpoint_snap_free(ToolBase const * const ec, Geom::Point& p, boost::optional<Geom::Point> &start_of_line, guint const /*state*/)
{
SPDesktop *dt = ec->desktop;
SnapManager &m = dt->namedview->snap_manager;
Inkscape::Selection *selection = sp_desktop_selection (dt);
// selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping)
// TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment
m.setup(dt, true, selection->singleItem());
Inkscape::SnapCandidatePoint scp(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
if (start_of_line) {
scp.addOrigin(*start_of_line);
}
Inkscape::SnappedPoint sp = m.freeSnap(scp);
p = sp.getPoint();
m.unSetup();
}
static SPCurve *reverse_then_unref(SPCurve *orig)
{
SPCurve *ret = orig->create_reverse();
orig->unref();
return ret;
}
void spdc_concat_colors_and_flush(FreehandBase *dc, gboolean forceclosed)
{
// Concat RBG
SPCurve *c = dc->green_curve;
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
// Green
dc->green_curve = new SPCurve();
while (dc->green_bpaths) {
sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data));
dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data);
}
// Blue
c->append_continuous(dc->blue_curve, 0.0625);
dc->blue_curve->reset();
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->blue_bpath), NULL);
// Red
if (dc->red_curve_is_valid) {
c->append_continuous(dc->red_curve, 0.0625);
}
dc->red_curve->reset();
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->red_bpath), NULL);
// Blue2
dc->blue2_curve->reset();
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->blue2_bpath), NULL);
/* if c is empty, it might be that the user was trying to continue an existing curve and cancelled.
if this is the case and we are in bspline or spirolive the previous curve needs to be selected again because
we modify it when continuing through an anchor. */
if (c->is_empty()) {
if(prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1 || prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2){
SPDesktop *desktop = dc->desktop;
spdc_selection_modified(sp_desktop_selection(desktop), 0, dc);
}
c->unref();
return;
}
// Step A - test, whether we ended on green anchor
if ( forceclosed || ( dc->green_anchor && dc->green_anchor->active ) ) {
// We hit green anchor, closing Green-Blue-Red
dc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed."));
c->closepath_current();
// Closed path, just flush
spdc_flush_white(dc, c);
c->unref();
return;
}
// Step B - both start and end anchored to same curve
if ( dc->sa && dc->ea
&& ( dc->sa->curve == dc->ea->curve )
&& ( ( dc->sa != dc->ea )
|| dc->sa->curve->is_closed() ) )
{
// We hit bot start and end of single curve, closing paths
dc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Closing path."));
/* if we are in bspline or spirolive mode, the continuation and ending curve are updated when continuing or ending the curve in an anchor.
this causes that the original function doesn't detect if it's the same curve in case the curves have multiples parts -shift- and
close incorrectly one of the parts */
if(prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1 ||
prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2){
if (dc->sa->start && !(dc->sa->curve->is_closed()) ) {
dc->sa->curve = reverse_then_unref(dc->sa->curve);
}
dc->sa->curve->append_continuous(c, 0.0625);
c->unref();
if(Geom::are_near(dc->sa->curve->first_path()->initialPoint(), dc->ea->dp)){
dc->sa->curve->closepath_current();
}
}else{
if (dc->sa->start && !(dc->sa->curve->is_closed()) ) {
c = reverse_then_unref(c);
}
dc->sa->curve->append_continuous(c, 0.0625);
c->unref();
dc->sa->curve->closepath_current();
}
//if the curve has an bspline or spiro LPE, we execute spdc_flush_white, passing the necessary starting curve.
if(prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1 ||
prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2){
dc->white_curves = g_slist_remove(dc->white_curves, dc->sa->curve);
spdc_flush_white(dc, dc->sa->curve);
}else{
spdc_flush_white(dc, NULL);
}
return;
}
// Step C - test start
if (dc->sa) {
SPCurve *s = dc->sa->curve;
dc->white_curves = g_slist_remove(dc->white_curves, s);
if (dc->sa->start) {
s = reverse_then_unref(s);
}
s->append_continuous(c, 0.0625);
c->unref();
c = s;
} else /* Step D - test end */ if (dc->ea) {
SPCurve *e = dc->ea->curve;
dc->white_curves = g_slist_remove(dc->white_curves, e);
if (!dc->ea->start) {
e = reverse_then_unref(e);
}
c->append_continuous(e, 0.0625);
e->unref();
}
spdc_flush_white(dc, c);
c->unref();
}
static void spdc_flush_white(FreehandBase *dc, SPCurve *gc)
{
SPCurve *c;
if (dc->white_curves) {
g_assert(dc->white_item);
c = SPCurve::concat(dc->white_curves);
g_slist_free(dc->white_curves);
dc->white_curves = NULL;
if (gc) {
c->append(gc, FALSE);
}
} else if (gc) {
c = gc;
c->ref();
} else {
return;
}
// Now we have to go back to item coordinates at last
c->transform( dc->white_item
? (dc->white_item)->dt2i_affine()
: dc->desktop->dt2doc() );
SPDesktop *desktop = dc->desktop;
SPDocument *doc = sp_desktop_document(desktop);
Inkscape::XML::Document *xml_doc = doc->getReprDoc();
if ( c && !c->is_empty() ) {
// We actually have something to write
bool has_lpe = false;
Inkscape::XML::Node *repr;
if (dc->white_item) {
repr = dc->white_item->getRepr();
has_lpe = SP_LPE_ITEM(dc->white_item)->hasPathEffectRecursive();
} else {
repr = xml_doc->createElement("svg:path");
// Set style
sp_desktop_apply_style_tool(desktop, repr, tool_name(dc).data(), false);
}
gchar *str = sp_svg_write_path( c->get_pathvector() );
g_assert( str != NULL );
if (has_lpe)
repr->setAttribute("inkscape:original-d", str);
else
repr->setAttribute("d", str);
g_free(str);
if (!dc->white_item) {
// Attach repr
SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
// we finished the path; now apply any waiting LPEs or freehand shapes
spdc_check_for_and_apply_waiting_LPE(dc, item, c);
dc->selection->set(repr);
Inkscape::GC::release(repr);
item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
item->doWriteTransform(item->getRepr(), item->transform, NULL, true);
item->updateRepr();
}
DocumentUndo::done(doc, SP_IS_PEN_CONTEXT(dc)? SP_VERB_CONTEXT_PEN : SP_VERB_CONTEXT_PENCIL,
_("Draw path"));
// When quickly drawing several subpaths with Shift, the next subpath may be finished and
// flushed before the selection_modified signal is fired by the previous change, which
// results in the tool losing all of the selected path's curve except that last subpath. To
// fix this, we force the selection_modified callback now, to make sure the tool's curve is
// in sync immediately.
spdc_selection_modified(sp_desktop_selection(desktop), 0, dc);
}
c->unref();
// Flush pending updates
doc->ensureUpToDate();
}
SPDrawAnchor *spdc_test_inside(FreehandBase *dc, Geom::Point p)
{
SPDrawAnchor *active = NULL;
// Test green anchor
if (dc->green_anchor) {
active = sp_draw_anchor_test(dc->green_anchor, p, TRUE);
}
for (GSList *l = dc->white_anchors; l != NULL; l = l->next) {
SPDrawAnchor *na = sp_draw_anchor_test(static_cast<SPDrawAnchor*>(l->data), p, !active);
if ( !active && na ) {
active = na;
}
}
/* modify the anchoring curve so it is equal to the starting curve.
this curve is modified when it's modified and we need them to be equal to the closing curve */
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
if((prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1 ||
prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2) &&
dc->sa && !dc->red_curve->is_empty() && !dc->green_anchor){
if(active){
active->curve = dc->sa->curve;
active->curve->ref();
}
}
return active;
}
static void spdc_reset_white(FreehandBase *dc)
{
if (dc->white_item) {
// We do not hold refcount
dc->white_item = NULL;
}
while (dc->white_curves) {
reinterpret_cast<SPCurve *>(dc->white_curves->data)->unref();
dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data);
}
while (dc->white_anchors) {
sp_draw_anchor_destroy(static_cast<SPDrawAnchor*>(dc->white_anchors->data));
dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data);
}
}
static void spdc_free_colors(FreehandBase *dc)
{
// Red
if (dc->red_bpath) {
sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->red_bpath));
dc->red_bpath = NULL;
}
if (dc->red_curve) {
dc->red_curve = dc->red_curve->unref();
}
// Blue
if (dc->blue_bpath) {
sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->blue_bpath));
dc->blue_bpath = NULL;
}
if (dc->blue_curve) {
dc->blue_curve = dc->blue_curve->unref();
}
// Blue2
if (dc->blue2_bpath) {
sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->blue2_bpath));
dc->blue2_bpath = NULL;
}
if (dc->blue2_curve) {
dc->blue2_curve = dc->blue2_curve->unref();
}
// Green
while (dc->green_bpaths) {
sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data));
dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data);
}
if (dc->green_curve) {
dc->green_curve = dc->green_curve->unref();
}
if (dc->green_anchor) {
dc->green_anchor = sp_draw_anchor_destroy(dc->green_anchor);
}
// White
if (dc->white_item) {
// We do not hold refcount
dc->white_item = NULL;
}
while (dc->white_curves) {
reinterpret_cast<SPCurve *>(dc->white_curves->data)->unref();
dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data);
}
while (dc->white_anchors) {
sp_draw_anchor_destroy(static_cast<SPDrawAnchor *>(dc->white_anchors->data));
dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data);
}
}
void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state) {
g_return_if_fail(!strcmp(tool, "/tools/freehand/pen") || !strcmp(tool, "/tools/freehand/pencil"));
Glib::ustring tool_path = tool;
SPDesktop *desktop = ec->desktop;
Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
repr->setAttribute("sodipodi:type", "arc");
SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
Inkscape::GC::release(repr);
// apply the tool's current style
sp_desktop_apply_style_tool(desktop, repr, tool, false);
// find out stroke width (TODO: is there an easier way??)
double stroke_width = 3.0;
gchar const *style_str = NULL;
style_str = repr->attribute("style");
if (style_str) {
SPStyle *style = sp_style_new(SP_ACTIVE_DOCUMENT);
sp_style_merge_from_style_string(style, style_str);
stroke_width = style->stroke_width.computed;
style->stroke_width.computed = 0;
sp_style_unref(style);
}
// unset stroke and set fill color to former stroke color
gchar * str;
str = g_strdup_printf("fill:#%06x;stroke:none;", sp_desktop_get_color_tool(desktop, tool, false) >> 8);
repr->setAttribute("style", str);
g_free(str);
// put the circle where the mouse click occurred and set the diameter to the
// current stroke width, multiplied by the amount specified in the preferences
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
Geom::Affine const i2d (item->i2dt_affine ());
Geom::Point pp = pt * i2d.inverse();
double rad = 0.5 * prefs->getDouble(tool_path + "/dot-size", 3.0);
if (event_state & GDK_MOD1_MASK) {
// TODO: We vary the dot size between 0.5*rad and 1.5*rad, where rad is the dot size
// as specified in prefs. Very simple, but it might be sufficient in practice. If not,
// we need to devise something more sophisticated.
double s = g_random_double_range(-0.5, 0.5);
rad *= (1 + s);
}
if (event_state & GDK_SHIFT_MASK) {
// double the point size
rad *= 2;
}
sp_repr_set_svg_double (repr, "sodipodi:cx", pp[Geom::X]);
sp_repr_set_svg_double (repr, "sodipodi:cy", pp[Geom::Y]);
sp_repr_set_svg_double (repr, "sodipodi:rx", rad * stroke_width);
sp_repr_set_svg_double (repr, "sodipodi:ry", rad * stroke_width);
item->updateRepr();
sp_desktop_selection(desktop)->set(item);
desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating single dot"));
DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_NONE, _("Create single dot"));
}
}
}
}
/*
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:fileencoding=utf-8:textwidth=99 :