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 },
191 { NULL, 0, 0, SP_UNIT_PX },
192 };
196 //########################################################################
197 //# P A G E S I Z E R
198 //########################################################################
200 //The default unit for this widget and its calculations
201 static const SPUnit _px_unit = sp_unit_get_by_id (SP_UNIT_PX);
204 /**
205 * Constructor
206 */
207 PageSizer::PageSizer(Registry & _wr)
208 : Gtk::VBox(false,4),
209 _dimensionUnits( _("U_nits:"), "units", _wr ),
210 _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ),
211 _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ),
212 _widgetRegistry(&_wr)
213 {
214 //# Set up the Paper Size combo box
215 _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns);
216 _paperSizeList.set_model(_paperSizeListStore);
217 _paperSizeList.append_column(_("Name"),
218 _paperSizeListColumns.nameColumn);
219 _paperSizeList.append_column(_("Description"),
220 _paperSizeListColumns.descColumn);
221 _paperSizeList.set_headers_visible(false);
222 _paperSizeListSelection = _paperSizeList.get_selection();
223 _paper_size_list_connection =
224 _paperSizeListSelection->signal_changed().connect (
225 sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
226 _paperSizeListScroller.add(_paperSizeList);
227 _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN);
228 _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
229 _paperSizeListScroller.set_size_request(-1, 90);
231 fill_landscape_papers();
233 for (PaperSizeRec const *p = inkscape_papers; p->name; p++)
234 {
235 Glib::ustring name = p->name;
236 char formatBuf[80];
237 snprintf(formatBuf, 79, "%0.1f x %0.1f", p->smaller, p->larger);
238 Glib::ustring desc = formatBuf;
239 if (p->unit == SP_UNIT_IN)
240 desc.append(" in");
241 else if (p->unit == SP_UNIT_MM)
242 desc.append(" mm");
243 else if (p->unit == SP_UNIT_PX)
244 desc.append(" px");
245 PaperSize paper(name, p->smaller, p->larger, p->unit);
246 _paperSizeTable[name] = paper;
247 Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
248 row[_paperSizeListColumns.nameColumn] = name;
249 row[_paperSizeListColumns.descColumn] = desc;
250 }
251 //Gtk::TreeModel::iterator iter = _paperSizeListStore->children().begin();
252 //if (iter)
253 // _paperSizeListSelection->select(iter);
256 pack_start (_paperSizeListBox, true, true, 0);
257 _paperSizeListLabel.set_label(_("P_age size:"));
258 _paperSizeListLabel.set_use_underline();
259 _paperSizeListBox.pack_start (_paperSizeListLabel, false, false, 0);
260 _paperSizeListLabel.set_mnemonic_widget (_paperSizeList);
261 _paperSizeListBox.pack_start (_paperSizeListScroller, true, true, 0);
263 //## Set up orientation radio buttons
264 pack_start (_orientationBox, false, false, 0);
265 _orientationLabel.set_label(_("Page orientation:"));
266 _orientationBox.pack_start(_orientationLabel, false, false, 0);
267 _landscapeButton.set_use_underline();
268 _landscapeButton.set_label(_("_Landscape"));
269 _landscapeButton.set_active(true);
270 Gtk::RadioButton::Group group = _landscapeButton.get_group();
271 _orientationBox.pack_end (_landscapeButton, false, false, 5);
272 _portraitButton.set_use_underline();
273 _portraitButton.set_label(_("_Portrait"));
274 _portraitButton.set_active(true);
275 _orientationBox.pack_end (_portraitButton, false, false, 5);
276 _portraitButton.set_group (group);
277 _portraitButton.set_active (true);
279 //## Set up custom size frame
280 _customFrame.set_label(_("Custom size"));
281 pack_start (_customFrame, false, false, 0);
282 _customTable.resize(2, 2);
283 _customTable.set_border_width (4);
284 _customTable.set_row_spacings (4);
285 _customTable.set_col_spacings (4);
286 _customTable.attach(_dimensionWidth, 0,1,0,1);
287 _customTable.attach(_dimensionUnits, 1,2,0,1);
288 _customTable.attach(_dimensionHeight, 0,1,1,2);
289 _customTable.attach(_fitPageButton, 1,2,1,2);
290 _customFrame.add(_customTable);
292 _fitPageButton.set_use_underline();
293 _fitPageButton.set_label(_("_Fit page to selection"));
294 _tips.set_tip(_fitPageButton, _("Resize the page to fit the current selection, or the entire drawing if there is no selection"));
295 }
298 /**
299 * Destructor
300 */
301 PageSizer::~PageSizer()
302 {
303 }
307 /**
308 * Initialize or reset this widget
309 */
310 void
311 PageSizer::init ()
312 {
313 _landscape_connection = _landscapeButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_landscape));
314 _portrait_connection = _portraitButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_portrait));
315 _changedw_connection = _dimensionWidth.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
316 _changedh_connection = _dimensionHeight.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
317 _fitPageButton.signal_clicked().connect(sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
319 show_all_children();
320 }
323 /**
324 * Set document dimensions (if not called by Doc prop's update()) and
325 * set the PageSizer's widgets and text entries accordingly. If
326 * 'chageList' is true, then adjust the paperSizeList to show the closest
327 * standard page size.
328 *
329 * \param w, h given in px
330 * \param changeList whether to modify the paper size list
331 */
332 void
333 PageSizer::setDim (double w, double h, bool changeList)
334 {
335 static bool _called = false;
336 if (_called) {
337 return;
338 }
340 _called = true;
342 _paper_size_list_connection.block();
343 _landscape_connection.block();
344 _portrait_connection.block();
345 _changedw_connection.block();
346 _changedh_connection.block();
348 if ( w != h ) {
349 _landscapeButton.set_sensitive(true);
350 _portraitButton.set_sensitive (true);
351 _landscape = ( w > h );
352 _landscapeButton.set_active(_landscape ? true : false);
353 _portraitButton.set_active (_landscape ? false : true);
354 } else {
355 _landscapeButton.set_sensitive(false);
356 _portraitButton.set_sensitive (false);
357 }
359 if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
360 SPDocument *doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
361 double const old_height = sp_document_height(doc);
362 sp_document_set_width (doc, w, &_px_unit);
363 sp_document_set_height (doc, h, &_px_unit);
364 sp_document_set_landscape (doc, _landscape);
365 // The origin for the user is in the lower left corner; this point should remain stationary when
366 // changing the page size. The SVG's origin however is in the upper left corner, so we must compensate for this
367 Geom::Translate const vert_offset(Geom::Point(0, (old_height - h)));
368 SP_GROUP(SP_ROOT(doc->root))->translateChildItems(vert_offset);
369 sp_document_done (doc, SP_VERB_NONE, _("Set page size"));
370 }
372 if (changeList)
373 {
374 Gtk::TreeModel::Row row = (*find_paper_size(w, h));
375 if (row)
376 _paperSizeListSelection->select(row);
377 }
379 Unit const& unit = _dimensionUnits.getUnit();
380 _dimensionWidth.setValue (w / unit.factor);
381 _dimensionHeight.setValue (h / unit.factor);
383 _paper_size_list_connection.unblock();
384 _landscape_connection.unblock();
385 _portrait_connection.unblock();
386 _changedw_connection.unblock();
387 _changedh_connection.unblock();
389 _called = false;
390 }
393 /**
394 * Returns an iterator pointing to a row in paperSizeListStore which
395 * contains a paper of the specified size (specified in px), or
396 * paperSizeListStore->children().end() if no such paper exists.
397 */
398 Gtk::ListStore::iterator
399 PageSizer::find_paper_size (double w, double h) const
400 {
401 double smaller = w;
402 double larger = h;
403 if ( h < w ) {
404 smaller = h; larger = w;
405 }
407 g_return_val_if_fail(smaller <= larger, _paperSizeListStore->children().end());
409 std::map<Glib::ustring, PaperSize>::const_iterator iter;
410 for (iter = _paperSizeTable.begin() ;
411 iter != _paperSizeTable.end() ; iter++) {
412 PaperSize paper = iter->second;
413 SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
414 double smallX = sp_units_get_pixels(paper.smaller, i_unit);
415 double largeX = sp_units_get_pixels(paper.larger, i_unit);
417 g_return_val_if_fail(smallX <= largeX, _paperSizeListStore->children().end());
419 if ((std::abs(smaller - smallX) <= 0.1) &&
420 (std::abs(larger - largeX) <= 0.1) ) {
421 Gtk::ListStore::iterator p;
422 // We need to search paperSizeListStore explicitly for the
423 // specified paper size because it is sorted in a different
424 // way than paperSizeTable (which is sorted alphabetically)
425 for (p = _paperSizeListStore->children().begin(); p != _paperSizeListStore->children().end(); p++) {
426 if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
427 return p;
428 }
429 }
430 }
431 }
432 return _paperSizeListStore->children().end();
433 }
437 /**
438 * Tell the desktop to change the page size
439 */
440 void
441 PageSizer::fire_fit_canvas_to_selection_or_drawing()
442 {
443 SPDesktop *dt = SP_ACTIVE_DESKTOP;
444 if (!dt) {
445 return;
446 }
447 Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
448 if (verb) {
449 SPAction *action = verb->get_action(dt);
450 if (action)
451 sp_action_perform(action, NULL);
452 }
453 }
457 /**
458 * Paper Size list callback for when a user changes the selection
459 */
460 void
461 PageSizer::on_paper_size_list_changed()
462 {
463 //Glib::ustring name = _paperSizeList.get_active_text();
464 Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
465 if(!miter)
466 {
467 //error?
468 return;
469 }
470 Gtk::TreeModel::Row row = *miter;
471 Glib::ustring name = row[_paperSizeListColumns.nameColumn];
472 std::map<Glib::ustring, PaperSize>::const_iterator piter =
473 _paperSizeTable.find(name);
474 if (piter == _paperSizeTable.end()) {
475 g_warning("paper size '%s' not found in table", name.c_str());
476 return;
477 }
478 PaperSize paper = piter->second;
479 double w = paper.smaller;
480 double h = paper.larger;
482 if (std::find(lscape_papers.begin(), lscape_papers.end(), paper.name.c_str()) != lscape_papers.end()) {
483 // enforce landscape mode if this is desired for the given page format
484 _landscape = true;
485 } else {
486 // otherwise we keep the current mode
487 _landscape = _landscapeButton.get_active();
488 }
490 SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
491 sp_convert_distance (&w, &src_unit, &_px_unit);
492 sp_convert_distance (&h, &src_unit, &_px_unit);
494 if (_landscape)
495 setDim (h, w, false);
496 else
497 setDim (w, h, false);
499 }
502 /**
503 * Portrait button callback
504 */
505 void
506 PageSizer::on_portrait()
507 {
508 if (!_portraitButton.get_active())
509 return;
510 double w = _dimensionWidth.getValue ("px");
511 double h = _dimensionHeight.getValue ("px");
512 if (h < w) {
513 setDim (h, w);
514 }
515 }
518 /**
519 * Landscape button callback
520 */
521 void
522 PageSizer::on_landscape()
523 {
524 if (!_landscapeButton.get_active())
525 return;
526 double w = _dimensionWidth.getValue ("px");
527 double h = _dimensionHeight.getValue ("px");
528 if (w < h) {
529 setDim (h, w);
530 }
531 }
533 /**
534 * Callback for the dimension widgets
535 */
536 void
537 PageSizer::on_value_changed()
538 {
539 if (_widgetRegistry->isUpdating()) return;
541 setDim (_dimensionWidth.getValue("px"),
542 _dimensionHeight.getValue("px"));
543 }
546 } // namespace Widget
547 } // namespace UI
548 } // namespace Inkscape
550 /*
551 Local Variables:
552 mode:c++
553 c-file-style:"stroustrup"
554 c-file-offsets:((innamespace . 0)(inline-open . 0))
555 indent-tabs-mode:nil
556 fill-column:99
557 End:
558 */
559 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :