#ifndef LURCHPACKAGE
#define LURCHPACKAGE
#include <QObject>
#include <QMap>
#include <QPointer>
#include "lob.h"
#include "lobscript.h"
#include "lenv.h"
#include "flexibleevaluator.h"
class QScriptProgram;
/** \brief A Lurch Package functions like a Lurch library, but is compiled for speed
*
* Although all Lurch documents and libraries could be built on script code, it will be
* useful to have the most frequently used tools built in C++ for two reasons. First,
* they will be compiled, and thus much faster than script code. Second, they will be
* subjected to the same rigorous testing to which the rest of Lurch is subjected.
* Developers convert a library that should become C++ code into a LurchPackage.
* This can be done in phases, as needed. The first phase is simplest, as documented
* in \ref makepackageShellScript "the makepackage.sh section, below".
*
* Each LurchPackage subclass registers itself with the LurchPackage class and the QMetaType
* system, providing the specific document URN that it implements. The idea is that a
* LurchPackage is just like any other dependency, but instead of a document with scripts
* in it, it is a virtual document. Thus it implements asDocument(), so that it can look
* like a document from script land, but it also adds other features to the scripting
* environment beyond simply appearing as a document.
*
* LurchEnvironments create their own instances of LurchPackage subclasses, as they
* encounter documents that depend on the class's URN. Thus a LurchPackage object can assume
* that it has been created to live inside one environment only; other LurchEnvironments will
* create other instances of the same class if they need it. The important consequence of
* this for designing subclasses of LurchPackage is that, if you're writing a package that
* stores data about the currently loaded document, you only need to declare variables
* and store data for one document; other instances of your class will exist for other
* documents, and the creation of those instances and attaching them to other documents
* will be handled by LurchEnvironment.
*
* LurchEnvironment guarantees three things in its relationship to LurchPackage subclasses.
* <ol>
* <li>The virtual document provided by asDocument() will be scanned for auto-run scripts
* just as if it were a real document. This enbles the simple conversion of scripts
* to packages using \ref makepackageShellScript "the makepackage.sh script described
* below". Such scanning takes place when the document is loaded, and again in a
* fresh scripting environment if important changes take place that necessitate
* it.</li>
* <li>Immediately before any such scan of a LurchPackage's asDocument() representation,
* there will be a call to that object's setup() routine. This enables any
* script-land setup that needs to be done from the C++ side to happen before any
* scripts in asDocument() are called.</li>
* <li>Whenever a new document is loaded, after the load complete successfully, the
* initialize() member of every package on which it depends is called. This is so
* that any internal data the package wants to track about the document and/or its
* dependencies can be initialized. If the document wants to track changes to the
* document and update any internal data on an ongoing basis, it should use the
* initialize() call to connect one of its slots to the environment's
* documentModified() signal. (See initialize() documentation, below.)</li>
* </ol>
*
* \anchor makepackageShellScript
* <h2>The <code>makepackage.sh</code> script</h2>
* Take your existing .lurch file,
* and simply paste it into a giant C++ string inside a subclass definition, like this.
* \code
* doc = Lob::fromXML(
* "<OMATTR>\n"
* " <OMATP>\n"
* " ...\n"
* " </OMATP>\n"
* " <OMA>\n"
* " <OMS name=\"document\" cd=\"LurchCore\"/>\n"
* " ...\n"
* " Make this an enormous C string containing\n"
* " all the data in your existing .lurch file.\n"
* " ...\n"
* " </OMA>\n"
* "</OMATTR>\n"
* ).child().copy();
*
* // ...
*
* Lob asDocument () const
* {
* return doc;
* };
* \endcode
* A LurchPackage created this way tells the world about itself solely through
* its Lob form, which is what a .lurch file does anyway. So this is a simple wrapping
* of a .lurch file in a LurchPackage, and thus is a very easy way to create a LurchPackage
* from an existing library that is in a .lurch file.
* In fact, there is a handy shell script
* <a href='http://lurch.svn.sourceforge.net/viewvc/lurch/Lurch/trunk/makepackage.sh'
* >makepackage.sh</a> that will do this conversion for you, reading in a .lurch file
* and outputting a .h file and a .cpp file.
*
* \anchor howToWriteALurchPackage
* <h2>How to write a LurchPackage subclass</h2>
*
* To subclass LurchPackage, you need to follow these steps. Failure to do so will result
* in your package not being registered with the master list kept in static data structures
* of the LurchPackage class, and thus not being visible to LurchEnvironment, documents,
* scripts, etc.
* <ol>
* <li>Subclass LurchPackage as usual, implemeting all pure virtual functions and
* whatever other functions are necessary for your particular package.
* (There are two virtual functions you do not need to implement, metaTypeID()
* and classURN(); these will be implemented for you by the LURCH_PACKAGE macro,
* described in the following step.)
* Be sure to provide your package with an URN unique to your new class
* in your implementation of getURN(). When implementing asDocument(),
* a good place to start is with Lob::newDocument( getURN() ); thereafter,
* comments or scripts can be added as children.</li>
* <li>In your header file:<br>
* Ensure that your class is declared with the Q_OBJECT macro, and immediately
* below it, also include the LURCH_PACKAGE macro, like this.
* \code
* class MyPackage : public LurchPackage
* {
* Q_OBJECT
* LURCH_PACKAGE
* ...
* \endcode
* This step ensures that static variables to hold metatype information are added
* to your class.
* </li>
* <li>Also in your header file:<br>
* After your class's declaration, include a line like the following, as per
* <a href='http://doc.trolltech.com/4.4/qmetatype.html#Q_DECLARE_METATYPE'>the
* Qt documentation for this macro</a>.
* \code
* Q_DECLARE_METATYPE( MyPackage )
* \endcode
* This step ensures that your subclass is registered with the QMetaType system,
* so that it can be constructed and deconstructed from just its name or id
* at runtime. (This enables LurchPackage to function as a factory for objects of
* your class.)</li>
* <li>In your source file:<br>
* Include in your class's constructor an invocation of the
* REGISTER_LURCH_PACKAGE() macro, called with two arguments,
* the name of your class, and the URN it returns from getURN().
* Here is an example.
* \code
* MyPackage::MyPackage ()
* {
* REGISTER_LURCH_PACKAGE(
* MyPackage,
* Lob::makeLurchURN( "My Package Name", "My Name", "en", "v1.0" ) );
* }
* \endcode
* This step ensures that the metatype data in your class is correctly
* set up, and that the static variables in the LurchPackage class know about your
* class, and how to ask the QMetaType system to construct instances of it.
* </li>
* </ol>
*/
class LurchPackage : public QObject
{
Q_OBJECT
public:
/** \brief Constructor registers the package in the list of all instances
*
* Because this parent class constructor handles registering the package with the
* global list stored in the static variable allInstances (and accessed with methods
* like lookupPackage()), it is essential for descendants to remember to call the parent
* class constructor.
*/
LurchPackage ();
/** \brief Copy constructor necessary for QMetaType
*/
LurchPackage ( const LurchPackage& other );
/** \brief Destructor unregisters the package from the list of all instances
*
* \see LurchPackage()
*/
virtual ~LurchPackage ();
/** \brief The representation of this package as a Lob
*
* This has three purposes; the third is the most significant.
* <ol>
* <li>Every dependency of a document, whether it be another document stored on disk
* as a .lurch file or a package built into Lurch, should be inspectable by scripts
* at runtime. Just as scripts can call <code>numDependencies()</code> and
* <code>dependency(i)</code> to fetch actual document dependencies, as Lob
* objects that pass the Lob::isDocument() test, they should also be able to do
* this for packages. Thus every package needs to represent itself as a Lob
* that is a document. It can simply be an empty document with the appropriate
* URN for the package, or just a document containing comments or documentation,
* but it must be there. The default implementation provides an empty document
* whose URN matches that given by getURN().</li>
* <li>Developers creating packages usually do not wish to code every part of the package
* in C++; sometimes it's more convenient to write some of the package in script.
* The most common way to do this is to have a separate <tt>.js</tt> file containing
* the script as a Qt resource, which you load with the source() function and pass
* to evaluateInNamespace() during the setup() routine. However,
* an alternative is to put one or more auto-run script Lobs inside the
* document returned from this function. Because a LurchEnvironment treats the
* document returned from this function just like any other dependency document,
* running any of the auto-run scripts in it, that is a perfectly valid way to
* do some or all of the setup this package requires. This is why
* \ref makepackageShellScript "the makepackage.sh script described above"
* is viable.</li>
* <li>Developers using packages will expect to be able to open the package and learn some
* basic facts about what it does, ideally with a help link to full documentation with
* examples. Thus the document returned from this function can have content that
* introduces the package and describes briefly what it does, and can have an attribute
* that points to a help document, which will show up in the Help menu in interfaces
* like Lurch. See how subclasses of LurchPackage use the static functions of
* this class to create simple representations of themselves.
* </ol>
*
* Before a LurchEnvironment processes a LurchPackage as a document Lob,
* running the auto-run scripts in it, it first calls its setup() function appropriately,
* which is what makes packages more than just document Lobs.
*
* One convenient implementation of this method is in the way that
* \ref makepackageShellScript "the makepackage.sh
* script described above" does, by simply returning a Lob created from the full XML
* code for a document. Another convenient implementation is to create a new document
* using documentSkeleton(), and then appending blocks using addText(), addScript(),
* describeLoadedScriptCode(), and describeSetupSlotsAsFunctions().
*
* If the package is only going to be loaded as a dependency, creating all that visual detail
* is just a waste of time. In fact, calls to addScript() are particularly costly (sometimes
* even taking a full second for large scripts). For this reason, the asDocument() method
* takes the \a forDisplay parameter. When it is true, the results of the call may be
* displayed to the user on screen, and thus all visual detail should be included; otherwise,
* it can be omitted. Descendants reimplementing asDocument() are encouraged to make use of
* this parameter for efficiency reasons. It defaults to false because that is the most
* common use case.
*
* \see LurchEnvironment, setup(), getURN()
*/
virtual Lob asDocument ( bool forDisplay = false ) const;
/** \brief The URN for this package
*
* This is a pure virtual function because it must be unique for each package.
* If asDocument() is implemented to create an entire document Lob with an URN,
* then one simple implementation of this function is as follows.
* \code
* return asDocument().getURN();
* \endcode
* However, note that the default implementation of asDocument() relies upon getURN()!
* So you should only use the code above if you have reimplemented asDocument(), to
* prevent infinite recursion.
*
* The routine Lob::makeLurchURN() will probably come in handy in implementing this
* routine in any way other than the one just given.
*/
virtual QString getURN () const = 0;
/** \brief Sets up a QScriptEnvironment with the routines and data this package provides
*
* This routine will be called by LurchEnvironment immediately before loading all
* auto-run scripts in the result of asDocument(). This is the difference between a
* .lurch file dependency and a package dependency; packages get to run this routine,
* and use it to add C++-implemented functions to the script environment.
*
* Note that you can add functions or data or anything you like.
* Note further that the setupSlotsAsFunctions() routine in this object is very useful
* for making it easy to implement this function with minimal trouble.
*
* The default implementation of this function calls setupSlotsAsFunctions(), and
* that's it.
*
* \param scope The QScriptValue into which any new data and/or functions should be
* placed. Think of it as a namespace. Thus you should avoid making calls
* like the following, which add data to the global object.
* \code
* scope.engine().globalObject().setProperty( "name", value );
* \endcode
* Rather, try to use code like the following (unless of course you actually
* want to add data/functions to the global namespace).
* \code
* scope.setProperty( "name", value );
* \endcode
*/
virtual void setup ( QScriptValue scope );
/** \brief Stores the pointer \a env in a member variable for later use
*
* This function does three things.
* <ol>
* <li>If the member variable \a environment has a nonzero value, then all signals from
* that object to this object are disconnected. This is convenient in case some
* descendant class of this one listens to signals in the environment; they will
* automatically be disconnected when a new environment is initialized, if that
* class's implementation of initialize() remembers to call this version.</li>
* <li>Stores the parameter \a env in a protected member variable, one thus
* accessible to all subclasses.</li>
* <li>Because the protected member variable is a QPointer, this means that if the
* object to which \a env points is ever destroyed, that pointer will automatically
* be set to NULL rather than become a dangling pointer.</li>
* </ol>
*/
void setEnvironment ( LurchEnvironment* env );
/** \brief Allows a LurchPackage to set up internal data computed from the environment
*
* A LurchPackage may have internal data stored about the state of the environment
* (document and dependencies) to help speed up the functions it provides in script.
* This function will be called by the LurchEnvironment whenever a new document is loaded
* into the environment, and the document depends on this package. You can assume that
* the call is taking place immediately after a successful load of the document, and
* you should also assume that your class's data structures may still be filled with old
* data from a previous call to initialize() on a previous document, and thus they need
* to be cleared out before being filled with new data.
*
* It is guaranteed that both setEnvironment() and setup() have been called by the
* LurchEnvironment on this object before this call to initialize(). Thus it is
* guaranteed that the member variable \a environment is non-NULL and points to the
* correct, existing LurchEnvironment object, and futhermore that any scripts run by
* the setup() function are already complete. See the documentation for setEnvironment()
* and setup() for more details on the useful consequences of these guarantees.
*
* In this function, if you want to establish an ongoing monitoring of the document's
* changes, to keep your internal data structures constantly up-to-date with its state,
* you should declare a slot such as this one
* \code
* public slots:
* void documentWasModified ( const LobChange& change );
* \endcode
* and maintain a single connection to the environment's documentModified()
* signal in your initialize routine, like this.
* \code
* void initialize ( LurchEnvironment* env )
* {
* connect( environment, SIGNAL(documentModified(const LobChange&)),
* this, SLOT(documentWasModified(const LobChange&)) );
* }
* \endcode
* This is <b><i>not</i></b> the same thing as connecting to the document's modified()
* signal, because LurchEnvironment::documentModified() filters out impermissible
* changes before they even happen.
*
* \see LurchEnvironment::documentModified()
*/
virtual void initialize ();
#if 0
// this used to make sense, but isn't needed anymore
/** \brief Fetch the LurchPackage instance that corresponds to the given URN
*
* If there exists in memory a LurchPackage instance whose URN matches the given one,
* then it is returned. Otherwise, NULL is returned.
*/
static LurchPackage* lookupPackage ( QString urn );
#endif
/** \brief Compute a list of all URNs corresponding to all LurchPackage classes registered
*
* This list is complete if and only if users faithfully call the REGISTER_LURCH_PACKAGE
* macro \ref howToWriteALurchPackage "as described above".
*/
static QStringList allURNs ();
/** \brief The QMetaType ID of an individual subclass
*
* Because this function is only for subclasses, it is pure virtual.
* Subclasses do not need to explicitly implement it; if they follow
* \ref howToWriteALurchPackage "the procedure described above" when writing their
* subclass, the REGISTER_LURCH_PACKAGE macro will implement it appropriately.
*/
virtual int metaTypeID () const = 0;
/** \brief The URN of an individual subclass
*
* Because this function is only for subclasses, it is pure virtual.
* Subclasses do not need to explicitly implement it; if they follow
* \ref howToWriteALurchPackage "the procedure described above" when writing their
* subclass, the REGISTER_LURCH_PACKAGE macro will implement it appropriately.
*/
virtual QString classURN () const = 0;
/** \brief Construct an instance of the subclass whose URN is given
*
* URNs are unique to a subclass, and are bound to that subclass with a call to the
* REGISTER_LURCH_PACKAGE macro \ref howToWriteALurchPackage "as described above".
* This function uses the QMetaType system to construct an instance of the subclass
* uniquely identified by the given URN. If the URN matches no subclass, NULL is
* returned.
*/
static LurchPackage* create ( QString urn );
/** \brief Register a LurchPackage subclass's QMetaType ID, indexed by its URN
*
* An internal static map associates subclass URNs with QMetaType IDs.
* This function adds a class's URN and ID to that map. Subclass authors should not
* need to call this function directly, but just use the REGISTER_LURCH_PACKAGE
* macro \ref howToWriteALurchPackage "as described above".
*/
static int registerSubclass ( QString urn, int id );
protected:
/** \brief Search for slots that should be added to the script environment as functions
*
* This function looks through all slots in this object, and for each slot whose name
* begins with <code>script_</code>, it adds that slot to the script environment
* \a scope, after having stripped the <code>script_</code> prefix from its name.
* For instance, rather than creating a function outside your class with a signature
* like the following,
* \code
* QScriptValue doFoo ( QScriptContext* context, QScriptEngine* engine )
* \endcode
* and then having to construct an appropriate script function object in setup(),
* as well as somehow track this particular LurchPackage object in your function's
* data field, you could instead simply write a slot with this signature,
* \code
* QScriptValue script_doFoo ( QScriptContext* context, QScriptEngine* engine )
* \endcode
* and then call setupSlotsAsFunctions() in your setup() routine. All those details
* will be handled for you, and because the slot is in this object, you have access
* to all its members when implementing the slot.
*/
void setupSlotsAsFunctions ( QScriptValue scope );
/** \brief Read the full contents of the named file as text, and return it
*
* Useful for reading in a file full of script code, so that it can be passed to
* a script engine for interpreting. If any error occurs, an empty string is returned.
*/
static QString source ( QString filename );
/** \brief Creates a simple representation of this package as a document
*
* That representation includes title, author, language, and version, as well as flagging
* the document with an attribute that indicates that it is the document representation of
* a LurchPackage.
*
* For information about how to use this function, see asDocument() and how subclasses
* reimplement that function using this one.
*/
Lob documentSkeleton ( QStringList dependencies = QStringList() ) const;
/** \brief An HTML paragraph describing what setupSlotsAsFunctions() does for this package
*
* Many packages install JavaScript functions by implementing them in C++ as package slots,
* and using a setupSlotsAsFunctions() call. So this function is a convenient way to
* document that fact in a consistent way across packages. It appends to the given document
* a list of such script slots installed this package. It does so with one call to addText(),
* after having generated the HTML to pass to that call.
* If there are no slots in this package that setupSlotsAsFunctions() would install as
* script functions, this method does nothing.
*/
void describeSetupSlotsAsFunctions ( Lob document ) const;
/** \brief An HTML paragraph and script block stating that this package uses loaded script code
*
* Many packages load script code from a Qt resource, so this function is a convenient way to
* document that fact in a consistent way across packages. It appends to the given document
* a statement that the code in the given \a filename is executed by this package. It
* includes the code in the document for the user to read, but does not mark it with any
* script type, so that it will not impact any LurchEnvironment into which this package is
* loaded. It does so with calls to addText() and addScript().
*/
static void describeLoadedScriptCode ( Lob document, QString filename );
/** \brief Adds to a package representation document a link to its help file
*
* For information about how to use this function, see asDocument() and how subclasses
* reimplement that function using this one.
*/
static void addHelpAttribute ( Lob document, QString helpURN );
/** \brief Adds to a package representation document a segment of text
*
* No HTML tags are placed in the text, so for example the caller may wish to surround
* \a text in paragraph tags, if that is the intent.
*
* For information about how to use this function, see asDocument() and how subclasses
* reimplement that function using this one.
*
* It can be convenient to use this function with describeSetupSlotsAsFunctions().
*/
static void addText ( Lob document, QString text );
/** \brief Adds to a package representation document a block of JavaScript code
*
* This is simply a call to addText(), passing along a syntax-highlighted version of the
* given code. It does not include it as a script Lob, but rather just as an HTML
* representation of the code.
*
* For more information about how to use this function, see asDocument() and how subclasses
* reimplement that function using this one.
*/
static void addScript ( Lob document, QString code );
/** \brief Caches a string of script code under a given name, for later retrieval
*
* Later you can retrieve it with program().
* This works well when used in tandem with source().
*
* This is global data storage, and thus names must be unique within a subclass of
* LurchPackage. That is, each object does not get its own cache, but rather each
* subclass does. So only store data that is determined by the class, and not a function
* of the individual object of that class. This improves efficiency.
*/
void cacheProgram ( QString name, QString code );
/** \brief Retrieves a cached string of script code, given the name under which it was stored
*
* See cacheProgram().
*/
const QScriptProgram& program ( QString name ) const;
/** The environment into which this package was last initialized
*/
QPointer<LurchEnvironment> environment;
signals:
/** \brief Emitted when this package wishes to communicate a message to the UI, if any
*
* The environment in which this package was loaded will pass this signal on. If there is
* a user interface (e.g., not in simple_script) then it may listen to and react to this
* signal.
*/
void packageMessageToInterface ( QString message );
private:
#ifdef LURCH_UNIT_TEST
friend class LURCH_UNIT_TEST;
#endif
/** Where the LurchPackage() constructor stores the <code>this</code> pointer, so that all
* created instances of the class can be tracked using methods like lookupPackage().
*/
static QList<LurchPackage*> allInstances;
/** Find at which index into allInstances this object lies (inverse of lookupPackage())
*/
int index ();
/** Look up a package object given its index (inverse of index())
*/
static LurchPackage* lookupPackage ( int index );
/** A function outside of the LurchPackage class which is used by
* LurchPackage::setupSlotsAsFunctions() to dispatch function calls to slots within a
* particular LurchPackage object.
*
* Declaring it <code>friend</code> allows it to call lookupPackage() in this class.
*/
friend QScriptValue dispatcher ( QScriptContext* context, QScriptEngine* engine );
/** Provides access to a static member that is guaranteed to be initialized before being
* needed. If this were simply declared as a static member variable, it might not be
* initialized before calls to registerSubclass() during static initialization time
* (at program startup). This causes bus errors. So instead we create this function
* that returns the object in question, and which guarantees its existence.
*/
static QMap<QString,int>& urn2id ();
/** Provides a way to store code loaded from (real or virtual) files.
*
* \see cacheProgram(), program()
*/
static QCache<QString,QScriptProgram> codeCache;
};
#define LURCH_PACKAGE \
private: \
static int typeId; \
static QString urn; \
public: \
int metaTypeID () const; \
QString classURN () const;
#define REGISTER_LURCH_PACKAGE(_class,_urn) \
QString _class::urn = _urn; \
int _class::typeId = LurchPackage::registerSubclass(_class::urn,qRegisterMetaType<_class>()); \
QString _class::classURN () const { return urn; } \
int _class::metaTypeID () const { return typeId; }
#endif // LURCHPACKAGE