X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fdocument.cpp;h=101c54e30b687d38160ac0d9e2c55d4d0227e93a;hb=817e7487ecbca8958844479ae421ac4b32403f0b;hp=e42f6c8cb57029c2b55e4225e76582c0b50c596e;hpb=c73fd7fd8d1713c95ecf111e49dd068d4b9adf69;p=inkscape.git diff --git a/src/document.cpp b/src/document.cpp index e42f6c8cb..101c54e30 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -38,61 +38,76 @@ # include "config.h" #endif #include +#include +#include + #include "application/application.h" #include "application/editor.h" -#include "libnr/nr-matrix-fns.h" -#include "xml/repr.h" +#include "desktop.h" +#include "dir-util.h" +#include "display/nr-arena-item.h" +#include "document-private.h" #include "helper/units.h" #include "inkscape-private.h" -#include "inkscape_version.h" -#include "sp-object-repr.h" -#include "document-private.h" -#include "dir-util.h" -#include "unit-constants.h" -#include "prefs-utils.h" +#include "inkscape-version.h" #include "libavoid/router.h" -#include "libnr/nr-rect.h" +#include "persp3d.h" +#include "preferences.h" +#include "profile-manager.h" +#include "rdf.h" #include "sp-item-group.h" +#include "sp-namedview.h" +#include "sp-object-repr.h" +#include "transf_mat_3x4.h" +#include "unit-constants.h" +#include "xml/repr.h" +#include "xml/rebase-hrefs.h" -#include "display/nr-arena-item.h" - -#include "dialogs/rdf.h" - -#define A4_WIDTH_STR "210mm" -#define A4_HEIGHT_STR "297mm" +// Higher number means lower priority. +#define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 2) -#define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 1) +// Should have a lower priority than SP_DOCUMENT_UPDATE_PRIORITY, +// since we want it to happen when there are no more updates. +#define SP_DOCUMENT_REROUTING_PRIORITY (G_PRIORITY_HIGH_IDLE - 1) static gint sp_document_idle_handler(gpointer data); +static gint sp_document_rerouting_handler(gpointer data); gboolean sp_document_resource_list_free(gpointer key, gpointer value, gpointer data); static gint doc_count = 0; -SPDocument::SPDocument() { - SPDocumentPrivate *p; - - keepalive = FALSE; - virgin = TRUE; - - modified_id = 0; - - rdoc = NULL; - rroot = NULL; - root = NULL; - style_cascade = cr_cascade_new(NULL, NULL, NULL); - - uri = NULL; - base = NULL; - name = NULL; - - _collection_queue = NULL; +static unsigned long next_serial = 0; + +SPDocument::SPDocument() : + keepalive(FALSE), + virgin(TRUE), + modified_since_save(FALSE), + rdoc(0), + rroot(0), + root(0), + style_cascade(cr_cascade_new(NULL, NULL, NULL)), + uri(0), + base(0), + name(0), + priv(0), // reset in ctor + actionkey(0), + modified_id(0), + rerouting_handler_id(0), + profileManager(0), // deferred until after other initialization + router(new Avoid::Router(Avoid::PolyLineRouting|Avoid::OrthogonalRouting)), + _collection_queue(0), + oldSignalsConnected(false), + current_persp3d(0) +{ + // Penalise libavoid for choosing paths with needless extra segments. + // This results in much better looking orthogonal connector paths. + router->setRoutingPenalty(Avoid::segmentPenalty); - // Initialise instance of connector router. - router = new Avoid::Router(); + SPDocumentPrivate *p = new SPDocumentPrivate(); - p = new SPDocumentPrivate(); + p->serial = next_serial++; p->iddef = g_hash_table_new(g_direct_hash, g_direct_equal); p->reprdef = g_hash_table_new(g_direct_hash, g_direct_equal); @@ -104,9 +119,13 @@ SPDocument::SPDocument() { p->history_size = 0; p->undo = NULL; p->redo = NULL; + p->seeking = false; priv = p; + // Once things are set, hook in the manager + profileManager = new Inkscape::ProfileManager(this); + // XXX only for testing! priv->undoStackObservers.add(p->console_output_undo_observer); } @@ -114,9 +133,18 @@ SPDocument::SPDocument() { SPDocument::~SPDocument() { collectOrphans(); - if (priv) { - inkscape_remove_document(this); + // kill/unhook this first + if ( profileManager ) { + delete profileManager; + profileManager = 0; + } + if (router) { + delete router; + router = NULL; + } + + if (priv) { if (priv->partial) { sp_repr_free_log(priv->partial); priv->partial = NULL; @@ -126,7 +154,7 @@ SPDocument::~SPDocument() { sp_document_clear_undo(this); if (root) { - sp_object_invoke_release(root); + root->releaseReferences(); sp_object_unref(root); root = NULL; } @@ -161,24 +189,80 @@ SPDocument::~SPDocument() { } if (modified_id) { - gtk_idle_remove(modified_id); + g_source_remove(modified_id); modified_id = 0; } - _selection_changed_connection.disconnect(); - _desktop_activated_connection.disconnect(); + if (rerouting_handler_id) { + g_source_remove(rerouting_handler_id); + rerouting_handler_id = 0; + } + + if (oldSignalsConnected) { + g_signal_handlers_disconnect_by_func(G_OBJECT(INKSCAPE), + reinterpret_cast(sp_document_reset_key), + static_cast(this)); + } else { + _selection_changed_connection.disconnect(); + _desktop_activated_connection.disconnect(); + } if (keepalive) { inkscape_unref(); keepalive = FALSE; } - if (router) { - delete router; - router = NULL; + //delete this->_whiteboard_session_manager; +} + +Persp3D * +SPDocument::getCurrentPersp3D() { + // Check if current_persp3d is still valid + std::vector plist; + getPerspectivesInDefs(plist); + for (unsigned int i = 0; i < plist.size(); ++i) { + if (current_persp3d == plist[i]) + return current_persp3d; } - //delete this->_whiteboard_session_manager; + // If not, return the first perspective in defs (which may be NULL of none exists) + current_persp3d = persp3d_document_first_persp (this); + + return current_persp3d; +} + +Persp3DImpl * +SPDocument::getCurrentPersp3DImpl() { + return current_persp3d_impl; +} + +void +SPDocument::setCurrentPersp3D(Persp3D * const persp) { + current_persp3d = persp; + //current_persp3d_impl = persp->perspective_impl; +} + +void +SPDocument::getPerspectivesInDefs(std::vector &list) { + SPDefs *defs = SP_ROOT(this->root)->defs; + for (SPObject *i = sp_object_first_child(SP_OBJECT(defs)); i != NULL; i = SP_OBJECT_NEXT(i) ) { + if (SP_IS_PERSP3D(i)) + list.push_back(SP_PERSP3D(i)); + } +} + +/** +void SPDocument::initialize_current_persp3d() +{ + this->current_persp3d = persp3d_document_first_persp(this); + if (!this->current_persp3d) { + this->current_persp3d = persp3d_create_xml_element(this); + } +} +**/ + +unsigned long SPDocument::serial() const { + return priv->serial; } void SPDocument::queueForOrphanCollection(SPObject *object) { @@ -202,12 +286,12 @@ void SPDocument::collectOrphans() { } } -void SPDocument::reset_key (void *dummy) +void SPDocument::reset_key (void */*dummy*/) { actionkey = NULL; } -static SPDocument * +SPDocument * sp_document_create(Inkscape::XML::Document *rdoc, gchar const *uri, gchar const *base, @@ -216,9 +300,9 @@ sp_document_create(Inkscape::XML::Document *rdoc, { SPDocument *document; Inkscape::XML::Node *rroot; - Inkscape::Version sodipodi_version; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - rroot = sp_repr_document_root(rdoc); + rroot = rdoc->root(); document = new SPDocument(); @@ -228,7 +312,7 @@ sp_document_create(Inkscape::XML::Document *rdoc, document->rroot = rroot; #ifndef WIN32 - prepend_current_dir_if_relative(&(document->uri), uri); + document->uri = prepend_current_dir_if_relative(uri); #else // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test! document->uri = uri? g_strdup(uri) : NULL; @@ -245,42 +329,62 @@ sp_document_create(Inkscape::XML::Document *rdoc, document->root = sp_object_repr_build_tree(document, rroot); - sodipodi_version = SP_ROOT(document->root)->version.sodipodi; - /* fixme: Not sure about this, but lets assume ::build updates */ - rroot->setAttribute("sodipodi:version", SODIPODI_VERSION); - rroot->setAttribute("inkscape:version", INKSCAPE_VERSION); + rroot->setAttribute("inkscape:version", Inkscape::version_string); /* fixme: Again, I moved these here to allow version determining in ::build (Lauris) */ /* Quick hack 2 - get default image size into document */ - if (!rroot->attribute("width")) rroot->setAttribute("width", A4_WIDTH_STR); - if (!rroot->attribute("height")) rroot->setAttribute("height", A4_HEIGHT_STR); + if (!rroot->attribute("width")) rroot->setAttribute("width", "100%"); + if (!rroot->attribute("height")) rroot->setAttribute("height", "100%"); /* End of quick hack 2 */ /* Quick hack 3 - Set uri attributes */ if (uri) { - /* fixme: Think, what this means for images (Lauris) */ rroot->setAttribute("sodipodi:docname", uri); - if (document->base) - rroot->setAttribute("sodipodi:docbase", document->base); } /* End of quick hack 3 */ + /* Eliminate obsolete sodipodi:docbase, for privacy reasons */ + rroot->setAttribute("sodipodi:docbase", NULL); + + /* Eliminate any claim to adhere to a profile, as we don't try to */ + rroot->setAttribute("baseProfile", NULL); + // creating namedview if (!sp_item_group_get_child_by_name((SPGroup *) document->root, NULL, "sodipodi:namedview")) { // if there's none in the document already, - Inkscape::XML::Node *r = NULL; Inkscape::XML::Node *rnew = NULL; - r = inkscape_get_repr(INKSCAPE, "template.base"); - // see if there's a template with id="base" in the preferences - if (!r) { - // if there's none, create an empty element - rnew = sp_repr_new("sodipodi:namedview"); - rnew->setAttribute("id", "base"); - } else { - // otherwise, take from preferences - rnew = r->duplicate(); + + rnew = rdoc->createElement("sodipodi:namedview"); + //rnew->setAttribute("id", "base"); + + // Add namedview data from the preferences + // we can't use getAllEntries because this could produce non-SVG doubles + Glib::ustring pagecolor = prefs->getString("/template/base/pagecolor"); + if (!pagecolor.empty()) { + rnew->setAttribute("pagecolor", pagecolor.data()); } + Glib::ustring bordercolor = prefs->getString("/template/base/bordercolor"); + if (!bordercolor.empty()) { + rnew->setAttribute("bordercolor", bordercolor.data()); + } + sp_repr_set_svg_double(rnew, "borderopacity", + prefs->getDouble("/template/base/borderopacity", 1.0)); + sp_repr_set_svg_double(rnew, "objecttolerance", + prefs->getDouble("/template/base/objecttolerance", 10.0)); + sp_repr_set_svg_double(rnew, "gridtolerance", + prefs->getDouble("/template/base/gridtolerance", 10.0)); + sp_repr_set_svg_double(rnew, "guidetolerance", + prefs->getDouble("/template/base/guidetolerance", 10.0)); + sp_repr_set_svg_double(rnew, "inkscape:pageopacity", + prefs->getDouble("/template/base/inkscape:pageopacity", 0.0)); + sp_repr_set_int(rnew, "inkscape:pageshadow", + prefs->getInt("/template/base/inkscape:pageshadow", 2)); + sp_repr_set_int(rnew, "inkscape:window-width", + prefs->getInt("/template/base/inkscape:window-width", 640)); + sp_repr_set_int(rnew, "inkscape:window-height", + prefs->getInt("/template/base/inkscape:window-height", 480)); + // insert into the document rroot->addChild(rnew, NULL); // clean up @@ -290,7 +394,7 @@ sp_document_create(Inkscape::XML::Document *rdoc, /* Defs */ if (!SP_ROOT(document->root)->defs) { Inkscape::XML::Node *r; - r = sp_repr_new("svg:defs"); + r = rdoc->createElement("svg:defs"); rroot->addChild(r, NULL); Inkscape::GC::release(r); g_assert(SP_ROOT(document->root)->defs); @@ -303,7 +407,16 @@ sp_document_create(Inkscape::XML::Document *rdoc, inkscape_ref(); } - sp_document_set_undo_sensitive(document, TRUE); + // Check if the document already has a perspective (e.g., when opening an existing + // document). If not, create a new one and set it as the current perspective. + document->setCurrentPersp3D(persp3d_document_first_persp(document)); + if (!document->getCurrentPersp3D()) { + //document->setCurrentPersp3D(persp3d_create_xml_element (document)); + Persp3DImpl *persp_impl = new Persp3DImpl(); + document->setCurrentPersp3DImpl(persp_impl); + } + + sp_document_set_undo_sensitive(document, true); // reset undo key when selection changes, so that same-key actions on different objects are not coalesced if (!Inkscape::NSApplication::Application::getNewGui()) { @@ -311,11 +424,12 @@ sp_document_create(Inkscape::XML::Document *rdoc, G_CALLBACK(sp_document_reset_key), document); g_signal_connect(G_OBJECT(INKSCAPE), "activate_desktop", G_CALLBACK(sp_document_reset_key), document); + document->oldSignalsConnected = true; } else { document->_selection_changed_connection = Inkscape::NSApplication::Editor::connectSelectionChanged (sigc::mem_fun (*document, &SPDocument::reset_key)); document->_desktop_activated_connection = Inkscape::NSApplication::Editor::connectDesktopActivated (sigc::mem_fun (*document, &SPDocument::reset_key)); + document->oldSignalsConnected = false; } - inkscape_add_document(document); return document; } @@ -339,7 +453,7 @@ sp_document_new(gchar const *uri, unsigned int keepalive, bool make_new) rdoc = sp_repr_read_file(uri, SP_SVG_NS_URI); /* If file cannot be loaded, return NULL without warning */ if (rdoc == NULL) return NULL; - rroot = sp_repr_document_root(rdoc); + rroot = rdoc->root(); /* If xml file is not svg, return NULL without warning */ /* fixme: destroy document */ if (strcmp(rroot->name(), "svg:svg") != 0) return NULL; @@ -388,7 +502,7 @@ sp_document_new_from_mem(gchar const *buffer, gint length, unsigned int keepaliv /* If it cannot be loaded, return NULL without warning */ if (rdoc == NULL) return NULL; - rroot = sp_repr_document_root(rdoc); + rroot = rdoc->root(); /* If xml file is not svg, return NULL without warning */ /* fixme: destroy document */ if (strcmp(rroot->name(), "svg:svg") != 0) return NULL; @@ -400,12 +514,6 @@ sp_document_new_from_mem(gchar const *buffer, gint length, unsigned int keepaliv return doc; } -SPDocument *sp_document_new_dummy() { - SPDocument *document = new SPDocument(); - inkscape_add_document(document); - return document; -} - SPDocument * sp_document_ref(SPDocument *doc) { @@ -428,7 +536,11 @@ gdouble sp_document_width(SPDocument *document) g_return_val_if_fail(document->priv != NULL, 0.0); g_return_val_if_fail(document->root != NULL, 0.0); - return SP_ROOT(document->root)->width.computed; + SPRoot *root = SP_ROOT(document->root); + + if (root->width.unit == SVGLength::PERCENT && root->viewBox_set) + return root->viewBox.x1 - root->viewBox.x0; + return root->width.computed; } void @@ -439,6 +551,7 @@ sp_document_set_width (SPDocument *document, gdouble width, const SPUnit *unit) if (root->width.unit == SVGLength::PERCENT && root->viewBox_set) { // set to viewBox= root->viewBox.x1 = root->viewBox.x0 + sp_units_get_pixels (width, *unit); } else { // set to width= + gdouble old_computed = root->width.computed; root->width.computed = sp_units_get_pixels (width, *unit); /* SVG does not support meters as a unit, so we must translate meters to * cm when writing */ @@ -449,6 +562,9 @@ sp_document_set_width (SPDocument *document, gdouble width, const SPUnit *unit) root->width.value = width; root->width.unit = (SVGLength::Unit) sp_unit_get_svg_unit(unit); } + + if (root->viewBox_set) + root->viewBox.x1 = root->viewBox.x0 + (root->width.computed / old_computed) * (root->viewBox.x1 - root->viewBox.x0); } SP_OBJECT (root)->updateRepr(); @@ -461,6 +577,7 @@ void sp_document_set_height (SPDocument * document, gdouble height, const SPUnit if (root->height.unit == SVGLength::PERCENT && root->viewBox_set) { // set to viewBox= root->viewBox.y1 = root->viewBox.y0 + sp_units_get_pixels (height, *unit); } else { // set to height= + gdouble old_computed = root->height.computed; root->height.computed = sp_units_get_pixels (height, *unit); /* SVG does not support meters as a unit, so we must translate meters to * cm when writing */ @@ -471,6 +588,9 @@ void sp_document_set_height (SPDocument * document, gdouble height, const SPUnit root->height.value = height; root->height.unit = (SVGLength::Unit) sp_unit_get_svg_unit(unit); } + + if (root->viewBox_set) + root->viewBox.y1 = root->viewBox.y0 + (root->height.computed / old_computed) * (root->viewBox.y1 - root->viewBox.y0); } SP_OBJECT (root)->updateRepr(); @@ -482,79 +602,198 @@ gdouble sp_document_height(SPDocument *document) g_return_val_if_fail(document->priv != NULL, 0.0); g_return_val_if_fail(document->root != NULL, 0.0); - return SP_ROOT(document->root)->height.computed; + SPRoot *root = SP_ROOT(document->root); + + if (root->height.unit == SVGLength::PERCENT && root->viewBox_set) + return root->viewBox.y1 - root->viewBox.y0; + return root->height.computed; +} + +Geom::Point sp_document_dimensions(SPDocument *doc) +{ + return Geom::Point(sp_document_width(doc), sp_document_height(doc)); +} + +/** + * 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. + */ +static double getMarginLength(Inkscape::XML::Node * const nv_repr, + gchar const * const key, + SPUnit const * const margin_units, + SPUnit const * const return_units, + double const width, + double const height, + bool const use_width) +{ + double value; + if (!sp_repr_get_double (nv_repr, key, &value)) { + return 0.0; + } + if (margin_units == &sp_unit_get_by_id (SP_UNIT_PERCENT)) { + return (use_width)? width * value : height * value; + } + if (!sp_convert_distance (&value, margin_units, return_units)) { + return 0.0; + } + return value; } /** - * Given an NRRect that may, for example, correspond to the bbox of an object + * Given a Geom::Rect that may, for example, correspond to the bbox of an object, * this function fits the canvas to that rect by resizing the canvas * and translating the document root into position. + * \param rect fit document size to this + * \param with_margins add margins to rect, by taking margins from this + * document's namedview ( "fit-margin-..." + * attributes, and "units") */ -void SPDocument::fitToRect(NRRect const & rect) +void SPDocument::fitToRect(Geom::Rect const &rect, bool with_margins) { - g_return_if_fail(!empty(rect)); + double const w = rect.width(); + double const h = rect.height(); + + double const old_height = sp_document_height(this); + SPUnit const &px(sp_unit_get_by_id(SP_UNIT_PX)); + + /* in px */ + double margin_top = 0.0; + double margin_left = 0.0; + double margin_right = 0.0; + double margin_bottom = 0.0; + + SPNamedView *nv = sp_document_namedview(this, 0); + + if (with_margins && nv) { + Inkscape::XML::Node *nv_repr = SP_OBJECT_REPR (nv); + if (nv_repr != NULL) { + gchar const * const units_abbr = nv_repr->attribute("units"); + SPUnit const *margin_units = NULL; + if (units_abbr != NULL) { + margin_units = sp_unit_get_by_abbreviation(units_abbr); + } + if (margin_units == NULL) { + margin_units = &sp_unit_get_by_id(SP_UNIT_PX); + } + margin_top = getMarginLength(nv_repr, "fit-margin-top", + margin_units, &px, w, h, false); + margin_left = getMarginLength(nv_repr, "fit-margin-left", + margin_units, &px, w, h, true); + margin_right = getMarginLength(nv_repr, "fit-margin-right", + margin_units, &px, w, h, true); + margin_bottom = getMarginLength(nv_repr, "fit-margin-bottom", + margin_units, &px, w, h, false); + } + } + + Geom::Rect const rect_with_margins( + rect.min() - Geom::Point(margin_left, margin_bottom), + rect.max() + Geom::Point(margin_right, margin_top)); - gdouble w = rect.x1 - rect.x0; - gdouble h = rect.y1 - rect.y0; - gdouble old_height = sp_document_height(this); - SPUnit unit = sp_unit_get_by_id(SP_UNIT_PX); - sp_document_set_width(this, w, &unit); - sp_document_set_height(this, h, &unit); + + sp_document_set_width(this, rect_with_margins.width(), &px); + sp_document_set_height(this, rect_with_margins.height(), &px); + + Geom::Translate const tr( + Geom::Point(0, old_height - rect_with_margins.height()) + - to_2geom(rect_with_margins.min())); + SP_GROUP(root)->translateChildItems(tr); - NR::translate tr = NR::translate::translate(-rect.x0,-(rect.y0 + (h - old_height))); - static_cast(root)->translateChildItems(tr); + if(nv) { + Geom::Translate tr2(-rect_with_margins.min()); + nv->translateGuides(tr2); + + // update the viewport so the drawing appears to stay where it was + nv->scrollAllDesktops(-tr2[0], tr2[1], false); + } } -void sp_document_set_uri(SPDocument *document, gchar const *uri) +static void +do_change_uri(SPDocument *const document, gchar const *const filename, bool const rebase) { g_return_if_fail(document != NULL); - if (document->name) { - g_free(document->name); - document->name = NULL; - } - if (document->base) { - g_free(document->base); - document->base = NULL; - } - if (document->uri) { - g_free(document->uri); - document->uri = NULL; - } - - if (uri) { + gchar *new_base; + gchar *new_name; + gchar *new_uri; + if (filename) { #ifndef WIN32 - prepend_current_dir_if_relative(&(document->uri), uri); + new_uri = prepend_current_dir_if_relative(filename); #else // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test! - document->uri = g_strdup(uri); + new_uri = g_strdup(filename); #endif - /* fixme: Think, what this means for images (Lauris) */ - document->base = g_path_get_dirname(document->uri); - document->name = g_path_get_basename(document->uri); - + new_base = g_path_get_dirname(new_uri); + new_name = g_path_get_basename(new_uri); } else { - document->uri = g_strdup_printf(_("Unnamed document %d"), ++doc_count); - document->base = NULL; - document->name = g_strdup(document->uri); + new_uri = g_strdup_printf(_("Unnamed document %d"), ++doc_count); + new_base = NULL; + new_name = g_strdup(document->uri); } // Update saveable repr attributes. Inkscape::XML::Node *repr = sp_document_repr_root(document); - // changing uri in the document repr must not be not undoable - gboolean saved = sp_document_get_undo_sensitive(document); - sp_document_set_undo_sensitive(document, FALSE); - if (document->base) - repr->setAttribute("sodipodi:docbase", document->base); + + // Changing uri in the document repr must not be not undoable. + bool const saved = sp_document_get_undo_sensitive(document); + sp_document_set_undo_sensitive(document, false); + + if (rebase) { + Inkscape::XML::rebase_hrefs(document, new_base, true); + } repr->setAttribute("sodipodi:docname", document->name); sp_document_set_undo_sensitive(document, saved); + + g_free(document->name); + g_free(document->base); + g_free(document->uri); + document->name = new_name; + document->base = new_base; + document->uri = new_uri; + document->priv->uri_set_signal.emit(document->uri); } +/** + * Sets base, name and uri members of \a document. Doesn't update + * any relative hrefs in the document: thus, this is primarily for + * newly-created documents. + * + * \see sp_document_change_uri_and_hrefs + */ +void sp_document_set_uri(SPDocument *document, gchar const *filename) +{ + g_return_if_fail(document != NULL); + + do_change_uri(document, filename, false); +} + +/** + * Changes the base, name and uri members of \a document, and updates any + * relative hrefs in the document to be relative to the new base. + * + * \see sp_document_set_uri + */ +void sp_document_change_uri_and_hrefs(SPDocument *document, gchar const *filename) +{ + g_return_if_fail(document != NULL); + + do_change_uri(document, filename, true); +} + void sp_document_resized_signal_emit(SPDocument *doc, gdouble width, gdouble height) { @@ -603,10 +842,22 @@ SPDocument::emitReconstructionFinish(void) { // printf("Finishing Reconstruction\n"); priv->_reconstruction_finish_signal.emit(); + +/** + // Reference to the old persp3d object is invalid after reconstruction. + initialize_current_persp3d(); + return; +**/ +} + +sigc::connection SPDocument::connectCommit(SPDocument::CommitSignal::slot_type slot) +{ + return priv->commit_signal.connect(slot); } + void SPDocument::_emitModified() { static guint const flags = SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG; root->emitModified(0); @@ -639,13 +890,13 @@ void SPDocument::bindObjectToId(gchar const *id, SPObject *object) { void SPDocument::addUndoObserver(Inkscape::UndoStackObserver& observer) { - this->priv->undoStackObservers.add(observer); + this->priv->undoStackObservers.add(observer); } void SPDocument::removeUndoObserver(Inkscape::UndoStackObserver& observer) { - this->priv->undoStackObservers.remove(observer); + this->priv->undoStackObservers.remove(observer); } SPObject *SPDocument::getObjectById(gchar const *id) { @@ -691,9 +942,9 @@ Glib::ustring SPDocument::getLanguage() { if ( NULL == document_language || *document_language == 0 ) { document_language = getenv ("LANG"); } - + if ( NULL != document_language ) { - gchar *pos = strchr(document_language, '_'); + const char *pos = strchr(document_language, '_'); if ( NULL != pos ) { return Glib::ustring(document_language, pos - document_language); } @@ -711,7 +962,12 @@ void sp_document_request_modified(SPDocument *doc) { if (!doc->modified_id) { - doc->modified_id = gtk_idle_add_priority(SP_DOCUMENT_UPDATE_PRIORITY, sp_document_idle_handler, doc); + doc->modified_id = g_idle_add_full(SP_DOCUMENT_UPDATE_PRIORITY, + sp_document_idle_handler, doc, NULL); + } + if (!doc->rerouting_handler_id) { + doc->rerouting_handler_id = g_idle_add_full(SP_DOCUMENT_REROUTING_PRIORITY, + sp_document_rerouting_handler, doc, NULL); } } @@ -719,7 +975,7 @@ void sp_document_setup_viewport (SPDocument *doc, SPItemCtx *ctx) { ctx->ctx.flags = 0; - ctx->i2doc = NR::identity(); + ctx->i2doc = Geom::identity(); /* Set up viewport in case svg has it defined as percentages */ if (SP_ROOT(doc->root)->viewBox_set) { // if set, take from viewBox ctx->vp.x0 = SP_ROOT(doc->root)->viewBox.x0; @@ -732,86 +988,133 @@ sp_document_setup_viewport (SPDocument *doc, SPItemCtx *ctx) ctx->vp.x1 = 210 * PX_PER_MM; ctx->vp.y1 = 297 * PX_PER_MM; } - ctx->i2vp = NR::identity(); + ctx->i2vp = Geom::identity(); +} + +/** + * Tries to update the document state based on the modified and + * "update required" flags, and return true if the document has + * been brought fully up to date. + */ +bool +SPDocument::_updateDocument() +{ + /* Process updates */ + if (this->root->uflags || this->root->mflags) { + if (this->root->uflags) { + SPItemCtx ctx; + sp_document_setup_viewport (this, &ctx); + + bool saved = sp_document_get_undo_sensitive(this); + sp_document_set_undo_sensitive(this, false); + + this->root->updateDisplay((SPCtx *)&ctx, 0); + + sp_document_set_undo_sensitive(this, saved); + } + this->_emitModified(); + } + + return !(this->root->uflags || this->root->mflags); } + +/** + * Repeatedly works on getting the document updated, since sometimes + * it takes more than one pass to get the document updated. But it + * usually should not take more than a few loops, and certainly never + * more than 32 iterations. So we bail out if we hit 32 iterations, + * since this typically indicates we're stuck in an update loop. + */ gint sp_document_ensure_up_to_date(SPDocument *doc) { - int lc; - lc = 32; - while (doc->root->uflags || doc->root->mflags) { - lc -= 1; - if (lc < 0) { - g_warning("More than 32 iterations while updating document '%s'", doc->uri); - if (doc->modified_id) { - /* Remove handler */ - gtk_idle_remove(doc->modified_id); - doc->modified_id = 0; + // Bring the document up-to-date, specifically via the following: + // 1a) Process all document updates. + // 1b) When completed, process connector routing changes. + // 2a) Process any updates resulting from connector reroutings. + int counter = 32; + for (unsigned int pass = 1; pass <= 2; ++pass) { + // Process document updates. + while (!doc->_updateDocument()) { + if (counter == 0) { + g_warning("More than 32 iteration while updating document '%s'", doc->uri); + break; } - return FALSE; + counter--; } - /* Process updates */ - if (doc->root->uflags) { - SPItemCtx ctx; - sp_document_setup_viewport (doc, &ctx); - doc->root->updateDisplay((SPCtx *)&ctx, 0); + if (counter == 0) + { + break; + } + + // After updates on the first pass we get libavoid to process all the + // changed objects and provide new routings. This may cause some objects + // to be modified, hence the second update pass. + if (pass == 1) { + doc->router->processTransaction(); } - doc->_emitModified(); } + if (doc->modified_id) { /* Remove handler */ - gtk_idle_remove(doc->modified_id); + g_source_remove(doc->modified_id); doc->modified_id = 0; } - return TRUE; + if (doc->rerouting_handler_id) { + /* Remove handler */ + g_source_remove(doc->rerouting_handler_id); + doc->rerouting_handler_id = 0; + } + return counter>0; } +/** + * An idle handler to update the document. Returns true if + * the document needs further updates. + */ static gint sp_document_idle_handler(gpointer data) { - SPDocument *doc; - int repeat; - - doc = static_cast(data); - -#ifdef SP_DOCUMENT_DEBUG_IDLE - g_print("->\n"); -#endif - - /* Process updates */ - if (doc->root->uflags) { - SPItemCtx ctx; - sp_document_setup_viewport (doc, &ctx); - - gboolean saved = sp_document_get_undo_sensitive(doc); - sp_document_set_undo_sensitive(doc, FALSE); - - doc->root->updateDisplay((SPCtx *)&ctx, 0); - - sp_document_set_undo_sensitive(doc, saved); - /* if (doc->root->uflags & SP_OBJECT_MODIFIED_FLAG) return TRUE; */ + SPDocument *doc = static_cast(data); + if (doc->_updateDocument()) { + doc->modified_id = 0; + return false; + } else { + return true; } +} - doc->_emitModified(); - - repeat = (doc->root->uflags || doc->root->mflags); - if (!repeat) doc->modified_id = 0; - return repeat; +/** + * An idle handler to reroute connectors in the document. + */ +static gint +sp_document_rerouting_handler(gpointer data) +{ + // Process any queued movement actions and determine new routings for + // object-avoiding connectors. Callbacks will be used to update and + // redraw affected connectors. + SPDocument *doc = static_cast(data); + doc->router->processTransaction(); + + // We don't need to handle rerouting again until there are further + // diagram updates. + doc->rerouting_handler_id = 0; + return false; } -static bool is_within(NR::Rect const &area, NR::Rect const &box) +static bool is_within(Geom::Rect const &area, Geom::Rect const &box) { return area.contains(box); } -static bool overlaps(NR::Rect const &area, NR::Rect const &box) +static bool overlaps(Geom::Rect const &area, Geom::Rect const &box) { return area.intersects(box); } -static GSList *find_items_in_area(GSList *s, SPGroup *group, unsigned int dkey, NR::Rect const &area, - bool (*test)(NR::Rect const &, NR::Rect const &), bool take_insensitive = false) +static GSList *find_items_in_area(GSList *s, SPGroup *group, unsigned int dkey, Geom::Rect const &area, + bool (*test)(Geom::Rect const &, Geom::Rect const &), bool take_insensitive = false) { g_return_val_if_fail(SP_IS_GROUP(group), s); @@ -823,8 +1126,8 @@ static GSList *find_items_in_area(GSList *s, SPGroup *group, unsigned int dkey, s = find_items_in_area(s, SP_GROUP(o), dkey, area, test); } else { SPItem *child = SP_ITEM(o); - NR::Rect box = sp_item_bbox_desktop(child); - if (test(area, box) && (take_insensitive || child->isVisibleAndUnlocked(dkey))) { + Geom::OptRect box = sp_item_bbox_desktop(child); + if ( box && test(area, *box) && (take_insensitive || child->isVisibleAndUnlocked(dkey))) { s = g_slist_append(s, child); } } @@ -854,11 +1157,11 @@ Returns the bottommost item from the list which is at the point, or NULL if none */ SPItem* sp_document_item_from_list_at_point_bottom(unsigned int dkey, SPGroup *group, GSList const *list, - NR::Point const p, bool take_insensitive) + Geom::Point const p, bool take_insensitive) { g_return_val_if_fail(group, NULL); - - gdouble delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0); for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { @@ -891,11 +1194,11 @@ upwards in z-order and returns what it has found so far (i.e. the found item is guaranteed to be lower than upto). */ SPItem* -find_item_at_point(unsigned int dkey, SPGroup *group, NR::Point const p, gboolean into_groups, bool take_insensitive = false, SPItem *upto = NULL) +find_item_at_point(unsigned int dkey, SPGroup *group, Geom::Point const p, gboolean into_groups, bool take_insensitive = false, SPItem *upto = NULL) { SPItem *seen = NULL, *newseen = NULL; - - gdouble delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0); for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { if (!SP_IS_ITEM(o)) continue; @@ -903,7 +1206,7 @@ find_item_at_point(unsigned int dkey, SPGroup *group, NR::Point const p, gboolea if (upto && SP_ITEM(o) == upto) break; - if (SP_IS_GROUP(o) && (SP_GROUP(o)->effectiveLayerMode(dkey) == SPGroup::LAYER || into_groups)) { + if (SP_IS_GROUP(o) && (SP_GROUP(o)->effectiveLayerMode(dkey) == SPGroup::LAYER || into_groups)) { // if nothing found yet, recurse into the group newseen = find_item_at_point(dkey, SP_GROUP(o), p, into_groups, take_insensitive, upto); if (newseen) { @@ -933,11 +1236,11 @@ Returns the topmost non-layer group from the descendants of group which is at po p, or NULL if none. Recurses into layers but not into groups. */ SPItem* -find_group_at_point(unsigned int dkey, SPGroup *group, NR::Point const p) +find_group_at_point(unsigned int dkey, SPGroup *group, Geom::Point const p) { SPItem *seen = NULL; - - gdouble delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0); for (SPObject *o = sp_object_first_child(SP_OBJECT(group)) ; o != NULL ; o = SP_OBJECT_NEXT(o) ) { if (!SP_IS_ITEM(o)) continue; @@ -967,7 +1270,7 @@ find_group_at_point(unsigned int dkey, SPGroup *group, NR::Point const p) * */ -GSList *sp_document_items_in_box(SPDocument *document, unsigned int dkey, NR::Rect const &box) +GSList *sp_document_items_in_box(SPDocument *document, unsigned int dkey, Geom::Rect const &box) { g_return_val_if_fail(document != NULL, NULL); g_return_val_if_fail(document->priv != NULL, NULL); @@ -982,7 +1285,7 @@ GSList *sp_document_items_in_box(SPDocument *document, unsigned int dkey, NR::Re * */ -GSList *sp_document_partial_items_in_box(SPDocument *document, unsigned int dkey, NR::Rect const &box) +GSList *sp_document_partial_items_in_box(SPDocument *document, unsigned int dkey, Geom::Rect const &box) { g_return_val_if_fail(document != NULL, NULL); g_return_val_if_fail(document->priv != NULL, NULL); @@ -990,8 +1293,33 @@ GSList *sp_document_partial_items_in_box(SPDocument *document, unsigned int dkey return find_items_in_area(NULL, SP_GROUP(document->root), dkey, box, overlaps); } +GSList * +sp_document_items_at_points(SPDocument *document, unsigned const key, std::vector points) +{ + GSList *items = NULL; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // When picking along the path, we don't want small objects close together + // (such as hatching strokes) to obscure each other by their deltas, + // so we temporarily set delta to a small value + gdouble saved_delta = prefs->getDouble("/options/cursortolerance/value", 1.0); + prefs->setDouble("/options/cursortolerance/value", 0.25); + + for(unsigned int i = 0; i < points.size(); i++) { + SPItem *item = sp_document_item_at_point(document, key, points[i], + false, NULL); + if (item && !g_slist_find(items, item)) + items = g_slist_prepend (items, item); + } + + // and now we restore it back + prefs->setDouble("/options/cursortolerance/value", saved_delta); + + return items; +} + SPItem * -sp_document_item_at_point(SPDocument *document, unsigned const key, NR::Point const p, +sp_document_item_at_point(SPDocument *document, unsigned const key, Geom::Point const p, gboolean const into_groups, SPItem *upto) { g_return_val_if_fail(document != NULL, NULL); @@ -1001,7 +1329,7 @@ sp_document_item_at_point(SPDocument *document, unsigned const key, NR::Point co } SPItem* -sp_document_group_at_point(SPDocument *document, unsigned int key, NR::Point const p) +sp_document_group_at_point(SPDocument *document, unsigned int key, Geom::Point const p) { g_return_val_if_fail(document != NULL, NULL); g_return_val_if_fail(document->priv != NULL, NULL); @@ -1084,7 +1412,7 @@ sigc::connection sp_document_resources_changed_connect(SPDocument *document, /* Helpers */ gboolean -sp_document_resource_list_free(gpointer key, gpointer value, gpointer data) +sp_document_resource_list_free(gpointer /*key*/, gpointer value, gpointer /*data*/) { g_slist_free((GSList *) value); return TRUE; @@ -1146,6 +1474,10 @@ vacuum_document(SPDocument *document) return start - newend; } +bool SPDocument::isSeeking() const { + return priv->seeking; +} + /* Local Variables: