8439f7086871b9f2945d180c9c18f6bc81998f6f
1 /*
2 * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs
3 *
4 * Authors:
5 * Maximilian Albert <maximilian.albert@gmail.com>
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 *
8 * Copyright (C) 1998 The Free Software Foundation
9 * Copyright (C) 1999-2005 authors
10 * Copyright (C) 2001-2002 Ximian, Inc.
11 * Copyright (C) 2008 Maximilian Albert
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #ifdef HAVE_CONFIG_H
17 #include "config.h"
18 #endif
20 #include <2geom/sbasis-geometric.h>
21 #include <gdk/gdkkeysyms.h>
23 #include "macros.h"
24 #include "forward.h"
25 #include "pixmaps/cursor-node.xpm"
26 #include "pixmaps/cursor-crosshairs.xpm"
27 #include <gtk/gtk.h>
28 #include "desktop.h"
29 #include "message-context.h"
30 #include "preferences.h"
31 #include "shape-editor.h"
32 #include "selection.h"
33 #include "desktop-handles.h"
34 #include "document.h"
35 #include "display/curve.h"
36 #include "display/canvas-bpath.h"
37 #include "message-stack.h"
38 #include "sp-path.h"
39 #include "helper/units.h"
41 #include "lpe-tool-context.h"
43 static void sp_lpetool_context_class_init(SPLPEToolContextClass *klass);
44 static void sp_lpetool_context_init(SPLPEToolContext *erc);
45 static void sp_lpetool_context_dispose(GObject *object);
47 static void sp_lpetool_context_setup(SPEventContext *ec);
48 static void sp_lpetool_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *);
49 static gint sp_lpetool_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event);
50 static gint sp_lpetool_context_root_handler(SPEventContext *ec, GdkEvent *event);
52 void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data);
54 const int num_subtools = 8;
56 Inkscape::LivePathEffect::EffectType lpesubtools[] = {
57 Inkscape::LivePathEffect::INVALID_LPE, // this must be here to account for the "all inactive" action
58 Inkscape::LivePathEffect::LINE_SEGMENT,
59 Inkscape::LivePathEffect::CIRCLE_3PTS,
60 Inkscape::LivePathEffect::CIRCLE_WITH_RADIUS,
61 Inkscape::LivePathEffect::PARALLEL,
62 Inkscape::LivePathEffect::PERP_BISECTOR,
63 Inkscape::LivePathEffect::ANGLE_BISECTOR,
64 Inkscape::LivePathEffect::MIRROR_SYMMETRY,
65 };
67 static SPPenContextClass *lpetool_parent_class = 0;
69 GType sp_lpetool_context_get_type(void)
70 {
71 static GType type = 0;
72 if (!type) {
73 GTypeInfo info = {
74 sizeof(SPLPEToolContextClass),
75 0, // base_init
76 0, // base_finalize
77 (GClassInitFunc)sp_lpetool_context_class_init,
78 0, // class_finalize
79 0, // class_data
80 sizeof(SPLPEToolContext),
81 0, // n_preallocs
82 (GInstanceInitFunc)sp_lpetool_context_init,
83 0 // value_table
84 };
85 type = g_type_register_static(SP_TYPE_PEN_CONTEXT, "SPLPEToolContext", &info, static_cast<GTypeFlags>(0));
86 }
87 return type;
88 }
90 static void
91 sp_lpetool_context_class_init(SPLPEToolContextClass *klass)
92 {
93 GObjectClass *object_class = (GObjectClass *) klass;
94 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
96 lpetool_parent_class = (SPPenContextClass*)g_type_class_peek_parent(klass);
98 object_class->dispose = sp_lpetool_context_dispose;
100 event_context_class->setup = sp_lpetool_context_setup;
101 event_context_class->set = sp_lpetool_context_set;
102 event_context_class->root_handler = sp_lpetool_context_root_handler;
103 event_context_class->item_handler = sp_lpetool_context_item_handler;
104 }
106 static void
107 sp_lpetool_context_init(SPLPEToolContext *lc)
108 {
109 lc->cursor_shape = cursor_crosshairs_xpm;
110 lc->hot_x = 7;
111 lc->hot_y = 7;
113 lc->canvas_bbox = NULL;
114 lc->measuring_items = new std::map<SPPath *, SPCanvasItem*>;
116 new (&lc->sel_changed_connection) sigc::connection();
117 }
119 static void
120 sp_lpetool_context_dispose(GObject *object)
121 {
122 SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(object);
123 delete lc->shape_editor;
125 if (lc->canvas_bbox) {
126 gtk_object_destroy(GTK_OBJECT(lc->canvas_bbox));
127 lc->canvas_bbox = NULL;
128 }
130 lpetool_delete_measuring_items(lc);
131 delete lc->measuring_items;
132 lc->measuring_items = NULL;
134 lc->sel_changed_connection.disconnect();
135 lc->sel_changed_connection.~connection();
137 if (lc->_lpetool_message_context) {
138 delete lc->_lpetool_message_context;
139 }
141 G_OBJECT_CLASS(lpetool_parent_class)->dispose(object);
142 }
144 static void
145 sp_lpetool_context_setup(SPEventContext *ec)
146 {
147 SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(ec);
149 if (((SPEventContextClass *) lpetool_parent_class)->setup)
150 ((SPEventContextClass *) lpetool_parent_class)->setup(ec);
152 Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
153 SPItem *item = selection->singleItem();
155 lc->sel_changed_connection.disconnect();
156 lc->sel_changed_connection =
157 selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_lpetool_context_selection_changed), (gpointer)lc));
159 lc->shape_editor = new ShapeEditor(ec->desktop);
161 lpetool_context_switch_mode(lc, Inkscape::LivePathEffect::INVALID_LPE);
162 lpetool_context_reset_limiting_bbox(lc);
163 lpetool_create_measuring_items(lc);
165 // TODO temp force:
166 ec->enableSelectionCue();
168 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
170 if (item) {
171 lc->shape_editor->set_item(item, SH_NODEPATH);
172 lc->shape_editor->set_item(item, SH_KNOTHOLDER);
173 }
175 if (prefs->getBool("/tools/lpetool/selcue")) {
176 ec->enableSelectionCue();
177 }
179 lc->_lpetool_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
182 lc->shape_editor->update_statusbar();
183 }
185 /**
186 \brief Callback that processes the "changed" signal on the selection;
187 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
188 */
189 void
190 sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data)
191 {
192 SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(data);
194 // TODO: update ShapeEditorsCollective instead
195 lc->shape_editor->unset_item(SH_NODEPATH);
196 lc->shape_editor->unset_item(SH_KNOTHOLDER);
197 SPItem *item = selection->singleItem();
198 lc->shape_editor->set_item(item, SH_NODEPATH);
199 lc->shape_editor->set_item(item, SH_KNOTHOLDER);
200 lc->shape_editor->update_statusbar();
201 }
203 static void
204 sp_lpetool_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val)
205 {
206 // FIXME: how to set this correcly? the value from preferences-skeleton.h doesn't seem to get
207 // read (it wants to set drag = 1)
208 // lpetool_parent_class->set(ec, key, "drag");
210 /*
211 //pass on up to parent class to handle common attributes.
212 if ( lpetool_parent_class->set ) {
213 lpetool_parent_class->set(ec, key, val);
214 }
215 */
216 }
218 static gint
219 sp_lpetool_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
220 {
221 gint ret = FALSE;
223 switch (event->type) {
224 case GDK_BUTTON_PRESS:
225 {
226 // select the clicked item but do nothing else
227 Inkscape::Selection * const selection = sp_desktop_selection(ec->desktop);
228 selection->clear();
229 selection->add(item);
230 ret = TRUE;
231 break;
232 }
233 case GDK_BUTTON_RELEASE:
234 // TODO: do we need to catch this or can we pass it on to the parent handler?
235 ret = TRUE;
236 break;
237 default:
238 break;
239 }
241 if (!ret) {
242 if (((SPEventContextClass *) lpetool_parent_class)->item_handler)
243 ret = ((SPEventContextClass *) lpetool_parent_class)->item_handler(ec, item, event);
244 }
246 return ret;
247 }
249 gint
250 sp_lpetool_context_root_handler(SPEventContext *event_context, GdkEvent *event)
251 {
252 SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(event_context);
253 SPDesktop *desktop = event_context->desktop;
254 Inkscape::Selection *selection = sp_desktop_selection (desktop);
256 bool ret = false;
258 if (sp_pen_context_has_waiting_LPE(lc)) {
259 // quit when we are waiting for a LPE to be applied
260 ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event);
261 return ret;
262 }
264 switch (event->type) {
265 case GDK_BUTTON_PRESS:
266 if (event->button.button == 1 && !event_context->space_panning) {
267 if (lc->mode == Inkscape::LivePathEffect::INVALID_LPE) {
268 // don't do anything for now if we are inactive (except clearing the selection
269 // since this was a click into empty space)
270 selection->clear();
271 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Choose a construction tool from the toolbar."));
272 ret = true;
273 break;
274 }
276 // save drag origin
277 event_context->xp = (gint) event->button.x;
278 event_context->yp = (gint) event->button.y;
279 event_context->within_tolerance = true;
280 lc->shape_editor->cancel_hit();
282 using namespace Inkscape::LivePathEffect;
284 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
285 int mode = prefs->getInt("/tools/lpetool/mode");
286 EffectType type = lpesubtools[mode];
288 //bool over_stroke = lc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
290 sp_pen_context_wait_for_LPE_mouse_clicks(lc, type, Inkscape::LivePathEffect::Effect::acceptsNumClicks(type));
292 // we pass the mouse click on to pen tool as the first click which it should collect
293 ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event);
294 }
295 break;
296 case GDK_MOTION_NOTIFY:
297 {
298 if (!lc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
299 break;
300 }
302 bool over_stroke = false;
303 over_stroke = lc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
305 if (over_stroke) {
306 event_context->cursor_shape = cursor_node_xpm;
307 event_context->hot_x = 1;
308 event_context->hot_y = 1;
309 sp_event_context_update_cursor(event_context);
310 } else {
311 lc->cursor_shape = cursor_crosshairs_xpm;
312 lc->hot_x = 7;
313 lc->hot_y = 7;
314 sp_event_context_update_cursor(event_context);
315 }
316 }
317 break;
320 case GDK_BUTTON_RELEASE:
321 {
322 /**
323 break;
324 **/
325 }
327 case GDK_KEY_PRESS:
328 /**
329 switch (get_group0_keyval (&event->key)) {
330 }
331 break;
332 **/
334 case GDK_KEY_RELEASE:
335 /**
336 switch (get_group0_keyval(&event->key)) {
337 case GDK_Control_L:
338 case GDK_Control_R:
339 dc->_message_context->clear();
340 break;
341 default:
342 break;
343 }
344 **/
346 default:
347 break;
348 }
350 if (!ret) {
351 if (((SPEventContextClass *) lpetool_parent_class)->root_handler) {
352 ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event);
353 }
354 }
356 return ret;
357 }
359 /*
360 * Finds the index in the list of geometric subtools corresponding to the given LPE type.
361 * Returns -1 if no subtool is found.
362 */
363 int
364 lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type) {
365 for (int i = 0; i < num_subtools; ++i) {
366 if (lpesubtools[i] == type) {
367 return i;
368 }
369 }
370 return -1;
371 }
373 /*
374 * Checks whether an item has a construction applied as LPE and if so returns the index in
375 * lpesubtools of this construction
376 */
377 int lpetool_item_has_construction(SPLPEToolContext *lc, SPItem *item)
378 {
379 if (!SP_IS_LPE_ITEM(item)) {
380 return -1;
381 }
383 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
384 if (!lpe) {
385 return -1;
386 }
387 return lpetool_mode_to_index(lpe->effectType());
388 }
390 /*
391 * Attempts to perform the construction of the given type (i.e., to apply the corresponding LPE) to
392 * a single selected item. Returns whether we succeeded.
393 */
394 bool
395 lpetool_try_construction(SPLPEToolContext *lc, Inkscape::LivePathEffect::EffectType const type)
396 {
397 Inkscape::Selection *selection = sp_desktop_selection(lc->desktop);
398 SPItem *item = selection->singleItem();
400 // TODO: should we check whether type represents a valid geometric construction?
401 if (item && SP_IS_LPE_ITEM(item) && Inkscape::LivePathEffect::Effect::acceptsNumClicks(type) == 0) {
402 Inkscape::LivePathEffect::Effect::createAndApply(type, sp_desktop_document(lc->desktop), item);
403 return true;
404 }
405 return false;
406 }
408 void
409 lpetool_context_switch_mode(SPLPEToolContext *lc, Inkscape::LivePathEffect::EffectType const type)
410 {
411 int index = lpetool_mode_to_index(type);
412 if (index != -1) {
413 lc->mode = type;
414 lc->desktop->setToolboxSelectOneValue ("lpetool_mode_action", index);
415 } else {
416 g_warning ("Invalid mode selected: %d", type);
417 return;
418 }
419 }
421 void
422 lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B) {
423 Geom::Coord w = sp_document_width(document);
424 Geom::Coord h = sp_document_height(document);
425 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
427 double ulx = prefs->getDouble("/tools/lpetool/bbox_upperleftx", 0);
428 double uly = prefs->getDouble("/tools/lpetool/bbox_upperlefty", 0);
429 double lrx = prefs->getDouble("/tools/lpetool/bbox_lowerrightx", w);
430 double lry = prefs->getDouble("/tools/lpetool/bbox_lowerrighty", h);
432 A = Geom::Point(ulx, uly);
433 B = Geom::Point(lrx, lry);
434 }
436 /*
437 * Reads the limiting bounding box from preferences and draws it on the screen
438 */
439 // TODO: Note that currently the bbox is not user-settable; we simply use the page borders
440 void
441 lpetool_context_reset_limiting_bbox(SPLPEToolContext *lc)
442 {
443 if (lc->canvas_bbox) {
444 gtk_object_destroy(GTK_OBJECT(lc->canvas_bbox));
445 lc->canvas_bbox = NULL;
446 }
448 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
449 if (!prefs->getBool("/tools/lpetool/show_bbox", true))
450 return;
452 SPDocument *document = sp_desktop_document(lc->desktop);
454 Geom::Point A, B;
455 lpetool_get_limiting_bbox_corners(document, A, B);
456 NR::Matrix doc2dt(lc->desktop->doc2dt());
457 A *= doc2dt;
458 B *= doc2dt;
460 Geom::Rect rect(A, B);
461 SPCurve *curve = SPCurve::new_from_rect(rect);
463 lc->canvas_bbox = sp_canvas_bpath_new (sp_desktop_controls(lc->desktop), curve);
464 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(lc->canvas_bbox), 0x0000ffff, 0.8, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 5);
465 }
467 static void
468 set_pos_and_anchor(SPCanvasText *canvas_text, const Geom::Piecewise<Geom::D2<Geom::SBasis> > &pwd2,
469 const double t, const double length, bool use_curvature = false)
470 {
471 using namespace Geom;
473 Piecewise<D2<SBasis> > pwd2_reparam = arc_length_parametrization(pwd2, 2 , 0.1);
474 double t_reparam = pwd2_reparam.cuts.back() * t;
475 Point pos = pwd2_reparam.valueAt(t_reparam);
476 Point dir = unit_vector(derivative(pwd2_reparam).valueAt(t_reparam));
477 Point n = -rot90(dir);
478 double angle = Geom::angle_between(dir, Point(1,0));
480 sp_canvastext_set_coords(canvas_text, pos + n * length);
481 sp_canvastext_set_anchor(canvas_text, std::sin(angle), -std::cos(angle));
482 }
484 void
485 lpetool_create_measuring_items(SPLPEToolContext *lc, Inkscape::Selection *selection)
486 {
487 if (!selection) {
488 selection = sp_desktop_selection(lc->desktop);
489 }
490 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
491 bool show = prefs->getBool("/tools/lpetool/show_measuring_info", true);
493 SPPath *path;
494 SPCurve *curve;
495 SPCanvasText *canvas_text;
496 SPCanvasGroup *tmpgrp = sp_desktop_tempgroup(lc->desktop);
497 gchar *arc_length;
498 double lengthval;
500 for (GSList const *i = selection->itemList(); i != NULL; i = i->next) {
501 if (SP_IS_PATH(i->data)) {
502 path = SP_PATH(i->data);
503 curve = sp_shape_get_curve(SP_SHAPE(path));
504 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = paths_to_pw(curve->get_pathvector());
505 canvas_text = (SPCanvasText *) sp_canvastext_new(tmpgrp, lc->desktop, Geom::Point(0,0), "");
506 if (!show)
507 sp_canvas_item_hide(SP_CANVAS_ITEM(canvas_text));
509 SPUnitId unitid = static_cast<SPUnitId>(prefs->getInt("/tools/lpetool/unitid", SP_UNIT_PX));
510 SPUnit unit = sp_unit_get_by_id(unitid);
512 lengthval = Geom::length(pwd2);
513 gboolean success;
514 success = sp_convert_distance(&lengthval, &sp_unit_get_by_id(SP_UNIT_PX), &unit);
515 arc_length = g_strdup_printf("%.2f %s", lengthval, success ? sp_unit_get_abbreviation(&unit) : "px");
516 sp_canvastext_set_text (canvas_text, arc_length);
517 set_pos_and_anchor(canvas_text, pwd2, 0.5, 10);
518 // TODO: must we free arc_length?
519 (*lc->measuring_items)[path] = SP_CANVAS_ITEM(canvas_text);
520 }
521 }
522 }
524 void
525 lpetool_delete_measuring_items(SPLPEToolContext *lc)
526 {
527 std::map<SPPath *, SPCanvasItem*>::iterator i;
528 for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
529 gtk_object_destroy(GTK_OBJECT(i->second));
530 }
531 lc->measuring_items->clear();
532 }
534 void
535 lpetool_update_measuring_items(SPLPEToolContext *lc)
536 {
537 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
538 SPPath *path;
539 SPCurve *curve;
540 double lengthval;
541 gchar *arc_length;
542 std::map<SPPath *, SPCanvasItem*>::iterator i;
543 for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
544 path = i->first;
545 curve = sp_shape_get_curve(SP_SHAPE(path));
546 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = Geom::paths_to_pw(curve->get_pathvector());
547 SPUnitId unitid = static_cast<SPUnitId>(prefs->getInt("/tools/lpetool/unitid", SP_UNIT_PX));
548 SPUnit unit = sp_unit_get_by_id(unitid);
549 lengthval = Geom::length(pwd2);
550 gboolean success;
551 success = sp_convert_distance(&lengthval, &sp_unit_get_by_id(SP_UNIT_PX), &unit);
552 arc_length = g_strdup_printf("%.2f %s", lengthval, success ? sp_unit_get_abbreviation(&unit) : "px");
553 sp_canvastext_set_text (SP_CANVASTEXT(i->second), arc_length);
554 set_pos_and_anchor(SP_CANVASTEXT(i->second), pwd2, 0.5, 10);
555 // TODO: must we free arc_length?
556 }
557 }
559 void
560 lpetool_show_measuring_info(SPLPEToolContext *lc, bool show)
561 {
562 std::map<SPPath *, SPCanvasItem*>::iterator i;
563 for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
564 if (show) {
565 sp_canvas_item_show(i->second);
566 } else {
567 sp_canvas_item_hide(i->second);
568 }
569 }
570 }
572 /*
573 Local Variables:
574 mode:c++
575 c-file-style:"stroustrup"
576 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
577 indent-tabs-mode:nil
578 fill-column:99
579 End:
580 */
581 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :