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 { NULL, 0, 0, SP_UNIT_PX },
173 };
177 //########################################################################
178 //# P A G E S I Z E R
179 //########################################################################
181 //The default unit for this widget and its calculations
182 static const SPUnit _px_unit = sp_unit_get_by_id (SP_UNIT_PX);
185 /**
186 * Constructor
187 */
188 PageSizer::PageSizer() : Gtk::VBox(false,4)
189 {
192 //# Set up the Paper Size combo box
193 _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns);
194 _paperSizeList.set_model(_paperSizeListStore);
195 _paperSizeList.append_column(_("Name"),
196 _paperSizeListColumns.nameColumn);
197 _paperSizeList.append_column(_("Description"),
198 _paperSizeListColumns.descColumn);
199 _paperSizeList.set_headers_visible(false);
200 _paperSizeListSelection = _paperSizeList.get_selection();
201 _paper_size_list_connection =
202 _paperSizeListSelection->signal_changed().connect (
203 sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
204 _paperSizeListScroller.add(_paperSizeList);
205 _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN);
206 _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
207 _paperSizeListScroller.set_size_request(-1, 90);
209 for (PaperSizeRec const *p = inkscape_papers; p->name; p++)
210 {
211 Glib::ustring name = p->name;
212 char formatBuf[80];
213 snprintf(formatBuf, 79, "%0.1f x %0.1f", p->smaller, p->larger);
214 Glib::ustring desc = formatBuf;
215 if (p->unit == SP_UNIT_IN)
216 desc.append(" in");
217 else if (p->unit == SP_UNIT_MM)
218 desc.append(" mm");
219 else if (p->unit == SP_UNIT_PX)
220 desc.append(" px");
221 PaperSize paper(name, p->smaller, p->larger, p->unit);
222 _paperSizeTable[name] = paper;
223 Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
224 row[_paperSizeListColumns.nameColumn] = name;
225 row[_paperSizeListColumns.descColumn] = desc;
226 }
227 //Gtk::TreeModel::iterator iter = _paperSizeListStore->children().begin();
228 //if (iter)
229 // _paperSizeListSelection->select(iter);
232 pack_start (_paperSizeListBox, false, false, 0);
233 _paperSizeListLabel.set_label(_("P_age size:"));
234 _paperSizeListLabel.set_use_underline();
235 _paperSizeListBox.pack_start (_paperSizeListLabel, false, false, 0);
236 _paperSizeListLabel.set_mnemonic_widget (_paperSizeList);
237 _paperSizeListBox.pack_start (_paperSizeListScroller, true, true, 0);
239 //## Set up orientation radio buttons
240 pack_start (_orientationBox, false, false, 0);
241 _orientationLabel.set_label(_("Page orientation:"));
242 _orientationBox.pack_start(_orientationLabel, false, false, 0);
243 _landscapeButton.set_use_underline();
244 _landscapeButton.set_label(_("_Landscape"));
245 _landscapeButton.set_active(true);
246 Gtk::RadioButton::Group group = _landscapeButton.get_group();
247 _orientationBox.pack_end (_landscapeButton, false, false, 5);
248 _portraitButton.set_use_underline();
249 _portraitButton.set_label(_("_Portrait"));
250 _portraitButton.set_active(true);
251 _orientationBox.pack_end (_portraitButton, false, false, 5);
252 _portraitButton.set_group (group);
253 _portraitButton.set_active (true);
255 //## Set up custom size frame
256 _customFrame.set_label(_("Custom size"));
257 pack_start (_customFrame, false, false, 0);
258 _customTable.resize(2, 2);
259 _customTable.set_border_width (4);
260 _customTable.set_row_spacings (4);
261 _customTable.set_col_spacings (4);
262 _customFrame.add(_customTable);
264 _fitPageButton.set_use_underline();
265 _fitPageButton.set_label(_("_Fit page to selection"));
266 _tips.set_tip(_fitPageButton,
267 _("Resize the page to fit the current selection, or the entire drawing if there is no selection"));
271 }
274 /**
275 * Destructor
276 */
277 PageSizer::~PageSizer()
278 {
279 }
283 /**
284 * Initialize or reset this widget
285 */
286 void
287 PageSizer::init (Registry& reg)
288 {
290 /*
291 Note that the registered widgets can only be placed onto a
292 container after they have been init()-ed. That is why some
293 of the widget creation is in the constructor, and the rest is
294 here.
295 */
297 _widgetRegistry = ®
299 _dimensionUnits.init (_("U_nits:"), "units",
300 *_widgetRegistry);
301 _dimensionWidth.init (_("_Width:"), _("Width of paper"), "width",
302 _dimensionUnits, *_widgetRegistry);
303 _dimensionHeight.init (_("_Height:"), _("Height of paper"), "height",
304 _dimensionUnits, *_widgetRegistry);
306 _customTable.attach(*(_dimensionWidth.getSU()), 0,1,0,1);
307 _customTable.attach(*(_dimensionUnits._sel), 1,2,0,1);
308 _customTable.attach(*(_dimensionHeight.getSU()), 0,1,1,2);
309 _customTable.attach(_fitPageButton, 1,2,1,2);
311 _landscape_connection = _landscapeButton.signal_toggled().connect (
312 sigc::mem_fun (*this, &PageSizer::on_landscape));
313 _portrait_connection = _portraitButton.signal_toggled().connect (
314 sigc::mem_fun (*this, &PageSizer::on_portrait));
315 _changedw_connection = _dimensionWidth.getSU()->signal_value_changed().connect (
316 sigc::mem_fun (*this, &PageSizer::on_value_changed));
317 _changedh_connection = _dimensionHeight.getSU()->signal_value_changed().connect (
318 sigc::mem_fun (*this, &PageSizer::on_value_changed));
319 _fitPageButton.signal_clicked().connect(
320 sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
322 show_all_children();
324 }
327 /**
328 * Set document dimensions (if not called by Doc prop's update()) and
329 * set the PageSizer's widgets and text entries accordingly. If
330 * 'chageList' is true, then adjust the paperSizeList to show the closest
331 * standard page size.
332 *
333 * \param w, h given in px
334 * \param changeList whether to modify the paper size list
335 */
336 void
337 PageSizer::setDim (double w, double h, bool changeList)
338 {
339 static bool _called = false;
340 if (_called)
341 return;
343 _called = true;
345 _paper_size_list_connection.block();
346 _landscape_connection.block();
347 _portrait_connection.block();
348 _changedw_connection.block();
349 _changedh_connection.block();
351 if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
352 SPDocument *doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
353 sp_document_set_width (doc, w, &_px_unit);
354 sp_document_set_height (doc, h, &_px_unit);
355 sp_document_done (doc, SP_VERB_NONE, _("Set page size"));
356 }
358 _landscape = ( w > h );
359 _landscapeButton.set_active(_landscape ? true : false);
360 _portraitButton.set_active (_landscape ? false : true);
362 if (changeList)
363 {
364 Gtk::TreeModel::Row row = (*find_paper_size(w, h));
365 if (row)
366 _paperSizeListSelection->select(row);
367 }
369 Unit const& unit = _dimensionUnits._sel->getUnit();
370 _dimensionWidth.setValue (w / unit.factor);
371 _dimensionHeight.setValue (h / unit.factor);
373 _paper_size_list_connection.unblock();
374 _landscape_connection.unblock();
375 _portrait_connection.unblock();
376 _changedw_connection.unblock();
377 _changedh_connection.unblock();
379 _called = false;
380 }
383 /**
384 * Returns an iterator pointing to a row in paperSizeListStore which
385 * contains a paper of the specified size (specified in px), or
386 * paperSizeListStore->children().end() if no such paper exists.
387 */
388 Gtk::ListStore::iterator
389 PageSizer::find_paper_size (double w, double h) const
390 {
391 double smaller = w;
392 double larger = h;
393 if ( h < w ) {
394 smaller = h; larger = w;
395 }
397 g_return_val_if_fail(smaller <= larger, _paperSizeListStore->children().end());
399 std::map<Glib::ustring, PaperSize>::const_iterator iter;
400 for (iter = _paperSizeTable.begin() ;
401 iter != _paperSizeTable.end() ; iter++) {
402 PaperSize paper = iter->second;
403 SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
404 double smallX = sp_units_get_pixels(paper.smaller, i_unit);
405 double largeX = sp_units_get_pixels(paper.larger, i_unit);
407 g_return_val_if_fail(smallX <= largeX, _paperSizeListStore->children().end());
409 if ((std::abs(smaller - smallX) <= 0.1) &&
410 (std::abs(larger - largeX) <= 0.1) ) {
411 Gtk::ListStore::iterator p;
412 // We need to search paperSizeListStore explicitly for the
413 // specified paper size because it is sorted in a different
414 // way than paperSizeTable (which is sorted alphabetically)
415 for (p = _paperSizeListStore->children().begin(); p != _paperSizeListStore->children().end(); p++) {
416 if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
417 return p;
418 }
419 }
420 }
421 }
422 return _paperSizeListStore->children().end();
423 }
427 /**
428 * Tell the desktop to change the page size
429 */
430 void
431 PageSizer::fire_fit_canvas_to_selection_or_drawing()
432 {
433 SPDesktop *dt = SP_ACTIVE_DESKTOP;
434 if (!dt)
435 return;
436 Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
437 if (verb) {
438 SPAction *action = verb->get_action(dt);
439 if (action)
440 sp_action_perform(action, NULL);
441 }
442 }
446 /**
447 * Paper Size list callback for when a user changes the selection
448 */
449 void
450 PageSizer::on_paper_size_list_changed()
451 {
452 //Glib::ustring name = _paperSizeList.get_active_text();
453 Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
454 if(!miter)
455 {
456 //error?
457 return;
458 }
459 Gtk::TreeModel::Row row = *miter;
460 Glib::ustring name = row[_paperSizeListColumns.nameColumn];
461 std::map<Glib::ustring, PaperSize>::const_iterator piter =
462 _paperSizeTable.find(name);
463 if (piter == _paperSizeTable.end()) {
464 g_warning("paper size '%s' not found in table", name.c_str());
465 return;
466 }
467 PaperSize paper = piter->second;
468 double w = paper.smaller;
469 double h = paper.larger;
470 SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
471 sp_convert_distance (&w, &src_unit, &_px_unit);
472 sp_convert_distance (&h, &src_unit, &_px_unit);
474 if (_landscape)
475 setDim (h, w, false);
476 else
477 setDim (w, h, false);
479 }
482 /**
483 * Portrait button callback
484 */
485 void
486 PageSizer::on_portrait()
487 {
488 if (!_portraitButton.get_active())
489 return;
490 double w = _dimensionWidth.getSU()->getValue ("px");
491 double h = _dimensionHeight.getSU()->getValue ("px");
492 if (h < w)
493 setDim (h, w);
494 }
497 /**
498 * Landscape button callback
499 */
500 void
501 PageSizer::on_landscape()
502 {
503 if (!_landscapeButton.get_active())
504 return;
505 double w = _dimensionWidth.getSU()->getValue ("px");
506 double h = _dimensionHeight.getSU()->getValue ("px");
507 if (w < h)
508 setDim (h, w);
509 }
511 /**
512 * Callback for the dimension widgets
513 */
514 void
515 PageSizer::on_value_changed()
516 {
517 if (_widgetRegistry->isUpdating()) return;
519 setDim (_dimensionWidth.getSU()->getValue("px"),
520 _dimensionHeight.getSU()->getValue("px"));
521 }
524 } // namespace Widget
525 } // namespace UI
526 } // namespace Inkscape
528 /*
529 Local Variables:
530 mode:c++
531 c-file-style:"stroustrup"
532 c-file-offsets:((innamespace . 0)(inline-open . 0))
533 indent-tabs-mode:nil
534 fill-column:99
535 End:
536 */
537 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :