Code

Attempt to fix crashes in geometry tool which I don't see :-/
[inkscape.git] / src / lpe-tool-context.cpp
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 "prefs-utils.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, gchar const *key, gchar const *val);
49 static gint sp_lpetool_context_root_handler(SPEventContext *ec, GdkEvent *event);
51 void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data);
53 const int num_subtools = 8;
55 Inkscape::LivePathEffect::EffectType lpesubtools[] = {
56     Inkscape::LivePathEffect::INVALID_LPE, // this must be here to account for the "all inactive" action
57     Inkscape::LivePathEffect::LINE_SEGMENT,
58     Inkscape::LivePathEffect::CIRCLE_3PTS,
59     Inkscape::LivePathEffect::CIRCLE_WITH_RADIUS,
60     Inkscape::LivePathEffect::PARALLEL,
61     Inkscape::LivePathEffect::PERP_BISECTOR,
62     Inkscape::LivePathEffect::ANGLE_BISECTOR,
63     Inkscape::LivePathEffect::MIRROR_SYMMETRY,
64 };
66 static SPPenContextClass *lpetool_parent_class = 0;
68 GType sp_lpetool_context_get_type(void)
69 {
70     static GType type = 0;
71     if (!type) {
72         GTypeInfo info = {
73             sizeof(SPLPEToolContextClass),
74             0, // base_init
75             0, // base_finalize
76             (GClassInitFunc)sp_lpetool_context_class_init,
77             0, // class_finalize
78             0, // class_data
79             sizeof(SPLPEToolContext),
80             0, // n_preallocs
81             (GInstanceInitFunc)sp_lpetool_context_init,
82             0 // value_table
83         };
84         type = g_type_register_static(SP_TYPE_PEN_CONTEXT, "SPLPEToolContext", &info, static_cast<GTypeFlags>(0));
85     }
86     return type;
87 }
89 static void
90 sp_lpetool_context_class_init(SPLPEToolContextClass *klass)
91 {
92     GObjectClass *object_class = (GObjectClass *) klass;
93     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
95     lpetool_parent_class = (SPPenContextClass*)g_type_class_peek_parent(klass);
97     object_class->dispose = sp_lpetool_context_dispose;
99     event_context_class->setup = sp_lpetool_context_setup;
100     event_context_class->set = sp_lpetool_context_set;
101     event_context_class->root_handler = sp_lpetool_context_root_handler;
104 static void
105 sp_lpetool_context_init(SPLPEToolContext *lc)
107     lc->cursor_shape = cursor_crosshairs_xpm;
108     lc->hot_x = 7;
109     lc->hot_y = 7;
111     lc->canvas_bbox = NULL;
112     lc->measuring_items = new std::map<SPPath *, SPCanvasItem*>;
114     new (&lc->sel_changed_connection) sigc::connection();
117 static void
118 sp_lpetool_context_dispose(GObject *object)
120     SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(object);
121     delete lc->shape_editor;
123     if (lc->canvas_bbox) {
124         gtk_object_destroy(GTK_OBJECT(lc->canvas_bbox));
125         lc->canvas_bbox = NULL;
126     }
128     lpetool_delete_measuring_items(lc);
129     delete lc->measuring_items;
130     lc->measuring_items = NULL;
132     lc->sel_changed_connection.disconnect();
133     lc->sel_changed_connection.~connection();
135     if (lc->_lpetool_message_context) {
136         delete lc->_lpetool_message_context;
137     }
139     G_OBJECT_CLASS(lpetool_parent_class)->dispose(object);
142 static void
143 sp_lpetool_context_setup(SPEventContext *ec)
145     SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(ec);
147     if (((SPEventContextClass *) lpetool_parent_class)->setup)
148         ((SPEventContextClass *) lpetool_parent_class)->setup(ec);
150     Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
151     SPItem *item = selection->singleItem();
153     lc->sel_changed_connection.disconnect();
154     lc->sel_changed_connection =
155         selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_lpetool_context_selection_changed), (gpointer)lc));
157     lc->shape_editor = new ShapeEditor(ec->desktop);
159     lpetool_context_switch_mode(lc, Inkscape::LivePathEffect::INVALID_LPE);
160     lpetool_context_reset_limiting_bbox(lc);
161     lpetool_create_measuring_items(lc);
163 // TODO temp force:
164     ec->enableSelectionCue();
166     if (item) {
167         lc->shape_editor->set_item(item, SH_NODEPATH);
168         lc->shape_editor->set_item(item, SH_KNOTHOLDER);
169     }
171     if (prefs_get_int_attribute("tools.lpetool", "selcue", 0) != 0) {
172         ec->enableSelectionCue();
173     }
175     lc->_lpetool_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
178     lc->shape_editor->update_statusbar();
181 /**
182 \brief  Callback that processes the "changed" signal on the selection;
183 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
184 */
185 void
186 sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data)
188     SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(data);
190     // TODO: update ShapeEditorsCollective instead
191     lc->shape_editor->unset_item(SH_NODEPATH);
192     lc->shape_editor->unset_item(SH_KNOTHOLDER);
193     SPItem *item = selection->singleItem();
194     lc->shape_editor->set_item(item, SH_NODEPATH);
195     lc->shape_editor->set_item(item, SH_KNOTHOLDER);
196     lc->shape_editor->update_statusbar();
199 static void
200 sp_lpetool_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
202     // FIXME: how to set this correcly? the value from preferences-skeleton.h doesn't seem to get
203     // read (it wants to set drag = 1)
204     lpetool_parent_class->set(ec, key, "drag");
206     /**
207     //pass on up to parent class to handle common attributes.
208     if ( lpetool_parent_class->set ) {
209         lpetool_parent_class->set(ec, key, val);
210     }
211     **/
214 gint
215 sp_lpetool_context_root_handler(SPEventContext *event_context, GdkEvent *event)
217     SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(event_context);
218     SPDesktop *desktop = event_context->desktop;
219     Inkscape::Selection *selection = sp_desktop_selection (desktop);
221     bool ret = false;
223     if (sp_pen_context_has_waiting_LPE(lc)) {
224         // quit when we are waiting for a LPE to be applied
225         ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event);
226         return ret;
227     }
229     switch (event->type) {
230         case GDK_BUTTON_PRESS:
231             if (event->button.button == 1 && !event_context->space_panning) {
232                 if (lc->mode == Inkscape::LivePathEffect::INVALID_LPE) {
233                     // don't do anything for now if we are inactive
234                     desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Choose a construction tool from the toolbar."));
235                     ret = true;
236                     break;
237                 }
239                 // save drag origin
240                 event_context->xp = (gint) event->button.x;
241                 event_context->yp = (gint) event->button.y;
242                 event_context->within_tolerance = true;
243                 lc->shape_editor->cancel_hit();
245                 using namespace Inkscape::LivePathEffect;
247                 int mode = prefs_get_int_attribute("tools.lpetool", "mode", 0);
248                 EffectType type = lpesubtools[mode];
250                 //bool over_stroke = lc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
252                 sp_pen_context_wait_for_LPE_mouse_clicks(lc, type, Inkscape::LivePathEffect::Effect::acceptsNumClicks(type));
254                 // we pass the mouse click on to pen tool as the first click which it should collect
255                 ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event);
256             }
257             break;
258         case GDK_MOTION_NOTIFY:
259         {
260             if (!lc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
261                 break;
262             }
264             bool over_stroke = false;
265             over_stroke = lc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
267             if (over_stroke) {
268                 event_context->cursor_shape = cursor_node_xpm;
269                 event_context->hot_x = 1;
270                 event_context->hot_y = 1;
271                 sp_event_context_update_cursor(event_context);
272             } else {
273                 lc->cursor_shape = cursor_crosshairs_xpm;
274                 lc->hot_x = 7;
275                 lc->hot_y = 7;
276                 sp_event_context_update_cursor(event_context);
277             }
278         }
279         break;
282     case GDK_BUTTON_RELEASE:
283     {
284         /**
285         break;
286         **/
287     }
289     case GDK_KEY_PRESS:
290         /**
291         switch (get_group0_keyval (&event->key)) {
292         }
293         break;
294         **/
296     case GDK_KEY_RELEASE:
297         /**
298         switch (get_group0_keyval(&event->key)) {
299             case GDK_Control_L:
300             case GDK_Control_R:
301                 dc->_message_context->clear();
302                 break;
303             default:
304                 break;
305         }
306         **/
308     default:
309         break;
310     }
312     if (!ret) {
313         if (((SPEventContextClass *) lpetool_parent_class)->root_handler) {
314             ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event);
315         }
316     }
318     return ret;
321 /*
322  * Finds the index in the list of geometric subtools corresponding to the given LPE type.
323  * Returns -1 if no subtool is found.
324  */
325 int
326 lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type) {
327     for (int i = 0; i < num_subtools; ++i) {
328         if (lpesubtools[i] == type) {
329             return i;
330         }
331     }
332     return -1;
335 /*
336  * Checks whether an item has a construction applied as LPE and if so returns the index in
337  * lpesubtools of this construction
338  */
339 int lpetool_item_has_construction(SPLPEToolContext *lc, SPItem *item)
341     if (!SP_IS_LPE_ITEM(item)) {
342         return -1;
343     }
345     Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
346     if (!lpe) {
347         return -1;
348     }
349     return lpetool_mode_to_index(lpe->effectType());
352 /*
353  * Attempts to perform the construction of the given type (i.e., to apply the corresponding LPE) to
354  * a single selected item. Returns whether we succeeded.
355  */
356 bool
357 lpetool_try_construction(SPLPEToolContext *lc, Inkscape::LivePathEffect::EffectType const type)
359     Inkscape::Selection *selection = sp_desktop_selection(lc->desktop);
360     SPItem *item = selection->singleItem();
362     // TODO: should we check whether type represents a valid geometric construction?
363     if (item && SP_IS_LPE_ITEM(item) && Inkscape::LivePathEffect::Effect::acceptsNumClicks(type) == 0) {
364         Inkscape::LivePathEffect::Effect::createAndApply(type, sp_desktop_document(lc->desktop), item);
365         return true;
366     }
367     return false;
370 void
371 lpetool_context_switch_mode(SPLPEToolContext *lc, Inkscape::LivePathEffect::EffectType const type)
373     int index = lpetool_mode_to_index(type);
374     if (index != -1) {
375         lc->mode = type;
376         lc->desktop->setToolboxSelectOneValue ("lpetool_mode_action", index);
377     } else {
378         g_warning ("Invalid mode selected: %d", type);
379         return;
380     }
383 void
384 lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B) {
385     Geom::Coord w = sp_document_width(document);
386     Geom::Coord h = sp_document_height(document);
388     double ulx = prefs_get_double_attribute ("tools.lpetool", "bbox_upperleftx", 0);
389     double uly = prefs_get_double_attribute ("tools.lpetool", "bbox_upperlefty", 0);
390     double lrx = prefs_get_double_attribute ("tools.lpetool", "bbox_lowerrightx", w);
391     double lry = prefs_get_double_attribute ("tools.lpetool", "bbox_lowerrighty", h);
393     A = Geom::Point(ulx, uly);
394     B = Geom::Point(lrx, lry);
397 /*
398  * Reads the limiting bounding box from preferences and draws it on the screen
399  */
400 // TODO: Note that currently the bbox is not user-settable; we simply use the page borders
401 void
402 lpetool_context_reset_limiting_bbox(SPLPEToolContext *lc)
404     if (lc->canvas_bbox) {
405         gtk_object_destroy(GTK_OBJECT(lc->canvas_bbox));
406         lc->canvas_bbox = NULL;
407     }
409     if (prefs_get_int_attribute("tools.lpetool", "show_bbox", 1) == 0)
410         return;
412     SPDocument *document = sp_desktop_document(lc->desktop);
414     Geom::Point A, B;
415     lpetool_get_limiting_bbox_corners(document, A, B);
416     NR::Matrix doc2dt(lc->desktop->doc2dt());
417     A *= doc2dt;
418     B *= doc2dt;
420     Geom::Rect rect(A, B);
421     SPCurve *curve = SPCurve::new_from_rect(rect);
423     lc->canvas_bbox = sp_canvas_bpath_new (sp_desktop_controls(lc->desktop), curve);
424     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(lc->canvas_bbox), 0x0000ffff, 0.8, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 5);
427 static void
428 set_pos_and_anchor(SPCanvasText *canvas_text, const Geom::Piecewise<Geom::D2<Geom::SBasis> > &pwd2,
429                    const double t, const double length, bool use_curvature = false)
431     using namespace Geom;
433     Piecewise<D2<SBasis> > pwd2_reparam = arc_length_parametrization(pwd2, 2 , 0.1);
434     double t_reparam = pwd2_reparam.cuts.back() * t;
435     Point pos = pwd2_reparam.valueAt(t_reparam);
436     Point dir = unit_vector(derivative(pwd2_reparam).valueAt(t_reparam));
437     Point n = -rot90(dir);
438     double angle = Geom::angle_between(dir, Point(1,0));
440     sp_canvastext_set_coords(canvas_text, pos + n * length);
441     sp_canvastext_set_anchor(canvas_text, std::sin(angle), -std::cos(angle));
444 void
445 lpetool_create_measuring_items(SPLPEToolContext *lc, Inkscape::Selection *selection)
447     bool show = prefs_get_int_attribute ("tools.lpetool", "show_measuring_info",  1) == 1 ? true : false;
448     if (!selection) {
449         selection = sp_desktop_selection(lc->desktop);
450     }
452     SPPath *path;
453     SPCurve *curve;
454     SPCanvasText *canvas_text;
455     SPCanvasGroup *tmpgrp = sp_desktop_tempgroup(lc->desktop);
456     gchar *arc_length;
457     double lengthval;
459     for (GSList const *i = selection->itemList(); i != NULL; i = i->next) {
460         if (SP_IS_PATH(i->data)) {
461             path = SP_PATH(i->data);
462             curve = sp_shape_get_curve(SP_SHAPE(path));
463             Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = paths_to_pw(curve->get_pathvector());
464             canvas_text = (SPCanvasText *) sp_canvastext_new(tmpgrp, lc->desktop, Geom::Point(0,0), "");
465             if (!show)
466                 sp_canvas_item_hide(SP_CANVAS_ITEM(canvas_text));
468             SPUnitId unitid = static_cast<SPUnitId>(prefs_get_int_attribute("tools.lpetool", "unitid", SP_UNIT_PX));
469             SPUnit unit = sp_unit_get_by_id(unitid);
471             lengthval = Geom::length(pwd2);
472             gboolean success;
473             success = sp_convert_distance(&lengthval, &sp_unit_get_by_id(SP_UNIT_PX), &unit);
474             arc_length = g_strdup_printf("%.2f %s", lengthval, success ? sp_unit_get_abbreviation(&unit) : "px");
475             sp_canvastext_set_text (canvas_text, arc_length);
476             set_pos_and_anchor(canvas_text, pwd2, 0.5, 10);
477             // TODO: must we free arc_length?
478             (*lc->measuring_items)[path] = SP_CANVAS_ITEM(canvas_text);
479         }
480     }
483 void
484 lpetool_delete_measuring_items(SPLPEToolContext *lc)
486     std::map<SPPath *, SPCanvasItem*>::iterator i;
487     for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
488         gtk_object_destroy(GTK_OBJECT(i->second));
489     }
490     lc->measuring_items->clear();
493 void
494 lpetool_update_measuring_items(SPLPEToolContext *lc)
496     SPPath *path;
497     SPCurve *curve;
498     double lengthval;
499     gchar *arc_length;
500     std::map<SPPath *, SPCanvasItem*>::iterator i;
501     for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
502         path = i->first;
503         curve = sp_shape_get_curve(SP_SHAPE(path));
504         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = Geom::paths_to_pw(curve->get_pathvector());
505         SPUnitId unitid = static_cast<SPUnitId>(prefs_get_int_attribute("tools.lpetool", "unitid", SP_UNIT_PX));
506         SPUnit unit = sp_unit_get_by_id(unitid);
507         lengthval = Geom::length(pwd2);
508         gboolean success;
509         success = sp_convert_distance(&lengthval, &sp_unit_get_by_id(SP_UNIT_PX), &unit);
510         arc_length = g_strdup_printf("%.2f %s", lengthval, success ? sp_unit_get_abbreviation(&unit) : "px");
511         sp_canvastext_set_text (SP_CANVASTEXT(i->second), arc_length);
512         set_pos_and_anchor(SP_CANVASTEXT(i->second), pwd2, 0.5, 10);
513         // TODO: must we free arc_length?
514     }
517 void
518 lpetool_show_measuring_info(SPLPEToolContext *lc, bool show)
520     std::map<SPPath *, SPCanvasItem*>::iterator i;
521     for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
522         if (show) {
523             sp_canvas_item_show(i->second);
524         } else {
525             sp_canvas_item_hide(i->second);
526         }
527     }
530 /*
531   Local Variables:
532   mode:c++
533   c-file-style:"stroustrup"
534   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
535   indent-tabs-mode:nil
536   fill-column:99
537   End:
538 */
539 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :