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 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
13 *
14 * Copyright (C) 2007 Jon A. Cruz
15 * Copyright (C) 2006-2007 Johan Engelen
16 * Copyright (C) 2006 John Bintz
17 * Copyright (C) 2004 MenTaLguY
18 * Copyright (C) 1999-2002 Lauris Kaplinski
19 * Copyright (C) 2000-2001 Ximian, Inc.
20 *
21 * Released under GNU GPL, read the file 'COPYING' for more information
22 */
24 /** \class SPDesktop
25 * SPDesktop is a subclass of View, implementing an editable document
26 * canvas. It is extensively used by many UI controls that need certain
27 * visual representations of their own.
28 *
29 * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
30 * layers of different control objects. The one containing the whole
31 * document is the drawing layer. In addition to it, there are grid,
32 * guide, sketch and control layers. The sketch layer is used for
33 * temporary drawing objects, before the real objects in document are
34 * created. The control layer contains editing knots, rubberband and
35 * similar non-document UI objects.
36 *
37 * Each SPDesktop is associated with a SPNamedView node of the document
38 * tree. Currently, all desktops are created from a single main named
39 * view, but in the future there may be support for different ones.
40 * SPNamedView serves as an in-document container for desktop-related
41 * data, like grid and guideline placement, snapping options and so on.
42 *
43 * Associated with each SPDesktop are the two most important editing
44 * related objects - SPSelection and SPEventContext.
45 *
46 * Sodipodi keeps track of the active desktop and invokes notification
47 * signals whenever it changes. UI elements can use these to update their
48 * display to the selection of the currently active editing window.
49 * (Lauris Kaplinski)
50 */
52 #ifdef HAVE_CONFIG_H
53 # include "config.h"
54 #endif
56 #include <glibmm/i18n.h>
57 #include <sigc++/functors/mem_fun.h>
58 #include <gtkmm.h>
60 #include "macros.h"
61 #include "inkscape-private.h"
62 #include "desktop.h"
63 #include "desktop-events.h"
64 #include "desktop-handles.h"
65 #include "document.h"
66 #include "message-stack.h"
67 #include "selection.h"
68 #include "select-context.h"
69 #include "sp-namedview.h"
70 #include "color.h"
71 #include "sp-item-group.h"
72 #include "prefs-utils.h"
73 #include "object-hierarchy.h"
74 #include "helper/units.h"
75 #include "display/canvas-arena.h"
76 #include "display/nr-arena.h"
77 #include "display/gnome-canvas-acetate.h"
78 #include "display/sodipodi-ctrlrect.h"
79 #include "display/sp-canvas-util.h"
80 #include "libnr/nr-matrix-div.h"
81 #include "libnr/nr-rect-ops.h"
82 #include "ui/dialog/dialog-manager.h"
83 #include "xml/repr.h"
84 #include "message-context.h"
85 #include "layer-manager.h"
86 #include "event-log.h"
87 #include "display/canvas-grid.h"
89 #include "display/sp-canvas.h"
91 namespace Inkscape { namespace XML { class Node; }}
93 // Callback declarations
94 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
95 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
96 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
97 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
98 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
99 static void _reconstruction_start(SPDesktop * desktop);
100 static void _reconstruction_finish(SPDesktop * desktop);
101 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
102 static void _update_snap_distances (SPDesktop *desktop);
104 /**
105 * Return new desktop object.
106 * \pre namedview != NULL.
107 * \pre canvas != NULL.
108 */
109 SPDesktop::SPDesktop() :
110 _dlg_mgr( 0 ),
111 namedview( 0 ),
112 canvas( 0 ),
113 selection( 0 ),
114 event_context( 0 ),
115 layer_manager( 0 ),
116 event_log( 0 ),
117 acetate( 0 ),
118 main( 0 ),
119 gridgroup( 0 ),
120 guides( 0 ),
121 drawing( 0 ),
122 sketch( 0 ),
123 controls( 0 ),
124 table( 0 ),
125 page( 0 ),
126 page_border( 0 ),
127 current( 0 ),
128 zooms_past( 0 ),
129 zooms_future( 0 ),
130 dkey( 0 ),
131 number( 0 ),
132 window_state(0),
133 interaction_disabled_counter( 0 ),
134 waiting_cursor( false ),
135 perspectives (NULL),
136 guides_active( false ),
137 gr_item( 0 ),
138 gr_point_type( 0 ),
139 gr_point_i( 0 ),
140 gr_fill_or_stroke( true ),
141 _layer_hierarchy( 0 ),
142 _reconstruction_old_layer_id( 0 ),
143 _widget( 0 ),
144 _inkscape( 0 ),
145 _guides_message_context( 0 ),
146 _active( false ),
147 _w2d(),
148 _d2w(),
149 _doc2dt( NR::Matrix(NR::scale(1, -1)) ),
150 grids_visible( true )
151 {
152 _d2w.set_identity();
153 _w2d.set_identity();
155 selection = Inkscape::GC::release( new Inkscape::Selection(this) );
156 }
158 void
159 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
160 {
161 _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
163 current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
165 namedview = nv;
166 canvas = aCanvas;
168 SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
169 /* Kill flicker */
170 sp_document_ensure_up_to_date (document);
172 /* Setup Dialog Manager */
173 _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
175 dkey = sp_item_display_key_new (1);
177 /* Connect document */
178 setDocument (document);
180 number = namedview->getViewCount();
183 /* Setup Canvas */
184 g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
186 SPCanvasGroup *root = sp_canvas_root (canvas);
188 /* Setup adminstrative layers */
189 acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
190 g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
191 main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
192 g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
194 table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
195 SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
196 SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
197 sp_canvas_item_move_to_z (table, 0);
199 page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
200 ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
201 page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
203 drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
204 g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
206 SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
208 if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
209 // Start in outline mode
210 setDisplayModeOutline();
211 } else {
212 // Start in normal mode, default
213 setDisplayModeNormal();
214 }
216 gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
217 guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
218 sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
219 controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
221 /* Push select tool to the bottom of stack */
222 /** \todo
223 * FIXME: this is the only call to this. Everything else seems to just
224 * call "set" instead of "push". Can we assume that there is only one
225 * context ever?
226 */
227 push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
229 // display rect and zoom are now handled in sp_desktop_widget_realize()
231 NR::Rect const d(NR::Point(0.0, 0.0),
232 NR::Point(sp_document_width(document), sp_document_height(document)));
234 SP_CTRLRECT(page)->setRectangle(d);
235 SP_CTRLRECT(page_border)->setRectangle(d);
237 /* the following sets the page shadow on the canvas
238 It was originally set to 5, which is really cheesy!
239 It now is an attribute in the document's namedview. If a value of
240 0 is used, then the constructor for a shadow is not initialized.
241 */
243 if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
244 SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
245 }
248 /* Connect event for page resize */
249 _doc2dt[5] = sp_document_height (document);
250 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
252 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
254 NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
255 SP_CANVAS_ARENA (drawing)->arena,
256 dkey,
257 SP_ITEM_SHOW_DISPLAY);
258 if (ai) {
259 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
260 nr_arena_item_unref (ai);
261 }
263 namedview->show(this);
264 /* Ugly hack */
265 activate_guides (true);
266 /* Ugly hack */
267 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
269 /* Set up notification of rebuilding the document, this allows
270 for saving object related settings in the document. */
271 _reconstruction_start_connection =
272 document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
273 _reconstruction_finish_connection =
274 document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
275 _reconstruction_old_layer_id = NULL;
277 // ?
278 // sp_active_desktop_set (desktop);
279 _inkscape = INKSCAPE;
281 _activate_connection = _activate_signal.connect(
282 sigc::bind(
283 sigc::ptr_fun(_onActivate),
284 this
285 )
286 );
287 _deactivate_connection = _deactivate_signal.connect(
288 sigc::bind(
289 sigc::ptr_fun(_onDeactivate),
290 this
291 )
292 );
294 _sel_modified_connection = selection->connectModified(
295 sigc::bind(
296 sigc::ptr_fun(&_onSelectionModified),
297 this
298 )
299 );
300 _sel_changed_connection = selection->connectChanged(
301 sigc::bind(
302 sigc::ptr_fun(&_onSelectionChanged),
303 this
304 )
305 );
308 /* setup LayerManager */
309 // (Setting up after the connections are all in place, as it may use some of them)
310 layer_manager = new Inkscape::LayerManager( this );
312 grids_visible = true;
314 /* Create initial perspective, append it to the list of existing perspectives
315 and make it the current perspective */
316 Box3D::Perspective3D *initial_persp = new Box3D::Perspective3D (
317 // VP in x-direction
318 Box3D::VanishingPoint( NR::Point(-50.0, 600.0),
319 NR::Point( -1.0, 0.0), Box3D::VP_FINITE),
320 // VP in y-direction
321 Box3D::VanishingPoint( NR::Point(500.0,1000.0),
322 NR::Point( 0.0, 1.0), Box3D::VP_INFINITE),
323 // VP in z-direction
324 Box3D::VanishingPoint( NR::Point(700.0, 600.0),
325 NR::Point(sqrt(3.0),1.0), Box3D::VP_FINITE));
326 this->add_perspective (initial_persp);
327 Box3D::Perspective3D::current_perspective = (Box3D::Perspective3D *) perspectives->data;
328 }
331 void SPDesktop::destroy()
332 {
333 _activate_connection.disconnect();
334 _deactivate_connection.disconnect();
335 _sel_modified_connection.disconnect();
336 _sel_changed_connection.disconnect();
337 _modified_connection.disconnect();
338 _commit_connection.disconnect();
339 _reconstruction_start_connection.disconnect();
340 _reconstruction_finish_connection.disconnect();
342 g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
343 g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
344 g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
346 while (event_context) {
347 SPEventContext *ec = event_context;
348 event_context = ec->next;
349 sp_event_context_finish (ec);
350 g_object_unref (G_OBJECT (ec));
351 }
353 if (_layer_hierarchy) {
354 delete _layer_hierarchy;
355 }
357 if (_inkscape) {
358 _inkscape = NULL;
359 }
361 if (drawing) {
362 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
363 drawing = NULL;
364 }
366 delete _guides_message_context;
367 _guides_message_context = NULL;
369 g_list_free (zooms_past);
370 g_list_free (zooms_future);
372 for (GSList *p = this->perspectives; p != NULL; p = p->next) {
373 delete ((Box3D::Perspective3D *) p->data);
374 }
375 g_slist_free (perspectives);
376 }
378 SPDesktop::~SPDesktop() {}
380 //--------------------------------------------------------------------
381 /* Public methods */
383 void SPDesktop::setDisplayModeNormal()
384 {
385 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
386 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
387 displayMode = RENDERMODE_NORMAL;
388 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
389 _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
390 }
392 void SPDesktop::setDisplayModeOutline()
393 {
394 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
395 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
396 displayMode = RENDERMODE_OUTLINE;
397 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
398 _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
399 }
401 void SPDesktop::displayModeToggle()
402 {
403 if (displayMode == RENDERMODE_OUTLINE)
404 setDisplayModeNormal();
405 else
406 setDisplayModeOutline();
407 }
409 /**
410 * Returns current root (=bottom) layer.
411 */
412 SPObject *SPDesktop::currentRoot() const
413 {
414 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
415 }
417 /**
418 * Returns current top layer.
419 */
420 SPObject *SPDesktop::currentLayer() const
421 {
422 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
423 }
425 /**
426 * Sets the current layer of the desktop.
427 *
428 * Make \a object the top layer.
429 */
430 void SPDesktop::setCurrentLayer(SPObject *object) {
431 g_return_if_fail(SP_IS_GROUP(object));
432 g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
433 // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
434 _layer_hierarchy->setBottom(object);
435 }
437 /**
438 * Return layer that contains \a object.
439 */
440 SPObject *SPDesktop::layerForObject(SPObject *object) {
441 g_return_val_if_fail(object != NULL, NULL);
443 SPObject *root=currentRoot();
444 object = SP_OBJECT_PARENT(object);
445 while ( object && object != root && !isLayer(object) ) {
446 object = SP_OBJECT_PARENT(object);
447 }
448 return object;
449 }
451 /**
452 * True if object is a layer.
453 */
454 bool SPDesktop::isLayer(SPObject *object) const {
455 return ( SP_IS_GROUP(object)
456 && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
457 == SPGroup::LAYER ) );
458 }
460 /**
461 * True if desktop viewport fully contains \a item's bbox.
462 */
463 bool SPDesktop::isWithinViewport (SPItem *item) const
464 {
465 NR::Rect const viewport = get_display_area();
466 NR::Maybe<NR::Rect> const bbox = sp_item_bbox_desktop(item);
467 if (bbox) {
468 return viewport.contains(*bbox);
469 } else {
470 return true;
471 }
472 }
474 ///
475 bool SPDesktop::itemIsHidden(SPItem const *item) const {
476 return item->isHidden(this->dkey);
477 }
479 /**
480 * Set activate property of desktop; emit signal if changed.
481 */
482 void
483 SPDesktop::set_active (bool new_active)
484 {
485 if (new_active != _active) {
486 _active = new_active;
487 if (new_active) {
488 _activate_signal.emit();
489 } else {
490 _deactivate_signal.emit();
491 }
492 }
493 }
495 /**
496 * Set activate status of current desktop's named view.
497 */
498 void
499 SPDesktop::activate_guides(bool activate)
500 {
501 guides_active = activate;
502 namedview->activateGuides(this, activate);
503 }
505 /**
506 * Make desktop switch documents.
507 */
508 void
509 SPDesktop::change_document (SPDocument *theDocument)
510 {
511 g_return_if_fail (theDocument != NULL);
513 /* unselect everything before switching documents */
514 selection->clear();
516 setDocument (theDocument);
517 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
518 _document_replaced_signal.emit (this, theDocument);
519 }
521 /**
522 * Make desktop switch event contexts.
523 */
524 void
525 SPDesktop::set_event_context (GtkType type, const gchar *config)
526 {
527 SPEventContext *ec;
528 while (event_context) {
529 ec = event_context;
530 sp_event_context_deactivate (ec);
531 event_context = ec->next;
532 sp_event_context_finish (ec);
533 g_object_unref (G_OBJECT (ec));
534 }
536 Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
537 ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
538 ec->next = event_context;
539 event_context = ec;
540 sp_event_context_activate (ec);
541 _event_context_changed_signal.emit (this, ec);
542 }
544 /**
545 * Push event context onto desktop's context stack.
546 */
547 void
548 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
549 {
550 SPEventContext *ref, *ec;
551 Inkscape::XML::Node *repr;
553 if (event_context && event_context->key == key) return;
554 ref = event_context;
555 while (ref && ref->next && ref->next->key != key) ref = ref->next;
556 if (ref && ref->next) {
557 ec = ref->next;
558 ref->next = ec->next;
559 sp_event_context_finish (ec);
560 g_object_unref (G_OBJECT (ec));
561 }
563 if (event_context) sp_event_context_deactivate (event_context);
564 repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
565 ec = sp_event_context_new (type, this, repr, key);
566 ec->next = event_context;
567 event_context = ec;
568 sp_event_context_activate (ec);
569 _event_context_changed_signal.emit (this, ec);
570 }
572 /**
573 * Sets the coordinate status to a given point
574 */
575 void
576 SPDesktop::set_coordinate_status (NR::Point p) {
577 _widget->setCoordinateStatus(p);
578 }
580 /**
581 * \see sp_document_item_from_list_at_point_bottom()
582 */
583 SPItem *
584 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
585 {
586 g_return_val_if_fail (doc() != NULL, NULL);
587 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
588 }
590 /**
591 * \see sp_document_item_at_point()
592 */
593 SPItem *
594 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
595 {
596 g_return_val_if_fail (doc() != NULL, NULL);
597 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
598 }
600 /**
601 * \see sp_document_group_at_point()
602 */
603 SPItem *
604 SPDesktop::group_at_point (NR::Point const p) const
605 {
606 g_return_val_if_fail (doc() != NULL, NULL);
607 return sp_document_group_at_point (doc(), dkey, p);
608 }
610 /**
611 * \brief Returns the mouse point in document coordinates; if mouse is
612 * outside the canvas, returns the center of canvas viewpoint
613 */
614 NR::Point
615 SPDesktop::point() const
616 {
617 NR::Point p = _widget->getPointer();
618 NR::Point pw = sp_canvas_window_to_world (canvas, p);
619 p = w2d(pw);
621 NR::Rect const r = canvas->getViewbox();
623 NR::Point r0 = w2d(r.min());
624 NR::Point r1 = w2d(r.max());
626 if (p[NR::X] >= r0[NR::X] &&
627 p[NR::X] <= r1[NR::X] &&
628 p[NR::Y] >= r1[NR::Y] &&
629 p[NR::Y] <= r0[NR::Y])
630 {
631 return p;
632 } else {
633 return (r0 + r1) / 2;
634 }
635 }
637 /**
638 * Put current zoom data in history list.
639 */
640 void
641 SPDesktop::push_current_zoom (GList **history)
642 {
643 NR::Rect const area = get_display_area();
645 NRRect *old_zoom = g_new(NRRect, 1);
646 old_zoom->x0 = area.min()[NR::X];
647 old_zoom->x1 = area.max()[NR::X];
648 old_zoom->y0 = area.min()[NR::Y];
649 old_zoom->y1 = area.max()[NR::Y];
650 if ( *history == NULL
651 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
652 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
653 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
654 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
655 {
656 *history = g_list_prepend (*history, old_zoom);
657 }
658 }
660 /**
661 * Set viewbox.
662 */
663 void
664 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
665 {
666 g_assert(_widget);
668 // save the zoom
669 if (log) {
670 push_current_zoom(&zooms_past);
671 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
672 g_list_free (zooms_future);
673 zooms_future = NULL;
674 }
676 double const cx = 0.5 * (x0 + x1);
677 double const cy = 0.5 * (y0 + y1);
679 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
681 double scale = expansion(_d2w);
682 double newscale;
683 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
684 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
685 } else {
686 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
687 }
689 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
691 int clear = FALSE;
692 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
693 /* Set zoom factors */
694 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
695 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
696 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
697 clear = TRUE;
698 }
700 /* Calculate top left corner */
701 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
702 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
704 /* Scroll */
705 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
707 _widget->updateRulers();
708 _widget->updateScrollbars(expansion(_d2w));
709 _widget->updateZoom();
710 }
712 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
713 {
714 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
715 }
717 /**
718 * Add a perspective to the desktop if it doesn't exist yet
719 */
720 void
721 SPDesktop::add_perspective (Box3D::Perspective3D * const persp)
722 {
723 // check that the perspective is not yet linked to another desktop
724 if (persp->desktop != NULL) {
725 g_assert (persp->desktop == this);
726 }
728 // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical?
729 // If so, we need to take care of relinking the boxes, etc.
730 if (persp == NULL || g_slist_find (this->perspectives, persp)) return;
731 this->perspectives = g_slist_prepend (this->perspectives, persp);
732 persp->desktop = this;
733 }
735 void SPDesktop::remove_perspective (Box3D::Perspective3D * const persp)
736 {
737 if (persp == NULL) return;
738 if (!g_slist_find (this->perspectives, persp)) {
739 g_warning ("Could not find perspective in current desktop. Not removed.\n");
740 return;
741 }
742 this->perspectives = g_slist_remove (this->perspectives, persp);
743 }
745 // find an existing perspective whose VPs are equal to those of persp
746 Box3D::Perspective3D * SPDesktop::find_perspective (Box3D::Perspective3D * const persp)
747 {
748 for (GSList *p = this->perspectives; p != NULL; p = p->next) {
749 if (*((Box3D::Perspective3D *) p->data) == *persp) {
750 return ((Box3D::Perspective3D *) p->data);
751 }
752 }
753 return NULL; // perspective was not found
754 }
756 /**
757 * Return viewbox dimensions.
758 */
759 NR::Rect SPDesktop::get_display_area() const
760 {
761 NR::Rect const viewbox = canvas->getViewbox();
763 double const scale = _d2w[0];
765 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
766 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
767 }
769 /**
770 * Revert back to previous zoom if possible.
771 */
772 void
773 SPDesktop::prev_zoom()
774 {
775 if (zooms_past == NULL) {
776 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
777 return;
778 }
780 // push current zoom into forward zooms list
781 push_current_zoom (&zooms_future);
783 // restore previous zoom
784 set_display_area (((NRRect *) zooms_past->data)->x0,
785 ((NRRect *) zooms_past->data)->y0,
786 ((NRRect *) zooms_past->data)->x1,
787 ((NRRect *) zooms_past->data)->y1,
788 0, false);
790 // remove the just-added zoom from the past zooms list
791 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
792 }
794 /**
795 * Set zoom to next in list.
796 */
797 void
798 SPDesktop::next_zoom()
799 {
800 if (zooms_future == NULL) {
801 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
802 return;
803 }
805 // push current zoom into past zooms list
806 push_current_zoom (&zooms_past);
808 // restore next zoom
809 set_display_area (((NRRect *) zooms_future->data)->x0,
810 ((NRRect *) zooms_future->data)->y0,
811 ((NRRect *) zooms_future->data)->x1,
812 ((NRRect *) zooms_future->data)->y1,
813 0, false);
815 // remove the just-used zoom from the zooms_future list
816 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
817 }
819 /**
820 * Zoom to point with absolute zoom factor.
821 */
822 void
823 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
824 {
825 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
827 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
828 // this check prevents "sliding" when trying to zoom in at maximum zoom;
829 /// \todo someone please fix calculations properly and remove this hack
830 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
831 return;
833 NR::Rect const viewbox = canvas->getViewbox();
835 double const width2 = viewbox.dimensions()[NR::X] / zoom;
836 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
838 set_display_area(cx - px * width2,
839 cy - py * height2,
840 cx + (1 - px) * width2,
841 cy + (1 - py) * height2,
842 0.0);
843 }
845 /**
846 * Zoom to center with absolute zoom factor.
847 */
848 void
849 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
850 {
851 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
852 }
854 /**
855 * Zoom to point with relative zoom factor.
856 */
857 void
858 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
859 {
860 NR::Rect const area = get_display_area();
862 if (cx < area.min()[NR::X]) {
863 cx = area.min()[NR::X];
864 }
865 if (cx > area.max()[NR::X]) {
866 cx = area.max()[NR::X];
867 }
868 if (cy < area.min()[NR::Y]) {
869 cy = area.min()[NR::Y];
870 }
871 if (cy > area.max()[NR::Y]) {
872 cy = area.max()[NR::Y];
873 }
875 gdouble const scale = expansion(_d2w) * zoom;
876 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
877 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
879 zoom_absolute_keep_point(cx, cy, px, py, scale);
880 }
882 /**
883 * Zoom to center with relative zoom factor.
884 */
885 void
886 SPDesktop::zoom_relative (double cx, double cy, double zoom)
887 {
888 gdouble scale = expansion(_d2w) * zoom;
889 zoom_absolute (cx, cy, scale);
890 }
892 /**
893 * Set display area to origin and current document dimensions.
894 */
895 void
896 SPDesktop::zoom_page()
897 {
898 NR::Rect d(NR::Point(0, 0),
899 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
901 if (d.isEmpty(1.0)) {
902 return;
903 }
905 set_display_area(d, 10);
906 }
908 /**
909 * Set display area to current document width.
910 */
911 void
912 SPDesktop::zoom_page_width()
913 {
914 NR::Rect const a = get_display_area();
916 if (sp_document_width(doc()) < 1.0) {
917 return;
918 }
920 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
921 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
923 set_display_area(d, 10);
924 }
926 /**
927 * Zoom to selection.
928 */
929 void
930 SPDesktop::zoom_selection()
931 {
932 NR::Maybe<NR::Rect> const d = selection->bounds();
934 if ( !d || d->isEmpty(0.1) ) {
935 return;
936 }
938 set_display_area(*d, 10);
939 }
941 /**
942 * Tell widget to let zoom widget grab keyboard focus.
943 */
944 void
945 SPDesktop::zoom_grab_focus()
946 {
947 _widget->letZoomGrabFocus();
948 }
950 /**
951 * Zoom to whole drawing.
952 */
953 void
954 SPDesktop::zoom_drawing()
955 {
956 g_return_if_fail (doc() != NULL);
957 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
958 g_return_if_fail (docitem != NULL);
960 NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
962 /* Note that the second condition here indicates that
963 ** there are no items in the drawing.
964 */
965 if ( !d || d->isEmpty(1.0) ) {
966 return;
967 }
969 set_display_area(*d, 10);
970 }
972 /**
973 * Scroll canvas by specific coordinate amount.
974 */
975 void
976 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
977 {
978 g_assert(_widget);
980 NR::Rect const viewbox = canvas->getViewbox();
982 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
984 _widget->updateRulers();
985 _widget->updateScrollbars(expansion(_d2w));
986 }
988 bool
989 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
990 {
991 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
993 // autoscrolldistance is in screen pixels, but the display area is in document units
994 autoscrolldistance /= expansion(_d2w);
995 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
997 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
998 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
1000 NR::Point const s_w( (*p) * _d2w );
1002 gdouble x_to;
1003 if ((*p)[NR::X] < dbox.min()[NR::X])
1004 x_to = dbox.min()[NR::X];
1005 else if ((*p)[NR::X] > dbox.max()[NR::X])
1006 x_to = dbox.max()[NR::X];
1007 else
1008 x_to = (*p)[NR::X];
1010 gdouble y_to;
1011 if ((*p)[NR::Y] < dbox.min()[NR::Y])
1012 y_to = dbox.min()[NR::Y];
1013 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
1014 y_to = dbox.max()[NR::Y];
1015 else
1016 y_to = (*p)[NR::Y];
1018 NR::Point const d_dt(x_to, y_to);
1019 NR::Point const d_w( d_dt * _d2w );
1020 NR::Point const moved_w( d_w - s_w );
1022 if (autoscrollspeed == 0)
1023 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1025 if (autoscrollspeed != 0)
1026 scroll_world (autoscrollspeed * moved_w);
1028 return true;
1029 }
1030 return false;
1031 }
1033 bool
1034 SPDesktop::is_iconified()
1035 {
1036 return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1037 }
1039 void
1040 SPDesktop::iconify()
1041 {
1042 _widget->setIconified();
1043 }
1045 bool
1046 SPDesktop::is_maximized()
1047 {
1048 return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1049 }
1051 void
1052 SPDesktop::maximize()
1053 {
1054 _widget->setMaximized();
1055 }
1057 bool
1058 SPDesktop::is_fullscreen()
1059 {
1060 return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1061 }
1063 void
1064 SPDesktop::fullscreen()
1065 {
1066 _widget->setFullscreen();
1067 }
1069 void
1070 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1071 {
1072 _widget->getGeometry (x, y, w, h);
1073 }
1075 void
1076 SPDesktop::setWindowPosition (NR::Point p)
1077 {
1078 _widget->setPosition (p);
1079 }
1081 void
1082 SPDesktop::setWindowSize (gint w, gint h)
1083 {
1084 _widget->setSize (w, h);
1085 }
1087 void
1088 SPDesktop::setWindowTransient (void *p, int transient_policy)
1089 {
1090 _widget->setTransient (p, transient_policy);
1091 }
1093 Gtk::Window*
1094 SPDesktop::getToplevel( )
1095 {
1096 return _widget->getWindow();
1097 }
1099 void
1100 SPDesktop::presentWindow()
1101 {
1102 _widget->present();
1103 }
1105 bool
1106 SPDesktop::warnDialog (gchar *text)
1107 {
1108 return _widget->warnDialog (text);
1109 }
1111 void
1112 SPDesktop::toggleRulers()
1113 {
1114 _widget->toggleRulers();
1115 }
1117 void
1118 SPDesktop::toggleScrollbars()
1119 {
1120 _widget->toggleScrollbars();
1121 }
1123 void
1124 SPDesktop::layoutWidget()
1125 {
1126 _widget->layout();
1127 }
1129 void
1130 SPDesktop::destroyWidget()
1131 {
1132 _widget->destroy();
1133 }
1135 bool
1136 SPDesktop::shutdown()
1137 {
1138 return _widget->shutdown();
1139 }
1141 bool SPDesktop::onDeleteUI (GdkEventAny*)
1142 {
1143 if(shutdown()) return true;
1144 destroyWidget();
1145 return false;
1146 }
1148 /**
1149 * onWindowStateEvent
1150 *
1151 * Called when the window changes its maximize/fullscreen/iconify/pinned state.
1152 * Since GTK doesn't have a way to query this state information directly, we
1153 * record it for the desktop here, and also possibly trigger a layout.
1154 */
1155 bool
1156 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1157 {
1158 // Record the desktop window's state
1159 window_state = event->new_window_state;
1161 // Layout may differ depending on full-screen mode or not
1162 GdkWindowState changed = event->changed_mask;
1163 if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1164 layoutWidget();
1165 }
1167 return false;
1168 }
1170 void
1171 SPDesktop::setToolboxFocusTo (gchar const *label)
1172 {
1173 _widget->setToolboxFocusTo (label);
1174 }
1176 void
1177 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1178 {
1179 _widget->setToolboxAdjustmentValue (id, val);
1180 }
1182 bool
1183 SPDesktop::isToolboxButtonActive (gchar const *id)
1184 {
1185 return _widget->isToolboxButtonActive (id);
1186 }
1188 void
1189 SPDesktop::emitToolSubselectionChanged(gpointer data)
1190 {
1191 _tool_subselection_changed.emit(data);
1192 inkscape_subselection_changed (this);
1193 }
1195 void
1196 SPDesktop::updateNow()
1197 {
1198 sp_canvas_update_now(canvas);
1199 }
1201 void
1202 SPDesktop::enableInteraction()
1203 {
1204 _widget->enableInteraction();
1205 }
1207 void SPDesktop::disableInteraction()
1208 {
1209 _widget->disableInteraction();
1210 }
1212 void SPDesktop::setWaitingCursor()
1213 {
1214 GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1215 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1216 gdk_cursor_unref(waiting);
1217 waiting_cursor = true;
1219 // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1220 // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1221 // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1222 // after the call to setWaitingCursor as it was before
1223 while( Gtk::Main::events_pending() )
1224 Gtk::Main::iteration();
1225 }
1227 void SPDesktop::clearWaitingCursor()
1228 {
1229 if (waiting_cursor)
1230 sp_event_context_update_cursor(sp_desktop_event_context(this));
1231 }
1233 void SPDesktop::toggleGrid()
1234 {
1235 if (namedview->grids) {
1236 if(gridgroup) {
1237 grids_visible = !grids_visible;
1238 if (grids_visible) {
1239 sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1240 } else {
1241 sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1242 }
1243 }
1244 } else {
1245 //there is no grid present at the moment. add a rectangular grid and make it visible
1246 Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1247 Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1248 grids_visible = true;
1249 sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1250 }
1251 }
1254 //----------------------------------------------------------------------
1255 // Callback implementations. The virtual ones are connected by the view.
1257 void
1258 SPDesktop::onPositionSet (double x, double y)
1259 {
1260 _widget->viewSetPosition (NR::Point(x,y));
1261 }
1263 void
1264 SPDesktop::onResized (double x, double y)
1265 {
1266 // Nothing called here
1267 }
1269 /**
1270 * Redraw callback; queues Gtk redraw; connected by View.
1271 */
1272 void
1273 SPDesktop::onRedrawRequested ()
1274 {
1275 if (main) {
1276 _widget->requestCanvasUpdate();
1277 }
1278 }
1280 void
1281 SPDesktop::updateCanvasNow()
1282 {
1283 _widget->requestCanvasUpdateAndWait();
1284 }
1286 /**
1287 * Associate document with desktop.
1288 */
1289 void
1290 SPDesktop::setDocument (SPDocument *doc)
1291 {
1292 if (this->doc() && doc) {
1293 namedview->hide(this);
1294 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1295 }
1297 if (_layer_hierarchy) {
1298 _layer_hierarchy->clear();
1299 delete _layer_hierarchy;
1300 }
1301 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1302 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1303 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1304 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1305 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1307 /* setup EventLog */
1308 event_log = new Inkscape::EventLog(doc);
1309 doc->addUndoObserver(*event_log);
1311 _commit_connection.disconnect();
1312 _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1314 /// \todo fixme: This condition exists to make sure the code
1315 /// inside is NOT called on initialization, only on replacement. But there
1316 /// are surely more safe methods to accomplish this.
1317 // TODO since the comment had reversed logic, check the intent of this block of code:
1318 if (drawing) {
1319 NRArenaItem *ai = 0;
1321 namedview = sp_document_namedview (doc, NULL);
1322 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1323 number = namedview->getViewCount();
1325 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1326 SP_CANVAS_ARENA (drawing)->arena,
1327 dkey,
1328 SP_ITEM_SHOW_DISPLAY);
1329 if (ai) {
1330 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1331 nr_arena_item_unref (ai);
1332 }
1333 namedview->show(this);
1334 /* Ugly hack */
1335 activate_guides (true);
1336 /* Ugly hack */
1337 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1338 }
1340 _document_replaced_signal.emit (this, doc);
1342 View::setDocument (doc);
1343 }
1345 void
1346 SPDesktop::onStatusMessage
1347 (Inkscape::MessageType type, gchar const *message)
1348 {
1349 if (_widget) {
1350 _widget->setMessage(type, message);
1351 }
1352 }
1354 void
1355 SPDesktop::onDocumentURISet (gchar const* uri)
1356 {
1357 _widget->setTitle(uri);
1358 }
1360 /**
1361 * Resized callback.
1362 */
1363 void
1364 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1365 {
1366 _doc2dt[5] = height;
1367 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1368 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1369 SP_CTRLRECT(page)->setRectangle(a);
1370 SP_CTRLRECT(page_border)->setRectangle(a);
1371 }
1374 void
1375 SPDesktop::_onActivate (SPDesktop* dt)
1376 {
1377 if (!dt->_widget) return;
1378 dt->_widget->activateDesktop();
1379 }
1381 void
1382 SPDesktop::_onDeactivate (SPDesktop* dt)
1383 {
1384 if (!dt->_widget) return;
1385 dt->_widget->deactivateDesktop();
1386 }
1388 void
1389 SPDesktop::_onSelectionModified
1390 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1391 {
1392 if (!dt->_widget) return;
1393 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1394 }
1396 static void
1397 _onSelectionChanged
1398 (Inkscape::Selection *selection, SPDesktop *desktop)
1399 {
1400 /** \todo
1401 * only change the layer for single selections, or what?
1402 * This seems reasonable -- for multiple selections there can be many
1403 * different layers involved.
1404 */
1405 SPItem *item=selection->singleItem();
1406 if (item) {
1407 SPObject *layer=desktop->layerForObject(item);
1408 if ( layer && layer != desktop->currentLayer() ) {
1409 desktop->setCurrentLayer(layer);
1410 }
1411 }
1412 }
1414 /**
1415 * Calls event handler of current event context.
1416 * \param arena Unused
1417 * \todo fixme
1418 */
1419 static gint
1420 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1421 {
1422 if (ai) {
1423 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1424 return sp_event_context_item_handler (desktop->event_context, spi, event);
1425 } else {
1426 return sp_event_context_root_handler (desktop->event_context, event);
1427 }
1428 }
1430 static void
1431 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1432 g_return_if_fail(SP_IS_GROUP(layer));
1433 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1434 }
1436 /// Callback
1437 static void
1438 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1439 g_return_if_fail(SP_IS_GROUP(layer));
1440 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1441 }
1443 /// Callback
1444 static void
1445 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1446 SPDesktop *desktop)
1447 {
1448 desktop->_layer_changed_signal.emit (bottom);
1449 }
1451 /// Called when document is starting to be rebuilt.
1452 static void
1453 _reconstruction_start (SPDesktop * desktop)
1454 {
1455 // printf("Desktop, starting reconstruction\n");
1456 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1457 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1459 /*
1460 GSList const * selection_objs = desktop->selection->list();
1461 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1463 }
1464 */
1465 desktop->selection->clear();
1467 // printf("Desktop, starting reconstruction end\n");
1468 }
1470 /// Called when document rebuild is finished.
1471 static void
1472 _reconstruction_finish (SPDesktop * desktop)
1473 {
1474 // printf("Desktop, finishing reconstruction\n");
1475 if (desktop->_reconstruction_old_layer_id == NULL)
1476 return;
1478 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1479 if (newLayer != NULL)
1480 desktop->setCurrentLayer(newLayer);
1482 g_free(desktop->_reconstruction_old_layer_id);
1483 desktop->_reconstruction_old_layer_id = NULL;
1484 // printf("Desktop, finishing reconstruction end\n");
1485 return;
1486 }
1488 /**
1489 * Namedview_modified callback.
1490 */
1491 static void
1492 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1493 {
1494 SPNamedView *nv=SP_NAMEDVIEW(obj);
1496 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1498 /* Recalculate snap distances */
1499 /* FIXME: why is the desktop getting involved in setting up something
1500 ** that is entirely to do with the namedview?
1501 */
1502 _update_snap_distances (desktop);
1504 /* Show/hide page background */
1505 if (nv->pagecolor & 0xff) {
1506 sp_canvas_item_show (desktop->table);
1507 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1508 sp_canvas_item_move_to_z (desktop->table, 0);
1509 } else {
1510 sp_canvas_item_hide (desktop->table);
1511 }
1513 /* Show/hide page border */
1514 if (nv->showborder) {
1515 // show
1516 sp_canvas_item_show (desktop->page_border);
1517 // set color and shadow
1518 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1519 if (nv->pageshadow) {
1520 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1521 }
1522 // place in the z-order stack
1523 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1524 sp_canvas_item_move_to_z (desktop->page_border, 2);
1525 } else {
1526 int order = sp_canvas_item_order (desktop->page_border);
1527 int morder = sp_canvas_item_order (desktop->drawing);
1528 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1529 morder - order);
1530 }
1531 } else {
1532 sp_canvas_item_hide (desktop->page_border);
1533 if (nv->pageshadow) {
1534 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1535 }
1536 }
1538 /* Show/hide page shadow */
1539 if (nv->showpageshadow && nv->pageshadow) {
1540 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1541 } else {
1542 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1543 }
1545 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1546 (SP_RGBA32_R_U(nv->pagecolor) +
1547 SP_RGBA32_G_U(nv->pagecolor) +
1548 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1549 // the background color is light or transparent, use black outline
1550 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1551 } else { // use white outline
1552 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1553 }
1554 }
1555 }
1557 /**
1558 * Callback to reset snapper's distances.
1559 */
1560 static void
1561 _update_snap_distances (SPDesktop *desktop)
1562 {
1563 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1565 SPNamedView &nv = *desktop->namedview;
1567 //tell all grid snappers
1568 for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1569 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1570 grid->snapper->setDistance(sp_convert_distance_full(nv.gridtolerance,
1571 *nv.gridtoleranceunit,
1572 px));
1573 }
1575 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1576 *nv.guidetoleranceunit,
1577 px));
1578 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1579 *nv.objecttoleranceunit,
1580 px));
1581 }
1584 NR::Matrix SPDesktop::w2d() const
1585 {
1586 return _w2d;
1587 }
1589 NR::Point SPDesktop::w2d(NR::Point const &p) const
1590 {
1591 return p * _w2d;
1592 }
1594 NR::Point SPDesktop::d2w(NR::Point const &p) const
1595 {
1596 return p * _d2w;
1597 }
1599 NR::Matrix SPDesktop::doc2dt() const
1600 {
1601 return _doc2dt;
1602 }
1604 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1605 {
1606 return p * _doc2dt;
1607 }
1609 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1610 {
1611 return p / _doc2dt;
1612 }
1615 /**
1616 * Pop event context from desktop's context stack. Never used.
1617 */
1618 // void
1619 // SPDesktop::pop_event_context (unsigned int key)
1620 // {
1621 // SPEventContext *ec = NULL;
1622 //
1623 // if (event_context && event_context->key == key) {
1624 // g_return_if_fail (event_context);
1625 // g_return_if_fail (event_context->next);
1626 // ec = event_context;
1627 // sp_event_context_deactivate (ec);
1628 // event_context = ec->next;
1629 // sp_event_context_activate (event_context);
1630 // _event_context_changed_signal.emit (this, ec);
1631 // }
1632 //
1633 // SPEventContext *ref = event_context;
1634 // while (ref && ref->next && ref->next->key != key)
1635 // ref = ref->next;
1636 //
1637 // if (ref && ref->next) {
1638 // ec = ref->next;
1639 // ref->next = ec->next;
1640 // }
1641 //
1642 // if (ec) {
1643 // sp_event_context_finish (ec);
1644 // g_object_unref (G_OBJECT (ec));
1645 // }
1646 // }
1648 /*
1649 Local Variables:
1650 mode:c++
1651 c-file-style:"stroustrup"
1652 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1653 indent-tabs-mode:nil
1654 fill-column:99
1655 End:
1656 */
1657 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :