Code

added getHaveSSL()
[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 John Bintz
14  * Copyright (C) 2004 MenTaLguY
15  * Copyright (C) 1999-2002 Lauris Kaplinski
16  * Copyright (C) 2000-2001 Ximian, Inc.
17  *
18  * Released under GNU GPL, read the file 'COPYING' for more information
19  */
21 /** \class SPDesktop
22  * SPDesktop is a subclass of View, implementing an editable document
23  * canvas.  It is extensively used by many UI controls that need certain
24  * visual representations of their own.
25  *
26  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
27  * layers of different control objects. The one containing the whole
28  * document is the drawing layer. In addition to it, there are grid,
29  * guide, sketch and control layers. The sketch layer is used for
30  * temporary drawing objects, before the real objects in document are
31  * created. The control layer contains editing knots, rubberband and
32  * similar non-document UI objects.
33  *
34  * Each SPDesktop is associated with a SPNamedView node of the document
35  * tree.  Currently, all desktops are created from a single main named
36  * view, but in the future there may be support for different ones.
37  * SPNamedView serves as an in-document container for desktop-related
38  * data, like grid and guideline placement, snapping options and so on.
39  *
40  * Associated with each SPDesktop are the two most important editing
41  * related objects - SPSelection and SPEventContext.
42  *
43  * Sodipodi keeps track of the active desktop and invokes notification
44  * signals whenever it changes. UI elements can use these to update their
45  * display to the selection of the currently active editing window.
46  * (Lauris Kaplinski)
47  */
49 #ifdef HAVE_CONFIG_H
50 # include "config.h"
51 #endif
53 #include <glibmm/i18n.h>
54 #include <sigc++/functors/mem_fun.h>
56 #include "macros.h"
57 #include "inkscape-private.h"
58 #include "desktop.h"
59 #include "desktop-events.h"
60 #include "desktop-handles.h"
61 #include "document.h"
62 #include "message-stack.h"
63 #include "selection.h"
64 #include "select-context.h"
65 #include "sp-namedview.h"
66 #include "color.h"
67 #include "sp-item-group.h"
68 #include "prefs-utils.h"
69 #include "object-hierarchy.h"
70 #include "helper/units.h"
71 #include "display/canvas-arena.h"
72 #include "display/nr-arena.h"
73 #include "display/gnome-canvas-acetate.h"
74 #include "display/sodipodi-ctrlrect.h"
75 #include "display/sp-canvas-util.h"
76 #include "libnr/nr-matrix-div.h"
77 #include "libnr/nr-rect-ops.h"
78 #include "ui/dialog/dialog-manager.h"
79 #include "xml/repr.h"
80 #include "message-context.h"
81 #include "layer-manager.h"
83 namespace Inkscape { namespace XML { class Node; }}
85 // Callback declarations
86 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
87 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
88 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
89 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
90 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
91 static void _reconstruction_start(SPDesktop * desktop);
92 static void _reconstruction_finish(SPDesktop * desktop);
93 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
94 static void _update_snap_distances (SPDesktop *desktop);
96 /**
97  * Return new desktop object.
98  * \pre namedview != NULL.
99  * \pre canvas != NULL.
100  */
101 SPDesktop::SPDesktop()
103     _dlg_mgr = NULL;
104     _widget = 0;
105     namedview = NULL;
106     selection = NULL;
107     acetate = NULL;
108     main = NULL;
109     grid = NULL;
110     guides = NULL;
111     drawing = NULL;
112     sketch = NULL;
113     controls = NULL;
114     event_context = 0;
115     layer_manager = 0;
117     _d2w.set_identity();
118     _w2d.set_identity();
119     _doc2dt = NR::Matrix(NR::scale(1, -1));
121     guides_active = false;
123     zooms_past = NULL;
124     zooms_future = NULL;
126     is_fullscreen = false;
128     gr_item = NULL;
129     gr_point_num = 0;
130     gr_fill_or_stroke = true;
132     _layer_hierarchy = NULL;
133     _active = false;
135     selection = Inkscape::GC::release (new Inkscape::Selection (this));
138 void
139 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
142     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
144     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
146     namedview = nv;
147     canvas = aCanvas;
149     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
150     /* Kill flicker */
151     sp_document_ensure_up_to_date (document);
153     /* Setup Dialog Manager */
154     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
156     dkey = sp_item_display_key_new (1);
158     /* Connect document */
159     setDocument (document);
161     number = namedview->getViewCount();
164     /* Setup Canvas */
165     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
167     SPCanvasGroup *root = sp_canvas_root (canvas);
169     /* Setup adminstrative layers */
170     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
171     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
172     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
173     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
175     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
176     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
177     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
178     sp_canvas_item_move_to_z (table, 0);
180     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
181     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
182     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
184     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
185     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
187     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
189     // Start always in normal mode
190     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
191     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
193     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
194     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
198     /* Push select tool to the bottom of stack */
199     /** \todo
200      * FIXME: this is the only call to this.  Everything else seems to just
201      * call "set" instead of "push".  Can we assume that there is only one
202      * context ever?
203      */
204     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
206     // display rect and zoom are now handled in sp_desktop_widget_realize()
208     NR::Rect const d(NR::Point(0.0, 0.0),
209                      NR::Point(sp_document_width(document), sp_document_height(document)));
211     SP_CTRLRECT(page)->setRectangle(d);
212     SP_CTRLRECT(page_border)->setRectangle(d);
214     /* the following sets the page shadow on the canvas
215        It was originally set to 5, which is really cheesy!
216        It now is an attribute in the document's namedview. If a value of
217        0 is used, then the constructor for a shadow is not initialized.
218     */
220     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
221         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
222     }
225     /* Connect event for page resize */
226     _doc2dt[5] = sp_document_height (document);
227     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
229     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
231     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
232             SP_CANVAS_ARENA (drawing)->arena,
233             dkey,
234             SP_ITEM_SHOW_DISPLAY);
235     if (ai) {
236         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
237         nr_arena_item_unref (ai);
238     }
240     namedview->show(this);
241     /* Ugly hack */
242     activate_guides (true);
243     /* Ugly hack */
244     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
246 /* Set up notification of rebuilding the document, this allows
247        for saving object related settings in the document. */
248     _reconstruction_start_connection =
249         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
250     _reconstruction_finish_connection =
251         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
252     _reconstruction_old_layer_id = NULL;
253     
254     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
255     
256     // ?
257     // sp_active_desktop_set (desktop);
258     _inkscape = INKSCAPE;
260     _activate_connection = _activate_signal.connect(
261         sigc::bind(
262             sigc::ptr_fun(_onActivate),
263             this
264         )
265     );
266      _deactivate_connection = _deactivate_signal.connect(
267         sigc::bind(
268             sigc::ptr_fun(_onDeactivate),
269             this
270         )
271     );
273     _sel_modified_connection = selection->connectModified(
274         sigc::bind(
275             sigc::ptr_fun(&_onSelectionModified),
276             this
277         )
278     );
279     _sel_changed_connection = selection->connectChanged(
280         sigc::bind(
281             sigc::ptr_fun(&_onSelectionChanged),
282             this
283         )
284     );
287     /* setup LayerManager */
288     //   (Setting up after the connections are all in place, as it may use some of them)
289     layer_manager = new Inkscape::LayerManager( this );
293 void SPDesktop::destroy()
295     _activate_connection.disconnect();
296     _deactivate_connection.disconnect();
297     _sel_modified_connection.disconnect();
298     _sel_changed_connection.disconnect();
299     _modified_connection.disconnect();
300     _commit_connection.disconnect();
301     _reconstruction_start_connection.disconnect();
302     _reconstruction_finish_connection.disconnect();
304     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
305     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
306     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
308     while (event_context) {
309         SPEventContext *ec = event_context;
310         event_context = ec->next;
311         sp_event_context_finish (ec);
312         g_object_unref (G_OBJECT (ec));
313     }
315     if (_layer_hierarchy) {
316         delete _layer_hierarchy;
317     }
319     if (_inkscape) {
320         _inkscape = NULL;
321     }
323     if (drawing) {
324         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
325         drawing = NULL;
326     }
328     delete _guides_message_context;
329     _guides_message_context = NULL;
331     g_list_free (zooms_past);
332     g_list_free (zooms_future);
335 SPDesktop::~SPDesktop() {}
337 //--------------------------------------------------------------------
338 /* Public methods */
340 void SPDesktop::setDisplayModeNormal()
342     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
343     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
344     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
347 void SPDesktop::setDisplayModeOutline()
349     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
350     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
351     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
354 /**
355  * Returns current root (=bottom) layer.
356  */
357 SPObject *SPDesktop::currentRoot() const
359     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
362 /**
363  * Returns current top layer.
364  */
365 SPObject *SPDesktop::currentLayer() const
367     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
370 /**
371  * Sets the current layer of the desktop.
372  * 
373  * Make \a object the top layer.
374  */
375 void SPDesktop::setCurrentLayer(SPObject *object) {
376     g_return_if_fail(SP_IS_GROUP(object));
377     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
378     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
379     _layer_hierarchy->setBottom(object);
382 /**
383  * Return layer that contains \a object.
384  */
385 SPObject *SPDesktop::layerForObject(SPObject *object) {
386     g_return_val_if_fail(object != NULL, NULL);
388     SPObject *root=currentRoot();
389     object = SP_OBJECT_PARENT(object);
390     while ( object && object != root && !isLayer(object) ) {
391         object = SP_OBJECT_PARENT(object);
392     }
393     return object;
396 /**
397  * True if object is a layer.
398  */
399 bool SPDesktop::isLayer(SPObject *object) const {
400     return ( SP_IS_GROUP(object)
401              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
402                   == SPGroup::LAYER ) );
405 /**
406  * True if desktop viewport fully contains \a item's bbox.
407  */
408 bool SPDesktop::isWithinViewport (SPItem *item) const
410     NR::Rect const viewport = get_display_area();
411     NR::Rect const bbox = sp_item_bbox_desktop(item);
412     return viewport.contains(bbox);
415 ///
416 bool SPDesktop::itemIsHidden(SPItem const *item) const {
417     return item->isHidden(this->dkey);
420 /**
421  * Set activate property of desktop; emit signal if changed.
422  */
423 void
424 SPDesktop::set_active (bool new_active)
426     if (new_active != _active) {
427         _active = new_active;
428         if (new_active) {
429             _activate_signal.emit();
430         } else {
431             _deactivate_signal.emit();
432         }
433     }
436 /**
437  * Set activate status of current desktop's named view.
438  */
439 void
440 SPDesktop::activate_guides(bool activate)
442     guides_active = activate;
443     namedview->activateGuides(this, activate);
446 /**
447  * Make desktop switch documents.
448  */
449 void
450 SPDesktop::change_document (SPDocument *theDocument)
452     g_return_if_fail (theDocument != NULL);
454     /* unselect everything before switching documents */
455     selection->clear();
457     setDocument (theDocument);
458     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
459     _document_replaced_signal.emit (this, theDocument);
462 /**
463  * Make desktop switch event contexts.
464  */
465 void
466 SPDesktop::set_event_context (GtkType type, const gchar *config)
468     SPEventContext *ec;
469     while (event_context) {
470         ec = event_context;
471         sp_event_context_deactivate (ec);
472         event_context = ec->next;
473         sp_event_context_finish (ec);
474         g_object_unref (G_OBJECT (ec));
475     }
477     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
478     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
479     ec->next = event_context;
480     event_context = ec;
481     sp_event_context_activate (ec);
482     _event_context_changed_signal.emit (this, ec);
485 /**
486  * Push event context onto desktop's context stack.
487  */
488 void
489 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
491     SPEventContext *ref, *ec;
492     Inkscape::XML::Node *repr;
494     if (event_context && event_context->key == key) return;
495     ref = event_context;
496     while (ref && ref->next && ref->next->key != key) ref = ref->next;
497     if (ref && ref->next) {
498         ec = ref->next;
499         ref->next = ec->next;
500         sp_event_context_finish (ec);
501         g_object_unref (G_OBJECT (ec));
502     }
504     if (event_context) sp_event_context_deactivate (event_context);
505     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
506     ec = sp_event_context_new (type, this, repr, key);
507     ec->next = event_context;
508     event_context = ec;
509     sp_event_context_activate (ec);
510     _event_context_changed_signal.emit (this, ec);
513 /**
514  * Sets the coordinate status to a given point
515  */
516 void
517 SPDesktop::set_coordinate_status (NR::Point p) {
518     _widget->setCoordinateStatus(p);
521 /**
522  * \see sp_document_item_from_list_at_point_bottom()
523  */
524 SPItem *
525 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
527     g_return_val_if_fail (doc() != NULL, NULL);
528     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
531 /**
532  * \see sp_document_item_at_point()
533  */
534 SPItem *
535 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
537     g_return_val_if_fail (doc() != NULL, NULL);
538     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
541 /**
542  * \see sp_document_group_at_point()
543  */
544 SPItem *
545 SPDesktop::group_at_point (NR::Point const p) const
547     g_return_val_if_fail (doc() != NULL, NULL);
548     return sp_document_group_at_point (doc(), dkey, p);
551 /**
552  * \brief  Returns the mouse point in document coordinates; if mouse is
553  * outside the canvas, returns the center of canvas viewpoint
554  */
555 NR::Point
556 SPDesktop::point() const
558     NR::Point p = _widget->getPointer();
559     NR::Point pw = sp_canvas_window_to_world (canvas, p);
560     p = w2d(pw);
562     NR::Rect const r = canvas->getViewbox();
564     NR::Point r0 = w2d(r.min());
565     NR::Point r1 = w2d(r.max());
567     if (p[NR::X] >= r0[NR::X] &&
568         p[NR::X] <= r1[NR::X] &&
569         p[NR::Y] >= r1[NR::Y] &&
570         p[NR::Y] <= r0[NR::Y])
571     {
572         return p;
573     } else {
574         return (r0 + r1) / 2;
575     }
578 /**
579  * Put current zoom data in history list.
580  */
581 void
582 SPDesktop::push_current_zoom (GList **history)
584     NR::Rect const area = get_display_area();
586     NRRect *old_zoom = g_new(NRRect, 1);
587     old_zoom->x0 = area.min()[NR::X];
588     old_zoom->x1 = area.max()[NR::X];
589     old_zoom->y0 = area.min()[NR::Y];
590     old_zoom->y1 = area.max()[NR::Y];
591     if ( *history == NULL
592          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
593                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
594                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
595                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
596     {
597         *history = g_list_prepend (*history, old_zoom);
598     }
601 /**
602  * Set viewbox.
603  */
604 void
605 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
607     g_assert(_widget);
609     // save the zoom
610     if (log) {
611         push_current_zoom(&zooms_past);
612         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
613         g_list_free (zooms_future);
614         zooms_future = NULL;
615     }
617     double const cx = 0.5 * (x0 + x1);
618     double const cy = 0.5 * (y0 + y1);
620     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
622     double scale = expansion(_d2w);
623     double newscale;
624     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
625         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
626     } else {
627         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
628     }
630     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
632     int clear = FALSE;
633     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
634         /* Set zoom factors */
635         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
636         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
637         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
638         clear = TRUE;
639     }
641     /* Calculate top left corner */
642     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
643     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
645     /* Scroll */
646     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
648     _widget->updateRulers();
649     _widget->updateScrollbars(expansion(_d2w));
650     _widget->updateZoom();
653 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
655     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
658 /**
659  * Return viewbox dimensions.
660  */
661 NR::Rect SPDesktop::get_display_area() const
663     NR::Rect const viewbox = canvas->getViewbox();
665     double const scale = _d2w[0];
667     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
668                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
671 /**
672  * Revert back to previous zoom if possible.
673  */
674 void
675 SPDesktop::prev_zoom()
677     if (zooms_past == NULL) {
678         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
679         return;
680     }
682     // push current zoom into forward zooms list
683     push_current_zoom (&zooms_future);
685     // restore previous zoom
686     set_display_area (((NRRect *) zooms_past->data)->x0,
687             ((NRRect *) zooms_past->data)->y0,
688             ((NRRect *) zooms_past->data)->x1,
689             ((NRRect *) zooms_past->data)->y1,
690             0, false);
692     // remove the just-added zoom from the past zooms list
693     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
696 /**
697  * Set zoom to next in list.
698  */
699 void
700 SPDesktop::next_zoom()
702     if (zooms_future == NULL) {
703         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
704         return;
705     }
707     // push current zoom into past zooms list
708     push_current_zoom (&zooms_past);
710     // restore next zoom
711     set_display_area (((NRRect *) zooms_future->data)->x0,
712             ((NRRect *) zooms_future->data)->y0,
713             ((NRRect *) zooms_future->data)->x1,
714             ((NRRect *) zooms_future->data)->y1,
715             0, false);
717     // remove the just-used zoom from the zooms_future list
718     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
721 /**
722  * Zoom to point with absolute zoom factor.
723  */
724 void
725 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
727     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
729     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
730     // this check prevents "sliding" when trying to zoom in at maximum zoom;
731     /// \todo someone please fix calculations properly and remove this hack
732     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
733         return;
735     NR::Rect const viewbox = canvas->getViewbox();
737     double const width2 = viewbox.dimensions()[NR::X] / zoom;
738     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
740     set_display_area(cx - px * width2,
741                      cy - py * height2,
742                      cx + (1 - px) * width2,
743                      cy + (1 - py) * height2,
744                      0.0);
747 /**
748  * Zoom to center with absolute zoom factor.
749  */
750 void
751 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
753     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
756 /**
757  * Zoom to point with relative zoom factor.
758  */
759 void
760 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
762     NR::Rect const area = get_display_area();
764     if (cx < area.min()[NR::X]) {
765         cx = area.min()[NR::X];
766     }
767     if (cx > area.max()[NR::X]) {
768         cx = area.max()[NR::X];
769     }
770     if (cy < area.min()[NR::Y]) {
771         cy = area.min()[NR::Y];
772     }
773     if (cy > area.max()[NR::Y]) {
774         cy = area.max()[NR::Y];
775     }
777     gdouble const scale = expansion(_d2w) * zoom;
778     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
779     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
781     zoom_absolute_keep_point(cx, cy, px, py, scale);
784 /**
785  * Zoom to center with relative zoom factor.
786  */
787 void
788 SPDesktop::zoom_relative (double cx, double cy, double zoom)
790     gdouble scale = expansion(_d2w) * zoom;
791     zoom_absolute (cx, cy, scale);
794 /**
795  * Set display area to origin and current document dimensions.
796  */
797 void
798 SPDesktop::zoom_page()
800     NR::Rect d(NR::Point(0, 0),
801                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
803     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
804         return;
805     }
807     set_display_area(d, 10);
810 /**
811  * Set display area to current document width.
812  */
813 void
814 SPDesktop::zoom_page_width()
816     NR::Rect const a = get_display_area();
818     if (sp_document_width(doc()) < 1.0) {
819         return;
820     }
822     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
823                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
825     set_display_area(d, 10);
828 /**
829  * Zoom to selection.
830  */
831 void
832 SPDesktop::zoom_selection()
834     NR::Rect const d = selection->bounds();
836     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
837         return;
838     }
840     set_display_area(d, 10);
843 /**
844  * Tell widget to let zoom widget grab keyboard focus.
845  */
846 void
847 SPDesktop::zoom_grab_focus()
849     _widget->letZoomGrabFocus();
852 /**
853  * Zoom to whole drawing.
854  */
855 void
856 SPDesktop::zoom_drawing()
858     g_return_if_fail (doc() != NULL);
859     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
860     g_return_if_fail (docitem != NULL);
862     NR::Rect d = sp_item_bbox_desktop(docitem);
864     /* Note that the second condition here indicates that
865     ** there are no items in the drawing.
866     */
867     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
868         return;
869     }
871     set_display_area(d, 10);
874 /**
875  * Scroll canvas by specific coordinate amount.
876  */
877 void
878 SPDesktop::scroll_world (double dx, double dy)
880     g_assert(_widget);
882     NR::Rect const viewbox = canvas->getViewbox();
884     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
886     _widget->updateRulers();
887     _widget->updateScrollbars(expansion(_d2w));
890 bool
891 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
893     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
895     // autoscrolldistance is in screen pixels, but the display area is in document units
896     autoscrolldistance /= expansion(_d2w);
897     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
899     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
900         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
902         NR::Point const s_w( (*p) * _d2w );
904         gdouble x_to;
905         if ((*p)[NR::X] < dbox.min()[NR::X])
906             x_to = dbox.min()[NR::X];
907         else if ((*p)[NR::X] > dbox.max()[NR::X])
908             x_to = dbox.max()[NR::X];
909         else
910             x_to = (*p)[NR::X];
912         gdouble y_to;
913         if ((*p)[NR::Y] < dbox.min()[NR::Y])
914             y_to = dbox.min()[NR::Y];
915         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
916             y_to = dbox.max()[NR::Y];
917         else
918             y_to = (*p)[NR::Y];
920         NR::Point const d_dt(x_to, y_to);
921         NR::Point const d_w( d_dt * _d2w );
922         NR::Point const moved_w( d_w - s_w );
924         if (autoscrollspeed == 0)
925             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
927         if (autoscrollspeed != 0)
928             scroll_world (autoscrollspeed * moved_w);
930         return true;
931     }
932     return false;
935 void
936 SPDesktop::fullscreen()
938     _widget->setFullscreen();
941 void
942 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
944     _widget->getGeometry (x, y, w, h);
947 void
948 SPDesktop::setWindowPosition (NR::Point p)
950     _widget->setPosition (p);
953 void
954 SPDesktop::setWindowSize (gint w, gint h)
956     _widget->setSize (w, h);
959 void
960 SPDesktop::setWindowTransient (void *p, int transient_policy)
962     _widget->setTransient (p, transient_policy);
965 void
966 SPDesktop::presentWindow()
968     _widget->present();
971 bool
972 SPDesktop::warnDialog (gchar *text)
974     return _widget->warnDialog (text);
977 void
978 SPDesktop::toggleRulers()
980     _widget->toggleRulers();
983 void
984 SPDesktop::toggleScrollbars()
986     _widget->toggleScrollbars();
989 void
990 SPDesktop::layoutWidget()
992     _widget->layout();
995 void
996 SPDesktop::destroyWidget()
998     _widget->destroy();
1001 bool
1002 SPDesktop::shutdown()
1004     return _widget->shutdown();
1007 void
1008 SPDesktop::setToolboxFocusTo (gchar const *label)
1010     _widget->setToolboxFocusTo (label);
1013 void
1014 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1016     _widget->setToolboxAdjustmentValue (id, val);
1019 bool
1020 SPDesktop::isToolboxButtonActive (gchar const *id)
1022     return _widget->isToolboxButtonActive (id);
1025 void
1026 SPDesktop::emitToolSubselectionChanged(gpointer data)
1028         _tool_subselection_changed.emit(data);
1029         inkscape_subselection_changed (this);
1032 void
1033 SPDesktop::updateNow()
1035   sp_canvas_update_now(canvas);
1038 void
1039 SPDesktop::enableInteraction()
1041   _widget->enableInteraction();
1044 void SPDesktop::disableInteraction()
1046   _widget->disableInteraction();
1049 //----------------------------------------------------------------------
1050 // Callback implementations. The virtual ones are connected by the view.
1052 void
1053 SPDesktop::onPositionSet (double x, double y)
1055     _widget->viewSetPosition (NR::Point(x,y));
1058 void
1059 SPDesktop::onResized (double x, double y)
1061    // Nothing called here
1064 /**
1065  * Redraw callback; queues Gtk redraw; connected by View.
1066  */
1067 void
1068 SPDesktop::onRedrawRequested ()
1070     if (main) {
1071         _widget->requestCanvasUpdate();
1072     }
1075 void
1076 SPDesktop::updateCanvasNow()
1078   _widget->requestCanvasUpdateAndWait();
1081 /**
1082  * Associate document with desktop.
1083  */
1084 /// \todo fixme: refactor SPDesktop::init to use setDocument
1085 void
1086 SPDesktop::setDocument (SPDocument *doc)
1088     if (this->doc() && doc) {
1089         namedview->hide(this);
1090         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1091     }
1093     if (_layer_hierarchy) {
1094         _layer_hierarchy->clear();
1095         delete _layer_hierarchy;
1096     }
1097     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1098     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1099     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1100     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1101     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1103     _commit_connection.disconnect();
1104     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1106     /// \todo fixme: This condition exists to make sure the code
1107     /// inside is called only once on initialization. But there
1108     /// are surely more safe methods to accomplish this.
1109     if (drawing) {
1110         NRArenaItem *ai;
1112         namedview = sp_document_namedview (doc, NULL);
1113         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1114         number = namedview->getViewCount();
1116         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1117                 SP_CANVAS_ARENA (drawing)->arena,
1118                 dkey,
1119                 SP_ITEM_SHOW_DISPLAY);
1120         if (ai) {
1121             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1122             nr_arena_item_unref (ai);
1123         }
1124         namedview->show(this);
1125         /* Ugly hack */
1126         activate_guides (true);
1127         /* Ugly hack */
1128         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1129     }
1131     _document_replaced_signal.emit (this, doc);
1133     View::setDocument (doc);
1136 void
1137 SPDesktop::onStatusMessage
1138 (Inkscape::MessageType type, gchar const *message)
1140     if (_widget) {
1141         _widget->setMessage(type, message);
1142     }
1145 void
1146 SPDesktop::onDocumentURISet (gchar const* uri)
1148     _widget->setTitle(uri);
1151 /**
1152  * Resized callback.
1153  */
1154 void
1155 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1157     _doc2dt[5] = height;
1158     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1159     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1160     SP_CTRLRECT(page)->setRectangle(a);
1161     SP_CTRLRECT(page_border)->setRectangle(a);
1165 void
1166 SPDesktop::_onActivate (SPDesktop* dt)
1168     if (!dt->_widget) return;
1169     dt->_widget->activateDesktop();
1172 void
1173 SPDesktop::_onDeactivate (SPDesktop* dt)
1175     if (!dt->_widget) return;
1176     dt->_widget->deactivateDesktop();
1179 void
1180 SPDesktop::_onSelectionModified
1181 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1183     if (!dt->_widget) return;
1184     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1187 static void
1188 _onSelectionChanged
1189 (Inkscape::Selection *selection, SPDesktop *desktop)
1191     /** \todo
1192      * only change the layer for single selections, or what?
1193      * This seems reasonable -- for multiple selections there can be many
1194      * different layers involved.
1195      */
1196     SPItem *item=selection->singleItem();
1197     if (item) {
1198         SPObject *layer=desktop->layerForObject(item);
1199         if ( layer && layer != desktop->currentLayer() ) {
1200             desktop->setCurrentLayer(layer);
1201         }
1202     }
1205 /**
1206  * Calls event handler of current event context.
1207  * \param arena Unused
1208  * \todo fixme
1209  */
1210 static gint
1211 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1213     if (ai) {
1214         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1215         return sp_event_context_item_handler (desktop->event_context, spi, event);
1216     } else {
1217         return sp_event_context_root_handler (desktop->event_context, event);
1218     }
1221 static void
1222 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1223     g_return_if_fail(SP_IS_GROUP(layer));
1224     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1227 /// Callback
1228 static void
1229 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1230     g_return_if_fail(SP_IS_GROUP(layer));
1231     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1234 /// Callback
1235 static void
1236 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1237                                          SPDesktop *desktop)
1239     desktop->_layer_changed_signal.emit (bottom);
1242 /// Called when document is starting to be rebuilt.
1243 static void
1244 _reconstruction_start (SPDesktop * desktop)
1246     // printf("Desktop, starting reconstruction\n");
1247     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1248     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1250     /*
1251     GSList const * selection_objs = desktop->selection->list();
1252     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1254     }
1255     */
1256     desktop->selection->clear();
1258     // printf("Desktop, starting reconstruction end\n");
1261 /// Called when document rebuild is finished.
1262 static void
1263 _reconstruction_finish (SPDesktop * desktop)
1265     // printf("Desktop, finishing reconstruction\n");
1266     if (desktop->_reconstruction_old_layer_id == NULL)
1267         return;
1269     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1270     if (newLayer != NULL)
1271         desktop->setCurrentLayer(newLayer);
1273     g_free(desktop->_reconstruction_old_layer_id);
1274     desktop->_reconstruction_old_layer_id = NULL;
1275     // printf("Desktop, finishing reconstruction end\n");
1276     return;
1279 /**
1280  * Namedview_modified callback.
1281  */
1282 static void
1283 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1285     SPNamedView *nv=SP_NAMEDVIEW(obj);
1287     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1289         /* Recalculate snap distances */
1290         /* FIXME: why is the desktop getting involved in setting up something
1291         ** that is entirely to do with the namedview?
1292         */
1293         _update_snap_distances (desktop);
1295         /* Show/hide page background */
1296         if (nv->pagecolor & 0xff) {
1297             sp_canvas_item_show (desktop->table);
1298             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1299             sp_canvas_item_move_to_z (desktop->table, 0);
1300         } else {
1301             sp_canvas_item_hide (desktop->table);
1302         }
1304         /* Show/hide page border */
1305         if (nv->showborder) {
1306             // show
1307             sp_canvas_item_show (desktop->page_border);
1308             // set color and shadow
1309             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1310             if (nv->pageshadow) {
1311                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1312             }
1313             // place in the z-order stack
1314             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1315                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1316             } else {
1317                 int order = sp_canvas_item_order (desktop->page_border);
1318                 int morder = sp_canvas_item_order (desktop->drawing);
1319                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1320                                     morder - order);
1321             }
1322         } else {
1323                 sp_canvas_item_hide (desktop->page_border);
1324                 if (nv->pageshadow) {
1325                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1326                 }
1327         }
1328         
1329         /* Show/hide page shadow */
1330         if (nv->showpageshadow && nv->pageshadow) {
1331             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1332         } else {
1333             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1334         }
1336         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1337             (SP_RGBA32_R_U(nv->pagecolor) +
1338              SP_RGBA32_G_U(nv->pagecolor) +
1339              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1340             // the background color is light or transparent, use black outline
1341             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1342         } else { // use white outline
1343             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1344         }
1345     }
1348 /**
1349  * Callback to reset snapper's distances.
1350  */
1351 static void
1352 _update_snap_distances (SPDesktop *desktop)
1354     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1356     SPNamedView &nv = *desktop->namedview;
1358     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1359                                                                       *nv.gridtoleranceunit,
1360                                                                       px));
1361     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1362                                                                        *nv.guidetoleranceunit,
1363                                                                        px));
1364     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1365                                                                         *nv.objecttoleranceunit,
1366                                                                         px));
1370 NR::Matrix SPDesktop::w2d() const
1372     return _w2d;
1375 NR::Point SPDesktop::w2d(NR::Point const &p) const
1377     return p * _w2d;
1380 NR::Point SPDesktop::d2w(NR::Point const &p) const
1382     return p * _d2w;
1385 NR::Matrix SPDesktop::doc2dt() const
1387     return _doc2dt;
1390 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1392     return p * _doc2dt;
1395 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1397     return p / _doc2dt;
1401 /**
1402  * Pop event context from desktop's context stack. Never used.
1403  */
1404 // void
1405 // SPDesktop::pop_event_context (unsigned int key)
1406 // {
1407 //    SPEventContext *ec = NULL;
1408 //
1409 //    if (event_context && event_context->key == key) {
1410 //        g_return_if_fail (event_context);
1411 //        g_return_if_fail (event_context->next);
1412 //        ec = event_context;
1413 //        sp_event_context_deactivate (ec);
1414 //        event_context = ec->next;
1415 //        sp_event_context_activate (event_context);
1416 //        _event_context_changed_signal.emit (this, ec);
1417 //    }
1418 //
1419 //    SPEventContext *ref = event_context;
1420 //    while (ref && ref->next && ref->next->key != key)
1421 //        ref = ref->next;
1422 //
1423 //    if (ref && ref->next) {
1424 //        ec = ref->next;
1425 //        ref->next = ec->next;
1426 //    }
1427 //
1428 //    if (ec) {
1429 //        sp_event_context_finish (ec);
1430 //        g_object_unref (G_OBJECT (ec));
1431 //    }
1432 // }
1434 /*
1435   Local Variables:
1436   mode:c++
1437   c-file-style:"stroustrup"
1438   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1439   indent-tabs-mode:nil
1440   fill-column:99
1441   End:
1442 */
1443 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :