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