Code

Sorry, forgot the copyright text.
[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     prefs_set_int_attribute("options.outlinemode", "value", 0);
351     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
352     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
353     displayMode = RENDERMODE_NORMAL;
354     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
355     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
358 void SPDesktop::setDisplayModeOutline()
360     prefs_set_int_attribute("options.outlinemode", "value", 1);
361     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
362     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
363     displayMode = RENDERMODE_OUTLINE;
364     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
365     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
368 void SPDesktop::displayModeToggle()
370     if (prefs_get_int_attribute("options.outlinemode", "value", prefs_get_int_attribute("options.startmode", "outline", 0)))
371         setDisplayModeNormal();
372     else 
373         setDisplayModeOutline();
376 /**
377  * Returns current root (=bottom) layer.
378  */
379 SPObject *SPDesktop::currentRoot() const
381     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
384 /**
385  * Returns current top layer.
386  */
387 SPObject *SPDesktop::currentLayer() const
389     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
392 /**
393  * Sets the current layer of the desktop.
394  * 
395  * Make \a object the top layer.
396  */
397 void SPDesktop::setCurrentLayer(SPObject *object) {
398     g_return_if_fail(SP_IS_GROUP(object));
399     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
400     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
401     _layer_hierarchy->setBottom(object);
404 /**
405  * Return layer that contains \a object.
406  */
407 SPObject *SPDesktop::layerForObject(SPObject *object) {
408     g_return_val_if_fail(object != NULL, NULL);
410     SPObject *root=currentRoot();
411     object = SP_OBJECT_PARENT(object);
412     while ( object && object != root && !isLayer(object) ) {
413         object = SP_OBJECT_PARENT(object);
414     }
415     return object;
418 /**
419  * True if object is a layer.
420  */
421 bool SPDesktop::isLayer(SPObject *object) const {
422     return ( SP_IS_GROUP(object)
423              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
424                   == SPGroup::LAYER ) );
427 /**
428  * True if desktop viewport fully contains \a item's bbox.
429  */
430 bool SPDesktop::isWithinViewport (SPItem *item) const
432     NR::Rect const viewport = get_display_area();
433     NR::Rect const bbox = sp_item_bbox_desktop(item);
434     return viewport.contains(bbox);
437 ///
438 bool SPDesktop::itemIsHidden(SPItem const *item) const {
439     return item->isHidden(this->dkey);
442 /**
443  * Set activate property of desktop; emit signal if changed.
444  */
445 void
446 SPDesktop::set_active (bool new_active)
448     if (new_active != _active) {
449         _active = new_active;
450         if (new_active) {
451             _activate_signal.emit();
452         } else {
453             _deactivate_signal.emit();
454         }
455     }
458 /**
459  * Set activate status of current desktop's named view.
460  */
461 void
462 SPDesktop::activate_guides(bool activate)
464     guides_active = activate;
465     namedview->activateGuides(this, activate);
468 /**
469  * Make desktop switch documents.
470  */
471 void
472 SPDesktop::change_document (SPDocument *theDocument)
474     g_return_if_fail (theDocument != NULL);
476     /* unselect everything before switching documents */
477     selection->clear();
479     setDocument (theDocument);
480     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
481     _document_replaced_signal.emit (this, theDocument);
484 /**
485  * Make desktop switch event contexts.
486  */
487 void
488 SPDesktop::set_event_context (GtkType type, const gchar *config)
490     SPEventContext *ec;
491     while (event_context) {
492         ec = event_context;
493         sp_event_context_deactivate (ec);
494         event_context = ec->next;
495         sp_event_context_finish (ec);
496         g_object_unref (G_OBJECT (ec));
497     }
499     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
500     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
501     ec->next = event_context;
502     event_context = ec;
503     sp_event_context_activate (ec);
504     _event_context_changed_signal.emit (this, ec);
507 /**
508  * Push event context onto desktop's context stack.
509  */
510 void
511 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
513     SPEventContext *ref, *ec;
514     Inkscape::XML::Node *repr;
516     if (event_context && event_context->key == key) return;
517     ref = event_context;
518     while (ref && ref->next && ref->next->key != key) ref = ref->next;
519     if (ref && ref->next) {
520         ec = ref->next;
521         ref->next = ec->next;
522         sp_event_context_finish (ec);
523         g_object_unref (G_OBJECT (ec));
524     }
526     if (event_context) sp_event_context_deactivate (event_context);
527     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
528     ec = sp_event_context_new (type, this, repr, key);
529     ec->next = event_context;
530     event_context = ec;
531     sp_event_context_activate (ec);
532     _event_context_changed_signal.emit (this, ec);
535 /**
536  * Sets the coordinate status to a given point
537  */
538 void
539 SPDesktop::set_coordinate_status (NR::Point p) {
540     _widget->setCoordinateStatus(p);
543 /**
544  * \see sp_document_item_from_list_at_point_bottom()
545  */
546 SPItem *
547 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
549     g_return_val_if_fail (doc() != NULL, NULL);
550     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
553 /**
554  * \see sp_document_item_at_point()
555  */
556 SPItem *
557 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
559     g_return_val_if_fail (doc() != NULL, NULL);
560     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
563 /**
564  * \see sp_document_group_at_point()
565  */
566 SPItem *
567 SPDesktop::group_at_point (NR::Point const p) const
569     g_return_val_if_fail (doc() != NULL, NULL);
570     return sp_document_group_at_point (doc(), dkey, p);
573 /**
574  * \brief  Returns the mouse point in document coordinates; if mouse is
575  * outside the canvas, returns the center of canvas viewpoint
576  */
577 NR::Point
578 SPDesktop::point() const
580     NR::Point p = _widget->getPointer();
581     NR::Point pw = sp_canvas_window_to_world (canvas, p);
582     p = w2d(pw);
584     NR::Rect const r = canvas->getViewbox();
586     NR::Point r0 = w2d(r.min());
587     NR::Point r1 = w2d(r.max());
589     if (p[NR::X] >= r0[NR::X] &&
590         p[NR::X] <= r1[NR::X] &&
591         p[NR::Y] >= r1[NR::Y] &&
592         p[NR::Y] <= r0[NR::Y])
593     {
594         return p;
595     } else {
596         return (r0 + r1) / 2;
597     }
600 /**
601  * Put current zoom data in history list.
602  */
603 void
604 SPDesktop::push_current_zoom (GList **history)
606     NR::Rect const area = get_display_area();
608     NRRect *old_zoom = g_new(NRRect, 1);
609     old_zoom->x0 = area.min()[NR::X];
610     old_zoom->x1 = area.max()[NR::X];
611     old_zoom->y0 = area.min()[NR::Y];
612     old_zoom->y1 = area.max()[NR::Y];
613     if ( *history == NULL
614          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
615                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
616                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
617                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
618     {
619         *history = g_list_prepend (*history, old_zoom);
620     }
623 /**
624  * Set viewbox.
625  */
626 void
627 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
629     g_assert(_widget);
631     // save the zoom
632     if (log) {
633         push_current_zoom(&zooms_past);
634         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
635         g_list_free (zooms_future);
636         zooms_future = NULL;
637     }
639     double const cx = 0.5 * (x0 + x1);
640     double const cy = 0.5 * (y0 + y1);
642     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
644     double scale = expansion(_d2w);
645     double newscale;
646     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
647         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
648     } else {
649         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
650     }
652     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
654     int clear = FALSE;
655     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
656         /* Set zoom factors */
657         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
658         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
659         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
660         clear = TRUE;
661     }
663     /* Calculate top left corner */
664     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
665     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
667     /* Scroll */
668     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
670     _widget->updateRulers();
671     _widget->updateScrollbars(expansion(_d2w));
672     _widget->updateZoom();
675 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
677     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
680 /**
681  * Return viewbox dimensions.
682  */
683 NR::Rect SPDesktop::get_display_area() const
685     NR::Rect const viewbox = canvas->getViewbox();
687     double const scale = _d2w[0];
689     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
690                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
693 /**
694  * Revert back to previous zoom if possible.
695  */
696 void
697 SPDesktop::prev_zoom()
699     if (zooms_past == NULL) {
700         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
701         return;
702     }
704     // push current zoom into forward zooms list
705     push_current_zoom (&zooms_future);
707     // restore previous zoom
708     set_display_area (((NRRect *) zooms_past->data)->x0,
709             ((NRRect *) zooms_past->data)->y0,
710             ((NRRect *) zooms_past->data)->x1,
711             ((NRRect *) zooms_past->data)->y1,
712             0, false);
714     // remove the just-added zoom from the past zooms list
715     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
718 /**
719  * Set zoom to next in list.
720  */
721 void
722 SPDesktop::next_zoom()
724     if (zooms_future == NULL) {
725         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
726         return;
727     }
729     // push current zoom into past zooms list
730     push_current_zoom (&zooms_past);
732     // restore next zoom
733     set_display_area (((NRRect *) zooms_future->data)->x0,
734             ((NRRect *) zooms_future->data)->y0,
735             ((NRRect *) zooms_future->data)->x1,
736             ((NRRect *) zooms_future->data)->y1,
737             0, false);
739     // remove the just-used zoom from the zooms_future list
740     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
743 /**
744  * Zoom to point with absolute zoom factor.
745  */
746 void
747 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
749     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
751     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
752     // this check prevents "sliding" when trying to zoom in at maximum zoom;
753     /// \todo someone please fix calculations properly and remove this hack
754     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
755         return;
757     NR::Rect const viewbox = canvas->getViewbox();
759     double const width2 = viewbox.dimensions()[NR::X] / zoom;
760     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
762     set_display_area(cx - px * width2,
763                      cy - py * height2,
764                      cx + (1 - px) * width2,
765                      cy + (1 - py) * height2,
766                      0.0);
769 /**
770  * Zoom to center with absolute zoom factor.
771  */
772 void
773 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
775     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
778 /**
779  * Zoom to point with relative zoom factor.
780  */
781 void
782 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
784     NR::Rect const area = get_display_area();
786     if (cx < area.min()[NR::X]) {
787         cx = area.min()[NR::X];
788     }
789     if (cx > area.max()[NR::X]) {
790         cx = area.max()[NR::X];
791     }
792     if (cy < area.min()[NR::Y]) {
793         cy = area.min()[NR::Y];
794     }
795     if (cy > area.max()[NR::Y]) {
796         cy = area.max()[NR::Y];
797     }
799     gdouble const scale = expansion(_d2w) * zoom;
800     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
801     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
803     zoom_absolute_keep_point(cx, cy, px, py, scale);
806 /**
807  * Zoom to center with relative zoom factor.
808  */
809 void
810 SPDesktop::zoom_relative (double cx, double cy, double zoom)
812     gdouble scale = expansion(_d2w) * zoom;
813     zoom_absolute (cx, cy, scale);
816 /**
817  * Set display area to origin and current document dimensions.
818  */
819 void
820 SPDesktop::zoom_page()
822     NR::Rect d(NR::Point(0, 0),
823                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
825     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
826         return;
827     }
829     set_display_area(d, 10);
832 /**
833  * Set display area to current document width.
834  */
835 void
836 SPDesktop::zoom_page_width()
838     NR::Rect const a = get_display_area();
840     if (sp_document_width(doc()) < 1.0) {
841         return;
842     }
844     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
845                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
847     set_display_area(d, 10);
850 /**
851  * Zoom to selection.
852  */
853 void
854 SPDesktop::zoom_selection()
856     NR::Rect const d = selection->bounds();
858     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
859         return;
860     }
862     set_display_area(d, 10);
865 /**
866  * Tell widget to let zoom widget grab keyboard focus.
867  */
868 void
869 SPDesktop::zoom_grab_focus()
871     _widget->letZoomGrabFocus();
874 /**
875  * Zoom to whole drawing.
876  */
877 void
878 SPDesktop::zoom_drawing()
880     g_return_if_fail (doc() != NULL);
881     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
882     g_return_if_fail (docitem != NULL);
884     NR::Rect d = sp_item_bbox_desktop(docitem);
886     /* Note that the second condition here indicates that
887     ** there are no items in the drawing.
888     */
889     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
890         return;
891     }
893     set_display_area(d, 10);
896 /**
897  * Scroll canvas by specific coordinate amount.
898  */
899 void
900 SPDesktop::scroll_world (double dx, double dy)
902     g_assert(_widget);
904     NR::Rect const viewbox = canvas->getViewbox();
906     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
908     _widget->updateRulers();
909     _widget->updateScrollbars(expansion(_d2w));
912 bool
913 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
915     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
917     // autoscrolldistance is in screen pixels, but the display area is in document units
918     autoscrolldistance /= expansion(_d2w);
919     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
921     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
922         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
924         NR::Point const s_w( (*p) * _d2w );
926         gdouble x_to;
927         if ((*p)[NR::X] < dbox.min()[NR::X])
928             x_to = dbox.min()[NR::X];
929         else if ((*p)[NR::X] > dbox.max()[NR::X])
930             x_to = dbox.max()[NR::X];
931         else
932             x_to = (*p)[NR::X];
934         gdouble y_to;
935         if ((*p)[NR::Y] < dbox.min()[NR::Y])
936             y_to = dbox.min()[NR::Y];
937         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
938             y_to = dbox.max()[NR::Y];
939         else
940             y_to = (*p)[NR::Y];
942         NR::Point const d_dt(x_to, y_to);
943         NR::Point const d_w( d_dt * _d2w );
944         NR::Point const moved_w( d_w - s_w );
946         if (autoscrollspeed == 0)
947             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
949         if (autoscrollspeed != 0)
950             scroll_world (autoscrollspeed * moved_w);
952         return true;
953     }
954     return false;
957 void
958 SPDesktop::fullscreen()
960     _widget->setFullscreen();
963 void
964 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
966     _widget->getGeometry (x, y, w, h);
969 void
970 SPDesktop::setWindowPosition (NR::Point p)
972     _widget->setPosition (p);
975 void
976 SPDesktop::setWindowSize (gint w, gint h)
978     _widget->setSize (w, h);
981 void
982 SPDesktop::setWindowTransient (void *p, int transient_policy)
984     _widget->setTransient (p, transient_policy);
987 void
988 SPDesktop::presentWindow()
990     _widget->present();
993 bool
994 SPDesktop::warnDialog (gchar *text)
996     return _widget->warnDialog (text);
999 void
1000 SPDesktop::toggleRulers()
1002     _widget->toggleRulers();
1005 void
1006 SPDesktop::toggleScrollbars()
1008     _widget->toggleScrollbars();
1011 void
1012 SPDesktop::layoutWidget()
1014     _widget->layout();
1017 void
1018 SPDesktop::destroyWidget()
1020     _widget->destroy();
1023 bool
1024 SPDesktop::shutdown()
1026     return _widget->shutdown();
1029 void
1030 SPDesktop::setToolboxFocusTo (gchar const *label)
1032     _widget->setToolboxFocusTo (label);
1035 void
1036 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1038     _widget->setToolboxAdjustmentValue (id, val);
1041 bool
1042 SPDesktop::isToolboxButtonActive (gchar const *id)
1044     return _widget->isToolboxButtonActive (id);
1047 void
1048 SPDesktop::emitToolSubselectionChanged(gpointer data)
1050         _tool_subselection_changed.emit(data);
1051         inkscape_subselection_changed (this);
1054 void
1055 SPDesktop::updateNow()
1057   sp_canvas_update_now(canvas);
1060 void
1061 SPDesktop::enableInteraction()
1063   _widget->enableInteraction();
1066 void SPDesktop::disableInteraction()
1068   _widget->disableInteraction();
1071 //----------------------------------------------------------------------
1072 // Callback implementations. The virtual ones are connected by the view.
1074 void
1075 SPDesktop::onPositionSet (double x, double y)
1077     _widget->viewSetPosition (NR::Point(x,y));
1080 void
1081 SPDesktop::onResized (double x, double y)
1083    // Nothing called here
1086 /**
1087  * Redraw callback; queues Gtk redraw; connected by View.
1088  */
1089 void
1090 SPDesktop::onRedrawRequested ()
1092     if (main) {
1093         _widget->requestCanvasUpdate();
1094     }
1097 void
1098 SPDesktop::updateCanvasNow()
1100   _widget->requestCanvasUpdateAndWait();
1103 /**
1104  * Associate document with desktop.
1105  */
1106 /// \todo fixme: refactor SPDesktop::init to use setDocument
1107 void
1108 SPDesktop::setDocument (SPDocument *doc)
1110     if (this->doc() && doc) {
1111         namedview->hide(this);
1112         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1113     }
1115     if (_layer_hierarchy) {
1116         _layer_hierarchy->clear();
1117         delete _layer_hierarchy;
1118     }
1119     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1120     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1121     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1122     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1123     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1125     /* setup EventLog */
1126     event_log = new Inkscape::EventLog(doc);
1127     doc->addUndoObserver(*event_log);
1129     _commit_connection.disconnect();
1130     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1132     /// \todo fixme: This condition exists to make sure the code
1133     /// inside is called only once on initialization. But there
1134     /// are surely more safe methods to accomplish this.
1135     if (drawing) {
1136         NRArenaItem *ai;
1138         namedview = sp_document_namedview (doc, NULL);
1139         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1140         number = namedview->getViewCount();
1142         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1143                 SP_CANVAS_ARENA (drawing)->arena,
1144                 dkey,
1145                 SP_ITEM_SHOW_DISPLAY);
1146         if (ai) {
1147             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1148             nr_arena_item_unref (ai);
1149         }
1150         namedview->show(this);
1151         /* Ugly hack */
1152         activate_guides (true);
1153         /* Ugly hack */
1154         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1155     }
1157     _document_replaced_signal.emit (this, doc);
1159     View::setDocument (doc);
1162 void
1163 SPDesktop::onStatusMessage
1164 (Inkscape::MessageType type, gchar const *message)
1166     if (_widget) {
1167         _widget->setMessage(type, message);
1168     }
1171 void
1172 SPDesktop::onDocumentURISet (gchar const* uri)
1174     _widget->setTitle(uri);
1177 /**
1178  * Resized callback.
1179  */
1180 void
1181 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1183     _doc2dt[5] = height;
1184     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1185     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1186     SP_CTRLRECT(page)->setRectangle(a);
1187     SP_CTRLRECT(page_border)->setRectangle(a);
1191 void
1192 SPDesktop::_onActivate (SPDesktop* dt)
1194     if (!dt->_widget) return;
1195     dt->_widget->activateDesktop();
1198 void
1199 SPDesktop::_onDeactivate (SPDesktop* dt)
1201     if (!dt->_widget) return;
1202     dt->_widget->deactivateDesktop();
1205 void
1206 SPDesktop::_onSelectionModified
1207 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1209     if (!dt->_widget) return;
1210     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1213 static void
1214 _onSelectionChanged
1215 (Inkscape::Selection *selection, SPDesktop *desktop)
1217     /** \todo
1218      * only change the layer for single selections, or what?
1219      * This seems reasonable -- for multiple selections there can be many
1220      * different layers involved.
1221      */
1222     SPItem *item=selection->singleItem();
1223     if (item) {
1224         SPObject *layer=desktop->layerForObject(item);
1225         if ( layer && layer != desktop->currentLayer() ) {
1226             desktop->setCurrentLayer(layer);
1227         }
1228     }
1231 /**
1232  * Calls event handler of current event context.
1233  * \param arena Unused
1234  * \todo fixme
1235  */
1236 static gint
1237 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1239     if (ai) {
1240         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1241         return sp_event_context_item_handler (desktop->event_context, spi, event);
1242     } else {
1243         return sp_event_context_root_handler (desktop->event_context, event);
1244     }
1247 static void
1248 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1249     g_return_if_fail(SP_IS_GROUP(layer));
1250     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1253 /// Callback
1254 static void
1255 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1256     g_return_if_fail(SP_IS_GROUP(layer));
1257     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1260 /// Callback
1261 static void
1262 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1263                                          SPDesktop *desktop)
1265     desktop->_layer_changed_signal.emit (bottom);
1268 /// Called when document is starting to be rebuilt.
1269 static void
1270 _reconstruction_start (SPDesktop * desktop)
1272     // printf("Desktop, starting reconstruction\n");
1273     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1274     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1276     /*
1277     GSList const * selection_objs = desktop->selection->list();
1278     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1280     }
1281     */
1282     desktop->selection->clear();
1284     // printf("Desktop, starting reconstruction end\n");
1287 /// Called when document rebuild is finished.
1288 static void
1289 _reconstruction_finish (SPDesktop * desktop)
1291     // printf("Desktop, finishing reconstruction\n");
1292     if (desktop->_reconstruction_old_layer_id == NULL)
1293         return;
1295     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1296     if (newLayer != NULL)
1297         desktop->setCurrentLayer(newLayer);
1299     g_free(desktop->_reconstruction_old_layer_id);
1300     desktop->_reconstruction_old_layer_id = NULL;
1301     // printf("Desktop, finishing reconstruction end\n");
1302     return;
1305 /**
1306  * Namedview_modified callback.
1307  */
1308 static void
1309 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1311     SPNamedView *nv=SP_NAMEDVIEW(obj);
1313     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1315         /* Recalculate snap distances */
1316         /* FIXME: why is the desktop getting involved in setting up something
1317         ** that is entirely to do with the namedview?
1318         */
1319         _update_snap_distances (desktop);
1321         /* Show/hide page background */
1322         if (nv->pagecolor & 0xff) {
1323             sp_canvas_item_show (desktop->table);
1324             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1325             sp_canvas_item_move_to_z (desktop->table, 0);
1326         } else {
1327             sp_canvas_item_hide (desktop->table);
1328         }
1330         /* Show/hide page border */
1331         if (nv->showborder) {
1332             // show
1333             sp_canvas_item_show (desktop->page_border);
1334             // set color and shadow
1335             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1336             if (nv->pageshadow) {
1337                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1338             }
1339             // place in the z-order stack
1340             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1341                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1342             } else {
1343                 int order = sp_canvas_item_order (desktop->page_border);
1344                 int morder = sp_canvas_item_order (desktop->drawing);
1345                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1346                                     morder - order);
1347             }
1348         } else {
1349                 sp_canvas_item_hide (desktop->page_border);
1350                 if (nv->pageshadow) {
1351                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1352                 }
1353         }
1354         
1355         /* Show/hide page shadow */
1356         if (nv->showpageshadow && nv->pageshadow) {
1357             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1358         } else {
1359             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1360         }
1362         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1363             (SP_RGBA32_R_U(nv->pagecolor) +
1364              SP_RGBA32_G_U(nv->pagecolor) +
1365              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1366             // the background color is light or transparent, use black outline
1367             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1368         } else { // use white outline
1369             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1370         }
1371     }
1374 /**
1375  * Callback to reset snapper's distances.
1376  */
1377 static void
1378 _update_snap_distances (SPDesktop *desktop)
1380     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1382     SPNamedView &nv = *desktop->namedview;
1384     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1385                                                                       *nv.gridtoleranceunit,
1386                                                                       px));
1387     nv.snap_manager.axonomgrid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1388                                                                       *nv.gridtoleranceunit,
1389                                                                       px));
1390     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1391                                                                        *nv.guidetoleranceunit,
1392                                                                        px));
1393     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1394                                                                         *nv.objecttoleranceunit,
1395                                                                         px));
1399 NR::Matrix SPDesktop::w2d() const
1401     return _w2d;
1404 NR::Point SPDesktop::w2d(NR::Point const &p) const
1406     return p * _w2d;
1409 NR::Point SPDesktop::d2w(NR::Point const &p) const
1411     return p * _d2w;
1414 NR::Matrix SPDesktop::doc2dt() const
1416     return _doc2dt;
1419 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1421     return p * _doc2dt;
1424 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1426     return p / _doc2dt;
1430 /**
1431  * Pop event context from desktop's context stack. Never used.
1432  */
1433 // void
1434 // SPDesktop::pop_event_context (unsigned int key)
1435 // {
1436 //    SPEventContext *ec = NULL;
1437 //
1438 //    if (event_context && event_context->key == key) {
1439 //        g_return_if_fail (event_context);
1440 //        g_return_if_fail (event_context->next);
1441 //        ec = event_context;
1442 //        sp_event_context_deactivate (ec);
1443 //        event_context = ec->next;
1444 //        sp_event_context_activate (event_context);
1445 //        _event_context_changed_signal.emit (this, ec);
1446 //    }
1447 //
1448 //    SPEventContext *ref = event_context;
1449 //    while (ref && ref->next && ref->next->key != key)
1450 //        ref = ref->next;
1451 //
1452 //    if (ref && ref->next) {
1453 //        ec = ref->next;
1454 //        ref->next = ec->next;
1455 //    }
1456 //
1457 //    if (ec) {
1458 //        sp_event_context_finish (ec);
1459 //        g_object_unref (G_OBJECT (ec));
1460 //    }
1461 // }
1463 /*
1464   Local Variables:
1465   mode:c++
1466   c-file-style:"stroustrup"
1467   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1468   indent-tabs-mode:nil
1469   fill-column:99
1470   End:
1471 */
1472 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :