X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fdocument.cpp;h=356fba577055c7cab0d165401117f68c68fcadc6;hb=f5e01381df27a3dd9343e50b4e03bf92ca79edbc;hp=e5c0cd82495e1a91b5c925a315f157d124d9457c;hpb=e7423aec32440b35613538215bc3318ac44916c9;p=inkscape.git diff --git a/src/document.cpp b/src/document.cpp index e5c0cd824..356fba577 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -18,7 +18,7 @@ /** \class SPDocument * SPDocument serves as the container of both model trees (agnostic XML * and typed object tree), and implements all of the document-level - * functionality used by the program. Many document level operations, like + * functionality used by the program. Many document level operations, like * load, save, print, export and so on, use SPDocument as their basic datatype. * * SPDocument implements undo and redo stacks and an id-based object @@ -38,6 +38,8 @@ # include "config.h" #endif #include +#include +#include #include "application/application.h" #include "application/editor.h" #include "libnr/nr-matrix-fns.h" @@ -46,17 +48,23 @@ #include "inkscape-private.h" #include "inkscape_version.h" #include "sp-object-repr.h" +#include "sp-namedview.h" +#include "desktop.h" #include "document-private.h" #include "dir-util.h" #include "unit-constants.h" #include "prefs-utils.h" +#include "libavoid/router.h" +#include "libnr/nr-rect.h" +#include "sp-item-group.h" +#include "profile-manager.h" +#include "persp3d.h" #include "display/nr-arena-item.h" #include "dialogs/rdf.h" -#define A4_WIDTH_STR "210mm" -#define A4_HEIGHT_STR "297mm" +#include "transf_mat_3x4.h" #define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 1) @@ -67,26 +75,34 @@ gboolean sp_document_resource_list_free(gpointer key, gpointer value, gpointer d 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; +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), + profileManager(0), // deferred until after other initialization + router(new Avoid::Router()), + perspectives(0), + current_persp3d(0), + _collection_queue(0) +{ + // Don't use the Consolidate moves optimisation. + router->ConsolidateMoves = false; - _collection_queue = NULL; + 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); @@ -98,16 +114,27 @@ 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); } SPDocument::~SPDocument() { collectOrphans(); - if (priv) { - inkscape_remove_document(this); + // kill/unhook this first + if ( profileManager ) { + delete profileManager; + profileManager = 0; + } + if (priv) { if (priv->partial) { sp_repr_free_log(priv->partial); priv->partial = NULL; @@ -117,8 +144,8 @@ SPDocument::~SPDocument() { sp_document_clear_undo(this); if (root) { - sp_object_invoke_release(root); - g_object_unref(G_OBJECT(root)); + root->releaseReferences(); + sp_object_unref(root); root = NULL; } @@ -164,7 +191,37 @@ SPDocument::~SPDocument() { keepalive = FALSE; } + if (router) { + delete router; + router = NULL; + } + //delete this->_whiteboard_session_manager; + +} + +void SPDocument::add_persp3d (Persp3D * const /*persp*/) +{ + 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)) { + g_print ("Encountered a Persp3D in defs\n"); + } + } + + g_print ("Adding Persp3D to defs\n"); + persp3d_create_xml_element (this); +} + +void SPDocument::remove_persp3d (Persp3D * const /*persp*/) +{ + // TODO: Delete the repr, maybe perform a check if any boxes are still linked to the perspective. + // Anything else? + g_print ("Please implement deletion of perspectives here.\n"); +} + +unsigned long SPDocument::serial() const { + return priv->serial; } void SPDocument::queueForOrphanCollection(SPObject *object) { @@ -188,12 +245,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, @@ -204,7 +261,7 @@ sp_document_create(Inkscape::XML::Document *rdoc, Inkscape::XML::Node *rroot; Inkscape::Version sodipodi_version; - rroot = sp_repr_document_root(rdoc); + rroot = rdoc->root(); document = new SPDocument(); @@ -239,19 +296,22 @@ sp_document_create(Inkscape::XML::Document *rdoc, /* 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, @@ -261,11 +321,11 @@ sp_document_create(Inkscape::XML::Document *rdoc, // 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 = rdoc->createElement("sodipodi:namedview"); rnew->setAttribute("id", "base"); } else { // otherwise, take from preferences - rnew = r->duplicate(); + rnew = r->duplicate(rroot->document()); } // insert into the document rroot->addChild(rnew, NULL); @@ -276,7 +336,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); @@ -289,7 +349,16 @@ sp_document_create(Inkscape::XML::Document *rdoc, inkscape_ref(); } - sp_document_set_undo_sensitive(document, TRUE); + // Remark: Here, we used to create a "currentpersp3d" element in the document defs. + // But this is probably a bad idea since we need to adapt it for every change of selection, which will + // completely clutter the undo history. Maybe rather save it to prefs on exit and re-read it on startup? + + document->current_persp3d = persp3d_document_first_persp(document); + if (!document->current_persp3d) { + document->current_persp3d = persp3d_create_xml_element (document); + } + + 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()) { @@ -301,13 +370,12 @@ sp_document_create(Inkscape::XML::Document *rdoc, 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)); } - inkscape_add_document(document); return document; } /** - * Fetches document from URI, or creates new, if NULL; public document + * Fetches document from URI, or creates new, if NULL; public document * appears in document list. */ SPDocument * @@ -325,7 +393,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; @@ -374,7 +442,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; @@ -386,12 +454,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) { @@ -414,7 +476,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 @@ -468,7 +534,42 @@ 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; +} + +/** + * Given an NR::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. + */ +void SPDocument::fitToRect(NR::Rect const &rect) +{ + g_return_if_fail(!rect.isEmpty()); + + using NR::X; using NR::Y; + double const w = rect.extent(X); + double const h = rect.extent(Y); + + double const old_height = sp_document_height(this); + SPUnit const &px(sp_unit_get_by_id(SP_UNIT_PX)); + sp_document_set_width(this, w, &px); + sp_document_set_height(this, h, &px); + + NR::translate const tr(NR::Point(0, (old_height - h)) + - rect.min()); + SP_GROUP(root)->translateChildItems(tr); + SPNamedView *nv = sp_document_namedview(this, 0); + if(nv) { + NR::translate tr2(-rect.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) @@ -510,10 +611,8 @@ void sp_document_set_uri(SPDocument *document, gchar const *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); + bool saved = sp_document_get_undo_sensitive(document); + sp_document_set_undo_sensitive(document, false); repr->setAttribute("sodipodi:docname", document->name); sp_document_set_undo_sensitive(document, saved); @@ -572,6 +671,12 @@ SPDocument::emitReconstructionFinish(void) 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; @@ -605,13 +710,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) { @@ -642,6 +747,35 @@ SPObject *SPDocument::getObjectByRepr(Inkscape::XML::Node *repr) { return (SPObject*)g_hash_table_lookup(priv->reprdef, repr); } +Glib::ustring SPDocument::getLanguage() { + gchar const *document_language = rdf_get_work_entity(this, rdf_find_entity("language")); + if (document_language) { + while (isspace(*document_language)) + document_language++; + } + if ( !document_language || 0 == *document_language) { + // retrieve system language + document_language = getenv("LC_ALL"); + if ( NULL == document_language || *document_language == 0 ) { + document_language = getenv ("LC_MESSAGES"); + } + if ( NULL == document_language || *document_language == 0 ) { + document_language = getenv ("LANG"); + } + + if ( NULL != document_language ) { + gchar *pos = strchr(document_language, '_'); + if ( NULL != pos ) { + return Glib::ustring(document_language, pos - document_language); + } + } + } + + if ( NULL == document_language ) + return Glib::ustring(); + return document_language; +} + /* Object modification root handler */ void @@ -672,69 +806,75 @@ sp_document_setup_viewport (SPDocument *doc, SPItemCtx *ctx) ctx->i2vp = NR::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; - } - return FALSE; - } - /* Process updates */ - if (doc->root->uflags) { - SPItemCtx ctx; - sp_document_setup_viewport (doc, &ctx); - doc->root->updateDisplay((SPCtx *)&ctx, 0); + int counter = 32; + while (!doc->_updateDocument()) { + if (counter == 0) { + g_warning("More than 32 iteration while updating document '%s'", doc->uri); + break; } - doc->_emitModified(); + counter--; } + if (doc->modified_id) { /* Remove handler */ gtk_idle_remove(doc->modified_id); doc->modified_id = 0; } - return TRUE; + 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; } static bool is_within(NR::Rect const &area, NR::Rect const &box) @@ -760,8 +900,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))) { + NR::Maybe box = sp_item_bbox_desktop(child); + if ( box && test(area, *box) && (take_insensitive || child->isVisibleAndUnlocked(dkey))) { s = g_slist_append(s, child); } } @@ -840,7 +980,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) { @@ -927,6 +1067,30 @@ 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; + + // 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_get_double_attribute ("options.cursortolerance", "value", 1.0); + prefs_set_double_attribute ("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_set_double_attribute ("options.cursortolerance", "value", saved_delta); + + return items; +} + SPItem * sp_document_item_at_point(SPDocument *document, unsigned const key, NR::Point const p, gboolean const into_groups, SPItem *upto) @@ -1021,7 +1185,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; @@ -1083,6 +1247,10 @@ vacuum_document(SPDocument *document) return start - newend; } +bool SPDocument::isSeeking() const { + return priv->seeking; +} + /* Local Variables: