ac78c5279d4248bda2df14b17d764aa7f32e3953
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(_paperSizeColumns);
194 _paperSizeList.set_model(_paperSizeListStore);
195 _paperSizeList.append_column(_("Name"),
196 _paperSizeColumns.nameColumn);
197 _paperSizeList.append_column(_("Description"),
198 _paperSizeColumns.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);
208 for (PaperSizeRec const *p = inkscape_papers; p->name; p++)
209 {
210 Glib::ustring name = p->name;
211 char formatBuf[80];
212 snprintf(formatBuf, 79, "%0.2f x %0.2f", p->smaller, p->larger);
213 Glib::ustring desc = formatBuf;
214 if (p->unit == SP_UNIT_IN)
215 desc.append(" in");
216 else if (p->unit == SP_UNIT_MM)
217 desc.append(" mm");
218 else if (p->unit == SP_UNIT_PX)
219 desc.append(" px");
220 PaperSize paper(name, p->smaller, p->larger, p->unit);
221 _paperSizeTable[name] = paper;
222 Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
223 row[_paperSizeColumns.nameColumn] = name;
224 row[_paperSizeColumns.descColumn] = desc;
225 }
226 //Gtk::TreeModel::Row row = _paperSizeListStore->children()[0];
227 //if (row)
228 // _paperSizeListSelection->select(row);
231 Gtk::HBox *hbox_size = manage (new Gtk::HBox (false, 4));
232 pack_start (*hbox_size, false, false, 0);
233 Gtk::Label *label_size = manage (new Gtk::Label (_("P_age size:"), 1.0, 0.5));
234 label_size->set_use_underline();
235 hbox_size->pack_start (*label_size, false, false, 0);
236 label_size->set_mnemonic_widget (_paperSizeList);
237 hbox_size->pack_start (_paperSizeListScroller, true, true, 0);
239 }
242 /**
243 * Destructor
244 */
245 PageSizer::~PageSizer()
246 {
247 }
251 /**
252 * Initialize or reset this widget
253 */
254 void
255 PageSizer::init (Registry& reg)
256 {
258 Gtk::HBox *hbox_ori = manage (new Gtk::HBox);
259 pack_start (*hbox_ori, false, false, 0);
260 Gtk::Label *label_ori = manage (new Gtk::Label (_("Page orientation:"), 0.0, 0.5));
261 hbox_ori->pack_start (*label_ori, false, false, 0);
263 _landscapeButton.set_label(_("_Landscape"));
264 _landscapeButton.set_active(true);
265 Gtk::RadioButton::Group group = _landscapeButton.get_group();
266 hbox_ori->pack_end (_landscapeButton, false, false, 5);
267 _portraitButton.set_label(_("_Portrait"));
268 _portraitButton.set_active(true);
269 hbox_ori->pack_end (_portraitButton, false, false, 5);
270 _portraitButton.set_group (group);
271 _portraitButton.set_active (true);
273 /* Custom paper frame */
274 Gtk::Frame *frame = manage (new Gtk::Frame(_("Custom size")));
275 pack_start (*frame, false, false, 0);
276 Gtk::Table *table = manage (new Gtk::Table (5, 2, false));
277 table->set_border_width (4);
278 table->set_row_spacings (4);
279 table->set_col_spacings (4);
281 Inkscape::UI::Widget::Button* fit_canv =
282 manage(new Inkscape::UI::Widget::Button(_("_Fit page to selection"),
283 _("Resize the page to fit the current selection, or the entire drawing if there is no selection")));
285 // prevent fit_canv from expanding
286 Gtk::Alignment *fit_canv_cont = manage(new Gtk::Alignment(1.0,0.5,0.0,0.0));
287 fit_canv_cont->add(*fit_canv);
289 frame->add (*table);
291 _widgetRegistry = ®
293 _dimensionUnits.init (_("U_nits:"), "units",
294 *_widgetRegistry);
295 _dimensionWidth.init (_("_Width:"), _("Width of paper"), "width",
296 _dimensionUnits, *_widgetRegistry);
297 _dimensionHeight.init (_("_Height:"), _("Height of paper"), "height",
298 _dimensionUnits, *_widgetRegistry);
300 table->attach (*_dimensionUnits._label, 0,1,0,1, Gtk::FILL|Gtk::EXPAND,
301 (Gtk::AttachOptions)0,0,0);
302 table->attach (*_dimensionUnits._sel, 1,2,0,1, Gtk::FILL|Gtk::EXPAND,
303 (Gtk::AttachOptions)0,0,0);
304 table->attach (*_dimensionWidth.getSU(), 0,2,1,2, Gtk::FILL|Gtk::EXPAND,
305 (Gtk::AttachOptions)0,0,0);
306 table->attach (*_dimensionHeight.getSU(), 0,2,2,3, Gtk::FILL|Gtk::EXPAND,
307 (Gtk::AttachOptions)0,0,0);
308 table->attach (*fit_canv_cont, 0,2,3,4, Gtk::FILL|Gtk::EXPAND,
309 (Gtk::AttachOptions)0,0,0);
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 fit_canv->signal_clicked().connect(
320 sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
322 show_all_children();
323 }
326 /**
327 * Set document dimensions (if not called by Doc prop's update()) and
328 * set the PageSizer's widgets and text entries accordingly. If
329 * 'chageList' is true, then adjust the paperSizeList to show the closest
330 * standard page size.
331 *
332 * \param w, h given in px
333 * \param changeList whether to modify the paper size list
334 */
335 void
336 PageSizer::setDim (double w, double h, bool changeList)
337 {
338 static bool _called = false;
339 if (_called)
340 return;
342 _called = true;
344 _paper_size_list_connection.block();
345 _landscape_connection.block();
346 _portrait_connection.block();
347 _changedw_connection.block();
348 _changedh_connection.block();
350 if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
351 SPDocument *doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
352 sp_document_set_width (doc, w, &_px_unit);
353 sp_document_set_height (doc, h, &_px_unit);
354 sp_document_done (doc, SP_VERB_NONE,
355 /* TODO: annotate */ "page-sizer.cpp:301");
356 }
358 _landscape = ( w > h );
359 _landscapeButton.set_active(_landscape ? true : false);
360 _portraitButton.set_active (_landscape ? false : true);
362 if (changeList)
363 {
364 int index = find_paper_size(w, h);
365 Gtk::TreeModel::Row row = _paperSizeListStore->children()[index];
366 if (row)
367 _paperSizeListSelection->select(row);
368 }
370 Unit const& unit = _dimensionUnits._sel->getUnit();
371 _dimensionWidth.setValue (w / unit.factor);
372 _dimensionHeight.setValue (h / unit.factor);
374 _paper_size_list_connection.unblock();
375 _landscape_connection.unblock();
376 _portrait_connection.unblock();
377 _changedw_connection.unblock();
378 _changedh_connection.unblock();
380 _called = false;
381 }
384 /**
385 * Returns an index into paperSizeTable of a paper of the specified
386 * size (specified in px), or -1 if there's no such paper.
387 */
388 int
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, -1);
399 int index = 0;
400 std::map<Glib::ustring, PaperSize>::const_iterator iter;
401 for (iter = _paperSizeTable.begin() ;
402 iter != _paperSizeTable.end() ; iter++) {
403 PaperSize paper = iter->second;
404 SPUnit const &i_unit = sp_unit_get_by_id(paper.unit);
405 double smallX = sp_units_get_pixels(paper.smaller, i_unit);
406 double largeX = sp_units_get_pixels(paper.larger, i_unit);
408 g_return_val_if_fail(smallX <= largeX, -1);
410 if ((std::abs(smaller - smallX) <= 0.1) &&
411 (std::abs(larger - largeX) <= 0.1) )
412 return index;
414 index++;
415 }
416 return -1;
417 }
421 /**
422 * Tell the desktop to change the page size
423 */
424 void
425 PageSizer::fire_fit_canvas_to_selection_or_drawing()
426 {
427 SPDesktop *dt = SP_ACTIVE_DESKTOP;
428 if (!dt)
429 return;
430 Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
431 if (verb) {
432 SPAction *action = verb->get_action(dt);
433 if (action)
434 sp_action_perform(action, NULL);
435 }
436 }
440 /**
441 * Paper Size list callback for when a user changes the selection
442 */
443 void
444 PageSizer::on_paper_size_list_changed()
445 {
446 //Glib::ustring name = _paperSizeList.get_active_text();
447 Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
448 if(!miter)
449 {
450 //error?
451 return;
452 }
453 Gtk::TreeModel::Row row = *miter;
454 Glib::ustring name = row[_paperSizeColumns.nameColumn];
455 std::map<Glib::ustring, PaperSize>::const_iterator piter =
456 _paperSizeTable.find(name);
457 if (piter == _paperSizeTable.end()) {
458 g_warning("paper size '%s' not found in table", name.c_str());
459 return;
460 }
461 PaperSize paper = piter->second;
462 double w = paper.smaller;
463 double h = paper.larger;
464 SPUnit const &src_unit = sp_unit_get_by_id (paper.unit);
465 sp_convert_distance (&w, &src_unit, &_px_unit);
466 sp_convert_distance (&h, &src_unit, &_px_unit);
468 if (_landscape)
469 setDim (h, w, false);
470 else
471 setDim (w, h, false);
473 }
476 /**
477 * Portrait button callback
478 */
479 void
480 PageSizer::on_portrait()
481 {
482 if (!_portraitButton.get_active())
483 return;
484 double w = _dimensionWidth.getSU()->getValue ("px");
485 double h = _dimensionHeight.getSU()->getValue ("px");
486 if (h < w)
487 setDim (h, w);
488 }
491 /**
492 * Landscape button callback
493 */
494 void
495 PageSizer::on_landscape()
496 {
497 if (!_landscapeButton.get_active())
498 return;
499 double w = _dimensionWidth.getSU()->getValue ("px");
500 double h = _dimensionHeight.getSU()->getValue ("px");
501 if (w < h)
502 setDim (h, w);
503 }
505 /**
506 * Callback for the dimension widgets
507 */
508 void
509 PageSizer::on_value_changed()
510 {
511 if (_widgetRegistry->isUpdating()) return;
513 setDim (_dimensionWidth.getSU()->getValue("px"),
514 _dimensionHeight.getSU()->getValue("px"));
515 }
518 } // namespace Widget
519 } // namespace UI
520 } // namespace Inkscape
522 /*
523 Local Variables:
524 mode:c++
525 c-file-style:"stroustrup"
526 c-file-offsets:((innamespace . 0)(inline-open . 0))
527 indent-tabs-mode:nil
528 fill-column:99
529 End:
530 */
531 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :