Code

Store cached icons to disk between runs, and invalidate/purge as needed.
[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  *   Abhishek Sharma
7  *
8  * Copyright (C) 2001 Ximian, Inc.
9  * Released under GNU GPL, read the file 'COPYING' for more information
10  */
12 #include <gtk/gtktable.h>
13 #include <gtk/gtklabel.h>
14 #include "xml/repr.h"
15 #include "macros.h"
16 #include "document.h"
17 #include "sp-object.h"
18 #include <glibmm/i18n.h>
20 #include <sigc++/functors/ptr_fun.h>
21 #include <sigc++/adaptors/bind.h>
23 #include "sp-attribute-widget.h"
25 using Inkscape::DocumentUndo;
27 static void sp_attribute_widget_class_init (SPAttributeWidgetClass *klass);
28 static void sp_attribute_widget_init (SPAttributeWidget *widget);
29 static void sp_attribute_widget_destroy (GtkObject *object);
31 static void sp_attribute_widget_changed (GtkEditable *editable);
33 static void sp_attribute_widget_object_modified ( SPObject *object,
34                                                   guint flags,
35                                                   SPAttributeWidget *spaw );
36 static void sp_attribute_widget_object_release ( SPObject *object,
37                                                  SPAttributeWidget *spaw );
39 static GtkEntryClass *parent_class;
44 GType sp_attribute_widget_get_type(void)
45 {
46     static GtkType type = 0;
47     if (!type) {
48         GTypeInfo info = {
49             sizeof(SPAttributeWidgetClass),
50             0, // base_init
51             0, // base_finalize
52             (GClassInitFunc)sp_attribute_widget_class_init,
53             0, // class_finalize
54             0, // class_data
55             sizeof(SPAttributeWidget),
56             0, // n_preallocs
57             (GInstanceInitFunc)sp_attribute_widget_init,
58             0 // value_table
59         };
60         type = g_type_register_static(GTK_TYPE_ENTRY, "SPAttributeWidget", &info, static_cast<GTypeFlags>(0));
61     }
62     return type;
63 } // end of sp_attribute_widget_get_type()
67 static void
68 sp_attribute_widget_class_init (SPAttributeWidgetClass *klass)
69 {
70     GtkObjectClass *object_class;
71     GtkWidgetClass *widget_class;
72     GtkEditableClass *editable_class;
74     object_class = GTK_OBJECT_CLASS (klass);
75     widget_class = GTK_WIDGET_CLASS (klass);
76     editable_class = GTK_EDITABLE_CLASS (klass);
78     parent_class = (GtkEntryClass*)gtk_type_class (GTK_TYPE_ENTRY);
80     object_class->destroy = sp_attribute_widget_destroy;
82     editable_class->changed = sp_attribute_widget_changed;
84 } // end of sp_attribute_widget_class_init()
88 static void
89 sp_attribute_widget_init (SPAttributeWidget *spaw)
90 {
91     spaw->blocked = FALSE;
92     spaw->hasobj = FALSE;
94     spaw->src.object = NULL;
96     spaw->attribute = NULL;
98     new (&spaw->modified_connection) sigc::connection();
99     new (&spaw->release_connection) sigc::connection();
104 static void
105 sp_attribute_widget_destroy (GtkObject *object)
108     SPAttributeWidget *spaw;
110     spaw = SP_ATTRIBUTE_WIDGET (object);
112     if (spaw->attribute) {
113         g_free (spaw->attribute);
114         spaw->attribute = NULL;
115     }
118     if (spaw->hasobj) {
120         if (spaw->src.object) {
121             spaw->modified_connection.disconnect();
122             spaw->release_connection.disconnect();
123             spaw->src.object = NULL;
124         }
125     } else {
127         if (spaw->src.repr) {
128             spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
129         }
130     } // end of if()
132     spaw->modified_connection.~connection();
133     spaw->release_connection.~connection();
135     ((GtkObjectClass *) parent_class)->destroy (object);
141 static void
142 sp_attribute_widget_changed (GtkEditable *editable)
145     SPAttributeWidget *spaw;
147     spaw = SP_ATTRIBUTE_WIDGET (editable);
149     if (!spaw->blocked) {
151         const gchar *text;
152         spaw->blocked = TRUE;
153         text = gtk_entry_get_text (GTK_ENTRY (spaw));
154         if (!*text)
155             text = NULL;
157         if (spaw->hasobj && spaw->src.object) {
158         
159             SP_OBJECT_REPR (spaw->src.object)->setAttribute(spaw->attribute, text, false);
160             DocumentUndo::done(SP_OBJECT_DOCUMENT (spaw->src.object), SP_VERB_NONE,
161                                 _("Set attribute"));
163         } else if (spaw->src.repr) {
165             spaw->src.repr->setAttribute(spaw->attribute, text, false);
166             /* TODO: Warning! Undo will not be flushed in given case */
167         }
168         spaw->blocked = FALSE;
169     }
171 } // end of sp_attribute_widget_changed()
175 GtkWidget *
176 sp_attribute_widget_new ( SPObject *object, const gchar *attribute )
178     SPAttributeWidget *spaw;
180     g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL);
181     g_return_val_if_fail (!object || attribute, NULL);
183     spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET);
185     sp_attribute_widget_set_object (spaw, object, attribute);
187     return GTK_WIDGET (spaw);
189 } // end of sp_attribute_widget_new()
193 GtkWidget *
194 sp_attribute_widget_new_repr ( Inkscape::XML::Node *repr, const gchar *attribute )
196     SPAttributeWidget *spaw;
198     spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET);
200     sp_attribute_widget_set_repr (spaw, repr, attribute);
202     return GTK_WIDGET (spaw);
207 void
208 sp_attribute_widget_set_object ( SPAttributeWidget *spaw,
209                                  SPObject *object,
210                                  const gchar *attribute )
213     g_return_if_fail (spaw != NULL);
214     g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw));
215     g_return_if_fail (!object || SP_IS_OBJECT (object));
216     g_return_if_fail (!object || attribute);
217     g_return_if_fail (attribute != NULL);
219     if (spaw->attribute) {
220         g_free (spaw->attribute);
221         spaw->attribute = NULL;
222     }
224     if (spaw->hasobj) {
226         if (spaw->src.object) {
227             spaw->modified_connection.disconnect();
228             spaw->release_connection.disconnect();
229             spaw->src.object = NULL;
230         }
231     } else {
233         if (spaw->src.repr) {
234             spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
235         }
236     }
238     spaw->hasobj = TRUE;
240     if (object) {
241         const gchar *val;
243         spaw->blocked = TRUE;
244         spaw->src.object = object;
246         spaw->modified_connection = object->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_attribute_widget_object_modified), spaw));
247         spaw->release_connection = object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_attribute_widget_object_release), spaw));
249         spaw->attribute = g_strdup (attribute);
251         val = SP_OBJECT_REPR (object)->attribute(attribute);
252         gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) "");
253         spaw->blocked = FALSE;
254     }
256     gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.object != NULL));
258 } // end of sp_attribute_widget_set_object()
262 void
263 sp_attribute_widget_set_repr ( SPAttributeWidget *spaw,
264                                Inkscape::XML::Node *repr,
265                                const gchar *attribute )
268     g_return_if_fail (spaw != NULL);
269     g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw));
270     g_return_if_fail (attribute != NULL);
272     if (spaw->attribute) {
273         g_free (spaw->attribute);
274         spaw->attribute = NULL;
275     }
277     if (spaw->hasobj) {
279         if (spaw->src.object) {
280             spaw->modified_connection.disconnect();
281             spaw->release_connection.disconnect();
282             spaw->src.object = NULL;
283         }
284     } else {
286         if (spaw->src.repr) {
287             spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
288         }
289     }
291     spaw->hasobj = FALSE;
293     if (repr) {
294         const gchar *val;
296         spaw->blocked = TRUE;
297         spaw->src.repr = Inkscape::GC::anchor(repr);
298         spaw->attribute = g_strdup (attribute);
300         val = repr->attribute(attribute);
301         gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) "");
302         spaw->blocked = FALSE;
303     }
305     gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.repr != NULL));
307 } // end of sp_attribute_widget_set_repr()
311 static void
312 sp_attribute_widget_object_modified ( SPObject */*object*/,
313                                       guint flags,
314                                       SPAttributeWidget *spaw )
317     if (flags && SP_OBJECT_MODIFIED_FLAG) {
319         const gchar *val, *text;
320         val = SP_OBJECT_REPR (spaw->src.object)->attribute(spaw->attribute);
321         text = gtk_entry_get_text (GTK_ENTRY (spaw));
323         if (val || text) {
325             if (!val || !text || strcmp (val, text)) {
326                 /* We are different */
327                 spaw->blocked = TRUE;
328                 gtk_entry_set_text ( GTK_ENTRY (spaw),
329                                      val ? val : (const gchar *) "");
330                 spaw->blocked = FALSE;
331             } // end of if()
333         } // end of if()
335     } //end of if()
337 } // end of sp_attribute_widget_object_modified()
341 static void
342 sp_attribute_widget_object_release ( SPObject */*object*/,
343                                      SPAttributeWidget *spaw )
345     sp_attribute_widget_set_object (spaw, NULL, NULL);
350 /* SPAttributeTable */
352 static void sp_attribute_table_class_init (SPAttributeTableClass *klass);
353 static void sp_attribute_table_init (SPAttributeTable *widget);
354 static void sp_attribute_table_destroy (GtkObject *object);
356 static void sp_attribute_table_object_modified (SPObject *object, guint flags, SPAttributeTable *spaw);
357 static void sp_attribute_table_object_release (SPObject *object, SPAttributeTable *spaw);
358 static void sp_attribute_table_entry_changed (GtkEditable *editable, SPAttributeTable *spat);
360 static GtkVBoxClass *table_parent_class;
365 GType sp_attribute_table_get_type(void)
367     static GtkType type = 0;
368     if (!type) {
369         GTypeInfo info = {
370             sizeof(SPAttributeTableClass),
371             0, // base_init
372             0, // base_finalize
373             (GClassInitFunc)sp_attribute_table_class_init,
374             0, // class_finalize
375             0, // class_data
376             sizeof(SPAttributeTable),
377             0, // n_preallocs
378             (GInstanceInitFunc)sp_attribute_table_init,
379             0 // value_table
380         };
381         type = g_type_register_static(GTK_TYPE_VBOX, "SPAttributeTable", &info, static_cast<GTypeFlags>(0));
382     }
383     return type;
384 } // end of sp_attribute_table_get_type()
388 static void
389 sp_attribute_table_class_init (SPAttributeTableClass *klass)
391     GtkObjectClass *object_class;
392     GtkWidgetClass *widget_class;
394     object_class = GTK_OBJECT_CLASS (klass);
395     widget_class = GTK_WIDGET_CLASS (klass);
397     table_parent_class = (GtkVBoxClass*)gtk_type_class (GTK_TYPE_VBOX);
399     object_class->destroy = sp_attribute_table_destroy;
401 } // end of sp_attribute_table_class_init()
405 static void
406 sp_attribute_table_init ( SPAttributeTable *spat )
408     spat->blocked = FALSE;
409     spat->hasobj = FALSE;
410     spat->table = NULL;
411     spat->src.object = NULL;
412     spat->num_attr = 0;
413     spat->attributes = NULL;
414     spat->entries = NULL;
416     new (&spat->modified_connection) sigc::connection();
417     new (&spat->release_connection) sigc::connection();
420 static void
421 sp_attribute_table_destroy ( GtkObject *object )
423     SPAttributeTable *spat;
425     spat = SP_ATTRIBUTE_TABLE (object);
427     if (spat->attributes) {
428         gint i;
429         for (i = 0; i < spat->num_attr; i++) {
430             g_free (spat->attributes[i]);
431         }
432         g_free (spat->attributes);
433         spat->attributes = NULL;
434     }
436     if (spat->hasobj) {
438         if (spat->src.object) {
439             spat->modified_connection.disconnect();
440             spat->release_connection.disconnect();
441             spat->src.object = NULL;
442         }
443     } else {
444         if (spat->src.repr) {
445             spat->src.repr = Inkscape::GC::release(spat->src.repr);
446         }
447     } // end of if()
449     spat->modified_connection.~connection();
450     spat->release_connection.~connection();
452     if (spat->entries) {
453         g_free (spat->entries);
454         spat->entries = NULL;
455     }
457     spat->table = NULL;
459     if (((GtkObjectClass *) table_parent_class)->destroy) {
460         (* ((GtkObjectClass *) table_parent_class)->destroy) (object);
461     }
463 } // end of sp_attribute_table_destroy()
466 GtkWidget *
467 sp_attribute_table_new ( SPObject *object,
468                          gint num_attr,
469                          const gchar **labels,
470                          const gchar **attributes )
472     SPAttributeTable *spat;
474     g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL);
475     g_return_val_if_fail (!object || (num_attr > 0), NULL);
476     g_return_val_if_fail (!num_attr || (labels && attributes), NULL);
478     spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE);
480     sp_attribute_table_set_object (spat, object, num_attr, labels, attributes);
482     return GTK_WIDGET (spat);
484 } // end of sp_attribute_table_new()
488 GtkWidget *
489 sp_attribute_table_new_repr ( Inkscape::XML::Node *repr,
490                               gint num_attr,
491                               const gchar **labels,
492                               const gchar **attributes )
494     SPAttributeTable *spat;
496     g_return_val_if_fail (!num_attr || (labels && attributes), NULL);
498     spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE);
500     sp_attribute_table_set_repr (spat, repr, num_attr, labels, attributes);
502     return GTK_WIDGET (spat);
504 } // end of sp_attribute_table_new_repr()
508 #define XPAD 4
509 #define YPAD 0
511 void
512 sp_attribute_table_set_object ( SPAttributeTable *spat,
513                                 SPObject *object,
514                                 gint num_attr,
515                                 const gchar **labels,
516                                 const gchar **attributes )
519     g_return_if_fail (spat != NULL);
520     g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat));
521     g_return_if_fail (!object || SP_IS_OBJECT (object));
522     g_return_if_fail (!object || (num_attr > 0));
523     g_return_if_fail (!num_attr || (labels && attributes));
525     if (spat->table) {
526         gtk_widget_destroy (spat->table);
527         spat->table = NULL;
528     }
530     if (spat->attributes) {
531         gint i;
532         for (i = 0; i < spat->num_attr; i++) {
533             g_free (spat->attributes[i]);
534         }
535         g_free (spat->attributes);
536         spat->attributes = NULL;
537     }
539     if (spat->entries) {
540         g_free (spat->entries);
541         spat->entries = NULL;
542     }
544     if (spat->hasobj) {
545         if (spat->src.object) {
546             spat->modified_connection.disconnect();
547             spat->release_connection.disconnect();
548             spat->src.object = NULL;
549         }
550     } else {
551         if (spat->src.repr) {
552             spat->src.repr = Inkscape::GC::release(spat->src.repr);
553         }
554     }
556     spat->hasobj = TRUE;
558     if (object) {
559         gint i;
561         spat->blocked = TRUE;
563         /* Set up object */
564         spat->src.object = object;
565         spat->num_attr = num_attr;
567         spat->modified_connection = object->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_attribute_table_object_modified), spat));
568         spat->release_connection = object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_attribute_table_object_release), spat));
570         /* Create table */
571         spat->table = gtk_table_new (num_attr, 2, FALSE);
572         gtk_container_add (GTK_CONTAINER (spat), spat->table);
573         /* Arrays */
574         spat->attributes = g_new0 (gchar *, num_attr);
575         spat->entries = g_new0 (GtkWidget *, num_attr);
576         /* Fill rows */
577         for (i = 0; i < num_attr; i++) {
578             GtkWidget *w;
579             const gchar *val;
581             spat->attributes[i] = g_strdup (attributes[i]);
582             w = gtk_label_new (_(labels[i]));
583             gtk_widget_show (w);
584             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
585             gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1,
586                                GTK_FILL,
587                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
588                                XPAD, YPAD );
589             w = gtk_entry_new ();
590             gtk_widget_show (w);
591             val = SP_OBJECT_REPR (object)->attribute(attributes[i]);
592             gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) "");
593             gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1,
594                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
595                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
596                                XPAD, YPAD );
597             spat->entries[i] = w;
598             g_signal_connect ( G_OBJECT (w), "changed",
599                                G_CALLBACK (sp_attribute_table_entry_changed),
600                                spat );
601         }
602         /* Show table */
603         gtk_widget_show (spat->table);
605         spat->blocked = FALSE;
606     }
608     gtk_widget_set_sensitive ( GTK_WIDGET (spat),
609                                (spat->src.object != NULL) );
611 } // end of sp_attribute_table_set_object()
615 void
616 sp_attribute_table_set_repr ( SPAttributeTable *spat,
617                               Inkscape::XML::Node *repr,
618                               gint num_attr,
619                               const gchar **labels,
620                               const gchar **attributes )
622     g_return_if_fail (spat != NULL);
623     g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat));
624     g_return_if_fail (!num_attr || (labels && attributes));
626     if (spat->table) {
627         gtk_widget_destroy (spat->table);
628         spat->table = NULL;
629     }
631     if (spat->attributes) {
632         gint i;
633         for (i = 0; i < spat->num_attr; i++) {
634             g_free (spat->attributes[i]);
635         }
636         g_free (spat->attributes);
637         spat->attributes = NULL;
638     }
640     if (spat->entries) {
641         g_free (spat->entries);
642         spat->entries = NULL;
643     }
645     if (spat->hasobj) {
646         if (spat->src.object) {
647             spat->modified_connection.disconnect();
648             spat->release_connection.disconnect();
649             spat->src.object = NULL;
650         }
651     } else {
652         if (spat->src.repr) {
653             spat->src.repr = Inkscape::GC::release(spat->src.repr);
654         }
655     }
657     spat->hasobj = FALSE;
659     if (repr) {
660         gint i;
662         spat->blocked = TRUE;
664         /* Set up repr */
665         spat->src.repr = Inkscape::GC::anchor(repr);
666         spat->num_attr = num_attr;
667         /* Create table */
668         spat->table = gtk_table_new (num_attr, 2, FALSE);
669         gtk_container_add (GTK_CONTAINER (spat), spat->table);
670         /* Arrays */
671         spat->attributes = g_new0 (gchar *, num_attr);
672         spat->entries = g_new0 (GtkWidget *, num_attr);
674         /* Fill rows */
675         for (i = 0; i < num_attr; i++) {
676             GtkWidget *w;
677             const gchar *val;
679             spat->attributes[i] = g_strdup (attributes[i]);
680             w = gtk_label_new (labels[i]);
681             gtk_widget_show (w);
682             gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
683             gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1,
684                                GTK_FILL,
685                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
686                                XPAD, YPAD );
687             w = gtk_entry_new ();
688             gtk_widget_show (w);
689             val = repr->attribute(attributes[i]);
690             gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) "");
691             gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1,
692                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
693                                (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
694                                XPAD, YPAD );
695             spat->entries[i] = w;
696             g_signal_connect ( G_OBJECT (w), "changed",
697                                G_CALLBACK (sp_attribute_table_entry_changed),
698                                spat );
699         }
700         /* Show table */
701         gtk_widget_show (spat->table);
703         spat->blocked = FALSE;
704     }
706     gtk_widget_set_sensitive (GTK_WIDGET (spat), (spat->src.repr != NULL));
708 } // end of sp_attribute_table_set_repr()
712 static void
713 sp_attribute_table_object_modified ( SPObject */*object*/,
714                                      guint flags,
715                                      SPAttributeTable *spat )
717     if (flags && SP_OBJECT_MODIFIED_FLAG)
718     {
719         gint i;
720         for (i = 0; i < spat->num_attr; i++) {
721             const gchar *val, *text;
722             val = SP_OBJECT_REPR (spat->src.object)->attribute(spat->attributes[i]);
723             text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i]));
724             if (val || text) {
725                 if (!val || !text || strcmp (val, text)) {
726                     /* We are different */
727                     spat->blocked = TRUE;
728                     gtk_entry_set_text ( GTK_ENTRY (spat->entries[i]),
729                                          val ? val : (const gchar *) "");
730                     spat->blocked = FALSE;
731                 }
732             }
733         }
734     } // end of if()
736 } // end of sp_attribute_table_object_modified()
740 static void
741 sp_attribute_table_object_release (SPObject */*object*/, SPAttributeTable *spat)
743     sp_attribute_table_set_object (spat, NULL, 0, NULL, NULL);
748 static void
749 sp_attribute_table_entry_changed ( GtkEditable *editable,
750                                    SPAttributeTable *spat )
752     if (!spat->blocked)
753     {
754         gint i;
755         for (i = 0; i < spat->num_attr; i++) {
757             if (GTK_WIDGET (editable) == spat->entries[i]) {
758                 const gchar *text;
759                 spat->blocked = TRUE;
760                 text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i]));
762                 if (!*text)
763                     text = NULL;
765                 if (spat->hasobj && spat->src.object) {
766                     SP_OBJECT_REPR (spat->src.object)->setAttribute(spat->attributes[i], text, false);
767                     DocumentUndo::done(SP_OBJECT_DOCUMENT (spat->src.object), SP_VERB_NONE,
768                                        _("Set attribute"));
770                 } else if (spat->src.repr) {
772                     spat->src.repr->setAttribute(spat->attributes[i], text, false);
773                     /* TODO: Warning! Undo will not be flushed in given case */
774                 }
775                 spat->blocked = FALSE;
776                 return;
777             }
778         }
779         g_warning ("file %s: line %d: Entry signalled change, but there is no such entry", __FILE__, __LINE__);
780     } // end of if()
782 } // end of sp_attribute_table_entry_changed()
784 /*
785   Local Variables:
786   mode:c++
787   c-file-style:"stroustrup"
788   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
789   indent-tabs-mode:nil
790   fill-column:99
791   End:
792 */
793 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :