#include "lpackage.h"
#include "lobscript.h"
#include "htmlutilities.h"
#include "qtextdocumentlobs.h"
#ifdef LOB_WORD_PROCESSING_TOOLS_AVAILABLE
#include <QTextDocument>
#include "lurchdocumentconverter.h"
#endif
LurchPackage::LurchPackage ()
: QObject(), environment( NULL )
{
// NOTE: This same code is repeated in the copy ctor; if you change one, change both
int i = allInstances.indexOf( NULL );
if ( i == -1 )
allInstances << this;
else
allInstances[i] = this;
codeCache.setMaxCost( 30 );
}
LurchPackage::LurchPackage ( const LurchPackage& /*other*/ )
: QObject(), environment( NULL )
{
// NOTE: This same code is repeated in the non-copy ctor; if you change one, change both
int i = allInstances.indexOf( NULL );
if ( i == -1 )
allInstances << this;
else
allInstances[i] = this;
codeCache.setMaxCost( 30 );
}
LurchPackage::~LurchPackage ()
{
int i = index();
if ( i > -1 )
allInstances[i] = NULL;
}
Lob LurchPackage::asDocument ( bool /*forDisplay*/ ) const
{
return Lob::newDocument( getURN() );
}
void LurchPackage::setup ( QScriptValue scope )
{
setupSlotsAsFunctions( scope );
}
void LurchPackage::setEnvironment ( LurchEnvironment* env )
{
// first, disconnect any signals from the old environment to this package
// (irrelevant for this class, but quite handy for all its descendant classes)
if ( environment )
disconnect( environment, 0, this, 0 );
// now store the new package pointer
environment = env;
}
void LurchPackage::initialize ()
{
// pass
}
void LurchPackage::cacheProgram ( QString name, QString code )
{
QStringList bits = Lob::splitLurchURN( getURN() );
if ( bits.empty() )
bits << "unknown package";
codeCache.insert( getURN() + name, new QScriptProgram( code, name + " of " + bits[0] ) );
}
const QScriptProgram& LurchPackage::program ( QString name ) const
{
return *codeCache[getURN() + name];
}
QScriptValue dispatcher ( QScriptContext* context, QScriptEngine* engine )
{
QScriptValue data = context->callee().data();
// ensure that the data contains an index that refers to a package
QScriptValue indexSV = data.property( "packageIndex" );
if ( !indexSV.isValid() )
return context->throwError( QScriptContext::UnknownError,
QString( "dispatcher: No packageIndex datum for %1" )
.arg( context->callee().toString() ) );
int index = ( int )( indexSV.toInteger() );
LurchPackage* package = LurchPackage::lookupPackage( index );
if ( package == NULL )
return context->throwError( QString( "dispatcher: No package found with index %1" )
.arg( index ) );
// ensure that the data contains a name
QScriptValue nameSV = data.property( "slotName" );
if ( !nameSV.isValid() )
return context->throwError( QScriptContext::UnknownError,
QString( "dispatcher: No slotName datum for %1" )
.arg( context->callee().toString() ) );
QString str = nameSV.toString();
char* name = new char[str.length()+1];
strcpy( name, str.toLatin1().constData() );
// try to call the named slot in the package
QScriptValue result;
bool success = QMetaObject::invokeMethod( package, name, Qt::DirectConnection,
Q_RETURN_ARG( QScriptValue, result ),
Q_ARG( QScriptContext*, context ),
Q_ARG( QScriptEngine*, engine ) );
delete name;
if ( !success ) {
QStringList bits = Lob::splitLurchURN( package->getURN() );
return context->throwError( QScriptContext::TypeError,
QString( "dispatcher: Could not invoke %1 in package "
"%2 by %3" )
.arg( context->callee().toString() )
.arg( bits[0] ).arg( bits[1] ) );
}
// success! return result
return result;
}
void LurchPackage::setupSlotsAsFunctions ( QScriptValue scope )
{
const QMetaObject* mo = metaObject();
QScriptEngine* engine = scope.engine();
for ( int i = mo->methodOffset() ; i <= mo->methodCount() ; i++ ) {
QMetaMethod m = mo->method( i );
QString name = m.methodSignature();
name = name.left( name.indexOf( "(" ) );
if ( ( m.methodType() == QMetaMethod::Slot ) && name.startsWith( "script_" ) ) {
QScriptValue func = engine->newFunction( dispatcher );
QScriptValue data = engine->newObject();
data.setProperty( "slotName", engine->toScriptValue( name ) );
data.setProperty( "packageIndex", engine->toScriptValue( index() ) );
func.setData( data );
QString helpdata = getHelpDataFor( mo->className(), name );
if ( !helpdata.isEmpty() )
func.setProperty( "helpHTML", helpdata );
scope.setProperty( name.mid( 7 ), func );
}
}
}
QString LurchPackage::source ( QString filename )
{
QFile f( filename );
if ( !f.open( QIODevice::ReadOnly ) )
return QString();
QTextStream in( &f );
return in.readAll();
}
QList<LurchPackage*> LurchPackage::allInstances;
QCache<QString,QScriptProgram> LurchPackage::codeCache;
int LurchPackage::index ()
{
return allInstances.indexOf( this );
}
LurchPackage* LurchPackage::lookupPackage ( int index )
{
if ( ( index < 0 ) || ( index >= allInstances.count() ) )
return NULL;
return allInstances[index];
}
#if 0
// this used to make sense, but isn't needed anymore
LurchPackage* LurchPackage::lookupPackage ( QString urn )
{
// first let's ensure there's no null pointers in there, for paranoia sake
while ( allInstances.removeOne( NULL ) ) { /* keep doing it */ }
// ok now search
foreach ( LurchPackage* package, allInstances )
if ( package->getURN() == urn )
return package;
return NULL;
}
#endif
QStringList LurchPackage::allURNs ()
{
return urn2id().keys();
}
LurchPackage* LurchPackage::create ( QString urn )
{
return urn2id().contains( urn ) ?
( LurchPackage* )( QMetaType::create( urn2id()[urn] ) ) :
NULL;
}
int LurchPackage::registerSubclass ( QString urn, int id )
{
return urn2id().contains( urn ) ? urn2id()[urn] : ( urn2id()[urn] = id );
}
QMap<QString,int>& LurchPackage::urn2id ()
{
static QMap<QString,int> result;
return result;
}
#define TEXT_HEAD "<OMS name=\"text\" cd=\"LurchPackage\"/>"
Lob LurchPackage::documentSkeleton ( QStringList dependencies ) const
{
Lob result = Lob::newDocument( getURN(), dependencies );
result.setAttribute( "isPackage", "LurchCore", Lob::fromXML( "<OMSTR>true</OMSTR>" ).child() );
QStringList bits = Lob::splitLurchURN( getURN() );
QString opening = QString( "<h1>%1</h1>"
"<p><font color=\"#ff0000\"><i>Packages are virtual documents "
"built into Lurch, and are therefore READ-ONLY.</i></font></p>"
"<table border=0>"
"<tr><td>Author:</td><td>%2</td></tr>"
"<tr><td>Language:</td><td>%3</td></tr>"
"<tr><td>Version:</td><td>%4</td></tr>"
"</table>" )
.arg( bits[0] ).arg( bits[1] ).arg( bits[2] ).arg( bits[3] );
addText( result, opening );
#ifndef LOB_WORD_PROCESSING_TOOLS_AVAILABLE
Lob firstParagraph = result.child( result.numChildren() - 1 );
QString code =
"var TEXT_HEAD = Lob( '" TEXT_HEAD "' );\n"
"handle = function ( feature, L ) {\n"
" if ( feature != 'representation' ) return false;\n"
" // see LurchPackage::addText():\n"
" if ( ( L.type == OmTypes.Application )\n"
" && L.child().equivalentTo( TEXT_HEAD )\n"
" && ( L.child( 1 ).type == OmTypes.String ) )\n"
" return '<html>' + L.child( 1 ).basicValue + '</html>';\n"
"};";
code = QString( "if ( document().URN == '%1' ) { " + code + " }" ).arg( getURN() );
firstParagraph.child( 1 ).addChild( Lob::fromXML(
"<OMATTR>"
" <OMATP>"
" <OMS name=\"script language\" cd=\"LurchCore\"/>"
" <OMSTR>Javascript</OMSTR>"
" <OMS name=\"script type\" cd=\"LurchCore\"/>"
" <OMSTR>auto-run</OMSTR>"
" </OMATP>"
" <OMSTR>" + escapeXML( code ) + "</OMSTR>"
"</OMATTR>" ).child() );
#endif
return result;
}
void LurchPackage::addHelpAttribute ( Lob document, QString helpURN )
{
document.setAttribute( "helpURN", "LurchCore",
Lob::fromXML( "<OMSTR>" + helpURN + "</OMSTR>" ).child() );
}
void LurchPackage::addText ( Lob document, QString text )
{
#ifdef LOB_WORD_PROCESSING_TOOLS_AVAILABLE
QTextDocument tmp;
QTextCursor c( &tmp );
c.insertHtml( text );
Lob frame = LurchDocumentConverter::structureToLob( tmp.rootFrame() );
while ( frame.numChildren() > 1 )
document.addChild( frame.child( 1 ) ); // just move it--faster than copying
#else
Lob paragraph = Lob::fromXML( "<OMA></OMA>" ).child();
paragraph.addChild( BlockSym() );
paragraph.addChild( Lob::fromXML( "<OMA>" TEXT_HEAD
"<OMSTR>" + escapeXML( text ) + "</OMSTR></OMA>" )
.child() );
document.addChild( paragraph );
#endif
}
void LurchPackage::addScript ( Lob document, QString code )
{
addText( document, "<pre>" + syntaxHighlightJavascript( code ) + "</pre>" );
}
void LurchPackage::describeSetupSlotsAsFunctions ( Lob document ) const
{
QString result = "<p>This package defines the following JavaScript functions implemented"
"in the C++ code for the package.</p>"
"<table border=1>"
"<tr><td>JavaScript function</td><td>C++ implementation</td></tr>";
const QMetaObject* mo = metaObject();
int count = 0;
for ( int i = mo->methodOffset() ; i <= mo->methodCount() ; i++ ) {
QMetaMethod m = mo->method( i );
QString name = m.methodSignature();
name = name.left( name.indexOf( "(" ) );
if ( ( m.methodType() == QMetaMethod::Slot ) && name.startsWith( "script_" ) ) {
// QString helpdata = getHelpDataFor( mo->className(), name );
// if ( !helpdata.isEmpty() ) ...
result += "<tr><td><pre>" + name.mid( 7 ) + "()</pre></td>"
"<td><pre>" + name + "(QScriptContext*,QScriptEngine*)</pre></td></tr>";
count++;
}
}
if ( count > 0 )
addText( document, result + "</table>" );
}
void LurchPackage::describeLoadedScriptCode ( Lob document, QString filename )
{
addText( document, "<p>This package runs the following script code.</p>" );
addScript( document, source( filename ) );
}