51580249e1fc9de49e6a19ebf345d3709543e41b
1 #define __SP_DESKTOP_C__
3 /** \file
4 * Editable view implementation
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * MenTaLguY <mental@rydia.net>
9 * bulia byak <buliabyak@users.sf.net>
10 * Ralf Stephan <ralf@ark.in-berlin.de>
11 * John Bintz <jcoswell@coswellproductions.org>
12 *
13 * Copyright (C) 2006 John Bintz
14 * Copyright (C) 2004 MenTaLguY
15 * Copyright (C) 1999-2002 Lauris Kaplinski
16 * Copyright (C) 2000-2001 Ximian, Inc.
17 *
18 * Released under GNU GPL, read the file 'COPYING' for more information
19 */
21 /** \class SPDesktop
22 * SPDesktop is a subclass of View, implementing an editable document
23 * canvas. It is extensively used by many UI controls that need certain
24 * visual representations of their own.
25 *
26 * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
27 * layers of different control objects. The one containing the whole
28 * document is the drawing layer. In addition to it, there are grid,
29 * guide, sketch and control layers. The sketch layer is used for
30 * temporary drawing objects, before the real objects in document are
31 * created. The control layer contains editing knots, rubberband and
32 * similar non-document UI objects.
33 *
34 * Each SPDesktop is associated with a SPNamedView node of the document
35 * tree. Currently, all desktops are created from a single main named
36 * view, but in the future there may be support for different ones.
37 * SPNamedView serves as an in-document container for desktop-related
38 * data, like grid and guideline placement, snapping options and so on.
39 *
40 * Associated with each SPDesktop are the two most important editing
41 * related objects - SPSelection and SPEventContext.
42 *
43 * Sodipodi keeps track of the active desktop and invokes notification
44 * signals whenever it changes. UI elements can use these to update their
45 * display to the selection of the currently active editing window.
46 * (Lauris Kaplinski)
47 */
49 #ifdef HAVE_CONFIG_H
50 # include "config.h"
51 #endif
53 #include <glibmm/i18n.h>
54 #include <sigc++/functors/mem_fun.h>
56 #include "macros.h"
57 #include "inkscape-private.h"
58 #include "desktop.h"
59 #include "desktop-events.h"
60 #include "desktop-handles.h"
61 #include "document.h"
62 #include "message-stack.h"
63 #include "selection.h"
64 #include "select-context.h"
65 #include "sp-namedview.h"
66 #include "color.h"
67 #include "sp-item-group.h"
68 #include "prefs-utils.h"
69 #include "object-hierarchy.h"
70 #include "helper/units.h"
71 #include "display/canvas-arena.h"
72 #include "display/nr-arena.h"
73 #include "display/gnome-canvas-acetate.h"
74 #include "display/sodipodi-ctrlrect.h"
75 #include "display/sp-canvas-util.h"
76 #include "libnr/nr-matrix-div.h"
77 #include "libnr/nr-rect-ops.h"
78 #include "ui/dialog/dialog-manager.h"
79 #include "xml/repr.h"
80 #include "message-context.h"
81 #include "layer-manager.h"
83 namespace Inkscape { namespace XML { class Node; }}
85 // Callback declarations
86 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
87 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
88 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
89 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
90 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
91 static void _reconstruction_start(SPDesktop * desktop);
92 static void _reconstruction_finish(SPDesktop * desktop);
93 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
94 static void _update_snap_distances (SPDesktop *desktop);
96 /**
97 * Return new desktop object.
98 * \pre namedview != NULL.
99 * \pre canvas != NULL.
100 */
101 SPDesktop::SPDesktop()
102 {
103 _dlg_mgr = NULL;
104 _widget = 0;
105 namedview = NULL;
106 selection = NULL;
107 acetate = NULL;
108 main = NULL;
109 grid = NULL;
110 guides = NULL;
111 drawing = NULL;
112 sketch = NULL;
113 controls = NULL;
114 event_context = 0;
115 layer_manager = 0;
117 _d2w.set_identity();
118 _w2d.set_identity();
119 _doc2dt = NR::Matrix(NR::scale(1, -1));
121 guides_active = false;
123 zooms_past = NULL;
124 zooms_future = NULL;
126 is_fullscreen = false;
128 gr_item = NULL;
129 gr_point_num = 0;
130 gr_fill_or_stroke = true;
132 _layer_hierarchy = NULL;
133 _active = false;
135 selection = Inkscape::GC::release (new Inkscape::Selection (this));
136 }
138 void
139 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
141 {
142 _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
144 current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
146 namedview = nv;
147 canvas = aCanvas;
149 SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
150 /* Kill flicker */
151 sp_document_ensure_up_to_date (document);
153 /* Setup Dialog Manager */
154 _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
156 dkey = sp_item_display_key_new (1);
158 /* Connect document */
159 setDocument (document);
161 number = namedview->getViewCount();
164 /* Setup Canvas */
165 g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
167 SPCanvasGroup *root = sp_canvas_root (canvas);
169 /* Setup adminstrative layers */
170 acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
171 g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
172 main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
173 g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
175 table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
176 SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
177 SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
178 sp_canvas_item_move_to_z (table, 0);
180 page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
181 ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
182 page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
184 drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
185 g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
187 SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
189 // Start always in normal mode
190 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
191 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
193 grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
194 guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195 sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196 controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
198 /* Push select tool to the bottom of stack */
199 /** \todo
200 * FIXME: this is the only call to this. Everything else seems to just
201 * call "set" instead of "push". Can we assume that there is only one
202 * context ever?
203 */
204 push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
206 // display rect and zoom are now handled in sp_desktop_widget_realize()
208 NR::Rect const d(NR::Point(0.0, 0.0),
209 NR::Point(sp_document_width(document), sp_document_height(document)));
211 SP_CTRLRECT(page)->setRectangle(d);
212 SP_CTRLRECT(page_border)->setRectangle(d);
214 /* the following sets the page shadow on the canvas
215 It was originally set to 5, which is really cheesy!
216 It now is an attribute in the document's namedview. If a value of
217 0 is used, then the constructor for a shadow is not initialized.
218 */
220 if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
221 SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
222 }
225 /* Connect event for page resize */
226 _doc2dt[5] = sp_document_height (document);
227 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
229 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
231 NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
232 SP_CANVAS_ARENA (drawing)->arena,
233 dkey,
234 SP_ITEM_SHOW_DISPLAY);
235 if (ai) {
236 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
237 nr_arena_item_unref (ai);
238 }
240 namedview->show(this);
241 /* Ugly hack */
242 activate_guides (true);
243 /* Ugly hack */
244 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
246 /* Set up notification of rebuilding the document, this allows
247 for saving object related settings in the document. */
248 _reconstruction_start_connection =
249 document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
250 _reconstruction_finish_connection =
251 document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
252 _reconstruction_old_layer_id = NULL;
254 _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
256 // ?
257 // sp_active_desktop_set (desktop);
258 _inkscape = INKSCAPE;
260 _activate_connection = _activate_signal.connect(
261 sigc::bind(
262 sigc::ptr_fun(_onActivate),
263 this
264 )
265 );
266 _deactivate_connection = _deactivate_signal.connect(
267 sigc::bind(
268 sigc::ptr_fun(_onDeactivate),
269 this
270 )
271 );
273 _sel_modified_connection = selection->connectModified(
274 sigc::bind(
275 sigc::ptr_fun(&_onSelectionModified),
276 this
277 )
278 );
279 _sel_changed_connection = selection->connectChanged(
280 sigc::bind(
281 sigc::ptr_fun(&_onSelectionChanged),
282 this
283 )
284 );
287 /* setup LayerManager */
288 // (Setting up after the connections are all in place, as it may use some of them)
289 layer_manager = new Inkscape::LayerManager( this );
290 }
293 void SPDesktop::destroy()
294 {
295 _activate_connection.disconnect();
296 _deactivate_connection.disconnect();
297 _sel_modified_connection.disconnect();
298 _sel_changed_connection.disconnect();
299 _modified_connection.disconnect();
301 while (event_context) {
302 SPEventContext *ec = event_context;
303 event_context = ec->next;
304 sp_event_context_finish (ec);
305 g_object_unref (G_OBJECT (ec));
306 }
308 if (_layer_hierarchy) {
309 delete _layer_hierarchy;
310 }
312 if (_inkscape) {
313 _inkscape = NULL;
314 }
316 if (drawing) {
317 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
318 drawing = NULL;
319 }
321 delete _guides_message_context;
322 _guides_message_context = NULL;
324 _modified_connection.disconnect();
326 g_list_free (zooms_past);
327 g_list_free (zooms_future);
328 }
330 SPDesktop::~SPDesktop() {}
332 //--------------------------------------------------------------------
333 /* Public methods */
335 void SPDesktop::setDisplayModeNormal()
336 {
337 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
338 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
339 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
340 }
342 void SPDesktop::setDisplayModeOutline()
343 {
344 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
345 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
346 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
347 }
349 /**
350 * Returns current root (=bottom) layer.
351 */
352 SPObject *SPDesktop::currentRoot() const
353 {
354 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
355 }
357 /**
358 * Returns current top layer.
359 */
360 SPObject *SPDesktop::currentLayer() const
361 {
362 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
363 }
365 /**
366 * Sets the current layer of the desktop.
367 *
368 * Make \a object the top layer.
369 */
370 void SPDesktop::setCurrentLayer(SPObject *object) {
371 g_return_if_fail(SP_IS_GROUP(object));
372 g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
373 // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
374 _layer_hierarchy->setBottom(object);
375 }
377 /**
378 * Return layer that contains \a object.
379 */
380 SPObject *SPDesktop::layerForObject(SPObject *object) {
381 g_return_val_if_fail(object != NULL, NULL);
383 SPObject *root=currentRoot();
384 object = SP_OBJECT_PARENT(object);
385 while ( object && object != root && !isLayer(object) ) {
386 object = SP_OBJECT_PARENT(object);
387 }
388 return object;
389 }
391 /**
392 * True if object is a layer.
393 */
394 bool SPDesktop::isLayer(SPObject *object) const {
395 return ( SP_IS_GROUP(object)
396 && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
397 == SPGroup::LAYER ) );
398 }
400 /**
401 * True if desktop viewport fully contains \a item's bbox.
402 */
403 bool SPDesktop::isWithinViewport (SPItem *item) const
404 {
405 NR::Rect const viewport = get_display_area();
406 NR::Rect const bbox = sp_item_bbox_desktop(item);
407 return viewport.contains(bbox);
408 }
410 ///
411 bool SPDesktop::itemIsHidden(SPItem const *item) const {
412 return item->isHidden(this->dkey);
413 }
415 /**
416 * Set activate property of desktop; emit signal if changed.
417 */
418 void
419 SPDesktop::set_active (bool new_active)
420 {
421 if (new_active != _active) {
422 _active = new_active;
423 if (new_active) {
424 _activate_signal.emit();
425 } else {
426 _deactivate_signal.emit();
427 }
428 }
429 }
431 /**
432 * Set activate status of current desktop's named view.
433 */
434 void
435 SPDesktop::activate_guides(bool activate)
436 {
437 guides_active = activate;
438 namedview->activateGuides(this, activate);
439 }
441 /**
442 * Make desktop switch documents.
443 */
444 void
445 SPDesktop::change_document (SPDocument *theDocument)
446 {
447 g_return_if_fail (theDocument != NULL);
449 /* unselect everything before switching documents */
450 selection->clear();
452 setDocument (theDocument);
453 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
454 _document_replaced_signal.emit (this, theDocument);
455 }
457 /**
458 * Make desktop switch event contexts.
459 */
460 void
461 SPDesktop::set_event_context (GtkType type, const gchar *config)
462 {
463 SPEventContext *ec;
464 while (event_context) {
465 ec = event_context;
466 sp_event_context_deactivate (ec);
467 event_context = ec->next;
468 sp_event_context_finish (ec);
469 g_object_unref (G_OBJECT (ec));
470 }
472 Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
473 ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
474 ec->next = event_context;
475 event_context = ec;
476 sp_event_context_activate (ec);
477 _event_context_changed_signal.emit (this, ec);
478 }
480 /**
481 * Push event context onto desktop's context stack.
482 */
483 void
484 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
485 {
486 SPEventContext *ref, *ec;
487 Inkscape::XML::Node *repr;
489 if (event_context && event_context->key == key) return;
490 ref = event_context;
491 while (ref && ref->next && ref->next->key != key) ref = ref->next;
492 if (ref && ref->next) {
493 ec = ref->next;
494 ref->next = ec->next;
495 sp_event_context_finish (ec);
496 g_object_unref (G_OBJECT (ec));
497 }
499 if (event_context) sp_event_context_deactivate (event_context);
500 repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
501 ec = sp_event_context_new (type, this, repr, key);
502 ec->next = event_context;
503 event_context = ec;
504 sp_event_context_activate (ec);
505 _event_context_changed_signal.emit (this, ec);
506 }
508 /**
509 * Sets the coordinate status to a given point
510 */
511 void
512 SPDesktop::set_coordinate_status (NR::Point p) {
513 _widget->setCoordinateStatus(p);
514 }
516 /**
517 * \see sp_document_item_from_list_at_point_bottom()
518 */
519 SPItem *
520 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
521 {
522 g_return_val_if_fail (doc() != NULL, NULL);
523 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
524 }
526 /**
527 * \see sp_document_item_at_point()
528 */
529 SPItem *
530 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
531 {
532 g_return_val_if_fail (doc() != NULL, NULL);
533 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
534 }
536 /**
537 * \see sp_document_group_at_point()
538 */
539 SPItem *
540 SPDesktop::group_at_point (NR::Point const p) const
541 {
542 g_return_val_if_fail (doc() != NULL, NULL);
543 return sp_document_group_at_point (doc(), dkey, p);
544 }
546 /**
547 * \brief Returns the mouse point in document coordinates; if mouse is
548 * outside the canvas, returns the center of canvas viewpoint
549 */
550 NR::Point
551 SPDesktop::point() const
552 {
553 NR::Point p = _widget->getPointer();
554 NR::Point pw = sp_canvas_window_to_world (canvas, p);
555 p = w2d(pw);
557 NR::Rect const r = canvas->getViewbox();
559 NR::Point r0 = w2d(r.min());
560 NR::Point r1 = w2d(r.max());
562 if (p[NR::X] >= r0[NR::X] &&
563 p[NR::X] <= r1[NR::X] &&
564 p[NR::Y] >= r1[NR::Y] &&
565 p[NR::Y] <= r0[NR::Y])
566 {
567 return p;
568 } else {
569 return (r0 + r1) / 2;
570 }
571 }
573 /**
574 * Put current zoom data in history list.
575 */
576 void
577 SPDesktop::push_current_zoom (GList **history)
578 {
579 NR::Rect const area = get_display_area();
581 NRRect *old_zoom = g_new(NRRect, 1);
582 old_zoom->x0 = area.min()[NR::X];
583 old_zoom->x1 = area.max()[NR::X];
584 old_zoom->y0 = area.min()[NR::Y];
585 old_zoom->y1 = area.max()[NR::Y];
586 if ( *history == NULL
587 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
588 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
589 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
590 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
591 {
592 *history = g_list_prepend (*history, old_zoom);
593 }
594 }
596 /**
597 * Set viewbox.
598 */
599 void
600 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
601 {
602 g_assert(_widget);
604 // save the zoom
605 if (log) {
606 push_current_zoom(&zooms_past);
607 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
608 g_list_free (zooms_future);
609 zooms_future = NULL;
610 }
612 double const cx = 0.5 * (x0 + x1);
613 double const cy = 0.5 * (y0 + y1);
615 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
617 double scale = expansion(_d2w);
618 double newscale;
619 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
620 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
621 } else {
622 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
623 }
625 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
627 int clear = FALSE;
628 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
629 /* Set zoom factors */
630 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
631 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
632 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
633 clear = TRUE;
634 }
636 /* Calculate top left corner */
637 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
638 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
640 /* Scroll */
641 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
643 _widget->updateRulers();
644 _widget->updateScrollbars(expansion(_d2w));
645 _widget->updateZoom();
646 }
648 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
649 {
650 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
651 }
653 /**
654 * Return viewbox dimensions.
655 */
656 NR::Rect SPDesktop::get_display_area() const
657 {
658 NR::Rect const viewbox = canvas->getViewbox();
660 double const scale = _d2w[0];
662 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
663 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
664 }
666 /**
667 * Revert back to previous zoom if possible.
668 */
669 void
670 SPDesktop::prev_zoom()
671 {
672 if (zooms_past == NULL) {
673 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
674 return;
675 }
677 // push current zoom into forward zooms list
678 push_current_zoom (&zooms_future);
680 // restore previous zoom
681 set_display_area (((NRRect *) zooms_past->data)->x0,
682 ((NRRect *) zooms_past->data)->y0,
683 ((NRRect *) zooms_past->data)->x1,
684 ((NRRect *) zooms_past->data)->y1,
685 0, false);
687 // remove the just-added zoom from the past zooms list
688 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
689 }
691 /**
692 * Set zoom to next in list.
693 */
694 void
695 SPDesktop::next_zoom()
696 {
697 if (zooms_future == NULL) {
698 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
699 return;
700 }
702 // push current zoom into past zooms list
703 push_current_zoom (&zooms_past);
705 // restore next zoom
706 set_display_area (((NRRect *) zooms_future->data)->x0,
707 ((NRRect *) zooms_future->data)->y0,
708 ((NRRect *) zooms_future->data)->x1,
709 ((NRRect *) zooms_future->data)->y1,
710 0, false);
712 // remove the just-used zoom from the zooms_future list
713 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
714 }
716 /**
717 * Zoom to point with absolute zoom factor.
718 */
719 void
720 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
721 {
722 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
724 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
725 // this check prevents "sliding" when trying to zoom in at maximum zoom;
726 /// \todo someone please fix calculations properly and remove this hack
727 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
728 return;
730 NR::Rect const viewbox = canvas->getViewbox();
732 double const width2 = viewbox.dimensions()[NR::X] / zoom;
733 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
735 set_display_area(cx - px * width2,
736 cy - py * height2,
737 cx + (1 - px) * width2,
738 cy + (1 - py) * height2,
739 0.0);
740 }
742 /**
743 * Zoom to center with absolute zoom factor.
744 */
745 void
746 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
747 {
748 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
749 }
751 /**
752 * Zoom to point with relative zoom factor.
753 */
754 void
755 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
756 {
757 NR::Rect const area = get_display_area();
759 if (cx < area.min()[NR::X]) {
760 cx = area.min()[NR::X];
761 }
762 if (cx > area.max()[NR::X]) {
763 cx = area.max()[NR::X];
764 }
765 if (cy < area.min()[NR::Y]) {
766 cy = area.min()[NR::Y];
767 }
768 if (cy > area.max()[NR::Y]) {
769 cy = area.max()[NR::Y];
770 }
772 gdouble const scale = expansion(_d2w) * zoom;
773 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
774 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
776 zoom_absolute_keep_point(cx, cy, px, py, scale);
777 }
779 /**
780 * Zoom to center with relative zoom factor.
781 */
782 void
783 SPDesktop::zoom_relative (double cx, double cy, double zoom)
784 {
785 gdouble scale = expansion(_d2w) * zoom;
786 zoom_absolute (cx, cy, scale);
787 }
789 /**
790 * Set display area to origin and current document dimensions.
791 */
792 void
793 SPDesktop::zoom_page()
794 {
795 NR::Rect d(NR::Point(0, 0),
796 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
798 if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
799 return;
800 }
802 set_display_area(d, 10);
803 }
805 /**
806 * Set display area to current document width.
807 */
808 void
809 SPDesktop::zoom_page_width()
810 {
811 NR::Rect const a = get_display_area();
813 if (sp_document_width(doc()) < 1.0) {
814 return;
815 }
817 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
818 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
820 set_display_area(d, 10);
821 }
823 /**
824 * Zoom to selection.
825 */
826 void
827 SPDesktop::zoom_selection()
828 {
829 NR::Rect const d = selection->bounds();
831 if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
832 return;
833 }
835 set_display_area(d, 10);
836 }
838 /**
839 * Tell widget to let zoom widget grab keyboard focus.
840 */
841 void
842 SPDesktop::zoom_grab_focus()
843 {
844 _widget->letZoomGrabFocus();
845 }
847 /**
848 * Zoom to whole drawing.
849 */
850 void
851 SPDesktop::zoom_drawing()
852 {
853 g_return_if_fail (doc() != NULL);
854 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
855 g_return_if_fail (docitem != NULL);
857 NR::Rect d = sp_item_bbox_desktop(docitem);
859 /* Note that the second condition here indicates that
860 ** there are no items in the drawing.
861 */
862 if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
863 return;
864 }
866 set_display_area(d, 10);
867 }
869 /**
870 * Scroll canvas by specific coordinate amount.
871 */
872 void
873 SPDesktop::scroll_world (double dx, double dy)
874 {
875 g_assert(_widget);
877 NR::Rect const viewbox = canvas->getViewbox();
879 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
881 _widget->updateRulers();
882 _widget->updateScrollbars(expansion(_d2w));
883 }
885 bool
886 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
887 {
888 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
890 // autoscrolldistance is in screen pixels, but the display area is in document units
891 autoscrolldistance /= expansion(_d2w);
892 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
894 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
895 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
897 NR::Point const s_w( (*p) * _d2w );
899 gdouble x_to;
900 if ((*p)[NR::X] < dbox.min()[NR::X])
901 x_to = dbox.min()[NR::X];
902 else if ((*p)[NR::X] > dbox.max()[NR::X])
903 x_to = dbox.max()[NR::X];
904 else
905 x_to = (*p)[NR::X];
907 gdouble y_to;
908 if ((*p)[NR::Y] < dbox.min()[NR::Y])
909 y_to = dbox.min()[NR::Y];
910 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
911 y_to = dbox.max()[NR::Y];
912 else
913 y_to = (*p)[NR::Y];
915 NR::Point const d_dt(x_to, y_to);
916 NR::Point const d_w( d_dt * _d2w );
917 NR::Point const moved_w( d_w - s_w );
919 if (autoscrollspeed == 0)
920 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
922 if (autoscrollspeed != 0)
923 scroll_world (autoscrollspeed * moved_w);
925 return true;
926 }
927 return false;
928 }
930 void
931 SPDesktop::fullscreen()
932 {
933 _widget->setFullscreen();
934 }
936 void
937 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
938 {
939 _widget->getGeometry (x, y, w, h);
940 }
942 void
943 SPDesktop::setWindowPosition (NR::Point p)
944 {
945 _widget->setPosition (p);
946 }
948 void
949 SPDesktop::setWindowSize (gint w, gint h)
950 {
951 _widget->setSize (w, h);
952 }
954 void
955 SPDesktop::setWindowTransient (void *p, int transient_policy)
956 {
957 _widget->setTransient (p, transient_policy);
958 }
960 void
961 SPDesktop::presentWindow()
962 {
963 _widget->present();
964 }
966 bool
967 SPDesktop::warnDialog (gchar *text)
968 {
969 return _widget->warnDialog (text);
970 }
972 void
973 SPDesktop::toggleRulers()
974 {
975 _widget->toggleRulers();
976 }
978 void
979 SPDesktop::toggleScrollbars()
980 {
981 _widget->toggleScrollbars();
982 }
984 void
985 SPDesktop::layoutWidget()
986 {
987 _widget->layout();
988 }
990 void
991 SPDesktop::destroyWidget()
992 {
993 _widget->destroy();
994 }
996 bool
997 SPDesktop::shutdown()
998 {
999 return _widget->shutdown();
1000 }
1002 void
1003 SPDesktop::setToolboxFocusTo (gchar const *label)
1004 {
1005 _widget->setToolboxFocusTo (label);
1006 }
1008 void
1009 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1010 {
1011 _widget->setToolboxAdjustmentValue (id, val);
1012 }
1014 bool
1015 SPDesktop::isToolboxButtonActive (gchar const *id)
1016 {
1017 return _widget->isToolboxButtonActive (id);
1018 }
1020 void
1021 SPDesktop::emitToolSubselectionChanged(gpointer data)
1022 {
1023 _tool_subselection_changed.emit(data);
1024 inkscape_subselection_changed (this);
1025 }
1027 void
1028 SPDesktop::updateNow()
1029 {
1030 sp_canvas_update_now(canvas);
1031 }
1033 void
1034 SPDesktop::enableInteraction()
1035 {
1036 _widget->enableInteraction();
1037 }
1039 void SPDesktop::disableInteraction()
1040 {
1041 _widget->disableInteraction();
1042 }
1044 //----------------------------------------------------------------------
1045 // Callback implementations. The virtual ones are connected by the view.
1047 void
1048 SPDesktop::onPositionSet (double x, double y)
1049 {
1050 _widget->viewSetPosition (NR::Point(x,y));
1051 }
1053 void
1054 SPDesktop::onResized (double x, double y)
1055 {
1056 // Nothing called here
1057 }
1059 /**
1060 * Redraw callback; queues Gtk redraw; connected by View.
1061 */
1062 void
1063 SPDesktop::onRedrawRequested ()
1064 {
1065 if (main) {
1066 _widget->requestCanvasUpdate();
1067 }
1068 }
1070 void
1071 SPDesktop::updateCanvasNow()
1072 {
1073 _widget->requestCanvasUpdateAndWait();
1074 }
1076 /**
1077 * Associate document with desktop.
1078 */
1079 /// \todo fixme: refactor SPDesktop::init to use setDocument
1080 void
1081 SPDesktop::setDocument (SPDocument *doc)
1082 {
1083 if (this->doc() && doc) {
1084 namedview->hide(this);
1085 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1086 }
1088 if (_layer_hierarchy) {
1089 _layer_hierarchy->clear();
1090 delete _layer_hierarchy;
1091 }
1092 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1093 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1094 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1095 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1096 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1098 _commit_connection.disconnect();
1099 _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1101 /// \todo fixme: This condition exists to make sure the code
1102 /// inside is called only once on initialization. But there
1103 /// are surely more safe methods to accomplish this.
1104 if (drawing) {
1105 NRArenaItem *ai;
1107 namedview = sp_document_namedview (doc, NULL);
1108 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1109 number = namedview->getViewCount();
1111 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1112 SP_CANVAS_ARENA (drawing)->arena,
1113 dkey,
1114 SP_ITEM_SHOW_DISPLAY);
1115 if (ai) {
1116 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1117 nr_arena_item_unref (ai);
1118 }
1119 namedview->show(this);
1120 /* Ugly hack */
1121 activate_guides (true);
1122 /* Ugly hack */
1123 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1124 }
1126 _document_replaced_signal.emit (this, doc);
1128 View::setDocument (doc);
1129 }
1131 void
1132 SPDesktop::onStatusMessage
1133 (Inkscape::MessageType type, gchar const *message)
1134 {
1135 if (_widget) {
1136 _widget->setMessage(type, message);
1137 }
1138 }
1140 void
1141 SPDesktop::onDocumentURISet (gchar const* uri)
1142 {
1143 _widget->setTitle(uri);
1144 }
1146 /**
1147 * Resized callback.
1148 */
1149 void
1150 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1151 {
1152 _doc2dt[5] = height;
1153 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1154 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1155 SP_CTRLRECT(page)->setRectangle(a);
1156 SP_CTRLRECT(page_border)->setRectangle(a);
1157 }
1160 void
1161 SPDesktop::_onActivate (SPDesktop* dt)
1162 {
1163 if (!dt->_widget) return;
1164 dt->_widget->activateDesktop();
1165 }
1167 void
1168 SPDesktop::_onDeactivate (SPDesktop* dt)
1169 {
1170 if (!dt->_widget) return;
1171 dt->_widget->deactivateDesktop();
1172 }
1174 void
1175 SPDesktop::_onSelectionModified
1176 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1177 {
1178 if (!dt->_widget) return;
1179 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1180 }
1182 static void
1183 _onSelectionChanged
1184 (Inkscape::Selection *selection, SPDesktop *desktop)
1185 {
1186 /** \todo
1187 * only change the layer for single selections, or what?
1188 * This seems reasonable -- for multiple selections there can be many
1189 * different layers involved.
1190 */
1191 SPItem *item=selection->singleItem();
1192 if (item) {
1193 SPObject *layer=desktop->layerForObject(item);
1194 if ( layer && layer != desktop->currentLayer() ) {
1195 desktop->setCurrentLayer(layer);
1196 }
1197 }
1198 }
1200 /**
1201 * Calls event handler of current event context.
1202 * \param arena Unused
1203 * \todo fixme
1204 */
1205 static gint
1206 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1207 {
1208 if (ai) {
1209 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1210 return sp_event_context_item_handler (desktop->event_context, spi, event);
1211 } else {
1212 return sp_event_context_root_handler (desktop->event_context, event);
1213 }
1214 }
1216 static void
1217 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1218 g_return_if_fail(SP_IS_GROUP(layer));
1219 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1220 }
1222 /// Callback
1223 static void
1224 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1225 g_return_if_fail(SP_IS_GROUP(layer));
1226 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1227 }
1229 /// Callback
1230 static void
1231 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1232 SPDesktop *desktop)
1233 {
1234 desktop->_layer_changed_signal.emit (bottom);
1235 }
1237 /// Called when document is starting to be rebuilt.
1238 static void
1239 _reconstruction_start (SPDesktop * desktop)
1240 {
1241 // printf("Desktop, starting reconstruction\n");
1242 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1243 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1245 /*
1246 GSList const * selection_objs = desktop->selection->list();
1247 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1249 }
1250 */
1251 desktop->selection->clear();
1253 // printf("Desktop, starting reconstruction end\n");
1254 }
1256 /// Called when document rebuild is finished.
1257 static void
1258 _reconstruction_finish (SPDesktop * desktop)
1259 {
1260 // printf("Desktop, finishing reconstruction\n");
1261 if (desktop->_reconstruction_old_layer_id == NULL)
1262 return;
1264 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1265 if (newLayer != NULL)
1266 desktop->setCurrentLayer(newLayer);
1268 g_free(desktop->_reconstruction_old_layer_id);
1269 desktop->_reconstruction_old_layer_id = NULL;
1270 // printf("Desktop, finishing reconstruction end\n");
1271 return;
1272 }
1274 /**
1275 * Namedview_modified callback.
1276 */
1277 static void
1278 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1279 {
1280 SPNamedView *nv=SP_NAMEDVIEW(obj);
1282 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1284 /* Recalculate snap distances */
1285 /* FIXME: why is the desktop getting involved in setting up something
1286 ** that is entirely to do with the namedview?
1287 */
1288 _update_snap_distances (desktop);
1290 /* Show/hide page background */
1291 if (nv->pagecolor & 0xff) {
1292 sp_canvas_item_show (desktop->table);
1293 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1294 sp_canvas_item_move_to_z (desktop->table, 0);
1295 } else {
1296 sp_canvas_item_hide (desktop->table);
1297 }
1299 /* Show/hide page border */
1300 if (nv->showborder) {
1301 // show
1302 sp_canvas_item_show (desktop->page_border);
1303 // set color and shadow
1304 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1305 if (nv->pageshadow) {
1306 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1307 }
1308 // place in the z-order stack
1309 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1310 sp_canvas_item_move_to_z (desktop->page_border, 2);
1311 } else {
1312 int order = sp_canvas_item_order (desktop->page_border);
1313 int morder = sp_canvas_item_order (desktop->drawing);
1314 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1315 morder - order);
1316 }
1317 } else {
1318 sp_canvas_item_hide (desktop->page_border);
1319 if (nv->pageshadow) {
1320 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1321 }
1322 }
1324 /* Show/hide page shadow */
1325 if (nv->showpageshadow && nv->pageshadow) {
1326 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1327 } else {
1328 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1329 }
1331 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1332 (SP_RGBA32_R_U(nv->pagecolor) +
1333 SP_RGBA32_G_U(nv->pagecolor) +
1334 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1335 // the background color is light or transparent, use black outline
1336 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1337 } else { // use white outline
1338 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1339 }
1340 }
1341 }
1343 /**
1344 * Callback to reset snapper's distances.
1345 */
1346 static void
1347 _update_snap_distances (SPDesktop *desktop)
1348 {
1349 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1351 SPNamedView &nv = *desktop->namedview;
1353 nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1354 *nv.gridtoleranceunit,
1355 px));
1356 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1357 *nv.guidetoleranceunit,
1358 px));
1359 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1360 *nv.objecttoleranceunit,
1361 px));
1362 }
1365 NR::Matrix SPDesktop::w2d() const
1366 {
1367 return _w2d;
1368 }
1370 NR::Point SPDesktop::w2d(NR::Point const &p) const
1371 {
1372 return p * _w2d;
1373 }
1375 NR::Point SPDesktop::d2w(NR::Point const &p) const
1376 {
1377 return p * _d2w;
1378 }
1380 NR::Matrix SPDesktop::doc2dt() const
1381 {
1382 return _doc2dt;
1383 }
1385 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1386 {
1387 return p * _doc2dt;
1388 }
1390 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1391 {
1392 return p / _doc2dt;
1393 }
1396 /**
1397 * Pop event context from desktop's context stack. Never used.
1398 */
1399 // void
1400 // SPDesktop::pop_event_context (unsigned int key)
1401 // {
1402 // SPEventContext *ec = NULL;
1403 //
1404 // if (event_context && event_context->key == key) {
1405 // g_return_if_fail (event_context);
1406 // g_return_if_fail (event_context->next);
1407 // ec = event_context;
1408 // sp_event_context_deactivate (ec);
1409 // event_context = ec->next;
1410 // sp_event_context_activate (event_context);
1411 // _event_context_changed_signal.emit (this, ec);
1412 // }
1413 //
1414 // SPEventContext *ref = event_context;
1415 // while (ref && ref->next && ref->next->key != key)
1416 // ref = ref->next;
1417 //
1418 // if (ref && ref->next) {
1419 // ec = ref->next;
1420 // ref->next = ec->next;
1421 // }
1422 //
1423 // if (ec) {
1424 // sp_event_context_finish (ec);
1425 // g_object_unref (G_OBJECT (ec));
1426 // }
1427 // }
1429 /*
1430 Local Variables:
1431 mode:c++
1432 c-file-style:"stroustrup"
1433 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1434 indent-tabs-mode:nil
1435 fill-column:99
1436 End:
1437 */
1438 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :