Code

version to 0.44pre4
[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  * Sets the current layer of the desktop.
380  * 
381  * Make \a object the top layer.
382  */
383 void SPDesktop::setCurrentLayer(SPObject *object) {
384     g_return_if_fail(SP_IS_GROUP(object));
385     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
386     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
387     _layer_hierarchy->setBottom(object);
390 /**
391  * Return layer that contains \a object.
392  */
393 SPObject *SPDesktop::layerForObject(SPObject *object) {
394     g_return_val_if_fail(object != NULL, NULL);
396     SPObject *root=currentRoot();
397     object = SP_OBJECT_PARENT(object);
398     while ( object && object != root && !isLayer(object) ) {
399         object = SP_OBJECT_PARENT(object);
400     }
401     return object;
404 /**
405  * True if object is a layer.
406  */
407 bool SPDesktop::isLayer(SPObject *object) const {
408     return ( SP_IS_GROUP(object)
409              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
410                   == SPGroup::LAYER ) );
413 /**
414  * True if desktop viewport fully contains \a item's bbox.
415  */
416 bool SPDesktop::isWithinViewport (SPItem *item) const
418     NR::Rect const viewport = get_display_area();
419     NR::Rect const bbox = sp_item_bbox_desktop(item);
420     return viewport.contains(bbox);
423 ///
424 bool SPDesktop::itemIsHidden(SPItem const *item) const {
425     return item->isHidden(this->dkey);
428 /**
429  * Set activate property of desktop; emit signal if changed.
430  */
431 void
432 SPDesktop::set_active (bool new_active)
434     if (new_active != _active) {
435         _active = new_active;
436         if (new_active) {
437             _activate_signal.emit();
438         } else {
439             _deactivate_signal.emit();
440         }
441     }
444 /**
445  * Set activate status of current desktop's named view.
446  */
447 void
448 SPDesktop::activate_guides(bool activate)
450     guides_active = activate;
451     namedview->activateGuides(this, activate);
454 /**
455  * Make desktop switch documents.
456  */
457 void
458 SPDesktop::change_document (SPDocument *theDocument)
460     g_return_if_fail (theDocument != NULL);
462     /* unselect everything before switching documents */
463     selection->clear();
465     setDocument (theDocument);
466     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
467     _document_replaced_signal.emit (this, theDocument);
470 /**
471  * Make desktop switch event contexts.
472  */
473 void
474 SPDesktop::set_event_context (GtkType type, const gchar *config)
476     SPEventContext *ec;
477     while (event_context) {
478         ec = event_context;
479         sp_event_context_deactivate (ec);
480         event_context = ec->next;
481         sp_event_context_finish (ec);
482         g_object_unref (G_OBJECT (ec));
483     }
485     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
486     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
487     ec->next = event_context;
488     event_context = ec;
489     sp_event_context_activate (ec);
490     _event_context_changed_signal.emit (this, ec);
493 /**
494  * Push event context onto desktop's context stack.
495  */
496 void
497 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
499     SPEventContext *ref, *ec;
500     Inkscape::XML::Node *repr;
502     if (event_context && event_context->key == key) return;
503     ref = event_context;
504     while (ref && ref->next && ref->next->key != key) ref = ref->next;
505     if (ref && ref->next) {
506         ec = ref->next;
507         ref->next = ec->next;
508         sp_event_context_finish (ec);
509         g_object_unref (G_OBJECT (ec));
510     }
512     if (event_context) sp_event_context_deactivate (event_context);
513     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
514     ec = sp_event_context_new (type, this, repr, key);
515     ec->next = event_context;
516     event_context = ec;
517     sp_event_context_activate (ec);
518     _event_context_changed_signal.emit (this, ec);
521 /**
522  * Sets the coordinate status to a given point
523  */
524 void
525 SPDesktop::set_coordinate_status (NR::Point p) {
526     _widget->setCoordinateStatus(p);
529 /**
530  * \see sp_document_item_from_list_at_point_bottom()
531  */
532 SPItem *
533 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
535     g_return_val_if_fail (doc() != NULL, NULL);
536     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
539 /**
540  * \see sp_document_item_at_point()
541  */
542 SPItem *
543 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
545     g_return_val_if_fail (doc() != NULL, NULL);
546     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
549 /**
550  * \see sp_document_group_at_point()
551  */
552 SPItem *
553 SPDesktop::group_at_point (NR::Point const p) const
555     g_return_val_if_fail (doc() != NULL, NULL);
556     return sp_document_group_at_point (doc(), dkey, p);
559 /**
560  * \brief  Returns the mouse point in document coordinates; if mouse is
561  * outside the canvas, returns the center of canvas viewpoint
562  */
563 NR::Point
564 SPDesktop::point() const
566     NR::Point p = _widget->getPointer();
567     NR::Point pw = sp_canvas_window_to_world (canvas, p);
568     p = w2d(pw);
570     NR::Rect const r = canvas->getViewbox();
572     NR::Point r0 = w2d(r.min());
573     NR::Point r1 = w2d(r.max());
575     if (p[NR::X] >= r0[NR::X] &&
576         p[NR::X] <= r1[NR::X] &&
577         p[NR::Y] >= r1[NR::Y] &&
578         p[NR::Y] <= r0[NR::Y])
579     {
580         return p;
581     } else {
582         return (r0 + r1) / 2;
583     }
586 /**
587  * Put current zoom data in history list.
588  */
589 void
590 SPDesktop::push_current_zoom (GList **history)
592     NR::Rect const area = get_display_area();
594     NRRect *old_zoom = g_new(NRRect, 1);
595     old_zoom->x0 = area.min()[NR::X];
596     old_zoom->x1 = area.max()[NR::X];
597     old_zoom->y0 = area.min()[NR::Y];
598     old_zoom->y1 = area.max()[NR::Y];
599     if ( *history == NULL
600          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
601                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
602                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
603                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
604     {
605         *history = g_list_prepend (*history, old_zoom);
606     }
609 /**
610  * Set viewbox.
611  */
612 void
613 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
615     g_assert(_widget);
617     // save the zoom
618     if (log) {
619         push_current_zoom(&zooms_past);
620         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
621         g_list_free (zooms_future);
622         zooms_future = NULL;
623     }
625     double const cx = 0.5 * (x0 + x1);
626     double const cy = 0.5 * (y0 + y1);
628     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
630     double scale = expansion(_d2w);
631     double newscale;
632     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
633         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
634     } else {
635         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
636     }
638     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
640     int clear = FALSE;
641     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
642         /* Set zoom factors */
643         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
644         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
645         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
646         clear = TRUE;
647     }
649     /* Calculate top left corner */
650     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
651     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
653     /* Scroll */
654     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
656     _widget->updateRulers();
657     _widget->updateScrollbars(expansion(_d2w));
658     _widget->updateZoom();
661 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
663     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
666 /**
667  * Return viewbox dimensions.
668  */
669 NR::Rect SPDesktop::get_display_area() const
671     NR::Rect const viewbox = canvas->getViewbox();
673     double const scale = _d2w[0];
675     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
676                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
679 /**
680  * Revert back to previous zoom if possible.
681  */
682 void
683 SPDesktop::prev_zoom()
685     if (zooms_past == NULL) {
686         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
687         return;
688     }
690     // push current zoom into forward zooms list
691     push_current_zoom (&zooms_future);
693     // restore previous zoom
694     set_display_area (((NRRect *) zooms_past->data)->x0,
695             ((NRRect *) zooms_past->data)->y0,
696             ((NRRect *) zooms_past->data)->x1,
697             ((NRRect *) zooms_past->data)->y1,
698             0, false);
700     // remove the just-added zoom from the past zooms list
701     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
704 /**
705  * Set zoom to next in list.
706  */
707 void
708 SPDesktop::next_zoom()
710     if (zooms_future == NULL) {
711         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
712         return;
713     }
715     // push current zoom into past zooms list
716     push_current_zoom (&zooms_past);
718     // restore next zoom
719     set_display_area (((NRRect *) zooms_future->data)->x0,
720             ((NRRect *) zooms_future->data)->y0,
721             ((NRRect *) zooms_future->data)->x1,
722             ((NRRect *) zooms_future->data)->y1,
723             0, false);
725     // remove the just-used zoom from the zooms_future list
726     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
729 /**
730  * Zoom to point with absolute zoom factor.
731  */
732 void
733 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
735     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
737     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
738     // this check prevents "sliding" when trying to zoom in at maximum zoom;
739     /// \todo someone please fix calculations properly and remove this hack
740     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
741         return;
743     NR::Rect const viewbox = canvas->getViewbox();
745     double const width2 = viewbox.dimensions()[NR::X] / zoom;
746     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
748     set_display_area(cx - px * width2,
749                      cy - py * height2,
750                      cx + (1 - px) * width2,
751                      cy + (1 - py) * height2,
752                      0.0);
755 /**
756  * Zoom to center with absolute zoom factor.
757  */
758 void
759 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
761     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
764 /**
765  * Zoom to point with relative zoom factor.
766  */
767 void
768 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
770     NR::Rect const area = get_display_area();
772     if (cx < area.min()[NR::X]) {
773         cx = area.min()[NR::X];
774     }
775     if (cx > area.max()[NR::X]) {
776         cx = area.max()[NR::X];
777     }
778     if (cy < area.min()[NR::Y]) {
779         cy = area.min()[NR::Y];
780     }
781     if (cy > area.max()[NR::Y]) {
782         cy = area.max()[NR::Y];
783     }
785     gdouble const scale = expansion(_d2w) * zoom;
786     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
787     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
789     zoom_absolute_keep_point(cx, cy, px, py, scale);
792 /**
793  * Zoom to center with relative zoom factor.
794  */
795 void
796 SPDesktop::zoom_relative (double cx, double cy, double zoom)
798     gdouble scale = expansion(_d2w) * zoom;
799     zoom_absolute (cx, cy, scale);
802 /**
803  * Set display area to origin and current document dimensions.
804  */
805 void
806 SPDesktop::zoom_page()
808     NR::Rect d(NR::Point(0, 0),
809                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
811     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
812         return;
813     }
815     set_display_area(d, 10);
818 /**
819  * Set display area to current document width.
820  */
821 void
822 SPDesktop::zoom_page_width()
824     NR::Rect const a = get_display_area();
826     if (sp_document_width(doc()) < 1.0) {
827         return;
828     }
830     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
831                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
833     set_display_area(d, 10);
836 /**
837  * Zoom to selection.
838  */
839 void
840 SPDesktop::zoom_selection()
842     NR::Rect const d = selection->bounds();
844     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
845         return;
846     }
848     set_display_area(d, 10);
851 /**
852  * Tell widget to let zoom widget grab keyboard focus.
853  */
854 void
855 SPDesktop::zoom_grab_focus()
857     _widget->letZoomGrabFocus();
860 /**
861  * Zoom to whole drawing.
862  */
863 void
864 SPDesktop::zoom_drawing()
866     g_return_if_fail (doc() != NULL);
867     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
868     g_return_if_fail (docitem != NULL);
870     NR::Rect d = sp_item_bbox_desktop(docitem);
872     /* Note that the second condition here indicates that
873     ** there are no items in the drawing.
874     */
875     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
876         return;
877     }
879     set_display_area(d, 10);
882 /**
883  * Scroll canvas by specific coordinate amount.
884  */
885 void
886 SPDesktop::scroll_world (double dx, double dy)
888     g_assert(_widget);
890     NR::Rect const viewbox = canvas->getViewbox();
892     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
894     _widget->updateRulers();
895     _widget->updateScrollbars(expansion(_d2w));
898 bool
899 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
901     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
903     // autoscrolldistance is in screen pixels, but the display area is in document units
904     autoscrolldistance /= expansion(_d2w);
905     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
907     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
908         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
910         NR::Point const s_w( (*p) * _d2w );
912         gdouble x_to;
913         if ((*p)[NR::X] < dbox.min()[NR::X])
914             x_to = dbox.min()[NR::X];
915         else if ((*p)[NR::X] > dbox.max()[NR::X])
916             x_to = dbox.max()[NR::X];
917         else
918             x_to = (*p)[NR::X];
920         gdouble y_to;
921         if ((*p)[NR::Y] < dbox.min()[NR::Y])
922             y_to = dbox.min()[NR::Y];
923         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
924             y_to = dbox.max()[NR::Y];
925         else
926             y_to = (*p)[NR::Y];
928         NR::Point const d_dt(x_to, y_to);
929         NR::Point const d_w( d_dt * _d2w );
930         NR::Point const moved_w( d_w - s_w );
932         if (autoscrollspeed == 0)
933             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
935         if (autoscrollspeed != 0)
936             scroll_world (autoscrollspeed * moved_w);
938         return true;
939     }
940     return false;
943 void
944 SPDesktop::fullscreen()
946     _widget->setFullscreen();
949 void
950 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
952     _widget->getGeometry (x, y, w, h);
955 void
956 SPDesktop::setWindowPosition (NR::Point p)
958     _widget->setPosition (p);
961 void
962 SPDesktop::setWindowSize (gint w, gint h)
964     _widget->setSize (w, h);
967 void
968 SPDesktop::setWindowTransient (void *p, int transient_policy)
970     _widget->setTransient (p, transient_policy);
973 void
974 SPDesktop::presentWindow()
976     _widget->present();
979 bool
980 SPDesktop::warnDialog (gchar *text)
982     return _widget->warnDialog (text);
985 void
986 SPDesktop::toggleRulers()
988     _widget->toggleRulers();
991 void
992 SPDesktop::toggleScrollbars()
994     _widget->toggleScrollbars();
997 void
998 SPDesktop::layoutWidget()
1000     _widget->layout();
1003 void
1004 SPDesktop::destroyWidget()
1006     _widget->destroy();
1009 bool
1010 SPDesktop::shutdown()
1012     return _widget->shutdown();
1015 void
1016 SPDesktop::setToolboxFocusTo (gchar const *label)
1018     _widget->setToolboxFocusTo (label);
1021 void
1022 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1024     _widget->setToolboxAdjustmentValue (id, val);
1027 bool
1028 SPDesktop::isToolboxButtonActive (gchar const *id)
1030     return _widget->isToolboxButtonActive (id);
1033 void
1034 SPDesktop::emitToolSubselectionChanged(gpointer data)
1036         _tool_subselection_changed.emit(data);
1037         inkscape_subselection_changed (this);
1040 //----------------------------------------------------------------------
1041 // Callback implementations. The virtual ones are connected by the view.
1043 void
1044 SPDesktop::onPositionSet (double x, double y)
1046     _widget->viewSetPosition (NR::Point(x,y));
1049 void
1050 SPDesktop::onResized (double x, double y)
1052    // Nothing called here
1055 /**
1056  * Redraw callback; queues Gtk redraw; connected by View.
1057  */
1058 void
1059 SPDesktop::onRedrawRequested ()
1061     if (main) {
1062         _widget->requestCanvasUpdate();
1063     }
1066 /**
1067  * Associate document with desktop.
1068  */
1069 void
1070 SPDesktop::setDocument (SPDocument *doc)
1072     if (this->doc() && doc) {
1073         namedview->hide(this);
1074         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1075     }
1077     if (_layer_hierarchy) {
1078         _layer_hierarchy->clear();
1079         delete _layer_hierarchy;
1080     }
1081     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1082     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1083     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1084     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1085     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1087     /// \todo fixme: This condition exists to make sure the code
1088     /// inside is called only once on initialization. But there
1089     /// are surely more safe methods to accomplish this.
1090     if (drawing) {
1091         NRArenaItem *ai;
1093         namedview = sp_document_namedview (doc, NULL);
1094         g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1095         number = namedview->getViewCount();
1097         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1098                 SP_CANVAS_ARENA (drawing)->arena,
1099                 dkey,
1100                 SP_ITEM_SHOW_DISPLAY);
1101         if (ai) {
1102             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1103             nr_arena_item_unref (ai);
1104         }
1105         namedview->show(this);
1106         /* Ugly hack */
1107         activate_guides (true);
1108         /* Ugly hack */
1109         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1110     }
1112     _document_replaced_signal.emit (this, doc);
1114     View::setDocument (doc);
1117 void
1118 SPDesktop::onStatusMessage
1119 (Inkscape::MessageType type, gchar const *message)
1121     if (_widget) {
1122         _widget->setMessage(type, message);
1123     }
1126 void
1127 SPDesktop::onDocumentURISet (gchar const* uri)
1129     _widget->setTitle(uri);
1132 /**
1133  * Resized callback.
1134  */
1135 void
1136 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1138     _doc2dt[5] = height;
1139     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1140     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1141     SP_CTRLRECT(page)->setRectangle(a);
1142     SP_CTRLRECT(page_border)->setRectangle(a);
1146 void
1147 SPDesktop::_onActivate (SPDesktop* dt)
1149     if (!dt->_widget) return;
1150     dt->_widget->activateDesktop();
1153 void
1154 SPDesktop::_onDeactivate (SPDesktop* dt)
1156     if (!dt->_widget) return;
1157     dt->_widget->deactivateDesktop();
1160 void
1161 SPDesktop::_onSelectionModified
1162 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1164     if (!dt->_widget) return;
1165     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1168 static void
1169 _onSelectionChanged
1170 (Inkscape::Selection *selection, SPDesktop *desktop)
1172     /** \todo
1173      * only change the layer for single selections, or what?
1174      * This seems reasonable -- for multiple selections there can be many
1175      * different layers involved.
1176      */
1177     SPItem *item=selection->singleItem();
1178     if (item) {
1179         SPObject *layer=desktop->layerForObject(item);
1180         if ( layer && layer != desktop->currentLayer() ) {
1181             desktop->setCurrentLayer(layer);
1182         }
1183     }
1186 /**
1187  * Calls event handler of current event context.
1188  * \param arena Unused
1189  * \todo fixme
1190  */
1191 static gint
1192 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1194     if (ai) {
1195         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1196         return sp_event_context_item_handler (desktop->event_context, spi, event);
1197     } else {
1198         return sp_event_context_root_handler (desktop->event_context, event);
1199     }
1202 static void
1203 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1204     g_return_if_fail(SP_IS_GROUP(layer));
1205     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1208 /// Callback
1209 static void
1210 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1211     g_return_if_fail(SP_IS_GROUP(layer));
1212     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1215 /// Callback
1216 static void
1217 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1218                                          SPDesktop *desktop)
1220     desktop->_layer_changed_signal.emit (bottom);
1223 /// Called when document is starting to be rebuilt.
1224 static void
1225 _reconstruction_start (SPDesktop * desktop)
1227     // printf("Desktop, starting reconstruction\n");
1228     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1229     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1231     /*
1232     GSList const * selection_objs = desktop->selection->list();
1233     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1235     }
1236     */
1237     desktop->selection->clear();
1239     // printf("Desktop, starting reconstruction end\n");
1242 /// Called when document rebuild is finished.
1243 static void
1244 _reconstruction_finish (SPDesktop * desktop)
1246     // printf("Desktop, finishing reconstruction\n");
1247     if (desktop->_reconstruction_old_layer_id == NULL)
1248         return;
1250     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1251     if (newLayer != NULL)
1252         desktop->setCurrentLayer(newLayer);
1254     g_free(desktop->_reconstruction_old_layer_id);
1255     desktop->_reconstruction_old_layer_id = NULL;
1256     // printf("Desktop, finishing reconstruction end\n");
1257     return;
1260 /**
1261  * Namedview_modified callback.
1262  */
1263 static void
1264 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1266     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1268         /* Recalculate snap distances */
1269         /* FIXME: why is the desktop getting involved in setting up something
1270         ** that is entirely to do with the namedview?
1271         */
1272         _update_snap_distances (desktop);
1274         /* Show/hide page background */
1275         if (nv->pagecolor & 0xff) {
1276             sp_canvas_item_show (desktop->table);
1277             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1278             sp_canvas_item_move_to_z (desktop->table, 0);
1279         } else {
1280             sp_canvas_item_hide (desktop->table);
1281         }
1283         /* Show/hide page border */
1284         if (nv->showborder) {
1285             // show
1286             sp_canvas_item_show (desktop->page_border);
1287             // set color and shadow
1288             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1289             if (nv->pageshadow) {
1290                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1291             }
1292             // place in the z-order stack
1293             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1294                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1295             } else {
1296                 int order = sp_canvas_item_order (desktop->page_border);
1297                 int morder = sp_canvas_item_order (desktop->drawing);
1298                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1299                                     morder - order);
1300             }
1301         } else {
1302                 sp_canvas_item_hide (desktop->page_border);
1303                 if (nv->pageshadow) {
1304                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1305                 }
1306         }
1307         
1308         /* Show/hide page shadow */
1309         if (nv->showpageshadow && nv->pageshadow) {
1310             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1311         } else {
1312             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1313         }
1315         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1316             (SP_RGBA32_R_U(nv->pagecolor) +
1317              SP_RGBA32_G_U(nv->pagecolor) +
1318              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1319             // the background color is light or transparent, use black outline
1320             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1321         } else { // use white outline
1322             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1323         }
1324     }
1327 /**
1328  * Callback to reset snapper's distances.
1329  */
1330 static void
1331 _update_snap_distances (SPDesktop *desktop)
1333     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1335     SPNamedView &nv = *desktop->namedview;
1337     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1338                                                                       *nv.gridtoleranceunit,
1339                                                                       px));
1340     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1341                                                                        *nv.guidetoleranceunit,
1342                                                                        px));
1343     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1344                                                                         *nv.objecttoleranceunit,
1345                                                                         px));
1349 NR::Matrix SPDesktop::w2d() const
1351     return _w2d;
1354 NR::Point SPDesktop::w2d(NR::Point const &p) const
1356     return p * _w2d;
1359 NR::Point SPDesktop::d2w(NR::Point const &p) const
1361     return p * _d2w;
1364 NR::Matrix SPDesktop::doc2dt() const
1366     return _doc2dt;
1369 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1371     return p * _doc2dt;
1374 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1376     return p / _doc2dt;
1380 /**
1381  * Pop event context from desktop's context stack. Never used.
1382  */
1383 // void
1384 // SPDesktop::pop_event_context (unsigned int key)
1385 // {
1386 //    SPEventContext *ec = NULL;
1387 //
1388 //    if (event_context && event_context->key == key) {
1389 //        g_return_if_fail (event_context);
1390 //        g_return_if_fail (event_context->next);
1391 //        ec = event_context;
1392 //        sp_event_context_deactivate (ec);
1393 //        event_context = ec->next;
1394 //        sp_event_context_activate (event_context);
1395 //        _event_context_changed_signal.emit (this, ec);
1396 //    }
1397 //
1398 //    SPEventContext *ref = event_context;
1399 //    while (ref && ref->next && ref->next->key != key)
1400 //        ref = ref->next;
1401 //
1402 //    if (ref && ref->next) {
1403 //        ec = ref->next;
1404 //        ref->next = ec->next;
1405 //    }
1406 //
1407 //    if (ec) {
1408 //        sp_event_context_finish (ec);
1409 //        g_object_unref (G_OBJECT (ec));
1410 //    }
1411 // }
1413 /*
1414   Local Variables:
1415   mode:c++
1416   c-file-style:"stroustrup"
1417   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1418   indent-tabs-mode:nil
1419   fill-column:99
1420   End:
1421 */
1422 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :