Code

Write/read perspectives to/from SVG; store ratios of the distances from corners to...
[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-2007 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 "libnr/nr-matrix-div.h"
81 #include "libnr/nr-rect-ops.h"
82 #include "ui/dialog/dialog-manager.h"
83 #include "xml/repr.h"
84 #include "message-context.h"
85 #include "layer-manager.h"
86 #include "event-log.h"
87 #include "display/canvas-grid.h"
89 #include "display/sp-canvas.h"
91 namespace Inkscape { namespace XML { class Node; }}
93 // Callback declarations
94 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
95 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
96 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
97 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
98 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
99 static void _reconstruction_start(SPDesktop * desktop);
100 static void _reconstruction_finish(SPDesktop * desktop);
101 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
102 static void _update_snap_distances (SPDesktop *desktop);
104 /**
105  * Return new desktop object.
106  * \pre namedview != NULL.
107  * \pre canvas != NULL.
108  */
109 SPDesktop::SPDesktop() :
110     _dlg_mgr( 0 ),
111     namedview( 0 ),
112     canvas( 0 ),
113     selection( 0 ),
114     event_context( 0 ),
115     layer_manager( 0 ),
116     event_log( 0 ),
117     acetate( 0 ),
118     main( 0 ),
119     gridgroup( 0 ),
120     guides( 0 ),
121     drawing( 0 ),
122     sketch( 0 ),
123     controls( 0 ),
124     table( 0 ),
125     page( 0 ),
126     page_border( 0 ),
127     current( 0 ),
128     zooms_past( 0 ),
129     zooms_future( 0 ),
130     dkey( 0 ),
131     number( 0 ),
132     window_state(0),
133     interaction_disabled_counter( 0 ),
134     waiting_cursor( false ),
135     perspectives (NULL),
136     guides_active( false ),
137     gr_item( 0 ),
138     gr_point_type( 0 ),
139     gr_point_i( 0 ),
140     gr_fill_or_stroke( true ),
141     _layer_hierarchy( 0 ),
142     _reconstruction_old_layer_id( 0 ),
143     _widget( 0 ),
144     _inkscape( 0 ),
145     _guides_message_context( 0 ),
146     _active( false ),
147     _w2d(),
148     _d2w(),
149     _doc2dt( NR::Matrix(NR::scale(1, -1)) ),
150     grids_visible( true )
152     _d2w.set_identity();
153     _w2d.set_identity();
155     selection = Inkscape::GC::release( new Inkscape::Selection(this) );
158 void
159 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
161     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
163     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
165     namedview = nv;
166     canvas = aCanvas;
168     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
169     /* Kill flicker */
170     sp_document_ensure_up_to_date (document);
172     /* Setup Dialog Manager */
173     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
175     dkey = sp_item_display_key_new (1);
177     /* Connect document */
178     setDocument (document);
180     number = namedview->getViewCount();
183     /* Setup Canvas */
184     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
186     SPCanvasGroup *root = sp_canvas_root (canvas);
188     /* Setup adminstrative layers */
189     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
190     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
191     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
192     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
194     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
195     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
196     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
197     sp_canvas_item_move_to_z (table, 0);
199     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
200     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
201     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
203     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
204     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
206     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
208     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
209         // Start in outline mode
210         setDisplayModeOutline();
211     } else {
212         // Start in normal mode, default
213         setDisplayModeNormal();
214     }
216     gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
217     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
218     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
219     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
221     /* Push select tool to the bottom of stack */
222     /** \todo
223      * FIXME: this is the only call to this.  Everything else seems to just
224      * call "set" instead of "push".  Can we assume that there is only one
225      * context ever?
226      */
227     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
229     // display rect and zoom are now handled in sp_desktop_widget_realize()
231     NR::Rect const d(NR::Point(0.0, 0.0),
232                      NR::Point(sp_document_width(document), sp_document_height(document)));
234     SP_CTRLRECT(page)->setRectangle(d);
235     SP_CTRLRECT(page_border)->setRectangle(d);
237     /* the following sets the page shadow on the canvas
238        It was originally set to 5, which is really cheesy!
239        It now is an attribute in the document's namedview. If a value of
240        0 is used, then the constructor for a shadow is not initialized.
241     */
243     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
244         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
245     }
248     /* Connect event for page resize */
249     _doc2dt[5] = sp_document_height (document);
250     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
252     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
254     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
255             SP_CANVAS_ARENA (drawing)->arena,
256             dkey,
257             SP_ITEM_SHOW_DISPLAY);
258     if (ai) {
259         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
260         nr_arena_item_unref (ai);
261     }
263     namedview->show(this);
264     /* Ugly hack */
265     activate_guides (true);
266     /* Ugly hack */
267     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
269 /* Set up notification of rebuilding the document, this allows
270        for saving object related settings in the document. */
271     _reconstruction_start_connection =
272         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
273     _reconstruction_finish_connection =
274         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
275     _reconstruction_old_layer_id = NULL;
277     // ?
278     // sp_active_desktop_set (desktop);
279     _inkscape = INKSCAPE;
281     _activate_connection = _activate_signal.connect(
282         sigc::bind(
283             sigc::ptr_fun(_onActivate),
284             this
285         )
286     );
287      _deactivate_connection = _deactivate_signal.connect(
288         sigc::bind(
289             sigc::ptr_fun(_onDeactivate),
290             this
291         )
292     );
294     _sel_modified_connection = selection->connectModified(
295         sigc::bind(
296             sigc::ptr_fun(&_onSelectionModified),
297             this
298         )
299     );
300     _sel_changed_connection = selection->connectChanged(
301         sigc::bind(
302             sigc::ptr_fun(&_onSelectionChanged),
303             this
304         )
305     );
308     /* setup LayerManager */
309     //   (Setting up after the connections are all in place, as it may use some of them)
310     layer_manager = new Inkscape::LayerManager( this );
312     grids_visible = true;
314     /* Create initial perspective, append it to the list of existing perspectives
315        and make it the current perspective */
316     Box3D::Perspective3D *initial_persp = new Box3D::Perspective3D (
317                                           // VP in x-direction
318                                           Box3D::VanishingPoint( NR::Point(-50.0, 600.0),
319                                                                  NR::Point( -1.0,   0.0), Box3D::VP_FINITE),
320                                           // VP in y-direction
321                                           Box3D::VanishingPoint( NR::Point(500.0,1000.0),
322                                                                  NR::Point(  0.0,   1.0), Box3D::VP_INFINITE),
323                                           // VP in z-direction
324                                           Box3D::VanishingPoint( NR::Point(700.0, 600.0),
325                                                                  NR::Point(sqrt(3.0),1.0), Box3D::VP_FINITE));
326     this->add_perspective (initial_persp);
327     Box3D::Perspective3D::current_perspective = (Box3D::Perspective3D *) perspectives->data;
331 void SPDesktop::destroy()
333     _activate_connection.disconnect();
334     _deactivate_connection.disconnect();
335     _sel_modified_connection.disconnect();
336     _sel_changed_connection.disconnect();
337     _modified_connection.disconnect();
338     _commit_connection.disconnect();
339     _reconstruction_start_connection.disconnect();
340     _reconstruction_finish_connection.disconnect();
342     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
343     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
344     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
346     while (event_context) {
347         SPEventContext *ec = event_context;
348         event_context = ec->next;
349         sp_event_context_finish (ec);
350         g_object_unref (G_OBJECT (ec));
351     }
353     if (_layer_hierarchy) {
354         delete _layer_hierarchy;
355     }
357     if (_inkscape) {
358         _inkscape = NULL;
359     }
361     if (drawing) {
362         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
363         drawing = NULL;
364     }
366     delete _guides_message_context;
367     _guides_message_context = NULL;
369     g_list_free (zooms_past);
370     g_list_free (zooms_future);
372     for (GSList *p = this->perspectives; p != NULL; p = p->next) {
373         delete ((Box3D::Perspective3D *) p->data);
374     }
375     g_slist_free (perspectives);
378 SPDesktop::~SPDesktop() {}
380 //--------------------------------------------------------------------
381 /* Public methods */
383 void SPDesktop::setDisplayModeNormal()
385     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
386     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
387     displayMode = RENDERMODE_NORMAL;
388     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
389     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
392 void SPDesktop::setDisplayModeOutline()
394     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
395     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
396     displayMode = RENDERMODE_OUTLINE;
397     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
398     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
401 void SPDesktop::displayModeToggle()
403     if (displayMode == RENDERMODE_OUTLINE)
404         setDisplayModeNormal();
405     else 
406         setDisplayModeOutline();
409 /**
410  * Returns current root (=bottom) layer.
411  */
412 SPObject *SPDesktop::currentRoot() const
414     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
417 /**
418  * Returns current top layer.
419  */
420 SPObject *SPDesktop::currentLayer() const
422     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
425 /**
426  * Sets the current layer of the desktop.
427  * 
428  * Make \a object the top layer.
429  */
430 void SPDesktop::setCurrentLayer(SPObject *object) {
431     g_return_if_fail(SP_IS_GROUP(object));
432     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
433     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
434     _layer_hierarchy->setBottom(object);
437 /**
438  * Return layer that contains \a object.
439  */
440 SPObject *SPDesktop::layerForObject(SPObject *object) {
441     g_return_val_if_fail(object != NULL, NULL);
443     SPObject *root=currentRoot();
444     object = SP_OBJECT_PARENT(object);
445     while ( object && object != root && !isLayer(object) ) {
446         object = SP_OBJECT_PARENT(object);
447     }
448     return object;
451 /**
452  * True if object is a layer.
453  */
454 bool SPDesktop::isLayer(SPObject *object) const {
455     return ( SP_IS_GROUP(object)
456              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
457                   == SPGroup::LAYER ) );
460 /**
461  * True if desktop viewport fully contains \a item's bbox.
462  */
463 bool SPDesktop::isWithinViewport (SPItem *item) const
465     NR::Rect const viewport = get_display_area();
466     NR::Maybe<NR::Rect> const bbox = sp_item_bbox_desktop(item);
467     if (bbox) {
468         return viewport.contains(*bbox);
469     } else {
470         return true;
471     }
474 ///
475 bool SPDesktop::itemIsHidden(SPItem const *item) const {
476     return item->isHidden(this->dkey);
479 /**
480  * Set activate property of desktop; emit signal if changed.
481  */
482 void
483 SPDesktop::set_active (bool new_active)
485     if (new_active != _active) {
486         _active = new_active;
487         if (new_active) {
488             _activate_signal.emit();
489         } else {
490             _deactivate_signal.emit();
491         }
492     }
495 /**
496  * Set activate status of current desktop's named view.
497  */
498 void
499 SPDesktop::activate_guides(bool activate)
501     guides_active = activate;
502     namedview->activateGuides(this, activate);
505 /**
506  * Make desktop switch documents.
507  */
508 void
509 SPDesktop::change_document (SPDocument *theDocument)
511     g_return_if_fail (theDocument != NULL);
513     /* unselect everything before switching documents */
514     selection->clear();
516     setDocument (theDocument);
517     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
518     _document_replaced_signal.emit (this, theDocument);
521 /**
522  * Make desktop switch event contexts.
523  */
524 void
525 SPDesktop::set_event_context (GtkType type, const gchar *config)
527     SPEventContext *ec;
528     while (event_context) {
529         ec = event_context;
530         sp_event_context_deactivate (ec);
531         event_context = ec->next;
532         sp_event_context_finish (ec);
533         g_object_unref (G_OBJECT (ec));
534     }
536     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
537     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
538     ec->next = event_context;
539     event_context = ec;
540     sp_event_context_activate (ec);
541     _event_context_changed_signal.emit (this, ec);
544 /**
545  * Push event context onto desktop's context stack.
546  */
547 void
548 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
550     SPEventContext *ref, *ec;
551     Inkscape::XML::Node *repr;
553     if (event_context && event_context->key == key) return;
554     ref = event_context;
555     while (ref && ref->next && ref->next->key != key) ref = ref->next;
556     if (ref && ref->next) {
557         ec = ref->next;
558         ref->next = ec->next;
559         sp_event_context_finish (ec);
560         g_object_unref (G_OBJECT (ec));
561     }
563     if (event_context) sp_event_context_deactivate (event_context);
564     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
565     ec = sp_event_context_new (type, this, repr, key);
566     ec->next = event_context;
567     event_context = ec;
568     sp_event_context_activate (ec);
569     _event_context_changed_signal.emit (this, ec);
572 /**
573  * Sets the coordinate status to a given point
574  */
575 void
576 SPDesktop::set_coordinate_status (NR::Point p) {
577     _widget->setCoordinateStatus(p);
580 /**
581  * \see sp_document_item_from_list_at_point_bottom()
582  */
583 SPItem *
584 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
586     g_return_val_if_fail (doc() != NULL, NULL);
587     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
590 /**
591  * \see sp_document_item_at_point()
592  */
593 SPItem *
594 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
596     g_return_val_if_fail (doc() != NULL, NULL);
597     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
600 /**
601  * \see sp_document_group_at_point()
602  */
603 SPItem *
604 SPDesktop::group_at_point (NR::Point const p) const
606     g_return_val_if_fail (doc() != NULL, NULL);
607     return sp_document_group_at_point (doc(), dkey, p);
610 /**
611  * \brief  Returns the mouse point in document coordinates; if mouse is
612  * outside the canvas, returns the center of canvas viewpoint
613  */
614 NR::Point
615 SPDesktop::point() const
617     NR::Point p = _widget->getPointer();
618     NR::Point pw = sp_canvas_window_to_world (canvas, p);
619     p = w2d(pw);
621     NR::Rect const r = canvas->getViewbox();
623     NR::Point r0 = w2d(r.min());
624     NR::Point r1 = w2d(r.max());
626     if (p[NR::X] >= r0[NR::X] &&
627         p[NR::X] <= r1[NR::X] &&
628         p[NR::Y] >= r1[NR::Y] &&
629         p[NR::Y] <= r0[NR::Y])
630     {
631         return p;
632     } else {
633         return (r0 + r1) / 2;
634     }
637 /**
638  * Put current zoom data in history list.
639  */
640 void
641 SPDesktop::push_current_zoom (GList **history)
643     NR::Rect const area = get_display_area();
645     NRRect *old_zoom = g_new(NRRect, 1);
646     old_zoom->x0 = area.min()[NR::X];
647     old_zoom->x1 = area.max()[NR::X];
648     old_zoom->y0 = area.min()[NR::Y];
649     old_zoom->y1 = area.max()[NR::Y];
650     if ( *history == NULL
651          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
652                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
653                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
654                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
655     {
656         *history = g_list_prepend (*history, old_zoom);
657     }
660 /**
661  * Set viewbox.
662  */
663 void
664 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
666     g_assert(_widget);
668     // save the zoom
669     if (log) {
670         push_current_zoom(&zooms_past);
671         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
672         g_list_free (zooms_future);
673         zooms_future = NULL;
674     }
676     double const cx = 0.5 * (x0 + x1);
677     double const cy = 0.5 * (y0 + y1);
679     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
681     double scale = expansion(_d2w);
682     double newscale;
683     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
684         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
685     } else {
686         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
687     }
689     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
691     int clear = FALSE;
692     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
693         /* Set zoom factors */
694         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
695         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
696         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
697         clear = TRUE;
698     }
700     /* Calculate top left corner */
701     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
702     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
704     /* Scroll */
705     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
707     _widget->updateRulers();
708     _widget->updateScrollbars(expansion(_d2w));
709     _widget->updateZoom();
712 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
714     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
717 /**
718  * Add a perspective to the desktop if it doesn't exist yet
719  */
720 void
721 SPDesktop::add_perspective (Box3D::Perspective3D * const persp)
723     // check that the perspective is not yet linked to another desktop
724     if (persp->desktop != NULL) {
725         g_assert (persp->desktop == this);
726     }
728     // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical?
729     //        If so, we need to take care of relinking the boxes, etc.
730     if (persp == NULL || g_slist_find (this->perspectives, persp)) return;
731     this->perspectives = g_slist_prepend (this->perspectives, persp);
732     persp->desktop = this;
735 void SPDesktop::remove_perspective (Box3D::Perspective3D * const persp)
737     if (persp == NULL) return;
738     if (!g_slist_find (this->perspectives, persp)) {
739         g_warning ("Could not find perspective in current desktop. Not removed.\n");
740         return;
741     }
742     this->perspectives = g_slist_remove (this->perspectives, persp);
745 // find an existing perspective whose VPs are equal to those of persp
746 Box3D::Perspective3D * SPDesktop::find_perspective (Box3D::Perspective3D * const persp)
748     for (GSList *p = this->perspectives; p != NULL; p = p->next) {
749         if (*((Box3D::Perspective3D *) p->data) == *persp) {
750             return ((Box3D::Perspective3D *) p->data);
751         }
752     }
753     return NULL; // perspective was not found
756 /**
757  * Return viewbox dimensions.
758  */
759 NR::Rect SPDesktop::get_display_area() const
761     NR::Rect const viewbox = canvas->getViewbox();
763     double const scale = _d2w[0];
765     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
766                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
769 /**
770  * Revert back to previous zoom if possible.
771  */
772 void
773 SPDesktop::prev_zoom()
775     if (zooms_past == NULL) {
776         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
777         return;
778     }
780     // push current zoom into forward zooms list
781     push_current_zoom (&zooms_future);
783     // restore previous zoom
784     set_display_area (((NRRect *) zooms_past->data)->x0,
785             ((NRRect *) zooms_past->data)->y0,
786             ((NRRect *) zooms_past->data)->x1,
787             ((NRRect *) zooms_past->data)->y1,
788             0, false);
790     // remove the just-added zoom from the past zooms list
791     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
794 /**
795  * Set zoom to next in list.
796  */
797 void
798 SPDesktop::next_zoom()
800     if (zooms_future == NULL) {
801         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
802         return;
803     }
805     // push current zoom into past zooms list
806     push_current_zoom (&zooms_past);
808     // restore next zoom
809     set_display_area (((NRRect *) zooms_future->data)->x0,
810             ((NRRect *) zooms_future->data)->y0,
811             ((NRRect *) zooms_future->data)->x1,
812             ((NRRect *) zooms_future->data)->y1,
813             0, false);
815     // remove the just-used zoom from the zooms_future list
816     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
819 /**
820  * Zoom to point with absolute zoom factor.
821  */
822 void
823 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
825     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
827     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
828     // this check prevents "sliding" when trying to zoom in at maximum zoom;
829     /// \todo someone please fix calculations properly and remove this hack
830     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
831         return;
833     NR::Rect const viewbox = canvas->getViewbox();
835     double const width2 = viewbox.dimensions()[NR::X] / zoom;
836     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
838     set_display_area(cx - px * width2,
839                      cy - py * height2,
840                      cx + (1 - px) * width2,
841                      cy + (1 - py) * height2,
842                      0.0);
845 /**
846  * Zoom to center with absolute zoom factor.
847  */
848 void
849 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
851     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
854 /**
855  * Zoom to point with relative zoom factor.
856  */
857 void
858 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
860     NR::Rect const area = get_display_area();
862     if (cx < area.min()[NR::X]) {
863         cx = area.min()[NR::X];
864     }
865     if (cx > area.max()[NR::X]) {
866         cx = area.max()[NR::X];
867     }
868     if (cy < area.min()[NR::Y]) {
869         cy = area.min()[NR::Y];
870     }
871     if (cy > area.max()[NR::Y]) {
872         cy = area.max()[NR::Y];
873     }
875     gdouble const scale = expansion(_d2w) * zoom;
876     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
877     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
879     zoom_absolute_keep_point(cx, cy, px, py, scale);
882 /**
883  * Zoom to center with relative zoom factor.
884  */
885 void
886 SPDesktop::zoom_relative (double cx, double cy, double zoom)
888     gdouble scale = expansion(_d2w) * zoom;
889     zoom_absolute (cx, cy, scale);
892 /**
893  * Set display area to origin and current document dimensions.
894  */
895 void
896 SPDesktop::zoom_page()
898     NR::Rect d(NR::Point(0, 0),
899                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
901     if (d.isEmpty(1.0)) {
902         return;
903     }
905     set_display_area(d, 10);
908 /**
909  * Set display area to current document width.
910  */
911 void
912 SPDesktop::zoom_page_width()
914     NR::Rect const a = get_display_area();
916     if (sp_document_width(doc()) < 1.0) {
917         return;
918     }
920     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
921                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
923     set_display_area(d, 10);
926 /**
927  * Zoom to selection.
928  */
929 void
930 SPDesktop::zoom_selection()
932     NR::Maybe<NR::Rect> const d = selection->bounds();
934     if ( !d || d->isEmpty(0.1) ) {
935         return;
936     }
938     set_display_area(*d, 10);
941 /**
942  * Tell widget to let zoom widget grab keyboard focus.
943  */
944 void
945 SPDesktop::zoom_grab_focus()
947     _widget->letZoomGrabFocus();
950 /**
951  * Zoom to whole drawing.
952  */
953 void
954 SPDesktop::zoom_drawing()
956     g_return_if_fail (doc() != NULL);
957     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
958     g_return_if_fail (docitem != NULL);
960     NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
962     /* Note that the second condition here indicates that
963     ** there are no items in the drawing.
964     */
965     if ( !d || d->isEmpty(1.0) ) {
966         return;
967     }
969     set_display_area(*d, 10);
972 /**
973  * Scroll canvas by specific coordinate amount.
974  */
975 void
976 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
978     g_assert(_widget);
980     NR::Rect const viewbox = canvas->getViewbox();
982     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
984     _widget->updateRulers();
985     _widget->updateScrollbars(expansion(_d2w));
988 bool
989 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
991     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
993     // autoscrolldistance is in screen pixels, but the display area is in document units
994     autoscrolldistance /= expansion(_d2w);
995     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
997     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
998         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
1000         NR::Point const s_w( (*p) * _d2w );
1002         gdouble x_to;
1003         if ((*p)[NR::X] < dbox.min()[NR::X])
1004             x_to = dbox.min()[NR::X];
1005         else if ((*p)[NR::X] > dbox.max()[NR::X])
1006             x_to = dbox.max()[NR::X];
1007         else
1008             x_to = (*p)[NR::X];
1010         gdouble y_to;
1011         if ((*p)[NR::Y] < dbox.min()[NR::Y])
1012             y_to = dbox.min()[NR::Y];
1013         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
1014             y_to = dbox.max()[NR::Y];
1015         else
1016             y_to = (*p)[NR::Y];
1018         NR::Point const d_dt(x_to, y_to);
1019         NR::Point const d_w( d_dt * _d2w );
1020         NR::Point const moved_w( d_w - s_w );
1022         if (autoscrollspeed == 0)
1023             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1025         if (autoscrollspeed != 0)
1026             scroll_world (autoscrollspeed * moved_w);
1028         return true;
1029     }
1030     return false;
1033 bool
1034 SPDesktop::is_iconified()
1036     return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1039 void
1040 SPDesktop::iconify()
1042     _widget->setIconified();
1045 bool
1046 SPDesktop::is_maximized()
1048     return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1051 void
1052 SPDesktop::maximize()
1054     _widget->setMaximized();
1057 bool
1058 SPDesktop::is_fullscreen()
1060     return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1063 void
1064 SPDesktop::fullscreen()
1066     _widget->setFullscreen();
1069 void
1070 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1072     _widget->getGeometry (x, y, w, h);
1075 void
1076 SPDesktop::setWindowPosition (NR::Point p)
1078     _widget->setPosition (p);
1081 void
1082 SPDesktop::setWindowSize (gint w, gint h)
1084     _widget->setSize (w, h);
1087 void
1088 SPDesktop::setWindowTransient (void *p, int transient_policy)
1090     _widget->setTransient (p, transient_policy);
1093 Gtk::Window*
1094 SPDesktop::getToplevel( )
1096     return _widget->getWindow();
1099 void
1100 SPDesktop::presentWindow()
1102     _widget->present();
1105 bool
1106 SPDesktop::warnDialog (gchar *text)
1108     return _widget->warnDialog (text);
1111 void
1112 SPDesktop::toggleRulers()
1114     _widget->toggleRulers();
1117 void
1118 SPDesktop::toggleScrollbars()
1120     _widget->toggleScrollbars();
1123 void
1124 SPDesktop::layoutWidget()
1126     _widget->layout();
1129 void
1130 SPDesktop::destroyWidget()
1132     _widget->destroy();
1135 bool
1136 SPDesktop::shutdown()
1138     return _widget->shutdown();
1141 bool SPDesktop::onDeleteUI (GdkEventAny*)
1143         if(shutdown()) return true;
1144         destroyWidget();
1145         return false;
1148 /**
1149  *  onWindowStateEvent
1150  *
1151  *  Called when the window changes its maximize/fullscreen/iconify/pinned state.
1152  *  Since GTK doesn't have a way to query this state information directly, we
1153  *  record it for the desktop here, and also possibly trigger a layout.
1154  */
1155 bool
1156 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1158         // Record the desktop window's state
1159     window_state = event->new_window_state;
1161     // Layout may differ depending on full-screen mode or not
1162     GdkWindowState changed = event->changed_mask;
1163     if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1164         layoutWidget();
1165     }
1166         
1167         return false;
1170 void
1171 SPDesktop::setToolboxFocusTo (gchar const *label)
1173     _widget->setToolboxFocusTo (label);
1176 void
1177 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1179     _widget->setToolboxAdjustmentValue (id, val);
1182 bool
1183 SPDesktop::isToolboxButtonActive (gchar const *id)
1185     return _widget->isToolboxButtonActive (id);
1188 void
1189 SPDesktop::emitToolSubselectionChanged(gpointer data)
1191         _tool_subselection_changed.emit(data);
1192         inkscape_subselection_changed (this);
1195 void
1196 SPDesktop::updateNow()
1198   sp_canvas_update_now(canvas);
1201 void
1202 SPDesktop::enableInteraction()
1204   _widget->enableInteraction();
1207 void SPDesktop::disableInteraction()
1209   _widget->disableInteraction();
1212 void SPDesktop::setWaitingCursor()
1214     GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1215     gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1216     gdk_cursor_unref(waiting);
1217     waiting_cursor = true;
1219     // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1220     // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1221     // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1222     // after the call to setWaitingCursor as it was before
1223     while( Gtk::Main::events_pending() )
1224        Gtk::Main::iteration();
1227 void SPDesktop::clearWaitingCursor()
1229   if (waiting_cursor)
1230       sp_event_context_update_cursor(sp_desktop_event_context(this));
1233 void SPDesktop::toggleGrid()
1235     if (namedview->grids) {
1236         if(gridgroup) {
1237             grids_visible = !grids_visible;
1238             if (grids_visible) {
1239                 sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1240             } else {
1241                 sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1242             }
1243         }
1244     } else {
1245         //there is no grid present at the moment. add a rectangular grid and make it visible
1246         Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1247         Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1248         grids_visible = true;
1249         sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1250     }
1254 //----------------------------------------------------------------------
1255 // Callback implementations. The virtual ones are connected by the view.
1257 void
1258 SPDesktop::onPositionSet (double x, double y)
1260     _widget->viewSetPosition (NR::Point(x,y));
1263 void
1264 SPDesktop::onResized (double x, double y)
1266    // Nothing called here
1269 /**
1270  * Redraw callback; queues Gtk redraw; connected by View.
1271  */
1272 void
1273 SPDesktop::onRedrawRequested ()
1275     if (main) {
1276         _widget->requestCanvasUpdate();
1277     }
1280 void
1281 SPDesktop::updateCanvasNow()
1283   _widget->requestCanvasUpdateAndWait();
1286 /**
1287  * Associate document with desktop.
1288  */
1289 void
1290 SPDesktop::setDocument (SPDocument *doc)
1292     if (this->doc() && doc) {
1293         namedview->hide(this);
1294         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1295     }
1297     if (_layer_hierarchy) {
1298         _layer_hierarchy->clear();
1299         delete _layer_hierarchy;
1300     }
1301     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1302     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1303     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1304     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1305     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1307     /* setup EventLog */
1308     event_log = new Inkscape::EventLog(doc);
1309     doc->addUndoObserver(*event_log);
1311     _commit_connection.disconnect();
1312     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1314     /// \todo fixme: This condition exists to make sure the code
1315     /// inside is NOT called on initialization, only on replacement. But there
1316     /// are surely more safe methods to accomplish this.
1317     // TODO since the comment had reversed logic, check the intent of this block of code:
1318     if (drawing) {
1319         NRArenaItem *ai = 0;
1321         namedview = sp_document_namedview (doc, NULL);
1322         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1323         number = namedview->getViewCount();
1325         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1326                 SP_CANVAS_ARENA (drawing)->arena,
1327                 dkey,
1328                 SP_ITEM_SHOW_DISPLAY);
1329         if (ai) {
1330             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1331             nr_arena_item_unref (ai);
1332         }
1333         namedview->show(this);
1334         /* Ugly hack */
1335         activate_guides (true);
1336         /* Ugly hack */
1337         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1338     }
1340     _document_replaced_signal.emit (this, doc);
1342     View::setDocument (doc);
1345 void
1346 SPDesktop::onStatusMessage
1347 (Inkscape::MessageType type, gchar const *message)
1349     if (_widget) {
1350         _widget->setMessage(type, message);
1351     }
1354 void
1355 SPDesktop::onDocumentURISet (gchar const* uri)
1357     _widget->setTitle(uri);
1360 /**
1361  * Resized callback.
1362  */
1363 void
1364 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1366     _doc2dt[5] = height;
1367     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1368     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1369     SP_CTRLRECT(page)->setRectangle(a);
1370     SP_CTRLRECT(page_border)->setRectangle(a);
1374 void
1375 SPDesktop::_onActivate (SPDesktop* dt)
1377     if (!dt->_widget) return;
1378     dt->_widget->activateDesktop();
1381 void
1382 SPDesktop::_onDeactivate (SPDesktop* dt)
1384     if (!dt->_widget) return;
1385     dt->_widget->deactivateDesktop();
1388 void
1389 SPDesktop::_onSelectionModified
1390 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1392     if (!dt->_widget) return;
1393     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1396 static void
1397 _onSelectionChanged
1398 (Inkscape::Selection *selection, SPDesktop *desktop)
1400     /** \todo
1401      * only change the layer for single selections, or what?
1402      * This seems reasonable -- for multiple selections there can be many
1403      * different layers involved.
1404      */
1405     SPItem *item=selection->singleItem();
1406     if (item) {
1407         SPObject *layer=desktop->layerForObject(item);
1408         if ( layer && layer != desktop->currentLayer() ) {
1409             desktop->setCurrentLayer(layer);
1410         }
1411     }
1414 /**
1415  * Calls event handler of current event context.
1416  * \param arena Unused
1417  * \todo fixme
1418  */
1419 static gint
1420 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1422     if (ai) {
1423         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1424         return sp_event_context_item_handler (desktop->event_context, spi, event);
1425     } else {
1426         return sp_event_context_root_handler (desktop->event_context, event);
1427     }
1430 static void
1431 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1432     g_return_if_fail(SP_IS_GROUP(layer));
1433     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1436 /// Callback
1437 static void
1438 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1439     g_return_if_fail(SP_IS_GROUP(layer));
1440     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1443 /// Callback
1444 static void
1445 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1446                                          SPDesktop *desktop)
1448     desktop->_layer_changed_signal.emit (bottom);
1451 /// Called when document is starting to be rebuilt.
1452 static void
1453 _reconstruction_start (SPDesktop * desktop)
1455     // printf("Desktop, starting reconstruction\n");
1456     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1457     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1459     /*
1460     GSList const * selection_objs = desktop->selection->list();
1461     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1463     }
1464     */
1465     desktop->selection->clear();
1467     // printf("Desktop, starting reconstruction end\n");
1470 /// Called when document rebuild is finished.
1471 static void
1472 _reconstruction_finish (SPDesktop * desktop)
1474     // printf("Desktop, finishing reconstruction\n");
1475     if (desktop->_reconstruction_old_layer_id == NULL)
1476         return;
1478     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1479     if (newLayer != NULL)
1480         desktop->setCurrentLayer(newLayer);
1482     g_free(desktop->_reconstruction_old_layer_id);
1483     desktop->_reconstruction_old_layer_id = NULL;
1484     // printf("Desktop, finishing reconstruction end\n");
1485     return;
1488 /**
1489  * Namedview_modified callback.
1490  */
1491 static void
1492 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1494     SPNamedView *nv=SP_NAMEDVIEW(obj);
1496     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1498         /* Recalculate snap distances */
1499         /* FIXME: why is the desktop getting involved in setting up something
1500         ** that is entirely to do with the namedview?
1501         */
1502         _update_snap_distances (desktop);
1504         /* Show/hide page background */
1505         if (nv->pagecolor & 0xff) {
1506             sp_canvas_item_show (desktop->table);
1507             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1508             sp_canvas_item_move_to_z (desktop->table, 0);
1509         } else {
1510             sp_canvas_item_hide (desktop->table);
1511         }
1513         /* Show/hide page border */
1514         if (nv->showborder) {
1515             // show
1516             sp_canvas_item_show (desktop->page_border);
1517             // set color and shadow
1518             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1519             if (nv->pageshadow) {
1520                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1521             }
1522             // place in the z-order stack
1523             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1524                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1525             } else {
1526                 int order = sp_canvas_item_order (desktop->page_border);
1527                 int morder = sp_canvas_item_order (desktop->drawing);
1528                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1529                                     morder - order);
1530             }
1531         } else {
1532                 sp_canvas_item_hide (desktop->page_border);
1533                 if (nv->pageshadow) {
1534                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1535                 }
1536         }
1537         
1538         /* Show/hide page shadow */
1539         if (nv->showpageshadow && nv->pageshadow) {
1540             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1541         } else {
1542             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1543         }
1545         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1546             (SP_RGBA32_R_U(nv->pagecolor) +
1547              SP_RGBA32_G_U(nv->pagecolor) +
1548              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1549             // the background color is light or transparent, use black outline
1550             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1551         } else { // use white outline
1552             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1553         }
1554     }
1557 /**
1558  * Callback to reset snapper's distances.
1559  */
1560 static void
1561 _update_snap_distances (SPDesktop *desktop)
1563     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1565     SPNamedView &nv = *desktop->namedview;
1567     //tell all grid snappers
1568     for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1569         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1570         grid->snapper->setDistance(sp_convert_distance_full(nv.gridtolerance,
1571                                                                       *nv.gridtoleranceunit,
1572                                                                       px));
1573     }
1574     
1575     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1576                                                                        *nv.guidetoleranceunit,
1577                                                                        px));
1578     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1579                                                                         *nv.objecttoleranceunit,
1580                                                                         px));
1584 NR::Matrix SPDesktop::w2d() const
1586     return _w2d;
1589 NR::Point SPDesktop::w2d(NR::Point const &p) const
1591     return p * _w2d;
1594 NR::Point SPDesktop::d2w(NR::Point const &p) const
1596     return p * _d2w;
1599 NR::Matrix SPDesktop::doc2dt() const
1601     return _doc2dt;
1604 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1606     return p * _doc2dt;
1609 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1611     return p / _doc2dt;
1615 /**
1616  * Pop event context from desktop's context stack. Never used.
1617  */
1618 // void
1619 // SPDesktop::pop_event_context (unsigned int key)
1620 // {
1621 //    SPEventContext *ec = NULL;
1622 //
1623 //    if (event_context && event_context->key == key) {
1624 //        g_return_if_fail (event_context);
1625 //        g_return_if_fail (event_context->next);
1626 //        ec = event_context;
1627 //        sp_event_context_deactivate (ec);
1628 //        event_context = ec->next;
1629 //        sp_event_context_activate (event_context);
1630 //        _event_context_changed_signal.emit (this, ec);
1631 //    }
1632 //
1633 //    SPEventContext *ref = event_context;
1634 //    while (ref && ref->next && ref->next->key != key)
1635 //        ref = ref->next;
1636 //
1637 //    if (ref && ref->next) {
1638 //        ec = ref->next;
1639 //        ref->next = ec->next;
1640 //    }
1641 //
1642 //    if (ec) {
1643 //        sp_event_context_finish (ec);
1644 //        g_object_unref (G_OBJECT (ec));
1645 //    }
1646 // }
1648 /*
1649   Local Variables:
1650   mode:c++
1651   c-file-style:"stroustrup"
1652   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1653   indent-tabs-mode:nil
1654   fill-column:99
1655   End:
1656 */
1657 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :