diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
commit | 35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch) | |
tree | 657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/widgets/desktop-widget.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.tar.xz inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.zip |
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/widgets/desktop-widget.cpp')
-rw-r--r-- | src/widgets/desktop-widget.cpp | 2570 |
1 files changed, 2570 insertions, 0 deletions
diff --git a/src/widgets/desktop-widget.cpp b/src/widgets/desktop-widget.cpp new file mode 100644 index 0000000..491ce41 --- /dev/null +++ b/src/widgets/desktop-widget.cpp @@ -0,0 +1,2570 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Desktop widget implementation + */ +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * MenTaLguY <mental@rydia.net> + * bulia byak <buliabyak@users.sf.net> + * Ralf Stephan <ralf@ark.in-berlin.de> + * John Bintz <jcoswell@coswellproductions.org> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2006 John Bintz + * Copyright (C) 2004 MenTaLguY + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#include <2geom/rect.h> + +#include "attributes.h" +#include "cms-system.h" +#include "conn-avoid-ref.h" +#include "desktop-events.h" +#include "desktop-widget.h" +#include "desktop.h" +#include "document-undo.h" +#include "ege-color-prof-tracker.h" +#include "file.h" +#include "inkscape-version.h" +#include "verbs.h" + +#include "display/canvas-arena.h" +#include "display/canvas-axonomgrid.h" +#include "display/guideline.h" +#include "display/sp-canvas.h" + +#include "extension/db.h" + +#include "helper/action.h" + +#include "object/sp-image.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" + +#include "ui/dialog/dialog-manager.h" +#include "ui/dialog/swatches.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "ui/tools/box3d-tool.h" +#include "ui/uxmanager.h" +#include "ui/widget/button.h" +#include "ui/widget/dock.h" +#include "ui/widget/ink-ruler.h" +#include "ui/widget/layer-selector.h" +#include "ui/widget/selected-style.h" +#include "ui/widget/spin-button-tool-item.h" +#include "ui/widget/unit-tracker.h" + +// TEMP +#include "ui/desktop/menubar.h" + +#include "util/ege-appear-time-tracker.h" +#include "util/units.h" + +// We're in the "widgets" directory, so no need to explicitly prefix these: +#include "spinbutton-events.h" +#include "spw-utilities.h" +#include "toolbox.h" +#include "widget-sizes.h" + +#ifdef GDK_WINDOWING_QUARTZ +#include <gtkosxapplication.h> +#endif + +using Inkscape::DocumentUndo; +using Inkscape::UI::Widget::UnitTracker; +using Inkscape::UI::UXManager; +using Inkscape::UI::ToolboxFactory; +using ege::AppearTimeTracker; +using Inkscape::Util::unit_table; + + +//--------------------------------------------------------------------- +/* SPDesktopWidget */ + +static void sp_desktop_widget_class_init (SPDesktopWidgetClass *klass); + +static void sp_desktop_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation); +static void sp_desktop_widget_realize (GtkWidget *widget); + +static void sp_desktop_widget_adjustment_value_changed (GtkAdjustment *adj, SPDesktopWidget *dtw); + +static gdouble sp_dtw_zoom_value_to_display (gdouble value); +static gdouble sp_dtw_zoom_display_to_value (gdouble value); +static void sp_dtw_zoom_menu_handler (SPDesktop *dt, gdouble factor); + +SPViewWidgetClass *dtw_parent_class; + +class CMSPrefWatcher { +public: + CMSPrefWatcher() : + _dpw(*this), + _spw(*this), + _tracker(ege_color_prof_tracker_new(nullptr)) + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + g_signal_connect( G_OBJECT(_tracker), "modified", G_CALLBACK(hook), this ); + prefs->addObserver(_dpw); + prefs->addObserver(_spw); + } + virtual ~CMSPrefWatcher() = default; + + //virtual void notify(PrefValue &); + void add( SPDesktopWidget* dtw ) { + _widget_list.push_back(dtw); + } + void remove( SPDesktopWidget* dtw ) { + _widget_list.remove(dtw); + } + +private: + static void hook(EgeColorProfTracker *tracker, gint b, CMSPrefWatcher *watcher); + + class DisplayProfileWatcher : public Inkscape::Preferences::Observer { + public: + DisplayProfileWatcher(CMSPrefWatcher &pw) : Observer("/options/displayprofile"), _pw(pw) {} + void notify(Inkscape::Preferences::Entry const &/*val*/) override { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _pw._setCmsSensitive(!prefs->getString("/options/displayprofile/uri").empty()); + _pw._refreshAll(); + } + private: + CMSPrefWatcher &_pw; + }; + + DisplayProfileWatcher _dpw; + + class SoftProofWatcher : public Inkscape::Preferences::Observer { + public: + SoftProofWatcher(CMSPrefWatcher &pw) : Observer("/options/softproof"), _pw(pw) {} + void notify(Inkscape::Preferences::Entry const &) override { + _pw._refreshAll(); + } + private: + CMSPrefWatcher &_pw; + }; + + SoftProofWatcher _spw; + + void _refreshAll(); + void _setCmsSensitive(bool value); + + std::list<SPDesktopWidget*> _widget_list; + EgeColorProfTracker *_tracker; + + friend class DisplayProfileWatcher; + friend class SoftproofWatcher; +}; + +#if defined(HAVE_LIBLCMS2) +void CMSPrefWatcher::hook(EgeColorProfTracker * /*tracker*/, gint monitor, CMSPrefWatcher * /*watcher*/) +{ + unsigned char* buf = nullptr; + guint len = 0; + + ege_color_prof_tracker_get_profile_for( monitor, reinterpret_cast<gpointer*>(&buf), &len ); + Glib::ustring id = Inkscape::CMSSystem::setDisplayPer( buf, len, monitor ); +} +#else +void CMSPrefWatcher::hook(EgeColorProfTracker * /*tracker*/, gint /*monitor*/, CMSPrefWatcher * /*watcher*/) +{ +} +#endif // defined(HAVE_LIBLCMS2) + +/// @todo Use conditional compilation in saner places. The whole PrefWatcher +/// object is unnecessary if defined(HAVE_LIBLCMS2) is not defined. +void CMSPrefWatcher::_refreshAll() +{ +#if defined(HAVE_LIBLCMS2) + for (auto & it : _widget_list) { + it->requestCanvasUpdate(); + } +#endif // defined(HAVE_LIBLCMS2) +} + +void CMSPrefWatcher::_setCmsSensitive(bool enabled) +{ +#if defined(HAVE_LIBLCMS2) + for ( auto dtw : _widget_list ) { + auto cms_adj = dtw->get_cms_adjust(); + if ( cms_adj->get_sensitive() != enabled ) { + dtw->cms_adjust_set_sensitive(enabled); + } + } +#else + (void) enabled; +#endif // defined(HAVE_LIBLCMS2) +} + +static CMSPrefWatcher* watcher = nullptr; + +void +SPDesktopWidget::setMessage (Inkscape::MessageType type, const gchar *message) +{ + _select_status->set_markup(message ? message : ""); + + // make sure the important messages are displayed immediately! + if (type == Inkscape::IMMEDIATE_MESSAGE && _select_status->get_is_drawable()) { + _select_status->queue_draw(); + } + + _select_status->set_tooltip_text(_select_status->get_text()); +} + +Geom::Point +SPDesktopWidget::window_get_pointer() +{ + int x, y; + auto window = Glib::wrap(GTK_WIDGET(_canvas))->get_window(); + auto display = window->get_display(); + auto seat = display->get_default_seat(); + auto device = seat->get_pointer(); + Gdk::ModifierType m; + window->get_device_position(device, x, y, m); + + return Geom::Point(x, y); +} + +static GTimer *overallTimer = nullptr; + +/** + * Registers SPDesktopWidget class and returns its type number. + */ +GType SPDesktopWidget::getType() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPDesktopWidgetClass), + nullptr, // base_init + nullptr, // base_finalize + (GClassInitFunc)sp_desktop_widget_class_init, + nullptr, // class_finalize + nullptr, // class_data + sizeof(SPDesktopWidget), + 0, // n_preallocs + (GInstanceInitFunc)SPDesktopWidget::init, + nullptr // value_table + }; + type = g_type_register_static(SP_TYPE_VIEW_WIDGET, "SPDesktopWidget", &info, static_cast<GTypeFlags>(0)); + // Begin a timer to watch for the first desktop to appear on-screen + overallTimer = g_timer_new(); + } + return type; +} + +/** + * SPDesktopWidget vtable initialization + */ +static void +sp_desktop_widget_class_init (SPDesktopWidgetClass *klass) +{ + dtw_parent_class = SP_VIEW_WIDGET_CLASS(g_type_class_peek_parent(klass)); + + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + + object_class->dispose = SPDesktopWidget::dispose; + + widget_class->size_allocate = sp_desktop_widget_size_allocate; + widget_class->realize = sp_desktop_widget_realize; +} + +/** + * Callback for changes in size of the canvas table (i.e. the container for + * the canvas, the rulers etc). + * + * This adjusts the range of the rulers when the dock container is adjusted + * (fixes lp:950552) + * + * This fix was causing the rulers to be completely redrawn when not needed. + * Added check to see if allocation really changed. + * + *(Question, why is the callback being called when allocation not changed?) + */ +void +SPDesktopWidget::canvas_tbl_size_allocate(Gtk::Allocation& allocation) +{ + if (_allocation == allocation) { + return; + } + + _allocation = allocation; + update_rulers(); +} + +/** + * Callback for SPDesktopWidget object initialization. + */ +void SPDesktopWidget::init( SPDesktopWidget *dtw ) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + new (&dtw->modified_connection) sigc::connection(); + + dtw->_ruler_clicked = false; + dtw->_ruler_dragged = false; + dtw->_active_guide = nullptr; + dtw->_xp = 0; + dtw->_yp = 0; + dtw->window = nullptr; + dtw->desktop = nullptr; + dtw->_interaction_disabled_counter = 0; + + /* Main table */ + dtw->_vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + dtw->_vbox->set_name("DesktopMainTable"); + gtk_container_add( GTK_CONTAINER(dtw), GTK_WIDGET(dtw->_vbox->gobj()) ); + + /* Status bar */ + dtw->_statusbar = Gtk::manage(new Gtk::Box()); + dtw->_statusbar->set_name("DesktopStatusBar"); + dtw->_vbox->pack_end(*dtw->_statusbar, false, true); + + /* Swatches panel */ + { + dtw->_panels = new Inkscape::UI::Dialog::SwatchesPanel("/embedded/swatches"); + dtw->_panels->set_vexpand(false); + dtw->_vbox->pack_end(*dtw->_panels, false, true); + } + + /* DesktopHBox (Vertical toolboxes, canvas) */ + dtw->_hbox = Gtk::manage(new Gtk::Box()); + dtw->_hbox->set_name("DesktopHbox"); + dtw->_vbox->pack_end(*dtw->_hbox, true, true); + + /* Toolboxes */ + dtw->aux_toolbox = ToolboxFactory::createAuxToolbox(); + dtw->_vbox->pack_end(*Glib::wrap(dtw->aux_toolbox), false, true); + + dtw->snap_toolbox = ToolboxFactory::createSnapToolbox(); + ToolboxFactory::setOrientation( dtw->snap_toolbox, GTK_ORIENTATION_VERTICAL ); + dtw->_hbox->pack_end(*Glib::wrap(dtw->snap_toolbox), false, true); + + dtw->commands_toolbox = ToolboxFactory::createCommandsToolbox(); + dtw->_vbox->pack_end(*Glib::wrap(dtw->commands_toolbox), false, true); + + dtw->tool_toolbox = ToolboxFactory::createToolToolbox(); + ToolboxFactory::setOrientation( dtw->tool_toolbox, GTK_ORIENTATION_VERTICAL ); + dtw->_hbox->pack_start(*Glib::wrap(dtw->tool_toolbox), false, true); + + /* Canvas table wrapper */ + auto tbl_wrapper = Gtk::manage(new Gtk::Grid()); // Is this widget really needed? No! + tbl_wrapper->set_name("CanvasTableWrapper"); + dtw->_hbox->pack_start(*tbl_wrapper, true, true, 1); + + /* Canvas table */ + dtw->_canvas_tbl = Gtk::manage(new Gtk::Grid()); + dtw->_canvas_tbl->set_name("CanvasTable"); + // Added to table wrapper later either directly or via paned window shared with dock. + + + + // Lock guides button + dtw->_guides_lock = Gtk::manage(new Inkscape::UI::Widget::Button(GTK_ICON_SIZE_MENU, + Inkscape::UI::Widget::BUTTON_TYPE_TOGGLE, + nullptr, + INKSCAPE_ICON("object-locked"), + _("Toggle lock of all guides in the document"))); + + auto guides_lock_style_provider = Gtk::CssProvider::create(); + guides_lock_style_provider->load_from_data("GtkWidget { padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; }"); + dtw->_guides_lock->set_name("LockGuides"); + auto context = dtw->_guides_lock->get_style_context(); + context->add_provider(guides_lock_style_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + dtw->_guides_lock->signal_toggled().connect(sigc::mem_fun(dtw, &SPDesktopWidget::update_guides_lock)); + dtw->_canvas_tbl->attach(*dtw->_guides_lock, 0, 0, 1, 1); + + /* Rulers */ + Inkscape::Util::Unit const *pt = unit_table.getUnit("pt"); + + /* Horizontal ruler */ + dtw->_hruler = Gtk::manage(new Inkscape::UI::Widget::Ruler(Gtk::ORIENTATION_HORIZONTAL)); + dtw->_hruler->set_unit(pt); + + // We should probably get rid of this and attach the signals directly rulers. + dtw->_hruler_box = Gtk::manage(new Gtk::EventBox()); + dtw->_hruler_box->set_tooltip_text(gettext(pt->name_plural.c_str())); + dtw->_hruler_box->add(*dtw->_hruler); + dtw->_hruler_box->signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*dtw, &SPDesktopWidget::on_ruler_box_button_press_event), dtw->_hruler_box, true)); + dtw->_hruler_box->signal_button_release_event().connect(sigc::bind(sigc::mem_fun(*dtw, &SPDesktopWidget::on_ruler_box_button_release_event), dtw->_hruler_box, true)); + dtw->_hruler_box->signal_motion_notify_event().connect(sigc::bind(sigc::mem_fun(*dtw, &SPDesktopWidget::on_ruler_box_motion_notify_event), dtw->_hruler_box, true)); + + dtw->_canvas_tbl->attach(*dtw->_hruler_box, 1, 0, 1, 1); + + /* Vertical ruler */ + dtw->_vruler = Gtk::manage(new Inkscape::UI::Widget::Ruler(Gtk::ORIENTATION_VERTICAL)); + dtw->_vruler->set_unit(pt); + + dtw->_vruler_box = Gtk::manage(new Gtk::EventBox()); + dtw->_vruler_box->set_tooltip_text(gettext(pt->name_plural.c_str())); + dtw->_vruler_box->add(*dtw->_vruler); + dtw->_vruler_box->signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*dtw, &SPDesktopWidget::on_ruler_box_button_press_event), dtw->_vruler_box, false)); + dtw->_vruler_box->signal_button_release_event().connect(sigc::bind(sigc::mem_fun(*dtw, &SPDesktopWidget::on_ruler_box_button_release_event), dtw->_vruler_box, false)); + dtw->_vruler_box->signal_motion_notify_event().connect(sigc::bind(sigc::mem_fun(*dtw, &SPDesktopWidget::on_ruler_box_motion_notify_event), dtw->_vruler_box, false)); + + dtw->_canvas_tbl->attach(*dtw->_vruler_box, 0, 1, 1, 1); + + // Horizontal scrollbar + dtw->_hadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0); + dtw->_hscrollbar = Gtk::manage(new Gtk::Scrollbar(dtw->_hadj)); + dtw->_hscrollbar->set_name("HorizontalScrollbar"); + dtw->_canvas_tbl->attach(*dtw->_hscrollbar, 1, 2, 1, 1); + + // By packing the sticky zoom button and vertical scrollbar in a box it allows the canvas to + // expand fully to the top if the rulers are hidden. + // (Otherwise, the canvas is pushed down by the height of the sticky zoom button.) + + // Vertical Scrollbar box + dtw->_vscrollbar_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + dtw->_canvas_tbl->attach(*dtw->_vscrollbar_box, 2, 0, 1, 2); + + // Sticky zoom button + dtw->_sticky_zoom = Gtk::manage(new Inkscape::UI::Widget::Button(GTK_ICON_SIZE_MENU, + Inkscape::UI::Widget::BUTTON_TYPE_TOGGLE, + nullptr, + INKSCAPE_ICON("zoom-original"), + _("Zoom drawing if window size changes"))); + dtw->_sticky_zoom->set_name("StickyZoom"); + dtw->_sticky_zoom->set_active(prefs->getBool("/options/stickyzoom/value")); + dtw->_sticky_zoom->signal_toggled().connect(sigc::mem_fun(dtw, &SPDesktopWidget::sticky_zoom_toggled)); + dtw->_vscrollbar_box->pack_start(*dtw->_sticky_zoom, false, false); + + // Vertical scrollbar + dtw->_vadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0); + dtw->_vscrollbar = Gtk::manage(new Gtk::Scrollbar(dtw->_vadj, Gtk::ORIENTATION_VERTICAL)); + dtw->_vscrollbar->set_name("VerticalScrollbar"); + dtw->_vscrollbar_box->pack_start(*dtw->_vscrollbar, true, true, 0); + + gchar const* tip = ""; + Inkscape::Verb* verb = Inkscape::Verb::get( SP_VERB_VIEW_CMS_TOGGLE ); + if ( verb ) { + SPAction *act = verb->get_action( Inkscape::ActionContext( dtw->viewwidget.view ) ); + if ( act && act->tip ) { + tip = act->tip; + } + } + dtw->_cms_adjust = Gtk::manage(new Inkscape::UI::Widget::Button(GTK_ICON_SIZE_MENU, + Inkscape::UI::Widget::BUTTON_TYPE_TOGGLE, + nullptr, + INKSCAPE_ICON("color-management"), + tip )); + dtw->_cms_adjust->set_name("CMS_Adjust"); + +#if defined(HAVE_LIBLCMS2) + { + Glib::ustring current = prefs->getString("/options/displayprofile/uri"); + bool enabled = current.length() > 0; + dtw->cms_adjust_set_sensitive(enabled ); + if ( enabled ) { + bool active = prefs->getBool("/options/displayprofile/enable"); + if ( active ) { + dtw->_cms_adjust->toggle_set_down(true); + } + } + } + g_signal_connect_after( G_OBJECT(dtw->_cms_adjust->gobj()), "clicked", G_CALLBACK(SPDesktopWidget::cms_adjust_toggled), dtw ); +#else + dtw->cms_adjust_set_sensitive(false); +#endif // defined(HAVE_LIBLCMS2) + + dtw->_canvas_tbl->attach(*dtw->_cms_adjust, 2, 2, 1, 1); + { + if (!watcher) { + watcher = new CMSPrefWatcher(); + } + watcher->add(dtw); + } + /* Canvas */ + dtw->_canvas = SP_CANVAS(SPCanvas::createAA()); +#if defined(HAVE_LIBLCMS2) + dtw->_canvas->_enable_cms_display_adj = prefs->getBool("/options/displayprofile/enable"); +#endif // defined(HAVE_LIBLCMS2) + gtk_widget_set_can_focus (GTK_WIDGET (dtw->_canvas), TRUE); + + dtw->_hruler->add_track_widget(*Glib::wrap(GTK_WIDGET(dtw->_canvas))); + dtw->_vruler->add_track_widget(*Glib::wrap(GTK_WIDGET(dtw->_canvas))); + + auto css_provider = gtk_css_provider_new(); + auto style_context = gtk_widget_get_style_context(GTK_WIDGET(dtw->_canvas)); + + gtk_css_provider_load_from_data(css_provider, + "SPCanvas {\n" + " background-color: white;\n" + "}\n", + -1, nullptr); + + gtk_style_context_add_provider(style_context, + GTK_STYLE_PROVIDER(css_provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); + g_signal_connect(G_OBJECT(dtw->_canvas), "event", G_CALLBACK(SPDesktopWidget::event), dtw); + + gtk_widget_set_hexpand(GTK_WIDGET(dtw->_canvas), TRUE); + gtk_widget_set_vexpand(GTK_WIDGET(dtw->_canvas), TRUE); + dtw->_canvas_tbl->attach(*Glib::wrap(GTK_WIDGET(dtw->_canvas)), 1, 1, 1, 1); + + /* Dock */ + bool create_dock = + prefs->getIntLimited("/options/dialogtype/value", Inkscape::UI::Dialog::FLOATING, 0, 1) == + Inkscape::UI::Dialog::DOCK; + + if (create_dock) { + dtw->_dock = new Inkscape::UI::Widget::Dock(); + auto paned = new Gtk::Paned(); + paned->set_name("Canvas_and_Dock"); + + paned->pack1(*dtw->_canvas_tbl); + paned->pack2(dtw->_dock->getWidget(), Gtk::FILL); + + /* Prevent the paned from catching F6 and F8 by unsetting the default callbacks */ + if (GtkPanedClass *paned_class = GTK_PANED_CLASS (G_OBJECT_GET_CLASS (paned->gobj()))) { + paned_class->cycle_child_focus = nullptr; + paned_class->cycle_handle_focus = nullptr; + } + + paned->set_hexpand(true); + paned->set_vexpand(true); + tbl_wrapper->attach(*paned, 1, 1, 1, 1); + } else { + dtw->_canvas_tbl->set_hexpand(true); + dtw->_canvas_tbl->set_vexpand(true); + tbl_wrapper->attach(*(dtw->_canvas_tbl), 1, 1, 1, 1); + } + + // connect scrollbar signals + dtw->_hadj->signal_value_changed().connect(sigc::mem_fun(dtw, &SPDesktopWidget::on_adjustment_value_changed)); + dtw->_vadj->signal_value_changed().connect(sigc::mem_fun(dtw, &SPDesktopWidget::on_adjustment_value_changed)); + + // --------------- Status Tool Bar ------------------// + + // Selected Style (Fill/Stroke/Opacity) + dtw->_selected_style = new Inkscape::UI::Widget::SelectedStyle(true); + dtw->_statusbar->pack_start(*dtw->_selected_style, false, false); + + // Separator + dtw->_statusbar->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_VERTICAL)), + false, false); + + // Layer Selector + dtw->layer_selector = new Inkscape::UI::Widget::LayerSelector(nullptr); + // FIXME: need to unreference on container destruction to avoid leak + dtw->layer_selector->reference(); + dtw->_statusbar->pack_start(*dtw->layer_selector, false, false, 1); + + // Select Status + dtw->_select_status = Gtk::manage(new Gtk::Label()); + dtw->_select_status->set_name("SelectStatus"); + dtw->_select_status->set_ellipsize(Pango::ELLIPSIZE_END); + dtw->_select_status->set_line_wrap(true); + dtw->_select_status->set_lines(2); + dtw->_select_status->set_halign(Gtk::ALIGN_START); + dtw->_select_status->set_size_request(1, -1); + + // Display the initial welcome message in the statusbar + dtw->_select_status->set_markup(_("<b>Welcome to Inkscape!</b> Use shape or freehand tools to create objects; use selector (arrow) to move or transform them.")); + + dtw->_statusbar->pack_start(*dtw->_select_status, true, true); + + + // Zoom status spinbutton --------------- + auto zoom_adj = Gtk::Adjustment::create(100.0, log(SP_DESKTOP_ZOOM_MIN)/log(2), log(SP_DESKTOP_ZOOM_MAX)/log(2), 0.1); + dtw->_zoom_status = Gtk::manage(new Gtk::SpinButton(zoom_adj)); + + dtw->_zoom_status->set_data("dtw", dtw->_canvas); + dtw->_zoom_status->set_tooltip_text(_("Zoom")); + dtw->_zoom_status->set_size_request(STATUS_ZOOM_WIDTH, -1); + dtw->_zoom_status->set_width_chars(6); + dtw->_zoom_status->set_numeric(false); + dtw->_zoom_status->set_update_policy(Gtk::UPDATE_ALWAYS); + + // Callbacks + dtw->_zoom_status_input_connection = dtw->_zoom_status->signal_input().connect(sigc::mem_fun(dtw, &SPDesktopWidget::zoom_input)); + dtw->_zoom_status_output_connection = dtw->_zoom_status->signal_output().connect(sigc::mem_fun(dtw, &SPDesktopWidget::zoom_output)); + g_signal_connect (G_OBJECT (dtw->_zoom_status->gobj()), "focus-in-event", G_CALLBACK (spinbutton_focus_in), dtw->_zoom_status->gobj()); + g_signal_connect (G_OBJECT (dtw->_zoom_status->gobj()), "key-press-event", G_CALLBACK (spinbutton_keypress), dtw->_zoom_status->gobj()); + dtw->_zoom_status_value_changed_connection = dtw->_zoom_status->signal_value_changed().connect(sigc::mem_fun(dtw, &SPDesktopWidget::zoom_value_changed)); + dtw->_zoom_status_populate_popup_connection = dtw->_zoom_status->signal_populate_popup().connect(sigc::mem_fun(dtw, &SPDesktopWidget::zoom_populate_popup)); + + // Style + auto css_provider_spinbutton = Gtk::CssProvider::create(); + css_provider_spinbutton->load_from_data("* { padding-left: 2px; padding-right: 2px; padding-top: 0px; padding-bottom: 0px;}"); // Shouldn't this be in a style sheet? Used also by rotate. + + dtw->_zoom_status->set_name("ZoomStatus"); + auto context_zoom = dtw->_zoom_status->get_style_context(); + context_zoom->add_provider(css_provider_spinbutton, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + // Rotate status spinbutton --------------- + auto rotation_adj = Gtk::Adjustment::create(0, -360.0, 360.0, 1.0); + dtw->_rotation_status = Gtk::manage(new Gtk::SpinButton(rotation_adj)); + dtw->_rotation_status->set_data("dtw", dtw->_canvas); + dtw->_rotation_status->set_tooltip_text(_("Rotation. (Also Ctrl+Shift+Scroll)")); + dtw->_rotation_status->set_size_request(STATUS_ROTATION_WIDTH, -1); + dtw->_rotation_status->set_width_chars(7); + dtw->_rotation_status->set_numeric(false); + dtw->_rotation_status->set_digits(2); + dtw->_rotation_status->set_increments(1.0, 15.0); + dtw->_rotation_status->set_update_policy(Gtk::UPDATE_ALWAYS); + + // Callbacks + dtw->_rotation_status_input_connection = dtw->_rotation_status->signal_input().connect(sigc::mem_fun(dtw, &SPDesktopWidget::rotation_input)); + dtw->_rotation_status_output_connection = dtw->_rotation_status->signal_output().connect(sigc::mem_fun(dtw, &SPDesktopWidget::rotation_output)); + g_signal_connect (G_OBJECT (dtw->_rotation_status->gobj()), "focus-in-event", G_CALLBACK (spinbutton_focus_in), dtw->_rotation_status->gobj()); + g_signal_connect (G_OBJECT (dtw->_rotation_status->gobj()), "key-press-event", G_CALLBACK (spinbutton_keypress), dtw->_rotation_status->gobj()); + dtw->_rotation_status_value_changed_connection = dtw->_rotation_status->signal_value_changed().connect(sigc::mem_fun(dtw, &SPDesktopWidget::rotation_value_changed)); + dtw->_rotation_status_populate_popup_connection = dtw->_rotation_status->signal_populate_popup().connect(sigc::mem_fun(dtw, &SPDesktopWidget::rotation_populate_popup)); + + // Style + dtw->_rotation_status->set_name("RotationStatus"); + auto context_rotation = dtw->_rotation_status->get_style_context(); + context_rotation->add_provider(css_provider_spinbutton, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + + // Cursor coordinates + dtw->_coord_status = Gtk::manage(new Gtk::Grid()); + dtw->_coord_status->set_name("CoordinateAndZStatus"); + dtw->_coord_status->set_row_spacing(0); + dtw->_coord_status->set_column_spacing(10); + dtw->_coord_status->set_margin_end(10); + auto sep = Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_VERTICAL)); + sep->set_name("CoordinateSeparator"); + dtw->_coord_status->attach(*sep, 0, 0, 1, 2); + + dtw->_coord_status->set_tooltip_text(_("Cursor coordinates")); + auto label_x = Gtk::manage(new Gtk::Label(_("X:"))); + auto label_y = Gtk::manage(new Gtk::Label(_("Y:"))); + label_x->set_halign(Gtk::ALIGN_START); + label_y->set_halign(Gtk::ALIGN_START); + dtw->_coord_status->attach(*label_x, 1, 0, 1, 1); + dtw->_coord_status->attach(*label_y, 1, 1, 1, 1); + dtw->_coord_status_x = Gtk::manage(new Gtk::Label()); + dtw->_coord_status_y = Gtk::manage(new Gtk::Label()); + dtw->_coord_status_x->set_name("CoordinateStatusX"); + dtw->_coord_status_y->set_name("CoordinateStatusY"); + dtw->_coord_status_x->set_markup(" 0.00 "); + dtw->_coord_status_y->set_markup(" 0.00 "); + + auto label_z = Gtk::manage(new Gtk::Label(_("Z:"))); + label_z->set_name("ZLabel"); + auto label_r = Gtk::manage(new Gtk::Label(_("R:"))); + label_r->set_name("RLabel"); + + dtw->_coord_status_x->set_halign(Gtk::ALIGN_END); + dtw->_coord_status_y->set_halign(Gtk::ALIGN_END); + dtw->_coord_status->attach(*dtw->_coord_status_x, 2, 0, 1, 1); + dtw->_coord_status->attach(*dtw->_coord_status_y, 2, 1, 1, 1); + + dtw->_coord_status->attach(*label_z, 3, 0, 1, 2); + dtw->_coord_status->attach(*dtw->_zoom_status, 4, 0, 1, 2); + + dtw->_coord_status->attach(*label_r, 5, 0, 1, 2); + dtw->_coord_status->attach(*dtw->_rotation_status, 6, 0, 1, 2); + + dtw->_statusbar->pack_end(*dtw->_coord_status, false, false); + + // --------------- Color Management ---------------- // + dtw->_tracker = ege_color_prof_tracker_new(GTK_WIDGET(dtw->layer_selector->gobj())); +#if defined(HAVE_LIBLCMS2) + bool fromDisplay = prefs->getBool( "/options/displayprofile/from_display"); + if ( fromDisplay ) { + Glib::ustring id = Inkscape::CMSSystem::getDisplayId( 0 ); + + bool enabled = false; + dtw->_canvas->_cms_key = id; + enabled = !dtw->_canvas->_cms_key.empty(); + dtw->cms_adjust_set_sensitive(enabled); + } + g_signal_connect( G_OBJECT(dtw->_tracker), "changed", G_CALLBACK(SPDesktopWidget::color_profile_event), dtw ); +#endif // defined(HAVE_LIBLCMS2) + + // ------------------ Finish Up -------------------- // + dtw->_vbox->show_all(); + + gtk_widget_grab_focus (GTK_WIDGET(dtw->_canvas)); + + // If this is the first desktop created, report the time it takes to show up + if ( overallTimer ) { + if ( prefs->getBool("/dialogs/debug/trackAppear", false) ) { + // Time tracker takes ownership of the timer. + AppearTimeTracker *tracker = new AppearTimeTracker(overallTimer, GTK_WIDGET(dtw), "first SPDesktopWidget"); + tracker->setAutodelete(true); + } else { + g_timer_destroy(overallTimer); + } + overallTimer = nullptr; + } + // Ensure that ruler ranges are updated correctly whenever the canvas table + // is resized + dtw->_canvas_tbl_size_allocate_connection = dtw->_canvas_tbl->signal_size_allocate().connect(sigc::mem_fun(dtw, &SPDesktopWidget::canvas_tbl_size_allocate)); +} + +/** + * Called before SPDesktopWidget destruction. + */ +void +SPDesktopWidget::dispose(GObject *object) +{ + SPDesktopWidget *dtw = SP_DESKTOP_WIDGET (object); + + if (dtw == nullptr) { + return; + } + + UXManager::getInstance()->delTrack(dtw); + + if (dtw->desktop) { + if ( watcher ) { + watcher->remove(dtw); + } + + // Zoom + dtw->_zoom_status_input_connection.disconnect(); + dtw->_zoom_status_output_connection.disconnect(); + g_signal_handlers_disconnect_matched (G_OBJECT (dtw->_zoom_status->gobj()), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, dtw->_zoom_status); + dtw->_zoom_status_value_changed_connection.disconnect(); + dtw->_zoom_status_populate_popup_connection.disconnect(); + + // Rotation + dtw->_rotation_status_input_connection.disconnect(); + dtw->_rotation_status_output_connection.disconnect(); + g_signal_handlers_disconnect_matched (G_OBJECT (dtw->_rotation_status->gobj()), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, dtw->_rotation_status); + dtw->_rotation_status_value_changed_connection.disconnect(); + dtw->_rotation_status_populate_popup_connection.disconnect(); + + // Canvas + g_signal_handlers_disconnect_by_func (G_OBJECT (dtw->_canvas), (gpointer) G_CALLBACK (SPDesktopWidget::event), dtw); + dtw->_canvas_tbl_size_allocate_connection.disconnect(); + + dtw->layer_selector->setDesktop(nullptr); + dtw->layer_selector->unreference(); + INKSCAPE.remove_desktop(dtw->desktop); // clears selection and event_context + dtw->modified_connection.disconnect(); + dtw->desktop->destroy(); + Inkscape::GC::release (dtw->desktop); + dtw->desktop = nullptr; + } + + dtw->modified_connection.~connection(); + + if (G_OBJECT_CLASS (dtw_parent_class)->dispose) { + (* G_OBJECT_CLASS (dtw_parent_class)->dispose) (object); + } +} + +/** + * Set the title in the desktop-window (if desktop has an own window). + * + * The title has form file name: desktop number - Inkscape. + * The desktop number is only shown if it's 2 or higher, + */ +void +SPDesktopWidget::updateTitle(gchar const* uri) +{ + if (window) { + + SPDocument *doc = this->desktop->doc(); + + std::string Name; + if (doc->isModifiedSinceSave()) { + Name += "*"; + } + + Name += uri; + + if (desktop->number > 1) { + Name += ": "; + Name += std::to_string(desktop->number); + } + Name += " ("; + + if (desktop->getMode() == Inkscape::RENDERMODE_OUTLINE) { + Name += N_("outline"); + } else if (desktop->getMode() == Inkscape::RENDERMODE_NO_FILTERS) { + Name += N_("no filters"); + } else if (desktop->getMode() == Inkscape::RENDERMODE_VISIBLE_HAIRLINES) { + Name += N_("visible hairlines"); + } + + if (desktop->getColorMode() != Inkscape::COLORMODE_NORMAL && + desktop->getMode() != Inkscape::RENDERMODE_NORMAL) { + Name += ", "; + } + + if (desktop->getColorMode() == Inkscape::COLORMODE_GRAYSCALE) { + Name += N_("grayscale"); + } else if (desktop->getColorMode() == Inkscape::COLORMODE_PRINT_COLORS_PREVIEW) { + Name += N_("print colors preview"); + } + + if (*Name.rbegin() == '(') { // Can not use C++11 .back() or .pop_back() with ustring! + Name.erase(Name.size() - 2); + } else { + Name += ")"; + } + + Name += " - Inkscape"; + + // Name += " ("; + // Name += Inkscape::version_string; + // Name += ")"; + + window->set_title (Name); + } +} + +Inkscape::UI::Widget::Dock* +SPDesktopWidget::getDock() +{ + return _dock; +} + +/** + * Callback to allocate space for desktop widget. + */ +static void +sp_desktop_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + SPDesktopWidget *dtw = SP_DESKTOP_WIDGET (widget); + GtkAllocation widg_allocation; + gtk_widget_get_allocation(widget, &widg_allocation); + + if ((allocation->x == widg_allocation.x) && + (allocation->y == widg_allocation.y) && + (allocation->width == widg_allocation.width) && + (allocation->height == widg_allocation.height)) { + if (GTK_WIDGET_CLASS (dtw_parent_class)->size_allocate) + GTK_WIDGET_CLASS (dtw_parent_class)->size_allocate (widget, allocation); + return; + } + + if (gtk_widget_get_realized (widget)) { + Geom::Rect const area = dtw->desktop->get_display_area(); + Geom::Rect const d_canvas = dtw->desktop->getCanvas()->getViewbox(); + Geom::Point midpoint = dtw->desktop->w2d(d_canvas.midpoint()); + + double zoom = dtw->desktop->current_zoom(); + + if (GTK_WIDGET_CLASS(dtw_parent_class)->size_allocate) { + GTK_WIDGET_CLASS(dtw_parent_class)->size_allocate (widget, allocation); + } + + if (dtw->get_sticky_zoom_active()) { + /* Find new visible area */ + Geom::Rect newarea = dtw->desktop->get_display_area(); + /* Calculate adjusted zoom */ + double oldshortside = MIN( area.width(), area.height()); + double newshortside = MIN(newarea.width(), newarea.height()); + zoom *= newshortside / oldshortside; + } + dtw->desktop->zoom_absolute_center_point (midpoint, zoom); + + // TODO - Should call show_dialogs() from sp_namedview_window_from_document only. + // But delaying the call to here solves dock sizing issues on OS X, (see #171579) + dtw->desktop->show_dialogs(); + + } else { + if (GTK_WIDGET_CLASS (dtw_parent_class)->size_allocate) { + GTK_WIDGET_CLASS (dtw_parent_class)->size_allocate (widget, allocation); + } +// this->size_allocate (widget, allocation); + } +} + +#ifdef GDK_WINDOWING_QUARTZ +static GtkMenuItem *_get_help_menu(GtkMenuShell *menu) +{ + // Assume "Help" is the last child in menu + GtkMenuItem *last = nullptr; + auto callback = [](GtkWidget *widget, gpointer data) { + *static_cast<GtkMenuItem **>(data) = GTK_MENU_ITEM(widget); + }; + gtk_container_foreach(GTK_CONTAINER(menu), callback, &last); + return last; +} +#endif + +/** + * Callback to realize desktop widget. + */ +static void +sp_desktop_widget_realize (GtkWidget *widget) +{ + + SPDesktopWidget *dtw = SP_DESKTOP_WIDGET (widget); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (GTK_WIDGET_CLASS (dtw_parent_class)->realize) + (* GTK_WIDGET_CLASS (dtw_parent_class)->realize) (widget); + + Geom::Rect d = Geom::Rect::from_xywh(Geom::Point(0,0), (dtw->desktop->doc())->getDimensions()); + + if (d.width() < 1.0 || d.height() < 1.0) return; + + dtw->desktop->set_display_area (d, 10); + + dtw->updateNamedview(); + gchar *gtkThemeName; + gboolean gtkApplicationPreferDarkTheme; + GtkSettings *settings = gtk_settings_get_default(); + Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel(); + if (settings && window) { + g_object_get(settings, "gtk-theme-name", >kThemeName, NULL); + g_object_get(settings, "gtk-application-prefer-dark-theme", >kApplicationPreferDarkTheme, NULL); + bool dark = Glib::ustring(gtkThemeName).find(":dark") != std::string::npos; + if (!dark) { + Glib::RefPtr<Gtk::StyleContext> stylecontext = window->get_style_context(); + Gdk::RGBA rgba; + bool background_set = stylecontext->lookup_color("theme_bg_color", rgba); + if (background_set && (0.299 * rgba.get_red() + 0.587 * rgba.get_green() + 0.114 * rgba.get_blue()) < 0.5) { + dark = true; + } + } + if (dark) { + window->get_style_context()->add_class("dark"); + window->get_style_context()->remove_class("bright"); + } else { + window->get_style_context()->add_class("bright"); + window->get_style_context()->remove_class("dark"); + } + if (prefs->getBool("/theme/symbolicIcons", false)) { + window->get_style_context()->add_class("symbolic"); + window->get_style_context()->remove_class("regular"); + } else { + window->get_style_context()->add_class("regular"); + window->get_style_context()->remove_class("symbolic"); + } + INKSCAPE.signal_change_theme.emit(); + } + +#ifdef GDK_WINDOWING_QUARTZ + // native macOS menu + auto osxapp = gtkosx_application_get(); + auto menushell = static_cast<Gtk::MenuShell *>(dtw->menubar()); + if (osxapp && menushell && window) { + menushell->set_parent(*window); + gtkosx_application_set_menu_bar(osxapp, menushell->gobj()); + // using quartz accelerators gives menu shortcuts priority over everything else, + // messes up text input because Inkscape has single key shortcuts (e.g. 1-6). + gtkosx_application_set_use_quartz_accelerators(osxapp, false); + gtkosx_application_set_help_menu(osxapp, _get_help_menu(menushell->gobj())); + + // Window menu disabled because hidden windows which are brought back + // from the menu are non-functional. + // https://gitlab.com/inkscape/inkscape/-/issues/1105 +#if 0 + gtkosx_application_set_window_menu(osxapp, nullptr); +#endif + + // move some items to "Inkscape" menu + unsigned app_menu_verbs[] = { + SP_VERB_NONE, + SP_VERB_DIALOG_INPUT, + SP_VERB_DIALOG_DISPLAY, + SP_VERB_NONE, + SP_VERB_HELP_ABOUT, + }; + for (auto verb : app_menu_verbs) { + GtkWidget *menuitem = nullptr; + if (verb == SP_VERB_NONE) { + menuitem = gtk_separator_menu_item_new(); + } else if (auto item = get_menu_item_for_verb(verb, dtw->desktop)) { + menuitem = static_cast<Gtk::Widget *>(item)->gobj(); + } else { + continue; + } + // Don't use index 0 because it appends the app name. Index 1 + // seems to work perfectly with inserting items in reverse order. + gtkosx_application_insert_app_menu_item(osxapp, menuitem, 1); + } + } +#endif +} + +/* This is just to provide access to common functionality from sp_desktop_widget_realize() above + as well as from SPDesktop::change_document() */ +void SPDesktopWidget::updateNamedview() +{ + // Listen on namedview modification + // originally (prior to the sigc++ conversion) the signal was simply + // connected twice rather than disconnecting the first connection + modified_connection.disconnect(); + + modified_connection = desktop->namedview->connectModified(sigc::mem_fun(*this, &SPDesktopWidget::namedviewModified)); + namedviewModified(desktop->namedview, SP_OBJECT_MODIFIED_FLAG); + + updateTitle( desktop->doc()->getDocumentName() ); +} + +/** + * Callback to handle desktop widget event. + */ +gint +SPDesktopWidget::event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw) +{ + if (event->type == GDK_BUTTON_PRESS) { + // defocus any spinbuttons + gtk_widget_grab_focus (GTK_WIDGET(dtw->_canvas)); + } + + if ((event->type == GDK_BUTTON_PRESS) && (event->button.button == 3)) { + if (event->button.state & GDK_SHIFT_MASK) { + sp_canvas_arena_set_sticky (SP_CANVAS_ARENA (dtw->desktop->drawing), TRUE); + } else { + sp_canvas_arena_set_sticky (SP_CANVAS_ARENA (dtw->desktop->drawing), FALSE); + } + } + + if (GTK_WIDGET_CLASS (dtw_parent_class)->event) { + return (* GTK_WIDGET_CLASS (dtw_parent_class)->event) (widget, event); + } else { + // The key press/release events need to be passed to desktop handler explicitly, + // because otherwise the event contexts only receive key events when the mouse cursor + // is over the canvas. This redirection is only done for key events and only if there's no + // current item on the canvas, because item events and all mouse events are caught + // and passed on by the canvas acetate (I think). --bb + + if ((event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) + && !dtw->_canvas->_current_item) { + return sp_desktop_root_handler (nullptr, event, dtw->desktop); + } + } + + return FALSE; +} + +#if defined(HAVE_LIBLCMS2) +void +SPDesktopWidget::color_profile_event(EgeColorProfTracker */*tracker*/, SPDesktopWidget *dtw) +{ + // Handle profile changes + GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET(dtw)); + GdkWindow *window = gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(dtw))); + + // Figure out the ID for the monitor + auto display = gdk_display_get_default(); + auto monitor = gdk_display_get_monitor_at_window(display, window); + + int n_monitors = gdk_display_get_n_monitors(display); + + int monitorNum = -1; + + // Now loop through the set of monitors and figure out whether this monitor matches + for (int i_monitor = 0; i_monitor < n_monitors; ++i_monitor) { + auto monitor_at_index = gdk_display_get_monitor(display, i_monitor); + if(monitor_at_index == monitor) monitorNum = i_monitor; + } + + Glib::ustring id = Inkscape::CMSSystem::getDisplayId( monitorNum ); + bool enabled = false; + dtw->_canvas->_cms_key = id; + dtw->requestCanvasUpdate(); + enabled = !dtw->_canvas->_cms_key.empty(); + dtw->cms_adjust_set_sensitive(enabled); +} +#else // defined(HAVE_LIBLCMS2) +void sp_dtw_color_profile_event(EgeColorProfTracker */*tracker*/, SPDesktopWidget * /*dtw*/) +{ +} +#endif // defined(HAVE_LIBLCMS2) + +void +SPDesktopWidget::update_guides_lock() +{ + bool down = _guides_lock->get_active(); + + auto doc = desktop->getDocument(); + auto nv = desktop->getNamedView(); + auto repr = nv->getRepr(); + + if ( down != nv->lockguides ) { + nv->lockguides = down; + sp_namedview_guides_toggle_lock(doc, nv); + if (down) { + setMessage (Inkscape::NORMAL_MESSAGE, _("Locked all guides")); + } else { + setMessage (Inkscape::NORMAL_MESSAGE, _("Unlocked all guides")); + } + } +} + +#if defined(HAVE_LIBLCMS2) +void +SPDesktopWidget::cms_adjust_toggled( GtkWidget */*button*/, gpointer data ) +{ + SPDesktopWidget *dtw = SP_DESKTOP_WIDGET(data); + + bool down = dtw->_cms_adjust->get_active(); + if ( down != dtw->_canvas->_enable_cms_display_adj ) { + dtw->_canvas->_enable_cms_display_adj = down; + dtw->desktop->redrawDesktop(); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/displayprofile/enable", down); + if (down) { + dtw->setMessage (Inkscape::NORMAL_MESSAGE, _("Color-managed display is <b>enabled</b> in this window")); + } else { + dtw->setMessage (Inkscape::NORMAL_MESSAGE, _("Color-managed display is <b>disabled</b> in this window")); + } + } +} +#endif // defined(HAVE_LIBLCMS2) + +void +SPDesktopWidget::cms_adjust_set_sensitive(bool enabled) +{ + Inkscape::Verb* verb = Inkscape::Verb::get( SP_VERB_VIEW_CMS_TOGGLE ); + if ( verb ) { + SPAction *act = verb->get_action( Inkscape::ActionContext(viewwidget.view) ); + if ( act ) { + sp_action_set_sensitive( act, enabled ); + } + } + _cms_adjust->set_sensitive(enabled); +} + +void +sp_dtw_desktop_activate (SPDesktopWidget */*dtw*/) +{ + /* update active desktop indicator */ +} + +void +sp_dtw_desktop_deactivate (SPDesktopWidget */*dtw*/) +{ + /* update inactive desktop indicator */ +} + +/** + * Shuts down the desktop object for the view being closed. It checks + * to see if the document has been edited, and if so prompts the user + * to save, discard, or cancel. Returns TRUE if the shutdown operation + * is cancelled or if the save is cancelled or fails, FALSE otherwise. + */ +bool +SPDesktopWidget::shutdown() +{ + g_assert(desktop != nullptr); + + if (INKSCAPE.sole_desktop_for_document(*desktop)) { + SPDocument *doc = desktop->doc(); + if (doc->isModifiedSinceSave()) { + Gtk::Window *toplevel_window = Glib::wrap(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(this)))); + Glib::ustring message = g_markup_printf_escaped( + _("<span weight=\"bold\" size=\"larger\">Save changes to document \"%s\" before closing?</span>\n\n" + "If you close without saving, your changes will be discarded."), + doc->getDocumentName()); + Gtk::MessageDialog dialog = Gtk::MessageDialog(*toplevel_window, message, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE); + dialog.property_destroy_with_parent() = true; + + // fix for bug lp:168809 + Gtk::Container *ma = dialog.get_message_area(); + std::vector<Gtk::Widget*> ma_labels = ma->get_children(); + ma_labels[0]->set_can_focus(false); + + Gtk::Button close_button(_("Close _without saving"), true); + close_button.show(); + dialog.add_action_widget(close_button, Gtk::RESPONSE_NO); + + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + dialog.add_button(_("_Save"), Gtk::RESPONSE_YES); + dialog.set_default_response(Gtk::RESPONSE_YES); + + gint response = dialog.run(); + + switch (response) { + case GTK_RESPONSE_YES: + { + doc->doRef(); + sp_namedview_document_from_window(desktop); + if (sp_file_save_document(*window, doc)) { + doc->doUnref(); + } else { // save dialog cancelled or save failed + doc->doUnref(); + return TRUE; + } + + break; + } + case GTK_RESPONSE_NO: + break; + default: // cancel pressed, or dialog was closed + return TRUE; + break; + } + } + /* Code to check data loss */ + bool allow_data_loss = FALSE; + while (doc->getReprRoot()->attribute("inkscape:dataloss") != nullptr && allow_data_loss == FALSE) { + Gtk::Window *toplevel_window = Glib::wrap(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(this)))); + Glib::ustring message = g_markup_printf_escaped( + _("<span weight=\"bold\" size=\"larger\">The file \"%s\" was saved with a format that may cause data loss!</span>\n\n" + "Do you want to save this file as Inkscape SVG?"), + doc->getDocumentName() ? doc->getDocumentName() : "Unnamed"); + Gtk::MessageDialog dialog = Gtk::MessageDialog(*toplevel_window, message, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE); + dialog.property_destroy_with_parent() = true; + + // fix for bug lp:168809 + Gtk::Container *ma = dialog.get_message_area(); + std::vector<Gtk::Widget*> ma_labels = ma->get_children(); + ma_labels[0]->set_can_focus(false); + + Gtk::Button close_button(_("Close _without saving"), true); + close_button.show(); + dialog.add_action_widget(close_button, Gtk::RESPONSE_NO); + + dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + + Gtk::Button save_button(_("_Save as Inkscape SVG"), true); + save_button.set_can_default(true); + save_button.show(); + dialog.add_action_widget(save_button, Gtk::RESPONSE_YES); + dialog.set_default_response(Gtk::RESPONSE_YES); + + gint response = dialog.run(); + + switch (response) { + case GTK_RESPONSE_YES: + { + doc->doRef(); + + if (sp_file_save_dialog(*window, doc, Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG)) { + doc->doUnref(); + } else { // save dialog cancelled or save failed + doc->doUnref(); + return TRUE; + } + + break; + } + case GTK_RESPONSE_NO: + allow_data_loss = TRUE; + break; + default: // cancel pressed, or dialog was closed + return TRUE; + break; + } + } + } + + /* Save window geometry to prefs for use as a default. + * Use depends on setting of "options.savewindowgeometry". + * But we save the info here regardless of the setting. + */ + storeDesktopPosition(); + + return FALSE; +} + +/** + * \store dessktop position + */ +void SPDesktopWidget::storeDesktopPosition() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool maxed = desktop->is_maximized(); + bool full = desktop->is_fullscreen(); + prefs->setBool("/desktop/geometry/fullscreen", full); + prefs->setBool("/desktop/geometry/maximized", maxed); + gint w, h, x, y; + desktop->getWindowGeometry(x, y, w, h); + // Don't save geom for maximized windows. It + // just tells you the current maximized size, which is not + // as useful as whatever value it had previously. + if (!maxed && !full) { + prefs->setInt("/desktop/geometry/width", w); + prefs->setInt("/desktop/geometry/height", h); + prefs->setInt("/desktop/geometry/x", x); + prefs->setInt("/desktop/geometry/y", y); + } +} + +/** + * \pre this->desktop->main != 0 + */ +void +SPDesktopWidget::requestCanvasUpdate() { + // ^^ also this->desktop != 0 + g_return_if_fail(this->desktop != nullptr); + g_return_if_fail(this->desktop->main != nullptr); + gtk_widget_queue_draw (GTK_WIDGET (SP_CANVAS_ITEM (this->desktop->main)->canvas)); +} + +void +SPDesktopWidget::requestCanvasUpdateAndWait() { + requestCanvasUpdate(); + + while (gtk_events_pending()) + gtk_main_iteration_do(FALSE); + +} + +void +SPDesktopWidget::enableInteraction() +{ + g_return_if_fail(_interaction_disabled_counter > 0); + + _interaction_disabled_counter--; + + if (_interaction_disabled_counter == 0) { + gtk_widget_set_sensitive(GTK_WIDGET(this), TRUE); + } +} + +void +SPDesktopWidget::disableInteraction() +{ + if (_interaction_disabled_counter == 0) { + gtk_widget_set_sensitive(GTK_WIDGET(this), FALSE); + } + + _interaction_disabled_counter++; +} + +void +SPDesktopWidget::setCoordinateStatus(Geom::Point p) +{ + gchar *cstr; + cstr = g_strdup_printf("%7.2f", _dt2r * p[Geom::X]); + _coord_status_x->set_markup(cstr); + g_free(cstr); + + cstr = g_strdup_printf("%7.2f", _dt2r * p[Geom::Y]); + _coord_status_y->set_markup(cstr); + g_free(cstr); +} + +void +SPDesktopWidget::letZoomGrabFocus() +{ + if (_zoom_status) _zoom_status->grab_focus(); +} + +void +SPDesktopWidget::getWindowGeometry (gint &x, gint &y, gint &w, gint &h) +{ + if (window) + { + window->get_size (w, h); + window->get_position (x, y); + } +} + +void +SPDesktopWidget::setWindowPosition (Geom::Point p) +{ + if (window) + { + window->move (gint(round(p[Geom::X])), gint(round(p[Geom::Y]))); + } +} + +void +SPDesktopWidget::setWindowSize (gint w, gint h) +{ + if (window) + { + window->set_default_size (w, h); + window->resize (w, h); + } +} + +#ifdef __APPLE__ +/** + * This should be a no-op, but on macOS it raises the given transient window to the + * top of other transient windows from the same parent. + */ +static gboolean transient_focus_in_callback(GtkWindow *window, GdkEvent *, gpointer) +{ + gtk_window_set_transient_for(window, gtk_window_get_transient_for(window)); + return FALSE; +} +#endif + +/** + * \note transientizing does not work on windows; when you minimize a document + * and then open it back, only its transient emerges and you cannot access + * the document window. The document window must be restored by rightclicking + * the taskbar button and pressing "Restore" + */ +void +SPDesktopWidget::setWindowTransient (void *p, int transient_policy) +{ + if (window) + { + GtkWindow *w = GTK_WINDOW(window->gobj()); + +#ifdef __APPLE__ + // Workaround for https://gitlab.gnome.org/GNOME/gtk/issues/2436 + // The first time this window is made transient, connect the focus-in event to + // re-transientize the window in order to raise it above other transient windows. + if (gtk_window_get_transient_for(GTK_WINDOW(p)) == nullptr) { + g_signal_connect(GTK_WIDGET(p), "focus-in-event", G_CALLBACK(transient_focus_in_callback), nullptr); + } +#endif + + gtk_window_set_transient_for (GTK_WINDOW(p), w); + + /* + * This enables "aggressive" transientization, + * i.e. dialogs always emerging on top when you switch documents. Note + * however that this breaks "click to raise" policy of a window + * manager because the switched-to document will be raised at once + * (so that its transients also could raise) + */ + if (transient_policy == 2) + // without this, a transient window not always emerges on top + gtk_window_present (w); + } +} + +void +SPDesktopWidget::presentWindow() +{ + GtkWindow *w =GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(this))); + if (w) + gtk_window_present (w); +} + +bool SPDesktopWidget::showInfoDialog( Glib::ustring const &message ) +{ + bool result = false; + Gtk::Window *window = Glib::wrap(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(this)))); + if (window) + { + Gtk::MessageDialog dialog(*window, message, false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK); + dialog.property_destroy_with_parent() = true; + dialog.set_name("InfoDialog"); + dialog.set_title(_("Note:")); // probably want to take this as a parameter. + dialog.run(); + } + return result; +} + +bool SPDesktopWidget::warnDialog (Glib::ustring const &text) +{ + Gtk::MessageDialog dialog (*window, text, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK_CANCEL); + gint response = dialog.run(); + if (response == Gtk::RESPONSE_OK) + return true; + else + return false; +} + +void +SPDesktopWidget::iconify() +{ + GtkWindow *topw = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(_canvas))); + if (GTK_IS_WINDOW(topw)) { + if (desktop->is_iconified()) { + gtk_window_deiconify(topw); + } else { + gtk_window_iconify(topw); + } + } +} + +void +SPDesktopWidget::maximize() +{ + GtkWindow *topw = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(_canvas))); + if (GTK_IS_WINDOW(topw)) { + if (desktop->is_maximized()) { + gtk_window_unmaximize(topw); + } else { + // Save geometry to prefs before maximizing so that + // something useful is stored there, because GTK doesn't maintain + // a separate non-maximized size. + if (!desktop->is_iconified() && !desktop->is_fullscreen()) + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint w = -1; + gint h, x, y; + getWindowGeometry(x, y, w, h); + g_assert(w != -1); + prefs->setInt("/desktop/geometry/width", w); + prefs->setInt("/desktop/geometry/height", h); + prefs->setInt("/desktop/geometry/x", x); + prefs->setInt("/desktop/geometry/y", y); + } + gtk_window_maximize(topw); + } + } +} + +void +SPDesktopWidget::fullscreen() +{ + GtkWindow *topw = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(_canvas))); + if (GTK_IS_WINDOW(topw)) { + if (desktop->is_fullscreen()) { + gtk_window_unfullscreen(topw); + // widget layout is triggered by the resulting window_state_event + } else { + // Save geometry to prefs before maximizing so that + // something useful is stored there, because GTK doesn't maintain + // a separate non-maximized size. + if (!desktop->is_iconified() && !desktop->is_maximized()) + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint w, h, x, y; + getWindowGeometry(x, y, w, h); + prefs->setInt("/desktop/geometry/width", w); + prefs->setInt("/desktop/geometry/height", h); + prefs->setInt("/desktop/geometry/x", x); + prefs->setInt("/desktop/geometry/y", y); + } + gtk_window_fullscreen(topw); + // widget layout is triggered by the resulting window_state_event + } + } +} + +/** + * Hide whatever the user does not want to see in the window + */ +void SPDesktopWidget::layoutWidgets() +{ + SPDesktopWidget *dtw = this; + Glib::ustring pref_root; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (dtw->desktop->is_focusMode()) { + pref_root = "/focus/"; + } else if (dtw->desktop->is_fullscreen()) { + pref_root = "/fullscreen/"; + } else { + pref_root = "/window/"; + } + + if (!prefs->getBool(pref_root + "commands/state", true)) { + gtk_widget_hide (dtw->commands_toolbox); + } else { + gtk_widget_show_all (dtw->commands_toolbox); + } + + if (!prefs->getBool(pref_root + "snaptoolbox/state", true)) { + gtk_widget_hide (dtw->snap_toolbox); + } else { + gtk_widget_show_all (dtw->snap_toolbox); + } + + if (!prefs->getBool(pref_root + "toppanel/state", true)) { + gtk_widget_hide (dtw->aux_toolbox); + } else { + // we cannot just show_all because that will show all tools' panels; + // this is a function from toolbox.cpp that shows only the current tool's panel + ToolboxFactory::showAuxToolbox(dtw->aux_toolbox); + } + + if (!prefs->getBool(pref_root + "toolbox/state", true)) { + gtk_widget_hide (dtw->tool_toolbox); + } else { + gtk_widget_show_all (dtw->tool_toolbox); + } + + if (!prefs->getBool(pref_root + "statusbar/state", true)) { + dtw->_statusbar->hide(); + } else { + dtw->_statusbar->show_all(); + } + + if (!prefs->getBool(pref_root + "panels/state", true)) { + dtw->_panels->hide(); + } else { + dtw->_panels->show_all(); + } + + if (!prefs->getBool(pref_root + "scrollbars/state", true)) { + dtw->_hscrollbar->hide(); + dtw->_vscrollbar_box->hide(); + dtw->_cms_adjust->hide(); + } else { + dtw->_hscrollbar->show_all(); + dtw->_vscrollbar_box->show_all(); + dtw->_cms_adjust->show_all(); + } + + if (!prefs->getBool(pref_root + "rulers/state", true)) { + dtw->_guides_lock->hide(); + dtw->_hruler->hide(); + dtw->_vruler->hide(); + } else { + dtw->_guides_lock->show_all(); + dtw->_hruler->show_all(); + dtw->_vruler->show_all(); + } +} + +Gtk::Toolbar * +SPDesktopWidget::get_toolbar_by_name(const Glib::ustring& name) +{ + // The name is actually attached to the GtkGrid that contains + // the toolbar, so we need to get the grid first + auto widget = sp_search_by_name_recursive(Glib::wrap(aux_toolbox), name); + auto grid = dynamic_cast<Gtk::Grid*>(widget); + + if (!grid) return nullptr; + + auto child = grid->get_child_at(0,0); + auto tb = dynamic_cast<Gtk::Toolbar*>(child); + + return tb; +} + +void +SPDesktopWidget::setToolboxFocusTo (const gchar* label) +{ + // First try looking for a named widget + auto hb = sp_search_by_name_recursive(Glib::wrap(aux_toolbox), label); + + // Fallback to looking for a named data member (deprecated) + if (!hb) { + hb = Glib::wrap(GTK_WIDGET(sp_search_by_data_recursive(aux_toolbox, (gpointer) label))); + } + + if (hb) + { + hb->grab_focus(); + } +} + +void +SPDesktopWidget::setToolboxAdjustmentValue (gchar const *id, double value) +{ + // First try looking for a named widget + auto hb = sp_search_by_name_recursive(Glib::wrap(aux_toolbox), id); + + // Fallback to looking for a named data member (deprecated) + if (!hb) { + hb = Glib::wrap(GTK_WIDGET(sp_search_by_data_recursive(aux_toolbox, (gpointer)id))); + } + + if (hb) { + auto sb = dynamic_cast<Inkscape::UI::Widget::SpinButtonToolItem *>(hb); + auto a = sb->get_adjustment(); + + if(a) a->set_value(value); + } + + else g_warning ("Could not find GtkAdjustment for %s\n", id); +} + + +bool +SPDesktopWidget::isToolboxButtonActive (const gchar* id) +{ + bool isActive = false; + gpointer thing = sp_search_by_data_recursive(aux_toolbox, (gpointer) id); + if ( !thing ) { + //g_message( "Unable to locate item for {%s}", id ); + } else if ( GTK_IS_TOGGLE_BUTTON(thing) ) { + GtkToggleButton *b = GTK_TOGGLE_BUTTON(thing); + isActive = gtk_toggle_button_get_active( b ) != 0; + } else if ( GTK_IS_TOGGLE_ACTION(thing) ) { + GtkToggleAction* act = GTK_TOGGLE_ACTION(thing); + isActive = gtk_toggle_action_get_active( act ) != 0; + } else if ( GTK_IS_TOGGLE_TOOL_BUTTON(thing) ) { + GtkToggleToolButton *b = GTK_TOGGLE_TOOL_BUTTON(thing); + isActive = gtk_toggle_tool_button_get_active( b ) != 0; + } else { + //g_message( "Item for {%s} is of an unsupported type", id ); + } + + return isActive; +} + +void SPDesktopWidget::setToolboxPosition(Glib::ustring const& id, GtkPositionType pos) +{ + // Note - later on these won't be individual member variables. + GtkWidget* toolbox = nullptr; + if (id == "ToolToolbar") { + toolbox = tool_toolbox; + } else if (id == "AuxToolbar") { + toolbox = aux_toolbox; + } else if (id == "CommandsToolbar") { + toolbox = commands_toolbox; + } else if (id == "SnapToolbar") { + toolbox = snap_toolbox; + } + + + if (toolbox) { + switch(pos) { + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + if ( gtk_widget_is_ancestor(toolbox, GTK_WIDGET(_hbox->gobj())) ) { + // Removing a widget can reduce ref count to zero + g_object_ref(G_OBJECT(toolbox)); + _hbox->remove(*Glib::wrap(toolbox)); + _vbox->add(*Glib::wrap(toolbox)); + g_object_unref(G_OBJECT(toolbox)); + + // Function doesn't seem to be in Gtkmm wrapper yet + gtk_box_set_child_packing(_vbox->gobj(), toolbox, FALSE, TRUE, 0, GTK_PACK_START); + } + ToolboxFactory::setOrientation(toolbox, GTK_ORIENTATION_HORIZONTAL); + break; + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + if ( !gtk_widget_is_ancestor(toolbox, GTK_WIDGET(_hbox->gobj())) ) { + g_object_ref(G_OBJECT(toolbox)); + _vbox->remove(*Glib::wrap(toolbox)); + _hbox->add(*Glib::wrap(toolbox)); + g_object_unref(G_OBJECT(toolbox)); + + // Function doesn't seem to be in Gtkmm wrapper yet + gtk_box_set_child_packing(_hbox->gobj(), toolbox, FALSE, TRUE, 0, GTK_PACK_START); + if (pos == GTK_POS_LEFT) { + _hbox->reorder_child(*Glib::wrap(toolbox), 0 ); + } + } + ToolboxFactory::setOrientation(toolbox, GTK_ORIENTATION_VERTICAL); + break; + } + } +} + + +SPDesktopWidget *sp_desktop_widget_new(SPDocument *document) +{ + SPDesktopWidget* dtw = SPDesktopWidget::createInstance(document); + return dtw; +} + +SPDesktopWidget* SPDesktopWidget::createInstance(SPDocument *document) +{ + SPDesktopWidget *dtw = static_cast<SPDesktopWidget*>(g_object_new(SP_TYPE_DESKTOP_WIDGET, nullptr)); + + SPNamedView *namedview = sp_document_namedview(document, nullptr); + + dtw->_dt2r = 1. / namedview->display_units->factor; + + dtw->_ruler_origin = Geom::Point(0,0); //namedview->gridorigin; Why was the grid origin used here? + + dtw->desktop = new SPDesktop(); + dtw->stub = new SPDesktopWidget::WidgetStub (dtw); + dtw->desktop->init (namedview, dtw->_canvas, dtw->stub); + INKSCAPE.add_desktop (dtw->desktop); + + // Add the shape geometry to libavoid for autorouting connectors. + // This needs desktop set for its spacing preferences. + init_avoided_shape_geometry(dtw->desktop); + + dtw->_selected_style->setDesktop(dtw->desktop); + + /* Once desktop is set, we can update rulers */ + dtw->update_rulers(); + + sp_view_widget_set_view (SP_VIEW_WIDGET (dtw), dtw->desktop); + + /* Listen on namedview modification */ + dtw->modified_connection = namedview->connectModified(sigc::mem_fun(*dtw, &SPDesktopWidget::namedviewModified)); + + dtw->layer_selector->setDesktop(dtw->desktop); + + // TEMP + dtw->_menubar = build_menubar(dtw->desktop); + dtw->_menubar->set_name("MenuBar"); + dtw->_menubar->show_all(); + +#ifdef GDK_WINDOWING_QUARTZ + // native macOS menu: do this later because we don't have the window handle yet +#else + dtw->_vbox->pack_start(*dtw->_menubar, false, false); +#endif + + dtw->layoutWidgets(); + + std::vector<GtkWidget *> toolboxes; + toolboxes.push_back(dtw->tool_toolbox); + toolboxes.push_back(dtw->aux_toolbox); + toolboxes.push_back(dtw->commands_toolbox); + toolboxes.push_back(dtw->snap_toolbox); + + dtw->_panels->setDesktop( dtw->desktop ); + + UXManager::getInstance()->addTrack(dtw); + UXManager::getInstance()->connectToDesktop( toolboxes, dtw->desktop ); + + return dtw; +} + + +void +SPDesktopWidget::update_rulers() +{ + Geom::Rect viewbox = desktop->get_display_area(true); + // "true" means: Use integer values of the canvas for calculating the display area, similar + // to the integer values used for positioning the grid lines. (see SPCanvas::scrollTo(), + // where ix and iy are rounded integer values; these values are stored in SPCanvasBuf->rect, + // and used for drawing the grid). By using the integer values here too, the ruler ticks + // will be perfectly aligned to the grid + + double lower_x = _dt2r * (viewbox.left() - _ruler_origin[Geom::X]); + double upper_x = _dt2r * (viewbox.right() - _ruler_origin[Geom::X]); + _hruler->set_range(lower_x, upper_x); + + double lower_y = _dt2r * (viewbox.bottom() - _ruler_origin[Geom::Y]); + double upper_y = _dt2r * (viewbox.top() - _ruler_origin[Geom::Y]); + if (desktop->is_yaxisdown()) { + std::swap(lower_y, upper_y); + } + _vruler->set_range(lower_y, upper_y); +} + + +void SPDesktopWidget::namedviewModified(SPObject *obj, guint flags) +{ + SPNamedView *nv=SP_NAMEDVIEW(obj); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + _dt2r = 1. / nv->display_units->factor; + _ruler_origin = Geom::Point(0,0); //nv->gridorigin; Why was the grid origin used here? + + _vruler->set_unit(nv->getDisplayUnit()); + _hruler->set_unit(nv->getDisplayUnit()); + + /* This loops through all the grandchildren of aux toolbox, + * and for each that it finds, it performs an sp_search_by_data_recursive(), + * looking for widgets that hold some "tracker" data (this is used by + * all toolboxes to refer to the unit selector). The default document units + * is then selected within these unit selectors. + * + * Of course it would be nice to be able to refer to the toolbox and the + * unit selector directly by name, but I don't yet see a way to do that. + * + * This should solve: https://bugs.launchpad.net/inkscape/+bug/362995 + */ + if (GTK_IS_CONTAINER(aux_toolbox)) { + std::vector<Gtk::Widget*> ch = Glib::wrap(GTK_CONTAINER(aux_toolbox))->get_children(); + for (auto i:ch) { + if (GTK_IS_CONTAINER(i->gobj())) { + std::vector<Gtk::Widget*> grch = dynamic_cast<Gtk::Container*>(i)->get_children(); + for (auto j:grch) { + + if (!GTK_IS_WIDGET(j->gobj())) // wasn't a widget + continue; + + // Don't apply to text toolbar. We want to be able to + // use different units for text. (Bug 1562217) + const Glib::ustring name = j->get_name(); + if ( name == "TextToolbar" || name == "MeasureToolbar") + continue; + + gpointer t = sp_search_by_data_recursive(GTK_WIDGET(j->gobj()), (gpointer) "unit-tracker"); + if (t == nullptr) // didn't find any tracker data + continue; + + UnitTracker *tracker = reinterpret_cast<UnitTracker*>( t ); + if (tracker == nullptr) // it's null when inkscape is first opened + continue; + + tracker->setActiveUnit( nv->display_units ); + } // grandchildren + } // if child is a container + } // children + } // if aux_toolbox is a container + + _hruler_box->set_tooltip_text(gettext(nv->display_units->name_plural.c_str())); + _vruler_box->set_tooltip_text(gettext(nv->display_units->name_plural.c_str())); + + update_rulers(); + ToolboxFactory::updateSnapToolbox(this->desktop, nullptr, this->snap_toolbox); + } +} + +void +SPDesktopWidget::on_adjustment_value_changed() +{ + if (update) + return; + + update = 1; + + // Do not call canvas->scrollTo directly... messes up 'offset'. + desktop->scroll_absolute( Geom::Point(_hadj->get_value(), + _vadj->get_value()), false); + + update = 0; +} + +/* we make the desktop window with focus active, signal is connected in interface.c */ +bool SPDesktopWidget::onFocusInEvent(GdkEventFocus*) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/options/bitmapautoreload/value", true)) { + std::vector<SPObject *> imageList = (desktop->doc())->getResourceList("image"); + for (auto it : imageList) { + SPImage* image = SP_IMAGE(it); + image->refresh_if_outdated(); + } + } + + INKSCAPE.activate_desktop (desktop); + + return false; +} + +// ------------------------ Zoom ------------------------ +static gdouble +sp_dtw_zoom_value_to_display (gdouble value) +{ + return floor (10 * (pow (2, value) * 100.0 + 0.05)) / 10; +} + +static gdouble +sp_dtw_zoom_display_to_value (gdouble value) +{ + return log (value / 100.0) / log (2); +} + +int +SPDesktopWidget::zoom_input(double *new_val) +{ + gchar *b = g_strdup(_zoom_status->get_text().c_str()); + + gchar *comma = g_strstr_len (b, -1, ","); + if (comma) { + *comma = '.'; + } + + char *oldlocale = g_strdup (setlocale(LC_NUMERIC, nullptr)); + setlocale (LC_NUMERIC, "C"); + gdouble new_typed = atof (b); + setlocale (LC_NUMERIC, oldlocale); + g_free (oldlocale); + g_free (b); + + *new_val = sp_dtw_zoom_display_to_value (new_typed); + return TRUE; +} + +bool +SPDesktopWidget::zoom_output() +{ + gchar b[64]; + double val = sp_dtw_zoom_value_to_display (_zoom_status->get_value()); + if (val < 10) { + g_snprintf (b, 64, "%4.1f%%", val); + } else { + g_snprintf (b, 64, "%4.0f%%", val); + } + _zoom_status->set_text(b); + return true; +} + +void +SPDesktopWidget::zoom_value_changed() +{ + double const zoom_factor = pow (2, _zoom_status->get_value()); + + // Zoom around center of window + Geom::Rect const d_canvas = desktop->getCanvas()->getViewbox(); + Geom::Point midpoint = desktop->w2d(d_canvas.midpoint()); + _zoom_status_value_changed_connection.block(); + desktop->zoom_absolute_center_point (midpoint, zoom_factor); + _zoom_status_value_changed_connection.unblock(); + + spinbutton_defocus(GTK_WIDGET(_zoom_status->gobj())); +} + +void +SPDesktopWidget::zoom_menu_handler(double factor) +{ + Geom::Rect const d = desktop->get_display_area(); + desktop->zoom_absolute_center_point (d.midpoint(), factor); +} + +void +SPDesktopWidget::zoom_populate_popup(Gtk::Menu *menu) +{ + for ( auto iter : menu->get_children()) { + menu->remove(*iter); + } + + auto item_1000 = Gtk::manage(new Gtk::MenuItem("1000%")); + auto item_500 = Gtk::manage(new Gtk::MenuItem("500%")); + auto item_200 = Gtk::manage(new Gtk::MenuItem("200%")); + auto item_100 = Gtk::manage(new Gtk::MenuItem("100%")); + auto item_50 = Gtk::manage(new Gtk::MenuItem( "50%")); + auto item_25 = Gtk::manage(new Gtk::MenuItem( "25%")); + auto item_10 = Gtk::manage(new Gtk::MenuItem( "10%")); + + item_1000->signal_activate().connect(sigc::bind(sigc::mem_fun(this, &SPDesktopWidget::zoom_menu_handler), 10.00)); + item_500->signal_activate().connect( sigc::bind(sigc::mem_fun(this, &SPDesktopWidget::zoom_menu_handler), 5.00)); + item_200->signal_activate().connect( sigc::bind(sigc::mem_fun(this, &SPDesktopWidget::zoom_menu_handler), 2.00)); + item_100->signal_activate().connect( sigc::bind(sigc::mem_fun(this, &SPDesktopWidget::zoom_menu_handler), 1.00)); + item_50->signal_activate().connect( sigc::bind(sigc::mem_fun(this, &SPDesktopWidget::zoom_menu_handler), 0.50)); + item_25->signal_activate().connect( sigc::bind(sigc::mem_fun(this, &SPDesktopWidget::zoom_menu_handler), 0.25)); + item_10->signal_activate().connect( sigc::bind(sigc::mem_fun(this, &SPDesktopWidget::zoom_menu_handler), 0.10)); + + menu->append(*item_1000); + menu->append(*item_500); + menu->append(*item_200); + menu->append(*item_100); + menu->append(*item_50); + menu->append(*item_25); + menu->append(*item_10); + + auto sep = Gtk::manage(new Gtk::SeparatorMenuItem()); + menu->append(*sep); + + auto item_page = Gtk::manage(new Gtk::MenuItem(_("Page"))); + item_page->signal_activate().connect(sigc::mem_fun(desktop, &SPDesktop::zoom_page)); + menu->append(*item_page); + + auto item_drawing = Gtk::manage(new Gtk::MenuItem(_("Drawing"))); + item_drawing->signal_activate().connect(sigc::mem_fun(desktop, &SPDesktop::zoom_drawing)); + menu->append(*item_drawing); + + auto item_selection = Gtk::manage(new Gtk::MenuItem(_("Selection"))); + item_selection->signal_activate().connect(sigc::mem_fun(desktop, &SPDesktop::zoom_selection)); + menu->append(*item_selection); + + auto item_center_page = Gtk::manage(new Gtk::MenuItem(_("Centre Page"))); + item_center_page->signal_activate().connect(sigc::mem_fun(desktop, &SPDesktop::zoom_center_page)); + menu->append(*item_center_page); + + menu->show_all(); +} + + +void +SPDesktopWidget::sticky_zoom_toggled() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/stickyzoom/value", _sticky_zoom->get_active()); +} + + +void +SPDesktopWidget::update_zoom() +{ + _zoom_status_value_changed_connection.block(); + _zoom_status->set_value(log(desktop->current_zoom()) / log(2)); + _zoom_status->queue_draw(); + _zoom_status_value_changed_connection.unblock(); +} + + +// ---------------------- Rotation ------------------------ +int +SPDesktopWidget::rotation_input(double *new_val) +{ + auto *b = g_strdup(_rotation_status->get_text().c_str()); + + gchar *comma = g_strstr_len (b, -1, ","); + if (comma) { + *comma = '.'; + } + + char *oldlocale = g_strdup (setlocale(LC_NUMERIC, nullptr)); + setlocale (LC_NUMERIC, "C"); + gdouble new_value = atof (b); + setlocale (LC_NUMERIC, oldlocale); + g_free (oldlocale); + g_free (b); + + *new_val = new_value; + return true; +} + +bool +SPDesktopWidget::rotation_output() +{ + gchar b[64]; + double val = _rotation_status->get_value(); + + if (val < -180) val += 360; + if (val > 180) val -= 360; + + g_snprintf (b, 64, "%7.2f°", val); + + _rotation_status->set_text(b); + return true; +} + +void +SPDesktopWidget::rotation_value_changed() +{ + double const rotate_factor = M_PI / 180.0 * _rotation_status->get_value(); + // std::cout << "SPDesktopWidget::rotation_value_changed: " + // << _rotation_status->get_value() + // << " (" << rotate_factor << ")" <<std::endl; + + // Rotate around center of window + Geom::Rect const d_canvas = desktop->getCanvas()->getViewbox(); + _rotation_status_value_changed_connection.block(); + Geom::Point midpoint = desktop->w2d(d_canvas.midpoint()); + desktop->rotate_absolute_center_point (midpoint, rotate_factor); + _rotation_status_value_changed_connection.unblock(); + + spinbutton_defocus(GTK_WIDGET(_rotation_status->gobj())); +} + +void +SPDesktopWidget::rotation_populate_popup(Gtk::Menu *menu) +{ + for ( auto iter : menu->get_children()) { + menu->remove(*iter); + } + + auto item_m135 = Gtk::manage(new Gtk::MenuItem("-135°")); + auto item_m90 = Gtk::manage(new Gtk::MenuItem( "-90°")); + auto item_m45 = Gtk::manage(new Gtk::MenuItem( "-45°")); + auto item_0 = Gtk::manage(new Gtk::MenuItem( "0°")); + auto item_p45 = Gtk::manage(new Gtk::MenuItem( "45°")); + auto item_p90 = Gtk::manage(new Gtk::MenuItem( "90°")); + auto item_p135 = Gtk::manage(new Gtk::MenuItem( "135°")); + auto item_p180 = Gtk::manage(new Gtk::MenuItem( "180°")); + + item_m135->signal_activate().connect(sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), -135)); + item_m90->signal_activate().connect( sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), -90)); + item_m45->signal_activate().connect( sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), -45)); + item_0->signal_activate().connect( sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), 0)); + item_p45->signal_activate().connect( sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), 45)); + item_p90->signal_activate().connect( sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), 90)); + item_p135->signal_activate().connect(sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), 135)); + item_p180->signal_activate().connect(sigc::bind(sigc::mem_fun(_rotation_status, &Gtk::SpinButton::set_value), 180)); + + menu->append(*item_m135); + menu->append(*item_m90); + menu->append(*item_m45); + menu->append(*item_0); + menu->append(*item_p45); + menu->append(*item_p90); + menu->append(*item_p135); + menu->append(*item_p180); + + menu->show_all(); +} + + +void +SPDesktopWidget::update_rotation() +{ + _rotation_status_value_changed_connection.block(); + _rotation_status->set_value(desktop->current_rotation() / M_PI * 180.0); + _rotation_status->queue_draw(); + _rotation_status_value_changed_connection.unblock(); + +} + + +// --------------- Rulers/Scrollbars/Etc. ----------------- +void +SPDesktopWidget::toggle_rulers() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (_guides_lock->get_visible()) { + _guides_lock->hide(); + _hruler->hide(); + _vruler->hide(); + prefs->setBool(desktop->is_fullscreen() ? "/fullscreen/rulers/state" : "/window/rulers/state", false); + } else { + _guides_lock->show_all(); + _hruler->show_all(); + _vruler->show_all(); + prefs->setBool(desktop->is_fullscreen() ? "/fullscreen/rulers/state" : "/window/rulers/state", true); + } +} + +void +SPDesktopWidget::toggle_scrollbars() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (_hscrollbar->get_visible()) { + _hscrollbar->hide(); + _vscrollbar_box->hide(); + _cms_adjust->hide(); + prefs->setBool(desktop->is_fullscreen() ? "/fullscreen/scrollbars/state" : "/window/scrollbars/state", false); + } else { + _hscrollbar->show_all(); + _vscrollbar_box->show_all(); + _cms_adjust->show_all(); + prefs->setBool(desktop->is_fullscreen() ? "/fullscreen/scrollbars/state" : "/window/scrollbars/state", true); + } +} + +bool +SPDesktopWidget::get_color_prof_adj_enabled() const +{ + return _cms_adjust->get_sensitive() && _cms_adjust->get_active(); +} + +void +SPDesktopWidget::toggle_color_prof_adj() +{ + if (_cms_adjust->get_sensitive()) { + if (_cms_adjust->get_active()) { + _cms_adjust->toggle_set_down(false); + } else { + _cms_adjust->toggle_set_down(true); + } + } +} + +static void +set_adjustment (Glib::RefPtr<Gtk::Adjustment> &adj, double l, double u, double ps, double si, double pi) +{ + if ((l != adj->get_lower()) || + (u != adj->get_upper()) || + (ps != adj->get_page_size()) || + (si != adj->get_step_increment()) || + (pi != adj->get_page_increment())) { + adj->set_lower(l); + adj->set_upper(u); + adj->set_page_size(ps); + adj->set_step_increment(si); + adj->set_page_increment(pi); + } +} + +void +SPDesktopWidget::update_scrollbars(double scale) +{ + if (update) return; + update = 1; + + /* The desktop region we always show unconditionally */ + SPDocument *doc = desktop->doc(); + Geom::Rect darea ( Geom::Point(-doc->getWidth().value("px"), -doc->getHeight().value("px")), + Geom::Point(2 * doc->getWidth().value("px"), 2 * doc->getHeight().value("px")) ); + + Geom::OptRect deskarea; + if (Inkscape::Preferences::get()->getInt("/tools/bounding_box") == 0) { + deskarea = darea | doc->getRoot()->desktopVisualBounds(); + } else { + deskarea = darea | doc->getRoot()->desktopGeometricBounds(); + } + + /* Canvas region we always show unconditionally */ + double const y_dir = desktop->yaxisdir(); + Geom::Rect carea( Geom::Point(deskarea->left() * scale - 64, (deskarea->top() * scale + 64) * y_dir), + Geom::Point(deskarea->right() * scale + 64, (deskarea->bottom() * scale - 64) * y_dir) ); + + Geom::Rect viewbox = _canvas->getViewbox(); + + /* Viewbox is always included into scrollable region */ + carea = Geom::unify(carea, viewbox); + + set_adjustment(_hadj, carea.min()[Geom::X], carea.max()[Geom::X], + viewbox.dimensions()[Geom::X], + 0.1 * viewbox.dimensions()[Geom::X], + viewbox.dimensions()[Geom::X]); + _hadj->set_value(viewbox.min()[Geom::X]); + + set_adjustment(_vadj, carea.min()[Geom::Y], carea.max()[Geom::Y], + viewbox.dimensions()[Geom::Y], + 0.1 * viewbox.dimensions()[Geom::Y], + viewbox.dimensions()[Geom::Y]); + _vadj->set_value(viewbox.min()[Geom::Y]); + + update = 0; +} + +bool +SPDesktopWidget::get_sticky_zoom_active() const +{ + return _sticky_zoom->get_active(); +} + +double +SPDesktopWidget::get_hruler_thickness() const +{ + auto allocation = _hruler->get_allocation(); + return allocation.get_height(); +} + +double +SPDesktopWidget::get_vruler_thickness() const +{ + auto allocation = _vruler->get_allocation(); + return allocation.get_width(); +} + +gint +SPDesktopWidget::ruler_event(GtkWidget *widget, GdkEvent *event, SPDesktopWidget *dtw, bool horiz) +{ + switch (event->type) { + case GDK_BUTTON_PRESS: + dtw->on_ruler_box_button_press_event(&event->button, Glib::wrap(GTK_EVENT_BOX(widget)), horiz); + break; + case GDK_MOTION_NOTIFY: + dtw->on_ruler_box_motion_notify_event(&event->motion, Glib::wrap(GTK_EVENT_BOX(widget)), horiz); + break; + case GDK_BUTTON_RELEASE: + dtw->on_ruler_box_button_release_event(&event->button, Glib::wrap(GTK_EVENT_BOX(widget)), horiz); + break; + default: + break; + } + + return FALSE; +} + +bool +SPDesktopWidget::on_ruler_box_motion_notify_event(GdkEventMotion *event, Gtk::EventBox *widget, bool horiz) +{ + if (horiz) { + sp_event_context_snap_delay_handler(desktop->event_context, (gpointer) widget->gobj(), (gpointer) this, event, Inkscape::UI::Tools::DelayedSnapEvent::GUIDE_HRULER); + } + else { + sp_event_context_snap_delay_handler(desktop->event_context, (gpointer) widget->gobj(), (gpointer) this, event, Inkscape::UI::Tools::DelayedSnapEvent::GUIDE_VRULER); + } + + int wx, wy; + + GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(_canvas)); + + gint width, height; + + gdk_window_get_device_position(window, event->device, &wx, &wy, nullptr); + gdk_window_get_geometry(window, nullptr /*x*/, nullptr /*y*/, &width, &height); + + Geom::Point const event_win(wx, wy); + + if (_ruler_clicked) { + Geom::Point const event_w(sp_canvas_window_to_world(_canvas, event_win)); + Geom::Point event_dt(desktop->w2d(event_w)); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + if ( ( abs( (gint) event->x - _xp ) < tolerance ) + && ( abs( (gint) event->y - _yp ) < tolerance ) ) { + return false; + } + + _ruler_dragged = true; + + // explicitly show guidelines; if I draw a guide, I want them on + if ((horiz ? wy : wx) >= 0) { + desktop->namedview->setGuides(true); + } + + if (!(event->state & GDK_SHIFT_MASK)) { + ruler_snap_new_guide(desktop, _active_guide, event_dt, _normal); + } + sp_guideline_set_normal(SP_GUIDELINE(_active_guide), _normal); + sp_guideline_set_position(SP_GUIDELINE(_active_guide), event_dt); + + desktop->set_coordinate_status(event_dt); + } + + return false; +} + +bool +SPDesktopWidget::on_ruler_box_button_release_event(GdkEventButton *event, Gtk::EventBox *widget, bool horiz) +{ + int wx, wy; + + GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(_canvas)); + + gint width, height; + + gdk_window_get_device_position(window, event->device, &wx, &wy, nullptr); + gdk_window_get_geometry(window, nullptr /*x*/, nullptr /*y*/, &width, &height); + + Geom::Point const event_win(wx, wy); + + if (_ruler_clicked && event->button == 1) { + sp_event_context_discard_delayed_snap_event(desktop->event_context); + + auto seat = gdk_device_get_seat(event->device); + gdk_seat_ungrab(seat); + + Geom::Point const event_w(sp_canvas_window_to_world(_canvas, event_win)); + Geom::Point event_dt(desktop->w2d(event_w)); + + if (!(event->state & GDK_SHIFT_MASK)) { + ruler_snap_new_guide(desktop, _active_guide, event_dt, _normal); + } + + sp_canvas_item_destroy(_active_guide); + _active_guide = nullptr; + if ((horiz ? wy : wx) >= 0) { + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("sodipodi:guide"); + + // If root viewBox set, interpret guides in terms of viewBox (90/96) + double newx = event_dt.x(); + double newy = event_dt.y(); + + // <sodipodi:guide> stores inverted y-axis coordinates + if (desktop->is_yaxisdown()) { + newy = desktop->doc()->getHeight().value("px") - newy; + _normal[Geom::Y] *= -1.0; + } + + SPRoot *root = desktop->doc()->getRoot(); + if( root->viewBox_set ) { + newx = newx * root->viewBox.width() / root->width.computed; + newy = newy * root->viewBox.height() / root->height.computed; + } + sp_repr_set_point(repr, "position", Geom::Point( newx, newy )); + sp_repr_set_point(repr, "orientation", _normal); + desktop->namedview->appendChild(repr); + Inkscape::GC::release(repr); + DocumentUndo::done(desktop->getDocument(), SP_VERB_NONE, + _("Create guide")); + } + desktop->set_coordinate_status(event_dt); + + if (!_ruler_dragged) { + // Ruler click (without drag) toggle the guide visibility on and off + Inkscape::XML::Node *repr = desktop->namedview->getRepr(); + sp_namedview_toggle_guides(desktop->getDocument(), desktop->namedview); + } + + _ruler_clicked = false; + _ruler_dragged = false; + } + + return false; +} + +bool +SPDesktopWidget::on_ruler_box_button_press_event(GdkEventButton *event, Gtk::EventBox *widget, bool horiz) +{ + if (_ruler_clicked) // event triggerred on a double click: do no process the click + return false; + + int wx, wy; + + GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(_canvas)); + + gint width, height; + + gdk_window_get_device_position(window, event->device, &wx, &wy, nullptr); + gdk_window_get_geometry(window, nullptr /*x*/, nullptr /*y*/, &width, &height); + + Geom::Point const event_win(wx, wy); + + if (event->button == 1) { + _ruler_clicked = true; + _ruler_dragged = false; + // save click origin + _xp = (gint) event->x; + _yp = (gint) event->y; + + Geom::Point const event_w(sp_canvas_window_to_world(_canvas, event_win)); + Geom::Point const event_dt(desktop->w2d(event_w)); + + // calculate the normal of the guidelines when dragged from the edges of rulers. + auto const y_dir = desktop->yaxisdir(); + Geom::Point normal_bl_to_tr(1., y_dir); //bottomleft to topright + Geom::Point normal_tr_to_bl(-1., y_dir); //topright to bottomleft + normal_bl_to_tr.normalize(); + normal_tr_to_bl.normalize(); + Inkscape::CanvasGrid * grid = sp_namedview_get_first_enabled_grid(desktop->namedview); + if (grid){ + if (grid->getGridType() == Inkscape::GRID_AXONOMETRIC ) { + Inkscape::CanvasAxonomGrid *axonomgrid = dynamic_cast<Inkscape::CanvasAxonomGrid *>(grid); + if (event->state & GDK_CONTROL_MASK) { + // guidelines normal to gridlines + normal_bl_to_tr = Geom::Point::polar(-axonomgrid->angle_rad[0], 1.0); + normal_tr_to_bl = Geom::Point::polar(axonomgrid->angle_rad[2], 1.0); + } else { + normal_bl_to_tr = rot90(Geom::Point::polar(axonomgrid->angle_rad[2], 1.0)); + normal_tr_to_bl = rot90(Geom::Point::polar(-axonomgrid->angle_rad[0], 1.0)); + } + } + } + if (horiz) { + if (wx < 50) { + _normal = normal_bl_to_tr; + } else if (wx > width - 50) { + _normal = normal_tr_to_bl; + } else { + _normal = Geom::Point(0.,1.); + } + } else { + if (wy < 50) { + _normal = normal_bl_to_tr; + } else if (wy > height - 50) { + _normal = normal_tr_to_bl; + } else { + _normal = Geom::Point(1.,0.); + } + } + + _active_guide = sp_guideline_new(desktop->guides, nullptr, event_dt, _normal); + sp_guideline_set_color(SP_GUIDELINE(_active_guide), desktop->namedview->guidehicolor); + + auto window = widget->get_window()->gobj(); + + auto seat = gdk_device_get_seat(event->device); + gdk_seat_grab(seat, + window, + GDK_SEAT_CAPABILITY_ALL_POINTING, + FALSE, + nullptr, + (GdkEvent*)event, + nullptr, + nullptr); + } + + return false; +} + +GtkAllocation +SPDesktopWidget::get_canvas_allocation() const +{ + GtkAllocation allocation; + gtk_widget_get_allocation(GTK_WIDGET(_canvas), &allocation); + return allocation; +} + +void +SPDesktopWidget::ruler_snap_new_guide(SPDesktop *desktop, SPCanvasItem * /*guide*/, Geom::Point &event_dt, Geom::Point &normal) +{ + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + // We're dragging a brand new guide, just pulled of the rulers seconds ago. When snapping to a + // path this guide will change it slope to become either tangential or perpendicular to that path. It's + // therefore not useful to try tangential or perpendicular snapping, so this will be disabled temporarily + bool pref_perp = m.snapprefs.getSnapPerp(); + bool pref_tang = m.snapprefs.getSnapTang(); + m.snapprefs.setSnapPerp(false); + m.snapprefs.setSnapTang(false); + // We only have a temporary guide which is not stored in our document yet. + // Because the guide snapper only looks in the document for guides to snap to, + // we don't have to worry about a guide snapping to itself here + Geom::Point normal_orig = normal; + m.guideFreeSnap(event_dt, normal, false, false); + // After snapping, both event_dt and normal have been modified accordingly; we'll take the normal (of the + // curve we snapped to) to set the normal the guide. And rotate it by 90 deg. if needed + if (pref_perp) { // Perpendicular snapping to paths is requested by the user, so let's do that + if (normal != normal_orig) { + normal = Geom::rot90(normal); + } + } + if (!(pref_tang || pref_perp)) { // if we don't want to snap either perpendicularly or tangentially, then + normal = normal_orig; // we must restore the normal to it's original state + } + // Restore the preferences + m.snapprefs.setSnapPerp(pref_perp); + m.snapprefs.setSnapTang(pref_tang); + m.unSetup(); +} + +/* + 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 : |