9848d9f266c86430ee12aff55b9aebac6550571b
1 #define __SP_ATTRIBUTE_WIDGET_C__
3 /**
4 * \brief SPAttributeWidget
5 *
6 * Widget, that listens and modifies repr attributes
7 *
8 * Authors:
9 * Lauris Kaplinski <lauris@ximian.com>
10 *
11 * Copyright (C) 2001 Ximian, Inc.
12 *
13 * Licensed under GNU GPL
14 */
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
19 #include <gtk/gtktable.h>
20 #include <gtk/gtklabel.h>
21 #include "xml/repr.h"
22 #include "macros.h"
23 #include "document.h"
24 #include "sp-object.h"
25 #include <glibmm/i18n.h>
27 #include <sigc++/functors/ptr_fun.h>
28 #include <sigc++/adaptors/bind.h>
30 #include "sp-attribute-widget.h"
32 static void sp_attribute_widget_class_init (SPAttributeWidgetClass *klass);
33 static void sp_attribute_widget_init (SPAttributeWidget *widget);
34 static void sp_attribute_widget_destroy (GtkObject *object);
36 static void sp_attribute_widget_changed (GtkEditable *editable);
38 static void sp_attribute_widget_object_modified ( SPObject *object,
39 guint flags,
40 SPAttributeWidget *spaw );
41 static void sp_attribute_widget_object_release ( SPObject *object,
42 SPAttributeWidget *spaw );
44 static GtkEntryClass *parent_class;
49 GType sp_attribute_widget_get_type(void)
50 {
51 static GtkType type = 0;
52 if (!type) {
53 GTypeInfo info = {
54 sizeof(SPAttributeWidgetClass),
55 0, // base_init
56 0, // base_finalize
57 (GClassInitFunc)sp_attribute_widget_class_init,
58 0, // class_finalize
59 0, // class_data
60 sizeof(SPAttributeWidget),
61 0, // n_preallocs
62 (GInstanceInitFunc)sp_attribute_widget_init,
63 0 // value_table
64 };
65 type = g_type_register_static(GTK_TYPE_ENTRY, "SPAttributeWidget", &info, static_cast<GTypeFlags>(0));
66 }
67 return type;
68 } // end of sp_attribute_widget_get_type()
72 static void
73 sp_attribute_widget_class_init (SPAttributeWidgetClass *klass)
74 {
75 GtkObjectClass *object_class;
76 GtkWidgetClass *widget_class;
77 GtkEditableClass *editable_class;
79 object_class = GTK_OBJECT_CLASS (klass);
80 widget_class = GTK_WIDGET_CLASS (klass);
81 editable_class = GTK_EDITABLE_CLASS (klass);
83 parent_class = (GtkEntryClass*)gtk_type_class (GTK_TYPE_ENTRY);
85 object_class->destroy = sp_attribute_widget_destroy;
87 editable_class->changed = sp_attribute_widget_changed;
89 } // end of sp_attribute_widget_class_init()
93 static void
94 sp_attribute_widget_init (SPAttributeWidget *spaw)
95 {
96 spaw->blocked = FALSE;
97 spaw->hasobj = FALSE;
99 spaw->src.object = NULL;
101 spaw->attribute = NULL;
103 new (&spaw->modified_connection) sigc::connection();
104 new (&spaw->release_connection) sigc::connection();
105 }
109 static void
110 sp_attribute_widget_destroy (GtkObject *object)
111 {
113 SPAttributeWidget *spaw;
115 spaw = SP_ATTRIBUTE_WIDGET (object);
117 if (spaw->attribute) {
118 g_free (spaw->attribute);
119 spaw->attribute = NULL;
120 }
123 if (spaw->hasobj) {
125 if (spaw->src.object) {
126 spaw->modified_connection.disconnect();
127 spaw->release_connection.disconnect();
128 spaw->src.object = NULL;
129 }
130 } else {
132 if (spaw->src.repr) {
133 spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
134 }
135 } // end of if()
137 spaw->modified_connection.~connection();
138 spaw->release_connection.~connection();
140 ((GtkObjectClass *) parent_class)->destroy (object);
142 }
146 static void
147 sp_attribute_widget_changed (GtkEditable *editable)
148 {
150 SPAttributeWidget *spaw;
152 spaw = SP_ATTRIBUTE_WIDGET (editable);
154 if (!spaw->blocked) {
156 const gchar *text;
157 spaw->blocked = TRUE;
158 text = gtk_entry_get_text (GTK_ENTRY (spaw));
159 if (!*text)
160 text = NULL;
162 if (spaw->hasobj && spaw->src.object) {
164 SP_OBJECT_REPR (spaw->src.object)->setAttribute(spaw->attribute, text, false);
165 sp_document_done (SP_OBJECT_DOCUMENT (spaw->src.object), SP_VERB_NONE,
166 _("Set attribute"));
168 } else if (spaw->src.repr) {
170 spaw->src.repr->setAttribute(spaw->attribute, text, false);
171 /* TODO: Warning! Undo will not be flushed in given case */
172 }
173 spaw->blocked = FALSE;
174 }
176 } // end of sp_attribute_widget_changed()
180 GtkWidget *
181 sp_attribute_widget_new ( SPObject *object, const gchar *attribute )
182 {
183 SPAttributeWidget *spaw;
185 g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL);
186 g_return_val_if_fail (!object || attribute, NULL);
188 spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET);
190 sp_attribute_widget_set_object (spaw, object, attribute);
192 return GTK_WIDGET (spaw);
194 } // end of sp_attribute_widget_new()
198 GtkWidget *
199 sp_attribute_widget_new_repr ( Inkscape::XML::Node *repr, const gchar *attribute )
200 {
201 SPAttributeWidget *spaw;
203 spaw = (SPAttributeWidget*)gtk_type_new (SP_TYPE_ATTRIBUTE_WIDGET);
205 sp_attribute_widget_set_repr (spaw, repr, attribute);
207 return GTK_WIDGET (spaw);
208 }
212 void
213 sp_attribute_widget_set_object ( SPAttributeWidget *spaw,
214 SPObject *object,
215 const gchar *attribute )
216 {
218 g_return_if_fail (spaw != NULL);
219 g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw));
220 g_return_if_fail (!object || SP_IS_OBJECT (object));
221 g_return_if_fail (!object || attribute);
222 g_return_if_fail (attribute != NULL);
224 if (spaw->attribute) {
225 g_free (spaw->attribute);
226 spaw->attribute = NULL;
227 }
229 if (spaw->hasobj) {
231 if (spaw->src.object) {
232 spaw->modified_connection.disconnect();
233 spaw->release_connection.disconnect();
234 spaw->src.object = NULL;
235 }
236 } else {
238 if (spaw->src.repr) {
239 spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
240 }
241 }
243 spaw->hasobj = TRUE;
245 if (object) {
246 const gchar *val;
248 spaw->blocked = TRUE;
249 spaw->src.object = object;
251 spaw->modified_connection = object->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_attribute_widget_object_modified), spaw));
252 spaw->release_connection = object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_attribute_widget_object_release), spaw));
254 spaw->attribute = g_strdup (attribute);
256 val = SP_OBJECT_REPR (object)->attribute(attribute);
257 gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) "");
258 spaw->blocked = FALSE;
259 }
261 gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.object != NULL));
263 } // end of sp_attribute_widget_set_object()
267 void
268 sp_attribute_widget_set_repr ( SPAttributeWidget *spaw,
269 Inkscape::XML::Node *repr,
270 const gchar *attribute )
271 {
273 g_return_if_fail (spaw != NULL);
274 g_return_if_fail (SP_IS_ATTRIBUTE_WIDGET (spaw));
275 g_return_if_fail (attribute != NULL);
277 if (spaw->attribute) {
278 g_free (spaw->attribute);
279 spaw->attribute = NULL;
280 }
282 if (spaw->hasobj) {
284 if (spaw->src.object) {
285 spaw->modified_connection.disconnect();
286 spaw->release_connection.disconnect();
287 spaw->src.object = NULL;
288 }
289 } else {
291 if (spaw->src.repr) {
292 spaw->src.repr = Inkscape::GC::release(spaw->src.repr);
293 }
294 }
296 spaw->hasobj = FALSE;
298 if (repr) {
299 const gchar *val;
301 spaw->blocked = TRUE;
302 spaw->src.repr = Inkscape::GC::anchor(repr);
303 spaw->attribute = g_strdup (attribute);
305 val = repr->attribute(attribute);
306 gtk_entry_set_text (GTK_ENTRY (spaw), val ? val : (const gchar *) "");
307 spaw->blocked = FALSE;
308 }
310 gtk_widget_set_sensitive (GTK_WIDGET (spaw), (spaw->src.repr != NULL));
312 } // end of sp_attribute_widget_set_repr()
316 static void
317 sp_attribute_widget_object_modified ( SPObject */*object*/,
318 guint flags,
319 SPAttributeWidget *spaw )
320 {
322 if (flags && SP_OBJECT_MODIFIED_FLAG) {
324 const gchar *val, *text;
325 val = SP_OBJECT_REPR (spaw->src.object)->attribute(spaw->attribute);
326 text = gtk_entry_get_text (GTK_ENTRY (spaw));
328 if (val || text) {
330 if (!val || !text || strcmp (val, text)) {
331 /* We are different */
332 spaw->blocked = TRUE;
333 gtk_entry_set_text ( GTK_ENTRY (spaw),
334 val ? val : (const gchar *) "");
335 spaw->blocked = FALSE;
336 } // end of if()
338 } // end of if()
340 } //end of if()
342 } // end of sp_attribute_widget_object_modified()
346 static void
347 sp_attribute_widget_object_release ( SPObject */*object*/,
348 SPAttributeWidget *spaw )
349 {
350 sp_attribute_widget_set_object (spaw, NULL, NULL);
351 }
355 /* SPAttributeTable */
357 static void sp_attribute_table_class_init (SPAttributeTableClass *klass);
358 static void sp_attribute_table_init (SPAttributeTable *widget);
359 static void sp_attribute_table_destroy (GtkObject *object);
361 static void sp_attribute_table_object_modified (SPObject *object, guint flags, SPAttributeTable *spaw);
362 static void sp_attribute_table_object_release (SPObject *object, SPAttributeTable *spaw);
363 static void sp_attribute_table_entry_changed (GtkEditable *editable, SPAttributeTable *spat);
365 static GtkVBoxClass *table_parent_class;
370 GType sp_attribute_table_get_type(void)
371 {
372 static GtkType type = 0;
373 if (!type) {
374 GTypeInfo info = {
375 sizeof(SPAttributeTableClass),
376 0, // base_init
377 0, // base_finalize
378 (GClassInitFunc)sp_attribute_table_class_init,
379 0, // class_finalize
380 0, // class_data
381 sizeof(SPAttributeTable),
382 0, // n_preallocs
383 (GInstanceInitFunc)sp_attribute_table_init,
384 0 // value_table
385 };
386 type = g_type_register_static(GTK_TYPE_VBOX, "SPAttributeTable", &info, static_cast<GTypeFlags>(0));
387 }
388 return type;
389 } // end of sp_attribute_table_get_type()
393 static void
394 sp_attribute_table_class_init (SPAttributeTableClass *klass)
395 {
396 GtkObjectClass *object_class;
397 GtkWidgetClass *widget_class;
399 object_class = GTK_OBJECT_CLASS (klass);
400 widget_class = GTK_WIDGET_CLASS (klass);
402 table_parent_class = (GtkVBoxClass*)gtk_type_class (GTK_TYPE_VBOX);
404 object_class->destroy = sp_attribute_table_destroy;
406 } // end of sp_attribute_table_class_init()
410 static void
411 sp_attribute_table_init ( SPAttributeTable *spat )
412 {
413 spat->blocked = FALSE;
414 spat->hasobj = FALSE;
415 spat->table = NULL;
416 spat->src.object = NULL;
417 spat->num_attr = 0;
418 spat->attributes = NULL;
419 spat->entries = NULL;
421 new (&spat->modified_connection) sigc::connection();
422 new (&spat->release_connection) sigc::connection();
423 }
425 static void
426 sp_attribute_table_destroy ( GtkObject *object )
427 {
428 SPAttributeTable *spat;
430 spat = SP_ATTRIBUTE_TABLE (object);
432 if (spat->attributes) {
433 gint i;
434 for (i = 0; i < spat->num_attr; i++) {
435 g_free (spat->attributes[i]);
436 }
437 g_free (spat->attributes);
438 spat->attributes = NULL;
439 }
441 if (spat->hasobj) {
443 if (spat->src.object) {
444 spat->modified_connection.disconnect();
445 spat->release_connection.disconnect();
446 spat->src.object = NULL;
447 }
448 } else {
449 if (spat->src.repr) {
450 spat->src.repr = Inkscape::GC::release(spat->src.repr);
451 }
452 } // end of if()
454 spat->modified_connection.~connection();
455 spat->release_connection.~connection();
457 if (spat->entries) {
458 g_free (spat->entries);
459 spat->entries = NULL;
460 }
462 spat->table = NULL;
464 if (((GtkObjectClass *) table_parent_class)->destroy) {
465 (* ((GtkObjectClass *) table_parent_class)->destroy) (object);
466 }
468 } // end of sp_attribute_table_destroy()
471 GtkWidget *
472 sp_attribute_table_new ( SPObject *object,
473 gint num_attr,
474 const gchar **labels,
475 const gchar **attributes )
476 {
477 SPAttributeTable *spat;
479 g_return_val_if_fail (!object || SP_IS_OBJECT (object), NULL);
480 g_return_val_if_fail (!object || (num_attr > 0), NULL);
481 g_return_val_if_fail (!num_attr || (labels && attributes), NULL);
483 spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE);
485 sp_attribute_table_set_object (spat, object, num_attr, labels, attributes);
487 return GTK_WIDGET (spat);
489 } // end of sp_attribute_table_new()
493 GtkWidget *
494 sp_attribute_table_new_repr ( Inkscape::XML::Node *repr,
495 gint num_attr,
496 const gchar **labels,
497 const gchar **attributes )
498 {
499 SPAttributeTable *spat;
501 g_return_val_if_fail (!num_attr || (labels && attributes), NULL);
503 spat = (SPAttributeTable*)gtk_type_new (SP_TYPE_ATTRIBUTE_TABLE);
505 sp_attribute_table_set_repr (spat, repr, num_attr, labels, attributes);
507 return GTK_WIDGET (spat);
509 } // end of sp_attribute_table_new_repr()
513 #define XPAD 4
514 #define YPAD 0
516 void
517 sp_attribute_table_set_object ( SPAttributeTable *spat,
518 SPObject *object,
519 gint num_attr,
520 const gchar **labels,
521 const gchar **attributes )
522 {
524 g_return_if_fail (spat != NULL);
525 g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat));
526 g_return_if_fail (!object || SP_IS_OBJECT (object));
527 g_return_if_fail (!object || (num_attr > 0));
528 g_return_if_fail (!num_attr || (labels && attributes));
530 if (spat->table) {
531 gtk_widget_destroy (spat->table);
532 spat->table = NULL;
533 }
535 if (spat->attributes) {
536 gint i;
537 for (i = 0; i < spat->num_attr; i++) {
538 g_free (spat->attributes[i]);
539 }
540 g_free (spat->attributes);
541 spat->attributes = NULL;
542 }
544 if (spat->entries) {
545 g_free (spat->entries);
546 spat->entries = NULL;
547 }
549 if (spat->hasobj) {
550 if (spat->src.object) {
551 spat->modified_connection.disconnect();
552 spat->release_connection.disconnect();
553 spat->src.object = NULL;
554 }
555 } else {
556 if (spat->src.repr) {
557 spat->src.repr = Inkscape::GC::release(spat->src.repr);
558 }
559 }
561 spat->hasobj = TRUE;
563 if (object) {
564 gint i;
566 spat->blocked = TRUE;
568 /* Set up object */
569 spat->src.object = object;
570 spat->num_attr = num_attr;
572 spat->modified_connection = object->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_attribute_table_object_modified), spat));
573 spat->release_connection = object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_attribute_table_object_release), spat));
575 /* Create table */
576 spat->table = gtk_table_new (num_attr, 2, FALSE);
577 gtk_container_add (GTK_CONTAINER (spat), spat->table);
578 /* Arrays */
579 spat->attributes = g_new0 (gchar *, num_attr);
580 spat->entries = g_new0 (GtkWidget *, num_attr);
581 /* Fill rows */
582 for (i = 0; i < num_attr; i++) {
583 GtkWidget *w;
584 const gchar *val;
586 spat->attributes[i] = g_strdup (attributes[i]);
587 w = gtk_label_new (_(labels[i]));
588 gtk_widget_show (w);
589 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
590 gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1,
591 GTK_FILL,
592 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
593 XPAD, YPAD );
594 w = gtk_entry_new ();
595 gtk_widget_show (w);
596 val = SP_OBJECT_REPR (object)->attribute(attributes[i]);
597 gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) "");
598 gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1,
599 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
600 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
601 XPAD, YPAD );
602 spat->entries[i] = w;
603 g_signal_connect ( G_OBJECT (w), "changed",
604 G_CALLBACK (sp_attribute_table_entry_changed),
605 spat );
606 }
607 /* Show table */
608 gtk_widget_show (spat->table);
610 spat->blocked = FALSE;
611 }
613 gtk_widget_set_sensitive ( GTK_WIDGET (spat),
614 (spat->src.object != NULL) );
616 } // end of sp_attribute_table_set_object()
620 void
621 sp_attribute_table_set_repr ( SPAttributeTable *spat,
622 Inkscape::XML::Node *repr,
623 gint num_attr,
624 const gchar **labels,
625 const gchar **attributes )
626 {
627 g_return_if_fail (spat != NULL);
628 g_return_if_fail (SP_IS_ATTRIBUTE_TABLE (spat));
629 g_return_if_fail (!num_attr || (labels && attributes));
631 if (spat->table) {
632 gtk_widget_destroy (spat->table);
633 spat->table = NULL;
634 }
636 if (spat->attributes) {
637 gint i;
638 for (i = 0; i < spat->num_attr; i++) {
639 g_free (spat->attributes[i]);
640 }
641 g_free (spat->attributes);
642 spat->attributes = NULL;
643 }
645 if (spat->entries) {
646 g_free (spat->entries);
647 spat->entries = NULL;
648 }
650 if (spat->hasobj) {
651 if (spat->src.object) {
652 spat->modified_connection.disconnect();
653 spat->release_connection.disconnect();
654 spat->src.object = NULL;
655 }
656 } else {
657 if (spat->src.repr) {
658 spat->src.repr = Inkscape::GC::release(spat->src.repr);
659 }
660 }
662 spat->hasobj = FALSE;
664 if (repr) {
665 gint i;
667 spat->blocked = TRUE;
669 /* Set up repr */
670 spat->src.repr = Inkscape::GC::anchor(repr);
671 spat->num_attr = num_attr;
672 /* Create table */
673 spat->table = gtk_table_new (num_attr, 2, FALSE);
674 gtk_container_add (GTK_CONTAINER (spat), spat->table);
675 /* Arrays */
676 spat->attributes = g_new0 (gchar *, num_attr);
677 spat->entries = g_new0 (GtkWidget *, num_attr);
679 /* Fill rows */
680 for (i = 0; i < num_attr; i++) {
681 GtkWidget *w;
682 const gchar *val;
684 spat->attributes[i] = g_strdup (attributes[i]);
685 w = gtk_label_new (labels[i]);
686 gtk_widget_show (w);
687 gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
688 gtk_table_attach ( GTK_TABLE (spat->table), w, 0, 1, i, i + 1,
689 GTK_FILL,
690 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
691 XPAD, YPAD );
692 w = gtk_entry_new ();
693 gtk_widget_show (w);
694 val = repr->attribute(attributes[i]);
695 gtk_entry_set_text (GTK_ENTRY (w), val ? val : (const gchar *) "");
696 gtk_table_attach ( GTK_TABLE (spat->table), w, 1, 2, i, i + 1,
697 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
698 (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
699 XPAD, YPAD );
700 spat->entries[i] = w;
701 g_signal_connect ( G_OBJECT (w), "changed",
702 G_CALLBACK (sp_attribute_table_entry_changed),
703 spat );
704 }
705 /* Show table */
706 gtk_widget_show (spat->table);
708 spat->blocked = FALSE;
709 }
711 gtk_widget_set_sensitive (GTK_WIDGET (spat), (spat->src.repr != NULL));
713 } // end of sp_attribute_table_set_repr()
717 static void
718 sp_attribute_table_object_modified ( SPObject */*object*/,
719 guint flags,
720 SPAttributeTable *spat )
721 {
722 if (flags && SP_OBJECT_MODIFIED_FLAG)
723 {
724 gint i;
725 for (i = 0; i < spat->num_attr; i++) {
726 const gchar *val, *text;
727 val = SP_OBJECT_REPR (spat->src.object)->attribute(spat->attributes[i]);
728 text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i]));
729 if (val || text) {
730 if (!val || !text || strcmp (val, text)) {
731 /* We are different */
732 spat->blocked = TRUE;
733 gtk_entry_set_text ( GTK_ENTRY (spat->entries[i]),
734 val ? val : (const gchar *) "");
735 spat->blocked = FALSE;
736 }
737 }
738 }
739 } // end of if()
741 } // end of sp_attribute_table_object_modified()
745 static void
746 sp_attribute_table_object_release (SPObject */*object*/, SPAttributeTable *spat)
747 {
748 sp_attribute_table_set_object (spat, NULL, 0, NULL, NULL);
749 }
753 static void
754 sp_attribute_table_entry_changed ( GtkEditable *editable,
755 SPAttributeTable *spat )
756 {
757 if (!spat->blocked)
758 {
759 gint i;
760 for (i = 0; i < spat->num_attr; i++) {
762 if (GTK_WIDGET (editable) == spat->entries[i]) {
763 const gchar *text;
764 spat->blocked = TRUE;
765 text = gtk_entry_get_text (GTK_ENTRY (spat->entries[i]));
767 if (!*text)
768 text = NULL;
770 if (spat->hasobj && spat->src.object) {
771 SP_OBJECT_REPR (spat->src.object)->setAttribute(spat->attributes[i], text, false);
772 sp_document_done (SP_OBJECT_DOCUMENT (spat->src.object), SP_VERB_NONE,
773 _("Set attribute"));
775 } else if (spat->src.repr) {
777 spat->src.repr->setAttribute(spat->attributes[i], text, false);
778 /* TODO: Warning! Undo will not be flushed in given case */
779 }
780 spat->blocked = FALSE;
781 return;
782 }
783 }
784 g_warning ("file %s: line %d: Entry signalled change, but there is no such entry", __FILE__, __LINE__);
785 } // end of if()
787 } // end of sp_attribute_table_entry_changed()
789 /*
790 Local Variables:
791 mode:c++
792 c-file-style:"stroustrup"
793 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
794 indent-tabs-mode:nil
795 fill-column:99
796 End:
797 */
798 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :