Code

05d20b31b9ab10652c910f4d5606512e01816a97
[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"
79 #ifdef WITH_INKBOARD
80 #include "jabber_whiteboard/session-manager.h"
81 #endif
83 namespace Inkscape { namespace XML { class Node; }}
85 // Callback declarations
86 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
87 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
88 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
89 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
90 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
91 static void _reconstruction_start(SPDesktop * desktop);
92 static void _reconstruction_finish(SPDesktop * desktop);
93 static void _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop);
94 static void _update_snap_distances (SPDesktop *desktop);
96 /**
97  * Return new desktop object.
98  * \pre namedview != NULL.
99  * \pre canvas != NULL.
100  */
101 SPDesktop::SPDesktop()
103     _dlg_mgr = NULL;
104     _widget = 0;
105     namedview = NULL;
106     selection = NULL;
107     acetate = NULL;
108     main = NULL;
109     grid = NULL;
110     guides = NULL;
111     drawing = NULL;
112     sketch = NULL;
113     controls = NULL;
114     event_context = 0;
116     _d2w.set_identity();
117     _w2d.set_identity();
118     _doc2dt = NR::Matrix(NR::scale(1, -1));
120     guides_active = false;
122     zooms_past = NULL;
123     zooms_future = NULL;
125     is_fullscreen = false;
127     gr_item = NULL;
128     gr_point_num = 0;
129     gr_fill_or_stroke = true;
131     _layer_hierarchy = NULL;
132     _active = false;
134     selection = Inkscape::GC::release (new Inkscape::Selection (this));
137 void
138 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
141     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
143     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
145     namedview = nv;
146     canvas = aCanvas;
148     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
149     /* Kill flicker */
150     sp_document_ensure_up_to_date (document);
152     /* Setup Dialog Manager */
153     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
155     dkey = sp_item_display_key_new (1);
157     /* Connect document */
158     setDocument (document);
160     number = namedview->getViewCount();
163     /* Setup Canvas */
164     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
166     SPCanvasGroup *root = sp_canvas_root (canvas);
168     /* Setup adminstrative layers */
169     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
170     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
171     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
172     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
174     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
175     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
176     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
177     sp_canvas_item_move_to_z (table, 0);
179     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
180     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
181     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
183     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
184     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
186     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
188     // Start always in normal mode
189     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
190     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
192     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
193     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
194     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
197     /* Push select tool to the bottom of stack */
198     /** \todo
199      * FIXME: this is the only call to this.  Everything else seems to just
200      * call "set" instead of "push".  Can we assume that there is only one
201      * context ever?
202      */
203     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
205     // display rect and zoom are now handled in sp_desktop_widget_realize()
207     NR::Rect const d(NR::Point(0.0, 0.0),
208                      NR::Point(sp_document_width(document), sp_document_height(document)));
210     SP_CTRLRECT(page)->setRectangle(d);
211     SP_CTRLRECT(page_border)->setRectangle(d);
213     /* the following sets the page shadow on the canvas
214        It was originally set to 5, which is really cheesy!
215        It now is an attribute in the document's namedview. If a value of
216        0 is used, then the constructor for a shadow is not initialized.
217     */
219     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
220         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
221     }
224     /* Connect event for page resize */
225     _doc2dt[5] = sp_document_height (document);
226     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
228     g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
231     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
232             SP_CANVAS_ARENA (drawing)->arena,
233             dkey,
234             SP_ITEM_SHOW_DISPLAY);
235     if (ai) {
236         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
237         nr_arena_item_unref (ai);
238     }
240     namedview->show(this);
241     /* Ugly hack */
242     activate_guides (true);
243     /* Ugly hack */
244     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
246         /* Construct SessionManager
247          *
248          * SessionManager construction needs to be done after document connection
249          */
250 #ifdef WITH_INKBOARD
251         _whiteboard_session_manager = new Inkscape::Whiteboard::SessionManager(this);
252 #endif
254 /* Set up notification of rebuilding the document, this allows
255        for saving object related settings in the document. */
256     _reconstruction_start_connection =
257         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
258     _reconstruction_finish_connection =
259         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
260     _reconstruction_old_layer_id = NULL;
262     // ?
263     // sp_active_desktop_set (desktop);
264     _inkscape = INKSCAPE;
266     _activate_connection = _activate_signal.connect(
267         sigc::bind(
268             sigc::ptr_fun(_onActivate),
269             this
270         )
271     );
272      _deactivate_connection = _deactivate_signal.connect(
273         sigc::bind(
274             sigc::ptr_fun(_onDeactivate),
275             this
276         )
277     );
279     _sel_modified_connection = selection->connectModified(
280         sigc::bind(
281             sigc::ptr_fun(&_onSelectionModified),
282             this
283         )
284     );
285     _sel_changed_connection = selection->connectChanged(
286         sigc::bind(
287             sigc::ptr_fun(&_onSelectionChanged),
288             this
289         )
290     );
295 void SPDesktop::destroy()
297     _activate_connection.disconnect();
298     _deactivate_connection.disconnect();
299     _sel_modified_connection.disconnect();
300     _sel_changed_connection.disconnect();
302     while (event_context) {
303         SPEventContext *ec = event_context;
304         event_context = ec->next;
305         sp_event_context_finish (ec);
306         g_object_unref (G_OBJECT (ec));
307     }
309     if (_layer_hierarchy) {
310         delete _layer_hierarchy;
311     }
313     if (_inkscape) {
314         _inkscape = NULL;
315     }
317     if (drawing) {
318         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
319         drawing = NULL;
320     }
322 #ifdef WITH_INKBOARD
323         if (_whiteboard_session_manager) {
324                 delete _whiteboard_session_manager;
325         }
326 #endif
328     delete _guides_message_context;
329     _guides_message_context = NULL;
331     sp_signal_disconnect_by_data (G_OBJECT (namedview), this);
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  * Make \a object the top layer.
374  */
375 void SPDesktop::setCurrentLayer(SPObject *object) {
376     g_return_if_fail(SP_IS_GROUP(object));
377     g_return_if_fail( currentRoot() == object || currentRoot()->isAncestorOf(object));
378     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
379     _layer_hierarchy->setBottom(object);
382 /**
383  * Return layer that contains \a object.
384  */
385 SPObject *SPDesktop::layerForObject(SPObject *object) {
386     g_return_val_if_fail(object != NULL, NULL);
388     SPObject *root=currentRoot();
389     object = SP_OBJECT_PARENT(object);
390     while ( object && object != root && !isLayer(object) ) {
391         object = SP_OBJECT_PARENT(object);
392     }
393     return object;
396 /**
397  * True if object is a layer.
398  */
399 bool SPDesktop::isLayer(SPObject *object) const {
400     return ( SP_IS_GROUP(object)
401              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
402                   == SPGroup::LAYER ) );
405 /**
406  * True if desktop viewport fully contains \a item's bbox.
407  */
408 bool SPDesktop::isWithinViewport (SPItem *item) const
410     NR::Rect const viewport = get_display_area();
411     NR::Rect const bbox = sp_item_bbox_desktop(item);
412     return viewport.contains(bbox);
415 ///
416 bool SPDesktop::itemIsHidden(SPItem const *item) const {
417     return item->isHidden(this->dkey);
420 /**
421  * Set activate property of desktop; emit signal if changed.
422  */
423 void
424 SPDesktop::set_active (bool new_active)
426     if (new_active != _active) {
427         _active = new_active;
428         if (new_active) {
429             _activate_signal.emit();
430         } else {
431             _deactivate_signal.emit();
432         }
433     }
436 /**
437  * Set activate status of current desktop's named view.
438  */
439 void
440 SPDesktop::activate_guides(bool activate)
442     guides_active = activate;
443     namedview->activateGuides(this, activate);
446 /**
447  * Make desktop switch documents.
448  */
449 void
450 SPDesktop::change_document (SPDocument *theDocument)
452     g_return_if_fail (theDocument != NULL);
454     /* unselect everything before switching documents */
455     selection->clear();
457     setDocument (theDocument);
458     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
459     _document_replaced_signal.emit (this, theDocument);
462 /**
463  * Make desktop switch event contexts.
464  */
465 void
466 SPDesktop::set_event_context (GtkType type, const gchar *config)
468     SPEventContext *ec;
469     while (event_context) {
470         ec = event_context;
471         sp_event_context_deactivate (ec);
472         event_context = ec->next;
473         sp_event_context_finish (ec);
474         g_object_unref (G_OBJECT (ec));
475     }
477     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
478     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
479     ec->next = event_context;
480     event_context = ec;
481     sp_event_context_activate (ec);
482     _event_context_changed_signal.emit (this, ec);
485 /**
486  * Push event context onto desktop's context stack.
487  */
488 void
489 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
491     SPEventContext *ref, *ec;
492     Inkscape::XML::Node *repr;
494     if (event_context && event_context->key == key) return;
495     ref = event_context;
496     while (ref && ref->next && ref->next->key != key) ref = ref->next;
497     if (ref && ref->next) {
498         ec = ref->next;
499         ref->next = ec->next;
500         sp_event_context_finish (ec);
501         g_object_unref (G_OBJECT (ec));
502     }
504     if (event_context) sp_event_context_deactivate (event_context);
505     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
506     ec = sp_event_context_new (type, this, repr, key);
507     ec->next = event_context;
508     event_context = ec;
509     sp_event_context_activate (ec);
510     _event_context_changed_signal.emit (this, ec);
513 void
514 SPDesktop::set_coordinate_status (NR::Point p) {
515     _widget->setCoordinateStatus(p);
519 SPItem *
520 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
522     g_return_val_if_fail (doc() != NULL, NULL);
523     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
526 SPItem *
527 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
529     g_return_val_if_fail (doc() != NULL, NULL);
530     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
533 SPItem *
534 SPDesktop::group_at_point (NR::Point const p) const
536     g_return_val_if_fail (doc() != NULL, NULL);
537     return sp_document_group_at_point (doc(), dkey, p);
540 /**
541  * \brief  Returns the mouse point in document coordinates; if mouse is
542  * outside the canvas, returns the center of canvas viewpoint
543  */
544 NR::Point
545 SPDesktop::point() const
547     NR::Point p = _widget->getPointer();
548     NR::Point pw = sp_canvas_window_to_world (canvas, p);
549     p = w2d(pw);
551     NR::Rect const r = canvas->getViewbox();
553     NR::Point r0 = w2d(r.min());
554     NR::Point r1 = w2d(r.max());
556     if (p[NR::X] >= r0[NR::X] &&
557         p[NR::X] <= r1[NR::X] &&
558         p[NR::Y] >= r1[NR::Y] &&
559         p[NR::Y] <= r0[NR::Y])
560     {
561         return p;
562     } else {
563         return (r0 + r1) / 2;
564     }
567 /**
568  * Put current zoom data in history list.
569  */
570 void
571 SPDesktop::push_current_zoom (GList **history)
573     NR::Rect const area = get_display_area();
575     NRRect *old_zoom = g_new(NRRect, 1);
576     old_zoom->x0 = area.min()[NR::X];
577     old_zoom->x1 = area.max()[NR::X];
578     old_zoom->y0 = area.min()[NR::Y];
579     old_zoom->y1 = area.max()[NR::Y];
580     if ( *history == NULL
581          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
582                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
583                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
584                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
585     {
586         *history = g_list_prepend (*history, old_zoom);
587     }
590 /**
591  * Set viewbox.
592  */
593 void
594 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
596     g_assert(_widget);
598     // save the zoom
599     if (log) {
600         push_current_zoom(&zooms_past);
601         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
602         g_list_free (zooms_future);
603         zooms_future = NULL;
604     }
606     double const cx = 0.5 * (x0 + x1);
607     double const cy = 0.5 * (y0 + y1);
609     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
611     double scale = expansion(_d2w);
612     double newscale;
613     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
614         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
615     } else {
616         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
617     }
619     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
621     int clear = FALSE;
622     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
623         /* Set zoom factors */
624         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
625         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
626         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
627         clear = TRUE;
628     }
630     /* Calculate top left corner */
631     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
632     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
634     /* Scroll */
635     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
637     _widget->updateRulers();
638     _widget->updateScrollbars(expansion(_d2w));
639     _widget->updateZoom();
642 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
644     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
647 /**
648  * Return viewbox dimensions.
649  */
650 NR::Rect SPDesktop::get_display_area() const
652     NR::Rect const viewbox = canvas->getViewbox();
654     double const scale = _d2w[0];
656     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
657                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
660 /**
661  * Revert back to previous zoom if possible.
662  */
663 void
664 SPDesktop::prev_zoom()
666     if (zooms_past == NULL) {
667         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
668         return;
669     }
671     // push current zoom into forward zooms list
672     push_current_zoom (&zooms_future);
674     // restore previous zoom
675     set_display_area (((NRRect *) zooms_past->data)->x0,
676             ((NRRect *) zooms_past->data)->y0,
677             ((NRRect *) zooms_past->data)->x1,
678             ((NRRect *) zooms_past->data)->y1,
679             0, false);
681     // remove the just-added zoom from the past zooms list
682     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
685 /**
686  * Set zoom to next in list.
687  */
688 void
689 SPDesktop::next_zoom()
691     if (zooms_future == NULL) {
692         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
693         return;
694     }
696     // push current zoom into past zooms list
697     push_current_zoom (&zooms_past);
699     // restore next zoom
700     set_display_area (((NRRect *) zooms_future->data)->x0,
701             ((NRRect *) zooms_future->data)->y0,
702             ((NRRect *) zooms_future->data)->x1,
703             ((NRRect *) zooms_future->data)->y1,
704             0, false);
706     // remove the just-used zoom from the zooms_future list
707     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
710 /**
711  * Zoom to point with absolute zoom factor.
712  */
713 void
714 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
716     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
718     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
719     // this check prevents "sliding" when trying to zoom in at maximum zoom;
720     /// \todo someone please fix calculations properly and remove this hack
721     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
722         return;
724     NR::Rect const viewbox = canvas->getViewbox();
726     double const width2 = viewbox.dimensions()[NR::X] / zoom;
727     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
729     set_display_area(cx - px * width2,
730                      cy - py * height2,
731                      cx + (1 - px) * width2,
732                      cy + (1 - py) * height2,
733                      0.0);
736 /**
737  * Zoom to center with absolute zoom factor.
738  */
739 void
740 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
742     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
745 /**
746  * Zoom to point with relative zoom factor.
747  */
748 void
749 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
751     NR::Rect const area = get_display_area();
753     if (cx < area.min()[NR::X]) {
754         cx = area.min()[NR::X];
755     }
756     if (cx > area.max()[NR::X]) {
757         cx = area.max()[NR::X];
758     }
759     if (cy < area.min()[NR::Y]) {
760         cy = area.min()[NR::Y];
761     }
762     if (cy > area.max()[NR::Y]) {
763         cy = area.max()[NR::Y];
764     }
766     gdouble const scale = expansion(_d2w) * zoom;
767     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
768     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
770     zoom_absolute_keep_point(cx, cy, px, py, scale);
773 /**
774  * Zoom to center with relative zoom factor.
775  */
776 void
777 SPDesktop::zoom_relative (double cx, double cy, double zoom)
779     gdouble scale = expansion(_d2w) * zoom;
780     zoom_absolute (cx, cy, scale);
783 /**
784  * Set display area to origin and current document dimensions.
785  */
786 void
787 SPDesktop::zoom_page()
789     NR::Rect d(NR::Point(0, 0),
790                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
792     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
793         return;
794     }
796     set_display_area(d, 10);
799 /**
800  * Set display area to current document width.
801  */
802 void
803 SPDesktop::zoom_page_width()
805     NR::Rect const a = get_display_area();
807     if (sp_document_width(doc()) < 1.0) {
808         return;
809     }
811     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
812                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
814     set_display_area(d, 10);
817 /**
818  * Zoom to selection.
819  */
820 void
821 SPDesktop::zoom_selection()
823     NR::Rect const d = selection->bounds();
825     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
826         return;
827     }
829     set_display_area(d, 10);
832 /**
833  * Tell widget to let zoom widget grab keyboard focus.
834  */
835 void
836 SPDesktop::zoom_grab_focus()
838     _widget->letZoomGrabFocus();
841 /**
842  * Zoom to whole drawing.
843  */
844 void
845 SPDesktop::zoom_drawing()
847     g_return_if_fail (doc() != NULL);
848     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
849     g_return_if_fail (docitem != NULL);
851     NR::Rect d = sp_item_bbox_desktop(docitem);
853     /* Note that the second condition here indicates that
854     ** there are no items in the drawing.
855     */
856     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
857         return;
858     }
860     set_display_area(d, 10);
863 /**
864  * Scroll canvas by specific coordinate amount.
865  */
866 void
867 SPDesktop::scroll_world (double dx, double dy)
869     g_assert(_widget);
871     NR::Rect const viewbox = canvas->getViewbox();
873     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
875     _widget->updateRulers();
876     _widget->updateScrollbars(expansion(_d2w));
879 bool
880 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
882     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
884     // autoscrolldistance is in screen pixels, but the display area is in document units
885     autoscrolldistance /= expansion(_d2w);
886     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
888     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
889         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
891         NR::Point const s_w( (*p) * _d2w );
893         gdouble x_to;
894         if ((*p)[NR::X] < dbox.min()[NR::X])
895             x_to = dbox.min()[NR::X];
896         else if ((*p)[NR::X] > dbox.max()[NR::X])
897             x_to = dbox.max()[NR::X];
898         else
899             x_to = (*p)[NR::X];
901         gdouble y_to;
902         if ((*p)[NR::Y] < dbox.min()[NR::Y])
903             y_to = dbox.min()[NR::Y];
904         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
905             y_to = dbox.max()[NR::Y];
906         else
907             y_to = (*p)[NR::Y];
909         NR::Point const d_dt(x_to, y_to);
910         NR::Point const d_w( d_dt * _d2w );
911         NR::Point const moved_w( d_w - s_w );
913         if (autoscrollspeed == 0)
914             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
916         if (autoscrollspeed != 0)
917             scroll_world (autoscrollspeed * moved_w);
919         return true;
920     }
921     return false;
924 void
925 SPDesktop::fullscreen()
927     _widget->setFullscreen();
930 void
931 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
933     _widget->getGeometry (x, y, w, h);
936 void
937 SPDesktop::setWindowPosition (NR::Point p)
939     _widget->setPosition (p);
942 void
943 SPDesktop::setWindowSize (gint w, gint h)
945     _widget->setSize (w, h);
948 void
949 SPDesktop::setWindowTransient (void *p, int transient_policy)
951     _widget->setTransient (p, transient_policy);
954 void
955 SPDesktop::presentWindow()
957     _widget->present();
960 bool
961 SPDesktop::warnDialog (gchar *text)
963     return _widget->warnDialog (text);
966 void
967 SPDesktop::toggleRulers()
969     _widget->toggleRulers();
972 void
973 SPDesktop::toggleScrollbars()
975     _widget->toggleScrollbars();
978 void
979 SPDesktop::layoutWidget()
981     _widget->layout();
984 void
985 SPDesktop::destroyWidget()
987     _widget->destroy();
990 bool
991 SPDesktop::shutdown()
993     return _widget->shutdown();
996 void
997 SPDesktop::setToolboxFocusTo (gchar const *label)
999     _widget->setToolboxFocusTo (label);
1002 void
1003 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1005     _widget->setToolboxAdjustmentValue (id, val);
1008 bool
1009 SPDesktop::isToolboxButtonActive (gchar const *id)
1011     return _widget->isToolboxButtonActive (id);
1014 void
1015 SPDesktop::emitToolSubselectionChanged(gpointer data)
1017         _tool_subselection_changed.emit(data);
1018         inkscape_subselection_changed (this);
1021 //----------------------------------------------------------------------
1022 // Callback implementations. The virtual ones are connected by the view.
1024 void
1025 SPDesktop::onPositionSet (double x, double y)
1027     _widget->viewSetPosition (NR::Point(x,y));
1030 void
1031 SPDesktop::onResized (double x, double y)
1033    // Nothing called here
1036 /**
1037  * Redraw callback; queues Gtk redraw; connected by View.
1038  */
1039 void
1040 SPDesktop::onRedrawRequested ()
1042     if (main) {
1043         _widget->requestCanvasUpdate();
1044     }
1047 /**
1048  * Associate document with desktop.
1049  */
1050 void
1051 SPDesktop::setDocument (SPDocument *doc)
1053     if (this->doc() && doc) {
1054         namedview->hide(this);
1055         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1056     }
1058     if (_layer_hierarchy) {
1059         _layer_hierarchy->clear();
1060         delete _layer_hierarchy;
1061     }
1062     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1063     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1064     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1065     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1066     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1068     /// \todo fixme: This condition exists to make sure the code
1069     /// inside is called only once on initialization. But there
1070     /// are surely more safe methods to accomplish this.
1071     if (drawing) {
1072         NRArenaItem *ai;
1074         namedview = sp_document_namedview (doc, NULL);
1075         g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1076         number = namedview->getViewCount();
1078         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1079                 SP_CANVAS_ARENA (drawing)->arena,
1080                 dkey,
1081                 SP_ITEM_SHOW_DISPLAY);
1082         if (ai) {
1083             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1084             nr_arena_item_unref (ai);
1085         }
1086         namedview->show(this);
1087         /* Ugly hack */
1088         activate_guides (true);
1089         /* Ugly hack */
1090         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1091     }
1093     _document_replaced_signal.emit (this, doc);
1095     View::setDocument (doc);
1098 void
1099 SPDesktop::onStatusMessage
1100 (Inkscape::MessageType type, gchar const *message)
1102     if (_widget) {
1103         _widget->setMessage(type, message);
1104     }
1107 void
1108 SPDesktop::onDocumentURISet (gchar const* uri)
1110     _widget->setTitle(uri);
1113 /**
1114  * Resized callback.
1115  */
1116 void
1117 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1119     _doc2dt[5] = height;
1120     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1121     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1122     SP_CTRLRECT(page)->setRectangle(a);
1123     SP_CTRLRECT(page_border)->setRectangle(a);
1127 void
1128 SPDesktop::_onActivate (SPDesktop* dt)
1130     if (!dt->_widget) return;
1131     dt->_widget->activateDesktop();
1134 void
1135 SPDesktop::_onDeactivate (SPDesktop* dt)
1137     if (!dt->_widget) return;
1138     dt->_widget->deactivateDesktop();
1141 void
1142 SPDesktop::_onSelectionModified
1143 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1145     if (!dt->_widget) return;
1146     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1149 static void
1150 _onSelectionChanged
1151 (Inkscape::Selection *selection, SPDesktop *desktop)
1153     /** \todo
1154      * only change the layer for single selections, or what?
1155      * This seems reasonable -- for multiple selections there can be many
1156      * different layers involved.
1157      */
1158     SPItem *item=selection->singleItem();
1159     if (item) {
1160         SPObject *layer=desktop->layerForObject(item);
1161         if ( layer && layer != desktop->currentLayer() ) {
1162             desktop->setCurrentLayer(layer);
1163         }
1164     }
1167 /**
1168  * Calls event handler of current event context.
1169  * \param arena Unused
1170  * \todo fixme
1171  */
1172 static gint
1173 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1175     if (ai) {
1176         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1177         return sp_event_context_item_handler (desktop->event_context, spi, event);
1178     } else {
1179         return sp_event_context_root_handler (desktop->event_context, event);
1180     }
1183 static void
1184 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1185     g_return_if_fail(SP_IS_GROUP(layer));
1186     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1189 /// Callback
1190 static void
1191 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1192     g_return_if_fail(SP_IS_GROUP(layer));
1193     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1196 /// Callback
1197 static void
1198 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1199                                          SPDesktop *desktop)
1201     desktop->_layer_changed_signal.emit (bottom);
1204 /// Called when document is starting to be rebuilt.
1205 static void
1206 _reconstruction_start (SPDesktop * desktop)
1208     // printf("Desktop, starting reconstruction\n");
1209     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1210     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1212     /*
1213     GSList const * selection_objs = desktop->selection->list();
1214     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1216     }
1217     */
1218     desktop->selection->clear();
1220     // printf("Desktop, starting reconstruction end\n");
1223 /// Called when document rebuild is finished.
1224 static void
1225 _reconstruction_finish (SPDesktop * desktop)
1227     // printf("Desktop, finishing reconstruction\n");
1228     if (desktop->_reconstruction_old_layer_id == NULL)
1229         return;
1231     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1232     if (newLayer != NULL)
1233         desktop->setCurrentLayer(newLayer);
1235     g_free(desktop->_reconstruction_old_layer_id);
1236     desktop->_reconstruction_old_layer_id = NULL;
1237     // printf("Desktop, finishing reconstruction end\n");
1238     return;
1241 /**
1242  * Namedview_modified callback.
1243  */
1244 static void
1245 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1247     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1249         /* Recalculate snap distances */
1250         /* FIXME: why is the desktop getting involved in setting up something
1251         ** that is entirely to do with the namedview?
1252         */
1253         _update_snap_distances (desktop);
1255         /* Show/hide page background */
1256         if (nv->pagecolor & 0xff) {
1257             sp_canvas_item_show (desktop->table);
1258             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1259             sp_canvas_item_move_to_z (desktop->table, 0);
1260         } else {
1261             sp_canvas_item_hide (desktop->table);
1262         }
1264         /* Show/hide page border */
1265         if (nv->showborder) {
1266             // show
1267             sp_canvas_item_show (desktop->page_border);
1268             // set color and shadow
1269             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1270             if (nv->pageshadow) {
1271                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1272             }
1273             // place in the z-order stack
1274             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1275                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1276             } else {
1277                 int order = sp_canvas_item_order (desktop->page_border);
1278                 int morder = sp_canvas_item_order (desktop->drawing);
1279                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1280                                     morder - order);
1281             }
1282         } else {
1283                 sp_canvas_item_hide (desktop->page_border);
1284                 if (nv->pageshadow) {
1285                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1286                 }
1287         }
1288         
1289         /* Show/hide page shadow */
1290         if (nv->showpageshadow && nv->pageshadow) {
1291             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1292         } else {
1293             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1294         }
1296         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1297             (SP_RGBA32_R_U(nv->pagecolor) +
1298              SP_RGBA32_G_U(nv->pagecolor) +
1299              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1300             // the background color is light or transparent, use black outline
1301             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1302         } else { // use white outline
1303             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1304         }
1305     }
1308 /**
1309  * Callback to reset snapper's distances.
1310  */
1311 static void
1312 _update_snap_distances (SPDesktop *desktop)
1314     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1316     SPNamedView &nv = *desktop->namedview;
1318     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1319                                                                       *nv.gridtoleranceunit,
1320                                                                       px));
1321     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1322                                                                        *nv.guidetoleranceunit,
1323                                                                        px));
1324     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1325                                                                         *nv.objecttoleranceunit,
1326                                                                         px));
1330 NR::Matrix SPDesktop::w2d() const
1332     return _w2d;
1335 NR::Point SPDesktop::w2d(NR::Point const &p) const
1337     return p * _w2d;
1340 NR::Point SPDesktop::d2w(NR::Point const &p) const
1342     return p * _d2w;
1345 NR::Matrix SPDesktop::doc2dt() const
1347     return _doc2dt;
1350 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1352     return p * _doc2dt;
1355 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1357     return p / _doc2dt;
1361 /**
1362  * Pop event context from desktop's context stack. Never used.
1363  */
1364 // void
1365 // SPDesktop::pop_event_context (unsigned int key)
1366 // {
1367 //    SPEventContext *ec = NULL;
1368 //
1369 //    if (event_context && event_context->key == key) {
1370 //        g_return_if_fail (event_context);
1371 //        g_return_if_fail (event_context->next);
1372 //        ec = event_context;
1373 //        sp_event_context_deactivate (ec);
1374 //        event_context = ec->next;
1375 //        sp_event_context_activate (event_context);
1376 //        _event_context_changed_signal.emit (this, ec);
1377 //    }
1378 //
1379 //    SPEventContext *ref = event_context;
1380 //    while (ref && ref->next && ref->next->key != key)
1381 //        ref = ref->next;
1382 //
1383 //    if (ref && ref->next) {
1384 //        ec = ref->next;
1385 //        ref->next = ec->next;
1386 //    }
1387 //
1388 //    if (ec) {
1389 //        sp_event_context_finish (ec);
1390 //        g_object_unref (G_OBJECT (ec));
1391 //    }
1392 // }
1394 /*
1395   Local Variables:
1396   mode:c++
1397   c-file-style:"stroustrup"
1398   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1399   indent-tabs-mode:nil
1400   fill-column:99
1401   End:
1402 */
1403 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :