Code

17ce0907c67e3c6ffc8b9d8cb761737f74248885
[inkscape.git] / src / desktop.cpp
1 #define __SP_DESKTOP_C__
3 /** \file
4  * Editable view implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   MenTaLguY <mental@rydia.net>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Ralf Stephan <ralf@ark.in-berlin.de>
11  *
12  * Copyright (C) 2004 MenTaLguY
13  * Copyright (C) 1999-2002 Lauris Kaplinski
14  * Copyright (C) 2000-2001 Ximian, Inc.
15  *
16  * Released under GNU GPL, read the file 'COPYING' for more information
17  */
19 /** \class SPDesktop
20  * SPDesktop is a subclass of View, implementing an editable document
21  * canvas.  It is extensively used by many UI controls that need certain
22  * visual representations of their own.
23  *
24  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
25  * layers of different control objects. The one containing the whole
26  * document is the drawing layer. In addition to it, there are grid,
27  * guide, sketch and control layers. The sketch layer is used for
28  * temporary drawing objects, before the real objects in document are
29  * created. The control layer contains editing knots, rubberband and
30  * similar non-document UI objects.
31  *
32  * Each SPDesktop is associated with a SPNamedView node of the document
33  * tree.  Currently, all desktops are created from a single main named
34  * view, but in the future there may be support for different ones.
35  * SPNamedView serves as an in-document container for desktop-related
36  * data, like grid and guideline placement, snapping options and so on.
37  *
38  * Associated with each SPDesktop are the two most important editing
39  * related objects - SPSelection and SPEventContext.
40  *
41  * Sodipodi keeps track of the active desktop and invokes notification
42  * signals whenever it changes. UI elements can use these to update their
43  * display to the selection of the currently active editing window.
44  * (Lauris Kaplinski)
45  */
47 #ifdef HAVE_CONFIG_H
48 # include "config.h"
49 #endif
51 #include <glibmm/i18n.h>
53 #include "macros.h"
54 #include "inkscape-private.h"
55 #include "desktop.h"
56 #include "desktop-events.h"
57 #include "desktop-handles.h"
58 #include "document.h"
59 #include "message-stack.h"
60 #include "selection.h"
61 #include "select-context.h"
62 #include "sp-namedview.h"
63 #include "color.h"
64 #include "sp-item-group.h"
65 #include "prefs-utils.h"
66 #include "object-hierarchy.h"
67 #include "helper/units.h"
68 #include "display/canvas-arena.h"
69 #include "display/nr-arena.h"
70 #include "display/gnome-canvas-acetate.h"
71 #include "display/sodipodi-ctrlrect.h"
72 #include "display/sp-canvas-util.h"
73 #include "libnr/nr-matrix-div.h"
74 #include "libnr/nr-rect-ops.h"
75 #include "ui/dialog/dialog-manager.h"
76 #include "xml/repr.h"
77 #include "message-context.h"
78 #include "layer-manager.h"
80 #ifdef WITH_INKBOARD
81 #include "jabber_whiteboard/session-manager.h"
82 #endif
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 (SPNamedView *nv, 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     g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_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         /* Construct SessionManager
249          *
250          * SessionManager construction needs to be done after document connection
251          */
252 #ifdef WITH_INKBOARD
253         _whiteboard_session_manager = new Inkscape::Whiteboard::SessionManager(this);
254 #endif
256 /* Set up notification of rebuilding the document, this allows
257        for saving object related settings in the document. */
258     _reconstruction_start_connection =
259         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
260     _reconstruction_finish_connection =
261         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
262     _reconstruction_old_layer_id = NULL;
264     // ?
265     // sp_active_desktop_set (desktop);
266     _inkscape = INKSCAPE;
268     _activate_connection = _activate_signal.connect(
269         sigc::bind(
270             sigc::ptr_fun(_onActivate),
271             this
272         )
273     );
274      _deactivate_connection = _deactivate_signal.connect(
275         sigc::bind(
276             sigc::ptr_fun(_onDeactivate),
277             this
278         )
279     );
281     _sel_modified_connection = selection->connectModified(
282         sigc::bind(
283             sigc::ptr_fun(&_onSelectionModified),
284             this
285         )
286     );
287     _sel_changed_connection = selection->connectChanged(
288         sigc::bind(
289             sigc::ptr_fun(&_onSelectionChanged),
290             this
291         )
292     );
295     /* setup LayerManager */
296     //   (Setting up after the connections are all in place, as it may use some of them)
297     layer_manager = new Inkscape::LayerManager( this );
301 void SPDesktop::destroy()
303     _activate_connection.disconnect();
304     _deactivate_connection.disconnect();
305     _sel_modified_connection.disconnect();
306     _sel_changed_connection.disconnect();
308     while (event_context) {
309         SPEventContext *ec = event_context;
310         event_context = ec->next;
311         sp_event_context_finish (ec);
312         g_object_unref (G_OBJECT (ec));
313     }
315     if (_layer_hierarchy) {
316         delete _layer_hierarchy;
317     }
319     if (_inkscape) {
320         _inkscape = NULL;
321     }
323     if (drawing) {
324         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
325         drawing = NULL;
326     }
328 #ifdef WITH_INKBOARD
329         if (_whiteboard_session_manager) {
330                 delete _whiteboard_session_manager;
331         }
332 #endif
334     delete _guides_message_context;
335     _guides_message_context = NULL;
337     sp_signal_disconnect_by_data (G_OBJECT (namedview), this);
339     g_list_free (zooms_past);
340     g_list_free (zooms_future);
343 SPDesktop::~SPDesktop() {}
345 //--------------------------------------------------------------------
346 /* Public methods */
348 void SPDesktop::setDisplayModeNormal()
350     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
351     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
352     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
355 void SPDesktop::setDisplayModeOutline()
357     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
358     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
359     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
362 /**
363  * Returns current root (=bottom) layer.
364  */
365 SPObject *SPDesktop::currentRoot() const
367     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
370 /**
371  * Returns current top layer.
372  */
373 SPObject *SPDesktop::currentLayer() const
375     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
378 /**
379  * Make \a object the top layer.
380  */
381 void SPDesktop::setCurrentLayer(SPObject *object) {
382     g_return_if_fail(SP_IS_GROUP(object));
383     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
384     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
385     _layer_hierarchy->setBottom(object);
388 /**
389  * Return layer that contains \a object.
390  */
391 SPObject *SPDesktop::layerForObject(SPObject *object) {
392     g_return_val_if_fail(object != NULL, NULL);
394     SPObject *root=currentRoot();
395     object = SP_OBJECT_PARENT(object);
396     while ( object && object != root && !isLayer(object) ) {
397         object = SP_OBJECT_PARENT(object);
398     }
399     return object;
402 /**
403  * True if object is a layer.
404  */
405 bool SPDesktop::isLayer(SPObject *object) const {
406     return ( SP_IS_GROUP(object)
407              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
408                   == SPGroup::LAYER ) );
411 /**
412  * True if desktop viewport fully contains \a item's bbox.
413  */
414 bool SPDesktop::isWithinViewport (SPItem *item) const
416     NR::Rect const viewport = get_display_area();
417     NR::Rect const bbox = sp_item_bbox_desktop(item);
418     return viewport.contains(bbox);
421 ///
422 bool SPDesktop::itemIsHidden(SPItem const *item) const {
423     return item->isHidden(this->dkey);
426 /**
427  * Set activate property of desktop; emit signal if changed.
428  */
429 void
430 SPDesktop::set_active (bool new_active)
432     if (new_active != _active) {
433         _active = new_active;
434         if (new_active) {
435             _activate_signal.emit();
436         } else {
437             _deactivate_signal.emit();
438         }
439     }
442 /**
443  * Set activate status of current desktop's named view.
444  */
445 void
446 SPDesktop::activate_guides(bool activate)
448     guides_active = activate;
449     namedview->activateGuides(this, activate);
452 /**
453  * Make desktop switch documents.
454  */
455 void
456 SPDesktop::change_document (SPDocument *theDocument)
458     g_return_if_fail (theDocument != NULL);
460     /* unselect everything before switching documents */
461     selection->clear();
463     setDocument (theDocument);
464     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
465     _document_replaced_signal.emit (this, theDocument);
468 /**
469  * Make desktop switch event contexts.
470  */
471 void
472 SPDesktop::set_event_context (GtkType type, const gchar *config)
474     SPEventContext *ec;
475     while (event_context) {
476         ec = event_context;
477         sp_event_context_deactivate (ec);
478         event_context = ec->next;
479         sp_event_context_finish (ec);
480         g_object_unref (G_OBJECT (ec));
481     }
483     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
484     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
485     ec->next = event_context;
486     event_context = ec;
487     sp_event_context_activate (ec);
488     _event_context_changed_signal.emit (this, ec);
491 /**
492  * Push event context onto desktop's context stack.
493  */
494 void
495 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
497     SPEventContext *ref, *ec;
498     Inkscape::XML::Node *repr;
500     if (event_context && event_context->key == key) return;
501     ref = event_context;
502     while (ref && ref->next && ref->next->key != key) ref = ref->next;
503     if (ref && ref->next) {
504         ec = ref->next;
505         ref->next = ec->next;
506         sp_event_context_finish (ec);
507         g_object_unref (G_OBJECT (ec));
508     }
510     if (event_context) sp_event_context_deactivate (event_context);
511     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
512     ec = sp_event_context_new (type, this, repr, key);
513     ec->next = event_context;
514     event_context = ec;
515     sp_event_context_activate (ec);
516     _event_context_changed_signal.emit (this, ec);
519 void
520 SPDesktop::set_coordinate_status (NR::Point p) {
521     _widget->setCoordinateStatus(p);
525 SPItem *
526 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
528     g_return_val_if_fail (doc() != NULL, NULL);
529     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
532 SPItem *
533 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
535     g_return_val_if_fail (doc() != NULL, NULL);
536     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
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 //----------------------------------------------------------------------
1028 // Callback implementations. The virtual ones are connected by the view.
1030 void
1031 SPDesktop::onPositionSet (double x, double y)
1033     _widget->viewSetPosition (NR::Point(x,y));
1036 void
1037 SPDesktop::onResized (double x, double y)
1039    // Nothing called here
1042 /**
1043  * Redraw callback; queues Gtk redraw; connected by View.
1044  */
1045 void
1046 SPDesktop::onRedrawRequested ()
1048     if (main) {
1049         _widget->requestCanvasUpdate();
1050     }
1053 /**
1054  * Associate document with desktop.
1055  */
1056 void
1057 SPDesktop::setDocument (SPDocument *doc)
1059     if (this->doc() && doc) {
1060         namedview->hide(this);
1061         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1062     }
1064     if (_layer_hierarchy) {
1065         _layer_hierarchy->clear();
1066         delete _layer_hierarchy;
1067     }
1068     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1069     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1070     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1071     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1072     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1074     /// \todo fixme: This condition exists to make sure the code
1075     /// inside is called only once on initialization. But there
1076     /// are surely more safe methods to accomplish this.
1077     if (drawing) {
1078         NRArenaItem *ai;
1080         namedview = sp_document_namedview (doc, NULL);
1081         g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1082         number = namedview->getViewCount();
1084         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1085                 SP_CANVAS_ARENA (drawing)->arena,
1086                 dkey,
1087                 SP_ITEM_SHOW_DISPLAY);
1088         if (ai) {
1089             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1090             nr_arena_item_unref (ai);
1091         }
1092         namedview->show(this);
1093         /* Ugly hack */
1094         activate_guides (true);
1095         /* Ugly hack */
1096         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1097     }
1099     _document_replaced_signal.emit (this, doc);
1101     View::setDocument (doc);
1104 void
1105 SPDesktop::onStatusMessage
1106 (Inkscape::MessageType type, gchar const *message)
1108     if (_widget) {
1109         _widget->setMessage(type, message);
1110     }
1113 void
1114 SPDesktop::onDocumentURISet (gchar const* uri)
1116     _widget->setTitle(uri);
1119 /**
1120  * Resized callback.
1121  */
1122 void
1123 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1125     _doc2dt[5] = height;
1126     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1127     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1128     SP_CTRLRECT(page)->setRectangle(a);
1129     SP_CTRLRECT(page_border)->setRectangle(a);
1133 void
1134 SPDesktop::_onActivate (SPDesktop* dt)
1136     if (!dt->_widget) return;
1137     dt->_widget->activateDesktop();
1140 void
1141 SPDesktop::_onDeactivate (SPDesktop* dt)
1143     if (!dt->_widget) return;
1144     dt->_widget->deactivateDesktop();
1147 void
1148 SPDesktop::_onSelectionModified
1149 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1151     if (!dt->_widget) return;
1152     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1155 static void
1156 _onSelectionChanged
1157 (Inkscape::Selection *selection, SPDesktop *desktop)
1159     /** \todo
1160      * only change the layer for single selections, or what?
1161      * This seems reasonable -- for multiple selections there can be many
1162      * different layers involved.
1163      */
1164     SPItem *item=selection->singleItem();
1165     if (item) {
1166         SPObject *layer=desktop->layerForObject(item);
1167         if ( layer && layer != desktop->currentLayer() ) {
1168             desktop->setCurrentLayer(layer);
1169         }
1170     }
1173 /**
1174  * Calls event handler of current event context.
1175  * \param arena Unused
1176  * \todo fixme
1177  */
1178 static gint
1179 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1181     if (ai) {
1182         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1183         return sp_event_context_item_handler (desktop->event_context, spi, event);
1184     } else {
1185         return sp_event_context_root_handler (desktop->event_context, event);
1186     }
1189 static void
1190 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1191     g_return_if_fail(SP_IS_GROUP(layer));
1192     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1195 /// Callback
1196 static void
1197 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1198     g_return_if_fail(SP_IS_GROUP(layer));
1199     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1202 /// Callback
1203 static void
1204 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1205                                          SPDesktop *desktop)
1207     desktop->_layer_changed_signal.emit (bottom);
1210 /// Called when document is starting to be rebuilt.
1211 static void
1212 _reconstruction_start (SPDesktop * desktop)
1214     // printf("Desktop, starting reconstruction\n");
1215     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1216     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1218     /*
1219     GSList const * selection_objs = desktop->selection->list();
1220     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1222     }
1223     */
1224     desktop->selection->clear();
1226     // printf("Desktop, starting reconstruction end\n");
1229 /// Called when document rebuild is finished.
1230 static void
1231 _reconstruction_finish (SPDesktop * desktop)
1233     // printf("Desktop, finishing reconstruction\n");
1234     if (desktop->_reconstruction_old_layer_id == NULL)
1235         return;
1237     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1238     if (newLayer != NULL)
1239         desktop->setCurrentLayer(newLayer);
1241     g_free(desktop->_reconstruction_old_layer_id);
1242     desktop->_reconstruction_old_layer_id = NULL;
1243     // printf("Desktop, finishing reconstruction end\n");
1244     return;
1247 /**
1248  * Namedview_modified callback.
1249  */
1250 static void
1251 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1253     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1255         /* Recalculate snap distances */
1256         /* FIXME: why is the desktop getting involved in setting up something
1257         ** that is entirely to do with the namedview?
1258         */
1259         _update_snap_distances (desktop);
1261         /* Show/hide page background */
1262         if (nv->pagecolor & 0xff) {
1263             sp_canvas_item_show (desktop->table);
1264             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1265             sp_canvas_item_move_to_z (desktop->table, 0);
1266         } else {
1267             sp_canvas_item_hide (desktop->table);
1268         }
1270         /* Show/hide page border */
1271         if (nv->showborder) {
1272             // show
1273             sp_canvas_item_show (desktop->page_border);
1274             // set color and shadow
1275             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1276             if (nv->pageshadow) {
1277                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1278             }
1279             // place in the z-order stack
1280             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1281                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1282             } else {
1283                 int order = sp_canvas_item_order (desktop->page_border);
1284                 int morder = sp_canvas_item_order (desktop->drawing);
1285                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1286                                     morder - order);
1287             }
1288         } else {
1289                 sp_canvas_item_hide (desktop->page_border);
1290                 if (nv->pageshadow) {
1291                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1292                 }
1293         }
1294         
1295         /* Show/hide page shadow */
1296         if (nv->showpageshadow && nv->pageshadow) {
1297             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1298         } else {
1299             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1300         }
1302         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1303             (SP_RGBA32_R_U(nv->pagecolor) +
1304              SP_RGBA32_G_U(nv->pagecolor) +
1305              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1306             // the background color is light or transparent, use black outline
1307             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1308         } else { // use white outline
1309             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1310         }
1311     }
1314 /**
1315  * Callback to reset snapper's distances.
1316  */
1317 static void
1318 _update_snap_distances (SPDesktop *desktop)
1320     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1322     SPNamedView &nv = *desktop->namedview;
1324     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1325                                                                       *nv.gridtoleranceunit,
1326                                                                       px));
1327     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1328                                                                        *nv.guidetoleranceunit,
1329                                                                        px));
1330     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1331                                                                         *nv.objecttoleranceunit,
1332                                                                         px));
1336 NR::Matrix SPDesktop::w2d() const
1338     return _w2d;
1341 NR::Point SPDesktop::w2d(NR::Point const &p) const
1343     return p * _w2d;
1346 NR::Point SPDesktop::d2w(NR::Point const &p) const
1348     return p * _d2w;
1351 NR::Matrix SPDesktop::doc2dt() const
1353     return _doc2dt;
1356 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1358     return p * _doc2dt;
1361 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1363     return p / _doc2dt;
1367 /**
1368  * Pop event context from desktop's context stack. Never used.
1369  */
1370 // void
1371 // SPDesktop::pop_event_context (unsigned int key)
1372 // {
1373 //    SPEventContext *ec = NULL;
1374 //
1375 //    if (event_context && event_context->key == key) {
1376 //        g_return_if_fail (event_context);
1377 //        g_return_if_fail (event_context->next);
1378 //        ec = event_context;
1379 //        sp_event_context_deactivate (ec);
1380 //        event_context = ec->next;
1381 //        sp_event_context_activate (event_context);
1382 //        _event_context_changed_signal.emit (this, ec);
1383 //    }
1384 //
1385 //    SPEventContext *ref = event_context;
1386 //    while (ref && ref->next && ref->next->key != key)
1387 //        ref = ref->next;
1388 //
1389 //    if (ref && ref->next) {
1390 //        ec = ref->next;
1391 //        ref->next = ec->next;
1392 //    }
1393 //
1394 //    if (ec) {
1395 //        sp_event_context_finish (ec);
1396 //        g_object_unref (G_OBJECT (ec));
1397 //    }
1398 // }
1400 /*
1401   Local Variables:
1402   mode:c++
1403   c-file-style:"stroustrup"
1404   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1405   indent-tabs-mode:nil
1406   fill-column:99
1407   End:
1408 */
1409 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :