diff options
Diffstat (limited to 'src/desktop.cpp')
-rw-r--r-- | src/desktop.cpp | 2229 |
1 files changed, 2229 insertions, 0 deletions
diff --git a/src/desktop.cpp b/src/desktop.cpp new file mode 100644 index 0000000..573c044 --- /dev/null +++ b/src/desktop.cpp @@ -0,0 +1,2229 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Editable view 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 Jon A. Cruz + * Copyright (C) 2006-2008 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. + */ + +#include <glibmm/i18n.h> +#include <2geom/transforms.h> +#include <2geom/rect.h> +#include <memory> + +#include "desktop.h" + +#include "color.h" +#include "desktop-events.h" +#include "desktop-style.h" +#include "device-manager.h" +#include "document-undo.h" +#include "event-log.h" +#include "inkscape-window.h" +#include "layer-fns.h" +#include "layer-manager.h" +#include "message-context.h" +#include "message-stack.h" + +#include "display/canvas-arena.h" +#include "display/canvas-debug.h" +#include "display/canvas-grid.h" +#include "display/canvas-rotate.h" +#include "display/canvas-temporary-item-list.h" +#include "display/drawing-group.h" +#include "display/gnome-canvas-acetate.h" +#include "display/snap-indicator.h" +#include "display/sodipodi-ctrlrect.h" +#include "display/sp-canvas-group.h" +#include "display/sp-canvas-util.h" + +#include "helper/action-context.h" +#include "helper/action.h" //sp_action_perform + +#include "io/resource-manager.h" + +#include "object/sp-namedview.h" +#include "object/sp-root.h" + +#include "ui/desktop/menubar.h" +#include "ui/dialog/dialog-manager.h" +#include "ui/interface.h" // Only for getLayoutPrefPath +#include "ui/tool-factory.h" +#include "ui/tools/tool-base.h" +#include "ui/tools/box3d-tool.h" +#include "ui/tools/select-tool.h" + +#include "widgets/desktop-widget.h" + +// TODO those includes are only for node tool quick zoom. Remove them after fixing it. +#include "ui/tools/node-tool.h" +#include "ui/tool/control-point-selection.h" + +namespace Inkscape { namespace XML { class Node; }} + +// Callback declarations +static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop); +static gint _arena_handler (SPCanvasArena *arena, Inkscape::DrawingItem *ai, GdkEvent *event, SPDesktop *desktop); +static void _layer_activated(SPObject *layer, SPDesktop *desktop); +static void _layer_deactivated(SPObject *layer, SPDesktop *desktop); +static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop); +static void _reconstruction_start(SPDesktop * desktop); +static void _reconstruction_finish(SPDesktop * desktop); +static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop); + +static gdouble _pinch_begin_zoom = 1.; + +static void _pinch_begin_handler(GtkGesture *gesture, GdkEventSequence *sequence, SPDesktop *desktop) +{ + _pinch_begin_zoom = desktop->current_zoom(); +} + +static void _pinch_scale_changed_handler(GtkGesture *gesture, gdouble delta, SPDesktop *desktop) +{ + GdkEventSequence *sequence = gtk_gesture_get_last_updated_sequence(gesture); + const GdkEvent *event = gtk_gesture_get_last_event(gesture, sequence); + + Geom::Point button_window(event->button.x, event->button.y); + Geom::Point button_world = sp_canvas_window_to_world(desktop->canvas, button_window); + Geom::Point button_dt(desktop->w2d(button_world)); + + desktop->zoom_absolute_keep_point(button_dt, _pinch_begin_zoom * delta); +} + +SPDesktop::SPDesktop() + : _dlg_mgr(nullptr) + , namedview(nullptr) + , canvas(nullptr) + , layers(nullptr) + , selection(nullptr) + , event_context(nullptr) + , layer_manager(nullptr) + , event_log(nullptr) + , temporary_item_list(nullptr) + , snapindicator(nullptr) + , acetate(nullptr) + , main(nullptr) + , gridgroup(nullptr) + , guides(nullptr) + , drawing(nullptr) + , sketch(nullptr) + , controls(nullptr) + , tempgroup(nullptr) + , page(nullptr) + , page_border(nullptr) + , current(nullptr) + , _focusMode(false) + , dkey(0) + , number(0) + , window_state(0) + , interaction_disabled_counter(0) + , waiting_cursor(false) + , showing_dialogs(false) + , rotation_locked(false) + , guides_active(false) + , gr_item(nullptr) + , gr_point_type(POINT_LG_BEGIN) + , gr_point_i(0) + , gr_fill_or_stroke(Inkscape::FOR_FILL) + , _reconstruction_old_layer_id() + , // an id attribute is not allowed to be the empty string + _display_mode(Inkscape::RENDERMODE_NORMAL) + , _display_color_mode(Inkscape::COLORMODE_NORMAL) + , _split_canvas(false) + , _xray(false) + , _widget(nullptr) + , _guides_message_context(nullptr) + , _active(false) + , _image_render_observer(this, "/options/rendering/imageinoutlinemode") + , grids_visible(false) +{ + layers = new Inkscape::LayerModel(); + layers->_layer_activated_signal.connect(sigc::bind(sigc::ptr_fun(_layer_activated), this)); + layers->_layer_deactivated_signal.connect(sigc::bind(sigc::ptr_fun(_layer_deactivated), this)); + layers->_layer_changed_signal.connect(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this)); + selection = Inkscape::GC::release( new Inkscape::Selection(layers, this) ); +} + +void +SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas, Inkscape::UI::View::EditWidgetInterface *widget) +{ + _widget = widget; + + // Temporary workaround for link order issues: + Inkscape::DeviceManager::getManager().getDevices(); + Inkscape::ResourceManager::getManager(); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + _guides_message_context = std::unique_ptr<Inkscape::MessageContext>(new Inkscape::MessageContext(messageStack())); + + current = prefs->getStyle("/desktop/style"); + + namedview = nv; + canvas = aCanvas; + + SPDocument *document = namedview->document; + /* XXX: + * ensureUpToDate() sends a 'modified' signal to the root element. + * This is reportedly required to prevent flickering after the document + * loads. However, many SPObjects write to their repr in response + * to this signal. This is apparently done to support live path effects, + * which rewrite their result paths after each modification of the base object. + * This causes the generation of an incomplete undo transaction, + * which causes problems down the line, including crashes in the + * Undo History dialog. + * + * For now, this is handled by disabling undo tracking during this call. + * A proper fix would involve modifying the way ensureUpToDate() works, + * so that the LPE results are not rewritten. + */ + Inkscape::DocumentUndo::setUndoSensitive(document, false); + document->ensureUpToDate(); + Inkscape::DocumentUndo::setUndoSensitive(document, true); + + /* Setup Dialog Manager */ + _dlg_mgr = &Inkscape::UI::Dialog::DialogManager::getInstance(); + + dkey = SPItem::display_key_new(1); + + /* Connect display key to layer model */ + layers->setDisplayKey(dkey); + + /* Connect document */ + setDocument (document); + + number = namedview->getViewCount(); + + + /* Setup Canvas */ + g_object_set_data (G_OBJECT (canvas), "SPDesktop", this); + + SPCanvasGroup *root = canvas->getRoot(); + + /* Setup administrative layers */ + acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, nullptr); + g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this); + main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, nullptr); + g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this); + + /* This is the background the page sits on. */ + canvas->setBackgroundColor(0xffffff00); + + page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, nullptr); + ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000); + page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, nullptr); + + drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, nullptr); + g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this); + + // pinch zoom + zoomgesture = gtk_gesture_zoom_new(GTK_WIDGET(getCanvas())); + g_signal_connect(zoomgesture, "begin", G_CALLBACK(_pinch_begin_handler), this); + g_signal_connect(zoomgesture, "scale-changed", G_CALLBACK(_pinch_scale_changed_handler), this); + + SP_CANVAS_ARENA (drawing)->drawing.delta = prefs->getDouble("/options/cursortolerance/value", 1.0); // default is 1 px + + if (prefs->getBool("/options/startmode/outline")) { + // Start in outline mode + setDisplayModeOutline(); + } else { + // Start in normal mode, default + setDisplayModeNormal(); + } + + // Get default locked status + rotation_locked = prefs->getBool("/options/rotationlock"); + + // The order in which these canvas items are added determines the z-order. It's therefore + // important to add the tempgroup (which will contain the snapindicator) before adding the + // controls. Only this way one will be able to quickly (before the snap indicator has + // disappeared) reselect a node after snapping it. If the z-order is wrong however, this + // will not work (the snap indicator is on top of the node handler; is the snapindicator + // being selected? or does it intercept some of the events that should have gone to the + // node handler? see bug https://bugs.launchpad.net/inkscape/+bug/414142) + gridgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, nullptr); + guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, nullptr); + sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, nullptr); + tempgroup = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, nullptr); + controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, nullptr); + + // Set the select tool as the active tool. + setEventContext("/tools/select"); + + // display rect and zoom are now handled in sp_desktop_widget_realize() + + Geom::Rect const d(Geom::Point(0.0, 0.0), + Geom::Point(document->getWidth().value("px"), document->getHeight().value("px"))); + + SP_CTRLRECT(page)->setRectangle(d); + SP_CTRLRECT(page_border)->setRectangle(d); + + /* the following sets the page shadow on the canvas + It was originally set to 5, which is really cheesy! + It now is an attribute in the document's namedview. If a value of + 0 is used, then the constructor for a shadow is not initialized. + */ + + if ( namedview->pageshadow != 0 && namedview->showpageshadow ) { + SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff); + } + + + /* Connect event for page resize */ + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), doc2dt()); + + _modified_connection = + namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this)); + + Inkscape::DrawingItem *ai = document->getRoot()->invoke_show( + SP_CANVAS_ARENA (drawing)->drawing, + dkey, + SP_ITEM_SHOW_DISPLAY); + if (ai) { + SP_CANVAS_ARENA (drawing)->drawing.root()->prependChild(ai); + } + + namedview->show(this); + /* Ugly hack */ + activate_guides (true); + /* Ugly hack */ + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + +/* Set up notification of rebuilding the document, this allows + for saving object related settings in the document. */ + _reconstruction_start_connection = + document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this)); + _reconstruction_finish_connection = + document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this)); + _reconstruction_old_layer_id.clear(); + + // ? + // sp_active_desktop_set (desktop); + + _activate_connection = _activate_signal.connect( + sigc::bind( + sigc::ptr_fun(_onActivate), + this + ) + ); + _deactivate_connection = _deactivate_signal.connect( + sigc::bind( + sigc::ptr_fun(_onDeactivate), + this + ) + ); + + _sel_modified_connection = selection->connectModified( + sigc::bind( + sigc::ptr_fun(&_onSelectionModified), + this + ) + ); + _sel_changed_connection = selection->connectChanged( + sigc::bind( + sigc::ptr_fun(&_onSelectionChanged), + this + ) + ); + + + /* setup LayerManager */ + // (Setting up after the connections are all in place, as it may use some of them) + layer_manager = new Inkscape::LayerManager( this ); + + showGrids(namedview->grids_visible, false); + + temporary_item_list = new Inkscape::Display::TemporaryItemList( this ); + snapindicator = new Inkscape::Display::SnapIndicator ( this ); + + canvas_rotate = sp_canvas_item_new (root, SP_TYPE_CANVAS_ROTATE, nullptr); + sp_canvas_item_hide( canvas_rotate ); + // canvas_debug = sp_canvas_item_new (main, SP_TYPE_CANVAS_DEBUG, NULL); +} + +void SPDesktop::destroy() +{ + _destroy_signal.emit(this); + + if (snapindicator) { + delete snapindicator; + snapindicator = nullptr; + } + + if (temporary_item_list) { + delete temporary_item_list; + temporary_item_list = nullptr; + } + + if (selection) { + delete selection; + selection = nullptr; + } + + namedview->hide(this); + + _activate_connection.disconnect(); + _deactivate_connection.disconnect(); + _sel_modified_connection.disconnect(); + _sel_changed_connection.disconnect(); + _modified_connection.disconnect(); + _commit_connection.disconnect(); + _reconstruction_start_connection.disconnect(); + _reconstruction_finish_connection.disconnect(); + + g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this); + g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this); + g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this); + + if (zoomgesture) { + g_signal_handlers_disconnect_by_data(zoomgesture, this); + g_clear_object(&zoomgesture); + } + + delete layers; + + if (layer_manager) { + delete layer_manager; + layer_manager = nullptr; + } + + if (drawing) { + doc()->getRoot()->invoke_hide(dkey); + g_object_unref(drawing); + drawing = nullptr; + } + + _guides_message_context = nullptr; +} + +SPDesktop::~SPDesktop() += default; + + +Inkscape::UI::Tools::ToolBase* SPDesktop::getEventContext() const { + return event_context; +} + +Inkscape::Selection* SPDesktop::getSelection() const { + return selection; +} + +SPDocument* SPDesktop::getDocument() const { + return doc(); +} + +SPCanvas* SPDesktop::getCanvas() const { + return SP_CANVAS_ITEM(main)->canvas; +} + +SPCanvasItem* SPDesktop::getAcetate() const { + return acetate; +} + +SPCanvasGroup* SPDesktop::getMain() const { + return main; +} + +SPCanvasGroup* SPDesktop::getGridGroup() const { + return gridgroup; +} + +SPCanvasGroup* SPDesktop::getGuides() const { + return guides; +} + +SPCanvasItem* SPDesktop::getDrawing() const { + return drawing; +} + +SPCanvasGroup* SPDesktop::getSketch() const { + return sketch; +} + +SPCanvasGroup* SPDesktop::getControls() const { + return controls; +} + +SPCanvasGroup* SPDesktop::getTempGroup() const { + return tempgroup; +} + +Inkscape::MessageStack* SPDesktop::getMessageStack() const { + return messageStack().get(); +} + +SPNamedView* SPDesktop::getNamedView() const { + return namedview; +} + + + +//-------------------------------------------------------------------- +/* Public methods */ + + +/* These methods help for temporarily showing things on-canvas. + * The *only* valid use of the TemporaryItem* that you get from add_temporary_canvasitem + * is when you want to prematurely remove the item from the canvas, by calling + * desktop->remove_temporary_canvasitem(tempitem). + */ +/** Note that lifetime is measured in milliseconds + * One should *not* keep a reference to the SPCanvasItem, the temporary item code will + * delete the object for you and the reference will become invalid without you knowing it. + * It is perfectly safe to ignore the returned pointer: the object is deleted by itself, so don't delete it elsewhere! + * The *only* valid use of the returned TemporaryItem* is as argument for SPDesktop::remove_temporary_canvasitem, + * because the object might be deleted already without you knowing it. + * move_to_bottom = true by default so the item does not interfere with handling of other items on the canvas like nodes. + */ +Inkscape::Display::TemporaryItem * +SPDesktop::add_temporary_canvasitem (SPCanvasItem *item, guint lifetime, bool move_to_bottom) +{ + if (move_to_bottom) { + sp_canvas_item_move_to_z(item, 0); + } + + return temporary_item_list->add_item(item, lifetime); +} + +/** It is perfectly safe to call this function while the object has already been deleted due to a timeout. +*/ +void +SPDesktop::remove_temporary_canvasitem (Inkscape::Display::TemporaryItem * tempitem) +{ + // check for non-null temporary_item_list, because during destruction of desktop, some destructor might try to access this list! + if (tempitem && temporary_item_list) { + temporary_item_list->delete_item(tempitem); + } +} + +void SPDesktop::redrawDesktop() { + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _current_affine.d2w()); // redraw +} + +void SPDesktop::_setDisplayMode(Inkscape::RenderMode mode) { + SP_CANVAS_ARENA (drawing)->drawing.setRenderMode(mode); + canvas->_rendermode = mode; + _display_mode = mode; + if (_display_mode == Inkscape::RENDERMODE_OUTLINE) { + if (_split_canvas) { + toggleSplitMode(); + } + if (_xray) { + toggleXRay(); + } + } + redrawDesktop(); + _widget->setTitle( this->getDocument()->getDocumentName() ); +} + +void SPDesktop::_setDisplayColorMode(Inkscape::ColorMode mode) { + // reload grayscale matrix from prefs + if (mode == Inkscape::COLORMODE_GRAYSCALE) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gdouble r = prefs->getDoubleLimited("/options/rendering/grayscale/red-factor",0.21,0.,1.); + gdouble g = prefs->getDoubleLimited("/options/rendering/grayscale/green-factor",0.72,0.,1.); + gdouble b = prefs->getDoubleLimited("/options/rendering/grayscale/blue-factor",0.072,0.,1.); + gdouble grayscale_value_matrix[20] = { r, g, b, 0, 0, + r, g, b, 0, 0, + r, g, b, 0, 0, + 0, 0, 0, 1, 0 }; + // g_message("%g",grayscale_value_matrix[0]); + SP_CANVAS_ARENA (drawing)->drawing.setGrayscaleMatrix(grayscale_value_matrix); + } + + SP_CANVAS_ARENA (drawing)->drawing.setColorMode(mode); + canvas->_colorrendermode = mode; + _display_color_mode = mode; + redrawDesktop(); + _widget->setTitle( this->getDocument()->getDocumentName() ); +} + +bool SPDesktop::displayModeToggle() +{ + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_VIEW_MODE_NORMAL); + switch (_display_mode) { + case Inkscape::RENDERMODE_NORMAL: + _setDisplayMode(Inkscape::RENDERMODE_NO_FILTERS); + verb = Inkscape::Verb::get(SP_VERB_VIEW_MODE_NO_FILTERS); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayModeNoFilters()); + } + break; + case Inkscape::RENDERMODE_NO_FILTERS: + _setDisplayMode(Inkscape::RENDERMODE_OUTLINE); + verb = Inkscape::Verb::get(SP_VERB_VIEW_MODE_OUTLINE); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayModeOutline()); + } + + break; + case Inkscape::RENDERMODE_OUTLINE: + _setDisplayMode(Inkscape::RENDERMODE_VISIBLE_HAIRLINES); + verb = Inkscape::Verb::get(SP_VERB_VIEW_MODE_VISIBLE_HAIRLINES); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayModeVisibleHairlines()); + } + break; + case Inkscape::RENDERMODE_VISIBLE_HAIRLINES: + _setDisplayMode(Inkscape::RENDERMODE_NORMAL); + verb = Inkscape::Verb::get(SP_VERB_VIEW_MODE_NORMAL); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayModeNormal()); + } + break; + default: + _setDisplayMode(Inkscape::RENDERMODE_NORMAL); + verb = Inkscape::Verb::get(SP_VERB_VIEW_MODE_NORMAL); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayModeNormal()); + } + } + return true; +} +bool SPDesktop::displayColorModeToggle() +{ + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_VIEW_COLOR_MODE_NORMAL); + switch (_display_color_mode) { + case Inkscape::COLORMODE_NORMAL: + _setDisplayColorMode(Inkscape::COLORMODE_GRAYSCALE); + verb = Inkscape::Verb::get(SP_VERB_VIEW_COLOR_MODE_GRAYSCALE); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayColorModeGrayscale()); + } + break; + case Inkscape::COLORMODE_GRAYSCALE: + _setDisplayColorMode(Inkscape::COLORMODE_NORMAL); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayColorModeNormal()); + } + break; +// case Inkscape::COLORMODE_PRINT_COLORS_PREVIEW: + default: + _setDisplayColorMode(Inkscape::COLORMODE_NORMAL); + if (verb) { + _menu_update.emit(verb->get_code(), setDisplayColorModeNormal()); + } + } + return true; +} + +// Pass-through LayerModel functions +SPObject *SPDesktop::currentRoot() const +{ + return layers->currentRoot(); +} + +SPObject *SPDesktop::currentLayer() const +{ + return layers->currentLayer(); +} + +void SPDesktop::setCurrentLayer(SPObject *object) +{ + layers->setCurrentLayer(object); +} + +void SPDesktop::toggleLayerSolo(SPObject *object) +{ + layers->toggleLayerSolo(object); +} + +void SPDesktop::toggleHideAllLayers(bool hide) +{ + layers->toggleHideAllLayers(hide); +} + +void SPDesktop::toggleLockAllLayers(bool lock) +{ + layers->toggleLockAllLayers(lock); +} + +void SPDesktop::toggleLockOtherLayers(SPObject *object) +{ + layers->toggleLockOtherLayers(object); +} + +bool SPDesktop::isLayer(SPObject *object) const +{ + return layers->isLayer(object); +} + +/** + * True if desktop viewport intersects \a item's bbox. + */ +bool SPDesktop::isWithinViewport (SPItem *item) const +{ + Geom::Rect const viewport = get_display_area(); + Geom::OptRect const bbox = item->desktopVisualBounds(); + if (bbox) { + return viewport.intersects(*bbox); + } else { + return false; + } +} + +/// +bool SPDesktop::itemIsHidden(SPItem const *item) const { + return item->isHidden(this->dkey); +} + +/** + * Set activate property of desktop; emit signal if changed. + */ +void +SPDesktop::set_active (bool new_active) +{ + if (new_active != _active) { + _active = new_active; + if (new_active) { + _activate_signal.emit(); + } else { + _deactivate_signal.emit(); + } + } +} + +/** + * Set activate status of current desktop's named view. + */ +void +SPDesktop::activate_guides(bool activate) +{ + guides_active = activate; + namedview->activateGuides(this, activate); +} + +/** + * Make desktop switch documents. + */ +void +SPDesktop::change_document (SPDocument *theDocument) +{ + g_return_if_fail (theDocument != nullptr); + + /* unselect everything before switching documents */ + selection->clear(); + + setDocument (theDocument); + + /* update the rulers, connect the desktop widget's signal to the new namedview etc. + (this can probably be done in a better way) */ + InkscapeWindow *parent = this->getInkscapeWindow(); + g_assert(parent != nullptr); + parent->change_document(theDocument); + SPDesktopWidget *dtw = parent->get_desktop_widget(); + if (dtw) { + dtw->desktop = this; + dtw->updateNamedview(); + } else { + std::cerr << "SPDesktop::change_document: failed to get desktop widget!" << std::endl; + } + + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + _document_replaced_signal.emit (this, theDocument); +} + +/** + * Replaces the currently active tool with a new one. Pass the empty string to + * unset and free the current tool. + */ +void SPDesktop::setEventContext(const std::string& toolName) +{ + // Tool should be able to be replaced with itself. See commit 29df5ca05d + if (event_context) { + event_context->finish(); + delete event_context; + } + + if (toolName.empty()) { + event_context = nullptr; + } else { + event_context = ToolFactory::createObject(toolName); + event_context->desktop = this; + event_context->message_context = std::unique_ptr<Inkscape::MessageContext>(new Inkscape::MessageContext(this->messageStack())); + event_context->setup(); + + // Make sure no delayed snapping events are carried over after switching tools + // (this is only an additional safety measure against sloppy coding, because each + // tool should take care of this by itself) + sp_event_context_discard_delayed_snap_event(event_context); + } + + _event_context_changed_signal.emit(this, event_context); +} + +/** + * Sets the coordinate status to a given point + */ +void +SPDesktop::set_coordinate_status (Geom::Point p) { + _widget->setCoordinateStatus(p); +} + +Inkscape::UI::Widget::Dock* SPDesktop::getDock() { + return _widget->getDock(); +} + +/** + * \see SPDocument::getItemFromListAtPointBottom() + */ +SPItem *SPDesktop::getItemFromListAtPointBottom(const std::vector<SPItem*> &list, Geom::Point const &p) const +{ + g_return_val_if_fail (doc() != nullptr, NULL); + return SPDocument::getItemFromListAtPointBottom(dkey, doc()->getRoot(), list, p); +} + +/** + * \see SPDocument::getItemAtPoint() + */ +SPItem *SPDesktop::getItemAtPoint(Geom::Point const &p, bool into_groups, SPItem *upto) const +{ + g_return_val_if_fail (doc() != nullptr, NULL); + return doc()->getItemAtPoint( dkey, p, into_groups, upto); +} + +/** + * \see SPDocument::getGroupAtPoint() + */ +SPItem *SPDesktop::getGroupAtPoint(Geom::Point const &p) const +{ + g_return_val_if_fail (doc() != nullptr, NULL); + return doc()->getGroupAtPoint(dkey, p); +} + +/** + * Returns the mouse point in document coordinates; if mouse is + * outside the canvas, returns the center of canvas viewpoint. + */ +Geom::Point SPDesktop::point(bool outside_canvas) const +{ + Geom::Point p = _widget->getPointer(); + Geom::Point pw = sp_canvas_window_to_world (canvas, p); + Geom::Rect const r = canvas->getViewbox(); + + if (r.interiorContains(pw) || outside_canvas) { + p = w2d(pw); + return p; + } + Geom::Point r0 = w2d(r.min()); + Geom::Point r1 = w2d(r.max()); + return (r0 + r1) / 2.0; +} + + +/** + * Revert back to previous transform if possible. Note: current transform is + * always at front of stack. + */ +void +SPDesktop::prev_transform() +{ + if (transforms_past.empty()) { + std::cerr << "SPDesktop::prev_transform: current transform missing!" << std::endl; + return; + } + + if (transforms_past.size() == 1) { + messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous transform.")); + return; + } + + // Push current transform into future transforms list. + transforms_future.push_front( _current_affine ); + + // Remove the current transform from the past transforms list. + transforms_past.pop_front(); + + // restore previous transform + _current_affine = transforms_past.front(); + set_display_area (false); + +} + + +/** + * Set transform to next in list. + */ +void SPDesktop::next_transform() +{ + if (transforms_future.empty()) { + this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next transform.")); + return; + } + + // restore next transform + _current_affine = transforms_future.front(); + set_display_area (false); + + // remove the just-used transform from the future transforms list + transforms_future.pop_front(); + + // push current transform into past transforms list + transforms_past.push_front( _current_affine ); +} + + +/** + * Clear transform lists. + */ +void +SPDesktop::clear_transform_history() +{ + transforms_past.clear(); + transforms_future.clear(); +} + + +/** + * Does all the dirty work in setting the display area. + * _current_affine must already be full updated (including offset). + * log: if true, save transform in transform stack for reuse. + */ +void +SPDesktop::set_display_area (bool log) +{ + // Save the transform + if (log) { + transforms_past.push_front( _current_affine ); + // if we do a logged transform, our transform-forward list is invalidated, so delete it + transforms_future.clear(); + } + + redrawDesktop(); + + // Scroll + Geom::Point offset = _current_affine.getOffset(); + canvas->scrollTo(offset, true); + // To do: if transform unchanged call with 'false' (redraw only newly exposed areas). + + /* Update perspective lines if we are in the 3D box tool (so that infinite ones are shown + * correctly) */ + if (SP_IS_BOX3D_CONTEXT(event_context)) { + SP_BOX3D_CONTEXT(event_context)->_vpdrag->updateLines(); + } + + _widget->updateRulers(); + _widget->updateScrollbars(_current_affine.getZoom()); + _widget->updateZoom(); + _widget->updateRotation(); + + signal_zoom_changed.emit(_current_affine.getZoom()); +} + + +/** + * Map the drawing to the window so that 'c' lies at 'w' where where 'c' + * is a point on the canvas and 'w' is position in window in screen pixels. + */ +void +SPDesktop::set_display_area (Geom::Point const &c, Geom::Point const &w, bool log) +{ + // The relative offset needed to keep c at w. + Geom::Point offset = d2w(c) - w; + _current_affine.addOffset( offset ); + set_display_area( log ); +} + + +/** + * Map the center of rectangle 'r' (which specifies a non-rotated region of the + * drawing) to lie at the center of the window. The zoom factor is calculated such that + * the edges of 'r' closest to 'w' are 'border' length inside of the window (if + * there is no rotation). 'r' is in document pixel units, 'border' is in screen pixels. + */ +void +SPDesktop::set_display_area( Geom::Rect const &r, double border, bool log) +{ + // Create a rectangle the size of the window aligned with origin. + Geom::Rect w( Geom::Point(), canvas->getViewbox().dimensions() ); // Not the SVG 'viewBox'. + + // Shrink window to account for border padding. + w.expandBy( -border ); + + double zoom = 1.0; + // Determine which direction limits scale: + // if (r.width/w.width > r.height/w.height) then zoom using width. + // Avoiding division in test: + if ( r.width()*w.height() > r.height()*w.width() ) { + zoom = w.width() / r.width(); + } else { + zoom = w.height() / r.height(); + } + zoom = CLAMP(zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); + _current_affine.setScale( Geom::Scale(zoom, yaxisdir() * zoom) ); + // Zero offset, actual offset calculated later. + _current_affine.setOffset( Geom::Point( 0, 0 ) ); + + set_display_area( r.midpoint(), w.midpoint(), log ); +} + + +/** + * Return canvas viewbox bounding box in desktop coordinates + * + * @fixme Will be improved for non-90° rotations in https://gitlab.com/inkscape/inkscape/-/merge_requests/1399 + */ +Geom::Rect SPDesktop::get_display_area(bool use_integer_viewbox) const +{ + // viewbox in world coordinates + Geom::Rect const viewbox = use_integer_viewbox ? canvas->getViewboxIntegers() : canvas->getViewbox(); + + // display area in desktop coordinates + return viewbox * w2d(); +} + +/** + * Zoom keeping the point 'c' fixed in the desktop window. + */ +void +SPDesktop::zoom_absolute_keep_point (Geom::Point const &c, double zoom) +{ + zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); + Geom::Point w = d2w( c ); // Must be before zoom changed. + _current_affine.setScale( Geom::Scale(zoom, yaxisdir() * zoom) ); + set_display_area( c, w ); +} + + +void +SPDesktop::zoom_relative_keep_point (Geom::Point const &c, double zoom) +{ + double new_zoom = _current_affine.getZoom() * zoom; + zoom_absolute_keep_point( c, new_zoom ); +} + + +/** + * Zoom aligning the point 'c' to the center of desktop window. + */ +void +SPDesktop::zoom_absolute_center_point (Geom::Point const &c, double zoom) +{ + zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); + _current_affine.setScale( Geom::Scale(zoom, yaxisdir() * zoom) ); + Geom::Rect viewbox = canvas->getViewbox(); + set_display_area( c, viewbox.midpoint() ); +} + + +void +SPDesktop::zoom_relative_center_point (Geom::Point const &c, double zoom) +{ + double new_zoom = _current_affine.getZoom() * zoom; + zoom_absolute_center_point( c, new_zoom ); +} + + +/** + * Set display area to origin and current document dimensions. + */ +void +SPDesktop::zoom_page() +{ + Geom::Rect d(Geom::Point(0, 0), + Geom::Point(doc()->getWidth().value("px"), doc()->getHeight().value("px"))); + + if (d.minExtent() < 1.0) { + return; + } + + set_display_area(d, 10); +} + +/** + * Set display area to current document width. + */ +void +SPDesktop::zoom_page_width() +{ + Geom::Rect const a = get_display_area(); + + if (doc()->getWidth().value("px") < 1.0) { + return; + } + + Geom::Rect d(Geom::Point(0, a.midpoint()[Geom::Y]), + Geom::Point(doc()->getWidth().value("px"), a.midpoint()[Geom::Y])); + + set_display_area(d, 10); +} + + +/** + * Zoom to whole drawing. + */ +void +SPDesktop::zoom_drawing() +{ + g_return_if_fail (doc() != nullptr); + SPItem *docitem = doc()->getRoot(); + g_return_if_fail (docitem != nullptr); + + docitem->bbox_valid = FALSE; + Geom::OptRect d = docitem->desktopVisualBounds(); + + /* Note that the second condition here indicates that + ** there are no items in the drawing. + */ + if ( !d || d->minExtent() < 0.1 ) { + return; + } + + set_display_area(*d, 10); +} + + +/** + * Zoom to selection. + */ +void +SPDesktop::zoom_selection() +{ + Geom::OptRect const d = selection->visualBounds(); + + if ( !d || d->minExtent() < 0.1 ) { + return; + } + + set_display_area(*d, 10); +} + +/** + * Centre Page in window, without zooming + */ +void SPDesktop::zoom_center_page() +{ + zoom_absolute_center_point(Geom::Point(doc()->getWidth().value("px")/2, doc()->getHeight().value("px")/2), this->current_zoom()); +} + + +/** + * Performs a quick zoom into what the user is working on. + * + * @param enable Whether we're going in or out of quick zoom. + */ +void SPDesktop::zoom_quick(bool enable) +{ + if (enable == _quick_zoom_enabled) { + return; + } + + if (enable) { + _quick_zoom_affine = _current_affine; + bool zoomed = false; + + // TODO This needs to migrate into the node tool, but currently the design + // of this method is sufficiently wrong to prevent this. + if (!zoomed && INK_IS_NODE_TOOL(event_context)) { + Inkscape::UI::Tools::NodeTool *nt = static_cast<Inkscape::UI::Tools::NodeTool*>(event_context); + if (!nt->_selected_nodes->empty()) { + Geom::Rect nodes = *nt->_selected_nodes->bounds(); + double area = nodes.area(); + // do not zoom if a single cusp node is selected aand the bounds + // have zero area. + if (!Geom::are_near(area, 0)) { + set_display_area(nodes, true); + zoomed = true; + } + } + } + + if (!zoomed) { + Geom::OptRect const d = selection->visualBounds(); + if (d) { + set_display_area(*d, true); + zoomed = true; + } + } + + if (!zoomed) { + Geom::Rect const d_canvas = canvas->getViewbox(); // Not SVG 'viewBox' + Geom::Point midpoint = w2d(d_canvas.midpoint()); // Midpoint of drawing on canvas. + zoom_relative_center_point(midpoint, 2.0); + } + } else { + _current_affine = _quick_zoom_affine; + set_display_area( false ); + } + + _quick_zoom_enabled = enable; + return; +} + + +/** + * Tell widget to let zoom widget grab keyboard focus. + */ +void +SPDesktop::zoom_grab_focus() +{ + _widget->letZoomGrabFocus(); +} + + +/** + * Set new rotation, keeping the point 'c' fixed in the desktop window. + * + * @param c Point in desktop coordinates + * @param rotate Angle in clockwise direction + */ +void +SPDesktop::rotate_absolute_keep_point (Geom::Point const &c, double rotate) +{ + Geom::Point w = d2w( c ); // Must be before rotate changed. + _current_affine.setRotate( rotate ); + set_display_area( c, w ); +} + + +/** + * Rotate keeping the point 'c' fixed in the desktop window. + * + * @param c Point in desktop coordinates + * @param rotate Angle in clockwise direction + */ +void +SPDesktop::rotate_relative_keep_point (Geom::Point const &c, double rotate) +{ + Geom::Point w = d2w( c ); // Must be before rotate changed. + _current_affine.addRotate( rotate ); + set_display_area( c, w ); +} + + +/** + * Set new rotation, aligning the point 'c' to the center of desktop window. + * + * @param c Point in desktop coordinates + * @param rotate Angle in clockwise direction + */ +void +SPDesktop::rotate_absolute_center_point (Geom::Point const &c, double rotate) +{ + _current_affine.setRotate( rotate ); + Geom::Rect viewbox = canvas->getViewbox(); + set_display_area(c, viewbox.midpoint()); +} + + +/** + * Rotate aligning the point 'c' to the center of desktop window. + * + * @param c Point in desktop coordinates + * @param rotate Angle in clockwise direction + */ +void +SPDesktop::rotate_relative_center_point (Geom::Point const &c, double rotate) +{ + _current_affine.addRotate( rotate ); + Geom::Rect viewbox = canvas->getViewbox(); + set_display_area(c, viewbox.midpoint()); +} + + +/** + * Set new flip direction, keeping the point 'c' fixed in the desktop window. + * + * @param c Point in desktop coordinates + * @param flip Direction the canvas will be set as. + */ +void +SPDesktop::flip_absolute_keep_point (Geom::Point const &c, CanvasFlip flip) +{ + Geom::Point w = d2w(c); // Must be before flip. + _current_affine.setFlip(flip); + set_display_area(c, w); +} + + +/** + * Flip direction, keeping the point 'c' fixed in the desktop window. + * + * @param c Point in desktop coordinates + * @param flip Direction to flip canvas + */ +void +SPDesktop::flip_relative_keep_point (Geom::Point const &c, CanvasFlip flip) +{ + Geom::Point w = d2w(c); // Must be before flip. + _current_affine.addFlip(flip); + set_display_area(c, w); +} + + +/** + * Set new flip direction, aligning the point 'c' to the center of desktop window. + * + * @param c Point in desktop coordinates + * @param flip Direction the canvas will be set as. + */ +void +SPDesktop::flip_absolute_center_point (Geom::Point const &c, CanvasFlip flip) +{ + _current_affine.setFlip(flip); + Geom::Rect viewbox = canvas->getViewbox(); + set_display_area(c, viewbox.midpoint()); +} + + +/** + * Flip direction, aligning the point 'c' to the center of desktop window. + * + * @param c Point in desktop coordinates + * @param flip Direction to flip canvas + */ +void +SPDesktop::flip_relative_center_point (Geom::Point const &c, CanvasFlip flip) +{ + _current_affine.addFlip(flip); + Geom::Rect viewbox = canvas->getViewbox(); + set_display_area(c, viewbox.midpoint()); +} + +bool +SPDesktop::is_flipped (CanvasFlip flip) +{ + return _current_affine.isFlipped(flip); +} + + +/** + * Scroll canvas by to a particular point (window coordinates). + */ +void +SPDesktop::scroll_absolute (Geom::Point const &point, bool is_scrolling) +{ + canvas->scrollTo(point, FALSE, is_scrolling); + _current_affine.setOffset( point ); + + /* update perspective lines if we are in the 3D box tool (so that infinite ones are shown correctly) */ + //sp_box3d_context_update_lines(event_context); + if (SP_IS_BOX3D_CONTEXT(event_context)) { + SP_BOX3D_CONTEXT(event_context)->_vpdrag->updateLines(); + } + + _widget->updateRulers(); + _widget->updateScrollbars(_current_affine.getZoom()); +} + + +/** + * Scroll canvas by specific coordinate amount (window coordinates). + */ +void +SPDesktop::scroll_relative (Geom::Point const &delta, bool is_scrolling) +{ + Geom::Rect const viewbox = canvas->getViewbox(); + scroll_absolute( viewbox.min() - delta, is_scrolling ); +} + + +/** + * Scroll canvas by specific coordinate amount in svg coordinates. + */ +void +SPDesktop::scroll_relative_in_svg_coords (double dx, double dy, bool is_scrolling) +{ + double scale = _current_affine.getZoom(); + scroll_relative(Geom::Point(dx*scale, dy*scale), is_scrolling); +} + + +/** + * Scroll screen so as to keep point 'p' visible in window. + * (Used, for example, when a node is being dragged.) + * 'p': The point in desktop coordinates. + * 'autoscrollspeed': The scroll speed (or zero to use preferences' value). + */ +bool +SPDesktop::scroll_to_point (Geom::Point const &p, gdouble autoscrollspeed) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // autoscrolldistance is in screen pixels. + gdouble autoscrolldistance = (gdouble) prefs->getIntLimited("/options/autoscrolldistance/value", 0, -1000, 10000); + + Geom::Rect w = canvas->getViewbox(); // Window in screen coordinates. + w.expandBy(-autoscrolldistance); // Shrink window + + Geom::Point c = d2w(p); // Point 'p' in screen coordinates. + if (!w.contains(c)) { + + Geom::Point c2 = w.clamp(c); // Constrain c to window. + + if (autoscrollspeed == 0) + autoscrollspeed = prefs->getDoubleLimited("/options/autoscrollspeed/value", 1, 0, 10); + + if (autoscrollspeed != 0) + scroll_relative (autoscrollspeed * (c2 - c) ); + + return true; + } + return false; +} + +bool +SPDesktop::is_iconified() +{ + return 0!=(window_state & GDK_WINDOW_STATE_ICONIFIED); +} + +void +SPDesktop::iconify() +{ + _widget->setIconified(); +} + +bool SPDesktop::is_darktheme() { return getToplevel()->get_style_context()->has_class("dark"); } + +bool +SPDesktop::is_maximized() +{ + return 0!=(window_state & GDK_WINDOW_STATE_MAXIMIZED); +} + +void +SPDesktop::maximize() +{ + _widget->setMaximized(); +} + +bool +SPDesktop::is_fullscreen() +{ + return 0!=(window_state & GDK_WINDOW_STATE_FULLSCREEN); +} + +void +SPDesktop::fullscreen() +{ + _widget->setFullscreen(); +} + +/** + * Checks to see if the user is working in focused mode. + * + * @return the value of \c _focusMode. + */ +bool SPDesktop::is_focusMode() +{ + return _focusMode; +} + +/** + * Changes whether the user is in focus mode or not. + * + * @param mode Which mode the view should be in. + */ +void SPDesktop::focusMode(bool mode) +{ + if (mode == _focusMode) { return; } + + _focusMode = mode; + + layoutWidget(); + //sp_desktop_widget_layout(SPDesktopWidget); + + return; +} + +void +SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h) +{ + _widget->getGeometry (x, y, w, h); +} + +void +SPDesktop::setWindowPosition (Geom::Point p) +{ + _widget->setPosition (p); +} + +void +SPDesktop::setWindowSize (gint w, gint h) +{ + _widget->setSize (w, h); +} + +void +SPDesktop::setWindowTransient (void *p, int transient_policy) +{ + _widget->setTransient (p, transient_policy); +} + +Gtk::Window* +SPDesktop::getToplevel( ) +{ + return _widget->getWindow(); +} + +InkscapeWindow* +SPDesktop::getInkscapeWindow( ) +{ + InkscapeWindow* window = dynamic_cast<InkscapeWindow*>(_widget->getWindow()); + if (!window) { + std::cerr << "SPDesktop::getInkscapeWindow: Failed to get window." << std::endl; + } + return window; +} + +void +SPDesktop::presentWindow() +{ + _widget->present(); +} + +bool SPDesktop::showInfoDialog( Glib::ustring const & message ) +{ + return _widget->showInfoDialog( message ); +} + +bool +SPDesktop::warnDialog (Glib::ustring const &text) +{ + return _widget->warnDialog (text); +} + +void +SPDesktop::toggleRulers() +{ + _widget->toggleRulers(); + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS); + if (verb) { + _menu_update.emit(verb->get_code(), getStateFromPref(this, "rulers")); + } +} + +void +SPDesktop::toggleScrollbars() +{ + _widget->toggleScrollbars(); + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS); + if (verb) { + _menu_update.emit(verb->get_code(), getStateFromPref(this, "scrollbars")); + } +} + + +void SPDesktop::toggleToolbar(gchar const *toolbar_name, unsigned int verbenum) +{ + Glib::ustring pref_path = getLayoutPrefPath(this) + toolbar_name + "/state"; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gboolean visible = prefs->getBool(pref_path, true); + prefs->setBool(pref_path, !visible); + Inkscape::Verb *verb = Inkscape::Verb::get(verbenum); + if (verb) { + _menu_update.emit(verb->get_code(), getStateFromPref(this, toolbar_name)); + } + layoutWidget(); +} + +void +SPDesktop::layoutWidget() +{ + _widget->layout(); +} + +void +SPDesktop::destroyWidget() +{ + _widget->destroy(); +} + +bool +SPDesktop::shutdown() +{ + return _widget->shutdown(); +} + +bool SPDesktop::onDeleteUI (GdkEventAny*) +{ + if(shutdown()) + return true; + + destroyWidget(); + return false; +} + +/** + * onWindowStateEvent + * + * Called when the window changes its maximize/fullscreen/iconify/pinned state. + * Since GTK doesn't have a way to query this state information directly, we + * record it for the desktop here, and also possibly trigger a layout. + */ +bool +SPDesktop::onWindowStateEvent (GdkEventWindowState* event) +{ + // Record the desktop window's state + window_state = event->new_window_state; + + // Layout may differ depending on full-screen mode or not + GdkWindowState changed = event->changed_mask; + if (changed & (GDK_WINDOW_STATE_FULLSCREEN|GDK_WINDOW_STATE_MAXIMIZED)) { + layoutWidget(); + } + + return false; +} + + +/** + * Apply the desktop's current style or the tool style to the object. + */ +void SPDesktop::applyCurrentOrToolStyle(SPObject *obj, Glib::ustring const &tool_path, bool with_text) +{ + SPCSSAttr *css_current = sp_desktop_get_style(this, with_text); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool(tool_path + "/usecurrent") && css_current) { + obj->setCSS(css_current,"style"); + } else { + SPCSSAttr *css = prefs->getInheritedStyle(tool_path + "/style"); + obj->setCSS(css,"style"); + sp_repr_css_attr_unref(css); + } + if (css_current) { + sp_repr_css_attr_unref(css_current); + } +} + + +void +SPDesktop::setToolboxFocusTo (gchar const *label) +{ + _widget->setToolboxFocusTo (label); +} + +void +SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val) +{ + _widget->setToolboxAdjustmentValue (id, val); +} + +Gtk::Toolbar* +SPDesktop::get_toolbar_by_name(const Glib::ustring& name) +{ + return _widget->get_toolbar_by_name(name); +} + +bool +SPDesktop::isToolboxButtonActive (gchar const *id) +{ + return _widget->isToolboxButtonActive (id); +} + +void +SPDesktop::emitToolSubselectionChanged(gpointer data) +{ + _tool_subselection_changed.emit(data); + INKSCAPE.subselection_changed (this); +} + +void SPDesktop::updateNow() +{ + canvas->updateNow(); +} + +void +SPDesktop::enableInteraction() +{ + _widget->enableInteraction(); +} + +void SPDesktop::disableInteraction() +{ + _widget->disableInteraction(); +} + +void SPDesktop::setWaitingCursor() +{ + Glib::RefPtr<Gdk::Display> display = Gdk::Display::get_default(); + Glib::RefPtr<Gdk::Cursor> waiting = Gdk::Cursor::create(display, Gdk::WATCH); + Glib::wrap(GTK_WIDGET(getCanvas()))->get_window()->set_cursor(waiting); + // GDK needs the flush for the cursor change to take effect + display->flush(); + waiting_cursor = true; +} + +void SPDesktop::clearWaitingCursor() { + if (waiting_cursor) { + this->event_context->sp_event_context_update_cursor(); + } +} + +void SPDesktop::toggleColorProfAdjust() +{ + _widget->toggleColorProfAdjust(); + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_VIEW_CMS_TOGGLE); + if (verb) { + _menu_update.emit(verb->get_code(), colorProfAdjustEnabled()); + } +} + +void SPDesktop::toggleGuidesLock() +{ + sp_namedview_guides_toggle_lock(this->getDocument(), namedview); + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_EDIT_GUIDES_TOGGLE_LOCK); + if (verb) { + _menu_update.emit(verb->get_code(), namedview->lockguides); + } +} + +bool SPDesktop::colorProfAdjustEnabled() +{ + return _widget->colorProfAdjustEnabled(); +} + +void SPDesktop::toggleGrids() +{ + if (! namedview->grids.empty()) { + if(gridgroup) { + showGrids(!grids_visible); + } + } else { + //there is no grid present at the moment. add a rectangular grid and make it visible + namedview->writeNewGrid(this->getDocument(), Inkscape::GRID_RECTANGULAR); + showGrids(true); + } + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_TOGGLE_GRID); + if (verb) { + _menu_update.emit(verb->get_code(), gridsEnabled()); + } +} + +void SPDesktop::toggleSplitMode() +{ + if (this->getToplevel()) { + _split_canvas = !_split_canvas; + if (_split_canvas && _xray) { + toggleXRay(); + } + SPCanvas *canvas = getCanvas(); + canvas->requestFullRedraw(); + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_VIEW_TOGGLE_SPLIT); + if (verb) { + _menu_update.emit(verb->get_code(), splitMode()); + } + if (_display_mode == Inkscape::RENDERMODE_OUTLINE) { + if (_split_canvas) { + toggleSplitMode(); + } + } + } +} + +void SPDesktop::toggleXRay() +{ + if (this->getToplevel()) { + _xray = !_xray; + if (_split_canvas && _xray) { + toggleSplitMode(); + } + SPCanvas *canvas = getCanvas(); + canvas->requestFullRedraw(); + Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_VIEW_TOGGLE_XRAY); + if (verb) { + _menu_update.emit(verb->get_code(), xrayMode()); + } + if (_display_mode == Inkscape::RENDERMODE_OUTLINE) { + if (_xray) { + toggleXRay(); + } + } + } +} + +void SPDesktop::showGrids(bool show, bool dirty_document) +{ + grids_visible = show; + sp_namedview_show_grids(namedview, grids_visible, dirty_document); + if (show) { + sp_canvas_item_show(SP_CANVAS_ITEM(gridgroup)); + } else { + sp_canvas_item_hide(SP_CANVAS_ITEM(gridgroup)); + } +} + +void SPDesktop::toggleSnapGlobal() +{ + bool v = namedview->getSnapGlobal(); + namedview->setSnapGlobal(!v); +} + +//---------------------------------------------------------------------- +// Callback implementations. The virtual ones are connected by the view. + +void +SPDesktop::onResized (double /*x*/, double /*y*/) +{ + // Nothing called here +} + +/** + * Redraw callback; queues Gtk redraw; connected by View. + */ +void SPDesktop::storeDesktopPosition() +{ + if (main) { + _widget->storeDesktopPosition(); + } +} + +/** + * Redraw callback; queues Gtk redraw; connected by View. + */ +void +SPDesktop::onRedrawRequested () +{ + if (main) { + _widget->requestCanvasUpdate(); + } +} + +void +SPDesktop::updateCanvasNow() +{ + _widget->requestCanvasUpdateAndWait(); +} + +/** + * Associate document with desktop. + */ +void +SPDesktop::setDocument (SPDocument *doc) +{ + if (!doc) return; + + if (this->doc()) { + namedview->hide(this); + this->doc()->getRoot()->invoke_hide(dkey); + } + + layers->setDocument(doc); + selection->setDocument(doc); + + if (event_log) { + // Remove it from the replaced document. This prevents Inkscape from + // crashing since we access it in the replaced document's destructor + // which results in an undefined behavior. (See also: bug #1670688) + if (this->doc()) { + this->doc()->removeUndoObserver(*event_log); + } + delete event_log; + event_log = nullptr; + } + + /* setup EventLog */ + event_log = new Inkscape::EventLog(doc); + doc->addUndoObserver(*event_log); + + _commit_connection.disconnect(); + _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow)); + + /// \todo fixme: This condition exists to make sure the code + /// inside is NOT called on initialization, only on replacement. But there + /// are surely more safe methods to accomplish this. + // TODO since the comment had reversed logic, check the intent of this block of code: + if (drawing) { + Inkscape::DrawingItem *ai = nullptr; + + namedview = sp_document_namedview (doc, nullptr); + _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this)); + number = namedview->getViewCount(); + + ai = doc->getRoot()->invoke_show( + SP_CANVAS_ARENA (drawing)->drawing, + dkey, + SP_ITEM_SHOW_DISPLAY); + if (ai) { + SP_CANVAS_ARENA (drawing)->drawing.root()->prependChild(ai); + } + namedview->show(this); + /* Ugly hack */ + activate_guides (true); + /* Ugly hack */ + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + } + + _document_replaced_signal.emit (this, doc); + + View::setDocument (doc); +} + +void +SPDesktop::onStatusMessage +(Inkscape::MessageType type, gchar const *message) +{ + if (_widget) { + _widget->setMessage(type, message); + } +} + +void +SPDesktop::onDocumentURISet (gchar const* uri) +{ + _widget->setTitle(uri); +} + +/** + * Resized callback. + */ +void +SPDesktop::onDocumentResized (gdouble width, gdouble height) +{ + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), doc2dt()); + Geom::Rect const a(Geom::Point(0, 0), Geom::Point(width, height)); + SP_CTRLRECT(page)->setRectangle(a); + SP_CTRLRECT(page_border)->setRectangle(a); +} + + +void +SPDesktop::_onActivate (SPDesktop* dt) +{ + if (!dt->_widget) return; + dt->_widget->activateDesktop(); +} + +void +SPDesktop::_onDeactivate (SPDesktop* dt) +{ + if (!dt->_widget) return; + dt->_widget->deactivateDesktop(); +} + +void +SPDesktop::_onSelectionModified +(Inkscape::Selection */*selection*/, guint /*flags*/, SPDesktop *dt) +{ + if (!dt->_widget) return; + dt->_widget->updateScrollbars (dt->_current_affine.getZoom()); +} + +static void +_onSelectionChanged +(Inkscape::Selection *selection, SPDesktop *desktop) +{ + /** \todo + * only change the layer for single selections, or what? + * This seems reasonable -- for multiple selections there can be many + * different layers involved. + */ + SPItem *item=selection->singleItem(); + if (item) { + SPObject *layer=desktop->layers->layerForObject(item); + if ( layer && layer != desktop->currentLayer() ) { + desktop->layers->setCurrentLayer(layer); + } + } +} + +/** + * Calls event handler of current event context. + * \param arena Unused + * \todo fixme + */ +static gint +_arena_handler (SPCanvasArena */*arena*/, Inkscape::DrawingItem *ai, GdkEvent *event, SPDesktop *desktop) +{ + if (event->type == GDK_KEY_PRESS && + Inkscape::UI::Tools::get_latin_keyval(&event->key) == GDK_KEY_space && + desktop->event_context->space_panning) + { + return true; + } + if (ai) { + SPItem *spi = ai->getItem(); + return sp_event_context_item_handler (desktop->event_context, spi, event); + } else { + return sp_event_context_root_handler (desktop->event_context, event); + } +} + +static void +_layer_activated(SPObject *layer, SPDesktop *desktop) { + g_return_if_fail(SP_IS_GROUP(layer)); + SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER); +} + +/// Callback +static void +_layer_deactivated(SPObject *layer, SPDesktop *desktop) { + g_return_if_fail(SP_IS_GROUP(layer)); + SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP); +} + +/// Callback +static void +_layer_hierarchy_changed(SPObject */*top*/, SPObject *bottom, + SPDesktop *desktop) +{ + desktop->_layer_changed_signal.emit (bottom); +} + +/// Called when document is starting to be rebuilt. +static void _reconstruction_start(SPDesktop * desktop) +{ + desktop->_reconstruction_old_layer_id = desktop->currentLayer()->getId() ? desktop->currentLayer()->getId() : ""; + desktop->layers->reset(); + + desktop->selection->clear(); +} + +/// Called when document rebuild is finished. +static void _reconstruction_finish(SPDesktop * desktop) +{ + g_debug("Desktop, finishing reconstruction\n"); + if ( !desktop->_reconstruction_old_layer_id.empty() ) { + SPObject * newLayer = desktop->namedview->document->getObjectById(desktop->_reconstruction_old_layer_id); + if (newLayer != nullptr) { + desktop->layers->setCurrentLayer(newLayer); + } + + desktop->_reconstruction_old_layer_id.clear(); + } + g_debug("Desktop, finishing reconstruction end\n"); +} + +/** + * Namedview_modified callback. + */ +static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop) +{ + SPNamedView *nv=SP_NAMEDVIEW(obj); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + if (nv->pagecheckerboard) { + desktop->canvas->setBackgroundCheckerboard(nv->pagecolor); + } else { + desktop->canvas->setBackgroundColor(nv->pagecolor); + } + + /* Show/hide page border */ + if (nv->showborder) { + // show + sp_canvas_item_show (desktop->page_border); + // set color and shadow + ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000); + if (nv->pageshadow) { + ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor); + } + // place in the z-order stack + if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) { + sp_canvas_item_move_to_z (desktop->page_border, 1); + } else { + int order = sp_canvas_item_order (desktop->page_border); + int morder = sp_canvas_item_order (desktop->drawing); + if (morder > order) sp_canvas_item_raise (desktop->page_border, + morder - order); + } + } else { + sp_canvas_item_hide (desktop->page_border); + if (nv->pageshadow) { + ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000); + } + } + + /* Show/hide page shadow */ + if (nv->showpageshadow && nv->pageshadow) { + ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor); + } else { + ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (SP_RGBA32_R_U(nv->pagecolor) + + SP_RGBA32_G_U(nv->pagecolor) + + SP_RGBA32_B_U(nv->pagecolor) >= 384) { + // the background color is light, use black outline + SP_CANVAS_ARENA (desktop->drawing)->drawing.outlinecolor = prefs->getInt("/options/wireframecolors/onlight", 0xff); + } else { // use white outline + SP_CANVAS_ARENA (desktop->drawing)->drawing.outlinecolor = prefs->getInt("/options/wireframecolors/ondark", 0xffffffff); + } + } +} + +Geom::Affine SPDesktop::w2d() const +{ + return _current_affine.w2d(); +} + +Geom::Point SPDesktop::w2d(Geom::Point const &p) const +{ + return p * _current_affine.w2d(); +} + +Geom::Point SPDesktop::d2w(Geom::Point const &p) const +{ + return p * _current_affine.d2w(); +} + +const Geom::Affine &SPDesktop::doc2dt() const +{ + g_assert(doc() != nullptr); + return doc()->doc2dt(); +} + +Geom::Affine SPDesktop::dt2doc() const +{ + g_assert(doc() != nullptr); + return doc()->dt2doc(); +} + +Geom::Point SPDesktop::doc2dt(Geom::Point const &p) const +{ + return p * doc2dt(); +} + +Geom::Point SPDesktop::dt2doc(Geom::Point const &p) const +{ + return p * dt2doc(); +} + +void +SPDesktop::show_dialogs() +{ + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs == nullptr) { + return; + } + + int active = prefs->getInt("/options/savedialogposition/value", 1); + if (active == 0) { + // User has turned off this feature in preferences + return; + } + + if (showing_dialogs) { + return; + } + + showing_dialogs = TRUE; + + + /* + * Get each dialogs previous state from preferences and reopen on startup if needed, without grabbing focus (canvas retains focus). + * Map dialog manager's dialog IDs to dialog last visible state preference. FIXME: store this correspondence in dialogs themselves! + */ + std::map<Glib::ustring, Glib::ustring> mapVerbPreference; + mapVerbPreference.insert(std::make_pair ("LayersPanel", "/dialogs/layers") ); + mapVerbPreference.insert(std::make_pair ("FillAndStroke", "/dialogs/fillstroke") ); + mapVerbPreference.insert(std::make_pair ("ExtensionEditor", "/dialogs/extensioneditor") ); + mapVerbPreference.insert(std::make_pair ("AlignAndDistribute", "/dialogs/align") ); + mapVerbPreference.insert(std::make_pair ("DocumentMetadata", "/dialogs/documentmetadata") ); + mapVerbPreference.insert(std::make_pair ("DocumentProperties", "/dialogs/documentoptions") ); + mapVerbPreference.insert(std::make_pair ("FilterEffectsDialog", "/dialogs/filtereffects") ); + mapVerbPreference.insert(std::make_pair ("Find", "/dialogs/find") ); + mapVerbPreference.insert(std::make_pair ("Glyphs", "/dialogs/glyphs") ); + mapVerbPreference.insert(std::make_pair ("Messages", "/dialogs/messages") ); + mapVerbPreference.insert(std::make_pair ("Memory", "/dialogs/memory") ); + mapVerbPreference.insert(std::make_pair ("LivePathEffect", "/dialogs/livepatheffect") ); + mapVerbPreference.insert(std::make_pair ("UndoHistory", "/dialogs/undo-history") ); + mapVerbPreference.insert(std::make_pair ("Transformation", "/dialogs/transformation") ); + mapVerbPreference.insert(std::make_pair ("Swatches", "/dialogs/swatches") ); + mapVerbPreference.insert(std::make_pair ("IconPreviewPanel", "/dialogs/iconpreview") ); + mapVerbPreference.insert(std::make_pair ("SvgFontsDialog", "/dialogs/svgfonts") ); + mapVerbPreference.insert(std::make_pair ("InputDevices", "/dialogs/inputdevices") ); + mapVerbPreference.insert(std::make_pair ("InkscapePreferences", "/dialogs/preferences") ); + mapVerbPreference.insert(std::make_pair ("TileDialog", "/dialogs/gridtiler") ); + mapVerbPreference.insert(std::make_pair ("Trace", "/dialogs/trace") ); + mapVerbPreference.insert(std::make_pair ("TextFont", "/dialogs/textandfont") ); + mapVerbPreference.insert(std::make_pair ("Export", "/dialogs/export") ); + mapVerbPreference.insert(std::make_pair ("XmlTree", "/dialogs/xml") ); + mapVerbPreference.insert(std::make_pair ("Selectors", "/dialogs/selectors") ); + mapVerbPreference.insert(std::make_pair ("CloneTiler", "/dialogs/clonetiler") ); + mapVerbPreference.insert(std::make_pair ("ObjectProperties", "/dialogs/object") ); + mapVerbPreference.insert(std::make_pair ("SpellCheck", "/dialogs/spellcheck") ); + mapVerbPreference.insert(std::make_pair ("Symbols", "/dialogs/symbols") ); + mapVerbPreference.insert(std::make_pair ("PaintServers", "/dialogs/paint") ); + mapVerbPreference.insert(std::make_pair ("ObjectsPanel", "/dialogs/objects") ); + mapVerbPreference.insert(std::make_pair ("TagsPanel", "/dialogs/tags") ); + mapVerbPreference.insert(std::make_pair ("Prototype", "/dialogs/prototype") ); + + + for (std::map<Glib::ustring, Glib::ustring>::const_iterator iter = mapVerbPreference.begin(); iter != mapVerbPreference.end(); ++iter) { + Glib::ustring pref = iter->second; + + int visible = prefs->getInt(pref + "/visible", 0); + if (visible) { + +#ifdef GDK_WINDOWING_WAYLAND + // Hack to prevent crash with Wayland. See: https://gitlab.com/inkscape/inkscape/-/issues/454 + if (iter->first == "InkscapePreferences") { + Glib::ustring session_type = Glib::getenv("XDG_SESSION_TYPE"); // Window session + Glib::ustring session_type2 = Glib::getenv("GDK_BACKEND"); // Possible override + if (session_type == "wayland" && session_type2 != "x11") { + std::cerr << "SPDesktop::show_dialog: Cannot restore InkscapePreferences dialog due to GTK Wayland bug." << std::endl; + continue; + } + } +#endif + + // Try to ensure that the panel is created attached to the correct desktop (bug 1720096). + // There must be a better way of handling this problem! + INKSCAPE.activate_desktop(this); + + _dlg_mgr->showDialog(iter->first.c_str(), false); // without grabbing focus, we need focus to remain on the canvas + } + } +} +/* + * Pop event context from desktop's context stack. Never used. + */ +// void +// SPDesktop::pop_event_context (unsigned int key) +// { +// ToolBase *ec = NULL; +// +// if (event_context && event_context->key == key) { +// g_return_if_fail (event_context); +// g_return_if_fail (event_context->next); +// ec = event_context; +// sp_event_context_deactivate (ec); +// event_context = ec->next; +// sp_event_context_activate (event_context); +// _event_context_changed_signal.emit (this, ec); +// } +// +// ToolBase *ref = event_context; +// while (ref && ref->next && ref->next->key != key) +// ref = ref->next; +// +// if (ref && ref->next) { +// ec = ref->next; +// ref->next = ec->next; +// } +// +// if (ec) { +// sp_event_context_finish (ec); +// g_object_unref (G_OBJECT (ec)); +// } +// } + +/* + 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 : |