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)");
105 }
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),
237 _widgetRegistry(&_wr)
238 {
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);
313 //## Set up fit page expander
314 _fitPageMarginExpander.set_label(_("Resi_ze page to content..."));
315 _fitPageMarginExpander.set_use_underline();
316 _fitPageMarginExpander.add(_marginTable);
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);
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);
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"));
344 }
347 /**
348 * Destructor
349 */
350 PageSizer::~PageSizer()
351 {
352 }
356 /**
357 * Initialize or reset this widget
358 */
359 void
360 PageSizer::init ()
361 {
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();
369 }
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)
383 {
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;
438 }
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
448 {
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();
481 }
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()
490 {
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 }
501 }
505 /**
506 * Paper Size list callback for when a user changes the selection
507 */
508 void
509 PageSizer::on_paper_size_list_changed()
510 {
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);
547 }
550 /**
551 * Portrait button callback
552 */
553 void
554 PageSizer::on_portrait()
555 {
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 }
563 }
566 /**
567 * Landscape button callback
568 */
569 void
570 PageSizer::on_landscape()
571 {
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 }
579 }
581 /**
582 * Callback for the dimension widgets
583 */
584 void
585 PageSizer::on_value_changed()
586 {
587 if (_widgetRegistry->isUpdating()) return;
589 setDim (_dimensionWidth.getValue("px"),
590 _dimensionHeight.getValue("px"));
591 }
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 :