Code

Split data mode apart from UI and added button trackers
[inkscape.git] / src / desktop.cpp
1 #define __SP_DESKTOP_C__
3 /** \file
4  * Editable view implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   MenTaLguY <mental@rydia.net>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Ralf Stephan <ralf@ark.in-berlin.de>
11  *   John Bintz <jcoswell@coswellproductions.org>
12  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
13  *
14  * Copyright (C) 2007 Jon A. Cruz
15  * Copyright (C) 2006-2008 Johan Engelen
16  * Copyright (C) 2006 John Bintz
17  * Copyright (C) 2004 MenTaLguY
18  * Copyright (C) 1999-2002 Lauris Kaplinski
19  * Copyright (C) 2000-2001 Ximian, Inc.
20  *
21  * Released under GNU GPL, read the file 'COPYING' for more information
22  */
24 /** \class SPDesktop
25  * SPDesktop is a subclass of View, implementing an editable document
26  * canvas.  It is extensively used by many UI controls that need certain
27  * visual representations of their own.
28  *
29  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
30  * layers of different control objects. The one containing the whole
31  * document is the drawing layer. In addition to it, there are grid,
32  * guide, sketch and control layers. The sketch layer is used for
33  * temporary drawing objects, before the real objects in document are
34  * created. The control layer contains editing knots, rubberband and
35  * similar non-document UI objects.
36  *
37  * Each SPDesktop is associated with a SPNamedView node of the document
38  * tree.  Currently, all desktops are created from a single main named
39  * view, but in the future there may be support for different ones.
40  * SPNamedView serves as an in-document container for desktop-related
41  * data, like grid and guideline placement, snapping options and so on.
42  *
43  * Associated with each SPDesktop are the two most important editing
44  * related objects - SPSelection and SPEventContext.
45  *
46  * Sodipodi keeps track of the active desktop and invokes notification
47  * signals whenever it changes. UI elements can use these to update their
48  * display to the selection of the currently active editing window.
49  * (Lauris Kaplinski)
50  */
52 #ifdef HAVE_CONFIG_H
53 # include "config.h"
54 #endif
56 #include <glibmm/i18n.h>
57 #include <sigc++/functors/mem_fun.h>
58 #include <gtkmm.h>
60 #include "macros.h"
61 #include "inkscape-private.h"
62 #include "desktop.h"
63 #include "desktop-events.h"
64 #include "desktop-handles.h"
65 #include "document.h"
66 #include "message-stack.h"
67 #include "selection.h"
68 #include "select-context.h"
69 #include "sp-namedview.h"
70 #include "color.h"
71 #include "sp-item-group.h"
72 #include "prefs-utils.h"
73 #include "object-hierarchy.h"
74 #include "helper/units.h"
75 #include "display/canvas-arena.h"
76 #include "display/nr-arena.h"
77 #include "display/gnome-canvas-acetate.h"
78 #include "display/sodipodi-ctrlrect.h"
79 #include "display/sp-canvas-util.h"
80 #include "display/canvas-temporary-item-list.h"
81 #include "display/snap-indicator.h"
82 #include "libnr/nr-matrix-div.h"
83 #include "libnr/nr-rect-ops.h"
84 #include "ui/dialog/dialog-manager.h"
85 #include "xml/repr.h"
86 #include "message-context.h"
87 #include "device-manager.h"
88 #include "layer-manager.h"
89 #include "event-log.h"
90 #include "display/canvas-grid.h"
91 #include "widgets/desktop-widget.h"
92 #include "box3d-context.h"
94 #include "display/sp-canvas.h"
96 namespace Inkscape { namespace XML { class Node; }}
98 // Callback declarations
99 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
100 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
101 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
102 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
103 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
104 static void _reconstruction_start(SPDesktop * desktop);
105 static void _reconstruction_finish(SPDesktop * desktop);
106 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
107 static void _update_snap_distances (SPDesktop *desktop);
109 /**
110  * Return new desktop object.
111  * \pre namedview != NULL.
112  * \pre canvas != NULL.
113  */
114 SPDesktop::SPDesktop() :
115     _dlg_mgr( 0 ),
116     namedview( 0 ),
117     canvas( 0 ),
118     selection( 0 ),
119     event_context( 0 ),
120     layer_manager( 0 ),
121     event_log( 0 ),
122     temporary_item_list( 0 ),
123     snapindicator( 0 ),
124     acetate( 0 ),
125     main( 0 ),
126     gridgroup( 0 ),
127     guides( 0 ),
128     drawing( 0 ),
129     sketch( 0 ),
130     controls( 0 ),
131     tempgroup ( 0 ),
132     table( 0 ),
133     page( 0 ),
134     page_border( 0 ),
135     current( 0 ),
136     zooms_past( 0 ),
137     zooms_future( 0 ),
138     dkey( 0 ),
139     number( 0 ),
140     window_state(0),
141     interaction_disabled_counter( 0 ),
142     waiting_cursor( false ),
143     guides_active( false ),
144     gr_item( 0 ),
145     gr_point_type( 0 ),
146     gr_point_i( 0 ),
147     gr_fill_or_stroke( true ),
148     _layer_hierarchy( 0 ),
149     _reconstruction_old_layer_id( 0 ),
150     _widget( 0 ),
151     _inkscape( 0 ),
152     _guides_message_context( 0 ),
153     _active( false ),
154     _w2d(),
155     _d2w(),
156     _doc2dt( NR::Matrix(NR::scale(1, -1)) ),
157     grids_visible( false )
159     _d2w.set_identity();
160     _w2d.set_identity();
162     selection = Inkscape::GC::release( new Inkscape::Selection(this) );
165 void
166 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
168     // Temporary workaround for link order issues:
169     Inkscape::DeviceManager::getManager().getDevices();
171     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
173     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
175     namedview = nv;
176     canvas = aCanvas;
178     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
179     /* Kill flicker */
180     sp_document_ensure_up_to_date (document);
182     /* Setup Dialog Manager */
183     _dlg_mgr = &Inkscape::UI::Dialog::DialogManager::getInstance();
185     dkey = sp_item_display_key_new (1);
187     /* Connect document */
188     setDocument (document);
190     number = namedview->getViewCount();
193     /* Setup Canvas */
194     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
196     SPCanvasGroup *root = sp_canvas_root (canvas);
198     /* Setup adminstrative layers */
199     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
200     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
201     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
202     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
204     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
205     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
206     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
207     sp_canvas_item_move_to_z (table, 0);
209     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
210     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
211     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
213     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
214     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
216     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
218     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
219         // Start in outline mode
220         setDisplayModeOutline();
221     } else {
222         // Start in normal mode, default
223         setDisplayModeNormal();
224     }
226     gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
227     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
228     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
229     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
230     tempgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
232     /* Push select tool to the bottom of stack */
233     /** \todo
234      * FIXME: this is the only call to this.  Everything else seems to just
235      * call "set" instead of "push".  Can we assume that there is only one
236      * context ever?
237      */
238     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
240     // display rect and zoom are now handled in sp_desktop_widget_realize()
242     NR::Rect const d(NR::Point(0.0, 0.0),
243                      NR::Point(sp_document_width(document), sp_document_height(document)));
245     SP_CTRLRECT(page)->setRectangle(d);
246     SP_CTRLRECT(page_border)->setRectangle(d);
248     /* the following sets the page shadow on the canvas
249        It was originally set to 5, which is really cheesy!
250        It now is an attribute in the document's namedview. If a value of
251        0 is used, then the constructor for a shadow is not initialized.
252     */
254     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
255         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
256     }
259     /* Connect event for page resize */
260     _doc2dt[5] = sp_document_height (document);
261     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
263     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
265     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
266             SP_CANVAS_ARENA (drawing)->arena,
267             dkey,
268             SP_ITEM_SHOW_DISPLAY);
269     if (ai) {
270         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
271         nr_arena_item_unref (ai);
272     }
274     namedview->show(this);
275     /* Ugly hack */
276     activate_guides (true);
277     /* Ugly hack */
278     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
280 /* Set up notification of rebuilding the document, this allows
281        for saving object related settings in the document. */
282     _reconstruction_start_connection =
283         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
284     _reconstruction_finish_connection =
285         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
286     _reconstruction_old_layer_id = NULL;
288     // ?
289     // sp_active_desktop_set (desktop);
290     _inkscape = INKSCAPE;
292     _activate_connection = _activate_signal.connect(
293         sigc::bind(
294             sigc::ptr_fun(_onActivate),
295             this
296         )
297     );
298      _deactivate_connection = _deactivate_signal.connect(
299         sigc::bind(
300             sigc::ptr_fun(_onDeactivate),
301             this
302         )
303     );
305     _sel_modified_connection = selection->connectModified(
306         sigc::bind(
307             sigc::ptr_fun(&_onSelectionModified),
308             this
309         )
310     );
311     _sel_changed_connection = selection->connectChanged(
312         sigc::bind(
313             sigc::ptr_fun(&_onSelectionChanged),
314             this
315         )
316     );
319     /* setup LayerManager */
320     //   (Setting up after the connections are all in place, as it may use some of them)
321     layer_manager = new Inkscape::LayerManager( this );
323     showGrids(namedview->grids_visible, false);
325     temporary_item_list = new Inkscape::Display::TemporaryItemList( this );
326     snapindicator = new Inkscape::Display::SnapIndicator ( this );
330 void SPDesktop::destroy()
332     if (snapindicator) {
333         delete snapindicator;
334         snapindicator = NULL;
335     }
336     if (temporary_item_list) {
337         delete temporary_item_list;
338         temporary_item_list = NULL;
339     }
341     namedview->hide(this);
343     _activate_connection.disconnect();
344     _deactivate_connection.disconnect();
345     _sel_modified_connection.disconnect();
346     _sel_changed_connection.disconnect();
347     _modified_connection.disconnect();
348     _commit_connection.disconnect();
349     _reconstruction_start_connection.disconnect();
350     _reconstruction_finish_connection.disconnect();
352     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
353     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
354     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
356     while (event_context) {
357         SPEventContext *ec = event_context;
358         event_context = ec->next;
359         sp_event_context_finish (ec);
360         g_object_unref (G_OBJECT (ec));
361     }
363     if (_layer_hierarchy) {
364         delete _layer_hierarchy;
365 //        _layer_hierarchy = NULL; //this should be here, but commented to find other bug somewhere else.
366     }
368     if (layer_manager) {
369         delete layer_manager;
370         layer_manager = NULL;
371     }
373     if (_inkscape) {
374         _inkscape = NULL;
375     }
377     if (drawing) {
378         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
379         drawing = NULL;
380     }
382     delete _guides_message_context;
383     _guides_message_context = NULL;
385     g_list_free (zooms_past);
386     g_list_free (zooms_future);
389 SPDesktop::~SPDesktop() {}
391 //--------------------------------------------------------------------
392 /* Public methods */
395 /** Note that lifetime is measured in milliseconds
396 * it is perfectly safe to ignore the returned pointer: the object is deleted by itself, so don't delete it elsewhere!
397 * The return value should only be used as argument for SPDesktop::remove_temporary_canvasitem, because the object might be deleted already.
398 */
399 Inkscape::Display::TemporaryItem *
400 SPDesktop::add_temporary_canvasitem (SPCanvasItem *item, guint lifetime)
402     return temporary_item_list->add_item(item, lifetime);
405 /** It is perfectly safe to call this function while the object has already been deleted due to a timeout.
406 */
407 void
408 SPDesktop::remove_temporary_canvasitem (Inkscape::Display::TemporaryItem * tempitem)
410     // check for non-null temporary_item_list, because during destruction of desktop, some destructor might try to access this list!
411     if (tempitem && temporary_item_list) {
412         temporary_item_list->delete_item(tempitem);
413     }
416 void SPDesktop::setDisplayModeNormal()
418     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
419     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
420     displayMode = RENDERMODE_NORMAL;
421     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
422     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
425 void SPDesktop::setDisplayModeOutline()
427     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
428     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
429     displayMode = RENDERMODE_OUTLINE;
430     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
431     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
434 void SPDesktop::displayModeToggle()
436     if (displayMode == RENDERMODE_OUTLINE)
437         setDisplayModeNormal();
438     else
439         setDisplayModeOutline();
442 /**
443  * Returns current root (=bottom) layer.
444  */
445 SPObject *SPDesktop::currentRoot() const
447     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
450 /**
451  * Returns current top layer.
452  */
453 SPObject *SPDesktop::currentLayer() const
455     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
458 /**
459  * Sets the current layer of the desktop.
460  *
461  * Make \a object the top layer.
462  */
463 void SPDesktop::setCurrentLayer(SPObject *object) {
464     g_return_if_fail(SP_IS_GROUP(object));
465     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
466     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
467     _layer_hierarchy->setBottom(object);
470 /**
471  * Return layer that contains \a object.
472  */
473 SPObject *SPDesktop::layerForObject(SPObject *object) {
474     g_return_val_if_fail(object != NULL, NULL);
476     SPObject *root=currentRoot();
477     object = SP_OBJECT_PARENT(object);
478     while ( object && object != root && !isLayer(object) ) {
479         object = SP_OBJECT_PARENT(object);
480     }
481     return object;
484 /**
485  * True if object is a layer.
486  */
487 bool SPDesktop::isLayer(SPObject *object) const {
488     return ( SP_IS_GROUP(object)
489              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
490                   == SPGroup::LAYER ) );
493 /**
494  * True if desktop viewport fully contains \a item's bbox.
495  */
496 bool SPDesktop::isWithinViewport (SPItem *item) const
498     NR::Rect const viewport = get_display_area();
499     NR::Maybe<NR::Rect> const bbox = sp_item_bbox_desktop(item);
500     if (bbox) {
501         return viewport.contains(*bbox);
502     } else {
503         return true;
504     }
507 ///
508 bool SPDesktop::itemIsHidden(SPItem const *item) const {
509     return item->isHidden(this->dkey);
512 /**
513  * Set activate property of desktop; emit signal if changed.
514  */
515 void
516 SPDesktop::set_active (bool new_active)
518     if (new_active != _active) {
519         _active = new_active;
520         if (new_active) {
521             _activate_signal.emit();
522         } else {
523             _deactivate_signal.emit();
524         }
525     }
528 /**
529  * Set activate status of current desktop's named view.
530  */
531 void
532 SPDesktop::activate_guides(bool activate)
534     guides_active = activate;
535     namedview->activateGuides(this, activate);
538 /**
539  * Make desktop switch documents.
540  */
541 void
542 SPDesktop::change_document (SPDocument *theDocument)
544     g_return_if_fail (theDocument != NULL);
546     /* unselect everything before switching documents */
547     selection->clear();
549     setDocument (theDocument);
551     /* update the rulers, connect the desktop widget's signal to the new namedview etc.
552        (this can probably be done in a better way) */
553     Gtk::Window *parent = this->getToplevel();
554     g_assert(parent != NULL);
555     SPDesktopWidget *dtw = (SPDesktopWidget *) parent->get_data("desktopwidget");
556     if (dtw) dtw->desktop = this;
557     sp_desktop_widget_update_namedview(dtw);
559     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
560     _document_replaced_signal.emit (this, theDocument);
563 /**
564  * Make desktop switch event contexts.
565  */
566 void
567 SPDesktop::set_event_context (GtkType type, const gchar *config)
569     SPEventContext *ec;
570     while (event_context) {
571         ec = event_context;
572         sp_event_context_deactivate (ec);
573         event_context = ec->next;
574         sp_event_context_finish (ec);
575         g_object_unref (G_OBJECT (ec));
576     }
578     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
579     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
580     ec->next = event_context;
581     event_context = ec;
582     sp_event_context_activate (ec);
583     _event_context_changed_signal.emit (this, ec);
586 /**
587  * Push event context onto desktop's context stack.
588  */
589 void
590 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
592     SPEventContext *ref, *ec;
593     Inkscape::XML::Node *repr;
595     if (event_context && event_context->key == key) return;
596     ref = event_context;
597     while (ref && ref->next && ref->next->key != key) ref = ref->next;
598     if (ref && ref->next) {
599         ec = ref->next;
600         ref->next = ec->next;
601         sp_event_context_finish (ec);
602         g_object_unref (G_OBJECT (ec));
603     }
605     if (event_context) sp_event_context_deactivate (event_context);
606     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
607     ec = sp_event_context_new (type, this, repr, key);
608     ec->next = event_context;
609     event_context = ec;
610     sp_event_context_activate (ec);
611     _event_context_changed_signal.emit (this, ec);
614 /**
615  * Sets the coordinate status to a given point
616  */
617 void
618 SPDesktop::set_coordinate_status (NR::Point p) {
619     _widget->setCoordinateStatus(p);
622 /**
623  * \see sp_document_item_from_list_at_point_bottom()
624  */
625 SPItem *
626 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
628     g_return_val_if_fail (doc() != NULL, NULL);
629     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
632 /**
633  * \see sp_document_item_at_point()
634  */
635 SPItem *
636 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
638     g_return_val_if_fail (doc() != NULL, NULL);
639     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
642 /**
643  * \see sp_document_group_at_point()
644  */
645 SPItem *
646 SPDesktop::group_at_point (NR::Point const p) const
648     g_return_val_if_fail (doc() != NULL, NULL);
649     return sp_document_group_at_point (doc(), dkey, p);
652 /**
653  * \brief  Returns the mouse point in document coordinates; if mouse is
654  * outside the canvas, returns the center of canvas viewpoint
655  */
656 NR::Point
657 SPDesktop::point() const
659     NR::Point p = _widget->getPointer();
660     NR::Point pw = sp_canvas_window_to_world (canvas, p);
661     p = w2d(pw);
663     NR::Rect const r = canvas->getViewbox();
665     NR::Point r0 = w2d(r.min());
666     NR::Point r1 = w2d(r.max());
668     if (p[NR::X] >= r0[NR::X] &&
669         p[NR::X] <= r1[NR::X] &&
670         p[NR::Y] >= r1[NR::Y] &&
671         p[NR::Y] <= r0[NR::Y])
672     {
673         return p;
674     } else {
675         return (r0 + r1) / 2;
676     }
679 /**
680  * Put current zoom data in history list.
681  */
682 void
683 SPDesktop::push_current_zoom (GList **history)
685     NR::Rect const area = get_display_area();
687     NRRect *old_zoom = g_new(NRRect, 1);
688     old_zoom->x0 = area.min()[NR::X];
689     old_zoom->x1 = area.max()[NR::X];
690     old_zoom->y0 = area.min()[NR::Y];
691     old_zoom->y1 = area.max()[NR::Y];
692     if ( *history == NULL
693          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
694                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
695                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
696                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
697     {
698         *history = g_list_prepend (*history, old_zoom);
699     }
702 /**
703  * Set viewbox (x0, x1, y0 and y1 are in document pixels. Border is in screen pixels).
704  */
705 void
706 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
708     g_assert(_widget);
710     // save the zoom
711     if (log) {
712         push_current_zoom(&zooms_past);
713         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
714         g_list_free (zooms_future);
715         zooms_future = NULL;
716     }
718     double const cx = 0.5 * (x0 + x1);
719     double const cy = 0.5 * (y0 + y1);
721     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
723     double scale = expansion(_d2w);
724     double newscale;
725     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
726         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
727     } else {
728         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
729     }
731     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); // unit: 'screen pixels' per 'document pixels'
733     int clear = FALSE;
734     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
735         /* Set zoom factors */
736         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
737         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
738         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
739         clear = TRUE;
740     }
742     /* Calculate top left corner (in document pixels) */
743     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
744     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
746     /* Scroll */
747     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
749     /*  update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
750     sp_box3d_context_update_lines(event_context);
752     _widget->updateRulers();
753     _widget->updateScrollbars(expansion(_d2w));
754     _widget->updateZoom();
757 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
759     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
762 /**
763  * Return viewbox dimensions.
764  */
765 NR::Rect SPDesktop::get_display_area() const
767     NR::Rect const viewbox = canvas->getViewbox();
769     double const scale = _d2w[0];
771     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
772                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
775 /**
776  * Revert back to previous zoom if possible.
777  */
778 void
779 SPDesktop::prev_zoom()
781     if (zooms_past == NULL) {
782         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
783         return;
784     }
786     // push current zoom into forward zooms list
787     push_current_zoom (&zooms_future);
789     // restore previous zoom
790     set_display_area (((NRRect *) zooms_past->data)->x0,
791             ((NRRect *) zooms_past->data)->y0,
792             ((NRRect *) zooms_past->data)->x1,
793             ((NRRect *) zooms_past->data)->y1,
794             0, false);
796     // remove the just-added zoom from the past zooms list
797     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
800 /**
801  * Set zoom to next in list.
802  */
803 void
804 SPDesktop::next_zoom()
806     if (zooms_future == NULL) {
807         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
808         return;
809     }
811     // push current zoom into past zooms list
812     push_current_zoom (&zooms_past);
814     // restore next zoom
815     set_display_area (((NRRect *) zooms_future->data)->x0,
816             ((NRRect *) zooms_future->data)->y0,
817             ((NRRect *) zooms_future->data)->x1,
818             ((NRRect *) zooms_future->data)->y1,
819             0, false);
821     // remove the just-used zoom from the zooms_future list
822     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
825 /**
826  * Zoom to point with absolute zoom factor.
827  */
828 void
829 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
831     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
833     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
834     // this check prevents "sliding" when trying to zoom in at maximum zoom;
835     /// \todo someone please fix calculations properly and remove this hack
836     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
837         return;
839     NR::Rect const viewbox = canvas->getViewbox();
841     double const width2 = viewbox.dimensions()[NR::X] / zoom;
842     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
844     set_display_area(cx - px * width2,
845                      cy - py * height2,
846                      cx + (1 - px) * width2,
847                      cy + (1 - py) * height2,
848                      0.0);
851 /**
852  * Zoom to center with absolute zoom factor.
853  */
854 void
855 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
857     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
860 /**
861  * Zoom to point with relative zoom factor.
862  */
863 void
864 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
866     NR::Rect const area = get_display_area();
868     if (cx < area.min()[NR::X]) {
869         cx = area.min()[NR::X];
870     }
871     if (cx > area.max()[NR::X]) {
872         cx = area.max()[NR::X];
873     }
874     if (cy < area.min()[NR::Y]) {
875         cy = area.min()[NR::Y];
876     }
877     if (cy > area.max()[NR::Y]) {
878         cy = area.max()[NR::Y];
879     }
881     gdouble const scale = expansion(_d2w) * zoom;
882     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
883     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
885     zoom_absolute_keep_point(cx, cy, px, py, scale);
888 /**
889  * Zoom to center with relative zoom factor.
890  */
891 void
892 SPDesktop::zoom_relative (double cx, double cy, double zoom)
894     gdouble scale = expansion(_d2w) * zoom;
895     zoom_absolute (cx, cy, scale);
898 /**
899  * Set display area to origin and current document dimensions.
900  */
901 void
902 SPDesktop::zoom_page()
904     NR::Rect d(NR::Point(0, 0),
905                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
907     if (d.isEmpty(1.0)) {
908         return;
909     }
911     set_display_area(d, 10);
914 /**
915  * Set display area to current document width.
916  */
917 void
918 SPDesktop::zoom_page_width()
920     NR::Rect const a = get_display_area();
922     if (sp_document_width(doc()) < 1.0) {
923         return;
924     }
926     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
927                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
929     set_display_area(d, 10);
932 /**
933  * Zoom to selection.
934  */
935 void
936 SPDesktop::zoom_selection()
938     NR::Maybe<NR::Rect> const d = selection->bounds();
940     if ( !d || d->isEmpty(0.1) ) {
941         return;
942     }
944     set_display_area(*d, 10);
947 /**
948  * Tell widget to let zoom widget grab keyboard focus.
949  */
950 void
951 SPDesktop::zoom_grab_focus()
953     _widget->letZoomGrabFocus();
956 /**
957  * Zoom to whole drawing.
958  */
959 void
960 SPDesktop::zoom_drawing()
962     g_return_if_fail (doc() != NULL);
963     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
964     g_return_if_fail (docitem != NULL);
966     NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
968     /* Note that the second condition here indicates that
969     ** there are no items in the drawing.
970     */
971     if ( !d || d->isEmpty(1.0) ) {
972         return;
973     }
975     set_display_area(*d, 10);
978 /**
979  * Scroll canvas by specific coordinate amount.
980  */
981 void
982 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
984     g_assert(_widget);
986     NR::Rect const viewbox = canvas->getViewbox();
988     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
990     /*  update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
991     sp_box3d_context_update_lines(event_context);
993     _widget->updateRulers();
994     _widget->updateScrollbars(expansion(_d2w));
997 bool
998 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
1000     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
1002     // autoscrolldistance is in screen pixels, but the display area is in document units
1003     autoscrolldistance /= expansion(_d2w);
1004     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
1006     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
1007         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
1009         NR::Point const s_w( (*p) * _d2w );
1011         gdouble x_to;
1012         if ((*p)[NR::X] < dbox.min()[NR::X])
1013             x_to = dbox.min()[NR::X];
1014         else if ((*p)[NR::X] > dbox.max()[NR::X])
1015             x_to = dbox.max()[NR::X];
1016         else
1017             x_to = (*p)[NR::X];
1019         gdouble y_to;
1020         if ((*p)[NR::Y] < dbox.min()[NR::Y])
1021             y_to = dbox.min()[NR::Y];
1022         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
1023             y_to = dbox.max()[NR::Y];
1024         else
1025             y_to = (*p)[NR::Y];
1027         NR::Point const d_dt(x_to, y_to);
1028         NR::Point const d_w( d_dt * _d2w );
1029         NR::Point const moved_w( d_w - s_w );
1031         if (autoscrollspeed == 0)
1032             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1034         if (autoscrollspeed != 0)
1035             scroll_world (autoscrollspeed * moved_w);
1037         return true;
1038     }
1039     return false;
1042 bool
1043 SPDesktop::is_iconified()
1045     return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1048 void
1049 SPDesktop::iconify()
1051     _widget->setIconified();
1054 bool
1055 SPDesktop::is_maximized()
1057     return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1060 void
1061 SPDesktop::maximize()
1063     _widget->setMaximized();
1066 bool
1067 SPDesktop::is_fullscreen()
1069     return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1072 void
1073 SPDesktop::fullscreen()
1075     _widget->setFullscreen();
1078 void
1079 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1081     _widget->getGeometry (x, y, w, h);
1084 void
1085 SPDesktop::setWindowPosition (NR::Point p)
1087     _widget->setPosition (p);
1090 void
1091 SPDesktop::setWindowSize (gint w, gint h)
1093     _widget->setSize (w, h);
1096 void
1097 SPDesktop::setWindowTransient (void *p, int transient_policy)
1099     _widget->setTransient (p, transient_policy);
1102 Gtk::Window*
1103 SPDesktop::getToplevel( )
1105     return _widget->getWindow();
1108 void
1109 SPDesktop::presentWindow()
1111     _widget->present();
1114 bool
1115 SPDesktop::warnDialog (gchar *text)
1117     return _widget->warnDialog (text);
1120 void
1121 SPDesktop::toggleRulers()
1123     _widget->toggleRulers();
1126 void
1127 SPDesktop::toggleScrollbars()
1129     _widget->toggleScrollbars();
1132 void
1133 SPDesktop::layoutWidget()
1135     _widget->layout();
1138 void
1139 SPDesktop::destroyWidget()
1141     _widget->destroy();
1144 bool
1145 SPDesktop::shutdown()
1147     return _widget->shutdown();
1150 bool SPDesktop::onDeleteUI (GdkEventAny*)
1152     if(shutdown()) 
1153         return true;
1155     destroyWidget();
1156     return false;
1159 /**
1160  *  onWindowStateEvent
1161  *
1162  *  Called when the window changes its maximize/fullscreen/iconify/pinned state.
1163  *  Since GTK doesn't have a way to query this state information directly, we
1164  *  record it for the desktop here, and also possibly trigger a layout.
1165  */
1166 bool
1167 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1169         // Record the desktop window's state
1170     window_state = event->new_window_state;
1172     // Layout may differ depending on full-screen mode or not
1173     GdkWindowState changed = event->changed_mask;
1174     if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1175         layoutWidget();
1176     }
1178         return false;
1181 void
1182 SPDesktop::setToolboxFocusTo (gchar const *label)
1184     _widget->setToolboxFocusTo (label);
1187 void
1188 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1190     _widget->setToolboxAdjustmentValue (id, val);
1193 void
1194 SPDesktop::setToolboxSelectOneValue (gchar const* id, gint val)
1196     _widget->setToolboxSelectOneValue (id, val);
1199 bool
1200 SPDesktop::isToolboxButtonActive (gchar const *id)
1202     return _widget->isToolboxButtonActive (id);
1205 void
1206 SPDesktop::emitToolSubselectionChanged(gpointer data)
1208         _tool_subselection_changed.emit(data);
1209         inkscape_subselection_changed (this);
1212 void
1213 SPDesktop::updateNow()
1215   sp_canvas_update_now(canvas);
1218 void
1219 SPDesktop::enableInteraction()
1221   _widget->enableInteraction();
1224 void SPDesktop::disableInteraction()
1226   _widget->disableInteraction();
1229 void SPDesktop::setWaitingCursor()
1231     GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1232     gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1233     gdk_cursor_unref(waiting);
1234     waiting_cursor = true;
1236     // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1237     // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1238     // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1239     // after the call to setWaitingCursor as it was before
1240     while( Gtk::Main::events_pending() )
1241        Gtk::Main::iteration();
1244 void SPDesktop::clearWaitingCursor()
1246   if (waiting_cursor)
1247       sp_event_context_update_cursor(sp_desktop_event_context(this));
1250 void SPDesktop::toggleColorProfAdjust()
1252     _widget->toggleColorProfAdjust();
1255 void SPDesktop::toggleGrids()
1257     if (namedview->grids) {
1258         if(gridgroup) {
1259             showGrids(!grids_visible);
1260         }
1261     } else {
1262         //there is no grid present at the moment. add a rectangular grid and make it visible
1263         Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1264         Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1265         showGrids(true);
1266     }
1269 void SPDesktop::showGrids(bool show, bool dirty_document)
1271     grids_visible = show;
1272     sp_namedview_show_grids(namedview, grids_visible, dirty_document);
1273     if (show) {
1274         sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1275     } else {
1276         sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1277     }
1280 void SPDesktop::toggleSnapping()
1282     bool v = namedview->snap_manager.getSnapEnabledGlobally();
1283     Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1284     sp_repr_set_boolean(repr, "inkscape:snap-global", !v);
1287 //----------------------------------------------------------------------
1288 // Callback implementations. The virtual ones are connected by the view.
1290 void
1291 SPDesktop::onPositionSet (double x, double y)
1293     _widget->viewSetPosition (NR::Point(x,y));
1296 void
1297 SPDesktop::onResized (double /*x*/, double /*y*/)
1299    // Nothing called here
1302 /**
1303  * Redraw callback; queues Gtk redraw; connected by View.
1304  */
1305 void
1306 SPDesktop::onRedrawRequested ()
1308     if (main) {
1309         _widget->requestCanvasUpdate();
1310     }
1313 void
1314 SPDesktop::updateCanvasNow()
1316   _widget->requestCanvasUpdateAndWait();
1319 /**
1320  * Associate document with desktop.
1321  */
1322 void
1323 SPDesktop::setDocument (SPDocument *doc)
1325     if (this->doc() && doc) {
1326         namedview->hide(this);
1327         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1328     }
1330     if (_layer_hierarchy) {
1331         _layer_hierarchy->clear();
1332         delete _layer_hierarchy;
1333     }
1334     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1335     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1336     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1337     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1338     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1340     /* setup EventLog */
1341     event_log = new Inkscape::EventLog(doc);
1342     doc->addUndoObserver(*event_log);
1344     _commit_connection.disconnect();
1345     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1347     /// \todo fixme: This condition exists to make sure the code
1348     /// inside is NOT called on initialization, only on replacement. But there
1349     /// are surely more safe methods to accomplish this.
1350     // TODO since the comment had reversed logic, check the intent of this block of code:
1351     if (drawing) {
1352         NRArenaItem *ai = 0;
1354         namedview = sp_document_namedview (doc, NULL);
1355         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1356         number = namedview->getViewCount();
1358         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1359                 SP_CANVAS_ARENA (drawing)->arena,
1360                 dkey,
1361                 SP_ITEM_SHOW_DISPLAY);
1362         if (ai) {
1363             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1364             nr_arena_item_unref (ai);
1365         }
1366         namedview->show(this);
1367         /* Ugly hack */
1368         activate_guides (true);
1369         /* Ugly hack */
1370         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1371     }
1373     _document_replaced_signal.emit (this, doc);
1375     View::setDocument (doc);
1378 void
1379 SPDesktop::onStatusMessage
1380 (Inkscape::MessageType type, gchar const *message)
1382     if (_widget) {
1383         _widget->setMessage(type, message);
1384     }
1387 void
1388 SPDesktop::onDocumentURISet (gchar const* uri)
1390     _widget->setTitle(uri);
1393 /**
1394  * Resized callback.
1395  */
1396 void
1397 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1399     _doc2dt[5] = height;
1400     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1401     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1402     SP_CTRLRECT(page)->setRectangle(a);
1403     SP_CTRLRECT(page_border)->setRectangle(a);
1407 void
1408 SPDesktop::_onActivate (SPDesktop* dt)
1410     if (!dt->_widget) return;
1411     dt->_widget->activateDesktop();
1414 void
1415 SPDesktop::_onDeactivate (SPDesktop* dt)
1417     if (!dt->_widget) return;
1418     dt->_widget->deactivateDesktop();
1421 void
1422 SPDesktop::_onSelectionModified
1423 (Inkscape::Selection */*selection*/, guint /*flags*/, SPDesktop *dt)
1425     if (!dt->_widget) return;
1426     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1429 static void
1430 _onSelectionChanged
1431 (Inkscape::Selection *selection, SPDesktop *desktop)
1433     /** \todo
1434      * only change the layer for single selections, or what?
1435      * This seems reasonable -- for multiple selections there can be many
1436      * different layers involved.
1437      */
1438     SPItem *item=selection->singleItem();
1439     if (item) {
1440         SPObject *layer=desktop->layerForObject(item);
1441         if ( layer && layer != desktop->currentLayer() ) {
1442             desktop->setCurrentLayer(layer);
1443         }
1444     }
1447 /**
1448  * Calls event handler of current event context.
1449  * \param arena Unused
1450  * \todo fixme
1451  */
1452 static gint
1453 _arena_handler (SPCanvasArena */*arena*/, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1455     if (ai) {
1456         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1457         return sp_event_context_item_handler (desktop->event_context, spi, event);
1458     } else {
1459         return sp_event_context_root_handler (desktop->event_context, event);
1460     }
1463 static void
1464 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1465     g_return_if_fail(SP_IS_GROUP(layer));
1466     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1469 /// Callback
1470 static void
1471 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1472     g_return_if_fail(SP_IS_GROUP(layer));
1473     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1476 /// Callback
1477 static void
1478 _layer_hierarchy_changed(SPObject */*top*/, SPObject *bottom,
1479                                          SPDesktop *desktop)
1481     desktop->_layer_changed_signal.emit (bottom);
1484 /// Called when document is starting to be rebuilt.
1485 static void
1486 _reconstruction_start (SPDesktop * desktop)
1488     // printf("Desktop, starting reconstruction\n");
1489     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1490     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1492     /*
1493     GSList const * selection_objs = desktop->selection->list();
1494     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1496     }
1497     */
1498     desktop->selection->clear();
1500     // printf("Desktop, starting reconstruction end\n");
1503 /// Called when document rebuild is finished.
1504 static void
1505 _reconstruction_finish (SPDesktop * desktop)
1507     // printf("Desktop, finishing reconstruction\n");
1508     if (desktop->_reconstruction_old_layer_id == NULL)
1509         return;
1511     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1512     if (newLayer != NULL)
1513         desktop->setCurrentLayer(newLayer);
1515     g_free(desktop->_reconstruction_old_layer_id);
1516     desktop->_reconstruction_old_layer_id = NULL;
1517     // printf("Desktop, finishing reconstruction end\n");
1518     return;
1521 /**
1522  * Namedview_modified callback.
1523  */
1524 static void
1525 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1527     SPNamedView *nv=SP_NAMEDVIEW(obj);
1529     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1531         /* Recalculate snap distances */
1532         /* FIXME: why is the desktop getting involved in setting up something
1533         ** that is entirely to do with the namedview?
1534         */
1535         _update_snap_distances (desktop);
1537         /* Show/hide page background */
1538         if (nv->pagecolor & 0xff) {
1539             sp_canvas_item_show (desktop->table);
1540             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1541             sp_canvas_item_move_to_z (desktop->table, 0);
1542         } else {
1543             sp_canvas_item_hide (desktop->table);
1544         }
1546         /* Show/hide page border */
1547         if (nv->showborder) {
1548             // show
1549             sp_canvas_item_show (desktop->page_border);
1550             // set color and shadow
1551             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1552             if (nv->pageshadow) {
1553                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1554             }
1555             // place in the z-order stack
1556             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1557                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1558             } else {
1559                 int order = sp_canvas_item_order (desktop->page_border);
1560                 int morder = sp_canvas_item_order (desktop->drawing);
1561                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1562                                     morder - order);
1563             }
1564         } else {
1565                 sp_canvas_item_hide (desktop->page_border);
1566                 if (nv->pageshadow) {
1567                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1568                 }
1569         }
1571         /* Show/hide page shadow */
1572         if (nv->showpageshadow && nv->pageshadow) {
1573             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1574         } else {
1575             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1576         }
1578         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1579             (SP_RGBA32_R_U(nv->pagecolor) +
1580              SP_RGBA32_G_U(nv->pagecolor) +
1581              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1582             // the background color is light or transparent, use black outline
1583             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1584         } else { // use white outline
1585             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1586         }
1587     }
1590 /**
1591  * Callback to reset snapper's distances.
1592  */
1593 static void
1594 _update_snap_distances (SPDesktop *desktop)
1596     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1598     SPNamedView &nv = *desktop->namedview;
1600     //tell all grid snappers
1601     for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1602         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1603         grid->snapper->setSnapperTolerance(sp_convert_distance_full(nv.gridtolerance,
1604                                                                       *nv.gridtoleranceunit,
1605                                                                       px));
1606     }
1608     nv.snap_manager.guide.setSnapperTolerance(sp_convert_distance_full(nv.guidetolerance,
1609                                                                        *nv.guidetoleranceunit,
1610                                                                        px));
1611     nv.snap_manager.object.setSnapperTolerance(sp_convert_distance_full(nv.objecttolerance,
1612                                                                         *nv.objecttoleranceunit,
1613                                                                         px));
1617 NR::Matrix SPDesktop::w2d() const
1619     return _w2d;
1622 NR::Point SPDesktop::w2d(NR::Point const &p) const
1624     return p * _w2d;
1627 NR::Point SPDesktop::d2w(NR::Point const &p) const
1629     return p * _d2w;
1632 NR::Matrix SPDesktop::doc2dt() const
1634     return _doc2dt;
1637 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1639     return p * _doc2dt;
1642 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1644     return p / _doc2dt;
1648 /**
1649  * Pop event context from desktop's context stack. Never used.
1650  */
1651 // void
1652 // SPDesktop::pop_event_context (unsigned int key)
1653 // {
1654 //    SPEventContext *ec = NULL;
1655 //
1656 //    if (event_context && event_context->key == key) {
1657 //        g_return_if_fail (event_context);
1658 //        g_return_if_fail (event_context->next);
1659 //        ec = event_context;
1660 //        sp_event_context_deactivate (ec);
1661 //        event_context = ec->next;
1662 //        sp_event_context_activate (event_context);
1663 //        _event_context_changed_signal.emit (this, ec);
1664 //    }
1665 //
1666 //    SPEventContext *ref = event_context;
1667 //    while (ref && ref->next && ref->next->key != key)
1668 //        ref = ref->next;
1669 //
1670 //    if (ref && ref->next) {
1671 //        ec = ref->next;
1672 //        ref->next = ec->next;
1673 //    }
1674 //
1675 //    if (ec) {
1676 //        sp_event_context_finish (ec);
1677 //        g_object_unref (G_OBJECT (ec));
1678 //    }
1679 // }
1681 /*
1682   Local Variables:
1683   mode:c++
1684   c-file-style:"stroustrup"
1685   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1686   indent-tabs-mode:nil
1687   fill-column:99
1688   End:
1689 */
1690 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :