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