Code

added SPDesktop::updateNow() and forced redraw of canvas upon completion of selection...
[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>
52 #include <sigc++/functors/mem_fun.h>
54 #include "macros.h"
55 #include "inkscape-private.h"
56 #include "desktop.h"
57 #include "desktop-events.h"
58 #include "desktop-handles.h"
59 #include "document.h"
60 #include "message-stack.h"
61 #include "selection.h"
62 #include "select-context.h"
63 #include "sp-namedview.h"
64 #include "color.h"
65 #include "sp-item-group.h"
66 #include "prefs-utils.h"
67 #include "object-hierarchy.h"
68 #include "helper/units.h"
69 #include "display/canvas-arena.h"
70 #include "display/nr-arena.h"
71 #include "display/gnome-canvas-acetate.h"
72 #include "display/sodipodi-ctrlrect.h"
73 #include "display/sp-canvas-util.h"
74 #include "libnr/nr-matrix-div.h"
75 #include "libnr/nr-rect-ops.h"
76 #include "ui/dialog/dialog-manager.h"
77 #include "xml/repr.h"
78 #include "message-context.h"
79 #include "layer-manager.h"
81 namespace Inkscape { namespace XML { class Node; }}
83 // Callback declarations
84 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
85 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
86 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
87 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
88 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
89 static void _reconstruction_start(SPDesktop * desktop);
90 static void _reconstruction_finish(SPDesktop * desktop);
91 static void _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop);
92 static void _update_snap_distances (SPDesktop *desktop);
94 /**
95  * Return new desktop object.
96  * \pre namedview != NULL.
97  * \pre canvas != NULL.
98  */
99 SPDesktop::SPDesktop()
101     _dlg_mgr = NULL;
102     _widget = 0;
103     namedview = NULL;
104     selection = NULL;
105     acetate = NULL;
106     main = NULL;
107     grid = NULL;
108     guides = NULL;
109     drawing = NULL;
110     sketch = NULL;
111     controls = NULL;
112     event_context = 0;
113     layer_manager = 0;
115     _d2w.set_identity();
116     _w2d.set_identity();
117     _doc2dt = NR::Matrix(NR::scale(1, -1));
119     guides_active = false;
121     zooms_past = NULL;
122     zooms_future = NULL;
124     is_fullscreen = false;
126     gr_item = NULL;
127     gr_point_num = 0;
128     gr_fill_or_stroke = true;
130     _layer_hierarchy = NULL;
131     _active = false;
133     selection = Inkscape::GC::release (new Inkscape::Selection (this));
136 void
137 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
140     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
142     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
144     namedview = nv;
145     canvas = aCanvas;
147     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
148     /* Kill flicker */
149     sp_document_ensure_up_to_date (document);
151     /* Setup Dialog Manager */
152     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
154     dkey = sp_item_display_key_new (1);
156     /* Connect document */
157     setDocument (document);
159     number = namedview->getViewCount();
162     /* Setup Canvas */
163     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
165     SPCanvasGroup *root = sp_canvas_root (canvas);
167     /* Setup adminstrative layers */
168     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
169     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
170     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
171     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
173     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
174     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
175     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
176     sp_canvas_item_move_to_z (table, 0);
178     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
179     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
180     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
182     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
183     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
185     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
187     // Start always in normal mode
188     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
189     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
191     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
192     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
193     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
194     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196     /* Push select tool to the bottom of stack */
197     /** \todo
198      * FIXME: this is the only call to this.  Everything else seems to just
199      * call "set" instead of "push".  Can we assume that there is only one
200      * context ever?
201      */
202     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
204     // display rect and zoom are now handled in sp_desktop_widget_realize()
206     NR::Rect const d(NR::Point(0.0, 0.0),
207                      NR::Point(sp_document_width(document), sp_document_height(document)));
209     SP_CTRLRECT(page)->setRectangle(d);
210     SP_CTRLRECT(page_border)->setRectangle(d);
212     /* the following sets the page shadow on the canvas
213        It was originally set to 5, which is really cheesy!
214        It now is an attribute in the document's namedview. If a value of
215        0 is used, then the constructor for a shadow is not initialized.
216     */
218     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
219         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
220     }
223     /* Connect event for page resize */
224     _doc2dt[5] = sp_document_height (document);
225     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
227     g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
230     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
231             SP_CANVAS_ARENA (drawing)->arena,
232             dkey,
233             SP_ITEM_SHOW_DISPLAY);
234     if (ai) {
235         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
236         nr_arena_item_unref (ai);
237     }
239     namedview->show(this);
240     /* Ugly hack */
241     activate_guides (true);
242     /* Ugly hack */
243     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
245 /* Set up notification of rebuilding the document, this allows
246        for saving object related settings in the document. */
247     _reconstruction_start_connection =
248         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
249     _reconstruction_finish_connection =
250         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
251     _reconstruction_old_layer_id = NULL;
252     
253     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
254     
255     // ?
256     // sp_active_desktop_set (desktop);
257     _inkscape = INKSCAPE;
259     _activate_connection = _activate_signal.connect(
260         sigc::bind(
261             sigc::ptr_fun(_onActivate),
262             this
263         )
264     );
265      _deactivate_connection = _deactivate_signal.connect(
266         sigc::bind(
267             sigc::ptr_fun(_onDeactivate),
268             this
269         )
270     );
272     _sel_modified_connection = selection->connectModified(
273         sigc::bind(
274             sigc::ptr_fun(&_onSelectionModified),
275             this
276         )
277     );
278     _sel_changed_connection = selection->connectChanged(
279         sigc::bind(
280             sigc::ptr_fun(&_onSelectionChanged),
281             this
282         )
283     );
286     /* setup LayerManager */
287     //   (Setting up after the connections are all in place, as it may use some of them)
288     layer_manager = new Inkscape::LayerManager( this );
292 void SPDesktop::destroy()
294     _activate_connection.disconnect();
295     _deactivate_connection.disconnect();
296     _sel_modified_connection.disconnect();
297     _sel_changed_connection.disconnect();
299     while (event_context) {
300         SPEventContext *ec = event_context;
301         event_context = ec->next;
302         sp_event_context_finish (ec);
303         g_object_unref (G_OBJECT (ec));
304     }
306     if (_layer_hierarchy) {
307         delete _layer_hierarchy;
308     }
310     if (_inkscape) {
311         _inkscape = NULL;
312     }
314     if (drawing) {
315         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
316         drawing = NULL;
317     }
319     delete _guides_message_context;
320     _guides_message_context = NULL;
322     sp_signal_disconnect_by_data (G_OBJECT (namedview), this);
324     g_list_free (zooms_past);
325     g_list_free (zooms_future);
328 SPDesktop::~SPDesktop() {}
330 //--------------------------------------------------------------------
331 /* Public methods */
333 void SPDesktop::setDisplayModeNormal()
335     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
336     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
337     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
340 void SPDesktop::setDisplayModeOutline()
342     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
343     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
344     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
347 /**
348  * Returns current root (=bottom) layer.
349  */
350 SPObject *SPDesktop::currentRoot() const
352     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
355 /**
356  * Returns current top layer.
357  */
358 SPObject *SPDesktop::currentLayer() const
360     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
363 /**
364  * Sets the current layer of the desktop.
365  * 
366  * Make \a object the top layer.
367  */
368 void SPDesktop::setCurrentLayer(SPObject *object) {
369     g_return_if_fail(SP_IS_GROUP(object));
370     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
371     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
372     _layer_hierarchy->setBottom(object);
375 /**
376  * Return layer that contains \a object.
377  */
378 SPObject *SPDesktop::layerForObject(SPObject *object) {
379     g_return_val_if_fail(object != NULL, NULL);
381     SPObject *root=currentRoot();
382     object = SP_OBJECT_PARENT(object);
383     while ( object && object != root && !isLayer(object) ) {
384         object = SP_OBJECT_PARENT(object);
385     }
386     return object;
389 /**
390  * True if object is a layer.
391  */
392 bool SPDesktop::isLayer(SPObject *object) const {
393     return ( SP_IS_GROUP(object)
394              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
395                   == SPGroup::LAYER ) );
398 /**
399  * True if desktop viewport fully contains \a item's bbox.
400  */
401 bool SPDesktop::isWithinViewport (SPItem *item) const
403     NR::Rect const viewport = get_display_area();
404     NR::Rect const bbox = sp_item_bbox_desktop(item);
405     return viewport.contains(bbox);
408 ///
409 bool SPDesktop::itemIsHidden(SPItem const *item) const {
410     return item->isHidden(this->dkey);
413 /**
414  * Set activate property of desktop; emit signal if changed.
415  */
416 void
417 SPDesktop::set_active (bool new_active)
419     if (new_active != _active) {
420         _active = new_active;
421         if (new_active) {
422             _activate_signal.emit();
423         } else {
424             _deactivate_signal.emit();
425         }
426     }
429 /**
430  * Set activate status of current desktop's named view.
431  */
432 void
433 SPDesktop::activate_guides(bool activate)
435     guides_active = activate;
436     namedview->activateGuides(this, activate);
439 /**
440  * Make desktop switch documents.
441  */
442 void
443 SPDesktop::change_document (SPDocument *theDocument)
445     g_return_if_fail (theDocument != NULL);
447     /* unselect everything before switching documents */
448     selection->clear();
450     setDocument (theDocument);
451     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
452     _document_replaced_signal.emit (this, theDocument);
455 /**
456  * Make desktop switch event contexts.
457  */
458 void
459 SPDesktop::set_event_context (GtkType type, const gchar *config)
461     SPEventContext *ec;
462     while (event_context) {
463         ec = event_context;
464         sp_event_context_deactivate (ec);
465         event_context = ec->next;
466         sp_event_context_finish (ec);
467         g_object_unref (G_OBJECT (ec));
468     }
470     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
471     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
472     ec->next = event_context;
473     event_context = ec;
474     sp_event_context_activate (ec);
475     _event_context_changed_signal.emit (this, ec);
478 /**
479  * Push event context onto desktop's context stack.
480  */
481 void
482 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
484     SPEventContext *ref, *ec;
485     Inkscape::XML::Node *repr;
487     if (event_context && event_context->key == key) return;
488     ref = event_context;
489     while (ref && ref->next && ref->next->key != key) ref = ref->next;
490     if (ref && ref->next) {
491         ec = ref->next;
492         ref->next = ec->next;
493         sp_event_context_finish (ec);
494         g_object_unref (G_OBJECT (ec));
495     }
497     if (event_context) sp_event_context_deactivate (event_context);
498     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
499     ec = sp_event_context_new (type, this, repr, key);
500     ec->next = event_context;
501     event_context = ec;
502     sp_event_context_activate (ec);
503     _event_context_changed_signal.emit (this, ec);
506 /**
507  * Sets the coordinate status to a given point
508  */
509 void
510 SPDesktop::set_coordinate_status (NR::Point p) {
511     _widget->setCoordinateStatus(p);
514 /**
515  * \see sp_document_item_from_list_at_point_bottom()
516  */
517 SPItem *
518 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
520     g_return_val_if_fail (doc() != NULL, NULL);
521     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
524 /**
525  * \see sp_document_item_at_point()
526  */
527 SPItem *
528 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
530     g_return_val_if_fail (doc() != NULL, NULL);
531     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
534 /**
535  * \see sp_document_group_at_point()
536  */
537 SPItem *
538 SPDesktop::group_at_point (NR::Point const p) const
540     g_return_val_if_fail (doc() != NULL, NULL);
541     return sp_document_group_at_point (doc(), dkey, p);
544 /**
545  * \brief  Returns the mouse point in document coordinates; if mouse is
546  * outside the canvas, returns the center of canvas viewpoint
547  */
548 NR::Point
549 SPDesktop::point() const
551     NR::Point p = _widget->getPointer();
552     NR::Point pw = sp_canvas_window_to_world (canvas, p);
553     p = w2d(pw);
555     NR::Rect const r = canvas->getViewbox();
557     NR::Point r0 = w2d(r.min());
558     NR::Point r1 = w2d(r.max());
560     if (p[NR::X] >= r0[NR::X] &&
561         p[NR::X] <= r1[NR::X] &&
562         p[NR::Y] >= r1[NR::Y] &&
563         p[NR::Y] <= r0[NR::Y])
564     {
565         return p;
566     } else {
567         return (r0 + r1) / 2;
568     }
571 /**
572  * Put current zoom data in history list.
573  */
574 void
575 SPDesktop::push_current_zoom (GList **history)
577     NR::Rect const area = get_display_area();
579     NRRect *old_zoom = g_new(NRRect, 1);
580     old_zoom->x0 = area.min()[NR::X];
581     old_zoom->x1 = area.max()[NR::X];
582     old_zoom->y0 = area.min()[NR::Y];
583     old_zoom->y1 = area.max()[NR::Y];
584     if ( *history == NULL
585          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
586                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
587                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
588                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
589     {
590         *history = g_list_prepend (*history, old_zoom);
591     }
594 /**
595  * Set viewbox.
596  */
597 void
598 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
600     g_assert(_widget);
602     // save the zoom
603     if (log) {
604         push_current_zoom(&zooms_past);
605         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
606         g_list_free (zooms_future);
607         zooms_future = NULL;
608     }
610     double const cx = 0.5 * (x0 + x1);
611     double const cy = 0.5 * (y0 + y1);
613     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
615     double scale = expansion(_d2w);
616     double newscale;
617     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
618         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
619     } else {
620         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
621     }
623     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
625     int clear = FALSE;
626     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
627         /* Set zoom factors */
628         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
629         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
630         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
631         clear = TRUE;
632     }
634     /* Calculate top left corner */
635     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
636     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
638     /* Scroll */
639     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
641     _widget->updateRulers();
642     _widget->updateScrollbars(expansion(_d2w));
643     _widget->updateZoom();
646 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
648     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
651 /**
652  * Return viewbox dimensions.
653  */
654 NR::Rect SPDesktop::get_display_area() const
656     NR::Rect const viewbox = canvas->getViewbox();
658     double const scale = _d2w[0];
660     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
661                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
664 /**
665  * Revert back to previous zoom if possible.
666  */
667 void
668 SPDesktop::prev_zoom()
670     if (zooms_past == NULL) {
671         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
672         return;
673     }
675     // push current zoom into forward zooms list
676     push_current_zoom (&zooms_future);
678     // restore previous zoom
679     set_display_area (((NRRect *) zooms_past->data)->x0,
680             ((NRRect *) zooms_past->data)->y0,
681             ((NRRect *) zooms_past->data)->x1,
682             ((NRRect *) zooms_past->data)->y1,
683             0, false);
685     // remove the just-added zoom from the past zooms list
686     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
689 /**
690  * Set zoom to next in list.
691  */
692 void
693 SPDesktop::next_zoom()
695     if (zooms_future == NULL) {
696         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
697         return;
698     }
700     // push current zoom into past zooms list
701     push_current_zoom (&zooms_past);
703     // restore next zoom
704     set_display_area (((NRRect *) zooms_future->data)->x0,
705             ((NRRect *) zooms_future->data)->y0,
706             ((NRRect *) zooms_future->data)->x1,
707             ((NRRect *) zooms_future->data)->y1,
708             0, false);
710     // remove the just-used zoom from the zooms_future list
711     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
714 /**
715  * Zoom to point with absolute zoom factor.
716  */
717 void
718 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
720     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
722     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
723     // this check prevents "sliding" when trying to zoom in at maximum zoom;
724     /// \todo someone please fix calculations properly and remove this hack
725     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
726         return;
728     NR::Rect const viewbox = canvas->getViewbox();
730     double const width2 = viewbox.dimensions()[NR::X] / zoom;
731     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
733     set_display_area(cx - px * width2,
734                      cy - py * height2,
735                      cx + (1 - px) * width2,
736                      cy + (1 - py) * height2,
737                      0.0);
740 /**
741  * Zoom to center with absolute zoom factor.
742  */
743 void
744 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
746     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
749 /**
750  * Zoom to point with relative zoom factor.
751  */
752 void
753 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
755     NR::Rect const area = get_display_area();
757     if (cx < area.min()[NR::X]) {
758         cx = area.min()[NR::X];
759     }
760     if (cx > area.max()[NR::X]) {
761         cx = area.max()[NR::X];
762     }
763     if (cy < area.min()[NR::Y]) {
764         cy = area.min()[NR::Y];
765     }
766     if (cy > area.max()[NR::Y]) {
767         cy = area.max()[NR::Y];
768     }
770     gdouble const scale = expansion(_d2w) * zoom;
771     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
772     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
774     zoom_absolute_keep_point(cx, cy, px, py, scale);
777 /**
778  * Zoom to center with relative zoom factor.
779  */
780 void
781 SPDesktop::zoom_relative (double cx, double cy, double zoom)
783     gdouble scale = expansion(_d2w) * zoom;
784     zoom_absolute (cx, cy, scale);
787 /**
788  * Set display area to origin and current document dimensions.
789  */
790 void
791 SPDesktop::zoom_page()
793     NR::Rect d(NR::Point(0, 0),
794                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
796     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
797         return;
798     }
800     set_display_area(d, 10);
803 /**
804  * Set display area to current document width.
805  */
806 void
807 SPDesktop::zoom_page_width()
809     NR::Rect const a = get_display_area();
811     if (sp_document_width(doc()) < 1.0) {
812         return;
813     }
815     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
816                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
818     set_display_area(d, 10);
821 /**
822  * Zoom to selection.
823  */
824 void
825 SPDesktop::zoom_selection()
827     NR::Rect const d = selection->bounds();
829     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
830         return;
831     }
833     set_display_area(d, 10);
836 /**
837  * Tell widget to let zoom widget grab keyboard focus.
838  */
839 void
840 SPDesktop::zoom_grab_focus()
842     _widget->letZoomGrabFocus();
845 /**
846  * Zoom to whole drawing.
847  */
848 void
849 SPDesktop::zoom_drawing()
851     g_return_if_fail (doc() != NULL);
852     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
853     g_return_if_fail (docitem != NULL);
855     NR::Rect d = sp_item_bbox_desktop(docitem);
857     /* Note that the second condition here indicates that
858     ** there are no items in the drawing.
859     */
860     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
861         return;
862     }
864     set_display_area(d, 10);
867 /**
868  * Scroll canvas by specific coordinate amount.
869  */
870 void
871 SPDesktop::scroll_world (double dx, double dy)
873     g_assert(_widget);
875     NR::Rect const viewbox = canvas->getViewbox();
877     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
879     _widget->updateRulers();
880     _widget->updateScrollbars(expansion(_d2w));
883 bool
884 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
886     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
888     // autoscrolldistance is in screen pixels, but the display area is in document units
889     autoscrolldistance /= expansion(_d2w);
890     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
892     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
893         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
895         NR::Point const s_w( (*p) * _d2w );
897         gdouble x_to;
898         if ((*p)[NR::X] < dbox.min()[NR::X])
899             x_to = dbox.min()[NR::X];
900         else if ((*p)[NR::X] > dbox.max()[NR::X])
901             x_to = dbox.max()[NR::X];
902         else
903             x_to = (*p)[NR::X];
905         gdouble y_to;
906         if ((*p)[NR::Y] < dbox.min()[NR::Y])
907             y_to = dbox.min()[NR::Y];
908         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
909             y_to = dbox.max()[NR::Y];
910         else
911             y_to = (*p)[NR::Y];
913         NR::Point const d_dt(x_to, y_to);
914         NR::Point const d_w( d_dt * _d2w );
915         NR::Point const moved_w( d_w - s_w );
917         if (autoscrollspeed == 0)
918             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
920         if (autoscrollspeed != 0)
921             scroll_world (autoscrollspeed * moved_w);
923         return true;
924     }
925     return false;
928 void
929 SPDesktop::fullscreen()
931     _widget->setFullscreen();
934 void
935 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
937     _widget->getGeometry (x, y, w, h);
940 void
941 SPDesktop::setWindowPosition (NR::Point p)
943     _widget->setPosition (p);
946 void
947 SPDesktop::setWindowSize (gint w, gint h)
949     _widget->setSize (w, h);
952 void
953 SPDesktop::setWindowTransient (void *p, int transient_policy)
955     _widget->setTransient (p, transient_policy);
958 void
959 SPDesktop::presentWindow()
961     _widget->present();
964 bool
965 SPDesktop::warnDialog (gchar *text)
967     return _widget->warnDialog (text);
970 void
971 SPDesktop::toggleRulers()
973     _widget->toggleRulers();
976 void
977 SPDesktop::toggleScrollbars()
979     _widget->toggleScrollbars();
982 void
983 SPDesktop::layoutWidget()
985     _widget->layout();
988 void
989 SPDesktop::destroyWidget()
991     _widget->destroy();
994 bool
995 SPDesktop::shutdown()
997     return _widget->shutdown();
1000 void
1001 SPDesktop::setToolboxFocusTo (gchar const *label)
1003     _widget->setToolboxFocusTo (label);
1006 void
1007 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1009     _widget->setToolboxAdjustmentValue (id, val);
1012 bool
1013 SPDesktop::isToolboxButtonActive (gchar const *id)
1015     return _widget->isToolboxButtonActive (id);
1018 void
1019 SPDesktop::emitToolSubselectionChanged(gpointer data)
1021         _tool_subselection_changed.emit(data);
1022         inkscape_subselection_changed (this);
1025 void
1026 SPDesktop::updateNow()
1028   sp_canvas_update_now(canvas);
1031 //----------------------------------------------------------------------
1032 // Callback implementations. The virtual ones are connected by the view.
1034 void
1035 SPDesktop::onPositionSet (double x, double y)
1037     _widget->viewSetPosition (NR::Point(x,y));
1040 void
1041 SPDesktop::onResized (double x, double y)
1043    // Nothing called here
1046 /**
1047  * Redraw callback; queues Gtk redraw; connected by View.
1048  */
1049 void
1050 SPDesktop::onRedrawRequested ()
1052     if (main) {
1053         _widget->requestCanvasUpdate();
1054     }
1057 /**
1058  * Associate document with desktop.
1059  */
1060 /// \todo fixme: refactor SPDesktop::init to use setDocument
1061 void
1062 SPDesktop::setDocument (SPDocument *doc)
1064     if (this->doc() && doc) {
1065         namedview->hide(this);
1066         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1067     }
1069     if (_layer_hierarchy) {
1070         _layer_hierarchy->clear();
1071         delete _layer_hierarchy;
1072     }
1073     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1074     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1075     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1076     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1077     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1079     _commit_connection.disconnect();
1080     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1082     /// \todo fixme: This condition exists to make sure the code
1083     /// inside is called only once on initialization. But there
1084     /// are surely more safe methods to accomplish this.
1085     if (drawing) {
1086         NRArenaItem *ai;
1088         namedview = sp_document_namedview (doc, NULL);
1089         g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1090         number = namedview->getViewCount();
1092         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1093                 SP_CANVAS_ARENA (drawing)->arena,
1094                 dkey,
1095                 SP_ITEM_SHOW_DISPLAY);
1096         if (ai) {
1097             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1098             nr_arena_item_unref (ai);
1099         }
1100         namedview->show(this);
1101         /* Ugly hack */
1102         activate_guides (true);
1103         /* Ugly hack */
1104         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1105     }
1107     _document_replaced_signal.emit (this, doc);
1109     View::setDocument (doc);
1112 void
1113 SPDesktop::onStatusMessage
1114 (Inkscape::MessageType type, gchar const *message)
1116     if (_widget) {
1117         _widget->setMessage(type, message);
1118     }
1121 void
1122 SPDesktop::onDocumentURISet (gchar const* uri)
1124     _widget->setTitle(uri);
1127 /**
1128  * Resized callback.
1129  */
1130 void
1131 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1133     _doc2dt[5] = height;
1134     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1135     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1136     SP_CTRLRECT(page)->setRectangle(a);
1137     SP_CTRLRECT(page_border)->setRectangle(a);
1141 void
1142 SPDesktop::_onActivate (SPDesktop* dt)
1144     if (!dt->_widget) return;
1145     dt->_widget->activateDesktop();
1148 void
1149 SPDesktop::_onDeactivate (SPDesktop* dt)
1151     if (!dt->_widget) return;
1152     dt->_widget->deactivateDesktop();
1155 void
1156 SPDesktop::_onSelectionModified
1157 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1159     if (!dt->_widget) return;
1160     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1163 static void
1164 _onSelectionChanged
1165 (Inkscape::Selection *selection, SPDesktop *desktop)
1167     /** \todo
1168      * only change the layer for single selections, or what?
1169      * This seems reasonable -- for multiple selections there can be many
1170      * different layers involved.
1171      */
1172     SPItem *item=selection->singleItem();
1173     if (item) {
1174         SPObject *layer=desktop->layerForObject(item);
1175         if ( layer && layer != desktop->currentLayer() ) {
1176             desktop->setCurrentLayer(layer);
1177         }
1178     }
1181 /**
1182  * Calls event handler of current event context.
1183  * \param arena Unused
1184  * \todo fixme
1185  */
1186 static gint
1187 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1189     if (ai) {
1190         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1191         return sp_event_context_item_handler (desktop->event_context, spi, event);
1192     } else {
1193         return sp_event_context_root_handler (desktop->event_context, event);
1194     }
1197 static void
1198 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1199     g_return_if_fail(SP_IS_GROUP(layer));
1200     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1203 /// Callback
1204 static void
1205 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1206     g_return_if_fail(SP_IS_GROUP(layer));
1207     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1210 /// Callback
1211 static void
1212 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1213                                          SPDesktop *desktop)
1215     desktop->_layer_changed_signal.emit (bottom);
1218 /// Called when document is starting to be rebuilt.
1219 static void
1220 _reconstruction_start (SPDesktop * desktop)
1222     // printf("Desktop, starting reconstruction\n");
1223     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1224     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1226     /*
1227     GSList const * selection_objs = desktop->selection->list();
1228     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1230     }
1231     */
1232     desktop->selection->clear();
1234     // printf("Desktop, starting reconstruction end\n");
1237 /// Called when document rebuild is finished.
1238 static void
1239 _reconstruction_finish (SPDesktop * desktop)
1241     // printf("Desktop, finishing reconstruction\n");
1242     if (desktop->_reconstruction_old_layer_id == NULL)
1243         return;
1245     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1246     if (newLayer != NULL)
1247         desktop->setCurrentLayer(newLayer);
1249     g_free(desktop->_reconstruction_old_layer_id);
1250     desktop->_reconstruction_old_layer_id = NULL;
1251     // printf("Desktop, finishing reconstruction end\n");
1252     return;
1255 /**
1256  * Namedview_modified callback.
1257  */
1258 static void
1259 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1261     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1263         /* Recalculate snap distances */
1264         /* FIXME: why is the desktop getting involved in setting up something
1265         ** that is entirely to do with the namedview?
1266         */
1267         _update_snap_distances (desktop);
1269         /* Show/hide page background */
1270         if (nv->pagecolor & 0xff) {
1271             sp_canvas_item_show (desktop->table);
1272             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1273             sp_canvas_item_move_to_z (desktop->table, 0);
1274         } else {
1275             sp_canvas_item_hide (desktop->table);
1276         }
1278         /* Show/hide page border */
1279         if (nv->showborder) {
1280             // show
1281             sp_canvas_item_show (desktop->page_border);
1282             // set color and shadow
1283             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1284             if (nv->pageshadow) {
1285                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1286             }
1287             // place in the z-order stack
1288             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1289                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1290             } else {
1291                 int order = sp_canvas_item_order (desktop->page_border);
1292                 int morder = sp_canvas_item_order (desktop->drawing);
1293                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1294                                     morder - order);
1295             }
1296         } else {
1297                 sp_canvas_item_hide (desktop->page_border);
1298                 if (nv->pageshadow) {
1299                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1300                 }
1301         }
1302         
1303         /* Show/hide page shadow */
1304         if (nv->showpageshadow && nv->pageshadow) {
1305             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1306         } else {
1307             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1308         }
1310         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1311             (SP_RGBA32_R_U(nv->pagecolor) +
1312              SP_RGBA32_G_U(nv->pagecolor) +
1313              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1314             // the background color is light or transparent, use black outline
1315             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1316         } else { // use white outline
1317             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1318         }
1319     }
1322 /**
1323  * Callback to reset snapper's distances.
1324  */
1325 static void
1326 _update_snap_distances (SPDesktop *desktop)
1328     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1330     SPNamedView &nv = *desktop->namedview;
1332     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1333                                                                       *nv.gridtoleranceunit,
1334                                                                       px));
1335     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1336                                                                        *nv.guidetoleranceunit,
1337                                                                        px));
1338     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1339                                                                         *nv.objecttoleranceunit,
1340                                                                         px));
1344 NR::Matrix SPDesktop::w2d() const
1346     return _w2d;
1349 NR::Point SPDesktop::w2d(NR::Point const &p) const
1351     return p * _w2d;
1354 NR::Point SPDesktop::d2w(NR::Point const &p) const
1356     return p * _d2w;
1359 NR::Matrix SPDesktop::doc2dt() const
1361     return _doc2dt;
1364 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1366     return p * _doc2dt;
1369 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1371     return p / _doc2dt;
1375 /**
1376  * Pop event context from desktop's context stack. Never used.
1377  */
1378 // void
1379 // SPDesktop::pop_event_context (unsigned int key)
1380 // {
1381 //    SPEventContext *ec = NULL;
1382 //
1383 //    if (event_context && event_context->key == key) {
1384 //        g_return_if_fail (event_context);
1385 //        g_return_if_fail (event_context->next);
1386 //        ec = event_context;
1387 //        sp_event_context_deactivate (ec);
1388 //        event_context = ec->next;
1389 //        sp_event_context_activate (event_context);
1390 //        _event_context_changed_signal.emit (this, ec);
1391 //    }
1392 //
1393 //    SPEventContext *ref = event_context;
1394 //    while (ref && ref->next && ref->next->key != key)
1395 //        ref = ref->next;
1396 //
1397 //    if (ref && ref->next) {
1398 //        ec = ref->next;
1399 //        ref->next = ec->next;
1400 //    }
1401 //
1402 //    if (ec) {
1403 //        sp_event_context_finish (ec);
1404 //        g_object_unref (G_OBJECT (ec));
1405 //    }
1406 // }
1408 /*
1409   Local Variables:
1410   mode:c++
1411   c-file-style:"stroustrup"
1412   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1413   indent-tabs-mode:nil
1414   fill-column:99
1415   End:
1416 */
1417 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :