Code

df13bed37361adcf4ad94adb223ac9494ff82da2
[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  *   John Bintz <jcoswell@coswellproductions.org>
12  *
13  * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
14  * Copyright (C) 2006 John Bintz
15  * Copyright (C) 2004 MenTaLguY
16  * Copyright (C) 1999-2002 Lauris Kaplinski
17  * Copyright (C) 2000-2001 Ximian, Inc.
18  *
19  * Released under GNU GPL, read the file 'COPYING' for more information
20  */
22 /** \class SPDesktop
23  * SPDesktop is a subclass of View, implementing an editable document
24  * canvas.  It is extensively used by many UI controls that need certain
25  * visual representations of their own.
26  *
27  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
28  * layers of different control objects. The one containing the whole
29  * document is the drawing layer. In addition to it, there are grid,
30  * guide, sketch and control layers. The sketch layer is used for
31  * temporary drawing objects, before the real objects in document are
32  * created. The control layer contains editing knots, rubberband and
33  * similar non-document UI objects.
34  *
35  * Each SPDesktop is associated with a SPNamedView node of the document
36  * tree.  Currently, all desktops are created from a single main named
37  * view, but in the future there may be support for different ones.
38  * SPNamedView serves as an in-document container for desktop-related
39  * data, like grid and guideline placement, snapping options and so on.
40  *
41  * Associated with each SPDesktop are the two most important editing
42  * related objects - SPSelection and SPEventContext.
43  *
44  * Sodipodi keeps track of the active desktop and invokes notification
45  * signals whenever it changes. UI elements can use these to update their
46  * display to the selection of the currently active editing window.
47  * (Lauris Kaplinski)
48  */
50 #ifdef HAVE_CONFIG_H
51 # include "config.h"
52 #endif
54 #include <glibmm/i18n.h>
55 #include <sigc++/functors/mem_fun.h>
57 #include "macros.h"
58 #include "inkscape-private.h"
59 #include "desktop.h"
60 #include "desktop-events.h"
61 #include "desktop-handles.h"
62 #include "document.h"
63 #include "message-stack.h"
64 #include "selection.h"
65 #include "select-context.h"
66 #include "sp-namedview.h"
67 #include "color.h"
68 #include "sp-item-group.h"
69 #include "prefs-utils.h"
70 #include "object-hierarchy.h"
71 #include "helper/units.h"
72 #include "display/canvas-arena.h"
73 #include "display/nr-arena.h"
74 #include "display/gnome-canvas-acetate.h"
75 #include "display/sodipodi-ctrlrect.h"
76 #include "display/sp-canvas-util.h"
77 #include "libnr/nr-matrix-div.h"
78 #include "libnr/nr-rect-ops.h"
79 #include "ui/dialog/dialog-manager.h"
80 #include "xml/repr.h"
81 #include "message-context.h"
82 #include "layer-manager.h"
83 #include "event-log.h"
85 namespace Inkscape { namespace XML { class Node; }}
87 // Callback declarations
88 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
89 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
90 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
91 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
92 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
93 static void _reconstruction_start(SPDesktop * desktop);
94 static void _reconstruction_finish(SPDesktop * desktop);
95 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
96 static void _update_snap_distances (SPDesktop *desktop);
98 /**
99  * Return new desktop object.
100  * \pre namedview != NULL.
101  * \pre canvas != NULL.
102  */
103 SPDesktop::SPDesktop()
105     _dlg_mgr = NULL;
106     _widget = 0;
107     namedview = NULL;
108     selection = NULL;
109     acetate = NULL;
110     main = NULL;
111     grid = NULL;
112     guides = NULL;
113     drawing = NULL;
114     sketch = NULL;
115     controls = NULL;
116     event_context = 0;
117     layer_manager = 0;
119     _d2w.set_identity();
120     _w2d.set_identity();
121     _doc2dt = NR::Matrix(NR::scale(1, -1));
123     guides_active = false;
125     zooms_past = NULL;
126     zooms_future = NULL;
128     is_fullscreen = false;
130     gr_item = NULL;
131     gr_point_num = 0;
132     gr_fill_or_stroke = true;
134     _layer_hierarchy = NULL;
135     _active = false;
137     selection = Inkscape::GC::release (new Inkscape::Selection (this));
140 void
141 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
144     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
146     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
148     namedview = nv;
149     canvas = aCanvas;
151     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
152     /* Kill flicker */
153     sp_document_ensure_up_to_date (document);
155     /* Setup Dialog Manager */
156     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
158     dkey = sp_item_display_key_new (1);
160     /* Connect document */
161     setDocument (document);
163     number = namedview->getViewCount();
166     /* Setup Canvas */
167     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
169     SPCanvasGroup *root = sp_canvas_root (canvas);
171     /* Setup adminstrative layers */
172     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
173     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
174     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
175     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
177     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
178     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
179     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
180     sp_canvas_item_move_to_z (table, 0);
182     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
183     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
184     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
186     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
187     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
189     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
191     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
192         // Start in outline mode
193         SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
194         canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
195     } else {
196         // Start in normal mode, default
197         SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
198         canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
199     }
201     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
202     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
203     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
204     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
206     /* Push select tool to the bottom of stack */
207     /** \todo
208      * FIXME: this is the only call to this.  Everything else seems to just
209      * call "set" instead of "push".  Can we assume that there is only one
210      * context ever?
211      */
212     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
214     // display rect and zoom are now handled in sp_desktop_widget_realize()
216     NR::Rect const d(NR::Point(0.0, 0.0),
217                      NR::Point(sp_document_width(document), sp_document_height(document)));
219     SP_CTRLRECT(page)->setRectangle(d);
220     SP_CTRLRECT(page_border)->setRectangle(d);
222     /* the following sets the page shadow on the canvas
223        It was originally set to 5, which is really cheesy!
224        It now is an attribute in the document's namedview. If a value of
225        0 is used, then the constructor for a shadow is not initialized.
226     */
228     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
229         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
230     }
233     /* Connect event for page resize */
234     _doc2dt[5] = sp_document_height (document);
235     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
237     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
239     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
240             SP_CANVAS_ARENA (drawing)->arena,
241             dkey,
242             SP_ITEM_SHOW_DISPLAY);
243     if (ai) {
244         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
245         nr_arena_item_unref (ai);
246     }
248     namedview->show(this);
249     /* Ugly hack */
250     activate_guides (true);
251     /* Ugly hack */
252     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
254 /* Set up notification of rebuilding the document, this allows
255        for saving object related settings in the document. */
256     _reconstruction_start_connection =
257         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
258     _reconstruction_finish_connection =
259         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
260     _reconstruction_old_layer_id = NULL;
261     
262     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
263     
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();
307     _modified_connection.disconnect();
308     _commit_connection.disconnect();
309     _reconstruction_start_connection.disconnect();
310     _reconstruction_finish_connection.disconnect();
312     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
313     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
314     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
316     while (event_context) {
317         SPEventContext *ec = event_context;
318         event_context = ec->next;
319         sp_event_context_finish (ec);
320         g_object_unref (G_OBJECT (ec));
321     }
323     if (_layer_hierarchy) {
324         delete _layer_hierarchy;
325     }
327     if (_inkscape) {
328         _inkscape = NULL;
329     }
331     if (drawing) {
332         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
333         drawing = NULL;
334     }
336     delete _guides_message_context;
337     _guides_message_context = NULL;
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 void
1041 SPDesktop::updateNow()
1043   sp_canvas_update_now(canvas);
1046 void
1047 SPDesktop::enableInteraction()
1049   _widget->enableInteraction();
1052 void SPDesktop::disableInteraction()
1054   _widget->disableInteraction();
1057 //----------------------------------------------------------------------
1058 // Callback implementations. The virtual ones are connected by the view.
1060 void
1061 SPDesktop::onPositionSet (double x, double y)
1063     _widget->viewSetPosition (NR::Point(x,y));
1066 void
1067 SPDesktop::onResized (double x, double y)
1069    // Nothing called here
1072 /**
1073  * Redraw callback; queues Gtk redraw; connected by View.
1074  */
1075 void
1076 SPDesktop::onRedrawRequested ()
1078     if (main) {
1079         _widget->requestCanvasUpdate();
1080     }
1083 void
1084 SPDesktop::updateCanvasNow()
1086   _widget->requestCanvasUpdateAndWait();
1089 /**
1090  * Associate document with desktop.
1091  */
1092 /// \todo fixme: refactor SPDesktop::init to use setDocument
1093 void
1094 SPDesktop::setDocument (SPDocument *doc)
1096     if (this->doc() && doc) {
1097         namedview->hide(this);
1098         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1099     }
1101     if (_layer_hierarchy) {
1102         _layer_hierarchy->clear();
1103         delete _layer_hierarchy;
1104     }
1105     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1106     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1107     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1108     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1109     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1111     /* setup EventLog */
1112     event_log = new Inkscape::EventLog(doc);
1113     doc->addUndoObserver(*event_log);
1115     _commit_connection.disconnect();
1116     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1118     /// \todo fixme: This condition exists to make sure the code
1119     /// inside is called only once on initialization. But there
1120     /// are surely more safe methods to accomplish this.
1121     if (drawing) {
1122         NRArenaItem *ai;
1124         namedview = sp_document_namedview (doc, NULL);
1125         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1126         number = namedview->getViewCount();
1128         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1129                 SP_CANVAS_ARENA (drawing)->arena,
1130                 dkey,
1131                 SP_ITEM_SHOW_DISPLAY);
1132         if (ai) {
1133             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1134             nr_arena_item_unref (ai);
1135         }
1136         namedview->show(this);
1137         /* Ugly hack */
1138         activate_guides (true);
1139         /* Ugly hack */
1140         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1141     }
1143     _document_replaced_signal.emit (this, doc);
1145     View::setDocument (doc);
1148 void
1149 SPDesktop::onStatusMessage
1150 (Inkscape::MessageType type, gchar const *message)
1152     if (_widget) {
1153         _widget->setMessage(type, message);
1154     }
1157 void
1158 SPDesktop::onDocumentURISet (gchar const* uri)
1160     _widget->setTitle(uri);
1163 /**
1164  * Resized callback.
1165  */
1166 void
1167 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1169     _doc2dt[5] = height;
1170     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1171     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1172     SP_CTRLRECT(page)->setRectangle(a);
1173     SP_CTRLRECT(page_border)->setRectangle(a);
1177 void
1178 SPDesktop::_onActivate (SPDesktop* dt)
1180     if (!dt->_widget) return;
1181     dt->_widget->activateDesktop();
1184 void
1185 SPDesktop::_onDeactivate (SPDesktop* dt)
1187     if (!dt->_widget) return;
1188     dt->_widget->deactivateDesktop();
1191 void
1192 SPDesktop::_onSelectionModified
1193 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1195     if (!dt->_widget) return;
1196     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1199 static void
1200 _onSelectionChanged
1201 (Inkscape::Selection *selection, SPDesktop *desktop)
1203     /** \todo
1204      * only change the layer for single selections, or what?
1205      * This seems reasonable -- for multiple selections there can be many
1206      * different layers involved.
1207      */
1208     SPItem *item=selection->singleItem();
1209     if (item) {
1210         SPObject *layer=desktop->layerForObject(item);
1211         if ( layer && layer != desktop->currentLayer() ) {
1212             desktop->setCurrentLayer(layer);
1213         }
1214     }
1217 /**
1218  * Calls event handler of current event context.
1219  * \param arena Unused
1220  * \todo fixme
1221  */
1222 static gint
1223 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1225     if (ai) {
1226         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1227         return sp_event_context_item_handler (desktop->event_context, spi, event);
1228     } else {
1229         return sp_event_context_root_handler (desktop->event_context, event);
1230     }
1233 static void
1234 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1235     g_return_if_fail(SP_IS_GROUP(layer));
1236     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1239 /// Callback
1240 static void
1241 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1242     g_return_if_fail(SP_IS_GROUP(layer));
1243     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1246 /// Callback
1247 static void
1248 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1249                                          SPDesktop *desktop)
1251     desktop->_layer_changed_signal.emit (bottom);
1254 /// Called when document is starting to be rebuilt.
1255 static void
1256 _reconstruction_start (SPDesktop * desktop)
1258     // printf("Desktop, starting reconstruction\n");
1259     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1260     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1262     /*
1263     GSList const * selection_objs = desktop->selection->list();
1264     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1266     }
1267     */
1268     desktop->selection->clear();
1270     // printf("Desktop, starting reconstruction end\n");
1273 /// Called when document rebuild is finished.
1274 static void
1275 _reconstruction_finish (SPDesktop * desktop)
1277     // printf("Desktop, finishing reconstruction\n");
1278     if (desktop->_reconstruction_old_layer_id == NULL)
1279         return;
1281     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1282     if (newLayer != NULL)
1283         desktop->setCurrentLayer(newLayer);
1285     g_free(desktop->_reconstruction_old_layer_id);
1286     desktop->_reconstruction_old_layer_id = NULL;
1287     // printf("Desktop, finishing reconstruction end\n");
1288     return;
1291 /**
1292  * Namedview_modified callback.
1293  */
1294 static void
1295 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1297     SPNamedView *nv=SP_NAMEDVIEW(obj);
1299     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1301         /* Recalculate snap distances */
1302         /* FIXME: why is the desktop getting involved in setting up something
1303         ** that is entirely to do with the namedview?
1304         */
1305         _update_snap_distances (desktop);
1307         /* Show/hide page background */
1308         if (nv->pagecolor & 0xff) {
1309             sp_canvas_item_show (desktop->table);
1310             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1311             sp_canvas_item_move_to_z (desktop->table, 0);
1312         } else {
1313             sp_canvas_item_hide (desktop->table);
1314         }
1316         /* Show/hide page border */
1317         if (nv->showborder) {
1318             // show
1319             sp_canvas_item_show (desktop->page_border);
1320             // set color and shadow
1321             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1322             if (nv->pageshadow) {
1323                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1324             }
1325             // place in the z-order stack
1326             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1327                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1328             } else {
1329                 int order = sp_canvas_item_order (desktop->page_border);
1330                 int morder = sp_canvas_item_order (desktop->drawing);
1331                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1332                                     morder - order);
1333             }
1334         } else {
1335                 sp_canvas_item_hide (desktop->page_border);
1336                 if (nv->pageshadow) {
1337                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1338                 }
1339         }
1340         
1341         /* Show/hide page shadow */
1342         if (nv->showpageshadow && nv->pageshadow) {
1343             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1344         } else {
1345             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1346         }
1348         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1349             (SP_RGBA32_R_U(nv->pagecolor) +
1350              SP_RGBA32_G_U(nv->pagecolor) +
1351              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1352             // the background color is light or transparent, use black outline
1353             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1354         } else { // use white outline
1355             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1356         }
1357     }
1360 /**
1361  * Callback to reset snapper's distances.
1362  */
1363 static void
1364 _update_snap_distances (SPDesktop *desktop)
1366     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1368     SPNamedView &nv = *desktop->namedview;
1370     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1371                                                                       *nv.gridtoleranceunit,
1372                                                                       px));
1373     nv.snap_manager.axonomgrid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1374                                                                       *nv.gridtoleranceunit,
1375                                                                       px));
1376     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1377                                                                        *nv.guidetoleranceunit,
1378                                                                        px));
1379     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1380                                                                         *nv.objecttoleranceunit,
1381                                                                         px));
1385 NR::Matrix SPDesktop::w2d() const
1387     return _w2d;
1390 NR::Point SPDesktop::w2d(NR::Point const &p) const
1392     return p * _w2d;
1395 NR::Point SPDesktop::d2w(NR::Point const &p) const
1397     return p * _d2w;
1400 NR::Matrix SPDesktop::doc2dt() const
1402     return _doc2dt;
1405 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1407     return p * _doc2dt;
1410 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1412     return p / _doc2dt;
1416 /**
1417  * Pop event context from desktop's context stack. Never used.
1418  */
1419 // void
1420 // SPDesktop::pop_event_context (unsigned int key)
1421 // {
1422 //    SPEventContext *ec = NULL;
1423 //
1424 //    if (event_context && event_context->key == key) {
1425 //        g_return_if_fail (event_context);
1426 //        g_return_if_fail (event_context->next);
1427 //        ec = event_context;
1428 //        sp_event_context_deactivate (ec);
1429 //        event_context = ec->next;
1430 //        sp_event_context_activate (event_context);
1431 //        _event_context_changed_signal.emit (this, ec);
1432 //    }
1433 //
1434 //    SPEventContext *ref = event_context;
1435 //    while (ref && ref->next && ref->next->key != key)
1436 //        ref = ref->next;
1437 //
1438 //    if (ref && ref->next) {
1439 //        ec = ref->next;
1440 //        ref->next = ec->next;
1441 //    }
1442 //
1443 //    if (ec) {
1444 //        sp_event_context_finish (ec);
1445 //        g_object_unref (G_OBJECT (ec));
1446 //    }
1447 // }
1449 /*
1450   Local Variables:
1451   mode:c++
1452   c-file-style:"stroustrup"
1453   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1454   indent-tabs-mode:nil
1455   fill-column:99
1456   End:
1457 */
1458 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :