Code

49a43bc37b53e98bdfbb7064fe0c7d53cd649aaa
[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  *
12  * Copyright (C) 2004 MenTaLguY
13  * Copyright (C) 1999-2002 Lauris Kaplinski
14  * Copyright (C) 2000-2001 Ximian, Inc.
15  *
16  * Released under GNU GPL, read the file 'COPYING' for more information
17  */
19 /** \class SPDesktop
20  * SPDesktop is a subclass of View, implementing an editable document
21  * canvas.  It is extensively used by many UI controls that need certain
22  * visual representations of their own.
23  *
24  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
25  * layers of different control objects. The one containing the whole
26  * document is the drawing layer. In addition to it, there are grid,
27  * guide, sketch and control layers. The sketch layer is used for
28  * temporary drawing objects, before the real objects in document are
29  * created. The control layer contains editing knots, rubberband and
30  * similar non-document UI objects.
31  *
32  * Each SPDesktop is associated with a SPNamedView node of the document
33  * tree.  Currently, all desktops are created from a single main named
34  * view, but in the future there may be support for different ones.
35  * SPNamedView serves as an in-document container for desktop-related
36  * data, like grid and guideline placement, snapping options and so on.
37  *
38  * Associated with each SPDesktop are the two most important editing
39  * related objects - SPSelection and SPEventContext.
40  *
41  * Sodipodi keeps track of the active desktop and invokes notification
42  * signals whenever it changes. UI elements can use these to update their
43  * display to the selection of the currently active editing window.
44  * (Lauris Kaplinski)
45  */
47 #ifdef HAVE_CONFIG_H
48 # include "config.h"
49 #endif
51 #include <glibmm/i18n.h>
53 #include "macros.h"
54 #include "inkscape-private.h"
55 #include "desktop.h"
56 #include "desktop-events.h"
57 #include "desktop-handles.h"
58 #include "document.h"
59 #include "message-stack.h"
60 #include "selection.h"
61 #include "select-context.h"
62 #include "sp-namedview.h"
63 #include "color.h"
64 #include "sp-item-group.h"
65 #include "prefs-utils.h"
66 #include "object-hierarchy.h"
67 #include "helper/units.h"
68 #include "display/canvas-arena.h"
69 #include "display/nr-arena.h"
70 #include "display/gnome-canvas-acetate.h"
71 #include "display/sodipodi-ctrlrect.h"
72 #include "display/sp-canvas-util.h"
73 #include "libnr/nr-matrix-div.h"
74 #include "libnr/nr-rect-ops.h"
75 #include "ui/dialog/dialog-manager.h"
76 #include "xml/repr.h"
77 #include "message-context.h"
78 #include "layer-manager.h"
80 namespace Inkscape { namespace XML { class Node; }}
82 // Callback declarations
83 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
84 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
85 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
86 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
87 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
88 static void _reconstruction_start(SPDesktop * desktop);
89 static void _reconstruction_finish(SPDesktop * desktop);
90 static void _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop);
91 static void _update_snap_distances (SPDesktop *desktop);
93 /**
94  * Return new desktop object.
95  * \pre namedview != NULL.
96  * \pre canvas != NULL.
97  */
98 SPDesktop::SPDesktop()
99 {
100     _dlg_mgr = NULL;
101     _widget = 0;
102     namedview = NULL;
103     selection = NULL;
104     acetate = NULL;
105     main = NULL;
106     grid = NULL;
107     guides = NULL;
108     drawing = NULL;
109     sketch = NULL;
110     controls = NULL;
111     event_context = 0;
112     layer_manager = 0;
114     _d2w.set_identity();
115     _w2d.set_identity();
116     _doc2dt = NR::Matrix(NR::scale(1, -1));
118     guides_active = false;
120     zooms_past = NULL;
121     zooms_future = NULL;
123     is_fullscreen = false;
125     gr_item = NULL;
126     gr_point_num = 0;
127     gr_fill_or_stroke = true;
129     _layer_hierarchy = NULL;
130     _active = false;
132     selection = Inkscape::GC::release (new Inkscape::Selection (this));
135 void
136 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
139     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
141     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
143     namedview = nv;
144     canvas = aCanvas;
146     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
147     /* Kill flicker */
148     sp_document_ensure_up_to_date (document);
150     /* Setup Dialog Manager */
151     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
153     dkey = sp_item_display_key_new (1);
155     /* Connect document */
156     setDocument (document);
158     number = namedview->getViewCount();
161     /* Setup Canvas */
162     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
164     SPCanvasGroup *root = sp_canvas_root (canvas);
166     /* Setup adminstrative layers */
167     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
168     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
169     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
170     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
172     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
173     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
174     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
175     sp_canvas_item_move_to_z (table, 0);
177     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
178     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
179     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
181     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
182     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
184     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
186     // Start always in normal mode
187     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
188     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
190     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
191     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
192     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
193     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195     /* Push select tool to the bottom of stack */
196     /** \todo
197      * FIXME: this is the only call to this.  Everything else seems to just
198      * call "set" instead of "push".  Can we assume that there is only one
199      * context ever?
200      */
201     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
203     // display rect and zoom are now handled in sp_desktop_widget_realize()
205     NR::Rect const d(NR::Point(0.0, 0.0),
206                      NR::Point(sp_document_width(document), sp_document_height(document)));
208     SP_CTRLRECT(page)->setRectangle(d);
209     SP_CTRLRECT(page_border)->setRectangle(d);
211     /* the following sets the page shadow on the canvas
212        It was originally set to 5, which is really cheesy!
213        It now is an attribute in the document's namedview. If a value of
214        0 is used, then the constructor for a shadow is not initialized.
215     */
217     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
218         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
219     }
222     /* Connect event for page resize */
223     _doc2dt[5] = sp_document_height (document);
224     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
226     g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
229     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
230             SP_CANVAS_ARENA (drawing)->arena,
231             dkey,
232             SP_ITEM_SHOW_DISPLAY);
233     if (ai) {
234         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
235         nr_arena_item_unref (ai);
236     }
238     namedview->show(this);
239     /* Ugly hack */
240     activate_guides (true);
241     /* Ugly hack */
242     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
244 /* Set up notification of rebuilding the document, this allows
245        for saving object related settings in the document. */
246     _reconstruction_start_connection =
247         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
248     _reconstruction_finish_connection =
249         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
250     _reconstruction_old_layer_id = NULL;
252     // ?
253     // sp_active_desktop_set (desktop);
254     _inkscape = INKSCAPE;
256     _activate_connection = _activate_signal.connect(
257         sigc::bind(
258             sigc::ptr_fun(_onActivate),
259             this
260         )
261     );
262      _deactivate_connection = _deactivate_signal.connect(
263         sigc::bind(
264             sigc::ptr_fun(_onDeactivate),
265             this
266         )
267     );
269     _sel_modified_connection = selection->connectModified(
270         sigc::bind(
271             sigc::ptr_fun(&_onSelectionModified),
272             this
273         )
274     );
275     _sel_changed_connection = selection->connectChanged(
276         sigc::bind(
277             sigc::ptr_fun(&_onSelectionChanged),
278             this
279         )
280     );
283     /* setup LayerManager */
284     //   (Setting up after the connections are all in place, as it may use some of them)
285     layer_manager = new Inkscape::LayerManager( this );
289 void SPDesktop::destroy()
291     _activate_connection.disconnect();
292     _deactivate_connection.disconnect();
293     _sel_modified_connection.disconnect();
294     _sel_changed_connection.disconnect();
296     while (event_context) {
297         SPEventContext *ec = event_context;
298         event_context = ec->next;
299         sp_event_context_finish (ec);
300         g_object_unref (G_OBJECT (ec));
301     }
303     if (_layer_hierarchy) {
304         delete _layer_hierarchy;
305     }
307     if (_inkscape) {
308         _inkscape = NULL;
309     }
311     if (drawing) {
312         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
313         drawing = NULL;
314     }
316     delete _guides_message_context;
317     _guides_message_context = NULL;
319     sp_signal_disconnect_by_data (G_OBJECT (namedview), this);
321     g_list_free (zooms_past);
322     g_list_free (zooms_future);
325 SPDesktop::~SPDesktop() {}
327 //--------------------------------------------------------------------
328 /* Public methods */
330 void SPDesktop::setDisplayModeNormal()
332     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
333     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
334     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
337 void SPDesktop::setDisplayModeOutline()
339     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
340     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
341     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
344 /**
345  * Returns current root (=bottom) layer.
346  */
347 SPObject *SPDesktop::currentRoot() const
349     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
352 /**
353  * Returns current top layer.
354  */
355 SPObject *SPDesktop::currentLayer() const
357     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
360 /**
361  * Sets the current layer of the desktop.
362  * 
363  * Make \a object the top layer.
364  */
365 void SPDesktop::setCurrentLayer(SPObject *object) {
366     g_return_if_fail(SP_IS_GROUP(object));
367     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
368     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
369     _layer_hierarchy->setBottom(object);
372 /**
373  * Return layer that contains \a object.
374  */
375 SPObject *SPDesktop::layerForObject(SPObject *object) {
376     g_return_val_if_fail(object != NULL, NULL);
378     SPObject *root=currentRoot();
379     object = SP_OBJECT_PARENT(object);
380     while ( object && object != root && !isLayer(object) ) {
381         object = SP_OBJECT_PARENT(object);
382     }
383     return object;
386 /**
387  * True if object is a layer.
388  */
389 bool SPDesktop::isLayer(SPObject *object) const {
390     return ( SP_IS_GROUP(object)
391              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
392                   == SPGroup::LAYER ) );
395 /**
396  * True if desktop viewport fully contains \a item's bbox.
397  */
398 bool SPDesktop::isWithinViewport (SPItem *item) const
400     NR::Rect const viewport = get_display_area();
401     NR::Rect const bbox = sp_item_bbox_desktop(item);
402     return viewport.contains(bbox);
405 ///
406 bool SPDesktop::itemIsHidden(SPItem const *item) const {
407     return item->isHidden(this->dkey);
410 /**
411  * Set activate property of desktop; emit signal if changed.
412  */
413 void
414 SPDesktop::set_active (bool new_active)
416     if (new_active != _active) {
417         _active = new_active;
418         if (new_active) {
419             _activate_signal.emit();
420         } else {
421             _deactivate_signal.emit();
422         }
423     }
426 /**
427  * Set activate status of current desktop's named view.
428  */
429 void
430 SPDesktop::activate_guides(bool activate)
432     guides_active = activate;
433     namedview->activateGuides(this, activate);
436 /**
437  * Make desktop switch documents.
438  */
439 void
440 SPDesktop::change_document (SPDocument *theDocument)
442     g_return_if_fail (theDocument != NULL);
444     /* unselect everything before switching documents */
445     selection->clear();
447     setDocument (theDocument);
448     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
449     _document_replaced_signal.emit (this, theDocument);
452 /**
453  * Make desktop switch event contexts.
454  */
455 void
456 SPDesktop::set_event_context (GtkType type, const gchar *config)
458     SPEventContext *ec;
459     while (event_context) {
460         ec = event_context;
461         sp_event_context_deactivate (ec);
462         event_context = ec->next;
463         sp_event_context_finish (ec);
464         g_object_unref (G_OBJECT (ec));
465     }
467     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
468     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
469     ec->next = event_context;
470     event_context = ec;
471     sp_event_context_activate (ec);
472     _event_context_changed_signal.emit (this, ec);
475 /**
476  * Push event context onto desktop's context stack.
477  */
478 void
479 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
481     SPEventContext *ref, *ec;
482     Inkscape::XML::Node *repr;
484     if (event_context && event_context->key == key) return;
485     ref = event_context;
486     while (ref && ref->next && ref->next->key != key) ref = ref->next;
487     if (ref && ref->next) {
488         ec = ref->next;
489         ref->next = ec->next;
490         sp_event_context_finish (ec);
491         g_object_unref (G_OBJECT (ec));
492     }
494     if (event_context) sp_event_context_deactivate (event_context);
495     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
496     ec = sp_event_context_new (type, this, repr, key);
497     ec->next = event_context;
498     event_context = ec;
499     sp_event_context_activate (ec);
500     _event_context_changed_signal.emit (this, ec);
503 /**
504  * Sets the coordinate status to a given point
505  */
506 void
507 SPDesktop::set_coordinate_status (NR::Point p) {
508     _widget->setCoordinateStatus(p);
511 /**
512  * \see sp_document_item_from_list_at_point_bottom()
513  */
514 SPItem *
515 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
517     g_return_val_if_fail (doc() != NULL, NULL);
518     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
521 /**
522  * \see sp_document_item_at_point()
523  */
524 SPItem *
525 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
527     g_return_val_if_fail (doc() != NULL, NULL);
528     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
531 /**
532  * \see sp_document_group_at_point()
533  */
534 SPItem *
535 SPDesktop::group_at_point (NR::Point const p) const
537     g_return_val_if_fail (doc() != NULL, NULL);
538     return sp_document_group_at_point (doc(), dkey, p);
541 /**
542  * \brief  Returns the mouse point in document coordinates; if mouse is
543  * outside the canvas, returns the center of canvas viewpoint
544  */
545 NR::Point
546 SPDesktop::point() const
548     NR::Point p = _widget->getPointer();
549     NR::Point pw = sp_canvas_window_to_world (canvas, p);
550     p = w2d(pw);
552     NR::Rect const r = canvas->getViewbox();
554     NR::Point r0 = w2d(r.min());
555     NR::Point r1 = w2d(r.max());
557     if (p[NR::X] >= r0[NR::X] &&
558         p[NR::X] <= r1[NR::X] &&
559         p[NR::Y] >= r1[NR::Y] &&
560         p[NR::Y] <= r0[NR::Y])
561     {
562         return p;
563     } else {
564         return (r0 + r1) / 2;
565     }
568 /**
569  * Put current zoom data in history list.
570  */
571 void
572 SPDesktop::push_current_zoom (GList **history)
574     NR::Rect const area = get_display_area();
576     NRRect *old_zoom = g_new(NRRect, 1);
577     old_zoom->x0 = area.min()[NR::X];
578     old_zoom->x1 = area.max()[NR::X];
579     old_zoom->y0 = area.min()[NR::Y];
580     old_zoom->y1 = area.max()[NR::Y];
581     if ( *history == NULL
582          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
583                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
584                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
585                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
586     {
587         *history = g_list_prepend (*history, old_zoom);
588     }
591 /**
592  * Set viewbox.
593  */
594 void
595 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
597     g_assert(_widget);
599     // save the zoom
600     if (log) {
601         push_current_zoom(&zooms_past);
602         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
603         g_list_free (zooms_future);
604         zooms_future = NULL;
605     }
607     double const cx = 0.5 * (x0 + x1);
608     double const cy = 0.5 * (y0 + y1);
610     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
612     double scale = expansion(_d2w);
613     double newscale;
614     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
615         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
616     } else {
617         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
618     }
620     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
622     int clear = FALSE;
623     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
624         /* Set zoom factors */
625         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
626         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
627         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
628         clear = TRUE;
629     }
631     /* Calculate top left corner */
632     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
633     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
635     /* Scroll */
636     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
638     _widget->updateRulers();
639     _widget->updateScrollbars(expansion(_d2w));
640     _widget->updateZoom();
643 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
645     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
648 /**
649  * Return viewbox dimensions.
650  */
651 NR::Rect SPDesktop::get_display_area() const
653     NR::Rect const viewbox = canvas->getViewbox();
655     double const scale = _d2w[0];
657     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
658                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
661 /**
662  * Revert back to previous zoom if possible.
663  */
664 void
665 SPDesktop::prev_zoom()
667     if (zooms_past == NULL) {
668         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
669         return;
670     }
672     // push current zoom into forward zooms list
673     push_current_zoom (&zooms_future);
675     // restore previous zoom
676     set_display_area (((NRRect *) zooms_past->data)->x0,
677             ((NRRect *) zooms_past->data)->y0,
678             ((NRRect *) zooms_past->data)->x1,
679             ((NRRect *) zooms_past->data)->y1,
680             0, false);
682     // remove the just-added zoom from the past zooms list
683     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
686 /**
687  * Set zoom to next in list.
688  */
689 void
690 SPDesktop::next_zoom()
692     if (zooms_future == NULL) {
693         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
694         return;
695     }
697     // push current zoom into past zooms list
698     push_current_zoom (&zooms_past);
700     // restore next zoom
701     set_display_area (((NRRect *) zooms_future->data)->x0,
702             ((NRRect *) zooms_future->data)->y0,
703             ((NRRect *) zooms_future->data)->x1,
704             ((NRRect *) zooms_future->data)->y1,
705             0, false);
707     // remove the just-used zoom from the zooms_future list
708     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
711 /**
712  * Zoom to point with absolute zoom factor.
713  */
714 void
715 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
717     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
719     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
720     // this check prevents "sliding" when trying to zoom in at maximum zoom;
721     /// \todo someone please fix calculations properly and remove this hack
722     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
723         return;
725     NR::Rect const viewbox = canvas->getViewbox();
727     double const width2 = viewbox.dimensions()[NR::X] / zoom;
728     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
730     set_display_area(cx - px * width2,
731                      cy - py * height2,
732                      cx + (1 - px) * width2,
733                      cy + (1 - py) * height2,
734                      0.0);
737 /**
738  * Zoom to center with absolute zoom factor.
739  */
740 void
741 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
743     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
746 /**
747  * Zoom to point with relative zoom factor.
748  */
749 void
750 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
752     NR::Rect const area = get_display_area();
754     if (cx < area.min()[NR::X]) {
755         cx = area.min()[NR::X];
756     }
757     if (cx > area.max()[NR::X]) {
758         cx = area.max()[NR::X];
759     }
760     if (cy < area.min()[NR::Y]) {
761         cy = area.min()[NR::Y];
762     }
763     if (cy > area.max()[NR::Y]) {
764         cy = area.max()[NR::Y];
765     }
767     gdouble const scale = expansion(_d2w) * zoom;
768     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
769     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
771     zoom_absolute_keep_point(cx, cy, px, py, scale);
774 /**
775  * Zoom to center with relative zoom factor.
776  */
777 void
778 SPDesktop::zoom_relative (double cx, double cy, double zoom)
780     gdouble scale = expansion(_d2w) * zoom;
781     zoom_absolute (cx, cy, scale);
784 /**
785  * Set display area to origin and current document dimensions.
786  */
787 void
788 SPDesktop::zoom_page()
790     NR::Rect d(NR::Point(0, 0),
791                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
793     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
794         return;
795     }
797     set_display_area(d, 10);
800 /**
801  * Set display area to current document width.
802  */
803 void
804 SPDesktop::zoom_page_width()
806     NR::Rect const a = get_display_area();
808     if (sp_document_width(doc()) < 1.0) {
809         return;
810     }
812     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
813                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
815     set_display_area(d, 10);
818 /**
819  * Zoom to selection.
820  */
821 void
822 SPDesktop::zoom_selection()
824     NR::Rect const d = selection->bounds();
826     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
827         return;
828     }
830     set_display_area(d, 10);
833 /**
834  * Tell widget to let zoom widget grab keyboard focus.
835  */
836 void
837 SPDesktop::zoom_grab_focus()
839     _widget->letZoomGrabFocus();
842 /**
843  * Zoom to whole drawing.
844  */
845 void
846 SPDesktop::zoom_drawing()
848     g_return_if_fail (doc() != NULL);
849     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
850     g_return_if_fail (docitem != NULL);
852     NR::Rect d = sp_item_bbox_desktop(docitem);
854     /* Note that the second condition here indicates that
855     ** there are no items in the drawing.
856     */
857     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
858         return;
859     }
861     set_display_area(d, 10);
864 /**
865  * Scroll canvas by specific coordinate amount.
866  */
867 void
868 SPDesktop::scroll_world (double dx, double dy)
870     g_assert(_widget);
872     NR::Rect const viewbox = canvas->getViewbox();
874     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
876     _widget->updateRulers();
877     _widget->updateScrollbars(expansion(_d2w));
880 bool
881 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
883     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
885     // autoscrolldistance is in screen pixels, but the display area is in document units
886     autoscrolldistance /= expansion(_d2w);
887     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
889     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
890         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
892         NR::Point const s_w( (*p) * _d2w );
894         gdouble x_to;
895         if ((*p)[NR::X] < dbox.min()[NR::X])
896             x_to = dbox.min()[NR::X];
897         else if ((*p)[NR::X] > dbox.max()[NR::X])
898             x_to = dbox.max()[NR::X];
899         else
900             x_to = (*p)[NR::X];
902         gdouble y_to;
903         if ((*p)[NR::Y] < dbox.min()[NR::Y])
904             y_to = dbox.min()[NR::Y];
905         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
906             y_to = dbox.max()[NR::Y];
907         else
908             y_to = (*p)[NR::Y];
910         NR::Point const d_dt(x_to, y_to);
911         NR::Point const d_w( d_dt * _d2w );
912         NR::Point const moved_w( d_w - s_w );
914         if (autoscrollspeed == 0)
915             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
917         if (autoscrollspeed != 0)
918             scroll_world (autoscrollspeed * moved_w);
920         return true;
921     }
922     return false;
925 void
926 SPDesktop::fullscreen()
928     _widget->setFullscreen();
931 void
932 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
934     _widget->getGeometry (x, y, w, h);
937 void
938 SPDesktop::setWindowPosition (NR::Point p)
940     _widget->setPosition (p);
943 void
944 SPDesktop::setWindowSize (gint w, gint h)
946     _widget->setSize (w, h);
949 void
950 SPDesktop::setWindowTransient (void *p, int transient_policy)
952     _widget->setTransient (p, transient_policy);
955 void
956 SPDesktop::presentWindow()
958     _widget->present();
961 bool
962 SPDesktop::warnDialog (gchar *text)
964     return _widget->warnDialog (text);
967 void
968 SPDesktop::toggleRulers()
970     _widget->toggleRulers();
973 void
974 SPDesktop::toggleScrollbars()
976     _widget->toggleScrollbars();
979 void
980 SPDesktop::layoutWidget()
982     _widget->layout();
985 void
986 SPDesktop::destroyWidget()
988     _widget->destroy();
991 bool
992 SPDesktop::shutdown()
994     return _widget->shutdown();
997 void
998 SPDesktop::setToolboxFocusTo (gchar const *label)
1000     _widget->setToolboxFocusTo (label);
1003 void
1004 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1006     _widget->setToolboxAdjustmentValue (id, val);
1009 bool
1010 SPDesktop::isToolboxButtonActive (gchar const *id)
1012     return _widget->isToolboxButtonActive (id);
1015 void
1016 SPDesktop::emitToolSubselectionChanged(gpointer data)
1018         _tool_subselection_changed.emit(data);
1019         inkscape_subselection_changed (this);
1022 //----------------------------------------------------------------------
1023 // Callback implementations. The virtual ones are connected by the view.
1025 void
1026 SPDesktop::onPositionSet (double x, double y)
1028     _widget->viewSetPosition (NR::Point(x,y));
1031 void
1032 SPDesktop::onResized (double x, double y)
1034    // Nothing called here
1037 /**
1038  * Redraw callback; queues Gtk redraw; connected by View.
1039  */
1040 void
1041 SPDesktop::onRedrawRequested ()
1043     if (main) {
1044         _widget->requestCanvasUpdate();
1045     }
1048 /**
1049  * Associate document with desktop.
1050  */
1051 void
1052 SPDesktop::setDocument (SPDocument *doc)
1054     if (this->doc() && doc) {
1055         namedview->hide(this);
1056         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1057     }
1059     if (_layer_hierarchy) {
1060         _layer_hierarchy->clear();
1061         delete _layer_hierarchy;
1062     }
1063     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1064     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1065     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1066     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1067     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1069     /// \todo fixme: This condition exists to make sure the code
1070     /// inside is called only once on initialization. But there
1071     /// are surely more safe methods to accomplish this.
1072     if (drawing) {
1073         NRArenaItem *ai;
1075         namedview = sp_document_namedview (doc, NULL);
1076         g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1077         number = namedview->getViewCount();
1079         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1080                 SP_CANVAS_ARENA (drawing)->arena,
1081                 dkey,
1082                 SP_ITEM_SHOW_DISPLAY);
1083         if (ai) {
1084             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1085             nr_arena_item_unref (ai);
1086         }
1087         namedview->show(this);
1088         /* Ugly hack */
1089         activate_guides (true);
1090         /* Ugly hack */
1091         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1092     }
1094     _document_replaced_signal.emit (this, doc);
1096     View::setDocument (doc);
1099 void
1100 SPDesktop::onStatusMessage
1101 (Inkscape::MessageType type, gchar const *message)
1103     if (_widget) {
1104         _widget->setMessage(type, message);
1105     }
1108 void
1109 SPDesktop::onDocumentURISet (gchar const* uri)
1111     _widget->setTitle(uri);
1114 /**
1115  * Resized callback.
1116  */
1117 void
1118 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1120     _doc2dt[5] = height;
1121     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1122     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1123     SP_CTRLRECT(page)->setRectangle(a);
1124     SP_CTRLRECT(page_border)->setRectangle(a);
1128 void
1129 SPDesktop::_onActivate (SPDesktop* dt)
1131     if (!dt->_widget) return;
1132     dt->_widget->activateDesktop();
1135 void
1136 SPDesktop::_onDeactivate (SPDesktop* dt)
1138     if (!dt->_widget) return;
1139     dt->_widget->deactivateDesktop();
1142 void
1143 SPDesktop::_onSelectionModified
1144 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1146     if (!dt->_widget) return;
1147     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1150 static void
1151 _onSelectionChanged
1152 (Inkscape::Selection *selection, SPDesktop *desktop)
1154     /** \todo
1155      * only change the layer for single selections, or what?
1156      * This seems reasonable -- for multiple selections there can be many
1157      * different layers involved.
1158      */
1159     SPItem *item=selection->singleItem();
1160     if (item) {
1161         SPObject *layer=desktop->layerForObject(item);
1162         if ( layer && layer != desktop->currentLayer() ) {
1163             desktop->setCurrentLayer(layer);
1164         }
1165     }
1168 /**
1169  * Calls event handler of current event context.
1170  * \param arena Unused
1171  * \todo fixme
1172  */
1173 static gint
1174 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1176     if (ai) {
1177         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1178         return sp_event_context_item_handler (desktop->event_context, spi, event);
1179     } else {
1180         return sp_event_context_root_handler (desktop->event_context, event);
1181     }
1184 static void
1185 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1186     g_return_if_fail(SP_IS_GROUP(layer));
1187     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1190 /// Callback
1191 static void
1192 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1193     g_return_if_fail(SP_IS_GROUP(layer));
1194     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1197 /// Callback
1198 static void
1199 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1200                                          SPDesktop *desktop)
1202     desktop->_layer_changed_signal.emit (bottom);
1205 /// Called when document is starting to be rebuilt.
1206 static void
1207 _reconstruction_start (SPDesktop * desktop)
1209     // printf("Desktop, starting reconstruction\n");
1210     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1211     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1213     /*
1214     GSList const * selection_objs = desktop->selection->list();
1215     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1217     }
1218     */
1219     desktop->selection->clear();
1221     // printf("Desktop, starting reconstruction end\n");
1224 /// Called when document rebuild is finished.
1225 static void
1226 _reconstruction_finish (SPDesktop * desktop)
1228     // printf("Desktop, finishing reconstruction\n");
1229     if (desktop->_reconstruction_old_layer_id == NULL)
1230         return;
1232     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1233     if (newLayer != NULL)
1234         desktop->setCurrentLayer(newLayer);
1236     g_free(desktop->_reconstruction_old_layer_id);
1237     desktop->_reconstruction_old_layer_id = NULL;
1238     // printf("Desktop, finishing reconstruction end\n");
1239     return;
1242 /**
1243  * Namedview_modified callback.
1244  */
1245 static void
1246 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1248     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1250         /* Recalculate snap distances */
1251         /* FIXME: why is the desktop getting involved in setting up something
1252         ** that is entirely to do with the namedview?
1253         */
1254         _update_snap_distances (desktop);
1256         /* Show/hide page background */
1257         if (nv->pagecolor & 0xff) {
1258             sp_canvas_item_show (desktop->table);
1259             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1260             sp_canvas_item_move_to_z (desktop->table, 0);
1261         } else {
1262             sp_canvas_item_hide (desktop->table);
1263         }
1265         /* Show/hide page border */
1266         if (nv->showborder) {
1267             // show
1268             sp_canvas_item_show (desktop->page_border);
1269             // set color and shadow
1270             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1271             if (nv->pageshadow) {
1272                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1273             }
1274             // place in the z-order stack
1275             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1276                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1277             } else {
1278                 int order = sp_canvas_item_order (desktop->page_border);
1279                 int morder = sp_canvas_item_order (desktop->drawing);
1280                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1281                                     morder - order);
1282             }
1283         } else {
1284                 sp_canvas_item_hide (desktop->page_border);
1285                 if (nv->pageshadow) {
1286                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1287                 }
1288         }
1289         
1290         /* Show/hide page shadow */
1291         if (nv->showpageshadow && nv->pageshadow) {
1292             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1293         } else {
1294             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1295         }
1297         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1298             (SP_RGBA32_R_U(nv->pagecolor) +
1299              SP_RGBA32_G_U(nv->pagecolor) +
1300              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1301             // the background color is light or transparent, use black outline
1302             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1303         } else { // use white outline
1304             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1305         }
1306     }
1309 /**
1310  * Callback to reset snapper's distances.
1311  */
1312 static void
1313 _update_snap_distances (SPDesktop *desktop)
1315     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1317     SPNamedView &nv = *desktop->namedview;
1319     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1320                                                                       *nv.gridtoleranceunit,
1321                                                                       px));
1322     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1323                                                                        *nv.guidetoleranceunit,
1324                                                                        px));
1325     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1326                                                                         *nv.objecttoleranceunit,
1327                                                                         px));
1331 NR::Matrix SPDesktop::w2d() const
1333     return _w2d;
1336 NR::Point SPDesktop::w2d(NR::Point const &p) const
1338     return p * _w2d;
1341 NR::Point SPDesktop::d2w(NR::Point const &p) const
1343     return p * _d2w;
1346 NR::Matrix SPDesktop::doc2dt() const
1348     return _doc2dt;
1351 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1353     return p * _doc2dt;
1356 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1358     return p / _doc2dt;
1362 /**
1363  * Pop event context from desktop's context stack. Never used.
1364  */
1365 // void
1366 // SPDesktop::pop_event_context (unsigned int key)
1367 // {
1368 //    SPEventContext *ec = NULL;
1369 //
1370 //    if (event_context && event_context->key == key) {
1371 //        g_return_if_fail (event_context);
1372 //        g_return_if_fail (event_context->next);
1373 //        ec = event_context;
1374 //        sp_event_context_deactivate (ec);
1375 //        event_context = ec->next;
1376 //        sp_event_context_activate (event_context);
1377 //        _event_context_changed_signal.emit (this, ec);
1378 //    }
1379 //
1380 //    SPEventContext *ref = event_context;
1381 //    while (ref && ref->next && ref->next->key != key)
1382 //        ref = ref->next;
1383 //
1384 //    if (ref && ref->next) {
1385 //        ec = ref->next;
1386 //        ref->next = ec->next;
1387 //    }
1388 //
1389 //    if (ec) {
1390 //        sp_event_context_finish (ec);
1391 //        g_object_unref (G_OBJECT (ec));
1392 //    }
1393 // }
1395 /*
1396   Local Variables:
1397   mode:c++
1398   c-file-style:"stroustrup"
1399   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1400   indent-tabs-mode:nil
1401   fill-column:99
1402   End:
1403 */
1404 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :