1 #define __SP_DESKTOP_EVENTS_C__
3 /*
4 * Event handlers for SPDesktop
5 *
6 * Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 *
9 * Copyright (C) 1999-2002 Lauris Kaplinski
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
17 #include <map>
18 #include <string>
19 #include "display/guideline.h"
20 #include "display/snap-indicator.h"
21 #include "helper/unit-menu.h"
22 #include "helper/units.h"
23 #include "desktop.h"
24 #include "document.h"
25 #include "sp-guide.h"
26 #include "sp-namedview.h"
27 #include "desktop-handles.h"
28 #include "event-context.h"
29 #include "widgets/desktop-widget.h"
30 #include "sp-metrics.h"
31 #include <glibmm/i18n.h>
32 #include "dialogs/dialog-events.h"
33 #include "message-context.h"
34 #include "xml/repr.h"
35 #include "dialogs/guidelinedialog.h"
36 #include "snap.h"
37 #include "display/canvas-grid.h"
38 #include "display/canvas-axonomgrid.h"
39 #include "prefs-utils.h"
40 #include "helper/action.h"
41 #include "tools-switch.h"
42 #include <2geom/point.h>
44 static void snoop_extended(GdkEvent* event, SPDesktop *desktop);
45 static void init_extended();
47 /* Root item handler */
49 int sp_desktop_root_handler(SPCanvasItem */*item*/, GdkEvent *event, SPDesktop *desktop)
50 {
51 static bool watch = false;
52 static bool first = true;
54 if ( first ) {
55 if ( prefs_get_int_attribute("options.useextinput", "value", 1)
56 && prefs_get_int_attribute("options.switchonextinput", "value", 0) ) {
57 watch = true;
58 init_extended();
59 }
60 first = false;
61 }
62 if ( watch ) {
63 snoop_extended(event, desktop);
64 }
66 return sp_event_context_root_handler(desktop->event_context, event);
67 }
70 static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw, bool horiz)
71 {
72 static bool dragging = false;
73 static SPCanvasItem *guide = NULL;
74 static Geom::Point normal;
75 int wx, wy;
77 SPDesktop *desktop = dtw->desktop;
78 Inkscape::XML::Node *repr = SP_OBJECT_REPR(desktop->namedview);
80 gdk_window_get_pointer(GTK_WIDGET(dtw->canvas)->window, &wx, &wy, NULL);
81 NR::Point const event_win(wx, wy);
83 gint width, height;
84 gdk_window_get_geometry(GTK_WIDGET(dtw->canvas)->window, NULL /*x*/, NULL /*y*/, &width, &height, NULL/*depth*/);
86 switch (event->type) {
87 case GDK_BUTTON_PRESS:
88 if (event->button.button == 1) {
89 dragging = true;
90 NR::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win));
91 NR::Point const event_dt(desktop->w2d(event_w));
93 // explicitly show guidelines; if I draw a guide, I want them on
94 sp_repr_set_boolean(repr, "showguides", TRUE);
95 sp_repr_set_boolean(repr, "inkscape:guide-bbox", TRUE);
97 // calculate the normal of the guidelines when dragged from the edges of rulers.
98 Geom::Point normal_bl_to_tr(-1.,1.); //bottomleft to topright
99 Geom::Point normal_tr_to_bl(1.,1.); //topright to bottomleft
100 normal_bl_to_tr.normalize();
101 normal_tr_to_bl.normalize();
102 Inkscape::CanvasGrid * grid = sp_namedview_get_first_enabled_grid(desktop->namedview);
103 if ( grid && grid->getGridType() == Inkscape::GRID_AXONOMETRIC ) {
104 Inkscape::CanvasAxonomGrid *axonomgrid = dynamic_cast<Inkscape::CanvasAxonomGrid *>(grid);
105 if (event->button.state & GDK_CONTROL_MASK) {
106 // guidelines normal to gridlines
107 normal_bl_to_tr = Geom::Point::polar(-axonomgrid->angle_rad[0], 1.0);
108 normal_tr_to_bl = Geom::Point::polar(axonomgrid->angle_rad[2], 1.0);
109 } else {
110 normal_bl_to_tr = rot90(Geom::Point::polar(axonomgrid->angle_rad[2], 1.0));
111 normal_tr_to_bl = rot90(Geom::Point::polar(-axonomgrid->angle_rad[0], 1.0));
112 }
113 }
114 if (horiz) {
115 if (wx < 50) {
116 normal = normal_bl_to_tr;
117 } else if (wx > width - 50) {
118 normal = normal_tr_to_bl;
119 } else {
120 normal = Geom::Point(0.,1.);
121 }
122 } else {
123 if (wy < 50) {
124 normal = normal_bl_to_tr;
125 } else if (wy > height - 50) {
126 normal = normal_tr_to_bl;
127 } else {
128 normal = Geom::Point(1.,0.);
129 }
130 }
132 guide = sp_guideline_new(desktop->guides, event_dt.to_2geom(), normal);
133 sp_guideline_set_color(SP_GUIDELINE(guide), desktop->namedview->guidehicolor);
134 gdk_pointer_grab(widget->window, FALSE,
135 (GdkEventMask)(GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK ),
136 NULL, NULL,
137 event->button.time);
138 }
139 break;
140 case GDK_MOTION_NOTIFY:
141 if (dragging) {
142 NR::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win));
143 NR::Point event_dt(desktop->w2d(event_w));
145 SnapManager const &m = desktop->namedview->snap_manager;
146 Inkscape::SnappedPoint snappoint = m.guideSnap(event_dt, normal);
147 event_dt = snappoint.getPoint();
149 sp_guideline_set_position(SP_GUIDELINE(guide), event_dt.to_2geom());
150 desktop->set_coordinate_status(event_dt);
151 desktop->setPosition (event_dt);
153 if (snappoint.getDistance() < NR_HUGE) {
154 desktop->snapindicator->set_new_snappoint(snappoint.getPoint().to_2geom());
155 } else {
156 desktop->snapindicator->remove_snappoint();
157 }
158 }
159 break;
160 case GDK_BUTTON_RELEASE:
161 if (dragging && event->button.button == 1) {
162 gdk_pointer_ungrab(event->button.time);
163 NR::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win));
164 NR::Point event_dt(desktop->w2d(event_w));
166 SnapManager const &m = desktop->namedview->snap_manager;
167 event_dt = m.guideSnap(event_dt, normal).getPoint();
169 dragging = false;
170 gtk_object_destroy(GTK_OBJECT(guide));
171 guide = NULL;
172 if ((horiz ? wy : wx) >= 0) {
173 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
174 Inkscape::XML::Node *repr = xml_doc->createElement("sodipodi:guide");
175 sp_repr_set_point(repr, "orientation", normal);
176 sp_repr_set_point(repr, "position", event_dt.to_2geom());
177 SP_OBJECT_REPR(desktop->namedview)->appendChild(repr);
178 Inkscape::GC::release(repr);
179 sp_document_done(sp_desktop_document(desktop), SP_VERB_NONE,
180 _("Create guide"));
181 }
182 desktop->set_coordinate_status(event_dt);
183 }
184 default:
185 break;
186 }
188 return FALSE;
189 }
191 int sp_dt_hruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw)
192 {
193 return sp_dt_ruler_event(widget, event, dtw, true);
194 }
196 int sp_dt_vruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw)
197 {
198 return sp_dt_ruler_event(widget, event, dtw, false);
199 }
201 /* Guides */
203 gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data)
204 {
205 static bool dragging = false;
206 static bool moved = false;
207 gint ret = FALSE;
209 SPGuide *guide = SP_GUIDE(data);
210 SPDesktop *desktop = static_cast<SPDesktop*>(gtk_object_get_data(GTK_OBJECT(item->canvas), "SPDesktop"));
212 switch (event->type) {
213 case GDK_2BUTTON_PRESS:
214 if (event->button.button == 1) {
215 dragging = false;
216 sp_canvas_item_ungrab(item, event->button.time);
217 Inkscape::UI::Dialogs::GuidelinePropertiesDialog::showDialog(guide, desktop);
218 ret = TRUE;
219 }
220 break;
221 case GDK_BUTTON_PRESS:
222 if (event->button.button == 1) {
223 if (event->button.state & GDK_CONTROL_MASK) {
224 SPDocument *doc = SP_OBJECT_DOCUMENT(guide);
225 sp_guide_remove(guide);
226 sp_document_done(doc, SP_VERB_NONE, _("Delete guide"));
227 ret = TRUE;
228 break;
229 }
230 dragging = true;
231 sp_canvas_item_grab(item,
232 ( GDK_BUTTON_RELEASE_MASK |
233 GDK_BUTTON_PRESS_MASK |
234 GDK_POINTER_MOTION_MASK ),
235 NULL,
236 event->button.time);
237 ret = TRUE;
238 }
239 break;
240 case GDK_MOTION_NOTIFY:
241 if (dragging) {
242 NR::Point const motion_w(event->motion.x,
243 event->motion.y);
244 NR::Point motion_dt(desktop->w2d(motion_w));
246 // This is for snapping while dragging existing guidelines. New guidelines,
247 // which are dragged off the ruler, are being snapped in sp_dt_ruler_event
248 SnapManager const &m = desktop->namedview->snap_manager;
249 Inkscape::SnappedPoint snappoint = m.guideSnap(motion_dt, guide->normal_to_line);
250 motion_dt = snappoint.getPoint();
252 sp_guide_moveto(*guide, motion_dt.to_2geom(), false);
253 moved = true;
254 desktop->set_coordinate_status(motion_dt);
255 desktop->setPosition (motion_dt);
257 if (snappoint.getDistance() < NR_HUGE) {
258 desktop->snapindicator->set_new_snappoint(snappoint.getPoint().to_2geom());
259 } else {
260 desktop->snapindicator->remove_snappoint();
261 }
263 ret = TRUE;
264 }
265 break;
266 case GDK_BUTTON_RELEASE:
267 if (dragging && event->button.button == 1) {
268 if (moved) {
269 NR::Point const event_w(event->button.x,
270 event->button.y);
271 NR::Point event_dt(desktop->w2d(event_w));
273 SnapManager const &m = desktop->namedview->snap_manager;
274 event_dt = m.guideSnap(event_dt, guide->normal_to_line).getPoint();
276 if (sp_canvas_world_pt_inside_window(item->canvas, event_w)) {
277 sp_guide_moveto(*guide, event_dt.to_2geom(), true);
278 sp_document_done(sp_desktop_document(desktop), SP_VERB_NONE,
279 _("Move guide"));
280 } else {
281 /* Undo movement of any attached shapes. */
282 sp_guide_moveto(*guide, guide->point_on_line, false);
283 sp_guide_remove(guide);
284 sp_document_done(sp_desktop_document(desktop), SP_VERB_NONE,
285 _("Delete guide"));
286 }
287 moved = false;
288 desktop->set_coordinate_status(event_dt);
289 desktop->setPosition (event_dt);
290 }
291 dragging = false;
292 sp_canvas_item_ungrab(item, event->button.time);
293 ret=TRUE;
294 }
295 case GDK_ENTER_NOTIFY:
296 {
297 sp_guideline_set_color(SP_GUIDELINE(item), guide->hicolor);
299 char *guide_description = sp_guide_description(guide);
300 desktop->guidesMessageContext()->setF(Inkscape::NORMAL_MESSAGE, _("<b>Guideline</b>: %s"), guide_description);
301 g_free(guide_description);
302 break;
303 }
304 case GDK_LEAVE_NOTIFY:
305 sp_guideline_set_color(SP_GUIDELINE(item), guide->color);
306 desktop->guidesMessageContext()->clear();
307 break;
308 default:
309 break;
310 }
312 return ret;
313 }
315 static std::map<GdkInputSource, std::string> switchMap;
316 static std::map<GdkInputSource, int> toolToUse;
317 static std::string lastName;
318 static GdkInputSource lastType = GDK_SOURCE_MOUSE;
320 static void init_extended()
321 {
322 std::string avoidName = "pad";
323 GList* devices = gdk_devices_list();
324 if ( devices ) {
325 for ( GList* curr = devices; curr; curr = g_list_next(curr) ) {
326 GdkDevice* dev = reinterpret_cast<GdkDevice*>(curr->data);
327 if ( dev->name
328 && (avoidName != dev->name)
329 && (switchMap.find(dev->source) == switchMap.end())
330 && (dev->source != GDK_SOURCE_MOUSE) ) {
331 switchMap[dev->source] = dev->name;
332 // g_message("Adding '%s' as [%d]", dev->name, dev->source);
334 // Set the initial tool for the device
335 switch ( dev->source ) {
336 case GDK_SOURCE_PEN:
337 toolToUse[GDK_SOURCE_PEN] = TOOLS_CALLIGRAPHIC;
338 break;
339 case GDK_SOURCE_ERASER:
340 toolToUse[GDK_SOURCE_ERASER] = TOOLS_TWEAK;
341 break;
342 case GDK_SOURCE_CURSOR:
343 toolToUse[GDK_SOURCE_CURSOR] = TOOLS_SELECT;
344 break;
345 default:
346 ; // do not add
347 }
348 // } else {
349 // g_message("Skippn '%s' as [%s]", dev->name, namefor(dev->source));
350 }
351 }
352 }
353 }
356 void snoop_extended(GdkEvent* event, SPDesktop *desktop)
357 {
358 GdkInputSource source = GDK_SOURCE_MOUSE;
359 std::string name;
361 switch ( event->type ) {
362 case GDK_MOTION_NOTIFY:
363 {
364 GdkEventMotion* event2 = reinterpret_cast<GdkEventMotion*>(event);
365 if ( event2->device ) {
366 source = event2->device->source;
367 name = event2->device->name;
368 }
369 }
370 break;
372 case GDK_BUTTON_PRESS:
373 case GDK_2BUTTON_PRESS:
374 case GDK_3BUTTON_PRESS:
375 case GDK_BUTTON_RELEASE:
376 {
377 GdkEventButton* event2 = reinterpret_cast<GdkEventButton*>(event);
378 if ( event2->device ) {
379 source = event2->device->source;
380 name = event2->device->name;
381 }
382 }
383 break;
385 case GDK_SCROLL:
386 {
387 GdkEventScroll* event2 = reinterpret_cast<GdkEventScroll*>(event);
388 if ( event2->device ) {
389 source = event2->device->source;
390 name = event2->device->name;
391 }
392 }
393 break;
395 case GDK_PROXIMITY_IN:
396 case GDK_PROXIMITY_OUT:
397 {
398 GdkEventProximity* event2 = reinterpret_cast<GdkEventProximity*>(event);
399 if ( event2->device ) {
400 source = event2->device->source;
401 name = event2->device->name;
402 }
403 }
404 break;
406 default:
407 ;
408 }
410 if (!name.empty()) {
411 if ( lastName != name || lastType != source ) {
412 // The device switched. See if it is one we 'count'
413 std::map<GdkInputSource, std::string>::iterator it = switchMap.find(source);
414 if ( (it != switchMap.end()) && (name == it->second) ) {
415 std::map<GdkInputSource, int>::iterator it2 = toolToUse.find(source);
416 if (it2 != toolToUse.end() ) {
417 // Save the tool currently selected for next time the input device shows up.
418 if ( (switchMap.find(lastType) != switchMap.end())
419 && (lastName == switchMap.find(lastType)->second)) {
420 toolToUse[lastType] = tools_active(desktop);
421 }
422 tools_switch(desktop, it2->second);
423 }
424 lastName = name;
425 lastType = source;
426 }
427 }
428 }
429 }
433 /*
434 Local Variables:
435 mode:c++
436 c-file-style:"stroustrup"
437 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
438 indent-tabs-mode:nil
439 fill-column:99
440 End:
441 */
442 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :