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)
119 {
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);
197 }
199 void
200 sp_ui_new_view()
201 {
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));
213 }
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()
219 {
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);
231 }
233 /**
234 * \param widget unused
235 */
236 void
237 sp_ui_close_view(GtkWidget *widget)
238 {
239 if (SP_ACTIVE_DESKTOP == NULL) {
240 return;
241 }
242 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
243 return;
244 }
245 SP_ACTIVE_DESKTOP->destroyWidget();
246 }
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)
262 {
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;
274 }
276 static gint
277 sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view)
278 {
279 return view->shutdown();
280 }
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)
291 {
292 sp_action_perform(action, NULL);
293 }
295 static void
296 sp_ui_menu_select_action(void *object, SPAction *action)
297 {
298 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
299 }
301 static void
302 sp_ui_menu_deselect_action(void *object, SPAction *action)
303 {
304 action->view->tipsMessageContext()->clear();
305 }
307 static void
308 sp_ui_menu_select(gpointer object, gpointer tip)
309 {
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);
312 }
314 static void
315 sp_ui_menu_deselect(gpointer object)
316 {
317 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
318 view->tipsMessageContext()->clear();
319 }
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 )
329 {
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 )
347 {
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)
383 {
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;
404 }
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)
413 {
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.
447 }
449 void
450 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
451 {
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 }
475 }
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)
487 {
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)
559 {
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();
573 }
575 static gboolean
576 checkitem_update(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
577 {
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;
596 }
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)
604 {
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);
660 }
662 static void
663 sp_recent_open(GtkWidget *widget, gchar const *uri)
664 {
665 sp_file_open(uri, NULL);
666 }
668 static void
669 sp_file_new_from_template(GtkWidget *widget, gchar const *uri)
670 {
671 sp_file_new(uri);
672 }
674 void
675 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
676 {
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 }
729 }
731 void
732 sp_menu_append_recent_documents(GtkWidget *menu, Inkscape::UI::View::View* /* view */)
733 {
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 }
758 }
760 void
761 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
762 {
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 Bar"), _("Show or hide the Tool Controls bar"), "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, _("_Palette"), _("Show or hide the color palette"), "panels",
778 checkitem_toggled, checkitem_update, 0);
779 }
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)
798 {
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 }
866 }
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)
877 {
878 GtkWidget *mbar = gtk_menu_bar_new();
880 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
882 return mbar;
883 }
885 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
886 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
887 }
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();
892 }
894 GtkWidget *
895 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
896 {
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;
964 }
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)
975 {
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 }
1123 }
1125 static void
1126 sp_ui_import_files(gchar *buffer)
1127 {
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);
1134 }
1136 static void
1137 sp_ui_import_one_file_with_check(gpointer filename, gpointer unused)
1138 {
1139 if (filename) {
1140 if (strlen((char const *)filename) > 2)
1141 sp_ui_import_one_file((char const *)filename);
1142 }
1143 }
1145 static void
1146 sp_ui_import_one_file(char const *filename)
1147 {
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 );
1156 }
1158 void
1159 sp_ui_error_dialog(gchar const *message)
1160 {
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);
1171 }
1173 bool
1174 sp_ui_overwrite_file(gchar const *filename)
1175 {
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;
1225 }
1227 static void
1228 sp_ui_menu_item_set_sensitive(SPAction *action, unsigned int sensitive, void *data)
1229 {
1230 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1231 }
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 :