Code

Switch selection bounds and center to use NR::Maybe, addressing most of the
[inkscape.git] / src / desktop.cpp
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  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
13  *
14  * Copyright (C) 2006-2007 Johan Engelen
15  * Copyright (C) 2006 John Bintz
16  * Copyright (C) 2004 MenTaLguY
17  * Copyright (C) 1999-2002 Lauris Kaplinski
18  * Copyright (C) 2000-2001 Ximian, Inc.
19  *
20  * Released under GNU GPL, read the file 'COPYING' for more information
21  */
23 /** \class SPDesktop
24  * SPDesktop is a subclass of View, implementing an editable document
25  * canvas.  It is extensively used by many UI controls that need certain
26  * visual representations of their own.
27  *
28  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
29  * layers of different control objects. The one containing the whole
30  * document is the drawing layer. In addition to it, there are grid,
31  * guide, sketch and control layers. The sketch layer is used for
32  * temporary drawing objects, before the real objects in document are
33  * created. The control layer contains editing knots, rubberband and
34  * similar non-document UI objects.
35  *
36  * Each SPDesktop is associated with a SPNamedView node of the document
37  * tree.  Currently, all desktops are created from a single main named
38  * view, but in the future there may be support for different ones.
39  * SPNamedView serves as an in-document container for desktop-related
40  * data, like grid and guideline placement, snapping options and so on.
41  *
42  * Associated with each SPDesktop are the two most important editing
43  * related objects - SPSelection and SPEventContext.
44  *
45  * Sodipodi keeps track of the active desktop and invokes notification
46  * signals whenever it changes. UI elements can use these to update their
47  * display to the selection of the currently active editing window.
48  * (Lauris Kaplinski)
49  */
51 #ifdef HAVE_CONFIG_H
52 # include "config.h"
53 #endif
55 #include <glibmm/i18n.h>
56 #include <sigc++/functors/mem_fun.h>
58 #include "macros.h"
59 #include "inkscape-private.h"
60 #include "desktop.h"
61 #include "desktop-events.h"
62 #include "desktop-handles.h"
63 #include "document.h"
64 #include "message-stack.h"
65 #include "selection.h"
66 #include "select-context.h"
67 #include "sp-namedview.h"
68 #include "color.h"
69 #include "sp-item-group.h"
70 #include "prefs-utils.h"
71 #include "object-hierarchy.h"
72 #include "helper/units.h"
73 #include "display/canvas-arena.h"
74 #include "display/nr-arena.h"
75 #include "display/gnome-canvas-acetate.h"
76 #include "display/sodipodi-ctrlrect.h"
77 #include "display/sp-canvas-util.h"
78 #include "libnr/nr-matrix-div.h"
79 #include "libnr/nr-rect-ops.h"
80 #include "ui/dialog/dialog-manager.h"
81 #include "xml/repr.h"
82 #include "message-context.h"
83 #include "layer-manager.h"
84 #include "event-log.h"
86 namespace Inkscape { namespace XML { class Node; }}
88 // Callback declarations
89 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
90 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
91 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
92 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
93 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
94 static void _reconstruction_start(SPDesktop * desktop);
95 static void _reconstruction_finish(SPDesktop * desktop);
96 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
97 static void _update_snap_distances (SPDesktop *desktop);
99 /**
100  * Return new desktop object.
101  * \pre namedview != NULL.
102  * \pre canvas != NULL.
103  */
104 SPDesktop::SPDesktop()
106     _dlg_mgr = NULL;
107     _widget = 0;
108     namedview = NULL;
109     selection = NULL;
110     acetate = NULL;
111     main = NULL;
112     grid = NULL;
113     guides = NULL;
114     drawing = NULL;
115     sketch = NULL;
116     controls = NULL;
117     event_context = 0;
118     layer_manager = 0;
120     _d2w.set_identity();
121     _w2d.set_identity();
122     _doc2dt = NR::Matrix(NR::scale(1, -1));
124     guides_active = false;
126     zooms_past = NULL;
127     zooms_future = NULL;
129     is_fullscreen = false;
131     gr_item = NULL;
132     gr_point_type = 0;
133     gr_point_i = 0;
134     gr_fill_or_stroke = true;
136     _layer_hierarchy = NULL;
137     _active = false;
139     selection = Inkscape::GC::release (new Inkscape::Selection (this));
142 void
143 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
146     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
148     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
150     namedview = nv;
151     canvas = aCanvas;
153     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
154     /* Kill flicker */
155     sp_document_ensure_up_to_date (document);
157     /* Setup Dialog Manager */
158     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
160     dkey = sp_item_display_key_new (1);
162     /* Connect document */
163     setDocument (document);
165     number = namedview->getViewCount();
168     /* Setup Canvas */
169     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
171     SPCanvasGroup *root = sp_canvas_root (canvas);
173     /* Setup adminstrative layers */
174     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
175     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
176     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
177     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
179     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
180     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
181     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
182     sp_canvas_item_move_to_z (table, 0);
184     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
185     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
186     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
188     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
189     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
191     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
193     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
194         // Start in outline mode
195         setDisplayModeOutline();
196     } else {
197         // Start in normal mode, default
198         setDisplayModeNormal();
199     }
201     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
202     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
203     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
204     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
206     /* Push select tool to the bottom of stack */
207     /** \todo
208      * FIXME: this is the only call to this.  Everything else seems to just
209      * call "set" instead of "push".  Can we assume that there is only one
210      * context ever?
211      */
212     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
214     // display rect and zoom are now handled in sp_desktop_widget_realize()
216     NR::Rect const d(NR::Point(0.0, 0.0),
217                      NR::Point(sp_document_width(document), sp_document_height(document)));
219     SP_CTRLRECT(page)->setRectangle(d);
220     SP_CTRLRECT(page_border)->setRectangle(d);
222     /* the following sets the page shadow on the canvas
223        It was originally set to 5, which is really cheesy!
224        It now is an attribute in the document's namedview. If a value of
225        0 is used, then the constructor for a shadow is not initialized.
226     */
228     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
229         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
230     }
233     /* Connect event for page resize */
234     _doc2dt[5] = sp_document_height (document);
235     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
237     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
239     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
240             SP_CANVAS_ARENA (drawing)->arena,
241             dkey,
242             SP_ITEM_SHOW_DISPLAY);
243     if (ai) {
244         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
245         nr_arena_item_unref (ai);
246     }
248     namedview->show(this);
249     /* Ugly hack */
250     activate_guides (true);
251     /* Ugly hack */
252     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
254 /* Set up notification of rebuilding the document, this allows
255        for saving object related settings in the document. */
256     _reconstruction_start_connection =
257         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
258     _reconstruction_finish_connection =
259         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
260     _reconstruction_old_layer_id = NULL;
261     
262     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
263     
264     // ?
265     // sp_active_desktop_set (desktop);
266     _inkscape = INKSCAPE;
268     _activate_connection = _activate_signal.connect(
269         sigc::bind(
270             sigc::ptr_fun(_onActivate),
271             this
272         )
273     );
274      _deactivate_connection = _deactivate_signal.connect(
275         sigc::bind(
276             sigc::ptr_fun(_onDeactivate),
277             this
278         )
279     );
281     _sel_modified_connection = selection->connectModified(
282         sigc::bind(
283             sigc::ptr_fun(&_onSelectionModified),
284             this
285         )
286     );
287     _sel_changed_connection = selection->connectChanged(
288         sigc::bind(
289             sigc::ptr_fun(&_onSelectionChanged),
290             this
291         )
292     );
295     /* setup LayerManager */
296     //   (Setting up after the connections are all in place, as it may use some of them)
297     layer_manager = new Inkscape::LayerManager( this );
301 void SPDesktop::destroy()
303     _activate_connection.disconnect();
304     _deactivate_connection.disconnect();
305     _sel_modified_connection.disconnect();
306     _sel_changed_connection.disconnect();
307     _modified_connection.disconnect();
308     _commit_connection.disconnect();
309     _reconstruction_start_connection.disconnect();
310     _reconstruction_finish_connection.disconnect();
312     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
313     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
314     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
316     while (event_context) {
317         SPEventContext *ec = event_context;
318         event_context = ec->next;
319         sp_event_context_finish (ec);
320         g_object_unref (G_OBJECT (ec));
321     }
323     if (_layer_hierarchy) {
324         delete _layer_hierarchy;
325     }
327     if (_inkscape) {
328         _inkscape = NULL;
329     }
331     if (drawing) {
332         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
333         drawing = NULL;
334     }
336     delete _guides_message_context;
337     _guides_message_context = NULL;
339     g_list_free (zooms_past);
340     g_list_free (zooms_future);
343 SPDesktop::~SPDesktop() {}
345 //--------------------------------------------------------------------
346 /* Public methods */
348 void SPDesktop::setDisplayModeNormal()
350     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
351     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
352     displayMode = RENDERMODE_NORMAL;
353     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
354     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
357 void SPDesktop::setDisplayModeOutline()
359     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
360     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
361     displayMode = RENDERMODE_OUTLINE;
362     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
363     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
366 void SPDesktop::displayModeToggle()
368     if (displayMode == RENDERMODE_OUTLINE)
369         setDisplayModeNormal();
370     else 
371         setDisplayModeOutline();
374 /**
375  * Returns current root (=bottom) layer.
376  */
377 SPObject *SPDesktop::currentRoot() const
379     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
382 /**
383  * Returns current top layer.
384  */
385 SPObject *SPDesktop::currentLayer() const
387     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
390 /**
391  * Sets the current layer of the desktop.
392  * 
393  * Make \a object the top layer.
394  */
395 void SPDesktop::setCurrentLayer(SPObject *object) {
396     g_return_if_fail(SP_IS_GROUP(object));
397     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
398     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
399     _layer_hierarchy->setBottom(object);
402 /**
403  * Return layer that contains \a object.
404  */
405 SPObject *SPDesktop::layerForObject(SPObject *object) {
406     g_return_val_if_fail(object != NULL, NULL);
408     SPObject *root=currentRoot();
409     object = SP_OBJECT_PARENT(object);
410     while ( object && object != root && !isLayer(object) ) {
411         object = SP_OBJECT_PARENT(object);
412     }
413     return object;
416 /**
417  * True if object is a layer.
418  */
419 bool SPDesktop::isLayer(SPObject *object) const {
420     return ( SP_IS_GROUP(object)
421              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
422                   == SPGroup::LAYER ) );
425 /**
426  * True if desktop viewport fully contains \a item's bbox.
427  */
428 bool SPDesktop::isWithinViewport (SPItem *item) const
430     NR::Rect const viewport = get_display_area();
431     NR::Maybe<NR::Rect> const bbox = sp_item_bbox_desktop(item);
432     if (bbox) {
433         return viewport.contains(*bbox);
434     } else {
435         return true;
436     }
439 ///
440 bool SPDesktop::itemIsHidden(SPItem const *item) const {
441     return item->isHidden(this->dkey);
444 /**
445  * Set activate property of desktop; emit signal if changed.
446  */
447 void
448 SPDesktop::set_active (bool new_active)
450     if (new_active != _active) {
451         _active = new_active;
452         if (new_active) {
453             _activate_signal.emit();
454         } else {
455             _deactivate_signal.emit();
456         }
457     }
460 /**
461  * Set activate status of current desktop's named view.
462  */
463 void
464 SPDesktop::activate_guides(bool activate)
466     guides_active = activate;
467     namedview->activateGuides(this, activate);
470 /**
471  * Make desktop switch documents.
472  */
473 void
474 SPDesktop::change_document (SPDocument *theDocument)
476     g_return_if_fail (theDocument != NULL);
478     /* unselect everything before switching documents */
479     selection->clear();
481     setDocument (theDocument);
482     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
483     _document_replaced_signal.emit (this, theDocument);
486 /**
487  * Make desktop switch event contexts.
488  */
489 void
490 SPDesktop::set_event_context (GtkType type, const gchar *config)
492     SPEventContext *ec;
493     while (event_context) {
494         ec = event_context;
495         sp_event_context_deactivate (ec);
496         event_context = ec->next;
497         sp_event_context_finish (ec);
498         g_object_unref (G_OBJECT (ec));
499     }
501     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
502     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
503     ec->next = event_context;
504     event_context = ec;
505     sp_event_context_activate (ec);
506     _event_context_changed_signal.emit (this, ec);
509 /**
510  * Push event context onto desktop's context stack.
511  */
512 void
513 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
515     SPEventContext *ref, *ec;
516     Inkscape::XML::Node *repr;
518     if (event_context && event_context->key == key) return;
519     ref = event_context;
520     while (ref && ref->next && ref->next->key != key) ref = ref->next;
521     if (ref && ref->next) {
522         ec = ref->next;
523         ref->next = ec->next;
524         sp_event_context_finish (ec);
525         g_object_unref (G_OBJECT (ec));
526     }
528     if (event_context) sp_event_context_deactivate (event_context);
529     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
530     ec = sp_event_context_new (type, this, repr, key);
531     ec->next = event_context;
532     event_context = ec;
533     sp_event_context_activate (ec);
534     _event_context_changed_signal.emit (this, ec);
537 /**
538  * Sets the coordinate status to a given point
539  */
540 void
541 SPDesktop::set_coordinate_status (NR::Point p) {
542     _widget->setCoordinateStatus(p);
545 /**
546  * \see sp_document_item_from_list_at_point_bottom()
547  */
548 SPItem *
549 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
551     g_return_val_if_fail (doc() != NULL, NULL);
552     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
555 /**
556  * \see sp_document_item_at_point()
557  */
558 SPItem *
559 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
561     g_return_val_if_fail (doc() != NULL, NULL);
562     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
565 /**
566  * \see sp_document_group_at_point()
567  */
568 SPItem *
569 SPDesktop::group_at_point (NR::Point const p) const
571     g_return_val_if_fail (doc() != NULL, NULL);
572     return sp_document_group_at_point (doc(), dkey, p);
575 /**
576  * \brief  Returns the mouse point in document coordinates; if mouse is
577  * outside the canvas, returns the center of canvas viewpoint
578  */
579 NR::Point
580 SPDesktop::point() const
582     NR::Point p = _widget->getPointer();
583     NR::Point pw = sp_canvas_window_to_world (canvas, p);
584     p = w2d(pw);
586     NR::Rect const r = canvas->getViewbox();
588     NR::Point r0 = w2d(r.min());
589     NR::Point r1 = w2d(r.max());
591     if (p[NR::X] >= r0[NR::X] &&
592         p[NR::X] <= r1[NR::X] &&
593         p[NR::Y] >= r1[NR::Y] &&
594         p[NR::Y] <= r0[NR::Y])
595     {
596         return p;
597     } else {
598         return (r0 + r1) / 2;
599     }
602 /**
603  * Put current zoom data in history list.
604  */
605 void
606 SPDesktop::push_current_zoom (GList **history)
608     NR::Rect const area = get_display_area();
610     NRRect *old_zoom = g_new(NRRect, 1);
611     old_zoom->x0 = area.min()[NR::X];
612     old_zoom->x1 = area.max()[NR::X];
613     old_zoom->y0 = area.min()[NR::Y];
614     old_zoom->y1 = area.max()[NR::Y];
615     if ( *history == NULL
616          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
617                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
618                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
619                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
620     {
621         *history = g_list_prepend (*history, old_zoom);
622     }
625 /**
626  * Set viewbox.
627  */
628 void
629 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
631     g_assert(_widget);
633     // save the zoom
634     if (log) {
635         push_current_zoom(&zooms_past);
636         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
637         g_list_free (zooms_future);
638         zooms_future = NULL;
639     }
641     double const cx = 0.5 * (x0 + x1);
642     double const cy = 0.5 * (y0 + y1);
644     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
646     double scale = expansion(_d2w);
647     double newscale;
648     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
649         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
650     } else {
651         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
652     }
654     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
656     int clear = FALSE;
657     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
658         /* Set zoom factors */
659         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
660         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
661         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
662         clear = TRUE;
663     }
665     /* Calculate top left corner */
666     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
667     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
669     /* Scroll */
670     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
672     _widget->updateRulers();
673     _widget->updateScrollbars(expansion(_d2w));
674     _widget->updateZoom();
677 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
679     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
682 /**
683  * Return viewbox dimensions.
684  */
685 NR::Rect SPDesktop::get_display_area() const
687     NR::Rect const viewbox = canvas->getViewbox();
689     double const scale = _d2w[0];
691     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
692                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
695 /**
696  * Revert back to previous zoom if possible.
697  */
698 void
699 SPDesktop::prev_zoom()
701     if (zooms_past == NULL) {
702         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
703         return;
704     }
706     // push current zoom into forward zooms list
707     push_current_zoom (&zooms_future);
709     // restore previous zoom
710     set_display_area (((NRRect *) zooms_past->data)->x0,
711             ((NRRect *) zooms_past->data)->y0,
712             ((NRRect *) zooms_past->data)->x1,
713             ((NRRect *) zooms_past->data)->y1,
714             0, false);
716     // remove the just-added zoom from the past zooms list
717     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
720 /**
721  * Set zoom to next in list.
722  */
723 void
724 SPDesktop::next_zoom()
726     if (zooms_future == NULL) {
727         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
728         return;
729     }
731     // push current zoom into past zooms list
732     push_current_zoom (&zooms_past);
734     // restore next zoom
735     set_display_area (((NRRect *) zooms_future->data)->x0,
736             ((NRRect *) zooms_future->data)->y0,
737             ((NRRect *) zooms_future->data)->x1,
738             ((NRRect *) zooms_future->data)->y1,
739             0, false);
741     // remove the just-used zoom from the zooms_future list
742     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
745 /**
746  * Zoom to point with absolute zoom factor.
747  */
748 void
749 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
751     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
753     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
754     // this check prevents "sliding" when trying to zoom in at maximum zoom;
755     /// \todo someone please fix calculations properly and remove this hack
756     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
757         return;
759     NR::Rect const viewbox = canvas->getViewbox();
761     double const width2 = viewbox.dimensions()[NR::X] / zoom;
762     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
764     set_display_area(cx - px * width2,
765                      cy - py * height2,
766                      cx + (1 - px) * width2,
767                      cy + (1 - py) * height2,
768                      0.0);
771 /**
772  * Zoom to center with absolute zoom factor.
773  */
774 void
775 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
777     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
780 /**
781  * Zoom to point with relative zoom factor.
782  */
783 void
784 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
786     NR::Rect const area = get_display_area();
788     if (cx < area.min()[NR::X]) {
789         cx = area.min()[NR::X];
790     }
791     if (cx > area.max()[NR::X]) {
792         cx = area.max()[NR::X];
793     }
794     if (cy < area.min()[NR::Y]) {
795         cy = area.min()[NR::Y];
796     }
797     if (cy > area.max()[NR::Y]) {
798         cy = area.max()[NR::Y];
799     }
801     gdouble const scale = expansion(_d2w) * zoom;
802     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
803     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
805     zoom_absolute_keep_point(cx, cy, px, py, scale);
808 /**
809  * Zoom to center with relative zoom factor.
810  */
811 void
812 SPDesktop::zoom_relative (double cx, double cy, double zoom)
814     gdouble scale = expansion(_d2w) * zoom;
815     zoom_absolute (cx, cy, scale);
818 /**
819  * Set display area to origin and current document dimensions.
820  */
821 void
822 SPDesktop::zoom_page()
824     NR::Rect d(NR::Point(0, 0),
825                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
827     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
828         return;
829     }
831     set_display_area(d, 10);
834 /**
835  * Set display area to current document width.
836  */
837 void
838 SPDesktop::zoom_page_width()
840     NR::Rect const a = get_display_area();
842     if (sp_document_width(doc()) < 1.0) {
843         return;
844     }
846     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
847                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
849     set_display_area(d, 10);
852 /**
853  * Zoom to selection.
854  */
855 void
856 SPDesktop::zoom_selection()
858     NR::Maybe<NR::Rect> const d = selection->bounds();
860     if ( !d || d->dimensions()[NR::X] < 0.1 || d->dimensions()[NR::Y] < 0.1) {
861         return;
862     }
864     set_display_area(*d, 10);
867 /**
868  * Tell widget to let zoom widget grab keyboard focus.
869  */
870 void
871 SPDesktop::zoom_grab_focus()
873     _widget->letZoomGrabFocus();
876 /**
877  * Zoom to whole drawing.
878  */
879 void
880 SPDesktop::zoom_drawing()
882     g_return_if_fail (doc() != NULL);
883     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
884     g_return_if_fail (docitem != NULL);
886     NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
888     /* Note that the second condition here indicates that
889     ** there are no items in the drawing.
890     */
891     if ( !d || d->dimensions()[NR::X] < 1.0 || d->dimensions()[NR::Y] < 1.0 ) {
892         return;
893     }
895     set_display_area(*d, 10);
898 /**
899  * Scroll canvas by specific coordinate amount.
900  */
901 void
902 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
904     g_assert(_widget);
906     NR::Rect const viewbox = canvas->getViewbox();
908     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
910     _widget->updateRulers();
911     _widget->updateScrollbars(expansion(_d2w));
914 bool
915 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
917     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
919     // autoscrolldistance is in screen pixels, but the display area is in document units
920     autoscrolldistance /= expansion(_d2w);
921     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
923     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
924         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
926         NR::Point const s_w( (*p) * _d2w );
928         gdouble x_to;
929         if ((*p)[NR::X] < dbox.min()[NR::X])
930             x_to = dbox.min()[NR::X];
931         else if ((*p)[NR::X] > dbox.max()[NR::X])
932             x_to = dbox.max()[NR::X];
933         else
934             x_to = (*p)[NR::X];
936         gdouble y_to;
937         if ((*p)[NR::Y] < dbox.min()[NR::Y])
938             y_to = dbox.min()[NR::Y];
939         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
940             y_to = dbox.max()[NR::Y];
941         else
942             y_to = (*p)[NR::Y];
944         NR::Point const d_dt(x_to, y_to);
945         NR::Point const d_w( d_dt * _d2w );
946         NR::Point const moved_w( d_w - s_w );
948         if (autoscrollspeed == 0)
949             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
951         if (autoscrollspeed != 0)
952             scroll_world (autoscrollspeed * moved_w);
954         return true;
955     }
956     return false;
959 void
960 SPDesktop::fullscreen()
962     _widget->setFullscreen();
965 void
966 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
968     _widget->getGeometry (x, y, w, h);
971 void
972 SPDesktop::setWindowPosition (NR::Point p)
974     _widget->setPosition (p);
977 void
978 SPDesktop::setWindowSize (gint w, gint h)
980     _widget->setSize (w, h);
983 void
984 SPDesktop::setWindowTransient (void *p, int transient_policy)
986     _widget->setTransient (p, transient_policy);
989 void
990 SPDesktop::presentWindow()
992     _widget->present();
995 bool
996 SPDesktop::warnDialog (gchar *text)
998     return _widget->warnDialog (text);
1001 void
1002 SPDesktop::toggleRulers()
1004     _widget->toggleRulers();
1007 void
1008 SPDesktop::toggleScrollbars()
1010     _widget->toggleScrollbars();
1013 void
1014 SPDesktop::layoutWidget()
1016     _widget->layout();
1019 void
1020 SPDesktop::destroyWidget()
1022     _widget->destroy();
1025 bool
1026 SPDesktop::shutdown()
1028     return _widget->shutdown();
1031 void
1032 SPDesktop::setToolboxFocusTo (gchar const *label)
1034     _widget->setToolboxFocusTo (label);
1037 void
1038 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1040     _widget->setToolboxAdjustmentValue (id, val);
1043 bool
1044 SPDesktop::isToolboxButtonActive (gchar const *id)
1046     return _widget->isToolboxButtonActive (id);
1049 void
1050 SPDesktop::emitToolSubselectionChanged(gpointer data)
1052         _tool_subselection_changed.emit(data);
1053         inkscape_subselection_changed (this);
1056 void
1057 SPDesktop::updateNow()
1059   sp_canvas_update_now(canvas);
1062 void
1063 SPDesktop::enableInteraction()
1065   _widget->enableInteraction();
1068 void SPDesktop::disableInteraction()
1070   _widget->disableInteraction();
1073 //----------------------------------------------------------------------
1074 // Callback implementations. The virtual ones are connected by the view.
1076 void
1077 SPDesktop::onPositionSet (double x, double y)
1079     _widget->viewSetPosition (NR::Point(x,y));
1082 void
1083 SPDesktop::onResized (double x, double y)
1085    // Nothing called here
1088 /**
1089  * Redraw callback; queues Gtk redraw; connected by View.
1090  */
1091 void
1092 SPDesktop::onRedrawRequested ()
1094     if (main) {
1095         _widget->requestCanvasUpdate();
1096     }
1099 void
1100 SPDesktop::updateCanvasNow()
1102   _widget->requestCanvasUpdateAndWait();
1105 /**
1106  * Associate document with desktop.
1107  */
1108 /// \todo fixme: refactor SPDesktop::init to use setDocument
1109 void
1110 SPDesktop::setDocument (SPDocument *doc)
1112     if (this->doc() && doc) {
1113         namedview->hide(this);
1114         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1115     }
1117     if (_layer_hierarchy) {
1118         _layer_hierarchy->clear();
1119         delete _layer_hierarchy;
1120     }
1121     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1122     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1123     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1124     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1125     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1127     /* setup EventLog */
1128     event_log = new Inkscape::EventLog(doc);
1129     doc->addUndoObserver(*event_log);
1131     _commit_connection.disconnect();
1132     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1134     /// \todo fixme: This condition exists to make sure the code
1135     /// inside is called only once on initialization. But there
1136     /// are surely more safe methods to accomplish this.
1137     if (drawing) {
1138         NRArenaItem *ai;
1140         namedview = sp_document_namedview (doc, NULL);
1141         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1142         number = namedview->getViewCount();
1144         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1145                 SP_CANVAS_ARENA (drawing)->arena,
1146                 dkey,
1147                 SP_ITEM_SHOW_DISPLAY);
1148         if (ai) {
1149             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1150             nr_arena_item_unref (ai);
1151         }
1152         namedview->show(this);
1153         /* Ugly hack */
1154         activate_guides (true);
1155         /* Ugly hack */
1156         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1157     }
1159     _document_replaced_signal.emit (this, doc);
1161     View::setDocument (doc);
1164 void
1165 SPDesktop::onStatusMessage
1166 (Inkscape::MessageType type, gchar const *message)
1168     if (_widget) {
1169         _widget->setMessage(type, message);
1170     }
1173 void
1174 SPDesktop::onDocumentURISet (gchar const* uri)
1176     _widget->setTitle(uri);
1179 /**
1180  * Resized callback.
1181  */
1182 void
1183 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1185     _doc2dt[5] = height;
1186     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1187     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1188     SP_CTRLRECT(page)->setRectangle(a);
1189     SP_CTRLRECT(page_border)->setRectangle(a);
1193 void
1194 SPDesktop::_onActivate (SPDesktop* dt)
1196     if (!dt->_widget) return;
1197     dt->_widget->activateDesktop();
1200 void
1201 SPDesktop::_onDeactivate (SPDesktop* dt)
1203     if (!dt->_widget) return;
1204     dt->_widget->deactivateDesktop();
1207 void
1208 SPDesktop::_onSelectionModified
1209 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1211     if (!dt->_widget) return;
1212     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1215 static void
1216 _onSelectionChanged
1217 (Inkscape::Selection *selection, SPDesktop *desktop)
1219     /** \todo
1220      * only change the layer for single selections, or what?
1221      * This seems reasonable -- for multiple selections there can be many
1222      * different layers involved.
1223      */
1224     SPItem *item=selection->singleItem();
1225     if (item) {
1226         SPObject *layer=desktop->layerForObject(item);
1227         if ( layer && layer != desktop->currentLayer() ) {
1228             desktop->setCurrentLayer(layer);
1229         }
1230     }
1233 /**
1234  * Calls event handler of current event context.
1235  * \param arena Unused
1236  * \todo fixme
1237  */
1238 static gint
1239 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1241     if (ai) {
1242         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1243         return sp_event_context_item_handler (desktop->event_context, spi, event);
1244     } else {
1245         return sp_event_context_root_handler (desktop->event_context, event);
1246     }
1249 static void
1250 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1251     g_return_if_fail(SP_IS_GROUP(layer));
1252     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1255 /// Callback
1256 static void
1257 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1258     g_return_if_fail(SP_IS_GROUP(layer));
1259     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1262 /// Callback
1263 static void
1264 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1265                                          SPDesktop *desktop)
1267     desktop->_layer_changed_signal.emit (bottom);
1270 /// Called when document is starting to be rebuilt.
1271 static void
1272 _reconstruction_start (SPDesktop * desktop)
1274     // printf("Desktop, starting reconstruction\n");
1275     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1276     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1278     /*
1279     GSList const * selection_objs = desktop->selection->list();
1280     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1282     }
1283     */
1284     desktop->selection->clear();
1286     // printf("Desktop, starting reconstruction end\n");
1289 /// Called when document rebuild is finished.
1290 static void
1291 _reconstruction_finish (SPDesktop * desktop)
1293     // printf("Desktop, finishing reconstruction\n");
1294     if (desktop->_reconstruction_old_layer_id == NULL)
1295         return;
1297     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1298     if (newLayer != NULL)
1299         desktop->setCurrentLayer(newLayer);
1301     g_free(desktop->_reconstruction_old_layer_id);
1302     desktop->_reconstruction_old_layer_id = NULL;
1303     // printf("Desktop, finishing reconstruction end\n");
1304     return;
1307 /**
1308  * Namedview_modified callback.
1309  */
1310 static void
1311 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1313     SPNamedView *nv=SP_NAMEDVIEW(obj);
1315     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1317         /* Recalculate snap distances */
1318         /* FIXME: why is the desktop getting involved in setting up something
1319         ** that is entirely to do with the namedview?
1320         */
1321         _update_snap_distances (desktop);
1323         /* Show/hide page background */
1324         if (nv->pagecolor & 0xff) {
1325             sp_canvas_item_show (desktop->table);
1326             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1327             sp_canvas_item_move_to_z (desktop->table, 0);
1328         } else {
1329             sp_canvas_item_hide (desktop->table);
1330         }
1332         /* Show/hide page border */
1333         if (nv->showborder) {
1334             // show
1335             sp_canvas_item_show (desktop->page_border);
1336             // set color and shadow
1337             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1338             if (nv->pageshadow) {
1339                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1340             }
1341             // place in the z-order stack
1342             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1343                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1344             } else {
1345                 int order = sp_canvas_item_order (desktop->page_border);
1346                 int morder = sp_canvas_item_order (desktop->drawing);
1347                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1348                                     morder - order);
1349             }
1350         } else {
1351                 sp_canvas_item_hide (desktop->page_border);
1352                 if (nv->pageshadow) {
1353                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1354                 }
1355         }
1356         
1357         /* Show/hide page shadow */
1358         if (nv->showpageshadow && nv->pageshadow) {
1359             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1360         } else {
1361             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1362         }
1364         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1365             (SP_RGBA32_R_U(nv->pagecolor) +
1366              SP_RGBA32_G_U(nv->pagecolor) +
1367              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1368             // the background color is light or transparent, use black outline
1369             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1370         } else { // use white outline
1371             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1372         }
1373     }
1376 /**
1377  * Callback to reset snapper's distances.
1378  */
1379 static void
1380 _update_snap_distances (SPDesktop *desktop)
1382     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1384     SPNamedView &nv = *desktop->namedview;
1386     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1387                                                                       *nv.gridtoleranceunit,
1388                                                                       px));
1389     nv.snap_manager.axonomgrid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1390                                                                       *nv.gridtoleranceunit,
1391                                                                       px));
1392     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1393                                                                        *nv.guidetoleranceunit,
1394                                                                        px));
1395     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1396                                                                         *nv.objecttoleranceunit,
1397                                                                         px));
1401 NR::Matrix SPDesktop::w2d() const
1403     return _w2d;
1406 NR::Point SPDesktop::w2d(NR::Point const &p) const
1408     return p * _w2d;
1411 NR::Point SPDesktop::d2w(NR::Point const &p) const
1413     return p * _d2w;
1416 NR::Matrix SPDesktop::doc2dt() const
1418     return _doc2dt;
1421 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1423     return p * _doc2dt;
1426 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1428     return p / _doc2dt;
1432 /**
1433  * Pop event context from desktop's context stack. Never used.
1434  */
1435 // void
1436 // SPDesktop::pop_event_context (unsigned int key)
1437 // {
1438 //    SPEventContext *ec = NULL;
1439 //
1440 //    if (event_context && event_context->key == key) {
1441 //        g_return_if_fail (event_context);
1442 //        g_return_if_fail (event_context->next);
1443 //        ec = event_context;
1444 //        sp_event_context_deactivate (ec);
1445 //        event_context = ec->next;
1446 //        sp_event_context_activate (event_context);
1447 //        _event_context_changed_signal.emit (this, ec);
1448 //    }
1449 //
1450 //    SPEventContext *ref = event_context;
1451 //    while (ref && ref->next && ref->next->key != key)
1452 //        ref = ref->next;
1453 //
1454 //    if (ref && ref->next) {
1455 //        ec = ref->next;
1456 //        ref->next = ec->next;
1457 //    }
1458 //
1459 //    if (ec) {
1460 //        sp_event_context_finish (ec);
1461 //        g_object_unref (G_OBJECT (ec));
1462 //    }
1463 // }
1465 /*
1466   Local Variables:
1467   mode:c++
1468   c-file-style:"stroustrup"
1469   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1470   indent-tabs-mode:nil
1471   fill-column:99
1472   End:
1473 */
1474 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :