Code

wrtlprnft's patch for bug 234834 - keeps guidelines in same position relative to...
[inkscape.git] / src / desktop.cpp
1 #define __SP_DESKTOP_C__
3 /** \file
4  * Editable view implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   MenTaLguY <mental@rydia.net>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Ralf Stephan <ralf@ark.in-berlin.de>
11  *   John Bintz <jcoswell@coswellproductions.org>
12  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
13  *
14  * Copyright (C) 2007 Jon A. Cruz
15  * Copyright (C) 2006-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     _display_mode(Inkscape::RENDERMODE_NORMAL),
151     _saved_display_mode(Inkscape::RENDERMODE_NORMAL),
152     _widget( 0 ),
153     _inkscape( 0 ),
154     _guides_message_context( 0 ),
155     _active( false ),
156     _w2d(),
157     _d2w(),
158     _doc2dt( NR::Matrix(NR::scale(1, -1)) ),
159     grids_visible( false )
161     _d2w.set_identity();
162     _w2d.set_identity();
164     selection = Inkscape::GC::release( new Inkscape::Selection(this) );
167 void
168 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
170     // Temporary workaround for link order issues:
171     Inkscape::DeviceManager::getManager().getDevices();
173     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
175     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
177     namedview = nv;
178     canvas = aCanvas;
180     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
181     /* Kill flicker */
182     sp_document_ensure_up_to_date (document);
184     /* Setup Dialog Manager */
185     _dlg_mgr = &Inkscape::UI::Dialog::DialogManager::getInstance();
187     dkey = sp_item_display_key_new (1);
189     /* Connect document */
190     setDocument (document);
192     number = namedview->getViewCount();
195     /* Setup Canvas */
196     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
198     SPCanvasGroup *root = sp_canvas_root (canvas);
200     /* Setup adminstrative layers */
201     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
202     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
203     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
204     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
206     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
207     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
208     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
209     sp_canvas_item_move_to_z (table, 0);
211     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
212     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
213     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
215     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
216     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
218     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
220     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
221         // Start in outline mode
222         setDisplayModeOutline();
223     } else {
224         // Start in normal mode, default
225         setDisplayModeNormal();
226     }
228     gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
229     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
230     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
231     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
232     tempgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
234     /* Push select tool to the bottom of stack */
235     /** \todo
236      * FIXME: this is the only call to this.  Everything else seems to just
237      * call "set" instead of "push".  Can we assume that there is only one
238      * context ever?
239      */
240     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
242     // display rect and zoom are now handled in sp_desktop_widget_realize()
244     NR::Rect const d(NR::Point(0.0, 0.0),
245                      NR::Point(sp_document_width(document), sp_document_height(document)));
247     SP_CTRLRECT(page)->setRectangle(d);
248     SP_CTRLRECT(page_border)->setRectangle(d);
250     /* the following sets the page shadow on the canvas
251        It was originally set to 5, which is really cheesy!
252        It now is an attribute in the document's namedview. If a value of
253        0 is used, then the constructor for a shadow is not initialized.
254     */
256     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
257         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
258     }
261     /* Connect event for page resize */
262     _doc2dt[5] = sp_document_height (document);
263     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
265     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
267     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
268             SP_CANVAS_ARENA (drawing)->arena,
269             dkey,
270             SP_ITEM_SHOW_DISPLAY);
271     if (ai) {
272         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
273         nr_arena_item_unref (ai);
274     }
276     namedview->show(this);
277     /* Ugly hack */
278     activate_guides (true);
279     /* Ugly hack */
280     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
282 /* Set up notification of rebuilding the document, this allows
283        for saving object related settings in the document. */
284     _reconstruction_start_connection =
285         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
286     _reconstruction_finish_connection =
287         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
288     _reconstruction_old_layer_id = NULL;
290     // ?
291     // sp_active_desktop_set (desktop);
292     _inkscape = INKSCAPE;
294     _activate_connection = _activate_signal.connect(
295         sigc::bind(
296             sigc::ptr_fun(_onActivate),
297             this
298         )
299     );
300      _deactivate_connection = _deactivate_signal.connect(
301         sigc::bind(
302             sigc::ptr_fun(_onDeactivate),
303             this
304         )
305     );
307     _sel_modified_connection = selection->connectModified(
308         sigc::bind(
309             sigc::ptr_fun(&_onSelectionModified),
310             this
311         )
312     );
313     _sel_changed_connection = selection->connectChanged(
314         sigc::bind(
315             sigc::ptr_fun(&_onSelectionChanged),
316             this
317         )
318     );
321     /* setup LayerManager */
322     //   (Setting up after the connections are all in place, as it may use some of them)
323     layer_manager = new Inkscape::LayerManager( this );
325     showGrids(namedview->grids_visible, false);
327     temporary_item_list = new Inkscape::Display::TemporaryItemList( this );
328     snapindicator = new Inkscape::Display::SnapIndicator ( this );
332 void SPDesktop::destroy()
334     if (snapindicator) {
335         delete snapindicator;
336         snapindicator = NULL;
337     }
338     if (temporary_item_list) {
339         delete temporary_item_list;
340         temporary_item_list = NULL;
341     }
343     namedview->hide(this);
345     _activate_connection.disconnect();
346     _deactivate_connection.disconnect();
347     _sel_modified_connection.disconnect();
348     _sel_changed_connection.disconnect();
349     _modified_connection.disconnect();
350     _commit_connection.disconnect();
351     _reconstruction_start_connection.disconnect();
352     _reconstruction_finish_connection.disconnect();
354     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
355     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
356     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
358     while (event_context) {
359         SPEventContext *ec = event_context;
360         event_context = ec->next;
361         sp_event_context_finish (ec);
362         g_object_unref (G_OBJECT (ec));
363     }
365     if (_layer_hierarchy) {
366         delete _layer_hierarchy;
367 //        _layer_hierarchy = NULL; //this should be here, but commented to find other bug somewhere else.
368     }
370     if (layer_manager) {
371         delete layer_manager;
372         layer_manager = NULL;
373     }
375     if (_inkscape) {
376         _inkscape = NULL;
377     }
379     if (drawing) {
380         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
381         drawing = NULL;
382     }
384     delete _guides_message_context;
385     _guides_message_context = NULL;
387     g_list_free (zooms_past);
388     g_list_free (zooms_future);
391 SPDesktop::~SPDesktop() {}
393 //--------------------------------------------------------------------
394 /* Public methods */
397 /** Note that lifetime is measured in milliseconds
398 * it is perfectly safe to ignore the returned pointer: the object is deleted by itself, so don't delete it elsewhere!
399 * The return value should only be used as argument for SPDesktop::remove_temporary_canvasitem, because the object might be deleted already.
400 * move_to_bottom = true by default so the item does not interfere with handling of other items on the canvas like nodes.
401 */
402 Inkscape::Display::TemporaryItem *
403 SPDesktop::add_temporary_canvasitem (SPCanvasItem *item, guint lifetime, bool move_to_bottom)
405     if (move_to_bottom) {
406         sp_canvas_item_move_to_z(item, 0);
407     }
409     return temporary_item_list->add_item(item, lifetime);
412 /** It is perfectly safe to call this function while the object has already been deleted due to a timeout.
413 */
414 void
415 SPDesktop::remove_temporary_canvasitem (Inkscape::Display::TemporaryItem * tempitem)
417     // check for non-null temporary_item_list, because during destruction of desktop, some destructor might try to access this list!
418     if (tempitem && temporary_item_list) {
419         temporary_item_list->delete_item(tempitem);
420     }
423 void SPDesktop::_setDisplayMode(Inkscape::RenderMode mode) {
424     SP_CANVAS_ARENA (drawing)->arena->rendermode = mode;
425     canvas->rendermode = mode;
426     _display_mode = mode;
427     if (mode != Inkscape::RENDERMODE_OUTLINE) {
428         _saved_display_mode = _display_mode;
429     }
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() {
435     if (_display_mode == Inkscape::RENDERMODE_OUTLINE) {
436         _setDisplayMode(_saved_display_mode);
437     } else {
438         _setDisplayMode(Inkscape::RENDERMODE_OUTLINE);
439     }
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 in svg coordinates.
980  */
981 void
982 SPDesktop::scroll_world_in_svg_coords (double dx, double dy, bool is_scrolling)
984     double scale = expansion(_d2w);
985     scroll_world(dx*scale, dy*scale, is_scrolling);
988 /**
989  * Scroll canvas by specific coordinate amount.
990  */
991 void
992 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
994     g_assert(_widget);
996     NR::Rect const viewbox = canvas->getViewbox();
998     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
1000     /*  update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
1001     sp_box3d_context_update_lines(event_context);
1003     _widget->updateRulers();
1004     _widget->updateScrollbars(expansion(_d2w));
1007 bool
1008 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
1010     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
1012     // autoscrolldistance is in screen pixels, but the display area is in document units
1013     autoscrolldistance /= expansion(_d2w);
1014     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
1016     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
1017         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
1019         NR::Point const s_w( (*p) * _d2w );
1021         gdouble x_to;
1022         if ((*p)[NR::X] < dbox.min()[NR::X])
1023             x_to = dbox.min()[NR::X];
1024         else if ((*p)[NR::X] > dbox.max()[NR::X])
1025             x_to = dbox.max()[NR::X];
1026         else
1027             x_to = (*p)[NR::X];
1029         gdouble y_to;
1030         if ((*p)[NR::Y] < dbox.min()[NR::Y])
1031             y_to = dbox.min()[NR::Y];
1032         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
1033             y_to = dbox.max()[NR::Y];
1034         else
1035             y_to = (*p)[NR::Y];
1037         NR::Point const d_dt(x_to, y_to);
1038         NR::Point const d_w( d_dt * _d2w );
1039         NR::Point const moved_w( d_w - s_w );
1041         if (autoscrollspeed == 0)
1042             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1044         if (autoscrollspeed != 0)
1045             scroll_world (autoscrollspeed * moved_w);
1047         return true;
1048     }
1049     return false;
1052 bool
1053 SPDesktop::is_iconified()
1055     return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1058 void
1059 SPDesktop::iconify()
1061     _widget->setIconified();
1064 bool
1065 SPDesktop::is_maximized()
1067     return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1070 void
1071 SPDesktop::maximize()
1073     _widget->setMaximized();
1076 bool
1077 SPDesktop::is_fullscreen()
1079     return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1082 void
1083 SPDesktop::fullscreen()
1085     _widget->setFullscreen();
1088 void
1089 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1091     _widget->getGeometry (x, y, w, h);
1094 void
1095 SPDesktop::setWindowPosition (NR::Point p)
1097     _widget->setPosition (p);
1100 void
1101 SPDesktop::setWindowSize (gint w, gint h)
1103     _widget->setSize (w, h);
1106 void
1107 SPDesktop::setWindowTransient (void *p, int transient_policy)
1109     _widget->setTransient (p, transient_policy);
1112 Gtk::Window*
1113 SPDesktop::getToplevel( )
1115     return _widget->getWindow();
1118 void
1119 SPDesktop::presentWindow()
1121     _widget->present();
1124 bool
1125 SPDesktop::warnDialog (gchar *text)
1127     return _widget->warnDialog (text);
1130 void
1131 SPDesktop::toggleRulers()
1133     _widget->toggleRulers();
1136 void
1137 SPDesktop::toggleScrollbars()
1139     _widget->toggleScrollbars();
1142 void
1143 SPDesktop::layoutWidget()
1145     _widget->layout();
1148 void
1149 SPDesktop::destroyWidget()
1151     _widget->destroy();
1154 bool
1155 SPDesktop::shutdown()
1157     return _widget->shutdown();
1160 bool SPDesktop::onDeleteUI (GdkEventAny*)
1162     if(shutdown()) 
1163         return true;
1165     destroyWidget();
1166     return false;
1169 /**
1170  *  onWindowStateEvent
1171  *
1172  *  Called when the window changes its maximize/fullscreen/iconify/pinned state.
1173  *  Since GTK doesn't have a way to query this state information directly, we
1174  *  record it for the desktop here, and also possibly trigger a layout.
1175  */
1176 bool
1177 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1179         // Record the desktop window's state
1180     window_state = event->new_window_state;
1182     // Layout may differ depending on full-screen mode or not
1183     GdkWindowState changed = event->changed_mask;
1184     if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1185         layoutWidget();
1186     }
1188         return false;
1191 void
1192 SPDesktop::setToolboxFocusTo (gchar const *label)
1194     _widget->setToolboxFocusTo (label);
1197 void
1198 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1200     _widget->setToolboxAdjustmentValue (id, val);
1203 void
1204 SPDesktop::setToolboxSelectOneValue (gchar const* id, gint val)
1206     _widget->setToolboxSelectOneValue (id, val);
1209 bool
1210 SPDesktop::isToolboxButtonActive (gchar const *id)
1212     return _widget->isToolboxButtonActive (id);
1215 void
1216 SPDesktop::emitToolSubselectionChanged(gpointer data)
1218         _tool_subselection_changed.emit(data);
1219         inkscape_subselection_changed (this);
1222 void
1223 SPDesktop::updateNow()
1225   sp_canvas_update_now(canvas);
1228 void
1229 SPDesktop::enableInteraction()
1231   _widget->enableInteraction();
1234 void SPDesktop::disableInteraction()
1236   _widget->disableInteraction();
1239 void SPDesktop::setWaitingCursor()
1241     GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1242     gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1243     gdk_cursor_unref(waiting);
1244     waiting_cursor = true;
1246     // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1247     // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1248     // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1249     // after the call to setWaitingCursor as it was before
1250     while( Gtk::Main::events_pending() )
1251        Gtk::Main::iteration();
1254 void SPDesktop::clearWaitingCursor()
1256   if (waiting_cursor)
1257       sp_event_context_update_cursor(sp_desktop_event_context(this));
1260 void SPDesktop::toggleColorProfAdjust()
1262     _widget->toggleColorProfAdjust();
1265 void SPDesktop::toggleGrids()
1267     if (namedview->grids) {
1268         if(gridgroup) {
1269             showGrids(!grids_visible);
1270         }
1271     } else {
1272         //there is no grid present at the moment. add a rectangular grid and make it visible
1273         Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1274         Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1275         showGrids(true);
1276     }
1279 void SPDesktop::showGrids(bool show, bool dirty_document)
1281     grids_visible = show;
1282     sp_namedview_show_grids(namedview, grids_visible, dirty_document);
1283     if (show) {
1284         sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1285     } else {
1286         sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1287     }
1290 void SPDesktop::toggleSnapping()
1292     bool v = namedview->snap_manager.getSnapEnabledGlobally();
1293     Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1294     sp_repr_set_boolean(repr, "inkscape:snap-global", !v);
1297 //----------------------------------------------------------------------
1298 // Callback implementations. The virtual ones are connected by the view.
1300 void
1301 SPDesktop::onPositionSet (double x, double y)
1303     _widget->viewSetPosition (NR::Point(x,y));
1306 void
1307 SPDesktop::onResized (double /*x*/, double /*y*/)
1309    // Nothing called here
1312 /**
1313  * Redraw callback; queues Gtk redraw; connected by View.
1314  */
1315 void
1316 SPDesktop::onRedrawRequested ()
1318     if (main) {
1319         _widget->requestCanvasUpdate();
1320     }
1323 void
1324 SPDesktop::updateCanvasNow()
1326   _widget->requestCanvasUpdateAndWait();
1329 /**
1330  * Associate document with desktop.
1331  */
1332 void
1333 SPDesktop::setDocument (SPDocument *doc)
1335     if (this->doc() && doc) {
1336         namedview->hide(this);
1337         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1338     }
1340     if (_layer_hierarchy) {
1341         _layer_hierarchy->clear();
1342         delete _layer_hierarchy;
1343     }
1344     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1345     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1346     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1347     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1348     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1350     /* setup EventLog */
1351     event_log = new Inkscape::EventLog(doc);
1352     doc->addUndoObserver(*event_log);
1354     _commit_connection.disconnect();
1355     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1357     /// \todo fixme: This condition exists to make sure the code
1358     /// inside is NOT called on initialization, only on replacement. But there
1359     /// are surely more safe methods to accomplish this.
1360     // TODO since the comment had reversed logic, check the intent of this block of code:
1361     if (drawing) {
1362         NRArenaItem *ai = 0;
1364         namedview = sp_document_namedview (doc, NULL);
1365         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1366         number = namedview->getViewCount();
1368         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1369                 SP_CANVAS_ARENA (drawing)->arena,
1370                 dkey,
1371                 SP_ITEM_SHOW_DISPLAY);
1372         if (ai) {
1373             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1374             nr_arena_item_unref (ai);
1375         }
1376         namedview->show(this);
1377         /* Ugly hack */
1378         activate_guides (true);
1379         /* Ugly hack */
1380         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1381     }
1383     _document_replaced_signal.emit (this, doc);
1385     View::setDocument (doc);
1388 void
1389 SPDesktop::onStatusMessage
1390 (Inkscape::MessageType type, gchar const *message)
1392     if (_widget) {
1393         _widget->setMessage(type, message);
1394     }
1397 void
1398 SPDesktop::onDocumentURISet (gchar const* uri)
1400     _widget->setTitle(uri);
1403 /**
1404  * Resized callback.
1405  */
1406 void
1407 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1409     _doc2dt[5] = height;
1410     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1411     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1412     SP_CTRLRECT(page)->setRectangle(a);
1413     SP_CTRLRECT(page_border)->setRectangle(a);
1417 void
1418 SPDesktop::_onActivate (SPDesktop* dt)
1420     if (!dt->_widget) return;
1421     dt->_widget->activateDesktop();
1424 void
1425 SPDesktop::_onDeactivate (SPDesktop* dt)
1427     if (!dt->_widget) return;
1428     dt->_widget->deactivateDesktop();
1431 void
1432 SPDesktop::_onSelectionModified
1433 (Inkscape::Selection */*selection*/, guint /*flags*/, SPDesktop *dt)
1435     if (!dt->_widget) return;
1436     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1439 static void
1440 _onSelectionChanged
1441 (Inkscape::Selection *selection, SPDesktop *desktop)
1443     /** \todo
1444      * only change the layer for single selections, or what?
1445      * This seems reasonable -- for multiple selections there can be many
1446      * different layers involved.
1447      */
1448     SPItem *item=selection->singleItem();
1449     if (item) {
1450         SPObject *layer=desktop->layerForObject(item);
1451         if ( layer && layer != desktop->currentLayer() ) {
1452             desktop->setCurrentLayer(layer);
1453         }
1454     }
1457 /**
1458  * Calls event handler of current event context.
1459  * \param arena Unused
1460  * \todo fixme
1461  */
1462 static gint
1463 _arena_handler (SPCanvasArena */*arena*/, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1465     if (ai) {
1466         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1467         return sp_event_context_item_handler (desktop->event_context, spi, event);
1468     } else {
1469         return sp_event_context_root_handler (desktop->event_context, event);
1470     }
1473 static void
1474 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1475     g_return_if_fail(SP_IS_GROUP(layer));
1476     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1479 /// Callback
1480 static void
1481 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1482     g_return_if_fail(SP_IS_GROUP(layer));
1483     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1486 /// Callback
1487 static void
1488 _layer_hierarchy_changed(SPObject */*top*/, SPObject *bottom,
1489                                          SPDesktop *desktop)
1491     desktop->_layer_changed_signal.emit (bottom);
1494 /// Called when document is starting to be rebuilt.
1495 static void
1496 _reconstruction_start (SPDesktop * desktop)
1498     // printf("Desktop, starting reconstruction\n");
1499     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1500     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1502     /*
1503     GSList const * selection_objs = desktop->selection->list();
1504     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1506     }
1507     */
1508     desktop->selection->clear();
1510     // printf("Desktop, starting reconstruction end\n");
1513 /// Called when document rebuild is finished.
1514 static void
1515 _reconstruction_finish (SPDesktop * desktop)
1517     // printf("Desktop, finishing reconstruction\n");
1518     if (desktop->_reconstruction_old_layer_id == NULL)
1519         return;
1521     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1522     if (newLayer != NULL)
1523         desktop->setCurrentLayer(newLayer);
1525     g_free(desktop->_reconstruction_old_layer_id);
1526     desktop->_reconstruction_old_layer_id = NULL;
1527     // printf("Desktop, finishing reconstruction end\n");
1528     return;
1531 /**
1532  * Namedview_modified callback.
1533  */
1534 static void
1535 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1537     SPNamedView *nv=SP_NAMEDVIEW(obj);
1539     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1541         /* Recalculate snap distances */
1542         /* FIXME: why is the desktop getting involved in setting up something
1543         ** that is entirely to do with the namedview?
1544         */
1545         _update_snap_distances (desktop);
1547         /* Show/hide page background */
1548         if (nv->pagecolor & 0xff) {
1549             sp_canvas_item_show (desktop->table);
1550             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1551             sp_canvas_item_move_to_z (desktop->table, 0);
1552         } else {
1553             sp_canvas_item_hide (desktop->table);
1554         }
1556         /* Show/hide page border */
1557         if (nv->showborder) {
1558             // show
1559             sp_canvas_item_show (desktop->page_border);
1560             // set color and shadow
1561             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1562             if (nv->pageshadow) {
1563                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1564             }
1565             // place in the z-order stack
1566             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1567                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1568             } else {
1569                 int order = sp_canvas_item_order (desktop->page_border);
1570                 int morder = sp_canvas_item_order (desktop->drawing);
1571                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1572                                     morder - order);
1573             }
1574         } else {
1575                 sp_canvas_item_hide (desktop->page_border);
1576                 if (nv->pageshadow) {
1577                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1578                 }
1579         }
1581         /* Show/hide page shadow */
1582         if (nv->showpageshadow && nv->pageshadow) {
1583             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1584         } else {
1585             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1586         }
1588         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1589             (SP_RGBA32_R_U(nv->pagecolor) +
1590              SP_RGBA32_G_U(nv->pagecolor) +
1591              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1592             // the background color is light or transparent, use black outline
1593             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1594         } else { // use white outline
1595             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1596         }
1597     }
1600 /**
1601  * Callback to reset snapper's distances.
1602  */
1603 static void
1604 _update_snap_distances (SPDesktop *desktop)
1606     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1608     SPNamedView &nv = *desktop->namedview;
1610     //tell all grid snappers
1611     for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1612         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1613         grid->snapper->setSnapperTolerance(sp_convert_distance_full(nv.gridtolerance,
1614                                                                       *nv.gridtoleranceunit,
1615                                                                       px));
1616     }
1618     nv.snap_manager.guide.setSnapperTolerance(sp_convert_distance_full(nv.guidetolerance,
1619                                                                        *nv.guidetoleranceunit,
1620                                                                        px));
1621     nv.snap_manager.object.setSnapperTolerance(sp_convert_distance_full(nv.objecttolerance,
1622                                                                         *nv.objecttoleranceunit,
1623                                                                         px));
1627 NR::Matrix SPDesktop::w2d() const
1629     return _w2d;
1632 NR::Point SPDesktop::w2d(NR::Point const &p) const
1634     return p * _w2d;
1637 NR::Point SPDesktop::d2w(NR::Point const &p) const
1639     return p * _d2w;
1642 NR::Matrix SPDesktop::doc2dt() const
1644     return _doc2dt;
1647 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1649     return p * _doc2dt;
1652 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1654     return p / _doc2dt;
1658 /**
1659  * Pop event context from desktop's context stack. Never used.
1660  */
1661 // void
1662 // SPDesktop::pop_event_context (unsigned int key)
1663 // {
1664 //    SPEventContext *ec = NULL;
1665 //
1666 //    if (event_context && event_context->key == key) {
1667 //        g_return_if_fail (event_context);
1668 //        g_return_if_fail (event_context->next);
1669 //        ec = event_context;
1670 //        sp_event_context_deactivate (ec);
1671 //        event_context = ec->next;
1672 //        sp_event_context_activate (event_context);
1673 //        _event_context_changed_signal.emit (this, ec);
1674 //    }
1675 //
1676 //    SPEventContext *ref = event_context;
1677 //    while (ref && ref->next && ref->next->key != key)
1678 //        ref = ref->next;
1679 //
1680 //    if (ref && ref->next) {
1681 //        ec = ref->next;
1682 //        ref->next = ec->next;
1683 //    }
1684 //
1685 //    if (ec) {
1686 //        sp_event_context_finish (ec);
1687 //        g_object_unref (G_OBJECT (ec));
1688 //    }
1689 // }
1691 /*
1692   Local Variables:
1693   mode:c++
1694   c-file-style:"stroustrup"
1695   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1696   indent-tabs-mode:nil
1697   fill-column:99
1698   End:
1699 */
1700 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :