f193f04d20fa44aa9bc65e783e7023cb6bc8eb16
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(Registry & _wr)
189 : Gtk::VBox(false,4),
190 _dimensionUnits( _("U_nits:"), "units", _wr ),
191 _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ),
192 _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ),
193 _widgetRegistry(&_wr)
194 {
195 //# Set up the Paper Size combo box
196 _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns);
197 _paperSizeList.set_model(_paperSizeListStore);
198 _paperSizeList.append_column(_("Name"),
199 _paperSizeListColumns.nameColumn);
200 _paperSizeList.append_column(_("Description"),
201 _paperSizeListColumns.descColumn);
202 _paperSizeList.set_headers_visible(false);
203 _paperSizeListSelection = _paperSizeList.get_selection();
204 _paper_size_list_connection =
205 _paperSizeListSelection->signal_changed().connect (
206 sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
207 _paperSizeListScroller.add(_paperSizeList);
208 _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN);
209 _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
210 _paperSizeListScroller.set_size_request(-1, 90);
212 for (PaperSizeRec const *p = inkscape_papers; p->name; p++)
213 {
214 Glib::ustring name = p->name;
215 char formatBuf[80];
216 snprintf(formatBuf, 79, "%0.1f x %0.1f", p->smaller, p->larger);
217 Glib::ustring desc = formatBuf;
218 if (p->unit == SP_UNIT_IN)
219 desc.append(" in");
220 else if (p->unit == SP_UNIT_MM)
221 desc.append(" mm");
222 else if (p->unit == SP_UNIT_PX)
223 desc.append(" px");
224 PaperSize paper(name, p->smaller, p->larger, p->unit);
225 _paperSizeTable[name] = paper;
226 Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
227 row[_paperSizeListColumns.nameColumn] = name;
228 row[_paperSizeListColumns.descColumn] = desc;
229 }
230 //Gtk::TreeModel::iterator iter = _paperSizeListStore->children().begin();
231 //if (iter)
232 // _paperSizeListSelection->select(iter);
235 pack_start (_paperSizeListBox, false, false, 0);
236 _paperSizeListLabel.set_label(_("P_age size:"));
237 _paperSizeListLabel.set_use_underline();
238 _paperSizeListBox.pack_start (_paperSizeListLabel, false, false, 0);
239 _paperSizeListLabel.set_mnemonic_widget (_paperSizeList);
240 _paperSizeListBox.pack_start (_paperSizeListScroller, true, true, 0);
242 //## Set up orientation radio buttons
243 pack_start (_orientationBox, false, false, 0);
244 _orientationLabel.set_label(_("Page orientation:"));
245 _orientationBox.pack_start(_orientationLabel, false, false, 0);
246 _landscapeButton.set_use_underline();
247 _landscapeButton.set_label(_("_Landscape"));
248 _landscapeButton.set_active(true);
249 Gtk::RadioButton::Group group = _landscapeButton.get_group();
250 _orientationBox.pack_end (_landscapeButton, false, false, 5);
251 _portraitButton.set_use_underline();
252 _portraitButton.set_label(_("_Portrait"));
253 _portraitButton.set_active(true);
254 _orientationBox.pack_end (_portraitButton, false, false, 5);
255 _portraitButton.set_group (group);
256 _portraitButton.set_active (true);
258 //## Set up custom size frame
259 _customFrame.set_label(_("Custom size"));
260 pack_start (_customFrame, false, false, 0);
261 _customTable.resize(2, 2);
262 _customTable.set_border_width (4);
263 _customTable.set_row_spacings (4);
264 _customTable.set_col_spacings (4);
265 _customTable.attach(_dimensionWidth, 0,1,0,1);
266 _customTable.attach(_dimensionUnits, 1,2,0,1);
267 _customTable.attach(_dimensionHeight, 0,1,1,2);
268 _customTable.attach(_fitPageButton, 1,2,1,2);
269 _customFrame.add(_customTable);
271 _fitPageButton.set_use_underline();
272 _fitPageButton.set_label(_("_Fit page to selection"));
273 _tips.set_tip(_fitPageButton, _("Resize the page to fit the current selection, or the entire drawing if there is no selection"));
274 }
277 /**
278 * Destructor
279 */
280 PageSizer::~PageSizer()
281 {
282 }
286 /**
287 * Initialize or reset this widget
288 */
289 void
290 PageSizer::init ()
291 {
292 _landscape_connection = _landscapeButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_landscape));
293 _portrait_connection = _portraitButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_portrait));
294 _changedw_connection = _dimensionWidth.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
295 _changedh_connection = _dimensionHeight.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
296 _fitPageButton.signal_clicked().connect(sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
298 show_all_children();
299 }
302 /**
303 * Set document dimensions (if not called by Doc prop's update()) and
304 * set the PageSizer's widgets and text entries accordingly. If
305 * 'chageList' is true, then adjust the paperSizeList to show the closest
306 * standard page size.
307 *
308 * \param w, h given in px
309 * \param changeList whether to modify the paper size list
310 */
311 void
312 PageSizer::setDim (double w, double h, bool changeList)
313 {
314 static bool _called = false;
315 if (_called) {
316 return;
317 }
319 _called = true;
321 _paper_size_list_connection.block();
322 _landscape_connection.block();
323 _portrait_connection.block();
324 _changedw_connection.block();
325 _changedh_connection.block();
327 if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
328 SPDocument *doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
329 sp_document_set_width (doc, w, &_px_unit);
330 sp_document_set_height (doc, h, &_px_unit);
331 sp_document_done (doc, SP_VERB_NONE, _("Set page size"));
332 }
334 _landscape = ( w > h );
335 _landscapeButton.set_active(_landscape ? true : false);
336 _portraitButton.set_active (_landscape ? false : true);
338 if (changeList)
339 {
340 Gtk::TreeModel::Row row = (*find_paper_size(w, h));
341 if (row)
342 _paperSizeListSelection->select(row);
343 }
345 Unit const& unit = _dimensionUnits.getUnit();
346 _dimensionWidth.setValue (w / unit.factor);
347 _dimensionHeight.setValue (h / unit.factor);
349 _paper_size_list_connection.unblock();
350 _landscape_connection.unblock();
351 _portrait_connection.unblock();
352 _changedw_connection.unblock();
353 _changedh_connection.unblock();
355 _called = false;
356 }
359 /**
360 * Returns an iterator pointing to a row in paperSizeListStore which
361 * contains a paper of the specified size (specified in px), or
362 * paperSizeListStore->children().end() if no such paper exists.
363 */
364 Gtk::ListStore::iterator
365 PageSizer::find_paper_size (double w, double h) const
366 {
367 double smaller = w;
368 double larger = h;
369 if ( h < w ) {
370 smaller = h; larger = w;
371 }
373 g_return_val_if_fail(smaller <= larger, _paperSizeListStore->children().end());
375 std::map<Glib::ustring, PaperSize>::const_iterator iter;
376 for (iter = _paperSizeTable.begin() ;
377 iter != _paperSizeTable.end() ; iter++) {
378 PaperSize paper = iter->second;
379 SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
380 double smallX = sp_units_get_pixels(paper.smaller, i_unit);
381 double largeX = sp_units_get_pixels(paper.larger, i_unit);
383 g_return_val_if_fail(smallX <= largeX, _paperSizeListStore->children().end());
385 if ((std::abs(smaller - smallX) <= 0.1) &&
386 (std::abs(larger - largeX) <= 0.1) ) {
387 Gtk::ListStore::iterator p;
388 // We need to search paperSizeListStore explicitly for the
389 // specified paper size because it is sorted in a different
390 // way than paperSizeTable (which is sorted alphabetically)
391 for (p = _paperSizeListStore->children().begin(); p != _paperSizeListStore->children().end(); p++) {
392 if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
393 return p;
394 }
395 }
396 }
397 }
398 return _paperSizeListStore->children().end();
399 }
403 /**
404 * Tell the desktop to change the page size
405 */
406 void
407 PageSizer::fire_fit_canvas_to_selection_or_drawing()
408 {
409 SPDesktop *dt = SP_ACTIVE_DESKTOP;
410 if (!dt) {
411 return;
412 }
413 Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
414 if (verb) {
415 SPAction *action = verb->get_action(dt);
416 if (action)
417 sp_action_perform(action, NULL);
418 }
419 }
423 /**
424 * Paper Size list callback for when a user changes the selection
425 */
426 void
427 PageSizer::on_paper_size_list_changed()
428 {
429 //Glib::ustring name = _paperSizeList.get_active_text();
430 Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
431 if(!miter)
432 {
433 //error?
434 return;
435 }
436 Gtk::TreeModel::Row row = *miter;
437 Glib::ustring name = row[_paperSizeListColumns.nameColumn];
438 std::map<Glib::ustring, PaperSize>::const_iterator piter =
439 _paperSizeTable.find(name);
440 if (piter == _paperSizeTable.end()) {
441 g_warning("paper size '%s' not found in table", name.c_str());
442 return;
443 }
444 PaperSize paper = piter->second;
445 double w = paper.smaller;
446 double h = paper.larger;
447 SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
448 sp_convert_distance (&w, &src_unit, &_px_unit);
449 sp_convert_distance (&h, &src_unit, &_px_unit);
451 if (_landscape)
452 setDim (h, w, false);
453 else
454 setDim (w, h, false);
456 }
459 /**
460 * Portrait button callback
461 */
462 void
463 PageSizer::on_portrait()
464 {
465 if (!_portraitButton.get_active())
466 return;
467 double w = _dimensionWidth.getValue ("px");
468 double h = _dimensionHeight.getValue ("px");
469 if (h < w) {
470 setDim (h, w);
471 }
472 }
475 /**
476 * Landscape button callback
477 */
478 void
479 PageSizer::on_landscape()
480 {
481 if (!_landscapeButton.get_active())
482 return;
483 double w = _dimensionWidth.getValue ("px");
484 double h = _dimensionHeight.getValue ("px");
485 if (w < h) {
486 setDim (h, w);
487 }
488 }
490 /**
491 * Callback for the dimension widgets
492 */
493 void
494 PageSizer::on_value_changed()
495 {
496 if (_widgetRegistry->isUpdating()) return;
498 setDim (_dimensionWidth.getValue("px"),
499 _dimensionHeight.getValue("px"));
500 }
503 } // namespace Widget
504 } // namespace UI
505 } // namespace Inkscape
507 /*
508 Local Variables:
509 mode:c++
510 c-file-style:"stroustrup"
511 c-file-offsets:((innamespace . 0)(inline-open . 0))
512 indent-tabs-mode:nil
513 fill-column:99
514 End:
515 */
516 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :