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 #ifdef WITH_INKBOARD
81 #include "jabber_whiteboard/session-manager.h"
82 #endif
84 namespace Inkscape { namespace XML { class Node; }}
86 // Callback declarations
87 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
88 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
89 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
90 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
91 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
92 static void _reconstruction_start(SPDesktop * desktop);
93 static void _reconstruction_finish(SPDesktop * desktop);
94 static void _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop);
95 static void _update_snap_distances (SPDesktop *desktop);
97 /**
98 * Return new desktop object.
99 * \pre namedview != NULL.
100 * \pre canvas != NULL.
101 */
102 SPDesktop::SPDesktop()
103 {
104 _dlg_mgr = NULL;
105 _widget = 0;
106 namedview = NULL;
107 selection = NULL;
108 acetate = NULL;
109 main = NULL;
110 grid = NULL;
111 guides = NULL;
112 drawing = NULL;
113 sketch = NULL;
114 controls = NULL;
115 event_context = 0;
116 layer_manager = 0;
118 _d2w.set_identity();
119 _w2d.set_identity();
120 _doc2dt = NR::Matrix(NR::scale(1, -1));
122 guides_active = false;
124 zooms_past = NULL;
125 zooms_future = NULL;
127 is_fullscreen = false;
129 gr_item = NULL;
130 gr_point_num = 0;
131 gr_fill_or_stroke = true;
133 _layer_hierarchy = NULL;
134 _active = false;
136 selection = Inkscape::GC::release (new Inkscape::Selection (this));
137 }
139 void
140 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
142 {
143 _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
145 current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
147 namedview = nv;
148 canvas = aCanvas;
150 SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
151 /* Kill flicker */
152 sp_document_ensure_up_to_date (document);
154 /* Setup Dialog Manager */
155 _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
157 dkey = sp_item_display_key_new (1);
159 /* Connect document */
160 setDocument (document);
162 number = namedview->getViewCount();
165 /* Setup Canvas */
166 g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
168 SPCanvasGroup *root = sp_canvas_root (canvas);
170 /* Setup adminstrative layers */
171 acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
172 g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
173 main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
174 g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
176 table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
177 SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
178 SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
179 sp_canvas_item_move_to_z (table, 0);
181 page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
182 ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
183 page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
185 drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
186 g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
188 SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
190 // Start always in normal mode
191 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
192 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
194 grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195 guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196 sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
197 controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
199 /* Push select tool to the bottom of stack */
200 /** \todo
201 * FIXME: this is the only call to this. Everything else seems to just
202 * call "set" instead of "push". Can we assume that there is only one
203 * context ever?
204 */
205 push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
207 // display rect and zoom are now handled in sp_desktop_widget_realize()
209 NR::Rect const d(NR::Point(0.0, 0.0),
210 NR::Point(sp_document_width(document), sp_document_height(document)));
212 SP_CTRLRECT(page)->setRectangle(d);
213 SP_CTRLRECT(page_border)->setRectangle(d);
215 /* the following sets the page shadow on the canvas
216 It was originally set to 5, which is really cheesy!
217 It now is an attribute in the document's namedview. If a value of
218 0 is used, then the constructor for a shadow is not initialized.
219 */
221 if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
222 SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
223 }
226 /* Connect event for page resize */
227 _doc2dt[5] = sp_document_height (document);
228 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
230 g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
233 NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
234 SP_CANVAS_ARENA (drawing)->arena,
235 dkey,
236 SP_ITEM_SHOW_DISPLAY);
237 if (ai) {
238 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
239 nr_arena_item_unref (ai);
240 }
242 namedview->show(this);
243 /* Ugly hack */
244 activate_guides (true);
245 /* Ugly hack */
246 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
248 /* Construct SessionManager
249 *
250 * SessionManager construction needs to be done after document connection
251 */
252 #ifdef WITH_INKBOARD
253 _whiteboard_session_manager = new Inkscape::Whiteboard::SessionManager(this);
254 #endif
256 /* Set up notification of rebuilding the document, this allows
257 for saving object related settings in the document. */
258 _reconstruction_start_connection =
259 document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
260 _reconstruction_finish_connection =
261 document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
262 _reconstruction_old_layer_id = NULL;
264 // ?
265 // sp_active_desktop_set (desktop);
266 _inkscape = INKSCAPE;
268 _activate_connection = _activate_signal.connect(
269 sigc::bind(
270 sigc::ptr_fun(_onActivate),
271 this
272 )
273 );
274 _deactivate_connection = _deactivate_signal.connect(
275 sigc::bind(
276 sigc::ptr_fun(_onDeactivate),
277 this
278 )
279 );
281 _sel_modified_connection = selection->connectModified(
282 sigc::bind(
283 sigc::ptr_fun(&_onSelectionModified),
284 this
285 )
286 );
287 _sel_changed_connection = selection->connectChanged(
288 sigc::bind(
289 sigc::ptr_fun(&_onSelectionChanged),
290 this
291 )
292 );
295 /* setup LayerManager */
296 // (Setting up after the connections are all in place, as it may use some of them)
297 layer_manager = new Inkscape::LayerManager( this );
298 }
301 void SPDesktop::destroy()
302 {
303 _activate_connection.disconnect();
304 _deactivate_connection.disconnect();
305 _sel_modified_connection.disconnect();
306 _sel_changed_connection.disconnect();
308 while (event_context) {
309 SPEventContext *ec = event_context;
310 event_context = ec->next;
311 sp_event_context_finish (ec);
312 g_object_unref (G_OBJECT (ec));
313 }
315 if (_layer_hierarchy) {
316 delete _layer_hierarchy;
317 }
319 if (_inkscape) {
320 _inkscape = NULL;
321 }
323 if (drawing) {
324 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
325 drawing = NULL;
326 }
328 #ifdef WITH_INKBOARD
329 if (_whiteboard_session_manager) {
330 delete _whiteboard_session_manager;
331 }
332 #endif
334 delete _guides_message_context;
335 _guides_message_context = NULL;
337 sp_signal_disconnect_by_data (G_OBJECT (namedview), this);
339 g_list_free (zooms_past);
340 g_list_free (zooms_future);
341 }
343 SPDesktop::~SPDesktop() {}
345 //--------------------------------------------------------------------
346 /* Public methods */
348 void SPDesktop::setDisplayModeNormal()
349 {
350 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
351 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
352 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
353 }
355 void SPDesktop::setDisplayModeOutline()
356 {
357 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
358 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
359 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
360 }
362 /**
363 * Returns current root (=bottom) layer.
364 */
365 SPObject *SPDesktop::currentRoot() const
366 {
367 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
368 }
370 /**
371 * Returns current top layer.
372 */
373 SPObject *SPDesktop::currentLayer() const
374 {
375 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
376 }
378 /**
379 * Sets the current layer of the desktop.
380 *
381 * Make \a object the top layer.
382 */
383 void SPDesktop::setCurrentLayer(SPObject *object) {
384 g_return_if_fail(SP_IS_GROUP(object));
385 g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
386 // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
387 _layer_hierarchy->setBottom(object);
388 }
390 /**
391 * Return layer that contains \a object.
392 */
393 SPObject *SPDesktop::layerForObject(SPObject *object) {
394 g_return_val_if_fail(object != NULL, NULL);
396 SPObject *root=currentRoot();
397 object = SP_OBJECT_PARENT(object);
398 while ( object && object != root && !isLayer(object) ) {
399 object = SP_OBJECT_PARENT(object);
400 }
401 return object;
402 }
404 /**
405 * True if object is a layer.
406 */
407 bool SPDesktop::isLayer(SPObject *object) const {
408 return ( SP_IS_GROUP(object)
409 && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
410 == SPGroup::LAYER ) );
411 }
413 /**
414 * True if desktop viewport fully contains \a item's bbox.
415 */
416 bool SPDesktop::isWithinViewport (SPItem *item) const
417 {
418 NR::Rect const viewport = get_display_area();
419 NR::Rect const bbox = sp_item_bbox_desktop(item);
420 return viewport.contains(bbox);
421 }
423 ///
424 bool SPDesktop::itemIsHidden(SPItem const *item) const {
425 return item->isHidden(this->dkey);
426 }
428 /**
429 * Set activate property of desktop; emit signal if changed.
430 */
431 void
432 SPDesktop::set_active (bool new_active)
433 {
434 if (new_active != _active) {
435 _active = new_active;
436 if (new_active) {
437 _activate_signal.emit();
438 } else {
439 _deactivate_signal.emit();
440 }
441 }
442 }
444 /**
445 * Set activate status of current desktop's named view.
446 */
447 void
448 SPDesktop::activate_guides(bool activate)
449 {
450 guides_active = activate;
451 namedview->activateGuides(this, activate);
452 }
454 /**
455 * Make desktop switch documents.
456 */
457 void
458 SPDesktop::change_document (SPDocument *theDocument)
459 {
460 g_return_if_fail (theDocument != NULL);
462 /* unselect everything before switching documents */
463 selection->clear();
465 setDocument (theDocument);
466 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
467 _document_replaced_signal.emit (this, theDocument);
468 }
470 /**
471 * Make desktop switch event contexts.
472 */
473 void
474 SPDesktop::set_event_context (GtkType type, const gchar *config)
475 {
476 SPEventContext *ec;
477 while (event_context) {
478 ec = event_context;
479 sp_event_context_deactivate (ec);
480 event_context = ec->next;
481 sp_event_context_finish (ec);
482 g_object_unref (G_OBJECT (ec));
483 }
485 Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
486 ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
487 ec->next = event_context;
488 event_context = ec;
489 sp_event_context_activate (ec);
490 _event_context_changed_signal.emit (this, ec);
491 }
493 /**
494 * Push event context onto desktop's context stack.
495 */
496 void
497 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
498 {
499 SPEventContext *ref, *ec;
500 Inkscape::XML::Node *repr;
502 if (event_context && event_context->key == key) return;
503 ref = event_context;
504 while (ref && ref->next && ref->next->key != key) ref = ref->next;
505 if (ref && ref->next) {
506 ec = ref->next;
507 ref->next = ec->next;
508 sp_event_context_finish (ec);
509 g_object_unref (G_OBJECT (ec));
510 }
512 if (event_context) sp_event_context_deactivate (event_context);
513 repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
514 ec = sp_event_context_new (type, this, repr, key);
515 ec->next = event_context;
516 event_context = ec;
517 sp_event_context_activate (ec);
518 _event_context_changed_signal.emit (this, ec);
519 }
521 /**
522 * Sets the coordinate status to a given point
523 */
524 void
525 SPDesktop::set_coordinate_status (NR::Point p) {
526 _widget->setCoordinateStatus(p);
527 }
529 /**
530 * \see sp_document_item_from_list_at_point_bottom()
531 */
532 SPItem *
533 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
534 {
535 g_return_val_if_fail (doc() != NULL, NULL);
536 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
537 }
539 /**
540 * \see sp_document_item_at_point()
541 */
542 SPItem *
543 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
544 {
545 g_return_val_if_fail (doc() != NULL, NULL);
546 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
547 }
549 /**
550 * \see sp_document_group_at_point()
551 */
552 SPItem *
553 SPDesktop::group_at_point (NR::Point const p) const
554 {
555 g_return_val_if_fail (doc() != NULL, NULL);
556 return sp_document_group_at_point (doc(), dkey, p);
557 }
559 /**
560 * \brief Returns the mouse point in document coordinates; if mouse is
561 * outside the canvas, returns the center of canvas viewpoint
562 */
563 NR::Point
564 SPDesktop::point() const
565 {
566 NR::Point p = _widget->getPointer();
567 NR::Point pw = sp_canvas_window_to_world (canvas, p);
568 p = w2d(pw);
570 NR::Rect const r = canvas->getViewbox();
572 NR::Point r0 = w2d(r.min());
573 NR::Point r1 = w2d(r.max());
575 if (p[NR::X] >= r0[NR::X] &&
576 p[NR::X] <= r1[NR::X] &&
577 p[NR::Y] >= r1[NR::Y] &&
578 p[NR::Y] <= r0[NR::Y])
579 {
580 return p;
581 } else {
582 return (r0 + r1) / 2;
583 }
584 }
586 /**
587 * Put current zoom data in history list.
588 */
589 void
590 SPDesktop::push_current_zoom (GList **history)
591 {
592 NR::Rect const area = get_display_area();
594 NRRect *old_zoom = g_new(NRRect, 1);
595 old_zoom->x0 = area.min()[NR::X];
596 old_zoom->x1 = area.max()[NR::X];
597 old_zoom->y0 = area.min()[NR::Y];
598 old_zoom->y1 = area.max()[NR::Y];
599 if ( *history == NULL
600 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
601 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
602 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
603 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
604 {
605 *history = g_list_prepend (*history, old_zoom);
606 }
607 }
609 /**
610 * Set viewbox.
611 */
612 void
613 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
614 {
615 g_assert(_widget);
617 // save the zoom
618 if (log) {
619 push_current_zoom(&zooms_past);
620 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
621 g_list_free (zooms_future);
622 zooms_future = NULL;
623 }
625 double const cx = 0.5 * (x0 + x1);
626 double const cy = 0.5 * (y0 + y1);
628 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
630 double scale = expansion(_d2w);
631 double newscale;
632 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
633 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
634 } else {
635 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
636 }
638 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
640 int clear = FALSE;
641 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
642 /* Set zoom factors */
643 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
644 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
645 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
646 clear = TRUE;
647 }
649 /* Calculate top left corner */
650 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
651 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
653 /* Scroll */
654 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
656 _widget->updateRulers();
657 _widget->updateScrollbars(expansion(_d2w));
658 _widget->updateZoom();
659 }
661 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
662 {
663 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
664 }
666 /**
667 * Return viewbox dimensions.
668 */
669 NR::Rect SPDesktop::get_display_area() const
670 {
671 NR::Rect const viewbox = canvas->getViewbox();
673 double const scale = _d2w[0];
675 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
676 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
677 }
679 /**
680 * Revert back to previous zoom if possible.
681 */
682 void
683 SPDesktop::prev_zoom()
684 {
685 if (zooms_past == NULL) {
686 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
687 return;
688 }
690 // push current zoom into forward zooms list
691 push_current_zoom (&zooms_future);
693 // restore previous zoom
694 set_display_area (((NRRect *) zooms_past->data)->x0,
695 ((NRRect *) zooms_past->data)->y0,
696 ((NRRect *) zooms_past->data)->x1,
697 ((NRRect *) zooms_past->data)->y1,
698 0, false);
700 // remove the just-added zoom from the past zooms list
701 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
702 }
704 /**
705 * Set zoom to next in list.
706 */
707 void
708 SPDesktop::next_zoom()
709 {
710 if (zooms_future == NULL) {
711 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
712 return;
713 }
715 // push current zoom into past zooms list
716 push_current_zoom (&zooms_past);
718 // restore next zoom
719 set_display_area (((NRRect *) zooms_future->data)->x0,
720 ((NRRect *) zooms_future->data)->y0,
721 ((NRRect *) zooms_future->data)->x1,
722 ((NRRect *) zooms_future->data)->y1,
723 0, false);
725 // remove the just-used zoom from the zooms_future list
726 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
727 }
729 /**
730 * Zoom to point with absolute zoom factor.
731 */
732 void
733 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
734 {
735 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
737 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
738 // this check prevents "sliding" when trying to zoom in at maximum zoom;
739 /// \todo someone please fix calculations properly and remove this hack
740 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
741 return;
743 NR::Rect const viewbox = canvas->getViewbox();
745 double const width2 = viewbox.dimensions()[NR::X] / zoom;
746 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
748 set_display_area(cx - px * width2,
749 cy - py * height2,
750 cx + (1 - px) * width2,
751 cy + (1 - py) * height2,
752 0.0);
753 }
755 /**
756 * Zoom to center with absolute zoom factor.
757 */
758 void
759 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
760 {
761 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
762 }
764 /**
765 * Zoom to point with relative zoom factor.
766 */
767 void
768 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
769 {
770 NR::Rect const area = get_display_area();
772 if (cx < area.min()[NR::X]) {
773 cx = area.min()[NR::X];
774 }
775 if (cx > area.max()[NR::X]) {
776 cx = area.max()[NR::X];
777 }
778 if (cy < area.min()[NR::Y]) {
779 cy = area.min()[NR::Y];
780 }
781 if (cy > area.max()[NR::Y]) {
782 cy = area.max()[NR::Y];
783 }
785 gdouble const scale = expansion(_d2w) * zoom;
786 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
787 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
789 zoom_absolute_keep_point(cx, cy, px, py, scale);
790 }
792 /**
793 * Zoom to center with relative zoom factor.
794 */
795 void
796 SPDesktop::zoom_relative (double cx, double cy, double zoom)
797 {
798 gdouble scale = expansion(_d2w) * zoom;
799 zoom_absolute (cx, cy, scale);
800 }
802 /**
803 * Set display area to origin and current document dimensions.
804 */
805 void
806 SPDesktop::zoom_page()
807 {
808 NR::Rect d(NR::Point(0, 0),
809 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
811 if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
812 return;
813 }
815 set_display_area(d, 10);
816 }
818 /**
819 * Set display area to current document width.
820 */
821 void
822 SPDesktop::zoom_page_width()
823 {
824 NR::Rect const a = get_display_area();
826 if (sp_document_width(doc()) < 1.0) {
827 return;
828 }
830 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
831 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
833 set_display_area(d, 10);
834 }
836 /**
837 * Zoom to selection.
838 */
839 void
840 SPDesktop::zoom_selection()
841 {
842 NR::Rect const d = selection->bounds();
844 if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
845 return;
846 }
848 set_display_area(d, 10);
849 }
851 /**
852 * Tell widget to let zoom widget grab keyboard focus.
853 */
854 void
855 SPDesktop::zoom_grab_focus()
856 {
857 _widget->letZoomGrabFocus();
858 }
860 /**
861 * Zoom to whole drawing.
862 */
863 void
864 SPDesktop::zoom_drawing()
865 {
866 g_return_if_fail (doc() != NULL);
867 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
868 g_return_if_fail (docitem != NULL);
870 NR::Rect d = sp_item_bbox_desktop(docitem);
872 /* Note that the second condition here indicates that
873 ** there are no items in the drawing.
874 */
875 if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
876 return;
877 }
879 set_display_area(d, 10);
880 }
882 /**
883 * Scroll canvas by specific coordinate amount.
884 */
885 void
886 SPDesktop::scroll_world (double dx, double dy)
887 {
888 g_assert(_widget);
890 NR::Rect const viewbox = canvas->getViewbox();
892 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
894 _widget->updateRulers();
895 _widget->updateScrollbars(expansion(_d2w));
896 }
898 bool
899 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
900 {
901 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
903 // autoscrolldistance is in screen pixels, but the display area is in document units
904 autoscrolldistance /= expansion(_d2w);
905 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
907 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
908 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
910 NR::Point const s_w( (*p) * _d2w );
912 gdouble x_to;
913 if ((*p)[NR::X] < dbox.min()[NR::X])
914 x_to = dbox.min()[NR::X];
915 else if ((*p)[NR::X] > dbox.max()[NR::X])
916 x_to = dbox.max()[NR::X];
917 else
918 x_to = (*p)[NR::X];
920 gdouble y_to;
921 if ((*p)[NR::Y] < dbox.min()[NR::Y])
922 y_to = dbox.min()[NR::Y];
923 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
924 y_to = dbox.max()[NR::Y];
925 else
926 y_to = (*p)[NR::Y];
928 NR::Point const d_dt(x_to, y_to);
929 NR::Point const d_w( d_dt * _d2w );
930 NR::Point const moved_w( d_w - s_w );
932 if (autoscrollspeed == 0)
933 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
935 if (autoscrollspeed != 0)
936 scroll_world (autoscrollspeed * moved_w);
938 return true;
939 }
940 return false;
941 }
943 void
944 SPDesktop::fullscreen()
945 {
946 _widget->setFullscreen();
947 }
949 void
950 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
951 {
952 _widget->getGeometry (x, y, w, h);
953 }
955 void
956 SPDesktop::setWindowPosition (NR::Point p)
957 {
958 _widget->setPosition (p);
959 }
961 void
962 SPDesktop::setWindowSize (gint w, gint h)
963 {
964 _widget->setSize (w, h);
965 }
967 void
968 SPDesktop::setWindowTransient (void *p, int transient_policy)
969 {
970 _widget->setTransient (p, transient_policy);
971 }
973 void
974 SPDesktop::presentWindow()
975 {
976 _widget->present();
977 }
979 bool
980 SPDesktop::warnDialog (gchar *text)
981 {
982 return _widget->warnDialog (text);
983 }
985 void
986 SPDesktop::toggleRulers()
987 {
988 _widget->toggleRulers();
989 }
991 void
992 SPDesktop::toggleScrollbars()
993 {
994 _widget->toggleScrollbars();
995 }
997 void
998 SPDesktop::layoutWidget()
999 {
1000 _widget->layout();
1001 }
1003 void
1004 SPDesktop::destroyWidget()
1005 {
1006 _widget->destroy();
1007 }
1009 bool
1010 SPDesktop::shutdown()
1011 {
1012 return _widget->shutdown();
1013 }
1015 void
1016 SPDesktop::setToolboxFocusTo (gchar const *label)
1017 {
1018 _widget->setToolboxFocusTo (label);
1019 }
1021 void
1022 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1023 {
1024 _widget->setToolboxAdjustmentValue (id, val);
1025 }
1027 bool
1028 SPDesktop::isToolboxButtonActive (gchar const *id)
1029 {
1030 return _widget->isToolboxButtonActive (id);
1031 }
1033 void
1034 SPDesktop::emitToolSubselectionChanged(gpointer data)
1035 {
1036 _tool_subselection_changed.emit(data);
1037 inkscape_subselection_changed (this);
1038 }
1040 //----------------------------------------------------------------------
1041 // Callback implementations. The virtual ones are connected by the view.
1043 void
1044 SPDesktop::onPositionSet (double x, double y)
1045 {
1046 _widget->viewSetPosition (NR::Point(x,y));
1047 }
1049 void
1050 SPDesktop::onResized (double x, double y)
1051 {
1052 // Nothing called here
1053 }
1055 /**
1056 * Redraw callback; queues Gtk redraw; connected by View.
1057 */
1058 void
1059 SPDesktop::onRedrawRequested ()
1060 {
1061 if (main) {
1062 _widget->requestCanvasUpdate();
1063 }
1064 }
1066 /**
1067 * Associate document with desktop.
1068 */
1069 void
1070 SPDesktop::setDocument (SPDocument *doc)
1071 {
1072 if (this->doc() && doc) {
1073 namedview->hide(this);
1074 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1075 }
1077 if (_layer_hierarchy) {
1078 _layer_hierarchy->clear();
1079 delete _layer_hierarchy;
1080 }
1081 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1082 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1083 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1084 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1085 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1087 /// \todo fixme: This condition exists to make sure the code
1088 /// inside is called only once on initialization. But there
1089 /// are surely more safe methods to accomplish this.
1090 if (drawing) {
1091 NRArenaItem *ai;
1093 namedview = sp_document_namedview (doc, NULL);
1094 g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this);
1095 number = namedview->getViewCount();
1097 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1098 SP_CANVAS_ARENA (drawing)->arena,
1099 dkey,
1100 SP_ITEM_SHOW_DISPLAY);
1101 if (ai) {
1102 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1103 nr_arena_item_unref (ai);
1104 }
1105 namedview->show(this);
1106 /* Ugly hack */
1107 activate_guides (true);
1108 /* Ugly hack */
1109 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1110 }
1112 _document_replaced_signal.emit (this, doc);
1114 View::setDocument (doc);
1115 }
1117 void
1118 SPDesktop::onStatusMessage
1119 (Inkscape::MessageType type, gchar const *message)
1120 {
1121 if (_widget) {
1122 _widget->setMessage(type, message);
1123 }
1124 }
1126 void
1127 SPDesktop::onDocumentURISet (gchar const* uri)
1128 {
1129 _widget->setTitle(uri);
1130 }
1132 /**
1133 * Resized callback.
1134 */
1135 void
1136 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1137 {
1138 _doc2dt[5] = height;
1139 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1140 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1141 SP_CTRLRECT(page)->setRectangle(a);
1142 SP_CTRLRECT(page_border)->setRectangle(a);
1143 }
1146 void
1147 SPDesktop::_onActivate (SPDesktop* dt)
1148 {
1149 if (!dt->_widget) return;
1150 dt->_widget->activateDesktop();
1151 }
1153 void
1154 SPDesktop::_onDeactivate (SPDesktop* dt)
1155 {
1156 if (!dt->_widget) return;
1157 dt->_widget->deactivateDesktop();
1158 }
1160 void
1161 SPDesktop::_onSelectionModified
1162 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1163 {
1164 if (!dt->_widget) return;
1165 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1166 }
1168 static void
1169 _onSelectionChanged
1170 (Inkscape::Selection *selection, SPDesktop *desktop)
1171 {
1172 /** \todo
1173 * only change the layer for single selections, or what?
1174 * This seems reasonable -- for multiple selections there can be many
1175 * different layers involved.
1176 */
1177 SPItem *item=selection->singleItem();
1178 if (item) {
1179 SPObject *layer=desktop->layerForObject(item);
1180 if ( layer && layer != desktop->currentLayer() ) {
1181 desktop->setCurrentLayer(layer);
1182 }
1183 }
1184 }
1186 /**
1187 * Calls event handler of current event context.
1188 * \param arena Unused
1189 * \todo fixme
1190 */
1191 static gint
1192 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1193 {
1194 if (ai) {
1195 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1196 return sp_event_context_item_handler (desktop->event_context, spi, event);
1197 } else {
1198 return sp_event_context_root_handler (desktop->event_context, event);
1199 }
1200 }
1202 static void
1203 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1204 g_return_if_fail(SP_IS_GROUP(layer));
1205 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1206 }
1208 /// Callback
1209 static void
1210 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1211 g_return_if_fail(SP_IS_GROUP(layer));
1212 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1213 }
1215 /// Callback
1216 static void
1217 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1218 SPDesktop *desktop)
1219 {
1220 desktop->_layer_changed_signal.emit (bottom);
1221 }
1223 /// Called when document is starting to be rebuilt.
1224 static void
1225 _reconstruction_start (SPDesktop * desktop)
1226 {
1227 // printf("Desktop, starting reconstruction\n");
1228 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1229 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1231 /*
1232 GSList const * selection_objs = desktop->selection->list();
1233 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1235 }
1236 */
1237 desktop->selection->clear();
1239 // printf("Desktop, starting reconstruction end\n");
1240 }
1242 /// Called when document rebuild is finished.
1243 static void
1244 _reconstruction_finish (SPDesktop * desktop)
1245 {
1246 // printf("Desktop, finishing reconstruction\n");
1247 if (desktop->_reconstruction_old_layer_id == NULL)
1248 return;
1250 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1251 if (newLayer != NULL)
1252 desktop->setCurrentLayer(newLayer);
1254 g_free(desktop->_reconstruction_old_layer_id);
1255 desktop->_reconstruction_old_layer_id = NULL;
1256 // printf("Desktop, finishing reconstruction end\n");
1257 return;
1258 }
1260 /**
1261 * Namedview_modified callback.
1262 */
1263 static void
1264 _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop)
1265 {
1266 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1268 /* Recalculate snap distances */
1269 /* FIXME: why is the desktop getting involved in setting up something
1270 ** that is entirely to do with the namedview?
1271 */
1272 _update_snap_distances (desktop);
1274 /* Show/hide page background */
1275 if (nv->pagecolor & 0xff) {
1276 sp_canvas_item_show (desktop->table);
1277 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1278 sp_canvas_item_move_to_z (desktop->table, 0);
1279 } else {
1280 sp_canvas_item_hide (desktop->table);
1281 }
1283 /* Show/hide page border */
1284 if (nv->showborder) {
1285 // show
1286 sp_canvas_item_show (desktop->page_border);
1287 // set color and shadow
1288 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1289 if (nv->pageshadow) {
1290 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1291 }
1292 // place in the z-order stack
1293 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1294 sp_canvas_item_move_to_z (desktop->page_border, 2);
1295 } else {
1296 int order = sp_canvas_item_order (desktop->page_border);
1297 int morder = sp_canvas_item_order (desktop->drawing);
1298 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1299 morder - order);
1300 }
1301 } else {
1302 sp_canvas_item_hide (desktop->page_border);
1303 if (nv->pageshadow) {
1304 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1305 }
1306 }
1308 /* Show/hide page shadow */
1309 if (nv->showpageshadow && nv->pageshadow) {
1310 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1311 } else {
1312 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1313 }
1315 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1316 (SP_RGBA32_R_U(nv->pagecolor) +
1317 SP_RGBA32_G_U(nv->pagecolor) +
1318 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1319 // the background color is light or transparent, use black outline
1320 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1321 } else { // use white outline
1322 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1323 }
1324 }
1325 }
1327 /**
1328 * Callback to reset snapper's distances.
1329 */
1330 static void
1331 _update_snap_distances (SPDesktop *desktop)
1332 {
1333 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1335 SPNamedView &nv = *desktop->namedview;
1337 nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1338 *nv.gridtoleranceunit,
1339 px));
1340 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1341 *nv.guidetoleranceunit,
1342 px));
1343 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1344 *nv.objecttoleranceunit,
1345 px));
1346 }
1349 NR::Matrix SPDesktop::w2d() const
1350 {
1351 return _w2d;
1352 }
1354 NR::Point SPDesktop::w2d(NR::Point const &p) const
1355 {
1356 return p * _w2d;
1357 }
1359 NR::Point SPDesktop::d2w(NR::Point const &p) const
1360 {
1361 return p * _d2w;
1362 }
1364 NR::Matrix SPDesktop::doc2dt() const
1365 {
1366 return _doc2dt;
1367 }
1369 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1370 {
1371 return p * _doc2dt;
1372 }
1374 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1375 {
1376 return p / _doc2dt;
1377 }
1380 /**
1381 * Pop event context from desktop's context stack. Never used.
1382 */
1383 // void
1384 // SPDesktop::pop_event_context (unsigned int key)
1385 // {
1386 // SPEventContext *ec = NULL;
1387 //
1388 // if (event_context && event_context->key == key) {
1389 // g_return_if_fail (event_context);
1390 // g_return_if_fail (event_context->next);
1391 // ec = event_context;
1392 // sp_event_context_deactivate (ec);
1393 // event_context = ec->next;
1394 // sp_event_context_activate (event_context);
1395 // _event_context_changed_signal.emit (this, ec);
1396 // }
1397 //
1398 // SPEventContext *ref = event_context;
1399 // while (ref && ref->next && ref->next->key != key)
1400 // ref = ref->next;
1401 //
1402 // if (ref && ref->next) {
1403 // ec = ref->next;
1404 // ref->next = ec->next;
1405 // }
1406 //
1407 // if (ec) {
1408 // sp_event_context_finish (ec);
1409 // g_object_unref (G_OBJECT (ec));
1410 // }
1411 // }
1413 /*
1414 Local Variables:
1415 mode:c++
1416 c-file-style:"stroustrup"
1417 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1418 indent-tabs-mode:nil
1419 fill-column:99
1420 End:
1421 */
1422 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :