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>
52 #include <sigc++/functors/mem_fun.h>
54 #include "macros.h"
55 #include "inkscape-private.h"
56 #include "desktop.h"
57 #include "desktop-events.h"
58 #include "desktop-handles.h"
59 #include "document.h"
60 #include "message-stack.h"
61 #include "selection.h"
62 #include "select-context.h"
63 #include "sp-namedview.h"
64 #include "color.h"
65 #include "sp-item-group.h"
66 #include "prefs-utils.h"
67 #include "object-hierarchy.h"
68 #include "helper/units.h"
69 #include "display/canvas-arena.h"
70 #include "display/nr-arena.h"
71 #include "display/gnome-canvas-acetate.h"
72 #include "display/sodipodi-ctrlrect.h"
73 #include "display/sp-canvas-util.h"
74 #include "libnr/nr-matrix-div.h"
75 #include "libnr/nr-rect-ops.h"
76 #include "ui/dialog/dialog-manager.h"
77 #include "xml/repr.h"
78 #include "message-context.h"
79 #include "layer-manager.h"
81 namespace Inkscape { namespace XML { class Node; }}
83 // Callback declarations
84 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
85 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
86 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
87 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
88 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
89 static void _reconstruction_start(SPDesktop * desktop);
90 static void _reconstruction_finish(SPDesktop * desktop);
91 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
92 static void _update_snap_distances (SPDesktop *desktop);
94 /**
95 * Return new desktop object.
96 * \pre namedview != NULL.
97 * \pre canvas != NULL.
98 */
99 SPDesktop::SPDesktop()
100 {
101 _dlg_mgr = NULL;
102 _widget = 0;
103 namedview = NULL;
104 selection = NULL;
105 acetate = NULL;
106 main = NULL;
107 grid = NULL;
108 guides = NULL;
109 drawing = NULL;
110 sketch = NULL;
111 controls = NULL;
112 event_context = 0;
113 layer_manager = 0;
115 _d2w.set_identity();
116 _w2d.set_identity();
117 _doc2dt = NR::Matrix(NR::scale(1, -1));
119 guides_active = false;
121 zooms_past = NULL;
122 zooms_future = NULL;
124 is_fullscreen = false;
126 gr_item = NULL;
127 gr_point_num = 0;
128 gr_fill_or_stroke = true;
130 _layer_hierarchy = NULL;
131 _active = false;
133 selection = Inkscape::GC::release (new Inkscape::Selection (this));
134 }
136 void
137 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
139 {
140 _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
142 current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
144 namedview = nv;
145 canvas = aCanvas;
147 SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
148 /* Kill flicker */
149 sp_document_ensure_up_to_date (document);
151 /* Setup Dialog Manager */
152 _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
154 dkey = sp_item_display_key_new (1);
156 /* Connect document */
157 setDocument (document);
159 number = namedview->getViewCount();
162 /* Setup Canvas */
163 g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
165 SPCanvasGroup *root = sp_canvas_root (canvas);
167 /* Setup adminstrative layers */
168 acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
169 g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
170 main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
171 g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
173 table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
174 SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
175 SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
176 sp_canvas_item_move_to_z (table, 0);
178 page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
179 ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
180 page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
182 drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
183 g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
185 SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
187 // Start always in normal mode
188 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
189 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
191 grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
192 guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
193 sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
194 controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196 /* Push select tool to the bottom of stack */
197 /** \todo
198 * FIXME: this is the only call to this. Everything else seems to just
199 * call "set" instead of "push". Can we assume that there is only one
200 * context ever?
201 */
202 push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
204 // display rect and zoom are now handled in sp_desktop_widget_realize()
206 NR::Rect const d(NR::Point(0.0, 0.0),
207 NR::Point(sp_document_width(document), sp_document_height(document)));
209 SP_CTRLRECT(page)->setRectangle(d);
210 SP_CTRLRECT(page_border)->setRectangle(d);
212 /* the following sets the page shadow on the canvas
213 It was originally set to 5, which is really cheesy!
214 It now is an attribute in the document's namedview. If a value of
215 0 is used, then the constructor for a shadow is not initialized.
216 */
218 if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
219 SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
220 }
223 /* Connect event for page resize */
224 _doc2dt[5] = sp_document_height (document);
225 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
227 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_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 _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
254 // ?
255 // sp_active_desktop_set (desktop);
256 _inkscape = INKSCAPE;
258 _activate_connection = _activate_signal.connect(
259 sigc::bind(
260 sigc::ptr_fun(_onActivate),
261 this
262 )
263 );
264 _deactivate_connection = _deactivate_signal.connect(
265 sigc::bind(
266 sigc::ptr_fun(_onDeactivate),
267 this
268 )
269 );
271 _sel_modified_connection = selection->connectModified(
272 sigc::bind(
273 sigc::ptr_fun(&_onSelectionModified),
274 this
275 )
276 );
277 _sel_changed_connection = selection->connectChanged(
278 sigc::bind(
279 sigc::ptr_fun(&_onSelectionChanged),
280 this
281 )
282 );
285 /* setup LayerManager */
286 // (Setting up after the connections are all in place, as it may use some of them)
287 layer_manager = new Inkscape::LayerManager( this );
288 }
291 void SPDesktop::destroy()
292 {
293 _activate_connection.disconnect();
294 _deactivate_connection.disconnect();
295 _sel_modified_connection.disconnect();
296 _sel_changed_connection.disconnect();
297 _modified_connection.disconnect();
299 while (event_context) {
300 SPEventContext *ec = event_context;
301 event_context = ec->next;
302 sp_event_context_finish (ec);
303 g_object_unref (G_OBJECT (ec));
304 }
306 if (_layer_hierarchy) {
307 delete _layer_hierarchy;
308 }
310 if (_inkscape) {
311 _inkscape = NULL;
312 }
314 if (drawing) {
315 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
316 drawing = NULL;
317 }
319 delete _guides_message_context;
320 _guides_message_context = NULL;
322 _modified_connection.disconnect();
324 g_list_free (zooms_past);
325 g_list_free (zooms_future);
326 }
328 SPDesktop::~SPDesktop() {}
330 //--------------------------------------------------------------------
331 /* Public methods */
333 void SPDesktop::setDisplayModeNormal()
334 {
335 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
336 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
337 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
338 }
340 void SPDesktop::setDisplayModeOutline()
341 {
342 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
343 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
344 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
345 }
347 /**
348 * Returns current root (=bottom) layer.
349 */
350 SPObject *SPDesktop::currentRoot() const
351 {
352 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
353 }
355 /**
356 * Returns current top layer.
357 */
358 SPObject *SPDesktop::currentLayer() const
359 {
360 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
361 }
363 /**
364 * Sets the current layer of the desktop.
365 *
366 * Make \a object the top layer.
367 */
368 void SPDesktop::setCurrentLayer(SPObject *object) {
369 g_return_if_fail(SP_IS_GROUP(object));
370 g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
371 // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
372 _layer_hierarchy->setBottom(object);
373 }
375 /**
376 * Return layer that contains \a object.
377 */
378 SPObject *SPDesktop::layerForObject(SPObject *object) {
379 g_return_val_if_fail(object != NULL, NULL);
381 SPObject *root=currentRoot();
382 object = SP_OBJECT_PARENT(object);
383 while ( object && object != root && !isLayer(object) ) {
384 object = SP_OBJECT_PARENT(object);
385 }
386 return object;
387 }
389 /**
390 * True if object is a layer.
391 */
392 bool SPDesktop::isLayer(SPObject *object) const {
393 return ( SP_IS_GROUP(object)
394 && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
395 == SPGroup::LAYER ) );
396 }
398 /**
399 * True if desktop viewport fully contains \a item's bbox.
400 */
401 bool SPDesktop::isWithinViewport (SPItem *item) const
402 {
403 NR::Rect const viewport = get_display_area();
404 NR::Rect const bbox = sp_item_bbox_desktop(item);
405 return viewport.contains(bbox);
406 }
408 ///
409 bool SPDesktop::itemIsHidden(SPItem const *item) const {
410 return item->isHidden(this->dkey);
411 }
413 /**
414 * Set activate property of desktop; emit signal if changed.
415 */
416 void
417 SPDesktop::set_active (bool new_active)
418 {
419 if (new_active != _active) {
420 _active = new_active;
421 if (new_active) {
422 _activate_signal.emit();
423 } else {
424 _deactivate_signal.emit();
425 }
426 }
427 }
429 /**
430 * Set activate status of current desktop's named view.
431 */
432 void
433 SPDesktop::activate_guides(bool activate)
434 {
435 guides_active = activate;
436 namedview->activateGuides(this, activate);
437 }
439 /**
440 * Make desktop switch documents.
441 */
442 void
443 SPDesktop::change_document (SPDocument *theDocument)
444 {
445 g_return_if_fail (theDocument != NULL);
447 /* unselect everything before switching documents */
448 selection->clear();
450 setDocument (theDocument);
451 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
452 _document_replaced_signal.emit (this, theDocument);
453 }
455 /**
456 * Make desktop switch event contexts.
457 */
458 void
459 SPDesktop::set_event_context (GtkType type, const gchar *config)
460 {
461 SPEventContext *ec;
462 while (event_context) {
463 ec = event_context;
464 sp_event_context_deactivate (ec);
465 event_context = ec->next;
466 sp_event_context_finish (ec);
467 g_object_unref (G_OBJECT (ec));
468 }
470 Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
471 ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
472 ec->next = event_context;
473 event_context = ec;
474 sp_event_context_activate (ec);
475 _event_context_changed_signal.emit (this, ec);
476 }
478 /**
479 * Push event context onto desktop's context stack.
480 */
481 void
482 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
483 {
484 SPEventContext *ref, *ec;
485 Inkscape::XML::Node *repr;
487 if (event_context && event_context->key == key) return;
488 ref = event_context;
489 while (ref && ref->next && ref->next->key != key) ref = ref->next;
490 if (ref && ref->next) {
491 ec = ref->next;
492 ref->next = ec->next;
493 sp_event_context_finish (ec);
494 g_object_unref (G_OBJECT (ec));
495 }
497 if (event_context) sp_event_context_deactivate (event_context);
498 repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
499 ec = sp_event_context_new (type, this, repr, key);
500 ec->next = event_context;
501 event_context = ec;
502 sp_event_context_activate (ec);
503 _event_context_changed_signal.emit (this, ec);
504 }
506 /**
507 * Sets the coordinate status to a given point
508 */
509 void
510 SPDesktop::set_coordinate_status (NR::Point p) {
511 _widget->setCoordinateStatus(p);
512 }
514 /**
515 * \see sp_document_item_from_list_at_point_bottom()
516 */
517 SPItem *
518 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
519 {
520 g_return_val_if_fail (doc() != NULL, NULL);
521 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
522 }
524 /**
525 * \see sp_document_item_at_point()
526 */
527 SPItem *
528 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
529 {
530 g_return_val_if_fail (doc() != NULL, NULL);
531 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
532 }
534 /**
535 * \see sp_document_group_at_point()
536 */
537 SPItem *
538 SPDesktop::group_at_point (NR::Point const p) const
539 {
540 g_return_val_if_fail (doc() != NULL, NULL);
541 return sp_document_group_at_point (doc(), dkey, p);
542 }
544 /**
545 * \brief Returns the mouse point in document coordinates; if mouse is
546 * outside the canvas, returns the center of canvas viewpoint
547 */
548 NR::Point
549 SPDesktop::point() const
550 {
551 NR::Point p = _widget->getPointer();
552 NR::Point pw = sp_canvas_window_to_world (canvas, p);
553 p = w2d(pw);
555 NR::Rect const r = canvas->getViewbox();
557 NR::Point r0 = w2d(r.min());
558 NR::Point r1 = w2d(r.max());
560 if (p[NR::X] >= r0[NR::X] &&
561 p[NR::X] <= r1[NR::X] &&
562 p[NR::Y] >= r1[NR::Y] &&
563 p[NR::Y] <= r0[NR::Y])
564 {
565 return p;
566 } else {
567 return (r0 + r1) / 2;
568 }
569 }
571 /**
572 * Put current zoom data in history list.
573 */
574 void
575 SPDesktop::push_current_zoom (GList **history)
576 {
577 NR::Rect const area = get_display_area();
579 NRRect *old_zoom = g_new(NRRect, 1);
580 old_zoom->x0 = area.min()[NR::X];
581 old_zoom->x1 = area.max()[NR::X];
582 old_zoom->y0 = area.min()[NR::Y];
583 old_zoom->y1 = area.max()[NR::Y];
584 if ( *history == NULL
585 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
586 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
587 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
588 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
589 {
590 *history = g_list_prepend (*history, old_zoom);
591 }
592 }
594 /**
595 * Set viewbox.
596 */
597 void
598 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
599 {
600 g_assert(_widget);
602 // save the zoom
603 if (log) {
604 push_current_zoom(&zooms_past);
605 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
606 g_list_free (zooms_future);
607 zooms_future = NULL;
608 }
610 double const cx = 0.5 * (x0 + x1);
611 double const cy = 0.5 * (y0 + y1);
613 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
615 double scale = expansion(_d2w);
616 double newscale;
617 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
618 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
619 } else {
620 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
621 }
623 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
625 int clear = FALSE;
626 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
627 /* Set zoom factors */
628 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
629 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
630 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
631 clear = TRUE;
632 }
634 /* Calculate top left corner */
635 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
636 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
638 /* Scroll */
639 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
641 _widget->updateRulers();
642 _widget->updateScrollbars(expansion(_d2w));
643 _widget->updateZoom();
644 }
646 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
647 {
648 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
649 }
651 /**
652 * Return viewbox dimensions.
653 */
654 NR::Rect SPDesktop::get_display_area() const
655 {
656 NR::Rect const viewbox = canvas->getViewbox();
658 double const scale = _d2w[0];
660 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
661 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
662 }
664 /**
665 * Revert back to previous zoom if possible.
666 */
667 void
668 SPDesktop::prev_zoom()
669 {
670 if (zooms_past == NULL) {
671 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
672 return;
673 }
675 // push current zoom into forward zooms list
676 push_current_zoom (&zooms_future);
678 // restore previous zoom
679 set_display_area (((NRRect *) zooms_past->data)->x0,
680 ((NRRect *) zooms_past->data)->y0,
681 ((NRRect *) zooms_past->data)->x1,
682 ((NRRect *) zooms_past->data)->y1,
683 0, false);
685 // remove the just-added zoom from the past zooms list
686 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
687 }
689 /**
690 * Set zoom to next in list.
691 */
692 void
693 SPDesktop::next_zoom()
694 {
695 if (zooms_future == NULL) {
696 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
697 return;
698 }
700 // push current zoom into past zooms list
701 push_current_zoom (&zooms_past);
703 // restore next zoom
704 set_display_area (((NRRect *) zooms_future->data)->x0,
705 ((NRRect *) zooms_future->data)->y0,
706 ((NRRect *) zooms_future->data)->x1,
707 ((NRRect *) zooms_future->data)->y1,
708 0, false);
710 // remove the just-used zoom from the zooms_future list
711 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
712 }
714 /**
715 * Zoom to point with absolute zoom factor.
716 */
717 void
718 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
719 {
720 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
722 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
723 // this check prevents "sliding" when trying to zoom in at maximum zoom;
724 /// \todo someone please fix calculations properly and remove this hack
725 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
726 return;
728 NR::Rect const viewbox = canvas->getViewbox();
730 double const width2 = viewbox.dimensions()[NR::X] / zoom;
731 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
733 set_display_area(cx - px * width2,
734 cy - py * height2,
735 cx + (1 - px) * width2,
736 cy + (1 - py) * height2,
737 0.0);
738 }
740 /**
741 * Zoom to center with absolute zoom factor.
742 */
743 void
744 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
745 {
746 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
747 }
749 /**
750 * Zoom to point with relative zoom factor.
751 */
752 void
753 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
754 {
755 NR::Rect const area = get_display_area();
757 if (cx < area.min()[NR::X]) {
758 cx = area.min()[NR::X];
759 }
760 if (cx > area.max()[NR::X]) {
761 cx = area.max()[NR::X];
762 }
763 if (cy < area.min()[NR::Y]) {
764 cy = area.min()[NR::Y];
765 }
766 if (cy > area.max()[NR::Y]) {
767 cy = area.max()[NR::Y];
768 }
770 gdouble const scale = expansion(_d2w) * zoom;
771 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
772 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
774 zoom_absolute_keep_point(cx, cy, px, py, scale);
775 }
777 /**
778 * Zoom to center with relative zoom factor.
779 */
780 void
781 SPDesktop::zoom_relative (double cx, double cy, double zoom)
782 {
783 gdouble scale = expansion(_d2w) * zoom;
784 zoom_absolute (cx, cy, scale);
785 }
787 /**
788 * Set display area to origin and current document dimensions.
789 */
790 void
791 SPDesktop::zoom_page()
792 {
793 NR::Rect d(NR::Point(0, 0),
794 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
796 if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
797 return;
798 }
800 set_display_area(d, 10);
801 }
803 /**
804 * Set display area to current document width.
805 */
806 void
807 SPDesktop::zoom_page_width()
808 {
809 NR::Rect const a = get_display_area();
811 if (sp_document_width(doc()) < 1.0) {
812 return;
813 }
815 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
816 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
818 set_display_area(d, 10);
819 }
821 /**
822 * Zoom to selection.
823 */
824 void
825 SPDesktop::zoom_selection()
826 {
827 NR::Rect const d = selection->bounds();
829 if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
830 return;
831 }
833 set_display_area(d, 10);
834 }
836 /**
837 * Tell widget to let zoom widget grab keyboard focus.
838 */
839 void
840 SPDesktop::zoom_grab_focus()
841 {
842 _widget->letZoomGrabFocus();
843 }
845 /**
846 * Zoom to whole drawing.
847 */
848 void
849 SPDesktop::zoom_drawing()
850 {
851 g_return_if_fail (doc() != NULL);
852 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
853 g_return_if_fail (docitem != NULL);
855 NR::Rect d = sp_item_bbox_desktop(docitem);
857 /* Note that the second condition here indicates that
858 ** there are no items in the drawing.
859 */
860 if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
861 return;
862 }
864 set_display_area(d, 10);
865 }
867 /**
868 * Scroll canvas by specific coordinate amount.
869 */
870 void
871 SPDesktop::scroll_world (double dx, double dy)
872 {
873 g_assert(_widget);
875 NR::Rect const viewbox = canvas->getViewbox();
877 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
879 _widget->updateRulers();
880 _widget->updateScrollbars(expansion(_d2w));
881 }
883 bool
884 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
885 {
886 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
888 // autoscrolldistance is in screen pixels, but the display area is in document units
889 autoscrolldistance /= expansion(_d2w);
890 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
892 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
893 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
895 NR::Point const s_w( (*p) * _d2w );
897 gdouble x_to;
898 if ((*p)[NR::X] < dbox.min()[NR::X])
899 x_to = dbox.min()[NR::X];
900 else if ((*p)[NR::X] > dbox.max()[NR::X])
901 x_to = dbox.max()[NR::X];
902 else
903 x_to = (*p)[NR::X];
905 gdouble y_to;
906 if ((*p)[NR::Y] < dbox.min()[NR::Y])
907 y_to = dbox.min()[NR::Y];
908 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
909 y_to = dbox.max()[NR::Y];
910 else
911 y_to = (*p)[NR::Y];
913 NR::Point const d_dt(x_to, y_to);
914 NR::Point const d_w( d_dt * _d2w );
915 NR::Point const moved_w( d_w - s_w );
917 if (autoscrollspeed == 0)
918 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
920 if (autoscrollspeed != 0)
921 scroll_world (autoscrollspeed * moved_w);
923 return true;
924 }
925 return false;
926 }
928 void
929 SPDesktop::fullscreen()
930 {
931 _widget->setFullscreen();
932 }
934 void
935 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
936 {
937 _widget->getGeometry (x, y, w, h);
938 }
940 void
941 SPDesktop::setWindowPosition (NR::Point p)
942 {
943 _widget->setPosition (p);
944 }
946 void
947 SPDesktop::setWindowSize (gint w, gint h)
948 {
949 _widget->setSize (w, h);
950 }
952 void
953 SPDesktop::setWindowTransient (void *p, int transient_policy)
954 {
955 _widget->setTransient (p, transient_policy);
956 }
958 void
959 SPDesktop::presentWindow()
960 {
961 _widget->present();
962 }
964 bool
965 SPDesktop::warnDialog (gchar *text)
966 {
967 return _widget->warnDialog (text);
968 }
970 void
971 SPDesktop::toggleRulers()
972 {
973 _widget->toggleRulers();
974 }
976 void
977 SPDesktop::toggleScrollbars()
978 {
979 _widget->toggleScrollbars();
980 }
982 void
983 SPDesktop::layoutWidget()
984 {
985 _widget->layout();
986 }
988 void
989 SPDesktop::destroyWidget()
990 {
991 _widget->destroy();
992 }
994 bool
995 SPDesktop::shutdown()
996 {
997 return _widget->shutdown();
998 }
1000 void
1001 SPDesktop::setToolboxFocusTo (gchar const *label)
1002 {
1003 _widget->setToolboxFocusTo (label);
1004 }
1006 void
1007 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1008 {
1009 _widget->setToolboxAdjustmentValue (id, val);
1010 }
1012 bool
1013 SPDesktop::isToolboxButtonActive (gchar const *id)
1014 {
1015 return _widget->isToolboxButtonActive (id);
1016 }
1018 void
1019 SPDesktop::emitToolSubselectionChanged(gpointer data)
1020 {
1021 _tool_subselection_changed.emit(data);
1022 inkscape_subselection_changed (this);
1023 }
1025 void
1026 SPDesktop::updateNow()
1027 {
1028 sp_canvas_update_now(canvas);
1029 }
1031 //----------------------------------------------------------------------
1032 // Callback implementations. The virtual ones are connected by the view.
1034 void
1035 SPDesktop::onPositionSet (double x, double y)
1036 {
1037 _widget->viewSetPosition (NR::Point(x,y));
1038 }
1040 void
1041 SPDesktop::onResized (double x, double y)
1042 {
1043 // Nothing called here
1044 }
1046 /**
1047 * Redraw callback; queues Gtk redraw; connected by View.
1048 */
1049 void
1050 SPDesktop::onRedrawRequested ()
1051 {
1052 if (main) {
1053 _widget->requestCanvasUpdate();
1054 }
1055 }
1057 void
1058 SPDesktop::updateCanvasNow()
1059 {
1060 _widget->requestCanvasUpdateAndWait();
1061 }
1063 /**
1064 * Associate document with desktop.
1065 */
1066 /// \todo fixme: refactor SPDesktop::init to use setDocument
1067 void
1068 SPDesktop::setDocument (SPDocument *doc)
1069 {
1070 if (this->doc() && doc) {
1071 namedview->hide(this);
1072 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1073 }
1075 if (_layer_hierarchy) {
1076 _layer_hierarchy->clear();
1077 delete _layer_hierarchy;
1078 }
1079 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1080 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1081 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1082 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1083 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1085 _commit_connection.disconnect();
1086 _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1088 /// \todo fixme: This condition exists to make sure the code
1089 /// inside is called only once on initialization. But there
1090 /// are surely more safe methods to accomplish this.
1091 if (drawing) {
1092 NRArenaItem *ai;
1094 namedview = sp_document_namedview (doc, NULL);
1095 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1096 number = namedview->getViewCount();
1098 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1099 SP_CANVAS_ARENA (drawing)->arena,
1100 dkey,
1101 SP_ITEM_SHOW_DISPLAY);
1102 if (ai) {
1103 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1104 nr_arena_item_unref (ai);
1105 }
1106 namedview->show(this);
1107 /* Ugly hack */
1108 activate_guides (true);
1109 /* Ugly hack */
1110 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1111 }
1113 _document_replaced_signal.emit (this, doc);
1115 View::setDocument (doc);
1116 }
1118 void
1119 SPDesktop::onStatusMessage
1120 (Inkscape::MessageType type, gchar const *message)
1121 {
1122 if (_widget) {
1123 _widget->setMessage(type, message);
1124 }
1125 }
1127 void
1128 SPDesktop::onDocumentURISet (gchar const* uri)
1129 {
1130 _widget->setTitle(uri);
1131 }
1133 /**
1134 * Resized callback.
1135 */
1136 void
1137 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1138 {
1139 _doc2dt[5] = height;
1140 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1141 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1142 SP_CTRLRECT(page)->setRectangle(a);
1143 SP_CTRLRECT(page_border)->setRectangle(a);
1144 }
1147 void
1148 SPDesktop::_onActivate (SPDesktop* dt)
1149 {
1150 if (!dt->_widget) return;
1151 dt->_widget->activateDesktop();
1152 }
1154 void
1155 SPDesktop::_onDeactivate (SPDesktop* dt)
1156 {
1157 if (!dt->_widget) return;
1158 dt->_widget->deactivateDesktop();
1159 }
1161 void
1162 SPDesktop::_onSelectionModified
1163 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1164 {
1165 if (!dt->_widget) return;
1166 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1167 }
1169 static void
1170 _onSelectionChanged
1171 (Inkscape::Selection *selection, SPDesktop *desktop)
1172 {
1173 /** \todo
1174 * only change the layer for single selections, or what?
1175 * This seems reasonable -- for multiple selections there can be many
1176 * different layers involved.
1177 */
1178 SPItem *item=selection->singleItem();
1179 if (item) {
1180 SPObject *layer=desktop->layerForObject(item);
1181 if ( layer && layer != desktop->currentLayer() ) {
1182 desktop->setCurrentLayer(layer);
1183 }
1184 }
1185 }
1187 /**
1188 * Calls event handler of current event context.
1189 * \param arena Unused
1190 * \todo fixme
1191 */
1192 static gint
1193 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1194 {
1195 if (ai) {
1196 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1197 return sp_event_context_item_handler (desktop->event_context, spi, event);
1198 } else {
1199 return sp_event_context_root_handler (desktop->event_context, event);
1200 }
1201 }
1203 static void
1204 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1205 g_return_if_fail(SP_IS_GROUP(layer));
1206 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1207 }
1209 /// Callback
1210 static void
1211 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1212 g_return_if_fail(SP_IS_GROUP(layer));
1213 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1214 }
1216 /// Callback
1217 static void
1218 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1219 SPDesktop *desktop)
1220 {
1221 desktop->_layer_changed_signal.emit (bottom);
1222 }
1224 /// Called when document is starting to be rebuilt.
1225 static void
1226 _reconstruction_start (SPDesktop * desktop)
1227 {
1228 // printf("Desktop, starting reconstruction\n");
1229 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1230 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1232 /*
1233 GSList const * selection_objs = desktop->selection->list();
1234 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1236 }
1237 */
1238 desktop->selection->clear();
1240 // printf("Desktop, starting reconstruction end\n");
1241 }
1243 /// Called when document rebuild is finished.
1244 static void
1245 _reconstruction_finish (SPDesktop * desktop)
1246 {
1247 // printf("Desktop, finishing reconstruction\n");
1248 if (desktop->_reconstruction_old_layer_id == NULL)
1249 return;
1251 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1252 if (newLayer != NULL)
1253 desktop->setCurrentLayer(newLayer);
1255 g_free(desktop->_reconstruction_old_layer_id);
1256 desktop->_reconstruction_old_layer_id = NULL;
1257 // printf("Desktop, finishing reconstruction end\n");
1258 return;
1259 }
1261 /**
1262 * Namedview_modified callback.
1263 */
1264 static void
1265 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1266 {
1267 SPNamedView *nv=SP_NAMEDVIEW(obj);
1269 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1271 /* Recalculate snap distances */
1272 /* FIXME: why is the desktop getting involved in setting up something
1273 ** that is entirely to do with the namedview?
1274 */
1275 _update_snap_distances (desktop);
1277 /* Show/hide page background */
1278 if (nv->pagecolor & 0xff) {
1279 sp_canvas_item_show (desktop->table);
1280 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1281 sp_canvas_item_move_to_z (desktop->table, 0);
1282 } else {
1283 sp_canvas_item_hide (desktop->table);
1284 }
1286 /* Show/hide page border */
1287 if (nv->showborder) {
1288 // show
1289 sp_canvas_item_show (desktop->page_border);
1290 // set color and shadow
1291 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1292 if (nv->pageshadow) {
1293 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1294 }
1295 // place in the z-order stack
1296 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1297 sp_canvas_item_move_to_z (desktop->page_border, 2);
1298 } else {
1299 int order = sp_canvas_item_order (desktop->page_border);
1300 int morder = sp_canvas_item_order (desktop->drawing);
1301 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1302 morder - order);
1303 }
1304 } else {
1305 sp_canvas_item_hide (desktop->page_border);
1306 if (nv->pageshadow) {
1307 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1308 }
1309 }
1311 /* Show/hide page shadow */
1312 if (nv->showpageshadow && nv->pageshadow) {
1313 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1314 } else {
1315 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1316 }
1318 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1319 (SP_RGBA32_R_U(nv->pagecolor) +
1320 SP_RGBA32_G_U(nv->pagecolor) +
1321 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1322 // the background color is light or transparent, use black outline
1323 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1324 } else { // use white outline
1325 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1326 }
1327 }
1328 }
1330 /**
1331 * Callback to reset snapper's distances.
1332 */
1333 static void
1334 _update_snap_distances (SPDesktop *desktop)
1335 {
1336 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1338 SPNamedView &nv = *desktop->namedview;
1340 nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1341 *nv.gridtoleranceunit,
1342 px));
1343 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1344 *nv.guidetoleranceunit,
1345 px));
1346 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1347 *nv.objecttoleranceunit,
1348 px));
1349 }
1352 NR::Matrix SPDesktop::w2d() const
1353 {
1354 return _w2d;
1355 }
1357 NR::Point SPDesktop::w2d(NR::Point const &p) const
1358 {
1359 return p * _w2d;
1360 }
1362 NR::Point SPDesktop::d2w(NR::Point const &p) const
1363 {
1364 return p * _d2w;
1365 }
1367 NR::Matrix SPDesktop::doc2dt() const
1368 {
1369 return _doc2dt;
1370 }
1372 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1373 {
1374 return p * _doc2dt;
1375 }
1377 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1378 {
1379 return p / _doc2dt;
1380 }
1383 /**
1384 * Pop event context from desktop's context stack. Never used.
1385 */
1386 // void
1387 // SPDesktop::pop_event_context (unsigned int key)
1388 // {
1389 // SPEventContext *ec = NULL;
1390 //
1391 // if (event_context && event_context->key == key) {
1392 // g_return_if_fail (event_context);
1393 // g_return_if_fail (event_context->next);
1394 // ec = event_context;
1395 // sp_event_context_deactivate (ec);
1396 // event_context = ec->next;
1397 // sp_event_context_activate (event_context);
1398 // _event_context_changed_signal.emit (this, ec);
1399 // }
1400 //
1401 // SPEventContext *ref = event_context;
1402 // while (ref && ref->next && ref->next->key != key)
1403 // ref = ref->next;
1404 //
1405 // if (ref && ref->next) {
1406 // ec = ref->next;
1407 // ref->next = ec->next;
1408 // }
1409 //
1410 // if (ec) {
1411 // sp_event_context_finish (ec);
1412 // g_object_unref (G_OBJECT (ec));
1413 // }
1414 // }
1416 /*
1417 Local Variables:
1418 mode:c++
1419 c-file-style:"stroustrup"
1420 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1421 indent-tabs-mode:nil
1422 fill-column:99
1423 End:
1424 */
1425 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :