6314621c14e146805075c28fae13c2d916a893df
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 // FIXME: Should we handle the case that the perspectives have equal VPs but are not identical?
724 if (persp == NULL || g_slist_find (this->perspectives, persp)) return;
725 this->perspectives = g_slist_prepend (this->perspectives, persp);
726 persp->desktop = this;
727 }
729 void SPDesktop::remove_perspective (Box3D::Perspective3D * const persp)
730 {
731 if (persp == NULL) return;
733 if (!g_slist_find (this->perspectives, persp)) {
734 g_warning ("Could not find perspective in current desktop. Not removed.\n");
735 return;
736 }
738 this->perspectives = g_slist_remove (this->perspectives, persp);
739 }
741 /**
742 * Return viewbox dimensions.
743 */
744 NR::Rect SPDesktop::get_display_area() const
745 {
746 NR::Rect const viewbox = canvas->getViewbox();
748 double const scale = _d2w[0];
750 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
751 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
752 }
754 /**
755 * Revert back to previous zoom if possible.
756 */
757 void
758 SPDesktop::prev_zoom()
759 {
760 if (zooms_past == NULL) {
761 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
762 return;
763 }
765 // push current zoom into forward zooms list
766 push_current_zoom (&zooms_future);
768 // restore previous zoom
769 set_display_area (((NRRect *) zooms_past->data)->x0,
770 ((NRRect *) zooms_past->data)->y0,
771 ((NRRect *) zooms_past->data)->x1,
772 ((NRRect *) zooms_past->data)->y1,
773 0, false);
775 // remove the just-added zoom from the past zooms list
776 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
777 }
779 /**
780 * Set zoom to next in list.
781 */
782 void
783 SPDesktop::next_zoom()
784 {
785 if (zooms_future == NULL) {
786 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
787 return;
788 }
790 // push current zoom into past zooms list
791 push_current_zoom (&zooms_past);
793 // restore next zoom
794 set_display_area (((NRRect *) zooms_future->data)->x0,
795 ((NRRect *) zooms_future->data)->y0,
796 ((NRRect *) zooms_future->data)->x1,
797 ((NRRect *) zooms_future->data)->y1,
798 0, false);
800 // remove the just-used zoom from the zooms_future list
801 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
802 }
804 /**
805 * Zoom to point with absolute zoom factor.
806 */
807 void
808 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
809 {
810 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
812 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
813 // this check prevents "sliding" when trying to zoom in at maximum zoom;
814 /// \todo someone please fix calculations properly and remove this hack
815 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
816 return;
818 NR::Rect const viewbox = canvas->getViewbox();
820 double const width2 = viewbox.dimensions()[NR::X] / zoom;
821 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
823 set_display_area(cx - px * width2,
824 cy - py * height2,
825 cx + (1 - px) * width2,
826 cy + (1 - py) * height2,
827 0.0);
828 }
830 /**
831 * Zoom to center with absolute zoom factor.
832 */
833 void
834 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
835 {
836 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
837 }
839 /**
840 * Zoom to point with relative zoom factor.
841 */
842 void
843 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
844 {
845 NR::Rect const area = get_display_area();
847 if (cx < area.min()[NR::X]) {
848 cx = area.min()[NR::X];
849 }
850 if (cx > area.max()[NR::X]) {
851 cx = area.max()[NR::X];
852 }
853 if (cy < area.min()[NR::Y]) {
854 cy = area.min()[NR::Y];
855 }
856 if (cy > area.max()[NR::Y]) {
857 cy = area.max()[NR::Y];
858 }
860 gdouble const scale = expansion(_d2w) * zoom;
861 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
862 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
864 zoom_absolute_keep_point(cx, cy, px, py, scale);
865 }
867 /**
868 * Zoom to center with relative zoom factor.
869 */
870 void
871 SPDesktop::zoom_relative (double cx, double cy, double zoom)
872 {
873 gdouble scale = expansion(_d2w) * zoom;
874 zoom_absolute (cx, cy, scale);
875 }
877 /**
878 * Set display area to origin and current document dimensions.
879 */
880 void
881 SPDesktop::zoom_page()
882 {
883 NR::Rect d(NR::Point(0, 0),
884 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
886 if (d.isEmpty(1.0)) {
887 return;
888 }
890 set_display_area(d, 10);
891 }
893 /**
894 * Set display area to current document width.
895 */
896 void
897 SPDesktop::zoom_page_width()
898 {
899 NR::Rect const a = get_display_area();
901 if (sp_document_width(doc()) < 1.0) {
902 return;
903 }
905 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
906 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
908 set_display_area(d, 10);
909 }
911 /**
912 * Zoom to selection.
913 */
914 void
915 SPDesktop::zoom_selection()
916 {
917 NR::Maybe<NR::Rect> const d = selection->bounds();
919 if ( !d || d->isEmpty(0.1) ) {
920 return;
921 }
923 set_display_area(*d, 10);
924 }
926 /**
927 * Tell widget to let zoom widget grab keyboard focus.
928 */
929 void
930 SPDesktop::zoom_grab_focus()
931 {
932 _widget->letZoomGrabFocus();
933 }
935 /**
936 * Zoom to whole drawing.
937 */
938 void
939 SPDesktop::zoom_drawing()
940 {
941 g_return_if_fail (doc() != NULL);
942 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
943 g_return_if_fail (docitem != NULL);
945 NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
947 /* Note that the second condition here indicates that
948 ** there are no items in the drawing.
949 */
950 if ( !d || d->isEmpty(1.0) ) {
951 return;
952 }
954 set_display_area(*d, 10);
955 }
957 /**
958 * Scroll canvas by specific coordinate amount.
959 */
960 void
961 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
962 {
963 g_assert(_widget);
965 NR::Rect const viewbox = canvas->getViewbox();
967 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
969 _widget->updateRulers();
970 _widget->updateScrollbars(expansion(_d2w));
971 }
973 bool
974 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
975 {
976 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
978 // autoscrolldistance is in screen pixels, but the display area is in document units
979 autoscrolldistance /= expansion(_d2w);
980 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
982 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
983 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
985 NR::Point const s_w( (*p) * _d2w );
987 gdouble x_to;
988 if ((*p)[NR::X] < dbox.min()[NR::X])
989 x_to = dbox.min()[NR::X];
990 else if ((*p)[NR::X] > dbox.max()[NR::X])
991 x_to = dbox.max()[NR::X];
992 else
993 x_to = (*p)[NR::X];
995 gdouble y_to;
996 if ((*p)[NR::Y] < dbox.min()[NR::Y])
997 y_to = dbox.min()[NR::Y];
998 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
999 y_to = dbox.max()[NR::Y];
1000 else
1001 y_to = (*p)[NR::Y];
1003 NR::Point const d_dt(x_to, y_to);
1004 NR::Point const d_w( d_dt * _d2w );
1005 NR::Point const moved_w( d_w - s_w );
1007 if (autoscrollspeed == 0)
1008 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1010 if (autoscrollspeed != 0)
1011 scroll_world (autoscrollspeed * moved_w);
1013 return true;
1014 }
1015 return false;
1016 }
1018 bool
1019 SPDesktop::is_iconified()
1020 {
1021 return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1022 }
1024 void
1025 SPDesktop::iconify()
1026 {
1027 _widget->setIconified();
1028 }
1030 bool
1031 SPDesktop::is_maximized()
1032 {
1033 return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1034 }
1036 void
1037 SPDesktop::maximize()
1038 {
1039 _widget->setMaximized();
1040 }
1042 bool
1043 SPDesktop::is_fullscreen()
1044 {
1045 return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1046 }
1048 void
1049 SPDesktop::fullscreen()
1050 {
1051 _widget->setFullscreen();
1052 }
1054 void
1055 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1056 {
1057 _widget->getGeometry (x, y, w, h);
1058 }
1060 void
1061 SPDesktop::setWindowPosition (NR::Point p)
1062 {
1063 _widget->setPosition (p);
1064 }
1066 void
1067 SPDesktop::setWindowSize (gint w, gint h)
1068 {
1069 _widget->setSize (w, h);
1070 }
1072 void
1073 SPDesktop::setWindowTransient (void *p, int transient_policy)
1074 {
1075 _widget->setTransient (p, transient_policy);
1076 }
1078 Gtk::Window*
1079 SPDesktop::getToplevel( )
1080 {
1081 return _widget->getWindow();
1082 }
1084 void
1085 SPDesktop::presentWindow()
1086 {
1087 _widget->present();
1088 }
1090 bool
1091 SPDesktop::warnDialog (gchar *text)
1092 {
1093 return _widget->warnDialog (text);
1094 }
1096 void
1097 SPDesktop::toggleRulers()
1098 {
1099 _widget->toggleRulers();
1100 }
1102 void
1103 SPDesktop::toggleScrollbars()
1104 {
1105 _widget->toggleScrollbars();
1106 }
1108 void
1109 SPDesktop::layoutWidget()
1110 {
1111 _widget->layout();
1112 }
1114 void
1115 SPDesktop::destroyWidget()
1116 {
1117 _widget->destroy();
1118 }
1120 bool
1121 SPDesktop::shutdown()
1122 {
1123 return _widget->shutdown();
1124 }
1126 bool SPDesktop::onDeleteUI (GdkEventAny*)
1127 {
1128 if(shutdown()) return true;
1129 destroyWidget();
1130 return false;
1131 }
1133 /**
1134 * onWindowStateEvent
1135 *
1136 * Called when the window changes its maximize/fullscreen/iconify/pinned state.
1137 * Since GTK doesn't have a way to query this state information directly, we
1138 * record it for the desktop here, and also possibly trigger a layout.
1139 */
1140 bool
1141 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1142 {
1143 // Record the desktop window's state
1144 window_state = event->new_window_state;
1146 // Layout may differ depending on full-screen mode or not
1147 GdkWindowState changed = event->changed_mask;
1148 if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1149 layoutWidget();
1150 }
1152 return false;
1153 }
1155 void
1156 SPDesktop::setToolboxFocusTo (gchar const *label)
1157 {
1158 _widget->setToolboxFocusTo (label);
1159 }
1161 void
1162 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1163 {
1164 _widget->setToolboxAdjustmentValue (id, val);
1165 }
1167 bool
1168 SPDesktop::isToolboxButtonActive (gchar const *id)
1169 {
1170 return _widget->isToolboxButtonActive (id);
1171 }
1173 void
1174 SPDesktop::emitToolSubselectionChanged(gpointer data)
1175 {
1176 _tool_subselection_changed.emit(data);
1177 inkscape_subselection_changed (this);
1178 }
1180 void
1181 SPDesktop::updateNow()
1182 {
1183 sp_canvas_update_now(canvas);
1184 }
1186 void
1187 SPDesktop::enableInteraction()
1188 {
1189 _widget->enableInteraction();
1190 }
1192 void SPDesktop::disableInteraction()
1193 {
1194 _widget->disableInteraction();
1195 }
1197 void SPDesktop::setWaitingCursor()
1198 {
1199 GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1200 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1201 gdk_cursor_unref(waiting);
1202 waiting_cursor = true;
1204 // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1205 // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1206 // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1207 // after the call to setWaitingCursor as it was before
1208 while( Gtk::Main::events_pending() )
1209 Gtk::Main::iteration();
1210 }
1212 void SPDesktop::clearWaitingCursor()
1213 {
1214 if (waiting_cursor)
1215 sp_event_context_update_cursor(sp_desktop_event_context(this));
1216 }
1218 void SPDesktop::toggleGrid()
1219 {
1220 if (namedview->grids) {
1221 if(gridgroup) {
1222 grids_visible = !grids_visible;
1223 if (grids_visible) {
1224 sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1225 } else {
1226 sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1227 }
1228 }
1229 } else {
1230 //there is no grid present at the moment. add a rectangular grid and make it visible
1231 Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1232 Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1233 grids_visible = true;
1234 sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1235 }
1236 }
1239 //----------------------------------------------------------------------
1240 // Callback implementations. The virtual ones are connected by the view.
1242 void
1243 SPDesktop::onPositionSet (double x, double y)
1244 {
1245 _widget->viewSetPosition (NR::Point(x,y));
1246 }
1248 void
1249 SPDesktop::onResized (double x, double y)
1250 {
1251 // Nothing called here
1252 }
1254 /**
1255 * Redraw callback; queues Gtk redraw; connected by View.
1256 */
1257 void
1258 SPDesktop::onRedrawRequested ()
1259 {
1260 if (main) {
1261 _widget->requestCanvasUpdate();
1262 }
1263 }
1265 void
1266 SPDesktop::updateCanvasNow()
1267 {
1268 _widget->requestCanvasUpdateAndWait();
1269 }
1271 /**
1272 * Associate document with desktop.
1273 */
1274 void
1275 SPDesktop::setDocument (SPDocument *doc)
1276 {
1277 if (this->doc() && doc) {
1278 namedview->hide(this);
1279 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1280 }
1282 if (_layer_hierarchy) {
1283 _layer_hierarchy->clear();
1284 delete _layer_hierarchy;
1285 }
1286 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1287 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1288 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1289 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1290 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1292 /* setup EventLog */
1293 event_log = new Inkscape::EventLog(doc);
1294 doc->addUndoObserver(*event_log);
1296 _commit_connection.disconnect();
1297 _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1299 /// \todo fixme: This condition exists to make sure the code
1300 /// inside is NOT called on initialization, only on replacement. But there
1301 /// are surely more safe methods to accomplish this.
1302 // TODO since the comment had reversed logic, check the intent of this block of code:
1303 if (drawing) {
1304 NRArenaItem *ai = 0;
1306 namedview = sp_document_namedview (doc, NULL);
1307 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1308 number = namedview->getViewCount();
1310 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1311 SP_CANVAS_ARENA (drawing)->arena,
1312 dkey,
1313 SP_ITEM_SHOW_DISPLAY);
1314 if (ai) {
1315 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1316 nr_arena_item_unref (ai);
1317 }
1318 namedview->show(this);
1319 /* Ugly hack */
1320 activate_guides (true);
1321 /* Ugly hack */
1322 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1323 }
1325 _document_replaced_signal.emit (this, doc);
1327 View::setDocument (doc);
1328 }
1330 void
1331 SPDesktop::onStatusMessage
1332 (Inkscape::MessageType type, gchar const *message)
1333 {
1334 if (_widget) {
1335 _widget->setMessage(type, message);
1336 }
1337 }
1339 void
1340 SPDesktop::onDocumentURISet (gchar const* uri)
1341 {
1342 _widget->setTitle(uri);
1343 }
1345 /**
1346 * Resized callback.
1347 */
1348 void
1349 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1350 {
1351 _doc2dt[5] = height;
1352 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1353 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1354 SP_CTRLRECT(page)->setRectangle(a);
1355 SP_CTRLRECT(page_border)->setRectangle(a);
1356 }
1359 void
1360 SPDesktop::_onActivate (SPDesktop* dt)
1361 {
1362 if (!dt->_widget) return;
1363 dt->_widget->activateDesktop();
1364 }
1366 void
1367 SPDesktop::_onDeactivate (SPDesktop* dt)
1368 {
1369 if (!dt->_widget) return;
1370 dt->_widget->deactivateDesktop();
1371 }
1373 void
1374 SPDesktop::_onSelectionModified
1375 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1376 {
1377 if (!dt->_widget) return;
1378 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1379 }
1381 static void
1382 _onSelectionChanged
1383 (Inkscape::Selection *selection, SPDesktop *desktop)
1384 {
1385 /** \todo
1386 * only change the layer for single selections, or what?
1387 * This seems reasonable -- for multiple selections there can be many
1388 * different layers involved.
1389 */
1390 SPItem *item=selection->singleItem();
1391 if (item) {
1392 SPObject *layer=desktop->layerForObject(item);
1393 if ( layer && layer != desktop->currentLayer() ) {
1394 desktop->setCurrentLayer(layer);
1395 }
1396 }
1397 }
1399 /**
1400 * Calls event handler of current event context.
1401 * \param arena Unused
1402 * \todo fixme
1403 */
1404 static gint
1405 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1406 {
1407 if (ai) {
1408 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1409 return sp_event_context_item_handler (desktop->event_context, spi, event);
1410 } else {
1411 return sp_event_context_root_handler (desktop->event_context, event);
1412 }
1413 }
1415 static void
1416 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1417 g_return_if_fail(SP_IS_GROUP(layer));
1418 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1419 }
1421 /// Callback
1422 static void
1423 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1424 g_return_if_fail(SP_IS_GROUP(layer));
1425 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1426 }
1428 /// Callback
1429 static void
1430 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1431 SPDesktop *desktop)
1432 {
1433 desktop->_layer_changed_signal.emit (bottom);
1434 }
1436 /// Called when document is starting to be rebuilt.
1437 static void
1438 _reconstruction_start (SPDesktop * desktop)
1439 {
1440 // printf("Desktop, starting reconstruction\n");
1441 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1442 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1444 /*
1445 GSList const * selection_objs = desktop->selection->list();
1446 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1448 }
1449 */
1450 desktop->selection->clear();
1452 // printf("Desktop, starting reconstruction end\n");
1453 }
1455 /// Called when document rebuild is finished.
1456 static void
1457 _reconstruction_finish (SPDesktop * desktop)
1458 {
1459 // printf("Desktop, finishing reconstruction\n");
1460 if (desktop->_reconstruction_old_layer_id == NULL)
1461 return;
1463 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1464 if (newLayer != NULL)
1465 desktop->setCurrentLayer(newLayer);
1467 g_free(desktop->_reconstruction_old_layer_id);
1468 desktop->_reconstruction_old_layer_id = NULL;
1469 // printf("Desktop, finishing reconstruction end\n");
1470 return;
1471 }
1473 /**
1474 * Namedview_modified callback.
1475 */
1476 static void
1477 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1478 {
1479 SPNamedView *nv=SP_NAMEDVIEW(obj);
1481 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1483 /* Recalculate snap distances */
1484 /* FIXME: why is the desktop getting involved in setting up something
1485 ** that is entirely to do with the namedview?
1486 */
1487 _update_snap_distances (desktop);
1489 /* Show/hide page background */
1490 if (nv->pagecolor & 0xff) {
1491 sp_canvas_item_show (desktop->table);
1492 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1493 sp_canvas_item_move_to_z (desktop->table, 0);
1494 } else {
1495 sp_canvas_item_hide (desktop->table);
1496 }
1498 /* Show/hide page border */
1499 if (nv->showborder) {
1500 // show
1501 sp_canvas_item_show (desktop->page_border);
1502 // set color and shadow
1503 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1504 if (nv->pageshadow) {
1505 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1506 }
1507 // place in the z-order stack
1508 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1509 sp_canvas_item_move_to_z (desktop->page_border, 2);
1510 } else {
1511 int order = sp_canvas_item_order (desktop->page_border);
1512 int morder = sp_canvas_item_order (desktop->drawing);
1513 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1514 morder - order);
1515 }
1516 } else {
1517 sp_canvas_item_hide (desktop->page_border);
1518 if (nv->pageshadow) {
1519 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1520 }
1521 }
1523 /* Show/hide page shadow */
1524 if (nv->showpageshadow && nv->pageshadow) {
1525 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1526 } else {
1527 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1528 }
1530 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1531 (SP_RGBA32_R_U(nv->pagecolor) +
1532 SP_RGBA32_G_U(nv->pagecolor) +
1533 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1534 // the background color is light or transparent, use black outline
1535 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1536 } else { // use white outline
1537 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1538 }
1539 }
1540 }
1542 /**
1543 * Callback to reset snapper's distances.
1544 */
1545 static void
1546 _update_snap_distances (SPDesktop *desktop)
1547 {
1548 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1550 SPNamedView &nv = *desktop->namedview;
1552 //tell all grid snappers
1553 for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1554 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1555 grid->snapper->setDistance(sp_convert_distance_full(nv.gridtolerance,
1556 *nv.gridtoleranceunit,
1557 px));
1558 }
1560 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1561 *nv.guidetoleranceunit,
1562 px));
1563 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1564 *nv.objecttoleranceunit,
1565 px));
1566 }
1569 NR::Matrix SPDesktop::w2d() const
1570 {
1571 return _w2d;
1572 }
1574 NR::Point SPDesktop::w2d(NR::Point const &p) const
1575 {
1576 return p * _w2d;
1577 }
1579 NR::Point SPDesktop::d2w(NR::Point const &p) const
1580 {
1581 return p * _d2w;
1582 }
1584 NR::Matrix SPDesktop::doc2dt() const
1585 {
1586 return _doc2dt;
1587 }
1589 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1590 {
1591 return p * _doc2dt;
1592 }
1594 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1595 {
1596 return p / _doc2dt;
1597 }
1600 /**
1601 * Pop event context from desktop's context stack. Never used.
1602 */
1603 // void
1604 // SPDesktop::pop_event_context (unsigned int key)
1605 // {
1606 // SPEventContext *ec = NULL;
1607 //
1608 // if (event_context && event_context->key == key) {
1609 // g_return_if_fail (event_context);
1610 // g_return_if_fail (event_context->next);
1611 // ec = event_context;
1612 // sp_event_context_deactivate (ec);
1613 // event_context = ec->next;
1614 // sp_event_context_activate (event_context);
1615 // _event_context_changed_signal.emit (this, ec);
1616 // }
1617 //
1618 // SPEventContext *ref = event_context;
1619 // while (ref && ref->next && ref->next->key != key)
1620 // ref = ref->next;
1621 //
1622 // if (ref && ref->next) {
1623 // ec = ref->next;
1624 // ref->next = ec->next;
1625 // }
1626 //
1627 // if (ec) {
1628 // sp_event_context_finish (ec);
1629 // g_object_unref (G_OBJECT (ec));
1630 // }
1631 // }
1633 /*
1634 Local Variables:
1635 mode:c++
1636 c-file-style:"stroustrup"
1637 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1638 indent-tabs-mode:nil
1639 fill-column:99
1640 End:
1641 */
1642 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :