Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / arc-context.cpp
1 /** @file
2  * @brief Ellipse drawing context
3  */
4 /* Authors:
5  *   Mitsuru Oka
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Johan Engelen <johan@shouraizou.nl>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2000-2006 Authors
12  * Copyright (C) 2000-2001 Ximian, Inc.
13  *
14  * Released under GNU GPL, read the file 'COPYING' for more information
15  */
17 #define __SP_ARC_CONTEXT_C__
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <gdk/gdkkeysyms.h>
24 #include "macros.h"
25 #include <glibmm/i18n.h>
26 #include "display/sp-canvas.h"
27 #include "sp-ellipse.h"
28 #include "document.h"
29 #include "sp-namedview.h"
30 #include "selection.h"
31 #include "desktop-handles.h"
32 #include "snap.h"
33 #include "pixmaps/cursor-ellipse.xpm"
34 #include "sp-metrics.h"
35 #include "xml/repr.h"
36 #include "xml/node-event-vector.h"
37 #include "object-edit.h"
38 #include "preferences.h"
39 #include "message-context.h"
40 #include "desktop.h"
41 #include "desktop-style.h"
42 #include "context-fns.h"
43 #include "verbs.h"
44 #include "shape-editor.h"
45 #include "event-context.h"
47 #include "arc-context.h"
49 using Inkscape::DocumentUndo;
51 static void sp_arc_context_class_init(SPArcContextClass *klass);
52 static void sp_arc_context_init(SPArcContext *arc_context);
53 static void sp_arc_context_dispose(GObject *object);
55 static void sp_arc_context_setup(SPEventContext *ec);
56 static void sp_arc_context_finish(SPEventContext *ec);
57 static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent *event);
58 static gint sp_arc_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
60 static void sp_arc_drag(SPArcContext *ec, Geom::Point pt, guint state);
61 static void sp_arc_finish(SPArcContext *ec);
62 static void sp_arc_cancel(SPArcContext *ec);
64 static SPEventContextClass *parent_class;
66 GtkType sp_arc_context_get_type()
67 {
68     static GType type = 0;
69     if (!type) {
70         GTypeInfo info = {
71             sizeof(SPArcContextClass),
72             NULL, NULL,
73             (GClassInitFunc) sp_arc_context_class_init,
74             NULL, NULL,
75             sizeof(SPArcContext),
76             4,
77             (GInstanceInitFunc) sp_arc_context_init,
78             NULL,   /* value_table */
79         };
80         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPArcContext", &info, (GTypeFlags) 0);
81     }
82     return type;
83 }
85 static void sp_arc_context_class_init(SPArcContextClass *klass)
86 {
88     GObjectClass *object_class = (GObjectClass *) klass;
89     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
91     parent_class = (SPEventContextClass*) g_type_class_peek_parent(klass);
93     object_class->dispose = sp_arc_context_dispose;
95     event_context_class->setup = sp_arc_context_setup;
96     event_context_class->finish = sp_arc_context_finish;
97     event_context_class->root_handler = sp_arc_context_root_handler;
98     event_context_class->item_handler = sp_arc_context_item_handler;
99 }
101 static void sp_arc_context_init(SPArcContext *arc_context)
103     SPEventContext *event_context = SP_EVENT_CONTEXT(arc_context);
105     event_context->cursor_shape = cursor_ellipse_xpm;
106     event_context->hot_x = 4;
107     event_context->hot_y = 4;
108     event_context->xp = 0;
109     event_context->yp = 0;
110     event_context->tolerance = 0;
111     event_context->within_tolerance = false;
112     event_context->item_to_select = NULL;
113     event_context->tool_url = "/tools/shapes/arc";
115     arc_context->item = NULL;
117     new (&arc_context->sel_changed_connection) sigc::connection();
120 static void sp_arc_context_finish(SPEventContext *ec)
122     SPArcContext *ac = SP_ARC_CONTEXT(ec);
123     SPDesktop *desktop = ec->desktop;
125     sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME);
126     sp_arc_finish(ac);
127     ac->sel_changed_connection.disconnect();
129     if (((SPEventContextClass *) parent_class)->finish) {
130         ((SPEventContextClass *) parent_class)->finish(ec);
131     }
134 static void sp_arc_context_dispose(GObject *object)
136     SPEventContext *ec = SP_EVENT_CONTEXT(object);
137     SPArcContext *ac = SP_ARC_CONTEXT(object);
139     ec->enableGrDrag(false);
141     ac->sel_changed_connection.disconnect();
142     ac->sel_changed_connection.~connection();
144     delete ec->shape_editor;
145     ec->shape_editor = NULL;
147     /* fixme: This is necessary because we do not grab */
148     if (ac->item) {
149         sp_arc_finish(ac);
150     }
152     delete ac->_message_context;
154     G_OBJECT_CLASS(parent_class)->dispose(object);
157 /**
158 \brief  Callback that processes the "changed" signal on the selection;
159 destroys old and creates new knotholder.
160 */
161 void sp_arc_context_selection_changed(Inkscape::Selection * selection, gpointer data)
163     SPArcContext *ac = SP_ARC_CONTEXT(data);
164     SPEventContext *ec = SP_EVENT_CONTEXT(ac);
166     ec->shape_editor->unset_item(SH_KNOTHOLDER);
167     SPItem *item = selection->singleItem();
168     ec->shape_editor->set_item(item, SH_KNOTHOLDER);
171 static void sp_arc_context_setup(SPEventContext *ec)
173     SPArcContext *ac = SP_ARC_CONTEXT(ec);
174     Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
176     if (((SPEventContextClass *) parent_class)->setup) {
177         ((SPEventContextClass *) parent_class)->setup(ec);
178     }
180     ec->shape_editor = new ShapeEditor(ec->desktop);
182     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
183     if (item) {
184         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
185     }
187     ac->sel_changed_connection.disconnect();
188     ac->sel_changed_connection = selection->connectChanged(
189         sigc::bind(sigc::ptr_fun(&sp_arc_context_selection_changed), (gpointer) ac)
190         );
192     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
193     if (prefs->getBool("/tools/shapes/selcue")) {
194         ec->enableSelectionCue();
195     }
197     if (prefs->getBool("/tools/shapes/gradientdrag")) {
198         ec->enableGrDrag();
199     }
201     ac->_message_context = new Inkscape::MessageContext(ec->desktop->messageStack());
204 static gint sp_arc_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
206     SPDesktop *desktop = event_context->desktop;
207     gint ret = FALSE;
209     switch (event->type) {
210         case GDK_BUTTON_PRESS:
211             if (event->button.button == 1 && !event_context->space_panning) {
212                 Inkscape::setup_for_drag_start(desktop, event_context, event);
213                 ret = TRUE;
214             }
215             break;
216             // motion and release are always on root (why?)
217         default:
218             break;
219     }
221     if (((SPEventContextClass *) parent_class)->item_handler) {
222         ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
223     }
225     return ret;
228 static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent *event)
230     static bool dragging;
232     SPDesktop *desktop = event_context->desktop;
233     Inkscape::Selection *selection = sp_desktop_selection(desktop);
234     SPArcContext *ac = SP_ARC_CONTEXT(event_context);
235     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
237     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
239     gint ret = FALSE;
241     switch (event->type) {
242         case GDK_BUTTON_PRESS:
243             if (event->button.button == 1 && !event_context->space_panning) {
245                 dragging = true;
246                 ac->center = Inkscape::setup_for_drag_start(desktop, event_context, event);
248                 /* Snap center */
249                 SnapManager &m = desktop->namedview->snap_manager;
250                 m.setup(desktop);
251                 m.freeSnapReturnByRef(ac->center, Inkscape::SNAPSOURCE_NODE_HANDLE);
253                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
254                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
255                                     GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
256                                     NULL, event->button.time);
257                 ret = TRUE;
258                 m.unSetup();
259             }
260             break;
261         case GDK_MOTION_NOTIFY:
262             if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !event_context->space_panning) {
264                 if ( event_context->within_tolerance
265                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
266                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
267                     break; // do not drag if we're within tolerance from origin
268                 }
269                 // Once the user has moved farther than tolerance from the original location
270                 // (indicating they intend to draw, not click), then always process the
271                 // motion notify coordinates as given (no snapping back to origin)
272                 event_context->within_tolerance = false;
274                 Geom::Point const motion_w(event->motion.x, event->motion.y);
275                 Geom::Point motion_dt(desktop->w2d(motion_w));
277                 sp_arc_drag(ac, motion_dt, event->motion.state);
279                 gobble_motion_events(GDK_BUTTON1_MASK);
281                 ret = TRUE;
282             } else if (!sp_event_context_knot_mouseover(ac)){
283                 SnapManager &m = desktop->namedview->snap_manager;
284                 m.setup(desktop);
286                 Geom::Point const motion_w(event->motion.x, event->motion.y);
287                 Geom::Point motion_dt(desktop->w2d(motion_w));
288                 m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
289                 m.unSetup();
290             }
291             break;
292         case GDK_BUTTON_RELEASE:
293             event_context->xp = event_context->yp = 0;
294             if (event->button.button == 1 && !event_context->space_panning) {
295                 dragging = false;
296                 sp_event_context_discard_delayed_snap_event(event_context);
297                 if (!event_context->within_tolerance) {
298                     // we've been dragging, finish the arc
299                        sp_arc_finish(ac);
300                 } else if (event_context->item_to_select) {
301                     // no dragging, select clicked item if any
302                     if (event->button.state & GDK_SHIFT_MASK) {
303                         selection->toggle(event_context->item_to_select);
304                     } else {
305                         selection->set(event_context->item_to_select);
306                     }
307                 } else {
308                     // click in an empty space
309                     selection->clear();
310                 }
311                 event_context->xp = 0;
312                 event_context->yp = 0;
313                 event_context->item_to_select = NULL;
314                 ret = TRUE;
315             }
316             sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
317             break;
318         case GDK_KEY_PRESS:
319             switch (get_group0_keyval (&event->key)) {
320                 case GDK_Alt_L:
321                 case GDK_Alt_R:
322                 case GDK_Control_L:
323                 case GDK_Control_R:
324                 case GDK_Shift_L:
325                 case GDK_Shift_R:
326                 case GDK_Meta_L:  // Meta is when you press Shift+Alt (at least on my machine)
327                 case GDK_Meta_R:
328                     if (!dragging) {
329                         sp_event_show_modifier_tip(event_context->defaultMessageContext(), event,
330                                                    _("<b>Ctrl</b>: make circle or integer-ratio ellipse, snap arc/segment angle"),
331                                                    _("<b>Shift</b>: draw around the starting point"),
332                                                    NULL);
333                     }
334                     break;
335                 case GDK_Up:
336                 case GDK_Down:
337                 case GDK_KP_Up:
338                 case GDK_KP_Down:
339                     // prevent the zoom field from activation
340                     if (!MOD__CTRL_ONLY)
341                         ret = TRUE;
342                     break;
343                 case GDK_x:
344                 case GDK_X:
345                     if (MOD__ALT_ONLY) {
346                         desktop->setToolboxFocusTo ("altx-arc");
347                         ret = TRUE;
348                     }
349                     break;
350                 case GDK_Escape:
351                     if (dragging) {
352                         dragging = false;
353                         sp_event_context_discard_delayed_snap_event(event_context);
354                         // if drawing, cancel, otherwise pass it up for deselecting
355                         sp_arc_cancel(ac);
356                         ret = TRUE;
357                     }
358                     break;
359                 case GDK_space:
360                     if (dragging) {
361                         sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
362                                               event->button.time);
363                         dragging = false;
364                         sp_event_context_discard_delayed_snap_event(event_context);
365                         if (!event_context->within_tolerance) {
366                             // we've been dragging, finish the arc
367                             sp_arc_finish(ac);
368                         }
369                         // do not return true, so that space would work switching to selector
370                     }
371                     break;
373                 default:
374                     break;
375             }
376             break;
377         case GDK_KEY_RELEASE:
378             switch (event->key.keyval) {
379                 case GDK_Alt_L:
380                 case GDK_Alt_R:
381                 case GDK_Control_L:
382                 case GDK_Control_R:
383                 case GDK_Shift_L:
384                 case GDK_Shift_R:
385                 case GDK_Meta_L:  // Meta is when you press Shift+Alt
386                 case GDK_Meta_R:
387                     event_context->defaultMessageContext()->clear();
388                     break;
389                 default:
390                     break;
391             }
392             break;
393         default:
394             break;
395     }
397     if (!ret) {
398         if (((SPEventContextClass *) parent_class)->root_handler) {
399             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
400         }
401     }
403     return ret;
406 static void sp_arc_drag(SPArcContext *ac, Geom::Point pt, guint state)
408     SPDesktop *desktop = SP_EVENT_CONTEXT(ac)->desktop;
410     if (!ac->item) {
412         if (Inkscape::have_viable_layer(desktop, ac->_message_context) == false) {
413             return;
414         }
416         // Create object
417         Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
418         Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
419         repr->setAttribute("sodipodi:type", "arc");
421         // Set style
422         sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/arc", false);
424         ac->item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
425         Inkscape::GC::release(repr);
426         ac->item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
427         ac->item->updateRepr();
429         sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5);
430     }
432     bool ctrl_save = false;
433     if ((state & GDK_MOD1_MASK) && (state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) {
434         // if Alt is pressed without Shift in addition to Control, temporarily drop the CONTROL mask
435         // so that the ellipse is not constrained to integer ratios
436         ctrl_save = true;
437         state = state ^ GDK_CONTROL_MASK;
438     }
439     Geom::Rect r = Inkscape::snap_rectangular_box(desktop, ac->item, pt, ac->center, state);
440     if (ctrl_save) {
441         state = state ^ GDK_CONTROL_MASK;
442     }
444     Geom::Point dir = r.dimensions() / 2;
445     if (state & GDK_MOD1_MASK) {
446         /* With Alt let the ellipse pass through the mouse pointer */
447         Geom::Point c = r.midpoint();
448         if (!ctrl_save) {
449             if (fabs(dir[Geom::X]) > 1E-6 && fabs(dir[Geom::Y]) > 1E-6) {
450                 Geom::Matrix const i2d ((ac->item)->i2d_affine ());
451                 Geom::Point new_dir = pt * i2d - c;
452                 new_dir[Geom::X] *= dir[Geom::Y] / dir[Geom::X];
453                 double lambda = new_dir.length() / dir[Geom::Y];
454                 r = Geom::Rect (c - lambda*dir, c + lambda*dir);
455             }
456         } else {
457             /* with Alt+Ctrl (without Shift) we generate a perfect circle
458                with diameter click point <--> mouse pointer */
459                 double l = dir.length();
460                 Geom::Point d (l, l);
461                 r = Geom::Rect (c - d, c + d);
462         }
463     }
465     sp_arc_position_set(SP_ARC(ac->item),
466                         r.midpoint()[Geom::X], r.midpoint()[Geom::Y],
467                         r.dimensions()[Geom::X] / 2, r.dimensions()[Geom::Y] / 2);
469     double rdimx = r.dimensions()[Geom::X];
470     double rdimy = r.dimensions()[Geom::Y];
471     GString *xs = SP_PX_TO_METRIC_STRING(rdimx, desktop->namedview->getDefaultMetric());
472     GString *ys = SP_PX_TO_METRIC_STRING(rdimy, desktop->namedview->getDefaultMetric());
473     if (state & GDK_CONTROL_MASK) {
474         int ratio_x, ratio_y;
475         if (fabs (rdimx) > fabs (rdimy)) {
476             ratio_x = (int) rint (rdimx / rdimy);
477             ratio_y = 1;
478         } else {
479             ratio_x = 1;
480             ratio_y = (int) rint (rdimy / rdimx);
481         }
482         ac->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Ellipse</b>: %s &#215; %s (constrained to ratio %d:%d); with <b>Shift</b> to draw around the starting point"), xs->str, ys->str, ratio_x, ratio_y);
483     } else {
484         ac->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Ellipse</b>: %s &#215; %s; with <b>Ctrl</b> to make square or integer-ratio ellipse; with <b>Shift</b> to draw around the starting point"), xs->str, ys->str);
485     }
486     g_string_free(xs, FALSE);
487     g_string_free(ys, FALSE);
490 static void sp_arc_finish(SPArcContext *ac)
492     ac->_message_context->clear();
494     if (ac->item != NULL) {
496         SPGenericEllipse *ge = SP_GENERICELLIPSE(SP_ARC(ac->item));
497         if (ge->rx.computed == 0 || ge->ry.computed == 0) {
498             sp_arc_cancel(ac); // Don't allow the creating of zero sized arc, for example when the start and and point snap to the snap grid point
499             return;
500         }
502         SPDesktop *desktop = SP_EVENT_CONTEXT(ac)->desktop;
504         SP_OBJECT(ac->item)->updateRepr();
506         sp_canvas_end_forced_full_redraws(desktop->canvas);
508         sp_desktop_selection(desktop)->set(ac->item);
509                 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_ARC,
510                          _("Create ellipse"));
512         ac->item = NULL;
513     }
516 static void sp_arc_cancel(SPArcContext *ac)
518     SPDesktop *desktop = SP_EVENT_CONTEXT(ac)->desktop;
520     sp_desktop_selection(desktop)->clear();
521     sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0);
523     if (ac->item != NULL) {
524         SP_OBJECT(ac->item)->deleteObject();
525         ac->item = NULL;
526     }
528     ac->within_tolerance = false;
529     ac->xp = 0;
530     ac->yp = 0;
531     ac->item_to_select = NULL;
533     sp_canvas_end_forced_full_redraws(desktop->canvas);
535     DocumentUndo::cancel(sp_desktop_document(desktop));
539 /*
540   Local Variables:
541   mode:c++
542   c-file-style:"stroustrup"
543   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
544   indent-tabs-mode:nil
545   fill-column:99
546   End:
547 */
548 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :