Code

convert almost all libnrtype to Geom::
[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-rect-ops.h"
83 #include "ui/dialog/dialog-manager.h"
84 #include "xml/repr.h"
85 #include "message-context.h"
86 #include "device-manager.h"
87 #include "layer-fns.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 /* These methods help for temporarily showing things on-canvas.
398  * The *only* valid use of the TemporaryItem* that you get from add_temporary_canvasitem
399  * is when you want to prematurely remove the item from the canvas, by calling
400  * desktop->remove_temporary_canvasitem(tempitem).
401  */
402 /** Note that lifetime is measured in milliseconds
403  * One should *not* keep a reference to the SPCanvasItem, the temporary item code will
404  * delete the object for you and the reference will become invalid without you knowing it.
405  * It is perfectly safe to ignore the returned pointer: the object is deleted by itself, so don't delete it elsewhere!
406  * The *only* valid use of the returned TemporaryItem* is as argument for SPDesktop::remove_temporary_canvasitem,
407  * because the object might be deleted already without you knowing it.
408  * move_to_bottom = true by default so the item does not interfere with handling of other items on the canvas like nodes.
409  */
410 Inkscape::Display::TemporaryItem *
411 SPDesktop::add_temporary_canvasitem (SPCanvasItem *item, guint lifetime, bool move_to_bottom)
413     if (move_to_bottom) {
414         sp_canvas_item_move_to_z(item, 0);
415     }
417     return temporary_item_list->add_item(item, lifetime);
420 /** It is perfectly safe to call this function while the object has already been deleted due to a timeout.
421 */
422 void
423 SPDesktop::remove_temporary_canvasitem (Inkscape::Display::TemporaryItem * tempitem)
425     // check for non-null temporary_item_list, because during destruction of desktop, some destructor might try to access this list!
426     if (tempitem && temporary_item_list) {
427         temporary_item_list->delete_item(tempitem);
428     }
431 void SPDesktop::_setDisplayMode(Inkscape::RenderMode mode) {
432     SP_CANVAS_ARENA (drawing)->arena->rendermode = mode;
433     canvas->rendermode = mode;
434     _display_mode = mode;
435     if (mode != Inkscape::RENDERMODE_OUTLINE) {
436         _saved_display_mode = _display_mode;
437     }
438     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
439     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
442 void SPDesktop::displayModeToggle() {
443     if (_display_mode == Inkscape::RENDERMODE_OUTLINE) {
444         _setDisplayMode(_saved_display_mode);
445     } else {
446         _setDisplayMode(Inkscape::RENDERMODE_OUTLINE);
447     }
450 /**
451  * Returns current root (=bottom) layer.
452  */
453 SPObject *SPDesktop::currentRoot() const
455     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
458 /**
459  * Returns current top layer.
460  */
461 SPObject *SPDesktop::currentLayer() const
463     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
466 /**
467  * Sets the current layer of the desktop.
468  *
469  * Make \a object the top layer.
470  */
471 void SPDesktop::setCurrentLayer(SPObject *object) {
472     g_return_if_fail(SP_IS_GROUP(object));
473     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
474     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
475     _layer_hierarchy->setBottom(object);
478 void SPDesktop::toggleLayerSolo(SPObject *object) {
479     g_return_if_fail(SP_IS_GROUP(object));
480     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
482     bool othersShowing = false;
483     std::vector<SPObject*> layers;
484     for ( SPObject* obj = Inkscape::next_layer(currentRoot(), object); obj; obj = Inkscape::next_layer(currentRoot(), obj) ) {
485         layers.push_back(obj);
486         othersShowing |= !SP_ITEM(obj)->isHidden();
487     }
488     for ( SPObject* obj = Inkscape::previous_layer(currentRoot(), object); obj; obj = Inkscape::previous_layer(currentRoot(), obj) ) {
489         layers.push_back(obj);
490         othersShowing |= !SP_ITEM(obj)->isHidden();
491     }
494     if ( SP_ITEM(object)->isHidden() ) {
495         SP_ITEM(object)->setHidden(false);
496     }
498     for ( std::vector<SPObject*>::iterator it = layers.begin(); it != layers.end(); ++it ) {
499         SP_ITEM(*it)->setHidden(othersShowing);
500     }
503 /**
504  * Return layer that contains \a object.
505  */
506 SPObject *SPDesktop::layerForObject(SPObject *object) {
507     g_return_val_if_fail(object != NULL, NULL);
509     SPObject *root=currentRoot();
510     object = SP_OBJECT_PARENT(object);
511     while ( object && object != root && !isLayer(object) ) {
512         object = SP_OBJECT_PARENT(object);
513     }
514     return object;
517 /**
518  * True if object is a layer.
519  */
520 bool SPDesktop::isLayer(SPObject *object) const {
521     return ( SP_IS_GROUP(object)
522              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
523                   == SPGroup::LAYER ) );
526 /**
527  * True if desktop viewport fully contains \a item's bbox.
528  */
529 bool SPDesktop::isWithinViewport (SPItem *item) const
531     NR::Rect const viewport = get_display_area();
532     boost::optional<NR::Rect> const bbox = sp_item_bbox_desktop(item);
533     if (bbox) {
534         return viewport.contains(*bbox);
535     } else {
536         return true;
537     }
540 ///
541 bool SPDesktop::itemIsHidden(SPItem const *item) const {
542     return item->isHidden(this->dkey);
545 /**
546  * Set activate property of desktop; emit signal if changed.
547  */
548 void
549 SPDesktop::set_active (bool new_active)
551     if (new_active != _active) {
552         _active = new_active;
553         if (new_active) {
554             _activate_signal.emit();
555         } else {
556             _deactivate_signal.emit();
557         }
558     }
561 /**
562  * Set activate status of current desktop's named view.
563  */
564 void
565 SPDesktop::activate_guides(bool activate)
567     guides_active = activate;
568     namedview->activateGuides(this, activate);
571 /**
572  * Make desktop switch documents.
573  */
574 void
575 SPDesktop::change_document (SPDocument *theDocument)
577     g_return_if_fail (theDocument != NULL);
579     /* unselect everything before switching documents */
580     selection->clear();
582     setDocument (theDocument);
584     /* update the rulers, connect the desktop widget's signal to the new namedview etc.
585        (this can probably be done in a better way) */
586     Gtk::Window *parent = this->getToplevel();
587     g_assert(parent != NULL);
588     SPDesktopWidget *dtw = (SPDesktopWidget *) parent->get_data("desktopwidget");
589     if (dtw) dtw->desktop = this;
590     sp_desktop_widget_update_namedview(dtw);
592     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
593     _document_replaced_signal.emit (this, theDocument);
596 /**
597  * Make desktop switch event contexts.
598  */
599 void
600 SPDesktop::set_event_context (GtkType type, const gchar *config)
602     SPEventContext *ec;
603     while (event_context) {
604         ec = event_context;
605         sp_event_context_deactivate (ec);
606         event_context = ec->next;
607         sp_event_context_finish (ec);
608         g_object_unref (G_OBJECT (ec));
609     }
611     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
612     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
613     ec->next = event_context;
614     event_context = ec;
615     sp_event_context_activate (ec);
616     _event_context_changed_signal.emit (this, ec);
619 /**
620  * Push event context onto desktop's context stack.
621  */
622 void
623 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
625     SPEventContext *ref, *ec;
626     Inkscape::XML::Node *repr;
628     if (event_context && event_context->key == key) return;
629     ref = event_context;
630     while (ref && ref->next && ref->next->key != key) ref = ref->next;
631     if (ref && ref->next) {
632         ec = ref->next;
633         ref->next = ec->next;
634         sp_event_context_finish (ec);
635         g_object_unref (G_OBJECT (ec));
636     }
638     if (event_context) sp_event_context_deactivate (event_context);
639     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
640     ec = sp_event_context_new (type, this, repr, key);
641     ec->next = event_context;
642     event_context = ec;
643     sp_event_context_activate (ec);
644     _event_context_changed_signal.emit (this, ec);
647 /**
648  * Sets the coordinate status to a given point
649  */
650 void
651 SPDesktop::set_coordinate_status (NR::Point p) {
652     _widget->setCoordinateStatus(p);
655 /**
656  * \see sp_document_item_from_list_at_point_bottom()
657  */
658 SPItem *
659 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
661     g_return_val_if_fail (doc() != NULL, NULL);
662     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
665 /**
666  * \see sp_document_item_at_point()
667  */
668 SPItem *
669 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
671     g_return_val_if_fail (doc() != NULL, NULL);
672     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
675 /**
676  * \see sp_document_group_at_point()
677  */
678 SPItem *
679 SPDesktop::group_at_point (NR::Point const p) const
681     g_return_val_if_fail (doc() != NULL, NULL);
682     return sp_document_group_at_point (doc(), dkey, p);
685 /**
686  * \brief  Returns the mouse point in document coordinates; if mouse is
687  * outside the canvas, returns the center of canvas viewpoint
688  */
689 NR::Point
690 SPDesktop::point() const
692     NR::Point p = _widget->getPointer();
693     NR::Point pw = sp_canvas_window_to_world (canvas, p);
694     p = w2d(pw);
696     NR::Rect const r = canvas->getViewbox();
698     NR::Point r0 = w2d(r.min());
699     NR::Point r1 = w2d(r.max());
701     if (p[NR::X] >= r0[NR::X] &&
702         p[NR::X] <= r1[NR::X] &&
703         p[NR::Y] >= r1[NR::Y] &&
704         p[NR::Y] <= r0[NR::Y])
705     {
706         return p;
707     } else {
708         return (r0 + r1) / 2;
709     }
712 /**
713  * Put current zoom data in history list.
714  */
715 void
716 SPDesktop::push_current_zoom (GList **history)
718     NR::Rect const area = get_display_area();
720     NRRect *old_zoom = g_new(NRRect, 1);
721     old_zoom->x0 = area.min()[NR::X];
722     old_zoom->x1 = area.max()[NR::X];
723     old_zoom->y0 = area.min()[NR::Y];
724     old_zoom->y1 = area.max()[NR::Y];
725     if ( *history == NULL
726          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
727                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
728                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
729                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
730     {
731         *history = g_list_prepend (*history, old_zoom);
732     }
735 /**
736  * Set viewbox (x0, x1, y0 and y1 are in document pixels. Border is in screen pixels).
737  */
738 void
739 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
741     g_assert(_widget);
743     // save the zoom
744     if (log) {
745         push_current_zoom(&zooms_past);
746         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
747         g_list_free (zooms_future);
748         zooms_future = NULL;
749     }
751     double const cx = 0.5 * (x0 + x1);
752     double const cy = 0.5 * (y0 + y1);
754     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
756     double scale = expansion(_d2w);
757     double newscale;
758     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
759         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
760     } else {
761         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
762     }
764     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); // unit: 'screen pixels' per 'document pixels'
766     int clear = FALSE;
767     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
768         /* Set zoom factors */
769         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
770         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
771         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
772         clear = TRUE;
773     }
775     /* Calculate top left corner (in document pixels) */
776     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
777     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
779     /* Scroll */
780     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
782     /*  update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
783     sp_box3d_context_update_lines(event_context);
785     _widget->updateRulers();
786     _widget->updateScrollbars(expansion(_d2w));
787     _widget->updateZoom();
790 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
792     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
795 /**
796  * Return viewbox dimensions.
797  */
798 NR::Rect SPDesktop::get_display_area() const
800     NR::Rect const viewbox = canvas->getViewbox();
802     double const scale = _d2w[0];
804     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
805                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
808 /**
809  * Revert back to previous zoom if possible.
810  */
811 void
812 SPDesktop::prev_zoom()
814     if (zooms_past == NULL) {
815         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
816         return;
817     }
819     // push current zoom into forward zooms list
820     push_current_zoom (&zooms_future);
822     // restore previous zoom
823     set_display_area (((NRRect *) zooms_past->data)->x0,
824             ((NRRect *) zooms_past->data)->y0,
825             ((NRRect *) zooms_past->data)->x1,
826             ((NRRect *) zooms_past->data)->y1,
827             0, false);
829     // remove the just-added zoom from the past zooms list
830     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
833 /**
834  * Set zoom to next in list.
835  */
836 void
837 SPDesktop::next_zoom()
839     if (zooms_future == NULL) {
840         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
841         return;
842     }
844     // push current zoom into past zooms list
845     push_current_zoom (&zooms_past);
847     // restore next zoom
848     set_display_area (((NRRect *) zooms_future->data)->x0,
849             ((NRRect *) zooms_future->data)->y0,
850             ((NRRect *) zooms_future->data)->x1,
851             ((NRRect *) zooms_future->data)->y1,
852             0, false);
854     // remove the just-used zoom from the zooms_future list
855     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
858 /**
859  * Zoom to point with absolute zoom factor.
860  */
861 void
862 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
864     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
866     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
867     // this check prevents "sliding" when trying to zoom in at maximum zoom;
868     /// \todo someone please fix calculations properly and remove this hack
869     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
870         return;
872     NR::Rect const viewbox = canvas->getViewbox();
874     double const width2 = viewbox.dimensions()[NR::X] / zoom;
875     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
877     set_display_area(cx - px * width2,
878                      cy - py * height2,
879                      cx + (1 - px) * width2,
880                      cy + (1 - py) * height2,
881                      0.0);
884 /**
885  * Zoom to center with absolute zoom factor.
886  */
887 void
888 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
890     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
893 /**
894  * Zoom to point with relative zoom factor.
895  */
896 void
897 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
899     NR::Rect const area = get_display_area();
901     if (cx < area.min()[NR::X]) {
902         cx = area.min()[NR::X];
903     }
904     if (cx > area.max()[NR::X]) {
905         cx = area.max()[NR::X];
906     }
907     if (cy < area.min()[NR::Y]) {
908         cy = area.min()[NR::Y];
909     }
910     if (cy > area.max()[NR::Y]) {
911         cy = area.max()[NR::Y];
912     }
914     gdouble const scale = expansion(_d2w) * zoom;
915     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
916     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
918     zoom_absolute_keep_point(cx, cy, px, py, scale);
921 /**
922  * Zoom to center with relative zoom factor.
923  */
924 void
925 SPDesktop::zoom_relative (double cx, double cy, double zoom)
927     gdouble scale = expansion(_d2w) * zoom;
928     zoom_absolute (cx, cy, scale);
931 /**
932  * Set display area to origin and current document dimensions.
933  */
934 void
935 SPDesktop::zoom_page()
937     NR::Rect d(NR::Point(0, 0),
938                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
940     if (d.isEmpty(1.0)) {
941         return;
942     }
944     set_display_area(d, 10);
947 /**
948  * Set display area to current document width.
949  */
950 void
951 SPDesktop::zoom_page_width()
953     NR::Rect const a = get_display_area();
955     if (sp_document_width(doc()) < 1.0) {
956         return;
957     }
959     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
960                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
962     set_display_area(d, 10);
965 /**
966  * Zoom to selection.
967  */
968 void
969 SPDesktop::zoom_selection()
971     boost::optional<NR::Rect> const d = selection->bounds();
973     if ( !d || d->isEmpty(0.1) ) {
974         return;
975     }
977     set_display_area(*d, 10);
980 /**
981  * Tell widget to let zoom widget grab keyboard focus.
982  */
983 void
984 SPDesktop::zoom_grab_focus()
986     _widget->letZoomGrabFocus();
989 /**
990  * Zoom to whole drawing.
991  */
992 void
993 SPDesktop::zoom_drawing()
995     g_return_if_fail (doc() != NULL);
996     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
997     g_return_if_fail (docitem != NULL);
999     boost::optional<NR::Rect> d = sp_item_bbox_desktop(docitem);
1001     /* Note that the second condition here indicates that
1002     ** there are no items in the drawing.
1003     */
1004     if ( !d || d->isEmpty(1.0) ) {
1005         return;
1006     }
1008     set_display_area(*d, 10);
1011 /**
1012  * Scroll canvas by specific coordinate amount in svg coordinates.
1013  */
1014 void
1015 SPDesktop::scroll_world_in_svg_coords (double dx, double dy, bool is_scrolling)
1017     double scale = expansion(_d2w);
1018     scroll_world(dx*scale, dy*scale, is_scrolling);
1021 /**
1022  * Scroll canvas by specific coordinate amount.
1023  */
1024 void
1025 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
1027     g_assert(_widget);
1029     NR::Rect const viewbox = canvas->getViewbox();
1031     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
1033     /*  update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
1034     sp_box3d_context_update_lines(event_context);
1036     _widget->updateRulers();
1037     _widget->updateScrollbars(expansion(_d2w));
1040 bool
1041 SPDesktop::scroll_to_point (Geom::Point const &p, gdouble autoscrollspeed)
1043     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
1045     // autoscrolldistance is in screen pixels, but the display area is in document units
1046     autoscrolldistance /= expansion(_d2w);
1047     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
1049     if (!(p[NR::X] > dbox.min()[NR::X] && p[NR::X] < dbox.max()[NR::X]) ||
1050         !(p[NR::Y] > dbox.min()[NR::Y] && p[NR::Y] < dbox.max()[NR::Y])   ) {
1052         NR::Point const s_w( p * (Geom::Matrix)_d2w );
1054         gdouble x_to;
1055         if (p[NR::X] < dbox.min()[NR::X])
1056             x_to = dbox.min()[NR::X];
1057         else if (p[NR::X] > dbox.max()[NR::X])
1058             x_to = dbox.max()[NR::X];
1059         else
1060             x_to = p[NR::X];
1062         gdouble y_to;
1063         if (p[NR::Y] < dbox.min()[NR::Y])
1064             y_to = dbox.min()[NR::Y];
1065         else if (p[NR::Y] > dbox.max()[NR::Y])
1066             y_to = dbox.max()[NR::Y];
1067         else
1068             y_to = p[NR::Y];
1070         NR::Point const d_dt(x_to, y_to);
1071         NR::Point const d_w( d_dt * _d2w );
1072         NR::Point const moved_w( d_w - s_w );
1074         if (autoscrollspeed == 0)
1075             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1077         if (autoscrollspeed != 0)
1078             scroll_world (autoscrollspeed * moved_w);
1080         return true;
1081     }
1082     return false;
1085 bool
1086 SPDesktop::is_iconified()
1088     return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1091 void
1092 SPDesktop::iconify()
1094     _widget->setIconified();
1097 bool
1098 SPDesktop::is_maximized()
1100     return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1103 void
1104 SPDesktop::maximize()
1106     _widget->setMaximized();
1109 bool
1110 SPDesktop::is_fullscreen()
1112     return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1115 void
1116 SPDesktop::fullscreen()
1118     _widget->setFullscreen();
1121 void
1122 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1124     _widget->getGeometry (x, y, w, h);
1127 void
1128 SPDesktop::setWindowPosition (NR::Point p)
1130     _widget->setPosition (p);
1133 void
1134 SPDesktop::setWindowSize (gint w, gint h)
1136     _widget->setSize (w, h);
1139 void
1140 SPDesktop::setWindowTransient (void *p, int transient_policy)
1142     _widget->setTransient (p, transient_policy);
1145 Gtk::Window*
1146 SPDesktop::getToplevel( )
1148     return _widget->getWindow();
1151 void
1152 SPDesktop::presentWindow()
1154     _widget->present();
1157 bool
1158 SPDesktop::warnDialog (gchar *text)
1160     return _widget->warnDialog (text);
1163 void
1164 SPDesktop::toggleRulers()
1166     _widget->toggleRulers();
1169 void
1170 SPDesktop::toggleScrollbars()
1172     _widget->toggleScrollbars();
1175 void
1176 SPDesktop::layoutWidget()
1178     _widget->layout();
1181 void
1182 SPDesktop::destroyWidget()
1184     _widget->destroy();
1187 bool
1188 SPDesktop::shutdown()
1190     return _widget->shutdown();
1193 bool SPDesktop::onDeleteUI (GdkEventAny*)
1195     if(shutdown()) 
1196         return true;
1198     destroyWidget();
1199     return false;
1202 /**
1203  *  onWindowStateEvent
1204  *
1205  *  Called when the window changes its maximize/fullscreen/iconify/pinned state.
1206  *  Since GTK doesn't have a way to query this state information directly, we
1207  *  record it for the desktop here, and also possibly trigger a layout.
1208  */
1209 bool
1210 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1212         // Record the desktop window's state
1213     window_state = event->new_window_state;
1215     // Layout may differ depending on full-screen mode or not
1216     GdkWindowState changed = event->changed_mask;
1217     if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1218         layoutWidget();
1219     }
1221         return false;
1224 void
1225 SPDesktop::setToolboxFocusTo (gchar const *label)
1227     _widget->setToolboxFocusTo (label);
1230 void
1231 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1233     _widget->setToolboxAdjustmentValue (id, val);
1236 void
1237 SPDesktop::setToolboxSelectOneValue (gchar const* id, gint val)
1239     _widget->setToolboxSelectOneValue (id, val);
1242 bool
1243 SPDesktop::isToolboxButtonActive (gchar const *id)
1245     return _widget->isToolboxButtonActive (id);
1248 void
1249 SPDesktop::emitToolSubselectionChanged(gpointer data)
1251         _tool_subselection_changed.emit(data);
1252         inkscape_subselection_changed (this);
1255 void
1256 SPDesktop::updateNow()
1258   sp_canvas_update_now(canvas);
1261 void
1262 SPDesktop::enableInteraction()
1264   _widget->enableInteraction();
1267 void SPDesktop::disableInteraction()
1269   _widget->disableInteraction();
1272 void SPDesktop::setWaitingCursor()
1274     GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1275     gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1276     gdk_cursor_unref(waiting);
1277     // GDK needs the flush for the cursor change to take effect
1278     gdk_flush();
1279     waiting_cursor = true;
1282 void SPDesktop::clearWaitingCursor()
1284   if (waiting_cursor)
1285       sp_event_context_update_cursor(sp_desktop_event_context(this));
1288 void SPDesktop::toggleColorProfAdjust()
1290     _widget->toggleColorProfAdjust();
1293 void SPDesktop::toggleGrids()
1295     if (namedview->grids) {
1296         if(gridgroup) {
1297             showGrids(!grids_visible);
1298         }
1299     } else {
1300         //there is no grid present at the moment. add a rectangular grid and make it visible
1301         Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1302         Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1303         showGrids(true);
1304     }
1307 void SPDesktop::showGrids(bool show, bool dirty_document)
1309     grids_visible = show;
1310     sp_namedview_show_grids(namedview, grids_visible, dirty_document);
1311     if (show) {
1312         sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1313     } else {
1314         sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1315     }
1318 void SPDesktop::toggleSnapping()
1320     bool v = namedview->snap_manager.getSnapEnabledGlobally();
1321     Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1322     sp_repr_set_boolean(repr, "inkscape:snap-global", !v);
1325 //----------------------------------------------------------------------
1326 // Callback implementations. The virtual ones are connected by the view.
1328 void
1329 SPDesktop::onPositionSet (double x, double y)
1331     _widget->viewSetPosition (NR::Point(x,y));
1334 void
1335 SPDesktop::onResized (double /*x*/, double /*y*/)
1337    // Nothing called here
1340 /**
1341  * Redraw callback; queues Gtk redraw; connected by View.
1342  */
1343 void
1344 SPDesktop::onRedrawRequested ()
1346     if (main) {
1347         _widget->requestCanvasUpdate();
1348     }
1351 void
1352 SPDesktop::updateCanvasNow()
1354   _widget->requestCanvasUpdateAndWait();
1357 /**
1358  * Associate document with desktop.
1359  */
1360 void
1361 SPDesktop::setDocument (SPDocument *doc)
1363     if (this->doc() && doc) {
1364         namedview->hide(this);
1365         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1366     }
1368     if (_layer_hierarchy) {
1369         _layer_hierarchy->clear();
1370         delete _layer_hierarchy;
1371     }
1372     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1373     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1374     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1375     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1376     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1378     /* setup EventLog */
1379     event_log = new Inkscape::EventLog(doc);
1380     doc->addUndoObserver(*event_log);
1382     _commit_connection.disconnect();
1383     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1385     /// \todo fixme: This condition exists to make sure the code
1386     /// inside is NOT called on initialization, only on replacement. But there
1387     /// are surely more safe methods to accomplish this.
1388     // TODO since the comment had reversed logic, check the intent of this block of code:
1389     if (drawing) {
1390         NRArenaItem *ai = 0;
1392         namedview = sp_document_namedview (doc, NULL);
1393         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1394         number = namedview->getViewCount();
1396         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1397                 SP_CANVAS_ARENA (drawing)->arena,
1398                 dkey,
1399                 SP_ITEM_SHOW_DISPLAY);
1400         if (ai) {
1401             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1402             nr_arena_item_unref (ai);
1403         }
1404         namedview->show(this);
1405         /* Ugly hack */
1406         activate_guides (true);
1407         /* Ugly hack */
1408         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1409     }
1411     _document_replaced_signal.emit (this, doc);
1413     View::setDocument (doc);
1416 void
1417 SPDesktop::onStatusMessage
1418 (Inkscape::MessageType type, gchar const *message)
1420     if (_widget) {
1421         _widget->setMessage(type, message);
1422     }
1425 void
1426 SPDesktop::onDocumentURISet (gchar const* uri)
1428     _widget->setTitle(uri);
1431 /**
1432  * Resized callback.
1433  */
1434 void
1435 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1437     _doc2dt[5] = height;
1438     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1439     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1440     SP_CTRLRECT(page)->setRectangle(a);
1441     SP_CTRLRECT(page_border)->setRectangle(a);
1445 void
1446 SPDesktop::_onActivate (SPDesktop* dt)
1448     if (!dt->_widget) return;
1449     dt->_widget->activateDesktop();
1452 void
1453 SPDesktop::_onDeactivate (SPDesktop* dt)
1455     if (!dt->_widget) return;
1456     dt->_widget->deactivateDesktop();
1459 void
1460 SPDesktop::_onSelectionModified
1461 (Inkscape::Selection */*selection*/, guint /*flags*/, SPDesktop *dt)
1463     if (!dt->_widget) return;
1464     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1467 static void
1468 _onSelectionChanged
1469 (Inkscape::Selection *selection, SPDesktop *desktop)
1471     /** \todo
1472      * only change the layer for single selections, or what?
1473      * This seems reasonable -- for multiple selections there can be many
1474      * different layers involved.
1475      */
1476     SPItem *item=selection->singleItem();
1477     if (item) {
1478         SPObject *layer=desktop->layerForObject(item);
1479         if ( layer && layer != desktop->currentLayer() ) {
1480             desktop->setCurrentLayer(layer);
1481         }
1482     }
1485 /**
1486  * Calls event handler of current event context.
1487  * \param arena Unused
1488  * \todo fixme
1489  */
1490 static gint
1491 _arena_handler (SPCanvasArena */*arena*/, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1493     if (ai) {
1494         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1495         return sp_event_context_item_handler (desktop->event_context, spi, event);
1496     } else {
1497         return sp_event_context_root_handler (desktop->event_context, event);
1498     }
1501 static void
1502 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1503     g_return_if_fail(SP_IS_GROUP(layer));
1504     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1507 /// Callback
1508 static void
1509 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1510     g_return_if_fail(SP_IS_GROUP(layer));
1511     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1514 /// Callback
1515 static void
1516 _layer_hierarchy_changed(SPObject */*top*/, SPObject *bottom,
1517                                          SPDesktop *desktop)
1519     desktop->_layer_changed_signal.emit (bottom);
1522 /// Called when document is starting to be rebuilt.
1523 static void
1524 _reconstruction_start (SPDesktop * desktop)
1526     // printf("Desktop, starting reconstruction\n");
1527     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1528     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1530     /*
1531     GSList const * selection_objs = desktop->selection->list();
1532     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1534     }
1535     */
1536     desktop->selection->clear();
1538     // printf("Desktop, starting reconstruction end\n");
1541 /// Called when document rebuild is finished.
1542 static void
1543 _reconstruction_finish (SPDesktop * desktop)
1545     // printf("Desktop, finishing reconstruction\n");
1546     if (desktop->_reconstruction_old_layer_id == NULL)
1547         return;
1549     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1550     if (newLayer != NULL)
1551         desktop->setCurrentLayer(newLayer);
1553     g_free(desktop->_reconstruction_old_layer_id);
1554     desktop->_reconstruction_old_layer_id = NULL;
1555     // printf("Desktop, finishing reconstruction end\n");
1556     return;
1559 /**
1560  * Namedview_modified callback.
1561  */
1562 static void
1563 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1565     SPNamedView *nv=SP_NAMEDVIEW(obj);
1567     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1569         /* Recalculate snap distances */
1570         /* FIXME: why is the desktop getting involved in setting up something
1571         ** that is entirely to do with the namedview?
1572         */
1573         _update_snap_distances (desktop);
1575         /* Show/hide page background */
1576         if (nv->pagecolor & 0xff) {
1577             sp_canvas_item_show (desktop->table);
1578             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1579             sp_canvas_item_move_to_z (desktop->table, 0);
1580         } else {
1581             sp_canvas_item_hide (desktop->table);
1582         }
1584         /* Show/hide page border */
1585         if (nv->showborder) {
1586             // show
1587             sp_canvas_item_show (desktop->page_border);
1588             // set color and shadow
1589             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1590             if (nv->pageshadow) {
1591                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1592             }
1593             // place in the z-order stack
1594             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1595                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1596             } else {
1597                 int order = sp_canvas_item_order (desktop->page_border);
1598                 int morder = sp_canvas_item_order (desktop->drawing);
1599                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1600                                     morder - order);
1601             }
1602         } else {
1603                 sp_canvas_item_hide (desktop->page_border);
1604                 if (nv->pageshadow) {
1605                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1606                 }
1607         }
1609         /* Show/hide page shadow */
1610         if (nv->showpageshadow && nv->pageshadow) {
1611             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1612         } else {
1613             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1614         }
1616         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1617             (SP_RGBA32_R_U(nv->pagecolor) +
1618              SP_RGBA32_G_U(nv->pagecolor) +
1619              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1620             // the background color is light or transparent, use black outline
1621             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1622         } else { // use white outline
1623             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1624         }
1625     }
1628 /**
1629  * Callback to reset snapper's distances.
1630  */
1631 static void
1632 _update_snap_distances (SPDesktop *desktop)
1634     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1636     SPNamedView &nv = *desktop->namedview;
1638     //tell all grid snappers
1639     for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1640         Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1641         grid->snapper->setSnapperTolerance(sp_convert_distance_full(nv.gridtolerance,
1642                                                                       *nv.gridtoleranceunit,
1643                                                                       px));
1644     }
1646     nv.snap_manager.guide.setSnapperTolerance(sp_convert_distance_full(nv.guidetolerance,
1647                                                                        *nv.guidetoleranceunit,
1648                                                                        px));
1649     nv.snap_manager.object.setSnapperTolerance(sp_convert_distance_full(nv.objecttolerance,
1650                                                                         *nv.objecttoleranceunit,
1651                                                                         px));
1655 NR::Matrix SPDesktop::w2d() const
1657     return _w2d;
1660 NR::Point SPDesktop::w2d(NR::Point const &p) const
1662     return p * _w2d;
1665 NR::Point SPDesktop::d2w(NR::Point const &p) const
1667     return p * _d2w;
1670 NR::Matrix SPDesktop::doc2dt() const
1672     return _doc2dt;
1675 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1677     return p * _doc2dt;
1680 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1682     return p * _doc2dt.inverse();
1686 /**
1687  * Pop event context from desktop's context stack. Never used.
1688  */
1689 // void
1690 // SPDesktop::pop_event_context (unsigned int key)
1691 // {
1692 //    SPEventContext *ec = NULL;
1693 //
1694 //    if (event_context && event_context->key == key) {
1695 //        g_return_if_fail (event_context);
1696 //        g_return_if_fail (event_context->next);
1697 //        ec = event_context;
1698 //        sp_event_context_deactivate (ec);
1699 //        event_context = ec->next;
1700 //        sp_event_context_activate (event_context);
1701 //        _event_context_changed_signal.emit (this, ec);
1702 //    }
1703 //
1704 //    SPEventContext *ref = event_context;
1705 //    while (ref && ref->next && ref->next->key != key)
1706 //        ref = ref->next;
1707 //
1708 //    if (ref && ref->next) {
1709 //        ec = ref->next;
1710 //        ref->next = ec->next;
1711 //    }
1712 //
1713 //    if (ec) {
1714 //        sp_event_context_finish (ec);
1715 //        g_object_unref (G_OBJECT (ec));
1716 //    }
1717 // }
1719 /*
1720   Local Variables:
1721   mode:c++
1722   c-file-style:"stroustrup"
1723   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1724   indent-tabs-mode:nil
1725   fill-column:99
1726   End:
1727 */
1728 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :