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)");
104 }
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)
212 {
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"));
294 }
297 /**
298 * Destructor
299 */
300 PageSizer::~PageSizer()
301 {
302 }
306 /**
307 * Initialize or reset this widget
308 */
309 void
310 PageSizer::init ()
311 {
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();
319 }
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)
333 {
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 if ( w != h ) {
355 _landscapeButton.set_sensitive(true);
356 _portraitButton.set_sensitive (true);
357 _landscape = ( w > h );
358 _landscapeButton.set_active(_landscape ? true : false);
359 _portraitButton.set_active (_landscape ? false : true);
360 } else {
361 _landscapeButton.set_sensitive(false);
362 _portraitButton.set_sensitive (false);
363 }
365 if (changeList)
366 {
367 Gtk::TreeModel::Row row = (*find_paper_size(w, h));
368 if (row)
369 _paperSizeListSelection->select(row);
370 }
372 Unit const& unit = _dimensionUnits.getUnit();
373 _dimensionWidth.setValue (w / unit.factor);
374 _dimensionHeight.setValue (h / unit.factor);
376 _paper_size_list_connection.unblock();
377 _landscape_connection.unblock();
378 _portrait_connection.unblock();
379 _changedw_connection.unblock();
380 _changedh_connection.unblock();
382 _called = false;
383 }
386 /**
387 * Returns an iterator pointing to a row in paperSizeListStore which
388 * contains a paper of the specified size (specified in px), or
389 * paperSizeListStore->children().end() if no such paper exists.
390 */
391 Gtk::ListStore::iterator
392 PageSizer::find_paper_size (double w, double h) const
393 {
394 double smaller = w;
395 double larger = h;
396 if ( h < w ) {
397 smaller = h; larger = w;
398 }
400 g_return_val_if_fail(smaller <= larger, _paperSizeListStore->children().end());
402 std::map<Glib::ustring, PaperSize>::const_iterator iter;
403 for (iter = _paperSizeTable.begin() ;
404 iter != _paperSizeTable.end() ; iter++) {
405 PaperSize paper = iter->second;
406 SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
407 double smallX = sp_units_get_pixels(paper.smaller, i_unit);
408 double largeX = sp_units_get_pixels(paper.larger, i_unit);
410 g_return_val_if_fail(smallX <= largeX, _paperSizeListStore->children().end());
412 if ((std::abs(smaller - smallX) <= 0.1) &&
413 (std::abs(larger - largeX) <= 0.1) ) {
414 Gtk::ListStore::iterator p;
415 // We need to search paperSizeListStore explicitly for the
416 // specified paper size because it is sorted in a different
417 // way than paperSizeTable (which is sorted alphabetically)
418 for (p = _paperSizeListStore->children().begin(); p != _paperSizeListStore->children().end(); p++) {
419 if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
420 return p;
421 }
422 }
423 }
424 }
425 return _paperSizeListStore->children().end();
426 }
430 /**
431 * Tell the desktop to change the page size
432 */
433 void
434 PageSizer::fire_fit_canvas_to_selection_or_drawing()
435 {
436 SPDesktop *dt = SP_ACTIVE_DESKTOP;
437 if (!dt) {
438 return;
439 }
440 Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
441 if (verb) {
442 SPAction *action = verb->get_action(dt);
443 if (action)
444 sp_action_perform(action, NULL);
445 }
446 }
450 /**
451 * Paper Size list callback for when a user changes the selection
452 */
453 void
454 PageSizer::on_paper_size_list_changed()
455 {
456 //Glib::ustring name = _paperSizeList.get_active_text();
457 Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
458 if(!miter)
459 {
460 //error?
461 return;
462 }
463 Gtk::TreeModel::Row row = *miter;
464 Glib::ustring name = row[_paperSizeListColumns.nameColumn];
465 std::map<Glib::ustring, PaperSize>::const_iterator piter =
466 _paperSizeTable.find(name);
467 if (piter == _paperSizeTable.end()) {
468 g_warning("paper size '%s' not found in table", name.c_str());
469 return;
470 }
471 PaperSize paper = piter->second;
472 double w = paper.smaller;
473 double h = paper.larger;
475 if (std::find(lscape_papers.begin(), lscape_papers.end(), paper.name.c_str()) != lscape_papers.end()) {
476 // enforce landscape mode if this is desired for the given page format
477 _landscape = true;
478 } else {
479 // otherwise we keep the current mode
480 _landscape = _landscapeButton.get_active();
481 }
483 SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
484 sp_convert_distance (&w, &src_unit, &_px_unit);
485 sp_convert_distance (&h, &src_unit, &_px_unit);
487 if (_landscape)
488 setDim (h, w, false);
489 else
490 setDim (w, h, false);
492 }
495 /**
496 * Portrait button callback
497 */
498 void
499 PageSizer::on_portrait()
500 {
501 if (!_portraitButton.get_active())
502 return;
503 double w = _dimensionWidth.getValue ("px");
504 double h = _dimensionHeight.getValue ("px");
505 if (h < w) {
506 setDim (h, w);
507 }
508 }
511 /**
512 * Landscape button callback
513 */
514 void
515 PageSizer::on_landscape()
516 {
517 if (!_landscapeButton.get_active())
518 return;
519 double w = _dimensionWidth.getValue ("px");
520 double h = _dimensionHeight.getValue ("px");
521 if (w < h) {
522 setDim (h, w);
523 }
524 }
526 /**
527 * Callback for the dimension widgets
528 */
529 void
530 PageSizer::on_value_changed()
531 {
532 if (_widgetRegistry->isUpdating()) return;
534 setDim (_dimensionWidth.getValue("px"),
535 _dimensionHeight.getValue("px"));
536 }
539 } // namespace Widget
540 } // namespace UI
541 } // namespace Inkscape
543 /*
544 Local Variables:
545 mode:c++
546 c-file-style:"stroustrup"
547 c-file-offsets:((innamespace . 0)(inline-open . 0))
548 indent-tabs-mode:nil
549 fill-column:99
550 End:
551 */
552 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :