Code

6314621c14e146805075c28fae13c2d916a893df
[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     // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical?
724     if (persp == NULL || g_slist_find (this->perspectives, persp)) return;
725     this->perspectives = g_slist_prepend (this->perspectives, persp);
726     persp->desktop = this;
729 void SPDesktop::remove_perspective (Box3D::Perspective3D * const persp)
731     if (persp == NULL) return;
733     if (!g_slist_find (this->perspectives, persp)) {
734         g_warning ("Could not find perspective in current desktop. Not removed.\n");
735         return;
736     }
738     this->perspectives = g_slist_remove (this->perspectives, persp);
741 /**
742  * Return viewbox dimensions.
743  */
744 NR::Rect SPDesktop::get_display_area() const
746     NR::Rect const viewbox = canvas->getViewbox();
748     double const scale = _d2w[0];
750     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
751                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
754 /**
755  * Revert back to previous zoom if possible.
756  */
757 void
758 SPDesktop::prev_zoom()
760     if (zooms_past == NULL) {
761         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
762         return;
763     }
765     // push current zoom into forward zooms list
766     push_current_zoom (&zooms_future);
768     // restore previous zoom
769     set_display_area (((NRRect *) zooms_past->data)->x0,
770             ((NRRect *) zooms_past->data)->y0,
771             ((NRRect *) zooms_past->data)->x1,
772             ((NRRect *) zooms_past->data)->y1,
773             0, false);
775     // remove the just-added zoom from the past zooms list
776     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
779 /**
780  * Set zoom to next in list.
781  */
782 void
783 SPDesktop::next_zoom()
785     if (zooms_future == NULL) {
786         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
787         return;
788     }
790     // push current zoom into past zooms list
791     push_current_zoom (&zooms_past);
793     // restore next zoom
794     set_display_area (((NRRect *) zooms_future->data)->x0,
795             ((NRRect *) zooms_future->data)->y0,
796             ((NRRect *) zooms_future->data)->x1,
797             ((NRRect *) zooms_future->data)->y1,
798             0, false);
800     // remove the just-used zoom from the zooms_future list
801     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
804 /**
805  * Zoom to point with absolute zoom factor.
806  */
807 void
808 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
810     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
812     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
813     // this check prevents "sliding" when trying to zoom in at maximum zoom;
814     /// \todo someone please fix calculations properly and remove this hack
815     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
816         return;
818     NR::Rect const viewbox = canvas->getViewbox();
820     double const width2 = viewbox.dimensions()[NR::X] / zoom;
821     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
823     set_display_area(cx - px * width2,
824                      cy - py * height2,
825                      cx + (1 - px) * width2,
826                      cy + (1 - py) * height2,
827                      0.0);
830 /**
831  * Zoom to center with absolute zoom factor.
832  */
833 void
834 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
836     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
839 /**
840  * Zoom to point with relative zoom factor.
841  */
842 void
843 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
845     NR::Rect const area = get_display_area();
847     if (cx < area.min()[NR::X]) {
848         cx = area.min()[NR::X];
849     }
850     if (cx > area.max()[NR::X]) {
851         cx = area.max()[NR::X];
852     }
853     if (cy < area.min()[NR::Y]) {
854         cy = area.min()[NR::Y];
855     }
856     if (cy > area.max()[NR::Y]) {
857         cy = area.max()[NR::Y];
858     }
860     gdouble const scale = expansion(_d2w) * zoom;
861     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
862     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
864     zoom_absolute_keep_point(cx, cy, px, py, scale);
867 /**
868  * Zoom to center with relative zoom factor.
869  */
870 void
871 SPDesktop::zoom_relative (double cx, double cy, double zoom)
873     gdouble scale = expansion(_d2w) * zoom;
874     zoom_absolute (cx, cy, scale);
877 /**
878  * Set display area to origin and current document dimensions.
879  */
880 void
881 SPDesktop::zoom_page()
883     NR::Rect d(NR::Point(0, 0),
884                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
886     if (d.isEmpty(1.0)) {
887         return;
888     }
890     set_display_area(d, 10);
893 /**
894  * Set display area to current document width.
895  */
896 void
897 SPDesktop::zoom_page_width()
899     NR::Rect const a = get_display_area();
901     if (sp_document_width(doc()) < 1.0) {
902         return;
903     }
905     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
906                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
908     set_display_area(d, 10);
911 /**
912  * Zoom to selection.
913  */
914 void
915 SPDesktop::zoom_selection()
917     NR::Maybe<NR::Rect> const d = selection->bounds();
919     if ( !d || d->isEmpty(0.1) ) {
920         return;
921     }
923     set_display_area(*d, 10);
926 /**
927  * Tell widget to let zoom widget grab keyboard focus.
928  */
929 void
930 SPDesktop::zoom_grab_focus()
932     _widget->letZoomGrabFocus();
935 /**
936  * Zoom to whole drawing.
937  */
938 void
939 SPDesktop::zoom_drawing()
941     g_return_if_fail (doc() != NULL);
942     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
943     g_return_if_fail (docitem != NULL);
945     NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
947     /* Note that the second condition here indicates that
948     ** there are no items in the drawing.
949     */
950     if ( !d || d->isEmpty(1.0) ) {
951         return;
952     }
954     set_display_area(*d, 10);
957 /**
958  * Scroll canvas by specific coordinate amount.
959  */
960 void
961 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
963     g_assert(_widget);
965     NR::Rect const viewbox = canvas->getViewbox();
967     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
969     _widget->updateRulers();
970     _widget->updateScrollbars(expansion(_d2w));
973 bool
974 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
976     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
978     // autoscrolldistance is in screen pixels, but the display area is in document units
979     autoscrolldistance /= expansion(_d2w);
980     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
982     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
983         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
985         NR::Point const s_w( (*p) * _d2w );
987         gdouble x_to;
988         if ((*p)[NR::X] < dbox.min()[NR::X])
989             x_to = dbox.min()[NR::X];
990         else if ((*p)[NR::X] > dbox.max()[NR::X])
991             x_to = dbox.max()[NR::X];
992         else
993             x_to = (*p)[NR::X];
995         gdouble y_to;
996         if ((*p)[NR::Y] < dbox.min()[NR::Y])
997             y_to = dbox.min()[NR::Y];
998         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
999             y_to = dbox.max()[NR::Y];
1000         else
1001             y_to = (*p)[NR::Y];
1003         NR::Point const d_dt(x_to, y_to);
1004         NR::Point const d_w( d_dt * _d2w );
1005         NR::Point const moved_w( d_w - s_w );
1007         if (autoscrollspeed == 0)
1008             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1010         if (autoscrollspeed != 0)
1011             scroll_world (autoscrollspeed * moved_w);
1013         return true;
1014     }
1015     return false;
1018 bool
1019 SPDesktop::is_iconified()
1021     return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1024 void
1025 SPDesktop::iconify()
1027     _widget->setIconified();
1030 bool
1031 SPDesktop::is_maximized()
1033     return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1036 void
1037 SPDesktop::maximize()
1039     _widget->setMaximized();
1042 bool
1043 SPDesktop::is_fullscreen()
1045     return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1048 void
1049 SPDesktop::fullscreen()
1051     _widget->setFullscreen();
1054 void
1055 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1057     _widget->getGeometry (x, y, w, h);
1060 void
1061 SPDesktop::setWindowPosition (NR::Point p)
1063     _widget->setPosition (p);
1066 void
1067 SPDesktop::setWindowSize (gint w, gint h)
1069     _widget->setSize (w, h);
1072 void
1073 SPDesktop::setWindowTransient (void *p, int transient_policy)
1075     _widget->setTransient (p, transient_policy);
1078 Gtk::Window*
1079 SPDesktop::getToplevel( )
1081     return _widget->getWindow();
1084 void
1085 SPDesktop::presentWindow()
1087     _widget->present();
1090 bool
1091 SPDesktop::warnDialog (gchar *text)
1093     return _widget->warnDialog (text);
1096 void
1097 SPDesktop::toggleRulers()
1099     _widget->toggleRulers();
1102 void
1103 SPDesktop::toggleScrollbars()
1105     _widget->toggleScrollbars();
1108 void
1109 SPDesktop::layoutWidget()
1111     _widget->layout();
1114 void
1115 SPDesktop::destroyWidget()
1117     _widget->destroy();
1120 bool
1121 SPDesktop::shutdown()
1123     return _widget->shutdown();
1126 bool SPDesktop::onDeleteUI (GdkEventAny*)
1128         if(shutdown()) return true;
1129         destroyWidget();
1130         return false;
1133 /**
1134  *  onWindowStateEvent
1135  *
1136  *  Called when the window changes its maximize/fullscreen/iconify/pinned state.
1137  *  Since GTK doesn't have a way to query this state information directly, we
1138  *  record it for the desktop here, and also possibly trigger a layout.
1139  */
1140 bool
1141 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1143         // Record the desktop window's state
1144     window_state = event->new_window_state;
1146     // Layout may differ depending on full-screen mode or not
1147     GdkWindowState changed = event->changed_mask;
1148     if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1149         layoutWidget();
1150     }
1151         
1152         return false;
1155 void
1156 SPDesktop::setToolboxFocusTo (gchar const *label)
1158     _widget->setToolboxFocusTo (label);
1161 void
1162 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1164     _widget->setToolboxAdjustmentValue (id, val);
1167 bool
1168 SPDesktop::isToolboxButtonActive (gchar const *id)
1170     return _widget->isToolboxButtonActive (id);
1173 void
1174 SPDesktop::emitToolSubselectionChanged(gpointer data)
1176         _tool_subselection_changed.emit(data);
1177         inkscape_subselection_changed (this);
1180 void
1181 SPDesktop::updateNow()
1183   sp_canvas_update_now(canvas);
1186 void
1187 SPDesktop::enableInteraction()
1189   _widget->enableInteraction();
1192 void SPDesktop::disableInteraction()
1194   _widget->disableInteraction();
1197 void SPDesktop::setWaitingCursor()
1199     GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1200     gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1201     gdk_cursor_unref(waiting);
1202     waiting_cursor = true;
1204     // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1205     // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1206     // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1207     // after the call to setWaitingCursor as it was before
1208     while( Gtk::Main::events_pending() )
1209        Gtk::Main::iteration();
1212 void SPDesktop::clearWaitingCursor()
1214   if (waiting_cursor)
1215       sp_event_context_update_cursor(sp_desktop_event_context(this));
1218 void SPDesktop::toggleGrid()
1220     if (namedview->grids) {
1221         if(gridgroup) {
1222             grids_visible = !grids_visible;
1223             if (grids_visible) {
1224                 sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1225             } else {
1226                 sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1227             }
1228         }
1229     } else {
1230         //there is no grid present at the moment. add a rectangular grid and make it visible
1231         Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1232         Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1233         grids_visible = true;
1234         sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1235     }
1239 //----------------------------------------------------------------------
1240 // Callback implementations. The virtual ones are connected by the view.
1242 void
1243 SPDesktop::onPositionSet (double x, double y)
1245     _widget->viewSetPosition (NR::Point(x,y));
1248 void
1249 SPDesktop::onResized (double x, double y)
1251    // Nothing called here
1254 /**
1255  * Redraw callback; queues Gtk redraw; connected by View.
1256  */
1257 void
1258 SPDesktop::onRedrawRequested ()
1260     if (main) {
1261         _widget->requestCanvasUpdate();
1262     }
1265 void
1266 SPDesktop::updateCanvasNow()
1268   _widget->requestCanvasUpdateAndWait();
1271 /**
1272  * Associate document with desktop.
1273  */
1274 void
1275 SPDesktop::setDocument (SPDocument *doc)
1277     if (this->doc() && doc) {
1278         namedview->hide(this);
1279         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1280     }
1282     if (_layer_hierarchy) {
1283         _layer_hierarchy->clear();
1284         delete _layer_hierarchy;
1285     }
1286     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1287     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1288     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1289     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1290     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1292     /* setup EventLog */
1293     event_log = new Inkscape::EventLog(doc);
1294     doc->addUndoObserver(*event_log);
1296     _commit_connection.disconnect();
1297     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1299     /// \todo fixme: This condition exists to make sure the code
1300     /// inside is NOT called on initialization, only on replacement. But there
1301     /// are surely more safe methods to accomplish this.
1302     // TODO since the comment had reversed logic, check the intent of this block of code:
1303     if (drawing) {
1304         NRArenaItem *ai = 0;
1306         namedview = sp_document_namedview (doc, NULL);
1307         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1308         number = namedview->getViewCount();
1310         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1311                 SP_CANVAS_ARENA (drawing)->arena,
1312                 dkey,
1313                 SP_ITEM_SHOW_DISPLAY);
1314         if (ai) {
1315             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1316             nr_arena_item_unref (ai);
1317         }
1318         namedview->show(this);
1319         /* Ugly hack */
1320         activate_guides (true);
1321         /* Ugly hack */
1322         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1323     }
1325     _document_replaced_signal.emit (this, doc);
1327     View::setDocument (doc);
1330 void
1331 SPDesktop::onStatusMessage
1332 (Inkscape::MessageType type, gchar const *message)
1334     if (_widget) {
1335         _widget->setMessage(type, message);
1336     }
1339 void
1340 SPDesktop::onDocumentURISet (gchar const* uri)
1342     _widget->setTitle(uri);
1345 /**
1346  * Resized callback.
1347  */
1348 void
1349 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1351     _doc2dt[5] = height;
1352     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1353     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1354     SP_CTRLRECT(page)->setRectangle(a);
1355     SP_CTRLRECT(page_border)->setRectangle(a);
1359 void
1360 SPDesktop::_onActivate (SPDesktop* dt)
1362     if (!dt->_widget) return;
1363     dt->_widget->activateDesktop();
1366 void
1367 SPDesktop::_onDeactivate (SPDesktop* dt)
1369     if (!dt->_widget) return;
1370     dt->_widget->deactivateDesktop();
1373 void
1374 SPDesktop::_onSelectionModified
1375 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1377     if (!dt->_widget) return;
1378     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1381 static void
1382 _onSelectionChanged
1383 (Inkscape::Selection *selection, SPDesktop *desktop)
1385     /** \todo
1386      * only change the layer for single selections, or what?
1387      * This seems reasonable -- for multiple selections there can be many
1388      * different layers involved.
1389      */
1390     SPItem *item=selection->singleItem();
1391     if (item) {
1392         SPObject *layer=desktop->layerForObject(item);
1393         if ( layer && layer != desktop->currentLayer() ) {
1394             desktop->setCurrentLayer(layer);
1395         }
1396     }
1399 /**
1400  * Calls event handler of current event context.
1401  * \param arena Unused
1402  * \todo fixme
1403  */
1404 static gint
1405 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1407     if (ai) {
1408         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1409         return sp_event_context_item_handler (desktop->event_context, spi, event);
1410     } else {
1411         return sp_event_context_root_handler (desktop->event_context, event);
1412     }
1415 static void
1416 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1417     g_return_if_fail(SP_IS_GROUP(layer));
1418     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1421 /// Callback
1422 static void
1423 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1424     g_return_if_fail(SP_IS_GROUP(layer));
1425     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1428 /// Callback
1429 static void
1430 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1431                                          SPDesktop *desktop)
1433     desktop->_layer_changed_signal.emit (bottom);
1436 /// Called when document is starting to be rebuilt.
1437 static void
1438 _reconstruction_start (SPDesktop * desktop)
1440     // printf("Desktop, starting reconstruction\n");
1441     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1442     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1444     /*
1445     GSList const * selection_objs = desktop->selection->list();
1446     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1448     }
1449     */
1450     desktop->selection->clear();
1452     // printf("Desktop, starting reconstruction end\n");
1455 /// Called when document rebuild is finished.
1456 static void
1457 _reconstruction_finish (SPDesktop * desktop)
1459     // printf("Desktop, finishing reconstruction\n");
1460     if (desktop->_reconstruction_old_layer_id == NULL)
1461         return;
1463     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1464     if (newLayer != NULL)
1465         desktop->setCurrentLayer(newLayer);
1467     g_free(desktop->_reconstruction_old_layer_id);
1468     desktop->_reconstruction_old_layer_id = NULL;
1469     // printf("Desktop, finishing reconstruction end\n");
1470     return;
1473 /**
1474  * Namedview_modified callback.
1475  */
1476 static void
1477 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1479     SPNamedView *nv=SP_NAMEDVIEW(obj);
1481     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1483         /* Recalculate snap distances */
1484         /* FIXME: why is the desktop getting involved in setting up something
1485         ** that is entirely to do with the namedview?
1486         */
1487         _update_snap_distances (desktop);
1489         /* Show/hide page background */
1490         if (nv->pagecolor & 0xff) {
1491             sp_canvas_item_show (desktop->table);
1492             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1493             sp_canvas_item_move_to_z (desktop->table, 0);
1494         } else {
1495             sp_canvas_item_hide (desktop->table);
1496         }
1498         /* Show/hide page border */
1499         if (nv->showborder) {
1500             // show
1501             sp_canvas_item_show (desktop->page_border);
1502             // set color and shadow
1503             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1504             if (nv->pageshadow) {
1505                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1506             }
1507             // place in the z-order stack
1508             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1509                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1510             } else {
1511                 int order = sp_canvas_item_order (desktop->page_border);
1512                 int morder = sp_canvas_item_order (desktop->drawing);
1513                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1514                                     morder - order);
1515             }
1516         } else {
1517                 sp_canvas_item_hide (desktop->page_border);
1518                 if (nv->pageshadow) {
1519                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1520                 }
1521         }
1522         
1523         /* Show/hide page shadow */
1524         if (nv->showpageshadow && nv->pageshadow) {
1525             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1526         } else {
1527             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1528         }
1530         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1531             (SP_RGBA32_R_U(nv->pagecolor) +
1532              SP_RGBA32_G_U(nv->pagecolor) +
1533              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1534             // the background color is light or transparent, use black outline
1535             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1536         } else { // use white outline
1537             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1538         }
1539     }
1542 /**
1543  * Callback to reset snapper's distances.
1544  */
1545 static void
1546 _update_snap_distances (SPDesktop *desktop)
1548     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1550     SPNamedView &nv = *desktop->namedview;
1552     //tell all grid snappers
1553     for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1554         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1555         grid->snapper->setDistance(sp_convert_distance_full(nv.gridtolerance,
1556                                                                       *nv.gridtoleranceunit,
1557                                                                       px));
1558     }
1559     
1560     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1561                                                                        *nv.guidetoleranceunit,
1562                                                                        px));
1563     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1564                                                                         *nv.objecttoleranceunit,
1565                                                                         px));
1569 NR::Matrix SPDesktop::w2d() const
1571     return _w2d;
1574 NR::Point SPDesktop::w2d(NR::Point const &p) const
1576     return p * _w2d;
1579 NR::Point SPDesktop::d2w(NR::Point const &p) const
1581     return p * _d2w;
1584 NR::Matrix SPDesktop::doc2dt() const
1586     return _doc2dt;
1589 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1591     return p * _doc2dt;
1594 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1596     return p / _doc2dt;
1600 /**
1601  * Pop event context from desktop's context stack. Never used.
1602  */
1603 // void
1604 // SPDesktop::pop_event_context (unsigned int key)
1605 // {
1606 //    SPEventContext *ec = NULL;
1607 //
1608 //    if (event_context && event_context->key == key) {
1609 //        g_return_if_fail (event_context);
1610 //        g_return_if_fail (event_context->next);
1611 //        ec = event_context;
1612 //        sp_event_context_deactivate (ec);
1613 //        event_context = ec->next;
1614 //        sp_event_context_activate (event_context);
1615 //        _event_context_changed_signal.emit (this, ec);
1616 //    }
1617 //
1618 //    SPEventContext *ref = event_context;
1619 //    while (ref && ref->next && ref->next->key != key)
1620 //        ref = ref->next;
1621 //
1622 //    if (ref && ref->next) {
1623 //        ec = ref->next;
1624 //        ref->next = ec->next;
1625 //    }
1626 //
1627 //    if (ec) {
1628 //        sp_event_context_finish (ec);
1629 //        g_object_unref (G_OBJECT (ec));
1630 //    }
1631 // }
1633 /*
1634   Local Variables:
1635   mode:c++
1636   c-file-style:"stroustrup"
1637   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1638   indent-tabs-mode:nil
1639   fill-column:99
1640   End:
1641 */
1642 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :