Code

snap indicator: try a diamond shaped indicator for snapping to nodes. see how we...
[inkscape.git] / src / desktop-events.cpp
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 "preferences.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         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
56         if ( prefs->getBool("/options/useextinput/value", true)
57             && prefs->getBool("/options/switchonextinput/value") ) {
58             watch = true;
59             init_extended();
60         }
61         first = false;
62     }
63     if ( watch ) {
64         snoop_extended(event, desktop);
65     }
67     return sp_event_context_root_handler(desktop->event_context, event);
68 }
71 static gint sp_dt_ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw, bool horiz)
72 {
73     static bool dragging = false;
74     static SPCanvasItem *guide = NULL;
75     static Geom::Point normal;
76     int wx, wy;
78     SPDesktop *desktop = dtw->desktop;
79     Inkscape::XML::Node *repr = SP_OBJECT_REPR(desktop->namedview);
81     gdk_window_get_pointer(GTK_WIDGET(dtw->canvas)->window, &wx, &wy, NULL);
82     Geom::Point const event_win(wx, wy);
84     gint width, height;
85     gdk_window_get_geometry(GTK_WIDGET(dtw->canvas)->window, NULL /*x*/, NULL /*y*/, &width, &height, NULL/*depth*/);
87     switch (event->type) {
88     case GDK_BUTTON_PRESS:
89             if (event->button.button == 1) {
90                 dragging = true;
91                 Geom::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win));
92                 Geom::Point const event_dt(desktop->w2d(event_w));
94                 // explicitly show guidelines; if I draw a guide, I want them on
95                 sp_repr_set_boolean(repr, "showguides", TRUE);
96                 sp_repr_set_boolean(repr, "inkscape:guide-bbox", TRUE);
98                 // calculate the normal of the guidelines when dragged from the edges of rulers.
99                 Geom::Point normal_bl_to_tr(-1.,1.); //bottomleft to topright
100                 Geom::Point normal_tr_to_bl(1.,1.); //topright to bottomleft
101                 normal_bl_to_tr.normalize();
102                 normal_tr_to_bl.normalize();
103                 Inkscape::CanvasGrid * grid = sp_namedview_get_first_enabled_grid(desktop->namedview);
104                 if ( grid && grid->getGridType() == Inkscape::GRID_AXONOMETRIC ) {
105                     Inkscape::CanvasAxonomGrid *axonomgrid = dynamic_cast<Inkscape::CanvasAxonomGrid *>(grid);
106                     if (event->button.state & GDK_CONTROL_MASK) {
107                         // guidelines normal to gridlines
108                         normal_bl_to_tr = Geom::Point::polar(-axonomgrid->angle_rad[0], 1.0);
109                         normal_tr_to_bl = Geom::Point::polar(axonomgrid->angle_rad[2], 1.0);
110                     } else {
111                         normal_bl_to_tr = rot90(Geom::Point::polar(axonomgrid->angle_rad[2], 1.0));
112                         normal_tr_to_bl = rot90(Geom::Point::polar(-axonomgrid->angle_rad[0], 1.0));
113                     }
114                 }
115                 if (horiz) {
116                     if (wx < 50) {
117                         normal = normal_bl_to_tr;
118                     } else if (wx > width - 50) {
119                         normal = normal_tr_to_bl;
120                     } else {
121                         normal = Geom::Point(0.,1.);
122                     }
123                 } else {
124                     if (wy < 50) {
125                         normal = normal_bl_to_tr;
126                     } else if (wy > height - 50) {
127                         normal = normal_tr_to_bl;
128                     } else {
129                         normal = Geom::Point(1.,0.);
130                     }
131                 }
133                 guide = sp_guideline_new(desktop->guides, event_dt, normal);
134                 sp_guideline_set_color(SP_GUIDELINE(guide), desktop->namedview->guidehicolor);
135                 gdk_pointer_grab(widget->window, FALSE,
136                                  (GdkEventMask)(GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK ),
137                                  NULL, NULL,
138                                  event->button.time);
139             }
140             break;
141     case GDK_MOTION_NOTIFY:
142             if (dragging) {
143                 Geom::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win));
144                 Geom::Point event_dt(desktop->w2d(event_w));
145                 
146                 SnapManager &m = desktop->namedview->snap_manager;
147                 m.setup(desktop);
148                 m.guideSnap(event_dt, normal);
149                 
150                 sp_guideline_set_position(SP_GUIDELINE(guide), from_2geom(event_dt));
151                 desktop->set_coordinate_status(to_2geom(event_dt));
152                 desktop->setPosition(to_2geom(event_dt));                
153             }
154             break;
155     case GDK_BUTTON_RELEASE:
156             if (dragging && event->button.button == 1) {
157                 gdk_pointer_ungrab(event->button.time);
158                 Geom::Point const event_w(sp_canvas_window_to_world(dtw->canvas, event_win));
159                 Geom::Point event_dt(desktop->w2d(event_w));
160                 
161                 SnapManager &m = desktop->namedview->snap_manager;
162                 m.setup(desktop);
163                 m.guideSnap(event_dt, normal);
164                                 
165                 dragging = false;
166                 gtk_object_destroy(GTK_OBJECT(guide));
167                 guide = NULL;
168                 if ((horiz ? wy : wx) >= 0) {
169                     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
170                     Inkscape::XML::Node *repr = xml_doc->createElement("sodipodi:guide");
171                     sp_repr_set_point(repr, "orientation", normal);
172                     sp_repr_set_point(repr, "position", from_2geom(event_dt));
173                     SP_OBJECT_REPR(desktop->namedview)->appendChild(repr);
174                     Inkscape::GC::release(repr);
175                     sp_document_done(sp_desktop_document(desktop), SP_VERB_NONE, 
176                                      _("Create guide"));
177                 }
178                 desktop->set_coordinate_status(from_2geom(event_dt));
179             }
180         default:
181             break;
182     }
184     return FALSE;
187 int sp_dt_hruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw)
189     return sp_dt_ruler_event(widget, event, dtw, true);
192 int sp_dt_vruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw)
194     return sp_dt_ruler_event(widget, event, dtw, false);
197 /* Guides */
199 gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data)
201     static bool dragging = false;
202     static bool moved = false;
203     gint ret = FALSE;
205     SPGuide *guide = SP_GUIDE(data);
206     SPDesktop *desktop = static_cast<SPDesktop*>(gtk_object_get_data(GTK_OBJECT(item->canvas), "SPDesktop"));
208         switch (event->type) {
209         case GDK_2BUTTON_PRESS:
210             if (event->button.button == 1) {
211                 dragging = false;
212                 sp_canvas_item_ungrab(item, event->button.time);
213                 Inkscape::UI::Dialogs::GuidelinePropertiesDialog::showDialog(guide, desktop);
214                 ret = TRUE;
215             }
216             break;
217         case GDK_BUTTON_PRESS:
218             if (event->button.button == 1) {
219                 if (event->button.state & GDK_CONTROL_MASK) {
220                     SPDocument *doc = SP_OBJECT_DOCUMENT(guide);
221                     sp_guide_remove(guide);
222                     sp_document_done(doc, SP_VERB_NONE, _("Delete guide"));
223                     ret = TRUE;
224                     break;
225                 }
226                 dragging = true;
227                 sp_canvas_item_grab(item,
228                                     ( GDK_BUTTON_RELEASE_MASK  |
229                                       GDK_BUTTON_PRESS_MASK    |
230                                       GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK ),
231                                     NULL,
232                                     event->button.time);
233                 ret = TRUE;
234             }
235             break;
236     case GDK_MOTION_NOTIFY:
237             if (dragging) {
238                 Geom::Point const motion_w(event->motion.x,
239                                          event->motion.y);
240                 Geom::Point motion_dt(to_2geom(desktop->w2d(from_2geom(motion_w))));
241                 
242                 // This is for snapping while dragging existing guidelines. New guidelines, 
243                 // which are dragged off the ruler, are being snapped in sp_dt_ruler_event
244                 SnapManager &m = desktop->namedview->snap_manager;
245                 m.setup(desktop);
246                 m.guideSnap(motion_dt, to_2geom(guide->normal_to_line));
247                 
248                 sp_guide_moveto(*guide, from_2geom(motion_dt), false);
249                 moved = true;
250                 desktop->set_coordinate_status(from_2geom(motion_dt));
251                 desktop->setPosition(from_2geom(motion_dt));
253                 ret = TRUE;
254             }
255             break;
256     case GDK_BUTTON_RELEASE:
257             if (dragging && event->button.button == 1) {
258                 if (moved) {
259                     Geom::Point const event_w(event->button.x,
260                                               event->button.y);
261                     Geom::Point event_dt(desktop->w2d(event_w));
263                     SnapManager &m = desktop->namedview->snap_manager;
264                     m.setup(desktop);
265                     m.guideSnap(event_dt, guide->normal_to_line);
267                     if (sp_canvas_world_pt_inside_window(item->canvas, event_w)) {
268                         sp_guide_moveto(*guide, from_2geom(event_dt), true);
269                         sp_document_done(sp_desktop_document(desktop), SP_VERB_NONE,
270                                      _("Move guide"));
271                     } else {
272                         /* Undo movement of any attached shapes. */
273                         sp_guide_moveto(*guide, guide->point_on_line, false);
274                         sp_guide_remove(guide);
275                         sp_document_done(sp_desktop_document(desktop), SP_VERB_NONE,
276                                      _("Delete guide"));
277                     }
278                     moved = false;
279                     desktop->set_coordinate_status(from_2geom(event_dt));
280                     desktop->setPosition (from_2geom(event_dt));
281                 }
282                 dragging = false;
283                 sp_canvas_item_ungrab(item, event->button.time);
284                 ret=TRUE;
285             }
286     case GDK_ENTER_NOTIFY:
287     {
288             sp_guideline_set_color(SP_GUIDELINE(item), guide->hicolor);
290             char *guide_description = sp_guide_description(guide);
291             desktop->guidesMessageContext()->setF(Inkscape::NORMAL_MESSAGE, _("<b>Guideline</b>: %s"), guide_description);
292             g_free(guide_description);
293             break;
294     }
295     case GDK_LEAVE_NOTIFY:
296             sp_guideline_set_color(SP_GUIDELINE(item), guide->color);
297             desktop->guidesMessageContext()->clear();
298             break;
299     default:
300             break;
301     }
303     return ret;
306 //static std::map<GdkInputSource, std::string> switchMap;
307 static std::map<std::string, int> toolToUse;
308 static std::string lastName;
309 static GdkInputSource lastType = GDK_SOURCE_MOUSE;
311 static void init_extended()
313     std::string avoidName = "pad";
314     GList* devices = gdk_devices_list();
315     if ( devices ) {
316         for ( GList* curr = devices; curr; curr = g_list_next(curr) ) {
317             GdkDevice* dev = reinterpret_cast<GdkDevice*>(curr->data);
318             if ( dev->name
319                  && (avoidName != dev->name)
320                  && (dev->source != GDK_SOURCE_MOUSE) ) {
321 //                 g_message("Adding '%s' as [%d]", dev->name, dev->source);
323                 // Set the initial tool for the device
324                 switch ( dev->source ) {
325                     case GDK_SOURCE_PEN:
326                         toolToUse[dev->name] = TOOLS_CALLIGRAPHIC;
327                         break;
328                     case GDK_SOURCE_ERASER:
329                         toolToUse[dev->name] = TOOLS_ERASER;
330                         break;
331                     case GDK_SOURCE_CURSOR:
332                         toolToUse[dev->name] = TOOLS_SELECT;
333                         break;
334                     default:
335                         ; // do not add
336                 }
337 //            } else if (dev->name) {
338 //                 g_message("Skippn '%s' as [%s]", dev->name, dev->source);
339             }
340         }
341     }
345 void snoop_extended(GdkEvent* event, SPDesktop *desktop)
347     GdkInputSource source = GDK_SOURCE_MOUSE;
348     std::string name;
350     switch ( event->type ) {
351         case GDK_MOTION_NOTIFY:
352         {
353             GdkEventMotion* event2 = reinterpret_cast<GdkEventMotion*>(event);
354             if ( event2->device ) {
355                 source = event2->device->source;
356                 name = event2->device->name;
357             }
358         }
359         break;
361         case GDK_BUTTON_PRESS:
362         case GDK_2BUTTON_PRESS:
363         case GDK_3BUTTON_PRESS:
364         case GDK_BUTTON_RELEASE:
365         {
366             GdkEventButton* event2 = reinterpret_cast<GdkEventButton*>(event);
367             if ( event2->device ) {
368                 source = event2->device->source;
369                 name = event2->device->name;
370             }
371         }
372         break;
374         case GDK_SCROLL:
375         {
376             GdkEventScroll* event2 = reinterpret_cast<GdkEventScroll*>(event);
377             if ( event2->device ) {
378                 source = event2->device->source;
379                 name = event2->device->name;
380             }
381         }
382         break;
384         case GDK_PROXIMITY_IN:
385         case GDK_PROXIMITY_OUT:
386         {
387             GdkEventProximity* event2 = reinterpret_cast<GdkEventProximity*>(event);
388             if ( event2->device ) {
389                 source = event2->device->source;
390                 name = event2->device->name;
391             }
392         }
393         break;
395         default:
396             ;
397     }
399     if (!name.empty()) {
400         if ( lastType != source || lastName != name ) {
401             // The device switched. See if it is one we 'count'
402             //g_message("Changed device %s -> %s", lastName.c_str(), name.c_str());
403             std::map<std::string, int>::iterator it = toolToUse.find(lastName);
404             if (it != toolToUse.end()) {
405                 // Save the tool currently selected for next time the input
406                 // device shows up.
407                 it->second = tools_active(desktop);
408             }
409             
410             it = toolToUse.find(name);
411             if (it != toolToUse.end() ) {
412                 tools_switch(desktop, it->second);
413             }
415             lastName = name;
416             lastType = source;
417         }
418     }
423 /*
424   Local Variables:
425   mode:c++
426   c-file-style:"stroustrup"
427   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
428   indent-tabs-mode:nil
429   fill-column:99
430   End:
431 */
432 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :