Code

A simple layout document as to what, why and how is cppification.
[inkscape.git] / src / widgets / sp-attribute-widget.cpp
1 /** @file
2  * @brief Widget that listens and modifies repr attributes
3  */
4 /* Authors:
5  *  Lauris Kaplinski <lauris@ximian.com>
6  *
7  * Copyright (C) 2001 Ximian, Inc.
8  * Released under GNU GPL, read the file 'COPYING' for more information
9  */
11 #include <gtk/gtktable.h>
12 #include <gtk/gtklabel.h>
13 #include "xml/repr.h"
14 #include "macros.h"
15 #include "document.h"
16 #include "sp-object.h"
17 #include <glibmm/i18n.h>
19 #include <sigc++/functors/ptr_fun.h>
20 #include <sigc++/adaptors/bind.h>
22 #include "sp-attribute-widget.h"
24 static void sp_attribute_widget_class_init (SPAttributeWidgetClass *klass);
25 static void sp_attribute_widget_init (SPAttributeWidget *widget);
26 static void sp_attribute_widget_destroy (GtkObject *object);
28 static void sp_attribute_widget_changed (GtkEditable *editable);
30 static void sp_attribute_widget_object_modified ( SPObject *object,
31                                                   guint flags,
32                                                   SPAttributeWidget *spaw );
33 static void sp_attribute_widget_object_release ( SPObject *object,
34                                                  SPAttributeWidget *spaw );
36 static GtkEntryClass *parent_class;
41 GType sp_attribute_widget_get_type(void)
42 {
43     static GtkType type = 0;
44     if (!type) {
45         GTypeInfo info = {
46             sizeof(SPAttributeWidgetClass),
47             0, // base_init
48             0, // base_finalize
49             (GClassInitFunc)sp_attribute_widget_class_init,
50             0, // class_finalize
51             0, // class_data
52             sizeof(SPAttributeWidget),
53             0, // n_preallocs
54             (GInstanceInitFunc)sp_attribute_widget_init,
55             0 // value_table
56         };
57         type = g_type_register_static(GTK_TYPE_ENTRY, "SPAttributeWidget", &info, static_cast<GTypeFlags>(0));
58     }
59     return type;
60 } // end of sp_attribute_widget_get_type()
64 static void
65 sp_attribute_widget_class_init (SPAttributeWidgetClass *klass)
66 {
67     GtkObjectClass *object_class;
68     GtkWidgetClass *widget_class;
69     GtkEditableClass *editable_class;
71     object_class = GTK_OBJECT_CLASS (klass);
72     widget_class = GTK_WIDGET_CLASS (klass);
73     editable_class = GTK_EDITABLE_CLASS (klass);
75     parent_class = (GtkEntryClass*)gtk_type_class (GTK_TYPE_ENTRY);
77     object_class->destroy = sp_attribute_widget_destroy;
79     editable_class->changed = sp_attribute_widget_changed;
81 } // end of sp_attribute_widget_class_init()
85 static void
86 sp_attribute_widget_init (SPAttributeWidget *spaw)
87 {
88     spaw->blocked = FALSE;
89     spaw->hasobj = FALSE;
91     spaw->src.object = NULL;
93     spaw->attribute = NULL;
95     new (&spaw->modified_connection) sigc::connection();
96     new (&spaw->release_connection) sigc::connection();
97 }
101 static void
102 sp_attribute_widget_destroy (GtkObject *object)
105     SPAttributeWidget *spaw;
107     spaw = SP_ATTRIBUTE_WIDGET (object);
109     if (spaw->attribute) {
110         g_free (spaw->attribute);
111         spaw->attribute = NULL;
112     }
115     if (spaw->hasobj) {
117         if (spaw->src.object) {
118             spaw->modified_connection.disconnect();
119             spaw->release_connection.disconnect();
120             spaw->src.object = NULL;
121         }
122     } else {
124         if (spaw->src.repr) {
125             spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
126         }
127     } // end of if()
129     spaw->modified_connection.~connection();
130     spaw->release_connection.~connection();
132     ((GtkObjectClass *) parent_class)->destroy (object);
138 static void
139 sp_attribute_widget_changed (GtkEditable *editable)
142     SPAttributeWidget *spaw;
144     spaw = SP_ATTRIBUTE_WIDGET (editable);
146     if (!spaw->blocked) {
148         const gchar *text;
149         spaw->blocked = TRUE;
150         text = gtk_entry_get_text (GTK_ENTRY (spaw));
151         if (!*text)
152             text = NULL;
154         if (spaw->hasobj && spaw->src.object) {
155         
156             SP_OBJECT_REPR (spaw->src.object)->setAttribute(spaw->attribute, text, false);
157             SPDocumentUndo::done (SP_OBJECT_DOCUMENT (spaw->src.object), SP_VERB_NONE,
158                               _("Set attribute"));
160         } else if (spaw->src.repr) {
162             spaw->src.repr->setAttribute(spaw->attribute, text, false);
163             /* TODO: Warning! Undo will not be flushed in given case */
164         }
165         spaw->blocked = FALSE;
166     }
168 } // end of sp_attribute_widget_changed()
172 GtkWidget *
173 sp_attribute_widget_new ( SPObject *object, const gchar *attribute )
175     SPAttributeWidget *spaw;
177     g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL);
178     g_return_val_if_fail (!object || attribute, NULL);
180     spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET);
182     sp_attribute_widget_set_object (spaw, object, attribute);
184     return GTK_WIDGET (spaw);
186 } // end of sp_attribute_widget_new()
190 GtkWidget *
191 sp_attribute_widget_new_repr ( Inkscape::XML::Node *repr, const gchar *attribute )
193     SPAttributeWidget *spaw;
195     spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET);
197     sp_attribute_widget_set_repr (spaw, repr, attribute);
199     return GTK_WIDGET (spaw);
204 void
205 sp_attribute_widget_set_object ( SPAttributeWidget *spaw,
206                                  SPObject *object,
207                                  const gchar *attribute )
210     g_return_if_fail (spaw != NULL);
211     g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw));
212     g_return_if_fail (!object || SP_IS_OBJECT (object));
213     g_return_if_fail (!object || attribute);
214     g_return_if_fail (attribute != NULL);
216     if (spaw->attribute) {
217         g_free (spaw->attribute);
218         spaw->attribute = NULL;
219     }
221     if (spaw->hasobj) {
223         if (spaw->src.object) {
224             spaw->modified_connection.disconnect();
225             spaw->release_connection.disconnect();
226             spaw->src.object = NULL;
227         }
228     } else {
230         if (spaw->src.repr) {
231             spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
232         }
233     }
235     spaw->hasobj = TRUE;
237     if (object) {
238         const gchar *val;
240         spaw->blocked = TRUE;
241         spaw->src.object = object;
243         spaw->modified_connection = object->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_attribute_widget_object_modified), spaw));
244         spaw->release_connection = object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_attribute_widget_object_release), spaw));
246         spaw->attribute = g_strdup (attribute);
248         val = SP_OBJECT_REPR (object)->attribute(attribute);
249         gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) "");
250         spaw->blocked = FALSE;
251     }
253     gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.object != NULL));
255 } // end of sp_attribute_widget_set_object()
259 void
260 sp_attribute_widget_set_repr ( SPAttributeWidget *spaw,
261                                Inkscape::XML::Node *repr,
262                                const gchar *attribute )
265     g_return_if_fail (spaw != NULL);
266     g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw));
267     g_return_if_fail (attribute != NULL);
269     if (spaw->attribute) {
270         g_free (spaw->attribute);
271         spaw->attribute = NULL;
272     }
274     if (spaw->hasobj) {
276         if (spaw->src.object) {
277             spaw->modified_connection.disconnect();
278             spaw->release_connection.disconnect();
279             spaw->src.object = NULL;
280         }
281     } else {
283         if (spaw->src.repr) {
284             spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
285         }
286     }
288     spaw->hasobj = FALSE;
290     if (repr) {
291         const gchar *val;
293         spaw->blocked = TRUE;
294         spaw->src.repr = Inkscape::GC::anchor(repr);
295         spaw->attribute = g_strdup (attribute);
297         val = repr->attribute(attribute);
298         gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) "");
299         spaw->blocked = FALSE;
300     }
302     gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.repr != NULL));
304 } // end of sp_attribute_widget_set_repr()
308 static void
309 sp_attribute_widget_object_modified ( SPObject */*object*/,
310                                       guint flags,
311                                       SPAttributeWidget *spaw )
314     if (flags && SP_OBJECT_MODIFIED_FLAG) {
316         const gchar *val, *text;
317         val = SP_OBJECT_REPR (spaw->src.object)->attribute(spaw->attribute);
318         text = gtk_entry_get_text (GTK_ENTRY (spaw));
320         if (val || text) {
322             if (!val || !text || strcmp (val, text)) {
323                 /* We are different */
324                 spaw->blocked = TRUE;
325                 gtk_entry_set_text ( GTK_ENTRY (spaw),
326                                      val ? val : (const gchar *) "");
327                 spaw->blocked = FALSE;
328             } // end of if()
330         } // end of if()
332     } //end of if()
334 } // end of sp_attribute_widget_object_modified()
338 static void
339 sp_attribute_widget_object_release ( SPObject */*object*/,
340                                      SPAttributeWidget *spaw )
342     sp_attribute_widget_set_object (spaw, NULL, NULL);
347 /* SPAttributeTable */
349 static void sp_attribute_table_class_init (SPAttributeTableClass *klass);
350 static void sp_attribute_table_init (SPAttributeTable *widget);
351 static void sp_attribute_table_destroy (GtkObject *object);
353 static void sp_attribute_table_object_modified (SPObject *object, guint flags, SPAttributeTable *spaw);
354 static void sp_attribute_table_object_release (SPObject *object, SPAttributeTable *spaw);
355 static void sp_attribute_table_entry_changed (GtkEditable *editable, SPAttributeTable *spat);
357 static GtkVBoxClass *table_parent_class;
362 GType sp_attribute_table_get_type(void)
364     static GtkType type = 0;
365     if (!type) {
366         GTypeInfo info = {
367             sizeof(SPAttributeTableClass),
368             0, // base_init
369             0, // base_finalize
370             (GClassInitFunc)sp_attribute_table_class_init,
371             0, // class_finalize
372             0, // class_data
373             sizeof(SPAttributeTable),
374             0, // n_preallocs
375             (GInstanceInitFunc)sp_attribute_table_init,
376             0 // value_table
377         };
378         type = g_type_register_static(GTK_TYPE_VBOX, "SPAttributeTable", &info, static_cast<GTypeFlags>(0));
379     }
380     return type;
381 } // end of sp_attribute_table_get_type()
385 static void
386 sp_attribute_table_class_init (SPAttributeTableClass *klass)
388     GtkObjectClass *object_class;
389     GtkWidgetClass *widget_class;
391     object_class = GTK_OBJECT_CLASS (klass);
392     widget_class = GTK_WIDGET_CLASS (klass);
394     table_parent_class = (GtkVBoxClass*)gtk_type_class (GTK_TYPE_VBOX);
396     object_class->destroy = sp_attribute_table_destroy;
398 } // end of sp_attribute_table_class_init()
402 static void
403 sp_attribute_table_init ( SPAttributeTable *spat )
405     spat->blocked = FALSE;
406     spat->hasobj = FALSE;
407     spat->table = NULL;
408     spat->src.object = NULL;
409     spat->num_attr = 0;
410     spat->attributes = NULL;
411     spat->entries = NULL;
413     new (&spat->modified_connection) sigc::connection();
414     new (&spat->release_connection) sigc::connection();
417 static void
418 sp_attribute_table_destroy ( GtkObject *object )
420     SPAttributeTable *spat;
422     spat = SP_ATTRIBUTE_TABLE (object);
424     if (spat->attributes) {
425         gint i;
426         for (i = 0; i < spat->num_attr; i++) {
427             g_free (spat->attributes[i]);
428         }
429         g_free (spat->attributes);
430         spat->attributes = NULL;
431     }
433     if (spat->hasobj) {
435         if (spat->src.object) {
436             spat->modified_connection.disconnect();
437             spat->release_connection.disconnect();
438             spat->src.object = NULL;
439         }
440     } else {
441         if (spat->src.repr) {
442             spat->src.repr = Inkscape::GC::release(spat->src.repr);
443         }
444     } // end of if()
446     spat->modified_connection.~connection();
447     spat->release_connection.~connection();
449     if (spat->entries) {
450         g_free (spat->entries);
451         spat->entries = NULL;
452     }
454     spat->table = NULL;
456     if (((GtkObjectClass *) table_parent_class)->destroy) {
457         (* ((GtkObjectClass *) table_parent_class)->destroy) (object);
458     }
460 } // end of sp_attribute_table_destroy()
463 GtkWidget *
464 sp_attribute_table_new ( SPObject *object,
465                          gint num_attr,
466                          const gchar **labels,
467                          const gchar **attributes )
469     SPAttributeTable *spat;
471     g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL);
472     g_return_val_if_fail (!object || (num_attr > 0), NULL);
473     g_return_val_if_fail (!num_attr || (labels && attributes), NULL);
475     spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE);
477     sp_attribute_table_set_object (spat, object, num_attr, labels, attributes);
479     return GTK_WIDGET (spat);
481 } // end of sp_attribute_table_new()
485 GtkWidget *
486 sp_attribute_table_new_repr ( Inkscape::XML::Node *repr,
487                               gint num_attr,
488                               const gchar **labels,
489                               const gchar **attributes )
491     SPAttributeTable *spat;
493     g_return_val_if_fail (!num_attr || (labels && attributes), NULL);
495     spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE);
497     sp_attribute_table_set_repr (spat, repr, num_attr, labels, attributes);
499     return GTK_WIDGET (spat);
501 } // end of sp_attribute_table_new_repr()
505 #define XPAD 4
506 #define YPAD 0
508 void
509 sp_attribute_table_set_object ( SPAttributeTable *spat,
510                                 SPObject *object,
511                                 gint num_attr,
512                                 const gchar **labels,
513                                 const gchar **attributes )
516     g_return_if_fail (spat != NULL);
517     g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat));
518     g_return_if_fail (!object || SP_IS_OBJECT (object));
519     g_return_if_fail (!object || (num_attr > 0));
520     g_return_if_fail (!num_attr || (labels && attributes));
522     if (spat->table) {
523         gtk_widget_destroy (spat->table);
524         spat->table = NULL;
525     }
527     if (spat->attributes) {
528         gint i;
529         for (i = 0; i < spat->num_attr; i++) {
530             g_free (spat->attributes[i]);
531         }
532         g_free (spat->attributes);
533         spat->attributes = NULL;
534     }
536     if (spat->entries) {
537         g_free (spat->entries);
538         spat->entries = NULL;
539     }
541     if (spat->hasobj) {
542         if (spat->src.object) {
543             spat->modified_connection.disconnect();
544             spat->release_connection.disconnect();
545             spat->src.object = NULL;
546         }
547     } else {
548         if (spat->src.repr) {
549             spat->src.repr = Inkscape::GC::release(spat->src.repr);
550         }
551     }
553     spat->hasobj = TRUE;
555     if (object) {
556         gint i;
558         spat->blocked = TRUE;
560         /* Set up object */
561         spat->src.object = object;
562         spat->num_attr = num_attr;
564         spat->modified_connection = object->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_attribute_table_object_modified), spat));
565         spat->release_connection = object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_attribute_table_object_release), spat));
567         /* Create table */
568         spat->table = gtk_table_new (num_attr, 2, FALSE);
569         gtk_container_add (GTK_CONTAINER (spat), spat->table);
570         /* Arrays */
571         spat->attributes = g_new0 (gchar *, num_attr);
572         spat->entries = g_new0 (GtkWidget *, num_attr);
573         /* Fill rows */
574         for (i = 0; i < num_attr; i++) {
575             GtkWidget *w;
576             const gchar *val;
578             spat->attributes[i] = g_strdup (attributes[i]);
579             w = gtk_label_new (_(labels[i]));
580             gtk_widget_show (w);
581             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
582             gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1,
583                                GTK_FILL,
584                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
585                                XPAD, YPAD );
586             w = gtk_entry_new ();
587             gtk_widget_show (w);
588             val = SP_OBJECT_REPR (object)->attribute(attributes[i]);
589             gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) "");
590             gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1,
591                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
592                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
593                                XPAD, YPAD );
594             spat->entries[i] = w;
595             g_signal_connect ( G_OBJECT (w), "changed",
596                                G_CALLBACK (sp_attribute_table_entry_changed),
597                                spat );
598         }
599         /* Show table */
600         gtk_widget_show (spat->table);
602         spat->blocked = FALSE;
603     }
605     gtk_widget_set_sensitive ( GTK_WIDGET (spat),
606                                (spat->src.object != NULL) );
608 } // end of sp_attribute_table_set_object()
612 void
613 sp_attribute_table_set_repr ( SPAttributeTable *spat,
614                               Inkscape::XML::Node *repr,
615                               gint num_attr,
616                               const gchar **labels,
617                               const gchar **attributes )
619     g_return_if_fail (spat != NULL);
620     g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat));
621     g_return_if_fail (!num_attr || (labels && attributes));
623     if (spat->table) {
624         gtk_widget_destroy (spat->table);
625         spat->table = NULL;
626     }
628     if (spat->attributes) {
629         gint i;
630         for (i = 0; i < spat->num_attr; i++) {
631             g_free (spat->attributes[i]);
632         }
633         g_free (spat->attributes);
634         spat->attributes = NULL;
635     }
637     if (spat->entries) {
638         g_free (spat->entries);
639         spat->entries = NULL;
640     }
642     if (spat->hasobj) {
643         if (spat->src.object) {
644             spat->modified_connection.disconnect();
645             spat->release_connection.disconnect();
646             spat->src.object = NULL;
647         }
648     } else {
649         if (spat->src.repr) {
650             spat->src.repr = Inkscape::GC::release(spat->src.repr);
651         }
652     }
654     spat->hasobj = FALSE;
656     if (repr) {
657         gint i;
659         spat->blocked = TRUE;
661         /* Set up repr */
662         spat->src.repr = Inkscape::GC::anchor(repr);
663         spat->num_attr = num_attr;
664         /* Create table */
665         spat->table = gtk_table_new (num_attr, 2, FALSE);
666         gtk_container_add (GTK_CONTAINER (spat), spat->table);
667         /* Arrays */
668         spat->attributes = g_new0 (gchar *, num_attr);
669         spat->entries = g_new0 (GtkWidget *, num_attr);
671         /* Fill rows */
672         for (i = 0; i < num_attr; i++) {
673             GtkWidget *w;
674             const gchar *val;
676             spat->attributes[i] = g_strdup (attributes[i]);
677             w = gtk_label_new (labels[i]);
678             gtk_widget_show (w);
679             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
680             gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1,
681                                GTK_FILL,
682                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
683                                XPAD, YPAD );
684             w = gtk_entry_new ();
685             gtk_widget_show (w);
686             val = repr->attribute(attributes[i]);
687             gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) "");
688             gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1,
689                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
690                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
691                                XPAD, YPAD );
692             spat->entries[i] = w;
693             g_signal_connect ( G_OBJECT (w), "changed",
694                                G_CALLBACK (sp_attribute_table_entry_changed),
695                                spat );
696         }
697         /* Show table */
698         gtk_widget_show (spat->table);
700         spat->blocked = FALSE;
701     }
703     gtk_widget_set_sensitive (GTK_WIDGET (spat), (spat->src.repr != NULL));
705 } // end of sp_attribute_table_set_repr()
709 static void
710 sp_attribute_table_object_modified ( SPObject */*object*/,
711                                      guint flags,
712                                      SPAttributeTable *spat )
714     if (flags && SP_OBJECT_MODIFIED_FLAG)
715     {
716         gint i;
717         for (i = 0; i < spat->num_attr; i++) {
718             const gchar *val, *text;
719             val = SP_OBJECT_REPR (spat->src.object)->attribute(spat->attributes[i]);
720             text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i]));
721             if (val || text) {
722                 if (!val || !text || strcmp (val, text)) {
723                     /* We are different */
724                     spat->blocked = TRUE;
725                     gtk_entry_set_text ( GTK_ENTRY (spat->entries[i]),
726                                          val ? val : (const gchar *) "");
727                     spat->blocked = FALSE;
728                 }
729             }
730         }
731     } // end of if()
733 } // end of sp_attribute_table_object_modified()
737 static void
738 sp_attribute_table_object_release (SPObject */*object*/, SPAttributeTable *spat)
740     sp_attribute_table_set_object (spat, NULL, 0, NULL, NULL);
745 static void
746 sp_attribute_table_entry_changed ( GtkEditable *editable,
747                                    SPAttributeTable *spat )
749     if (!spat->blocked)
750     {
751         gint i;
752         for (i = 0; i < spat->num_attr; i++) {
754             if (GTK_WIDGET (editable) == spat->entries[i]) {
755                 const gchar *text;
756                 spat->blocked = TRUE;
757                 text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i]));
759                 if (!*text)
760                     text = NULL;
762                 if (spat->hasobj && spat->src.object) {
763                     SP_OBJECT_REPR (spat->src.object)->setAttribute(spat->attributes[i], text, false);
764                     SPDocumentUndo::done (SP_OBJECT_DOCUMENT (spat->src.object), SP_VERB_NONE,
765                                       _("Set attribute"));
767                 } else if (spat->src.repr) {
769                     spat->src.repr->setAttribute(spat->attributes[i], text, false);
770                     /* TODO: Warning! Undo will not be flushed in given case */
771                 }
772                 spat->blocked = FALSE;
773                 return;
774             }
775         }
776         g_warning ("file %s: line %d: Entry signalled change, but there is no such entry", __FILE__, __LINE__);
777     } // end of if()
779 } // end of sp_attribute_table_entry_changed()
781 /*
782   Local Variables:
783   mode:c++
784   c-file-style:"stroustrup"
785   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
786   indent-tabs-mode:nil
787   fill-column:99
788   End:
789 */
790 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :