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 * John Bintz <jcoswell@coswellproductions.org>
12 *
13 * Copyright (C) 2006 John Bintz
14 * Copyright (C) 2004 MenTaLguY
15 * Copyright (C) 1999-2002 Lauris Kaplinski
16 * Copyright (C) 2000-2001 Ximian, Inc.
17 *
18 * Released under GNU GPL, read the file 'COPYING' for more information
19 */
21 /** \class SPDesktop
22 * SPDesktop is a subclass of View, implementing an editable document
23 * canvas. It is extensively used by many UI controls that need certain
24 * visual representations of their own.
25 *
26 * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
27 * layers of different control objects. The one containing the whole
28 * document is the drawing layer. In addition to it, there are grid,
29 * guide, sketch and control layers. The sketch layer is used for
30 * temporary drawing objects, before the real objects in document are
31 * created. The control layer contains editing knots, rubberband and
32 * similar non-document UI objects.
33 *
34 * Each SPDesktop is associated with a SPNamedView node of the document
35 * tree. Currently, all desktops are created from a single main named
36 * view, but in the future there may be support for different ones.
37 * SPNamedView serves as an in-document container for desktop-related
38 * data, like grid and guideline placement, snapping options and so on.
39 *
40 * Associated with each SPDesktop are the two most important editing
41 * related objects - SPSelection and SPEventContext.
42 *
43 * Sodipodi keeps track of the active desktop and invokes notification
44 * signals whenever it changes. UI elements can use these to update their
45 * display to the selection of the currently active editing window.
46 * (Lauris Kaplinski)
47 */
49 #ifdef HAVE_CONFIG_H
50 # include "config.h"
51 #endif
53 #include <glibmm/i18n.h>
54 #include <sigc++/functors/mem_fun.h>
56 #include "macros.h"
57 #include "inkscape-private.h"
58 #include "desktop.h"
59 #include "desktop-events.h"
60 #include "desktop-handles.h"
61 #include "document.h"
62 #include "message-stack.h"
63 #include "selection.h"
64 #include "select-context.h"
65 #include "sp-namedview.h"
66 #include "color.h"
67 #include "sp-item-group.h"
68 #include "prefs-utils.h"
69 #include "object-hierarchy.h"
70 #include "helper/units.h"
71 #include "display/canvas-arena.h"
72 #include "display/nr-arena.h"
73 #include "display/gnome-canvas-acetate.h"
74 #include "display/sodipodi-ctrlrect.h"
75 #include "display/sp-canvas-util.h"
76 #include "libnr/nr-matrix-div.h"
77 #include "libnr/nr-rect-ops.h"
78 #include "ui/dialog/dialog-manager.h"
79 #include "xml/repr.h"
80 #include "message-context.h"
81 #include "layer-manager.h"
82 #include "event-log.h"
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 (SPObject *obj, 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 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
232 NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
233 SP_CANVAS_ARENA (drawing)->arena,
234 dkey,
235 SP_ITEM_SHOW_DISPLAY);
236 if (ai) {
237 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
238 nr_arena_item_unref (ai);
239 }
241 namedview->show(this);
242 /* Ugly hack */
243 activate_guides (true);
244 /* Ugly hack */
245 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
247 /* Set up notification of rebuilding the document, this allows
248 for saving object related settings in the document. */
249 _reconstruction_start_connection =
250 document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
251 _reconstruction_finish_connection =
252 document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
253 _reconstruction_old_layer_id = NULL;
255 _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
257 // ?
258 // sp_active_desktop_set (desktop);
259 _inkscape = INKSCAPE;
261 _activate_connection = _activate_signal.connect(
262 sigc::bind(
263 sigc::ptr_fun(_onActivate),
264 this
265 )
266 );
267 _deactivate_connection = _deactivate_signal.connect(
268 sigc::bind(
269 sigc::ptr_fun(_onDeactivate),
270 this
271 )
272 );
274 _sel_modified_connection = selection->connectModified(
275 sigc::bind(
276 sigc::ptr_fun(&_onSelectionModified),
277 this
278 )
279 );
280 _sel_changed_connection = selection->connectChanged(
281 sigc::bind(
282 sigc::ptr_fun(&_onSelectionChanged),
283 this
284 )
285 );
288 /* setup LayerManager */
289 // (Setting up after the connections are all in place, as it may use some of them)
290 layer_manager = new Inkscape::LayerManager( this );
292 /* setup EventLog */
293 event_log = new Inkscape::EventLog(document);
294 document->addUndoObserver(*event_log);
295 }
298 void SPDesktop::destroy()
299 {
300 _activate_connection.disconnect();
301 _deactivate_connection.disconnect();
302 _sel_modified_connection.disconnect();
303 _sel_changed_connection.disconnect();
304 _modified_connection.disconnect();
305 _commit_connection.disconnect();
306 _reconstruction_start_connection.disconnect();
307 _reconstruction_finish_connection.disconnect();
309 g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
310 g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
311 g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
313 while (event_context) {
314 SPEventContext *ec = event_context;
315 event_context = ec->next;
316 sp_event_context_finish (ec);
317 g_object_unref (G_OBJECT (ec));
318 }
320 if (_layer_hierarchy) {
321 delete _layer_hierarchy;
322 }
324 if (_inkscape) {
325 _inkscape = NULL;
326 }
328 if (drawing) {
329 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
330 drawing = NULL;
331 }
333 delete _guides_message_context;
334 _guides_message_context = NULL;
336 g_list_free (zooms_past);
337 g_list_free (zooms_future);
338 }
340 SPDesktop::~SPDesktop() {}
342 //--------------------------------------------------------------------
343 /* Public methods */
345 void SPDesktop::setDisplayModeNormal()
346 {
347 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
348 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
349 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
350 }
352 void SPDesktop::setDisplayModeOutline()
353 {
354 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
355 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
356 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
357 }
359 /**
360 * Returns current root (=bottom) layer.
361 */
362 SPObject *SPDesktop::currentRoot() const
363 {
364 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
365 }
367 /**
368 * Returns current top layer.
369 */
370 SPObject *SPDesktop::currentLayer() const
371 {
372 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
373 }
375 /**
376 * Sets the current layer of the desktop.
377 *
378 * Make \a object the top layer.
379 */
380 void SPDesktop::setCurrentLayer(SPObject *object) {
381 g_return_if_fail(SP_IS_GROUP(object));
382 g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
383 // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
384 _layer_hierarchy->setBottom(object);
385 }
387 /**
388 * Return layer that contains \a object.
389 */
390 SPObject *SPDesktop::layerForObject(SPObject *object) {
391 g_return_val_if_fail(object != NULL, NULL);
393 SPObject *root=currentRoot();
394 object = SP_OBJECT_PARENT(object);
395 while ( object && object != root && !isLayer(object) ) {
396 object = SP_OBJECT_PARENT(object);
397 }
398 return object;
399 }
401 /**
402 * True if object is a layer.
403 */
404 bool SPDesktop::isLayer(SPObject *object) const {
405 return ( SP_IS_GROUP(object)
406 && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
407 == SPGroup::LAYER ) );
408 }
410 /**
411 * True if desktop viewport fully contains \a item's bbox.
412 */
413 bool SPDesktop::isWithinViewport (SPItem *item) const
414 {
415 NR::Rect const viewport = get_display_area();
416 NR::Rect const bbox = sp_item_bbox_desktop(item);
417 return viewport.contains(bbox);
418 }
420 ///
421 bool SPDesktop::itemIsHidden(SPItem const *item) const {
422 return item->isHidden(this->dkey);
423 }
425 /**
426 * Set activate property of desktop; emit signal if changed.
427 */
428 void
429 SPDesktop::set_active (bool new_active)
430 {
431 if (new_active != _active) {
432 _active = new_active;
433 if (new_active) {
434 _activate_signal.emit();
435 } else {
436 _deactivate_signal.emit();
437 }
438 }
439 }
441 /**
442 * Set activate status of current desktop's named view.
443 */
444 void
445 SPDesktop::activate_guides(bool activate)
446 {
447 guides_active = activate;
448 namedview->activateGuides(this, activate);
449 }
451 /**
452 * Make desktop switch documents.
453 */
454 void
455 SPDesktop::change_document (SPDocument *theDocument)
456 {
457 g_return_if_fail (theDocument != NULL);
459 /* unselect everything before switching documents */
460 selection->clear();
462 setDocument (theDocument);
463 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
464 _document_replaced_signal.emit (this, theDocument);
465 }
467 /**
468 * Make desktop switch event contexts.
469 */
470 void
471 SPDesktop::set_event_context (GtkType type, const gchar *config)
472 {
473 SPEventContext *ec;
474 while (event_context) {
475 ec = event_context;
476 sp_event_context_deactivate (ec);
477 event_context = ec->next;
478 sp_event_context_finish (ec);
479 g_object_unref (G_OBJECT (ec));
480 }
482 Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
483 ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
484 ec->next = event_context;
485 event_context = ec;
486 sp_event_context_activate (ec);
487 _event_context_changed_signal.emit (this, ec);
488 }
490 /**
491 * Push event context onto desktop's context stack.
492 */
493 void
494 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
495 {
496 SPEventContext *ref, *ec;
497 Inkscape::XML::Node *repr;
499 if (event_context && event_context->key == key) return;
500 ref = event_context;
501 while (ref && ref->next && ref->next->key != key) ref = ref->next;
502 if (ref && ref->next) {
503 ec = ref->next;
504 ref->next = ec->next;
505 sp_event_context_finish (ec);
506 g_object_unref (G_OBJECT (ec));
507 }
509 if (event_context) sp_event_context_deactivate (event_context);
510 repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
511 ec = sp_event_context_new (type, this, repr, key);
512 ec->next = event_context;
513 event_context = ec;
514 sp_event_context_activate (ec);
515 _event_context_changed_signal.emit (this, ec);
516 }
518 /**
519 * Sets the coordinate status to a given point
520 */
521 void
522 SPDesktop::set_coordinate_status (NR::Point p) {
523 _widget->setCoordinateStatus(p);
524 }
526 /**
527 * \see sp_document_item_from_list_at_point_bottom()
528 */
529 SPItem *
530 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
531 {
532 g_return_val_if_fail (doc() != NULL, NULL);
533 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
534 }
536 /**
537 * \see sp_document_item_at_point()
538 */
539 SPItem *
540 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
541 {
542 g_return_val_if_fail (doc() != NULL, NULL);
543 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
544 }
546 /**
547 * \see sp_document_group_at_point()
548 */
549 SPItem *
550 SPDesktop::group_at_point (NR::Point const p) const
551 {
552 g_return_val_if_fail (doc() != NULL, NULL);
553 return sp_document_group_at_point (doc(), dkey, p);
554 }
556 /**
557 * \brief Returns the mouse point in document coordinates; if mouse is
558 * outside the canvas, returns the center of canvas viewpoint
559 */
560 NR::Point
561 SPDesktop::point() const
562 {
563 NR::Point p = _widget->getPointer();
564 NR::Point pw = sp_canvas_window_to_world (canvas, p);
565 p = w2d(pw);
567 NR::Rect const r = canvas->getViewbox();
569 NR::Point r0 = w2d(r.min());
570 NR::Point r1 = w2d(r.max());
572 if (p[NR::X] >= r0[NR::X] &&
573 p[NR::X] <= r1[NR::X] &&
574 p[NR::Y] >= r1[NR::Y] &&
575 p[NR::Y] <= r0[NR::Y])
576 {
577 return p;
578 } else {
579 return (r0 + r1) / 2;
580 }
581 }
583 /**
584 * Put current zoom data in history list.
585 */
586 void
587 SPDesktop::push_current_zoom (GList **history)
588 {
589 NR::Rect const area = get_display_area();
591 NRRect *old_zoom = g_new(NRRect, 1);
592 old_zoom->x0 = area.min()[NR::X];
593 old_zoom->x1 = area.max()[NR::X];
594 old_zoom->y0 = area.min()[NR::Y];
595 old_zoom->y1 = area.max()[NR::Y];
596 if ( *history == NULL
597 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
598 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
599 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
600 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
601 {
602 *history = g_list_prepend (*history, old_zoom);
603 }
604 }
606 /**
607 * Set viewbox.
608 */
609 void
610 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
611 {
612 g_assert(_widget);
614 // save the zoom
615 if (log) {
616 push_current_zoom(&zooms_past);
617 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
618 g_list_free (zooms_future);
619 zooms_future = NULL;
620 }
622 double const cx = 0.5 * (x0 + x1);
623 double const cy = 0.5 * (y0 + y1);
625 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
627 double scale = expansion(_d2w);
628 double newscale;
629 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
630 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
631 } else {
632 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
633 }
635 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
637 int clear = FALSE;
638 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
639 /* Set zoom factors */
640 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
641 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
642 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
643 clear = TRUE;
644 }
646 /* Calculate top left corner */
647 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
648 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
650 /* Scroll */
651 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
653 _widget->updateRulers();
654 _widget->updateScrollbars(expansion(_d2w));
655 _widget->updateZoom();
656 }
658 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
659 {
660 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
661 }
663 /**
664 * Return viewbox dimensions.
665 */
666 NR::Rect SPDesktop::get_display_area() const
667 {
668 NR::Rect const viewbox = canvas->getViewbox();
670 double const scale = _d2w[0];
672 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
673 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
674 }
676 /**
677 * Revert back to previous zoom if possible.
678 */
679 void
680 SPDesktop::prev_zoom()
681 {
682 if (zooms_past == NULL) {
683 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
684 return;
685 }
687 // push current zoom into forward zooms list
688 push_current_zoom (&zooms_future);
690 // restore previous zoom
691 set_display_area (((NRRect *) zooms_past->data)->x0,
692 ((NRRect *) zooms_past->data)->y0,
693 ((NRRect *) zooms_past->data)->x1,
694 ((NRRect *) zooms_past->data)->y1,
695 0, false);
697 // remove the just-added zoom from the past zooms list
698 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
699 }
701 /**
702 * Set zoom to next in list.
703 */
704 void
705 SPDesktop::next_zoom()
706 {
707 if (zooms_future == NULL) {
708 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
709 return;
710 }
712 // push current zoom into past zooms list
713 push_current_zoom (&zooms_past);
715 // restore next zoom
716 set_display_area (((NRRect *) zooms_future->data)->x0,
717 ((NRRect *) zooms_future->data)->y0,
718 ((NRRect *) zooms_future->data)->x1,
719 ((NRRect *) zooms_future->data)->y1,
720 0, false);
722 // remove the just-used zoom from the zooms_future list
723 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
724 }
726 /**
727 * Zoom to point with absolute zoom factor.
728 */
729 void
730 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
731 {
732 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
734 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
735 // this check prevents "sliding" when trying to zoom in at maximum zoom;
736 /// \todo someone please fix calculations properly and remove this hack
737 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
738 return;
740 NR::Rect const viewbox = canvas->getViewbox();
742 double const width2 = viewbox.dimensions()[NR::X] / zoom;
743 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
745 set_display_area(cx - px * width2,
746 cy - py * height2,
747 cx + (1 - px) * width2,
748 cy + (1 - py) * height2,
749 0.0);
750 }
752 /**
753 * Zoom to center with absolute zoom factor.
754 */
755 void
756 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
757 {
758 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
759 }
761 /**
762 * Zoom to point with relative zoom factor.
763 */
764 void
765 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
766 {
767 NR::Rect const area = get_display_area();
769 if (cx < area.min()[NR::X]) {
770 cx = area.min()[NR::X];
771 }
772 if (cx > area.max()[NR::X]) {
773 cx = area.max()[NR::X];
774 }
775 if (cy < area.min()[NR::Y]) {
776 cy = area.min()[NR::Y];
777 }
778 if (cy > area.max()[NR::Y]) {
779 cy = area.max()[NR::Y];
780 }
782 gdouble const scale = expansion(_d2w) * zoom;
783 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
784 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
786 zoom_absolute_keep_point(cx, cy, px, py, scale);
787 }
789 /**
790 * Zoom to center with relative zoom factor.
791 */
792 void
793 SPDesktop::zoom_relative (double cx, double cy, double zoom)
794 {
795 gdouble scale = expansion(_d2w) * zoom;
796 zoom_absolute (cx, cy, scale);
797 }
799 /**
800 * Set display area to origin and current document dimensions.
801 */
802 void
803 SPDesktop::zoom_page()
804 {
805 NR::Rect d(NR::Point(0, 0),
806 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
808 if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
809 return;
810 }
812 set_display_area(d, 10);
813 }
815 /**
816 * Set display area to current document width.
817 */
818 void
819 SPDesktop::zoom_page_width()
820 {
821 NR::Rect const a = get_display_area();
823 if (sp_document_width(doc()) < 1.0) {
824 return;
825 }
827 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
828 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
830 set_display_area(d, 10);
831 }
833 /**
834 * Zoom to selection.
835 */
836 void
837 SPDesktop::zoom_selection()
838 {
839 NR::Rect const d = selection->bounds();
841 if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
842 return;
843 }
845 set_display_area(d, 10);
846 }
848 /**
849 * Tell widget to let zoom widget grab keyboard focus.
850 */
851 void
852 SPDesktop::zoom_grab_focus()
853 {
854 _widget->letZoomGrabFocus();
855 }
857 /**
858 * Zoom to whole drawing.
859 */
860 void
861 SPDesktop::zoom_drawing()
862 {
863 g_return_if_fail (doc() != NULL);
864 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
865 g_return_if_fail (docitem != NULL);
867 NR::Rect d = sp_item_bbox_desktop(docitem);
869 /* Note that the second condition here indicates that
870 ** there are no items in the drawing.
871 */
872 if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
873 return;
874 }
876 set_display_area(d, 10);
877 }
879 /**
880 * Scroll canvas by specific coordinate amount.
881 */
882 void
883 SPDesktop::scroll_world (double dx, double dy)
884 {
885 g_assert(_widget);
887 NR::Rect const viewbox = canvas->getViewbox();
889 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
891 _widget->updateRulers();
892 _widget->updateScrollbars(expansion(_d2w));
893 }
895 bool
896 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
897 {
898 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
900 // autoscrolldistance is in screen pixels, but the display area is in document units
901 autoscrolldistance /= expansion(_d2w);
902 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
904 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
905 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
907 NR::Point const s_w( (*p) * _d2w );
909 gdouble x_to;
910 if ((*p)[NR::X] < dbox.min()[NR::X])
911 x_to = dbox.min()[NR::X];
912 else if ((*p)[NR::X] > dbox.max()[NR::X])
913 x_to = dbox.max()[NR::X];
914 else
915 x_to = (*p)[NR::X];
917 gdouble y_to;
918 if ((*p)[NR::Y] < dbox.min()[NR::Y])
919 y_to = dbox.min()[NR::Y];
920 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
921 y_to = dbox.max()[NR::Y];
922 else
923 y_to = (*p)[NR::Y];
925 NR::Point const d_dt(x_to, y_to);
926 NR::Point const d_w( d_dt * _d2w );
927 NR::Point const moved_w( d_w - s_w );
929 if (autoscrollspeed == 0)
930 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
932 if (autoscrollspeed != 0)
933 scroll_world (autoscrollspeed * moved_w);
935 return true;
936 }
937 return false;
938 }
940 void
941 SPDesktop::fullscreen()
942 {
943 _widget->setFullscreen();
944 }
946 void
947 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
948 {
949 _widget->getGeometry (x, y, w, h);
950 }
952 void
953 SPDesktop::setWindowPosition (NR::Point p)
954 {
955 _widget->setPosition (p);
956 }
958 void
959 SPDesktop::setWindowSize (gint w, gint h)
960 {
961 _widget->setSize (w, h);
962 }
964 void
965 SPDesktop::setWindowTransient (void *p, int transient_policy)
966 {
967 _widget->setTransient (p, transient_policy);
968 }
970 void
971 SPDesktop::presentWindow()
972 {
973 _widget->present();
974 }
976 bool
977 SPDesktop::warnDialog (gchar *text)
978 {
979 return _widget->warnDialog (text);
980 }
982 void
983 SPDesktop::toggleRulers()
984 {
985 _widget->toggleRulers();
986 }
988 void
989 SPDesktop::toggleScrollbars()
990 {
991 _widget->toggleScrollbars();
992 }
994 void
995 SPDesktop::layoutWidget()
996 {
997 _widget->layout();
998 }
1000 void
1001 SPDesktop::destroyWidget()
1002 {
1003 _widget->destroy();
1004 }
1006 bool
1007 SPDesktop::shutdown()
1008 {
1009 return _widget->shutdown();
1010 }
1012 void
1013 SPDesktop::setToolboxFocusTo (gchar const *label)
1014 {
1015 _widget->setToolboxFocusTo (label);
1016 }
1018 void
1019 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1020 {
1021 _widget->setToolboxAdjustmentValue (id, val);
1022 }
1024 bool
1025 SPDesktop::isToolboxButtonActive (gchar const *id)
1026 {
1027 return _widget->isToolboxButtonActive (id);
1028 }
1030 void
1031 SPDesktop::emitToolSubselectionChanged(gpointer data)
1032 {
1033 _tool_subselection_changed.emit(data);
1034 inkscape_subselection_changed (this);
1035 }
1037 void
1038 SPDesktop::updateNow()
1039 {
1040 sp_canvas_update_now(canvas);
1041 }
1043 void
1044 SPDesktop::enableInteraction()
1045 {
1046 _widget->enableInteraction();
1047 }
1049 void SPDesktop::disableInteraction()
1050 {
1051 _widget->disableInteraction();
1052 }
1054 //----------------------------------------------------------------------
1055 // Callback implementations. The virtual ones are connected by the view.
1057 void
1058 SPDesktop::onPositionSet (double x, double y)
1059 {
1060 _widget->viewSetPosition (NR::Point(x,y));
1061 }
1063 void
1064 SPDesktop::onResized (double x, double y)
1065 {
1066 // Nothing called here
1067 }
1069 /**
1070 * Redraw callback; queues Gtk redraw; connected by View.
1071 */
1072 void
1073 SPDesktop::onRedrawRequested ()
1074 {
1075 if (main) {
1076 _widget->requestCanvasUpdate();
1077 }
1078 }
1080 void
1081 SPDesktop::updateCanvasNow()
1082 {
1083 _widget->requestCanvasUpdateAndWait();
1084 }
1086 /**
1087 * Associate document with desktop.
1088 */
1089 /// \todo fixme: refactor SPDesktop::init to use setDocument
1090 void
1091 SPDesktop::setDocument (SPDocument *doc)
1092 {
1093 if (this->doc() && doc) {
1094 namedview->hide(this);
1095 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1096 }
1098 if (_layer_hierarchy) {
1099 _layer_hierarchy->clear();
1100 delete _layer_hierarchy;
1101 }
1102 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1103 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1104 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1105 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1106 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1108 _commit_connection.disconnect();
1109 _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1111 /// \todo fixme: This condition exists to make sure the code
1112 /// inside is called only once on initialization. But there
1113 /// are surely more safe methods to accomplish this.
1114 if (drawing) {
1115 NRArenaItem *ai;
1117 namedview = sp_document_namedview (doc, NULL);
1118 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1119 number = namedview->getViewCount();
1121 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1122 SP_CANVAS_ARENA (drawing)->arena,
1123 dkey,
1124 SP_ITEM_SHOW_DISPLAY);
1125 if (ai) {
1126 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1127 nr_arena_item_unref (ai);
1128 }
1129 namedview->show(this);
1130 /* Ugly hack */
1131 activate_guides (true);
1132 /* Ugly hack */
1133 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1134 }
1136 _document_replaced_signal.emit (this, doc);
1138 View::setDocument (doc);
1139 }
1141 void
1142 SPDesktop::onStatusMessage
1143 (Inkscape::MessageType type, gchar const *message)
1144 {
1145 if (_widget) {
1146 _widget->setMessage(type, message);
1147 }
1148 }
1150 void
1151 SPDesktop::onDocumentURISet (gchar const* uri)
1152 {
1153 _widget->setTitle(uri);
1154 }
1156 /**
1157 * Resized callback.
1158 */
1159 void
1160 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1161 {
1162 _doc2dt[5] = height;
1163 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1164 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1165 SP_CTRLRECT(page)->setRectangle(a);
1166 SP_CTRLRECT(page_border)->setRectangle(a);
1167 }
1170 void
1171 SPDesktop::_onActivate (SPDesktop* dt)
1172 {
1173 if (!dt->_widget) return;
1174 dt->_widget->activateDesktop();
1175 }
1177 void
1178 SPDesktop::_onDeactivate (SPDesktop* dt)
1179 {
1180 if (!dt->_widget) return;
1181 dt->_widget->deactivateDesktop();
1182 }
1184 void
1185 SPDesktop::_onSelectionModified
1186 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1187 {
1188 if (!dt->_widget) return;
1189 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1190 }
1192 static void
1193 _onSelectionChanged
1194 (Inkscape::Selection *selection, SPDesktop *desktop)
1195 {
1196 /** \todo
1197 * only change the layer for single selections, or what?
1198 * This seems reasonable -- for multiple selections there can be many
1199 * different layers involved.
1200 */
1201 SPItem *item=selection->singleItem();
1202 if (item) {
1203 SPObject *layer=desktop->layerForObject(item);
1204 if ( layer && layer != desktop->currentLayer() ) {
1205 desktop->setCurrentLayer(layer);
1206 }
1207 }
1208 }
1210 /**
1211 * Calls event handler of current event context.
1212 * \param arena Unused
1213 * \todo fixme
1214 */
1215 static gint
1216 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1217 {
1218 if (ai) {
1219 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1220 return sp_event_context_item_handler (desktop->event_context, spi, event);
1221 } else {
1222 return sp_event_context_root_handler (desktop->event_context, event);
1223 }
1224 }
1226 static void
1227 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1228 g_return_if_fail(SP_IS_GROUP(layer));
1229 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1230 }
1232 /// Callback
1233 static void
1234 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1235 g_return_if_fail(SP_IS_GROUP(layer));
1236 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1237 }
1239 /// Callback
1240 static void
1241 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1242 SPDesktop *desktop)
1243 {
1244 desktop->_layer_changed_signal.emit (bottom);
1245 }
1247 /// Called when document is starting to be rebuilt.
1248 static void
1249 _reconstruction_start (SPDesktop * desktop)
1250 {
1251 // printf("Desktop, starting reconstruction\n");
1252 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1253 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1255 /*
1256 GSList const * selection_objs = desktop->selection->list();
1257 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1259 }
1260 */
1261 desktop->selection->clear();
1263 // printf("Desktop, starting reconstruction end\n");
1264 }
1266 /// Called when document rebuild is finished.
1267 static void
1268 _reconstruction_finish (SPDesktop * desktop)
1269 {
1270 // printf("Desktop, finishing reconstruction\n");
1271 if (desktop->_reconstruction_old_layer_id == NULL)
1272 return;
1274 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1275 if (newLayer != NULL)
1276 desktop->setCurrentLayer(newLayer);
1278 g_free(desktop->_reconstruction_old_layer_id);
1279 desktop->_reconstruction_old_layer_id = NULL;
1280 // printf("Desktop, finishing reconstruction end\n");
1281 return;
1282 }
1284 /**
1285 * Namedview_modified callback.
1286 */
1287 static void
1288 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1289 {
1290 SPNamedView *nv=SP_NAMEDVIEW(obj);
1292 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1294 /* Recalculate snap distances */
1295 /* FIXME: why is the desktop getting involved in setting up something
1296 ** that is entirely to do with the namedview?
1297 */
1298 _update_snap_distances (desktop);
1300 /* Show/hide page background */
1301 if (nv->pagecolor & 0xff) {
1302 sp_canvas_item_show (desktop->table);
1303 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1304 sp_canvas_item_move_to_z (desktop->table, 0);
1305 } else {
1306 sp_canvas_item_hide (desktop->table);
1307 }
1309 /* Show/hide page border */
1310 if (nv->showborder) {
1311 // show
1312 sp_canvas_item_show (desktop->page_border);
1313 // set color and shadow
1314 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1315 if (nv->pageshadow) {
1316 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1317 }
1318 // place in the z-order stack
1319 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1320 sp_canvas_item_move_to_z (desktop->page_border, 2);
1321 } else {
1322 int order = sp_canvas_item_order (desktop->page_border);
1323 int morder = sp_canvas_item_order (desktop->drawing);
1324 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1325 morder - order);
1326 }
1327 } else {
1328 sp_canvas_item_hide (desktop->page_border);
1329 if (nv->pageshadow) {
1330 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1331 }
1332 }
1334 /* Show/hide page shadow */
1335 if (nv->showpageshadow && nv->pageshadow) {
1336 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1337 } else {
1338 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1339 }
1341 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1342 (SP_RGBA32_R_U(nv->pagecolor) +
1343 SP_RGBA32_G_U(nv->pagecolor) +
1344 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1345 // the background color is light or transparent, use black outline
1346 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1347 } else { // use white outline
1348 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1349 }
1350 }
1351 }
1353 /**
1354 * Callback to reset snapper's distances.
1355 */
1356 static void
1357 _update_snap_distances (SPDesktop *desktop)
1358 {
1359 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1361 SPNamedView &nv = *desktop->namedview;
1363 nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1364 *nv.gridtoleranceunit,
1365 px));
1366 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1367 *nv.guidetoleranceunit,
1368 px));
1369 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1370 *nv.objecttoleranceunit,
1371 px));
1372 }
1375 NR::Matrix SPDesktop::w2d() const
1376 {
1377 return _w2d;
1378 }
1380 NR::Point SPDesktop::w2d(NR::Point const &p) const
1381 {
1382 return p * _w2d;
1383 }
1385 NR::Point SPDesktop::d2w(NR::Point const &p) const
1386 {
1387 return p * _d2w;
1388 }
1390 NR::Matrix SPDesktop::doc2dt() const
1391 {
1392 return _doc2dt;
1393 }
1395 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1396 {
1397 return p * _doc2dt;
1398 }
1400 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1401 {
1402 return p / _doc2dt;
1403 }
1406 /**
1407 * Pop event context from desktop's context stack. Never used.
1408 */
1409 // void
1410 // SPDesktop::pop_event_context (unsigned int key)
1411 // {
1412 // SPEventContext *ec = NULL;
1413 //
1414 // if (event_context && event_context->key == key) {
1415 // g_return_if_fail (event_context);
1416 // g_return_if_fail (event_context->next);
1417 // ec = event_context;
1418 // sp_event_context_deactivate (ec);
1419 // event_context = ec->next;
1420 // sp_event_context_activate (event_context);
1421 // _event_context_changed_signal.emit (this, ec);
1422 // }
1423 //
1424 // SPEventContext *ref = event_context;
1425 // while (ref && ref->next && ref->next->key != key)
1426 // ref = ref->next;
1427 //
1428 // if (ref && ref->next) {
1429 // ec = ref->next;
1430 // ref->next = ec->next;
1431 // }
1432 //
1433 // if (ec) {
1434 // sp_event_context_finish (ec);
1435 // g_object_unref (G_OBJECT (ec));
1436 // }
1437 // }
1439 /*
1440 Local Variables:
1441 mode:c++
1442 c-file-style:"stroustrup"
1443 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1444 indent-tabs-mode:nil
1445 fill-column:99
1446 End:
1447 */
1448 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :