Code

prevent assert crashes on bad data in cal1 or cal2
[inkscape.git] / src / desktop.cpp
1 #define __SP_DESKTOP_C__
3 /** \file
4  * Editable view implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   MenTaLguY <mental@rydia.net>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Ralf Stephan <ralf@ark.in-berlin.de>
11  *   John Bintz <jcoswell@coswellproductions.org>
12  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
13  *
14  * Copyright (C) 2007 Jon A. Cruz
15  * Copyright (C) 2006-2008 Johan Engelen
16  * Copyright (C) 2006 John Bintz
17  * Copyright (C) 2004 MenTaLguY
18  * Copyright (C) 1999-2002 Lauris Kaplinski
19  * Copyright (C) 2000-2001 Ximian, Inc.
20  *
21  * Released under GNU GPL, read the file 'COPYING' for more information
22  */
24 /** \class SPDesktop
25  * SPDesktop is a subclass of View, implementing an editable document
26  * canvas.  It is extensively used by many UI controls that need certain
27  * visual representations of their own.
28  *
29  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
30  * layers of different control objects. The one containing the whole
31  * document is the drawing layer. In addition to it, there are grid,
32  * guide, sketch and control layers. The sketch layer is used for
33  * temporary drawing objects, before the real objects in document are
34  * created. The control layer contains editing knots, rubberband and
35  * similar non-document UI objects.
36  *
37  * Each SPDesktop is associated with a SPNamedView node of the document
38  * tree.  Currently, all desktops are created from a single main named
39  * view, but in the future there may be support for different ones.
40  * SPNamedView serves as an in-document container for desktop-related
41  * data, like grid and guideline placement, snapping options and so on.
42  *
43  * Associated with each SPDesktop are the two most important editing
44  * related objects - SPSelection and SPEventContext.
45  *
46  * Sodipodi keeps track of the active desktop and invokes notification
47  * signals whenever it changes. UI elements can use these to update their
48  * display to the selection of the currently active editing window.
49  * (Lauris Kaplinski)
50  */
52 #ifdef HAVE_CONFIG_H
53 # include "config.h"
54 #endif
56 #include <glibmm/i18n.h>
57 #include <sigc++/functors/mem_fun.h>
58 #include <gtkmm.h>
60 #include "macros.h"
61 #include "inkscape-private.h"
62 #include "desktop.h"
63 #include "desktop-events.h"
64 #include "desktop-handles.h"
65 #include "document.h"
66 #include "message-stack.h"
67 #include "selection.h"
68 #include "select-context.h"
69 #include "sp-namedview.h"
70 #include "color.h"
71 #include "sp-item-group.h"
72 #include "prefs-utils.h"
73 #include "object-hierarchy.h"
74 #include "helper/units.h"
75 #include "display/canvas-arena.h"
76 #include "display/nr-arena.h"
77 #include "display/gnome-canvas-acetate.h"
78 #include "display/sodipodi-ctrlrect.h"
79 #include "display/sp-canvas-util.h"
80 #include "display/canvas-temporary-item-list.h"
81 #include "display/snap-indicator.h"
82 #include "libnr/nr-matrix-div.h"
83 #include "libnr/nr-rect-ops.h"
84 #include "ui/dialog/dialog-manager.h"
85 #include "xml/repr.h"
86 #include "message-context.h"
87 #include "device-manager.h"
88 #include "layer-fns.h"
89 #include "layer-manager.h"
90 #include "event-log.h"
91 #include "display/canvas-grid.h"
92 #include "widgets/desktop-widget.h"
93 #include "box3d-context.h"
95 #include "display/sp-canvas.h"
97 namespace Inkscape { namespace XML { class Node; }}
99 // Callback declarations
100 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
101 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
102 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
103 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
104 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
105 static void _reconstruction_start(SPDesktop * desktop);
106 static void _reconstruction_finish(SPDesktop * desktop);
107 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
108 static void _update_snap_distances (SPDesktop *desktop);
110 /**
111  * Return new desktop object.
112  * \pre namedview != NULL.
113  * \pre canvas != NULL.
114  */
115 SPDesktop::SPDesktop() :
116     _dlg_mgr( 0 ),
117     namedview( 0 ),
118     canvas( 0 ),
119     selection( 0 ),
120     event_context( 0 ),
121     layer_manager( 0 ),
122     event_log( 0 ),
123     temporary_item_list( 0 ),
124     snapindicator( 0 ),
125     acetate( 0 ),
126     main( 0 ),
127     gridgroup( 0 ),
128     guides( 0 ),
129     drawing( 0 ),
130     sketch( 0 ),
131     controls( 0 ),
132     tempgroup ( 0 ),
133     table( 0 ),
134     page( 0 ),
135     page_border( 0 ),
136     current( 0 ),
137     zooms_past( 0 ),
138     zooms_future( 0 ),
139     dkey( 0 ),
140     number( 0 ),
141     window_state(0),
142     interaction_disabled_counter( 0 ),
143     waiting_cursor( false ),
144     guides_active( false ),
145     gr_item( 0 ),
146     gr_point_type( 0 ),
147     gr_point_i( 0 ),
148     gr_fill_or_stroke( true ),
149     _layer_hierarchy( 0 ),
150     _reconstruction_old_layer_id( 0 ),
151     _display_mode(Inkscape::RENDERMODE_NORMAL),
152     _saved_display_mode(Inkscape::RENDERMODE_NORMAL),
153     _widget( 0 ),
154     _inkscape( 0 ),
155     _guides_message_context( 0 ),
156     _active( false ),
157     _w2d(),
158     _d2w(),
159     _doc2dt( NR::Matrix(NR::scale(1, -1)) ),
160     grids_visible( false )
162     _d2w.set_identity();
163     _w2d.set_identity();
165     selection = Inkscape::GC::release( new Inkscape::Selection(this) );
168 void
169 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
171     // Temporary workaround for link order issues:
172     Inkscape::DeviceManager::getManager().getDevices();
174     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
176     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
178     namedview = nv;
179     canvas = aCanvas;
181     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
182     /* Kill flicker */
183     sp_document_ensure_up_to_date (document);
185     /* Setup Dialog Manager */
186     _dlg_mgr = &Inkscape::UI::Dialog::DialogManager::getInstance();
188     dkey = sp_item_display_key_new (1);
190     /* Connect document */
191     setDocument (document);
193     number = namedview->getViewCount();
196     /* Setup Canvas */
197     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
199     SPCanvasGroup *root = sp_canvas_root (canvas);
201     /* Setup adminstrative layers */
202     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
203     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
204     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
205     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
207     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
208     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
209     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
210     sp_canvas_item_move_to_z (table, 0);
212     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
213     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
214     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
216     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
217     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
219     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
221     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
222         // Start in outline mode
223         setDisplayModeOutline();
224     } else {
225         // Start in normal mode, default
226         setDisplayModeNormal();
227     }
229     gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
230     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
231     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
232     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
233     tempgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
235     /* Push select tool to the bottom of stack */
236     /** \todo
237      * FIXME: this is the only call to this.  Everything else seems to just
238      * call "set" instead of "push".  Can we assume that there is only one
239      * context ever?
240      */
241     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
243     // display rect and zoom are now handled in sp_desktop_widget_realize()
245     NR::Rect const d(NR::Point(0.0, 0.0),
246                      NR::Point(sp_document_width(document), sp_document_height(document)));
248     SP_CTRLRECT(page)->setRectangle(d);
249     SP_CTRLRECT(page_border)->setRectangle(d);
251     /* the following sets the page shadow on the canvas
252        It was originally set to 5, which is really cheesy!
253        It now is an attribute in the document's namedview. If a value of
254        0 is used, then the constructor for a shadow is not initialized.
255     */
257     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
258         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
259     }
262     /* Connect event for page resize */
263     _doc2dt[5] = sp_document_height (document);
264     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
266     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
268     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
269             SP_CANVAS_ARENA (drawing)->arena,
270             dkey,
271             SP_ITEM_SHOW_DISPLAY);
272     if (ai) {
273         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
274         nr_arena_item_unref (ai);
275     }
277     namedview->show(this);
278     /* Ugly hack */
279     activate_guides (true);
280     /* Ugly hack */
281     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
283 /* Set up notification of rebuilding the document, this allows
284        for saving object related settings in the document. */
285     _reconstruction_start_connection =
286         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
287     _reconstruction_finish_connection =
288         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
289     _reconstruction_old_layer_id = NULL;
291     // ?
292     // sp_active_desktop_set (desktop);
293     _inkscape = INKSCAPE;
295     _activate_connection = _activate_signal.connect(
296         sigc::bind(
297             sigc::ptr_fun(_onActivate),
298             this
299         )
300     );
301      _deactivate_connection = _deactivate_signal.connect(
302         sigc::bind(
303             sigc::ptr_fun(_onDeactivate),
304             this
305         )
306     );
308     _sel_modified_connection = selection->connectModified(
309         sigc::bind(
310             sigc::ptr_fun(&_onSelectionModified),
311             this
312         )
313     );
314     _sel_changed_connection = selection->connectChanged(
315         sigc::bind(
316             sigc::ptr_fun(&_onSelectionChanged),
317             this
318         )
319     );
322     /* setup LayerManager */
323     //   (Setting up after the connections are all in place, as it may use some of them)
324     layer_manager = new Inkscape::LayerManager( this );
326     showGrids(namedview->grids_visible, false);
328     temporary_item_list = new Inkscape::Display::TemporaryItemList( this );
329     snapindicator = new Inkscape::Display::SnapIndicator ( this );
333 void SPDesktop::destroy()
335     if (snapindicator) {
336         delete snapindicator;
337         snapindicator = NULL;
338     }
339     if (temporary_item_list) {
340         delete temporary_item_list;
341         temporary_item_list = NULL;
342     }
344     namedview->hide(this);
346     _activate_connection.disconnect();
347     _deactivate_connection.disconnect();
348     _sel_modified_connection.disconnect();
349     _sel_changed_connection.disconnect();
350     _modified_connection.disconnect();
351     _commit_connection.disconnect();
352     _reconstruction_start_connection.disconnect();
353     _reconstruction_finish_connection.disconnect();
355     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
356     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
357     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
359     while (event_context) {
360         SPEventContext *ec = event_context;
361         event_context = ec->next;
362         sp_event_context_finish (ec);
363         g_object_unref (G_OBJECT (ec));
364     }
366     if (_layer_hierarchy) {
367         delete _layer_hierarchy;
368 //        _layer_hierarchy = NULL; //this should be here, but commented to find other bug somewhere else.
369     }
371     if (layer_manager) {
372         delete layer_manager;
373         layer_manager = NULL;
374     }
376     if (_inkscape) {
377         _inkscape = NULL;
378     }
380     if (drawing) {
381         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
382         drawing = NULL;
383     }
385     delete _guides_message_context;
386     _guides_message_context = NULL;
388     g_list_free (zooms_past);
389     g_list_free (zooms_future);
392 SPDesktop::~SPDesktop() {}
394 //--------------------------------------------------------------------
395 /* Public methods */
398 /* These methods help for temporarily showing things on-canvas.
399  * The *only* valid use of the TemporaryItem* that you get from add_temporary_canvasitem
400  * is when you want to prematurely remove the item from the canvas, by calling
401  * desktop->remove_temporary_canvasitem(tempitem).
402  */
403 /** Note that lifetime is measured in milliseconds
404  * One should *not* keep a reference to the SPCanvasItem, the temporary item code will
405  * delete the object for you and the reference will become invalid without you knowing it.
406  * It is perfectly safe to ignore the returned pointer: the object is deleted by itself, so don't delete it elsewhere!
407  * The *only* valid use of the returned TemporaryItem* is as argument for SPDesktop::remove_temporary_canvasitem,
408  * because the object might be deleted already without you knowing it.
409  * move_to_bottom = true by default so the item does not interfere with handling of other items on the canvas like nodes.
410  */
411 Inkscape::Display::TemporaryItem *
412 SPDesktop::add_temporary_canvasitem (SPCanvasItem *item, guint lifetime, bool move_to_bottom)
414     if (move_to_bottom) {
415         sp_canvas_item_move_to_z(item, 0);
416     }
418     return temporary_item_list->add_item(item, lifetime);
421 /** It is perfectly safe to call this function while the object has already been deleted due to a timeout.
422 */
423 void
424 SPDesktop::remove_temporary_canvasitem (Inkscape::Display::TemporaryItem * tempitem)
426     // check for non-null temporary_item_list, because during destruction of desktop, some destructor might try to access this list!
427     if (tempitem && temporary_item_list) {
428         temporary_item_list->delete_item(tempitem);
429     }
432 void SPDesktop::_setDisplayMode(Inkscape::RenderMode mode) {
433     SP_CANVAS_ARENA (drawing)->arena->rendermode = mode;
434     canvas->rendermode = mode;
435     _display_mode = mode;
436     if (mode != Inkscape::RENDERMODE_OUTLINE) {
437         _saved_display_mode = _display_mode;
438     }
439     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
440     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
443 void SPDesktop::displayModeToggle() {
444     if (_display_mode == Inkscape::RENDERMODE_OUTLINE) {
445         _setDisplayMode(_saved_display_mode);
446     } else {
447         _setDisplayMode(Inkscape::RENDERMODE_OUTLINE);
448     }
451 /**
452  * Returns current root (=bottom) layer.
453  */
454 SPObject *SPDesktop::currentRoot() const
456     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
459 /**
460  * Returns current top layer.
461  */
462 SPObject *SPDesktop::currentLayer() const
464     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
467 /**
468  * Sets the current layer of the desktop.
469  *
470  * Make \a object the top layer.
471  */
472 void SPDesktop::setCurrentLayer(SPObject *object) {
473     g_return_if_fail(SP_IS_GROUP(object));
474     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
475     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
476     _layer_hierarchy->setBottom(object);
479 void SPDesktop::toggleLayerSolo(SPObject *object) {
480     g_return_if_fail(SP_IS_GROUP(object));
481     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
483     bool othersShowing = false;
484     std::vector<SPObject*> layers;
485     for ( SPObject* obj = Inkscape::next_layer(currentRoot(), object); obj; obj = Inkscape::next_layer(currentRoot(), obj) ) {
486         layers.push_back(obj);
487         othersShowing |= !SP_ITEM(obj)->isHidden();
488     }
489     for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), object); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
490         layers.push_back(obj);
491         othersShowing |= !SP_ITEM(obj)->isHidden();
492     }
495     if ( SP_ITEM(object)->isHidden() ) {
496         SP_ITEM(object)->setHidden(false);
497     }
499     for ( std::vector<SPObject*>::iterator it = layers.begin(); it != layers.end(); ++it ) {
500         SP_ITEM(*it)->setHidden(othersShowing);
501     }
504 /**
505  * Return layer that contains \a object.
506  */
507 SPObject *SPDesktop::layerForObject(SPObject *object) {
508     g_return_val_if_fail(object != NULL, NULL);
510     SPObject *root=currentRoot();
511     object = SP_OBJECT_PARENT(object);
512     while ( object && object != root && !isLayer(object) ) {
513         object = SP_OBJECT_PARENT(object);
514     }
515     return object;
518 /**
519  * True if object is a layer.
520  */
521 bool SPDesktop::isLayer(SPObject *object) const {
522     return ( SP_IS_GROUP(object)
523              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
524                   == SPGroup::LAYER ) );
527 /**
528  * True if desktop viewport fully contains \a item's bbox.
529  */
530 bool SPDesktop::isWithinViewport (SPItem *item) const
532     NR::Rect const viewport = get_display_area();
533     NR::Maybe<NR::Rect> const bbox = sp_item_bbox_desktop(item);
534     if (bbox) {
535         return viewport.contains(*bbox);
536     } else {
537         return true;
538     }
541 ///
542 bool SPDesktop::itemIsHidden(SPItem const *item) const {
543     return item->isHidden(this->dkey);
546 /**
547  * Set activate property of desktop; emit signal if changed.
548  */
549 void
550 SPDesktop::set_active (bool new_active)
552     if (new_active != _active) {
553         _active = new_active;
554         if (new_active) {
555             _activate_signal.emit();
556         } else {
557             _deactivate_signal.emit();
558         }
559     }
562 /**
563  * Set activate status of current desktop's named view.
564  */
565 void
566 SPDesktop::activate_guides(bool activate)
568     guides_active = activate;
569     namedview->activateGuides(this, activate);
572 /**
573  * Make desktop switch documents.
574  */
575 void
576 SPDesktop::change_document (SPDocument *theDocument)
578     g_return_if_fail (theDocument != NULL);
580     /* unselect everything before switching documents */
581     selection->clear();
583     setDocument (theDocument);
585     /* update the rulers, connect the desktop widget's signal to the new namedview etc.
586        (this can probably be done in a better way) */
587     Gtk::Window *parent = this->getToplevel();
588     g_assert(parent != NULL);
589     SPDesktopWidget *dtw = (SPDesktopWidget *) parent->get_data("desktopwidget");
590     if (dtw) dtw->desktop = this;
591     sp_desktop_widget_update_namedview(dtw);
593     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
594     _document_replaced_signal.emit (this, theDocument);
597 /**
598  * Make desktop switch event contexts.
599  */
600 void
601 SPDesktop::set_event_context (GtkType type, const gchar *config)
603     SPEventContext *ec;
604     while (event_context) {
605         ec = event_context;
606         sp_event_context_deactivate (ec);
607         event_context = ec->next;
608         sp_event_context_finish (ec);
609         g_object_unref (G_OBJECT (ec));
610     }
612     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
613     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
614     ec->next = event_context;
615     event_context = ec;
616     sp_event_context_activate (ec);
617     _event_context_changed_signal.emit (this, ec);
620 /**
621  * Push event context onto desktop's context stack.
622  */
623 void
624 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
626     SPEventContext *ref, *ec;
627     Inkscape::XML::Node *repr;
629     if (event_context && event_context->key == key) return;
630     ref = event_context;
631     while (ref && ref->next && ref->next->key != key) ref = ref->next;
632     if (ref && ref->next) {
633         ec = ref->next;
634         ref->next = ec->next;
635         sp_event_context_finish (ec);
636         g_object_unref (G_OBJECT (ec));
637     }
639     if (event_context) sp_event_context_deactivate (event_context);
640     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
641     ec = sp_event_context_new (type, this, repr, key);
642     ec->next = event_context;
643     event_context = ec;
644     sp_event_context_activate (ec);
645     _event_context_changed_signal.emit (this, ec);
648 /**
649  * Sets the coordinate status to a given point
650  */
651 void
652 SPDesktop::set_coordinate_status (NR::Point p) {
653     _widget->setCoordinateStatus(p);
656 /**
657  * \see sp_document_item_from_list_at_point_bottom()
658  */
659 SPItem *
660 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
662     g_return_val_if_fail (doc() != NULL, NULL);
663     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
666 /**
667  * \see sp_document_item_at_point()
668  */
669 SPItem *
670 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
672     g_return_val_if_fail (doc() != NULL, NULL);
673     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
676 /**
677  * \see sp_document_group_at_point()
678  */
679 SPItem *
680 SPDesktop::group_at_point (NR::Point const p) const
682     g_return_val_if_fail (doc() != NULL, NULL);
683     return sp_document_group_at_point (doc(), dkey, p);
686 /**
687  * \brief  Returns the mouse point in document coordinates; if mouse is
688  * outside the canvas, returns the center of canvas viewpoint
689  */
690 NR::Point
691 SPDesktop::point() const
693     NR::Point p = _widget->getPointer();
694     NR::Point pw = sp_canvas_window_to_world (canvas, p);
695     p = w2d(pw);
697     NR::Rect const r = canvas->getViewbox();
699     NR::Point r0 = w2d(r.min());
700     NR::Point r1 = w2d(r.max());
702     if (p[NR::X] >= r0[NR::X] &&
703         p[NR::X] <= r1[NR::X] &&
704         p[NR::Y] >= r1[NR::Y] &&
705         p[NR::Y] <= r0[NR::Y])
706     {
707         return p;
708     } else {
709         return (r0 + r1) / 2;
710     }
713 /**
714  * Put current zoom data in history list.
715  */
716 void
717 SPDesktop::push_current_zoom (GList **history)
719     NR::Rect const area = get_display_area();
721     NRRect *old_zoom = g_new(NRRect, 1);
722     old_zoom->x0 = area.min()[NR::X];
723     old_zoom->x1 = area.max()[NR::X];
724     old_zoom->y0 = area.min()[NR::Y];
725     old_zoom->y1 = area.max()[NR::Y];
726     if ( *history == NULL
727          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
728                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
729                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
730                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
731     {
732         *history = g_list_prepend (*history, old_zoom);
733     }
736 /**
737  * Set viewbox (x0, x1, y0 and y1 are in document pixels. Border is in screen pixels).
738  */
739 void
740 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
742     g_assert(_widget);
744     // save the zoom
745     if (log) {
746         push_current_zoom(&zooms_past);
747         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
748         g_list_free (zooms_future);
749         zooms_future = NULL;
750     }
752     double const cx = 0.5 * (x0 + x1);
753     double const cy = 0.5 * (y0 + y1);
755     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
757     double scale = expansion(_d2w);
758     double newscale;
759     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
760         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
761     } else {
762         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
763     }
765     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); // unit: 'screen pixels' per 'document pixels'
767     int clear = FALSE;
768     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
769         /* Set zoom factors */
770         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
771         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
772         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
773         clear = TRUE;
774     }
776     /* Calculate top left corner (in document pixels) */
777     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
778     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
780     /* Scroll */
781     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
783     /*  update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
784     sp_box3d_context_update_lines(event_context);
786     _widget->updateRulers();
787     _widget->updateScrollbars(expansion(_d2w));
788     _widget->updateZoom();
791 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
793     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
796 /**
797  * Return viewbox dimensions.
798  */
799 NR::Rect SPDesktop::get_display_area() const
801     NR::Rect const viewbox = canvas->getViewbox();
803     double const scale = _d2w[0];
805     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
806                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
809 /**
810  * Revert back to previous zoom if possible.
811  */
812 void
813 SPDesktop::prev_zoom()
815     if (zooms_past == NULL) {
816         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
817         return;
818     }
820     // push current zoom into forward zooms list
821     push_current_zoom (&zooms_future);
823     // restore previous zoom
824     set_display_area (((NRRect *) zooms_past->data)->x0,
825             ((NRRect *) zooms_past->data)->y0,
826             ((NRRect *) zooms_past->data)->x1,
827             ((NRRect *) zooms_past->data)->y1,
828             0, false);
830     // remove the just-added zoom from the past zooms list
831     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
834 /**
835  * Set zoom to next in list.
836  */
837 void
838 SPDesktop::next_zoom()
840     if (zooms_future == NULL) {
841         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
842         return;
843     }
845     // push current zoom into past zooms list
846     push_current_zoom (&zooms_past);
848     // restore next zoom
849     set_display_area (((NRRect *) zooms_future->data)->x0,
850             ((NRRect *) zooms_future->data)->y0,
851             ((NRRect *) zooms_future->data)->x1,
852             ((NRRect *) zooms_future->data)->y1,
853             0, false);
855     // remove the just-used zoom from the zooms_future list
856     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
859 /**
860  * Zoom to point with absolute zoom factor.
861  */
862 void
863 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
865     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
867     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
868     // this check prevents "sliding" when trying to zoom in at maximum zoom;
869     /// \todo someone please fix calculations properly and remove this hack
870     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
871         return;
873     NR::Rect const viewbox = canvas->getViewbox();
875     double const width2 = viewbox.dimensions()[NR::X] / zoom;
876     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
878     set_display_area(cx - px * width2,
879                      cy - py * height2,
880                      cx + (1 - px) * width2,
881                      cy + (1 - py) * height2,
882                      0.0);
885 /**
886  * Zoom to center with absolute zoom factor.
887  */
888 void
889 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
891     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
894 /**
895  * Zoom to point with relative zoom factor.
896  */
897 void
898 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
900     NR::Rect const area = get_display_area();
902     if (cx < area.min()[NR::X]) {
903         cx = area.min()[NR::X];
904     }
905     if (cx > area.max()[NR::X]) {
906         cx = area.max()[NR::X];
907     }
908     if (cy < area.min()[NR::Y]) {
909         cy = area.min()[NR::Y];
910     }
911     if (cy > area.max()[NR::Y]) {
912         cy = area.max()[NR::Y];
913     }
915     gdouble const scale = expansion(_d2w) * zoom;
916     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
917     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
919     zoom_absolute_keep_point(cx, cy, px, py, scale);
922 /**
923  * Zoom to center with relative zoom factor.
924  */
925 void
926 SPDesktop::zoom_relative (double cx, double cy, double zoom)
928     gdouble scale = expansion(_d2w) * zoom;
929     zoom_absolute (cx, cy, scale);
932 /**
933  * Set display area to origin and current document dimensions.
934  */
935 void
936 SPDesktop::zoom_page()
938     NR::Rect d(NR::Point(0, 0),
939                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
941     if (d.isEmpty(1.0)) {
942         return;
943     }
945     set_display_area(d, 10);
948 /**
949  * Set display area to current document width.
950  */
951 void
952 SPDesktop::zoom_page_width()
954     NR::Rect const a = get_display_area();
956     if (sp_document_width(doc()) < 1.0) {
957         return;
958     }
960     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
961                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
963     set_display_area(d, 10);
966 /**
967  * Zoom to selection.
968  */
969 void
970 SPDesktop::zoom_selection()
972     NR::Maybe<NR::Rect> const d = selection->bounds();
974     if ( !d || d->isEmpty(0.1) ) {
975         return;
976     }
978     set_display_area(*d, 10);
981 /**
982  * Tell widget to let zoom widget grab keyboard focus.
983  */
984 void
985 SPDesktop::zoom_grab_focus()
987     _widget->letZoomGrabFocus();
990 /**
991  * Zoom to whole drawing.
992  */
993 void
994 SPDesktop::zoom_drawing()
996     g_return_if_fail (doc() != NULL);
997     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
998     g_return_if_fail (docitem != NULL);
1000     NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
1002     /* Note that the second condition here indicates that
1003     ** there are no items in the drawing.
1004     */
1005     if ( !d || d->isEmpty(1.0) ) {
1006         return;
1007     }
1009     set_display_area(*d, 10);
1012 /**
1013  * Scroll canvas by specific coordinate amount in svg coordinates.
1014  */
1015 void
1016 SPDesktop::scroll_world_in_svg_coords (double dx, double dy, bool is_scrolling)
1018     double scale = expansion(_d2w);
1019     scroll_world(dx*scale, dy*scale, is_scrolling);
1022 /**
1023  * Scroll canvas by specific coordinate amount.
1024  */
1025 void
1026 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
1028     g_assert(_widget);
1030     NR::Rect const viewbox = canvas->getViewbox();
1032     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
1034     /*  update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
1035     sp_box3d_context_update_lines(event_context);
1037     _widget->updateRulers();
1038     _widget->updateScrollbars(expansion(_d2w));
1041 bool
1042 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
1044     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
1046     // autoscrolldistance is in screen pixels, but the display area is in document units
1047     autoscrolldistance /= expansion(_d2w);
1048     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
1050     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
1051         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
1053         NR::Point const s_w( (*p) * _d2w );
1055         gdouble x_to;
1056         if ((*p)[NR::X] < dbox.min()[NR::X])
1057             x_to = dbox.min()[NR::X];
1058         else if ((*p)[NR::X] > dbox.max()[NR::X])
1059             x_to = dbox.max()[NR::X];
1060         else
1061             x_to = (*p)[NR::X];
1063         gdouble y_to;
1064         if ((*p)[NR::Y] < dbox.min()[NR::Y])
1065             y_to = dbox.min()[NR::Y];
1066         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
1067             y_to = dbox.max()[NR::Y];
1068         else
1069             y_to = (*p)[NR::Y];
1071         NR::Point const d_dt(x_to, y_to);
1072         NR::Point const d_w( d_dt * _d2w );
1073         NR::Point const moved_w( d_w - s_w );
1075         if (autoscrollspeed == 0)
1076             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1078         if (autoscrollspeed != 0)
1079             scroll_world (autoscrollspeed * moved_w);
1081         return true;
1082     }
1083     return false;
1086 bool
1087 SPDesktop::is_iconified()
1089     return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1092 void
1093 SPDesktop::iconify()
1095     _widget->setIconified();
1098 bool
1099 SPDesktop::is_maximized()
1101     return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1104 void
1105 SPDesktop::maximize()
1107     _widget->setMaximized();
1110 bool
1111 SPDesktop::is_fullscreen()
1113     return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1116 void
1117 SPDesktop::fullscreen()
1119     _widget->setFullscreen();
1122 void
1123 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1125     _widget->getGeometry (x, y, w, h);
1128 void
1129 SPDesktop::setWindowPosition (NR::Point p)
1131     _widget->setPosition (p);
1134 void
1135 SPDesktop::setWindowSize (gint w, gint h)
1137     _widget->setSize (w, h);
1140 void
1141 SPDesktop::setWindowTransient (void *p, int transient_policy)
1143     _widget->setTransient (p, transient_policy);
1146 Gtk::Window*
1147 SPDesktop::getToplevel( )
1149     return _widget->getWindow();
1152 void
1153 SPDesktop::presentWindow()
1155     _widget->present();
1158 bool
1159 SPDesktop::warnDialog (gchar *text)
1161     return _widget->warnDialog (text);
1164 void
1165 SPDesktop::toggleRulers()
1167     _widget->toggleRulers();
1170 void
1171 SPDesktop::toggleScrollbars()
1173     _widget->toggleScrollbars();
1176 void
1177 SPDesktop::layoutWidget()
1179     _widget->layout();
1182 void
1183 SPDesktop::destroyWidget()
1185     _widget->destroy();
1188 bool
1189 SPDesktop::shutdown()
1191     return _widget->shutdown();
1194 bool SPDesktop::onDeleteUI (GdkEventAny*)
1196     if(shutdown()) 
1197         return true;
1199     destroyWidget();
1200     return false;
1203 /**
1204  *  onWindowStateEvent
1205  *
1206  *  Called when the window changes its maximize/fullscreen/iconify/pinned state.
1207  *  Since GTK doesn't have a way to query this state information directly, we
1208  *  record it for the desktop here, and also possibly trigger a layout.
1209  */
1210 bool
1211 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1213         // Record the desktop window's state
1214     window_state = event->new_window_state;
1216     // Layout may differ depending on full-screen mode or not
1217     GdkWindowState changed = event->changed_mask;
1218     if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1219         layoutWidget();
1220     }
1222         return false;
1225 void
1226 SPDesktop::setToolboxFocusTo (gchar const *label)
1228     _widget->setToolboxFocusTo (label);
1231 void
1232 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1234     _widget->setToolboxAdjustmentValue (id, val);
1237 void
1238 SPDesktop::setToolboxSelectOneValue (gchar const* id, gint val)
1240     _widget->setToolboxSelectOneValue (id, val);
1243 bool
1244 SPDesktop::isToolboxButtonActive (gchar const *id)
1246     return _widget->isToolboxButtonActive (id);
1249 void
1250 SPDesktop::emitToolSubselectionChanged(gpointer data)
1252         _tool_subselection_changed.emit(data);
1253         inkscape_subselection_changed (this);
1256 void
1257 SPDesktop::updateNow()
1259   sp_canvas_update_now(canvas);
1262 void
1263 SPDesktop::enableInteraction()
1265   _widget->enableInteraction();
1268 void SPDesktop::disableInteraction()
1270   _widget->disableInteraction();
1273 void SPDesktop::setWaitingCursor()
1275     GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1276     gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1277     gdk_cursor_unref(waiting);
1278     waiting_cursor = true;
1280     // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1281     // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1282     // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1283     // after the call to setWaitingCursor as it was before
1284     while( Gtk::Main::events_pending() )
1285        Gtk::Main::iteration();
1288 void SPDesktop::clearWaitingCursor()
1290   if (waiting_cursor)
1291       sp_event_context_update_cursor(sp_desktop_event_context(this));
1294 void SPDesktop::toggleColorProfAdjust()
1296     _widget->toggleColorProfAdjust();
1299 void SPDesktop::toggleGrids()
1301     if (namedview->grids) {
1302         if(gridgroup) {
1303             showGrids(!grids_visible);
1304         }
1305     } else {
1306         //there is no grid present at the moment. add a rectangular grid and make it visible
1307         Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1308         Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1309         showGrids(true);
1310     }
1313 void SPDesktop::showGrids(bool show, bool dirty_document)
1315     grids_visible = show;
1316     sp_namedview_show_grids(namedview, grids_visible, dirty_document);
1317     if (show) {
1318         sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1319     } else {
1320         sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1321     }
1324 void SPDesktop::toggleSnapping()
1326     bool v = namedview->snap_manager.getSnapEnabledGlobally();
1327     Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1328     sp_repr_set_boolean(repr, "inkscape:snap-global", !v);
1331 //----------------------------------------------------------------------
1332 // Callback implementations. The virtual ones are connected by the view.
1334 void
1335 SPDesktop::onPositionSet (double x, double y)
1337     _widget->viewSetPosition (NR::Point(x,y));
1340 void
1341 SPDesktop::onResized (double /*x*/, double /*y*/)
1343    // Nothing called here
1346 /**
1347  * Redraw callback; queues Gtk redraw; connected by View.
1348  */
1349 void
1350 SPDesktop::onRedrawRequested ()
1352     if (main) {
1353         _widget->requestCanvasUpdate();
1354     }
1357 void
1358 SPDesktop::updateCanvasNow()
1360   _widget->requestCanvasUpdateAndWait();
1363 /**
1364  * Associate document with desktop.
1365  */
1366 void
1367 SPDesktop::setDocument (SPDocument *doc)
1369     if (this->doc() && doc) {
1370         namedview->hide(this);
1371         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1372     }
1374     if (_layer_hierarchy) {
1375         _layer_hierarchy->clear();
1376         delete _layer_hierarchy;
1377     }
1378     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1379     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1380     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1381     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1382     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1384     /* setup EventLog */
1385     event_log = new Inkscape::EventLog(doc);
1386     doc->addUndoObserver(*event_log);
1388     _commit_connection.disconnect();
1389     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1391     /// \todo fixme: This condition exists to make sure the code
1392     /// inside is NOT called on initialization, only on replacement. But there
1393     /// are surely more safe methods to accomplish this.
1394     // TODO since the comment had reversed logic, check the intent of this block of code:
1395     if (drawing) {
1396         NRArenaItem *ai = 0;
1398         namedview = sp_document_namedview (doc, NULL);
1399         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1400         number = namedview->getViewCount();
1402         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1403                 SP_CANVAS_ARENA (drawing)->arena,
1404                 dkey,
1405                 SP_ITEM_SHOW_DISPLAY);
1406         if (ai) {
1407             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1408             nr_arena_item_unref (ai);
1409         }
1410         namedview->show(this);
1411         /* Ugly hack */
1412         activate_guides (true);
1413         /* Ugly hack */
1414         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1415     }
1417     _document_replaced_signal.emit (this, doc);
1419     View::setDocument (doc);
1422 void
1423 SPDesktop::onStatusMessage
1424 (Inkscape::MessageType type, gchar const *message)
1426     if (_widget) {
1427         _widget->setMessage(type, message);
1428     }
1431 void
1432 SPDesktop::onDocumentURISet (gchar const* uri)
1434     _widget->setTitle(uri);
1437 /**
1438  * Resized callback.
1439  */
1440 void
1441 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1443     _doc2dt[5] = height;
1444     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1445     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1446     SP_CTRLRECT(page)->setRectangle(a);
1447     SP_CTRLRECT(page_border)->setRectangle(a);
1451 void
1452 SPDesktop::_onActivate (SPDesktop* dt)
1454     if (!dt->_widget) return;
1455     dt->_widget->activateDesktop();
1458 void
1459 SPDesktop::_onDeactivate (SPDesktop* dt)
1461     if (!dt->_widget) return;
1462     dt->_widget->deactivateDesktop();
1465 void
1466 SPDesktop::_onSelectionModified
1467 (Inkscape::Selection */*selection*/, guint /*flags*/, SPDesktop *dt)
1469     if (!dt->_widget) return;
1470     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1473 static void
1474 _onSelectionChanged
1475 (Inkscape::Selection *selection, SPDesktop *desktop)
1477     /** \todo
1478      * only change the layer for single selections, or what?
1479      * This seems reasonable -- for multiple selections there can be many
1480      * different layers involved.
1481      */
1482     SPItem *item=selection->singleItem();
1483     if (item) {
1484         SPObject *layer=desktop->layerForObject(item);
1485         if ( layer && layer != desktop->currentLayer() ) {
1486             desktop->setCurrentLayer(layer);
1487         }
1488     }
1491 /**
1492  * Calls event handler of current event context.
1493  * \param arena Unused
1494  * \todo fixme
1495  */
1496 static gint
1497 _arena_handler (SPCanvasArena */*arena*/, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1499     if (ai) {
1500         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1501         return sp_event_context_item_handler (desktop->event_context, spi, event);
1502     } else {
1503         return sp_event_context_root_handler (desktop->event_context, event);
1504     }
1507 static void
1508 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1509     g_return_if_fail(SP_IS_GROUP(layer));
1510     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1513 /// Callback
1514 static void
1515 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1516     g_return_if_fail(SP_IS_GROUP(layer));
1517     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1520 /// Callback
1521 static void
1522 _layer_hierarchy_changed(SPObject */*top*/, SPObject *bottom,
1523                                          SPDesktop *desktop)
1525     desktop->_layer_changed_signal.emit (bottom);
1528 /// Called when document is starting to be rebuilt.
1529 static void
1530 _reconstruction_start (SPDesktop * desktop)
1532     // printf("Desktop, starting reconstruction\n");
1533     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1534     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1536     /*
1537     GSList const * selection_objs = desktop->selection->list();
1538     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1540     }
1541     */
1542     desktop->selection->clear();
1544     // printf("Desktop, starting reconstruction end\n");
1547 /// Called when document rebuild is finished.
1548 static void
1549 _reconstruction_finish (SPDesktop * desktop)
1551     // printf("Desktop, finishing reconstruction\n");
1552     if (desktop->_reconstruction_old_layer_id == NULL)
1553         return;
1555     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1556     if (newLayer != NULL)
1557         desktop->setCurrentLayer(newLayer);
1559     g_free(desktop->_reconstruction_old_layer_id);
1560     desktop->_reconstruction_old_layer_id = NULL;
1561     // printf("Desktop, finishing reconstruction end\n");
1562     return;
1565 /**
1566  * Namedview_modified callback.
1567  */
1568 static void
1569 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1571     SPNamedView *nv=SP_NAMEDVIEW(obj);
1573     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1575         /* Recalculate snap distances */
1576         /* FIXME: why is the desktop getting involved in setting up something
1577         ** that is entirely to do with the namedview?
1578         */
1579         _update_snap_distances (desktop);
1581         /* Show/hide page background */
1582         if (nv->pagecolor & 0xff) {
1583             sp_canvas_item_show (desktop->table);
1584             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1585             sp_canvas_item_move_to_z (desktop->table, 0);
1586         } else {
1587             sp_canvas_item_hide (desktop->table);
1588         }
1590         /* Show/hide page border */
1591         if (nv->showborder) {
1592             // show
1593             sp_canvas_item_show (desktop->page_border);
1594             // set color and shadow
1595             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1596             if (nv->pageshadow) {
1597                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1598             }
1599             // place in the z-order stack
1600             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1601                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1602             } else {
1603                 int order = sp_canvas_item_order (desktop->page_border);
1604                 int morder = sp_canvas_item_order (desktop->drawing);
1605                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1606                                     morder - order);
1607             }
1608         } else {
1609                 sp_canvas_item_hide (desktop->page_border);
1610                 if (nv->pageshadow) {
1611                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1612                 }
1613         }
1615         /* Show/hide page shadow */
1616         if (nv->showpageshadow && nv->pageshadow) {
1617             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1618         } else {
1619             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1620         }
1622         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1623             (SP_RGBA32_R_U(nv->pagecolor) +
1624              SP_RGBA32_G_U(nv->pagecolor) +
1625              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1626             // the background color is light or transparent, use black outline
1627             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1628         } else { // use white outline
1629             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1630         }
1631     }
1634 /**
1635  * Callback to reset snapper's distances.
1636  */
1637 static void
1638 _update_snap_distances (SPDesktop *desktop)
1640     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1642     SPNamedView &nv = *desktop->namedview;
1644     //tell all grid snappers
1645     for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1646         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1647         grid->snapper->setSnapperTolerance(sp_convert_distance_full(nv.gridtolerance,
1648                                                                       *nv.gridtoleranceunit,
1649                                                                       px));
1650     }
1652     nv.snap_manager.guide.setSnapperTolerance(sp_convert_distance_full(nv.guidetolerance,
1653                                                                        *nv.guidetoleranceunit,
1654                                                                        px));
1655     nv.snap_manager.object.setSnapperTolerance(sp_convert_distance_full(nv.objecttolerance,
1656                                                                         *nv.objecttoleranceunit,
1657                                                                         px));
1661 NR::Matrix SPDesktop::w2d() const
1663     return _w2d;
1666 NR::Point SPDesktop::w2d(NR::Point const &p) const
1668     return p * _w2d;
1671 NR::Point SPDesktop::d2w(NR::Point const &p) const
1673     return p * _d2w;
1676 NR::Matrix SPDesktop::doc2dt() const
1678     return _doc2dt;
1681 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1683     return p * _doc2dt;
1686 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1688     return p / _doc2dt;
1692 /**
1693  * Pop event context from desktop's context stack. Never used.
1694  */
1695 // void
1696 // SPDesktop::pop_event_context (unsigned int key)
1697 // {
1698 //    SPEventContext *ec = NULL;
1699 //
1700 //    if (event_context && event_context->key == key) {
1701 //        g_return_if_fail (event_context);
1702 //        g_return_if_fail (event_context->next);
1703 //        ec = event_context;
1704 //        sp_event_context_deactivate (ec);
1705 //        event_context = ec->next;
1706 //        sp_event_context_activate (event_context);
1707 //        _event_context_changed_signal.emit (this, ec);
1708 //    }
1709 //
1710 //    SPEventContext *ref = event_context;
1711 //    while (ref && ref->next && ref->next->key != key)
1712 //        ref = ref->next;
1713 //
1714 //    if (ref && ref->next) {
1715 //        ec = ref->next;
1716 //        ref->next = ec->next;
1717 //    }
1718 //
1719 //    if (ec) {
1720 //        sp_event_context_finish (ec);
1721 //        g_object_unref (G_OBJECT (ec));
1722 //    }
1723 // }
1725 /*
1726   Local Variables:
1727   mode:c++
1728   c-file-style:"stroustrup"
1729   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1730   indent-tabs-mode:nil
1731   fill-column:99
1732   End:
1733 */
1734 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :