// SPDX-License-Identifier: GPL-2.0-or-later #ifndef SEEN_SP_DESKTOP_H #define SEEN_SP_DESKTOP_H /* * Author: * Lauris Kaplinski * Frank Felfe * bulia byak * Ralf Stephan * John Bintz * Johan Engelen * Jon A. Cruz get * Abhishek Sharma * * Copyright (C) 2007 Johan Engelen * Copyright (C) 2006 John Bintz * Copyright (C) 1999-2005 authors * Copyright (C) 2000-2001 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. * */ #include #include #include #include <2geom/affine.h> #include <2geom/transforms.h> #include <2geom/rect.h> #include "preferences.h" #include "display/rendermode.h" #include "object/sp-gradient.h" // TODO refactor enums out to their own .h file #include "ui/dialog/print.h" #include "ui/view/view.h" class SPCSSAttr; struct SPCanvas; struct SPCanvasItem; struct SPCanvasGroup; struct DesktopPrefObserver; namespace Inkscape { namespace UI { namespace Tools { class ToolBase; } } } class SPItem; class SPNamedView; class SPObject; class SPStyle; typedef struct _DocumentInterface DocumentInterface;//struct DocumentInterface; class InkscapeWindow; namespace Gtk { class Toolbar; class Window; } typedef int sp_verb_t; struct _GdkEventAny; typedef struct _GdkEventAny GdkEventAny; struct _GdkEventWindowState; typedef struct _GdkEventWindowState GdkEventWindowState; struct InkscapeApplication; namespace Inkscape { class LayerModel; class MessageContext; class Selection; class LayerManager; class EventLog; namespace UI { namespace Dialog { class DialogManager; } namespace Widget { class Dock; } namespace View { struct EditWidgetInterface; } } namespace Whiteboard { class SessionManager; } namespace Display { class TemporaryItemList; class TemporaryItem; class SnapIndicator; } } #define SP_DESKTOP_ZOOM_MAX 256.0 #define SP_DESKTOP_ZOOM_MIN 0.01 /** * SPDesktop is a subclass of View, implementing an editable document * canvas. It is extensively used by many UI controls that need certain * visual representations of their own. * * SPDesktop provides a certain set of SPCanvasItems, serving as GUI * layers of different control objects. The one containing the whole * document is the drawing layer. In addition to it, there are grid, * guide, sketch and control layers. The sketch layer is used for * temporary drawing objects, before the real objects in document are * created. The control layer contains editing knots, rubberband and * similar non-document UI objects. * * Each SPDesktop is associated with a SPNamedView node of the document * tree. Currently, all desktops are created from a single main named * view, but in the future there may be support for different ones. * SPNamedView serves as an in-document container for desktop-related * data, like grid and guideline placement, snapping options and so on. * * Associated with each SPDesktop are the two most important editing * related objects - SPSelection and ToolBase. * * Sodipodi keeps track of the active desktop and invokes notification * signals whenever it changes. UI elements can use these to update their * display to the selection of the currently active editing window. * (Lauris Kaplinski) * * @see \ref desktop-handles.h for desktop macros. */ class SPDesktop : public Inkscape::UI::View::View { public: Inkscape::UI::Dialog::DialogManager *_dlg_mgr; SPNamedView *namedview; SPCanvas *canvas; Inkscape::LayerModel *layers; /// current selection; will never generally be NULL Inkscape::Selection *selection; Inkscape::UI::Tools::ToolBase *event_context; Inkscape::LayerManager *layer_manager; Inkscape::EventLog *event_log; DocumentInterface *dbus_document_interface; Inkscape::Display::TemporaryItemList *temporary_item_list; Inkscape::Display::SnapIndicator *snapindicator; /// Stored settings for print dialogue Inkscape::UI::Dialog::PrinterSettings printer_settings; Inkscape::UI::Tools::ToolBase* getEventContext() const; Inkscape::Selection* getSelection() const; SPDocument* getDocument() const; SPCanvas* getCanvas() const; SPCanvasItem* getAcetate() const; SPCanvasGroup* getMain() const; SPCanvasGroup* getGridGroup() const; SPCanvasGroup* getGuides() const; SPCanvasItem* getDrawing() const; SPCanvasGroup* getSketch() const; SPCanvasGroup* getControls() const; SPCanvasGroup* getTempGroup() const; Inkscape::MessageStack* getMessageStack() const; SPNamedView* getNamedView() const; SPCanvasItem *acetate; SPCanvasGroup *main; SPCanvasGroup *gridgroup; SPCanvasGroup *guides; SPCanvasItem *drawing; SPCanvasGroup *sketch; SPCanvasGroup *controls; SPCanvasGroup *tempgroup; ///< contains temporary canvas items SPCanvasItem *page; ///< page background SPCanvasItem *page_border; ///< page border SPCanvasItem *canvas_rotate; ///< quickly show canvas rotation SPCanvasItem *canvas_debug; ///< shows tiling SPCSSAttr *current; ///< current style bool _focusMode; ///< Whether we're focused working or general working unsigned int dkey; unsigned int number; guint window_state; unsigned int interaction_disabled_counter; bool waiting_cursor; bool showing_dialogs; bool rotation_locked; /// \todo fixme: This has to be implemented in different way */ guint guides_active : 1; // storage for selected dragger used by GrDrag as it's // created and deleted by tools SPItem *gr_item; GrPointType gr_point_type; guint gr_point_i; Inkscape::PaintTarget gr_fill_or_stroke; Glib::ustring _reconstruction_old_layer_id; sigc::signal _tool_changed; sigc::signal _menu_update; sigc::signal _layer_changed_signal; sigc::signal::accumulated _set_style_signal; sigc::signal::accumulated _query_style_signal; /// Emitted when the zoom factor changes (not emitted when scrolling). /// The parameter is the new zoom factor sigc::signal signal_zoom_changed; sigc::connection connectDestroy(const sigc::slot &slot) { return _destroy_signal.connect(slot); } sigc::connection connectDocumentReplaced (const sigc::slot & slot) { return _document_replaced_signal.connect (slot); } sigc::connection connectEventContextChanged (const sigc::slot & slot) { return _event_context_changed_signal.connect (slot); } sigc::connection connectSetStyle (const sigc::slot & slot) { return _set_style_signal.connect (slot); } sigc::connection connectQueryStyle (const sigc::slot & slot) { return _query_style_signal.connect (slot); } // subselection is some sort of selection which is specific to the tool, such as a handle in gradient tool, or a text selection sigc::connection connectToolSubselectionChanged(const sigc::slot & slot) { return _tool_subselection_changed.connect(slot); } void emitToolSubselectionChanged(gpointer data); sigc::connection connectCurrentLayerChanged(const sigc::slot & slot) { return _layer_changed_signal.connect(slot); } /** * Return new desktop object. * \pre namedview != NULL. * \pre canvas != NULL. */ SPDesktop(); void init (SPNamedView* nv, SPCanvas* canvas, Inkscape::UI::View::EditWidgetInterface *widget); ~SPDesktop() override; void destroy(); Inkscape::MessageContext *guidesMessageContext() const { return _guides_message_context.get(); } Inkscape::Display::TemporaryItem * add_temporary_canvasitem (SPCanvasItem *item, guint lifetime, bool move_to_bottom = true); void remove_temporary_canvasitem (Inkscape::Display::TemporaryItem * tempitem); void redrawDesktop(); void _setDisplayMode(Inkscape::RenderMode mode); bool setDisplayModeNormal() { _setDisplayMode(Inkscape::RENDERMODE_NORMAL); return true; } bool setDisplayModeNoFilters() { _setDisplayMode(Inkscape::RENDERMODE_NO_FILTERS); return true; } bool setDisplayModeOutline() { _setDisplayMode(Inkscape::RENDERMODE_OUTLINE); return true; } bool setDisplayModeVisibleHairlines() { _setDisplayMode(Inkscape::RENDERMODE_VISIBLE_HAIRLINES); return true; } bool displayModeToggle(); Inkscape::RenderMode _display_mode; Inkscape::RenderMode getMode() const { return _display_mode; } void _setDisplayColorMode(Inkscape::ColorMode mode); bool setDisplayColorModeNormal() { _setDisplayColorMode(Inkscape::COLORMODE_NORMAL); return true; } bool setDisplayColorModeGrayscale() { _setDisplayColorMode(Inkscape::COLORMODE_GRAYSCALE); return true; } // void setDisplayColorModePrintColorsPreview() { // _setDisplayColorMode(Inkscape::COLORMODE_PRINT_COLORS_PREVIEW); // } bool displayColorModeToggle(); Inkscape::ColorMode _display_color_mode; Inkscape::ColorMode getColorMode() const { return _display_color_mode; } Inkscape::UI::Widget::Dock* getDock(); void set_active (bool new_active); // Could make all callers use this->layers instead of passing calls through? SPObject *currentRoot() const; SPObject *currentLayer() const; void setCurrentLayer(SPObject *object); void toggleLayerSolo(SPObject *object); void toggleHideAllLayers(bool hide); void toggleLockAllLayers(bool lock); void toggleLockOtherLayers(SPObject *object); bool isLayer(SPObject *object) const; bool isWithinViewport(SPItem *item) const; bool itemIsHidden(SPItem const *item) const; void activate_guides (bool activate); void change_document (SPDocument *document); void setEventContext(const std::string& toolName); void set_coordinate_status (Geom::Point p); SPItem *getItemFromListAtPointBottom(const std::vector &list, Geom::Point const &p) const; SPItem *getItemAtPoint(Geom::Point const &p, bool into_groups, SPItem *upto = nullptr) const; SPItem *getGroupAtPoint(Geom::Point const &p) const; Geom::Point point(bool outside_canvas = false) const; void prev_transform(); void next_transform(); void clear_transform_history(); void set_display_area (bool log = true); void set_display_area (Geom::Point const &c, Geom::Point const &w, bool log = true); void set_display_area (Geom::Rect const &a, Geom::Coord border, bool log = true); Geom::Rect get_display_area(bool use_integer_viewbox = false) const; void zoom_absolute_keep_point (Geom::Point const &c, double const zoom); void zoom_relative_keep_point (Geom::Point const &c, double const zoom); void zoom_absolute_center_point (Geom::Point const &c, double const zoom); void zoom_relative_center_point (Geom::Point const &c, double const zoom); void zoom_page(); void zoom_page_width(); void zoom_drawing(); void zoom_selection(); void zoom_center_page(); double current_zoom() const { return _current_affine.getZoom(); } void zoom_quick(bool enable = true); /** \brief Returns whether the desktop is in quick zoom mode or not */ bool quick_zoomed() { return _quick_zoom_enabled; } void toggle_rotation_lock() { rotation_locked = !rotation_locked; } bool get_rotation_lock() const { return rotation_locked; } void zoom_grab_focus(); void rotate_absolute_keep_point (Geom::Point const &c, double const rotate); void rotate_relative_keep_point (Geom::Point const &c, double const rotate); void rotate_absolute_center_point (Geom::Point const &c, double const rotate); void rotate_relative_center_point (Geom::Point const &c, double const rotate); enum CanvasFlip { FLIP_NONE = 0, FLIP_HORIZONTAL = 1, FLIP_VERTICAL = 2 }; void flip_absolute_keep_point (Geom::Point const &c, CanvasFlip flip); void flip_relative_keep_point (Geom::Point const &c, CanvasFlip flip); void flip_absolute_center_point (Geom::Point const &c, CanvasFlip flip); void flip_relative_center_point (Geom::Point const &c, CanvasFlip flip); bool is_flipped (CanvasFlip flip); double current_rotation() const { return _current_affine.getRotation(); } void scroll_absolute (Geom::Point const &point, bool is_scrolling = false); void scroll_relative (Geom::Point const &delta, bool is_scrolling = false); void scroll_relative_in_svg_coords (double dx, double dy, bool is_scrolling = false); bool scroll_to_point (Geom::Point const &s_dt, gdouble autoscrollspeed = 0); void getWindowGeometry (gint &x, gint &y, gint &w, gint &h); void setWindowPosition (Geom::Point p); void setWindowSize (gint w, gint h); void setWindowTransient (void* p, int transient_policy=1); Gtk::Window* getToplevel(); // To be removed in favor of getInkscapeWindow InkscapeWindow* getInkscapeWindow(); void presentWindow(); bool showInfoDialog( Glib::ustring const &message ); bool warnDialog (Glib::ustring const &text); void toggleRulers(); void toggleScrollbars(); void layoutWidget(); void destroyWidget(); void setToolboxFocusTo (gchar const* label); Gtk::Toolbar* get_toolbar_by_name(const Glib::ustring& name); void setToolboxAdjustmentValue (gchar const* id, double val); bool isToolboxButtonActive (gchar const *id); void updateNow(); void updateCanvasNow(); void storeDesktopPosition(); void enableInteraction(); void disableInteraction(); void setWaitingCursor(); void clearWaitingCursor(); bool isWaitingCursor() const { return waiting_cursor; }; void toggleGuidesLock(); void toggleColorProfAdjust(); bool colorProfAdjustEnabled(); void toggleGrids(); void toggleSplitMode(); void toggleXRay(); bool splitMode() const { return _split_canvas; }; bool xrayMode() const { return _xray; }; void toggleSnapGlobal(); bool gridsEnabled() const { return grids_visible; }; void showGrids(bool show, bool dirty_document = true); void toggleToolbar(gchar const *toolbar_name, unsigned int verbenum); bool is_iconified(); bool is_darktheme(); bool is_maximized(); bool is_fullscreen(); bool is_focusMode(); void iconify(); void maximize(); void fullscreen(); void focusMode(bool mode = true); /** * Reopen any dialogs that were open when inkscape last shutdown */ void show_dialogs(); // TODO return const ref instead of copy Geom::Affine w2d() const; //transformation from window to desktop coordinates (zoom/rotate). Geom::Point w2d(Geom::Point const &p) const; /// Transformation from desktop to window coordinates Geom::Affine d2w() const { return _current_affine.d2w(); } Geom::Point d2w(Geom::Point const &p) const; const Geom::Affine& doc2dt() const; Geom::Affine dt2doc() const; Geom::Point doc2dt(Geom::Point const &p) const; Geom::Point dt2doc(Geom::Point const &p) const; bool is_yaxisdown() const { return doc2dt()[3] > 0; } double yaxisdir() const { return doc2dt()[3]; } void setDocument (SPDocument* doc) override; bool shutdown() override; virtual bool onDeleteUI (GdkEventAny*); virtual bool onWindowStateEvent (GdkEventWindowState* event); void applyCurrentOrToolStyle(SPObject *obj, Glib::ustring const &tool_path, bool with_text); private: GtkGesture *zoomgesture = nullptr; Inkscape::UI::View::EditWidgetInterface *_widget; std::unique_ptr _guides_message_context; bool _active; // This simple class ensures that _w2d is always in sync with _rotation and _scale // We keep rotation and scale separate to avoid having to extract them from the affine. // With offset, this describes fully how to map the drawing to the window. // Future: merge offset as a translation in w2d. class DesktopAffine { public: Geom::Affine w2d() const { return _w2d; }; Geom::Affine d2w() const { return _d2w; }; void setScale( Geom::Scale scale ) { _scale = scale; _update(); } void addScale( Geom::Scale scale) { _scale *= scale; _update(); } void setRotate( Geom::Rotate rotate ) { _rotate = rotate; _update(); } void setRotate( double rotate ) { _rotate = Geom::Rotate( rotate ); _update(); } void addRotate( Geom::Rotate rotate ) { _rotate *= rotate; _update(); } void addRotate( double rotate ) { _rotate *= Geom::Rotate( rotate ); _update(); } void setFlip( CanvasFlip flip ) { _flip = Geom::Scale(); addFlip( flip ); } bool isFlipped( CanvasFlip flip ) { if ((flip & FLIP_HORIZONTAL) && Geom::are_near(_flip[0], -1)) { return true; } if ((flip & FLIP_VERTICAL) && Geom::are_near(_flip[1], -1)) { return true; } return false; } void addFlip( CanvasFlip flip ) { if (flip & FLIP_HORIZONTAL) { _flip *= Geom::Scale(-1.0, 1.0); } if (flip & FLIP_VERTICAL) { _flip *= Geom::Scale(1.0, -1.0); } _update(); } double getZoom() const { return _d2w.descrim(); } double getRotation() const { return _rotate.angle(); } void setOffset( Geom::Point offset ) { _offset = offset; } void addOffset( Geom::Point offset ) { _offset += offset; } Geom::Point getOffset() { return _offset; } private: void _update() { _d2w = _scale * _rotate * _flip; _w2d = _d2w.inverse(); } Geom::Affine _w2d; // Window to desktop Geom::Affine _d2w; // Desktop to window Geom::Rotate _rotate; // Rotate part of _w2d Geom::Scale _scale; // Scale part of _w2d, holds y-axis direction Geom::Scale _flip; // Flip part of _w2d Geom::Point _offset; // Point on canvas to align to (0,0) of window }; DesktopAffine _current_affine; std::list transforms_past; std::list transforms_future; bool _split_canvas; bool _xray; bool _quick_zoom_enabled; ///< Signifies that currently we're in quick zoom mode DesktopAffine _quick_zoom_affine; ///< The transform of the screen before quick zoom /* * Allow redrawing or refreshing if preferences change */ class DesktopPrefObserver : public Inkscape::Preferences::Observer { public: DesktopPrefObserver(SPDesktop *desktop, Glib::ustring const &path) : Inkscape::Preferences::Observer(path) , _desktop(desktop) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->addObserver(*this); } private: void notify(Inkscape::Preferences::Entry const &) override { _desktop->redrawDesktop(); } SPDesktop *_desktop; }; DesktopPrefObserver _image_render_observer; bool grids_visible; /* don't set this variable directly, use the method below */ void set_grids_visible(bool visible); sigc::signal _destroy_signal; sigc::signal _document_replaced_signal; sigc::signal _activate_signal; sigc::signal _deactivate_signal; sigc::signal _event_context_changed_signal; sigc::signal _tool_subselection_changed; sigc::connection _activate_connection; sigc::connection _deactivate_connection; sigc::connection _sel_modified_connection; sigc::connection _sel_changed_connection; sigc::connection _reconstruction_start_connection; sigc::connection _reconstruction_finish_connection; sigc::connection _commit_connection; sigc::connection _modified_connection; void onResized (double, double) override; void onRedrawRequested() override; void onStatusMessage (Inkscape::MessageType type, gchar const *message) override; void onDocumentURISet (gchar const* uri) override; void onDocumentResized (double, double) override; static void _onActivate (SPDesktop* dt); static void _onDeactivate (SPDesktop* dt); static void _onSelectionModified (Inkscape::Selection *selection, guint flags, SPDesktop *dt); }; #endif // SEEN_SP_DESKTOP_H /* 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 :