diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/toolbar/page-toolbar.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip |
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ui/toolbar/page-toolbar.cpp')
-rw-r--r-- | src/ui/toolbar/page-toolbar.cpp | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/src/ui/toolbar/page-toolbar.cpp b/src/ui/toolbar/page-toolbar.cpp new file mode 100644 index 0000000..a228232 --- /dev/null +++ b/src/ui/toolbar/page-toolbar.cpp @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Page aux toolbar: Temp until we convert all toolbars to ui files with Gio::Actions. + */ +/* Authors: + * Martin Owens <doctormo@geek-2.com> + + * Copyright (C) 2021 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "page-toolbar.h" + +#include <glibmm/i18n.h> +#include <gtkmm.h> +#include <regex> + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "extension/db.h" +#include "extension/template.h" +#include "io/resource.h" +#include "object/sp-namedview.h" +#include "object/sp-page.h" +#include "ui/builder-utils.h" +#include "ui/icon-names.h" +#include "ui/themes.h" +#include "ui/tools/pages-tool.h" +#include "util/paper.h" +#include "util/units.h" + +using Inkscape::IO::Resource::UIS; + +namespace Inkscape { +namespace UI { +namespace Toolbar { + +class SearchCols : public Gtk::TreeModel::ColumnRecord +{ +public: + // These types must match those for the model in the ui file + SearchCols() + { + add(name); + add(label); + add(key); + } + Gtk::TreeModelColumn<Glib::ustring> name; // translated name + Gtk::TreeModelColumn<Glib::ustring> label; // translated label + Gtk::TreeModelColumn<Glib::ustring> key; +}; + +PageToolbar::PageToolbar(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &builder, SPDesktop *desktop) + : Gtk::Toolbar(cobject) + , _desktop(desktop) + , combo_page_sizes(nullptr) + , text_page_label(nullptr) +{ + builder->get_widget("page_sizes", combo_page_sizes); + builder->get_widget("page_margins", text_page_margins); + builder->get_widget("page_bleeds", text_page_bleeds); + builder->get_widget("page_label", text_page_label); + builder->get_widget("page_pos", label_page_pos); + builder->get_widget("page_backward", btn_page_backward); + builder->get_widget("page_foreward", btn_page_foreward); + builder->get_widget("page_delete", btn_page_delete); + builder->get_widget("page_move_objects", btn_move_toggle); + builder->get_widget("sep1", sep1); + + sizes_list = Glib::RefPtr<Gtk::ListStore>::cast_dynamic( + builder->get_object("page_sizes_list") + ); + sizes_search = Glib::RefPtr<Gtk::ListStore>::cast_dynamic( + builder->get_object("page_sizes_search") + ); + sizes_searcher = Glib::RefPtr<Gtk::EntryCompletion>::cast_dynamic( + builder->get_object("sizes_searcher") + ); + + builder->get_widget("margin_popover", margin_popover); + builder->get_widget_derived("margin_top", margin_top); + builder->get_widget_derived("margin_right", margin_right); + builder->get_widget_derived("margin_bottom", margin_bottom); + builder->get_widget_derived("margin_left", margin_left); + + if (text_page_label) { + text_page_label->signal_changed().connect(sigc::mem_fun(*this, &PageToolbar::labelEdited)); + } + if (sizes_searcher) { + sizes_searcher->signal_match_selected().connect([=](const Gtk::TreeModel::iterator &iter) { + SearchCols cols; + Gtk::TreeModel::Row row = *(iter); + Glib::ustring preset_key = row[cols.key]; + sizeChoose(preset_key); + return false; + }, false); + } + text_page_bleeds->signal_activate().connect(sigc::mem_fun(*this, &PageToolbar::bleedsEdited)); + text_page_margins->signal_activate().connect(sigc::mem_fun(*this, &PageToolbar::marginsEdited)); + text_page_margins->signal_icon_press().connect([=](Gtk::EntryIconPosition, const GdkEventButton*){ + if (auto page = _document->getPageManager().getSelected()) { + auto margin = page->getMargin(); + auto unit = _document->getDisplayUnit()->abbr; + margin_top->set_value(margin.top().toValue(unit)); + margin_right->set_value(margin.right().toValue(unit)); + margin_bottom->set_value(margin.bottom().toValue(unit)); + margin_left->set_value(margin.left().toValue(unit)); + text_page_bleeds->set_text(page->getBleedLabel()); + } + margin_popover->show(); + }); + margin_top->signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginTopEdited)); + margin_right->signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginRightEdited)); + margin_bottom->signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginBottomEdited)); + margin_left->signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginLeftEdited)); + + if (combo_page_sizes) { + combo_page_sizes->set_id_column(2); + combo_page_sizes->signal_changed().connect([=] { + std::string preset_key = combo_page_sizes->get_active_id(); + sizeChoose(preset_key); + }); + entry_page_sizes = dynamic_cast<Gtk::Entry *>(combo_page_sizes->get_child()); + if (entry_page_sizes) { + entry_page_sizes->set_placeholder_text(_("ex.: 100x100cm")); + entry_page_sizes->set_tooltip_text(_("Type in width & height of a page. (ex.: 15x10cm, 10in x 100mm)\n" + "or choose preset from dropdown.")); + entry_page_sizes->get_style_context()->add_class("symbolic"); + entry_page_sizes->signal_activate().connect(sigc::mem_fun(*this, &PageToolbar::sizeChanged)); + entry_page_sizes->signal_icon_press().connect([=](Gtk::EntryIconPosition, const GdkEventButton*){ + _document->getPageManager().changeOrientation(); + DocumentUndo::maybeDone(_document, "page-resize", _("Resize Page"), INKSCAPE_ICON("tool-pages")); + setSizeText(); + }); + entry_page_sizes->signal_focus_in_event().connect([=](GdkEventFocus *) { + setSizeText(nullptr, false); // Show just raw dimensions when user starts editing + return false; + }); + entry_page_sizes->signal_focus_out_event().connect([=](GdkEventFocus *) { + if (_document) + setSizeText(nullptr, true); + return false; + }); + populate_sizes(); + } + } + + // Watch for when the tool changes + _ec_connection = _desktop->connectEventContextChanged(sigc::mem_fun(*this, &PageToolbar::toolChanged)); + _doc_connection = _desktop->connectDocumentReplaced([=](SPDesktop *desktop, SPDocument *doc) { + if (doc) { + toolChanged(desktop, desktop->getEventContext()); + } + }); + + // Constructed by a builder, so we're going to protect the widget from destruction. + this->reference(); + was_referenced = true; +} + +/** + * Take all selectable page sizes and add to search and dropdowns + */ +void PageToolbar::populate_sizes() +{ + SearchCols cols; + + Inkscape::Extension::DB::TemplateList extensions; + Inkscape::Extension::db.get_template_list(extensions); + + for (auto tmod : extensions) { + if (!tmod->can_resize()) + continue; + for (auto preset : tmod->get_presets()) { + auto label = preset->get_label(); + if (!label.empty()) label = _(label.c_str()); + + if (preset->is_visible(Inkscape::Extension::TEMPLATE_SIZE_LIST)) { + // Goes into drop down + Gtk::TreeModel::Row row = *(sizes_list->append()); + row[cols.name] = _(preset->get_name().c_str()); + row[cols.label] = " <small><span fgalpha=\"50%\">" + label + "</span></small>"; + row[cols.key] = preset->get_key(); + } + if (preset->is_visible(Inkscape::Extension::TEMPLATE_SIZE_SEARCH)) { + // Goes into text search + Gtk::TreeModel::Row row = *(sizes_search->append()); + row[cols.name] = _(preset->get_name().c_str()); + row[cols.label] = label; + row[cols.key] = preset->get_key(); + } + } + } +} + +void PageToolbar::on_parent_changed(Gtk::Widget *) +{ + if (was_referenced) { + // Undo the gtkbuilder protection now that we have a parent + this->unreference(); + was_referenced = false; + } +} + +PageToolbar::~PageToolbar() +{ + _ec_connection.disconnect(); + _doc_connection.disconnect(); + toolChanged(nullptr, nullptr); +} + +void PageToolbar::toolChanged(SPDesktop *desktop, Inkscape::UI::Tools::ToolBase *ec) +{ + // Disconnect previous page changed signal + _page_selected.disconnect(); + _pages_changed.disconnect(); + _page_modified.disconnect(); + _document = nullptr; + + if (dynamic_cast<Inkscape::UI::Tools::PagesTool *>(ec)) { + // Save the document and page_manager for future use. + if ((_document = desktop->getDocument())) { + auto &page_manager = _document->getPageManager(); + // Connect the page changed signal and indicate changed + _pages_changed = page_manager.connectPagesChanged(sigc::mem_fun(*this, &PageToolbar::pagesChanged)); + _page_selected = page_manager.connectPageSelected(sigc::mem_fun(*this, &PageToolbar::selectionChanged)); + // Update everything now. + pagesChanged(); + } + } +} + +void PageToolbar::labelEdited() +{ + auto text = text_page_label->get_text(); + if (auto page = _document->getPageManager().getSelected()) { + page->setLabel(text.empty() ? nullptr : text.c_str()); + DocumentUndo::maybeDone(_document, "page-relabel", _("Relabel Page"), INKSCAPE_ICON("tool-pages")); + } +} + +void PageToolbar::bleedsEdited() +{ + auto text = text_page_bleeds->get_text(); + + // And modifiction to the bleed causes pages to be enabled + auto &pm = _document->getPageManager(); + pm.enablePages(); + + if (auto page = pm.getSelected()) { + page->setBleed(text); + DocumentUndo::maybeDone(_document, "page-bleed", _("Edit page bleed"), INKSCAPE_ICON("tool-pages")); + + auto bleed = page->getBleed(); + text_page_bleeds->set_text(page->getBleedLabel()); + } +} + +void PageToolbar::marginsEdited() +{ + auto text = text_page_margins->get_text(); + + // And modifiction to the margin causes pages to be enabled + auto &pm = _document->getPageManager(); + pm.enablePages(); + + if (auto page = pm.getSelected()) { + page->setMargin(text); + DocumentUndo::maybeDone(_document, "page-margin", _("Edit page margin"), INKSCAPE_ICON("tool-pages")); + setMarginText(page); + } +} + +void PageToolbar::marginTopEdited() +{ + marginSideEdited(0, margin_top->get_text()); +} +void PageToolbar::marginRightEdited() +{ + marginSideEdited(1, margin_right->get_text()); +} +void PageToolbar::marginBottomEdited() +{ + marginSideEdited(2, margin_bottom->get_text()); +} +void PageToolbar::marginLeftEdited() +{ + marginSideEdited(3, margin_left->get_text()); +} +void PageToolbar::marginSideEdited(int side, const Glib::ustring &value) +{ + // And modifiction to the margin causes pages to be enabled + auto &pm = _document->getPageManager(); + pm.enablePages(); + + if (auto page = pm.getSelected()) { + page->setMarginSide(side, value, false); + DocumentUndo::maybeDone(_document, "page-margin", _("Edit page margin"), INKSCAPE_ICON("tool-pages")); + setMarginText(page); + } +} + +void PageToolbar::sizeChoose(const std::string &preset_key) +{ + if (auto preset = Extension::Template::get_any_preset(preset_key)) { + auto &pm = _document->getPageManager(); + // The page orientation is a part of the toolbar widget, so we pass this + // as a specially named pref, the extension can then decide to use it or not. + auto p_rect = pm.getSelectedPageRect(); + std::string orient = p_rect.width() > p_rect.height() ? "land" : "port"; + + auto page = pm.getSelected(); + preset->resize_to_template(_document, page, { + {"orientation", orient}, + }); + if (page) { + page->setSizeLabel(preset->get_name()); + } + + setSizeText(); + DocumentUndo::maybeDone(_document, "page-resize", _("Resize Page"), INKSCAPE_ICON("tool-pages")); + } else { + // Page not found, i.e., "Custom" was selected or user is typing in. + entry_page_sizes->grab_focus(); + } +} + +/** + * Convert the parsed sections of a text input into a desktop pixel value. + */ +double PageToolbar::_unit_to_size(std::string number, std::string unit_str, std::string backup) +{ + // We always support comma, even if not in that particular locale. + std::replace(number.begin(), number.end(), ',', '.'); + double value = std::stod(number); + + // Get the best unit, for example 50x40cm means cm for both + if (unit_str.empty() && !backup.empty()) + unit_str = backup; + if (unit_str == "\"") + unit_str = "in"; + + // Output is always in px as it's the most useful. + auto px = Inkscape::Util::unit_table.getUnit("px"); + + // Convert from user entered unit to display unit + if (!unit_str.empty()) + return Inkscape::Util::Quantity::convert(value, unit_str, px); + + // Default unit is the document's display unit + auto unit = _document->getDisplayUnit(); + return Inkscape::Util::Quantity::convert(value, unit, px); +} + +/** + * A manually typed input size, parse out what we can understand from + * the text or ignore it if the text can't be parsed. + * + * Format: 50cm x 40mm + * 20',40" + * 30,4-40.2 + */ +void PageToolbar::sizeChanged() +{ + // Parse the size out of the typed text if possible. + auto text = std::string(combo_page_sizes->get_active_text()); + // This does not support negative values, because pages can not be negatively sized. + static std::string arg = "([0-9]+[\\.,]?[0-9]*|\\.[0-9]+) ?(px|mm|cm|in|\\\")?"; + // We can't support × here since it's UTF8 and this doesn't match + static std::regex re_size("^ *" + arg + " *([ *Xx,\\-]) *" + arg + " *$"); + + std::smatch matches; + if (std::regex_match(text, matches, re_size)) { + // Convert the desktop px back into document units for 'resizePage' + double width = _unit_to_size(matches[1], matches[2], matches[5]); + double height = _unit_to_size(matches[4], matches[5], matches[2]); + if (width > 0 && height > 0) { + _document->getPageManager().resizePage(width, height); + } + } + setSizeText(); +} + +/** + * Sets the size of the current page into the entry page size. + */ +void PageToolbar::setSizeText(SPPage *page, bool display_only) +{ + SearchCols cols; + + if (!page) + page = _document->getPageManager().getSelected(); + + auto label = _document->getPageManager().getSizeLabel(page); + + // If this is a known size in our list, add the size paren to it. + for (auto iter : sizes_search->children()) { + auto row = *iter; + if (label == row[cols.name]) { + label = label + " (" + row[cols.label] + ")"; + break; + } + } + entry_page_sizes->set_text(label); + + + // Orientation button + auto box = page ? page->getDesktopRect() : *_document->preferredBounds(); + std::string icon = box.width() > box.height() ? "page-landscape" : "page-portrait"; + if (box.width() == box.height()) { + entry_page_sizes->unset_icon(Gtk::ENTRY_ICON_SECONDARY); + } else { + entry_page_sizes->set_icon_from_icon_name(INKSCAPE_ICON(icon), Gtk::ENTRY_ICON_SECONDARY); + } + + if (!display_only) { + // The user has started editing the combo box; we set up a convenient initial state. + // Select text if box is currently in focus. + if (entry_page_sizes->has_focus()) { + entry_page_sizes->select_region(0, -1); + } + } +} + +void PageToolbar::setMarginText(SPPage *page) +{ + text_page_margins->set_text(page ? page->getMarginLabel() : ""); + text_page_margins->set_sensitive(true); +} + +void PageToolbar::pagesChanged() +{ + selectionChanged(_document->getPageManager().getSelected()); +} + +void PageToolbar::selectionChanged(SPPage *page) +{ + _page_modified.disconnect(); + auto &page_manager = _document->getPageManager(); + text_page_label->set_tooltip_text(_("Page label")); + + setMarginText(page); + + // Set label widget content with page label. + if (page) { + text_page_label->set_sensitive(true); + text_page_label->set_placeholder_text(page->getDefaultLabel()); + + if (auto label = page->label()) { + text_page_label->set_text(label); + } else { + text_page_label->set_text(""); + } + + + // TRANSLATORS: "%1" is replaced with the page we are on, and "%2" is the total number of pages. + auto label = Glib::ustring::compose(_("%1/%2"), page->getPagePosition(), page_manager.getPageCount()); + label_page_pos->set_label(label); + + _page_modified = page->connectModified([=](SPObject *obj, unsigned int flags) { + if (auto page = cast<SPPage>(obj)) { + // Make sure we don't 'select' on removal of the page + if (flags & SP_OBJECT_MODIFIED_FLAG) { + selectionChanged(page); + } + } + }); + } else { + text_page_label->set_text(""); + text_page_label->set_sensitive(false); + text_page_label->set_placeholder_text(_("Single Page Document")); + label_page_pos->set_label(_("1/-")); + + _page_modified = _document->connectModified([=](guint) { + selectionChanged(nullptr); + }); + } + if (!page_manager.hasPrevPage() && !page_manager.hasNextPage() && !page) { + sep1->set_visible(false); + label_page_pos->get_parent()->set_visible(false); + btn_page_backward->set_visible(false); + btn_page_foreward->set_visible(false); + btn_page_delete->set_visible(false); + btn_move_toggle->set_sensitive(false); + } else { + // Set the forward and backward button sensitivities + sep1->set_visible(true); + label_page_pos->get_parent()->set_visible(true); + btn_page_backward->set_visible(true); + btn_page_foreward->set_visible(true); + btn_page_backward->set_sensitive(page_manager.hasPrevPage()); + btn_page_foreward->set_sensitive(page_manager.hasNextPage()); + btn_page_delete->set_visible(true); + btn_move_toggle->set_sensitive(true); + } + setSizeText(page); +} + +GtkWidget *PageToolbar::create(SPDesktop *desktop) +{ + PageToolbar *toolbar = nullptr; + auto builder = Inkscape::UI::create_builder("toolbar-page.ui"); + builder->get_widget_derived("page-toolbar", toolbar, desktop); + + if (!toolbar) { + std::cerr << "InkscapeWindow: Failed to load page toolbar!" << std::endl; + return nullptr; + } + // This widget will be auto-freed by the builder unless you have called reference(); + return GTK_WIDGET(toolbar->gobj()); +} + + +} // namespace Toolbar +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |