Code

Forced redraw of canvas upon document commit to work around event starvation issue...
[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 namespace Inkscape { namespace XML { class Node; }}
82 // Callback declarations
83 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
84 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
85 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
86 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
87 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
88 static void _reconstruction_start(SPDesktop * desktop);
89 static void _reconstruction_finish(SPDesktop * desktop);
90 static void _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop);
91 static void _update_snap_distances (SPDesktop *desktop);
93 /**
94  * Return new desktop object.
95  * \pre namedview != NULL.
96  * \pre canvas != NULL.
97  */
98 SPDesktop::SPDesktop()
99 {
100     _dlg_mgr = NULL;
101     _widget = 0;
102     namedview = NULL;
103     selection = NULL;
104     acetate = NULL;
105     main = NULL;
106     grid = NULL;
107     guides = NULL;
108     drawing = NULL;
109     sketch = NULL;
110     controls = NULL;
111     event_context = 0;
112     layer_manager = 0;
114     _d2w.set_identity();
115     _w2d.set_identity();
116     _doc2dt = NR::Matrix(NR::scale(1, -1));
118     guides_active = false;
120     zooms_past = NULL;
121     zooms_future = NULL;
123     is_fullscreen = false;
125     gr_item = NULL;
126     gr_point_num = 0;
127     gr_fill_or_stroke = true;
129     _layer_hierarchy = NULL;
130     _active = false;
132     selection = Inkscape::GC::release (new Inkscape::Selection (this));
135 void
136 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
139     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
141     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
143     namedview = nv;
144     canvas = aCanvas;
146     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
147     /* Kill flicker */
148     sp_document_ensure_up_to_date (document);
150     /* Setup Dialog Manager */
151     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
153     dkey = sp_item_display_key_new (1);
155     /* Connect document */
156     setDocument (document);
158     number = namedview->getViewCount();
161     /* Setup Canvas */
162     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
164     SPCanvasGroup *root = sp_canvas_root (canvas);
166     /* Setup adminstrative layers */
167     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
168     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
169     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
170     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
172     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
173     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
174     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
175     sp_canvas_item_move_to_z (table, 0);
177     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
178     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
179     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
181     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
182     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
184     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
186     // Start always in normal mode
187     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
188     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
190     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
191     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
192     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
193     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195     /* Push select tool to the bottom of stack */
196     /** \todo
197      * FIXME: this is the only call to this.  Everything else seems to just
198      * call "set" instead of "push".  Can we assume that there is only one
199      * context ever?
200      */
201     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
203     // display rect and zoom are now handled in sp_desktop_widget_realize()
205     NR::Rect const d(NR::Point(0.0, 0.0),
206                      NR::Point(sp_document_width(document), sp_document_height(document)));
208     SP_CTRLRECT(page)->setRectangle(d);
209     SP_CTRLRECT(page_border)->setRectangle(d);
211     /* the following sets the page shadow on the canvas
212        It was originally set to 5, which is really cheesy!
213        It now is an attribute in the document's namedview. If a value of
214        0 is used, then the constructor for a shadow is not initialized.
215     */
217     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
218         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
219     }
222     /* Connect event for page resize */
223     _doc2dt[5] = sp_document_height (document);
224     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
226     g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
229     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
230             SP_CANVAS_ARENA (drawing)->arena,
231             dkey,
232             SP_ITEM_SHOW_DISPLAY);
233     if (ai) {
234         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
235         nr_arena_item_unref (ai);
236     }
238     namedview->show(this);
239     /* Ugly hack */
240     activate_guides (true);
241     /* Ugly hack */
242     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
244 /* Set up notification of rebuilding the document, this allows
245        for saving object related settings in the document. */
246     _reconstruction_start_connection =
247         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
248     _reconstruction_finish_connection =
249         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
250     _reconstruction_old_layer_id = NULL;
251     
252     _commit_connection = document->connectCommit(sigc::bind(sigc::ptr_fun(&sp_canvas_update_now), this->canvas));
253     
254     // ?
255     // sp_active_desktop_set (desktop);
256     _inkscape = INKSCAPE;
258     _activate_connection = _activate_signal.connect(
259         sigc::bind(
260             sigc::ptr_fun(_onActivate),
261             this
262         )
263     );
264      _deactivate_connection = _deactivate_signal.connect(
265         sigc::bind(
266             sigc::ptr_fun(_onDeactivate),
267             this
268         )
269     );
271     _sel_modified_connection = selection->connectModified(
272         sigc::bind(
273             sigc::ptr_fun(&_onSelectionModified),
274             this
275         )
276     );
277     _sel_changed_connection = selection->connectChanged(
278         sigc::bind(
279             sigc::ptr_fun(&_onSelectionChanged),
280             this
281         )
282     );
285     /* setup LayerManager */
286     //   (Setting up after the connections are all in place, as it may use some of them)
287     layer_manager = new Inkscape::LayerManager( this );
291 void SPDesktop::destroy()
293     _activate_connection.disconnect();
294     _deactivate_connection.disconnect();
295     _sel_modified_connection.disconnect();
296     _sel_changed_connection.disconnect();
298     while (event_context) {
299         SPEventContext *ec = event_context;
300         event_context = ec->next;
301         sp_event_context_finish (ec);
302         g_object_unref (G_OBJECT (ec));
303     }
305     if (_layer_hierarchy) {
306         delete _layer_hierarchy;
307     }
309     if (_inkscape) {
310         _inkscape = NULL;
311     }
313     if (drawing) {
314         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
315         drawing = NULL;
316     }
318     delete _guides_message_context;
319     _guides_message_context = NULL;
321     sp_signal_disconnect_by_data (G_OBJECT (namedview), this);
323     g_list_free (zooms_past);
324     g_list_free (zooms_future);
327 SPDesktop::~SPDesktop() {}
329 //--------------------------------------------------------------------
330 /* Public methods */
332 void SPDesktop::setDisplayModeNormal()
334     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
335     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
336     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
339 void SPDesktop::setDisplayModeOutline()
341     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
342     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
343     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
346 /**
347  * Returns current root (=bottom) layer.
348  */
349 SPObject *SPDesktop::currentRoot() const
351     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
354 /**
355  * Returns current top layer.
356  */
357 SPObject *SPDesktop::currentLayer() const
359     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
362 /**
363  * Sets the current layer of the desktop.
364  * 
365  * Make \a object the top layer.
366  */
367 void SPDesktop::setCurrentLayer(SPObject *object) {
368     g_return_if_fail(SP_IS_GROUP(object));
369     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
370     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
371     _layer_hierarchy->setBottom(object);
374 /**
375  * Return layer that contains \a object.
376  */
377 SPObject *SPDesktop::layerForObject(SPObject *object) {
378     g_return_val_if_fail(object != NULL, NULL);
380     SPObject *root=currentRoot();
381     object = SP_OBJECT_PARENT(object);
382     while ( object && object != root && !isLayer(object) ) {
383         object = SP_OBJECT_PARENT(object);
384     }
385     return object;
388 /**
389  * True if object is a layer.
390  */
391 bool SPDesktop::isLayer(SPObject *object) const {
392     return ( SP_IS_GROUP(object)
393              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
394                   == SPGroup::LAYER ) );
397 /**
398  * True if desktop viewport fully contains \a item's bbox.
399  */
400 bool SPDesktop::isWithinViewport (SPItem *item) const
402     NR::Rect const viewport = get_display_area();
403     NR::Rect const bbox = sp_item_bbox_desktop(item);
404     return viewport.contains(bbox);
407 ///
408 bool SPDesktop::itemIsHidden(SPItem const *item) const {
409     return item->isHidden(this->dkey);
412 /**
413  * Set activate property of desktop; emit signal if changed.
414  */
415 void
416 SPDesktop::set_active (bool new_active)
418     if (new_active != _active) {
419         _active = new_active;
420         if (new_active) {
421             _activate_signal.emit();
422         } else {
423             _deactivate_signal.emit();
424         }
425     }
428 /**
429  * Set activate status of current desktop's named view.
430  */
431 void
432 SPDesktop::activate_guides(bool activate)
434     guides_active = activate;
435     namedview->activateGuides(this, activate);
438 /**
439  * Make desktop switch documents.
440  */
441 void
442 SPDesktop::change_document (SPDocument *theDocument)
444     g_return_if_fail (theDocument != NULL);
446     /* unselect everything before switching documents */
447     selection->clear();
449     setDocument (theDocument);
450     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
451     _document_replaced_signal.emit (this, theDocument);
454 /**
455  * Make desktop switch event contexts.
456  */
457 void
458 SPDesktop::set_event_context (GtkType type, const gchar *config)
460     SPEventContext *ec;
461     while (event_context) {
462         ec = event_context;
463         sp_event_context_deactivate (ec);
464         event_context = ec->next;
465         sp_event_context_finish (ec);
466         g_object_unref (G_OBJECT (ec));
467     }
469     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
470     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
471     ec->next = event_context;
472     event_context = ec;
473     sp_event_context_activate (ec);
474     _event_context_changed_signal.emit (this, ec);
477 /**
478  * Push event context onto desktop's context stack.
479  */
480 void
481 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
483     SPEventContext *ref, *ec;
484     Inkscape::XML::Node *repr;
486     if (event_context && event_context->key == key) return;
487     ref = event_context;
488     while (ref && ref->next && ref->next->key != key) ref = ref->next;
489     if (ref && ref->next) {
490         ec = ref->next;
491         ref->next = ec->next;
492         sp_event_context_finish (ec);
493         g_object_unref (G_OBJECT (ec));
494     }
496     if (event_context) sp_event_context_deactivate (event_context);
497     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
498     ec = sp_event_context_new (type, this, repr, key);
499     ec->next = event_context;
500     event_context = ec;
501     sp_event_context_activate (ec);
502     _event_context_changed_signal.emit (this, ec);
505 /**
506  * Sets the coordinate status to a given point
507  */
508 void
509 SPDesktop::set_coordinate_status (NR::Point p) {
510     _widget->setCoordinateStatus(p);
513 /**
514  * \see sp_document_item_from_list_at_point_bottom()
515  */
516 SPItem *
517 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
519     g_return_val_if_fail (doc() != NULL, NULL);
520     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
523 /**
524  * \see sp_document_item_at_point()
525  */
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 /**
534  * \see sp_document_group_at_point()
535  */
536 SPItem *
537 SPDesktop::group_at_point (NR::Point const p) const
539     g_return_val_if_fail (doc() != NULL, NULL);
540     return sp_document_group_at_point (doc(), dkey, p);
543 /**
544  * \brief  Returns the mouse point in document coordinates; if mouse is
545  * outside the canvas, returns the center of canvas viewpoint
546  */
547 NR::Point
548 SPDesktop::point() const
550     NR::Point p = _widget->getPointer();
551     NR::Point pw = sp_canvas_window_to_world (canvas, p);
552     p = w2d(pw);
554     NR::Rect const r = canvas->getViewbox();
556     NR::Point r0 = w2d(r.min());
557     NR::Point r1 = w2d(r.max());
559     if (p[NR::X] >= r0[NR::X] &&
560         p[NR::X] <= r1[NR::X] &&
561         p[NR::Y] >= r1[NR::Y] &&
562         p[NR::Y] <= r0[NR::Y])
563     {
564         return p;
565     } else {
566         return (r0 + r1) / 2;
567     }
570 /**
571  * Put current zoom data in history list.
572  */
573 void
574 SPDesktop::push_current_zoom (GList **history)
576     NR::Rect const area = get_display_area();
578     NRRect *old_zoom = g_new(NRRect, 1);
579     old_zoom->x0 = area.min()[NR::X];
580     old_zoom->x1 = area.max()[NR::X];
581     old_zoom->y0 = area.min()[NR::Y];
582     old_zoom->y1 = area.max()[NR::Y];
583     if ( *history == NULL
584          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
585                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
586                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
587                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
588     {
589         *history = g_list_prepend (*history, old_zoom);
590     }
593 /**
594  * Set viewbox.
595  */
596 void
597 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
599     g_assert(_widget);
601     // save the zoom
602     if (log) {
603         push_current_zoom(&zooms_past);
604         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
605         g_list_free (zooms_future);
606         zooms_future = NULL;
607     }
609     double const cx = 0.5 * (x0 + x1);
610     double const cy = 0.5 * (y0 + y1);
612     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
614     double scale = expansion(_d2w);
615     double newscale;
616     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
617         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
618     } else {
619         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
620     }
622     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
624     int clear = FALSE;
625     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
626         /* Set zoom factors */
627         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
628         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
629         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
630         clear = TRUE;
631     }
633     /* Calculate top left corner */
634     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
635     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
637     /* Scroll */
638     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
640     _widget->updateRulers();
641     _widget->updateScrollbars(expansion(_d2w));
642     _widget->updateZoom();
645 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
647     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
650 /**
651  * Return viewbox dimensions.
652  */
653 NR::Rect SPDesktop::get_display_area() const
655     NR::Rect const viewbox = canvas->getViewbox();
657     double const scale = _d2w[0];
659     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
660                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
663 /**
664  * Revert back to previous zoom if possible.
665  */
666 void
667 SPDesktop::prev_zoom()
669     if (zooms_past == NULL) {
670         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
671         return;
672     }
674     // push current zoom into forward zooms list
675     push_current_zoom (&zooms_future);
677     // restore previous zoom
678     set_display_area (((NRRect *) zooms_past->data)->x0,
679             ((NRRect *) zooms_past->data)->y0,
680             ((NRRect *) zooms_past->data)->x1,
681             ((NRRect *) zooms_past->data)->y1,
682             0, false);
684     // remove the just-added zoom from the past zooms list
685     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
688 /**
689  * Set zoom to next in list.
690  */
691 void
692 SPDesktop::next_zoom()
694     if (zooms_future == NULL) {
695         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
696         return;
697     }
699     // push current zoom into past zooms list
700     push_current_zoom (&zooms_past);
702     // restore next zoom
703     set_display_area (((NRRect *) zooms_future->data)->x0,
704             ((NRRect *) zooms_future->data)->y0,
705             ((NRRect *) zooms_future->data)->x1,
706             ((NRRect *) zooms_future->data)->y1,
707             0, false);
709     // remove the just-used zoom from the zooms_future list
710     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
713 /**
714  * Zoom to point with absolute zoom factor.
715  */
716 void
717 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
719     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
721     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
722     // this check prevents "sliding" when trying to zoom in at maximum zoom;
723     /// \todo someone please fix calculations properly and remove this hack
724     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
725         return;
727     NR::Rect const viewbox = canvas->getViewbox();
729     double const width2 = viewbox.dimensions()[NR::X] / zoom;
730     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
732     set_display_area(cx - px * width2,
733                      cy - py * height2,
734                      cx + (1 - px) * width2,
735                      cy + (1 - py) * height2,
736                      0.0);
739 /**
740  * Zoom to center with absolute zoom factor.
741  */
742 void
743 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
745     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
748 /**
749  * Zoom to point with relative zoom factor.
750  */
751 void
752 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
754     NR::Rect const area = get_display_area();
756     if (cx < area.min()[NR::X]) {
757         cx = area.min()[NR::X];
758     }
759     if (cx > area.max()[NR::X]) {
760         cx = area.max()[NR::X];
761     }
762     if (cy < area.min()[NR::Y]) {
763         cy = area.min()[NR::Y];
764     }
765     if (cy > area.max()[NR::Y]) {
766         cy = area.max()[NR::Y];
767     }
769     gdouble const scale = expansion(_d2w) * zoom;
770     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
771     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
773     zoom_absolute_keep_point(cx, cy, px, py, scale);
776 /**
777  * Zoom to center with relative zoom factor.
778  */
779 void
780 SPDesktop::zoom_relative (double cx, double cy, double zoom)
782     gdouble scale = expansion(_d2w) * zoom;
783     zoom_absolute (cx, cy, scale);
786 /**
787  * Set display area to origin and current document dimensions.
788  */
789 void
790 SPDesktop::zoom_page()
792     NR::Rect d(NR::Point(0, 0),
793                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
795     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
796         return;
797     }
799     set_display_area(d, 10);
802 /**
803  * Set display area to current document width.
804  */
805 void
806 SPDesktop::zoom_page_width()
808     NR::Rect const a = get_display_area();
810     if (sp_document_width(doc()) < 1.0) {
811         return;
812     }
814     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
815                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
817     set_display_area(d, 10);
820 /**
821  * Zoom to selection.
822  */
823 void
824 SPDesktop::zoom_selection()
826     NR::Rect const d = selection->bounds();
828     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
829         return;
830     }
832     set_display_area(d, 10);
835 /**
836  * Tell widget to let zoom widget grab keyboard focus.
837  */
838 void
839 SPDesktop::zoom_grab_focus()
841     _widget->letZoomGrabFocus();
844 /**
845  * Zoom to whole drawing.
846  */
847 void
848 SPDesktop::zoom_drawing()
850     g_return_if_fail (doc() != NULL);
851     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
852     g_return_if_fail (docitem != NULL);
854     NR::Rect d = sp_item_bbox_desktop(docitem);
856     /* Note that the second condition here indicates that
857     ** there are no items in the drawing.
858     */
859     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
860         return;
861     }
863     set_display_area(d, 10);
866 /**
867  * Scroll canvas by specific coordinate amount.
868  */
869 void
870 SPDesktop::scroll_world (double dx, double dy)
872     g_assert(_widget);
874     NR::Rect const viewbox = canvas->getViewbox();
876     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
878     _widget->updateRulers();
879     _widget->updateScrollbars(expansion(_d2w));
882 bool
883 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
885     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
887     // autoscrolldistance is in screen pixels, but the display area is in document units
888     autoscrolldistance /= expansion(_d2w);
889     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
891     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
892         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
894         NR::Point const s_w( (*p) * _d2w );
896         gdouble x_to;
897         if ((*p)[NR::X] < dbox.min()[NR::X])
898             x_to = dbox.min()[NR::X];
899         else if ((*p)[NR::X] > dbox.max()[NR::X])
900             x_to = dbox.max()[NR::X];
901         else
902             x_to = (*p)[NR::X];
904         gdouble y_to;
905         if ((*p)[NR::Y] < dbox.min()[NR::Y])
906             y_to = dbox.min()[NR::Y];
907         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
908             y_to = dbox.max()[NR::Y];
909         else
910             y_to = (*p)[NR::Y];
912         NR::Point const d_dt(x_to, y_to);
913         NR::Point const d_w( d_dt * _d2w );
914         NR::Point const moved_w( d_w - s_w );
916         if (autoscrollspeed == 0)
917             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
919         if (autoscrollspeed != 0)
920             scroll_world (autoscrollspeed * moved_w);
922         return true;
923     }
924     return false;
927 void
928 SPDesktop::fullscreen()
930     _widget->setFullscreen();
933 void
934 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
936     _widget->getGeometry (x, y, w, h);
939 void
940 SPDesktop::setWindowPosition (NR::Point p)
942     _widget->setPosition (p);
945 void
946 SPDesktop::setWindowSize (gint w, gint h)
948     _widget->setSize (w, h);
951 void
952 SPDesktop::setWindowTransient (void *p, int transient_policy)
954     _widget->setTransient (p, transient_policy);
957 void
958 SPDesktop::presentWindow()
960     _widget->present();
963 bool
964 SPDesktop::warnDialog (gchar *text)
966     return _widget->warnDialog (text);
969 void
970 SPDesktop::toggleRulers()
972     _widget->toggleRulers();
975 void
976 SPDesktop::toggleScrollbars()
978     _widget->toggleScrollbars();
981 void
982 SPDesktop::layoutWidget()
984     _widget->layout();
987 void
988 SPDesktop::destroyWidget()
990     _widget->destroy();
993 bool
994 SPDesktop::shutdown()
996     return _widget->shutdown();
999 void
1000 SPDesktop::setToolboxFocusTo (gchar const *label)
1002     _widget->setToolboxFocusTo (label);
1005 void
1006 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1008     _widget->setToolboxAdjustmentValue (id, val);
1011 bool
1012 SPDesktop::isToolboxButtonActive (gchar const *id)
1014     return _widget->isToolboxButtonActive (id);
1017 void
1018 SPDesktop::emitToolSubselectionChanged(gpointer data)
1020         _tool_subselection_changed.emit(data);
1021         inkscape_subselection_changed (this);
1024 //----------------------------------------------------------------------
1025 // Callback implementations. The virtual ones are connected by the view.
1027 void
1028 SPDesktop::onPositionSet (double x, double y)
1030     _widget->viewSetPosition (NR::Point(x,y));
1033 void
1034 SPDesktop::onResized (double x, double y)
1036    // Nothing called here
1039 /**
1040  * Redraw callback; queues Gtk redraw; connected by View.
1041  */
1042 void
1043 SPDesktop::onRedrawRequested ()
1045     if (main) {
1046         _widget->requestCanvasUpdate();
1047     }
1050 /**
1051  * Associate document with desktop.
1052  */
1053 /// \todo fixme: refactor SPDesktop::init to use setDocument
1054 void
1055 SPDesktop::setDocument (SPDocument *doc)
1057     if (this->doc() && doc) {
1058         namedview->hide(this);
1059         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1060     }
1062     if (_layer_hierarchy) {
1063         _layer_hierarchy->clear();
1064         delete _layer_hierarchy;
1065     }
1066     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1067     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1068     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1069     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1070     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1072     _commit_connection.disconnect();
1073     _commit_connection = doc->connectCommit(sigc::bind(sigc::ptr_fun(&sp_canvas_update_now), this->canvas));
1075     /// \todo fixme: This condition exists to make sure the code
1076     /// inside is called only once on initialization. But there
1077     /// are surely more safe methods to accomplish this.
1078     if (drawing) {
1079         NRArenaItem *ai;
1081         namedview = sp_document_namedview (doc, NULL);
1082         g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1083         number = namedview->getViewCount();
1085         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1086                 SP_CANVAS_ARENA (drawing)->arena,
1087                 dkey,
1088                 SP_ITEM_SHOW_DISPLAY);
1089         if (ai) {
1090             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1091             nr_arena_item_unref (ai);
1092         }
1093         namedview->show(this);
1094         /* Ugly hack */
1095         activate_guides (true);
1096         /* Ugly hack */
1097         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1098     }
1100     _document_replaced_signal.emit (this, doc);
1102     View::setDocument (doc);
1105 void
1106 SPDesktop::onStatusMessage
1107 (Inkscape::MessageType type, gchar const *message)
1109     if (_widget) {
1110         _widget->setMessage(type, message);
1111     }
1114 void
1115 SPDesktop::onDocumentURISet (gchar const* uri)
1117     _widget->setTitle(uri);
1120 /**
1121  * Resized callback.
1122  */
1123 void
1124 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1126     _doc2dt[5] = height;
1127     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1128     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1129     SP_CTRLRECT(page)->setRectangle(a);
1130     SP_CTRLRECT(page_border)->setRectangle(a);
1134 void
1135 SPDesktop::_onActivate (SPDesktop* dt)
1137     if (!dt->_widget) return;
1138     dt->_widget->activateDesktop();
1141 void
1142 SPDesktop::_onDeactivate (SPDesktop* dt)
1144     if (!dt->_widget) return;
1145     dt->_widget->deactivateDesktop();
1148 void
1149 SPDesktop::_onSelectionModified
1150 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1152     if (!dt->_widget) return;
1153     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1156 static void
1157 _onSelectionChanged
1158 (Inkscape::Selection *selection, SPDesktop *desktop)
1160     /** \todo
1161      * only change the layer for single selections, or what?
1162      * This seems reasonable -- for multiple selections there can be many
1163      * different layers involved.
1164      */
1165     SPItem *item=selection->singleItem();
1166     if (item) {
1167         SPObject *layer=desktop->layerForObject(item);
1168         if ( layer && layer != desktop->currentLayer() ) {
1169             desktop->setCurrentLayer(layer);
1170         }
1171     }
1174 /**
1175  * Calls event handler of current event context.
1176  * \param arena Unused
1177  * \todo fixme
1178  */
1179 static gint
1180 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1182     if (ai) {
1183         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1184         return sp_event_context_item_handler (desktop->event_context, spi, event);
1185     } else {
1186         return sp_event_context_root_handler (desktop->event_context, event);
1187     }
1190 static void
1191 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1192     g_return_if_fail(SP_IS_GROUP(layer));
1193     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1196 /// Callback
1197 static void
1198 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1199     g_return_if_fail(SP_IS_GROUP(layer));
1200     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1203 /// Callback
1204 static void
1205 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1206                                          SPDesktop *desktop)
1208     desktop->_layer_changed_signal.emit (bottom);
1211 /// Called when document is starting to be rebuilt.
1212 static void
1213 _reconstruction_start (SPDesktop * desktop)
1215     // printf("Desktop, starting reconstruction\n");
1216     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1217     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1219     /*
1220     GSList const * selection_objs = desktop->selection->list();
1221     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1223     }
1224     */
1225     desktop->selection->clear();
1227     // printf("Desktop, starting reconstruction end\n");
1230 /// Called when document rebuild is finished.
1231 static void
1232 _reconstruction_finish (SPDesktop * desktop)
1234     // printf("Desktop, finishing reconstruction\n");
1235     if (desktop->_reconstruction_old_layer_id == NULL)
1236         return;
1238     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1239     if (newLayer != NULL)
1240         desktop->setCurrentLayer(newLayer);
1242     g_free(desktop->_reconstruction_old_layer_id);
1243     desktop->_reconstruction_old_layer_id = NULL;
1244     // printf("Desktop, finishing reconstruction end\n");
1245     return;
1248 /**
1249  * Namedview_modified callback.
1250  */
1251 static void
1252 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1254     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1256         /* Recalculate snap distances */
1257         /* FIXME: why is the desktop getting involved in setting up something
1258         ** that is entirely to do with the namedview?
1259         */
1260         _update_snap_distances (desktop);
1262         /* Show/hide page background */
1263         if (nv->pagecolor & 0xff) {
1264             sp_canvas_item_show (desktop->table);
1265             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1266             sp_canvas_item_move_to_z (desktop->table, 0);
1267         } else {
1268             sp_canvas_item_hide (desktop->table);
1269         }
1271         /* Show/hide page border */
1272         if (nv->showborder) {
1273             // show
1274             sp_canvas_item_show (desktop->page_border);
1275             // set color and shadow
1276             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1277             if (nv->pageshadow) {
1278                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1279             }
1280             // place in the z-order stack
1281             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1282                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1283             } else {
1284                 int order = sp_canvas_item_order (desktop->page_border);
1285                 int morder = sp_canvas_item_order (desktop->drawing);
1286                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1287                                     morder - order);
1288             }
1289         } else {
1290                 sp_canvas_item_hide (desktop->page_border);
1291                 if (nv->pageshadow) {
1292                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1293                 }
1294         }
1295         
1296         /* Show/hide page shadow */
1297         if (nv->showpageshadow && nv->pageshadow) {
1298             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1299         } else {
1300             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1301         }
1303         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1304             (SP_RGBA32_R_U(nv->pagecolor) +
1305              SP_RGBA32_G_U(nv->pagecolor) +
1306              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1307             // the background color is light or transparent, use black outline
1308             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1309         } else { // use white outline
1310             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1311         }
1312     }
1315 /**
1316  * Callback to reset snapper's distances.
1317  */
1318 static void
1319 _update_snap_distances (SPDesktop *desktop)
1321     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1323     SPNamedView &nv = *desktop->namedview;
1325     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1326                                                                       *nv.gridtoleranceunit,
1327                                                                       px));
1328     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1329                                                                        *nv.guidetoleranceunit,
1330                                                                        px));
1331     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1332                                                                         *nv.objecttoleranceunit,
1333                                                                         px));
1337 NR::Matrix SPDesktop::w2d() const
1339     return _w2d;
1342 NR::Point SPDesktop::w2d(NR::Point const &p) const
1344     return p * _w2d;
1347 NR::Point SPDesktop::d2w(NR::Point const &p) const
1349     return p * _d2w;
1352 NR::Matrix SPDesktop::doc2dt() const
1354     return _doc2dt;
1357 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1359     return p * _doc2dt;
1362 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1364     return p / _doc2dt;
1368 /**
1369  * Pop event context from desktop's context stack. Never used.
1370  */
1371 // void
1372 // SPDesktop::pop_event_context (unsigned int key)
1373 // {
1374 //    SPEventContext *ec = NULL;
1375 //
1376 //    if (event_context && event_context->key == key) {
1377 //        g_return_if_fail (event_context);
1378 //        g_return_if_fail (event_context->next);
1379 //        ec = event_context;
1380 //        sp_event_context_deactivate (ec);
1381 //        event_context = ec->next;
1382 //        sp_event_context_activate (event_context);
1383 //        _event_context_changed_signal.emit (this, ec);
1384 //    }
1385 //
1386 //    SPEventContext *ref = event_context;
1387 //    while (ref && ref->next && ref->next->key != key)
1388 //        ref = ref->next;
1389 //
1390 //    if (ref && ref->next) {
1391 //        ec = ref->next;
1392 //        ref->next = ec->next;
1393 //    }
1394 //
1395 //    if (ec) {
1396 //        sp_event_context_finish (ec);
1397 //        g_object_unref (G_OBJECT (ec));
1398 //    }
1399 // }
1401 /*
1402   Local Variables:
1403   mode:c++
1404   c-file-style:"stroustrup"
1405   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1406   indent-tabs-mode:nil
1407   fill-column:99
1408   End:
1409 */
1410 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :