#ifndef LURCH_ENVIRONMENT #define LURCH_ENVIRONMENT #include "lob.h" #include "lchange.h" #include "lobscript.h" #include "lpathtracker.h" class LurchPackage; /** \brief A script interpreter, current document, and set of document search paths, plus * the functionality for working with them together * * This object sets up the scripting environment in the interpeter using setupInterpreter(), * which calls addLobsToScriptEngine(). * * It keeps a current document which can be retrieved through document() and modified * with any of the Lob member functions, and loaded, saved, or erased with methods in this * class. It keeps a set of document search paths so that it can find and load dependencies * of any document it is asked to load. */ class LurchEnvironment : public LurchPathTracker { Q_OBJECT public: /** \brief Construct a new Lurch Environment with no search paths * * Although you can leave the list of search paths empty, this is rarely desirable, * because then no document can have dependencies, for they would never be found. * Use the add() method inherited from LurchPathTracker to add search paths, and * watch its return value to ensure no conflicts. * * This object is also indexed (using environments) at construction time. * * The indexing is tested in test_lenv::test_indexing(). */ LurchEnvironment (); /** \brief Un-indexes this object before deletion * * \see environments * * This routine is tested in test_lenv::test_indexing(). */ ~LurchEnvironment (); /** \brief Retrieve the current document * * If this environment was just constructed, no calls to load() have succeeded, * and no calls to newDocument() have been made, this will be an empty Lob. * * You can change the current document with any of the member functions in the Lob * class applied to the result of this document() call, except set(), which will * not replace the document with a new one, as you might think. Instead, * call newDocument() and then make calls to document().addChild(), * document().setAttribute(), etc. * * This routine is tested in test_lenv::test_doc_and_deps(). * * See load() and newDocument() for information on how they change the current * document. */ Lob document (); /** \brief Retrieve the script engine inside this environment * * Note that this function should be used with caution. You receive a pointer to * the script engine, which may be in any number of states. It is reasonable to * do things like * \code * engine()->globalObject().addProperty( ... ); * \endcode * but not necessarily reasonable to do things like * \code * engine()->evaluate( ... ); * \endcode * unless you know an evaluation is not already in progress. * * Furthermore, deleting the script engine object would certainly cause the program * to crash the next time the LurchEnvironment needed to access the script engine. * Thus use the results of this function wisely. */ QScriptEngine* engine (); /** \brief Tries to load the given document or package as the current document * * The argument \a fnOrURN can be a filename to a Lurch document on disk, or the URN of one * in the search paths, or the URN of a Lurch package. * * Attempting to load a document may fail for any number of reasons, * inability to open the file, inability to parse the file's contents, * the URN provided does not match any in the search paths, etc. * Success is returned as true and failure as false; * in the event of failure, check errorMessage() for more information. * In the event of success, check warningMessage() to see if there are any warnings * that did not prevent document loading, but may be relevant nonetheless * (e.g. a dependency was missing, but the document loaded anyway). * * In the case of success, document() will be the loaded document, and its dependencies, * in the order in which they needed to be loaded, will be accessible (though not * editable) via numDependencies() and dependency(). * In the case of failure, neither document() nor any dependency() will have been * altered. * * If \a fnOrURN was the URN of a package, then the current document will be read-only, * and will contain the results of LurchPackage::asDocument() called on the package object. * Otherwise, it will be editable. * * If running an auto-run script in a dependency or the document itself fails, this * routine will not return failure, and the document and dependencies will * have completed loading. You can detect failures of auto-run scripts by listening to * the autoRunScriptError() signal. * * This routine is tested in test_lenv::test_doc_and_deps() and * test_lenv::test_packages(). * * \see errorMessage(), warningMessage() */ bool load ( QString fnOrURN ); /** \brief Tries to reload the file by calling load(), assuming it has been saved recently * * If the document isDirty(), this sets errorMessage with error(), and * returns false. * * Otherwise, it executes load() passing as parameter lastSaveFilename, and returns * the result of the load operation. This may, in turn, raise errors with error(), * and cause false to be returned, or it may return true on succes; see load(). */ bool reload (); /** \brief Tries to save the current document under the given filename * * This may fail for reasons such as no write access to the file or directory, etc. * Success is returned as true and failure as false; * in the event of failure, check errorMessage() for more information. * * If it succeeds and \a remember is true, then isDirty() will become false. * You can set \a remember to false if you want Lurch to forget that it saved the file, * in two senses. First, it will not mark the data as clean; second, it will not change the * last save filename. This is useful when performing autosaves, which should not count as * actual saves of the document from the user's point of view. * * Before saving, this routine may modify the document by removing any unused IDs, * with removeExtraIDs(). This may modify the document. If you do not wish this to * happen, set \a makeChanges to false. For example, when autosaving the document * periodically for recovery in case of a crash, it is probably not good to remove IDs, * since they may be IDs the user was still using (e.g., amidst a cut + paste operation). * * You may wish to use the URN/filename conversion utilities this object inherits from * LurchPathTracker, most notably URNToFilename() and URNToNewFilename(), for determining * a filename if you only have a URN. * * This routine is tested in test_lenv::test_doc_and_deps(). * * \see errorMessage(), isDirty() */ bool save ( QString filename, bool remember = true, bool makeChanges = true ); /** \brief Returns the last filename under which a save took place * * For more details, see documentation for lastSaveFilename. */ QString filename () const; /** \brief Whether the data is "dirty," meaning recent modifications are unsaved * * This is set to true whenever the watchDocumentChanges() slot is called. * It is set to false whenever a load() or save() succeeds. * * \see setDirty() */ bool isDirty () const; /** \brief Change the "dirty" flag for this document's data (unsaved modifications) * * A LurchEnvironment guarantees that its isDirty() method returns reliable information * if and only if you do not call setDirty(). If you do, then you are responsible for * whether the information in the isDirty() flag is correct from that point until the * LurchEnvironment modifies the value again, as described in the documentation for * isDirty(). * * That having been said, this method can still be useful if you need to make a friendly * amendment to a document that should not mark it dirty. For instance, if you call * newDocument() and then immediately afterwards give the new document a sensible initial * title, you may not want the document to count as "dirty" despite your modification. * In such a case, setDirty( false ) is useful. * * \see isDirty() */ void setDirty ( bool on = true ); /** \brief Remove the current document and script context, and any dependencies; * replace them with a new one * * The new document will be an OmApplicationType node with one child, the symbol * "document" in content dictionary "LurchCore," with no additional children (arguments). * It will have attributes taken from \a urn, unless it is not a valid Lurch URN * (Lob::isLurchURN()), in which case default values will be put in (author * "unknown," title "New Document," version "0"). Thus in every case it will satisfy * the requirements of Lob::isDocument(). * * After filling this environment with a new document, this routine ensures the URN is * unique by calling ensureURNUniqueForAuthor() with no argument. * * This routine is tested in test_lenv::test_doc_and_deps(). */ void newDocument ( QString urn = QString() ); /** \brief Remove the current document and script context, and any dependencies; * replace them with the given one * * If \a filename does not refer to a loadable document, then this routine * simply calls newDocument() with an empty string. Otherwise, this routine * functions exactly as newDocument(), except that the new document will not be * the empty one described in that function's documentation, but will be the result * of loading the document in \a filename. * * Despite having been loaded from disk, the document will not have a default save * filename, as you would expect of a new document. This allows using the document * as a template without danger of saving over the original. * * After filling this environment with a new document, this routine ensures the URN is * unique by calling ensureURNUniqueForAuthor() with \a author as argument. You may let * \a author be empty here (the default); see the documentation for * ensureURNUniqueForAuthor() for how it handles empty authors. * * Returns true if the template was loaded successfully, or false if the template could * not load due to some error, so that a blank document had to be loaded instead. */ bool newDocumentFromTemplate ( QString filename, QString author = QString() ); /** \brief Set the list of temporary dependencies * * A temporary dependency is one that should always be on the dependencies list in * memory, even if individual loaded documents do not themselves require it, * and which should not be added to any individual document's dependency list, * to prevent permanent dependence of the document on the temporary dependency. * * This concept allows developers to run Lurch with developer-only dependencies, * just for one session, without altering the document they're using them to debug * or inspect. * * After this function records the given list for later use, it also adds every element * of the list to the loaded dependencies for the currently loaded * document, but does not add any that are already on the list, nor does it add these * dependencies to the document's attributes, so that the relationship will not be saved. * * Note that temporary dependencies cannot have their elements referenced by nicknames. * This is a limitation in current code, and could be solved by some extra work, but * I don't see it as a severe limitation, since the expectation is that this would be * used just to load developer routines for debugging, and that no permanent reference * to a temporary dependency (e.g., using a theorem in one as a reason in a proof) * should occur. * * \return True on success, false on failure. If failure, check errorMessage(). * If this function fails, it empties the list of temporary dependencies in * memory, rather than leave an invalid list around for later failures. */ bool setTemporaryDependencies ( QStringList filenames ); /** \brief Get the list of temporary dependencies * * This function simply returns the last list passed to setTemporaryDependencies(). * See the documentation for that function to learn what a temporary dependency is. */ QStringList getTemporaryDependencies () const; /** \brief How many dependencies the current document has (recursively) * * The order is created by reading the dependencies for the current document, * and inserting before each one any of its dependencies, and so on recursively. * For example, if the current document depends on two documents A and B in that order, * and A depends on nothing, yet B depends on C and D in that order, * then the current document has four dependencies, in the order A, C, D, B. * * This function returns how many there are, and you can use dependency() to look * them up. The dependencies list is populated when load() is called. * * This routine is tested a bit in test_lenv::test_doc_and_deps(). * * \see dependency() */ int numDependencies () const; /** \brief Look up one of the current document's dependencies (a read-only Lob) * * Descriptions of how the dependencies list is populated and in what order can be * found in load() and numDependencies(). This routine fetches one of the * dependencies, and returns it as a read-only Lob (see Lob::isEditable()). * If \a index is out of range (smaller than zero or greater than or equal to * numDependencies()) then an empty Lob is returned. * * This routine is tested a bit in test_lenv::test_doc_and_deps(). * * \see load(), numDependencies() */ Lob dependency ( int index ) const; /** \brief Whether the given dependency is a package (as opposed to just a document) * * Returns true if the dependency named is a package, false if it is a document only. * (Packages have virtual documents that appear in the dependency list, as returned * by dependency(), hence the phrase "document only".) * If \a index is outside the valid range (same range as for dependency()) then * false is returned. * * This routine is tested in test_lenv::test_packages(). */ bool dependencyIsPackage ( int index ) const; /** \brief The first package instantiating the template class \a T * * Obviously, \a T should be some descendant of LurchPackage for this to make any sense. * This will return the first package in the array of dependencies for the current * document that inherits the class \a T. * * Example use: * Say you're writing C++ code in one package A, which would like to check if package B * is also loaded, and if so, make use of it. One way to do so, inside a member function * of package A, would be as follows. * \code * // this assumes that the variable env was saved from the initialize() function, * // and refers to the current LurchEnvironment * MyPackageB* packageBInstance = qobject_cast( * env->loadedPackage( "MyPackageB" ) ); * if ( packageBInstance ) { * // then the package does exist, and I have a valid instance of it, * // in fact, the particular valid instance that's in the same LurchEnvironment * // as the instance of MyPackageA in which this code is being run * packageBInstance->doSomeSpecialPackageBThing(); * } * \endcode */ LurchPackage* loadedPackage ( const char* className ) const; /** \brief Look up the alias for one of the current document's dependencies * * By default, dependencies have no aliases, but if the dependency was specified * not just as a basic URN, but as one with the optional identifier=urn * syntax documented in Lob::isLurchURN(), then it will have the identifier as its * alias. This has an effect of creating a namespace for that dependency in the * LurchEnvironment's QScriptEngine. * * The alias for any dependency can be fetched with this function, and it returns * an empty string if no alias was given, or the alias otherwise. */ QString alias ( int index ) const; /** \brief Get the address of a Lob in the document/dependency list * * The LobAddress class (and supporting routines in Lob to use it) only deals with * addresses of Lobs inside other Lobs. LurchEnvironment, however, keeps an array * of Lobs (the document's dependencies) followed by the document itself (the one * top-level, non-dependency Lob). It is useful to think on the level of addressing * a Lob within an environment, where the first step answers the question "In which * dependency (or the document itself) is the addressed Lob?" * * This routine returns a LobAddress whose first step is of type AsChild, with index * between 0 and numDependencies()-1 if it is a step into a dependency, or * numDependencies() if it s a step into the document, and whose remaining steps * are the address within that document. * * This routine is tested in test_lenv::test_packages(). */ LobAddress address ( const Lob docOrDepNode ) const; /** \brief Get the a Lob from the document/dependency list given an extended address * * See address() for information about the concept of an extended address, for indexing * into the document/dependency list. * * This routine takes a LobAddress whose first step is of type AsChild, with index * between 0 and numDependencies() inclusive, as would be returned from address(), * and it returns the corresponding Lob inside either a dependency or the document. * That is, for any LurchEnvironment e and any Lob L in any of its dependencies or * document, this routine looks up Lobs such that * e.index( e.address( L ) ) == L. * * If the address does not begin with an AsChild step, or the index is not in the * specified range, this routine returns an empty Lob. An empty Lob may also be * returned if the address thereafter is invalid (e.g., asking for child 7 in a * dependency with 3 children, or any other error that can come about from * Lob::index()). * * This routine is tested in test_lenv::test_packages(). */ Lob index ( const LobAddress address ) const; /** \brief Execute the given script code in the interpreter * * Simply executes the given script code in the script interpreter in this object * and returns the result as a QScriptValue. (Errors in script execution do not * impact errorMessage(); the script value returned will be the error.) * * If any auto-run script in the document or any of its dependencies has been edited * since the last evaluation of any script code in this environment, the environment * will call scriptUpdate() to ensure it is up-to-date before running this code. * * If \a activationObject is nonempty, then a new context is pushed on the interpreter, * and in its activation object each property of \a activationObject is copied over. * When evaluation is done, the context is popped. * * If \a withPrevious is true, then any alterations this makes to the document will * not be added to the undo stack as a new action, but will be combined into the * previous action. * * This routine is tested a bit in test_lenv::test_modifications(). */ QScriptValue evaluate ( QString script, QScriptValue activationObject = QScriptValue(), bool withPrevious = false ); /** \brief If a recent operation failed, this gives the error message * * If the most recent operation (such as loading a document) succeeded, this routine * returns the empty string. * * This routine is tested a bit in test_lenv::test_doc_and_deps(). */ QString errorMessage () const; /** \brief If a recent operation succeeded but with warnings, this gives the warning message * * If the most recent operation (such as loading a document) succeeded, but something * happened that the user may wish to know about (e.g., some dependencies did not load), * this routine will return the warning message. If there were no warnings, it will be * empty. * * This routine is untested. */ QString warningMessage () const; /** \brief This environment's unique integer index, for use in script data fields * * The inverse getter for the environments property; see its documentation for more * details. * * This routine is tested in test_lenv::test_indexing(). */ int index (); /** \brief A way to retrieve the environment based on its index * * A getter for the environments property; see its documentation for more details. * * This routine is tested in test_lenv::test_indexing(). */ static LurchEnvironment* lookupEnvironment ( int index ); /** \brief Set the current document's URN * * This is a necessary function even though the option exists to simply call * document().setURN(). The reason is that this latter call will not * accomplish anything, because this object watches and prevents changes to the document * URN. This function, however, turns off such policing just for the duration of this * one change. */ void setCurrentDocumentURN ( QString urn ); /** \brief Set the current document's dependency list * * This is a necessary function even though the option exists to simply call * document().setDependencies(). The reason is that this latter call will * not accomplish anything, because this object watches and prevents changes to the * dependency list. * This function, however, turns off such policing just for the duration of this * one change. * * The document must (and will) be completely reloaded after * any changes to dependencies are made. This may cause errors if a recently added * dependency has errors in it, such as * conflicting IDs or nicknames, or XML or script syntax problems. * * Returns true on success, or false on failure. If false is returned, check * errorMessage() for details. Reasons for failure include the inability to save the * document temporarily to disk, a necessary step in changing dependencies. * * Note that if this function is called with an argument equal to the document's * current dependency list, this function does nothing (saves time). * The exception to that rule is if the parameter is an empty list, which is useful * for setting up the document's dependencies for the first time. */ bool setCurrentDocumentDependencies ( QStringList dependencies ); /** \brief Sets the given key-value pair as an attribute on the document, silently * * By "silently," I mean that the action will not be recorded on the undo/redo stack. * There may occasionally be other metadata besides the URN and dependencies * (settable with setCurrentDocumentURN() and setCurrentDocumentDependencies(), * respectively) that may need to be stored in the document. Since the action of storing * that metadata may be invisible to the user, it should not be stored on an undo/redo * stack. This function is useful in that situation. * * Note related but different behavior is available with setActionsInvisible(). That * function does record the actions on the undo stack, but merged with the previous * action. This function is more like a generalization of setCurrentDocumentURN() and * setCurrentDocumentDependencies(). */ void setDocumentMetadata ( Lob key, Lob value ); /** \brief Convert a list of direct dependencies into a list of indirect ones also * * Given a list of URNs that are to be viewed as a document's direct dependencies, what * is the corresponding list of indirect dependencies (also a list of URNs)? This * function does that computation and returns the result. The results will not always * be basic Lurch URNs; some may contain aliases. * * Returns an empty QStringList iff an error occurred (e.g., a file was unable to be * loaded, or successfully parsed). In such cases, errorMessage() contains information. * * This is a somewhat expensive function to call, because all the dependencies must be * loaded, so that we may read their dependencies, etc. */ QStringList directToIndirectDependencies ( const QStringList& directDeps ); /** \brief Overrides LurchPathTracker::indexedURNs(), extending it to include package URNs * * The idea of LurchPathTracker::indexedURNs() is to provide a list of every URN known to * the LurchPathTracker object. A LurchEnvironment has knowledge of more URNs, * specifically the URN for every package compiled into the application. So this * implementation just calls that of the parent class, then adds every package URN to the * result before returning it. */ QStringList indexedURNs () const; /** \brief Whether there are any elements on the undo stack that can be invoked * * If actions have been performed on the document since it was last loaded or created, * this will be true. Then you can call undo() and expect it to perform the top action * on the stack. * * \see undoAvailableChanged(), undo(), undoStack */ bool undoAvailable () const; /** \brief Whether there are any elements on the redo stack that can be invoked * * If actions have been undone on the document recently, with no intervening changes to * the document of any other kind, then this will be true. * Then you can call redo() and expect it to perform the top action on the stack. * * \see redoAvailableChanged(), redo(), redoStack */ bool redoAvailable () const; /** \brief If the currently loaded document is a package, return it; otherwise return null */ LurchPackage* documentPackage (); /** \brief Turn on/off the treating of all document changes as "invisible" to undo/redo * * By default, any change that takes place in the document is treated as an action and * placed on the undo stack, so that it can be undone. This is often desirable, because * each of the user's edits changes the document, and thus immediate undo/redo support * is present, at this foundational level. * * However, sometimes this behavior is undesirable. For example, when a background * process tweaks the contents of the document periodically, without the user's knowledge, * it can be confusing or even wrong to give the user the ability to undo/redo those * changes. What should happen instead is that any such changes invisible to the user * are automatically merged with the user's previous action. Although they happened at * different points in time, they are logically united as consequences of the same * action on the part of the user. * * This function supports that functionality by allowing any other code that is about to * modify the document outside of the user's initiative to turn on/off "invisible" mode. * Actions that happen without the user's initiative are said to be "invisible" to him * or her, and thus when invisible mode is turned on in the environment, all document * changes are merged with the previous item on the undo stack (iff there is one). * Thus a process that modifies the document outside without the user's specifically * having initiated the action should proceed as follows. * \code * // LE is an object of type LurchEnvironment * LE.setActionsInvisible( true ); * performLotsOfChangesInHere( LE ); * LE.setActionsInvisible( false ); * \endcode * Please ensure calls to this function come in matched pairs, as shown above! */ void setActionsInvisible ( bool on = true ); /** \brief Returns whether actions are currently invisible or not * * \see setActionsInvisible() */ bool getActionsInvisible () const; /** \brief Set the environment in a "busy" state, so that other tasks know to wait * * This class does not respect the "busy" state in any of its own actions, but other * tasks that manipulate the document or the script environment may share this datum. * If one such task is about to perform a large-scale document edit, and does not * want another task to interrupt it and begin other work until the large task is * complete, it can call setBusy() to true. If all tasks that periodically interrupt * others first check isBusy() before proceeding, this helps with performance and * responsiveness of the application. */ void setBusy ( bool on = true ); /** \brief Queries the most recent value set with setBusy() * * See that function's documentation for more information. * The "busy" value begins in the false state when this object is created. */ bool isBusy () const; public slots: /** \brief Undo the last document change * * Applies the action on the top of the undo stack. * This will only have an effect if changes have been made since the document was * loaded or created. * * \see undoAvailable(), undoStack */ void undo (); /** \brief Redo the last undone action * * Applies the action on the top of the redo stack. * This will only have an effect if an action was just undone, * and no document changes have happened since. * * \see redoAvailable(), redoStack */ void redo (); private slots: /** \brief Updates the script environment according to document changes * * This slot is designed to be triggered by the Lob::modified() signal in the current * document. The LurchEnvironment constructor sets up the connection. * This function serves several purposes. * *
    *
  1. If the change modified the author, version, title, or dependencies of the * document, change them back and set an error message stating that the * document's URN and dependencies are not directly modifiable. * This prevents scripts from modifying them; C++ clients of this class * may call setCurrentDocumentURN() and setCurrentDocumentDependencies().
  2. *
  3. If new data was inserted, check to see if it introduced any nickname * conflicts. If so, undo the change and give an appropriate error message. * Otherwise, add the new data's nickname information to the nickname lookup * table.
  4. *
  5. If old data was removed, remove it's nickname information from the nickname * lookup table.
  6. *
  7. Set the document dirty flag to true.
  8. *
  9. If the change involved an auto-run script, set the script environment dirty * flag to true.
  10. *
  11. Clear the error message, to indicate success.
  12. *
* * This routine is tested a bit in test_lenv::test_modifications(). */ void watchDocumentChanges ( const LobChange& change ); /** \brief Posts the accumulated changes to the undo stack and clears out \a scriptAction * * See noteChange() and scriptAction for more information. */ void changesEnded (); signals: /** \brief A signal propagated from the inner document Lob * * See Lob::modified() for more information on the signal itself, and its parameter. * * Note that this document watches the Lob::modified() signal emitted from the * document() object, and if it is an impermissible change (as described in the * watchDocumentChanges() signal handler), it reverses it and never emits this * signal to the outside. All other times, the document's modified signal is simply * passed on unchanged. */ void documentModified ( const LobChange& change ); /** \brief Same as documentModified(), but emitted last * * Qt signals and slots have no defined order in which they are fired. However, in Lurch, * Lob-to-WP syncing needs to be the last to handle any documentModified() signals. The * reason for this is that it sometimes changes the document further, which emits more * documentModified() signals. If that happened before all other signals had heard the * first ones, they would be received in the wrong order, and therefore be inconsistent * with the document itself. * * Thus we provide this special signal for just that purpose. It is emitted immediately * after any documentModified() signal, with the same data. If the slot to which it is * connected modifies the document, then further documentModified() and then * lateDocumentModified() signals will be emitted for those changes, of course. */ void lateDocumentModified ( const LobChange& change ); /** \brief Emitted when a new QScriptEngine has been created/refreshed within this object * * Occasionally this object needs to create and destroy its internal QScriptEngine * \a interp, when loading a new document or refreshing the script environment, etc. * This signal lets the outside world know that such events have taken place, in case * they have modifications to make to the interpreter, such as adding functionality. */ void newScriptEngine ( QScriptEngine* engine ); /** \brief Emitted when an auto-run script aborts with an error * * Whenever changes to auto-run scripts take place, the environment notices, and marks * the script engine dirty. This will trigger a complete destruction and recreation of * the script engine, together with a full re-running of all auto-run scripts in the * document and dependencies before the next evaluation of any script code in that engine. * Because of this (and that recreations of the scripting environment also happen at other * times) there are many points at which an auto-run script error could occur. Thus the * simplest solution is to emit a signal when that occurs. This is that signal. * * The \a message argument contains the error in human-readable form. * This includes a mention of the fact that because one auto-run script had an error, * subsequent auto-run scripts were not executed, and thus some functionality and/or * data may be missing from the script environment. */ void autoRunScriptError ( QString message ); /** \brief Emitted when the status of undo action availability changes * * \param yesno true if and only if an action has been done that can now be undone * * \see undoAvailable() */ void undoAvailableChanged ( bool yesno ); /** \brief Emitted when the status of redo action availability changes * * \param yesno true if and only if an action has been undone that can now be redone * * \see redoAvailable() */ void redoAvailableChanged ( bool yesno ); /** \brief Emitted when a script function wants to communicate with the user interface * * A LurchEnvironment does not come with a user interface, and so it provides a function * called UI(), in script, by which scripts can request that the environment * emit this signal. * * If no user interface is in place (e.g., in simple_script) then this * signal will not be heard and no effect will take place. The \a result parameter will * remain in its initial, unchanged (undefined/invalid) condition, so that the script's * call to UI() will be as if it had not contained a "return" statement. * * If a user interface is in place, listening for this signal, then it can handle the * function call and modify the \a result parameter to return a value. * * Those user interfaces that listen to this signal should interpret the first argument * in the context as a string indicating the action to take. It is suggested that user * interfaces at least implement the following actions: * */ void userInterfaceCall ( QScriptContext* context, QScriptEngine* engine, QScriptValue& result ); /** \brief Emitted when a package emits the signal of the same signature * * This signal is just here to pass along signals emitted by packages to any potentially * listening user interface. It is similar to */ void packageMessageToInterface ( QString message ); /** \brief Emitted when the document's "dirty" status changes * * If changes in the document is saved, its status will go from dirty to not dirty. * If a document with no changes since the last save is then changed, its status will go * from not dirty to dirty. * When either of these happens, this signal is emitted, with the new dirty status. */ void dirtyChanged ( bool toThis ); private: #ifdef LURCH_UNIT_TEST friend class LURCH_UNIT_TEST; #endif friend QScriptValue nicknameLookup ( QScriptContext* context, QScriptEngine* engine ); friend QScriptValue uiCall ( QScriptContext* context, QScriptEngine* engine ); /** \brief Holds the current document; may be empty if no document loaded */ Lob doc; /** \brief Whether the document has been modified since its last save */ bool dirty; /** \brief Whether auto-run scripts in the document have been modified since the last * invocation of a script */ bool envDirty; /** \brief The interpreter this environment uses on functions in the document, when needed */ QScriptEngine* interp; /** \brief List of dependencies on which the current document relies, in the order loaded * * Each Lob in this list will be read-only (!isEditable()). */ QList deps; /** \brief List of aliases for each dependency * * If a dependency was specified by an URN with the optional identifier=urn * syntax, its alias is that identifier. If not, its alias is empty. */ QStringList aliases; /** \brief List of temporary dependencies to use for this environment * * For an explanation of temporary dependencies, see the documentation of the * setTemporaryDependencies() function. */ QStringList tempdeps; /** \brief If a recent operation failed, the error message is stored here * * \see errorMessage(), error(), noErrors() */ QString err; /** \brief If a recent operation succeeded, but with a warning, the message is stored here * * This is a counterpart to the err member. * * \see warning(), warningMessage(), errorMessage(), error(), noErrors(), err */ QString war; /** \brief Filename into which the document was most recently saved * * This may be empty if the document has never been saved, and it will often be equal * to URNToFilename( document().getURN() ), but it is updated internally * every time dirty is set to false. */ QString lastSaveFilename; /** \brief Whether to merge the next set of changes with the previous on the undo stack * * This is respected (and cleared) by the changesEnded() slot, which does all posting * to the undo stack, by virtue of its being the only function that calls actionDone(). */ bool undoMerge; /** \brief Ensures the document's URN is unique for \a author, and remove template flags * * This is useful after a new document has been created, either by calling newDocument() * or newDocumentFromTemplate(). Call this routine with the appropriate author (or none * to keep the same author, such as the one specified in the URN passed to newDocument()), * and the environment will perform two useful steps. *
    *
  1. It will append #1, or #2, or whatever number is needed to the title, to ensure * the URN is unique among those in the search paths. The smallest available * number will be used.
  2. *
  3. It will remove any attributes that flag the document as a topic, so that if you * just called newDocumentFromTemplate() with a topic template, the document the * user then creates will not be misclassified as a topic itself.
  4. *
*/ void ensureURNUniqueForAuthor ( QString author = QString() ); /** \brief Load the temporary dependencies into memory without altering the document * * Used by setTemporaryDependencies() and other functions to load temporary dependencies * into memory, as described in the documentation for that function. Note that this can * be done to a brand new, empty document, or to an existing, already-loaded document * with its own dependencies, and it shouldn't interfere. * * Note that a call to this function will never return false for failure, unless the * list of temporary dependencies has just been changed. The reason for this is that * if setTemporaryDependencies() finds an error when trying to load the temporary * dependencies just set, it empties the list of temporary dependencies. */ bool loadTemporaryDependencies (); /** \brief Sets the error message to \a message and returns false * * Just a convenience, so that functions in this class and its descendants * can write code like this. *
     *  if ( something_wrong )
     *      return error( "Description of problem" );
     *  
* Or to propagate the existing error message up, this code. *
     *  if ( !other_member_function() )
     *      return error();
     *  
* * \see noErrors() */ bool error ( QString message = QString() ); /** \brief Counterpart to error(), returns true and sets error message to empty * * \see error() */ bool noErrors (); /** \brief Appends \a message to the warning message and returns true * * Just a convenience, so that functions in this class and its descendants * can write code like this. *
     *  if ( something_slightly_wrong )
     *      return warning( "This operation succeeded, but you should know: ..." );
     *  
* If the warning message is nonempty, this first appents a newline before \a message. * * \see clearWarning() */ bool warning ( QString message = QString() ); /** \brief Counterpart to warning(), sets the warning message to empty * * Should be called before the beginning of an operation that may result in warnings. * * \see warning() */ void clearWarning (); /** Stores current document's URN; this class can ensure it doesn't change unexpectedly * using this basis for comparison. */ QString currentDocumentURN; /** Stores current document's dependency list; * this class can ensure it doesn't change unexpectedly using this basis for comparison. */ QStringList currentDocumentDependencies; /** List of states in which the watchDocumentChanges() signal handler can be operating. * * * * \see watchState */ typedef enum { PreventChanges, AllowChanges, InversionInProgress, UndoOrRedo } watchType; /** Used for ensuring that the watchDocumentChanges() signal handler does its work * correctly, watching only when it should, and propagating signals only when it should. * \see setCurrentDocumentURN(), setCurrentDocumentDependencies() */ watchType watchState; /** \brief The \a doc field in this object (a Lob) changed, and needs processing * * Specifically, that processing means calling prepareModifiedSignal() in the Lob * and storing its URN and dependencies so that we can ensure that no later modifications * change them. */ void prepareNewDocument (); /** \brief Tries to load a document or package (with all dependencies), building a list of Lobs * * The argument \a fnOrURN can be a filename of a Lurch document on disk, or the URN of a * Lurch document in the search paths, or of a LurchPackage. This routine behaves differently * with filenames and URNs. * */ bool recursiveLoad ( QString fnOrURN, QList& results, QString alias, QStringList& URNsDoneOrPending, QStringList& URNaliases ); /** \brief Construct a fresh interp, with functions for accessing parts of the environment * * This function constructs a new interpreter (disposing of the old one first if there * was one), then calls addLobsToScriptEngine(), and on top of the general setup * done there, it adds several other global functions for accessing aspects of the * environment (this object). * * Specifically, these values are made available in the script environment: *
    *
  • document() - a function that returns the environment's current document. * Just a simple exposing of LurchEnvironment::document() to scripts. * The result is a Lob, and it is editable.
  • *
  • numDependencies() - a function that returns the number of dependencies * of the environment's current document (all of which are loaded, and accessible * as Lobs; see the following function). Just a simple exposing of * LurchEnvironment::numDependencies() to scripts.
  • *
  • dependency( n ) - a function that returns dependency number n, * just a simple exposing of LurchEnvironment::dependency() to scripts. * The result is a Lob, and it is not editable.
  • *
  • alias( n ) - a function that returns the string alias for dependency * number n, assuming that it is a package that was imported with an alias. * Just a simple exposing of LurchEnvironment::alias() to scripts. * If it was not, the empty string is returned.
  • *
  • lookupNickname( name ) - a function that searches for a Lob with the * given nickname (in the sense of Lob::nickname()) and returns it, if one exists * in the current document or one of its dependencies. Otherwise, an empty Lob * is returned. * Just a simple exposing of LurchEnvironment::lookupNickname() to scripts.
  • *
  • uiCommand( cmd, ... ) - a function that emits the userInterfaceCall() * signal with all its arguments. User interfaces that allow a user to interact * with a LurchEnvironment should listen to this signal and react to those commands * which they understand. See \link uiprotocol the Protocol for Document-UI * Communication\endlink for more information.
  • *
* * This function does nothing if the current interpreter is running, because destroying it * could cause a crash. Before calling this function, verify that the current interpreter * satisfies !isEvaluating(). If it doesn't, then wait until it finishes. */ void newInterpreter (); /** \brief Runs all "auto-run" type scripts in the given document * * Recursively proceeds through every attribute and child of the given Lob and runs * the Lob::scriptCode() of any node satisfying Lob::isScript() and whose * Lob::scriptType() is "auto-run". * * If \a space is nonempty, then any scripts found are evaluated in that namespace, * using evaluateInNamespace() defined in lobscript.h. * * \return True if no script errors occurred, false otherwise. * If false, check errorMessage() for details. */ bool autoRunScripts ( const Lob doc, QString space = QString() ); /** \brief Refresh the interpreter with a new one and run all scripts in dependencies and * the document * * This function first creates an entirely new interpreter, then walks through the * dependencies in order doing the following. If the dependency is a package, it calls * its LurchPackage::setup() routine; whether or not it is a package, it then calls * autoRunScripts() on it as a document. If at any point in those steps an error occurs, * emit autoRunScriptError() and return immediately without any further processing. If * no auto-run scripts cause errors in the dependencies, it then calls autoRunScripts() * on the document itself and handles errors in the same way as with dependencies (emit * signal and return). * * This routine is guaranteed to be called by the environment in between any edits to an * auto-run script and any execution of other script code, so that the interpreter is * up-to-date, as if the document had just been loaded in its latest state. */ void scriptUpdate (); /** \brief A map from integers to all Lurch Environments * * This allows us to tell script functions which environment they belong in by a simple * integer index, rather than trying to convert memory addresses into script values. * See the use of this map in LurchEnvironment(), ~LurchEnvironment(), and * setupInterpreter(). * * This array is tested in test_lenv::test_indexing(). */ static QList environments; /** \brief A map from nicknames to the Lobs which they name * * This enables fast nickname lookups. It is created by addToNicknameMap() and kept * up-to-date by that same routine, togethr with removeFromNicknameMap(), which are * called from watchDocumentChanges(). */ QMap nicknameToLob; /** \brief Adds nicknames in the given Lob to the internal map nicknameToLob * * This is useful when a new Lob is inserted into the document. This keeps the * nicknameToLob map up-to-date with the nickname(s) of that Lob and its whole * subtree. Note that this performs its operation blindly; you should first * call safeToAddNicknames() before calling this function, to be sure you do not * overwrite/invalidate data in nicknameToLob. */ void addToNicknameMap ( Lob thisTree ); /** \brief Removes nicknames in the given Lob from the internal map nicknameToLob * * This is useful when a Lob is removed from the document. This keeps the * nicknameToLob map up-to-date by dropping the nickname(s) of that Lob and its whole * subtree. */ void removeFromNicknameMap ( Lob thisTree ); /** \brief Finds all potential conflicts with adding the nicknames in the given Lob * * If any node in the given Lob's subtree is nicknamed with a name that already appears * in nicknameToLob, then there would be a conflict with introducing the given Lob's * tree to the existing document structure. This function checks for such conflicts. * * \return An empty list if no conflicts; a list of conflicts if there are any. * Conflicts may show up on the list more than once if the given Lob has * conflicts within itself. */ QStringList safeToAddNicknames ( Lob thisTree ) const; /** \brief Looks up a nickname and returns the corresponding Lob, if any * * Simply performs a lookup in nicknameToLob, which returns an empty Lob if the given * nickname is not a key in that map. Refer to the documentation for that field * for more details. */ Lob lookupNickname ( QString nickname ) const; /** This object is used internally when scripts perform multiple document changes * at a time. They are accumulated in this object, which gets put on the undo stack. */ LobChange scriptAction; /** Whether the actions taken in the document should be invisible to the user, in the sense * that they do not take up their own slots on the undo stack, but rather merge with the * last one. See setInvisibleActions(). */ bool invisibleActions; /** \brief Tell the undo/redo recording features about a change to the document * * This saves the initial change to the internal LobChange object \a scriptAction, * using the same composing feature that LobChange::recordStep() uses. It also sets * up a timer to call changesEnded() the moment the event loop resumes, * to post the cumulative action to the undo stack. */ void noteChange ( LobChange initialChange = LobChange() ); /** This holds the undo stack for the currently loaded document. * For more information, see the following routines. * * \see redoStack, undo(), undoAvailable() */ QStack undoStack; /** This holds the redo stack for the currently loaded document. * It will only ever be populated if some undo actions have been performed. * For more information, see the following routines. * * \see undoStack, redo(), redoAvailable() */ QStack redoStack; /** Places the given action on the undo stack for later undoing. * Emits the undoAvailableChanged() signal if needed. * See evaluate() for information on the \a withPrevious parameter. */ void actionDone ( const LobChange& change, bool withPrevious = false ); /** Places the given action on the redo stack for later redoing. * Emits the redoAvailableChanged() signal if needed. */ void actionUndone ( const LobChange& change ); /** Track what packages I have created instances of, based on their URNs */ QMap urn2pkg; /** Track what packages are dependencies of the currently loaded document */ QList activePackages; /** Look up a package in the cache of previously created ones, or create a new instance */ LurchPackage* getPackage ( QString urn ); /** Number of files actually loaded as a result of loading temporary dependencies * * This may, of course, be different than the number of filenames returned by * getTemporaryDependencies(). It may also fluctuate as different documents are loaded * and saved, because their dependencies impact which indirect temporary dependencies * need to be loaded. */ int numTempDepsLoaded; /** Whether the current document's title was auto-generated by this object * * If so, then when the document is saved, the temporary title will be replaced by the * filename under which to save the document. This is what users expect; the filename is * in some sense the document title. They do not expect to have an unknown, * auto-generated title instead. * * This value is set to true when titles are auto-generated, and set to false when the * document URN is replaced by something given from outside this object. */ bool titleWasAutoGenerated; /** Whether some large operation is happening on the document, and other tasks should wait * * \see setBusy() and isBusy() */ bool busy; }; #endif // LURCH_ENVIRONMENT