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 _commit_connection = document->connectCommit(sigc::bind(sigc::ptr_fun(&sp_canvas_update_now), this->canvas));
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();
298 while (event_context) {
299 SPEventContext *ec = event_context;
300 event_context = ec->next;
301 sp_event_context_finish (ec);
302 g_object_unref (G_OBJECT (ec));
303 }
305 if (_layer_hierarchy) {
306 delete _layer_hierarchy;
307 }
309 if (_inkscape) {
310 _inkscape = NULL;
311 }
313 if (drawing) {
314 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
315 drawing = NULL;
316 }
318 delete _guides_message_context;
319 _guides_message_context = NULL;
321 sp_signal_disconnect_by_data (G_OBJECT (namedview), this);
323 g_list_free (zooms_past);
324 g_list_free (zooms_future);
325 }
327 SPDesktop::~SPDesktop() {}
329 //--------------------------------------------------------------------
330 /* Public methods */
332 void SPDesktop::setDisplayModeNormal()
333 {
334 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
335 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
336 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
337 }
339 void SPDesktop::setDisplayModeOutline()
340 {
341 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
342 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
343 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
344 }
346 /**
347 * Returns current root (=bottom) layer.
348 */
349 SPObject *SPDesktop::currentRoot() const
350 {
351 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
352 }
354 /**
355 * Returns current top layer.
356 */
357 SPObject *SPDesktop::currentLayer() const
358 {
359 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
360 }
362 /**
363 * Sets the current layer of the desktop.
364 *
365 * Make \a object the top layer.
366 */
367 void SPDesktop::setCurrentLayer(SPObject *object) {
368 g_return_if_fail(SP_IS_GROUP(object));
369 g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
370 // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
371 _layer_hierarchy->setBottom(object);
372 }
374 /**
375 * Return layer that contains \a object.
376 */
377 SPObject *SPDesktop::layerForObject(SPObject *object) {
378 g_return_val_if_fail(object != NULL, NULL);
380 SPObject *root=currentRoot();
381 object = SP_OBJECT_PARENT(object);
382 while ( object && object != root && !isLayer(object) ) {
383 object = SP_OBJECT_PARENT(object);
384 }
385 return object;
386 }
388 /**
389 * True if object is a layer.
390 */
391 bool SPDesktop::isLayer(SPObject *object) const {
392 return ( SP_IS_GROUP(object)
393 && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
394 == SPGroup::LAYER ) );
395 }
397 /**
398 * True if desktop viewport fully contains \a item's bbox.
399 */
400 bool SPDesktop::isWithinViewport (SPItem *item) const
401 {
402 NR::Rect const viewport = get_display_area();
403 NR::Rect const bbox = sp_item_bbox_desktop(item);
404 return viewport.contains(bbox);
405 }
407 ///
408 bool SPDesktop::itemIsHidden(SPItem const *item) const {
409 return item->isHidden(this->dkey);
410 }
412 /**
413 * Set activate property of desktop; emit signal if changed.
414 */
415 void
416 SPDesktop::set_active (bool new_active)
417 {
418 if (new_active != _active) {
419 _active = new_active;
420 if (new_active) {
421 _activate_signal.emit();
422 } else {
423 _deactivate_signal.emit();
424 }
425 }
426 }
428 /**
429 * Set activate status of current desktop's named view.
430 */
431 void
432 SPDesktop::activate_guides(bool activate)
433 {
434 guides_active = activate;
435 namedview->activateGuides(this, activate);
436 }
438 /**
439 * Make desktop switch documents.
440 */
441 void
442 SPDesktop::change_document (SPDocument *theDocument)
443 {
444 g_return_if_fail (theDocument != NULL);
446 /* unselect everything before switching documents */
447 selection->clear();
449 setDocument (theDocument);
450 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
451 _document_replaced_signal.emit (this, theDocument);
452 }
454 /**
455 * Make desktop switch event contexts.
456 */
457 void
458 SPDesktop::set_event_context (GtkType type, const gchar *config)
459 {
460 SPEventContext *ec;
461 while (event_context) {
462 ec = event_context;
463 sp_event_context_deactivate (ec);
464 event_context = ec->next;
465 sp_event_context_finish (ec);
466 g_object_unref (G_OBJECT (ec));
467 }
469 Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
470 ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
471 ec->next = event_context;
472 event_context = ec;
473 sp_event_context_activate (ec);
474 _event_context_changed_signal.emit (this, ec);
475 }
477 /**
478 * Push event context onto desktop's context stack.
479 */
480 void
481 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
482 {
483 SPEventContext *ref, *ec;
484 Inkscape::XML::Node *repr;
486 if (event_context && event_context->key == key) return;
487 ref = event_context;
488 while (ref && ref->next && ref->next->key != key) ref = ref->next;
489 if (ref && ref->next) {
490 ec = ref->next;
491 ref->next = ec->next;
492 sp_event_context_finish (ec);
493 g_object_unref (G_OBJECT (ec));
494 }
496 if (event_context) sp_event_context_deactivate (event_context);
497 repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
498 ec = sp_event_context_new (type, this, repr, key);
499 ec->next = event_context;
500 event_context = ec;
501 sp_event_context_activate (ec);
502 _event_context_changed_signal.emit (this, ec);
503 }
505 /**
506 * Sets the coordinate status to a given point
507 */
508 void
509 SPDesktop::set_coordinate_status (NR::Point p) {
510 _widget->setCoordinateStatus(p);
511 }
513 /**
514 * \see sp_document_item_from_list_at_point_bottom()
515 */
516 SPItem *
517 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
518 {
519 g_return_val_if_fail (doc() != NULL, NULL);
520 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
521 }
523 /**
524 * \see sp_document_item_at_point()
525 */
526 SPItem *
527 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
528 {
529 g_return_val_if_fail (doc() != NULL, NULL);
530 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
531 }
533 /**
534 * \see sp_document_group_at_point()
535 */
536 SPItem *
537 SPDesktop::group_at_point (NR::Point const p) const
538 {
539 g_return_val_if_fail (doc() != NULL, NULL);
540 return sp_document_group_at_point (doc(), dkey, p);
541 }
543 /**
544 * \brief Returns the mouse point in document coordinates; if mouse is
545 * outside the canvas, returns the center of canvas viewpoint
546 */
547 NR::Point
548 SPDesktop::point() const
549 {
550 NR::Point p = _widget->getPointer();
551 NR::Point pw = sp_canvas_window_to_world (canvas, p);
552 p = w2d(pw);
554 NR::Rect const r = canvas->getViewbox();
556 NR::Point r0 = w2d(r.min());
557 NR::Point r1 = w2d(r.max());
559 if (p[NR::X] >= r0[NR::X] &&
560 p[NR::X] <= r1[NR::X] &&
561 p[NR::Y] >= r1[NR::Y] &&
562 p[NR::Y] <= r0[NR::Y])
563 {
564 return p;
565 } else {
566 return (r0 + r1) / 2;
567 }
568 }
570 /**
571 * Put current zoom data in history list.
572 */
573 void
574 SPDesktop::push_current_zoom (GList **history)
575 {
576 NR::Rect const area = get_display_area();
578 NRRect *old_zoom = g_new(NRRect, 1);
579 old_zoom->x0 = area.min()[NR::X];
580 old_zoom->x1 = area.max()[NR::X];
581 old_zoom->y0 = area.min()[NR::Y];
582 old_zoom->y1 = area.max()[NR::Y];
583 if ( *history == NULL
584 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
585 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
586 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
587 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
588 {
589 *history = g_list_prepend (*history, old_zoom);
590 }
591 }
593 /**
594 * Set viewbox.
595 */
596 void
597 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
598 {
599 g_assert(_widget);
601 // save the zoom
602 if (log) {
603 push_current_zoom(&zooms_past);
604 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
605 g_list_free (zooms_future);
606 zooms_future = NULL;
607 }
609 double const cx = 0.5 * (x0 + x1);
610 double const cy = 0.5 * (y0 + y1);
612 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
614 double scale = expansion(_d2w);
615 double newscale;
616 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
617 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
618 } else {
619 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
620 }
622 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
624 int clear = FALSE;
625 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
626 /* Set zoom factors */
627 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
628 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
629 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
630 clear = TRUE;
631 }
633 /* Calculate top left corner */
634 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
635 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
637 /* Scroll */
638 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
640 _widget->updateRulers();
641 _widget->updateScrollbars(expansion(_d2w));
642 _widget->updateZoom();
643 }
645 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
646 {
647 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
648 }
650 /**
651 * Return viewbox dimensions.
652 */
653 NR::Rect SPDesktop::get_display_area() const
654 {
655 NR::Rect const viewbox = canvas->getViewbox();
657 double const scale = _d2w[0];
659 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
660 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
661 }
663 /**
664 * Revert back to previous zoom if possible.
665 */
666 void
667 SPDesktop::prev_zoom()
668 {
669 if (zooms_past == NULL) {
670 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
671 return;
672 }
674 // push current zoom into forward zooms list
675 push_current_zoom (&zooms_future);
677 // restore previous zoom
678 set_display_area (((NRRect *) zooms_past->data)->x0,
679 ((NRRect *) zooms_past->data)->y0,
680 ((NRRect *) zooms_past->data)->x1,
681 ((NRRect *) zooms_past->data)->y1,
682 0, false);
684 // remove the just-added zoom from the past zooms list
685 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
686 }
688 /**
689 * Set zoom to next in list.
690 */
691 void
692 SPDesktop::next_zoom()
693 {
694 if (zooms_future == NULL) {
695 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
696 return;
697 }
699 // push current zoom into past zooms list
700 push_current_zoom (&zooms_past);
702 // restore next zoom
703 set_display_area (((NRRect *) zooms_future->data)->x0,
704 ((NRRect *) zooms_future->data)->y0,
705 ((NRRect *) zooms_future->data)->x1,
706 ((NRRect *) zooms_future->data)->y1,
707 0, false);
709 // remove the just-used zoom from the zooms_future list
710 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
711 }
713 /**
714 * Zoom to point with absolute zoom factor.
715 */
716 void
717 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
718 {
719 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
721 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
722 // this check prevents "sliding" when trying to zoom in at maximum zoom;
723 /// \todo someone please fix calculations properly and remove this hack
724 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
725 return;
727 NR::Rect const viewbox = canvas->getViewbox();
729 double const width2 = viewbox.dimensions()[NR::X] / zoom;
730 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
732 set_display_area(cx - px * width2,
733 cy - py * height2,
734 cx + (1 - px) * width2,
735 cy + (1 - py) * height2,
736 0.0);
737 }
739 /**
740 * Zoom to center with absolute zoom factor.
741 */
742 void
743 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
744 {
745 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
746 }
748 /**
749 * Zoom to point with relative zoom factor.
750 */
751 void
752 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
753 {
754 NR::Rect const area = get_display_area();
756 if (cx < area.min()[NR::X]) {
757 cx = area.min()[NR::X];
758 }
759 if (cx > area.max()[NR::X]) {
760 cx = area.max()[NR::X];
761 }
762 if (cy < area.min()[NR::Y]) {
763 cy = area.min()[NR::Y];
764 }
765 if (cy > area.max()[NR::Y]) {
766 cy = area.max()[NR::Y];
767 }
769 gdouble const scale = expansion(_d2w) * zoom;
770 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
771 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
773 zoom_absolute_keep_point(cx, cy, px, py, scale);
774 }
776 /**
777 * Zoom to center with relative zoom factor.
778 */
779 void
780 SPDesktop::zoom_relative (double cx, double cy, double zoom)
781 {
782 gdouble scale = expansion(_d2w) * zoom;
783 zoom_absolute (cx, cy, scale);
784 }
786 /**
787 * Set display area to origin and current document dimensions.
788 */
789 void
790 SPDesktop::zoom_page()
791 {
792 NR::Rect d(NR::Point(0, 0),
793 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
795 if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
796 return;
797 }
799 set_display_area(d, 10);
800 }
802 /**
803 * Set display area to current document width.
804 */
805 void
806 SPDesktop::zoom_page_width()
807 {
808 NR::Rect const a = get_display_area();
810 if (sp_document_width(doc()) < 1.0) {
811 return;
812 }
814 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
815 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
817 set_display_area(d, 10);
818 }
820 /**
821 * Zoom to selection.
822 */
823 void
824 SPDesktop::zoom_selection()
825 {
826 NR::Rect const d = selection->bounds();
828 if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
829 return;
830 }
832 set_display_area(d, 10);
833 }
835 /**
836 * Tell widget to let zoom widget grab keyboard focus.
837 */
838 void
839 SPDesktop::zoom_grab_focus()
840 {
841 _widget->letZoomGrabFocus();
842 }
844 /**
845 * Zoom to whole drawing.
846 */
847 void
848 SPDesktop::zoom_drawing()
849 {
850 g_return_if_fail (doc() != NULL);
851 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
852 g_return_if_fail (docitem != NULL);
854 NR::Rect d = sp_item_bbox_desktop(docitem);
856 /* Note that the second condition here indicates that
857 ** there are no items in the drawing.
858 */
859 if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
860 return;
861 }
863 set_display_area(d, 10);
864 }
866 /**
867 * Scroll canvas by specific coordinate amount.
868 */
869 void
870 SPDesktop::scroll_world (double dx, double dy)
871 {
872 g_assert(_widget);
874 NR::Rect const viewbox = canvas->getViewbox();
876 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
878 _widget->updateRulers();
879 _widget->updateScrollbars(expansion(_d2w));
880 }
882 bool
883 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
884 {
885 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
887 // autoscrolldistance is in screen pixels, but the display area is in document units
888 autoscrolldistance /= expansion(_d2w);
889 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
891 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
892 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
894 NR::Point const s_w( (*p) * _d2w );
896 gdouble x_to;
897 if ((*p)[NR::X] < dbox.min()[NR::X])
898 x_to = dbox.min()[NR::X];
899 else if ((*p)[NR::X] > dbox.max()[NR::X])
900 x_to = dbox.max()[NR::X];
901 else
902 x_to = (*p)[NR::X];
904 gdouble y_to;
905 if ((*p)[NR::Y] < dbox.min()[NR::Y])
906 y_to = dbox.min()[NR::Y];
907 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
908 y_to = dbox.max()[NR::Y];
909 else
910 y_to = (*p)[NR::Y];
912 NR::Point const d_dt(x_to, y_to);
913 NR::Point const d_w( d_dt * _d2w );
914 NR::Point const moved_w( d_w - s_w );
916 if (autoscrollspeed == 0)
917 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
919 if (autoscrollspeed != 0)
920 scroll_world (autoscrollspeed * moved_w);
922 return true;
923 }
924 return false;
925 }
927 void
928 SPDesktop::fullscreen()
929 {
930 _widget->setFullscreen();
931 }
933 void
934 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
935 {
936 _widget->getGeometry (x, y, w, h);
937 }
939 void
940 SPDesktop::setWindowPosition (NR::Point p)
941 {
942 _widget->setPosition (p);
943 }
945 void
946 SPDesktop::setWindowSize (gint w, gint h)
947 {
948 _widget->setSize (w, h);
949 }
951 void
952 SPDesktop::setWindowTransient (void *p, int transient_policy)
953 {
954 _widget->setTransient (p, transient_policy);
955 }
957 void
958 SPDesktop::presentWindow()
959 {
960 _widget->present();
961 }
963 bool
964 SPDesktop::warnDialog (gchar *text)
965 {
966 return _widget->warnDialog (text);
967 }
969 void
970 SPDesktop::toggleRulers()
971 {
972 _widget->toggleRulers();
973 }
975 void
976 SPDesktop::toggleScrollbars()
977 {
978 _widget->toggleScrollbars();
979 }
981 void
982 SPDesktop::layoutWidget()
983 {
984 _widget->layout();
985 }
987 void
988 SPDesktop::destroyWidget()
989 {
990 _widget->destroy();
991 }
993 bool
994 SPDesktop::shutdown()
995 {
996 return _widget->shutdown();
997 }
999 void
1000 SPDesktop::setToolboxFocusTo (gchar const *label)
1001 {
1002 _widget->setToolboxFocusTo (label);
1003 }
1005 void
1006 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1007 {
1008 _widget->setToolboxAdjustmentValue (id, val);
1009 }
1011 bool
1012 SPDesktop::isToolboxButtonActive (gchar const *id)
1013 {
1014 return _widget->isToolboxButtonActive (id);
1015 }
1017 void
1018 SPDesktop::emitToolSubselectionChanged(gpointer data)
1019 {
1020 _tool_subselection_changed.emit(data);
1021 inkscape_subselection_changed (this);
1022 }
1024 //----------------------------------------------------------------------
1025 // Callback implementations. The virtual ones are connected by the view.
1027 void
1028 SPDesktop::onPositionSet (double x, double y)
1029 {
1030 _widget->viewSetPosition (NR::Point(x,y));
1031 }
1033 void
1034 SPDesktop::onResized (double x, double y)
1035 {
1036 // Nothing called here
1037 }
1039 /**
1040 * Redraw callback; queues Gtk redraw; connected by View.
1041 */
1042 void
1043 SPDesktop::onRedrawRequested ()
1044 {
1045 if (main) {
1046 _widget->requestCanvasUpdate();
1047 }
1048 }
1050 /**
1051 * Associate document with desktop.
1052 */
1053 /// \todo fixme: refactor SPDesktop::init to use setDocument
1054 void
1055 SPDesktop::setDocument (SPDocument *doc)
1056 {
1057 if (this->doc() && doc) {
1058 namedview->hide(this);
1059 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1060 }
1062 if (_layer_hierarchy) {
1063 _layer_hierarchy->clear();
1064 delete _layer_hierarchy;
1065 }
1066 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1067 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1068 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1069 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1070 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1072 _commit_connection.disconnect();
1073 _commit_connection = doc->connectCommit(sigc::bind(sigc::ptr_fun(&sp_canvas_update_now), this->canvas));
1075 /// \todo fixme: This condition exists to make sure the code
1076 /// inside is called only once on initialization. But there
1077 /// are surely more safe methods to accomplish this.
1078 if (drawing) {
1079 NRArenaItem *ai;
1081 namedview = sp_document_namedview (doc, NULL);
1082 g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1083 number = namedview->getViewCount();
1085 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1086 SP_CANVAS_ARENA (drawing)->arena,
1087 dkey,
1088 SP_ITEM_SHOW_DISPLAY);
1089 if (ai) {
1090 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1091 nr_arena_item_unref (ai);
1092 }
1093 namedview->show(this);
1094 /* Ugly hack */
1095 activate_guides (true);
1096 /* Ugly hack */
1097 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1098 }
1100 _document_replaced_signal.emit (this, doc);
1102 View::setDocument (doc);
1103 }
1105 void
1106 SPDesktop::onStatusMessage
1107 (Inkscape::MessageType type, gchar const *message)
1108 {
1109 if (_widget) {
1110 _widget->setMessage(type, message);
1111 }
1112 }
1114 void
1115 SPDesktop::onDocumentURISet (gchar const* uri)
1116 {
1117 _widget->setTitle(uri);
1118 }
1120 /**
1121 * Resized callback.
1122 */
1123 void
1124 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1125 {
1126 _doc2dt[5] = height;
1127 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1128 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1129 SP_CTRLRECT(page)->setRectangle(a);
1130 SP_CTRLRECT(page_border)->setRectangle(a);
1131 }
1134 void
1135 SPDesktop::_onActivate (SPDesktop* dt)
1136 {
1137 if (!dt->_widget) return;
1138 dt->_widget->activateDesktop();
1139 }
1141 void
1142 SPDesktop::_onDeactivate (SPDesktop* dt)
1143 {
1144 if (!dt->_widget) return;
1145 dt->_widget->deactivateDesktop();
1146 }
1148 void
1149 SPDesktop::_onSelectionModified
1150 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1151 {
1152 if (!dt->_widget) return;
1153 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1154 }
1156 static void
1157 _onSelectionChanged
1158 (Inkscape::Selection *selection, SPDesktop *desktop)
1159 {
1160 /** \todo
1161 * only change the layer for single selections, or what?
1162 * This seems reasonable -- for multiple selections there can be many
1163 * different layers involved.
1164 */
1165 SPItem *item=selection->singleItem();
1166 if (item) {
1167 SPObject *layer=desktop->layerForObject(item);
1168 if ( layer && layer != desktop->currentLayer() ) {
1169 desktop->setCurrentLayer(layer);
1170 }
1171 }
1172 }
1174 /**
1175 * Calls event handler of current event context.
1176 * \param arena Unused
1177 * \todo fixme
1178 */
1179 static gint
1180 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1181 {
1182 if (ai) {
1183 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1184 return sp_event_context_item_handler (desktop->event_context, spi, event);
1185 } else {
1186 return sp_event_context_root_handler (desktop->event_context, event);
1187 }
1188 }
1190 static void
1191 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1192 g_return_if_fail(SP_IS_GROUP(layer));
1193 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1194 }
1196 /// Callback
1197 static void
1198 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1199 g_return_if_fail(SP_IS_GROUP(layer));
1200 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1201 }
1203 /// Callback
1204 static void
1205 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1206 SPDesktop *desktop)
1207 {
1208 desktop->_layer_changed_signal.emit (bottom);
1209 }
1211 /// Called when document is starting to be rebuilt.
1212 static void
1213 _reconstruction_start (SPDesktop * desktop)
1214 {
1215 // printf("Desktop, starting reconstruction\n");
1216 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1217 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1219 /*
1220 GSList const * selection_objs = desktop->selection->list();
1221 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1223 }
1224 */
1225 desktop->selection->clear();
1227 // printf("Desktop, starting reconstruction end\n");
1228 }
1230 /// Called when document rebuild is finished.
1231 static void
1232 _reconstruction_finish (SPDesktop * desktop)
1233 {
1234 // printf("Desktop, finishing reconstruction\n");
1235 if (desktop->_reconstruction_old_layer_id == NULL)
1236 return;
1238 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1239 if (newLayer != NULL)
1240 desktop->setCurrentLayer(newLayer);
1242 g_free(desktop->_reconstruction_old_layer_id);
1243 desktop->_reconstruction_old_layer_id = NULL;
1244 // printf("Desktop, finishing reconstruction end\n");
1245 return;
1246 }
1248 /**
1249 * Namedview_modified callback.
1250 */
1251 static void
1252 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1253 {
1254 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1256 /* Recalculate snap distances */
1257 /* FIXME: why is the desktop getting involved in setting up something
1258 ** that is entirely to do with the namedview?
1259 */
1260 _update_snap_distances (desktop);
1262 /* Show/hide page background */
1263 if (nv->pagecolor & 0xff) {
1264 sp_canvas_item_show (desktop->table);
1265 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1266 sp_canvas_item_move_to_z (desktop->table, 0);
1267 } else {
1268 sp_canvas_item_hide (desktop->table);
1269 }
1271 /* Show/hide page border */
1272 if (nv->showborder) {
1273 // show
1274 sp_canvas_item_show (desktop->page_border);
1275 // set color and shadow
1276 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1277 if (nv->pageshadow) {
1278 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1279 }
1280 // place in the z-order stack
1281 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1282 sp_canvas_item_move_to_z (desktop->page_border, 2);
1283 } else {
1284 int order = sp_canvas_item_order (desktop->page_border);
1285 int morder = sp_canvas_item_order (desktop->drawing);
1286 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1287 morder - order);
1288 }
1289 } else {
1290 sp_canvas_item_hide (desktop->page_border);
1291 if (nv->pageshadow) {
1292 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1293 }
1294 }
1296 /* Show/hide page shadow */
1297 if (nv->showpageshadow && nv->pageshadow) {
1298 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1299 } else {
1300 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1301 }
1303 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1304 (SP_RGBA32_R_U(nv->pagecolor) +
1305 SP_RGBA32_G_U(nv->pagecolor) +
1306 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1307 // the background color is light or transparent, use black outline
1308 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1309 } else { // use white outline
1310 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1311 }
1312 }
1313 }
1315 /**
1316 * Callback to reset snapper's distances.
1317 */
1318 static void
1319 _update_snap_distances (SPDesktop *desktop)
1320 {
1321 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1323 SPNamedView &nv = *desktop->namedview;
1325 nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1326 *nv.gridtoleranceunit,
1327 px));
1328 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1329 *nv.guidetoleranceunit,
1330 px));
1331 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1332 *nv.objecttoleranceunit,
1333 px));
1334 }
1337 NR::Matrix SPDesktop::w2d() const
1338 {
1339 return _w2d;
1340 }
1342 NR::Point SPDesktop::w2d(NR::Point const &p) const
1343 {
1344 return p * _w2d;
1345 }
1347 NR::Point SPDesktop::d2w(NR::Point const &p) const
1348 {
1349 return p * _d2w;
1350 }
1352 NR::Matrix SPDesktop::doc2dt() const
1353 {
1354 return _doc2dt;
1355 }
1357 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1358 {
1359 return p * _doc2dt;
1360 }
1362 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1363 {
1364 return p / _doc2dt;
1365 }
1368 /**
1369 * Pop event context from desktop's context stack. Never used.
1370 */
1371 // void
1372 // SPDesktop::pop_event_context (unsigned int key)
1373 // {
1374 // SPEventContext *ec = NULL;
1375 //
1376 // if (event_context && event_context->key == key) {
1377 // g_return_if_fail (event_context);
1378 // g_return_if_fail (event_context->next);
1379 // ec = event_context;
1380 // sp_event_context_deactivate (ec);
1381 // event_context = ec->next;
1382 // sp_event_context_activate (event_context);
1383 // _event_context_changed_signal.emit (this, ec);
1384 // }
1385 //
1386 // SPEventContext *ref = event_context;
1387 // while (ref && ref->next && ref->next->key != key)
1388 // ref = ref->next;
1389 //
1390 // if (ref && ref->next) {
1391 // ec = ref->next;
1392 // ref->next = ec->next;
1393 // }
1394 //
1395 // if (ec) {
1396 // sp_event_context_finish (ec);
1397 // g_object_unref (G_OBJECT (ec));
1398 // }
1399 // }
1401 /*
1402 Local Variables:
1403 mode:c++
1404 c-file-style:"stroustrup"
1405 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1406 indent-tabs-mode:nil
1407 fill-column:99
1408 End:
1409 */
1410 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :