#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
* <em>not</em> 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 <i>not</i> return failure, and the document and dependencies <i>will</i>
* 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, <code>setDirty( false )</code> 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 <b>not</b> 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<MyPackageB*>(
* 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 <code>identifier=urn</code>
* 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
* <code>e.index( e.address( L ) ) == L</code>.
*
* 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
* <code>document().setURN()</code>. 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
* <code>document().setDependencies()</code>. 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.
*
* <ol>
* <li>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().</li>
* <li>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.</li>
* <li>If old data was removed, remove it's nickname information from the nickname
* lookup table.</li>
* <li>Set the document dirty flag to true.</li>
* <li>If the change involved an auto-run script, set the script environment dirty
* flag to true.</li>
* <li>Clear the error message, to indicate success.</li>
* </ol>
*
* 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 <code>UI()</code>, 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 <code>UI()</code> 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:
* <ul>
* <li><b>provides</b>, as in <code>UI( "provides", "foo" )</code>:
* returns a boolean, true if and only if the user interface provides a command
* called foo, so that later calls to <code>UI( "foo", ... )</code> will be
* obeyed</li>
* <li><b>alert</b>, as in <code>UI( "alert", "message" )</code>:
* display a message to the user, like the typical Javascript alert function</li>
* </ul>
*/
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<Lob> deps;
/** \brief List of aliases for each dependency
*
* If a dependency was specified by an URN with the optional <code>identifier=urn</code>
* 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 <code>URNToFilename( document().getURN() )</code>, but it is updated internally
* every time <code>dirty</code> 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.
* <ol>
* <li>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.</li>
* <li>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.</li>
* </ol>
*/
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.
* <pre>
* if ( something_wrong )
* return error( "Description of problem" );
* </pre>
* Or to propagate the existing error message up, this code.
* <pre>
* if ( !other_member_function() )
* return error();
* </pre>
*
* \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.
* <pre>
* if ( something_slightly_wrong )
* return warning( "This operation succeeded, but you should know: ..." );
* </pre>
* 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.
*
* <ul>
* <li><code>PreventChanges</code> is the default; it means to prevent changes to the
* document's URN or depdencies. That is, the moment they occur, they are
* immediately undone without the documentModified() signal ever being emitted.</li>
* <li><code>InversionInProgress</code> is a temporary state used internally for the instant
* in which the changes prevented by <code>PreventChanges</code> are undone, so that
* we do not go into infinite regress, undoing the undoing, and so on.</li>
* <li><code>AllowChanges</code> is a temporary state used internally by the routines
* setCurrentDocumentURN() and setCurrentDocumentDependencies(), to prevent their
* effects from being undone, as they would be in a <code>PreventChanges</code>
* state.</li>
* <li><code>UndoOrRedo</code> is a temporary state used internally by the routines
* undo(), redo(), and setDocumentMetadata() to prevent their effects from being
* recorded on the undo stack.</li>
* </ul>
*
* \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.
* <ul>
* <li>
* If \a fnOrURN is a valid Lurch URN (see Lob::isLurchURN()), this routine behaves
* as follows.
*
* If \a URN has already been loaded, this routine does nothing.
*
* If \a URN refers to a package, this loads it analogously to how it loads files (see
* below), and recurs on the URNs of the package's dependencies, as expressed
* in the Lob::dependencies() array of the LurchPackage::asDocument() Lob.
*
* If \a URN refers to a document, this converts it to a filename and recurs on it,
* thereby taking the path of the second bullet point, below.
* </li>
* <li>
* However, if \a fnOrURN is not a valid Lurch URN, this routine assumes that it is
* a filename, and behaves as follows.
*
* Recursively loads all dependencies, placing them in \a results parameter
* in the order they should subsequently be processed (i.e., dependencies come before
* the document that depends on them). Does not set the editable property of any Lob.
*
* Handles errors and return value much like load(). In the event where
* false is returned (failure), some documents may still have been loaded, and the
* results array may be nonempty. Yet neither the current document nor the current
* list of dependencies in this object will have been altered.
*
* Be aware that if two different dependencies each depend on the same third depdency,
* and each gives it a different alias, then the first dependecy loaded is the one
* whose alias will be respected, and the latter dependency may then not find its
* dependency in the namespace it expects.
* </li>
* </ul>
*/
bool recursiveLoad ( QString fnOrURN, QList<Lob>& 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:
* <ul>
* <li><b>document()</b> - 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.</li>
* <li><b>numDependencies()</b> - 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.</li>
* <li><b>dependency( n )</b> - a function that returns dependency number <i>n</i>,
* just a simple exposing of LurchEnvironment::dependency() to scripts.
* The result is a Lob, and it is <i>not</i> editable.</li>
* <li><b>alias( n )</b> - a function that returns the string alias for dependency
* number <i>n</i>, 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.</li>
* <li><b>lookupNickname( name )</b> - 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.</li>
* <li><b>uiCommand( cmd, ... )</b> - 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.</li>
* </ul>
*
* 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<LurchEnvironment*> 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<QString,Lob> 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<LobChange> 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<LobChange> 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<QString,LurchPackage*> urn2pkg;
/** Track what packages are dependencies of the currently loaded document
*/
QList<LurchPackage*> 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