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)
102 {
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();
118 }
120 static void sp_arc_context_finish(SPEventContext *ec)
121 {
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 }
132 }
134 static void sp_arc_context_dispose(GObject *object)
135 {
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);
155 }
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)
162 {
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);
169 }
171 static void sp_arc_context_setup(SPEventContext *ec)
172 {
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());
202 }
204 static gint sp_arc_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
205 {
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;
226 }
228 static gint sp_arc_context_root_handler(SPEventContext *event_context, GdkEvent *event)
229 {
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;
404 }
406 static void sp_arc_drag(SPArcContext *ac, Geom::Point pt, guint state)
407 {
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 × %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 × %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);
488 }
490 static void sp_arc_finish(SPArcContext *ac)
491 {
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 }
514 }
516 static void sp_arc_cancel(SPArcContext *ac)
517 {
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));
536 }
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 :