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