25#include <glibmm/i18n.h>
26#include <giomm/menu.h>
27#include <giomm/simpleaction.h>
28#include <giomm/simpleactiongroup.h>
29#include <gtkmm/builder.h>
30#include <gtkmm/button.h>
31#include <gtkmm/checkbutton.h>
32#include <gtkmm/comboboxtext.h>
33#include <gtkmm/expander.h>
34#include <gtkmm/grid.h>
35#include <gtkmm/label.h>
36#include <gtkmm/menubutton.h>
37#include <gtkmm/popovermenu.h>
38#include <gtkmm/checkbutton.h>
39#include <gtkmm/spinbutton.h>
40#include <gtkmm/togglebutton.h>
57const char*
g_linked =
"entries-linked-symbolic";
67 std::string
const &abbr =
page.unit->abbr, &
name =
page.name;
70 if (
static auto const us = _(
"US"); abbr ==
"in" &&
name.find(us) !=
name.npos) {
74 if (abbr ==
"mm" &&
name.size() >= 2 &&
name[0] >=
'A' &&
name[0] <=
'E' &&
78 static auto const format = _(
"ISO %1");
79 return {1, Glib::ustring::compose(format,
name[0]), abbr};
83 static auto const others = _(
"Others");
84 return {2, others, abbr};
87class PagePropertiesBox final :
public PageProperties {
88 void create_template_menu()
90 static auto const group_name =
"page-properties",
action_name =
"template";
91 static auto const get_detailed_action = [](
int const index)
92 {
return Glib::ustring::compose(
"%1.%2(%3)", group_name,
action_name,
index); };
95 std::stable_sort(_page_sizes.begin(), _page_sizes.end(), [](
auto const &l,
auto const &r)
96 { return get_sorter(l) < get_sorter(r); });
98 auto group = Gio::SimpleActionGroup::create();
99 _template_action = group->add_action_radio_integer(
action_name, 0);
100 _template_action->property_state().signal_changed().connect([
this] {
101 _templates_menu_button.set_active(
false);
103 _template_action->get_state(
index);
104 set_page_template(
index);
106 insert_action_group(group_name, std::move(group));
108 Glib::RefPtr<Gio::Menu> menu = Gio::Menu::create(), submenu = {};
109 Glib::ustring prev_submenu_label;
110 for (std::size_t i = 0; i < _page_sizes.size(); ++i) {
111 auto const &
page = _page_sizes[i];
113 prev_submenu_label !=
label)
115 submenu = Gio::Menu::create();
116 menu->append_submenu(
label, submenu);
117 prev_submenu_label =
label;
119 submenu->append(
page.getDescription(
false), get_detailed_action(i));
121 menu->append(_(
"Custom"), get_detailed_action(_page_sizes.size()));
122 _templates_popover.set_menu_model(std::move(menu));
123 _templates_popover.set_flags(Gtk::PopoverMenu::Flags::NESTED);
134 , _portrait (
get_widget<
Gtk::CheckButton> (_builder,
"page-portrait"))
135 , _landscape (
get_widget<
Gtk::CheckButton> (_builder,
"page-landscape"))
136 , _y_axis_up (
get_widget<
Gtk::CheckButton> (_builder,
"y-axis-up"))
137 , _y_axis_down (
get_widget<
Gtk::CheckButton> (_builder,
"y-axis-down"))
138 , _origin_page (
get_widget<
Gtk::CheckButton> (_builder,
"origin-page"))
140 , _link_scale_content (
get_widget<
Gtk::Button> (_builder,
"link-scale-content"))
141 , _unsupported_size (
get_widget<
Gtk::Label> (_builder,
"unsupported"))
142 , _nonuniform_scale (
get_widget<
Gtk::Label> (_builder,
"nonuniform-scale"))
143 , _doc_units (
get_widget<
Gtk::Label> (_builder,
"user-units"))
148 , _templates_menu_button(
get_widget<
Gtk::MenuButton> (_builder,
"page-menu-btn"))
149 , _templates_popover (
get_widget<
Gtk::PopoverMenu> (_builder,
"templates-popover"))
150 , _template_name (
get_widget<
Gtk::Label> (_builder,
"page-template-name"))
151 , _preview_box (
get_widget<
Gtk::Box> (_builder,
"preview-box"))
152 , _checkerboard (
get_widget<
Gtk::CheckButton> (_builder,
"checkerboard"))
153 , _antialias (
get_widget<
Gtk::CheckButton> (_builder,
"use-antialias"))
154 , _clip_to_page (
get_widget<
Gtk::CheckButton> (_builder,
"clip-to-page"))
155 , _page_label_style (
get_widget<
Gtk::CheckButton> (_builder,
"page-label-style"))
156 , _border (
get_widget<
Gtk::CheckButton> (_builder,
"border"))
157 , _border_on_top (
get_widget<
Gtk::CheckButton> (_builder,
"border-top"))
158 , _shadow (
get_widget<
Gtk::CheckButton> (_builder,
"shadow"))
159 , _link_width_height (
get_widget<
Gtk::Button> (_builder,
"link-width-height"))
160 , _viewbox_expander (
get_widget<
Gtk::Expander> (_builder,
"viewbox-expander"))
161 , _linked_viewbox_scale (
get_widget<
Gtk::Image> (_builder,
"linked-scale-img"))
164 , _backgnd_color_picker (
get_derived_widget<ColorPicker> (_builder,
"background-color", _(
"Background color"), false))
165 , _border_color_picker (
get_derived_widget<ColorPicker> (_builder,
"border-color", _(
"Border and shadow color"), true))
166 , _desk_color_picker (
get_derived_widget<ColorPicker> (_builder,
"desk-color", _(
"Desk color"), false))
169 for (
auto element : {Color::Background, Color::Border, Color::Desk}) {
170 get_color_picker(element).connectChanged([element,
this](Colors::Color
const &color) {
171 update_preview_color(element, color);
172 if (_update.pending())
return;
177 _display_units.setUnitType(UNIT_TYPE_LINEAR,
false);
181 _page_units.setUnitType(UNIT_TYPE_LINEAR,
true);
182 _current_page_unit = _page_units.getUnit();
183 _page_units.signal_changed().connect([
this](){ set_page_unit(); });
185 create_template_menu();
187 _preview->set_expand(
true);
188 _preview_box.append(*_preview);
191 auto checkbutton = &get_checkbutton(check);
192 checkbutton->signal_toggled().connect([=,
this](){ fire_checkbox_toggled(*checkbutton, check); });
194 _border.signal_toggled().connect([
this](){
195 _preview->draw_border(_border.get_active());
197 _shadow.signal_toggled().connect([
this](){
198 _preview->enable_drop_shadow(_shadow.get_active());
200 _checkerboard.signal_toggled().connect([
this](){
201 _preview->enable_checkerboard(_checkerboard.get_active());
204 _viewbox_expander.property_expanded().signal_changed().connect([
this](){
206 show_viewbox(_viewbox_expander.get_expanded());
208 show_viewbox(_viewbox_expander.get_expanded());
210 _link_width_height.signal_clicked().connect([
this](){
212 _locked_size_ratio = !_locked_size_ratio;
216 _link_width_height.set_image_from_icon_name(
g_unlinked, Gtk::IconSize::NORMAL);
218 _link_scale_content.signal_clicked().connect([
this](){
219 _locked_content_scale = !_locked_content_scale;
222 _link_scale_content.set_image_from_icon_name(
s_unlinked, Gtk::IconSize::NORMAL);
225 _linked_viewbox_scale.set_from_icon_name(
s_linked);
228 _page_width .signal_value_changed().connect([
this](){ set_page_size_linked(
true); });
229 _page_height.signal_value_changed().connect([
this](){ set_page_size_linked(
false); });
231 _viewbox_width. signal_value_changed().connect([
this](){ set_viewbox_size_linked(
true); });
232 _viewbox_height.signal_value_changed().connect([
this](){ set_viewbox_size_linked(
false); });
234 _landscape.signal_toggled().connect([
this](){
if (_landscape.get_active()) swap_width_height(); });
235 _portrait .signal_toggled().connect([
this](){
if (_portrait .get_active()) swap_width_height(); });
238 auto pair = get_dimension(dim);
239 auto b1 = &pair.first;
240 auto b2 = &pair.second;
243 b1->signal_value_changed().connect([=,
this](){
249 b1->signal_value_changed().connect([=,
this](){ fire_value_changed(*b1, *b2,
nullptr, dim); });
250 b2->signal_value_changed().connect([=,
this](){ fire_value_changed(*b1, *b2,
nullptr, dim); });
254 auto& page_resize = get_widget<Gtk::Button>(_builder,
"page-resize");
262 void show_viewbox(
bool show_widgets) {
263 auto const show = [=](Gtk::Widget *
const w){
w->set_visible(show_widgets); };
265 for (
auto const widget : UI::
get_children(_left_grid)) {
266 if (widget->has_css_class(
"viewbox")) {
272 void update_preview_color(
Color const element, Colors::Color
const &color) {
274 case Color::Desk: _preview->set_desk_color(color.toRGBA());
break;
275 case Color::Border: _preview->set_border_color(color.toRGBA());
break;
276 case Color::Background: _preview->set_page_color(color.toRGBA());
break;
280 void set_page_template(std::int32_t
const index) {
281 if (_update.pending())
return;
283 g_assert(
index >= 0 &&
index <= _page_sizes.size());
285 if (
index != _page_sizes.size()) {
286 auto scoped(_update.block());
287 auto const &
page = _page_sizes.at(
index);
293 _page_width.set_value(
width);
294 _page_height.set_value(
height);
295 _page_units.setUnit(
page.unit->abbr);
296 _doc_units.set_text(
page.unit->abbr);
297 _current_page_unit = _page_units.getUnit();
306 void changed_linked_value(
bool width_changing, Gtk::SpinButton& wedit, Gtk::SpinButton& hedit) {
307 if (_size_ratio > 0) {
308 auto scoped(_update.block());
309 if (width_changing) {
310 auto width = wedit.get_value();
311 hedit.set_value(
width / _size_ratio);
314 auto height = hedit.get_value();
315 wedit.set_value(
height * _size_ratio);
320 void set_viewbox_size_linked(
bool width_changing) {
321 if (_update.pending())
return;
323 if (_scale_is_uniform) {
325 changed_linked_value(width_changing, _viewbox_width, _viewbox_height);
328 auto width = _viewbox_width.get_value();
329 auto height = _viewbox_height.get_value();
333 void set_page_size_linked(
bool width_changing) {
334 if (_update.pending())
return;
337 if (_locked_size_ratio) {
338 changed_linked_value(width_changing, _page_width, _page_height);
343 void set_page_size(
bool template_selected =
false) {
344 auto pending = _update.pending();
346 auto scoped(_update.block());
348 auto unit = _page_units.getUnit();
349 auto width = _page_width.get_value();
350 auto height = _page_height.get_value();
353 (
width >
height ? _landscape : _portrait).set_active();
354 _portrait.set_sensitive();
355 _landscape.set_sensitive();
357 _portrait.set_sensitive(
false);
358 _landscape.set_sensitive(
false);
364 auto templ = find_page_template(
width,
height, *unit);
365 auto const index = std::distance(_page_sizes.cbegin(), templ);
366 _template_action->set_state(Glib::Variant<std::int32_t>::create(
index));
368 Glib::ustring
const label = templ != _page_sizes.cend() && !templ->name.empty()
369 ? _(templ->name.c_str()) : _(
"Custom");
370 _template_name.set_label(
label);
371 _templates_menu_button.set_tooltip_text(
label);
379 void swap_width_height() {
380 if (_update.pending())
return;
383 auto scoped(_update.block());
384 auto width = _page_width.get_value();
385 auto height = _page_height.get_value();
386 _page_width.set_value(
height);
387 _page_height.set_value(
width);
393 if (_update.pending())
return;
395 const auto unit = _display_units.getUnit();
399 void set_page_unit() {
400 if (_update.pending())
return;
402 const auto old_unit = _current_page_unit;
403 _current_page_unit = _page_units.getUnit();
404 const auto new_unit = _current_page_unit;
407 auto width = _page_width.get_value();
408 auto height = _page_height.get_value();
409 Quantity
w(
width, old_unit->abbr);
410 Quantity h(
height, old_unit->abbr);
411 auto scoped(_update.block());
412 _page_width.set_value(
w.value(new_unit));
413 _page_height.set_value(h.value(new_unit));
415 _doc_units.set_text(new_unit->abbr);
420 void set_color(
Color element, Colors::Color
const &color)
override {
421 auto scoped(_update.block());
423 get_color_picker(element).setColor(color);
424 update_preview_color(element, color);
427 void set_check(
Check element,
bool checked)
override {
428 auto scoped(_update.block());
431 _nonuniform_scale.set_visible(checked);
432 _scale_is_uniform = !checked;
433 _scale_x.set_sensitive(_scale_is_uniform);
437 _scale_x.set_sensitive(!checked);
440 _unsupported_size.set_visible(checked);
443 get_checkbutton(element).set_active(checked);
447 if (element ==
Check::Shadow) _preview->enable_drop_shadow(checked);
448 if (element ==
Check::Border) _preview->draw_border(checked);
453 void set_dimension(
Dimension dimension,
double x,
double y)
override {
454 auto scoped(_update.block());
456 auto dim = get_dimension(dimension);
457 dim.first.set_value(x);
458 dim.second.set_value(y);
463 void set_unit(
Units unit,
const Glib::ustring& abbr)
override {
464 auto scoped(_update.block());
467 _display_units.setUnit(abbr);
470 _doc_units.set_text(abbr);
471 _page_units.setUnit(abbr);
472 _current_page_unit = _page_units.getUnit();
477 ColorPicker& get_color_picker(
Color element) {
479 case Color::Background:
return _backgnd_color_picker;
480 case Color::Desk:
return _desk_color_picker;
481 case Color::Border:
return _border_color_picker;
484 throw std::runtime_error(
"missing case in get_color_picker");
488 void fire_value_changed(Gtk::SpinButton& b1, Gtk::SpinButton& b2,
const Util::Unit* unit,
Dimension dim) {
489 if (!_update.pending()) {
494 void fire_checkbox_toggled(Gtk::CheckButton& checkbox,
Check check) {
495 if (!_update.pending()) {
500 std::vector<PaperSize>::const_iterator
501 find_page_template(
double const width,
double const height,
Unit const &unit)
506 static constexpr double eps = 1e-6;
507 return std::find_if(_page_sizes.cbegin(), _page_sizes.cend(), [&](
auto const &
page)
509 Quantity pw(std::min(page.width, page.height), page.unit);
510 Quantity ph(std::max(page.width, page.height), page.unit);
511 if (are_near(w, pw, eps) && are_near(h, ph, eps)) {
518 Gtk::CheckButton& get_checkbutton(Check check) {
520 case Check::AntiAlias:
return _antialias;
521 case Check::Border:
return _border;
522 case Check::Shadow:
return _shadow;
523 case Check::BorderOnTop:
return _border_on_top;
524 case Check::Checkerboard:
return _checkerboard;
525 case Check::ClipToPage:
return _clip_to_page;
526 case Check::PageLabelStyle:
return _page_label_style;
527 case Check::YAxisPointsDown:
return _y_axis_down;
528 case Check::OriginCurrentPage:
return _origin_page;
531 throw std::runtime_error(
"missing case in get_checkbutton");
535 typedef std::pair<Gtk::SpinButton&, Gtk::SpinButton&> spin_pair;
536 spin_pair get_dimension(Dimension dimension) {
538 case Dimension::PageSize:
return spin_pair(_page_width, _page_height);
539 case Dimension::PageTemplate:
return spin_pair(_page_width, _page_height);
540 case Dimension::Scale:
541 case Dimension::ScaleContent:
return spin_pair(_scale_x, _scale_x);
542 case Dimension::ViewboxPosition:
return spin_pair(_viewbox_x, _viewbox_y);
543 case Dimension::ViewboxSize:
return spin_pair(_viewbox_width, _viewbox_height);
546 throw std::runtime_error(
"missing case in get_dimension");
550 Glib::RefPtr<Gtk::Builder> _builder;
551 Gtk::Grid &_main_grid;
552 Gtk::Grid &_left_grid;
553 MathSpinButton &_page_width;
554 MathSpinButton &_page_height;
555 Gtk::CheckButton &_portrait;
556 Gtk::CheckButton &_landscape;
557 Gtk::CheckButton& _y_axis_up;
558 Gtk::CheckButton& _y_axis_down;
559 Gtk::CheckButton& _origin_page;
560 MathSpinButton &_scale_x;
561 Gtk::Button &_link_scale_content;
562 Gtk::Label &_unsupported_size;
563 Gtk::Label &_nonuniform_scale;
564 Gtk::Label &_doc_units;
565 MathSpinButton &_viewbox_x;
566 MathSpinButton &_viewbox_y;
567 MathSpinButton &_viewbox_width;
568 MathSpinButton &_viewbox_height;
569 ColorPicker& _backgnd_color_picker;
570 ColorPicker& _border_color_picker;
571 ColorPicker& _desk_color_picker;
572 std::vector<PaperSize> _page_sizes;
573 Glib::RefPtr<Gio::SimpleAction> _template_action;
574 Gtk::MenuButton &_templates_menu_button;
575 Gtk::PopoverMenu &_templates_popover;
576 Gtk::Label &_template_name;
577 Gtk::Box &_preview_box;
578 std::unique_ptr<PageSizePreview> _preview = std::make_unique<PageSizePreview>();
579 Gtk::CheckButton &_border;
580 Gtk::CheckButton &_border_on_top;
581 Gtk::CheckButton &_shadow;
582 Gtk::CheckButton &_checkerboard;
583 Gtk::CheckButton &_antialias;
584 Gtk::CheckButton &_clip_to_page;
585 Gtk::CheckButton &_page_label_style;
586 Gtk::Button &_link_width_height;
587 Gtk::Expander &_viewbox_expander;
588 Gtk::Image &_linked_viewbox_scale;
590 UnitMenu &_display_units;
591 UnitMenu &_page_units;
592 const Unit *_current_page_unit =
nullptr;
594 double _size_ratio = 1;
595 bool _locked_size_ratio =
false;
596 bool _scale_is_uniform =
true;
597 bool _locked_content_scale =
false;
601 return new PagePropertiesBox();
void set_display_unit(Glib::ustring abbr, SPDocument *document)
Data class used to store common paper dimensions from pages.csv.
static const std::vector< PaperSize > & getPageSizes()
Returns a list of page sizes.
sigc::signal< void(const Util::Unit *, Units)> _signal_unit_changed
sigc::signal< void(bool, Check)> _signal_check_toggled
sigc::signal< void(double, double, const Util::Unit *, Dimension)> _signal_dimension_changed
sigc::signal< void()> _signal_resize_to_fit
sigc::signal< void(Colors::Color const &, Color)> _signal_color_changed
Color picker button and window.
void set_color(SPDesktop *desktop, Glib::ustring operation, SPAttr color_key, SPAttr opacity_key, Colors::Color const &color)
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
static void append(std::vector< T > &target, std::vector< T > &&source)