Code

New file. use for building with gtk28
[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  *
13  * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
14  * Copyright (C) 2006 John Bintz
15  * Copyright (C) 2004 MenTaLguY
16  * Copyright (C) 1999-2002 Lauris Kaplinski
17  * Copyright (C) 2000-2001 Ximian, Inc.
18  *
19  * Released under GNU GPL, read the file 'COPYING' for more information
20  */
22 /** \class SPDesktop
23  * SPDesktop is a subclass of View, implementing an editable document
24  * canvas.  It is extensively used by many UI controls that need certain
25  * visual representations of their own.
26  *
27  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
28  * layers of different control objects. The one containing the whole
29  * document is the drawing layer. In addition to it, there are grid,
30  * guide, sketch and control layers. The sketch layer is used for
31  * temporary drawing objects, before the real objects in document are
32  * created. The control layer contains editing knots, rubberband and
33  * similar non-document UI objects.
34  *
35  * Each SPDesktop is associated with a SPNamedView node of the document
36  * tree.  Currently, all desktops are created from a single main named
37  * view, but in the future there may be support for different ones.
38  * SPNamedView serves as an in-document container for desktop-related
39  * data, like grid and guideline placement, snapping options and so on.
40  *
41  * Associated with each SPDesktop are the two most important editing
42  * related objects - SPSelection and SPEventContext.
43  *
44  * Sodipodi keeps track of the active desktop and invokes notification
45  * signals whenever it changes. UI elements can use these to update their
46  * display to the selection of the currently active editing window.
47  * (Lauris Kaplinski)
48  */
50 #ifdef HAVE_CONFIG_H
51 # include "config.h"
52 #endif
54 #include <glibmm/i18n.h>
55 #include <sigc++/functors/mem_fun.h>
57 #include "macros.h"
58 #include "inkscape-private.h"
59 #include "desktop.h"
60 #include "desktop-events.h"
61 #include "desktop-handles.h"
62 #include "document.h"
63 #include "message-stack.h"
64 #include "selection.h"
65 #include "select-context.h"
66 #include "sp-namedview.h"
67 #include "color.h"
68 #include "sp-item-group.h"
69 #include "prefs-utils.h"
70 #include "object-hierarchy.h"
71 #include "helper/units.h"
72 #include "display/canvas-arena.h"
73 #include "display/nr-arena.h"
74 #include "display/gnome-canvas-acetate.h"
75 #include "display/sodipodi-ctrlrect.h"
76 #include "display/sp-canvas-util.h"
77 #include "libnr/nr-matrix-div.h"
78 #include "libnr/nr-rect-ops.h"
79 #include "ui/dialog/dialog-manager.h"
80 #include "xml/repr.h"
81 #include "message-context.h"
82 #include "layer-manager.h"
83 #include "event-log.h"
85 namespace Inkscape { namespace XML { class Node; }}
87 // Callback declarations
88 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
89 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
90 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
91 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
92 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
93 static void _reconstruction_start(SPDesktop * desktop);
94 static void _reconstruction_finish(SPDesktop * desktop);
95 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
96 static void _update_snap_distances (SPDesktop *desktop);
98 /**
99  * Return new desktop object.
100  * \pre namedview != NULL.
101  * \pre canvas != NULL.
102  */
103 SPDesktop::SPDesktop()
105     _dlg_mgr = NULL;
106     _widget = 0;
107     namedview = NULL;
108     selection = NULL;
109     acetate = NULL;
110     main = NULL;
111     grid = NULL;
112     guides = NULL;
113     drawing = NULL;
114     sketch = NULL;
115     controls = NULL;
116     event_context = 0;
117     layer_manager = 0;
119     _d2w.set_identity();
120     _w2d.set_identity();
121     _doc2dt = NR::Matrix(NR::scale(1, -1));
123     guides_active = false;
125     zooms_past = NULL;
126     zooms_future = NULL;
128     is_fullscreen = false;
130     gr_item = NULL;
131     gr_point_num = 0;
132     gr_fill_or_stroke = true;
134     _layer_hierarchy = NULL;
135     _active = false;
137     selection = Inkscape::GC::release (new Inkscape::Selection (this));
140 void
141 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
144     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
146     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
148     namedview = nv;
149     canvas = aCanvas;
151     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
152     /* Kill flicker */
153     sp_document_ensure_up_to_date (document);
155     /* Setup Dialog Manager */
156     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
158     dkey = sp_item_display_key_new (1);
160     /* Connect document */
161     setDocument (document);
163     number = namedview->getViewCount();
166     /* Setup Canvas */
167     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
169     SPCanvasGroup *root = sp_canvas_root (canvas);
171     /* Setup adminstrative layers */
172     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
173     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
174     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
175     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
177     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
178     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
179     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
180     sp_canvas_item_move_to_z (table, 0);
182     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
183     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
184     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
186     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
187     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
189     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
191     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
192         // Start in outline mode
193         setDisplayModeOutline();
194     } else {
195         // Start in normal mode, default
196         setDisplayModeNormal();
197     }
199     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
200     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
201     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
202     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
204     /* Push select tool to the bottom of stack */
205     /** \todo
206      * FIXME: this is the only call to this.  Everything else seems to just
207      * call "set" instead of "push".  Can we assume that there is only one
208      * context ever?
209      */
210     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
212     // display rect and zoom are now handled in sp_desktop_widget_realize()
214     NR::Rect const d(NR::Point(0.0, 0.0),
215                      NR::Point(sp_document_width(document), sp_document_height(document)));
217     SP_CTRLRECT(page)->setRectangle(d);
218     SP_CTRLRECT(page_border)->setRectangle(d);
220     /* the following sets the page shadow on the canvas
221        It was originally set to 5, which is really cheesy!
222        It now is an attribute in the document's namedview. If a value of
223        0 is used, then the constructor for a shadow is not initialized.
224     */
226     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
227         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
228     }
231     /* Connect event for page resize */
232     _doc2dt[5] = sp_document_height (document);
233     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
235     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
237     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
238             SP_CANVAS_ARENA (drawing)->arena,
239             dkey,
240             SP_ITEM_SHOW_DISPLAY);
241     if (ai) {
242         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
243         nr_arena_item_unref (ai);
244     }
246     namedview->show(this);
247     /* Ugly hack */
248     activate_guides (true);
249     /* Ugly hack */
250     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
252 /* Set up notification of rebuilding the document, this allows
253        for saving object related settings in the document. */
254     _reconstruction_start_connection =
255         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
256     _reconstruction_finish_connection =
257         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
258     _reconstruction_old_layer_id = NULL;
259     
260     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
261     
262     // ?
263     // sp_active_desktop_set (desktop);
264     _inkscape = INKSCAPE;
266     _activate_connection = _activate_signal.connect(
267         sigc::bind(
268             sigc::ptr_fun(_onActivate),
269             this
270         )
271     );
272      _deactivate_connection = _deactivate_signal.connect(
273         sigc::bind(
274             sigc::ptr_fun(_onDeactivate),
275             this
276         )
277     );
279     _sel_modified_connection = selection->connectModified(
280         sigc::bind(
281             sigc::ptr_fun(&_onSelectionModified),
282             this
283         )
284     );
285     _sel_changed_connection = selection->connectChanged(
286         sigc::bind(
287             sigc::ptr_fun(&_onSelectionChanged),
288             this
289         )
290     );
293     /* setup LayerManager */
294     //   (Setting up after the connections are all in place, as it may use some of them)
295     layer_manager = new Inkscape::LayerManager( this );
299 void SPDesktop::destroy()
301     _activate_connection.disconnect();
302     _deactivate_connection.disconnect();
303     _sel_modified_connection.disconnect();
304     _sel_changed_connection.disconnect();
305     _modified_connection.disconnect();
306     _commit_connection.disconnect();
307     _reconstruction_start_connection.disconnect();
308     _reconstruction_finish_connection.disconnect();
310     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
311     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
312     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
314     while (event_context) {
315         SPEventContext *ec = event_context;
316         event_context = ec->next;
317         sp_event_context_finish (ec);
318         g_object_unref (G_OBJECT (ec));
319     }
321     if (_layer_hierarchy) {
322         delete _layer_hierarchy;
323     }
325     if (_inkscape) {
326         _inkscape = NULL;
327     }
329     if (drawing) {
330         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
331         drawing = NULL;
332     }
334     delete _guides_message_context;
335     _guides_message_context = NULL;
337     g_list_free (zooms_past);
338     g_list_free (zooms_future);
341 SPDesktop::~SPDesktop() {}
343 //--------------------------------------------------------------------
344 /* Public methods */
346 void SPDesktop::setDisplayModeNormal()
348     prefs_set_int_attribute("options.outlinemode", "value", 0);
349     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
350     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
351     displayMode = RENDERMODE_NORMAL;
352     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
353     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
356 void SPDesktop::setDisplayModeOutline()
358     prefs_set_int_attribute("options.outlinemode", "value", 1);
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 (prefs_get_int_attribute("options.outlinemode", "value", prefs_get_int_attribute("options.startmode", "outline", 0)))
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::Rect const bbox = sp_item_bbox_desktop(item);
432     return viewport.contains(bbox);
435 ///
436 bool SPDesktop::itemIsHidden(SPItem const *item) const {
437     return item->isHidden(this->dkey);
440 /**
441  * Set activate property of desktop; emit signal if changed.
442  */
443 void
444 SPDesktop::set_active (bool new_active)
446     if (new_active != _active) {
447         _active = new_active;
448         if (new_active) {
449             _activate_signal.emit();
450         } else {
451             _deactivate_signal.emit();
452         }
453     }
456 /**
457  * Set activate status of current desktop's named view.
458  */
459 void
460 SPDesktop::activate_guides(bool activate)
462     guides_active = activate;
463     namedview->activateGuides(this, activate);
466 /**
467  * Make desktop switch documents.
468  */
469 void
470 SPDesktop::change_document (SPDocument *theDocument)
472     g_return_if_fail (theDocument != NULL);
474     /* unselect everything before switching documents */
475     selection->clear();
477     setDocument (theDocument);
478     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
479     _document_replaced_signal.emit (this, theDocument);
482 /**
483  * Make desktop switch event contexts.
484  */
485 void
486 SPDesktop::set_event_context (GtkType type, const gchar *config)
488     SPEventContext *ec;
489     while (event_context) {
490         ec = event_context;
491         sp_event_context_deactivate (ec);
492         event_context = ec->next;
493         sp_event_context_finish (ec);
494         g_object_unref (G_OBJECT (ec));
495     }
497     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
498     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
499     ec->next = event_context;
500     event_context = ec;
501     sp_event_context_activate (ec);
502     _event_context_changed_signal.emit (this, ec);
505 /**
506  * Push event context onto desktop's context stack.
507  */
508 void
509 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
511     SPEventContext *ref, *ec;
512     Inkscape::XML::Node *repr;
514     if (event_context && event_context->key == key) return;
515     ref = event_context;
516     while (ref && ref->next && ref->next->key != key) ref = ref->next;
517     if (ref && ref->next) {
518         ec = ref->next;
519         ref->next = ec->next;
520         sp_event_context_finish (ec);
521         g_object_unref (G_OBJECT (ec));
522     }
524     if (event_context) sp_event_context_deactivate (event_context);
525     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
526     ec = sp_event_context_new (type, this, repr, key);
527     ec->next = event_context;
528     event_context = ec;
529     sp_event_context_activate (ec);
530     _event_context_changed_signal.emit (this, ec);
533 /**
534  * Sets the coordinate status to a given point
535  */
536 void
537 SPDesktop::set_coordinate_status (NR::Point p) {
538     _widget->setCoordinateStatus(p);
541 /**
542  * \see sp_document_item_from_list_at_point_bottom()
543  */
544 SPItem *
545 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
547     g_return_val_if_fail (doc() != NULL, NULL);
548     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
551 /**
552  * \see sp_document_item_at_point()
553  */
554 SPItem *
555 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
557     g_return_val_if_fail (doc() != NULL, NULL);
558     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
561 /**
562  * \see sp_document_group_at_point()
563  */
564 SPItem *
565 SPDesktop::group_at_point (NR::Point const p) const
567     g_return_val_if_fail (doc() != NULL, NULL);
568     return sp_document_group_at_point (doc(), dkey, p);
571 /**
572  * \brief  Returns the mouse point in document coordinates; if mouse is
573  * outside the canvas, returns the center of canvas viewpoint
574  */
575 NR::Point
576 SPDesktop::point() const
578     NR::Point p = _widget->getPointer();
579     NR::Point pw = sp_canvas_window_to_world (canvas, p);
580     p = w2d(pw);
582     NR::Rect const r = canvas->getViewbox();
584     NR::Point r0 = w2d(r.min());
585     NR::Point r1 = w2d(r.max());
587     if (p[NR::X] >= r0[NR::X] &&
588         p[NR::X] <= r1[NR::X] &&
589         p[NR::Y] >= r1[NR::Y] &&
590         p[NR::Y] <= r0[NR::Y])
591     {
592         return p;
593     } else {
594         return (r0 + r1) / 2;
595     }
598 /**
599  * Put current zoom data in history list.
600  */
601 void
602 SPDesktop::push_current_zoom (GList **history)
604     NR::Rect const area = get_display_area();
606     NRRect *old_zoom = g_new(NRRect, 1);
607     old_zoom->x0 = area.min()[NR::X];
608     old_zoom->x1 = area.max()[NR::X];
609     old_zoom->y0 = area.min()[NR::Y];
610     old_zoom->y1 = area.max()[NR::Y];
611     if ( *history == NULL
612          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
613                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
614                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
615                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
616     {
617         *history = g_list_prepend (*history, old_zoom);
618     }
621 /**
622  * Set viewbox.
623  */
624 void
625 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
627     g_assert(_widget);
629     // save the zoom
630     if (log) {
631         push_current_zoom(&zooms_past);
632         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
633         g_list_free (zooms_future);
634         zooms_future = NULL;
635     }
637     double const cx = 0.5 * (x0 + x1);
638     double const cy = 0.5 * (y0 + y1);
640     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
642     double scale = expansion(_d2w);
643     double newscale;
644     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
645         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
646     } else {
647         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
648     }
650     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
652     int clear = FALSE;
653     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
654         /* Set zoom factors */
655         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
656         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
657         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
658         clear = TRUE;
659     }
661     /* Calculate top left corner */
662     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
663     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
665     /* Scroll */
666     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
668     _widget->updateRulers();
669     _widget->updateScrollbars(expansion(_d2w));
670     _widget->updateZoom();
673 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
675     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
678 /**
679  * Return viewbox dimensions.
680  */
681 NR::Rect SPDesktop::get_display_area() const
683     NR::Rect const viewbox = canvas->getViewbox();
685     double const scale = _d2w[0];
687     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
688                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
691 /**
692  * Revert back to previous zoom if possible.
693  */
694 void
695 SPDesktop::prev_zoom()
697     if (zooms_past == NULL) {
698         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
699         return;
700     }
702     // push current zoom into forward zooms list
703     push_current_zoom (&zooms_future);
705     // restore previous zoom
706     set_display_area (((NRRect *) zooms_past->data)->x0,
707             ((NRRect *) zooms_past->data)->y0,
708             ((NRRect *) zooms_past->data)->x1,
709             ((NRRect *) zooms_past->data)->y1,
710             0, false);
712     // remove the just-added zoom from the past zooms list
713     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
716 /**
717  * Set zoom to next in list.
718  */
719 void
720 SPDesktop::next_zoom()
722     if (zooms_future == NULL) {
723         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
724         return;
725     }
727     // push current zoom into past zooms list
728     push_current_zoom (&zooms_past);
730     // restore next zoom
731     set_display_area (((NRRect *) zooms_future->data)->x0,
732             ((NRRect *) zooms_future->data)->y0,
733             ((NRRect *) zooms_future->data)->x1,
734             ((NRRect *) zooms_future->data)->y1,
735             0, false);
737     // remove the just-used zoom from the zooms_future list
738     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
741 /**
742  * Zoom to point with absolute zoom factor.
743  */
744 void
745 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
747     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
749     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
750     // this check prevents "sliding" when trying to zoom in at maximum zoom;
751     /// \todo someone please fix calculations properly and remove this hack
752     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
753         return;
755     NR::Rect const viewbox = canvas->getViewbox();
757     double const width2 = viewbox.dimensions()[NR::X] / zoom;
758     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
760     set_display_area(cx - px * width2,
761                      cy - py * height2,
762                      cx + (1 - px) * width2,
763                      cy + (1 - py) * height2,
764                      0.0);
767 /**
768  * Zoom to center with absolute zoom factor.
769  */
770 void
771 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
773     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
776 /**
777  * Zoom to point with relative zoom factor.
778  */
779 void
780 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
782     NR::Rect const area = get_display_area();
784     if (cx < area.min()[NR::X]) {
785         cx = area.min()[NR::X];
786     }
787     if (cx > area.max()[NR::X]) {
788         cx = area.max()[NR::X];
789     }
790     if (cy < area.min()[NR::Y]) {
791         cy = area.min()[NR::Y];
792     }
793     if (cy > area.max()[NR::Y]) {
794         cy = area.max()[NR::Y];
795     }
797     gdouble const scale = expansion(_d2w) * zoom;
798     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
799     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
801     zoom_absolute_keep_point(cx, cy, px, py, scale);
804 /**
805  * Zoom to center with relative zoom factor.
806  */
807 void
808 SPDesktop::zoom_relative (double cx, double cy, double zoom)
810     gdouble scale = expansion(_d2w) * zoom;
811     zoom_absolute (cx, cy, scale);
814 /**
815  * Set display area to origin and current document dimensions.
816  */
817 void
818 SPDesktop::zoom_page()
820     NR::Rect d(NR::Point(0, 0),
821                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
823     if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) {
824         return;
825     }
827     set_display_area(d, 10);
830 /**
831  * Set display area to current document width.
832  */
833 void
834 SPDesktop::zoom_page_width()
836     NR::Rect const a = get_display_area();
838     if (sp_document_width(doc()) < 1.0) {
839         return;
840     }
842     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
843                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
845     set_display_area(d, 10);
848 /**
849  * Zoom to selection.
850  */
851 void
852 SPDesktop::zoom_selection()
854     NR::Rect const d = selection->bounds();
856     if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) {
857         return;
858     }
860     set_display_area(d, 10);
863 /**
864  * Tell widget to let zoom widget grab keyboard focus.
865  */
866 void
867 SPDesktop::zoom_grab_focus()
869     _widget->letZoomGrabFocus();
872 /**
873  * Zoom to whole drawing.
874  */
875 void
876 SPDesktop::zoom_drawing()
878     g_return_if_fail (doc() != NULL);
879     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
880     g_return_if_fail (docitem != NULL);
882     NR::Rect d = sp_item_bbox_desktop(docitem);
884     /* Note that the second condition here indicates that
885     ** there are no items in the drawing.
886     */
887     if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) {
888         return;
889     }
891     set_display_area(d, 10);
894 /**
895  * Scroll canvas by specific coordinate amount.
896  */
897 void
898 SPDesktop::scroll_world (double dx, double dy)
900     g_assert(_widget);
902     NR::Rect const viewbox = canvas->getViewbox();
904     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE);
906     _widget->updateRulers();
907     _widget->updateScrollbars(expansion(_d2w));
910 bool
911 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
913     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
915     // autoscrolldistance is in screen pixels, but the display area is in document units
916     autoscrolldistance /= expansion(_d2w);
917     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
919     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
920         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
922         NR::Point const s_w( (*p) * _d2w );
924         gdouble x_to;
925         if ((*p)[NR::X] < dbox.min()[NR::X])
926             x_to = dbox.min()[NR::X];
927         else if ((*p)[NR::X] > dbox.max()[NR::X])
928             x_to = dbox.max()[NR::X];
929         else
930             x_to = (*p)[NR::X];
932         gdouble y_to;
933         if ((*p)[NR::Y] < dbox.min()[NR::Y])
934             y_to = dbox.min()[NR::Y];
935         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
936             y_to = dbox.max()[NR::Y];
937         else
938             y_to = (*p)[NR::Y];
940         NR::Point const d_dt(x_to, y_to);
941         NR::Point const d_w( d_dt * _d2w );
942         NR::Point const moved_w( d_w - s_w );
944         if (autoscrollspeed == 0)
945             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
947         if (autoscrollspeed != 0)
948             scroll_world (autoscrollspeed * moved_w);
950         return true;
951     }
952     return false;
955 void
956 SPDesktop::fullscreen()
958     _widget->setFullscreen();
961 void
962 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
964     _widget->getGeometry (x, y, w, h);
967 void
968 SPDesktop::setWindowPosition (NR::Point p)
970     _widget->setPosition (p);
973 void
974 SPDesktop::setWindowSize (gint w, gint h)
976     _widget->setSize (w, h);
979 void
980 SPDesktop::setWindowTransient (void *p, int transient_policy)
982     _widget->setTransient (p, transient_policy);
985 void
986 SPDesktop::presentWindow()
988     _widget->present();
991 bool
992 SPDesktop::warnDialog (gchar *text)
994     return _widget->warnDialog (text);
997 void
998 SPDesktop::toggleRulers()
1000     _widget->toggleRulers();
1003 void
1004 SPDesktop::toggleScrollbars()
1006     _widget->toggleScrollbars();
1009 void
1010 SPDesktop::layoutWidget()
1012     _widget->layout();
1015 void
1016 SPDesktop::destroyWidget()
1018     _widget->destroy();
1021 bool
1022 SPDesktop::shutdown()
1024     return _widget->shutdown();
1027 void
1028 SPDesktop::setToolboxFocusTo (gchar const *label)
1030     _widget->setToolboxFocusTo (label);
1033 void
1034 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1036     _widget->setToolboxAdjustmentValue (id, val);
1039 bool
1040 SPDesktop::isToolboxButtonActive (gchar const *id)
1042     return _widget->isToolboxButtonActive (id);
1045 void
1046 SPDesktop::emitToolSubselectionChanged(gpointer data)
1048         _tool_subselection_changed.emit(data);
1049         inkscape_subselection_changed (this);
1052 void
1053 SPDesktop::updateNow()
1055   sp_canvas_update_now(canvas);
1058 void
1059 SPDesktop::enableInteraction()
1061   _widget->enableInteraction();
1064 void SPDesktop::disableInteraction()
1066   _widget->disableInteraction();
1069 //----------------------------------------------------------------------
1070 // Callback implementations. The virtual ones are connected by the view.
1072 void
1073 SPDesktop::onPositionSet (double x, double y)
1075     _widget->viewSetPosition (NR::Point(x,y));
1078 void
1079 SPDesktop::onResized (double x, double y)
1081    // Nothing called here
1084 /**
1085  * Redraw callback; queues Gtk redraw; connected by View.
1086  */
1087 void
1088 SPDesktop::onRedrawRequested ()
1090     if (main) {
1091         _widget->requestCanvasUpdate();
1092     }
1095 void
1096 SPDesktop::updateCanvasNow()
1098   _widget->requestCanvasUpdateAndWait();
1101 /**
1102  * Associate document with desktop.
1103  */
1104 /// \todo fixme: refactor SPDesktop::init to use setDocument
1105 void
1106 SPDesktop::setDocument (SPDocument *doc)
1108     if (this->doc() && doc) {
1109         namedview->hide(this);
1110         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1111     }
1113     if (_layer_hierarchy) {
1114         _layer_hierarchy->clear();
1115         delete _layer_hierarchy;
1116     }
1117     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1118     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1119     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1120     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1121     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1123     /* setup EventLog */
1124     event_log = new Inkscape::EventLog(doc);
1125     doc->addUndoObserver(*event_log);
1127     _commit_connection.disconnect();
1128     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1130     /// \todo fixme: This condition exists to make sure the code
1131     /// inside is called only once on initialization. But there
1132     /// are surely more safe methods to accomplish this.
1133     if (drawing) {
1134         NRArenaItem *ai;
1136         namedview = sp_document_namedview (doc, NULL);
1137         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1138         number = namedview->getViewCount();
1140         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1141                 SP_CANVAS_ARENA (drawing)->arena,
1142                 dkey,
1143                 SP_ITEM_SHOW_DISPLAY);
1144         if (ai) {
1145             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1146             nr_arena_item_unref (ai);
1147         }
1148         namedview->show(this);
1149         /* Ugly hack */
1150         activate_guides (true);
1151         /* Ugly hack */
1152         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1153     }
1155     _document_replaced_signal.emit (this, doc);
1157     View::setDocument (doc);
1160 void
1161 SPDesktop::onStatusMessage
1162 (Inkscape::MessageType type, gchar const *message)
1164     if (_widget) {
1165         _widget->setMessage(type, message);
1166     }
1169 void
1170 SPDesktop::onDocumentURISet (gchar const* uri)
1172     _widget->setTitle(uri);
1175 /**
1176  * Resized callback.
1177  */
1178 void
1179 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1181     _doc2dt[5] = height;
1182     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1183     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1184     SP_CTRLRECT(page)->setRectangle(a);
1185     SP_CTRLRECT(page_border)->setRectangle(a);
1189 void
1190 SPDesktop::_onActivate (SPDesktop* dt)
1192     if (!dt->_widget) return;
1193     dt->_widget->activateDesktop();
1196 void
1197 SPDesktop::_onDeactivate (SPDesktop* dt)
1199     if (!dt->_widget) return;
1200     dt->_widget->deactivateDesktop();
1203 void
1204 SPDesktop::_onSelectionModified
1205 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1207     if (!dt->_widget) return;
1208     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1211 static void
1212 _onSelectionChanged
1213 (Inkscape::Selection *selection, SPDesktop *desktop)
1215     /** \todo
1216      * only change the layer for single selections, or what?
1217      * This seems reasonable -- for multiple selections there can be many
1218      * different layers involved.
1219      */
1220     SPItem *item=selection->singleItem();
1221     if (item) {
1222         SPObject *layer=desktop->layerForObject(item);
1223         if ( layer && layer != desktop->currentLayer() ) {
1224             desktop->setCurrentLayer(layer);
1225         }
1226     }
1229 /**
1230  * Calls event handler of current event context.
1231  * \param arena Unused
1232  * \todo fixme
1233  */
1234 static gint
1235 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1237     if (ai) {
1238         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1239         return sp_event_context_item_handler (desktop->event_context, spi, event);
1240     } else {
1241         return sp_event_context_root_handler (desktop->event_context, event);
1242     }
1245 static void
1246 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1247     g_return_if_fail(SP_IS_GROUP(layer));
1248     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1251 /// Callback
1252 static void
1253 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1254     g_return_if_fail(SP_IS_GROUP(layer));
1255     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1258 /// Callback
1259 static void
1260 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1261                                          SPDesktop *desktop)
1263     desktop->_layer_changed_signal.emit (bottom);
1266 /// Called when document is starting to be rebuilt.
1267 static void
1268 _reconstruction_start (SPDesktop * desktop)
1270     // printf("Desktop, starting reconstruction\n");
1271     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1272     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1274     /*
1275     GSList const * selection_objs = desktop->selection->list();
1276     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1278     }
1279     */
1280     desktop->selection->clear();
1282     // printf("Desktop, starting reconstruction end\n");
1285 /// Called when document rebuild is finished.
1286 static void
1287 _reconstruction_finish (SPDesktop * desktop)
1289     // printf("Desktop, finishing reconstruction\n");
1290     if (desktop->_reconstruction_old_layer_id == NULL)
1291         return;
1293     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1294     if (newLayer != NULL)
1295         desktop->setCurrentLayer(newLayer);
1297     g_free(desktop->_reconstruction_old_layer_id);
1298     desktop->_reconstruction_old_layer_id = NULL;
1299     // printf("Desktop, finishing reconstruction end\n");
1300     return;
1303 /**
1304  * Namedview_modified callback.
1305  */
1306 static void
1307 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1309     SPNamedView *nv=SP_NAMEDVIEW(obj);
1311     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1313         /* Recalculate snap distances */
1314         /* FIXME: why is the desktop getting involved in setting up something
1315         ** that is entirely to do with the namedview?
1316         */
1317         _update_snap_distances (desktop);
1319         /* Show/hide page background */
1320         if (nv->pagecolor & 0xff) {
1321             sp_canvas_item_show (desktop->table);
1322             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1323             sp_canvas_item_move_to_z (desktop->table, 0);
1324         } else {
1325             sp_canvas_item_hide (desktop->table);
1326         }
1328         /* Show/hide page border */
1329         if (nv->showborder) {
1330             // show
1331             sp_canvas_item_show (desktop->page_border);
1332             // set color and shadow
1333             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1334             if (nv->pageshadow) {
1335                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1336             }
1337             // place in the z-order stack
1338             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1339                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1340             } else {
1341                 int order = sp_canvas_item_order (desktop->page_border);
1342                 int morder = sp_canvas_item_order (desktop->drawing);
1343                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1344                                     morder - order);
1345             }
1346         } else {
1347                 sp_canvas_item_hide (desktop->page_border);
1348                 if (nv->pageshadow) {
1349                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1350                 }
1351         }
1352         
1353         /* Show/hide page shadow */
1354         if (nv->showpageshadow && nv->pageshadow) {
1355             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1356         } else {
1357             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1358         }
1360         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1361             (SP_RGBA32_R_U(nv->pagecolor) +
1362              SP_RGBA32_G_U(nv->pagecolor) +
1363              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1364             // the background color is light or transparent, use black outline
1365             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1366         } else { // use white outline
1367             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1368         }
1369     }
1372 /**
1373  * Callback to reset snapper's distances.
1374  */
1375 static void
1376 _update_snap_distances (SPDesktop *desktop)
1378     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1380     SPNamedView &nv = *desktop->namedview;
1382     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1383                                                                       *nv.gridtoleranceunit,
1384                                                                       px));
1385     nv.snap_manager.axonomgrid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1386                                                                       *nv.gridtoleranceunit,
1387                                                                       px));
1388     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1389                                                                        *nv.guidetoleranceunit,
1390                                                                        px));
1391     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1392                                                                         *nv.objecttoleranceunit,
1393                                                                         px));
1397 NR::Matrix SPDesktop::w2d() const
1399     return _w2d;
1402 NR::Point SPDesktop::w2d(NR::Point const &p) const
1404     return p * _w2d;
1407 NR::Point SPDesktop::d2w(NR::Point const &p) const
1409     return p * _d2w;
1412 NR::Matrix SPDesktop::doc2dt() const
1414     return _doc2dt;
1417 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1419     return p * _doc2dt;
1422 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1424     return p / _doc2dt;
1428 /**
1429  * Pop event context from desktop's context stack. Never used.
1430  */
1431 // void
1432 // SPDesktop::pop_event_context (unsigned int key)
1433 // {
1434 //    SPEventContext *ec = NULL;
1435 //
1436 //    if (event_context && event_context->key == key) {
1437 //        g_return_if_fail (event_context);
1438 //        g_return_if_fail (event_context->next);
1439 //        ec = event_context;
1440 //        sp_event_context_deactivate (ec);
1441 //        event_context = ec->next;
1442 //        sp_event_context_activate (event_context);
1443 //        _event_context_changed_signal.emit (this, ec);
1444 //    }
1445 //
1446 //    SPEventContext *ref = event_context;
1447 //    while (ref && ref->next && ref->next->key != key)
1448 //        ref = ref->next;
1449 //
1450 //    if (ref && ref->next) {
1451 //        ec = ref->next;
1452 //        ref->next = ec->next;
1453 //    }
1454 //
1455 //    if (ec) {
1456 //        sp_event_context_finish (ec);
1457 //        g_object_unref (G_OBJECT (ec));
1458 //    }
1459 // }
1461 /*
1462   Local Variables:
1463   mode:c++
1464   c-file-style:"stroustrup"
1465   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1466   indent-tabs-mode:nil
1467   fill-column:99
1468   End:
1469 */
1470 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :