// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * * Document properties widget: viewbox, document size, colors */ /* * Authors: * Mike Kowalski * * Copyright (C) 2021 Authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "page-properties.h" #include "page-size-preview.h" #include "ui/widget/spinbutton.h" #include "util/paper.h" #include "ui/widget/registry.h" #include "ui/widget/color-picker.h" #include "ui/widget/unit-menu.h" #include "ui/builder-utils.h" #include "ui/operation-blocker.h" using Inkscape::UI::create_builder; using Inkscape::UI::get_widget; namespace Inkscape { namespace UI { namespace Widget { void show_widget(Gtk::Widget& widget, bool show) { if (show) { widget.show(); } else { widget.hide(); } }; const char* g_linked = "entries-linked-symbolic"; const char* g_unlinked = "entries-unlinked-symbolic"; #define GET(prop, id) prop(get_widget>(_builder, id)) #define GETD(prop, id) prop(get_derived_widget>(_builder, id)) class PagePropertiesBox : public PageProperties { public: PagePropertiesBox() : _builder(create_builder("page-properties.glade")), GET(_main_grid, "main-grid"), GET(_left_grid, "left-grid"), GETD(_page_width, "page-width"), GETD(_page_height, "page-height"), GET(_portrait, "page-portrait"), GET(_landscape, "page-landscape"), GETD(_scale_x, "scale-x"), GET(_doc_units, "user-units"), GET(_unsupported_size, "unsupported"), GET(_nonuniform_scale, "nonuniform-scale"), GETD(_viewbox_x, "viewbox-x"), GETD(_viewbox_y, "viewbox-y"), GETD(_viewbox_width, "viewbox-width"), GETD(_viewbox_height, "viewbox-height"), GET(_page_templates_menu, "page-templates-menu"), GET(_template_name, "page-template-name"), GET(_preview_box, "preview-box"), GET(_checkerboard, "checkerboard"), GET(_antialias, "use-antialias"), GET(_clip_to_page, "clip-to-page"), GET(_page_label_style, "page-label-style"), GET(_border, "border"), GET(_border_on_top, "border-top"), GET(_shadow, "shadow"), GET(_link_width_height, "link-width-height"), GET(_viewbox_expander, "viewbox-expander"), GET(_linked_viewbox_scale, "linked-scale-img") { #undef GET #undef GETD _backgnd_color_picker = std::make_unique( _("Background color"), "", 0xffffff00, true, &get_widget(_builder, "background-color")); _backgnd_color_picker->use_transparency(false); _border_color_picker = std::make_unique( _("Border and shadow color"), "", 0x0000001f, true, &get_widget(_builder, "border-color")); _desk_color_picker = std::make_unique( _("Desk color"), "", 0xd0d0d0ff, true, &get_widget(_builder, "desk-color")); _desk_color_picker->use_transparency(false); for (auto element : {Color::Background, Color::Border, Color::Desk}) { get_color_picker(element).connectChanged([=](guint rgba) { update_preview_color(element, rgba); if (_update.pending()) return; _signal_color_changed.emit(rgba, element); }); } _builder->get_widget_derived("display-units", _display_units); _display_units->setUnitType(UNIT_TYPE_LINEAR); _display_units->signal_changed().connect([=](){ set_display_unit(); }); _builder->get_widget_derived("page-units", _page_units); _page_units->setUnitType(UNIT_TYPE_LINEAR); _current_page_unit = _page_units->getUnit(); _page_units->signal_changed().connect([=](){ set_page_unit(); }); for (auto&& page : PaperSize::getPageSizes()) { auto item = Gtk::manage(new Gtk::MenuItem(page.getDescription(false))); item->show(); _page_templates_menu.append(*item); item->signal_activate().connect([=](){ set_page_template(page); }); } _preview->set_hexpand(); _preview->set_vexpand(); _preview_box.add(*_preview); for (auto check : {Check::Border, Check::Shadow, Check::Checkerboard, Check::BorderOnTop, Check::AntiAlias, Check::ClipToPage, Check::PageLabelStyle}) { auto checkbutton = &get_checkbutton(check); checkbutton->signal_toggled().connect([=](){ fire_checkbox_toggled(*checkbutton, check); }); } _border.signal_toggled().connect([=](){ _preview->draw_border(_border.get_active()); }); _shadow.signal_toggled().connect([=](){ // _preview->enable_drop_shadow(_shadow.get_active()); }); _checkerboard.signal_toggled().connect([=](){ _preview->enable_checkerboard(_checkerboard.get_active()); }); _viewbox_expander.property_expanded().signal_changed().connect([=](){ // hide/show viewbox controls show_viewbox(_viewbox_expander.get_expanded()); }); show_viewbox(_viewbox_expander.get_expanded()); _link_width_height.signal_clicked().connect([=](){ // toggle size link _locked_size_ratio = !_locked_size_ratio; // set image _link_width_height.set_image_from_icon_name(_locked_size_ratio && _size_ratio > 0 ? g_linked : g_unlinked, Gtk::ICON_SIZE_LARGE_TOOLBAR); }); _link_width_height.set_image_from_icon_name(g_unlinked, Gtk::ICON_SIZE_LARGE_TOOLBAR); // set image for linked scale _linked_viewbox_scale.set_from_icon_name(g_linked, Gtk::ICON_SIZE_LARGE_TOOLBAR); // report page size changes _page_width .signal_value_changed().connect([=](){ set_page_size_linked(true); }); _page_height.signal_value_changed().connect([=](){ set_page_size_linked(false); }); // enforce uniform scale thru viewbox _viewbox_width. signal_value_changed().connect([=](){ set_viewbox_size_linked(true); }); _viewbox_height.signal_value_changed().connect([=](){ set_viewbox_size_linked(false); }); _landscape.signal_toggled().connect([=](){ if (_landscape.get_active()) swap_width_height(); }); _portrait .signal_toggled().connect([=](){ if (_portrait .get_active()) swap_width_height(); }); for (auto dim : {Dimension::Scale, Dimension::ViewboxPosition}) { auto pair = get_dimension(dim); auto b1 = &pair.first; auto b2 = &pair.second; if (dim == Dimension::Scale) { // uniform scale: report the same x and y b1->signal_value_changed().connect([=](){ fire_value_changed(*b1, *b1, nullptr, dim); }); } else { b1->signal_value_changed().connect([=](){ fire_value_changed(*b1, *b2, nullptr, dim); }); b2->signal_value_changed().connect([=](){ fire_value_changed(*b1, *b2, nullptr, dim); }); } } auto& page_resize = get_widget(_builder, "page-resize"); page_resize.signal_clicked().connect([=](){ _signal_resize_to_fit.emit(); }); add(_main_grid); show(); } private: void show_viewbox(bool show_widgets) { auto show = [=](Gtk::Widget* w) { show_widget(*w, show_widgets); }; for (auto&& widget : _left_grid.get_children()) { if (widget->get_style_context()->has_class("viewbox")) { show(widget); } } } void update_preview_color(Color element, guint rgba) { switch (element) { case Color::Desk: _preview->set_desk_color(rgba); break; case Color::Border: _preview->set_border_color(rgba); break; case Color::Background: _preview->set_page_color(rgba); break; } } void set_page_template(const PaperSize& page) { if (_update.pending()) return; { auto scoped(_update.block()); auto width = page.width; auto height = page.height; if (_landscape.get_active() != (width > height)) { std::swap(width, height); } _page_width.set_value(width); _page_height.set_value(height); _page_units->setUnit(page.unit->abbr); _doc_units.set_text(page.unit->abbr); _current_page_unit = _page_units->getUnit(); if (width > 0 && height > 0) { _size_ratio = width / height; } } set_page_size(true); } void changed_linked_value(bool width_changing, Gtk::SpinButton& wedit, Gtk::SpinButton& hedit) { if (_size_ratio > 0) { auto scoped(_update.block()); if (width_changing) { auto width = wedit.get_value(); hedit.set_value(width / _size_ratio); } else { auto height = hedit.get_value(); wedit.set_value(height * _size_ratio); } } } void set_viewbox_size_linked(bool width_changing) { if (_update.pending()) return; if (_scale_is_uniform) { // viewbox size - width and height always linked to make scaling uniform changed_linked_value(width_changing, _viewbox_width, _viewbox_height); } auto width = _viewbox_width.get_value(); auto height = _viewbox_height.get_value(); _signal_dimmension_changed.emit(width, height, nullptr, Dimension::ViewboxSize); } void set_page_size_linked(bool width_changing) { if (_update.pending()) return; // if size ratio is locked change the other dimension too if (_locked_size_ratio) { changed_linked_value(width_changing, _page_width, _page_height); } set_page_size(); } void set_page_size(bool template_selected = false) { auto pending = _update.pending(); auto scoped(_update.block()); auto unit = _page_units->getUnit(); auto width = _page_width.get_value(); auto height = _page_height.get_value(); _preview->set_page_size(width, height); if (width != height) { (width > height ? _landscape : _portrait).set_active(); _portrait.set_sensitive(); _landscape.set_sensitive(); } else { _portrait.set_sensitive(false); _landscape.set_sensitive(false); } if (width > 0 && height > 0) { _size_ratio = width / height; } auto templ = find_page_template(width, height, *unit); _template_name.set_label(templ && !templ->name.empty() ? _(templ->name.c_str()) : _("Custom")); if (!pending) { _signal_dimmension_changed.emit(width, height, unit, template_selected ? Dimension::PageTemplate : Dimension::PageSize); } } void swap_width_height() { if (_update.pending()) return; { auto scoped(_update.block()); auto width = _page_width.get_value(); auto height = _page_height.get_value(); _page_width.set_value(height); _page_height.set_value(width); } set_page_size(); }; void set_display_unit() { if (_update.pending()) return; const auto unit = _display_units->getUnit(); _signal_unit_changed.emit(unit, Units::Display); } void set_page_unit() { if (_update.pending()) return; const auto old_unit = _current_page_unit; _current_page_unit = _page_units->getUnit(); const auto new_unit = _current_page_unit; { auto width = _page_width.get_value(); auto height = _page_height.get_value(); Quantity w(width, old_unit->abbr); Quantity h(height, old_unit->abbr); auto scoped(_update.block()); _page_width.set_value(w.value(new_unit)); _page_height.set_value(h.value(new_unit)); } _doc_units.set_text(new_unit->abbr); set_page_size(); _signal_unit_changed.emit(new_unit, Units::Document); } void set_color(Color element, unsigned int color) override { auto scoped(_update.block()); get_color_picker(element).setRgba32(color); update_preview_color(element, color); } void set_check(Check element, bool checked) override { auto scoped(_update.block()); if (element == Check::NonuniformScale) { show_widget(_nonuniform_scale, checked); _scale_is_uniform = !checked; _scale_x.set_sensitive(_scale_is_uniform); _linked_viewbox_scale.set_from_icon_name(_scale_is_uniform ? g_linked : g_unlinked, Gtk::ICON_SIZE_LARGE_TOOLBAR); } else if (element == Check::DisabledScale) { _scale_x.set_sensitive(!checked); } else if (element == Check::UnsupportedSize) { show_widget(_unsupported_size, checked); } else { get_checkbutton(element).set_active(checked); // special cases if (element == Check::Checkerboard) _preview->enable_checkerboard(checked); if (element == Check::Shadow) _preview->enable_drop_shadow(checked); if (element == Check::Border) _preview->draw_border(checked); } } void set_dimension(Dimension dimension, double x, double y) override { auto scoped(_update.block()); auto dim = get_dimension(dimension); dim.first.set_value(x); dim.second.set_value(y); set_page_size(); } void set_unit(Units unit, const Glib::ustring& abbr) override { auto scoped(_update.block()); if (unit == Units::Display) { _display_units->setUnit(abbr); } else if (unit == Units::Document) { _doc_units.set_text(abbr); _page_units->setUnit(abbr); _current_page_unit = _page_units->getUnit(); set_page_size(); } } ColorPicker& get_color_picker(Color element) { switch (element) { case Color::Background: return *_backgnd_color_picker; case Color::Desk: return *_desk_color_picker; case Color::Border: return *_border_color_picker; default: throw std::runtime_error("missing case in get_color_picker"); } } void fire_value_changed(Gtk::SpinButton& b1, Gtk::SpinButton& b2, const Util::Unit* unit, Dimension dim) { if (!_update.pending()) { _signal_dimmension_changed.emit(b1.get_value(), b2.get_value(), unit, dim); } } void fire_checkbox_toggled(Gtk::CheckButton& checkbox, Check check) { if (!_update.pending()) { _signal_check_toggled.emit(checkbox.get_active(), check); } } const PaperSize* find_page_template(double width, double height, const Unit& unit) { Quantity w(std::min(width, height), &unit); Quantity h(std::max(width, height), &unit); const double eps = 1e-6; for (auto&& page : PaperSize::getPageSizes()) { Quantity pw(std::min(page.width, page.height), page.unit); Quantity ph(std::max(page.width, page.height), page.unit); if (are_near(w, pw, eps) && are_near(h, ph, eps)) { return &page; } } return nullptr; } Gtk::CheckButton& get_checkbutton(Check check) { switch (check) { case Check::AntiAlias: return _antialias; case Check::Border: return _border; case Check::Shadow: return _shadow; case Check::BorderOnTop: return _border_on_top; case Check::Checkerboard: return _checkerboard; case Check::ClipToPage: return _clip_to_page; case Check::PageLabelStyle: return _page_label_style; default: throw std::runtime_error("missing case in get_checkbutton"); } } typedef std::pair spin_pair; spin_pair get_dimension(Dimension dimension) { switch (dimension) { case Dimension::PageSize: return spin_pair(_page_width, _page_height); case Dimension::PageTemplate: return spin_pair(_page_width, _page_height); case Dimension::Scale: return spin_pair(_scale_x, _scale_x); case Dimension::ViewboxPosition: return spin_pair(_viewbox_x, _viewbox_y); case Dimension::ViewboxSize: return spin_pair(_viewbox_width, _viewbox_height); default: throw std::runtime_error("missing case in get_dimension"); } } Glib::RefPtr _builder; Gtk::Grid& _main_grid; Gtk::Grid& _left_grid; MathSpinButton& _page_width; MathSpinButton& _page_height; Gtk::RadioButton& _portrait; Gtk::RadioButton& _landscape; MathSpinButton& _scale_x; Gtk::Label& _unsupported_size; Gtk::Label& _nonuniform_scale; Gtk::Label& _doc_units; MathSpinButton& _viewbox_x; MathSpinButton& _viewbox_y; MathSpinButton& _viewbox_width; MathSpinButton& _viewbox_height; std::unique_ptr _backgnd_color_picker; std::unique_ptr _border_color_picker; std::unique_ptr _desk_color_picker; Gtk::Menu& _page_templates_menu; Gtk::Label& _template_name; Gtk::Box& _preview_box; std::unique_ptr _preview = std::make_unique(); Gtk::CheckButton& _border; Gtk::CheckButton& _border_on_top; Gtk::CheckButton& _shadow; Gtk::CheckButton& _checkerboard; Gtk::CheckButton& _antialias; Gtk::CheckButton& _clip_to_page; Gtk::CheckButton& _page_label_style; Gtk::Button& _link_width_height; UnitMenu *_display_units; UnitMenu *_page_units; const Unit* _current_page_unit = nullptr; OperationBlocker _update; double _size_ratio = 1; // width to height ratio bool _locked_size_ratio = false; bool _scale_is_uniform = true; Gtk::Expander& _viewbox_expander; Gtk::Image& _linked_viewbox_scale; }; PageProperties* PageProperties::create() { return new PagePropertiesBox(); } } } } // namespace Inkscape/Widget/UI