// SPDX-License-Identifier: GPL-2.0-or-later /* * implementation * * Authors: * Lauris Kaplinski * bulia byak * Jon A. Cruz * Abhishek Sharma * * Copyright (C) 2006 Johan Engelen * Copyright (C) 1999-2013 Authors * Copyright (C) 2000-2001 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include "event-log.h" #include <2geom/transforms.h> #include "display/canvas-grid.h" #include "util/units.h" #include "svg/svg-color.h" #include "xml/repr.h" #include "attributes.h" #include "document.h" #include "document-undo.h" #include "desktop-events.h" #include "enums.h" #include "ui/monitor.h" #include "sp-guide.h" #include "sp-item-group.h" #include "sp-namedview.h" #include "preferences.h" #include "desktop.h" #include "conn-avoid-ref.h" // for defaultConnSpacing. #include "sp-root.h" #include using Inkscape::DocumentUndo; using Inkscape::Util::unit_table; #define DEFAULTGRIDCOLOR 0x3f3fff25 #define DEFAULTGRIDEMPCOLOR 0x3f3fff60 #define DEFAULTGRIDEMPSPACING 5 #define DEFAULTGUIDECOLOR 0x0000ff7f #define DEFAULTGUIDEHICOLOR 0xff00007f #define DEFAULTBORDERCOLOR 0x000000ff #define DEFAULTPAGECOLOR 0xffffff00 static void sp_namedview_setup_guides(SPNamedView * nv); static void sp_namedview_lock_guides(SPNamedView * nv); static void sp_namedview_show_single_guide(SPGuide* guide, bool show); static void sp_namedview_lock_single_guide(SPGuide* guide, bool show); static gboolean sp_str_to_bool(const gchar *str); static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color); SPNamedView::SPNamedView() : SPObjectGroup(), snap_manager(this) { this->zoom = 0; this->guidecolor = 0; this->guidehicolor = 0; this->views.clear(); this->borderlayer = 0; this->page_size_units = nullptr; this->window_x = 0; this->cy = 0; this->window_y = 0; this->display_units = nullptr; this->page_size_units = nullptr; this->pagecolor = 0; this->cx = 0; this->pageshadow = 0; this->window_width = 0; this->window_height = 0; this->window_maximized = 0; this->bordercolor = 0; this->editable = TRUE; this->showguides = TRUE; this->lockguides = false; this->grids_visible = false; this->showborder = TRUE; this->pagecheckerboard = FALSE; this->showpageshadow = TRUE; this->guides.clear(); this->viewcount = 0; this->grids.clear(); this->default_layer_id = 0; this->connector_spacing = defaultConnSpacing; } SPNamedView::~SPNamedView() = default; static void sp_namedview_generate_old_grid(SPNamedView * /*nv*/, SPDocument *document, Inkscape::XML::Node *repr) { bool old_grid_settings_present = false; // set old settings const char* gridspacingx = "1px"; const char* gridspacingy = "1px"; const char* gridoriginy = "0px"; const char* gridoriginx = "0px"; const char* gridempspacing = "5"; const char* gridcolor = "#3f3fff"; const char* gridempcolor = "#3f3fff"; const char* gridopacity = "0.15"; const char* gridempopacity = "0.38"; const char* value = nullptr; if ((value = repr->attribute("gridoriginx"))) { gridoriginx = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridoriginy"))) { gridoriginy = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridspacingx"))) { gridspacingx = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridspacingy"))) { gridspacingy = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridcolor"))) { gridcolor = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridempcolor"))) { gridempcolor = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridempspacing"))) { gridempspacing = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridopacity"))) { gridopacity = value; old_grid_settings_present = true; } if ((value = repr->attribute("gridempopacity"))) { gridempopacity = value; old_grid_settings_present = true; } if (old_grid_settings_present) { // generate new xy grid with the correct settings // first create the child xml node, then hook it to repr. This order is important, to not set off listeners to repr before the new node is complete. Inkscape::XML::Document *xml_doc = document->getReprDoc(); Inkscape::XML::Node *newnode = xml_doc->createElement("inkscape:grid"); newnode->setAttribute("id", "GridFromPre046Settings"); newnode->setAttribute("type", Inkscape::CanvasGrid::getSVGName(Inkscape::GRID_RECTANGULAR)); newnode->setAttribute("originx", gridoriginx); newnode->setAttribute("originy", gridoriginy); newnode->setAttribute("spacingx", gridspacingx); newnode->setAttribute("spacingy", gridspacingy); newnode->setAttribute("color", gridcolor); newnode->setAttribute("empcolor", gridempcolor); newnode->setAttribute("opacity", gridopacity); newnode->setAttribute("empopacity", gridempopacity); newnode->setAttribute("empspacing", gridempspacing); repr->appendChild(newnode); Inkscape::GC::release(newnode); // remove all old settings repr->removeAttribute("gridoriginx"); repr->removeAttribute("gridoriginy"); repr->removeAttribute("gridspacingx"); repr->removeAttribute("gridspacingy"); repr->removeAttribute("gridcolor"); repr->removeAttribute("gridempcolor"); repr->removeAttribute("gridopacity"); repr->removeAttribute("gridempopacity"); repr->removeAttribute("gridempspacing"); // SPDocumentUndo::done(doc, SP_VERB_DIALOG_NAMEDVIEW, _("Create new grid from pre0.46 grid settings")); } } void SPNamedView::build(SPDocument *document, Inkscape::XML::Node *repr) { SPObjectGroup::build(document, repr); this->readAttr( "inkscape:document-units" ); this->readAttr( "units" ); this->readAttr( "viewonly" ); this->readAttr( "showguides" ); this->readAttr( "showgrid" ); this->readAttr( "gridtolerance" ); this->readAttr( "guidetolerance" ); this->readAttr( "objecttolerance" ); this->readAttr( "guidecolor" ); this->readAttr( "guideopacity" ); this->readAttr( "guidehicolor" ); this->readAttr( "guidehiopacity" ); this->readAttr( "showborder" ); this->readAttr( "inkscape:showpageshadow" ); this->readAttr( "borderlayer" ); this->readAttr( "bordercolor" ); this->readAttr( "borderopacity" ); this->readAttr( "pagecolor" ); this->readAttr( "inkscape:pagecheckerboard" ); this->readAttr( "inkscape:pageopacity" ); this->readAttr( "inkscape:pageshadow" ); this->readAttr( "inkscape:zoom" ); this->readAttr( "inkscape:cx" ); this->readAttr( "inkscape:cy" ); this->readAttr( "inkscape:window-width" ); this->readAttr( "inkscape:window-height" ); this->readAttr( "inkscape:window-x" ); this->readAttr( "inkscape:window-y" ); this->readAttr( "inkscape:window-maximized" ); this->readAttr( "inkscape:snap-global" ); this->readAttr( "inkscape:snap-bbox" ); this->readAttr( "inkscape:snap-nodes" ); this->readAttr( "inkscape:snap-others" ); this->readAttr( "inkscape:snap-from-guide" ); this->readAttr( "inkscape:snap-center" ); this->readAttr( "inkscape:snap-smooth-nodes" ); this->readAttr( "inkscape:snap-midpoints" ); this->readAttr( "inkscape:snap-object-midpoints" ); this->readAttr( "inkscape:snap-text-baseline" ); this->readAttr( "inkscape:snap-bbox-edge-midpoints" ); this->readAttr( "inkscape:snap-bbox-midpoints" ); this->readAttr( "inkscape:snap-to-guides" ); this->readAttr( "inkscape:snap-grids" ); this->readAttr( "inkscape:snap-intersection-paths" ); this->readAttr( "inkscape:object-paths" ); this->readAttr( "inkscape:snap-perpendicular" ); this->readAttr( "inkscape:snap-tangential" ); this->readAttr( "inkscape:snap-path-clip" ); this->readAttr( "inkscape:snap-path-mask" ); this->readAttr( "inkscape:object-nodes" ); this->readAttr( "inkscape:bbox-paths" ); this->readAttr( "inkscape:bbox-nodes" ); this->readAttr( "inkscape:snap-page" ); this->readAttr( "inkscape:current-layer" ); this->readAttr( "inkscape:connector-spacing" ); this->readAttr( "inkscape:lockguides" ); /* Construct guideline list */ for (auto& o: children) { if (SP_IS_GUIDE(&o)) { SPGuide * g = SP_GUIDE(&o); this->guides.push_back(g); //g_object_set(G_OBJECT(g), "color", nv->guidecolor, "hicolor", nv->guidehicolor, NULL); g->setColor(this->guidecolor); g->setHiColor(this->guidehicolor); g->readAttr( "inkscape:color" ); } } // backwards compatibility with grid settings (pre 0.46) sp_namedview_generate_old_grid(this, document, repr); } void SPNamedView::release() { this->guides.clear(); // delete grids: for(auto grid : this->grids) delete grid; this->grids.clear(); SPObjectGroup::release(); } void SPNamedView::set(SPAttributeEnum key, const gchar* value) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool global_snapping = prefs->getBool("/options/snapdefault/value", true); switch (key) { case SP_ATTR_VIEWONLY: this->editable = (!value); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SHOWGUIDES: if (!value) { // show guides if not specified, for backwards compatibility this->showguides = TRUE; } else { this->showguides = sp_str_to_bool(value); } sp_namedview_setup_guides(this); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SHOWGRIDS: if (!value) { // don't show grids if not specified, for backwards compatibility this->grids_visible = false; } else { this->grids_visible = sp_str_to_bool(value); } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_GRIDTOLERANCE: this->snap_manager.snapprefs.setGridTolerance(value ? g_ascii_strtod(value, nullptr) : 10000); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_GUIDETOLERANCE: this->snap_manager.snapprefs.setGuideTolerance(value ? g_ascii_strtod(value, nullptr) : 20); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_OBJECTTOLERANCE: this->snap_manager.snapprefs.setObjectTolerance(value ? g_ascii_strtod(value, nullptr) : 20); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_GUIDECOLOR: this->guidecolor = (this->guidecolor & 0xff) | (DEFAULTGUIDECOLOR & 0xffffff00); if (value) { this->guidecolor = (this->guidecolor & 0xff) | sp_svg_read_color(value, this->guidecolor); } for(auto guide : this->guides) { guide->setColor(this->guidecolor); guide->readAttr("inkscape:color"); } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_GUIDEOPACITY: this->guidecolor = (this->guidecolor & 0xffffff00) | (DEFAULTGUIDECOLOR & 0xff); sp_nv_read_opacity(value, &this->guidecolor); for(auto guide : this->guides) { guide->setColor(this->guidecolor); guide->readAttr("inkscape:color"); } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_GUIDEHICOLOR: this->guidehicolor = (this->guidehicolor & 0xff) | (DEFAULTGUIDEHICOLOR & 0xffffff00); if (value) { this->guidehicolor = (this->guidehicolor & 0xff) | sp_svg_read_color(value, this->guidehicolor); } for(auto guide : this->guides) { guide->setHiColor(this->guidehicolor); } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_GUIDEHIOPACITY: this->guidehicolor = (this->guidehicolor & 0xffffff00) | (DEFAULTGUIDEHICOLOR & 0xff); sp_nv_read_opacity(value, &this->guidehicolor); for(auto guide : this->guides) { guide->setHiColor(this->guidehicolor); } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SHOWBORDER: this->showborder = (value) ? sp_str_to_bool (value) : TRUE; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_BORDERLAYER: this->borderlayer = SP_BORDER_LAYER_BOTTOM; if (value && !strcasecmp(value, "true")) this->borderlayer = SP_BORDER_LAYER_TOP; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_BORDERCOLOR: this->bordercolor = (this->bordercolor & 0xff) | (DEFAULTBORDERCOLOR & 0xffffff00); if (value) { this->bordercolor = (this->bordercolor & 0xff) | sp_svg_read_color (value, this->bordercolor); } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_BORDEROPACITY: this->bordercolor = (this->bordercolor & 0xffffff00) | (DEFAULTBORDERCOLOR & 0xff); sp_nv_read_opacity(value, &this->bordercolor); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_PAGECOLOR: this->pagecolor = (this->pagecolor & 0xff) | (DEFAULTPAGECOLOR & 0xffffff00); if (value) { this->pagecolor = (this->pagecolor & 0xff) | sp_svg_read_color(value, this->pagecolor); } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_PAGECHECKERBOARD: this->pagecheckerboard = (value) ? sp_str_to_bool (value) : false; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_PAGEOPACITY: this->pagecolor = (this->pagecolor & 0xffffff00) | (DEFAULTPAGECOLOR & 0xff); sp_nv_read_opacity(value, &this->pagecolor); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_PAGESHADOW: this->pageshadow = value? atoi(value) : 2; // 2 is the default this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SHOWPAGESHADOW: this->showpageshadow = (value) ? sp_str_to_bool(value) : TRUE; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_ZOOM: this->zoom = value ? g_ascii_strtod(value, nullptr) : 0; // zero means not set this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_CX: this->cx = value ? g_ascii_strtod(value, nullptr) : HUGE_VAL; // HUGE_VAL means not set this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_CY: this->cy = value ? g_ascii_strtod(value, nullptr) : HUGE_VAL; // HUGE_VAL means not set this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_WINDOW_WIDTH: this->window_width = value? atoi(value) : -1; // -1 means not set this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_WINDOW_HEIGHT: this->window_height = value ? atoi(value) : -1; // -1 means not set this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_WINDOW_X: this->window_x = value ? atoi(value) : 0; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_WINDOW_Y: this->window_y = value ? atoi(value) : 0; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_WINDOW_MAXIMIZED: this->window_maximized = value ? atoi(value) : 0; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_GLOBAL: this->snap_manager.snapprefs.setSnapEnabledGlobally(value ? sp_str_to_bool(value) : global_snapping); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_BBOX: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_CATEGORY, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_NODE: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_NODE_CATEGORY, value ? sp_str_to_bool(value) : TRUE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_OTHERS: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_OTHERS_CATEGORY, value ? sp_str_to_bool(value) : TRUE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_ROTATION_CENTER: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_ROTATION_CENTER, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_GRID: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_GRID, value ? sp_str_to_bool(value) : TRUE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_GUIDE: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_GUIDE, value ? sp_str_to_bool(value) : TRUE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_NODE_SMOOTH: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_NODE_SMOOTH, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINT: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINT: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_TEXT_BASELINE: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_TEXT_BASELINE, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINT: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_EDGE_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINT: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_MIDPOINT, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_PATH_INTERSECTION: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH_INTERSECTION, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_PATH: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_PERP: this->snap_manager.snapprefs.setSnapPerp(value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_TANG: this->snap_manager.snapprefs.setSnapTang(value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_PATH_CLIP: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH_CLIP, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_PATH_MASK: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PATH_MASK, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_NODE_CUSP: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP, value ? sp_str_to_bool(value) : TRUE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_EDGE, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_BBOX_CORNER: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_BBOX_CORNER, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_SNAP_PAGE_BORDER: this->snap_manager.snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_PAGE_BORDER, value ? sp_str_to_bool(value) : FALSE); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_CURRENT_LAYER: this->default_layer_id = value ? g_quark_from_string(value) : 0; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_CONNECTOR_SPACING: this->connector_spacing = value ? g_ascii_strtod(value, nullptr) : defaultConnSpacing; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_INKSCAPE_DOCUMENT_UNITS: { /* The default display unit if the document doesn't override this: e.g. for files saved as * `plain SVG', or non-inkscape files, or files created by an inkscape 0.40 & * earlier. * * Note that these units are not the same as the units used for the values in SVG! * * We default to `px'. */ static Inkscape::Util::Unit const *px = unit_table.getUnit("px"); Inkscape::Util::Unit const *new_unit = px; if (value && document->getRoot()->viewBox_set) { Inkscape::Util::Unit const *const req_unit = unit_table.getUnit(value); if ( !unit_table.hasUnit(value) ) { g_warning("Unrecognized unit `%s'", value); /* fixme: Document errors should be reported in the status bar or * the like (e.g. as per * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing); g_log * should be only for programmer errors. */ } else if ( req_unit->isAbsolute() ) { new_unit = req_unit; } else { g_warning("Document units must be absolute like `mm', `pt' or `px', but found `%s'", value); /* fixme: Don't use g_log (see above). */ } } this->display_units = new_unit; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; } case SP_ATTR_UNITS: { // Only used in "Custom size" section of Document Properties dialog Inkscape::Util::Unit const *new_unit = nullptr; if (value) { Inkscape::Util::Unit const *const req_unit = unit_table.getUnit(value); if ( !unit_table.hasUnit(value) ) { g_warning("Unrecognized unit `%s'", value); /* fixme: Document errors should be reported in the status bar or * the like (e.g. as per * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing); g_log * should be only for programmer errors. */ } else if ( req_unit->isAbsolute() ) { new_unit = req_unit; } else { g_warning("Document units must be absolute like `mm', `pt' or `px', but found `%s'", value); /* fixme: Don't use g_log (see above). */ } } this->page_size_units = new_unit; this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; } case SP_ATTR_INKSCAPE_LOCKGUIDES: this->lockguides = value ? sp_str_to_bool(value) : FALSE; this->lockGuides(); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; default: SPObjectGroup::set(key, value); break; } } /** * add a grid item from SVG-repr. Check if this namedview already has a gridobject for this one! If desktop=null, add grid-canvasitem to all desktops of this namedview, * otherwise only add it to the specified desktop. */ static Inkscape::CanvasGrid* sp_namedview_add_grid(SPNamedView *nv, Inkscape::XML::Node *repr, SPDesktop *desktop) { Inkscape::CanvasGrid* grid = nullptr; //check if namedview already has an object for this grid for(auto it : nv->grids) { if (repr == it->repr) { grid = it; break; } } if (!grid) { //create grid object Inkscape::GridType gridtype = Inkscape::CanvasGrid::getGridTypeFromSVGName(repr->attribute("type")); if (!nv->document) { g_warning("sp_namedview_add_grid - how come doc is null here?!"); return nullptr; } grid = Inkscape::CanvasGrid::NewGrid(nv, repr, nv->document, gridtype); nv->grids.push_back(grid); } if (!desktop) { //add canvasitem to all desktops for(auto view : nv->views) { grid->createCanvasItem(view); } } else { //add canvasitem only for specified desktop grid->createCanvasItem(desktop); } return grid; } void SPNamedView::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { SPObjectGroup::child_added(child, ref); if (!strcmp(child->name(), "inkscape:grid")) { sp_namedview_add_grid(this, child, nullptr); } else { SPObject *no = this->document->getObjectByRepr(child); if ( !SP_IS_OBJECT(no) ) { return; } if (SP_IS_GUIDE(no)) { SPGuide *g = (SPGuide *) no; this->guides.push_back(g); //g_object_set(G_OBJECT(g), "color", this->guidecolor, "hicolor", this->guidehicolor, NULL); g->setColor(this->guidecolor); g->setHiColor(this->guidehicolor); g->readAttr("inkscape:color"); if (this->editable) { for(auto view : this->views) { g->SPGuide::showSPGuide(view->guides, (GCallback) sp_dt_guide_event); if (view->guides_active) { g->sensitize(view->getCanvas(), TRUE); } sp_namedview_show_single_guide(SP_GUIDE(g), this->showguides); } } } } } void SPNamedView::remove_child(Inkscape::XML::Node *child) { if (!strcmp(child->name(), "inkscape:grid")) { for(std::vector::iterator it=this->grids.begin();it!=this->grids.end();++it ) { if ( (*it)->repr == child ) { delete (*it); this->grids.erase(it); break; } } } else { for(std::vector::iterator it=this->guides.begin();it!=this->guides.end();++it ) { if ( (*it)->getRepr() == child ) { this->guides.erase(it); break; } } } SPObjectGroup::remove_child(child); } Inkscape::XML::Node* SPNamedView::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { if ( ( flags & SP_OBJECT_WRITE_EXT ) && repr != this->getRepr() ) { if (repr) { repr->mergeFrom(this->getRepr(), "id"); } else { repr = this->getRepr()->duplicate(xml_doc); } } return repr; } void SPNamedView::show(SPDesktop *desktop) { for(auto guide : this->guides) { guide->showSPGuide( desktop->guides, (GCallback) sp_dt_guide_event); if (desktop->guides_active) { guide->sensitize(desktop->getCanvas(), TRUE); } sp_namedview_show_single_guide(guide, showguides); } views.push_back(desktop); // generate grids specified in SVG: Inkscape::XML::Node *repr = this->getRepr(); if (repr) { for (Inkscape::XML::Node * child = repr->firstChild() ; child != nullptr; child = child->next() ) { if (!strcmp(child->name(), "inkscape:grid")) { sp_namedview_add_grid(this, child, desktop); } } } desktop->showGrids(grids_visible, false); } /* * Restores window geometry from the document settings or defaults in prefs */ void sp_namedview_window_from_document(SPDesktop *desktop) { SPNamedView *nv = desktop->namedview; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int window_geometry = prefs->getInt("/options/savewindowgeometry/value", PREFS_WINDOW_GEOMETRY_NONE); int default_size = prefs->getInt("/options/defaultwindowsize/value", PREFS_WINDOW_SIZE_NATURAL); bool new_document = (nv->window_width <= 0) || (nv->window_height <= 0); bool show_dialogs = true; // restore window size and position stored with the document Gtk::Window *win = desktop->getToplevel(); g_assert(win); if (window_geometry == PREFS_WINDOW_GEOMETRY_LAST) { gint pw = prefs->getInt("/desktop/geometry/width", -1); gint ph = prefs->getInt("/desktop/geometry/height", -1); gint px = prefs->getInt("/desktop/geometry/x", -1); gint py = prefs->getInt("/desktop/geometry/y", -1); gint full = prefs->getBool("/desktop/geometry/fullscreen"); gint maxed = prefs->getBool("/desktop/geometry/maximized"); if (pw>0 && ph>0) { Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_at_point(px, py); pw = std::min(pw, monitor_geometry.get_width()); ph = std::min(ph, monitor_geometry.get_height()); desktop->setWindowSize(pw, ph); desktop->setWindowPosition(Geom::Point(px, py)); } if (maxed) { win->maximize(); } if (full) { win->fullscreen(); } } else if ((window_geometry == PREFS_WINDOW_GEOMETRY_FILE && nv->window_maximized) || (new_document && (default_size == PREFS_WINDOW_SIZE_MAXIMIZED))) { win->maximize(); } else { const int MIN_WINDOW_SIZE = 600; int w = 0; int h = 0; bool move_to_screen = false; if (window_geometry == PREFS_WINDOW_GEOMETRY_FILE && !new_document) { Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_at_point(nv->window_x, nv->window_y); w = MIN(monitor_geometry.get_width(), nv->window_width); h = MIN(monitor_geometry.get_height(), nv->window_height); move_to_screen = true; } else if (default_size == PREFS_WINDOW_SIZE_LARGE) { Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_at_window(win->get_window()); w = MAX(0.75 * monitor_geometry.get_width(), MIN_WINDOW_SIZE); h = MAX(0.75 * monitor_geometry.get_height(), MIN_WINDOW_SIZE); } else if (default_size == PREFS_WINDOW_SIZE_SMALL) { w = h = MIN_WINDOW_SIZE; } else if (default_size == PREFS_WINDOW_SIZE_NATURAL) { // don't set size (i.e. keep the gtk+ default, which will be the natural size) // unless gtk+ decided it would be a good idea to show a window that is larger than the screen Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_at_window(win->get_window()); int monitor_width = monitor_geometry.get_width(); int monitor_height = monitor_geometry.get_height(); int window_width, window_height; win->get_size(window_width, window_height); if (window_width > monitor_width || window_height > monitor_height) { w = std::min(monitor_width, window_width); h = std::min(monitor_height, window_height); } } if ((w > 0) && (h > 0)) { #ifndef _WIN32 gint dx= 0; gint dy = 0; gint dw = 0; gint dh = 0; desktop->getWindowGeometry(dx, dy, dw, dh); if ((w != dw) || (h != dh)) { // Don't show dialogs when window is initially resized on OSX/Linux due to gdl dock bug // This will happen on sp_desktop_widget_size_allocate show_dialogs = FALSE; } #endif desktop->setWindowSize(w, h); if (move_to_screen) { desktop->setWindowPosition(Geom::Point(nv->window_x, nv->window_y)); } } } // Cancel any history of transforms up to this point (must be before call to zoom). desktop->clear_transform_history(); if (show_dialogs) { desktop->show_dialogs(); } } /* * Restores zoom and view from the document settings */ void sp_namedview_zoom_and_view_from_document(SPDesktop *desktop) { SPNamedView *nv = desktop->namedview; if (nv->zoom != 0 && nv->zoom != HUGE_VAL && !std::isnan(nv->zoom) && nv->cx != HUGE_VAL && !std::isnan(nv->cx) && nv->cy != HUGE_VAL && !std::isnan(nv->cy)) { desktop->zoom_absolute_center_point( Geom::Point(nv->cx, nv->cy), nv->zoom ); } else if (desktop->getDocument()) { // document without saved zoom, zoom to its page desktop->zoom_page(); } } void SPNamedView::writeNewGrid(SPDocument *document,int gridtype) { g_assert(this->getRepr() != nullptr); Inkscape::CanvasGrid::writeNewGridToRepr(this->getRepr(),document,static_cast(gridtype)); } bool SPNamedView::getSnapGlobal() const { return this->snap_manager.snapprefs.getSnapEnabledGlobally(); } void SPNamedView::setSnapGlobal(bool v) { g_assert(this->getRepr() != nullptr); sp_repr_set_boolean(this->getRepr(), "inkscape:snap-global", v); } void sp_namedview_update_layers_from_document (SPDesktop *desktop) { SPObject *layer = nullptr; SPDocument *document = desktop->doc(); SPNamedView *nv = desktop->namedview; if ( nv->default_layer_id != 0 ) { layer = document->getObjectById(g_quark_to_string(nv->default_layer_id)); } // don't use that object if it's not at least group if ( !layer || !SP_IS_GROUP(layer) ) { layer = nullptr; } // if that didn't work out, look for the topmost layer if (!layer) { for (auto& iter: document->getRoot()->children) { if (desktop->isLayer(&iter)) { layer = &iter; } } } if (layer) { desktop->setCurrentLayer(layer); } // FIXME: find a better place to do this desktop->event_log->updateUndoVerbs(); } void sp_namedview_document_from_window(SPDesktop *desktop) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); int window_geometry = prefs->getInt("/options/savewindowgeometry/value", PREFS_WINDOW_GEOMETRY_NONE); bool save_geometry_in_file = window_geometry == PREFS_WINDOW_GEOMETRY_FILE; bool save_viewport_in_file = prefs->getBool("/options/savedocviewport/value", true); Inkscape::XML::Node *view = desktop->namedview->getRepr(); Geom::Rect const r = desktop->get_display_area(); // saving window geometry is not undoable bool saved = DocumentUndo::getUndoSensitive(desktop->getDocument()); DocumentUndo::setUndoSensitive(desktop->getDocument(), false); if (save_viewport_in_file) { sp_repr_set_svg_double(view, "inkscape:zoom", desktop->current_zoom()); sp_repr_set_svg_double(view, "inkscape:cx", r.midpoint()[Geom::X]); sp_repr_set_svg_double(view, "inkscape:cy", r.midpoint()[Geom::Y]); } if (save_geometry_in_file) { gint w, h, x, y; desktop->getWindowGeometry(x, y, w, h); sp_repr_set_int(view, "inkscape:window-width", w); sp_repr_set_int(view, "inkscape:window-height", h); sp_repr_set_int(view, "inkscape:window-x", x); sp_repr_set_int(view, "inkscape:window-y", y); sp_repr_set_int(view, "inkscape:window-maximized", desktop->is_maximized()); } view->setAttribute("inkscape:current-layer", desktop->currentLayer()->getId()); // restore undoability DocumentUndo::setUndoSensitive(desktop->getDocument(), saved); } void SPNamedView::hide(SPDesktop const *desktop) { g_assert(desktop != nullptr); g_assert(std::find(views.begin(),views.end(),desktop)!=views.end()); for(auto & guide : this->guides) { guide->hideSPGuide(desktop->getCanvas()); } views.erase(std::remove(views.begin(),views.end(),desktop),views.end()); } void SPNamedView::activateGuides(void* desktop, bool active) { g_assert(desktop != nullptr); g_assert(std::find(views.begin(),views.end(),desktop)!=views.end()); SPDesktop *dt = static_cast(desktop); for(auto & guide : this->guides) { guide->sensitize(dt->getCanvas(), active); } } static void sp_namedview_setup_guides(SPNamedView *nv) { for(std::vector::iterator it=nv->guides.begin();it!=nv->guides.end();++it ) { sp_namedview_show_single_guide(*it, nv->showguides); } } static void sp_namedview_lock_guides(SPNamedView *nv) { for(std::vector::iterator it=nv->guides.begin();it!=nv->guides.end();++it ) { sp_namedview_lock_single_guide(*it, nv->lockguides); } } static void sp_namedview_show_single_guide(SPGuide* guide, bool show) { if (show) { guide->showSPGuide(); } else { guide->hideSPGuide(); } } static void sp_namedview_lock_single_guide(SPGuide* guide, bool locked) { guide->set_locked(locked, true); } void sp_namedview_toggle_guides(SPDocument *doc, SPNamedView *namedview) { unsigned int v; Inkscape::XML::Node *repr = namedview->getRepr(); unsigned int set = sp_repr_get_boolean(repr, "showguides", &v); if (!set) { // hide guides if not specified, for backwards compatibility v = FALSE; } else { v = !v; } bool saved = DocumentUndo::getUndoSensitive(doc); DocumentUndo::setUndoSensitive(doc, false); sp_repr_set_boolean(repr, "showguides", v); DocumentUndo::setUndoSensitive(doc, saved); SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (desktop) { Inkscape::Verb *verb = Inkscape::Verb::get(SP_VERB_TOGGLE_GUIDES); if (verb) { desktop->_menu_update.emit(verb->get_code(), namedview->getGuides()); } } doc->setModifiedSinceSave(); } void sp_namedview_guides_toggle_lock(SPDocument *doc, SPNamedView * namedview) { unsigned int v; Inkscape::XML::Node *repr = namedview->getRepr(); unsigned int set = sp_repr_get_boolean(repr, "inkscape:lockguides", &v); if (!set) { // hide guides if not specified, for backwards compatibility v = true; } else { v = !v; } bool saved = DocumentUndo::getUndoSensitive(doc); DocumentUndo::setUndoSensitive(doc, false); sp_repr_set_boolean(repr, "inkscape:lockguides", v); sp_namedview_lock_guides(namedview); DocumentUndo::setUndoSensitive(doc, saved); doc->setModifiedSinceSave(); } void sp_namedview_show_grids(SPNamedView * namedview, bool show, bool dirty_document) { namedview->grids_visible = show; SPDocument *doc = namedview->document; Inkscape::XML::Node *repr = namedview->getRepr(); bool saved = DocumentUndo::getUndoSensitive(doc); DocumentUndo::setUndoSensitive(doc, false); sp_repr_set_boolean(repr, "showgrid", namedview->grids_visible); DocumentUndo::setUndoSensitive(doc, saved); /* we don't want the document to get dirty on startup; that's when we call this function with dirty_document = false */ if (dirty_document) { doc->setModifiedSinceSave(); } } gchar const *SPNamedView::getName() const { SPException ex; SP_EXCEPTION_INIT(&ex); return this->getAttribute("id", &ex); } guint SPNamedView::getViewCount() { return ++viewcount; } std::vector const SPNamedView::getViewList() const { return views; } /* This should be moved somewhere */ static gboolean sp_str_to_bool(const gchar *str) { if (str) { if (!g_ascii_strcasecmp(str, "true") || !g_ascii_strcasecmp(str, "yes") || !g_ascii_strcasecmp(str, "y") || (atoi(str) != 0)) { return TRUE; } } return FALSE; } static gboolean sp_nv_read_opacity(const gchar *str, guint32 *color) { if (!str) { return FALSE; } gchar *u; gdouble v = g_ascii_strtod(str, &u); if (!u) { return FALSE; } v = CLAMP(v, 0.0, 1.0); *color = (*color & 0xffffff00) | (guint32) floor(v * 255.9999); return TRUE; } SPNamedView *sp_document_namedview(SPDocument *document, const gchar *id) { g_return_val_if_fail(document != nullptr, NULL); SPObject *nv = sp_item_group_get_child_by_name(document->getRoot(), nullptr, "sodipodi:namedview"); g_assert(nv != nullptr); if (id == nullptr) { return (SPNamedView *) nv; } while (nv && strcmp(nv->getId(), id)) { nv = sp_item_group_get_child_by_name(document->getRoot(), nv, "sodipodi:namedview"); } return (SPNamedView *) nv; } SPNamedView const *sp_document_namedview(SPDocument const *document, const gchar *id) { return sp_document_namedview(const_cast(document), id); // use a const_cast here to avoid duplicating code } void SPNamedView::setGuides(bool v) { g_assert(this->getRepr() != nullptr); sp_repr_set_boolean(this->getRepr(), "showguides", v); sp_repr_set_boolean(this->getRepr(), "inkscape:guide-bbox", v); } bool SPNamedView::getGuides() { g_assert(this->getRepr() != nullptr); unsigned int v; unsigned int set = sp_repr_get_boolean(this->getRepr(), "showguides", &v); if (!set) { // show guides if not specified, for backwards compatibility v = TRUE; } return v; } void SPNamedView::lockGuides() { sp_namedview_lock_guides(this); } /** * Gets page fitting margin information from the namedview node in the XML. * \param nv_repr reference to this document's namedview * \param key the same key used by the RegisteredScalarUnit in * ui/widget/page-sizer.cpp * \param margin_units units for the margin * \param return_units units to return the result in * \param width width in px (for percentage margins) * \param height height in px (for percentage margins) * \param use_width true if the this key is left or right margins, false * otherwise. Used for percentage margins. * \return the margin size in px, else 0.0 if anything is invalid. */ double SPNamedView::getMarginLength(gchar const * const key, Inkscape::Util::Unit const * const margin_units, Inkscape::Util::Unit const * const return_units, double const width, double const height, bool const use_width) { double value; static Inkscape::Util::Unit const *percent = unit_table.getUnit("%"); if(!this->storeAsDouble(key,&value)) { return 0.0; } if (*margin_units == *percent) { return (use_width)? width * value : height * value; } if (!margin_units->compatibleWith(return_units)) { return 0.0; } return value; } /** * Returns namedview's default unit. * If no default unit is set, "px" is returned */ Inkscape::Util::Unit const * SPNamedView::getDisplayUnit() const { return display_units ? display_units : unit_table.getUnit("px"); } /** * Returns the first grid it could find that isEnabled(). Returns NULL, if none is enabled */ Inkscape::CanvasGrid * sp_namedview_get_first_enabled_grid(SPNamedView *namedview) { for(auto grid : namedview->grids) { if (grid->isEnabled()) return grid; } return nullptr; } void SPNamedView::translateGuides(Geom::Translate const &tr) { for(auto & it : this->guides) { SPGuide &guide = *it; Geom::Point point_on_line = guide.getPoint(); point_on_line *= tr; guide.moveto(point_on_line, true); } } void SPNamedView::translateGrids(Geom::Translate const &tr) { for(auto & grid : this->grids) { grid->setOrigin(grid->origin * tr); } } void SPNamedView::scrollAllDesktops(double dx, double dy, bool is_scrolling) { for(auto & view : this->views) { view->scroll_relative_in_svg_coords(dx, dy, is_scrolling); } } /* 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 :