Code

Committed double code because of the hurry to let you use the axonom-snapping stuff.
[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 Johan Engelen <johan@shouraizou.nl>
14  * Copyright (C) 2006 John Bintz
15  * Copyright (C) 2004 MenTaLguY
16  * Copyright (C) 1999-2002 Lauris Kaplinski
17  * Copyright (C) 2000-2001 Ximian, Inc.
18  *
19  * Released under GNU GPL, read the file 'COPYING' for more information
20  */
22 /** \class SPDesktop
23  * SPDesktop is a subclass of View, implementing an editable document
24  * canvas.  It is extensively used by many UI controls that need certain
25  * visual representations of their own.
26  *
27  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
28  * layers of different control objects. The one containing the whole
29  * document is the drawing layer. In addition to it, there are grid,
30  * guide, sketch and control layers. The sketch layer is used for
31  * temporary drawing objects, before the real objects in document are
32  * created. The control layer contains editing knots, rubberband and
33  * similar non-document UI objects.
34  *
35  * Each SPDesktop is associated with a SPNamedView node of the document
36  * tree.  Currently, all desktops are created from a single main named
37  * view, but in the future there may be support for different ones.
38  * SPNamedView serves as an in-document container for desktop-related
39  * data, like grid and guideline placement, snapping options and so on.
40  *
41  * Associated with each SPDesktop are the two most important editing
42  * related objects - SPSelection and SPEventContext.
43  *
44  * Sodipodi keeps track of the active desktop and invokes notification
45  * signals whenever it changes. UI elements can use these to update their
46  * display to the selection of the currently active editing window.
47  * (Lauris Kaplinski)
48  */
50 #ifdef HAVE_CONFIG_H
51 # include "config.h"
52 #endif
54 #include <glibmm/i18n.h>
55 #include <sigc++/functors/mem_fun.h>
57 #include "macros.h"
58 #include "inkscape-private.h"
59 #include "desktop.h"
60 #include "desktop-events.h"
61 #include "desktop-handles.h"
62 #include "document.h"
63 #include "message-stack.h"
64 #include "selection.h"
65 #include "select-context.h"
66 #include "sp-namedview.h"
67 #include "color.h"
68 #include "sp-item-group.h"
69 #include "prefs-utils.h"
70 #include "object-hierarchy.h"
71 #include "helper/units.h"
72 #include "display/canvas-arena.h"
73 #include "display/nr-arena.h"
74 #include "display/gnome-canvas-acetate.h"
75 #include "display/sodipodi-ctrlrect.h"
76 #include "display/sp-canvas-util.h"
77 #include "libnr/nr-matrix-div.h"
78 #include "libnr/nr-rect-ops.h"
79 #include "ui/dialog/dialog-manager.h"
80 #include "xml/repr.h"
81 #include "message-context.h"
82 #include "layer-manager.h"
83 #include "event-log.h"
85 namespace Inkscape { namespace XML { class Node; }}
87 // Callback declarations
88 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
89 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
90 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
91 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
92 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
93 static void _reconstruction_start(SPDesktop * desktop);
94 static void _reconstruction_finish(SPDesktop * desktop);
95 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
96 static void _update_snap_distances (SPDesktop *desktop);
98 /**
99  * Return new desktop object.
100  * \pre namedview != NULL.
101  * \pre canvas != NULL.
102  */
103 SPDesktop::SPDesktop()
105     _dlg_mgr = NULL;
106     _widget = 0;
107     namedview = NULL;
108     selection = NULL;
109     acetate = NULL;
110     main = NULL;
111     grid = NULL;
112     guides = NULL;
113     drawing = NULL;
114     sketch = NULL;
115     controls = NULL;
116     event_context = 0;
117     layer_manager = 0;
119     _d2w.set_identity();
120     _w2d.set_identity();
121     _doc2dt = NR::Matrix(NR::scale(1, -1));
123     guides_active = false;
125     zooms_past = NULL;
126     zooms_future = NULL;
128     is_fullscreen = false;
130     gr_item = NULL;
131     gr_point_num = 0;
132     gr_fill_or_stroke = true;
134     _layer_hierarchy = NULL;
135     _active = false;
137     selection = Inkscape::GC::release (new Inkscape::Selection (this));
140 void
141 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
144     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
146     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
148     namedview = nv;
149     canvas = aCanvas;
151     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
152     /* Kill flicker */
153     sp_document_ensure_up_to_date (document);
155     /* Setup Dialog Manager */
156     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
158     dkey = sp_item_display_key_new (1);
160     /* Connect document */
161     setDocument (document);
163     number = namedview->getViewCount();
166     /* Setup Canvas */
167     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
169     SPCanvasGroup *root = sp_canvas_root (canvas);
171     /* Setup adminstrative layers */
172     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
173     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
174     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
175     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
177     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
178     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
179     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
180     sp_canvas_item_move_to_z (table, 0);
182     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
183     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
184     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
186     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
187     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
189     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
191     // Start always in normal mode
192     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
193     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
195     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
197     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
198     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
200     /* Push select tool to the bottom of stack */
201     /** \todo
202      * FIXME: this is the only call to this.  Everything else seems to just
203      * call "set" instead of "push".  Can we assume that there is only one
204      * context ever?
205      */
206     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
208     // display rect and zoom are now handled in sp_desktop_widget_realize()
210     NR::Rect const d(NR::Point(0.0, 0.0),
211                      NR::Point(sp_document_width(document), sp_document_height(document)));
213     SP_CTRLRECT(page)->setRectangle(d);
214     SP_CTRLRECT(page_border)->setRectangle(d);
216     /* the following sets the page shadow on the canvas
217        It was originally set to 5, which is really cheesy!
218        It now is an attribute in the document's namedview. If a value of
219        0 is used, then the constructor for a shadow is not initialized.
220     */
222     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
223         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
224     }
227     /* Connect event for page resize */
228     _doc2dt[5] = sp_document_height (document);
229     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
231     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
233     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
234             SP_CANVAS_ARENA (drawing)->arena,
235             dkey,
236             SP_ITEM_SHOW_DISPLAY);
237     if (ai) {
238         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
239         nr_arena_item_unref (ai);
240     }
242     namedview->show(this);
243     /* Ugly hack */
244     activate_guides (true);
245     /* Ugly hack */
246     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
248 /* Set up notification of rebuilding the document, this allows
249        for saving object related settings in the document. */
250     _reconstruction_start_connection =
251         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
252     _reconstruction_finish_connection =
253         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
254     _reconstruction_old_layer_id = NULL;
255     
256     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
257     
258     // ?
259     // sp_active_desktop_set (desktop);
260     _inkscape = INKSCAPE;
262     _activate_connection = _activate_signal.connect(
263         sigc::bind(
264             sigc::ptr_fun(_onActivate),
265             this
266         )
267     );
268      _deactivate_connection = _deactivate_signal.connect(
269         sigc::bind(
270             sigc::ptr_fun(_onDeactivate),
271             this
272         )
273     );
275     _sel_modified_connection = selection->connectModified(
276         sigc::bind(
277             sigc::ptr_fun(&_onSelectionModified),
278             this
279         )
280     );
281     _sel_changed_connection = selection->connectChanged(
282         sigc::bind(
283             sigc::ptr_fun(&_onSelectionChanged),
284             this
285         )
286     );
289     /* setup LayerManager */
290     //   (Setting up after the connections are all in place, as it may use some of them)
291     layer_manager = new Inkscape::LayerManager( this );
295 void SPDesktop::destroy()
297     _activate_connection.disconnect();
298     _deactivate_connection.disconnect();
299     _sel_modified_connection.disconnect();
300     _sel_changed_connection.disconnect();
301     _modified_connection.disconnect();
302     _commit_connection.disconnect();
303     _reconstruction_start_connection.disconnect();
304     _reconstruction_finish_connection.disconnect();
306     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
307     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
308     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
310     while (event_context) {
311         SPEventContext *ec = event_context;
312         event_context = ec->next;
313         sp_event_context_finish (ec);
314         g_object_unref (G_OBJECT (ec));
315     }
317     if (_layer_hierarchy) {
318         delete _layer_hierarchy;
319     }
321     if (_inkscape) {
322         _inkscape = NULL;
323     }
325     if (drawing) {
326         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
327         drawing = NULL;
328     }
330     delete _guides_message_context;
331     _guides_message_context = NULL;
333     g_list_free (zooms_past);
334     g_list_free (zooms_future);
337 SPDesktop::~SPDesktop() {}
339 //--------------------------------------------------------------------
340 /* Public methods */
342 void SPDesktop::setDisplayModeNormal()
344     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
345     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
346     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
349 void SPDesktop::setDisplayModeOutline()
351     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
352     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
353     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
356 /**
357  * Returns current root (=bottom) layer.
358  */
359 SPObject *SPDesktop::currentRoot() const
361     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
364 /**
365  * Returns current top layer.
366  */
367 SPObject *SPDesktop::currentLayer() const
369     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
372 /**
373  * Sets the current layer of the desktop.
374  * 
375  * Make \a object the top layer.
376  */
377 void SPDesktop::setCurrentLayer(SPObject *object) {
378     g_return_if_fail(SP_IS_GROUP(object));
379     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
380     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
381     _layer_hierarchy->setBottom(object);
384 /**
385  * Return layer that contains \a object.
386  */
387 SPObject *SPDesktop::layerForObject(SPObject *object) {
388     g_return_val_if_fail(object != NULL, NULL);
390     SPObject *root=currentRoot();
391     object = SP_OBJECT_PARENT(object);
392     while ( object && object != root && !isLayer(object) ) {
393         object = SP_OBJECT_PARENT(object);
394     }
395     return object;
398 /**
399  * True if object is a layer.
400  */
401 bool SPDesktop::isLayer(SPObject *object) const {
402     return ( SP_IS_GROUP(object)
403              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
404                   == SPGroup::LAYER ) );
407 /**
408  * True if desktop viewport fully contains \a item's bbox.
409  */
410 bool SPDesktop::isWithinViewport (SPItem *item) const
412     NR::Rect const viewport = get_display_area();
413     NR::Rect const bbox = sp_item_bbox_desktop(item);
414     return viewport.contains(bbox);
417 ///
418 bool SPDesktop::itemIsHidden(SPItem const *item) const {
419     return item->isHidden(this->dkey);
422 /**
423  * Set activate property of desktop; emit signal if changed.
424  */
425 void
426 SPDesktop::set_active (bool new_active)
428     if (new_active != _active) {
429         _active = new_active;
430         if (new_active) {
431             _activate_signal.emit();
432         } else {
433             _deactivate_signal.emit();
434         }
435     }
438 /**
439  * Set activate status of current desktop's named view.
440  */
441 void
442 SPDesktop::activate_guides(bool activate)
444     guides_active = activate;
445     namedview->activateGuides(this, activate);
448 /**
449  * Make desktop switch documents.
450  */
451 void
452 SPDesktop::change_document (SPDocument *theDocument)
454     g_return_if_fail (theDocument != NULL);
456     /* unselect everything before switching documents */
457     selection->clear();
459     setDocument (theDocument);
460     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
461     _document_replaced_signal.emit (this, theDocument);
464 /**
465  * Make desktop switch event contexts.
466  */
467 void
468 SPDesktop::set_event_context (GtkType type, const gchar *config)
470     SPEventContext *ec;
471     while (event_context) {
472         ec = event_context;
473         sp_event_context_deactivate (ec);
474         event_context = ec->next;
475         sp_event_context_finish (ec);
476         g_object_unref (G_OBJECT (ec));
477     }
479     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
480     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
481     ec->next = event_context;
482     event_context = ec;
483     sp_event_context_activate (ec);
484     _event_context_changed_signal.emit (this, ec);
487 /**
488  * Push event context onto desktop's context stack.
489  */
490 void
491 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
493     SPEventContext *ref, *ec;
494     Inkscape::XML::Node *repr;
496     if (event_context && event_context->key == key) return;
497     ref = event_context;
498     while (ref && ref->next && ref->next->key != key) ref = ref->next;
499     if (ref && ref->next) {
500         ec = ref->next;
501         ref->next = ec->next;
502         sp_event_context_finish (ec);
503         g_object_unref (G_OBJECT (ec));
504     }
506     if (event_context) sp_event_context_deactivate (event_context);
507     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
508     ec = sp_event_context_new (type, this, repr, key);
509     ec->next = event_context;
510     event_context = ec;
511     sp_event_context_activate (ec);
512     _event_context_changed_signal.emit (this, ec);
515 /**
516  * Sets the coordinate status to a given point
517  */
518 void
519 SPDesktop::set_coordinate_status (NR::Point p) {
520     _widget->setCoordinateStatus(p);
523 /**
524  * \see sp_document_item_from_list_at_point_bottom()
525  */
526 SPItem *
527 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
529     g_return_val_if_fail (doc() != NULL, NULL);
530     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
533 /**
534  * \see sp_document_item_at_point()
535  */
536 SPItem *
537 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
539     g_return_val_if_fail (doc() != NULL, NULL);
540     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
543 /**
544  * \see sp_document_group_at_point()
545  */
546 SPItem *
547 SPDesktop::group_at_point (NR::Point const p) const
549     g_return_val_if_fail (doc() != NULL, NULL);
550     return sp_document_group_at_point (doc(), dkey, p);
553 /**
554  * \brief  Returns the mouse point in document coordinates; if mouse is
555  * outside the canvas, returns the center of canvas viewpoint
556  */
557 NR::Point
558 SPDesktop::point() const
560     NR::Point p = _widget->getPointer();
561     NR::Point pw = sp_canvas_window_to_world (canvas, p);
562     p = w2d(pw);
564     NR::Rect const r = canvas->getViewbox();
566     NR::Point r0 = w2d(r.min());
567     NR::Point r1 = w2d(r.max());
569     if (p[NR::X] >= r0[NR::X] &&
570         p[NR::X] <= r1[NR::X] &&
571         p[NR::Y] >= r1[NR::Y] &&
572         p[NR::Y] <= r0[NR::Y])
573     {
574         return p;
575     } else {
576         return (r0 + r1) / 2;
577     }
580 /**
581  * Put current zoom data in history list.
582  */
583 void
584 SPDesktop::push_current_zoom (GList **history)
586     NR::Rect const area = get_display_area();
588     NRRect *old_zoom = g_new(NRRect, 1);
589     old_zoom->x0 = area.min()[NR::X];
590     old_zoom->x1 = area.max()[NR::X];
591     old_zoom->y0 = area.min()[NR::Y];
592     old_zoom->y1 = area.max()[NR::Y];
593     if ( *history == NULL
594          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
595                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
596                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
597                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
598     {
599         *history = g_list_prepend (*history, old_zoom);
600     }
603 /**
604  * Set viewbox.
605  */
606 void
607 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
609     g_assert(_widget);
611     // save the zoom
612     if (log) {
613         push_current_zoom(&zooms_past);
614         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
615         g_list_free (zooms_future);
616         zooms_future = NULL;
617     }
619     double const cx = 0.5 * (x0 + x1);
620     double const cy = 0.5 * (y0 + y1);
622     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
624     double scale = expansion(_d2w);
625     double newscale;
626     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
627         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
628     } else {
629         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
630     }
632     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
634     int clear = FALSE;
635     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
636         /* Set zoom factors */
637         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
638         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
639         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
640         clear = TRUE;
641     }
643     /* Calculate top left corner */
644     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
645     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
647     /* Scroll */
648     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
650     _widget->updateRulers();
651     _widget->updateScrollbars(expansion(_d2w));
652     _widget->updateZoom();
655 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
657     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
660 /**
661  * Return viewbox dimensions.
662  */
663 NR::Rect SPDesktop::get_display_area() const
665     NR::Rect const viewbox = canvas->getViewbox();
667     double const scale = _d2w[0];
669     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
670                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
673 /**
674  * Revert back to previous zoom if possible.
675  */
676 void
677 SPDesktop::prev_zoom()
679     if (zooms_past == NULL) {
680         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
681         return;
682     }
684     // push current zoom into forward zooms list
685     push_current_zoom (&zooms_future);
687     // restore previous zoom
688     set_display_area (((NRRect *) zooms_past->data)->x0,
689             ((NRRect *) zooms_past->data)->y0,
690             ((NRRect *) zooms_past->data)->x1,
691             ((NRRect *) zooms_past->data)->y1,
692             0, false);
694     // remove the just-added zoom from the past zooms list
695     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
698 /**
699  * Set zoom to next in list.
700  */
701 void
702 SPDesktop::next_zoom()
704     if (zooms_future == NULL) {
705         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
706         return;
707     }
709     // push current zoom into past zooms list
710     push_current_zoom (&zooms_past);
712     // restore next zoom
713     set_display_area (((NRRect *) zooms_future->data)->x0,
714             ((NRRect *) zooms_future->data)->y0,
715             ((NRRect *) zooms_future->data)->x1,
716             ((NRRect *) zooms_future->data)->y1,
717             0, false);
719     // remove the just-used zoom from the zooms_future list
720     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
723 /**
724  * Zoom to point with absolute zoom factor.
725  */
726 void
727 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
729     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
731     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
732     // this check prevents "sliding" when trying to zoom in at maximum zoom;
733     /// \todo someone please fix calculations properly and remove this hack
734     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
735         return;
737     NR::Rect const viewbox = canvas->getViewbox();
739     double const width2 = viewbox.dimensions()[NR::X] / zoom;
740     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
742     set_display_area(cx - px * width2,
743                      cy - py * height2,
744                      cx + (1 - px) * width2,
745                      cy + (1 - py) * height2,
746                      0.0);
749 /**
750  * Zoom to center with absolute zoom factor.
751  */
752 void
753 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
755     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
758 /**
759  * Zoom to point with relative zoom factor.
760  */
761 void
762 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
764     NR::Rect const area = get_display_area();
766     if (cx < area.min()[NR::X]) {
767         cx = area.min()[NR::X];
768     }
769     if (cx > area.max()[NR::X]) {
770         cx = area.max()[NR::X];
771     }
772     if (cy < area.min()[NR::Y]) {
773         cy = area.min()[NR::Y];
774     }
775     if (cy > area.max()[NR::Y]) {
776         cy = area.max()[NR::Y];
777     }
779     gdouble const scale = expansion(_d2w) * zoom;
780     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
781     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
783     zoom_absolute_keep_point(cx, cy, px, py, scale);
786 /**
787  * Zoom to center with relative zoom factor.
788  */
789 void
790 SPDesktop::zoom_relative (double cx, double cy, double zoom)
792     gdouble scale = expansion(_d2w) * zoom;
793     zoom_absolute (cx, cy, scale);
796 /**
797  * Set display area to origin and current document dimensions.
798  */
799 void
800 SPDesktop::zoom_page()
802     NR::Rect d(NR::Point(0, 0),
803                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
805     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
806         return;
807     }
809     set_display_area(d, 10);
812 /**
813  * Set display area to current document width.
814  */
815 void
816 SPDesktop::zoom_page_width()
818     NR::Rect const a = get_display_area();
820     if (sp_document_width(doc()) < 1.0) {
821         return;
822     }
824     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
825                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
827     set_display_area(d, 10);
830 /**
831  * Zoom to selection.
832  */
833 void
834 SPDesktop::zoom_selection()
836     NR::Rect const d = selection->bounds();
838     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
839         return;
840     }
842     set_display_area(d, 10);
845 /**
846  * Tell widget to let zoom widget grab keyboard focus.
847  */
848 void
849 SPDesktop::zoom_grab_focus()
851     _widget->letZoomGrabFocus();
854 /**
855  * Zoom to whole drawing.
856  */
857 void
858 SPDesktop::zoom_drawing()
860     g_return_if_fail (doc() != NULL);
861     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
862     g_return_if_fail (docitem != NULL);
864     NR::Rect d = sp_item_bbox_desktop(docitem);
866     /* Note that the second condition here indicates that
867     ** there are no items in the drawing.
868     */
869     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
870         return;
871     }
873     set_display_area(d, 10);
876 /**
877  * Scroll canvas by specific coordinate amount.
878  */
879 void
880 SPDesktop::scroll_world (double dx, double dy)
882     g_assert(_widget);
884     NR::Rect const viewbox = canvas->getViewbox();
886     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
888     _widget->updateRulers();
889     _widget->updateScrollbars(expansion(_d2w));
892 bool
893 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
895     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
897     // autoscrolldistance is in screen pixels, but the display area is in document units
898     autoscrolldistance /= expansion(_d2w);
899     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
901     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
902         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
904         NR::Point const s_w( (*p) * _d2w );
906         gdouble x_to;
907         if ((*p)[NR::X] < dbox.min()[NR::X])
908             x_to = dbox.min()[NR::X];
909         else if ((*p)[NR::X] > dbox.max()[NR::X])
910             x_to = dbox.max()[NR::X];
911         else
912             x_to = (*p)[NR::X];
914         gdouble y_to;
915         if ((*p)[NR::Y] < dbox.min()[NR::Y])
916             y_to = dbox.min()[NR::Y];
917         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
918             y_to = dbox.max()[NR::Y];
919         else
920             y_to = (*p)[NR::Y];
922         NR::Point const d_dt(x_to, y_to);
923         NR::Point const d_w( d_dt * _d2w );
924         NR::Point const moved_w( d_w - s_w );
926         if (autoscrollspeed == 0)
927             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
929         if (autoscrollspeed != 0)
930             scroll_world (autoscrollspeed * moved_w);
932         return true;
933     }
934     return false;
937 void
938 SPDesktop::fullscreen()
940     _widget->setFullscreen();
943 void
944 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
946     _widget->getGeometry (x, y, w, h);
949 void
950 SPDesktop::setWindowPosition (NR::Point p)
952     _widget->setPosition (p);
955 void
956 SPDesktop::setWindowSize (gint w, gint h)
958     _widget->setSize (w, h);
961 void
962 SPDesktop::setWindowTransient (void *p, int transient_policy)
964     _widget->setTransient (p, transient_policy);
967 void
968 SPDesktop::presentWindow()
970     _widget->present();
973 bool
974 SPDesktop::warnDialog (gchar *text)
976     return _widget->warnDialog (text);
979 void
980 SPDesktop::toggleRulers()
982     _widget->toggleRulers();
985 void
986 SPDesktop::toggleScrollbars()
988     _widget->toggleScrollbars();
991 void
992 SPDesktop::layoutWidget()
994     _widget->layout();
997 void
998 SPDesktop::destroyWidget()
1000     _widget->destroy();
1003 bool
1004 SPDesktop::shutdown()
1006     return _widget->shutdown();
1009 void
1010 SPDesktop::setToolboxFocusTo (gchar const *label)
1012     _widget->setToolboxFocusTo (label);
1015 void
1016 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1018     _widget->setToolboxAdjustmentValue (id, val);
1021 bool
1022 SPDesktop::isToolboxButtonActive (gchar const *id)
1024     return _widget->isToolboxButtonActive (id);
1027 void
1028 SPDesktop::emitToolSubselectionChanged(gpointer data)
1030         _tool_subselection_changed.emit(data);
1031         inkscape_subselection_changed (this);
1034 void
1035 SPDesktop::updateNow()
1037   sp_canvas_update_now(canvas);
1040 void
1041 SPDesktop::enableInteraction()
1043   _widget->enableInteraction();
1046 void SPDesktop::disableInteraction()
1048   _widget->disableInteraction();
1051 //----------------------------------------------------------------------
1052 // Callback implementations. The virtual ones are connected by the view.
1054 void
1055 SPDesktop::onPositionSet (double x, double y)
1057     _widget->viewSetPosition (NR::Point(x,y));
1060 void
1061 SPDesktop::onResized (double x, double y)
1063    // Nothing called here
1066 /**
1067  * Redraw callback; queues Gtk redraw; connected by View.
1068  */
1069 void
1070 SPDesktop::onRedrawRequested ()
1072     if (main) {
1073         _widget->requestCanvasUpdate();
1074     }
1077 void
1078 SPDesktop::updateCanvasNow()
1080   _widget->requestCanvasUpdateAndWait();
1083 /**
1084  * Associate document with desktop.
1085  */
1086 /// \todo fixme: refactor SPDesktop::init to use setDocument
1087 void
1088 SPDesktop::setDocument (SPDocument *doc)
1090     if (this->doc() && doc) {
1091         namedview->hide(this);
1092         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1093     }
1095     if (_layer_hierarchy) {
1096         _layer_hierarchy->clear();
1097         delete _layer_hierarchy;
1098     }
1099     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1100     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1101     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1102     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1103     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1105     /* setup EventLog */
1106     event_log = new Inkscape::EventLog(doc);
1107     doc->addUndoObserver(*event_log);
1109     _commit_connection.disconnect();
1110     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1112     /// \todo fixme: This condition exists to make sure the code
1113     /// inside is called only once on initialization. But there
1114     /// are surely more safe methods to accomplish this.
1115     if (drawing) {
1116         NRArenaItem *ai;
1118         namedview = sp_document_namedview (doc, NULL);
1119         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1120         number = namedview->getViewCount();
1122         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1123                 SP_CANVAS_ARENA (drawing)->arena,
1124                 dkey,
1125                 SP_ITEM_SHOW_DISPLAY);
1126         if (ai) {
1127             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1128             nr_arena_item_unref (ai);
1129         }
1130         namedview->show(this);
1131         /* Ugly hack */
1132         activate_guides (true);
1133         /* Ugly hack */
1134         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1135     }
1137     _document_replaced_signal.emit (this, doc);
1139     View::setDocument (doc);
1142 void
1143 SPDesktop::onStatusMessage
1144 (Inkscape::MessageType type, gchar const *message)
1146     if (_widget) {
1147         _widget->setMessage(type, message);
1148     }
1151 void
1152 SPDesktop::onDocumentURISet (gchar const* uri)
1154     _widget->setTitle(uri);
1157 /**
1158  * Resized callback.
1159  */
1160 void
1161 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1163     _doc2dt[5] = height;
1164     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1165     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1166     SP_CTRLRECT(page)->setRectangle(a);
1167     SP_CTRLRECT(page_border)->setRectangle(a);
1171 void
1172 SPDesktop::_onActivate (SPDesktop* dt)
1174     if (!dt->_widget) return;
1175     dt->_widget->activateDesktop();
1178 void
1179 SPDesktop::_onDeactivate (SPDesktop* dt)
1181     if (!dt->_widget) return;
1182     dt->_widget->deactivateDesktop();
1185 void
1186 SPDesktop::_onSelectionModified
1187 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1189     if (!dt->_widget) return;
1190     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1193 static void
1194 _onSelectionChanged
1195 (Inkscape::Selection *selection, SPDesktop *desktop)
1197     /** \todo
1198      * only change the layer for single selections, or what?
1199      * This seems reasonable -- for multiple selections there can be many
1200      * different layers involved.
1201      */
1202     SPItem *item=selection->singleItem();
1203     if (item) {
1204         SPObject *layer=desktop->layerForObject(item);
1205         if ( layer && layer != desktop->currentLayer() ) {
1206             desktop->setCurrentLayer(layer);
1207         }
1208     }
1211 /**
1212  * Calls event handler of current event context.
1213  * \param arena Unused
1214  * \todo fixme
1215  */
1216 static gint
1217 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1219     if (ai) {
1220         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1221         return sp_event_context_item_handler (desktop->event_context, spi, event);
1222     } else {
1223         return sp_event_context_root_handler (desktop->event_context, event);
1224     }
1227 static void
1228 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1229     g_return_if_fail(SP_IS_GROUP(layer));
1230     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1233 /// Callback
1234 static void
1235 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1236     g_return_if_fail(SP_IS_GROUP(layer));
1237     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1240 /// Callback
1241 static void
1242 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1243                                          SPDesktop *desktop)
1245     desktop->_layer_changed_signal.emit (bottom);
1248 /// Called when document is starting to be rebuilt.
1249 static void
1250 _reconstruction_start (SPDesktop * desktop)
1252     // printf("Desktop, starting reconstruction\n");
1253     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1254     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1256     /*
1257     GSList const * selection_objs = desktop->selection->list();
1258     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1260     }
1261     */
1262     desktop->selection->clear();
1264     // printf("Desktop, starting reconstruction end\n");
1267 /// Called when document rebuild is finished.
1268 static void
1269 _reconstruction_finish (SPDesktop * desktop)
1271     // printf("Desktop, finishing reconstruction\n");
1272     if (desktop->_reconstruction_old_layer_id == NULL)
1273         return;
1275     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1276     if (newLayer != NULL)
1277         desktop->setCurrentLayer(newLayer);
1279     g_free(desktop->_reconstruction_old_layer_id);
1280     desktop->_reconstruction_old_layer_id = NULL;
1281     // printf("Desktop, finishing reconstruction end\n");
1282     return;
1285 /**
1286  * Namedview_modified callback.
1287  */
1288 static void
1289 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1291     SPNamedView *nv=SP_NAMEDVIEW(obj);
1293     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1295         /* Recalculate snap distances */
1296         /* FIXME: why is the desktop getting involved in setting up something
1297         ** that is entirely to do with the namedview?
1298         */
1299         _update_snap_distances (desktop);
1301         /* Show/hide page background */
1302         if (nv->pagecolor & 0xff) {
1303             sp_canvas_item_show (desktop->table);
1304             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1305             sp_canvas_item_move_to_z (desktop->table, 0);
1306         } else {
1307             sp_canvas_item_hide (desktop->table);
1308         }
1310         /* Show/hide page border */
1311         if (nv->showborder) {
1312             // show
1313             sp_canvas_item_show (desktop->page_border);
1314             // set color and shadow
1315             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1316             if (nv->pageshadow) {
1317                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1318             }
1319             // place in the z-order stack
1320             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1321                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1322             } else {
1323                 int order = sp_canvas_item_order (desktop->page_border);
1324                 int morder = sp_canvas_item_order (desktop->drawing);
1325                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1326                                     morder - order);
1327             }
1328         } else {
1329                 sp_canvas_item_hide (desktop->page_border);
1330                 if (nv->pageshadow) {
1331                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1332                 }
1333         }
1334         
1335         /* Show/hide page shadow */
1336         if (nv->showpageshadow && nv->pageshadow) {
1337             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1338         } else {
1339             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1340         }
1342         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1343             (SP_RGBA32_R_U(nv->pagecolor) +
1344              SP_RGBA32_G_U(nv->pagecolor) +
1345              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1346             // the background color is light or transparent, use black outline
1347             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1348         } else { // use white outline
1349             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1350         }
1351     }
1354 /**
1355  * Callback to reset snapper's distances.
1356  */
1357 static void
1358 _update_snap_distances (SPDesktop *desktop)
1360     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1362     SPNamedView &nv = *desktop->namedview;
1364     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1365                                                                       *nv.gridtoleranceunit,
1366                                                                       px));
1367     nv.snap_manager.axonomgrid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1368                                                                       *nv.gridtoleranceunit,
1369                                                                       px));
1370     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1371                                                                        *nv.guidetoleranceunit,
1372                                                                        px));
1373     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1374                                                                         *nv.objecttoleranceunit,
1375                                                                         px));
1379 NR::Matrix SPDesktop::w2d() const
1381     return _w2d;
1384 NR::Point SPDesktop::w2d(NR::Point const &p) const
1386     return p * _w2d;
1389 NR::Point SPDesktop::d2w(NR::Point const &p) const
1391     return p * _d2w;
1394 NR::Matrix SPDesktop::doc2dt() const
1396     return _doc2dt;
1399 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1401     return p * _doc2dt;
1404 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1406     return p / _doc2dt;
1410 /**
1411  * Pop event context from desktop's context stack. Never used.
1412  */
1413 // void
1414 // SPDesktop::pop_event_context (unsigned int key)
1415 // {
1416 //    SPEventContext *ec = NULL;
1417 //
1418 //    if (event_context && event_context->key == key) {
1419 //        g_return_if_fail (event_context);
1420 //        g_return_if_fail (event_context->next);
1421 //        ec = event_context;
1422 //        sp_event_context_deactivate (ec);
1423 //        event_context = ec->next;
1424 //        sp_event_context_activate (event_context);
1425 //        _event_context_changed_signal.emit (this, ec);
1426 //    }
1427 //
1428 //    SPEventContext *ref = event_context;
1429 //    while (ref && ref->next && ref->next->key != key)
1430 //        ref = ref->next;
1431 //
1432 //    if (ref && ref->next) {
1433 //        ec = ref->next;
1434 //        ref->next = ec->next;
1435 //    }
1436 //
1437 //    if (ec) {
1438 //        sp_event_context_finish (ec);
1439 //        g_object_unref (G_OBJECT (ec));
1440 //    }
1441 // }
1443 /*
1444   Local Variables:
1445   mode:c++
1446   c-file-style:"stroustrup"
1447   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1448   indent-tabs-mode:nil
1449   fill-column:99
1450   End:
1451 */
1452 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :