// SPDX-License-Identifier: GPL-2.0-or-later /* * Author: * Tavmjong Bah * * Rewrite of code originally in desktop-widget.cpp. * * Copyright (C) 2020 Tavmjong Bah * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ // The scrollbars, and canvas are tightly coupled so it makes sense to have a dedicated // widget to handle their interactions. The buttons are along for the ride. I don't see // how to add the buttons easily via a .ui file (which would allow the user to put any // buttons they want in their place). #include #include #include #include "canvas-grid.h" #include "desktop.h" // Hopefully temp. #include "desktop-events.h" // Hopefully temp. #include "display/control/canvas-item-drawing.h" // sticky #include "page-manager.h" #include "ui/dialog/command-palette.h" #include "ui/icon-loader.h" #include "ui/widget/canvas.h" #include "ui/widget/canvas-notice.h" #include "ui/widget/ink-ruler.h" #include "io/resource.h" #include "widgets/desktop-widget.h" // Hopefully temp. namespace Inkscape { namespace UI { namespace Widget { CanvasGrid::CanvasGrid(SPDesktopWidget *dtw) { _dtw = dtw; set_name("CanvasGrid"); // Canvas _canvas = std::make_unique(); _canvas->set_hexpand(true); _canvas->set_vexpand(true); _canvas->set_can_focus(true); _canvas->signal_event().connect(sigc::mem_fun(*this, &CanvasGrid::SignalEvent)); // TEMP // Command palette _command_palette = std::make_unique(); // Notice overlay, note using unique_ptr will cause destruction race conditions _notice = CanvasNotice::create(); // Canvas overlay _canvas_overlay.add(*_canvas); _canvas_overlay.add_overlay(*_command_palette->get_base_widget()); _canvas_overlay.add_overlay(*_notice); // Horizontal Ruler _hruler = std::make_unique(Gtk::ORIENTATION_HORIZONTAL); _hruler->add_track_widget(*_canvas); _hruler->set_hexpand(true); _hruler->show(); // Tooltip/Unit set elsewhere // Vertical Ruler _vruler = std::make_unique(Gtk::ORIENTATION_VERTICAL); _vruler->add_track_widget(*_canvas); _vruler->set_vexpand(true); _vruler->show(); // Tooltip/Unit set elsewhere. // Guide Lock _guide_lock.set_name("LockGuides"); _guide_lock.add(*Gtk::make_managed("object-locked", Gtk::ICON_SIZE_MENU)); // To be replaced by Gio::Action: _guide_lock.signal_toggled().connect(sigc::mem_fun(*_dtw, &SPDesktopWidget::update_guides_lock)); _guide_lock.set_tooltip_text(_("Toggle lock of all guides in the document")); // Subgrid _subgrid.attach(_guide_lock, 0, 0, 1, 1); _subgrid.attach(*_vruler, 0, 1, 1, 1); _subgrid.attach(*_hruler, 1, 0, 1, 1); _subgrid.attach(_canvas_overlay, 1, 1, 1, 1); // Horizontal Scrollbar _hadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0); _hadj->signal_value_changed().connect(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_adjustment_value_changed)); _hscrollbar = Gtk::Scrollbar(_hadj, Gtk::ORIENTATION_HORIZONTAL); _hscrollbar.set_name("CanvasScrollbar"); _hscrollbar.set_hexpand(true); // Vertical Scrollbar _vadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0); _vadj->signal_value_changed().connect(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_adjustment_value_changed)); _vscrollbar = Gtk::Scrollbar(_vadj, Gtk::ORIENTATION_VERTICAL); _vscrollbar.set_name("CanvasScrollbar"); _vscrollbar.set_vexpand(true); // CMS Adjust (To be replaced by Gio::Action) _cms_adjust.set_name("CMS_Adjust"); _cms_adjust.add(*Gtk::make_managed("color-management", Gtk::ICON_SIZE_MENU)); // Can't access via C++ API, fixed in Gtk4. gtk_actionable_set_action_name( GTK_ACTIONABLE(_cms_adjust.gobj()), "win.canvas-color-manage"); _cms_adjust.set_tooltip_text(_("Toggle color-managed display for this document window")); // popover with some common display mode related options auto builder = Gtk::Builder::create_from_file(Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::UIS, "display-popup.glade")); _display_popup = builder; Gtk::Popover* popover; _display_popup->get_widget("popover", popover); Gtk::CheckButton* sticky_zoom; _display_popup->get_widget("zoom-resize", sticky_zoom); // To be replaced by Gio::Action: sticky_zoom->signal_toggled().connect([=](){ _dtw->sticky_zoom_toggled(); }); _quick_actions.set_name("QuickActions"); _quick_actions.set_popover(*popover); _quick_actions.set_image_from_icon_name("display-symbolic"); _quick_actions.set_direction(Gtk::ARROW_LEFT); _quick_actions.set_tooltip_text(_("Display options")); // Main grid attach(_subgrid, 0, 0, 1, 2); attach(_hscrollbar, 0, 2, 1, 1); attach(_cms_adjust, 1, 2, 1, 1); attach(_quick_actions, 1, 0, 1, 1); attach(_vscrollbar, 1, 1, 1, 1); // For creating guides, etc. _hruler->signal_button_press_event().connect( sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_press_event), _hruler.get(), true)); _hruler->signal_button_release_event().connect( sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_release_event), _hruler.get(), true)); _hruler->signal_motion_notify_event().connect( sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_motion_notify_event), _hruler.get(), true)); // For creating guides, etc. _vruler->signal_button_press_event().connect( sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_press_event), _vruler.get(), false)); _vruler->signal_button_release_event().connect( sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_button_release_event), _vruler.get(), false)); _vruler->signal_motion_notify_event().connect( sigc::bind(sigc::mem_fun(*_dtw, &SPDesktopWidget::on_ruler_box_motion_notify_event), _vruler.get(), false)); show_all(); } CanvasGrid::~CanvasGrid() { _page_modified_connection.disconnect(); _page_selected_connection.disconnect(); _sel_modified_connection.disconnect(); _sel_changed_connection.disconnect(); _document = nullptr; _notice = nullptr; } void CanvasGrid::on_realize() { // actions should be available now if (auto map = _dtw->get_action_map()) { auto set_display_icon = [=]() { Glib::ustring id; auto mode = _canvas->get_render_mode(); switch (mode) { case RenderMode::NORMAL: id = "display"; break; case RenderMode::OUTLINE: id = "display-outline"; break; case RenderMode::OUTLINE_OVERLAY: id = "display-outline-overlay"; break; case RenderMode::VISIBLE_HAIRLINES: id = "display-enhance-stroke"; break; case RenderMode::NO_FILTERS: id = "display-no-filter"; break; default: g_warning("Unknown display mode in canvas-grid"); break; } if (!id.empty()) { // if CMS is ON show alternative icons if (_canvas->get_cms_active()) { id += "-alt"; } _quick_actions.set_image_from_icon_name(id + "-symbolic"); } }; set_display_icon(); // when display mode state changes, update icon auto cms_action = Glib::RefPtr::cast_dynamic(map->lookup_action("canvas-color-manage")); auto disp_action = Glib::RefPtr::cast_dynamic(map->lookup_action("canvas-display-mode")); if (cms_action && disp_action) { disp_action->signal_activate().connect([=](const Glib::VariantBase& state){ set_display_icon(); }); cms_action-> signal_activate().connect([=](const Glib::VariantBase& state){ set_display_icon(); }); } else { g_warning("No canvas-display-mode and/or canvas-color-manage action available to canvas-grid"); } } else { g_warning("No action map available to canvas-grid"); } parent_type::on_realize(); } // TODO: remove when sticky zoom gets replaced by Gio::Action: Gtk::ToggleButton* CanvasGrid::GetStickyZoom() { Gtk::CheckButton* sticky_zoom; _display_popup->get_widget("zoom-resize", sticky_zoom); return sticky_zoom; } // _dt2r should be a member of _canvas. // get_display_area should be a member of _canvas. void CanvasGrid::UpdateRulers() { auto prefs = Inkscape::Preferences::get(); auto desktop = _dtw->desktop; auto document = desktop->getDocument(); auto &pm = document->getPageManager(); auto sel = desktop->getSelection(); // Our connections to the document are handled with a lazy pattern to avoid // having to refactor the SPDesktopWidget class. We know UpdateRulers is // called in all situations when documents are loaded and replaced. if (document != _document) { _document = document; _page_selected_connection = pm.connectPageSelected([=](SPPage *) { UpdateRulers(); }); _page_modified_connection = pm.connectPageModified([=](SPPage *) { UpdateRulers(); }); _sel_modified_connection = sel->connectModified([=](Inkscape::Selection *, int) { UpdateRulers(); }); _sel_changed_connection = sel->connectChanged([=](Inkscape::Selection *) { UpdateRulers(); }); } Geom::Rect viewbox = desktop->get_display_area().bounds(); Geom::Rect startbox = viewbox; if (prefs->getBool("/options/origincorrection/page", true)) { // Move viewbox according to the selected page's position (if any) startbox *= pm.getSelectedPageAffine().inverse(); } // Scale and offset the ruler coordinates // Use an integer box to align the ruler to the grid and page. auto rulerbox = (startbox * Geom::Scale(_dtw->_dt2r)); _hruler->set_range(rulerbox.left(), rulerbox.right()); if (_dtw->desktop->is_yaxisdown()) { _vruler->set_range(rulerbox.top(), rulerbox.bottom()); } else { _vruler->set_range(rulerbox.bottom(), rulerbox.top()); } Geom::Point pos(_canvas->get_pos()); auto scale = _canvas->get_affine(); auto d2c = Geom::Translate(pos * scale.inverse()).inverse() * scale; auto pagebox = (pm.getSelectedPageRect() * d2c).roundOutwards(); _hruler->set_page(pagebox.left(), pagebox.right()); _vruler->set_page(pagebox.top(), pagebox.bottom()); Geom::Rect selbox = Geom::IntRect(0, 0, 0, 0); if (auto bbox = sel->preferredBounds()) selbox = (*bbox * d2c).roundOutwards(); _hruler->set_selection(selbox.left(), selbox.right()); _vruler->set_selection(selbox.top(), selbox.bottom()); } void CanvasGrid::ShowScrollbars(bool state) { if (_show_scrollbars == state) return; _show_scrollbars = state; if (_show_scrollbars) { // Show scrollbars _hscrollbar.show(); _vscrollbar.show(); _cms_adjust.show(); _cms_adjust.show_all_children(); _quick_actions.show(); } else { // Hide scrollbars _hscrollbar.hide(); _vscrollbar.hide(); _cms_adjust.hide(); _quick_actions.hide(); } } void CanvasGrid::ToggleScrollbars() { _show_scrollbars = !_show_scrollbars; ShowScrollbars(_show_scrollbars); // Will be replaced by actions auto prefs = Inkscape::Preferences::get(); prefs->setBool("/fullscreen/scrollbars/state", _show_scrollbars); prefs->setBool("/window/scrollbars/state", _show_scrollbars); } void CanvasGrid::ShowRulers(bool state) { if (_show_rulers == state) return; _show_rulers = state; if (_show_rulers) { // Show rulers _hruler->show(); _vruler->show(); _guide_lock.show(); _guide_lock.show_all_children(); } else { // Hide rulers _hruler->hide(); _vruler->hide(); _guide_lock.hide(); } } void CanvasGrid::ToggleRulers() { _show_rulers = !_show_rulers; ShowRulers(_show_rulers); // Will be replaced by actions auto prefs = Inkscape::Preferences::get(); prefs->setBool("/fullscreen/rulers/state", _show_rulers); prefs->setBool("/window/rulers/state", _show_rulers); } void CanvasGrid::ToggleCommandPalette() { _command_palette->toggle(); } void CanvasGrid::showNotice(Glib::ustring const &msg, unsigned timeout) { _notice->show(msg, timeout); } void CanvasGrid::ShowCommandPalette(bool state) { if (state) { _command_palette->open(); } _command_palette->close(); } // Update rulers on change of widget size, but only if allocation really changed. void CanvasGrid::on_size_allocate(Gtk::Allocation& allocation) { Gtk::Grid::on_size_allocate(allocation); if (!(_allocation == allocation)) { // No != function defined! _allocation = allocation; UpdateRulers(); } } // This belong in Canvas class bool CanvasGrid::SignalEvent(GdkEvent *event) { if (event->type == GDK_BUTTON_PRESS) { _canvas->grab_focus(); _command_palette->close(); } if (event->type == GDK_BUTTON_PRESS && event->button.button == 3) { _dtw->desktop->getCanvasDrawing()->set_sticky(event->button.state & GDK_SHIFT_MASK); } // Pass keyboard events back to the desktop root handler so TextTool can work if ((event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) && !_canvas->get_current_canvas_item()) { return sp_desktop_root_handler(event, _dtw->desktop); } return false; } // TODO Add actions so we can set shortcuts. // * Sticky Zoom // * CMS Adjust // * Guide Lock } // namespace Widget } // 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 :