Code

Patch by Alex Leone to add margins to resize page options in Document Properties
[inkscape.git] / src / ui / widget / page-sizer.cpp
1 /** \file
2  *
3  * Paper-size widget and helper functions
4  *
5  * Authors:
6  *   bulia byak <buliabyak@users.sf.net>
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Jon Phillips <jon@rejon.org>
9  *   Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
10  *   Bob Jamison <ishmal@users.sf.net>
11  *
12  * Copyright (C) 2000 - 2006 Authors
13  *
14  * Released under GNU GPL.  Read the file 'COPYING' for more information
15  */
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
21 #include <string.h>
22 #include <vector>
23 #include <string>
25 #include <cmath>
26 #include <gtkmm.h>
27 #include "ui/widget/button.h"
29 #include "ui/widget/scalar-unit.h"
31 #include "helper/units.h"
32 #include "inkscape.h"
33 #include "verbs.h"
34 #include "desktop-handles.h"
35 #include "document.h"
36 #include "desktop.h"
37 #include "page-sizer.h"
38 #include "helper/action.h"
39 #include "sp-root.h"
41 using std::pair;
43 namespace Inkscape {
44 namespace UI {
45 namespace Widget {
47     /** \note
48      * The ISO page sizes in the table below differ from ghostscript's idea of page sizes (by
49      * less than 1pt).  Being off by <1pt should be OK for most purposes, but may cause fuzziness
50      * (antialiasing) problems when printing to 72dpi or 144dpi printers or bitmap files due to
51      * postscript's different coordinate system (y=0 meaning bottom of page in postscript and top
52      * of page in SVG).  I haven't looked into whether this does in fact cause fuzziness, I merely
53      * note the possibility.  Rounding done by extension/internal/ps.cpp (e.g. floor/ceil calls)
54      * will also affect whether fuzziness occurs.
55      *
56      * The remainder of this comment discusses the origin of the numbers used for ISO page sizes in
57      * this table and in ghostscript.
58      *
59      * The versions here, in mm, are the official sizes according to
60      * <a href="http://en.wikipedia.org/wiki/Paper_sizes">http://en.wikipedia.org/wiki/Paper_sizes</a>
61      * at 2005-01-25.  (The ISO entries in the below table
62      * were produced mechanically from the table on that page.)
63      *
64      * (The rule seems to be that A0, B0, ..., D0. sizes are rounded to the nearest number of mm
65      * from the "theoretical size" (i.e. 1000 * sqrt(2) or pow(2.0, .25) or the like), whereas
66      * going from e.g. A0 to A1 always take the floor of halving -- which by chance coincides
67      * exactly with flooring the "theoretical size" for n != 0 instead of the rounding to nearest
68      * done for n==0.)
69      *
70      * Ghostscript paper sizes are given in gs_statd.ps according to gs(1).  gs_statd.ps always
71      * uses an integer number of pt: sometimes gs_statd.ps rounds to nearest (e.g. a1), sometimes
72      * floors (e.g. a10), sometimes ceils (e.g. a8).
73      *
74      * I'm not sure how ghostscript's gs_statd.ps was calculated: it isn't just rounding the
75      * "theoretical size" of each page to pt (see a0), nor is it rounding the a0 size times an
76      * appropriate power of two (see a1).  Possibly it was prepared manually, with a human applying
77      * inconsistent rounding rules when converting from mm to pt.
78      */
79     /** \todo
80      * Should we include the JIS B series (used in Japan)
81      * (JIS B0 is sometimes called JB0, and similarly for JB1 etc)?
82      * Should we exclude B7--B10 and A7--10 to make the list smaller ?
83      * Should we include any of the ISO C, D and E series (see below) ?
84      */
86 struct PaperSizeRec {
87     char const * const name;  //name
88     double const smaller;     //lesser dimension
89     double const larger;      //greater dimension
90     SPUnitId const unit;      //units
91 };
93 // list of page formats that should be in landscape automatically
94 static std::vector<std::string> lscape_papers;
96 static void
97 fill_landscape_papers() {
98     lscape_papers.push_back("US #10 Envelope");
99     lscape_papers.push_back("DL Envelope");
100     lscape_papers.push_back("Banner 468x60");
101     lscape_papers.push_back("Business Card (ISO 7810)");
102     lscape_papers.push_back("Business Card (US)");
103     lscape_papers.push_back("Business Card (Europe)");
104     lscape_papers.push_back("Business Card (Aus/NZ)");
107 static PaperSizeRec const inkscape_papers[] = {
108     { "A4",                210,  297, SP_UNIT_MM },
109     { "US Letter",         8.5,   11, SP_UNIT_IN },
110     { "US Legal",          8.5,   14, SP_UNIT_IN },
111     { "US Executive",     7.25, 10.5, SP_UNIT_IN },
112     { "A0",                841, 1189, SP_UNIT_MM },
113     { "A1",                594,  841, SP_UNIT_MM },
114     { "A2",                420,  594, SP_UNIT_MM },
115     { "A3",                297,  420, SP_UNIT_MM },
116     { "A5",                148,  210, SP_UNIT_MM },
117     { "A6",                105,  148, SP_UNIT_MM },
118     { "A7",                 74,  105, SP_UNIT_MM },
119     { "A8",                 52,   74, SP_UNIT_MM },
120     { "A9",                 37,   52, SP_UNIT_MM },
121     { "A10",                26,   37, SP_UNIT_MM },
122     { "B0",               1000, 1414, SP_UNIT_MM },
123     { "B1",                707, 1000, SP_UNIT_MM },
124     { "B2",                500,  707, SP_UNIT_MM },
125     { "B3",                353,  500, SP_UNIT_MM },
126     { "B4",                250,  353, SP_UNIT_MM },
127     { "B5",                176,  250, SP_UNIT_MM },
128     { "B6",                125,  176, SP_UNIT_MM },
129     { "B7",                 88,  125, SP_UNIT_MM },
130     { "B8",                 62,   88, SP_UNIT_MM },
131     { "B9",                 44,   62, SP_UNIT_MM },
132     { "B10",                31,   44, SP_UNIT_MM },
136 //#if 0
137          /*
138          Whether to include or exclude these depends on how
139          big we mind our page size menu
140          becoming.  C series is used for envelopes;
141          don't know what D and E series are used for.
142          */
144     { "C0",                917, 1297, SP_UNIT_MM },
145     { "C1",                648,  917, SP_UNIT_MM },
146     { "C2",                458,  648, SP_UNIT_MM },
147     { "C3",                324,  458, SP_UNIT_MM },
148     { "C4",                229,  324, SP_UNIT_MM },
149     { "C5",                162,  229, SP_UNIT_MM },
150     { "C6",                114,  162, SP_UNIT_MM },
151     { "C7",                 81,  114, SP_UNIT_MM },
152     { "C8",                 57,   81, SP_UNIT_MM },
153     { "C9",                 40,   57, SP_UNIT_MM },
154     { "C10",                28,   40, SP_UNIT_MM },
155     { "D1",                545,  771, SP_UNIT_MM },
156     { "D2",                385,  545, SP_UNIT_MM },
157     { "D3",                272,  385, SP_UNIT_MM },
158     { "D4",                192,  272, SP_UNIT_MM },
159     { "D5",                136,  192, SP_UNIT_MM },
160     { "D6",                 96,  136, SP_UNIT_MM },
161     { "D7",                 68,   96, SP_UNIT_MM },
162     { "E3",                400,  560, SP_UNIT_MM },
163     { "E4",                280,  400, SP_UNIT_MM },
164     { "E5",                200,  280, SP_UNIT_MM },
165     { "E6",                140,  200, SP_UNIT_MM },
166 //#endif
170     { "CSE",               462,  649, SP_UNIT_PT },
171     { "US #10 Envelope", 4.125,  9.5, SP_UNIT_IN },
172     /* See http://www.hbp.com/content/PCR_envelopes.cfm for a much larger list of US envelope
173        sizes. */
174     { "DL Envelope",       110,  220, SP_UNIT_MM },
175     { "Ledger/Tabloid",     11,   17, SP_UNIT_IN },
176     /* Note that `Folio' (used in QPrinter/KPrinter) is deliberately absent from this list, as it
177        means different sizes to different people: different people may expect the width to be
178        either 8, 8.25 or 8.5 inches, and the height to be either 13 or 13.5 inches, even
179        restricting our interpretation to foolscap folio.  If you wish to introduce a folio-like
180        page size to the list, then please consider using a name more specific than just `Folio' or
181        `Foolscap Folio'. */
182     { "Banner 468x60",      60,  468, SP_UNIT_PX },
183     { "Icon 16x16",         16,   16, SP_UNIT_PX },
184     { "Icon 32x32",         32,   32, SP_UNIT_PX },
185     { "Icon 48x48",         48,   48, SP_UNIT_PX },
186     /* business cards */
187     { "Business Card (ISO 7810)", 53.98, 85.60, SP_UNIT_MM },
188     { "Business Card (US)",             2,     3.5,  SP_UNIT_IN },
189     { "Business Card (Europe)",        55,    85,    SP_UNIT_MM },
190     { "Business Card (Aus/NZ)",        55,    90,    SP_UNIT_MM },
192     // Start Arch Series List
195     { "Arch A",         9,    12,    SP_UNIT_IN },  // 229 x 305 mm
196     { "Arch B",        12,    18,    SP_UNIT_IN },  // 305 x 457 mm
197     { "Arch C",        18,    24,    SP_UNIT_IN },  // 457 x 610 mm
198     { "Arch D",        24,    36,    SP_UNIT_IN },  // 610 x 914 mm
199     { "Arch E",        36,    48,    SP_UNIT_IN },  // 914 x 1219 mm
200     { "Arch E1",       30,    42,    SP_UNIT_IN },  // 762 x 1067 mm
202     /*
203      * The above list of Arch sizes were taken from the following site:
204      * http://en.wikipedia.org/wiki/Paper_size
205      * Further detail can be found at http://www.ansi.org
206      * Sizes are assumed to be arbitrary rounding to MM unless shown to be otherwise
207      * No conflicting information was found regarding sizes in MM
208      * September 2009 - DAK
209      */
211     { NULL,                     0,    0, SP_UNIT_PX },
212 };
216 //########################################################################
217 //# P A G E    S I Z E R
218 //########################################################################
220 //The default unit for this widget and its calculations
221 static const SPUnit _px_unit = sp_unit_get_by_id (SP_UNIT_PX);
224 /**
225  * Constructor
226  */
227 PageSizer::PageSizer(Registry & _wr)
228     : Gtk::VBox(false,4),
229       _dimensionUnits( _("U_nits:"), "units", _wr ),
230       _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ),
231       _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ),
232       _marginTop( _("T_op margin:"), _("Top margin"), "fit-margin-top", _wr ),
233       _marginLeft( _("L_eft:"), _("Left margin"), "fit-margin-left", _wr),
234       _marginRight( _("Ri_ght:"), _("Right margin"), "fit-margin-right", _wr),
235       _marginBottom( _("Botto_m:"), _("Bottom margin"), "fit-margin-bottom", _wr),
236       
237       _widgetRegistry(&_wr)
239     //# Set up the Paper Size combo box
240     _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns);
241     _paperSizeList.set_model(_paperSizeListStore);
242     _paperSizeList.append_column(_("Name"),
243                                  _paperSizeListColumns.nameColumn);
244     _paperSizeList.append_column(_("Description"),
245                                  _paperSizeListColumns.descColumn);
246     _paperSizeList.set_headers_visible(false);
247     _paperSizeListSelection = _paperSizeList.get_selection();
248     _paper_size_list_connection =
249         _paperSizeListSelection->signal_changed().connect (
250             sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
251     _paperSizeListScroller.add(_paperSizeList);
252     _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN);
253     _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
254     _paperSizeListScroller.set_size_request(-1, 90);
256     fill_landscape_papers();
258     for (PaperSizeRec const *p = inkscape_papers; p->name; p++)
259     {
260         Glib::ustring name = p->name;
261         char formatBuf[80];
262         snprintf(formatBuf, 79, "%0.1f x %0.1f", p->smaller, p->larger);
263         Glib::ustring desc = formatBuf;
264         if (p->unit == SP_UNIT_IN)
265             desc.append(" in");
266         else if (p->unit == SP_UNIT_MM)
267              desc.append(" mm");
268         else if (p->unit == SP_UNIT_PX)
269             desc.append(" px");
270         PaperSize paper(name, p->smaller, p->larger, p->unit);
271         _paperSizeTable[name] = paper;
272         Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
273         row[_paperSizeListColumns.nameColumn] = name;
274         row[_paperSizeListColumns.descColumn] = desc;
275         }
276     //Gtk::TreeModel::iterator iter = _paperSizeListStore->children().begin();
277     //if (iter)
278     //    _paperSizeListSelection->select(iter);
281     pack_start (_paperSizeListScroller, true, true, 0);
283     //## Set up orientation radio buttons
284     pack_start (_orientationBox, false, false, 0);
285     _orientationLabel.set_label(_("Orientation:"));
286     _orientationBox.pack_start(_orientationLabel, false, false, 0);
287     _landscapeButton.set_use_underline();
288     _landscapeButton.set_label(_("_Landscape"));
289     _landscapeButton.set_active(true);
290     Gtk::RadioButton::Group group = _landscapeButton.get_group();
291     _orientationBox.pack_end (_landscapeButton, false, false, 5);
292     _portraitButton.set_use_underline();
293     _portraitButton.set_label(_("_Portrait"));
294     _portraitButton.set_active(true);
295     _orientationBox.pack_end (_portraitButton, false, false, 5);
296     _portraitButton.set_group (group);
297     _portraitButton.set_active (true);
299     //## Set up custom size frame
300     _customFrame.set_label(_("Custom size"));
301     pack_start (_customFrame, false, false, 0);
302     _customFrame.add(_customDimTable);
304     _customDimTable.resize(3, 2);
305     _customDimTable.set_border_width(4);
306     _customDimTable.set_row_spacings(4);
307     _customDimTable.set_col_spacings(4);
308     _customDimTable.attach(_dimensionWidth,        0,1, 0,1);
309     _customDimTable.attach(_dimensionUnits,        1,2, 0,1);
310     _customDimTable.attach(_dimensionHeight,       0,1, 1,2);
311     _customDimTable.attach(_fitPageMarginExpander, 0,2, 2,3);
312     
313     //## Set up fit page expander
314     _fitPageMarginExpander.set_label(_("Resi_ze page to content..."));
315     _fitPageMarginExpander.set_use_underline();
316     _fitPageMarginExpander.add(_marginTable);
317     
318     //## Set up margin settings
319     _marginTable.resize(4, 2);
320     _marginTable.set_border_width(4);
321     _marginTable.set_row_spacings(4);
322     _marginTable.set_col_spacings(4);
323     _marginTable.attach(_fitPageButtonAlign, 0,2, 0,1);
324     _marginTable.attach(_marginTopAlign,     0,2, 1,2);
325     _marginTable.attach(_marginLeftAlign,    0,1, 2,3);
326     _marginTable.attach(_marginRightAlign,   1,2, 2,3);
327     _marginTable.attach(_marginBottomAlign,  0,2, 3,4);
328     
329     _marginTopAlign.set(0.5, 0.5, 0.0, 1.0);
330     _marginTopAlign.add(_marginTop);
331     _marginLeftAlign.set(0.0, 0.5, 0.0, 1.0);
332     _marginLeftAlign.add(_marginLeft);
333     _marginRightAlign.set(1.0, 0.5, 0.0, 1.0);
334     _marginRightAlign.add(_marginRight);
335     _marginBottomAlign.set(0.5, 0.5, 0.0, 1.0);
336     _marginBottomAlign.add(_marginBottom);
337     
338     _fitPageButtonAlign.set(0.5, 0.5, 0.0, 1.0);
339     _fitPageButtonAlign.add(_fitPageButton);
340     _fitPageButton.set_use_underline();
341     _fitPageButton.set_label(_("_Resize page to drawing or selection"));
342     _tips.set_tip(_fitPageButton, _("Resize the page to fit the current selection, or the entire drawing if there is no selection"));
347 /**
348  * Destructor
349  */
350 PageSizer::~PageSizer()
356 /**
357  * Initialize or reset this widget
358  */
359 void
360 PageSizer::init ()
362     _landscape_connection = _landscapeButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_landscape));
363     _portrait_connection = _portraitButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_portrait));
364     _changedw_connection = _dimensionWidth.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
365     _changedh_connection = _dimensionHeight.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
366     _fitPageButton.signal_clicked().connect(sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
368     show_all_children();
372 /**
373  * Set document dimensions (if not called by Doc prop's update()) and
374  * set the PageSizer's widgets and text entries accordingly. If
375  * 'changeList' is true, then adjust the paperSizeList to show the closest
376  * standard page size.
377  *
378  * \param w, h given in px
379  * \param changeList whether to modify the paper size list
380  */
381 void
382 PageSizer::setDim (double w, double h, bool changeList)
384     static bool _called = false;
385     if (_called) {
386         return;
387     }
389     _called = true;
391     _paper_size_list_connection.block();
392     _landscape_connection.block();
393     _portrait_connection.block();
394     _changedw_connection.block();
395     _changedh_connection.block();
397     if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
398         SPDocument *doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
399         double const old_height = sp_document_height(doc);
400         sp_document_set_width (doc, w, &_px_unit);
401         sp_document_set_height (doc, h, &_px_unit);
402         // The origin for the user is in the lower left corner; this point should remain stationary when
403         // changing the page size. The SVG's origin however is in the upper left corner, so we must compensate for this
404         Geom::Translate const vert_offset(Geom::Point(0, (old_height - h)));
405                 SP_GROUP(SP_ROOT(doc->root))->translateChildItems(vert_offset);
406         sp_document_done (doc, SP_VERB_NONE, _("Set page size"));
407     }
409     if ( w != h ) {
410         _landscapeButton.set_sensitive(true);
411         _portraitButton.set_sensitive (true);
412         _landscape = ( w > h );
413         _landscapeButton.set_active(_landscape ? true : false);
414         _portraitButton.set_active (_landscape ? false : true);
415     } else {
416         _landscapeButton.set_sensitive(false);
417         _portraitButton.set_sensitive (false);
418     }
420     if (changeList)
421         {
422         Gtk::TreeModel::Row row = (*find_paper_size(w, h));
423         if (row)
424             _paperSizeListSelection->select(row);
425         }
427     Unit const& unit = _dimensionUnits.getUnit();
428     _dimensionWidth.setValue (w / unit.factor);
429     _dimensionHeight.setValue (h / unit.factor);
431     _paper_size_list_connection.unblock();
432     _landscape_connection.unblock();
433     _portrait_connection.unblock();
434     _changedw_connection.unblock();
435     _changedh_connection.unblock();
437     _called = false;
441 /**
442  * Returns an iterator pointing to a row in paperSizeListStore which
443  * contains a paper of the specified size (specified in px), or
444  * paperSizeListStore->children().end() if no such paper exists.
445  */
446 Gtk::ListStore::iterator
447 PageSizer::find_paper_size (double w, double h) const
449     double smaller = w;
450     double larger  = h;
451     if ( h < w ) {
452         smaller = h; larger = w;
453     }
455     g_return_val_if_fail(smaller <= larger, _paperSizeListStore->children().end());
457     std::map<Glib::ustring, PaperSize>::const_iterator iter;
458     for (iter = _paperSizeTable.begin() ;
459          iter != _paperSizeTable.end() ; iter++) {
460         PaperSize paper = iter->second;
461         SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
462         double smallX = sp_units_get_pixels(paper.smaller, i_unit);
463         double largeX = sp_units_get_pixels(paper.larger,  i_unit);
465         g_return_val_if_fail(smallX <= largeX, _paperSizeListStore->children().end());
467         if ((std::abs(smaller - smallX) <= 0.1) &&
468             (std::abs(larger  - largeX) <= 0.1)   ) {
469             Gtk::ListStore::iterator p;
470             // We need to search paperSizeListStore explicitly for the
471             // specified paper size because it is sorted in a different
472             // way than paperSizeTable (which is sorted alphabetically)
473             for (p = _paperSizeListStore->children().begin(); p != _paperSizeListStore->children().end(); p++) {
474                 if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
475                     return p;
476                 }
477             }
478         }
479     }
480     return _paperSizeListStore->children().end();
485 /**
486  * Tell the desktop to fit the page size to the selection or drawing.
487  */
488 void
489 PageSizer::fire_fit_canvas_to_selection_or_drawing()
491     SPDesktop *dt = SP_ACTIVE_DESKTOP;
492     if (!dt) {
493         return;
494     }
495     Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
496     if (verb) {
497         SPAction *action = verb->get_action(dt);
498         if (action)
499             sp_action_perform(action, NULL);
500     }
505 /**
506  * Paper Size list callback for when a user changes the selection
507  */
508 void
509 PageSizer::on_paper_size_list_changed()
511     //Glib::ustring name = _paperSizeList.get_active_text();
512     Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
513     if(!miter)
514         {
515         //error?
516         return;
517         }
518     Gtk::TreeModel::Row row = *miter;
519     Glib::ustring name = row[_paperSizeListColumns.nameColumn];
520     std::map<Glib::ustring, PaperSize>::const_iterator piter =
521                     _paperSizeTable.find(name);
522     if (piter == _paperSizeTable.end()) {
523         g_warning("paper size '%s' not found in table", name.c_str());
524         return;
525     }
526     PaperSize paper = piter->second;
527     double w = paper.smaller;
528     double h = paper.larger;
530     if (std::find(lscape_papers.begin(), lscape_papers.end(), paper.name.c_str()) != lscape_papers.end()) {
531         // enforce landscape mode if this is desired for the given page format
532         _landscape = true;
533     } else {
534         // otherwise we keep the current mode
535         _landscape = _landscapeButton.get_active();
536     }
538     SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
539     sp_convert_distance (&w, &src_unit, &_px_unit);
540     sp_convert_distance (&h, &src_unit, &_px_unit);
542     if (_landscape)
543         setDim (h, w, false);
544     else
545         setDim (w, h, false);
550 /**
551  * Portrait button callback
552  */
553 void
554 PageSizer::on_portrait()
556     if (!_portraitButton.get_active())
557         return;
558     double w = _dimensionWidth.getValue ("px");
559     double h = _dimensionHeight.getValue ("px");
560     if (h < w) {
561         setDim (h, w);
562     }
566 /**
567  * Landscape button callback
568  */
569 void
570 PageSizer::on_landscape()
572     if (!_landscapeButton.get_active())
573         return;
574     double w = _dimensionWidth.getValue ("px");
575     double h = _dimensionHeight.getValue ("px");
576     if (w < h) {
577         setDim (h, w);
578     }
581 /**
582  * Callback for the dimension widgets
583  */
584 void
585 PageSizer::on_value_changed()
587     if (_widgetRegistry->isUpdating()) return;
589     setDim (_dimensionWidth.getValue("px"),
590             _dimensionHeight.getValue("px"));
594 } // namespace Widget
595 } // namespace UI
596 } // namespace Inkscape
598 /*
599   Local Variables:
600   mode:c++
601   c-file-style:"stroustrup"
602   c-file-offsets:((innamespace . 0)(inline-open . 0))
603   indent-tabs-mode:nil
604   fill-column:99
605   End:
606 */
607 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :