Code

99c92aa859d4bf0b876c7ee5b818524143734c98
[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"
40 using std::pair;
42 namespace Inkscape {
43 namespace UI {
44 namespace Widget {
46     /** \note
47      * The ISO page sizes in the table below differ from ghostscript's idea of page sizes (by
48      * less than 1pt).  Being off by <1pt should be OK for most purposes, but may cause fuzziness
49      * (antialiasing) problems when printing to 72dpi or 144dpi printers or bitmap files due to
50      * postscript's different coordinate system (y=0 meaning bottom of page in postscript and top
51      * of page in SVG).  I haven't looked into whether this does in fact cause fuzziness, I merely
52      * note the possibility.  Rounding done by extension/internal/ps.cpp (e.g. floor/ceil calls)
53      * will also affect whether fuzziness occurs.
54      *
55      * The remainder of this comment discusses the origin of the numbers used for ISO page sizes in
56      * this table and in ghostscript.
57      *
58      * The versions here, in mm, are the official sizes according to
59      * <a href="http://en.wikipedia.org/wiki/Paper_sizes">http://en.wikipedia.org/wiki/Paper_sizes</a>
60      * at 2005-01-25.  (The ISO entries in the below table
61      * were produced mechanically from the table on that page.)
62      *
63      * (The rule seems to be that A0, B0, ..., D0. sizes are rounded to the nearest number of mm
64      * from the "theoretical size" (i.e. 1000 * sqrt(2) or pow(2.0, .25) or the like), whereas
65      * going from e.g. A0 to A1 always take the floor of halving -- which by chance coincides
66      * exactly with flooring the "theoretical size" for n != 0 instead of the rounding to nearest
67      * done for n==0.)
68      *
69      * Ghostscript paper sizes are given in gs_statd.ps according to gs(1).  gs_statd.ps always
70      * uses an integer number of pt: sometimes gs_statd.ps rounds to nearest (e.g. a1), sometimes
71      * floors (e.g. a10), sometimes ceils (e.g. a8).
72      *
73      * I'm not sure how ghostscript's gs_statd.ps was calculated: it isn't just rounding the
74      * "theoretical size" of each page to pt (see a0), nor is it rounding the a0 size times an
75      * appropriate power of two (see a1).  Possibly it was prepared manually, with a human applying
76      * inconsistent rounding rules when converting from mm to pt.
77      */
78     /** \todo
79      * Should we include the JIS B series (used in Japan)
80      * (JIS B0 is sometimes called JB0, and similarly for JB1 etc)?
81      * Should we exclude B7--B10 and A7--10 to make the list smaller ?
82      * Should we include any of the ISO C, D and E series (see below) ?
83      */
85 struct PaperSizeRec {
86     char const * const name;  //name
87     double const smaller;     //lesser dimension
88     double const larger;      //greater dimension
89     SPUnitId const unit;      //units
90 };
92 // list of page formats that should be in landscape automatically
93 static std::vector<std::string> lscape_papers;
95 static void
96 fill_landscape_papers() {
97     lscape_papers.push_back("US #10 Envelope");
98     lscape_papers.push_back("DL Envelope");
99     lscape_papers.push_back("Banner 468x60");
100     lscape_papers.push_back("Business Card (ISO 7810)");
101     lscape_papers.push_back("Business Card (US)");
102     lscape_papers.push_back("Business Card (Europe)");
103     lscape_papers.push_back("Business Card (Aus/NZ)");
106 static PaperSizeRec const inkscape_papers[] = {
107     { "A4",                210,  297, SP_UNIT_MM },
108     { "US Letter",         8.5,   11, SP_UNIT_IN },
109     { "US Legal",          8.5,   14, SP_UNIT_IN },
110     { "US Executive",     7.25, 10.5, SP_UNIT_IN },
111     { "A0",                841, 1189, SP_UNIT_MM },
112     { "A1",                594,  841, SP_UNIT_MM },
113     { "A2",                420,  594, SP_UNIT_MM },
114     { "A3",                297,  420, SP_UNIT_MM },
115     { "A5",                148,  210, SP_UNIT_MM },
116     { "A6",                105,  148, SP_UNIT_MM },
117     { "A7",                 74,  105, SP_UNIT_MM },
118     { "A8",                 52,   74, SP_UNIT_MM },
119     { "A9",                 37,   52, SP_UNIT_MM },
120     { "A10",                26,   37, SP_UNIT_MM },
121     { "B0",               1000, 1414, SP_UNIT_MM },
122     { "B1",                707, 1000, SP_UNIT_MM },
123     { "B2",                500,  707, SP_UNIT_MM },
124     { "B3",                353,  500, SP_UNIT_MM },
125     { "B4",                250,  353, SP_UNIT_MM },
126     { "B5",                176,  250, SP_UNIT_MM },
127     { "B6",                125,  176, SP_UNIT_MM },
128     { "B7",                 88,  125, SP_UNIT_MM },
129     { "B8",                 62,   88, SP_UNIT_MM },
130     { "B9",                 44,   62, SP_UNIT_MM },
131     { "B10",                31,   44, SP_UNIT_MM },
135 //#if 0
136          /*
137          Whether to include or exclude these depends on how
138          big we mind our page size menu
139          becoming.  C series is used for envelopes;
140          don't know what D and E series are used for.
141          */
143     { "C0",                917, 1297, SP_UNIT_MM },
144     { "C1",                648,  917, SP_UNIT_MM },
145     { "C2",                458,  648, SP_UNIT_MM },
146     { "C3",                324,  458, SP_UNIT_MM },
147     { "C4",                229,  324, SP_UNIT_MM },
148     { "C5",                162,  229, SP_UNIT_MM },
149     { "C6",                114,  162, SP_UNIT_MM },
150     { "C7",                 81,  114, SP_UNIT_MM },
151     { "C8",                 57,   81, SP_UNIT_MM },
152     { "C9",                 40,   57, SP_UNIT_MM },
153     { "C10",                28,   40, SP_UNIT_MM },
154     { "D1",                545,  771, SP_UNIT_MM },
155     { "D2",                385,  545, SP_UNIT_MM },
156     { "D3",                272,  385, SP_UNIT_MM },
157     { "D4",                192,  272, SP_UNIT_MM },
158     { "D5",                136,  192, SP_UNIT_MM },
159     { "D6",                 96,  136, SP_UNIT_MM },
160     { "D7",                 68,   96, SP_UNIT_MM },
161     { "E3",                400,  560, SP_UNIT_MM },
162     { "E4",                280,  400, SP_UNIT_MM },
163     { "E5",                200,  280, SP_UNIT_MM },
164     { "E6",                140,  200, SP_UNIT_MM },
165 //#endif
169     { "CSE",               462,  649, SP_UNIT_PT },
170     { "US #10 Envelope", 4.125,  9.5, SP_UNIT_IN },
171     /* See http://www.hbp.com/content/PCR_envelopes.cfm for a much larger list of US envelope
172        sizes. */
173     { "DL Envelope",       110,  220, SP_UNIT_MM },
174     { "Ledger/Tabloid",     11,   17, SP_UNIT_IN },
175     /* Note that `Folio' (used in QPrinter/KPrinter) is deliberately absent from this list, as it
176        means different sizes to different people: different people may expect the width to be
177        either 8, 8.25 or 8.5 inches, and the height to be either 13 or 13.5 inches, even
178        restricting our interpretation to foolscap folio.  If you wish to introduce a folio-like
179        page size to the list, then please consider using a name more specific than just `Folio' or
180        `Foolscap Folio'. */
181     { "Banner 468x60",      60,  468, SP_UNIT_PX },
182     { "Icon 16x16",         16,   16, SP_UNIT_PX },
183     { "Icon 32x32",         32,   32, SP_UNIT_PX },
184     { "Icon 48x48",         48,   48, SP_UNIT_PX },
185     /* business cards */
186     { "Business Card (ISO 7810)", 53.98, 85.60, SP_UNIT_MM },
187     { "Business Card (US)",             2,     3.5,  SP_UNIT_IN },
188     { "Business Card (Europe)",        55,    85,    SP_UNIT_MM },
189     { "Business Card (Aus/NZ)",        55,    90,    SP_UNIT_MM },
190     { NULL,                     0,    0, SP_UNIT_PX },
191 };
195 //########################################################################
196 //# P A G E    S I Z E R
197 //########################################################################
199 //The default unit for this widget and its calculations
200 static const SPUnit _px_unit = sp_unit_get_by_id (SP_UNIT_PX);
203 /**
204  * Constructor
205  */
206 PageSizer::PageSizer(Registry & _wr)
207     : Gtk::VBox(false,4),
208       _dimensionUnits( _("U_nits:"), "units", _wr ),
209       _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ),
210       _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ),
211       _widgetRegistry(&_wr)
213     //# Set up the Paper Size combo box
214     _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns);
215     _paperSizeList.set_model(_paperSizeListStore);
216     _paperSizeList.append_column(_("Name"),
217                                  _paperSizeListColumns.nameColumn);
218     _paperSizeList.append_column(_("Description"),
219                                  _paperSizeListColumns.descColumn);
220     _paperSizeList.set_headers_visible(false);
221     _paperSizeListSelection = _paperSizeList.get_selection();
222     _paper_size_list_connection =
223         _paperSizeListSelection->signal_changed().connect (
224             sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
225     _paperSizeListScroller.add(_paperSizeList);
226     _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN);
227     _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
228     _paperSizeListScroller.set_size_request(-1, 90);
230     fill_landscape_papers();
232     for (PaperSizeRec const *p = inkscape_papers; p->name; p++)
233     {
234         Glib::ustring name = p->name;
235         char formatBuf[80];
236         snprintf(formatBuf, 79, "%0.1f x %0.1f", p->smaller, p->larger);
237         Glib::ustring desc = formatBuf;
238         if (p->unit == SP_UNIT_IN)
239             desc.append(" in");
240         else if (p->unit == SP_UNIT_MM)
241              desc.append(" mm");
242         else if (p->unit == SP_UNIT_PX)
243             desc.append(" px");
244         PaperSize paper(name, p->smaller, p->larger, p->unit);
245         _paperSizeTable[name] = paper;
246         Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
247         row[_paperSizeListColumns.nameColumn] = name;
248         row[_paperSizeListColumns.descColumn] = desc;
249         }
250     //Gtk::TreeModel::iterator iter = _paperSizeListStore->children().begin();
251     //if (iter)
252     //    _paperSizeListSelection->select(iter);
255     pack_start (_paperSizeListBox, true, true, 0);
256     _paperSizeListLabel.set_label(_("P_age size:"));
257     _paperSizeListLabel.set_use_underline();
258     _paperSizeListBox.pack_start (_paperSizeListLabel, false, false, 0);
259     _paperSizeListLabel.set_mnemonic_widget (_paperSizeList);
260     _paperSizeListBox.pack_start (_paperSizeListScroller, true, true, 0);
262     //## Set up orientation radio buttons
263     pack_start (_orientationBox, false, false, 0);
264     _orientationLabel.set_label(_("Page orientation:"));
265     _orientationBox.pack_start(_orientationLabel, false, false, 0);
266     _landscapeButton.set_use_underline();
267     _landscapeButton.set_label(_("_Landscape"));
268     _landscapeButton.set_active(true);
269     Gtk::RadioButton::Group group = _landscapeButton.get_group();
270     _orientationBox.pack_end (_landscapeButton, false, false, 5);
271     _portraitButton.set_use_underline();
272     _portraitButton.set_label(_("_Portrait"));
273     _portraitButton.set_active(true);
274     _orientationBox.pack_end (_portraitButton, false, false, 5);
275     _portraitButton.set_group (group);
276     _portraitButton.set_active (true);
278     //## Set up custom size frame
279     _customFrame.set_label(_("Custom size"));
280     pack_start (_customFrame, false, false, 0);
281     _customTable.resize(2, 2);
282     _customTable.set_border_width (4);
283     _customTable.set_row_spacings (4);
284     _customTable.set_col_spacings (4);
285     _customTable.attach(_dimensionWidth, 0,1,0,1);
286     _customTable.attach(_dimensionUnits, 1,2,0,1);
287     _customTable.attach(_dimensionHeight, 0,1,1,2);
288     _customTable.attach(_fitPageButton,              1,2,1,2);
289     _customFrame.add(_customTable);
291     _fitPageButton.set_use_underline();
292     _fitPageButton.set_label(_("_Fit page to selection"));
293     _tips.set_tip(_fitPageButton, _("Resize the page to fit the current selection, or the entire drawing if there is no selection"));
297 /**
298  * Destructor
299  */
300 PageSizer::~PageSizer()
306 /**
307  * Initialize or reset this widget
308  */
309 void
310 PageSizer::init ()
312     _landscape_connection = _landscapeButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_landscape));
313     _portrait_connection = _portraitButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_portrait));
314     _changedw_connection = _dimensionWidth.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
315     _changedh_connection = _dimensionHeight.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
316     _fitPageButton.signal_clicked().connect(sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
318     show_all_children();
322 /**
323  * Set document dimensions (if not called by Doc prop's update()) and
324  * set the PageSizer's widgets and text entries accordingly. If
325  * 'chageList' is true, then adjust the paperSizeList to show the closest
326  * standard page size.
327  *
328  * \param w, h given in px
329  * \param changeList whether to modify the paper size list
330  */
331 void
332 PageSizer::setDim (double w, double h, bool changeList)
334     static bool _called = false;
335     if (_called) {
336         return;
337     }
339     _called = true;
341     _paper_size_list_connection.block();
342     _landscape_connection.block();
343     _portrait_connection.block();
344     _changedw_connection.block();
345     _changedh_connection.block();
347     if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
348         SPDocument *doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
349         sp_document_set_width (doc, w, &_px_unit);
350         sp_document_set_height (doc, h, &_px_unit);
351         sp_document_done (doc, SP_VERB_NONE, _("Set page size"));
352     }
354     _landscape = ( w > h );
355     _landscapeButton.set_active(_landscape ? true : false);
356     _portraitButton.set_active (_landscape ? false : true);
358     if (changeList)
359         {
360         Gtk::TreeModel::Row row = (*find_paper_size(w, h));
361         if (row)
362             _paperSizeListSelection->select(row);
363         }
365     Unit const& unit = _dimensionUnits.getUnit();
366     _dimensionWidth.setValue (w / unit.factor);
367     _dimensionHeight.setValue (h / unit.factor);
369     _paper_size_list_connection.unblock();
370     _landscape_connection.unblock();
371     _portrait_connection.unblock();
372     _changedw_connection.unblock();
373     _changedh_connection.unblock();
375     _called = false;
379 /**
380  * Returns an iterator pointing to a row in paperSizeListStore which
381  * contains a paper of the specified size (specified in px), or
382  * paperSizeListStore->children().end() if no such paper exists.
383  */
384 Gtk::ListStore::iterator
385 PageSizer::find_paper_size (double w, double h) const
387     double smaller = w;
388     double larger  = h;
389     if ( h < w ) {
390         smaller = h; larger = w;
391     }
393     g_return_val_if_fail(smaller <= larger, _paperSizeListStore->children().end());
395     std::map<Glib::ustring, PaperSize>::const_iterator iter;
396     for (iter = _paperSizeTable.begin() ;
397          iter != _paperSizeTable.end() ; iter++) {
398         PaperSize paper = iter->second;
399         SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
400         double smallX = sp_units_get_pixels(paper.smaller, i_unit);
401         double largeX = sp_units_get_pixels(paper.larger,  i_unit);
403         g_return_val_if_fail(smallX <= largeX, _paperSizeListStore->children().end());
405         if ((std::abs(smaller - smallX) <= 0.1) &&
406             (std::abs(larger  - largeX) <= 0.1)   ) {
407             Gtk::ListStore::iterator p;
408             // We need to search paperSizeListStore explicitly for the
409             // specified paper size because it is sorted in a different
410             // way than paperSizeTable (which is sorted alphabetically)
411             for (p = _paperSizeListStore->children().begin(); p != _paperSizeListStore->children().end(); p++) {
412                 if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
413                     return p;
414                 }
415             }
416         }
417     }
418     return _paperSizeListStore->children().end();
423 /**
424  * Tell the desktop to change the page size
425  */
426 void
427 PageSizer::fire_fit_canvas_to_selection_or_drawing()
429     SPDesktop *dt = SP_ACTIVE_DESKTOP;
430     if (!dt) {
431         return;
432     }
433     Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
434     if (verb) {
435         SPAction *action = verb->get_action(dt);
436         if (action)
437             sp_action_perform(action, NULL);
438     }
443 /**
444  * Paper Size list callback for when a user changes the selection
445  */
446 void
447 PageSizer::on_paper_size_list_changed()
449     //Glib::ustring name = _paperSizeList.get_active_text();
450     Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
451     if(!miter)
452         {
453         //error?
454         return;
455         }
456     Gtk::TreeModel::Row row = *miter;
457     Glib::ustring name = row[_paperSizeListColumns.nameColumn];
458     std::map<Glib::ustring, PaperSize>::const_iterator piter =
459                     _paperSizeTable.find(name);
460     if (piter == _paperSizeTable.end()) {
461         g_warning("paper size '%s' not found in table", name.c_str());
462         return;
463     }
464     PaperSize paper = piter->second;
465     double w = paper.smaller;
466     double h = paper.larger;
468     if (std::find(lscape_papers.begin(), lscape_papers.end(), paper.name.c_str()) != lscape_papers.end()) {
469         // enforce landscape mode if this is desired for the given page format
470         _landscape = true;
471     } else {
472         // otherwise we set portrait mode because
473         _landscape = false;
474     }
476     SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
477     sp_convert_distance (&w, &src_unit, &_px_unit);
478     sp_convert_distance (&h, &src_unit, &_px_unit);
480     if (_landscape)
481         setDim (h, w, false);
482     else
483         setDim (w, h, false);
488 /**
489  * Portrait button callback
490  */
491 void
492 PageSizer::on_portrait()
494     if (!_portraitButton.get_active())
495         return;
496     double w = _dimensionWidth.getValue ("px");
497     double h = _dimensionHeight.getValue ("px");
498     if (h < w) {
499         setDim (h, w);
500     }
504 /**
505  * Landscape button callback
506  */
507 void
508 PageSizer::on_landscape()
510     if (!_landscapeButton.get_active())
511         return;
512     double w = _dimensionWidth.getValue ("px");
513     double h = _dimensionHeight.getValue ("px");
514     if (w < h) {
515         setDim (h, w);
516     }
519 /**
520  * Callback for the dimension widgets
521  */
522 void
523 PageSizer::on_value_changed()
525     if (_widgetRegistry->isUpdating()) return;
527     setDim (_dimensionWidth.getValue("px"),
528             _dimensionHeight.getValue("px"));
532 } // namespace Widget
533 } // namespace UI
534 } // namespace Inkscape
536 /*
537   Local Variables:
538   mode:c++
539   c-file-style:"stroustrup"
540   c-file-offsets:((innamespace . 0)(inline-open . 0))
541   indent-tabs-mode:nil
542   fill-column:99
543   End:
544 */
545 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :