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-2008 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 "display/canvas-temporary-item-list.h"
81 #include "display/snap-indicator.h"
82 #include "libnr/nr-matrix-div.h"
83 #include "libnr/nr-rect-ops.h"
84 #include "ui/dialog/dialog-manager.h"
85 #include "xml/repr.h"
86 #include "message-context.h"
87 #include "layer-manager.h"
88 #include "event-log.h"
89 #include "display/canvas-grid.h"
90 #include "widgets/desktop-widget.h"
91 #include "box3d-context.h"
93 #include "display/sp-canvas.h"
95 namespace Inkscape { namespace XML { class Node; }}
97 // Callback declarations
98 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
99 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
100 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
101 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
102 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
103 static void _reconstruction_start(SPDesktop * desktop);
104 static void _reconstruction_finish(SPDesktop * desktop);
105 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
106 static void _update_snap_distances (SPDesktop *desktop);
108 /**
109 * Return new desktop object.
110 * \pre namedview != NULL.
111 * \pre canvas != NULL.
112 */
113 SPDesktop::SPDesktop() :
114 _dlg_mgr( 0 ),
115 namedview( 0 ),
116 canvas( 0 ),
117 selection( 0 ),
118 event_context( 0 ),
119 layer_manager( 0 ),
120 event_log( 0 ),
121 temporary_item_list( 0 ),
122 snapindicator( 0 ),
123 acetate( 0 ),
124 main( 0 ),
125 gridgroup( 0 ),
126 guides( 0 ),
127 drawing( 0 ),
128 sketch( 0 ),
129 controls( 0 ),
130 tempgroup ( 0 ),
131 table( 0 ),
132 page( 0 ),
133 page_border( 0 ),
134 current( 0 ),
135 zooms_past( 0 ),
136 zooms_future( 0 ),
137 dkey( 0 ),
138 number( 0 ),
139 window_state(0),
140 interaction_disabled_counter( 0 ),
141 waiting_cursor( false ),
142 guides_active( false ),
143 gr_item( 0 ),
144 gr_point_type( 0 ),
145 gr_point_i( 0 ),
146 gr_fill_or_stroke( true ),
147 _layer_hierarchy( 0 ),
148 _reconstruction_old_layer_id( 0 ),
149 _widget( 0 ),
150 _inkscape( 0 ),
151 _guides_message_context( 0 ),
152 _active( false ),
153 _w2d(),
154 _d2w(),
155 _doc2dt( NR::Matrix(NR::scale(1, -1)) ),
156 grids_visible( false )
157 {
158 _d2w.set_identity();
159 _w2d.set_identity();
161 selection = Inkscape::GC::release( new Inkscape::Selection(this) );
162 }
164 void
165 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
166 {
167 _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
169 current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
171 namedview = nv;
172 canvas = aCanvas;
174 SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
175 /* Kill flicker */
176 sp_document_ensure_up_to_date (document);
178 /* Setup Dialog Manager */
179 _dlg_mgr = &Inkscape::UI::Dialog::DialogManager::getInstance();
181 dkey = sp_item_display_key_new (1);
183 /* Connect document */
184 setDocument (document);
186 number = namedview->getViewCount();
189 /* Setup Canvas */
190 g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
192 SPCanvasGroup *root = sp_canvas_root (canvas);
194 /* Setup adminstrative layers */
195 acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
196 g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
197 main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
198 g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
200 table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
201 SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
202 SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
203 sp_canvas_item_move_to_z (table, 0);
205 page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
206 ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
207 page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
209 drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
210 g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
212 SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
214 if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
215 // Start in outline mode
216 setDisplayModeOutline();
217 } else {
218 // Start in normal mode, default
219 setDisplayModeNormal();
220 }
222 gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
223 guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
224 sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
225 controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
226 tempgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
228 /* Push select tool to the bottom of stack */
229 /** \todo
230 * FIXME: this is the only call to this. Everything else seems to just
231 * call "set" instead of "push". Can we assume that there is only one
232 * context ever?
233 */
234 push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
236 // display rect and zoom are now handled in sp_desktop_widget_realize()
238 NR::Rect const d(NR::Point(0.0, 0.0),
239 NR::Point(sp_document_width(document), sp_document_height(document)));
241 SP_CTRLRECT(page)->setRectangle(d);
242 SP_CTRLRECT(page_border)->setRectangle(d);
244 /* the following sets the page shadow on the canvas
245 It was originally set to 5, which is really cheesy!
246 It now is an attribute in the document's namedview. If a value of
247 0 is used, then the constructor for a shadow is not initialized.
248 */
250 if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
251 SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
252 }
255 /* Connect event for page resize */
256 _doc2dt[5] = sp_document_height (document);
257 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
259 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
261 NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
262 SP_CANVAS_ARENA (drawing)->arena,
263 dkey,
264 SP_ITEM_SHOW_DISPLAY);
265 if (ai) {
266 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
267 nr_arena_item_unref (ai);
268 }
270 namedview->show(this);
271 /* Ugly hack */
272 activate_guides (true);
273 /* Ugly hack */
274 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
276 /* Set up notification of rebuilding the document, this allows
277 for saving object related settings in the document. */
278 _reconstruction_start_connection =
279 document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
280 _reconstruction_finish_connection =
281 document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
282 _reconstruction_old_layer_id = NULL;
284 // ?
285 // sp_active_desktop_set (desktop);
286 _inkscape = INKSCAPE;
288 _activate_connection = _activate_signal.connect(
289 sigc::bind(
290 sigc::ptr_fun(_onActivate),
291 this
292 )
293 );
294 _deactivate_connection = _deactivate_signal.connect(
295 sigc::bind(
296 sigc::ptr_fun(_onDeactivate),
297 this
298 )
299 );
301 _sel_modified_connection = selection->connectModified(
302 sigc::bind(
303 sigc::ptr_fun(&_onSelectionModified),
304 this
305 )
306 );
307 _sel_changed_connection = selection->connectChanged(
308 sigc::bind(
309 sigc::ptr_fun(&_onSelectionChanged),
310 this
311 )
312 );
315 /* setup LayerManager */
316 // (Setting up after the connections are all in place, as it may use some of them)
317 layer_manager = new Inkscape::LayerManager( this );
319 showGrids(namedview->grids_visible, false);
321 temporary_item_list = new Inkscape::Display::TemporaryItemList( this );
322 snapindicator = new Inkscape::Display::SnapIndicator ( this );
323 }
326 void SPDesktop::destroy()
327 {
328 if (snapindicator) {
329 delete snapindicator;
330 snapindicator = NULL;
331 }
332 if (temporary_item_list) {
333 delete temporary_item_list;
334 temporary_item_list = NULL;
335 }
337 namedview->hide(this);
339 _activate_connection.disconnect();
340 _deactivate_connection.disconnect();
341 _sel_modified_connection.disconnect();
342 _sel_changed_connection.disconnect();
343 _modified_connection.disconnect();
344 _commit_connection.disconnect();
345 _reconstruction_start_connection.disconnect();
346 _reconstruction_finish_connection.disconnect();
348 g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
349 g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
350 g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
352 while (event_context) {
353 SPEventContext *ec = event_context;
354 event_context = ec->next;
355 sp_event_context_finish (ec);
356 g_object_unref (G_OBJECT (ec));
357 }
359 if (_layer_hierarchy) {
360 delete _layer_hierarchy;
361 // _layer_hierarchy = NULL; //this should be here, but commented to find other bug somewhere else.
362 }
364 if (layer_manager) {
365 delete layer_manager;
366 layer_manager = NULL;
367 }
369 if (_inkscape) {
370 _inkscape = NULL;
371 }
373 if (drawing) {
374 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
375 drawing = NULL;
376 }
378 delete _guides_message_context;
379 _guides_message_context = NULL;
381 g_list_free (zooms_past);
382 g_list_free (zooms_future);
383 }
385 SPDesktop::~SPDesktop() {}
387 //--------------------------------------------------------------------
388 /* Public methods */
391 /** Note that lifetime is measured in milliseconds
392 * it is perfectly safe to ignore the returned pointer: the object is deleted by itself, so don't delete it elsewhere!
393 * The return value should only be used as argument for SPDesktop::remove_temporary_canvasitem, because the object might be deleted already.
394 */
395 Inkscape::Display::TemporaryItem *
396 SPDesktop::add_temporary_canvasitem (SPCanvasItem *item, guint lifetime)
397 {
398 return temporary_item_list->add_item(item, lifetime);
399 }
401 /** It is perfectly safe to call this function while the object has already been deleted due to a timeout.
402 */
403 void
404 SPDesktop::remove_temporary_canvasitem (Inkscape::Display::TemporaryItem * tempitem)
405 {
406 // check for non-null temporary_item_list, because during destruction of desktop, some destructor might try to access this list!
407 if (tempitem && temporary_item_list) {
408 temporary_item_list->delete_item(tempitem);
409 }
410 }
412 void SPDesktop::setDisplayModeNormal()
413 {
414 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
415 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
416 displayMode = RENDERMODE_NORMAL;
417 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
418 _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
419 }
421 void SPDesktop::setDisplayModeOutline()
422 {
423 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
424 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
425 displayMode = RENDERMODE_OUTLINE;
426 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
427 _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
428 }
430 void SPDesktop::displayModeToggle()
431 {
432 if (displayMode == RENDERMODE_OUTLINE)
433 setDisplayModeNormal();
434 else
435 setDisplayModeOutline();
436 }
438 /**
439 * Returns current root (=bottom) layer.
440 */
441 SPObject *SPDesktop::currentRoot() const
442 {
443 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
444 }
446 /**
447 * Returns current top layer.
448 */
449 SPObject *SPDesktop::currentLayer() const
450 {
451 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
452 }
454 /**
455 * Sets the current layer of the desktop.
456 *
457 * Make \a object the top layer.
458 */
459 void SPDesktop::setCurrentLayer(SPObject *object) {
460 g_return_if_fail(SP_IS_GROUP(object));
461 g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
462 // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
463 _layer_hierarchy->setBottom(object);
464 }
466 /**
467 * Return layer that contains \a object.
468 */
469 SPObject *SPDesktop::layerForObject(SPObject *object) {
470 g_return_val_if_fail(object != NULL, NULL);
472 SPObject *root=currentRoot();
473 object = SP_OBJECT_PARENT(object);
474 while ( object && object != root && !isLayer(object) ) {
475 object = SP_OBJECT_PARENT(object);
476 }
477 return object;
478 }
480 /**
481 * True if object is a layer.
482 */
483 bool SPDesktop::isLayer(SPObject *object) const {
484 return ( SP_IS_GROUP(object)
485 && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
486 == SPGroup::LAYER ) );
487 }
489 /**
490 * True if desktop viewport fully contains \a item's bbox.
491 */
492 bool SPDesktop::isWithinViewport (SPItem *item) const
493 {
494 NR::Rect const viewport = get_display_area();
495 NR::Maybe<NR::Rect> const bbox = sp_item_bbox_desktop(item);
496 if (bbox) {
497 return viewport.contains(*bbox);
498 } else {
499 return true;
500 }
501 }
503 ///
504 bool SPDesktop::itemIsHidden(SPItem const *item) const {
505 return item->isHidden(this->dkey);
506 }
508 /**
509 * Set activate property of desktop; emit signal if changed.
510 */
511 void
512 SPDesktop::set_active (bool new_active)
513 {
514 if (new_active != _active) {
515 _active = new_active;
516 if (new_active) {
517 _activate_signal.emit();
518 } else {
519 _deactivate_signal.emit();
520 }
521 }
522 }
524 /**
525 * Set activate status of current desktop's named view.
526 */
527 void
528 SPDesktop::activate_guides(bool activate)
529 {
530 guides_active = activate;
531 namedview->activateGuides(this, activate);
532 }
534 /**
535 * Make desktop switch documents.
536 */
537 void
538 SPDesktop::change_document (SPDocument *theDocument)
539 {
540 g_return_if_fail (theDocument != NULL);
542 /* unselect everything before switching documents */
543 selection->clear();
545 setDocument (theDocument);
547 /* update the rulers, connect the desktop widget's signal to the new namedview etc.
548 (this can probably be done in a better way) */
549 Gtk::Window *parent = this->getToplevel();
550 g_assert(parent != NULL);
551 SPDesktopWidget *dtw = (SPDesktopWidget *) parent->get_data("desktopwidget");
552 if (dtw) dtw->desktop = this;
553 sp_desktop_widget_update_namedview(dtw);
555 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
556 _document_replaced_signal.emit (this, theDocument);
557 }
559 /**
560 * Make desktop switch event contexts.
561 */
562 void
563 SPDesktop::set_event_context (GtkType type, const gchar *config)
564 {
565 SPEventContext *ec;
566 while (event_context) {
567 ec = event_context;
568 sp_event_context_deactivate (ec);
569 event_context = ec->next;
570 sp_event_context_finish (ec);
571 g_object_unref (G_OBJECT (ec));
572 }
574 Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
575 ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
576 ec->next = event_context;
577 event_context = ec;
578 sp_event_context_activate (ec);
579 _event_context_changed_signal.emit (this, ec);
580 }
582 /**
583 * Push event context onto desktop's context stack.
584 */
585 void
586 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
587 {
588 SPEventContext *ref, *ec;
589 Inkscape::XML::Node *repr;
591 if (event_context && event_context->key == key) return;
592 ref = event_context;
593 while (ref && ref->next && ref->next->key != key) ref = ref->next;
594 if (ref && ref->next) {
595 ec = ref->next;
596 ref->next = ec->next;
597 sp_event_context_finish (ec);
598 g_object_unref (G_OBJECT (ec));
599 }
601 if (event_context) sp_event_context_deactivate (event_context);
602 repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
603 ec = sp_event_context_new (type, this, repr, key);
604 ec->next = event_context;
605 event_context = ec;
606 sp_event_context_activate (ec);
607 _event_context_changed_signal.emit (this, ec);
608 }
610 /**
611 * Sets the coordinate status to a given point
612 */
613 void
614 SPDesktop::set_coordinate_status (NR::Point p) {
615 _widget->setCoordinateStatus(p);
616 }
618 /**
619 * \see sp_document_item_from_list_at_point_bottom()
620 */
621 SPItem *
622 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
623 {
624 g_return_val_if_fail (doc() != NULL, NULL);
625 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
626 }
628 /**
629 * \see sp_document_item_at_point()
630 */
631 SPItem *
632 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
633 {
634 g_return_val_if_fail (doc() != NULL, NULL);
635 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
636 }
638 /**
639 * \see sp_document_group_at_point()
640 */
641 SPItem *
642 SPDesktop::group_at_point (NR::Point const p) const
643 {
644 g_return_val_if_fail (doc() != NULL, NULL);
645 return sp_document_group_at_point (doc(), dkey, p);
646 }
648 /**
649 * \brief Returns the mouse point in document coordinates; if mouse is
650 * outside the canvas, returns the center of canvas viewpoint
651 */
652 NR::Point
653 SPDesktop::point() const
654 {
655 NR::Point p = _widget->getPointer();
656 NR::Point pw = sp_canvas_window_to_world (canvas, p);
657 p = w2d(pw);
659 NR::Rect const r = canvas->getViewbox();
661 NR::Point r0 = w2d(r.min());
662 NR::Point r1 = w2d(r.max());
664 if (p[NR::X] >= r0[NR::X] &&
665 p[NR::X] <= r1[NR::X] &&
666 p[NR::Y] >= r1[NR::Y] &&
667 p[NR::Y] <= r0[NR::Y])
668 {
669 return p;
670 } else {
671 return (r0 + r1) / 2;
672 }
673 }
675 /**
676 * Put current zoom data in history list.
677 */
678 void
679 SPDesktop::push_current_zoom (GList **history)
680 {
681 NR::Rect const area = get_display_area();
683 NRRect *old_zoom = g_new(NRRect, 1);
684 old_zoom->x0 = area.min()[NR::X];
685 old_zoom->x1 = area.max()[NR::X];
686 old_zoom->y0 = area.min()[NR::Y];
687 old_zoom->y1 = area.max()[NR::Y];
688 if ( *history == NULL
689 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
690 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
691 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
692 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
693 {
694 *history = g_list_prepend (*history, old_zoom);
695 }
696 }
698 /**
699 * Set viewbox (x0, x1, y0 and y1 are in document pixels. Border is in screen pixels).
700 */
701 void
702 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
703 {
704 g_assert(_widget);
706 // save the zoom
707 if (log) {
708 push_current_zoom(&zooms_past);
709 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
710 g_list_free (zooms_future);
711 zooms_future = NULL;
712 }
714 double const cx = 0.5 * (x0 + x1);
715 double const cy = 0.5 * (y0 + y1);
717 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
719 double scale = expansion(_d2w);
720 double newscale;
721 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
722 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
723 } else {
724 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
725 }
727 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); // unit: 'screen pixels' per 'document pixels'
729 int clear = FALSE;
730 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
731 /* Set zoom factors */
732 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
733 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
734 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
735 clear = TRUE;
736 }
738 /* Calculate top left corner (in document pixels) */
739 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
740 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
742 /* Scroll */
743 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
745 /* update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
746 sp_box3d_context_update_lines(event_context);
748 _widget->updateRulers();
749 _widget->updateScrollbars(expansion(_d2w));
750 _widget->updateZoom();
751 }
753 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
754 {
755 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
756 }
758 /**
759 * Return viewbox dimensions.
760 */
761 NR::Rect SPDesktop::get_display_area() const
762 {
763 NR::Rect const viewbox = canvas->getViewbox();
765 double const scale = _d2w[0];
767 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
768 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
769 }
771 /**
772 * Revert back to previous zoom if possible.
773 */
774 void
775 SPDesktop::prev_zoom()
776 {
777 if (zooms_past == NULL) {
778 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
779 return;
780 }
782 // push current zoom into forward zooms list
783 push_current_zoom (&zooms_future);
785 // restore previous zoom
786 set_display_area (((NRRect *) zooms_past->data)->x0,
787 ((NRRect *) zooms_past->data)->y0,
788 ((NRRect *) zooms_past->data)->x1,
789 ((NRRect *) zooms_past->data)->y1,
790 0, false);
792 // remove the just-added zoom from the past zooms list
793 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
794 }
796 /**
797 * Set zoom to next in list.
798 */
799 void
800 SPDesktop::next_zoom()
801 {
802 if (zooms_future == NULL) {
803 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
804 return;
805 }
807 // push current zoom into past zooms list
808 push_current_zoom (&zooms_past);
810 // restore next zoom
811 set_display_area (((NRRect *) zooms_future->data)->x0,
812 ((NRRect *) zooms_future->data)->y0,
813 ((NRRect *) zooms_future->data)->x1,
814 ((NRRect *) zooms_future->data)->y1,
815 0, false);
817 // remove the just-used zoom from the zooms_future list
818 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
819 }
821 /**
822 * Zoom to point with absolute zoom factor.
823 */
824 void
825 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
826 {
827 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
829 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
830 // this check prevents "sliding" when trying to zoom in at maximum zoom;
831 /// \todo someone please fix calculations properly and remove this hack
832 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
833 return;
835 NR::Rect const viewbox = canvas->getViewbox();
837 double const width2 = viewbox.dimensions()[NR::X] / zoom;
838 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
840 set_display_area(cx - px * width2,
841 cy - py * height2,
842 cx + (1 - px) * width2,
843 cy + (1 - py) * height2,
844 0.0);
845 }
847 /**
848 * Zoom to center with absolute zoom factor.
849 */
850 void
851 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
852 {
853 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
854 }
856 /**
857 * Zoom to point with relative zoom factor.
858 */
859 void
860 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
861 {
862 NR::Rect const area = get_display_area();
864 if (cx < area.min()[NR::X]) {
865 cx = area.min()[NR::X];
866 }
867 if (cx > area.max()[NR::X]) {
868 cx = area.max()[NR::X];
869 }
870 if (cy < area.min()[NR::Y]) {
871 cy = area.min()[NR::Y];
872 }
873 if (cy > area.max()[NR::Y]) {
874 cy = area.max()[NR::Y];
875 }
877 gdouble const scale = expansion(_d2w) * zoom;
878 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
879 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
881 zoom_absolute_keep_point(cx, cy, px, py, scale);
882 }
884 /**
885 * Zoom to center with relative zoom factor.
886 */
887 void
888 SPDesktop::zoom_relative (double cx, double cy, double zoom)
889 {
890 gdouble scale = expansion(_d2w) * zoom;
891 zoom_absolute (cx, cy, scale);
892 }
894 /**
895 * Set display area to origin and current document dimensions.
896 */
897 void
898 SPDesktop::zoom_page()
899 {
900 NR::Rect d(NR::Point(0, 0),
901 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
903 if (d.isEmpty(1.0)) {
904 return;
905 }
907 set_display_area(d, 10);
908 }
910 /**
911 * Set display area to current document width.
912 */
913 void
914 SPDesktop::zoom_page_width()
915 {
916 NR::Rect const a = get_display_area();
918 if (sp_document_width(doc()) < 1.0) {
919 return;
920 }
922 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
923 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
925 set_display_area(d, 10);
926 }
928 /**
929 * Zoom to selection.
930 */
931 void
932 SPDesktop::zoom_selection()
933 {
934 NR::Maybe<NR::Rect> const d = selection->bounds();
936 if ( !d || d->isEmpty(0.1) ) {
937 return;
938 }
940 set_display_area(*d, 10);
941 }
943 /**
944 * Tell widget to let zoom widget grab keyboard focus.
945 */
946 void
947 SPDesktop::zoom_grab_focus()
948 {
949 _widget->letZoomGrabFocus();
950 }
952 /**
953 * Zoom to whole drawing.
954 */
955 void
956 SPDesktop::zoom_drawing()
957 {
958 g_return_if_fail (doc() != NULL);
959 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
960 g_return_if_fail (docitem != NULL);
962 NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
964 /* Note that the second condition here indicates that
965 ** there are no items in the drawing.
966 */
967 if ( !d || d->isEmpty(1.0) ) {
968 return;
969 }
971 set_display_area(*d, 10);
972 }
974 /**
975 * Scroll canvas by specific coordinate amount.
976 */
977 void
978 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
979 {
980 g_assert(_widget);
982 NR::Rect const viewbox = canvas->getViewbox();
984 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
986 /* update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */
987 sp_box3d_context_update_lines(event_context);
989 _widget->updateRulers();
990 _widget->updateScrollbars(expansion(_d2w));
991 }
993 bool
994 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
995 {
996 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
998 // autoscrolldistance is in screen pixels, but the display area is in document units
999 autoscrolldistance /= expansion(_d2w);
1000 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
1002 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
1003 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
1005 NR::Point const s_w( (*p) * _d2w );
1007 gdouble x_to;
1008 if ((*p)[NR::X] < dbox.min()[NR::X])
1009 x_to = dbox.min()[NR::X];
1010 else if ((*p)[NR::X] > dbox.max()[NR::X])
1011 x_to = dbox.max()[NR::X];
1012 else
1013 x_to = (*p)[NR::X];
1015 gdouble y_to;
1016 if ((*p)[NR::Y] < dbox.min()[NR::Y])
1017 y_to = dbox.min()[NR::Y];
1018 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
1019 y_to = dbox.max()[NR::Y];
1020 else
1021 y_to = (*p)[NR::Y];
1023 NR::Point const d_dt(x_to, y_to);
1024 NR::Point const d_w( d_dt * _d2w );
1025 NR::Point const moved_w( d_w - s_w );
1027 if (autoscrollspeed == 0)
1028 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
1030 if (autoscrollspeed != 0)
1031 scroll_world (autoscrollspeed * moved_w);
1033 return true;
1034 }
1035 return false;
1036 }
1038 bool
1039 SPDesktop::is_iconified()
1040 {
1041 return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED);
1042 }
1044 void
1045 SPDesktop::iconify()
1046 {
1047 _widget->setIconified();
1048 }
1050 bool
1051 SPDesktop::is_maximized()
1052 {
1053 return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED);
1054 }
1056 void
1057 SPDesktop::maximize()
1058 {
1059 _widget->setMaximized();
1060 }
1062 bool
1063 SPDesktop::is_fullscreen()
1064 {
1065 return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN);
1066 }
1068 void
1069 SPDesktop::fullscreen()
1070 {
1071 _widget->setFullscreen();
1072 }
1074 void
1075 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
1076 {
1077 _widget->getGeometry (x, y, w, h);
1078 }
1080 void
1081 SPDesktop::setWindowPosition (NR::Point p)
1082 {
1083 _widget->setPosition (p);
1084 }
1086 void
1087 SPDesktop::setWindowSize (gint w, gint h)
1088 {
1089 _widget->setSize (w, h);
1090 }
1092 void
1093 SPDesktop::setWindowTransient (void *p, int transient_policy)
1094 {
1095 _widget->setTransient (p, transient_policy);
1096 }
1098 Gtk::Window*
1099 SPDesktop::getToplevel( )
1100 {
1101 return _widget->getWindow();
1102 }
1104 void
1105 SPDesktop::presentWindow()
1106 {
1107 _widget->present();
1108 }
1110 bool
1111 SPDesktop::warnDialog (gchar *text)
1112 {
1113 return _widget->warnDialog (text);
1114 }
1116 void
1117 SPDesktop::toggleRulers()
1118 {
1119 _widget->toggleRulers();
1120 }
1122 void
1123 SPDesktop::toggleScrollbars()
1124 {
1125 _widget->toggleScrollbars();
1126 }
1128 void
1129 SPDesktop::layoutWidget()
1130 {
1131 _widget->layout();
1132 }
1134 void
1135 SPDesktop::destroyWidget()
1136 {
1137 _widget->destroy();
1138 }
1140 bool
1141 SPDesktop::shutdown()
1142 {
1143 return _widget->shutdown();
1144 }
1146 bool SPDesktop::onDeleteUI (GdkEventAny*)
1147 {
1148 if(shutdown())
1149 return true;
1151 destroyWidget();
1152 return false;
1153 }
1155 /**
1156 * onWindowStateEvent
1157 *
1158 * Called when the window changes its maximize/fullscreen/iconify/pinned state.
1159 * Since GTK doesn't have a way to query this state information directly, we
1160 * record it for the desktop here, and also possibly trigger a layout.
1161 */
1162 bool
1163 SPDesktop::onWindowStateEvent (GdkEventWindowState* event)
1164 {
1165 // Record the desktop window's state
1166 window_state = event->new_window_state;
1168 // Layout may differ depending on full-screen mode or not
1169 GdkWindowState changed = event->changed_mask;
1170 if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) {
1171 layoutWidget();
1172 }
1174 return false;
1175 }
1177 void
1178 SPDesktop::setToolboxFocusTo (gchar const *label)
1179 {
1180 _widget->setToolboxFocusTo (label);
1181 }
1183 void
1184 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1185 {
1186 _widget->setToolboxAdjustmentValue (id, val);
1187 }
1189 void
1190 SPDesktop::setToolboxSelectOneValue (gchar const* id, gint val)
1191 {
1192 _widget->setToolboxSelectOneValue (id, val);
1193 }
1195 bool
1196 SPDesktop::isToolboxButtonActive (gchar const *id)
1197 {
1198 return _widget->isToolboxButtonActive (id);
1199 }
1201 void
1202 SPDesktop::emitToolSubselectionChanged(gpointer data)
1203 {
1204 _tool_subselection_changed.emit(data);
1205 inkscape_subselection_changed (this);
1206 }
1208 void
1209 SPDesktop::updateNow()
1210 {
1211 sp_canvas_update_now(canvas);
1212 }
1214 void
1215 SPDesktop::enableInteraction()
1216 {
1217 _widget->enableInteraction();
1218 }
1220 void SPDesktop::disableInteraction()
1221 {
1222 _widget->disableInteraction();
1223 }
1225 void SPDesktop::setWaitingCursor()
1226 {
1227 GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1228 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1229 gdk_cursor_unref(waiting);
1230 waiting_cursor = true;
1232 // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1233 // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1234 // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1235 // after the call to setWaitingCursor as it was before
1236 while( Gtk::Main::events_pending() )
1237 Gtk::Main::iteration();
1238 }
1240 void SPDesktop::clearWaitingCursor()
1241 {
1242 if (waiting_cursor)
1243 sp_event_context_update_cursor(sp_desktop_event_context(this));
1244 }
1246 void SPDesktop::toggleColorProfAdjust()
1247 {
1248 _widget->toggleColorProfAdjust();
1249 }
1251 void SPDesktop::toggleGrids()
1252 {
1253 if (namedview->grids) {
1254 if(gridgroup) {
1255 showGrids(!grids_visible);
1256 }
1257 } else {
1258 //there is no grid present at the moment. add a rectangular grid and make it visible
1259 Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1260 Inkscape::CanvasGrid::writeNewGridToRepr(repr, sp_desktop_document(this), Inkscape::GRID_RECTANGULAR);
1261 showGrids(true);
1262 }
1263 }
1265 void SPDesktop::showGrids(bool show, bool dirty_document)
1266 {
1267 grids_visible = show;
1268 sp_namedview_show_grids(namedview, grids_visible, dirty_document);
1269 if (show) {
1270 sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup));
1271 } else {
1272 sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup));
1273 }
1274 }
1276 void SPDesktop::toggleSnapping()
1277 {
1278 bool v = namedview->snap_manager.getSnapEnabledGlobally();
1279 Inkscape::XML::Node *repr = SP_OBJECT_REPR(namedview);
1280 sp_repr_set_boolean(repr, "inkscape:snap-global", !v);
1281 }
1283 //----------------------------------------------------------------------
1284 // Callback implementations. The virtual ones are connected by the view.
1286 void
1287 SPDesktop::onPositionSet (double x, double y)
1288 {
1289 _widget->viewSetPosition (NR::Point(x,y));
1290 }
1292 void
1293 SPDesktop::onResized (double /*x*/, double /*y*/)
1294 {
1295 // Nothing called here
1296 }
1298 /**
1299 * Redraw callback; queues Gtk redraw; connected by View.
1300 */
1301 void
1302 SPDesktop::onRedrawRequested ()
1303 {
1304 if (main) {
1305 _widget->requestCanvasUpdate();
1306 }
1307 }
1309 void
1310 SPDesktop::updateCanvasNow()
1311 {
1312 _widget->requestCanvasUpdateAndWait();
1313 }
1315 /**
1316 * Associate document with desktop.
1317 */
1318 void
1319 SPDesktop::setDocument (SPDocument *doc)
1320 {
1321 if (this->doc() && doc) {
1322 namedview->hide(this);
1323 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1324 }
1326 if (_layer_hierarchy) {
1327 _layer_hierarchy->clear();
1328 delete _layer_hierarchy;
1329 }
1330 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1331 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1332 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1333 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1334 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1336 /* setup EventLog */
1337 event_log = new Inkscape::EventLog(doc);
1338 doc->addUndoObserver(*event_log);
1340 _commit_connection.disconnect();
1341 _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1343 /// \todo fixme: This condition exists to make sure the code
1344 /// inside is NOT called on initialization, only on replacement. But there
1345 /// are surely more safe methods to accomplish this.
1346 // TODO since the comment had reversed logic, check the intent of this block of code:
1347 if (drawing) {
1348 NRArenaItem *ai = 0;
1350 namedview = sp_document_namedview (doc, NULL);
1351 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1352 number = namedview->getViewCount();
1354 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1355 SP_CANVAS_ARENA (drawing)->arena,
1356 dkey,
1357 SP_ITEM_SHOW_DISPLAY);
1358 if (ai) {
1359 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1360 nr_arena_item_unref (ai);
1361 }
1362 namedview->show(this);
1363 /* Ugly hack */
1364 activate_guides (true);
1365 /* Ugly hack */
1366 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1367 }
1369 _document_replaced_signal.emit (this, doc);
1371 View::setDocument (doc);
1372 }
1374 void
1375 SPDesktop::onStatusMessage
1376 (Inkscape::MessageType type, gchar const *message)
1377 {
1378 if (_widget) {
1379 _widget->setMessage(type, message);
1380 }
1381 }
1383 void
1384 SPDesktop::onDocumentURISet (gchar const* uri)
1385 {
1386 _widget->setTitle(uri);
1387 }
1389 /**
1390 * Resized callback.
1391 */
1392 void
1393 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1394 {
1395 _doc2dt[5] = height;
1396 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1397 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1398 SP_CTRLRECT(page)->setRectangle(a);
1399 SP_CTRLRECT(page_border)->setRectangle(a);
1400 }
1403 void
1404 SPDesktop::_onActivate (SPDesktop* dt)
1405 {
1406 if (!dt->_widget) return;
1407 dt->_widget->activateDesktop();
1408 }
1410 void
1411 SPDesktop::_onDeactivate (SPDesktop* dt)
1412 {
1413 if (!dt->_widget) return;
1414 dt->_widget->deactivateDesktop();
1415 }
1417 void
1418 SPDesktop::_onSelectionModified
1419 (Inkscape::Selection */*selection*/, guint /*flags*/, SPDesktop *dt)
1420 {
1421 if (!dt->_widget) return;
1422 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1423 }
1425 static void
1426 _onSelectionChanged
1427 (Inkscape::Selection *selection, SPDesktop *desktop)
1428 {
1429 /** \todo
1430 * only change the layer for single selections, or what?
1431 * This seems reasonable -- for multiple selections there can be many
1432 * different layers involved.
1433 */
1434 SPItem *item=selection->singleItem();
1435 if (item) {
1436 SPObject *layer=desktop->layerForObject(item);
1437 if ( layer && layer != desktop->currentLayer() ) {
1438 desktop->setCurrentLayer(layer);
1439 }
1440 }
1441 }
1443 /**
1444 * Calls event handler of current event context.
1445 * \param arena Unused
1446 * \todo fixme
1447 */
1448 static gint
1449 _arena_handler (SPCanvasArena */*arena*/, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1450 {
1451 if (ai) {
1452 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1453 return sp_event_context_item_handler (desktop->event_context, spi, event);
1454 } else {
1455 return sp_event_context_root_handler (desktop->event_context, event);
1456 }
1457 }
1459 static void
1460 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1461 g_return_if_fail(SP_IS_GROUP(layer));
1462 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1463 }
1465 /// Callback
1466 static void
1467 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1468 g_return_if_fail(SP_IS_GROUP(layer));
1469 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1470 }
1472 /// Callback
1473 static void
1474 _layer_hierarchy_changed(SPObject */*top*/, SPObject *bottom,
1475 SPDesktop *desktop)
1476 {
1477 desktop->_layer_changed_signal.emit (bottom);
1478 }
1480 /// Called when document is starting to be rebuilt.
1481 static void
1482 _reconstruction_start (SPDesktop * desktop)
1483 {
1484 // printf("Desktop, starting reconstruction\n");
1485 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1486 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1488 /*
1489 GSList const * selection_objs = desktop->selection->list();
1490 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1492 }
1493 */
1494 desktop->selection->clear();
1496 // printf("Desktop, starting reconstruction end\n");
1497 }
1499 /// Called when document rebuild is finished.
1500 static void
1501 _reconstruction_finish (SPDesktop * desktop)
1502 {
1503 // printf("Desktop, finishing reconstruction\n");
1504 if (desktop->_reconstruction_old_layer_id == NULL)
1505 return;
1507 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1508 if (newLayer != NULL)
1509 desktop->setCurrentLayer(newLayer);
1511 g_free(desktop->_reconstruction_old_layer_id);
1512 desktop->_reconstruction_old_layer_id = NULL;
1513 // printf("Desktop, finishing reconstruction end\n");
1514 return;
1515 }
1517 /**
1518 * Namedview_modified callback.
1519 */
1520 static void
1521 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1522 {
1523 SPNamedView *nv=SP_NAMEDVIEW(obj);
1525 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1527 /* Recalculate snap distances */
1528 /* FIXME: why is the desktop getting involved in setting up something
1529 ** that is entirely to do with the namedview?
1530 */
1531 _update_snap_distances (desktop);
1533 /* Show/hide page background */
1534 if (nv->pagecolor & 0xff) {
1535 sp_canvas_item_show (desktop->table);
1536 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1537 sp_canvas_item_move_to_z (desktop->table, 0);
1538 } else {
1539 sp_canvas_item_hide (desktop->table);
1540 }
1542 /* Show/hide page border */
1543 if (nv->showborder) {
1544 // show
1545 sp_canvas_item_show (desktop->page_border);
1546 // set color and shadow
1547 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1548 if (nv->pageshadow) {
1549 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1550 }
1551 // place in the z-order stack
1552 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1553 sp_canvas_item_move_to_z (desktop->page_border, 2);
1554 } else {
1555 int order = sp_canvas_item_order (desktop->page_border);
1556 int morder = sp_canvas_item_order (desktop->drawing);
1557 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1558 morder - order);
1559 }
1560 } else {
1561 sp_canvas_item_hide (desktop->page_border);
1562 if (nv->pageshadow) {
1563 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1564 }
1565 }
1567 /* Show/hide page shadow */
1568 if (nv->showpageshadow && nv->pageshadow) {
1569 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1570 } else {
1571 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1572 }
1574 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1575 (SP_RGBA32_R_U(nv->pagecolor) +
1576 SP_RGBA32_G_U(nv->pagecolor) +
1577 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1578 // the background color is light or transparent, use black outline
1579 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1580 } else { // use white outline
1581 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1582 }
1583 }
1584 }
1586 /**
1587 * Callback to reset snapper's distances.
1588 */
1589 static void
1590 _update_snap_distances (SPDesktop *desktop)
1591 {
1592 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1594 SPNamedView &nv = *desktop->namedview;
1596 //tell all grid snappers
1597 for ( GSList const *l = nv.grids; l != NULL; l = l->next) {
1598 Inkscape::CanvasGrid *grid = (Inkscape::CanvasGrid*) l->data;
1599 grid->snapper->setSnapperTolerance(sp_convert_distance_full(nv.gridtolerance,
1600 *nv.gridtoleranceunit,
1601 px));
1602 }
1604 nv.snap_manager.guide.setSnapperTolerance(sp_convert_distance_full(nv.guidetolerance,
1605 *nv.guidetoleranceunit,
1606 px));
1607 nv.snap_manager.object.setSnapperTolerance(sp_convert_distance_full(nv.objecttolerance,
1608 *nv.objecttoleranceunit,
1609 px));
1610 }
1613 NR::Matrix SPDesktop::w2d() const
1614 {
1615 return _w2d;
1616 }
1618 NR::Point SPDesktop::w2d(NR::Point const &p) const
1619 {
1620 return p * _w2d;
1621 }
1623 NR::Point SPDesktop::d2w(NR::Point const &p) const
1624 {
1625 return p * _d2w;
1626 }
1628 NR::Matrix SPDesktop::doc2dt() const
1629 {
1630 return _doc2dt;
1631 }
1633 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1634 {
1635 return p * _doc2dt;
1636 }
1638 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1639 {
1640 return p / _doc2dt;
1641 }
1644 /**
1645 * Pop event context from desktop's context stack. Never used.
1646 */
1647 // void
1648 // SPDesktop::pop_event_context (unsigned int key)
1649 // {
1650 // SPEventContext *ec = NULL;
1651 //
1652 // if (event_context && event_context->key == key) {
1653 // g_return_if_fail (event_context);
1654 // g_return_if_fail (event_context->next);
1655 // ec = event_context;
1656 // sp_event_context_deactivate (ec);
1657 // event_context = ec->next;
1658 // sp_event_context_activate (event_context);
1659 // _event_context_changed_signal.emit (this, ec);
1660 // }
1661 //
1662 // SPEventContext *ref = event_context;
1663 // while (ref && ref->next && ref->next->key != key)
1664 // ref = ref->next;
1665 //
1666 // if (ref && ref->next) {
1667 // ec = ref->next;
1668 // ref->next = ec->next;
1669 // }
1670 //
1671 // if (ec) {
1672 // sp_event_context_finish (ec);
1673 // g_object_unref (G_OBJECT (ec));
1674 // }
1675 // }
1677 /*
1678 Local Variables:
1679 mode:c++
1680 c-file-style:"stroustrup"
1681 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1682 indent-tabs-mode:nil
1683 fill-column:99
1684 End:
1685 */
1686 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :