1 /** \file
2 * Pen event context implementation.
3 */
5 /*
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Copyright (C) 2000 Lauris Kaplinski
11 * Copyright (C) 2000-2001 Ximian, Inc.
12 * Copyright (C) 2002 Lauris Kaplinski
13 * Copyright (C) 2004 Monash University
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #include <gdk/gdkkeysyms.h>
19 #include <cstring>
20 #include <string>
22 #include "pen-context.h"
23 #include "sp-namedview.h"
24 #include "sp-metrics.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "selection-chemistry.h"
29 #include "draw-anchor.h"
30 #include "message-stack.h"
31 #include "message-context.h"
32 #include "preferences.h"
33 #include "sp-path.h"
34 #include "display/curve.h"
35 #include "pixmaps/cursor-pen.xpm"
36 #include "display/canvas-bpath.h"
37 #include "display/sp-ctrlline.h"
38 #include "display/sodipodi-ctrl.h"
39 #include <glibmm/i18n.h>
40 #include "libnr/nr-point-ops.h"
41 #include "helper/units.h"
42 #include "macros.h"
43 #include "context-fns.h"
44 #include "tools-switch.h"
46 static void sp_pen_context_class_init(SPPenContextClass *klass);
47 static void sp_pen_context_init(SPPenContext *pc);
48 static void sp_pen_context_dispose(GObject *object);
50 static void sp_pen_context_setup(SPEventContext *ec);
51 static void sp_pen_context_finish(SPEventContext *ec);
52 static void sp_pen_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val);
53 static gint sp_pen_context_root_handler(SPEventContext *ec, GdkEvent *event);
54 static gint sp_pen_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
56 static void spdc_pen_set_initial_point(SPPenContext *pc, Geom::Point const p);
57 static void spdc_pen_set_subsequent_point(SPPenContext *const pc, Geom::Point const p, bool statusbar, guint status = 0);
58 static void spdc_pen_set_ctrl(SPPenContext *pc, Geom::Point const p, guint state);
59 static void spdc_pen_finish_segment(SPPenContext *pc, Geom::Point p, guint state);
61 static void spdc_pen_finish(SPPenContext *pc, gboolean closed);
63 static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const &bevent);
64 static gint pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent);
65 static gint pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent);
66 static gint pen_handle_2button_press(SPPenContext *const pc, GdkEventButton const &bevent);
67 static gint pen_handle_key_press(SPPenContext *const pc, GdkEvent *event);
68 static void spdc_reset_colors(SPPenContext *pc);
70 static void pen_disable_events(SPPenContext *const pc);
71 static void pen_enable_events(SPPenContext *const pc);
73 static Geom::Point pen_drag_origin_w(0, 0);
74 static bool pen_within_tolerance = false;
76 static SPDrawContextClass *pen_parent_class;
78 static int pen_next_paraxial_direction(const SPPenContext *const pc, Geom::Point const &pt, Geom::Point const &origin, guint state);
79 static void pen_set_to_nearest_horiz_vert(const SPPenContext *const pc, Geom::Point &pt, guint const state);
81 static int pen_last_paraxial_dir = 0; // last used direction in horizontal/vertical mode; 0 = horizontal, 1 = vertical
83 /**
84 * Register SPPenContext with Gdk and return its type.
85 */
86 GType
87 sp_pen_context_get_type(void)
88 {
89 static GType type = 0;
90 if (!type) {
91 GTypeInfo info = {
92 sizeof(SPPenContextClass),
93 NULL, NULL,
94 (GClassInitFunc) sp_pen_context_class_init,
95 NULL, NULL,
96 sizeof(SPPenContext),
97 4,
98 (GInstanceInitFunc) sp_pen_context_init,
99 NULL, /* value_table */
100 };
101 type = g_type_register_static(SP_TYPE_DRAW_CONTEXT, "SPPenContext", &info, (GTypeFlags)0);
102 }
103 return type;
104 }
106 /**
107 * Initialize the SPPenContext vtable.
108 */
109 static void
110 sp_pen_context_class_init(SPPenContextClass *klass)
111 {
112 GObjectClass *object_class;
113 SPEventContextClass *event_context_class;
115 object_class = (GObjectClass *) klass;
116 event_context_class = (SPEventContextClass *) klass;
118 pen_parent_class = (SPDrawContextClass*)g_type_class_peek_parent(klass);
120 object_class->dispose = sp_pen_context_dispose;
122 event_context_class->setup = sp_pen_context_setup;
123 event_context_class->finish = sp_pen_context_finish;
124 event_context_class->set = sp_pen_context_set;
125 event_context_class->root_handler = sp_pen_context_root_handler;
126 event_context_class->item_handler = sp_pen_context_item_handler;
127 }
129 /**
130 * Callback to initialize SPPenContext object.
131 */
132 static void
133 sp_pen_context_init(SPPenContext *pc)
134 {
136 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
138 event_context->cursor_shape = cursor_pen_xpm;
139 event_context->hot_x = 4;
140 event_context->hot_y = 4;
142 pc->npoints = 0;
143 pc->mode = SP_PEN_CONTEXT_MODE_CLICK;
144 pc->state = SP_PEN_CONTEXT_POINT;
146 pc->c0 = NULL;
147 pc->c1 = NULL;
148 pc->cl0 = NULL;
149 pc->cl1 = NULL;
151 pc->events_disabled = 0;
153 pc->num_clicks = 0;
154 pc->waiting_LPE = NULL;
155 pc->waiting_item = NULL;
156 }
158 /**
159 * Callback to destroy the SPPenContext object's members and itself.
160 */
161 static void
162 sp_pen_context_dispose(GObject *object)
163 {
164 SPPenContext *pc;
166 pc = SP_PEN_CONTEXT(object);
168 if (pc->c0) {
169 gtk_object_destroy(GTK_OBJECT(pc->c0));
170 pc->c0 = NULL;
171 }
172 if (pc->c1) {
173 gtk_object_destroy(GTK_OBJECT(pc->c1));
174 pc->c1 = NULL;
175 }
176 if (pc->cl0) {
177 gtk_object_destroy(GTK_OBJECT(pc->cl0));
178 pc->cl0 = NULL;
179 }
180 if (pc->cl1) {
181 gtk_object_destroy(GTK_OBJECT(pc->cl1));
182 pc->cl1 = NULL;
183 }
185 G_OBJECT_CLASS(pen_parent_class)->dispose(object);
187 if (pc->expecting_clicks_for_LPE > 0) {
188 // we received too few clicks to sanely set the parameter path so we remove the LPE from the item
189 sp_lpe_item_remove_current_path_effect(pc->waiting_item, false);
190 }
191 }
193 void
194 sp_pen_context_set_polyline_mode(SPPenContext *const pc) {
195 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
196 guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0);
197 pc->polylines_only = (mode == 2 || mode == 3);
198 pc->polylines_paraxial = (mode == 3);
199 }
201 /**
202 * Callback to initialize SPPenContext object.
203 */
204 static void
205 sp_pen_context_setup(SPEventContext *ec)
206 {
207 SPPenContext *pc;
209 pc = SP_PEN_CONTEXT(ec);
211 if (((SPEventContextClass *) pen_parent_class)->setup) {
212 ((SPEventContextClass *) pen_parent_class)->setup(ec);
213 }
215 /* Pen indicators */
216 pc->c0 = sp_canvas_item_new(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRL, "shape", SP_CTRL_SHAPE_CIRCLE,
217 "size", 4.0, "filled", 0, "fill_color", 0xff00007f, "stroked", 1, "stroke_color", 0x0000ff7f, NULL);
218 pc->c1 = sp_canvas_item_new(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRL, "shape", SP_CTRL_SHAPE_CIRCLE,
219 "size", 4.0, "filled", 0, "fill_color", 0xff00007f, "stroked", 1, "stroke_color", 0x0000ff7f, NULL);
220 pc->cl0 = sp_canvas_item_new(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRLLINE, NULL);
221 sp_ctrlline_set_rgba32(SP_CTRLLINE(pc->cl0), 0x0000007f);
222 pc->cl1 = sp_canvas_item_new(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(ec)), SP_TYPE_CTRLLINE, NULL);
223 sp_ctrlline_set_rgba32(SP_CTRLLINE(pc->cl1), 0x0000007f);
225 sp_canvas_item_hide(pc->c0);
226 sp_canvas_item_hide(pc->c1);
227 sp_canvas_item_hide(pc->cl0);
228 sp_canvas_item_hide(pc->cl1);
230 sp_event_context_read(ec, "mode");
232 pc->anchor_statusbar = false;
234 sp_pen_context_set_polyline_mode(pc);
236 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
237 if (prefs->getBool("/tools/freehand/pen/selcue")) {
238 ec->enableSelectionCue();
239 }
240 }
242 static void
243 pen_cancel (SPPenContext *const pc)
244 {
245 pc->num_clicks = 0;
246 pc->state = SP_PEN_CONTEXT_STOP;
247 spdc_reset_colors(pc);
248 sp_canvas_item_hide(pc->c0);
249 sp_canvas_item_hide(pc->c1);
250 sp_canvas_item_hide(pc->cl0);
251 sp_canvas_item_hide(pc->cl1);
252 pc->_message_context->clear();
253 pc->_message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled"));
255 sp_canvas_end_forced_full_redraws(pc->desktop->canvas);
256 }
258 /**
259 * Finalization callback.
260 */
261 static void
262 sp_pen_context_finish(SPEventContext *ec)
263 {
264 SPPenContext *pc = SP_PEN_CONTEXT(ec);
266 sp_event_context_discard_delayed_snap_event(ec);
268 if (pc->npoints != 0) {
269 pen_cancel (pc);
270 }
272 if (((SPEventContextClass *) pen_parent_class)->finish) {
273 ((SPEventContextClass *) pen_parent_class)->finish(ec);
274 }
275 }
277 /**
278 * Callback that sets key to value in pen context.
279 */
280 static void
281 sp_pen_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val)
282 {
283 SPPenContext *pc = SP_PEN_CONTEXT(ec);
284 Glib::ustring name = val->getEntryName();
286 if (name == "mode") {
287 if ( val->getString() == "drag" ) {
288 pc->mode = SP_PEN_CONTEXT_MODE_DRAG;
289 } else {
290 pc->mode = SP_PEN_CONTEXT_MODE_CLICK;
291 }
292 }
293 }
295 /**
296 * Snaps new node relative to the previous node.
297 */
298 static void
299 spdc_endpoint_snap(SPPenContext const *const pc, Geom::Point &p, guint const state)
300 {
301 if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping
302 if (pc->npoints > 0) {
303 spdc_endpoint_snap_rotation(pc, p, pc->p[0], state);
304 }
305 } else {
306 if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above
307 //After all, the user explicitely asked for angular snapping by
308 //pressing CTRL
309 spdc_endpoint_snap_free(pc, p, state);
310 }
311 }
312 if (pc->polylines_paraxial) {
313 // TODO: must we avoid one of the snaps in the previous case distinction in some situations?
314 pen_set_to_nearest_horiz_vert(pc, p, state);
315 }
316 }
318 /**
319 * Snaps new node's handle relative to the new node.
320 */
321 static void
322 spdc_endpoint_snap_handle(SPPenContext const *const pc, Geom::Point &p, guint const state)
323 {
324 g_return_if_fail(( pc->npoints == 2 ||
325 pc->npoints == 5 ));
327 if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping
328 spdc_endpoint_snap_rotation(pc, p, pc->p[pc->npoints - 2], state);
329 } else {
330 if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above
331 spdc_endpoint_snap_free(pc, p, state);
332 }
333 }
334 }
336 static gint
337 sp_pen_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
338 {
339 SPPenContext *const pc = SP_PEN_CONTEXT(ec);
341 gint ret = FALSE;
343 switch (event->type) {
344 case GDK_BUTTON_PRESS:
345 ret = pen_handle_button_press(pc, event->button);
346 break;
347 case GDK_BUTTON_RELEASE:
348 ret = pen_handle_button_release(pc, event->button);
349 break;
350 default:
351 break;
352 }
354 if (!ret) {
355 if (((SPEventContextClass *) pen_parent_class)->item_handler)
356 ret = ((SPEventContextClass *) pen_parent_class)->item_handler(ec, item, event);
357 }
359 return ret;
360 }
362 /**
363 * Callback to handle all pen events.
364 */
365 static gint
366 sp_pen_context_root_handler(SPEventContext *ec, GdkEvent *event)
367 {
368 SPPenContext *const pc = SP_PEN_CONTEXT(ec);
370 gint ret = FALSE;
372 switch (event->type) {
373 case GDK_BUTTON_PRESS:
374 ret = pen_handle_button_press(pc, event->button);
375 break;
377 case GDK_MOTION_NOTIFY:
378 ret = pen_handle_motion_notify(pc, event->motion);
379 break;
381 case GDK_BUTTON_RELEASE:
382 ret = pen_handle_button_release(pc, event->button);
383 break;
385 case GDK_2BUTTON_PRESS:
386 ret = pen_handle_2button_press(pc, event->button);
387 break;
389 case GDK_KEY_PRESS:
390 ret = pen_handle_key_press(pc, event);
391 break;
393 default:
394 break;
395 }
397 if (!ret) {
398 gint (*const parent_root_handler)(SPEventContext *, GdkEvent *)
399 = ((SPEventContextClass *) pen_parent_class)->root_handler;
400 if (parent_root_handler) {
401 ret = parent_root_handler(ec, event);
402 }
403 }
405 return ret;
406 }
408 /**
409 * Handle mouse button press event.
410 */
411 static gint pen_handle_button_press(SPPenContext *const pc, GdkEventButton const &bevent)
412 {
413 if (pc->events_disabled) {
414 // skip event processing if events are disabled
415 return FALSE;
416 }
418 SPDrawContext * const dc = SP_DRAW_CONTEXT(pc);
419 SPDesktop * const desktop = SP_EVENT_CONTEXT_DESKTOP(dc);
420 Geom::Point const event_w(bevent.x, bevent.y);
421 Geom::Point event_dt(desktop->w2d(event_w));
422 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
424 gint ret = FALSE;
425 if (bevent.button == 1 && !event_context->space_panning
426 // make sure this is not the last click for a waiting LPE (otherwise we want to finish the path)
427 && pc->expecting_clicks_for_LPE != 1) {
429 if (Inkscape::have_viable_layer(desktop, dc->_message_context) == false) {
430 return TRUE;
431 }
433 if (!pc->grab ) {
434 /* Grab mouse, so release will not pass unnoticed */
435 pc->grab = SP_CANVAS_ITEM(desktop->acetate);
436 sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK |
437 GDK_BUTTON_RELEASE_MASK |
438 GDK_POINTER_MOTION_MASK ),
439 NULL, bevent.time);
440 }
442 pen_drag_origin_w = event_w;
443 pen_within_tolerance = true;
445 /* Test whether we hit any anchor. */
446 SPDrawAnchor * const anchor = spdc_test_inside(pc, event_w);
448 switch (pc->mode) {
449 case SP_PEN_CONTEXT_MODE_CLICK:
450 /* In click mode we add point on release */
451 switch (pc->state) {
452 case SP_PEN_CONTEXT_POINT:
453 case SP_PEN_CONTEXT_CONTROL:
454 case SP_PEN_CONTEXT_CLOSE:
455 break;
456 case SP_PEN_CONTEXT_STOP:
457 /* This is allowed, if we just canceled curve */
458 pc->state = SP_PEN_CONTEXT_POINT;
459 break;
460 default:
461 break;
462 }
463 break;
464 case SP_PEN_CONTEXT_MODE_DRAG:
465 switch (pc->state) {
466 case SP_PEN_CONTEXT_STOP:
467 /* This is allowed, if we just canceled curve */
468 case SP_PEN_CONTEXT_POINT:
469 if (pc->npoints == 0) {
471 Geom::Point p;
472 if ((bevent.state & GDK_CONTROL_MASK) && (pc->polylines_only || pc->polylines_paraxial)) {
473 p = event_dt;
474 if (!(bevent.state & GDK_SHIFT_MASK)) {
475 SnapManager &m = desktop->namedview->snap_manager;
476 m.setup(desktop);
477 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
478 }
479 spdc_create_single_dot(event_context, p, "/tools/freehand/pen", bevent.state);
480 ret = TRUE;
481 break;
482 }
484 // TODO: Perhaps it would be nicer to rearrange the following case
485 // distinction so that the case of a waiting LPE is treated separately
487 /* Set start anchor */
488 pc->sa = anchor;
489 if (anchor && !sp_pen_context_has_waiting_LPE(pc)) {
490 /* Adjust point to anchor if needed; if we have a waiting LPE, we need
491 a fresh path to be created so don't continue an existing one */
492 p = anchor->dp;
493 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path"));
494 } else {
495 // This is the first click of a new curve; deselect item so that
496 // this curve is not combined with it (unless it is drawn from its
497 // anchor, which is handled by the sibling branch above)
498 Inkscape::Selection * const selection = sp_desktop_selection(desktop);
499 if (!(bevent.state & GDK_SHIFT_MASK) || sp_pen_context_has_waiting_LPE(pc)) {
500 /* if we have a waiting LPE, we need a fresh path to be created
501 so don't append to an existing one */
502 selection->clear();
503 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path"));
504 } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) {
505 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path"));
506 }
508 /* Create green anchor */
509 p = event_dt;
510 if (!pc->polylines_paraxial) {
511 // only snap the starting point if we're not in horizontal/vertical mode
512 // because otherwise it gets shifted; TODO: why do we snap here at all??
513 spdc_endpoint_snap(pc, p, bevent.state);
514 }
515 pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, p);
516 }
517 spdc_pen_set_initial_point(pc, p);
518 } else {
520 /* Set end anchor */
521 pc->ea = anchor;
522 Geom::Point p;
523 if (anchor) {
524 p = anchor->dp;
525 // we hit an anchor, will finish the curve (either with or without closing)
526 // in release handler
527 pc->state = SP_PEN_CONTEXT_CLOSE;
529 if (pc->green_anchor && pc->green_anchor->active) {
530 // we clicked on the current curve start, so close it even if
531 // we drag a handle away from it
532 dc->green_closed = TRUE;
533 }
534 ret = TRUE;
535 break;
537 } else {
538 p = event_dt;
539 spdc_endpoint_snap(pc, p, bevent.state); /* Snap node only if not hitting anchor. */
540 spdc_pen_set_subsequent_point(pc, p, true);
541 }
542 }
544 pc->state = pc->polylines_only ? SP_PEN_CONTEXT_POINT : SP_PEN_CONTEXT_CONTROL;
545 ret = TRUE;
546 break;
547 case SP_PEN_CONTEXT_CONTROL:
548 g_warning("Button down in CONTROL state");
549 break;
550 case SP_PEN_CONTEXT_CLOSE:
551 g_warning("Button down in CLOSE state");
552 break;
553 default:
554 break;
555 }
556 break;
557 default:
558 break;
559 }
560 } else if (bevent.button == 3 || pc->expecting_clicks_for_LPE == 1) { // when the last click for a waiting LPE occurs we want to finish the path
561 if (pc->npoints != 0) {
563 spdc_pen_finish_segment(pc, event_dt, bevent.state);
564 if (pc->green_closed) {
565 // finishing at the start anchor, close curve
566 spdc_pen_finish(pc, TRUE);
567 } else {
568 // finishing at some other anchor, finish curve but not close
569 spdc_pen_finish(pc, FALSE);
570 }
572 ret = TRUE;
573 }
574 }
576 if (pc->expecting_clicks_for_LPE > 0) {
577 --pc->expecting_clicks_for_LPE;
578 }
580 return ret;
581 }
583 /**
584 * Handle motion_notify event.
585 */
586 static gint
587 pen_handle_motion_notify(SPPenContext *const pc, GdkEventMotion const &mevent)
588 {
589 gint ret = FALSE;
591 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
592 SPDesktop * const dt = SP_EVENT_CONTEXT_DESKTOP(event_context);
594 if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
595 // allow scrolling
596 return FALSE;
597 }
599 if (pc->events_disabled) {
600 // skip motion events if pen events are disabled
601 return FALSE;
602 }
604 Geom::Point const event_w(mevent.x,
605 mevent.y);
606 if (pen_within_tolerance) {
607 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
608 gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
609 if ( Geom::LInfty( event_w - pen_drag_origin_w ) < tolerance ) {
610 return FALSE; // Do not drag if we're within tolerance from origin.
611 }
612 }
613 // Once the user has moved farther than tolerance from the original location
614 // (indicating they intend to move the object, not click), then always process the
615 // motion notify coordinates as given (no snapping back to origin)
616 pen_within_tolerance = false;
618 /* Find desktop coordinates */
619 Geom::Point p = dt->w2d(event_w);
621 /* Test, whether we hit any anchor */
622 SPDrawAnchor *anchor = spdc_test_inside(pc, event_w);
624 switch (pc->mode) {
625 case SP_PEN_CONTEXT_MODE_CLICK:
626 switch (pc->state) {
627 case SP_PEN_CONTEXT_POINT:
628 if ( pc->npoints != 0 ) {
629 /* Only set point, if we are already appending */
630 spdc_endpoint_snap(pc, p, mevent.state);
631 spdc_pen_set_subsequent_point(pc, p, true);
632 ret = TRUE;
633 }
634 break;
635 case SP_PEN_CONTEXT_CONTROL:
636 case SP_PEN_CONTEXT_CLOSE:
637 /* Placing controls is last operation in CLOSE state */
638 spdc_endpoint_snap(pc, p, mevent.state);
639 spdc_pen_set_ctrl(pc, p, mevent.state);
640 ret = TRUE;
641 break;
642 case SP_PEN_CONTEXT_STOP:
643 /* This is perfectly valid */
644 break;
645 default:
646 break;
647 }
648 break;
649 case SP_PEN_CONTEXT_MODE_DRAG:
650 switch (pc->state) {
651 case SP_PEN_CONTEXT_POINT:
652 if ( pc->npoints > 0 ) {
653 /* Only set point, if we are already appending */
655 if (!anchor) { /* Snap node only if not hitting anchor */
656 spdc_endpoint_snap(pc, p, mevent.state);
657 }
659 spdc_pen_set_subsequent_point(pc, p, !anchor, mevent.state);
661 if (anchor && !pc->anchor_statusbar) {
662 pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to close and finish the path."));
663 pc->anchor_statusbar = true;
664 } else if (!anchor && pc->anchor_statusbar) {
665 pc->_message_context->clear();
666 pc->anchor_statusbar = false;
667 }
669 ret = TRUE;
670 } else {
671 if (anchor && !pc->anchor_statusbar) {
672 pc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to continue the path from this point."));
673 pc->anchor_statusbar = true;
674 } else if (!anchor && pc->anchor_statusbar) {
675 pc->_message_context->clear();
676 pc->anchor_statusbar = false;
677 }
678 }
679 break;
680 case SP_PEN_CONTEXT_CONTROL:
681 case SP_PEN_CONTEXT_CLOSE:
682 /* Placing controls is last operation in CLOSE state */
684 // snap the handle
685 spdc_endpoint_snap_handle(pc, p, mevent.state);
687 if (!pc->polylines_only) {
688 spdc_pen_set_ctrl(pc, p, mevent.state);
689 } else {
690 spdc_pen_set_ctrl(pc, pc->p[1], mevent.state);
691 }
692 gobble_motion_events(GDK_BUTTON1_MASK);
693 ret = TRUE;
694 break;
695 case SP_PEN_CONTEXT_STOP:
696 /* This is perfectly valid */
697 break;
698 default:
699 break;
700 }
701 break;
702 default:
703 break;
704 }
705 return ret;
706 }
708 /**
709 * Handle mouse button release event.
710 */
711 static gint
712 pen_handle_button_release(SPPenContext *const pc, GdkEventButton const &revent)
713 {
714 if (pc->events_disabled) {
715 // skip event processing if events are disabled
716 return FALSE;
717 }
719 gint ret = FALSE;
720 SPEventContext *event_context = SP_EVENT_CONTEXT(pc);
721 if ( revent.button == 1 && !event_context->space_panning) {
723 SPDrawContext *dc = SP_DRAW_CONTEXT (pc);
725 Geom::Point const event_w(revent.x,
726 revent.y);
727 /* Find desktop coordinates */
728 Geom::Point p = pc->desktop->w2d(event_w);
730 /* Test whether we hit any anchor. */
731 SPDrawAnchor *anchor = spdc_test_inside(pc, event_w);
733 switch (pc->mode) {
734 case SP_PEN_CONTEXT_MODE_CLICK:
735 switch (pc->state) {
736 case SP_PEN_CONTEXT_POINT:
737 if ( pc->npoints == 0 ) {
738 /* Start new thread only with button release */
739 if (anchor) {
740 p = anchor->dp;
741 }
742 pc->sa = anchor;
743 spdc_pen_set_initial_point(pc, p);
744 } else {
745 /* Set end anchor here */
746 pc->ea = anchor;
747 if (anchor) {
748 p = anchor->dp;
749 }
750 }
751 pc->state = SP_PEN_CONTEXT_CONTROL;
752 ret = TRUE;
753 break;
754 case SP_PEN_CONTEXT_CONTROL:
755 /* End current segment */
756 spdc_endpoint_snap(pc, p, revent.state);
757 spdc_pen_finish_segment(pc, p, revent.state);
758 pc->state = SP_PEN_CONTEXT_POINT;
759 ret = TRUE;
760 break;
761 case SP_PEN_CONTEXT_CLOSE:
762 /* End current segment */
763 if (!anchor) { /* Snap node only if not hitting anchor */
764 spdc_endpoint_snap(pc, p, revent.state);
765 }
766 spdc_pen_finish_segment(pc, p, revent.state);
767 spdc_pen_finish(pc, TRUE);
768 pc->state = SP_PEN_CONTEXT_POINT;
769 ret = TRUE;
770 break;
771 case SP_PEN_CONTEXT_STOP:
772 /* This is allowed, if we just canceled curve */
773 pc->state = SP_PEN_CONTEXT_POINT;
774 ret = TRUE;
775 break;
776 default:
777 break;
778 }
779 break;
780 case SP_PEN_CONTEXT_MODE_DRAG:
781 switch (pc->state) {
782 case SP_PEN_CONTEXT_POINT:
783 case SP_PEN_CONTEXT_CONTROL:
784 spdc_endpoint_snap(pc, p, revent.state);
785 spdc_pen_finish_segment(pc, p, revent.state);
786 break;
787 case SP_PEN_CONTEXT_CLOSE:
788 spdc_endpoint_snap(pc, p, revent.state);
789 spdc_pen_finish_segment(pc, p, revent.state);
790 if (pc->green_closed) {
791 // finishing at the start anchor, close curve
792 spdc_pen_finish(pc, TRUE);
793 } else {
794 // finishing at some other anchor, finish curve but not close
795 spdc_pen_finish(pc, FALSE);
796 }
797 break;
798 case SP_PEN_CONTEXT_STOP:
799 /* This is allowed, if we just cancelled curve */
800 break;
801 default:
802 break;
803 }
804 pc->state = SP_PEN_CONTEXT_POINT;
805 ret = TRUE;
806 break;
807 default:
808 break;
809 }
811 if (pc->grab) {
812 /* Release grab now */
813 sp_canvas_item_ungrab(pc->grab, revent.time);
814 pc->grab = NULL;
815 }
817 ret = TRUE;
819 dc->green_closed = FALSE;
820 }
822 // TODO: can we be sure that the path was created correctly?
823 // TODO: should we offer an option to collect the clicks in a list?
824 if (pc->expecting_clicks_for_LPE == 0 && sp_pen_context_has_waiting_LPE(pc)) {
825 sp_pen_context_set_polyline_mode(pc);
827 SPEventContext *ec = SP_EVENT_CONTEXT(pc);
828 Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
830 if (pc->waiting_LPE) {
831 // we have an already created LPE waiting for a path
832 pc->waiting_LPE->acceptParamPath(SP_PATH(selection->singleItem()));
833 selection->add(SP_OBJECT(pc->waiting_item));
834 pc->waiting_LPE = NULL;
835 } else {
836 // the case that we need to create a new LPE and apply it to the just-drawn path is
837 // handled in spdc_check_for_and_apply_waiting_LPE() in draw-context.cpp
838 }
839 }
841 return ret;
842 }
844 static gint
845 pen_handle_2button_press(SPPenContext *const pc, GdkEventButton const &bevent)
846 {
847 gint ret = FALSE;
848 // only end on LMB double click. Otherwise horizontal scrolling causes ending of the path
849 if (pc->npoints != 0 && bevent.button == 1) {
850 spdc_pen_finish(pc, FALSE);
851 ret = TRUE;
852 }
853 return ret;
854 }
856 void
857 pen_redraw_all (SPPenContext *const pc)
858 {
859 // green
860 if (pc->green_bpaths) {
861 // remove old piecewise green canvasitems
862 while (pc->green_bpaths) {
863 gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data));
864 pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
865 }
866 // one canvas bpath for all of green_curve
867 SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), pc->green_curve);
868 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
869 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cshape), 0, SP_WIND_RULE_NONZERO);
871 pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape);
872 }
874 if (pc->green_anchor)
875 SP_CTRL(pc->green_anchor->ctrl)->moveto(pc->green_anchor->dp);
877 pc->red_curve->reset();
878 pc->red_curve->moveto(pc->p[0]);
879 pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]);
880 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
882 // handles
883 if (pc->p[0] != pc->p[1]) {
884 SP_CTRL(pc->c1)->moveto(pc->p[1]);
885 sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[0], pc->p[1]);
886 sp_canvas_item_show (pc->c1);
887 sp_canvas_item_show (pc->cl1);
888 } else {
889 sp_canvas_item_hide (pc->c1);
890 sp_canvas_item_hide (pc->cl1);
891 }
893 Geom::Curve const * last_seg = pc->green_curve->last_segment();
894 if (last_seg) {
895 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>( last_seg );
896 if ( cubic &&
897 (*cubic)[2] != to_2geom(pc->p[0]) )
898 {
899 Geom::Point p2 = (*cubic)[2];
900 SP_CTRL(pc->c0)->moveto(p2);
901 sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl0), p2, pc->p[0]);
902 sp_canvas_item_show (pc->c0);
903 sp_canvas_item_show (pc->cl0);
904 } else {
905 sp_canvas_item_hide (pc->c0);
906 sp_canvas_item_hide (pc->cl0);
907 }
908 }
909 }
911 void
912 pen_lastpoint_move (SPPenContext *const pc, gdouble x, gdouble y)
913 {
914 if (pc->npoints != 5)
915 return;
917 // green
918 if (!pc->green_curve->is_empty()) {
919 pc->green_curve->last_point_additive_move( Geom::Point(x,y) );
920 } else {
921 // start anchor too
922 if (pc->green_anchor) {
923 pc->green_anchor->dp += Geom::Point(x, y);
924 }
925 }
927 // red
928 pc->p[0] += Geom::Point(x, y);
929 pc->p[1] += Geom::Point(x, y);
930 pen_redraw_all(pc);
931 }
933 void
934 pen_lastpoint_move_screen (SPPenContext *const pc, gdouble x, gdouble y)
935 {
936 pen_lastpoint_move (pc, x / pc->desktop->current_zoom(), y / pc->desktop->current_zoom());
937 }
939 void
940 pen_lastpoint_tocurve (SPPenContext *const pc)
941 {
942 if (pc->npoints != 5)
943 return;
945 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>( pc->green_curve->last_segment() );
946 if ( cubic ) {
947 pc->p[1] = pc->p[0] + (Geom::Point)( (*cubic)[3] - (*cubic)[2] );
948 } else {
949 pc->p[1] = pc->p[0] + (1./3)*(pc->p[3] - pc->p[0]);
950 }
952 pen_redraw_all(pc);
953 }
955 void
956 pen_lastpoint_toline (SPPenContext *const pc)
957 {
958 if (pc->npoints != 5)
959 return;
961 pc->p[1] = pc->p[0];
963 pen_redraw_all(pc);
964 }
967 static gint
968 pen_handle_key_press(SPPenContext *const pc, GdkEvent *event)
969 {
971 gint ret = FALSE;
972 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
973 gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px
975 switch (get_group0_keyval (&event->key)) {
977 case GDK_Left: // move last point left
978 case GDK_KP_Left:
979 case GDK_KP_4:
980 if (!MOD__CTRL) { // not ctrl
981 if (MOD__ALT) { // alt
982 if (MOD__SHIFT) pen_lastpoint_move_screen(pc, -10, 0); // shift
983 else pen_lastpoint_move_screen(pc, -1, 0); // no shift
984 }
985 else { // no alt
986 if (MOD__SHIFT) pen_lastpoint_move(pc, -10*nudge, 0); // shift
987 else pen_lastpoint_move(pc, -nudge, 0); // no shift
988 }
989 ret = TRUE;
990 }
991 break;
992 case GDK_Up: // move last point up
993 case GDK_KP_Up:
994 case GDK_KP_8:
995 if (!MOD__CTRL) { // not ctrl
996 if (MOD__ALT) { // alt
997 if (MOD__SHIFT) pen_lastpoint_move_screen(pc, 0, 10); // shift
998 else pen_lastpoint_move_screen(pc, 0, 1); // no shift
999 }
1000 else { // no alt
1001 if (MOD__SHIFT) pen_lastpoint_move(pc, 0, 10*nudge); // shift
1002 else pen_lastpoint_move(pc, 0, nudge); // no shift
1003 }
1004 ret = TRUE;
1005 }
1006 break;
1007 case GDK_Right: // move last point right
1008 case GDK_KP_Right:
1009 case GDK_KP_6:
1010 if (!MOD__CTRL) { // not ctrl
1011 if (MOD__ALT) { // alt
1012 if (MOD__SHIFT) pen_lastpoint_move_screen(pc, 10, 0); // shift
1013 else pen_lastpoint_move_screen(pc, 1, 0); // no shift
1014 }
1015 else { // no alt
1016 if (MOD__SHIFT) pen_lastpoint_move(pc, 10*nudge, 0); // shift
1017 else pen_lastpoint_move(pc, nudge, 0); // no shift
1018 }
1019 ret = TRUE;
1020 }
1021 break;
1022 case GDK_Down: // move last point down
1023 case GDK_KP_Down:
1024 case GDK_KP_2:
1025 if (!MOD__CTRL) { // not ctrl
1026 if (MOD__ALT) { // alt
1027 if (MOD__SHIFT) pen_lastpoint_move_screen(pc, 0, -10); // shift
1028 else pen_lastpoint_move_screen(pc, 0, -1); // no shift
1029 }
1030 else { // no alt
1031 if (MOD__SHIFT) pen_lastpoint_move(pc, 0, -10*nudge); // shift
1032 else pen_lastpoint_move(pc, 0, -nudge); // no shift
1033 }
1034 ret = TRUE;
1035 }
1036 break;
1038 /* TODO: this is not yet enabled?? looks like some traces of the Geometry tool
1039 case GDK_P:
1040 case GDK_p:
1041 if (MOD__SHIFT_ONLY) {
1042 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PARALLEL, 2);
1043 ret = TRUE;
1044 }
1045 break;
1047 case GDK_C:
1048 case GDK_c:
1049 if (MOD__SHIFT_ONLY) {
1050 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::CIRCLE_3PTS, 3);
1051 ret = TRUE;
1052 }
1053 break;
1055 case GDK_B:
1056 case GDK_b:
1057 if (MOD__SHIFT_ONLY) {
1058 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PERP_BISECTOR, 2);
1059 ret = TRUE;
1060 }
1061 break;
1063 case GDK_A:
1064 case GDK_a:
1065 if (MOD__SHIFT_ONLY) {
1066 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::ANGLE_BISECTOR, 3);
1067 ret = TRUE;
1068 }
1069 break;
1070 */
1072 case GDK_U:
1073 case GDK_u:
1074 if (MOD__SHIFT_ONLY) {
1075 pen_lastpoint_tocurve(pc);
1076 ret = TRUE;
1077 }
1078 break;
1079 case GDK_L:
1080 case GDK_l:
1081 if (MOD__SHIFT_ONLY) {
1082 pen_lastpoint_toline(pc);
1083 ret = TRUE;
1084 }
1085 break;
1087 case GDK_Return:
1088 case GDK_KP_Enter:
1089 if (pc->npoints != 0) {
1090 spdc_pen_finish(pc, FALSE);
1091 ret = TRUE;
1092 }
1093 break;
1094 case GDK_Escape:
1095 if (pc->npoints != 0) {
1096 // if drawing, cancel, otherwise pass it up for deselecting
1097 pen_cancel (pc);
1098 ret = TRUE;
1099 }
1100 break;
1101 case GDK_z:
1102 case GDK_Z:
1103 if (MOD__CTRL_ONLY && pc->npoints != 0) {
1104 // if drawing, cancel, otherwise pass it up for undo
1105 pen_cancel (pc);
1106 ret = TRUE;
1107 }
1108 break;
1109 case GDK_g:
1110 case GDK_G:
1111 if (MOD__SHIFT_ONLY) {
1112 sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop);
1113 ret = true;
1114 }
1115 break;
1116 case GDK_BackSpace:
1117 case GDK_Delete:
1118 case GDK_KP_Delete:
1119 if ( pc->green_curve->is_empty() || (pc->green_curve->last_segment() == NULL) ) {
1120 if (!pc->red_curve->is_empty()) {
1121 pen_cancel (pc);
1122 ret = TRUE;
1123 } else {
1124 // do nothing; this event should be handled upstream
1125 }
1126 } else {
1127 /* Reset red curve */
1128 pc->red_curve->reset();
1129 /* Destroy topmost green bpath */
1130 if (pc->green_bpaths) {
1131 if (pc->green_bpaths->data)
1132 gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data));
1133 pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
1134 }
1135 /* Get last segment */
1136 if ( pc->green_curve->is_empty() ) {
1137 g_warning("pen_handle_key_press, case GDK_KP_Delete: Green curve is empty");
1138 break;
1139 }
1140 // The code below assumes that pc->green_curve has only ONE path !
1141 Geom::Curve const * crv = pc->green_curve->last_segment();
1142 pc->p[0] = crv->initialPoint();
1143 if ( Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>(crv)) {
1144 pc->p[1] = (*cubic)[1];
1145 } else {
1146 pc->p[1] = pc->p[0];
1147 }
1148 Geom::Point const pt(( pc->npoints < 4
1149 ? (Geom::Point)(crv->finalPoint())
1150 : pc->p[3] ));
1151 pc->npoints = 2;
1152 pc->green_curve->backspace();
1153 sp_canvas_item_hide(pc->c0);
1154 sp_canvas_item_hide(pc->c1);
1155 sp_canvas_item_hide(pc->cl0);
1156 sp_canvas_item_hide(pc->cl1);
1157 pc->state = SP_PEN_CONTEXT_POINT;
1158 spdc_pen_set_subsequent_point(pc, pt, true);
1159 ret = TRUE;
1160 }
1161 break;
1162 default:
1163 break;
1164 }
1165 return ret;
1166 }
1168 static void
1169 spdc_reset_colors(SPPenContext *pc)
1170 {
1171 /* Red */
1172 pc->red_curve->reset();
1173 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
1174 /* Blue */
1175 pc->blue_curve->reset();
1176 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->blue_bpath), NULL);
1177 /* Green */
1178 while (pc->green_bpaths) {
1179 gtk_object_destroy(GTK_OBJECT(pc->green_bpaths->data));
1180 pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
1181 }
1182 pc->green_curve->reset();
1183 if (pc->green_anchor) {
1184 pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
1185 }
1186 pc->sa = NULL;
1187 pc->ea = NULL;
1188 pc->npoints = 0;
1189 pc->red_curve_is_valid = false;
1190 }
1193 static void
1194 spdc_pen_set_initial_point(SPPenContext *const pc, Geom::Point const p)
1195 {
1196 g_assert( pc->npoints == 0 );
1198 pc->p[0] = p;
1199 pc->p[1] = p;
1200 pc->npoints = 2;
1201 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
1203 sp_canvas_force_full_redraw_after_interruptions(pc->desktop->canvas, 5);
1204 }
1206 /**
1207 * Show the status message for the current line/curve segment.
1208 * This type of message always shows angle/distance as the last
1209 * two parameters ("angle %3.2f°, distance %s").
1210 */
1211 static void
1212 spdc_pen_set_angle_distance_status_message(SPPenContext *const pc, Geom::Point const p, int pc_point_to_compare, gchar const *message)
1213 {
1214 g_assert(pc != NULL);
1215 g_assert((pc_point_to_compare == 0) || (pc_point_to_compare == 3)); // exclude control handles
1216 g_assert(message != NULL);
1218 SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop;
1219 Geom::Point rel = p - pc->p[pc_point_to_compare];
1220 GString *dist = SP_PX_TO_METRIC_STRING(Geom::L2(rel), desktop->namedview->getDefaultMetric());
1221 double angle = atan2(rel[Geom::Y], rel[Geom::X]) * 180 / M_PI;
1222 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1223 if (prefs->getBool("/options/compassangledisplay/value", 0) != 0)
1224 angle = angle_to_compass (angle);
1226 pc->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, message, angle, dist->str);
1227 g_string_free(dist, FALSE);
1228 }
1230 static void
1231 spdc_pen_set_subsequent_point(SPPenContext *const pc, Geom::Point const p, bool statusbar, guint status)
1232 {
1233 g_assert( pc->npoints != 0 );
1234 /* todo: Check callers to see whether 2 <= npoints is guaranteed. */
1236 pc->p[2] = p;
1237 pc->p[3] = p;
1238 pc->p[4] = p;
1239 pc->npoints = 5;
1240 pc->red_curve->reset();
1241 bool is_curve;
1242 pc->red_curve->moveto(pc->p[0]);
1243 if (pc->polylines_paraxial && !statusbar) {
1244 // we are drawing horizontal/vertical lines and hit an anchor; draw an L-shaped path
1245 Geom::Point intermed = p;
1246 pen_set_to_nearest_horiz_vert(pc, intermed, status);
1247 pc->red_curve->lineto(intermed);
1248 pc->red_curve->lineto(p);
1249 is_curve = false;
1250 } else {
1251 // one of the 'regular' modes
1252 if (pc->p[1] != pc->p[0])
1253 {
1254 pc->red_curve->curveto(pc->p[1], p, p);
1255 is_curve = true;
1256 } else {
1257 pc->red_curve->lineto(p);
1258 is_curve = false;
1259 }
1260 }
1262 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
1264 if (statusbar) {
1265 gchar *message = is_curve ?
1266 _("<b>Curve segment</b>: angle %3.2f°, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> to finish the path" ):
1267 _("<b>Line segment</b>: angle %3.2f°, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> to finish the path");
1268 spdc_pen_set_angle_distance_status_message(pc, p, 0, message);
1269 }
1270 }
1272 static void
1273 spdc_pen_set_ctrl(SPPenContext *const pc, Geom::Point const p, guint const state)
1274 {
1275 sp_canvas_item_show(pc->c1);
1276 sp_canvas_item_show(pc->cl1);
1278 if ( pc->npoints == 2 ) {
1279 pc->p[1] = p;
1280 sp_canvas_item_hide(pc->c0);
1281 sp_canvas_item_hide(pc->cl0);
1282 SP_CTRL(pc->c1)->moveto(pc->p[1]);
1283 sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[0], pc->p[1]);
1285 spdc_pen_set_angle_distance_status_message(pc, p, 0, _("<b>Curve handle</b>: angle %3.2f°, length %s; with <b>Ctrl</b> to snap angle"));
1286 } else if ( pc->npoints == 5 ) {
1287 pc->p[4] = p;
1288 sp_canvas_item_show(pc->c0);
1289 sp_canvas_item_show(pc->cl0);
1290 bool is_symm = false;
1291 if ( ( ( pc->mode == SP_PEN_CONTEXT_MODE_CLICK ) && ( state & GDK_CONTROL_MASK ) ) ||
1292 ( ( pc->mode == SP_PEN_CONTEXT_MODE_DRAG ) && !( state & GDK_SHIFT_MASK ) ) ) {
1293 Geom::Point delta = p - pc->p[3];
1294 pc->p[2] = pc->p[3] - delta;
1295 is_symm = true;
1296 pc->red_curve->reset();
1297 pc->red_curve->moveto(pc->p[0]);
1298 pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]);
1299 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
1300 }
1301 SP_CTRL(pc->c0)->moveto(pc->p[2]);
1302 sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl0), pc->p[3], pc->p[2]);
1303 SP_CTRL(pc->c1)->moveto(pc->p[4]);
1304 sp_ctrlline_set_coords(SP_CTRLLINE(pc->cl1), pc->p[3], pc->p[4]);
1306 gchar *message = is_symm ?
1307 _("<b>Curve handle, symmetric</b>: angle %3.2f°, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only") :
1308 _("<b>Curve handle</b>: angle %3.2f°, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only");
1309 spdc_pen_set_angle_distance_status_message(pc, p, 3, message);
1310 } else {
1311 g_warning("Something bad happened - npoints is %d", pc->npoints);
1312 }
1313 }
1315 static void
1316 spdc_pen_finish_segment(SPPenContext *const pc, Geom::Point const p, guint const state)
1317 {
1318 if (pc->polylines_paraxial) {
1319 pen_last_paraxial_dir = pen_next_paraxial_direction(pc, p, pc->p[0], state);
1320 }
1321 ++pc->num_clicks;
1323 if (!pc->red_curve->is_empty()) {
1324 pc->green_curve->append_continuous(pc->red_curve, 0.0625);
1325 SPCurve *curve = pc->red_curve->copy();
1326 /// \todo fixme:
1327 SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve);
1328 curve->unref();
1329 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
1331 pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape);
1333 pc->p[0] = pc->p[3];
1334 pc->p[1] = pc->p[4];
1335 pc->npoints = 2;
1337 pc->red_curve->reset();
1338 }
1339 }
1341 static void
1342 spdc_pen_finish(SPPenContext *const pc, gboolean const closed)
1343 {
1344 if (pc->expecting_clicks_for_LPE > 1) {
1345 // don't let the path be finished before we have collected the required number of mouse clicks
1346 return;
1347 }
1349 pc->num_clicks = 0;
1351 pen_disable_events(pc);
1353 SPDesktop *const desktop = pc->desktop;
1354 pc->_message_context->clear();
1355 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Drawing finished"));
1357 pc->red_curve->reset();
1358 spdc_concat_colors_and_flush(pc, closed);
1359 pc->sa = NULL;
1360 pc->ea = NULL;
1362 pc->npoints = 0;
1363 pc->state = SP_PEN_CONTEXT_POINT;
1365 sp_canvas_item_hide(pc->c0);
1366 sp_canvas_item_hide(pc->c1);
1367 sp_canvas_item_hide(pc->cl0);
1368 sp_canvas_item_hide(pc->cl1);
1370 if (pc->green_anchor) {
1371 pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
1372 }
1375 sp_canvas_end_forced_full_redraws(pc->desktop->canvas);
1377 pen_enable_events(pc);
1378 }
1380 static void
1381 pen_disable_events(SPPenContext *const pc) {
1382 pc->events_disabled++;
1383 }
1385 static void
1386 pen_enable_events(SPPenContext *const pc) {
1387 g_return_if_fail(pc->events_disabled != 0);
1389 pc->events_disabled--;
1390 }
1392 void
1393 sp_pen_context_wait_for_LPE_mouse_clicks(SPPenContext *pc, Inkscape::LivePathEffect::EffectType effect_type,
1394 unsigned int num_clicks, bool use_polylines)
1395 {
1396 if (effect_type == Inkscape::LivePathEffect::INVALID_LPE)
1397 return;
1399 pc->waiting_LPE_type = effect_type;
1400 pc->expecting_clicks_for_LPE = num_clicks;
1401 pc->polylines_only = use_polylines;
1402 pc->polylines_paraxial = false; // TODO: think if this is correct for all cases
1403 }
1405 void
1406 sp_pen_context_cancel_waiting_for_LPE(SPPenContext *pc)
1407 {
1408 pc->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE;
1409 pc->expecting_clicks_for_LPE = 0;
1410 sp_pen_context_set_polyline_mode(pc);
1411 }
1413 static int pen_next_paraxial_direction(const SPPenContext *const pc,
1414 Geom::Point const &pt, Geom::Point const &origin, guint state) {
1415 /*
1416 * after the first mouse click we determine whether the mouse pointer is closest to a
1417 * horizontal or vertical segment; for all subsequent mouse clicks, we use the direction
1418 * orthogonal to the last one; pressing Shift toggles the direction
1419 */
1420 if (pc->num_clicks == 0) {
1421 // first mouse click
1422 double dist_h = fabs(pt[Geom::X] - origin[Geom::X]);
1423 double dist_v = fabs(pt[Geom::Y] - origin[Geom::Y]);
1424 int ret = (dist_h < dist_v) ? 1 : 0; // 0 = horizontal, 1 = vertical
1425 pen_last_paraxial_dir = (state & GDK_SHIFT_MASK) ? 1 - ret : ret;
1426 return pen_last_paraxial_dir;
1427 } else {
1428 // subsequent mouse click
1429 return (state & GDK_SHIFT_MASK) ? pen_last_paraxial_dir : 1 - pen_last_paraxial_dir;
1430 }
1431 }
1433 void pen_set_to_nearest_horiz_vert(const SPPenContext *const pc, Geom::Point &pt, guint const state)
1434 {
1435 Geom::Point const &origin = pc->p[0];
1437 int next_dir = pen_next_paraxial_direction(pc, pt, origin, state);
1439 if (next_dir == 0) {
1440 // line is forced to be horizontal
1441 pt[Geom::Y] = origin[Geom::Y];
1442 } else {
1443 // line is forced to be vertical
1444 pt[Geom::X] = origin[Geom::X];
1445 }
1446 }
1448 /*
1449 Local Variables:
1450 mode:c++
1451 c-file-style:"stroustrup"
1452 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1453 indent-tabs-mode:nil
1454 fill-column:99
1455 End:
1456 */
1457 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :