Code

add interface for disabling interaction during long-running operations
[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  *
13  * Copyright (C) 2006 John Bintz
14  * Copyright (C) 2004 MenTaLguY
15  * Copyright (C) 1999-2002 Lauris Kaplinski
16  * Copyright (C) 2000-2001 Ximian, Inc.
17  *
18  * Released under GNU GPL, read the file 'COPYING' for more information
19  */
21 /** \class SPDesktop
22  * SPDesktop is a subclass of View, implementing an editable document
23  * canvas.  It is extensively used by many UI controls that need certain
24  * visual representations of their own.
25  *
26  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
27  * layers of different control objects. The one containing the whole
28  * document is the drawing layer. In addition to it, there are grid,
29  * guide, sketch and control layers. The sketch layer is used for
30  * temporary drawing objects, before the real objects in document are
31  * created. The control layer contains editing knots, rubberband and
32  * similar non-document UI objects.
33  *
34  * Each SPDesktop is associated with a SPNamedView node of the document
35  * tree.  Currently, all desktops are created from a single main named
36  * view, but in the future there may be support for different ones.
37  * SPNamedView serves as an in-document container for desktop-related
38  * data, like grid and guideline placement, snapping options and so on.
39  *
40  * Associated with each SPDesktop are the two most important editing
41  * related objects - SPSelection and SPEventContext.
42  *
43  * Sodipodi keeps track of the active desktop and invokes notification
44  * signals whenever it changes. UI elements can use these to update their
45  * display to the selection of the currently active editing window.
46  * (Lauris Kaplinski)
47  */
49 #ifdef HAVE_CONFIG_H
50 # include "config.h"
51 #endif
53 #include <glibmm/i18n.h>
54 #include <sigc++/functors/mem_fun.h>
56 #include "macros.h"
57 #include "inkscape-private.h"
58 #include "desktop.h"
59 #include "desktop-events.h"
60 #include "desktop-handles.h"
61 #include "document.h"
62 #include "message-stack.h"
63 #include "selection.h"
64 #include "select-context.h"
65 #include "sp-namedview.h"
66 #include "color.h"
67 #include "sp-item-group.h"
68 #include "prefs-utils.h"
69 #include "object-hierarchy.h"
70 #include "helper/units.h"
71 #include "display/canvas-arena.h"
72 #include "display/nr-arena.h"
73 #include "display/gnome-canvas-acetate.h"
74 #include "display/sodipodi-ctrlrect.h"
75 #include "display/sp-canvas-util.h"
76 #include "libnr/nr-matrix-div.h"
77 #include "libnr/nr-rect-ops.h"
78 #include "ui/dialog/dialog-manager.h"
79 #include "xml/repr.h"
80 #include "message-context.h"
81 #include "layer-manager.h"
83 namespace Inkscape { namespace XML { class Node; }}
85 // Callback declarations
86 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
87 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
88 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
89 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
90 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
91 static void _reconstruction_start(SPDesktop * desktop);
92 static void _reconstruction_finish(SPDesktop * desktop);
93 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
94 static void _update_snap_distances (SPDesktop *desktop);
96 /**
97  * Return new desktop object.
98  * \pre namedview != NULL.
99  * \pre canvas != NULL.
100  */
101 SPDesktop::SPDesktop()
103     _dlg_mgr = NULL;
104     _widget = 0;
105     namedview = NULL;
106     selection = NULL;
107     acetate = NULL;
108     main = NULL;
109     grid = NULL;
110     guides = NULL;
111     drawing = NULL;
112     sketch = NULL;
113     controls = NULL;
114     event_context = 0;
115     layer_manager = 0;
117     _d2w.set_identity();
118     _w2d.set_identity();
119     _doc2dt = NR::Matrix(NR::scale(1, -1));
121     guides_active = false;
123     zooms_past = NULL;
124     zooms_future = NULL;
126     is_fullscreen = false;
128     gr_item = NULL;
129     gr_point_num = 0;
130     gr_fill_or_stroke = true;
132     _layer_hierarchy = NULL;
133     _active = false;
135     selection = Inkscape::GC::release (new Inkscape::Selection (this));
138 void
139 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
142     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
144     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
146     namedview = nv;
147     canvas = aCanvas;
149     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
150     /* Kill flicker */
151     sp_document_ensure_up_to_date (document);
153     /* Setup Dialog Manager */
154     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
156     dkey = sp_item_display_key_new (1);
158     /* Connect document */
159     setDocument (document);
161     number = namedview->getViewCount();
164     /* Setup Canvas */
165     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
167     SPCanvasGroup *root = sp_canvas_root (canvas);
169     /* Setup adminstrative layers */
170     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
171     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
172     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
173     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
175     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
176     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
177     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
178     sp_canvas_item_move_to_z (table, 0);
180     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
181     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
182     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
184     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
185     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
187     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
189     // Start always in normal mode
190     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
191     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
193     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
194     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
198     /* Push select tool to the bottom of stack */
199     /** \todo
200      * FIXME: this is the only call to this.  Everything else seems to just
201      * call "set" instead of "push".  Can we assume that there is only one
202      * context ever?
203      */
204     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
206     // display rect and zoom are now handled in sp_desktop_widget_realize()
208     NR::Rect const d(NR::Point(0.0, 0.0),
209                      NR::Point(sp_document_width(document), sp_document_height(document)));
211     SP_CTRLRECT(page)->setRectangle(d);
212     SP_CTRLRECT(page_border)->setRectangle(d);
214     /* the following sets the page shadow on the canvas
215        It was originally set to 5, which is really cheesy!
216        It now is an attribute in the document's namedview. If a value of
217        0 is used, then the constructor for a shadow is not initialized.
218     */
220     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
221         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
222     }
225     /* Connect event for page resize */
226     _doc2dt[5] = sp_document_height (document);
227     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
229     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
231     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
232             SP_CANVAS_ARENA (drawing)->arena,
233             dkey,
234             SP_ITEM_SHOW_DISPLAY);
235     if (ai) {
236         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
237         nr_arena_item_unref (ai);
238     }
240     namedview->show(this);
241     /* Ugly hack */
242     activate_guides (true);
243     /* Ugly hack */
244     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
246 /* Set up notification of rebuilding the document, this allows
247        for saving object related settings in the document. */
248     _reconstruction_start_connection =
249         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
250     _reconstruction_finish_connection =
251         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
252     _reconstruction_old_layer_id = NULL;
253     
254     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
255     
256     // ?
257     // sp_active_desktop_set (desktop);
258     _inkscape = INKSCAPE;
260     _activate_connection = _activate_signal.connect(
261         sigc::bind(
262             sigc::ptr_fun(_onActivate),
263             this
264         )
265     );
266      _deactivate_connection = _deactivate_signal.connect(
267         sigc::bind(
268             sigc::ptr_fun(_onDeactivate),
269             this
270         )
271     );
273     _sel_modified_connection = selection->connectModified(
274         sigc::bind(
275             sigc::ptr_fun(&_onSelectionModified),
276             this
277         )
278     );
279     _sel_changed_connection = selection->connectChanged(
280         sigc::bind(
281             sigc::ptr_fun(&_onSelectionChanged),
282             this
283         )
284     );
287     /* setup LayerManager */
288     //   (Setting up after the connections are all in place, as it may use some of them)
289     layer_manager = new Inkscape::LayerManager( this );
293 void SPDesktop::destroy()
295     _activate_connection.disconnect();
296     _deactivate_connection.disconnect();
297     _sel_modified_connection.disconnect();
298     _sel_changed_connection.disconnect();
299     _modified_connection.disconnect();
301     while (event_context) {
302         SPEventContext *ec = event_context;
303         event_context = ec->next;
304         sp_event_context_finish (ec);
305         g_object_unref (G_OBJECT (ec));
306     }
308     if (_layer_hierarchy) {
309         delete _layer_hierarchy;
310     }
312     if (_inkscape) {
313         _inkscape = NULL;
314     }
316     if (drawing) {
317         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
318         drawing = NULL;
319     }
321     delete _guides_message_context;
322     _guides_message_context = NULL;
324     _modified_connection.disconnect();
326     g_list_free (zooms_past);
327     g_list_free (zooms_future);
330 SPDesktop::~SPDesktop() {}
332 //--------------------------------------------------------------------
333 /* Public methods */
335 void SPDesktop::setDisplayModeNormal()
337     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
338     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
339     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
342 void SPDesktop::setDisplayModeOutline()
344     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
345     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
346     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
349 /**
350  * Returns current root (=bottom) layer.
351  */
352 SPObject *SPDesktop::currentRoot() const
354     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
357 /**
358  * Returns current top layer.
359  */
360 SPObject *SPDesktop::currentLayer() const
362     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
365 /**
366  * Sets the current layer of the desktop.
367  * 
368  * Make \a object the top layer.
369  */
370 void SPDesktop::setCurrentLayer(SPObject *object) {
371     g_return_if_fail(SP_IS_GROUP(object));
372     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
373     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
374     _layer_hierarchy->setBottom(object);
377 /**
378  * Return layer that contains \a object.
379  */
380 SPObject *SPDesktop::layerForObject(SPObject *object) {
381     g_return_val_if_fail(object != NULL, NULL);
383     SPObject *root=currentRoot();
384     object = SP_OBJECT_PARENT(object);
385     while ( object && object != root && !isLayer(object) ) {
386         object = SP_OBJECT_PARENT(object);
387     }
388     return object;
391 /**
392  * True if object is a layer.
393  */
394 bool SPDesktop::isLayer(SPObject *object) const {
395     return ( SP_IS_GROUP(object)
396              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
397                   == SPGroup::LAYER ) );
400 /**
401  * True if desktop viewport fully contains \a item's bbox.
402  */
403 bool SPDesktop::isWithinViewport (SPItem *item) const
405     NR::Rect const viewport = get_display_area();
406     NR::Rect const bbox = sp_item_bbox_desktop(item);
407     return viewport.contains(bbox);
410 ///
411 bool SPDesktop::itemIsHidden(SPItem const *item) const {
412     return item->isHidden(this->dkey);
415 /**
416  * Set activate property of desktop; emit signal if changed.
417  */
418 void
419 SPDesktop::set_active (bool new_active)
421     if (new_active != _active) {
422         _active = new_active;
423         if (new_active) {
424             _activate_signal.emit();
425         } else {
426             _deactivate_signal.emit();
427         }
428     }
431 /**
432  * Set activate status of current desktop's named view.
433  */
434 void
435 SPDesktop::activate_guides(bool activate)
437     guides_active = activate;
438     namedview->activateGuides(this, activate);
441 /**
442  * Make desktop switch documents.
443  */
444 void
445 SPDesktop::change_document (SPDocument *theDocument)
447     g_return_if_fail (theDocument != NULL);
449     /* unselect everything before switching documents */
450     selection->clear();
452     setDocument (theDocument);
453     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
454     _document_replaced_signal.emit (this, theDocument);
457 /**
458  * Make desktop switch event contexts.
459  */
460 void
461 SPDesktop::set_event_context (GtkType type, const gchar *config)
463     SPEventContext *ec;
464     while (event_context) {
465         ec = event_context;
466         sp_event_context_deactivate (ec);
467         event_context = ec->next;
468         sp_event_context_finish (ec);
469         g_object_unref (G_OBJECT (ec));
470     }
472     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
473     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
474     ec->next = event_context;
475     event_context = ec;
476     sp_event_context_activate (ec);
477     _event_context_changed_signal.emit (this, ec);
480 /**
481  * Push event context onto desktop's context stack.
482  */
483 void
484 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
486     SPEventContext *ref, *ec;
487     Inkscape::XML::Node *repr;
489     if (event_context && event_context->key == key) return;
490     ref = event_context;
491     while (ref && ref->next && ref->next->key != key) ref = ref->next;
492     if (ref && ref->next) {
493         ec = ref->next;
494         ref->next = ec->next;
495         sp_event_context_finish (ec);
496         g_object_unref (G_OBJECT (ec));
497     }
499     if (event_context) sp_event_context_deactivate (event_context);
500     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
501     ec = sp_event_context_new (type, this, repr, key);
502     ec->next = event_context;
503     event_context = ec;
504     sp_event_context_activate (ec);
505     _event_context_changed_signal.emit (this, ec);
508 /**
509  * Sets the coordinate status to a given point
510  */
511 void
512 SPDesktop::set_coordinate_status (NR::Point p) {
513     _widget->setCoordinateStatus(p);
516 /**
517  * \see sp_document_item_from_list_at_point_bottom()
518  */
519 SPItem *
520 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
522     g_return_val_if_fail (doc() != NULL, NULL);
523     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
526 /**
527  * \see sp_document_item_at_point()
528  */
529 SPItem *
530 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
532     g_return_val_if_fail (doc() != NULL, NULL);
533     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
536 /**
537  * \see sp_document_group_at_point()
538  */
539 SPItem *
540 SPDesktop::group_at_point (NR::Point const p) const
542     g_return_val_if_fail (doc() != NULL, NULL);
543     return sp_document_group_at_point (doc(), dkey, p);
546 /**
547  * \brief  Returns the mouse point in document coordinates; if mouse is
548  * outside the canvas, returns the center of canvas viewpoint
549  */
550 NR::Point
551 SPDesktop::point() const
553     NR::Point p = _widget->getPointer();
554     NR::Point pw = sp_canvas_window_to_world (canvas, p);
555     p = w2d(pw);
557     NR::Rect const r = canvas->getViewbox();
559     NR::Point r0 = w2d(r.min());
560     NR::Point r1 = w2d(r.max());
562     if (p[NR::X] >= r0[NR::X] &&
563         p[NR::X] <= r1[NR::X] &&
564         p[NR::Y] >= r1[NR::Y] &&
565         p[NR::Y] <= r0[NR::Y])
566     {
567         return p;
568     } else {
569         return (r0 + r1) / 2;
570     }
573 /**
574  * Put current zoom data in history list.
575  */
576 void
577 SPDesktop::push_current_zoom (GList **history)
579     NR::Rect const area = get_display_area();
581     NRRect *old_zoom = g_new(NRRect, 1);
582     old_zoom->x0 = area.min()[NR::X];
583     old_zoom->x1 = area.max()[NR::X];
584     old_zoom->y0 = area.min()[NR::Y];
585     old_zoom->y1 = area.max()[NR::Y];
586     if ( *history == NULL
587          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
588                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
589                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
590                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
591     {
592         *history = g_list_prepend (*history, old_zoom);
593     }
596 /**
597  * Set viewbox.
598  */
599 void
600 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
602     g_assert(_widget);
604     // save the zoom
605     if (log) {
606         push_current_zoom(&zooms_past);
607         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
608         g_list_free (zooms_future);
609         zooms_future = NULL;
610     }
612     double const cx = 0.5 * (x0 + x1);
613     double const cy = 0.5 * (y0 + y1);
615     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
617     double scale = expansion(_d2w);
618     double newscale;
619     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
620         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
621     } else {
622         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
623     }
625     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
627     int clear = FALSE;
628     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
629         /* Set zoom factors */
630         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
631         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
632         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
633         clear = TRUE;
634     }
636     /* Calculate top left corner */
637     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
638     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
640     /* Scroll */
641     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
643     _widget->updateRulers();
644     _widget->updateScrollbars(expansion(_d2w));
645     _widget->updateZoom();
648 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
650     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
653 /**
654  * Return viewbox dimensions.
655  */
656 NR::Rect SPDesktop::get_display_area() const
658     NR::Rect const viewbox = canvas->getViewbox();
660     double const scale = _d2w[0];
662     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
663                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
666 /**
667  * Revert back to previous zoom if possible.
668  */
669 void
670 SPDesktop::prev_zoom()
672     if (zooms_past == NULL) {
673         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
674         return;
675     }
677     // push current zoom into forward zooms list
678     push_current_zoom (&zooms_future);
680     // restore previous zoom
681     set_display_area (((NRRect *) zooms_past->data)->x0,
682             ((NRRect *) zooms_past->data)->y0,
683             ((NRRect *) zooms_past->data)->x1,
684             ((NRRect *) zooms_past->data)->y1,
685             0, false);
687     // remove the just-added zoom from the past zooms list
688     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
691 /**
692  * Set zoom to next in list.
693  */
694 void
695 SPDesktop::next_zoom()
697     if (zooms_future == NULL) {
698         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
699         return;
700     }
702     // push current zoom into past zooms list
703     push_current_zoom (&zooms_past);
705     // restore next zoom
706     set_display_area (((NRRect *) zooms_future->data)->x0,
707             ((NRRect *) zooms_future->data)->y0,
708             ((NRRect *) zooms_future->data)->x1,
709             ((NRRect *) zooms_future->data)->y1,
710             0, false);
712     // remove the just-used zoom from the zooms_future list
713     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
716 /**
717  * Zoom to point with absolute zoom factor.
718  */
719 void
720 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
722     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
724     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
725     // this check prevents "sliding" when trying to zoom in at maximum zoom;
726     /// \todo someone please fix calculations properly and remove this hack
727     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
728         return;
730     NR::Rect const viewbox = canvas->getViewbox();
732     double const width2 = viewbox.dimensions()[NR::X] / zoom;
733     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
735     set_display_area(cx - px * width2,
736                      cy - py * height2,
737                      cx + (1 - px) * width2,
738                      cy + (1 - py) * height2,
739                      0.0);
742 /**
743  * Zoom to center with absolute zoom factor.
744  */
745 void
746 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
748     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
751 /**
752  * Zoom to point with relative zoom factor.
753  */
754 void
755 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
757     NR::Rect const area = get_display_area();
759     if (cx < area.min()[NR::X]) {
760         cx = area.min()[NR::X];
761     }
762     if (cx > area.max()[NR::X]) {
763         cx = area.max()[NR::X];
764     }
765     if (cy < area.min()[NR::Y]) {
766         cy = area.min()[NR::Y];
767     }
768     if (cy > area.max()[NR::Y]) {
769         cy = area.max()[NR::Y];
770     }
772     gdouble const scale = expansion(_d2w) * zoom;
773     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
774     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
776     zoom_absolute_keep_point(cx, cy, px, py, scale);
779 /**
780  * Zoom to center with relative zoom factor.
781  */
782 void
783 SPDesktop::zoom_relative (double cx, double cy, double zoom)
785     gdouble scale = expansion(_d2w) * zoom;
786     zoom_absolute (cx, cy, scale);
789 /**
790  * Set display area to origin and current document dimensions.
791  */
792 void
793 SPDesktop::zoom_page()
795     NR::Rect d(NR::Point(0, 0),
796                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
798     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
799         return;
800     }
802     set_display_area(d, 10);
805 /**
806  * Set display area to current document width.
807  */
808 void
809 SPDesktop::zoom_page_width()
811     NR::Rect const a = get_display_area();
813     if (sp_document_width(doc()) < 1.0) {
814         return;
815     }
817     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
818                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
820     set_display_area(d, 10);
823 /**
824  * Zoom to selection.
825  */
826 void
827 SPDesktop::zoom_selection()
829     NR::Rect const d = selection->bounds();
831     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
832         return;
833     }
835     set_display_area(d, 10);
838 /**
839  * Tell widget to let zoom widget grab keyboard focus.
840  */
841 void
842 SPDesktop::zoom_grab_focus()
844     _widget->letZoomGrabFocus();
847 /**
848  * Zoom to whole drawing.
849  */
850 void
851 SPDesktop::zoom_drawing()
853     g_return_if_fail (doc() != NULL);
854     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
855     g_return_if_fail (docitem != NULL);
857     NR::Rect d = sp_item_bbox_desktop(docitem);
859     /* Note that the second condition here indicates that
860     ** there are no items in the drawing.
861     */
862     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
863         return;
864     }
866     set_display_area(d, 10);
869 /**
870  * Scroll canvas by specific coordinate amount.
871  */
872 void
873 SPDesktop::scroll_world (double dx, double dy)
875     g_assert(_widget);
877     NR::Rect const viewbox = canvas->getViewbox();
879     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
881     _widget->updateRulers();
882     _widget->updateScrollbars(expansion(_d2w));
885 bool
886 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
888     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
890     // autoscrolldistance is in screen pixels, but the display area is in document units
891     autoscrolldistance /= expansion(_d2w);
892     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
894     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
895         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
897         NR::Point const s_w( (*p) * _d2w );
899         gdouble x_to;
900         if ((*p)[NR::X] < dbox.min()[NR::X])
901             x_to = dbox.min()[NR::X];
902         else if ((*p)[NR::X] > dbox.max()[NR::X])
903             x_to = dbox.max()[NR::X];
904         else
905             x_to = (*p)[NR::X];
907         gdouble y_to;
908         if ((*p)[NR::Y] < dbox.min()[NR::Y])
909             y_to = dbox.min()[NR::Y];
910         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
911             y_to = dbox.max()[NR::Y];
912         else
913             y_to = (*p)[NR::Y];
915         NR::Point const d_dt(x_to, y_to);
916         NR::Point const d_w( d_dt * _d2w );
917         NR::Point const moved_w( d_w - s_w );
919         if (autoscrollspeed == 0)
920             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
922         if (autoscrollspeed != 0)
923             scroll_world (autoscrollspeed * moved_w);
925         return true;
926     }
927     return false;
930 void
931 SPDesktop::fullscreen()
933     _widget->setFullscreen();
936 void
937 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
939     _widget->getGeometry (x, y, w, h);
942 void
943 SPDesktop::setWindowPosition (NR::Point p)
945     _widget->setPosition (p);
948 void
949 SPDesktop::setWindowSize (gint w, gint h)
951     _widget->setSize (w, h);
954 void
955 SPDesktop::setWindowTransient (void *p, int transient_policy)
957     _widget->setTransient (p, transient_policy);
960 void
961 SPDesktop::presentWindow()
963     _widget->present();
966 bool
967 SPDesktop::warnDialog (gchar *text)
969     return _widget->warnDialog (text);
972 void
973 SPDesktop::toggleRulers()
975     _widget->toggleRulers();
978 void
979 SPDesktop::toggleScrollbars()
981     _widget->toggleScrollbars();
984 void
985 SPDesktop::layoutWidget()
987     _widget->layout();
990 void
991 SPDesktop::destroyWidget()
993     _widget->destroy();
996 bool
997 SPDesktop::shutdown()
999     return _widget->shutdown();
1002 void
1003 SPDesktop::setToolboxFocusTo (gchar const *label)
1005     _widget->setToolboxFocusTo (label);
1008 void
1009 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1011     _widget->setToolboxAdjustmentValue (id, val);
1014 bool
1015 SPDesktop::isToolboxButtonActive (gchar const *id)
1017     return _widget->isToolboxButtonActive (id);
1020 void
1021 SPDesktop::emitToolSubselectionChanged(gpointer data)
1023         _tool_subselection_changed.emit(data);
1024         inkscape_subselection_changed (this);
1027 void
1028 SPDesktop::updateNow()
1030   sp_canvas_update_now(canvas);
1033 void
1034 SPDesktop::enableInteraction()
1036   _widget->enableInteraction();
1039 void SPDesktop::disableInteraction()
1041   _widget->disableInteraction();
1044 //----------------------------------------------------------------------
1045 // Callback implementations. The virtual ones are connected by the view.
1047 void
1048 SPDesktop::onPositionSet (double x, double y)
1050     _widget->viewSetPosition (NR::Point(x,y));
1053 void
1054 SPDesktop::onResized (double x, double y)
1056    // Nothing called here
1059 /**
1060  * Redraw callback; queues Gtk redraw; connected by View.
1061  */
1062 void
1063 SPDesktop::onRedrawRequested ()
1065     if (main) {
1066         _widget->requestCanvasUpdate();
1067     }
1070 void
1071 SPDesktop::updateCanvasNow()
1073   _widget->requestCanvasUpdateAndWait();
1076 /**
1077  * Associate document with desktop.
1078  */
1079 /// \todo fixme: refactor SPDesktop::init to use setDocument
1080 void
1081 SPDesktop::setDocument (SPDocument *doc)
1083     if (this->doc() && doc) {
1084         namedview->hide(this);
1085         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1086     }
1088     if (_layer_hierarchy) {
1089         _layer_hierarchy->clear();
1090         delete _layer_hierarchy;
1091     }
1092     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1093     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1094     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1095     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1096     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1098     _commit_connection.disconnect();
1099     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1101     /// \todo fixme: This condition exists to make sure the code
1102     /// inside is called only once on initialization. But there
1103     /// are surely more safe methods to accomplish this.
1104     if (drawing) {
1105         NRArenaItem *ai;
1107         namedview = sp_document_namedview (doc, NULL);
1108         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1109         number = namedview->getViewCount();
1111         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1112                 SP_CANVAS_ARENA (drawing)->arena,
1113                 dkey,
1114                 SP_ITEM_SHOW_DISPLAY);
1115         if (ai) {
1116             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1117             nr_arena_item_unref (ai);
1118         }
1119         namedview->show(this);
1120         /* Ugly hack */
1121         activate_guides (true);
1122         /* Ugly hack */
1123         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1124     }
1126     _document_replaced_signal.emit (this, doc);
1128     View::setDocument (doc);
1131 void
1132 SPDesktop::onStatusMessage
1133 (Inkscape::MessageType type, gchar const *message)
1135     if (_widget) {
1136         _widget->setMessage(type, message);
1137     }
1140 void
1141 SPDesktop::onDocumentURISet (gchar const* uri)
1143     _widget->setTitle(uri);
1146 /**
1147  * Resized callback.
1148  */
1149 void
1150 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1152     _doc2dt[5] = height;
1153     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1154     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1155     SP_CTRLRECT(page)->setRectangle(a);
1156     SP_CTRLRECT(page_border)->setRectangle(a);
1160 void
1161 SPDesktop::_onActivate (SPDesktop* dt)
1163     if (!dt->_widget) return;
1164     dt->_widget->activateDesktop();
1167 void
1168 SPDesktop::_onDeactivate (SPDesktop* dt)
1170     if (!dt->_widget) return;
1171     dt->_widget->deactivateDesktop();
1174 void
1175 SPDesktop::_onSelectionModified
1176 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1178     if (!dt->_widget) return;
1179     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1182 static void
1183 _onSelectionChanged
1184 (Inkscape::Selection *selection, SPDesktop *desktop)
1186     /** \todo
1187      * only change the layer for single selections, or what?
1188      * This seems reasonable -- for multiple selections there can be many
1189      * different layers involved.
1190      */
1191     SPItem *item=selection->singleItem();
1192     if (item) {
1193         SPObject *layer=desktop->layerForObject(item);
1194         if ( layer && layer != desktop->currentLayer() ) {
1195             desktop->setCurrentLayer(layer);
1196         }
1197     }
1200 /**
1201  * Calls event handler of current event context.
1202  * \param arena Unused
1203  * \todo fixme
1204  */
1205 static gint
1206 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1208     if (ai) {
1209         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1210         return sp_event_context_item_handler (desktop->event_context, spi, event);
1211     } else {
1212         return sp_event_context_root_handler (desktop->event_context, event);
1213     }
1216 static void
1217 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1218     g_return_if_fail(SP_IS_GROUP(layer));
1219     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1222 /// Callback
1223 static void
1224 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1225     g_return_if_fail(SP_IS_GROUP(layer));
1226     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1229 /// Callback
1230 static void
1231 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1232                                          SPDesktop *desktop)
1234     desktop->_layer_changed_signal.emit (bottom);
1237 /// Called when document is starting to be rebuilt.
1238 static void
1239 _reconstruction_start (SPDesktop * desktop)
1241     // printf("Desktop, starting reconstruction\n");
1242     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1243     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1245     /*
1246     GSList const * selection_objs = desktop->selection->list();
1247     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1249     }
1250     */
1251     desktop->selection->clear();
1253     // printf("Desktop, starting reconstruction end\n");
1256 /// Called when document rebuild is finished.
1257 static void
1258 _reconstruction_finish (SPDesktop * desktop)
1260     // printf("Desktop, finishing reconstruction\n");
1261     if (desktop->_reconstruction_old_layer_id == NULL)
1262         return;
1264     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1265     if (newLayer != NULL)
1266         desktop->setCurrentLayer(newLayer);
1268     g_free(desktop->_reconstruction_old_layer_id);
1269     desktop->_reconstruction_old_layer_id = NULL;
1270     // printf("Desktop, finishing reconstruction end\n");
1271     return;
1274 /**
1275  * Namedview_modified callback.
1276  */
1277 static void
1278 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1280     SPNamedView *nv=SP_NAMEDVIEW(obj);
1282     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1284         /* Recalculate snap distances */
1285         /* FIXME: why is the desktop getting involved in setting up something
1286         ** that is entirely to do with the namedview?
1287         */
1288         _update_snap_distances (desktop);
1290         /* Show/hide page background */
1291         if (nv->pagecolor & 0xff) {
1292             sp_canvas_item_show (desktop->table);
1293             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1294             sp_canvas_item_move_to_z (desktop->table, 0);
1295         } else {
1296             sp_canvas_item_hide (desktop->table);
1297         }
1299         /* Show/hide page border */
1300         if (nv->showborder) {
1301             // show
1302             sp_canvas_item_show (desktop->page_border);
1303             // set color and shadow
1304             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1305             if (nv->pageshadow) {
1306                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1307             }
1308             // place in the z-order stack
1309             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1310                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1311             } else {
1312                 int order = sp_canvas_item_order (desktop->page_border);
1313                 int morder = sp_canvas_item_order (desktop->drawing);
1314                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1315                                     morder - order);
1316             }
1317         } else {
1318                 sp_canvas_item_hide (desktop->page_border);
1319                 if (nv->pageshadow) {
1320                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1321                 }
1322         }
1323         
1324         /* Show/hide page shadow */
1325         if (nv->showpageshadow && nv->pageshadow) {
1326             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1327         } else {
1328             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1329         }
1331         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1332             (SP_RGBA32_R_U(nv->pagecolor) +
1333              SP_RGBA32_G_U(nv->pagecolor) +
1334              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1335             // the background color is light or transparent, use black outline
1336             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1337         } else { // use white outline
1338             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1339         }
1340     }
1343 /**
1344  * Callback to reset snapper's distances.
1345  */
1346 static void
1347 _update_snap_distances (SPDesktop *desktop)
1349     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1351     SPNamedView &nv = *desktop->namedview;
1353     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1354                                                                       *nv.gridtoleranceunit,
1355                                                                       px));
1356     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1357                                                                        *nv.guidetoleranceunit,
1358                                                                        px));
1359     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1360                                                                         *nv.objecttoleranceunit,
1361                                                                         px));
1365 NR::Matrix SPDesktop::w2d() const
1367     return _w2d;
1370 NR::Point SPDesktop::w2d(NR::Point const &p) const
1372     return p * _w2d;
1375 NR::Point SPDesktop::d2w(NR::Point const &p) const
1377     return p * _d2w;
1380 NR::Matrix SPDesktop::doc2dt() const
1382     return _doc2dt;
1385 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1387     return p * _doc2dt;
1390 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1392     return p / _doc2dt;
1396 /**
1397  * Pop event context from desktop's context stack. Never used.
1398  */
1399 // void
1400 // SPDesktop::pop_event_context (unsigned int key)
1401 // {
1402 //    SPEventContext *ec = NULL;
1403 //
1404 //    if (event_context && event_context->key == key) {
1405 //        g_return_if_fail (event_context);
1406 //        g_return_if_fail (event_context->next);
1407 //        ec = event_context;
1408 //        sp_event_context_deactivate (ec);
1409 //        event_context = ec->next;
1410 //        sp_event_context_activate (event_context);
1411 //        _event_context_changed_signal.emit (this, ec);
1412 //    }
1413 //
1414 //    SPEventContext *ref = event_context;
1415 //    while (ref && ref->next && ref->next->key != key)
1416 //        ref = ref->next;
1417 //
1418 //    if (ref && ref->next) {
1419 //        ec = ref->next;
1420 //        ref->next = ec->next;
1421 //    }
1422 //
1423 //    if (ec) {
1424 //        sp_event_context_finish (ec);
1425 //        g_object_unref (G_OBJECT (ec));
1426 //    }
1427 // }
1429 /*
1430   Local Variables:
1431   mode:c++
1432   c-file-style:"stroustrup"
1433   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1434   indent-tabs-mode:nil
1435   fill-column:99
1436   End:
1437 */
1438 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :