Code

722aa81e9b61e2bf71324e04ee297eab224bd610
[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"
82 #include "event-log.h"
84 namespace Inkscape { namespace XML { class Node; }}
86 // Callback declarations
87 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
88 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
89 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
90 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
91 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
92 static void _reconstruction_start(SPDesktop * desktop);
93 static void _reconstruction_finish(SPDesktop * desktop);
94 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
95 static void _update_snap_distances (SPDesktop *desktop);
97 /**
98  * Return new desktop object.
99  * \pre namedview != NULL.
100  * \pre canvas != NULL.
101  */
102 SPDesktop::SPDesktop()
104     _dlg_mgr = NULL;
105     _widget = 0;
106     namedview = NULL;
107     selection = NULL;
108     acetate = NULL;
109     main = NULL;
110     grid = NULL;
111     guides = NULL;
112     drawing = NULL;
113     sketch = NULL;
114     controls = NULL;
115     event_context = 0;
116     layer_manager = 0;
118     _d2w.set_identity();
119     _w2d.set_identity();
120     _doc2dt = NR::Matrix(NR::scale(1, -1));
122     guides_active = false;
124     zooms_past = NULL;
125     zooms_future = NULL;
127     is_fullscreen = false;
129     gr_item = NULL;
130     gr_point_num = 0;
131     gr_fill_or_stroke = true;
133     _layer_hierarchy = NULL;
134     _active = false;
136     selection = Inkscape::GC::release (new Inkscape::Selection (this));
139 void
140 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
143     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
145     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
147     namedview = nv;
148     canvas = aCanvas;
150     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
151     /* Kill flicker */
152     sp_document_ensure_up_to_date (document);
154     /* Setup Dialog Manager */
155     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
157     dkey = sp_item_display_key_new (1);
159     /* Connect document */
160     setDocument (document);
162     number = namedview->getViewCount();
165     /* Setup Canvas */
166     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
168     SPCanvasGroup *root = sp_canvas_root (canvas);
170     /* Setup adminstrative layers */
171     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
172     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
173     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
174     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
176     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
177     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
178     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
179     sp_canvas_item_move_to_z (table, 0);
181     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
182     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
183     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
185     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
186     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
188     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
190     // Start always in normal mode
191     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
192     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
194     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
197     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
199     /* Push select tool to the bottom of stack */
200     /** \todo
201      * FIXME: this is the only call to this.  Everything else seems to just
202      * call "set" instead of "push".  Can we assume that there is only one
203      * context ever?
204      */
205     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
207     // display rect and zoom are now handled in sp_desktop_widget_realize()
209     NR::Rect const d(NR::Point(0.0, 0.0),
210                      NR::Point(sp_document_width(document), sp_document_height(document)));
212     SP_CTRLRECT(page)->setRectangle(d);
213     SP_CTRLRECT(page_border)->setRectangle(d);
215     /* the following sets the page shadow on the canvas
216        It was originally set to 5, which is really cheesy!
217        It now is an attribute in the document's namedview. If a value of
218        0 is used, then the constructor for a shadow is not initialized.
219     */
221     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
222         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
223     }
226     /* Connect event for page resize */
227     _doc2dt[5] = sp_document_height (document);
228     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
230     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
232     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
233             SP_CANVAS_ARENA (drawing)->arena,
234             dkey,
235             SP_ITEM_SHOW_DISPLAY);
236     if (ai) {
237         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
238         nr_arena_item_unref (ai);
239     }
241     namedview->show(this);
242     /* Ugly hack */
243     activate_guides (true);
244     /* Ugly hack */
245     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
247 /* Set up notification of rebuilding the document, this allows
248        for saving object related settings in the document. */
249     _reconstruction_start_connection =
250         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
251     _reconstruction_finish_connection =
252         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
253     _reconstruction_old_layer_id = NULL;
254     
255     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
256     
257     // ?
258     // sp_active_desktop_set (desktop);
259     _inkscape = INKSCAPE;
261     _activate_connection = _activate_signal.connect(
262         sigc::bind(
263             sigc::ptr_fun(_onActivate),
264             this
265         )
266     );
267      _deactivate_connection = _deactivate_signal.connect(
268         sigc::bind(
269             sigc::ptr_fun(_onDeactivate),
270             this
271         )
272     );
274     _sel_modified_connection = selection->connectModified(
275         sigc::bind(
276             sigc::ptr_fun(&_onSelectionModified),
277             this
278         )
279     );
280     _sel_changed_connection = selection->connectChanged(
281         sigc::bind(
282             sigc::ptr_fun(&_onSelectionChanged),
283             this
284         )
285     );
288     /* setup LayerManager */
289     //   (Setting up after the connections are all in place, as it may use some of them)
290     layer_manager = new Inkscape::LayerManager( this );
292     /* setup EventLog */
293     event_log = new Inkscape::EventLog(document);
294     document->addUndoObserver(*event_log);
298 void SPDesktop::destroy()
300     _activate_connection.disconnect();
301     _deactivate_connection.disconnect();
302     _sel_modified_connection.disconnect();
303     _sel_changed_connection.disconnect();
304     _modified_connection.disconnect();
305     _commit_connection.disconnect();
306     _reconstruction_start_connection.disconnect();
307     _reconstruction_finish_connection.disconnect();
309     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
310     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
311     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
313     while (event_context) {
314         SPEventContext *ec = event_context;
315         event_context = ec->next;
316         sp_event_context_finish (ec);
317         g_object_unref (G_OBJECT (ec));
318     }
320     if (_layer_hierarchy) {
321         delete _layer_hierarchy;
322     }
324     if (_inkscape) {
325         _inkscape = NULL;
326     }
328     if (drawing) {
329         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
330         drawing = NULL;
331     }
333     delete _guides_message_context;
334     _guides_message_context = NULL;
336     g_list_free (zooms_past);
337     g_list_free (zooms_future);
340 SPDesktop::~SPDesktop() {}
342 //--------------------------------------------------------------------
343 /* Public methods */
345 void SPDesktop::setDisplayModeNormal()
347     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
348     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
349     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
352 void SPDesktop::setDisplayModeOutline()
354     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
355     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
356     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
359 /**
360  * Returns current root (=bottom) layer.
361  */
362 SPObject *SPDesktop::currentRoot() const
364     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
367 /**
368  * Returns current top layer.
369  */
370 SPObject *SPDesktop::currentLayer() const
372     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
375 /**
376  * Sets the current layer of the desktop.
377  * 
378  * Make \a object the top layer.
379  */
380 void SPDesktop::setCurrentLayer(SPObject *object) {
381     g_return_if_fail(SP_IS_GROUP(object));
382     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
383     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
384     _layer_hierarchy->setBottom(object);
387 /**
388  * Return layer that contains \a object.
389  */
390 SPObject *SPDesktop::layerForObject(SPObject *object) {
391     g_return_val_if_fail(object != NULL, NULL);
393     SPObject *root=currentRoot();
394     object = SP_OBJECT_PARENT(object);
395     while ( object && object != root && !isLayer(object) ) {
396         object = SP_OBJECT_PARENT(object);
397     }
398     return object;
401 /**
402  * True if object is a layer.
403  */
404 bool SPDesktop::isLayer(SPObject *object) const {
405     return ( SP_IS_GROUP(object)
406              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
407                   == SPGroup::LAYER ) );
410 /**
411  * True if desktop viewport fully contains \a item's bbox.
412  */
413 bool SPDesktop::isWithinViewport (SPItem *item) const
415     NR::Rect const viewport = get_display_area();
416     NR::Rect const bbox = sp_item_bbox_desktop(item);
417     return viewport.contains(bbox);
420 ///
421 bool SPDesktop::itemIsHidden(SPItem const *item) const {
422     return item->isHidden(this->dkey);
425 /**
426  * Set activate property of desktop; emit signal if changed.
427  */
428 void
429 SPDesktop::set_active (bool new_active)
431     if (new_active != _active) {
432         _active = new_active;
433         if (new_active) {
434             _activate_signal.emit();
435         } else {
436             _deactivate_signal.emit();
437         }
438     }
441 /**
442  * Set activate status of current desktop's named view.
443  */
444 void
445 SPDesktop::activate_guides(bool activate)
447     guides_active = activate;
448     namedview->activateGuides(this, activate);
451 /**
452  * Make desktop switch documents.
453  */
454 void
455 SPDesktop::change_document (SPDocument *theDocument)
457     g_return_if_fail (theDocument != NULL);
459     /* unselect everything before switching documents */
460     selection->clear();
462     setDocument (theDocument);
463     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
464     _document_replaced_signal.emit (this, theDocument);
467 /**
468  * Make desktop switch event contexts.
469  */
470 void
471 SPDesktop::set_event_context (GtkType type, const gchar *config)
473     SPEventContext *ec;
474     while (event_context) {
475         ec = event_context;
476         sp_event_context_deactivate (ec);
477         event_context = ec->next;
478         sp_event_context_finish (ec);
479         g_object_unref (G_OBJECT (ec));
480     }
482     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
483     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
484     ec->next = event_context;
485     event_context = ec;
486     sp_event_context_activate (ec);
487     _event_context_changed_signal.emit (this, ec);
490 /**
491  * Push event context onto desktop's context stack.
492  */
493 void
494 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
496     SPEventContext *ref, *ec;
497     Inkscape::XML::Node *repr;
499     if (event_context && event_context->key == key) return;
500     ref = event_context;
501     while (ref && ref->next && ref->next->key != key) ref = ref->next;
502     if (ref && ref->next) {
503         ec = ref->next;
504         ref->next = ec->next;
505         sp_event_context_finish (ec);
506         g_object_unref (G_OBJECT (ec));
507     }
509     if (event_context) sp_event_context_deactivate (event_context);
510     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
511     ec = sp_event_context_new (type, this, repr, key);
512     ec->next = event_context;
513     event_context = ec;
514     sp_event_context_activate (ec);
515     _event_context_changed_signal.emit (this, ec);
518 /**
519  * Sets the coordinate status to a given point
520  */
521 void
522 SPDesktop::set_coordinate_status (NR::Point p) {
523     _widget->setCoordinateStatus(p);
526 /**
527  * \see sp_document_item_from_list_at_point_bottom()
528  */
529 SPItem *
530 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
532     g_return_val_if_fail (doc() != NULL, NULL);
533     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
536 /**
537  * \see sp_document_item_at_point()
538  */
539 SPItem *
540 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
542     g_return_val_if_fail (doc() != NULL, NULL);
543     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
546 /**
547  * \see sp_document_group_at_point()
548  */
549 SPItem *
550 SPDesktop::group_at_point (NR::Point const p) const
552     g_return_val_if_fail (doc() != NULL, NULL);
553     return sp_document_group_at_point (doc(), dkey, p);
556 /**
557  * \brief  Returns the mouse point in document coordinates; if mouse is
558  * outside the canvas, returns the center of canvas viewpoint
559  */
560 NR::Point
561 SPDesktop::point() const
563     NR::Point p = _widget->getPointer();
564     NR::Point pw = sp_canvas_window_to_world (canvas, p);
565     p = w2d(pw);
567     NR::Rect const r = canvas->getViewbox();
569     NR::Point r0 = w2d(r.min());
570     NR::Point r1 = w2d(r.max());
572     if (p[NR::X] >= r0[NR::X] &&
573         p[NR::X] <= r1[NR::X] &&
574         p[NR::Y] >= r1[NR::Y] &&
575         p[NR::Y] <= r0[NR::Y])
576     {
577         return p;
578     } else {
579         return (r0 + r1) / 2;
580     }
583 /**
584  * Put current zoom data in history list.
585  */
586 void
587 SPDesktop::push_current_zoom (GList **history)
589     NR::Rect const area = get_display_area();
591     NRRect *old_zoom = g_new(NRRect, 1);
592     old_zoom->x0 = area.min()[NR::X];
593     old_zoom->x1 = area.max()[NR::X];
594     old_zoom->y0 = area.min()[NR::Y];
595     old_zoom->y1 = area.max()[NR::Y];
596     if ( *history == NULL
597          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
598                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
599                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
600                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
601     {
602         *history = g_list_prepend (*history, old_zoom);
603     }
606 /**
607  * Set viewbox.
608  */
609 void
610 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
612     g_assert(_widget);
614     // save the zoom
615     if (log) {
616         push_current_zoom(&zooms_past);
617         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
618         g_list_free (zooms_future);
619         zooms_future = NULL;
620     }
622     double const cx = 0.5 * (x0 + x1);
623     double const cy = 0.5 * (y0 + y1);
625     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
627     double scale = expansion(_d2w);
628     double newscale;
629     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
630         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
631     } else {
632         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
633     }
635     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
637     int clear = FALSE;
638     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
639         /* Set zoom factors */
640         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
641         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
642         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
643         clear = TRUE;
644     }
646     /* Calculate top left corner */
647     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
648     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
650     /* Scroll */
651     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
653     _widget->updateRulers();
654     _widget->updateScrollbars(expansion(_d2w));
655     _widget->updateZoom();
658 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
660     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
663 /**
664  * Return viewbox dimensions.
665  */
666 NR::Rect SPDesktop::get_display_area() const
668     NR::Rect const viewbox = canvas->getViewbox();
670     double const scale = _d2w[0];
672     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
673                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
676 /**
677  * Revert back to previous zoom if possible.
678  */
679 void
680 SPDesktop::prev_zoom()
682     if (zooms_past == NULL) {
683         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
684         return;
685     }
687     // push current zoom into forward zooms list
688     push_current_zoom (&zooms_future);
690     // restore previous zoom
691     set_display_area (((NRRect *) zooms_past->data)->x0,
692             ((NRRect *) zooms_past->data)->y0,
693             ((NRRect *) zooms_past->data)->x1,
694             ((NRRect *) zooms_past->data)->y1,
695             0, false);
697     // remove the just-added zoom from the past zooms list
698     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
701 /**
702  * Set zoom to next in list.
703  */
704 void
705 SPDesktop::next_zoom()
707     if (zooms_future == NULL) {
708         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
709         return;
710     }
712     // push current zoom into past zooms list
713     push_current_zoom (&zooms_past);
715     // restore next zoom
716     set_display_area (((NRRect *) zooms_future->data)->x0,
717             ((NRRect *) zooms_future->data)->y0,
718             ((NRRect *) zooms_future->data)->x1,
719             ((NRRect *) zooms_future->data)->y1,
720             0, false);
722     // remove the just-used zoom from the zooms_future list
723     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
726 /**
727  * Zoom to point with absolute zoom factor.
728  */
729 void
730 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
732     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
734     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
735     // this check prevents "sliding" when trying to zoom in at maximum zoom;
736     /// \todo someone please fix calculations properly and remove this hack
737     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
738         return;
740     NR::Rect const viewbox = canvas->getViewbox();
742     double const width2 = viewbox.dimensions()[NR::X] / zoom;
743     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
745     set_display_area(cx - px * width2,
746                      cy - py * height2,
747                      cx + (1 - px) * width2,
748                      cy + (1 - py) * height2,
749                      0.0);
752 /**
753  * Zoom to center with absolute zoom factor.
754  */
755 void
756 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
758     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
761 /**
762  * Zoom to point with relative zoom factor.
763  */
764 void
765 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
767     NR::Rect const area = get_display_area();
769     if (cx < area.min()[NR::X]) {
770         cx = area.min()[NR::X];
771     }
772     if (cx > area.max()[NR::X]) {
773         cx = area.max()[NR::X];
774     }
775     if (cy < area.min()[NR::Y]) {
776         cy = area.min()[NR::Y];
777     }
778     if (cy > area.max()[NR::Y]) {
779         cy = area.max()[NR::Y];
780     }
782     gdouble const scale = expansion(_d2w) * zoom;
783     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
784     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
786     zoom_absolute_keep_point(cx, cy, px, py, scale);
789 /**
790  * Zoom to center with relative zoom factor.
791  */
792 void
793 SPDesktop::zoom_relative (double cx, double cy, double zoom)
795     gdouble scale = expansion(_d2w) * zoom;
796     zoom_absolute (cx, cy, scale);
799 /**
800  * Set display area to origin and current document dimensions.
801  */
802 void
803 SPDesktop::zoom_page()
805     NR::Rect d(NR::Point(0, 0),
806                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
808     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
809         return;
810     }
812     set_display_area(d, 10);
815 /**
816  * Set display area to current document width.
817  */
818 void
819 SPDesktop::zoom_page_width()
821     NR::Rect const a = get_display_area();
823     if (sp_document_width(doc()) < 1.0) {
824         return;
825     }
827     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
828                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
830     set_display_area(d, 10);
833 /**
834  * Zoom to selection.
835  */
836 void
837 SPDesktop::zoom_selection()
839     NR::Rect const d = selection->bounds();
841     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
842         return;
843     }
845     set_display_area(d, 10);
848 /**
849  * Tell widget to let zoom widget grab keyboard focus.
850  */
851 void
852 SPDesktop::zoom_grab_focus()
854     _widget->letZoomGrabFocus();
857 /**
858  * Zoom to whole drawing.
859  */
860 void
861 SPDesktop::zoom_drawing()
863     g_return_if_fail (doc() != NULL);
864     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
865     g_return_if_fail (docitem != NULL);
867     NR::Rect d = sp_item_bbox_desktop(docitem);
869     /* Note that the second condition here indicates that
870     ** there are no items in the drawing.
871     */
872     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
873         return;
874     }
876     set_display_area(d, 10);
879 /**
880  * Scroll canvas by specific coordinate amount.
881  */
882 void
883 SPDesktop::scroll_world (double dx, double dy)
885     g_assert(_widget);
887     NR::Rect const viewbox = canvas->getViewbox();
889     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
891     _widget->updateRulers();
892     _widget->updateScrollbars(expansion(_d2w));
895 bool
896 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
898     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
900     // autoscrolldistance is in screen pixels, but the display area is in document units
901     autoscrolldistance /= expansion(_d2w);
902     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
904     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
905         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
907         NR::Point const s_w( (*p) * _d2w );
909         gdouble x_to;
910         if ((*p)[NR::X] < dbox.min()[NR::X])
911             x_to = dbox.min()[NR::X];
912         else if ((*p)[NR::X] > dbox.max()[NR::X])
913             x_to = dbox.max()[NR::X];
914         else
915             x_to = (*p)[NR::X];
917         gdouble y_to;
918         if ((*p)[NR::Y] < dbox.min()[NR::Y])
919             y_to = dbox.min()[NR::Y];
920         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
921             y_to = dbox.max()[NR::Y];
922         else
923             y_to = (*p)[NR::Y];
925         NR::Point const d_dt(x_to, y_to);
926         NR::Point const d_w( d_dt * _d2w );
927         NR::Point const moved_w( d_w - s_w );
929         if (autoscrollspeed == 0)
930             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
932         if (autoscrollspeed != 0)
933             scroll_world (autoscrollspeed * moved_w);
935         return true;
936     }
937     return false;
940 void
941 SPDesktop::fullscreen()
943     _widget->setFullscreen();
946 void
947 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
949     _widget->getGeometry (x, y, w, h);
952 void
953 SPDesktop::setWindowPosition (NR::Point p)
955     _widget->setPosition (p);
958 void
959 SPDesktop::setWindowSize (gint w, gint h)
961     _widget->setSize (w, h);
964 void
965 SPDesktop::setWindowTransient (void *p, int transient_policy)
967     _widget->setTransient (p, transient_policy);
970 void
971 SPDesktop::presentWindow()
973     _widget->present();
976 bool
977 SPDesktop::warnDialog (gchar *text)
979     return _widget->warnDialog (text);
982 void
983 SPDesktop::toggleRulers()
985     _widget->toggleRulers();
988 void
989 SPDesktop::toggleScrollbars()
991     _widget->toggleScrollbars();
994 void
995 SPDesktop::layoutWidget()
997     _widget->layout();
1000 void
1001 SPDesktop::destroyWidget()
1003     _widget->destroy();
1006 bool
1007 SPDesktop::shutdown()
1009     return _widget->shutdown();
1012 void
1013 SPDesktop::setToolboxFocusTo (gchar const *label)
1015     _widget->setToolboxFocusTo (label);
1018 void
1019 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1021     _widget->setToolboxAdjustmentValue (id, val);
1024 bool
1025 SPDesktop::isToolboxButtonActive (gchar const *id)
1027     return _widget->isToolboxButtonActive (id);
1030 void
1031 SPDesktop::emitToolSubselectionChanged(gpointer data)
1033         _tool_subselection_changed.emit(data);
1034         inkscape_subselection_changed (this);
1037 void
1038 SPDesktop::updateNow()
1040   sp_canvas_update_now(canvas);
1043 void
1044 SPDesktop::enableInteraction()
1046   _widget->enableInteraction();
1049 void SPDesktop::disableInteraction()
1051   _widget->disableInteraction();
1054 //----------------------------------------------------------------------
1055 // Callback implementations. The virtual ones are connected by the view.
1057 void
1058 SPDesktop::onPositionSet (double x, double y)
1060     _widget->viewSetPosition (NR::Point(x,y));
1063 void
1064 SPDesktop::onResized (double x, double y)
1066    // Nothing called here
1069 /**
1070  * Redraw callback; queues Gtk redraw; connected by View.
1071  */
1072 void
1073 SPDesktop::onRedrawRequested ()
1075     if (main) {
1076         _widget->requestCanvasUpdate();
1077     }
1080 void
1081 SPDesktop::updateCanvasNow()
1083   _widget->requestCanvasUpdateAndWait();
1086 /**
1087  * Associate document with desktop.
1088  */
1089 /// \todo fixme: refactor SPDesktop::init to use setDocument
1090 void
1091 SPDesktop::setDocument (SPDocument *doc)
1093     if (this->doc() && doc) {
1094         namedview->hide(this);
1095         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1096     }
1098     if (_layer_hierarchy) {
1099         _layer_hierarchy->clear();
1100         delete _layer_hierarchy;
1101     }
1102     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1103     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1104     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1105     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1106     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1108     _commit_connection.disconnect();
1109     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1111     /// \todo fixme: This condition exists to make sure the code
1112     /// inside is called only once on initialization. But there
1113     /// are surely more safe methods to accomplish this.
1114     if (drawing) {
1115         NRArenaItem *ai;
1117         namedview = sp_document_namedview (doc, NULL);
1118         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1119         number = namedview->getViewCount();
1121         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1122                 SP_CANVAS_ARENA (drawing)->arena,
1123                 dkey,
1124                 SP_ITEM_SHOW_DISPLAY);
1125         if (ai) {
1126             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1127             nr_arena_item_unref (ai);
1128         }
1129         namedview->show(this);
1130         /* Ugly hack */
1131         activate_guides (true);
1132         /* Ugly hack */
1133         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1134     }
1136     _document_replaced_signal.emit (this, doc);
1138     View::setDocument (doc);
1141 void
1142 SPDesktop::onStatusMessage
1143 (Inkscape::MessageType type, gchar const *message)
1145     if (_widget) {
1146         _widget->setMessage(type, message);
1147     }
1150 void
1151 SPDesktop::onDocumentURISet (gchar const* uri)
1153     _widget->setTitle(uri);
1156 /**
1157  * Resized callback.
1158  */
1159 void
1160 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1162     _doc2dt[5] = height;
1163     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1164     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1165     SP_CTRLRECT(page)->setRectangle(a);
1166     SP_CTRLRECT(page_border)->setRectangle(a);
1170 void
1171 SPDesktop::_onActivate (SPDesktop* dt)
1173     if (!dt->_widget) return;
1174     dt->_widget->activateDesktop();
1177 void
1178 SPDesktop::_onDeactivate (SPDesktop* dt)
1180     if (!dt->_widget) return;
1181     dt->_widget->deactivateDesktop();
1184 void
1185 SPDesktop::_onSelectionModified
1186 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1188     if (!dt->_widget) return;
1189     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1192 static void
1193 _onSelectionChanged
1194 (Inkscape::Selection *selection, SPDesktop *desktop)
1196     /** \todo
1197      * only change the layer for single selections, or what?
1198      * This seems reasonable -- for multiple selections there can be many
1199      * different layers involved.
1200      */
1201     SPItem *item=selection->singleItem();
1202     if (item) {
1203         SPObject *layer=desktop->layerForObject(item);
1204         if ( layer && layer != desktop->currentLayer() ) {
1205             desktop->setCurrentLayer(layer);
1206         }
1207     }
1210 /**
1211  * Calls event handler of current event context.
1212  * \param arena Unused
1213  * \todo fixme
1214  */
1215 static gint
1216 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1218     if (ai) {
1219         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1220         return sp_event_context_item_handler (desktop->event_context, spi, event);
1221     } else {
1222         return sp_event_context_root_handler (desktop->event_context, event);
1223     }
1226 static void
1227 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1228     g_return_if_fail(SP_IS_GROUP(layer));
1229     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1232 /// Callback
1233 static void
1234 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1235     g_return_if_fail(SP_IS_GROUP(layer));
1236     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1239 /// Callback
1240 static void
1241 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1242                                          SPDesktop *desktop)
1244     desktop->_layer_changed_signal.emit (bottom);
1247 /// Called when document is starting to be rebuilt.
1248 static void
1249 _reconstruction_start (SPDesktop * desktop)
1251     // printf("Desktop, starting reconstruction\n");
1252     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1253     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1255     /*
1256     GSList const * selection_objs = desktop->selection->list();
1257     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1259     }
1260     */
1261     desktop->selection->clear();
1263     // printf("Desktop, starting reconstruction end\n");
1266 /// Called when document rebuild is finished.
1267 static void
1268 _reconstruction_finish (SPDesktop * desktop)
1270     // printf("Desktop, finishing reconstruction\n");
1271     if (desktop->_reconstruction_old_layer_id == NULL)
1272         return;
1274     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1275     if (newLayer != NULL)
1276         desktop->setCurrentLayer(newLayer);
1278     g_free(desktop->_reconstruction_old_layer_id);
1279     desktop->_reconstruction_old_layer_id = NULL;
1280     // printf("Desktop, finishing reconstruction end\n");
1281     return;
1284 /**
1285  * Namedview_modified callback.
1286  */
1287 static void
1288 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1290     SPNamedView *nv=SP_NAMEDVIEW(obj);
1292     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1294         /* Recalculate snap distances */
1295         /* FIXME: why is the desktop getting involved in setting up something
1296         ** that is entirely to do with the namedview?
1297         */
1298         _update_snap_distances (desktop);
1300         /* Show/hide page background */
1301         if (nv->pagecolor & 0xff) {
1302             sp_canvas_item_show (desktop->table);
1303             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1304             sp_canvas_item_move_to_z (desktop->table, 0);
1305         } else {
1306             sp_canvas_item_hide (desktop->table);
1307         }
1309         /* Show/hide page border */
1310         if (nv->showborder) {
1311             // show
1312             sp_canvas_item_show (desktop->page_border);
1313             // set color and shadow
1314             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1315             if (nv->pageshadow) {
1316                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1317             }
1318             // place in the z-order stack
1319             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1320                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1321             } else {
1322                 int order = sp_canvas_item_order (desktop->page_border);
1323                 int morder = sp_canvas_item_order (desktop->drawing);
1324                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1325                                     morder - order);
1326             }
1327         } else {
1328                 sp_canvas_item_hide (desktop->page_border);
1329                 if (nv->pageshadow) {
1330                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1331                 }
1332         }
1333         
1334         /* Show/hide page shadow */
1335         if (nv->showpageshadow && nv->pageshadow) {
1336             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1337         } else {
1338             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1339         }
1341         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1342             (SP_RGBA32_R_U(nv->pagecolor) +
1343              SP_RGBA32_G_U(nv->pagecolor) +
1344              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1345             // the background color is light or transparent, use black outline
1346             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1347         } else { // use white outline
1348             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1349         }
1350     }
1353 /**
1354  * Callback to reset snapper's distances.
1355  */
1356 static void
1357 _update_snap_distances (SPDesktop *desktop)
1359     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1361     SPNamedView &nv = *desktop->namedview;
1363     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1364                                                                       *nv.gridtoleranceunit,
1365                                                                       px));
1366     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1367                                                                        *nv.guidetoleranceunit,
1368                                                                        px));
1369     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1370                                                                         *nv.objecttoleranceunit,
1371                                                                         px));
1375 NR::Matrix SPDesktop::w2d() const
1377     return _w2d;
1380 NR::Point SPDesktop::w2d(NR::Point const &p) const
1382     return p * _w2d;
1385 NR::Point SPDesktop::d2w(NR::Point const &p) const
1387     return p * _d2w;
1390 NR::Matrix SPDesktop::doc2dt() const
1392     return _doc2dt;
1395 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1397     return p * _doc2dt;
1400 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1402     return p / _doc2dt;
1406 /**
1407  * Pop event context from desktop's context stack. Never used.
1408  */
1409 // void
1410 // SPDesktop::pop_event_context (unsigned int key)
1411 // {
1412 //    SPEventContext *ec = NULL;
1413 //
1414 //    if (event_context && event_context->key == key) {
1415 //        g_return_if_fail (event_context);
1416 //        g_return_if_fail (event_context->next);
1417 //        ec = event_context;
1418 //        sp_event_context_deactivate (ec);
1419 //        event_context = ec->next;
1420 //        sp_event_context_activate (event_context);
1421 //        _event_context_changed_signal.emit (this, ec);
1422 //    }
1423 //
1424 //    SPEventContext *ref = event_context;
1425 //    while (ref && ref->next && ref->next->key != key)
1426 //        ref = ref->next;
1427 //
1428 //    if (ref && ref->next) {
1429 //        ec = ref->next;
1430 //        ref->next = ec->next;
1431 //    }
1432 //
1433 //    if (ec) {
1434 //        sp_event_context_finish (ec);
1435 //        g_object_unref (G_OBJECT (ec));
1436 //    }
1437 // }
1439 /*
1440   Local Variables:
1441   mode:c++
1442   c-file-style:"stroustrup"
1443   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1444   indent-tabs-mode:nil
1445   fill-column:99
1446   End:
1447 */
1448 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :