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