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 <cmath>
22 #include <gtkmm.h>
23 #include "ui/widget/button.h"
25 #include "ui/widget/scalar-unit.h"
27 #include "helper/units.h"
28 #include "inkscape.h"
29 #include "verbs.h"
30 #include "desktop-handles.h"
31 #include "document.h"
32 #include "desktop.h"
33 #include "page-sizer.h"
34 #include "helper/action.h"
36 using std::pair;
38 namespace Inkscape {
39 namespace UI {
40 namespace Widget {
42 /** \note
43 * The ISO page sizes in the table below differ from ghostscript's idea of page sizes (by
44 * less than 1pt). Being off by <1pt should be OK for most purposes, but may cause fuzziness
45 * (antialiasing) problems when printing to 72dpi or 144dpi printers or bitmap files due to
46 * postscript's different coordinate system (y=0 meaning bottom of page in postscript and top
47 * of page in SVG). I haven't looked into whether this does in fact cause fuzziness, I merely
48 * note the possibility. Rounding done by extension/internal/ps.cpp (e.g. floor/ceil calls)
49 * will also affect whether fuzziness occurs.
50 *
51 * The remainder of this comment discusses the origin of the numbers used for ISO page sizes in
52 * this table and in ghostscript.
53 *
54 * The versions here, in mm, are the official sizes according to
55 * <a href="http://en.wikipedia.org/wiki/Paper_sizes">http://en.wikipedia.org/wiki/Paper_sizes</a>
56 * at 2005-01-25. (The ISO entries in the below table
57 * were produced mechanically from the table on that page.)
58 *
59 * (The rule seems to be that A0, B0, ..., D0. sizes are rounded to the nearest number of mm
60 * from the "theoretical size" (i.e. 1000 * sqrt(2) or pow(2.0, .25) or the like), whereas
61 * going from e.g. A0 to A1 always take the floor of halving -- which by chance coincides
62 * exactly with flooring the "theoretical size" for n != 0 instead of the rounding to nearest
63 * done for n==0.)
64 *
65 * Ghostscript paper sizes are given in gs_statd.ps according to gs(1). gs_statd.ps always
66 * uses an integer number of pt: sometimes gs_statd.ps rounds to nearest (e.g. a1), sometimes
67 * floors (e.g. a10), sometimes ceils (e.g. a8).
68 *
69 * I'm not sure how ghostscript's gs_statd.ps was calculated: it isn't just rounding the
70 * "theoretical size" of each page to pt (see a0), nor is it rounding the a0 size times an
71 * appropriate power of two (see a1). Possibly it was prepared manually, with a human applying
72 * inconsistent rounding rules when converting from mm to pt.
73 */
74 /** \todo
75 * Should we include the JIS B series (used in Japan)
76 * (JIS B0 is sometimes called JB0, and similarly for JB1 etc)?
77 * Should we exclude B7--B10 and A7--10 to make the list smaller ?
78 * Should we include any of the ISO C, D and E series (see below) ?
79 */
81 struct PaperSizeRec {
82 char const * const name; //name
83 double const smaller; //lesser dimension
84 double const larger; //greater dimension
85 SPUnitId const unit; //units
86 };
88 static PaperSizeRec const inkscape_papers[] = {
89 { "A4", 210, 297, SP_UNIT_MM },
90 { "US Letter", 8.5, 11, SP_UNIT_IN },
91 { "US Legal", 8.5, 14, SP_UNIT_IN },
92 { "US Executive", 7.25, 10.5, SP_UNIT_IN },
93 { "A0", 841, 1189, SP_UNIT_MM },
94 { "A1", 594, 841, SP_UNIT_MM },
95 { "A2", 420, 594, SP_UNIT_MM },
96 { "A3", 297, 420, SP_UNIT_MM },
97 { "A5", 148, 210, SP_UNIT_MM },
98 { "A6", 105, 148, SP_UNIT_MM },
99 { "A7", 74, 105, SP_UNIT_MM },
100 { "A8", 52, 74, SP_UNIT_MM },
101 { "A9", 37, 52, SP_UNIT_MM },
102 { "A10", 26, 37, SP_UNIT_MM },
103 { "B0", 1000, 1414, SP_UNIT_MM },
104 { "B1", 707, 1000, SP_UNIT_MM },
105 { "B2", 500, 707, SP_UNIT_MM },
106 { "B3", 353, 500, SP_UNIT_MM },
107 { "B4", 250, 353, SP_UNIT_MM },
108 { "B5", 176, 250, SP_UNIT_MM },
109 { "B6", 125, 176, SP_UNIT_MM },
110 { "B7", 88, 125, SP_UNIT_MM },
111 { "B8", 62, 88, SP_UNIT_MM },
112 { "B9", 44, 62, SP_UNIT_MM },
113 { "B10", 31, 44, SP_UNIT_MM },
115 #if 0 /*
116 Whether to include or exclude these depends on how
117 big we mind our page size menu
118 becoming. C series is used for envelopes;
119 don't know what D and E series are used for. */
120 { "C0", 917, 1297, SP_UNIT_MM },
121 { "C1", 648, 917, SP_UNIT_MM },
122 { "C2", 458, 648, SP_UNIT_MM },
123 { "C3", 324, 458, SP_UNIT_MM },
124 { "C4", 229, 324, SP_UNIT_MM },
125 { "C5", 162, 229, SP_UNIT_MM },
126 { "C6", 114, 162, SP_UNIT_MM },
127 { "C7", 81, 114, SP_UNIT_MM },
128 { "C8", 57, 81, SP_UNIT_MM },
129 { "C9", 40, 57, SP_UNIT_MM },
130 { "C10", 28, 40, SP_UNIT_MM },
131 { "D1", 545, 771, SP_UNIT_MM },
132 { "D2", 385, 545, SP_UNIT_MM },
133 { "D3", 272, 385, SP_UNIT_MM },
134 { "D4", 192, 272, SP_UNIT_MM },
135 { "D5", 136, 192, SP_UNIT_MM },
136 { "D6", 96, 136, SP_UNIT_MM },
137 { "D7", 68, 96, SP_UNIT_MM },
138 { "E3", 400, 560, SP_UNIT_MM },
139 { "E4", 280, 400, SP_UNIT_MM },
140 { "E5", 200, 280, SP_UNIT_MM },
141 { "E6", 140, 200, SP_UNIT_MM },
142 #endif
144 { "CSE", 462, 649, SP_UNIT_PT },
145 { "US #10 Envelope", 4.125, 9.5, SP_UNIT_IN },
146 // TODO: Select landscape by default.
147 /* See http://www.hbp.com/content/PCR_envelopes.cfm for a much larger list of US envelope
148 sizes. */
149 { "DL Envelope", 110, 220, SP_UNIT_MM },
150 // TODO: Select landscape by default.
151 { "Ledger/Tabloid", 11, 17, SP_UNIT_IN },
152 /* Note that `Folio' (used in QPrinter/KPrinter) is deliberately absent from this list, as it
153 means different sizes to different people: different people may expect the width to be
154 either 8, 8.25 or 8.5 inches, and the height to be either 13 or 13.5 inches, even
155 restricting our interpretation to foolscap folio. If you wish to introduce a folio-like
156 page size to the list, then please consider using a name more specific than just `Folio' or
157 `Foolscap Folio'. */
158 { "Banner 468x60", 60, 468, SP_UNIT_PX },
159 // TODO: Select landscape by default.
160 { "Icon 16x16", 16, 16, SP_UNIT_PX },
161 { "Icon 32x32", 32, 32, SP_UNIT_PX },
162 { "Icon 48x48", 48, 48, SP_UNIT_PX },
163 { NULL, 0, 0, SP_UNIT_PX },
164 };
168 //########################################################################
169 //# P A G E S I Z E R
170 //########################################################################
172 //The default unit for this widget and its calculations
173 static const SPUnit _px_unit = sp_unit_get_by_id (SP_UNIT_PX);
176 /**
177 * Constructor
178 */
179 PageSizer::PageSizer() : Gtk::VBox(false,4)
180 {
181 Gtk::HBox *hbox_size = manage (new Gtk::HBox (false, 4));
182 pack_start (*hbox_size, false, false, 0);
183 Gtk::Label *label_size = manage (new Gtk::Label (_("P_age size:"), 1.0, 0.5));
184 label_size->set_use_underline();
185 hbox_size->pack_start (*label_size, false, false, 0);
186 label_size->set_mnemonic_widget (_paperSizeList);
187 hbox_size->pack_start (_paperSizeList, true, true, 0);
189 //# Set up the Paper Size combo box
191 for (PaperSizeRec const *p = inkscape_papers; p->name; p++)
192 {
193 Glib::ustring name = p->name;
194 PaperSize paper(name, p->smaller, p->larger, p->unit);
195 paperSizeTable[name] = paper;
196 _paperSizeList.append_text(name);
197 }
199 }
202 /**
203 * Destructor
204 */
205 PageSizer::~PageSizer()
206 {
207 }
211 /**
212 * Initialize or reset this widget
213 */
214 void
215 PageSizer::init (Registry& reg)
216 {
217 Gtk::HBox *hbox_ori = manage (new Gtk::HBox);
218 pack_start (*hbox_ori, false, false, 0);
219 Gtk::Label *label_ori = manage (new Gtk::Label (_("Page orientation:"), 0.0, 0.5));
220 hbox_ori->pack_start (*label_ori, false, false, 0);
222 _landscapeButton.set_label(_("_Landscape"));
223 _landscapeButton.set_active(true);
224 Gtk::RadioButton::Group group = _landscapeButton.get_group();
225 hbox_ori->pack_end (_landscapeButton, false, false, 5);
226 _portraitButton.set_label(_("_Portrait"));
227 _portraitButton.set_active(true);
228 hbox_ori->pack_end (_portraitButton, false, false, 5);
229 _portraitButton.set_group (group);
230 _portraitButton.set_active (true);
232 /* Custom paper frame */
233 Gtk::Frame *frame = manage (new Gtk::Frame(_("Custom size")));
234 pack_start (*frame, false, false, 0);
235 Gtk::Table *table = manage (new Gtk::Table (5, 2, false));
236 table->set_border_width (4);
237 table->set_row_spacings (4);
238 table->set_col_spacings (4);
240 Inkscape::UI::Widget::Button* fit_canv =
241 manage(new Inkscape::UI::Widget::Button(_("_Fit page to selection"),
242 _("Resize the page to fit the current selection, or the entire drawing if there is no selection")));
244 // prevent fit_canv from expanding
245 Gtk::Alignment *fit_canv_cont = manage(new Gtk::Alignment(1.0,0.5,0.0,0.0));
246 fit_canv_cont->add(*fit_canv);
248 frame->add (*table);
250 _widgetRegistry = ®
252 _dimensionUnits.init (_("U_nits:"), "units",
253 *_widgetRegistry);
254 _dimensionWidth.init (_("_Width:"), _("Width of paper"), "width",
255 _dimensionUnits, *_widgetRegistry);
256 _dimensionHeight.init (_("_Height:"), _("Height of paper"), "height",
257 _dimensionUnits, *_widgetRegistry);
259 table->attach (*_dimensionUnits._label, 0,1,0,1, Gtk::FILL|Gtk::EXPAND,
260 (Gtk::AttachOptions)0,0,0);
261 table->attach (*_dimensionUnits._sel, 1,2,0,1, Gtk::FILL|Gtk::EXPAND,
262 (Gtk::AttachOptions)0,0,0);
263 table->attach (*_dimensionWidth.getSU(), 0,2,1,2, Gtk::FILL|Gtk::EXPAND,
264 (Gtk::AttachOptions)0,0,0);
265 table->attach (*_dimensionHeight.getSU(), 0,2,2,3, Gtk::FILL|Gtk::EXPAND,
266 (Gtk::AttachOptions)0,0,0);
267 table->attach (*fit_canv_cont, 0,2,3,4, Gtk::FILL|Gtk::EXPAND,
268 (Gtk::AttachOptions)0,0,0);
270 _paper_size_list_connection = _paperSizeList.signal_changed().connect (
271 sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
273 _landscape_connection = _landscapeButton.signal_toggled().connect (
274 sigc::mem_fun (*this, &PageSizer::on_landscape));
275 _portrait_connection = _portraitButton.signal_toggled().connect (
276 sigc::mem_fun (*this, &PageSizer::on_portrait));
277 _changedw_connection = _dimensionWidth.getSU()->signal_value_changed().connect (
278 sigc::mem_fun (*this, &PageSizer::on_value_changed));
279 _changedh_connection = _dimensionHeight.getSU()->signal_value_changed().connect (
280 sigc::mem_fun (*this, &PageSizer::on_value_changed));
281 fit_canv->signal_clicked().connect(
282 sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
284 show_all_children();
285 }
288 /**
289 * Set document dimensions (if not called by Doc prop's update()) and
290 * set the PageSizer's widgets and text entries accordingly. If
291 * 'chageList' is true, then adjust the paperSizeList to show the closest
292 * standard page size.
293 *
294 * \param w, h given in px
295 * \param changeList whether to modify the paper size list
296 */
297 void
298 PageSizer::setDim (double w, double h, bool changeList)
299 {
300 static bool _called = false;
301 if (_called)
302 return;
304 _called = true;
306 _paper_size_list_connection.block();
307 _landscape_connection.block();
308 _portrait_connection.block();
309 _changedw_connection.block();
310 _changedh_connection.block();
312 if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
313 SPDocument *doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
314 sp_document_set_width (doc, w, &_px_unit);
315 sp_document_set_height (doc, h, &_px_unit);
316 sp_document_done (doc, SP_VERB_NONE,
317 /* TODO: annotate */ "page-sizer.cpp:301");
318 }
320 _landscape = ( w > h );
321 _landscapeButton.set_active(_landscape ? true : false);
322 _portraitButton.set_active (_landscape ? false : true);
324 if (changeList)
325 _paperSizeList.set_active (find_paper_size (w, h));
327 Unit const& unit = _dimensionUnits._sel->getUnit();
328 _dimensionWidth.setValue (w / unit.factor);
329 _dimensionHeight.setValue (h / unit.factor);
331 _paper_size_list_connection.unblock();
332 _landscape_connection.unblock();
333 _portrait_connection.unblock();
334 _changedw_connection.unblock();
335 _changedh_connection.unblock();
337 _called = false;
338 }
341 /**
342 * Returns an index into paperSizeTable of a paper of the specified
343 * size (specified in px), or -1 if there's no such paper.
344 */
345 int
346 PageSizer::find_paper_size (double w, double h) const
347 {
348 double smaller = w;
349 double larger = h;
350 if ( h < w ) {
351 smaller = h; larger = w;
352 }
354 g_return_val_if_fail(smaller <= larger, -1);
356 int index = 0;
357 std::map<Glib::ustring, PaperSize>::const_iterator iter;
358 for (iter = paperSizeTable.begin() ; iter != paperSizeTable.end() ; iter++) {
359 PaperSize paper = iter->second;
360 SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
361 double smallX = sp_units_get_pixels(paper.smaller, i_unit);
362 double largeX = sp_units_get_pixels(paper.larger, i_unit);
364 g_return_val_if_fail(smallX <= largeX, -1);
366 if ((std::abs(smaller - smallX) <= 0.1) &&
367 (std::abs(larger - largeX) <= 0.1) )
368 return index;
370 index++;
371 }
372 return -1;
373 }
377 /**
378 * Tell the desktop to change the page size
379 */
380 void
381 PageSizer::fire_fit_canvas_to_selection_or_drawing()
382 {
383 SPDesktop *dt = SP_ACTIVE_DESKTOP;
384 if (!dt)
385 return;
386 Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
387 if (verb) {
388 SPAction *action = verb->get_action(dt);
389 if (action)
390 sp_action_perform(action, NULL);
391 }
392 }
396 /**
397 * Paper Size list callback for when a user changes the selection
398 */
399 void
400 PageSizer::on_paper_size_list_changed()
401 {
402 Glib::ustring name = _paperSizeList.get_active_text();
403 std::map<Glib::ustring, PaperSize>::const_iterator iter =
404 paperSizeTable.find(name);
405 if (iter == paperSizeTable.end()) {
406 g_warning("paper size '%s' not found in table", name.c_str());
407 return;
408 }
409 PaperSize paper = iter->second;
410 double w = paper.smaller;
411 double h = paper.larger;
412 SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
413 sp_convert_distance (&w, &src_unit, &_px_unit);
414 sp_convert_distance (&h, &src_unit, &_px_unit);
416 if (_landscape)
417 setDim (h, w, false);
418 else
419 setDim (w, h, false);
421 }
424 /**
425 * Portrait button callback
426 */
427 void
428 PageSizer::on_portrait()
429 {
430 if (!_portraitButton.get_active())
431 return;
432 double w = _dimensionWidth.getSU()->getValue ("px");
433 double h = _dimensionHeight.getSU()->getValue ("px");
434 if (h < w)
435 setDim (h, w);
436 }
439 /**
440 * Landscape button callback
441 */
442 void
443 PageSizer::on_landscape()
444 {
445 if (!_landscapeButton.get_active())
446 return;
447 double w = _dimensionWidth.getSU()->getValue ("px");
448 double h = _dimensionHeight.getSU()->getValue ("px");
449 if (w < h)
450 setDim (h, w);
451 }
453 /**
454 * Callback for the dimension widgets
455 */
456 void
457 PageSizer::on_value_changed()
458 {
459 if (_widgetRegistry->isUpdating()) return;
461 setDim (_dimensionWidth.getSU()->getValue("px"),
462 _dimensionHeight.getSU()->getValue("px"));
463 }
466 } // namespace Widget
467 } // namespace UI
468 } // namespace Inkscape
470 /*
471 Local Variables:
472 mode:c++
473 c-file-style:"stroustrup"
474 c-file-offsets:((innamespace . 0)(inline-open . 0))
475 indent-tabs-mode:nil
476 fill-column:99
477 End:
478 */
479 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :