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