stroke-style.cpp revision 4d79affe53b5c0e7dbc67014aee9be5251dc56df
#define __SP_STROKE_STYLE_C__
/**
* \brief Stroke style dialog
*
* Authors:
* Lauris Kaplinski <lauris@kaplinski.com>
* Bryce Harrington <brycehar@bryceharrington.org>
* bulia byak <buliabyak@users.sf.net>
*
* Copyright (C) 2001-2005 authors
* Copyright (C) 2001 Ximian, Inc.
* Copyright (C) 2004 John Cliff
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#define noSP_SS_VERBOSE
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "helper/unit-menu.h"
#include "svg/css-ostringstream.h"
#include "widgets/sp-widget.h"
#include "widgets/spw-utilities.h"
#include "sp-linear-gradient.h"
#include "sp-radial-gradient.h"
#include "marker.h"
#include "sp-pattern.h"
#include "widgets/paint-selector.h"
#include "widgets/dash-selector.h"
#include "style.h"
#include "gradient-chemistry.h"
#include "sp-namedview.h"
#include "desktop-handles.h"
#include "desktop-style.h"
#include "selection.h"
#include "inkscape.h"
#include "inkscape-stock.h"
#include "dialogs/dialog-events.h"
#include "sp-text.h"
#include "sp-rect.h"
#include "document-private.h"
#include "display/nr-arena.h"
#include "display/nr-arena-item.h"
#include "path-prefix.h"
#include "helper/stock-items.h"
#include "ui/cache/svg_preview_cache.h"
#include "dialogs/stroke-style.h"
/* Paint */
static void sp_stroke_style_paint_selection_modified (SPWidget *spw, Inkscape::Selection *selection, guint flags, SPPaintSelector *psel);
static void sp_stroke_style_paint_selection_changed (SPWidget *spw, Inkscape::Selection *selection, SPPaintSelector *psel);
static void sp_stroke_style_paint_mode_changed(SPPaintSelector *psel, SPPaintSelectorMode mode, SPWidget *spw);
static void sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw );
/** Marker selection option menus */
/**
* Create the stroke style widget, and hook up all the signals.
*/
{
psel);
psel);
psel);
g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_stroke_style_widget_change_subselection), spw);
g_signal_connect (G_OBJECT(INKSCAPE), "activate_desktop", G_CALLBACK (sp_stroke_style_widget_transientize_callback), spw );
spw);
spw);
spw);
return spw;
}
/**
* On construction, simply does an update of the stroke style paint object.
*/
static void
{
#ifdef SP_SS_VERBOSE
g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
#endif
}
}
/**
* On signal modified, invokes an update of the stroke style paint object.
*/
static void
{
}
}
/**
* On signal selection changed, invokes an update of the stroke style paint object.
*/
static void
{
}
/**
* On signal change subselection, invoke an update of the stroke style widget.
*/
static void
{
}
/**
* Gets the active stroke style property, then sets the appropriate color, alpha, gradient,
* pattern, etc. for the paint-selector.
*/
static void
{
return;
}
// create temporary style
// query into it
switch (result) {
case QUERY_STYLE_NOTHING:
{
/* No paint at all */
break;
}
case QUERY_STYLE_SINGLE:
case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
{
gfloat d[3];
sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->stroke_opacity.value));
if (SP_IS_LINEARGRADIENT (server)) {
SP_GRADIENT_SPREAD (lg));
} else if (SP_IS_RADIALGRADIENT (server)) {
SP_GRADIENT_SPREAD (rg));
} else if (SP_IS_PATTERN (server)) {
}
}
break;
}
{
break;
}
}
}
/**
* When the mode is changed, invoke a regular changed handler.
*/
static void
{
return;
}
/* TODO: Does this work?
* Not really, here we have to get old color back from object
* Instead of relying on paint widget having meaningful colors set
*/
}
/**
* When a drag callback occurs on a paint selector object, if it is a RGB or CMYK
* color mode, then set the stroke opacity to psel's flat color.
*/
static void
{
return;
}
{
sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE,
_("Set stroke color"));
break;
}
default:
g_warning( "file %s: line %d: Paint %d should not emit 'dragged'",
break;
}
}
/**
* When the stroke style's paint settings change, this handler updates the
* repr's stroke css style and applies the style to relevant drawing items.
*/
static void
{
return;
}
// This should not happen.
g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
break;
// This happens when you switch multiple objects with different gradients to flat color;
// nothing to do here.
break;
{
_("Remove stroke"));
break;
}
{
_("Set stroke color"));
// on release, toggle undo_label so that the next drag will not be lumped with this one
if (undo_label == undo_label_1)
else
break;
}
if (items) {
if (!vector) {
/* No vector in paint selector should mean that we just changed mode */
guint32 common_rgb = 0;
if (result == QUERY_STYLE_MULTIPLE_SAME) {
} else {
}
}
if (!vector) {
gradient_type, false);
} else {
}
}
} else {
}
}
_("Set gradient on stroke"));
}
break;
if (items) {
if (!pattern) {
/* No Pattern in paint selector should mean that we just
* changed mode - dont do jack.
*/
} else {
if (!selrepr)
continue;
// only if this object's pattern is not rooted in our selected pattern, apply
continue;
}
}
} // end if
_("Set pattern on stroke"));
} // end if
break;
if (items) {
_("Unset stroke"));
}
break;
default:
g_warning( "file %s: line %d: Paint selector should not be in "
"mode %d",
break;
}
}
/* Line */
/**
* Helper function for creating radio buttons. This should probably be re-thought out
* when reimplementing this with Gtkmm.
*/
static GtkWidget *
{
} else {
}
spw);
return tb;
}
static void
{
// TODO: Either of these will cause crashes sometimes
// sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
// ink_markers_menu_update(spw);
}
/**
* Creates a copy of the marker named mname, determines its visible and renderable
* area in menu_id's bounding box, and then renders it. This allows us to fill in
* preview images of each marker in the marker menu.
*/
static GtkWidget *
{
// Retrieve the marker named 'mname' from the source SVG document
return NULL;
// Create a copy repr of the marker with id="sample"
// Replace the old sample in the sandbox by the new one
if (oldmarker)
oldmarker->deleteObject(false);
// Uncomment this to get the sandbox documents saved (useful for debugging)
//FILE *fp = fopen (g_strconcat(mname, ".svg", NULL), "w");
//sp_repr_save_stream (sp_document_repr_doc (sandbox), fp);
//fclose (fp);
// object to render; note that the id is the same as that of the menu we're building
return NULL; // sandbox broken?
// Find object's bbox in document
if (!dbox) {
return NULL;
}
/* Update to renderable state */
double sf = 0.8;
}
// Create widget
return pb;
}
/**
* Returns a list of markers in the defs of the given source document as a GSList object
* Returns NULL if there are no markers in the document.
*/
GSList *
{
return NULL;
{
if (SP_IS_MARKER(child)) {
}
}
return ml;
}
#define MARKER_ITEM_MARGIN 0
/**
* Adds previews of markers in marker_list to the given menu widget
*/
static void
sp_marker_menu_build (GtkWidget *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar *menu_id)
{
// Do this here, outside of loop, to speed up preview generation:
NRArenaItem *root = sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
GtkWidget *i = gtk_menu_item_new();
gtk_widget_show(i);
else
// generate preview
// create label
gtk_widget_show(l);
gtk_menu_append(GTK_MENU(m), i);
}
}
/**
* sp_marker_list_from_doc()
*
* \brief Pick up all markers from source, except those that are in
* current_doc (if non-NULL), and add items to the m menu
*
*/
static void
sp_marker_list_from_doc (GtkWidget *m, SPDocument *current_doc, SPDocument *source, SPDocument *markers_doc, SPDocument *sandbox, gchar *menu_id)
{
continue;
// Add to the list of markers we really do wish to show
}
g_slist_free (ml);
}
/**
* Returns a new document containing default start, mid, and end markers.
*/
{
gchar const *buffer = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">"
" <defs id=\"defs\" />"
" <g id=\"marker-start\">"
" <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:url(#sample);marker-mid:none;marker-end:none\""
" d=\"M 12.5,13 L 25,13\" id=\"path1\" />"
" <rect style=\"fill:none;stroke:none\" id=\"rect2\""
" width=\"25\" height=\"25\" x=\"0\" y=\"0\" />"
" </g>"
" <g id=\"marker-mid\">"
" <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:url(#sample);marker-end:none\""
" d=\"M 0,113 L 12.5,113 L 25,113\" id=\"path11\" />"
" <rect style=\"fill:none;stroke:none\" id=\"rect22\""
" width=\"25\" height=\"25\" x=\"0\" y=\"100\" />"
" </g>"
" <g id=\"marker-end\">"
" <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:none;marker-end:url(#sample)\""
" d=\"M 0,213 L 12.5,213\" id=\"path111\" />"
" <rect style=\"fill:none;stroke:none\" id=\"rect222\""
" width=\"25\" height=\"25\" x=\"0\" y=\"200\" />"
" </g>"
"</svg>";
}
static void
{
// add "None"
GtkWidget *i = gtk_menu_item_new();
gtk_widget_show(i);
gtk_widget_show(l);
gtk_menu_append(GTK_MENU(m), i);
// find and load markers.svg
if (markers_doc == NULL) {
}
}
// suck in from current doc
// add separator
{
GtkWidget *i = gtk_separator_menu_item_new();
gtk_widget_show(i);
gtk_menu_append(GTK_MENU(m), i);
}
// suck in from markers.svg
if (markers_doc) {
}
}
/**
* Creates a menu widget to display markers from markers.svg
*/
static GtkWidget *
{
/* Create new menu widget */
GtkWidget *m = gtk_menu_new();
gtk_widget_show(m);
if (!doc) {
gtk_widget_show(i);
gtk_menu_append(GTK_MENU(m), i);
} else {
}
/* Set history */
return mnu;
}
/**
* Handles when user selects one of the markers from the marker menu.
* Defines a uri string to refer to it, then applies it to all selected
* items in the current desktop.
*/
static void
{
return;
}
if (!document) {
return;
}
/* Get Marker */
"marker"))
{
return;
}
gchar *markid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
"marker");
gchar *stockid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
"stockid");
if (mark) {
}
} else {
}
// Also update the marker dropdown menus, so the document's markers
// show up at the top of the menu
// sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using <path>
continue;
if (selrepr) {
}
SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
}
_("Set markers"));
};
static int
return 0;
int i = 0;
break;
}
i++;
}
return i;
}
static void
GtkWidget *m;
int pos;
gtk_signal_handler_block_by_func( GTK_OBJECT(marker_start_menu), GTK_SIGNAL_FUNC(sp_marker_select), spw);
pos = ink_marker_menu_get_pos(GTK_MENU(gtk_option_menu_get_menu(GTK_OPTION_MENU(marker_start_menu))), NULL);
m = gtk_menu_new();
gtk_widget_show(m);
gtk_signal_handler_unblock_by_func( GTK_OBJECT(marker_start_menu), GTK_SIGNAL_FUNC(sp_marker_select), spw);
gtk_signal_handler_block_by_func( GTK_OBJECT(marker_mid_menu), GTK_SIGNAL_FUNC(sp_marker_select), spw);
pos = ink_marker_menu_get_pos(GTK_MENU(gtk_option_menu_get_menu(GTK_OPTION_MENU(marker_mid_menu))), NULL);
m = gtk_menu_new();
gtk_widget_show(m);
gtk_signal_handler_unblock_by_func( GTK_OBJECT(marker_mid_menu), GTK_SIGNAL_FUNC(sp_marker_select), spw);
gtk_signal_handler_block_by_func( GTK_OBJECT(marker_end_menu), GTK_SIGNAL_FUNC(sp_marker_select), spw);
pos = ink_marker_menu_get_pos(GTK_MENU(gtk_option_menu_get_menu(GTK_OPTION_MENU(marker_end_menu))), NULL);
m = gtk_menu_new();
gtk_widget_show(m);
gtk_signal_handler_unblock_by_func( GTK_OBJECT(marker_end_menu), GTK_SIGNAL_FUNC(sp_marker_select), spw);
}
/**
* Sets the stroke width units for all selected items.
* Also handles absolute and dimensionless units.
*/
{
if (!desktop) {
return FALSE;
}
return FALSE;
/* Absolute to percentage */
return FALSE;
return TRUE;
/* Percentage to absolute */
return TRUE;
}
return FALSE;
}
/**
* \brief Creates a new widget for the line stroke style.
*
*/
{
GtkObject *a;
f = gtk_hbox_new (FALSE, 0);
gtk_widget_show(f);
gtk_widget_show(t);
gtk_container_add(GTK_CONTAINER(f), t);
gint i = 0;
/* Stroke width */
spw_label(t, _("Width:"), 0, i);
// TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate
// spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use
// setHundredPercent to remember the aeraged width corresponding to 100%. Then the
// stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and
// with it, the two remaining calls of stroke_average_width, allowing us to get rid of that
// function in desktop-style.
if (desktop)
gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(sp_stroke_style_width_changed), spw );
i++;
/* Join type */
// TRANSLATORS: The line join style specifies the shape to be used at the
// corners of paths. It can be "miter", "round" or "bevel".
spw_label(t, _("Join:"), 0, i);
// TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
// For an example, draw a triangle with a large stroke width and modify the
// "Join" option (in the Fill and Stroke dialog).
// TRANSLATORS: Round join: joining lines with a rounded corner.
// For an example, draw a triangle with a large stroke width and modify the
// "Join" option (in the Fill and Stroke dialog).
// TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
// For an example, draw a triangle with a large stroke width and modify the
// "Join" option (in the Fill and Stroke dialog).
i++;
/* Miterlimit */
// TRANSLATORS: Miter limit: only for "miter join", this limits the length
// of the sharp "spike" when the lines connect at too sharp an angle.
// When two line segments meet at a sharp angle, a miter join results in a
// spike that extends well beyond the connection point. The purpose of the
// miter limit is to cut off such spikes (i.e. convert them into bevels)
// when they become too long.
spw_label(t, _("Miter limit:"), 0, i);
i++;
/* Cap type */
// TRANSLATORS: cap type specifies the shape for the ends of lines
spw_label(t, _("Cap:"), 0, i);
// TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
// of the line; the ends of the line are square
// TRANSLATORS: Round cap: the line shape extends beyond the end point of the
// line; the ends of the line are rounded
// TRANSLATORS: Square cap: the line shape extends beyond the end point of the
// line; the ends of the line are square
i++;
/* Dash */
spw_label(t, _("Dashes:"), 0, i);
"palette.dashes") );
(GtkAttachOptions)0, 0, 0 );
spw );
i++;
/* Drop down marker selectors*/
// doing this here once, instead of for each preview, to speed things up
// TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
// (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
spw_label(t, _("Start Markers:"), 0, i);
gtk_signal_connect( GTK_OBJECT(marker_start_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
(GtkAttachOptions)0, 0, 0 );
i++;
spw_label(t, _("Mid Markers:"), 0, i);
gtk_signal_connect( GTK_OBJECT(marker_mid_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
(GtkAttachOptions)0, 0, 0 );
i++;
spw_label(t, _("End Markers:"), 0, i);
gtk_signal_connect( GTK_OBJECT(marker_end_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
(GtkAttachOptions)0, 0, 0 );
i++;
NULL );
NULL );
NULL );
return spw;
}
/**
* Callback for when the stroke style widget is called. It causes
* the stroke line style to be updated.
*/
static void
{
#ifdef SP_SS_VERBOSE
g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
#endif
: NULL ));
}
}
/**
* Callback for when stroke style widget is modified.
* Triggers update action.
*/
static void
{
}
}
/**
* Callback for when stroke style widget is changed.
* Triggers update action.
*/
static void
{
}
/**
* Sets selector widgets' dash style from an SPStyle object.
*/
static void
{
double d[64];
for (int i = 0; i < len; i++) {
else
}
} else {
}
}
/**
* Sets the join type for a line, and updates the stroke style widget's buttons
*/
static void
{
switch (jointype) {
case SP_STROKE_LINEJOIN_MITER:
break;
case SP_STROKE_LINEJOIN_ROUND:
break;
case SP_STROKE_LINEJOIN_BEVEL:
break;
default:
break;
}
}
/**
* Sets the cap type for a line, and updates the stroke style widget's buttons
*/
static void
{
switch (captype) {
case SP_STROKE_LINECAP_BUTT:
break;
case SP_STROKE_LINECAP_ROUND:
break;
case SP_STROKE_LINECAP_SQUARE:
break;
default:
break;
}
}
/**
* Callback for when stroke style widget is updated, including markers, cap type,
* join type, etc.
*/
static void
{
return;
}
// create temporary style
// query into it
int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT);
int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN);
if (result_sw == QUERY_STYLE_NOTHING) {
/* No objects stroked, set insensitive */
return;
} else {
if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
} else {
// same width, or only one object; no sense to keep percent, switch to absolute
sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
}
}
} else {
}
}
if (result_ml != QUERY_STYLE_NOTHING)
gtk_adjustment_set_value(GTK_ADJUSTMENT(ml), query->stroke_miterlimit.value); // TODO: reflect averagedness?
if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
} else {
}
if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
} else {
}
return;
/* Markers */
/* Dash */
}
/**
* Sets a line's dash properties in a CSS style object.
*/
static void
double scale)
{
if (ndash > 0) {
for (int i = 0; i < ndash; i++) {
if (i < (ndash - 1)) {
osarray << ",";
}
}
} else {
}
}
/**
* Sets line properties like width, dashes, markers, etc. on all currently selected items.
*/
static void
{
return;
}
/* TODO: Create some standardized method */
if (items) {
int ndash;
/* Set stroke width */
double width;
} else { // percentage
}
{
}
{
os_ml << miterlimit;
}
/* Set dash */
}
// reset to 100 percent
}
}
// we have already changed the items, so set style without changing selection
// FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
_("Set stroke style"));
}
/**
* Callback for when the stroke style's width changes.
* Causes all line styles to be applied to all selected items.
*/
static void
{
return;
}
}
/**
* Callback for when the stroke style's miterlimit changes.
* Causes all line styles to be applied to all selected items.
*/
static void
{
return;
}
}
/**
* Callback for when the stroke style's dash changes.
* Causes all line styles to be applied to all selected items.
*/
static void
{
return;
}
}
/**
* \brief This routine handles toggle events for buttons in the stroke style
* dialog.
* When activated, this routine gets the data for the various widgets, and then
* calls the respective routines to update css properties, etc.
*
*/
static void
{
return;
}
if (gtk_toggle_button_get_active(tb)) {
if (join) {
}
/* TODO: Create some standardized method */
if (join) {
} else if (cap) {
}
_("Set stroke style"));
}
}
/**
* Updates the join style toggle buttons
*/
static void
{
}
/**
* Updates the cap style toggle buttons
*/
static void
{
}
/**
* Sets the current marker in the marker menu.
*/
static void
{
bool mark_is_stock = false;
mark_is_stock = true;
if (mark_is_stock)
else
}
else {
}
}
/**
* Updates the marker menus to highlight the appropriate marker and scroll to
* that marker.
*/
static void
{
{ "start_mark_menu", SP_MARKER_LOC_START },
{ "mid_mark_menu", SP_MARKER_LOC_MID },
{ "end_mark_menu", SP_MARKER_LOC_END }
};
bool all_texts = true;
if (!SP_IS_TEXT (i->data)) {
all_texts = false;
}
}
for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
if (all_texts) {
// Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
} else {
}
}
// We show markers of the first object in the list only
// FIXME: use the first in the list that has the marker of each type, if any
for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
// For all three marker types,
// find the corresponding menu
// Quit if we're in update state
return;
}
// If the object has this type of markers,
// Extract the name of the marker that the object uses
// Scroll the menu to that marker
} else {
}
}
}
/**
* Extract the actual name of the link
* e.g. get mTriangle from url(#mTriangle).
* \return Buffer containing the actual name, allocated from GLib;
* the caller should free the buffer when they no longer need it.
*/
static SPObject*
ink_extract_marker_name(gchar const *n)
{
gchar const *p = n;
while (*p != '\0' && *p != '#') {
p++;
}
if (*p == '\0' || p[1] == '\0') {
return NULL;
}
p++;
int c = 0;
while (p[c] != '\0' && p[c] != ')') {
c++;
}
if (p[c] == '\0') {
return NULL;
}
b[c] = '\0';
return marker;
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :