Code

#include svg/svg-color.h instead of or as well as (as appropriate) svg/svg.h.
[inkscape.git] / src / interface.cpp
1 #define __SP_INTERFACE_C__
3 /**
4  * Main UI stuff
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Frank Felfe <innerspace@iname.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *
11  * Copyright (C) 1999-2005 authors
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  * Copyright (C) 2004 David Turner
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include <gtk/gtk.h>
23 #include "inkscape-private.h"
24 #include "extension/effect.h"
25 #include "widgets/icon.h"
26 #include "prefs-utils.h"
27 #include "path-prefix.h"
29 #include "shortcuts.h"
31 #include "document.h"
32 #include "desktop-handles.h"
33 #include "file.h"
34 #include "interface.h"
35 #include "desktop.h"
36 #include "object-ui.h"
37 #include "selection.h"
38 #include "selection-chemistry.h"
39 #include "svg-view-widget.h"
40 #include "widgets/desktop-widget.h"
41 #include "sp-item-group.h"
42 #include "sp-namedview.h"
44 #include "helper/action.h"
45 #include "helper/gnome-utils.h"
46 #include "helper/window.h"
48 #include "io/sys.h"
49 #include "io/stringstream.h"
50 #include "io/base64stream.h"
52 #include "dialogs/dialog-events.h"
54 #include "message-context.h"
56 // Added for color drag-n-drop
57 #include "display/sp-canvas.h"
58 #include "color.h"
59 #include "svg/svg-color.h"
60 #include "desktop-style.h"
61 #include "style.h"
64 using Inkscape::IO::StringOutputStream;
65 using Inkscape::IO::Base64OutputStream;
67 /* forward declaration */
68 static gint sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view);
70 /* Drag and Drop */
71 typedef enum {
72     URI_LIST,
73     SVG_XML_DATA,
74     SVG_DATA,
75     PNG_DATA,
76     JPEG_DATA,
77     IMAGE_DATA,
78     APP_X_COLOR
79 } ui_drop_target_info;
81 static GtkTargetEntry ui_drop_target_entries [] = {
82     {"text/uri-list", 0, URI_LIST},
83     {"image/svg+xml", 0, SVG_XML_DATA},
84     {"image/svg",     0, SVG_DATA},
85     {"image/png",     0, PNG_DATA},
86     {"image/jpeg",    0, JPEG_DATA},
87     {"application/x-color", 0, APP_X_COLOR}
88 };
90 static GtkTargetEntry *completeDropTargets = 0;
91 static int completeDropTargetsCount = 0;
93 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
94 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
95 static void sp_ui_import_files(gchar *buffer);
96 static void sp_ui_import_one_file(char const *filename);
97 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
98 static void sp_ui_drag_data_received(GtkWidget *widget,
99                                      GdkDragContext *drag_context,
100                                      gint x, gint y,
101                                      GtkSelectionData *data,
102                                      guint info,
103                                      guint event_time,
104                                      gpointer user_data);
105 static void sp_ui_menu_item_set_sensitive(SPAction *action,
106                                           unsigned int sensitive,
107                                           void *data);
109 SPActionEventVector menu_item_event_vector = {
110     {NULL},
111     NULL,
112     NULL, /* set_active */
113     sp_ui_menu_item_set_sensitive, /* set_sensitive */
114     NULL  /* set_shortcut */
115 };
117 void
118 sp_create_window(SPViewWidget *vw, gboolean editable)
120     GtkWidget *w, *hb;
122     g_return_if_fail(vw != NULL);
123     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
125     w = sp_window_new("", TRUE);
127     if (editable) {
128       g_object_set_data(G_OBJECT(vw), "window", w);
129       reinterpret_cast<SPDesktopWidget*>(vw)->window =
130         static_cast<GtkWindow*>((void*)w);
131     }
133     hb = gtk_hbox_new(FALSE, 0);
134     gtk_widget_show(hb);
135     gtk_container_add(GTK_CONTAINER(w), hb);
136     g_object_set_data(G_OBJECT(w), "hbox", hb);
138     /* fixme: */
139     if (editable) {
140         gtk_window_set_default_size((GtkWindow *) w, 640, 480);
141         g_object_set_data(G_OBJECT(w), "desktop", SP_DESKTOP_WIDGET(vw)->desktop);
142         g_object_set_data(G_OBJECT(w), "desktopwidget", vw);
143         g_signal_connect(G_OBJECT(w), "delete_event", G_CALLBACK(sp_ui_delete), vw->view);
144         g_signal_connect(G_OBJECT(w), "focus_in_event", G_CALLBACK(sp_desktop_widget_set_focus), vw);
145     } else {
146         gtk_window_set_policy(GTK_WINDOW(w), TRUE, TRUE, TRUE);
147     }
149     gtk_box_pack_end(GTK_BOX(hb), GTK_WIDGET(vw), TRUE, TRUE, 0);
150     gtk_widget_show(GTK_WIDGET(vw));
153     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
154     {
155         std::vector<gchar*> types;
157         GSList *list = gdk_pixbuf_get_formats();
158         while ( list ) {
159             int i = 0;
160             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
161             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
162             for ( i = 0; typesXX[i]; i++ ) {
163                 types.push_back(g_strdup(typesXX[i]));
164             }
165             g_strfreev(typesXX);
167             list = g_slist_next(list);
168         }
169         completeDropTargetsCount = nui_drop_target_entries + types.size();
170         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
171         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
172             completeDropTargets[i] = ui_drop_target_entries[i];
173         }
174         int pos = nui_drop_target_entries;
176         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
177             completeDropTargets[pos].target = *it;
178             completeDropTargets[pos].flags = 0;
179             completeDropTargets[pos].info = IMAGE_DATA;
180             pos++;
181         }
182     }
184     gtk_drag_dest_set(w,
185                       GTK_DEST_DEFAULT_ALL,
186                       completeDropTargets,
187                       completeDropTargetsCount,
188                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
189     g_signal_connect(G_OBJECT(w),
190                      "drag_data_received",
191                      G_CALLBACK(sp_ui_drag_data_received),
192                      NULL);
193     gtk_widget_show(w);
195     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
196     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
199 void
200 sp_ui_new_view()
202     SPDocument *document;
203     SPViewWidget *dtw;
205     document = SP_ACTIVE_DOCUMENT;
206     if (!document) return;
208     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
209     g_return_if_fail(dtw != NULL);
211     sp_create_window(dtw, TRUE);
212     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
215 /* TODO: not yet working */
216 /* To be re-enabled (by adding to menu) once it works. */
217 void
218 sp_ui_new_view_preview()
220     SPDocument *document;
221     SPViewWidget *dtw;
223     document = SP_ACTIVE_DOCUMENT;
224     if (!document) return;
226     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
227     g_return_if_fail(dtw != NULL);
228     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
230     sp_create_window(dtw, FALSE);
233 /**
234  * \param widget unused
235  */
236 void
237 sp_ui_close_view(GtkWidget *widget)
239     if (SP_ACTIVE_DESKTOP == NULL) {
240         return;
241     }
242     if ((SP_ACTIVE_DESKTOP)->shutdown()) {
243         return;
244     }
245     SP_ACTIVE_DESKTOP->destroyWidget();
249 /**
250  *  sp_ui_close_all
251  *
252  *  This function is called to exit the program, and iterates through all
253  *  open document view windows, attempting to close each in turn.  If the
254  *  view has unsaved information, the user will be prompted to save,
255  *  discard, or cancel.
256  *
257  *  Returns FALSE if the user cancels the close_all operation, TRUE
258  *  otherwise.
259  */
260 unsigned int
261 sp_ui_close_all(void)
263     /* Iterate through all the windows, destroying each in the order they
264        become active */
265     while (SP_ACTIVE_DESKTOP) {
266         if ((SP_ACTIVE_DESKTOP)->shutdown()) {
267             /* The user cancelled the operation, so end doing the close */
268             return FALSE;
269         }
270         SP_ACTIVE_DESKTOP->destroyWidget();
271     }
273     return TRUE;
276 static gint
277 sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view)
279     return view->shutdown();
282 /*
283  * Some day when the right-click menus are ready to start working
284  * smarter with the verbs, we'll need to change this NULL being
285  * sent to sp_action_perform to something useful, or set some kind
286  * of global "right-clicked position" variable for actions to
287  * investigate when they're called.
288  */
289 static void
290 sp_ui_menu_activate(void *object, SPAction *action)
292     sp_action_perform(action, NULL);
295 static void
296 sp_ui_menu_select_action(void *object, SPAction *action)
298     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
301 static void
302 sp_ui_menu_deselect_action(void *object, SPAction *action)
304     action->view->tipsMessageContext()->clear();
307 static void
308 sp_ui_menu_select(gpointer object, gpointer tip)
310     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
311     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
314 static void
315 sp_ui_menu_deselect(gpointer object)
317     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
318     view->tipsMessageContext()->clear();
321 /**
322  * sp_ui_menuitem_add_icon
323  *
324  * Creates and attaches a scaled icon to the given menu item.
325  *
326  */
327 void
328 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
330     GtkWidget *icon;
332     icon = sp_icon_new( GTK_ICON_SIZE_MENU, icon_name );
333     gtk_widget_show(icon);
334     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
335 } // end of sp_ui_menu_add_icon
337 /**
338  * sp_ui_menu_append_item
339  *
340  * Appends a UI item with specific info for Inkscape/Sodipodi.
341  *
342  */
343 static GtkWidget *
344 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
345                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
346                         gpointer data, gboolean with_mnemonic = TRUE )
348     GtkWidget *item;
350     if (stock) {
351         item = gtk_image_menu_item_new_from_stock(stock, NULL);
352     } else if (label) {
353         item = (with_mnemonic)
354             ? gtk_image_menu_item_new_with_mnemonic(label) :
355             gtk_image_menu_item_new_with_label(label);
356     } else {
357         item = gtk_separator_menu_item_new();
358     }
360     gtk_widget_show(item);
362     if (callback) {
363         g_signal_connect(G_OBJECT(item), "activate", callback, data);
364     }
366     if (tip && view) {
367         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
368         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
369         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
370     }
372     gtk_menu_append(GTK_MENU(menu), item);
374     return item;
376 } // end of sp_ui_menu_append_item()
378 /**
379 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
380  */
381 static gchar const *
382 sp_key_name(guint keyval)
384     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
385        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
386     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
388     if      (!strcmp(n, "asciicircum"))  return "^";
389     else if (!strcmp(n, "parenleft"  ))  return "(";
390     else if (!strcmp(n, "parenright" ))  return ")";
391     else if (!strcmp(n, "plus"       ))  return "+";
392     else if (!strcmp(n, "minus"      ))  return "-";
393     else if (!strcmp(n, "asterisk"   ))  return "*";
394     else if (!strcmp(n, "KP_Multiply"))  return "*";
395     else if (!strcmp(n, "Delete"     ))  return "Del";
396     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
397     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
398     else if (!strcmp(n, "grave"      ))  return "`";
399     else if (!strcmp(n, "numbersign" ))  return "#";
400     else if (!strcmp(n, "bar" ))  return "|";
401     else if (!strcmp(n, "slash" ))  return "/";
402     else if (!strcmp(n, "exclam" ))  return "!";
403     else return n;
407 /**
408  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
409  * \param c Points to a buffer at least 256 bytes long.
410  */
411 void
412 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
414     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
415      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
416      * Will probably need to change sp_shortcut_invoke callers.
417      *
418      * The existing gtk_label_new_with_mnemonic call can be replaced with
419      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
420      * gtk_label_set_text_with_mnemonic(lbl, str).
421      */
422     static GtkAccelLabelClass const &accel_lbl_cls
423         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
425     struct { unsigned test; char const *name; } const modifier_tbl[] = {
426         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
427         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
428         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
429     };
431     gchar *p = c;
432     gchar *end = p + 256;
434     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
435         if ((shortcut & modifier_tbl[i].test)
436             && (p < end))
437         {
438             p += g_snprintf(p, end - p, "%s%s",
439                             modifier_tbl[i].name,
440                             accel_lbl_cls.mod_separator);
441         }
442     }
443     if (p < end) {
444         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
445     }
446     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
449 void
450 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
452     SPAction     *action;
453     unsigned int shortcut;
454     gchar        *s;
455     gchar        key[256];
456     gchar        *atitle;
458     action = verb->get_action(NULL);
459     if (!action)
460         return;
462     atitle = sp_action_get_title(action);
464     s = g_stpcpy(c, atitle);
466     g_free(atitle);
468     shortcut = sp_shortcut_get_primary(verb);
469     if (shortcut) {
470         s = g_stpcpy(s, " (");
471         sp_ui_shortcut_string(shortcut, key);
472         s = g_stpcpy(s, key);
473         s = g_stpcpy(s, ")");
474     }
478 /**
479  * sp_ui_menu_append_item_from_verb
480  *
481  * Appends a custom menu UI from a verb.
482  *
483  */
485 static GtkWidget *
486 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
488     SPAction *action;
489     GtkWidget *item;
491     if (verb->get_code() == SP_VERB_NONE) {
493         item = gtk_separator_menu_item_new();
495     } else {
496         unsigned int shortcut;
498         action = verb->get_action(view);
500         if (!action) return NULL;
502         shortcut = sp_shortcut_get_primary(verb);
503         if (shortcut) {
504             gchar c[256];
505             sp_ui_shortcut_string(shortcut, c);
506             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
507             GtkWidget *const name_lbl = gtk_label_new("");
508             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
509             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
510             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
511             GtkWidget *const accel_lbl = gtk_label_new(c);
512             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
513             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
514             gtk_widget_show_all(hb);
515             if (radio) {
516                 item = gtk_radio_menu_item_new (group);
517             } else {
518                 item = gtk_image_menu_item_new();
519             }
520             gtk_container_add((GtkContainer *) item, hb);
521         } else {
522             if (radio) {
523                 item = gtk_radio_menu_item_new (group);
524             } else {
525                 item = gtk_image_menu_item_new ();
526             }
527             GtkWidget *const name_lbl = gtk_label_new("");
528             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
529             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
530             gtk_container_add((GtkContainer *) item, name_lbl);
531         }
533         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
534         if (!action->sensitive) {
535             gtk_widget_set_sensitive(item, FALSE);
536         }
538         if (action->image) {
539             sp_ui_menuitem_add_icon(item, action->image);
540         }
541         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
542         g_signal_connect( G_OBJECT(item), "activate",
543                           G_CALLBACK(sp_ui_menu_activate), action );
545         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
546         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
547     }
549     gtk_widget_show(item);
550     gtk_menu_append(GTK_MENU(menu), item);
552     return item;
554 } // end of sp_ui_menu_append_item_from_verb
557 static void
558 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
560     gchar const *pref = (gchar const *) user_data;
561     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
563     gchar const *pref_path;
564     if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen)
565         pref_path = g_strconcat("fullscreen.", pref, NULL);
566     else
567         pref_path = g_strconcat("window.", pref, NULL);
569     gboolean checked = gtk_check_menu_item_get_active(menuitem);
570     prefs_set_int_attribute(pref_path, "state", checked);
572     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
575 static gboolean
576 checkitem_update(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
578     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
580     gchar const *pref = (gchar const *) user_data;
581     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
583     gchar const *pref_path;
584     if (static_cast<SPDesktop*>(view)->is_fullscreen)
585         pref_path = g_strconcat("fullscreen.", pref, NULL);
586     else
587         pref_path = g_strconcat("window.", pref, NULL);
589     gint ison = prefs_get_int_attribute_limited(pref_path, "state", 1, 0, 1);
591     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
592     gtk_check_menu_item_set_active(menuitem, ison);
593     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
595     return FALSE;
599 void
600 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
601                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
602                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
603                                        Inkscape::Verb *verb)
605     GtkWidget *item;
607     unsigned int shortcut = 0;
608     SPAction *action = NULL;
610     if (verb) {
611         shortcut = sp_shortcut_get_primary(verb);
612         action = verb->get_action(view);
613     }
615     if (verb && shortcut) {
616         gchar c[256];
617         sp_ui_shortcut_string(shortcut, c);
619         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
621         {
622             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
623             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
624             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
625         }
627         {
628             GtkWidget *l = gtk_label_new(c);
629             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
630             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
631         }
633         gtk_widget_show_all(hb);
635         item = gtk_check_menu_item_new();
636         gtk_container_add((GtkContainer *) item, hb);
637     } else {
638         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
639         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
640         item = gtk_check_menu_item_new();
641         gtk_container_add((GtkContainer *) item, l);
642     }
643 #if 0
644     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
645     if (!action->sensitive) {
646         gtk_widget_set_sensitive(item, FALSE);
647     }
648 #endif
649     gtk_widget_show(item);
651     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
653     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
655     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
656     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
658     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
659     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
662 static void
663 sp_recent_open(GtkWidget *widget, gchar const *uri)
665     sp_file_open(uri, NULL);
668 static void
669 sp_file_new_from_template(GtkWidget *widget, gchar const *uri)
671     sp_file_new(uri);
674 void
675 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
677     std::list<gchar *> sources;
678     sources.push_back( profile_path("templates") ); // first try user's local dir
679     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
681     // Use this loop to iterate through a list of possible document locations.
682     while (!sources.empty()) {
683         gchar *dirname = sources.front();
685         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
686             GError *err = 0;
687             GDir *dir = g_dir_open(dirname, 0, &err);
689             if (dir) {
690                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
691                     if (!g_str_has_suffix(file, ".svg"))
692                         continue; // skip non-svg files
694                     gchar *basename = g_path_get_basename(file);
695                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
696                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
698                     gchar const *filepath = g_build_filename(dirname, file, NULL);
699                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
700                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
701                     g_free(dupfile);
702                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
703                     g_free(filename);
705                     gtk_widget_show(item);
706                     // how does "filepath" ever get freed?
707                     g_signal_connect(G_OBJECT(item),
708                                      "activate",
709                                      G_CALLBACK(sp_file_new_from_template),
710                                      (gpointer) filepath);
712                     if (view) {
713                         // set null tip for now; later use a description from the template file
714                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
715                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
716                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
717                     }
719                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
720                 }
721                 g_dir_close(dir);
722             }
723         }
725         // toss the dirname
726         g_free(dirname);
727         sources.pop_front();
728     }
731 void
732 sp_menu_append_recent_documents(GtkWidget *menu, Inkscape::UI::View::View* /* view */)
734     gchar const **recent = prefs_get_recent_files();
735     if (recent) {
736         int i;
738         for (i = 0; recent[i] != NULL; i += 2) {
739             gchar const *uri = recent[i];
740             gchar const *name = recent[i + 1];
742             GtkWidget *item = gtk_menu_item_new_with_label(name);
743             gtk_widget_show(item);
744             g_signal_connect(G_OBJECT(item),
745                              "activate",
746                              G_CALLBACK(sp_recent_open),
747                              (gpointer)uri);
748             gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
749         }
751         g_free(recent);
752     } else {
753         GtkWidget *item = gtk_menu_item_new_with_label(_("None"));
754         gtk_widget_show(item);
755         gtk_widget_set_sensitive(item, FALSE);
756         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
757     }
760 void
761 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
763     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
764     //                                       checkitem_toggled, checkitem_update, 0);
765     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
766                                            checkitem_toggled, checkitem_update, 0);
767     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls"), _("Show or hide the Tool Controls panel"), "toppanel",
768                                            checkitem_toggled, checkitem_update, 0);
769     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
770                                            checkitem_toggled, checkitem_update, 0);
771     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
772                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
773     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
774                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
775     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
776                                            checkitem_toggled, checkitem_update, 0);
777     sp_ui_menu_append_check_item_from_verb(m, view, _("_Panels"), _("Show or hide the panels"), "panels",
778                                            checkitem_toggled, checkitem_update, 0);
781 /** \brief  This function turns XML into a menu
782     \param  menus  This is the XML that defines the menu
783     \param  menu   Menu to be added to
784     \param  view   The View that this menu is being built for
786     This function is realitively simple as it just goes through the XML
787     and parses the individual elements.  In the case of a submenu, it
788     just calls itself recursively.  Because it is only reasonable to have
789     a couple of submenus, it is unlikely this will go more than two or
790     three times.
792     In the case of an unreconginzed verb, a menu item is made to identify
793     the verb that is missing, and display that.  The menu item is also made
794     insensitive.
795 */
796 void
797 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
799     if (menus == NULL) return;
800     if (menu == NULL)  return;
801     GSList *group = NULL;
803     for (Inkscape::XML::Node *menu_pntr = menus;
804          menu_pntr != NULL;
805          menu_pntr = menu_pntr->next()) {
806         if (!strcmp(menu_pntr->name(), "submenu")) {
807             if (!strcmp(menu_pntr->attribute("name"), "Effects") && !prefs_get_int_attribute("extensions", "show-effects-menu", 0)) {
808                 continue;
809             }
810             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
811             GtkWidget *submenu = gtk_menu_new();
812             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
813             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
814             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
815             continue;
816         }
817         if (!strcmp(menu_pntr->name(), "verb")) {
818             gchar const *verb_name = menu_pntr->attribute("verb-id");
819             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
821             if (verb != NULL) {
822                 if (menu_pntr->attribute("radio") != NULL) {
823                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
824                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
825                     if (menu_pntr->attribute("default") != NULL) {
826                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
827                     }
828                 } else {
829                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
830                     group = NULL;
831                 }
832             } else {
833                 gchar string[120];
834                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
835                 string[119] = '\0'; /* may not be terminated */
836                 GtkWidget *item = gtk_menu_item_new_with_label(string);
837                 gtk_widget_set_sensitive(item, false);
838                 gtk_widget_show(item);
839                 gtk_menu_append(GTK_MENU(menu), item);
840             }
841             continue;
842         }
843         if (!strcmp(menu_pntr->name(), "separator")
844                 // This was spelt wrong in the original version
845                 // and so this is for backward compatibility.  It can
846                 // probably be dropped after the 0.44 release.
847              || !strcmp(menu_pntr->name(), "seperator")) {
848             GtkWidget *item = gtk_separator_menu_item_new();
849             gtk_widget_show(item);
850             gtk_menu_append(GTK_MENU(menu), item);
851             continue;
852         }
853         if (!strcmp(menu_pntr->name(), "template-list")) {
854             sp_menu_append_new_templates(menu, view);
855             continue;
856         }
857         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
858             sp_menu_append_recent_documents(menu, view);
859             continue;
860         }
861         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
862             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
863             continue;
864         }
865     }
868 /** \brief  Build the main tool bar
869     \param  view  View to build the bar for
871     Currently the main tool bar is built as a dynamic XML menu using
872     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
873     pass it to get items attached to it.
874 */
875 GtkWidget *
876 sp_ui_main_menubar(Inkscape::UI::View::View *view)
878     GtkWidget *mbar = gtk_menu_bar_new();
880     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
882     return mbar;
885 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
886     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
889 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
890     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
891     SP_DT_SELECTION(desktop)->clear();
894 GtkWidget *
895 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
897     GtkWidget *m;
898     SPDesktop *dt;
900     dt = static_cast<SPDesktop*>(view);
902     m = gtk_menu_new();
904     /* Undo and Redo */
905     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
906     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
908     /* Separator */
909     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
911     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
912     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
913     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
915     /* Separator */
916     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
918     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
919     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
921     /* Item menu */
922     if (item) {
923         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
924         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
925     }
927     /* layer menu */
928     SPGroup *group=NULL;
929     if (item) {
930         if (SP_IS_GROUP(item)) {
931             group = SP_GROUP(item);
932         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
933             group = SP_GROUP(SP_OBJECT_PARENT(item));
934         }
935     }
937     if (( group && group != dt->currentLayer() ) ||
938         ( dt->currentLayer() != dt->currentRoot() ) ) {
939         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
940     }
942     if ( group && group != dt->currentLayer() ) {
943         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
944         gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
945         GtkWidget *w = gtk_menu_item_new_with_label(label);
946         g_free(label);
947         g_object_set_data(G_OBJECT(w), "group", group);
948         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
949         gtk_widget_show(w);
950         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
951     }
953     if ( dt->currentLayer() != dt->currentRoot() ) {
954         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
955             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
956             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
957             gtk_widget_show(w);
958             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
960         }
961     }
963     return m;
966 /* Drag and Drop */
967 void
968 sp_ui_drag_data_received(GtkWidget *widget,
969                          GdkDragContext *drag_context,
970                          gint x, gint y,
971                          GtkSelectionData *data,
972                          guint info,
973                          guint event_time,
974                          gpointer user_data)
976     switch (info) {
977         case APP_X_COLOR:
978         {
979             SPDesktop *desktop = SP_ACTIVE_DESKTOP;
980             int destX = 0;
981             int destY = 0;
982             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
983             NR::Point where( sp_canvas_window_to_world( desktop->canvas, NR::Point( destX, destY ) ) );
985             SPItem *item = desktop->item_at_point( where, true );
986             if ( item )
987             {
988                 if ( data->length == 8 ) {
989                     gchar c[64] = {0};
990                     // Careful about endian issues.
991                     guint16* dataVals = (guint16*)data->data;
992                     sp_svg_write_color( c, 64,
993                                         SP_RGBA32_U_COMPOSE(
994                                             0x0ff & (dataVals[0] >> 8),
995                                             0x0ff & (dataVals[1] >> 8),
996                                             0x0ff & (dataVals[2] >> 8),
997                                             0xff // can't have transparency in the color itself
998                                             //0x0ff & (data->data[3] >> 8),
999                                             ));
1000                     SPCSSAttr *css = sp_repr_css_attr_new();
1001                     sp_repr_css_set_property( css, (drag_context->action != GDK_ACTION_MOVE) ? "fill":"stroke", c );
1003                     sp_desktop_apply_css_recursive( item, css, true );
1004                     item->updateRepr();
1006                     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1007                     sp_document_done( doc );
1008                 }
1009             }
1010         }
1011         break;
1013         case SVG_DATA:
1014         case SVG_XML_DATA: {
1015             gchar *svgdata = (gchar *)data->data;
1017             SPDocument *doc = SP_ACTIVE_DOCUMENT;
1019             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1021             if (rnewdoc == NULL) {
1022                 sp_ui_error_dialog(_("Could not parse SVG data"));
1023                 return;
1024             }
1026             Inkscape::XML::Node *repr = sp_repr_document_root(rnewdoc);
1027             gchar const *style = repr->attribute("style");
1029             Inkscape::XML::Node *newgroup = sp_repr_new("svg:g");
1030             newgroup->setAttribute("style", style);
1032             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1033                 Inkscape::XML::Node *newchild = child->duplicate();
1034                 newgroup->appendChild(newchild);
1035             }
1037             Inkscape::GC::release(rnewdoc);
1039             SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1040             // Add it to the current layer
1042             // Greg's edits to add intelligent positioning of svg drops
1043             SPObject *new_obj = NULL;
1044             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1046             Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
1047             selection->set(SP_ITEM(new_obj));
1048             // To move the imported object, we must temporarily set the "transform pattern with
1049             // object" option.
1050             {
1051                 int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1);
1052                 prefs_set_int_attribute("options.transform", "pattern", 1);
1053                 sp_document_ensure_up_to_date(SP_DT_DOCUMENT(desktop));
1054                 NR::Point m( desktop->point() - selection->bounds().midpoint() );
1055                 sp_selection_move_relative(selection, m);
1056                 prefs_set_int_attribute("options.transform", "pattern", saved_pref);
1057             }
1059             Inkscape::GC::release(newgroup);
1060             sp_document_done(doc);
1061             break;
1062         }
1064         case URI_LIST: {
1065             gchar *uri = (gchar *)data->data;
1066             sp_ui_import_files(uri);
1067             break;
1068         }
1070         case PNG_DATA:
1071         case JPEG_DATA:
1072         case IMAGE_DATA: {
1073             char tmp[1024];
1075             StringOutputStream outs;
1076             Base64OutputStream b64out(outs);
1077             b64out.setColumnWidth(0);
1079             SPDocument *doc = SP_ACTIVE_DOCUMENT;
1081             Inkscape::XML::Node *newImage = sp_repr_new("svg:image");
1083             for ( int i = 0; i < data->length; i++ ) {
1084                 b64out.put( data->data[i] );
1085             }
1086             b64out.close();
1089             Glib::ustring str = outs.getString();
1091             snprintf( tmp, sizeof(tmp), "data:%s;base64,", gdk_atom_name(data->type) );
1092             str.insert( 0, tmp );
1093             newImage->setAttribute("xlink:href", str.c_str());
1095             GError *error = NULL;
1096             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1097             if ( loader ) {
1098                 error = NULL;
1099                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1100                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1101                     if ( pbuf ) {
1102                         int width = gdk_pixbuf_get_width(pbuf);
1103                         int height = gdk_pixbuf_get_height(pbuf);
1104                         snprintf( tmp, sizeof(tmp), "%d", width );
1105                         newImage->setAttribute("width", tmp);
1107                         snprintf( tmp, sizeof(tmp), "%d", height );
1108                         newImage->setAttribute("height", tmp);
1109                     }
1110                 }
1111             }
1113             SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1115             // Add it to the current layer
1116             desktop->currentLayer()->appendChildRepr(newImage);
1118             Inkscape::GC::release(newImage);
1119             sp_document_done( doc );
1120             break;
1121         }
1122     }
1125 static void
1126 sp_ui_import_files(gchar *buffer)
1128     GList *list = gnome_uri_list_extract_filenames(buffer);
1129     if (!list)
1130         return;
1131     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1132     g_list_foreach(list, (GFunc) g_free, NULL);
1133     g_list_free(list);
1136 static void
1137 sp_ui_import_one_file_with_check(gpointer filename, gpointer unused)
1139     if (filename) {
1140         if (strlen((char const *)filename) > 2)
1141             sp_ui_import_one_file((char const *)filename);
1142     }
1145 static void
1146 sp_ui_import_one_file(char const *filename)
1148     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1149     if (!doc) return;
1151     if (filename == NULL) return;
1153     // Pass off to common implementation
1154     // TODO might need to get the proper type of Inkscape::Extension::Extension
1155     file_import( doc, filename, NULL );
1158 void
1159 sp_ui_error_dialog(gchar const *message)
1161     GtkWidget *dlg;
1162     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1164     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1165                                  GTK_BUTTONS_CLOSE, safeMsg);
1166     sp_transientize(dlg);
1167     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1168     gtk_dialog_run(GTK_DIALOG(dlg));
1169     gtk_widget_destroy(dlg);
1170     g_free(safeMsg);
1173 bool
1174 sp_ui_overwrite_file(gchar const *filename)
1176     bool return_value = FALSE;
1177     GtkWidget *dialog;
1178     GtkWidget *hbox;
1179     GtkWidget *boxdata;
1180     gchar *title;
1181     gchar *text;
1183     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1185         title = g_strdup_printf(_("Overwrite %s"), filename);
1186         dialog = gtk_dialog_new_with_buttons(title,
1187                                              NULL,
1188                                              (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1189                                              GTK_STOCK_NO,
1190                                              GTK_RESPONSE_NO,
1191                                              GTK_STOCK_YES,
1192                                              GTK_RESPONSE_YES,
1193                                              NULL);
1194         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);
1196         sp_transientize(dialog);
1197         gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1199         hbox = gtk_hbox_new(FALSE, 5);
1200         boxdata = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
1201         gtk_widget_show(boxdata);
1202         gtk_box_pack_start(GTK_BOX(hbox), boxdata, TRUE, TRUE, 5);
1203         text = g_strdup_printf(_("The file %s already exists.  Do you want to overwrite that file with the current document?"), filename);
1204         boxdata = gtk_label_new(text);
1205         gtk_label_set_line_wrap(GTK_LABEL(boxdata), TRUE);
1206         gtk_widget_show(boxdata);
1207         gtk_box_pack_start(GTK_BOX(hbox), boxdata, FALSE, FALSE, 5);
1208         gtk_widget_show(hbox);
1209         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 5);
1211         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) {
1212             return_value = TRUE;
1213         } else {
1214             return_value = FALSE;
1215         }
1217         gtk_widget_destroy(dialog);
1218         g_free(title);
1219         g_free(text);
1220     } else {
1221         return_value = TRUE;
1222     }
1224     return return_value;
1227 static void
1228 sp_ui_menu_item_set_sensitive(SPAction *action, unsigned int sensitive, void *data)
1230     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1233 /*
1234   Local Variables:
1235   mode:c++
1236   c-file-style:"stroustrup"
1237   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1238   indent-tabs-mode:nil
1239   fill-column:99
1240   End:
1241 */
1242 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :