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