Code

Add translator hint.
[inkscape.git] / src / knot.cpp
1 #define __SP_KNOT_C__
3 /** \file
4  * SPKnot implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * Copyright (C) 1999-2005 authors
11  * Copyright (C) 2001-2002 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
19 #include <gdk/gdkkeysyms.h>
20 #include <glibmm/i18n.h>
21 #include "helper/sp-marshal.h"
22 #include "display/sodipodi-ctrl.h"
23 #include "desktop.h"
24 #include "desktop-handles.h"
25 #include "knot.h"
26 #include "document.h"
27 #include "prefs-utils.h"
28 #include "message-stack.h"
29 #include "message-context.h"
30 #include "event-context.h"
31 #include "sp-namedview.h"
32 #include "snap.h"
33 #include "selection.h"
36 #define KNOT_EVENT_MASK (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
37                          GDK_POINTER_MOTION_MASK | \
38                          GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK)
40 static bool nograb = false;
42 static bool grabbed = FALSE;
43 static bool moved = FALSE;
45 static gint xp = 0, yp = 0; // where drag started
46 static gint tolerance = 0;
47 static bool within_tolerance = false;
49 static bool transform_escaped = false; // true iff resize or rotate was cancelled by esc.
51 enum {
52     EVENT,
53     CLICKED,
54     DOUBLECLICKED,
55     GRABBED,
56     UNGRABBED,
57     MOVED,
58     REQUEST,
59     DISTANCE,
60     LAST_SIGNAL
61 };
63 static void sp_knot_class_init(SPKnotClass *klass);
64 static void sp_knot_init(SPKnot *knot);
65 static void sp_knot_dispose(GObject *object);
67 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot);
68 static void sp_knot_set_ctrl_state(SPKnot *knot);
70 static GObjectClass *parent_class;
71 static guint knot_signals[LAST_SIGNAL] = { 0 };
73 /**
74  * Registers SPKnot class and returns its type number.
75  */
76 GType sp_knot_get_type()
77 {
78     static GType type = 0;
79     if (!type) {
80         GTypeInfo info = {
81             sizeof(SPKnotClass),
82             NULL,       /* base_init */
83             NULL,       /* base_finalize */
84             (GClassInitFunc) sp_knot_class_init,
85             NULL,       /* class_finalize */
86             NULL,       /* class_data */
87             sizeof (SPKnot),
88             16, /* n_preallocs */
89             (GInstanceInitFunc) sp_knot_init,
90             NULL
91         };
92         type = g_type_register_static (G_TYPE_OBJECT, "SPKnot", &info, (GTypeFlags) 0);
93     }
94     return type;
95 }
97 /**
98  * SPKnot vtable initialization.
99  */
100 static void sp_knot_class_init(SPKnotClass *klass)
102     GObjectClass *object_class = (GObjectClass*)klass;
104     parent_class = (GObjectClass*) g_type_class_peek_parent(klass);
106     object_class->dispose = sp_knot_dispose;
108     knot_signals[EVENT] = g_signal_new("event",
109                                        G_TYPE_FROM_CLASS(klass),
110                                        G_SIGNAL_RUN_LAST,
111                                        G_STRUCT_OFFSET(SPKnotClass, event),
112                                        NULL, NULL,
113                                        sp_marshal_BOOLEAN__POINTER,
114                                        G_TYPE_BOOLEAN, 1,
115                                        GDK_TYPE_EVENT);
117     knot_signals[CLICKED] = g_signal_new("clicked",
118                                          G_TYPE_FROM_CLASS(klass),
119                                          G_SIGNAL_RUN_FIRST,
120                                          G_STRUCT_OFFSET(SPKnotClass, clicked),
121                                          NULL, NULL,
122                                          sp_marshal_NONE__UINT,
123                                          G_TYPE_NONE, 1,
124                                          G_TYPE_UINT);
126     knot_signals[DOUBLECLICKED] = g_signal_new("doubleclicked",
127                                                G_TYPE_FROM_CLASS(klass),
128                                                G_SIGNAL_RUN_FIRST,
129                                                G_STRUCT_OFFSET(SPKnotClass, doubleclicked),
130                                                NULL, NULL,
131                                                sp_marshal_NONE__UINT,
132                                                G_TYPE_NONE, 1,
133                                                G_TYPE_UINT);
135     knot_signals[GRABBED] = g_signal_new("grabbed",
136                                           G_TYPE_FROM_CLASS(klass),
137                                           G_SIGNAL_RUN_FIRST,
138                                           G_STRUCT_OFFSET(SPKnotClass, grabbed),
139                                           NULL, NULL,
140                                           sp_marshal_NONE__UINT,
141                                           G_TYPE_NONE, 1,
142                                           G_TYPE_UINT);
144     knot_signals[UNGRABBED] = g_signal_new("ungrabbed",
145                                             G_TYPE_FROM_CLASS(klass),
146                                             G_SIGNAL_RUN_FIRST,
147                                             G_STRUCT_OFFSET(SPKnotClass, ungrabbed),
148                                             NULL, NULL,
149                                             sp_marshal_NONE__UINT,
150                                             G_TYPE_NONE, 1,
151                                             G_TYPE_UINT);
153     knot_signals[MOVED] = g_signal_new("moved",
154                                         G_TYPE_FROM_CLASS(klass),
155                                         G_SIGNAL_RUN_FIRST,
156                                         G_STRUCT_OFFSET(SPKnotClass, moved),
157                                         NULL, NULL,
158                                         sp_marshal_NONE__POINTER_UINT,
159                                         G_TYPE_NONE, 2,
160                                         G_TYPE_POINTER, G_TYPE_UINT);
162     knot_signals[REQUEST] = g_signal_new("request",
163                                          G_TYPE_FROM_CLASS(klass),
164                                          G_SIGNAL_RUN_LAST,
165                                          G_STRUCT_OFFSET(SPKnotClass, request),
166                                          NULL, NULL,
167                                          sp_marshal_BOOLEAN__POINTER_UINT,
168                                          G_TYPE_BOOLEAN, 2,
169                                          G_TYPE_POINTER, G_TYPE_UINT);
171     knot_signals[DISTANCE] = g_signal_new("distance",
172                                            G_TYPE_FROM_CLASS(klass),
173                                            G_SIGNAL_RUN_LAST,
174                                            G_STRUCT_OFFSET(SPKnotClass, distance),
175                                            NULL, NULL,
176                                            sp_marshal_DOUBLE__POINTER_UINT,
177                                            G_TYPE_DOUBLE, 2,
178                                            G_TYPE_POINTER, G_TYPE_UINT);
180     const gchar *nograbenv = getenv("INKSCAPE_NO_GRAB");
181     nograb = (nograbenv && *nograbenv && (*nograbenv != '0'));
184 /**
185  * Callback for SPKnot initialization.
186  */
187 static void sp_knot_init(SPKnot *knot)
189     knot->desktop = NULL;
190     knot->item = NULL;
191     knot->flags = 0;
193     knot->size = 8;
194     knot->pos = NR::Point(0, 0);
195     knot->grabbed_rel_pos = NR::Point(0, 0);
196     knot->anchor = GTK_ANCHOR_CENTER;
197     knot->shape = SP_KNOT_SHAPE_SQUARE;
198     knot->mode = SP_KNOT_MODE_XOR;
199     knot->tip = NULL;
200     knot->_event_handler_id = 0;
201     knot->pressure = 0;
203     knot->fill[SP_KNOT_STATE_NORMAL] = 0xffffff00;
204     knot->fill[SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff;
205     knot->fill[SP_KNOT_STATE_DRAGGING] = 0x0000ffff;
207     knot->stroke[SP_KNOT_STATE_NORMAL] = 0x01000000;
208     knot->stroke[SP_KNOT_STATE_MOUSEOVER] = 0x01000000;
209     knot->stroke[SP_KNOT_STATE_DRAGGING] = 0x01000000;
211     knot->image[SP_KNOT_STATE_NORMAL] = NULL;
212     knot->image[SP_KNOT_STATE_MOUSEOVER] = NULL;
213     knot->image[SP_KNOT_STATE_DRAGGING] = NULL;
215     knot->cursor[SP_KNOT_STATE_NORMAL] = NULL;
216     knot->cursor[SP_KNOT_STATE_MOUSEOVER] = NULL;
217     knot->cursor[SP_KNOT_STATE_DRAGGING] = NULL;
219     knot->saved_cursor = NULL;
220     knot->pixbuf = NULL;
223 /**
224  * Called before SPKnot destruction.
225  */
226 static void sp_knot_dispose(GObject *object)
228     SPKnot *knot = (SPKnot *) object;
230     if ((knot->flags & SP_KNOT_GRABBED) && gdk_pointer_is_grabbed ()) {
231         // This happens e.g. when deleting a node in node tool while dragging it
232         gdk_pointer_ungrab (GDK_CURRENT_TIME);
233     }
235     if (knot->_event_handler_id > 0)
236         {
237         g_signal_handler_disconnect(G_OBJECT (knot->item), knot->_event_handler_id);
238         knot->_event_handler_id = 0;
239         }
241     if (knot->item) {
242         gtk_object_destroy (GTK_OBJECT (knot->item));
243         knot->item = NULL;
244     }
246     for (gint i = 0; i < SP_KNOT_VISIBLE_STATES; i++) {
247         if (knot->cursor[i]) {
248             gdk_cursor_unref(knot->cursor[i]);
249             knot->cursor[i] = NULL;
250         }
251     }
253     if (knot->tip) {
254         g_free(knot->tip);
255         knot->tip = NULL;
256     }
258     if (parent_class->dispose) {
259         (*parent_class->dispose) (object);
260     }
263 /**
264  * Update knot for dragging and tell canvas an item was grabbed.
265  */
266 void sp_knot_start_dragging(SPKnot *knot, NR::Point p, gint x, gint y, guint32 etime)
268     // save drag origin
269     xp = x;
270     yp = y;
271     within_tolerance = true;
273     knot->grabbed_rel_pos = p - knot->pos;
274     knot->drag_origin = knot->pos;
275     if (!nograb) {
276         sp_canvas_item_grab(knot->item,
277                             KNOT_EVENT_MASK,
278                             knot->cursor[SP_KNOT_STATE_DRAGGING],
279                             etime);
280     }
281     sp_knot_set_flag(knot, SP_KNOT_GRABBED, TRUE);
282     grabbed = TRUE;
285 /**
286  * Called to handle events on knots.
287  */
288 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot)
290     g_assert(knot != NULL);
291     g_assert(SP_IS_KNOT(knot));
293     /* Run client universal event handler, if present */
295     gboolean consumed = FALSE;
297     g_signal_emit(knot, knot_signals[EVENT], 0, event, &consumed);
299     if (consumed) {
300         return TRUE;
301     }
303     g_object_ref(knot);
304     tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
306     switch (event->type) {
307         case GDK_2BUTTON_PRESS:
308             if (event->button.button == 1) {
309                 g_signal_emit(knot, knot_signals[DOUBLECLICKED], 0, event->button.state);
311                 grabbed = FALSE;
312                 moved = FALSE;
313                 consumed = TRUE;
314             }
315             break;
316         case GDK_BUTTON_PRESS:
317             if (event->button.button == 1) {
318                 NR::Point const p = knot->desktop->w2d(NR::Point(event->button.x, event->button.y));
319                 sp_knot_start_dragging(knot, p, (gint) event->button.x, (gint) event->button.y, event->button.time);
320                 consumed = TRUE;
321             }
322             break;
323         case GDK_BUTTON_RELEASE:
324             if (event->button.button == 1) {
325                 knot->pressure = 0;
326                 if (transform_escaped) {
327                     transform_escaped = false;
328                     consumed = TRUE;
329                 } else {
330                     sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
331                     if (!nograb) {
332                         sp_canvas_item_ungrab(knot->item, event->button.time);
333                     }
334                     if (moved) {
335                         sp_knot_set_flag(knot,
336                                          SP_KNOT_DRAGGING,
337                                          FALSE);
338                         g_signal_emit(knot,
339                                       knot_signals[UNGRABBED], 0,
340                                       event->button.state);
341                     } else {
342                         g_signal_emit(knot,
343                                       knot_signals[CLICKED], 0,
344                                       event->button.state);
345                     }
346                     grabbed = FALSE;
347                     moved = FALSE;
348                     consumed = TRUE;
349                 }
350             }
351             break;
352         case GDK_MOTION_NOTIFY:
353             if (grabbed) {
354                 consumed = TRUE;
356                 if ( within_tolerance
357                      && ( abs( (gint) event->motion.x - xp ) < tolerance )
358                      && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
359                     break; // do not drag if we're within tolerance from origin
360                 }
362                 // Once the user has moved farther than tolerance from the original location
363                 // (indicating they intend to move the object, not click), then always process the
364                 // motion notify coordinates as given (no snapping back to origin)
365                 within_tolerance = false;
367                 if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &knot->pressure))
368                     knot->pressure = CLAMP (knot->pressure, 0, 1);
369                 else
370                     knot->pressure = 0.5;
372                 if (!moved) {
373                     g_signal_emit(knot,
374                                   knot_signals[GRABBED], 0,
375                                   event->motion.state);
376                     sp_knot_set_flag(knot,
377                                      SP_KNOT_DRAGGING,
378                                      TRUE);
379                 }
380                 NR::Point const motion_w(event->motion.x, event->motion.y);
381                 NR::Point motion_dt = knot->desktop->w2d(motion_w) - knot->grabbed_rel_pos;
382                 
383                 // Only snap path nodes, for which the anchor is set to GTK_ANCHOR_CENTER
384                 // (according to default value as set in sp_knot_init())
385                 // If we have one of the transform handles at hand, then the anchor will be
386                 // set to e.g. GTK_ANCHOR_SW (see seltrans-handles.cpp). The transform handles
387                 // will be snapped in seltrans.cpp, not here
388                 if (knot->anchor == GTK_ANCHOR_CENTER) {
389                     SnapManager const &m = knot->desktop->namedview->snap_manager;
390                     SPItem *curr = knot->desktop->selection->singleItem(); //Is this really the only way to get to the item to which this knot belongs?
391                     g_assert(curr != NULL);
392                     motion_dt = m.freeSnap(Inkscape::Snapper::BBOX_POINT | Inkscape::Snapper::SNAP_POINT, motion_dt, curr).getPoint();
393                 }
394             
395                 sp_knot_request_position (knot, &motion_dt, event->motion.state);
396                 knot->desktop->scroll_to_point (&motion_dt);
397                 knot->desktop->set_coordinate_status(knot->pos); // display the coordinate of knot, not cursor - they may be different!
398                 moved = TRUE;
399             }
400             break;
401         case GDK_ENTER_NOTIFY:
402             sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
403             sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
405             if (knot->tip) {
406                 knot->desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, knot->tip);
407             }
409             grabbed = FALSE;
410             moved = FALSE;
411             consumed = TRUE;
412             break;
413         case GDK_LEAVE_NOTIFY:
414             sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
415             sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
417             if (knot->tip) {
418                 knot->desktop->event_context->defaultMessageContext()->clear();
419             }
421             grabbed = FALSE;
422             moved = FALSE;
424             consumed = TRUE;
425             break;
426         case GDK_KEY_PRESS: // keybindings for knot
427             switch (get_group0_keyval(&event->key)) {
428                 case GDK_Escape:
429                     sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
430                     if (!nograb) {
431                         sp_canvas_item_ungrab(knot->item, event->button.time);
432                     }
433                     if (moved) {
434                         sp_knot_set_flag(knot,
435                                          SP_KNOT_DRAGGING,
436                                          FALSE);
437                         g_signal_emit(knot,
438                                       knot_signals[UNGRABBED], 0,
439                                       event->button.state);
440                         sp_document_undo(sp_desktop_document(knot->desktop));
441                         knot->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Node or handle drag canceled."));
442                         transform_escaped = true;
443                         consumed = TRUE;
444                     }
445                     grabbed = FALSE;
446                     moved = FALSE;
447                     break;
448                 default:
449                     consumed = FALSE;
450                     break;
451             }
452             break;
453         default:
454             break;
455     }
457     g_object_unref(knot);
459     return consumed;
462 /**
463  * Return new knot object.
464  */
465 SPKnot *sp_knot_new(SPDesktop *desktop, const gchar *tip)
467     g_return_val_if_fail(desktop != NULL, NULL);
469     SPKnot * knot = (SPKnot*) g_object_new(SP_TYPE_KNOT, 0);
471     knot->desktop = desktop;
472     knot->flags = SP_KNOT_VISIBLE;
473     if (tip) {
474         knot->tip = g_strdup (tip);
475     }
477     knot->item = sp_canvas_item_new(sp_desktop_controls (desktop),
478                                     SP_TYPE_CTRL,
479                                     "anchor", GTK_ANCHOR_CENTER,
480                                     "size", 8.0,
481                                     "filled", TRUE,
482                                     "fill_color", 0xffffff00,
483                                     "stroked", TRUE,
484                                     "stroke_color", 0x01000000,
485                                     "mode", SP_KNOT_MODE_XOR,
486                                     NULL);
488     knot->_event_handler_id = gtk_signal_connect(GTK_OBJECT(knot->item), "event",
489                                                  GTK_SIGNAL_FUNC(sp_knot_handler), knot);
491     return knot;
494 /**
495  * Show knot on its canvas.
496  */
497 void sp_knot_show(SPKnot *knot)
499     g_return_if_fail(knot != NULL);
500     g_return_if_fail(SP_IS_KNOT (knot));
502     sp_knot_set_flag(knot, SP_KNOT_VISIBLE, TRUE);
505 /**
506  * Hide knot on its canvas.
507  */
508 void sp_knot_hide(SPKnot *knot)
510     g_return_if_fail(knot != NULL);
511     g_return_if_fail(SP_IS_KNOT(knot));
513     sp_knot_set_flag(knot, SP_KNOT_VISIBLE, FALSE);
516 /**
517  * Request or set new position for knot.
518  */
519 void sp_knot_request_position(SPKnot *knot, NR::Point *p, guint state)
521     g_return_if_fail(knot != NULL);
522     g_return_if_fail(SP_IS_KNOT(knot));
524     gboolean done = FALSE;
526     g_signal_emit(knot,
527                   knot_signals[REQUEST], 0,
528                   p,
529                   state,
530                   &done);
532     /* If user did not complete, we simply move knot to new position */
534     if (!done) {
535         sp_knot_set_position (knot, p, state);
536     }
539 /**
540  * Return distance of point to knot's position; unused.
541  */
542 gdouble sp_knot_distance(SPKnot * knot, NR::Point *p, guint state)
544     g_return_val_if_fail(knot != NULL, 1e18);
545     g_return_val_if_fail(SP_IS_KNOT(knot), 1e18);
547     gdouble distance = NR::L2(*p - knot->pos);
549     g_signal_emit(knot,
550                   knot_signals[DISTANCE], 0,
551                   p,
552                   state,
553                   &distance);
555     return distance;
558 /**
559  * Move knot to new position.
560  */
561 void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state)
563     g_return_if_fail(knot != NULL);
564     g_return_if_fail(SP_IS_KNOT (knot));
566     knot->pos = *p;
568     if (knot->item) {
569         SP_CTRL(knot->item)->moveto (*p);
570     }
572     g_signal_emit(knot,
573                   knot_signals[MOVED], 0,
574                   p,
575                   state);
578 /**
579  * Move knot to new position, without emitting a MOVED signal. 
580  */
581 void sp_knot_moveto(SPKnot *knot, NR::Point *p)
583     g_return_if_fail(knot != NULL);
584     g_return_if_fail(SP_IS_KNOT(knot));
586     knot->pos = *p;
588     if (knot->item) {
589         SP_CTRL(knot->item)->moveto (*p);
590     }
593 /**
594  * Returns position of knot.
595  */
596 NR::Point sp_knot_position(SPKnot const *knot)
598     g_assert(knot != NULL);
599     g_assert(SP_IS_KNOT (knot));
601     return knot->pos;
604 /**
605  * Set flag in knot, with side effects.
606  */
607 void sp_knot_set_flag(SPKnot *knot, guint flag, bool set)
609     g_assert(knot != NULL);
610     g_assert(SP_IS_KNOT(knot));
612     if (set) {
613         knot->flags |= flag;
614     } else {
615         knot->flags &= ~flag;
616     }
618     switch (flag) {
619         case SP_KNOT_VISIBLE:
620             if (set) {
621                 sp_canvas_item_show(knot->item);
622             } else {
623                 sp_canvas_item_hide(knot->item);
624             }
625             break;
626         case SP_KNOT_MOUSEOVER:
627         case SP_KNOT_DRAGGING:
628             sp_knot_set_ctrl_state(knot);
629             break;
630         case SP_KNOT_GRABBED:
631             break;
632         default:
633             g_assert_not_reached();
634             break;
635     }
638 /**
639  * Update knot's pixbuf and set its control state.
640  */
641 void sp_knot_update_ctrl(SPKnot *knot)
643     if (!knot->item) {
644         return;
645     }
647     gtk_object_set(GTK_OBJECT(knot->item), "shape", knot->shape, NULL);
648     gtk_object_set(GTK_OBJECT(knot->item), "mode", knot->mode, NULL);
649     gtk_object_set(GTK_OBJECT(knot->item), "size", (gdouble) knot->size, NULL);
650     gtk_object_set(GTK_OBJECT(knot->item), "anchor", knot->anchor, NULL);
651     if (knot->pixbuf) {
652         gtk_object_set(GTK_OBJECT (knot->item), "pixbuf", knot->pixbuf, NULL);
653     }
655     sp_knot_set_ctrl_state(knot);
658 /**
659  * Set knot control state (dragging/mouseover/normal).
660  */
661 static void sp_knot_set_ctrl_state(SPKnot *knot)
663     if (knot->flags & SP_KNOT_DRAGGING) {
664         gtk_object_set(GTK_OBJECT (knot->item),
665                        "fill_color",
666                        knot->fill[SP_KNOT_STATE_DRAGGING],
667                        NULL);
668         gtk_object_set(GTK_OBJECT (knot->item),
669                        "stroke_color",
670                        knot->stroke[SP_KNOT_STATE_DRAGGING],
671                        NULL);
672     } else if (knot->flags & SP_KNOT_MOUSEOVER) {
673         gtk_object_set(GTK_OBJECT(knot->item),
674                        "fill_color",
675                        knot->fill[SP_KNOT_STATE_MOUSEOVER],
676                        NULL);
677         gtk_object_set(GTK_OBJECT(knot->item),
678                        "stroke_color",
679                        knot->stroke[SP_KNOT_STATE_MOUSEOVER],
680                        NULL);
681     } else {
682         gtk_object_set(GTK_OBJECT(knot->item),
683                        "fill_color",
684                         knot->fill[SP_KNOT_STATE_NORMAL],
685                        NULL);
686         gtk_object_set(GTK_OBJECT(knot->item),
687                        "stroke_color",
688                        knot->stroke[SP_KNOT_STATE_NORMAL],
689                        NULL);
690     }
695 /*
696   Local Variables:
697   mode:c++
698   c-file-style:"stroustrup"
699   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
700   indent-tabs-mode:nil
701   fill-column:99
702   End:
703 */
704 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :