57d89c5bcb4a47550b502d78daa5d4e92a201369
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"
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 (SPObject *obj, 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;
115 layer_manager = 0;
117 _d2w.set_identity();
118 _w2d.set_identity();
119 _doc2dt = NR::Matrix(NR::scale(1, -1));
121 guides_active = false;
123 zooms_past = NULL;
124 zooms_future = NULL;
126 is_fullscreen = false;
128 gr_item = NULL;
129 gr_point_num = 0;
130 gr_fill_or_stroke = true;
132 _layer_hierarchy = NULL;
133 _active = false;
135 selection = Inkscape::GC::release (new Inkscape::Selection (this));
136 }
138 void
139 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
141 {
142 _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
144 current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
146 namedview = nv;
147 canvas = aCanvas;
149 SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
150 /* Kill flicker */
151 sp_document_ensure_up_to_date (document);
153 /* Setup Dialog Manager */
154 _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
156 dkey = sp_item_display_key_new (1);
158 /* Connect document */
159 setDocument (document);
161 number = namedview->getViewCount();
164 /* Setup Canvas */
165 g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
167 SPCanvasGroup *root = sp_canvas_root (canvas);
169 /* Setup adminstrative layers */
170 acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
171 g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
172 main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
173 g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
175 table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
176 SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
177 SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
178 sp_canvas_item_move_to_z (table, 0);
180 page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
181 ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
182 page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
184 drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
185 g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
187 SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
189 // Start always in normal mode
190 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
191 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
193 grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
194 guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
195 sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
196 controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
198 /* Push select tool to the bottom of stack */
199 /** \todo
200 * FIXME: this is the only call to this. Everything else seems to just
201 * call "set" instead of "push". Can we assume that there is only one
202 * context ever?
203 */
204 push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
206 // display rect and zoom are now handled in sp_desktop_widget_realize()
208 NR::Rect const d(NR::Point(0.0, 0.0),
209 NR::Point(sp_document_width(document), sp_document_height(document)));
211 SP_CTRLRECT(page)->setRectangle(d);
212 SP_CTRLRECT(page_border)->setRectangle(d);
214 /* the following sets the page shadow on the canvas
215 It was originally set to 5, which is really cheesy!
216 It now is an attribute in the document's namedview. If a value of
217 0 is used, then the constructor for a shadow is not initialized.
218 */
220 if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
221 SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
222 }
225 /* Connect event for page resize */
226 _doc2dt[5] = sp_document_height (document);
227 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
229 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_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 /* Set up notification of rebuilding the document, this allows
247 for saving object related settings in the document. */
248 _reconstruction_start_connection =
249 document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
250 _reconstruction_finish_connection =
251 document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
252 _reconstruction_old_layer_id = NULL;
254 _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
256 // ?
257 // sp_active_desktop_set (desktop);
258 _inkscape = INKSCAPE;
260 _activate_connection = _activate_signal.connect(
261 sigc::bind(
262 sigc::ptr_fun(_onActivate),
263 this
264 )
265 );
266 _deactivate_connection = _deactivate_signal.connect(
267 sigc::bind(
268 sigc::ptr_fun(_onDeactivate),
269 this
270 )
271 );
273 _sel_modified_connection = selection->connectModified(
274 sigc::bind(
275 sigc::ptr_fun(&_onSelectionModified),
276 this
277 )
278 );
279 _sel_changed_connection = selection->connectChanged(
280 sigc::bind(
281 sigc::ptr_fun(&_onSelectionChanged),
282 this
283 )
284 );
287 /* setup LayerManager */
288 // (Setting up after the connections are all in place, as it may use some of them)
289 layer_manager = new Inkscape::LayerManager( this );
290 }
293 void SPDesktop::destroy()
294 {
295 _activate_connection.disconnect();
296 _deactivate_connection.disconnect();
297 _sel_modified_connection.disconnect();
298 _sel_changed_connection.disconnect();
299 _modified_connection.disconnect();
300 _commit_connection.disconnect();
301 _reconstruction_start_connection.disconnect();
302 _reconstruction_finish_connection.disconnect();
304 g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
305 g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
306 g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
308 while (event_context) {
309 SPEventContext *ec = event_context;
310 event_context = ec->next;
311 sp_event_context_finish (ec);
312 g_object_unref (G_OBJECT (ec));
313 }
315 if (_layer_hierarchy) {
316 delete _layer_hierarchy;
317 }
319 if (_inkscape) {
320 _inkscape = NULL;
321 }
323 if (drawing) {
324 sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
325 drawing = NULL;
326 }
328 delete _guides_message_context;
329 _guides_message_context = NULL;
331 g_list_free (zooms_past);
332 g_list_free (zooms_future);
333 }
335 SPDesktop::~SPDesktop() {}
337 //--------------------------------------------------------------------
338 /* Public methods */
340 void SPDesktop::setDisplayModeNormal()
341 {
342 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
343 canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
344 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
345 }
347 void SPDesktop::setDisplayModeOutline()
348 {
349 SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
350 canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
351 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
352 }
354 /**
355 * Returns current root (=bottom) layer.
356 */
357 SPObject *SPDesktop::currentRoot() const
358 {
359 return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
360 }
362 /**
363 * Returns current top layer.
364 */
365 SPObject *SPDesktop::currentLayer() const
366 {
367 return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
368 }
370 /**
371 * Sets the current layer of the desktop.
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() && 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 /**
514 * Sets the coordinate status to a given point
515 */
516 void
517 SPDesktop::set_coordinate_status (NR::Point p) {
518 _widget->setCoordinateStatus(p);
519 }
521 /**
522 * \see sp_document_item_from_list_at_point_bottom()
523 */
524 SPItem *
525 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
526 {
527 g_return_val_if_fail (doc() != NULL, NULL);
528 return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
529 }
531 /**
532 * \see sp_document_item_at_point()
533 */
534 SPItem *
535 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
536 {
537 g_return_val_if_fail (doc() != NULL, NULL);
538 return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
539 }
541 /**
542 * \see sp_document_group_at_point()
543 */
544 SPItem *
545 SPDesktop::group_at_point (NR::Point const p) const
546 {
547 g_return_val_if_fail (doc() != NULL, NULL);
548 return sp_document_group_at_point (doc(), dkey, p);
549 }
551 /**
552 * \brief Returns the mouse point in document coordinates; if mouse is
553 * outside the canvas, returns the center of canvas viewpoint
554 */
555 NR::Point
556 SPDesktop::point() const
557 {
558 NR::Point p = _widget->getPointer();
559 NR::Point pw = sp_canvas_window_to_world (canvas, p);
560 p = w2d(pw);
562 NR::Rect const r = canvas->getViewbox();
564 NR::Point r0 = w2d(r.min());
565 NR::Point r1 = w2d(r.max());
567 if (p[NR::X] >= r0[NR::X] &&
568 p[NR::X] <= r1[NR::X] &&
569 p[NR::Y] >= r1[NR::Y] &&
570 p[NR::Y] <= r0[NR::Y])
571 {
572 return p;
573 } else {
574 return (r0 + r1) / 2;
575 }
576 }
578 /**
579 * Put current zoom data in history list.
580 */
581 void
582 SPDesktop::push_current_zoom (GList **history)
583 {
584 NR::Rect const area = get_display_area();
586 NRRect *old_zoom = g_new(NRRect, 1);
587 old_zoom->x0 = area.min()[NR::X];
588 old_zoom->x1 = area.max()[NR::X];
589 old_zoom->y0 = area.min()[NR::Y];
590 old_zoom->y1 = area.max()[NR::Y];
591 if ( *history == NULL
592 || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
593 ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
594 ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
595 ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
596 {
597 *history = g_list_prepend (*history, old_zoom);
598 }
599 }
601 /**
602 * Set viewbox.
603 */
604 void
605 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
606 {
607 g_assert(_widget);
609 // save the zoom
610 if (log) {
611 push_current_zoom(&zooms_past);
612 // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
613 g_list_free (zooms_future);
614 zooms_future = NULL;
615 }
617 double const cx = 0.5 * (x0 + x1);
618 double const cy = 0.5 * (y0 + y1);
620 NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
622 double scale = expansion(_d2w);
623 double newscale;
624 if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
625 newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
626 } else {
627 newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
628 }
630 newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
632 int clear = FALSE;
633 if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
634 /* Set zoom factors */
635 _d2w = NR::Matrix(NR::scale(newscale, -newscale));
636 _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
637 sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
638 clear = TRUE;
639 }
641 /* Calculate top left corner */
642 x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
643 y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
645 /* Scroll */
646 sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
648 _widget->updateRulers();
649 _widget->updateScrollbars(expansion(_d2w));
650 _widget->updateZoom();
651 }
653 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
654 {
655 set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
656 }
658 /**
659 * Return viewbox dimensions.
660 */
661 NR::Rect SPDesktop::get_display_area() const
662 {
663 NR::Rect const viewbox = canvas->getViewbox();
665 double const scale = _d2w[0];
667 return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
668 NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
669 }
671 /**
672 * Revert back to previous zoom if possible.
673 */
674 void
675 SPDesktop::prev_zoom()
676 {
677 if (zooms_past == NULL) {
678 messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
679 return;
680 }
682 // push current zoom into forward zooms list
683 push_current_zoom (&zooms_future);
685 // restore previous zoom
686 set_display_area (((NRRect *) zooms_past->data)->x0,
687 ((NRRect *) zooms_past->data)->y0,
688 ((NRRect *) zooms_past->data)->x1,
689 ((NRRect *) zooms_past->data)->y1,
690 0, false);
692 // remove the just-added zoom from the past zooms list
693 zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
694 }
696 /**
697 * Set zoom to next in list.
698 */
699 void
700 SPDesktop::next_zoom()
701 {
702 if (zooms_future == NULL) {
703 this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
704 return;
705 }
707 // push current zoom into past zooms list
708 push_current_zoom (&zooms_past);
710 // restore next zoom
711 set_display_area (((NRRect *) zooms_future->data)->x0,
712 ((NRRect *) zooms_future->data)->y0,
713 ((NRRect *) zooms_future->data)->x1,
714 ((NRRect *) zooms_future->data)->y1,
715 0, false);
717 // remove the just-used zoom from the zooms_future list
718 zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
719 }
721 /**
722 * Zoom to point with absolute zoom factor.
723 */
724 void
725 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
726 {
727 zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
729 // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
730 // this check prevents "sliding" when trying to zoom in at maximum zoom;
731 /// \todo someone please fix calculations properly and remove this hack
732 if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
733 return;
735 NR::Rect const viewbox = canvas->getViewbox();
737 double const width2 = viewbox.dimensions()[NR::X] / zoom;
738 double const height2 = viewbox.dimensions()[NR::Y] / zoom;
740 set_display_area(cx - px * width2,
741 cy - py * height2,
742 cx + (1 - px) * width2,
743 cy + (1 - py) * height2,
744 0.0);
745 }
747 /**
748 * Zoom to center with absolute zoom factor.
749 */
750 void
751 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
752 {
753 zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
754 }
756 /**
757 * Zoom to point with relative zoom factor.
758 */
759 void
760 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
761 {
762 NR::Rect const area = get_display_area();
764 if (cx < area.min()[NR::X]) {
765 cx = area.min()[NR::X];
766 }
767 if (cx > area.max()[NR::X]) {
768 cx = area.max()[NR::X];
769 }
770 if (cy < area.min()[NR::Y]) {
771 cy = area.min()[NR::Y];
772 }
773 if (cy > area.max()[NR::Y]) {
774 cy = area.max()[NR::Y];
775 }
777 gdouble const scale = expansion(_d2w) * zoom;
778 double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
779 double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
781 zoom_absolute_keep_point(cx, cy, px, py, scale);
782 }
784 /**
785 * Zoom to center with relative zoom factor.
786 */
787 void
788 SPDesktop::zoom_relative (double cx, double cy, double zoom)
789 {
790 gdouble scale = expansion(_d2w) * zoom;
791 zoom_absolute (cx, cy, scale);
792 }
794 /**
795 * Set display area to origin and current document dimensions.
796 */
797 void
798 SPDesktop::zoom_page()
799 {
800 NR::Rect d(NR::Point(0, 0),
801 NR::Point(sp_document_width(doc()), sp_document_height(doc())));
803 if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
804 return;
805 }
807 set_display_area(d, 10);
808 }
810 /**
811 * Set display area to current document width.
812 */
813 void
814 SPDesktop::zoom_page_width()
815 {
816 NR::Rect const a = get_display_area();
818 if (sp_document_width(doc()) < 1.0) {
819 return;
820 }
822 NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
823 NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
825 set_display_area(d, 10);
826 }
828 /**
829 * Zoom to selection.
830 */
831 void
832 SPDesktop::zoom_selection()
833 {
834 NR::Rect const d = selection->bounds();
836 if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
837 return;
838 }
840 set_display_area(d, 10);
841 }
843 /**
844 * Tell widget to let zoom widget grab keyboard focus.
845 */
846 void
847 SPDesktop::zoom_grab_focus()
848 {
849 _widget->letZoomGrabFocus();
850 }
852 /**
853 * Zoom to whole drawing.
854 */
855 void
856 SPDesktop::zoom_drawing()
857 {
858 g_return_if_fail (doc() != NULL);
859 SPItem *docitem = SP_ITEM (sp_document_root (doc()));
860 g_return_if_fail (docitem != NULL);
862 NR::Rect d = sp_item_bbox_desktop(docitem);
864 /* Note that the second condition here indicates that
865 ** there are no items in the drawing.
866 */
867 if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
868 return;
869 }
871 set_display_area(d, 10);
872 }
874 /**
875 * Scroll canvas by specific coordinate amount.
876 */
877 void
878 SPDesktop::scroll_world (double dx, double dy)
879 {
880 g_assert(_widget);
882 NR::Rect const viewbox = canvas->getViewbox();
884 sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
886 _widget->updateRulers();
887 _widget->updateScrollbars(expansion(_d2w));
888 }
890 bool
891 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
892 {
893 gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
895 // autoscrolldistance is in screen pixels, but the display area is in document units
896 autoscrolldistance /= expansion(_d2w);
897 NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
899 if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
900 !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) {
902 NR::Point const s_w( (*p) * _d2w );
904 gdouble x_to;
905 if ((*p)[NR::X] < dbox.min()[NR::X])
906 x_to = dbox.min()[NR::X];
907 else if ((*p)[NR::X] > dbox.max()[NR::X])
908 x_to = dbox.max()[NR::X];
909 else
910 x_to = (*p)[NR::X];
912 gdouble y_to;
913 if ((*p)[NR::Y] < dbox.min()[NR::Y])
914 y_to = dbox.min()[NR::Y];
915 else if ((*p)[NR::Y] > dbox.max()[NR::Y])
916 y_to = dbox.max()[NR::Y];
917 else
918 y_to = (*p)[NR::Y];
920 NR::Point const d_dt(x_to, y_to);
921 NR::Point const d_w( d_dt * _d2w );
922 NR::Point const moved_w( d_w - s_w );
924 if (autoscrollspeed == 0)
925 autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
927 if (autoscrollspeed != 0)
928 scroll_world (autoscrollspeed * moved_w);
930 return true;
931 }
932 return false;
933 }
935 void
936 SPDesktop::fullscreen()
937 {
938 _widget->setFullscreen();
939 }
941 void
942 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
943 {
944 _widget->getGeometry (x, y, w, h);
945 }
947 void
948 SPDesktop::setWindowPosition (NR::Point p)
949 {
950 _widget->setPosition (p);
951 }
953 void
954 SPDesktop::setWindowSize (gint w, gint h)
955 {
956 _widget->setSize (w, h);
957 }
959 void
960 SPDesktop::setWindowTransient (void *p, int transient_policy)
961 {
962 _widget->setTransient (p, transient_policy);
963 }
965 void
966 SPDesktop::presentWindow()
967 {
968 _widget->present();
969 }
971 bool
972 SPDesktop::warnDialog (gchar *text)
973 {
974 return _widget->warnDialog (text);
975 }
977 void
978 SPDesktop::toggleRulers()
979 {
980 _widget->toggleRulers();
981 }
983 void
984 SPDesktop::toggleScrollbars()
985 {
986 _widget->toggleScrollbars();
987 }
989 void
990 SPDesktop::layoutWidget()
991 {
992 _widget->layout();
993 }
995 void
996 SPDesktop::destroyWidget()
997 {
998 _widget->destroy();
999 }
1001 bool
1002 SPDesktop::shutdown()
1003 {
1004 return _widget->shutdown();
1005 }
1007 void
1008 SPDesktop::setToolboxFocusTo (gchar const *label)
1009 {
1010 _widget->setToolboxFocusTo (label);
1011 }
1013 void
1014 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1015 {
1016 _widget->setToolboxAdjustmentValue (id, val);
1017 }
1019 bool
1020 SPDesktop::isToolboxButtonActive (gchar const *id)
1021 {
1022 return _widget->isToolboxButtonActive (id);
1023 }
1025 void
1026 SPDesktop::emitToolSubselectionChanged(gpointer data)
1027 {
1028 _tool_subselection_changed.emit(data);
1029 inkscape_subselection_changed (this);
1030 }
1032 void
1033 SPDesktop::updateNow()
1034 {
1035 sp_canvas_update_now(canvas);
1036 }
1038 void
1039 SPDesktop::enableInteraction()
1040 {
1041 _widget->enableInteraction();
1042 }
1044 void SPDesktop::disableInteraction()
1045 {
1046 _widget->disableInteraction();
1047 }
1049 //----------------------------------------------------------------------
1050 // Callback implementations. The virtual ones are connected by the view.
1052 void
1053 SPDesktop::onPositionSet (double x, double y)
1054 {
1055 _widget->viewSetPosition (NR::Point(x,y));
1056 }
1058 void
1059 SPDesktop::onResized (double x, double y)
1060 {
1061 // Nothing called here
1062 }
1064 /**
1065 * Redraw callback; queues Gtk redraw; connected by View.
1066 */
1067 void
1068 SPDesktop::onRedrawRequested ()
1069 {
1070 if (main) {
1071 _widget->requestCanvasUpdate();
1072 }
1073 }
1075 void
1076 SPDesktop::updateCanvasNow()
1077 {
1078 _widget->requestCanvasUpdateAndWait();
1079 }
1081 /**
1082 * Associate document with desktop.
1083 */
1084 /// \todo fixme: refactor SPDesktop::init to use setDocument
1085 void
1086 SPDesktop::setDocument (SPDocument *doc)
1087 {
1088 if (this->doc() && doc) {
1089 namedview->hide(this);
1090 sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1091 }
1093 if (_layer_hierarchy) {
1094 _layer_hierarchy->clear();
1095 delete _layer_hierarchy;
1096 }
1097 _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1098 _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1099 _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1100 _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1101 _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1103 _commit_connection.disconnect();
1104 _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1106 /// \todo fixme: This condition exists to make sure the code
1107 /// inside is called only once on initialization. But there
1108 /// are surely more safe methods to accomplish this.
1109 if (drawing) {
1110 NRArenaItem *ai;
1112 namedview = sp_document_namedview (doc, NULL);
1113 _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1114 number = namedview->getViewCount();
1116 ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1117 SP_CANVAS_ARENA (drawing)->arena,
1118 dkey,
1119 SP_ITEM_SHOW_DISPLAY);
1120 if (ai) {
1121 nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1122 nr_arena_item_unref (ai);
1123 }
1124 namedview->show(this);
1125 /* Ugly hack */
1126 activate_guides (true);
1127 /* Ugly hack */
1128 _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1129 }
1131 _document_replaced_signal.emit (this, doc);
1133 View::setDocument (doc);
1134 }
1136 void
1137 SPDesktop::onStatusMessage
1138 (Inkscape::MessageType type, gchar const *message)
1139 {
1140 if (_widget) {
1141 _widget->setMessage(type, message);
1142 }
1143 }
1145 void
1146 SPDesktop::onDocumentURISet (gchar const* uri)
1147 {
1148 _widget->setTitle(uri);
1149 }
1151 /**
1152 * Resized callback.
1153 */
1154 void
1155 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1156 {
1157 _doc2dt[5] = height;
1158 sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1159 NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1160 SP_CTRLRECT(page)->setRectangle(a);
1161 SP_CTRLRECT(page_border)->setRectangle(a);
1162 }
1165 void
1166 SPDesktop::_onActivate (SPDesktop* dt)
1167 {
1168 if (!dt->_widget) return;
1169 dt->_widget->activateDesktop();
1170 }
1172 void
1173 SPDesktop::_onDeactivate (SPDesktop* dt)
1174 {
1175 if (!dt->_widget) return;
1176 dt->_widget->deactivateDesktop();
1177 }
1179 void
1180 SPDesktop::_onSelectionModified
1181 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1182 {
1183 if (!dt->_widget) return;
1184 dt->_widget->updateScrollbars (expansion(dt->_d2w));
1185 }
1187 static void
1188 _onSelectionChanged
1189 (Inkscape::Selection *selection, SPDesktop *desktop)
1190 {
1191 /** \todo
1192 * only change the layer for single selections, or what?
1193 * This seems reasonable -- for multiple selections there can be many
1194 * different layers involved.
1195 */
1196 SPItem *item=selection->singleItem();
1197 if (item) {
1198 SPObject *layer=desktop->layerForObject(item);
1199 if ( layer && layer != desktop->currentLayer() ) {
1200 desktop->setCurrentLayer(layer);
1201 }
1202 }
1203 }
1205 /**
1206 * Calls event handler of current event context.
1207 * \param arena Unused
1208 * \todo fixme
1209 */
1210 static gint
1211 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1212 {
1213 if (ai) {
1214 SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1215 return sp_event_context_item_handler (desktop->event_context, spi, event);
1216 } else {
1217 return sp_event_context_root_handler (desktop->event_context, event);
1218 }
1219 }
1221 static void
1222 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1223 g_return_if_fail(SP_IS_GROUP(layer));
1224 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1225 }
1227 /// Callback
1228 static void
1229 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1230 g_return_if_fail(SP_IS_GROUP(layer));
1231 SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1232 }
1234 /// Callback
1235 static void
1236 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1237 SPDesktop *desktop)
1238 {
1239 desktop->_layer_changed_signal.emit (bottom);
1240 }
1242 /// Called when document is starting to be rebuilt.
1243 static void
1244 _reconstruction_start (SPDesktop * desktop)
1245 {
1246 // printf("Desktop, starting reconstruction\n");
1247 desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1248 desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1250 /*
1251 GSList const * selection_objs = desktop->selection->list();
1252 for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1254 }
1255 */
1256 desktop->selection->clear();
1258 // printf("Desktop, starting reconstruction end\n");
1259 }
1261 /// Called when document rebuild is finished.
1262 static void
1263 _reconstruction_finish (SPDesktop * desktop)
1264 {
1265 // printf("Desktop, finishing reconstruction\n");
1266 if (desktop->_reconstruction_old_layer_id == NULL)
1267 return;
1269 SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1270 if (newLayer != NULL)
1271 desktop->setCurrentLayer(newLayer);
1273 g_free(desktop->_reconstruction_old_layer_id);
1274 desktop->_reconstruction_old_layer_id = NULL;
1275 // printf("Desktop, finishing reconstruction end\n");
1276 return;
1277 }
1279 /**
1280 * Namedview_modified callback.
1281 */
1282 static void
1283 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1284 {
1285 SPNamedView *nv=SP_NAMEDVIEW(obj);
1287 if (flags & SP_OBJECT_MODIFIED_FLAG) {
1289 /* Recalculate snap distances */
1290 /* FIXME: why is the desktop getting involved in setting up something
1291 ** that is entirely to do with the namedview?
1292 */
1293 _update_snap_distances (desktop);
1295 /* Show/hide page background */
1296 if (nv->pagecolor & 0xff) {
1297 sp_canvas_item_show (desktop->table);
1298 ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1299 sp_canvas_item_move_to_z (desktop->table, 0);
1300 } else {
1301 sp_canvas_item_hide (desktop->table);
1302 }
1304 /* Show/hide page border */
1305 if (nv->showborder) {
1306 // show
1307 sp_canvas_item_show (desktop->page_border);
1308 // set color and shadow
1309 ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1310 if (nv->pageshadow) {
1311 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1312 }
1313 // place in the z-order stack
1314 if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1315 sp_canvas_item_move_to_z (desktop->page_border, 2);
1316 } else {
1317 int order = sp_canvas_item_order (desktop->page_border);
1318 int morder = sp_canvas_item_order (desktop->drawing);
1319 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1320 morder - order);
1321 }
1322 } else {
1323 sp_canvas_item_hide (desktop->page_border);
1324 if (nv->pageshadow) {
1325 ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1326 }
1327 }
1329 /* Show/hide page shadow */
1330 if (nv->showpageshadow && nv->pageshadow) {
1331 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1332 } else {
1333 ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1334 }
1336 if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1337 (SP_RGBA32_R_U(nv->pagecolor) +
1338 SP_RGBA32_G_U(nv->pagecolor) +
1339 SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1340 // the background color is light or transparent, use black outline
1341 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff;
1342 } else { // use white outline
1343 SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff;
1344 }
1345 }
1346 }
1348 /**
1349 * Callback to reset snapper's distances.
1350 */
1351 static void
1352 _update_snap_distances (SPDesktop *desktop)
1353 {
1354 SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1356 SPNamedView &nv = *desktop->namedview;
1358 nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1359 *nv.gridtoleranceunit,
1360 px));
1361 nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1362 *nv.guidetoleranceunit,
1363 px));
1364 nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1365 *nv.objecttoleranceunit,
1366 px));
1367 }
1370 NR::Matrix SPDesktop::w2d() const
1371 {
1372 return _w2d;
1373 }
1375 NR::Point SPDesktop::w2d(NR::Point const &p) const
1376 {
1377 return p * _w2d;
1378 }
1380 NR::Point SPDesktop::d2w(NR::Point const &p) const
1381 {
1382 return p * _d2w;
1383 }
1385 NR::Matrix SPDesktop::doc2dt() const
1386 {
1387 return _doc2dt;
1388 }
1390 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1391 {
1392 return p * _doc2dt;
1393 }
1395 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1396 {
1397 return p / _doc2dt;
1398 }
1401 /**
1402 * Pop event context from desktop's context stack. Never used.
1403 */
1404 // void
1405 // SPDesktop::pop_event_context (unsigned int key)
1406 // {
1407 // SPEventContext *ec = NULL;
1408 //
1409 // if (event_context && event_context->key == key) {
1410 // g_return_if_fail (event_context);
1411 // g_return_if_fail (event_context->next);
1412 // ec = event_context;
1413 // sp_event_context_deactivate (ec);
1414 // event_context = ec->next;
1415 // sp_event_context_activate (event_context);
1416 // _event_context_changed_signal.emit (this, ec);
1417 // }
1418 //
1419 // SPEventContext *ref = event_context;
1420 // while (ref && ref->next && ref->next->key != key)
1421 // ref = ref->next;
1422 //
1423 // if (ref && ref->next) {
1424 // ec = ref->next;
1425 // ref->next = ec->next;
1426 // }
1427 //
1428 // if (ec) {
1429 // sp_event_context_finish (ec);
1430 // g_object_unref (G_OBJECT (ec));
1431 // }
1432 // }
1434 /*
1435 Local Variables:
1436 mode:c++
1437 c-file-style:"stroustrup"
1438 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1439 indent-tabs-mode:nil
1440 fill-column:99
1441 End:
1442 */
1443 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :