% Instructions for generating source code and documentation % Step 1. Convert metapost diagrams into PDF documents % $ mptopdf pipes.mp ; epstopdf pipes.ps % $ mptopdf roast.mp ; epstopdf roast.ps % $ mptopdf search.mp ; epstopdf search.ps % Step 2. Weave and typeset % $ cweave typica % $ pdftex typica % Step 3. Tangle and moc % $ ctangle typica ; mv typica.c typica.cpp % $ moc typica.cpp > moc_typica.cpp % % If you have trouble compiling, check to make sure the required headers are in % your header search path and check to make sure the required libraries are % linked. If using qmake to generate a project file, remember to add the % following lines to your .pro file: % QT += xml % QT += script % Document style instructions \input graphicx.tex \mark{\noexpand\nullsec0{A Note on Notation}} \def\pn{Typica} \def\filebase{typica} \def\version{1.9.1 \number\year-\number\month-\number\day} \def\years{2007--2018} \def\title{\pn{} (Version \version)} \newskip\dangerskipb \newskip\dangerskip \dangerskip=20pt \dangerskipb=42pt \def\hang{\hangindent\dangerskip} \def\hangb{\hangindent\dangerskipb} \font\manual=manfnt at 12pt \def\danbend{{\manual\char127}} \def\datanger{\medbreak\begingroup\clubpenalty=10000 \def\par{\endgraf\endgroup\medbreak} \noindent\hang\hangafter=-2 \hbox to0pt{\hskip-3.5pc\danbend\hfill}} \outer\def\danger{\datanger}% % \def\datangerb{\medbreak\begingroup\clubpenalty=10000 \def\par{\endgraf\endgroup\medbreak} \noindent\hang\hangafter=-2 \hbox to0pt{\hskip-3.5pc\danbend\hfill}} \outer\def\dangerb{\datangerb} \def\endanger{\medskip} \def\nullsec{\S1} \def\lheader{\mainfont\the\pageno\kern1pc(\topsecno)\eightrm \qquad\grouptitle\hfill\title} \def\rheader{\eightrm\title\hfill\grouptitle\qquad\mainfont (\topsecno)\kern1pc\the\pageno} \def\botofcontents{\vfill \noindent Copyright \copyright\ \years~Neal Evan Wilson \bigskip\noindent Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ``Software''), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\medskip The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\medskip THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \bigskip\noindent Parts of \pn{} are from QextSerialPort which is used under the MIT license as follows: \bigskip\noindent Copyright \copyright\ 2000--2003 Wayne Roth \noindent Copyright \copyright\ 2004--2007 Stefan Sander \noindent Copyright \copyright\ 2007 Michal Policht \noindent Copyright \copyright\ 2008 Brandon Fosdick \noindent Copyright \copyright\ 2009--2010 Liam Staskawicz \noindent Copyright \copyright\ 2011 Debao Zhang \bigskip\noindent Web: http://code.google.com/p/qextserialport/ \bigskip\noindent Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ``Software''), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. } \let\K=\leftarrow \def\CPLUSPLUS/{{% \mc C{\hbox{\kern.5pt\raise1pt\hbox{\sevenrm+\kern-1pt+}\kern.5pt}} \spacefactor1000}} \def\PP{\uparrow} \def\MM{\downarrow} \newbox\DCBox \setbox\DCBox=\hbox{$\in$} \def\DC{\copy\DCBox} \newbox\MODbox \setbox\MODbox=\hbox{\eightrm MOD} \def\MOD{\mathbin{\copy\MODbox}} % Title page \font\authorfont=cmr12 \null\vfill \centerline{\titlefont \pn} \vskip 18pt\centerline{(Version \version)} \vskip 24pt\centerline{\authorfont Neal Evan Wilson} \vfill \titletrue\eject\hbox to 0pt{} \pageno=0 \titletrue\eject \secpagedepth=1 % Convenience macros \def\newline{\vskip\baselineskip} \def\cweb{\.{CWEB}} \def\web{\.{WEB}} \newcount\footnotenumber \def\nfnote{\global\advance\footnotenumber by 1 \footnote{$^{\the\footnotenumber}$}} % Listing macro from The TeXBook. See page 381 for an explaination. \def\uncatcodespecials{\def\do##1{\catcode`##1=12 }\dospecials} \newcount\lineno \def\setupverbatim{\tt \lineno=0 \def\par{\leavevmode\endgraf} \catcode`\`=\active \obeylines \uncatcodespecials \obeyspaces \everypar{\advance\lineno by1 \llap{\sevenrm\the\lineno\ \ }}} {\obeyspaces\global\let =\ } \def\listing#1{\par\begingroup\setupverbatim\input#1 \endgroup} % Javascript chunk handling \def\jsfile#1#2{\Y\B\4\X\secno:\.{#1}\X${}\E{}\6$\par \listing{#2}} % Type formatting @s QTime int @s QMetaType int @s DAQ int @s Channel int @s QString int @s QObject int @s QThread int @s DAQImplementation int @s QVector int @s TaskHandle int @s qint32 int @s int32 int @s QMessageBox int @s QLCDNumber int @s QWidget int @s AnnotationButton int @s AnnotationSpinBox int @s QPushButton int @s QTimer int @s QAction int @s QApplication int @s PackLayout int @s QLayout int @s QLayoutItem int @s QRect int @s QList int @s QSize int @s QGraphicsScene int @s SceneButton int @s QGraphicsSceneMouseEvent int @s QPoint int @s true const @s false const @s QGraphicsView int @s QGraphicsTextItem int @s QFrame int @s QPaintDevice int @s QColor int @s QBrush int @s QHash int @s QPointF int @s QGraphicsLineItem int @s MeasurementModel int @s QTableView int @s QVariant int @s QAbstractItemView int @s QAbstractItemModel int @s QStringList int @s QModelIndex int @s MeasurementList int @s QVariantList int @s QSplitter int @s QHBoxLayout int @s QMainWindow int @s QCoreApplication int @s QSettings int @s QMenu int @s QCloseEvent int @s LogEditWindow int @s QFile int @s QFileInfo int @s QDir int @s QXmlStreamWriter int @s QXmlStreamReader int @s QIODevice int @s QLabel int @s QTimeEdit int @s QSpinBox int @s QDoubleSpinBox int @s ThermocoupleType int @s TemperatureUnits int @s Qt int @s emit throw @s TemperatureDisplay int @s ZeroEmitter int @s MeasurementAdapter int @s GraphView int @s ZoomLog int @s TimerDisplay int @s QBoxLayout int @s WidgetDecorator int @s XMLInput int @s XMLOutput int @s CSVOutput int @s QTextStream int @s QTranslator int @s QLocale int @s Application int @s QScriptContext int @s QScriptEngine int @s QScriptEngineDebugger int @s QScriptValue int @s FakeDAQ int @s QMenuBar int @s QKeySequence int @s QFileDialog int @s Measurement int @s Date int @s QLibrary int @s daqfp int @s QResizeEvent int @s QVBoxLayout int @s QByteArray int @s QSqlDatabase int @s QComboBox int @s QXmlStreamAttribute int @s QSqlQuery int @s QLineEdit int @s QDoubleValidator int @s QIntValidator int @s QTextEdit int @s QStandardItemModel int @s QValidator int @s QMap int @s QDomElement int @s QDomNodeList int @s QDomNode int @s QStack int @s QDomDocument int @s QDomNamedNodeMap int @s QFormLayout int @s QAbstractButton int @s QAbstractScrollArea int @s SqlComboBox int @s QUuid int @s SqlComboBoxDelegate int @s QItemDelegate int @s SqlConnectionSetup int @s QDialog int @s QCheckBox int @s SaltModel int @s QStyleOptionViewItem int @s QBuffer int @s QDateEdit int @s QCalendarWidget int @s QDate int @s QFocusEvent int @s QGridLayout int @s QScrollArea int @s QSqlQueryModel int @s QSqlRecord int @s QSqlResult int @s SqlQueryConnection int @s QFont int @s SqlQueryView int @s QTextDocument int @s QTextCursor int @s QTextFrame int @s ReportTable int @s QTextTable int @s QTextTableFormat int @s QTextFrameFormat int @s QTextTableCell int @s QPrinter int @s QPrintDialog int @s QSqlError int @s FormArray int @s QRegExp int @s QRegExpValidator int @s QDomDocumentFragment int @s QStackedLayout int @s QMouseEvent int @s QGraphicsPolygonItem int @s QPolygonF int @s QGraphicsPathItem int @s QPainterPath int @s QXmlQuery int @s QGraphicsItem int @s QWebView int @s QUrl int @s QShowEvent int @s QDateTimeEdit int @s ThresholdDetector int @s EdgeDirection int @s DeviceTreeModelNode int @s QMetaObject int @s QTreeView int @s QToolButton int @s QextPortInfo int @s QextSerialEnumerator int @s QMetaEnum int @s quint16 int @s QextSerialPort int @s QGroupBox int @s QVariantMap int @s QIcon int @s QFileInfoList int @s QMetaMethod int @f error normal @f line normal @f signals public @f slots int @f qRegisterMetaType make_pair @f READ TeX @f WRITE TeX @f tr TeX @f this TeX @f foreach while @f qobject_cast make_pair @f t1 TeX @f t2 TeX @f AppInstance TeX @f getself make_pair @f TYPE TeX @f argument make_pair @f toScriptValue make_pair @f arg1 TeX @f arg2 TeX @f arg3 TeX @f arg4 TeX @f findChild make_pair @f qscriptvalue_cast make_pair \def\READ{\kern4pt{\tt READ}\kern4pt} \def\WRITE{\kern4pt{\tt WRITE}\kern4pt} \def\tr{\delta} \def\this{\forall} \def\t#1{t_{#1}} \def\AppInstance{\.{AppInstance}} \def\TYPE{\cal T\kern1pt} \def\arg#1{arg_{#1}} % Document @** A Note on Notation. \noindent As noted by Falkoff and Iverson\nfnote{A.~D.~Falkoff and K.~E.~Iverson, ``The Design of APL'' (1973)}~there is little need to limit the typography used to represent a computer program in print. The printed code of \pn{} uses a number of notations that I have found useful in making clear the intent of the code. For example, a common mistake in \CPLUSPLUS/ \kern-0.5em code is the confusion of assignment ({\tt =}) with a test for equality ({\tt ==}). The \web{} convention of using |=| for assignment and |==| to test for equality makes such errors obvious at a glance. A list of special symbols and the equivalent \CPLUSPLUS/text is provided in Table \secno. Most of these symbols should be familiar\nfnote{The {\tt NULL} symbol is a break with the conventions of most Qt applications. According the the \CPLUSPLUS/standard, |0| is both an integer constant and a null pointer constant. Most programs using Qt use |0| in place of any name for the null pointer, however conceptually these are two very different things. The notation chosen here was used by Knuth for similar purposes and seems to have worked well there.}. \medskip \settabs 9 \columns \+&&&{\tt =}&|=|&Assignment\cr \+&&&{\tt --}&|--|&Decrement\cr \+&&&{\tt ==}&|==|&Equality Test\cr \+&&&{\tt >=}&|>=|&Greater or Equal Test\cr \+&&&{\tt ++}&|++|&Increment\cr \+&&&{\tt !=}&|!=|&Inequality Test\cr \+&&&{\tt <=}&|<=|&Less or Equal Test\cr \+&&&{\tt \char'046\char'046}&$\land$&Logical AND\cr \+&&&{\tt \char'174\char'174}&$\lor$&Logical OR\cr \+&&&{\tt ::}&|::|&Member of\cr \+&&&{\tt !}&|!|&Negation\cr \+&&&{\tt NULL}&|NULL|&Null Pointer\cr \+&&&{\tt this}&|this|&Object\cr \+&&&{\tt \%}&|%|&Remainder\cr \+&&&{\tt tr()}&|tr()|&Translate\cr \smallskip \centerline{Table \secno: Special Characters In \pn} \medskip Reserved words are set in bold face. As some of these reserved words are also the names of types, type names that are not specified in \CPLUSPLUS/are also set in bold face. Type placeholders in template definitions, however, are set in caligraphic capitals to emphasize that it will be replaced with a real type at compile time. Variables and class members are set in italics, character strings are set in a typewriter style with visible spaces. Macro names are also set in typewriter style. Numeric constants and plain text comments are set in an upright roman style. Comments containing \CEE/ or mathematics are styled as such. Code that will be interpreted by the ECMA-262 host environment has no pretty printing. \danger With apologies to prof.~Knuth\nfnote{This symbol was introduced in {\underbar{Computers~\char'046~Typesetting}}@q'@> (Knuth, 1984) to point out material that is for ``wizards only.'' It seems to be as appropriate a symbol as any to point out the darker corners of this program.}, code that is known to be potentially buggy is flagged with a dangerous bend sign. Some of this code is buggy due to issues with the code \pn{} depends on, others are things that should be fixed in \pn{}. Of course, there may also be bugs that have not yet been noticed or have not been attached to a particular block of code.\endanger A basic familiarity with literate programming techniques (particularly the conventions of \cweb{}), Qt, and \CPLUSPLUS/is recommended before altering the program, but an effort has been made to keep the program understandable for those who are only interested in studying it. @** Introduction. \noindent A common tool in the craft of coffee roasting is the data logger. Perhaps the most commonly used of these fall into the category of manual data loggers which require the roaster to use paper and a writing utensil, periodically recording measurements and noting control changes and observations of interest as needed. While there are many benefits to recording roast data\nfnote{Torrey Lee, Stephan Diedrich, Carl Staub, and Jack Newall, ``How to Obtain Excellence with Drum Roasters'' (2002) {\it Specialty Coffee Association of America 14$^{th}$ Annual Conference and Exhibition}}, there are a number of limitations to the manual approach; maintaining the records in a useful order is time consuming and error prone, it is difficult to work with aggregates of such records, and the attention required to log the data by hand can distract from the roasting. Using a computer with automatic data logging software designed with coffee roasting in mind can reduce or eliminate these deficiencies. \pn{} is one such program. The file {\tt \filebase.w} contains both \CPLUSPLUS/source code and the documentation for that code. This file is intended to be processed by \cweb\nfnote{Donald E. Knuth and Silvio Levy, ``The \cweb{} System of Structured Documentation'' (1994)}~to produce source code for your compiler and plain \TeX{}\nfnote{\TeX{} (pronounced $\tau\epsilon\chi$) is a trademark of the American Mathematical Society.} documentation that can be used to generate a PDF document for gorgeous printable documentation. These generated files may have been distributed with your copy of {\tt \filebase.w} for convenience. Changes to the program can be made in three ways. \cweb{} provides a patching mechanism which can be used to experiment with the code without risk of clobbering it. Instructions for the construction of such a change file can be found in the \cweb{} documentation. Adding the name of the change file to the invocation of {\tt ctangle} and {\tt cweave} will incorporate that change seamlessly in both source and documentation files. A section is provided at the end of this program for use with this mechanism in the case that new sections must be added. Another way to create persistent modifications is to alter {\tt \filebase.w} however this may make it more difficult to use changes with future versions of the software. Changes should not be made to {\tt \filebase.cpp} if these changes are expected to persist. Finally, it is possible to make many changes to how the program looks and behaves by creating a new configuration document for the program to load. Modifications made in this way do not even require recompiling the software. Examples that can serve as a starting point for such customizations are provided with \pn{}. \pn{} is a work in progress. There are several portions of the documentation that contain suggestions for future improvement. These notes provide clues for my future development plans. Naturally, if you have needs which are not quite addressed by this program, you should feel free to modify the code to suit your needs. Hopefully this will be easy to do. In the spirit of Benjamin Franklin\nfnote{``\dots as we enjoy great advantages from the inventions of others, we should be glad of an opportunity to serve others by any invention of ours; and this we should do freely and generously.'' --- Benjamin Franklin, \underbar{The Private Life of the Late Benjamin Franklin, LL.D.~Originally Written By Himself, And Now}\par\noindent \underbar{Translated From The French} (1793)}, \pn{} is shared with minimal restriction (see the license after the table of contents for legal requirements). Libraries used by \pn{} may have other restrictions. Before undertaking to distribute a binary created from this code, you may want to either determine your rights with regard to these libraries or modify the program to remove them. As CWEB generates files with the wrong extension, we leave the default generated file empty. @c /* Nothing to see here. */ @ The following is an overview of the structure of \pn: @(typica.cpp@>= #define PROGRAM_NAME "Typica" @
@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @ #include "moc_typica.cpp" @ \pn{} is made of a number of distinct classes. @= @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @@/ @ A few headers are required for various parts of \pn{}. These allow the use of various Qt modules. @
= #include #include #include #include #include #include #include #include #include #include #include @ New code is being written in separate files in a long term effort to improve organization of the code. The result of this is that some additional headers are required here. @
= #include "helpmenu.h" @** The Scripting Engine. \noindent The main enhancement of \pn{} version 1.1 is the introduction of a scriptable environment. This change allows people to easily customize \pn{} without having to alter the program code. Instead, the user interface and program data flow is set up with a small script that runs in an ECMA-262 host environment\nfnote{Standard ECMA-262, 3$^{\rm{rd}}$ Edition\par\hbox{\indent% \pdfURL{% http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf}% {http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf}}} which requires significantly less expertise to modify than \pn{} itself. Such a scripting environment will be familiar to anybody with experience using JavaScript on web pages or ActionScript in Flash. \pn{}'@q'@>s configuration system was later updated to support running several script fragments found in an XML configuration document. Most of the application classes are available from the scripting environment. The functions that make this possible are presented along with the classes. A selection of classes provided by Qt are also available. These are presented here. This chunk provides two |QScriptValue| objects which are used in other sections appended to this chunk. @= QScriptEngine *engine = new QScriptEngine; QScriptValue constructor; QScriptValue value; @ A common task when working with objects created from a script is finding the object a method is called on from the current script context. The code for this is simple, but lengthy. This is shortened with the use of a template function that obtains the object in question and casts it to the appropriate type. If an incorrect type is specified, a null pointer or similarly invalid value will be returned. @= template TYPE@, getself(QScriptContext *context) { TYPE@, self = qobject_cast(context->thisObject().toQObject()); return self; } template<> QTime getself(QScriptContext *context) { QTime self = context->thisObject().toVariant().toTime(); return self; } template<> QModelIndex getself(QScriptContext *context) { QModelIndex self = context->thisObject().toVariant().value(); return self; } template<> QByteArray getself(QScriptContext *context) { QByteArray self = context->thisObject().toVariant().toByteArray(); return self; } template<> SqlQueryConnection* getself(QScriptContext *context) { SqlQueryConnection *self =@| (SqlQueryConnection *)qscriptvalue_cast(context->thisObject()); return self; } template<> QXmlQuery* getself(QScriptContext *context) { QXmlQuery *self = (QXmlQuery *)qscriptvalue_cast(context->thisObject()); return self; } template<> QXmlStreamWriter* getself(QScriptContext *context) { QXmlStreamWriter *self = @| (QXmlStreamWriter *)qscriptvalue_cast(context->thisObject()); return self; } template<> QXmlStreamReader* getself(QScriptContext *context) { QXmlStreamReader *self = @| (QXmlStreamReader *)qscriptvalue_cast(context->thisObject()); return self; } @ Another common task is obtaining the arguments of a method call from the script context and casting these arguments to the proper type. This is once again done with templates. @= template TYPE@, argument(int arg, QScriptContext *context) { TYPE@, argument = qobject_cast(context->argument(arg).toQObject()); return argument; } template<> QString argument(int arg, QScriptContext *context) { return context->argument(arg).toString(); } template<> QVariant argument(int arg, QScriptContext *context) { return context->argument(arg).toVariant(); } template<> int argument(int arg, QScriptContext *context) { return context->argument(arg).toInt32(); } template<> SqlQueryConnection* argument(int arg, QScriptContext *context) { return (SqlQueryConnection *) qscriptvalue_cast(context->argument(arg)); } template<> QModelIndex argument(int arg, QScriptContext *context) { return qscriptvalue_cast(context->argument(arg)); } template<> double argument(int arg, QScriptContext *context) { return (double)(context->argument(arg).toNumber()); } template<> Units::Unit argument(int arg, QScriptContext *context) { return (Units::Unit)(context->argument(arg).toInt32()); } template<> QByteArray argument(int arg, QScriptContext *context) { return qscriptvalue_cast(context->argument(arg)); } template<> bool argument(int arg, QScriptContext *context) { return context->argument(arg).toBool(); } @ The scripting engine is informed of a number of classes defined elsewhere in the program. Code related to scripting these classes is grouped with the code implementing the classes. Additionally, there are several classes from Qt which are also made scriptable. These are detailed in the following sections. @* Exposing an Object Hierarchy to the Host Environment. \noindent While QtScript does a generally acceptable job of exposing information about objects that are available through the meta-object system, some methods require special handling in order to make them fully available to the host environment. Several functions are provided which provide a |QScriptValue| with these additional properties. The functions providing these properties also call other functions providing the properties of any base classes. In this way, any additional functionality provided to the host environment for a base class is also provided for any class that inherits that base class. For example, a |QBoxLayout| created in a script will have properties from |QLayout| which in turn brings in properties from |QObject| and |QLayoutItem|. A |QMainWindow| would bring in properties from |QWidget| which would bring in properties from |QObject|. Neither all methods nor all Qt classes are available from the host environment. When adding functionality to the host environment, there is a priority on classes and methods that are useful for \pn{}'@q'@>s intended purpose. @* Base Classes. \noindent There are a few classes that are base classes of classes exposed to the scripting engine. There is no need for the host environment to allow the creation of these base classes and there may not be methods that must be added as properties in derived classes, however stub functions are provided so that in the event that a method from one of these base classes is needed later, it can be added once for all derived classes. The first of these is |QObject|. @= void setQObjectProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QObject_setProperty(QScriptContext *context, QScriptEngine *engine); @ Attaching properties to a |QScriptValue| that wraps a |QObject| does not create a dynamic property on the underlying |QObject| by default. This can cause issues with certain interactions between script and native code. Rather than change every wrapper, we can instead expose a |setProperty()| method. @= void setQObjectProperties(QScriptValue value, QScriptEngine *engine) { value.setProperty("setProperty", engine->newFunction(QObject_setProperty)); } QScriptValue QObject_setProperty(QScriptContext *context, QScriptEngine *) { QObject *self = getself(context); self->setProperty(argument(0, context).toUtf8().constData(), argument(1, context)); return QScriptValue(); } @ The same can be done for |QPaintDevice| and |QLayoutItem|. @= void setQPaintDeviceProperties(QScriptValue value, QScriptEngine *engine); void setQLayoutItemProperties(QScriptValue value, QScriptEngine *engine); @ The implementations are similarly empty. @= void setQPaintDeviceProperties(QScriptValue, QScriptEngine *) { /* Nothing needs to be done here. */ } void setQLayoutItemProperties(QScriptValue, QScriptEngine *) { /* Nothing needs to be done here. */ } @* Timers. \noindent Some features in Typica require access to functionality similar to what |QTimer| provides from the host environment. This includes allowing script devices to periodically poll connected hardware and allowing a safety delay on profile translation. <@Function prototypes for scripting@>= void setQTimerProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQTimer(QScriptContext *context, QScriptEngine *engine); @ The host environment is informed of the constructor. @= constructor = engine->newFunction(constructQTimer); value = engine->newQMetaObject(&QTimer::staticMetaObject, constructor); engine->globalObject().setProperty("Timer", value); @ Everything that we are interested in here is a signal, slot, or property so there is little else to do. @= void setQTimerProperties(QScriptValue value, QScriptEngine *engine) { setQObjectProperties(value, engine); } QScriptValue constructQTimer(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QTimer); setQTimerProperties(object, engine); return object; } @* Scripting QWidget. \noindent The first interesting class in this hierarchy is |QWidget|. This is mainly used as a base class for other widgets and in such a role it is not particularly interesting. It is, however, possible to apply a layout to a |QWidget| and use that to manage the size and position of one or more child widgets. A few functions are used to accomplish this. @= void setQWidgetProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQWidget(QScriptContext *context, QScriptEngine *engine); QScriptValue QWidget_setLayout(QScriptContext *context, QScriptEngine *engine); QScriptValue QWidget_activateWindow(QScriptContext *context, QScriptEngine *engine); @ The script constructor must be passed to the scripting engine. @= constructor = engine->newFunction(constructQWidget); value = engine->newQMetaObject(&QWidget::staticMetaObject, constructor); engine->globalObject().setProperty("QWidget", value); @ The constructor creates a script value, but uses another function to add properties that wrap methods we want to make available to subclasses. Note that properties of the base classes are added before properties of this class. This procedure ensures that properties added from base classes can be be replaced in subclasses. @= QScriptValue constructQWidget(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QWidget); setQWidgetProperties(object, engine); return object; } void setQWidgetProperties(QScriptValue value, QScriptEngine *engine) { setQObjectProperties(value, engine); setQPaintDeviceProperties(value, engine); value.setProperty("setLayout", engine->newFunction(QWidget_setLayout)); value.setProperty("activateWindow", engine->newFunction(QWidget_activateWindow)); } @ This just leaves the property implementations. |QWidget::setLayout()| takes one argument, a |QLayout| and returns |void|. This wrapper duplicates this interface. |QWidget::activateWindow()| takes no arguments and returns nothing meaningful. @= QScriptValue QWidget_setLayout(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QWidget *self = getself(context); QLayout *layout = argument(0, context); if(layout) { self->setLayout(layout); } else { context->throwError("Incorrect argument type passed to "@| "QWidget::setLayout(). This method requires "@| "a QLayout."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QWidget::setLayout(). This method takes one "@| "QLayout as an argument."); } return QScriptValue(); } QScriptValue QWidget_activateWindow(QScriptContext *context, QScriptEngine *) { QWidget *self = getself(context); self->activateWindow(); return QScriptValue(); } @* Scripting QMessageBox. \noindent Some features require that \pn{} pauses an operation until further information can be obtained. An example of this is discretionary validation where input is checked and if it seems unlikely but not impossible to be correct a dialog should come up asking if that input is correct. If it is not, the operation should be cancelled and the person using \pn{} should be allowed to correct the information and try again. For this use case, it is not necessary to fully expose the |QMessageBox| class. Instead, it is enough to provide a function that will raise an appropriate message and return the selected action. @= QScriptValue displayWarning(QScriptContext *context, QScriptEngine *engine); QScriptValue displayError(QScriptContext *context, QScriptEngine *engine); QScriptValue displayInfo(QScriptContext *context, QScriptEngine *engine); @ This function is exposed to the host environment. @= constructor = engine->newFunction(displayWarning); engine->globalObject().setProperty("displayWarning", constructor); constructor = engine->newFunction(displayError); engine->globalObject().setProperty("displayError", constructor); constructor = engine->newFunction(displayInfo); engine->globalObject().setProperty("displayInfo", constructor); @ The function takes some arguments. @= QScriptValue displayWarning(QScriptContext *context, QScriptEngine *) { QMessageBox::StandardButton selection = QMessageBox::warning(NULL, argument(0, context), argument(1, context), QMessageBox::Ok | QMessageBox::Cancel); if(selection == QMessageBox::Ok) { return QScriptValue(true); } return QScriptValue(false); } QScriptValue displayError(QScriptContext *context, QScriptEngine *) { QMessageBox::critical(NULL, argument(0, context), argument(1, context)); return QScriptValue(); } QScriptValue displayInfo(QScriptContext *context, QScriptEngine *) { QMessageBox::information(NULL, argument(0, context), argument(1, context)); return QScriptValue(); } @* Scripting QMainWindow. \noindent Rather than directly exposing |QMainWindow| to the scripting engine, we expose a class derived from |QMainWindow| with a minor change allowing the script to be notified when the window is about to be closed. This allows us to save settings for objects populating the window. Close handlers can be established by connecting to the |aboutToClose()| signal which is emitted in the |closeEvent()| handler. The close event is always accepted after the script has had a chance to respond, so this cannot be used to present an, ``Are you sure?'' message without additional modification. Slots are also provided for saving the size and position of the window to settings and restoring the window geometry from these settings. As of version 1.4 window geometry management is provided for all windows. The |restoreSizeAndPosition()| and |saveSizeAndPosition()| methods should be considered depreciated. Version 1.6 adds a new property for handling the |windowModified| property such that an appropriate prompt is provided to confirm or cancel close events. Version 1.8 adds a new |setupFinished()| slot which is called after the initial |show()| at the end of window creation. This emits a |windowReady()| signal. Scripts can connect to this signal to perform tasks that must happen after the window has fully finished opening. The initial use for this is validating that all required configuration has been performed for a given window to be useful and, if not, immediately closing that. Without this, a call to |close()| in the script is reversed when the function creating the window calls |show()|. @= class ScriptQMainWindow : public QMainWindow@/ {@t\1@>@/ Q_OBJECT@;@/ Q_PROPERTY(QString closePrompt READ closePrompt WRITE setClosePrompt)@;@/ public:@/ ScriptQMainWindow(); QString closePrompt();@/ @t\4@>public slots@t\kern-3pt@>:@/ void show(); void saveSizeAndPosition(const QString &key); void restoreSizeAndPosition(const QString &key); void displayStatus(const QString &message = QString()); void setClosePrompt(QString prompt); void setupFinished();@/ signals:@/ void aboutToClose(void); void windowReady(void);@/ protected:@/ void closeEvent(QCloseEvent *event); void showEvent(QShowEvent *event);@/ private:@/ QString cprompt;@t\2@>@/ }@t\kern-3pt@>; @ The implementation of these functions is simple. @= ScriptQMainWindow::ScriptQMainWindow()@+: QMainWindow(NULL), cprompt(tr("Closing this window may result in loss of data. Continue?"))@/ { if(!AppInstance->databaseConnected()) { statusBar()->addWidget(new QLabel(tr("Not connected to database"))); } else { statusBar()->addWidget(new UserLabel); } } void ScriptQMainWindow::saveSizeAndPosition(const QString &key) { QSettings settings; settings.beginGroup(key); settings.setValue("pos", pos()); settings.setValue("size", size()); settings.endGroup(); } void ScriptQMainWindow::restoreSizeAndPosition(const QString &key) { QSettings settings; settings.beginGroup(key); if(settings.contains("size")) { resize(settings.value("size").toSize()); } if(settings.contains("pos")) { move(settings.value("pos").toPoint()); } settings.endGroup(); } void ScriptQMainWindow::displayStatus(const QString &message) { statusBar()->showMessage(message); } void ScriptQMainWindow::showEvent(QShowEvent *event) { if(!event->spontaneous()) { @@; event->accept(); } else { event->ignore(); } } void ScriptQMainWindow::show() { QMainWindow::show(); } void ScriptQMainWindow::setupFinished() { emit windowReady(); } @ When a close event occurs, we check the |windowModified| property to determine if closing the window could result in loss of data. If this is true, we allow the event to be cancelled. Otherwise, a signal is emitted which allows scripts to perform any cleanup that may be required before closing the window and the window geometry data is saved before allowing the window to close. @= void ScriptQMainWindow::closeEvent(QCloseEvent *event) { if(isWindowModified()) { @@; } emit aboutToClose(); @@; event->accept(); } @ The prompt text for our confirmation window is provided through the |closePrompt| property. @= QMessageBox::StandardButton result; result = QMessageBox::warning(this, "Typica", closePrompt(), QMessageBox::Ok | QMessageBox::Cancel); if(result == QMessageBox::Cancel) { event->ignore(); return; } @ Implementation of the |closePrompt| property is trivial. @= QString ScriptQMainWindow::closePrompt() { return cprompt; } void ScriptQMainWindow::setClosePrompt(QString prompt) { cprompt = prompt; } @ Window geometry management from version 1.4 on makes use of the window ID to produce an appropriate QSettings key. This decision relies on the ID being set before any show or close events are received and it relies on every distinct type of window having a unique ID. If this is not the case then other things are likely very broken. Note that with this approach multiple instances of the same type of window will use the same key. This may not be ideal in all cases, but further refinements can be produced if necessary. @= QSettings settings; settings.setValue(QString("geometries/%1").arg(objectName()), saveGeometry()); @ Restoring saved geometry is performed similarly to saving it. @= QSettings settings; restoreGeometry(settings.value(QString("geometries/%1").arg(objectName())). toByteArray()); @ Three functions are required to obtain the required functionality from a script. A fourth adds properties for the object hierarchy. @= QScriptValue constructQMainWindow(QScriptContext *context, QScriptEngine *engine); QScriptValue QMainWindow_setCentralWidget(QScriptContext *context,@| QScriptEngine *engine); QScriptValue QMainWindow_menuBar(QScriptContext *context, QScriptEngine *engine); void setQMainWindowProperties(QScriptValue value, QScriptEngine *engine); @ Of these, the engine only needs to be informed of the constructor initially. @= constructor = engine->newFunction(constructQMainWindow); value = engine->newQMetaObject(&ScriptQMainWindow::staticMetaObject, constructor); engine->globalObject().setProperty("QMainWindow", value); @ The constructor calls a function to add the additional properties to the newly created value. @= QScriptValue constructQMainWindow(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new ScriptQMainWindow); setQMainWindowProperties(object, engine); return object; } void setQMainWindowProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); value.setProperty("setCentralWidget", engine->newFunction(QMainWindow_setCentralWidget)); value.setProperty("menuBar", engine->newFunction(QMainWindow_menuBar)); } @ The |"setCentralWidget"| property is used for setting a widget in the main area of the window. In \pn{} this will usually be a |QSplitter| object, but it could also be a bare |QWidget| with a layout managing multiple widgets or a custom widget defined in a local change. This is a simple wrapper around |QMainWindow::setCentralWidget()|. @= QScriptValue QMainWindow_setCentralWidget(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QMainWindow *self = getself(context); QWidget *widget = argument(0, context); if(widget) { self->setCentralWidget(widget); } else { context->throwError("Incorrect argument type passed to "@| "QMainWindow::setCentralWidget(). This "@| "method requires a QWidget."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QMainWindow::setCentralWidget(). This method "@| "takes one QWidget as an argument."); } return QScriptValue(); } @ The |"menuBar"| property requires that we expose |QMenuBar| to the scripting environment in a limited fashion. We don'@q'@>t need to allow scripts to create a new menu bar as it can be obtained from the window, however to add the menus to the menu bar, we need to add a property to the |QMenuBar| object before passing it back. @= QScriptValue QMainWindow_menuBar(QScriptContext *context, QScriptEngine *engine) { QScriptValue object; if(context->argumentCount() == 0) { QMainWindow *self = getself<@[QMainWindow *@]>(context); QMenuBar *bar = self->menuBar(); object = engine->newQObject(bar); setQMenuBarProperties(object, engine); } else { context->throwError("Incorrect number of arguments passed to "@| "QMainWindow::menuBar(). This method takes no "@| "arguments."); } return object; } @ The previous function is the only place a new |QMenuBar| is created through the host environment. Two functions are used in handling this object creation. @= void setQMenuBarProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QMenuBar_addMenu(QScriptContext *context, QScriptEngine *engine); @ The first of these adds the appropriate properties to the newly created object. @= void setQMenuBarProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); value.setProperty("addMenu", engine->newFunction(QMenuBar_addMenu)); } @ This function can be used to add new menus to a menu bar. In order to do anything with the newly created menus, two properties are added to the |QMenu| objects which allow actions to be added as menu items and allow separators to be placed between groups of menu items. At the time of this writing, there are three |QMenuBar::addMenu()| methods. This function wraps |QMenu* QMenuBar::addMenu(const QString &title)|. @= QScriptValue QMenuBar_addMenu(QScriptContext *context, QScriptEngine *engine) { QScriptValue object; if(context->argumentCount() == 1) { QMenuBar *self = getself<@[QMenuBar *@]>(context); QString title = argument(0, context); object = engine->newQObject(self->addMenu(title)); setQMenuProperties(object, engine); } else { context->throwError("Incorrect number of arguments passed to "@| "QMenuBar::addMenu(). This method takes one "@| "string as an argument."); } return object; } @ These three functions allow adding items to the menu and adding separators between groups of items. @= void setQMenuProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QMenu_addAction(QScriptContext *context, QScriptEngine *engine); QScriptValue QMenu_addSeparator(QScriptContext *context, QScriptEngine *engine); @ The first of these add properties to newly created |QMenu| objects. @= void setQMenuProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); value.setProperty("addAction", engine->newFunction(QMenu_addAction)); value.setProperty("addSeparator", engine->newFunction(QMenu_addSeparator)); } @ These functions are simple wrappers around |QMenu| methods. @= QScriptValue QMenu_addAction(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QMenu *self = getself<@[QMenu *@]>(context); QAction *action = argument(0, context); if(action) { self->addAction(action); } else { context->throwError("Incorrect argument type passed to "@| "QMenu::addAction(). This method requires a "@| "QAction."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QMenu::addAction(). This method takes one "@| "QAction as an argument."); } return QScriptValue(); } QScriptValue QMenu_addSeparator(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 0) { QMenu *self = getself<@[QMenu *@]>(context); self->addSeparator(); } else { context->throwError("Incorrect number of arguments passed to "@| "QMenu::addSeparator(). This method takes no "@| "arguments."); } return QScriptValue(); } @* Scripting QFrame. \noindent |QFrame| is another class for which little needs to be done. It exists as a subclass of |QWidget| and a superclass for |QSplitter|, |QLCDNumber|, and |QAbstractScrollArea| among other classes. @= void setQFrameProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQFrame(QScriptContext *context, QScriptEngine *engine); @ The constructor must be passed to the scripting engine. @= constructor = engine->newFunction(constructQFrame); value = engine->newQMetaObject(&QFrame::staticMetaObject, constructor); engine->globalObject().setProperty("QFrame", value); @ The implementation of these functions should seem familiar. @= QScriptValue constructQFrame(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QFrame); setQFrameProperties(object, engine); return object; } void setQFrameProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); } @* Scripting QLabel. \noindent When constructing an interface wholly or partially through dynamic means rather than entirely through the XML configuration document it can sometimes be desirable to construct |QLabel| instances. This is usually used to provide a very small amount of text. @= void setQLabelProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQLabel(QScriptContext *context, QScriptEngine *engine); @ The constructor must be passed to the scripting engine. @= constructor = engine->newFunction(constructQLabel); value = engine->newQMetaObject(&QLabel::staticMetaObject, constructor); engine->globalObject().setProperty("QLabel", value); @ In the constructor we allow an optional argument to specify the text of the label. @= QScriptValue constructQLabel(QScriptContext *context, QScriptEngine *engine) { QString text; if(context->argumentCount() == 1) { text = argument(0, context); } QScriptValue object = engine->newQObject(new QLabel(text)); setQLabelProperties(object, engine); return object; } void setQLabelProperties(QScriptValue value, QScriptEngine *engine) { setQFrameProperties(value, engine); } @* Scripting QSvgWidget. \noindent Sometimes it is useful to provide a space for simple drawings without the need for all of the other capabilities of a web view. This was introduced as a way to draw box plots to help guide the creation of roast specifications. @= void setQSvgWidgetProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQSvgWidget(QScriptContext *context, QScriptEngine *engine); QScriptValue QSvgWidget_loadDevice(QScriptContext *context, QScriptEngine *engine); void addSvgWidgetToLayout(QDomElement element, QStack *widgetStack, QStack *layoutStack); @ The constructor must be passed to the scripting engine. @= constructor = engine->newFunction(constructQSvgWidget); value = engine->newQMetaObject(&QSvgWidget::staticMetaObject, constructor); engine->globalObject().setProperty("QSvgWidget", value); @ The constructor is trivial. @= QScriptValue constructQSvgWidget(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QSvgWidget); setQSvgWidgetProperties(object, engine); return object; } @ A property is added that allows loading data from a |QIODevice|. @= void setQSvgWidgetProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); value.setProperty("loadDevice", engine->newFunction(QSvgWidget_loadDevice)); } QScriptValue QSvgWidget_loadDevice(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QSvgWidget *self = getself<@[QSvgWidget *@]>(context); QIODevice *device = argument(0, context); device->reset(); QByteArray data = device->readAll(); self->load(data); } else { context->throwError("Incorrect number of arguments passed to "@| "QSvgWidget::loadData(). This method takes one "@| "QIODevice as an argument."); } return QScriptValue(); } @ Additional work is needed to allow including this from the XML description of a window. @= else if(currentElement.tagName() == "svgwidget") { addSvgWidgetToLayout(currentElement, widgetStack, layoutStack); } @ The function used to create this follows the usual pattern. @= void addSvgWidgetToLayout(QDomElement element, QStack *, QStack *layoutStack) { QBoxLayout *layout = qobject_cast(layoutStack->top()); QSvgWidget *widget = new QSvgWidget; layout->addWidget(widget); QString id = element.attribute("id"); if(!id.isEmpty()) { widget->setObjectName(id); } } @* Scripting QLineEdit. \noindent Similarly, we may want to allow line edits in interfaces defined through the host environment. For example, this is used for the free text annotation control for roasters this has been configured on. @= void setQLineEditProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQLineEdit(QScriptContext *context, QScriptEngine *engine); @ The constructor must be passed to the host environment. @= constructor = engine->newFunction(constructQLineEdit); value = engine->newQMetaObject(&QLineEdit::staticMetaObject, constructor); engine->globalObject().setProperty("QLineEdit", value); @ The constructor is trivial. @= QScriptValue constructQLineEdit(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QLineEdit()); setQLineEditProperties(object, engine); return object; } @ At present all of the QLineEdit functionality exposed through this interface is provided automatically through the meta-object system. @= void setQLineEditProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); } @* Scripting QSplitter. \noindent The |QSplitter| class is one of the main classes used for user interface object layout in \pn{}. To provide this class to the scripting engine, we provide five functions: a constructor, a method for adding widgets to the splitter, a method for saving the size of each widget in the splitter, a method for restoring these saved sizes, and a function for adding these methods as properties of newly created |QSplitter| objects. @= QScriptValue constructQSplitter(QScriptContext *context, QScriptEngine *engine); QScriptValue QSplitter_addWidget(QScriptContext *context, QScriptEngine *engine); QScriptValue QSplitter_saveState(QScriptContext *context, QScriptEngine *engine); QScriptValue QSplitter_restoreState(QScriptContext *context, QScriptEngine *engine); QScriptValue QSplitter_count(QScriptContext *context, QScriptEngine *engine); QScriptValue QSplitter_setCollapsible(QScriptContext *context, QScriptEngine *engine); void setQSplitterProperties(QScriptValue value, QScriptEngine *engine); @ Of these, the scripting engine must be informed of the constructor. @= constructor = engine->newFunction(constructQSplitter); value = engine->newQMetaObject(&QSplitter::staticMetaObject, constructor); engine->globalObject().setProperty("QSplitter", value); @ The constructor creates the object and adds the required properties to it. @= QScriptValue constructQSplitter(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QSplitter); setQSplitterProperties(object, engine); return object; } void setQSplitterProperties(QScriptValue value, QScriptEngine *engine) { setQFrameProperties(value, engine); value.setProperty("addWidget", engine->newFunction(QSplitter_addWidget)); value.setProperty("saveState", engine->newFunction(QSplitter_saveState)); value.setProperty("restoreState", engine->newFunction(QSplitter_restoreState)); value.setProperty("count", engine->newFunction(QSplitter_count)); value.setProperty("setCollapsible", engine->newFunction(QSplitter_setCollapsible)); } @ The |"addWidget"| property is a simple wrapper around |QSplitter::addWidget()|. @= QScriptValue QSplitter_addWidget(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QSplitter *self = getself(context); QWidget *widget = argument(0, context); if(widget) { self->addWidget(widget); } else { context->throwError("Incorrect argument type passed to "@| "QSplitter::addWidget(). This method "@| "requires a QWidget."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QSplitter::addWidget(). This method takes one "@| "QWidget as an argument."); } return QScriptValue(); } @ The methods for saving and restoring the state of a splitter do not behave well when the number of widgets contained in the splitter increase. This is a problem in the logging view where we may want to allow zero width indicators but reconfiguration can cause the number of indicators to increase. This would result in the right most indicators such as the batch timer disappearing. Most people do not notice the splitter handle or think to drag that to the left to correct this issue so it would be better to save the number of indicators when saving the state and if the number of indicators does not match, we should not restore the obsolete saved state. @= QScriptValue QSplitter_count(QScriptContext *context, QScriptEngine *) { QSplitter *self = getself(context); return QScriptValue(self->count()); } @ When saving and restoring the state of a splitter, we always want to do this through a |QSettings| object. For this, we take an extra argument specifying the settings key to read from or write to. Unlike the equivalent functions called from native code, neither of these functions called from a script will return the data being saved. @= QScriptValue QSplitter_saveState(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QSplitter *self = getself(context); QString key = argument(0, context); QSettings settings; settings.setValue(key, self->saveState()); } else { context->throwError("Incorrect number of arguments passed to "@| "QSplitter::saveState(). This method takes one "@| "string as an argument."); } return QScriptValue(); } QScriptValue QSplitter_restoreState(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QSplitter *self = getself(context); QString key = argument(0, context); QSettings settings; self->restoreState(settings.value(key).toByteArray()); } else { context->throwError("Incorrect number of arguments passed to "@| "QSplitter::restoreState(). This method takes "@| "one string as an argument."); } return QScriptValue(); } @ Sometimes a |QSplitter| is used to make it easy to hide optional elements. In this use case it can be useful to indicate that required elements cannot be collapsed. @= QScriptValue QSplitter_setCollapsible(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 2) { QSplitter *self = getself(context); self->setCollapsible(argument(0, context), argument(1, context)); } else { context->throwError("Incorrect number of arguments"); } return QScriptValue(); } @* Scripting Layout classes. \noindent Layout classes simplify managing the size and position of widgets in a user interface. Qt provides several such classes, including |QBoxLayout| which can be used to construct a variety of different interfaces. As widgets containing a layout should not really need to care which layout is being used, the |QLayout| class acts as an abstract base for all layout classes. A bare |QLayout| will never be constructed, however subclasses can make use of the |addWidget()| property. @= void setQLayoutProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QLayout_addWidget(QScriptContext *context, QScriptEngine *engine); @ The implementation is trivial. @= void setQLayoutProperties(QScriptValue value, QScriptEngine *engine) { setQLayoutItemProperties(value, engine); value.setProperty("addWidget", engine->newFunction(QLayout_addWidget)); } QScriptValue QLayout_addWidget(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QLayout *self = getself(context); QWidget *widget = argument(0, context); if(widget) { self->addWidget(widget); } else { context->throwError("Incorrect argument type passed to "@| "QLayout::addWidget(). This method requires "@| "a QWidget."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QLayout::addWidget(). This method takes one "@| "QWidget as an argument."); } return QScriptValue(); } @ |QBoxLayout| is a more interesting layout class. This allows widgets to be arranged in a single row or column and can be used, for example, to arrange a row of buttons as in figure \secno. \medskip \resizebox*{6.3in}{!}{\includegraphics{boxlayoutexample}} \smallskip \centerline{Figure \secno: Buttons in a |QBoxLayout|.} \medskip This class makes use of the |addWidget()| method from |QLayout|. @= QScriptValue constructQBoxLayout(QScriptContext *context, QScriptEngine *engine); void setQBoxLayoutProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QBoxLayout_addLayout(QScriptContext *context, QScriptEngine *engine); QScriptValue QBoxLayout_addWidget(QScriptContext *context, QScriptEngine *engine); @ The script constructor must be passed to the scripting engine. @= constructor = engine->newFunction(constructQBoxLayout); value = engine->newQMetaObject(&QBoxLayout::staticMetaObject, constructor); engine->globalObject().setProperty("QBoxLayout", value); @ The implementation of these functions should seem familiar by now. Note that while a horizontal layout is provided by default, this can be changed from the script once the layout is created. @= QScriptValue constructQBoxLayout(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QBoxLayout(QBoxLayout::LeftToRight)); setQBoxLayoutProperties(object, engine); return object; } void setQBoxLayoutProperties(QScriptValue value, QScriptEngine *engine) { setQLayoutProperties(value, engine); value.setProperty("addLayout", engine->newFunction(QBoxLayout_addLayout)); value.setProperty("addWidget", engine->newFunction(QBoxLayout_addWidget)); } QScriptValue QBoxLayout_addLayout(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() > 0 && context->argumentCount() < 3) { QBoxLayout *self = getself(context); QLayout *layout = argument(0, context); int stretch = 0; if(context->argumentCount() == 2) { stretch = argument(1, context); } if(layout) { self->addLayout(layout, stretch); } else { context->throwError("Incorrect argument type passed to "@| "QLayout::addLayout(). This method requires "@| "a QLayout."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QLayout::addLayout(). This method takes one "@| "QLayout as an argument and optionally one integer."); } return QScriptValue(); } @ We override the base class wrapper for |addWidget| to add two optional arguments: one specifies the stretch factor of the widget and the other specifies the alignment of the widget within the layout. @= QScriptValue QBoxLayout_addWidget(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() > 0 && context->argumentCount() < 4) { QBoxLayout *self = getself(context); QWidget *widget = argument(0, context); int stretch = 0; Qt::Alignment alignment = 0; if(context->argumentCount() > 1) { stretch = argument(1, context); } if(context->argumentCount() > 2) { alignment = (Qt::Alignment)(argument(2, context)); } if(widget) { self->addWidget(widget, stretch, alignment); } else { context->throwError("Incorrect argument type passed to "@| "QBoxLayout::addWidget(). This method requires "@| "a QWidget."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QBoxLayout::addWidget(). This method takes one "@| "QWidget and optionally up to two integers as "@| "arguments."); } return QScriptValue(); } @* Scripting QAction. \noindent The |QAction| class is used in \pn{} to create menu items and respond to the selection of these items. Three functions are required for our scripting needs with regard to this class. @= QScriptValue constructQAction(QScriptContext *context, QScriptEngine *engine); QScriptValue QAction_setShortcut(QScriptContext *context, QScriptEngine *engine); void setQActionProperties(QScriptValue value, QScriptEngine *engine); @ The scripting engine must be informed of the constructor. @= constructor = engine->newFunction(constructQAction); value = engine->newQMetaObject(&QAction::staticMetaObject, constructor); engine->globalObject().setProperty("QAction", value); @ The constructor is simple, however some might sensibly question why the |"setShortcut"| property is needed at all. Why not have scripts simply set the |shortcut| property of the |QAction| directly? The answer to this is that the property expects data of the |QKeySequence| type. While this can be created from a |QString|, passing a string to the property through the scripting engine did not work at the time this was written. @= QScriptValue constructQAction(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QAction(NULL)); setQActionProperties(object, engine); return object; } void setQActionProperties(QScriptValue value, QScriptEngine *engine) { setQObjectProperties(value, engine); value.setProperty("setShortcut", engine->newFunction(QAction_setShortcut)); } QScriptValue QAction_setShortcut(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 1) { QAction *self = getself<@[QAction *@]>(context); self->setShortcut(argument(0, context)); } else { context->throwError("Incorrect number of arguments passed to "@| "QAction::setShortcut(). This method takes one "@| "string as an argument."); } return QScriptValue(); } @* Scripting QFileDialog. \noindent |QFileDialog| provides two static member functions which is all that we need. The objects returned from these methods are built on the |QDialog| abstract base class. @= QScriptValue QFileDialog_getOpenFileName(QScriptContext *context, QScriptEngine *engine); QScriptValue QFileDialog_getSaveFileName(QScriptContext *context, QScriptEngine *engine); void setQFileDialogProperties(QScriptValue value, QScriptEngine *engine); void setQDialogProperties(QScriptValue value, QScriptEngine *engine); @ The scripting engine must be informed of the wrapper functions for the static methods. @= value = engine->newQMetaObject(&QFileDialog::staticMetaObject); value.setProperty("getOpenFileName", engine->newFunction(QFileDialog_getOpenFileName)); value.setProperty("getSaveFileName", engine->newFunction(QFileDialog_getSaveFileName)); engine->globalObject().setProperty("QFileDialog", value); @ This function is just a simple wrapper around the |QFileDialog| method, but the object returned has any properties added to the base class available. @= QScriptValue QFileDialog_getOpenFileName(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 3) { QWidget *widget = argument(0, context); if(widget) { QString caption = argument(1, context); QString dir = argument(2, context); retval = QScriptValue(engine, QFileDialog::getOpenFileName(widget, caption, dir, "", 0, 0)); setQFileDialogProperties(retval, engine); } else { context->throwError("Incorrect argument type passed to "@| "QFileDialog::getOpenFileName(). The first "@| "argument to this method must be a QWidget."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QFileDialog::getOpenFileName(). This method "@| "takes one QWidget followed by two strings for a "@| "total of three arguments."); } return retval; } @ Similarly, this just wraps |QFileDialog::getSaveFileName()|. @= QScriptValue QFileDialog_getSaveFileName(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 3) { QWidget *widget = argument(0, context); if(widget) { QString caption = argument(1, context); QString dir = argument(2, context); retval = QScriptValue(engine, QFileDialog::getSaveFileName(widget, caption, dir, "", 0, 0)); setQFileDialogProperties(retval, engine); } else { context->throwError("Incorrect argument type passed to "@| "QFileDialog::getSaveFileName(). The first "@| "argument to this method must be a QWidget."); } } else { context->throwError("Incorrect number of arguments passed to "@| "QFileDialog::getSaveFileName(). This method "@| "takes one QWidget followed by two strings for a "@| "total of three arguments."); } return retval; } @ Adding object hierarchy properties to the objects created above is simple. @= void setQFileDialogProperties(QScriptValue value, QScriptEngine *engine) { setQDialogProperties(value, engine); } void setQDialogProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); } @* Scripting QFile. \noindent When using a |QFile| in a script, we only need the constructor and two functions for hooking it in with the rest of the object hierarchy. @= QScriptValue constructQFile(QScriptContext *context, QScriptEngine *engine); void setQFileProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QFile_remove(QScriptContext *context, QScriptEngine *engine); void setQIODeviceProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QIODevice_open(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_readToString(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_readBytes(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_peek(QScriptContext *context, QScriptEngine *engine); QScriptValue QIODevice_read(QScriptContext *context, QScriptEngine *engine); @ This function is passed to the scripting engine. @= constructor = engine->newFunction(constructQFile); value = engine->newQMetaObject(&QFile::staticMetaObject, constructor); engine->globalObject().setProperty("QFile", value); @ The implementation is trivial. @= QScriptValue constructQFile(QScriptContext *context, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QFile(argument(0, context)));@/ setQFileProperties(object, engine); return object; } @ |QFile| gets a wrapper around |remove()| to support deleting temporary files. @= void setQFileProperties(QScriptValue value, QScriptEngine *engine) { setQIODeviceProperties(value, engine); value.setProperty("remove", engine->newFunction(QFile_remove)); } QScriptValue QFile_remove(QScriptContext *context, QScriptEngine *engine) { QFile *self = getself(context); bool retval = self->remove(); return QScriptValue(engine, retval); } @ Although we aren'@q'@>t going to create any instances of |QIODevice| directly, subclasses such as |QFile| and |QBuffer| get two additional properties for opening and closing the device. In order to solve some class interoperability issues, a convenience method is also added which is equivalent to creating a |QString| from the |QByteArray| returned from the |readAll()| method. @= void setQIODeviceProperties(QScriptValue value, QScriptEngine *engine) { setQObjectProperties(value, engine); value.setProperty("open", engine->newFunction(QIODevice_open)); value.setProperty("close", engine->newFunction(QIODevice_close)); value.setProperty("readToString", engine->newFunction(QIODevice_readToString)); value.setProperty("putChar", engine->newFunction(QIODevice_putChar)); value.setProperty("writeString", engine->newFunction(QIODevice_writeString)); value.setProperty("writeBytes", engine->newFunction(QIODevice_writeBytes)); value.setProperty("readBytes", engine->newFunction(QIODevice_readBytes)); value.setProperty("peek", engine->newFunction(QIODevice_peek)); value.setProperty("read", engine->newFunction(QIODevice_read)); } @ These are simple wrappers. In the case of the |open()| property, one argument may be passed specifying the mode used for opening. The supported values for this are 1 (Read Only), 2 (Write Only), and 3 (Read Write). If this argument is not passed, it is assumed that the user wants read and write access. @= QScriptValue QIODevice_open(QScriptContext *context, QScriptEngine *) { QIODevice *self = getself(context); bool retval = false; if(context->argumentCount() == 1) { switch(argument(0, context)) { case 1: retval = self->open(QIODevice::ReadOnly); break; case 2: retval = self->open(QIODevice::WriteOnly); break; case 3: retval = self->open(QIODevice::ReadWrite); break; default: break; } } else { retval = self->open(QIODevice::ReadWrite); } return QScriptValue(retval); } QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *) { QIODevice *self = getself(context); self->close(); return QScriptValue(); } @ The |readToString()| method is a simple extension of |QIODevice::readAll()| to interface with classes that expect document data in the form of a string. Most notably, this includes |QWebView|. @= QScriptValue QIODevice_readToString(QScriptContext *context, QScriptEngine *) { QIODevice *self = getself(context); self->reset(); return QScriptValue(QString(self->readAll())); } @ In support of serial port communications, wrappers around two methods for writing data have been added. As these are valid for other classes derived from |QIODevice|, they are added here so the functionality is available more broadly. As we are unable to pass a type that guarantees only a single character, we instead accept a string and only pass along the first character. @= QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *) { QIODevice *self = getself(context); if(context->argumentCount() == 1) { return QScriptValue(self->putChar(argument(0, context).toUtf8().at(0))); } context->throwError("Incorrect number of arguments passed to "@| "QIODevice::putChar()"); return QScriptValue(); } @ Two wrappers are provided around |QIODevice::write()| for outputting multi-byte data. If we are writing strings that are valid UTF-8, we can use the |writeString| wrapper, but if we require full control over exactly which bytes are output, the |writeBytes| wrapper is more appropriate. @= QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *) { QIODevice *self = getself(context); if(context->argumentCount() == 1) { self->write(argument(0, context).toUtf8()); } else { context->throwError("Incorrect number of arguments passed to "@| "QIODevice::writeString()"); } return QScriptValue(); } QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *) { QIODevice *self = getself(context); if(context->argumentCount() == 1) { self->write(argument(0, context)); } else { context->throwError("Incorrect number of arguments passed to "@| "QIODevice::writeBytes()"); } return QScriptValue(); } @ The readBytes method is an alternate wrapper around |QByteArray::readAll()| which returns the |QByteArray| instead of converting this to a |QString|. @= QScriptValue QIODevice_readBytes(QScriptContext *context, QScriptEngine *engine) { QIODevice *self = getself(context); QScriptValue value = engine->toScriptValue(self->readAll()); setQByteArrayProperties(value, engine); return value; } @ Wrappers around |peek()| and |read()| are also provided. @= QScriptValue QIODevice_peek(QScriptContext *context, QScriptEngine *engine) { QIODevice *self = getself(context); QScriptValue value = engine->toScriptValue( self->peek(argument(0, context))); setQByteArrayProperties(value, engine); return value; } QScriptValue QIODevice_read(QScriptContext *context, QScriptEngine *engine) { QIODevice *self = getself(context); QScriptValue value = engine->toScriptValue( self->read(argument(0, context))); setQByteArrayProperties(value, engine); return value; } @* Scripting QProcess. \noindent Sometimes it is useful to have \pn work with an external program. The initial use case was document generation by typesetting instructions to a file and then running \TeX to generate a shelf sign or a sheet of labels. Other likely use cases include interfacing with external programs that output measurement streams. There are several methods which we may want to expose, however this is being done only as needed. @= QScriptValue constructQProcess(QScriptContext *context, QScriptEngine *engine); void setQProcessProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QProcess_execute(QScriptContext *context, QScriptEngine *engine); QScriptValue QProcess_startDetached(QScriptContext *context, QScriptEngine *engine); QScriptValue QProcess_setWorkingDirectory(QScriptContext *context, QScriptEngine *engine); QScriptValue QProcess_start(QScriptContext *context, QScriptEngine *engine); @ We follow the same pattern with this as with many other types. @= constructor = engine->newFunction(constructQProcess); value = engine->newQMetaObject(&QProcess::staticMetaObject, constructor); engine->globalObject().setProperty("QProcess", value); @ The constructor is trivial. @= QScriptValue constructQProcess(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QProcess); setQProcessProperties(object, engine); return object; } @ As |QProcess| is a |QIODevice| we inherit some properties from that. We also expose some details that are specific to |QProcess|. @= void setQProcessProperties(QScriptValue value, QScriptEngine *engine) { setQIODeviceProperties(value, engine); value.setProperty("execute", engine->newFunction(QProcess_execute)); value.setProperty("startDetached", engine->newFunction(QProcess_startDetached)); value.setProperty("setWorkingDirectory", engine->newFunction(QProcess_setWorkingDirectory)); value.setProperty("start", engine->newFunction(QProcess_start)); } @ The |execute()| method comes in two flavors: one with arguments and one without. We always call the one with arguments and simply pass in an empty list if no arguments are specified. @= QScriptValue QProcess_execute(QScriptContext *context, QScriptEngine *) { QProcess *self = getself(context); QString program = argument(0, context); QStringList arguments = QStringList(); if(context->argumentCount() > 1) { arguments = argument(1, context).toStringList(); } int retval = self->execute(program, arguments); return QScriptValue(retval); } @ Similarly |startDetached()| can be called in a few different ways. @= QScriptValue QProcess_startDetached(QScriptContext *context, QScriptEngine *) { QProcess *self = getself(context); QString program = argument(0, context); QStringList arguments = QStringList(); if(context->argumentCount() > 1) { arguments = argument(1, context).toStringList(); } QString workingDirectory = ""; if(context->argumentCount() > 2) { workingDirectory = argument(2, context); } bool retval; switch(context->argumentCount()) { case 1: retval = self->startDetached(program); break; case 2: retval = self->startDetached(program, arguments); break; case 3: retval = self->startDetached(program, arguments, workingDirectory); break; default: retval = false; } return QScriptValue(retval); } @ Sometimes we care about the working directory for our program. @= QScriptValue QProcess_setWorkingDirectory(QScriptContext *context, QScriptEngine *) { QProcess *self = getself(context); QString directory = argument(0, context); self->setWorkingDirectory(directory); return QScriptValue(); } @ When using the |start()| method we always assume that we want read and write access. @= QScriptValue QProcess_start(QScriptContext *context, QScriptEngine *) { QProcess *self = getself(context); QString program = argument(0, context); QStringList arguments = QStringList(); if(context->argumentCount() > 1) { arguments = argument(1, context).toStringList(); } self->start(program, arguments); return QScriptValue(); } @ In order to work with |QByteArray| this should also be exposed to the host environment. @= QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes); void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes); QScriptValue constructQByteArray(QScriptContext *context, QScriptEngine *engine); void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_getAt(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_setAt(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_appendBytes(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_appendString(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_size(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_left(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_right(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_mid(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_chop(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_remove(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_toInt8(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_toInt16(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_toInt32(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_toFloat(QScriptContext *context, QScriptEngine *engine); QScriptValue QByteArray_toDouble(QScriptContext *context, QScriptEngine *engine); @ First, we provide some functionns for moving array data across the language barrier. @= QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes) { QScriptValue object = engine->newVariant(QVariant(bytes)); setQByteArrayProperties(object, engine); return object; } void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes) { bytes = value.toVariant().toByteArray(); } @ We register this our conversion functions and allow creation of new arrays next. @= qScriptRegisterMetaType(engine, QByteArray_toScriptValue, QByteArray_fromScriptValue); constructor = engine->newFunction(constructQByteArray); engine->globalObject().setProperty("QByteArray", constructor); @ The constructor is straightforward. @= QScriptValue constructQByteArray(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->toScriptValue(QByteArray()); setQByteArrayProperties(object, engine); return object; } @ There are many methods which are not automatically available which we may want to have wrappers around. These should be added as required. @= void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine) { value.setProperty("fromHex", engine->newFunction(QByteArray_fromHex)); value.setProperty("getAt", engine->newFunction(QByteArray_getAt)); value.setProperty("setAt", engine->newFunction(QByteArray_setAt)); value.setProperty("appendBytes", engine->newFunction(QByteArray_appendBytes)); value.setProperty("appendString", engine->newFunction(QByteArray_appendString)); value.setProperty("size", engine->newFunction(QByteArray_size)); value.setProperty("left", engine->newFunction(QByteArray_left)); value.setProperty("right", engine->newFunction(QByteArray_right)); value.setProperty("mid", engine->newFunction(QByteArray_mid)); value.setProperty("chop", engine->newFunction(QByteArray_chop)); value.setProperty("remove", engine->newFunction(QByteArray_remove)); value.setProperty("toInt8", engine->newFunction(QByteArray_toInt8)); value.setProperty("toInt16", engine->newFunction(QByteArray_toInt16)); value.setProperty("toInt32", engine->newFunction(QByteArray_toInt32)); value.setProperty("toFloat", engine->newFunction(QByteArray_toFloat)); value.setProperty("toDouble", engine->newFunction(QByteArray_toDouble)); } @ Perhaps the easiest way to deal with fixed byte strings for serial communications across script boundaries is to use a hex encoded string. @= QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine) { QByteArray self = getself(context); QByteArray retval; retval = self.fromHex(argument(0, context).toUtf8()); QScriptValue value = engine->toScriptValue(retval); setQByteArrayProperties(value, engine); return value; } @ A pair of methods is provided for getting and setting values at a particular byte. @= QScriptValue QByteArray_getAt(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); return QScriptValue((int)(self.at(argument(0, context)))); } QScriptValue QByteArray_setAt(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); self[argument(0, context)] = (char)(argument(1, context)); return QScriptValue(); } @ Methods are provided for appending either another |QByteArray| or a string to a |QByteArray|. The only difference between these functions is the expected argument type. @= QScriptValue QByteArray_appendBytes(QScriptContext *context, QScriptEngine *engine) { QByteArray self = getself(context); QScriptValue value = engine->toScriptValue( self.append(argument(0, context))); setQByteArrayProperties(value, engine); return value; } QScriptValue QByteArray_appendString(QScriptContext *context, QScriptEngine *engine) { QByteArray self = getself(context); QScriptValue value = engine->toScriptValue( self.append(argument(0, context))); setQByteArrayProperties(value, engine); return value; } @ Checking the size of our byte array frequently a requirement. @= QScriptValue QByteArray_size(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); return QScriptValue(self.size()); } @ It is also frequently useful to be able to work with specific parts of a byte array, so a few methods are provided for carving these up. @= QScriptValue QByteArray_left(QScriptContext *context, QScriptEngine *engine) { QByteArray self = getself(context); QScriptValue value = engine->toScriptValue( self.left(argument(0, context))); setQByteArrayProperties(value, engine); return value; } QScriptValue QByteArray_right(QScriptContext *context, QScriptEngine *engine) { QByteArray self = getself(context); QScriptValue value = engine->toScriptValue( self.right(argument(0, context))); setQByteArrayProperties(value, engine); return value; } QScriptValue QByteArray_mid(QScriptContext *context, QScriptEngine *engine) { QByteArray self = getself(context); int length = -1; if(context->argumentCount() > 1) { length = argument(1, context); } QScriptValue value = engine->toScriptValue( self.mid(argument(0, context), length)); setQByteArrayProperties(value, engine); return value; } @ We may also want to remove bytes from an array. @= QScriptValue QByteArray_chop(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); self.chop(argument(0, context)); return QScriptValue(); } QScriptValue QByteArray_remove(QScriptContext *context, QScriptEngine *engine) { QByteArray self = getself(context); QScriptValue value = engine->toScriptValue( self.remove(argument(0, context), argument(1, context))); setQByteArrayProperties(value, engine); return value; } @ When receiving data in a byte array, bytes are sometimes intended to represent 8, 16, or 32 bit integers. In such cases we often want to perform some computation on these values so having the ability to split off that portion of the array (for example, with |mid()|) and convert to a Number is useful. @= QScriptValue QByteArray_toInt8(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); int value = 0; char *bytes = (char *)&value; bytes[0] = self[0]; return QScriptValue(value); } QScriptValue QByteArray_toInt16(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); int value = 0; char *bytes = (char *)&value; bytes[0] = self[0]; bytes[1] = self[1]; return QScriptValue(value); } QScriptValue QByteArray_toInt32(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); int value = 0; char *bytes = (char *)&value; bytes[0] = self[0]; bytes[1] = self[1]; bytes[2] = self[2]; bytes[3] = self[3]; return QScriptValue(value); } @ Similar methods are provided for converting bytes to a |float| or |double|. Note that the return value from |toFloat| will, in the host environment, be represented as a |double|. @= QScriptValue QByteArray_toFloat(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); float value = 0.0; char *bytes = (char *)&value; bytes[0] = self[0]; bytes[1] = self[1]; bytes[2] = self[2]; bytes[3] = self[3]; return QScriptValue(value); } QScriptValue QByteArray_toDouble(QScriptContext *context, QScriptEngine *) { QByteArray self = getself(context); double value = 0.0; char *bytes = (char *)&value; bytes[0] = self[0]; bytes[1] = self[1]; bytes[2] = self[2]; bytes[3] = self[3]; bytes[4] = self[4]; bytes[5] = self[5]; bytes[6] = self[6]; bytes[7] = self[7]; return QScriptValue(value); } @ Some protocols require manipulating larger than 8 bit numbers as a sequence of bytes. To facilitate this, methods are provided to construct a |QByteArray| from different sized numbers. 8 bit numbers are provided for uniformity. @= QScriptValue bytesFromInt8(QScriptContext *context, QScriptEngine *engine); QScriptValue bytesFromInt16(QScriptContext *context, QScriptEngine *engine); QScriptValue bytesFromInt32(QScriptContext *context, QScriptEngine *engine); QScriptValue bytesFromFloat(QScriptContext *context, QScriptEngine *engine); QScriptValue bytesFromDouble(QScriptContext *context, QScriptEngine *engine); @ These are globally available. @= engine->globalObject().setProperty("bytesFromInt8", engine->newFunction(bytesFromInt8)); engine->globalObject().setProperty("bytesFromInt16", engine->newFunction(bytesFromInt16)); engine->globalObject().setProperty("bytesFromInt32", engine->newFunction(bytesFromInt32)); engine->globalObject().setProperty("bytesFromFloat", engine->newFunction(bytesFromFloat)); engine->globalObject().setProperty("bytesFromDouble", engine->newFunction(bytesFromDouble)); @ The methods all work by casting the appropriate numeric type to a |char *| and copying the bytes to a new |QByteArray|. Note that the ECMA-262 standard only has one type of number and this is an IEEE 754 binary64 double precision floating point number. Functions other than |bytesFromDouble| will be cast from |double|. @= QScriptValue bytesFromInt8(QScriptContext *context, QScriptEngine *engine) { qint8 value = (qint8)(argument(0, context)); char *bytes = (char *)&value; QByteArray retval; retval.resize(1); retval[0] = bytes[0]; QScriptValue v = engine->toScriptValue(retval); setQByteArrayProperties(v, engine); return v; } QScriptValue bytesFromInt16(QScriptContext *context, QScriptEngine *engine) { qint16 value = (qint16)(argument(0, context)); char *bytes = (char *)&value; QByteArray retval; retval.resize(2); retval[0] = bytes[0]; retval[1] = bytes[1]; QScriptValue v = engine->toScriptValue(retval); setQByteArrayProperties(v, engine); return v; } QScriptValue bytesFromInt32(QScriptContext *context, QScriptEngine *engine) { qint32 value = (qint32)(argument(0, context)); char *bytes = (char *)&value; QByteArray retval; retval.resize(4); retval[0] = bytes[0]; retval[1] = bytes[1]; retval[2] = bytes[2]; retval[3] = bytes[3]; QScriptValue v = engine->toScriptValue(retval); setQByteArrayProperties(v, engine); return v; } QScriptValue bytesFromFloat(QScriptContext *context, QScriptEngine *engine) { float value = (float)(argument(0, context)); char *bytes = (char *)&value; QByteArray retval; retval.resize(4); retval[0] = bytes[0]; retval[1] = bytes[1]; retval[2] = bytes[2]; retval[3] = bytes[3]; QScriptValue v = engine->toScriptValue(retval); setQByteArrayProperties(v, engine); return v; } QScriptValue bytesFromDouble(QScriptContext *context, QScriptEngine *engine) { double value = (double)(argument(0, context)); char *bytes = (char *)&value; QByteArray retval; retval.resize(8); retval[0] = bytes[0]; retval[1] = bytes[1]; retval[2] = bytes[2]; retval[3] = bytes[3]; retval[4] = bytes[4]; retval[5] = bytes[5]; retval[6] = bytes[6]; retval[7] = bytes[7]; QScriptValue v = engine->toScriptValue(retval); setQByteArrayProperties(v, engine); return v; } @* Scripting QBuffer. \noindent Sometimes it is desirable to load a roast profile from a file. At other times, it is more useful to load that profile from a byte array stored in a database. The |XMLInput| class takes data from a |QIODevice| object, which means that we can choose from a |QFile| when we want the former or a |QBuffer| when we want the latter. @= QScriptValue constructQBuffer(QScriptContext *context, QScriptEngine *engine); void setQBufferProperties(QScriptValue value, QScriptEngine *engine); QScriptValue QBuffer_setData(QScriptContext *context, QScriptEngine *engine); QScriptValue QBuffer_data(QScriptContext *context, QScriptEngine *engine); @ The host environment needs to be aware of the constructor. @= constructor = engine->newFunction(constructQBuffer); value = engine->newQMetaObject(&QBuffer::staticMetaObject, constructor); engine->globalObject().setProperty("QBuffer", value); @ The implementation is trivial. @= QScriptValue constructQBuffer(QScriptContext *context, QScriptEngine *engine) { QByteArray *array = new QByteArray(argument(0, context).toAscii()); QScriptValue object = engine->newQObject(new QBuffer(array)); setQBufferProperties(object, engine); return object; } void setQBufferProperties(QScriptValue value, QScriptEngine *engine) { setQIODeviceProperties(value, engine); value.setProperty("setData", engine->newFunction(QBuffer_setData)); value.setProperty("data", engine->newFunction(QBuffer_data)); } QScriptValue QBuffer_setData(QScriptContext *context, QScriptEngine *) { QBuffer *self = getself(context); self->setData(argument(0, context).toAscii()); return QScriptValue(); } QScriptValue QBuffer_data(QScriptContext *context, QScriptEngine *) { QBuffer *self = getself(context); return QScriptValue(QString(self->data())); } @* Scripting QXmlQuery. \noindent Sometimes we have some XML data in a file or a buffer and we would like to extract certain information from that data in the host environment. Rather than write complicated string manipulation routines in an attempt to deal with this sensibly, we can use the XQuery language to extract the information we want. One common use case for this is extracting all measurements from a roast profile that are associated with an annotation. @= QScriptValue constructXQuery(QScriptContext *context, QScriptEngine *engine); QScriptValue XQuery_bind(QScriptContext *context, QScriptEngine *engine); QScriptValue XQuery_exec(QScriptContext *context, QScriptEngine *engine); QScriptValue XQuery_setQuery(QScriptContext *context, QScriptEngine *engine); QScriptValue XQuery_invalidate(QScriptContext *context, QScriptEngine *engine); void setXQueryProperties(QScriptValue value, QScriptEngine *engine); @ The constructor must be registered with the host environment. This is done a bit differently from most classes as |QXmlQuery| is not a |QObject|. @= constructor = engine->newFunction(constructXQuery); engine->globalObject().setProperty("XQuery", constructor); @ The constructor just needs to make sure the functions we want to make available are applied. A method is also provided to free the |QXmlQuery|. @= QScriptValue constructXQuery(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->toScriptValue(new QXmlQuery); setXQueryProperties(object, engine); return object; } QScriptValue XQuery_invalidate(QScriptContext *context, QScriptEngine *) { QXmlQuery *self = getself(context); delete self; return QScriptValue(); } void setXQueryProperties(QScriptValue value, QScriptEngine *engine) { value.setProperty("bind", engine->newFunction(XQuery_bind)); value.setProperty("exec", engine->newFunction(XQuery_exec)); value.setProperty("setQuery", engine->newFunction(XQuery_setQuery)); value.setProperty("invalidate", engine->newFunction(XQuery_invalidate)); } @ The |bind()| property can be used to specify a |QIODevice| to be referenced by a variable within a query. @= QScriptValue XQuery_bind(QScriptContext *context, QScriptEngine *) { QXmlQuery *self = getself(context); QIODevice *buffer = argument(1, context); self->bindVariable(argument(0, context), buffer); return QScriptValue(); } @ A method is also required for setting the query we wish to conduct. @= QScriptValue XQuery_setQuery(QScriptContext *context, QScriptEngine *) { QXmlQuery *self = getself(context); self->setQuery(argument(0, context)); return QScriptValue(); } @ This method runs the previously specified query. @= QScriptValue XQuery_exec(QScriptContext *context, QScriptEngine *) { QXmlQuery *self = getself(context); QString result; self->evaluateTo(&result); return QScriptValue(result); } @* Scripting QXmlStreamWriter. \noindent There are some cases where it may be desirable to produce XML from the host environment. While there are several ways to accomplish this, the |QXmlStreamWriter| class greatly simplifies generating complex XML documents. This class is not related to |QObject|, so several functions are needed to expose the functionality of this class to the host environment. @= QScriptValue constructXmlWriter(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_setDevice(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeAttribute(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeCDATA(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeCharacters(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeDTD(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeEmptyElement(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeEndDocument(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeEndElement(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeEntityReference(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeProcessingInstruction(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeStartDocument(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeStartElement(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlWriter_writeTextElement(QScriptContext *context, QScriptEngine *engine); void setXmlWriterProperties(QScriptValue value, QScriptEngine *engine); @ The constructor must be registered with the host environment. @= constructor = engine->newFunction(constructXmlWriter); engine->globalObject().setProperty("XmlWriter", constructor); @ The constructor takes an optional argument allowing the output device to be specified. @= QScriptValue constructXmlWriter(QScriptContext *context, QScriptEngine *engine) { QXmlStreamWriter *retval; if(context->argumentCount() == 1) { retval = new QXmlStreamWriter(argument(0, context)); } else { retval = new QXmlStreamWriter; } QScriptValue object = engine->toScriptValue(retval); setXmlWriterProperties(object, engine); return object; } void setXmlWriterProperties(QScriptValue value, QScriptEngine *engine) { value.setProperty("setDevice", engine->newFunction(XmlWriter_setDevice)); value.setProperty("writeAttribute", engine->newFunction(XmlWriter_writeAttribute)); value.setProperty("writeCDATA", engine->newFunction(XmlWriter_writeCDATA)); value.setProperty("writeCharacters", engine->newFunction(XmlWriter_writeCharacters)); value.setProperty("writeDTD", engine->newFunction(XmlWriter_writeDTD)); value.setProperty("writeEmptyElement", engine->newFunction(XmlWriter_writeEmptyElement)); value.setProperty("writeEndDocument", engine->newFunction(XmlWriter_writeEndDocument)); value.setProperty("writeEndElement", engine->newFunction(XmlWriter_writeEndElement)); value.setProperty("writeEntityReference", engine->newFunction(XmlWriter_writeEntityReference)); value.setProperty("writeProcessingInstruction", engine->newFunction(XmlWriter_writeProcessingInstruction)); value.setProperty("writeStartDocument", engine->newFunction(XmlWriter_writeStartDocument)); value.setProperty("writeStartElement", engine->newFunction(XmlWriter_writeStartElement)); value.setProperty("writeTextElement", engine->newFunction(XmlWriter_writeTextElement)); } @ If the output device needs to be changed or if one is not passed to the constructor, the |setDevice()| method can be used. @= QScriptValue XmlWriter_setDevice(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); QIODevice *device = argument(0, context); self->setDevice(device); return QScriptValue(); } @ The remaining functions are simple wrappers used for writing various types of data. After creating a writer and setting the output device, the start of the document should be written. One argument is required containing the XML version number. Another function handles writing the end of the document. @= QScriptValue XmlWriter_writeStartDocument(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeStartDocument(argument(0, context)); return QScriptValue(); } QScriptValue XmlWriter_writeEndDocument(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeEndDocument(); return QScriptValue(); } @ After the start of the document, a DTD is commonly needed. @= QScriptValue XmlWriter_writeDTD(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeDTD(argument(0, context)); return QScriptValue(); } @ After this, elements need to be written. For this, we write the start element, any attributes needed, character data, and the end element. @= QScriptValue XmlWriter_writeStartElement(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeStartElement(argument(0, context)); return QScriptValue(); } QScriptValue XmlWriter_writeAttribute(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeAttribute(argument(0, context), argument(1, context)); return QScriptValue(); } QScriptValue XmlWriter_writeCharacters(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeCharacters(argument(0, context)); return QScriptValue(); } QScriptValue XmlWriter_writeEndElement(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeEndElement(); return QScriptValue(); } @ For convenience, two other methods are provided for writing elements. Elements which do not require anything between the start and end elements can be created with |writeEmptyElement()|. Elements which do not require attributes, but do contain text can be created with |writeTextElement()|. @= QScriptValue XmlWriter_writeEmptyElement(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeEmptyElement(argument(0, context)); return QScriptValue(); } QScriptValue XmlWriter_writeTextElement(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeTextElement(argument(0, context), argument(1, context)); return QScriptValue(); } @ Less commonly needed are functions for writing CDATA sections, entity references, and processing instructions. @= QScriptValue XmlWriter_writeCDATA(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeCDATA(argument(0, context)); return QScriptValue(); } QScriptValue XmlWriter_writeEntityReference(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeEntityReference(argument(0, context)); return QScriptValue(); } QScriptValue XmlWriter_writeProcessingInstruction(QScriptContext *context, QScriptEngine *) { QXmlStreamWriter *self = getself(context); self->writeProcessingInstruction(argument(0, context), argument(1, context)); return QScriptValue(); } @* Scripting QXmlStreamReader. \noindent When a serializer is written using |QXmlStreamWriter|, a corresponding deserializer should also be written. While there are several possible ways to do this, using |QXmlStreamReader| is often the best choice. \pn{} provides a subset of the functionality from this class which should be adequate for most purposes. @= QScriptValue constructXmlReader(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_atEnd(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_attribute(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_hasAttribute(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_isDTD(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_isStartElement(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_name(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_readElementText(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_readNext(QScriptContext *context, QScriptEngine *engine); QScriptValue XmlReader_text(QScriptContext *context, QScriptEngine *engine); void setXmlReaderProperties(QScriptValue value, QScriptEngine *engine); @ The constructor must be registered with the host environment. @= constructor = engine->newFunction(constructXmlReader); engine->globalObject().setProperty("XmlReader", constructor); @ The constructor requires an argument specifying the output device. This can be any |QIODevice|. The |open()| method must be called on the device before passing it as an argument to this function. @= QScriptValue constructXmlReader(QScriptContext *context, QScriptEngine *engine) { QXmlStreamReader *retval = new QXmlStreamReader(argument(0, context)); QScriptValue object = engine->toScriptValue(retval); setXmlReaderProperties(object, engine); return object; } void setXmlReaderProperties(QScriptValue value, QScriptEngine *engine) { value.setProperty("atEnd", engine->newFunction(XmlReader_atEnd)); value.setProperty("attribute", engine->newFunction(XmlReader_attribute)); value.setProperty("hasAttribute", engine->newFunction(XmlReader_hasAttribute)); value.setProperty("isDTD", engine->newFunction(XmlReader_isDTD)); value.setProperty("isStartElement", engine->newFunction(XmlReader_isStartElement)); value.setProperty("name", engine->newFunction(XmlReader_name)); value.setProperty("readElementText", engine->newFunction(XmlReader_readElementText)); value.setProperty("readNext", engine->newFunction(XmlReader_readNext)); value.setProperty("text", engine->newFunction(XmlReader_text)); } @ Most of the functions are simple member function wrappers. Two of these properties are not. These are the |attribute()| and |hasAttribute()| properties. @= QScriptValue XmlReader_attribute(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); QString retval = self->attributes().value(argument(0, context)).toString(); return QScriptValue(retval); } QScriptValue XmlReader_hasAttribute(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); bool retval = self->attributes().hasAttribute(argument(0, context)); return QScriptValue(retval); } @ Other properties can be used for determining how to proceed with the processing. @= QScriptValue XmlReader_atEnd(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); return QScriptValue(self->atEnd()); } QScriptValue XmlReader_isDTD(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); return QScriptValue(self->isDTD()); } QScriptValue XmlReader_isStartElement(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); return QScriptValue(self->isStartElement()); } @ We move from one element to the next with the |readNext()| property. @= QScriptValue XmlReader_readNext(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); self->readNext(); return QScriptValue(); } @ The remaining properties return the element name and text. @= QScriptValue XmlReader_name(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); return QScriptValue(self->name().toString()); } QScriptValue XmlReader_readElementText(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); return QScriptValue(self->readElementText()); } QScriptValue XmlReader_text(QScriptContext *context, QScriptEngine *) { QXmlStreamReader *self = getself(context); return QScriptValue(self->text().toString()); } @* Scripting QSettings. \noindent Rather than have a script create a |QSettings| object when it needs to save or load settings, the object is provided along with properties for getting and setting values. Two functions are needed for this along with a third which ensures any properties added to |QObject| are also available to |QSettings| from the host environment. @= QScriptValue QSettings_value(QScriptContext *context, QScriptEngine *engine); QScriptValue QSettings_setValue(QScriptContext *context, QScriptEngine *engine); void setQSettingsProperties(QScriptValue value, QScriptEngine *engine); @ The object with properties for these functions is passed to the scripting engine. @= value = engine->newQObject(&settings); setQSettingsProperties(value, engine); engine->globalObject().setProperty("QSettings", value); @ Adding properties to the |QSettings| object should seem familiar. @= void setQSettingsProperties(QScriptValue value, QScriptEngine *engine) { setQObjectProperties(value, engine); value.setProperty("value", engine->newFunction(QSettings_value)); value.setProperty("setValue", engine->newFunction(QSettings_setValue)); } @ When getting a value from saved settings, there is the possibility that there will not be a value saved for the requested key. An optional second argument can be used to supply a default value. @= QScriptValue QSettings_value(QScriptContext *context, QScriptEngine *engine) { QScriptValue object; if(context->argumentCount() == 1 || context->argumentCount() == 2) { QSettings settings; QString key = argument(0, context); QVariant value; QVariant retval; if(context->argumentCount() > 1) { value = argument(1, context); retval = settings.value(key, value); } else { retval = settings.value(key); } object = engine->newVariant(retval); } else { context->throwError("Incorrect number of arguments passed to "@| "QSettings::value(). This method takes one "@| "string and one optional variant type."); } return object; } QScriptValue QSettings_setValue(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 2) { QSettings settings; QString key = argument(0, context); QVariant value = argument(1, context); settings.setValue(key, value); } else { context->throwError("Incorrect number of arguments passed to "@| "QSettings::setValue(). This method takes one "@| "string and one variant type for a total of two "@| "arguments."); } return QScriptValue(); } @* Scripting QLCDNumber. \noindent |QLCDNumber| is used as a base class for \pn{}'@q'@>s |TemperatureDisplay| and |TimerDisplay| classes, but it can also be used on its own for the display of mainly numeric information. @= QScriptValue constructQLCDNumber(QScriptContext *context, QScriptEngine *engine); void setQLCDNumberProperties(QScriptValue value, QScriptEngine *engine); @ The constructor must be passed to the scripting engine. @= constructor = engine->newFunction(constructQLCDNumber); value = engine->newQMetaObject(&QLCDNumber::staticMetaObject, constructor); engine->globalObject().setProperty("QLCDNumber", value); @ There is nothing special about the implementation. @= QScriptValue constructQLCDNumber(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QLCDNumber()); setQLCDNumberProperties(object, engine); return object; } void setQLCDNumberProperties(QScriptValue value, QScriptEngine *engine) { setQFrameProperties(value, engine); } @* Scripting QTime. \noindent |QTime| is a little different from the classes examined so far. This class can be used for synchonizing time among various objects by creating a common base reference time. This should not be needed as ECMA-262 already specifies a |Date| class, however this has historically been troublesome to use. One thing that makes this class different is that it is not related to |QObject|. This makes usefully exposing it to the scripting engine a little more difficult. @= QScriptValue constructQTime(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_addMSecs(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_addSecs(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_elapsed(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_hour(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_isNull(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_isValid(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_minute(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_msec(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_msecsTo(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_restart(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_second(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_secsTo(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_setHMS(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_start(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_toString(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_currentTime(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_fromString(QScriptContext *context, QScriptEngine *engine); QScriptValue QTime_valueOf(QScriptContext *context, QScriptEngine *engine); void setQTimeProperties(QScriptValue value, QScriptEngine *engine); @ We must tell the script engine about the constructor. This is not done in quite the same way as is done for |QObject| derived types. @= constructor = engine->newFunction(constructQTime); engine->globalObject().setProperty("QTime", constructor); @ The constructor has a couple interesting twists. The first is the ability to accept a variable number of integer arguments. The other is that |QTime| is not derived from |QObject|. The lack of |break| statements in the |switch| is intended. @= QScriptValue constructQTime(QScriptContext *context, QScriptEngine *engine) { QScriptValue object; if(context->argumentCount() == 0 || (context->argumentCount() >= 2 && context->argumentCount() <= 4))@/ { int arg1 = 0; int arg2 = 0; int arg3 = 0; int arg4 = 0; switch(context->argumentCount()) {@t\1@>@/ case 4:@/ arg4 = argument(3, context); case 3:@/ arg3 = argument(2, context); case 2:@/ arg2 = argument(1, context); arg1 = argument(0, context); default:@/ break;@t\2@>@/ } if(context->argumentCount()) { object = engine->toScriptValue(QTime(arg1, arg2, arg3, arg4)); } else { object = engine->toScriptValue(QTime()); } setQTimeProperties(object, engine); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::QTime(). This method takes zero, two, "@| "three, or four integer arguments."); } return object; } @ In order to use the various |QTime| methods, we must add wrapper functions as properties of newly created script objects. The last two of these should really be callable without starting from an existing |QTime|. @= void setQTimeProperties(QScriptValue value, QScriptEngine *engine) { value.setProperty("addMSecs", engine->newFunction(QTime_addMSecs)); value.setProperty("addSecs", engine->newFunction(QTime_addSecs)); value.setProperty("elapsed", engine->newFunction(QTime_elapsed)); value.setProperty("hour", engine->newFunction(QTime_hour)); value.setProperty("isNull", engine->newFunction(QTime_isNull)); value.setProperty("isValid", engine->newFunction(QTime_isValid)); value.setProperty("minute", engine->newFunction(QTime_minute)); value.setProperty("msec", engine->newFunction(QTime_msec)); value.setProperty("msecsTo", engine->newFunction(QTime_msecsTo)); value.setProperty("restart", engine->newFunction(QTime_restart)); value.setProperty("second", engine->newFunction(QTime_second)); value.setProperty("secsTo", engine->newFunction(QTime_secsTo)); value.setProperty("setHMS", engine->newFunction(QTime_setHMS)); value.setProperty("start", engine->newFunction(QTime_start)); value.setProperty("toString", engine->newFunction(QTime_toString)); value.setProperty("currentTime", engine->newFunction(QTime_currentTime)); value.setProperty("fromString", engine->newFunction(QTime_fromString)); value.setProperty("valueOf", engine->newFunction(QTime_valueOf)); } @ The |valueOf()| method exposes a numeric representation of the time suitable for use in comparing two time values. With this it is possible to take two |QTime| values in script code {\tt t1} and {\tt t2} and get the expected results from {\tt t1 == t2}, {\tt t1 < t2}, {\tt t1 > t2} and similar comparative operations. @= QScriptValue QTime_valueOf(QScriptContext *context, QScriptEngine *) { QTime self = getself(context); int retval = (self.hour() * 60 * 60 * 1000) + (self.minute() * 60 * 1000) + (self.second() * 1000) + self.msec(); return QScriptValue(retval); } @ These functions are effectively wrapper functions around existing |QTime| functionality with some error checking for the scripting engine. The |addMSecs()| and |addSecs()| methods return a new |QTime| object. @= QScriptValue QTime_addMSecs(QScriptContext *context, QScriptEngine *engine) { QTime time; QScriptValue retval; if(context->argumentCount() == 1) { QTime self = getself(context); time = self.addMSecs(argument(0, context)); retval = engine->toScriptValue(time); setQTimeProperties(retval, engine); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::addMSecs(). This method takes one "@| "integer as an argument."); } return retval; } QScriptValue QTime_addSecs(QScriptContext *context, QScriptEngine *engine) { QTime time; QScriptValue retval; if(context->argumentCount() == 1) { QTime self = getself(context); time = self.addSecs(argument(0, context)); retval = engine->toScriptValue(time); setQTimeProperties(retval, engine); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::addSecs(). This method takes one "@| "integer as an argument."); } return retval; } @ The |elapsed()| method returns an integer value. @= QScriptValue QTime_elapsed(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.elapsed()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::elapsed(). This method takes no "@| "arguments."); } return retval; } @ The |hour()|, |minute()|, |second()| and |msec()| methods return an integer with various parts of the time. The |hour()| method is typical of these methods. @= QScriptValue QTime_hour(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.hour()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::hour(). This method takes no "@| "arguments."); } return retval; } @ The |minute()|, |second()|, and |msec()| methods are implemented similarly. @= QScriptValue QTime_minute(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.minute()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::minute(). This method takes no "@| "arguments."); } return retval; } QScriptValue QTime_second(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.second()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::second(). This method takes no "@| "arguments."); } return retval; } QScriptValue QTime_msec(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.msec()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::msec(). This method takes no "@| "arguments."); } return retval; } @ The |isNull()| and |isValid()| methods return a boolean value. A |QTime| is considered null if it was created with a constructor with no arguments. It is considered invalid if it is null or if part of the time is out of range. @= QScriptValue QTime_isNull(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.isNull()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::isNull(). This method takes no "@| "arguments."); } return retval; } QScriptValue QTime_isValid(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.isValid()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::isValid(). This method takes no "@| "arguments."); } return retval; } @ The |secsTo()| and |msecsTo()| methods return an integer value indicating the number of seconds or milliseconds until a |QTime| argument. @= QScriptValue QTime_msecsTo(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 1) { QTime self = getself(context); QTime arg = argument(0, context).toTime(); retval = QScriptValue(engine, self.msecsTo(arg)); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::msecsTo(). This method takes one QTime."); } return retval; } QScriptValue QTime_secsTo(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 1) { QTime self = getself(context); QTime arg = argument(0, context).toTime(); retval = QScriptValue(engine, self.secsTo(arg)); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::secsTo(). This method takes one QTime."); } return retval; } @ The |start()| and |restart()| methods each set the value of the |QTime()| to the current time. The |restart()| method additionally returns the same value as the |elapsed()| method. @= QScriptValue QTime_restart(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 0) { QTime self = getself(context); retval = QScriptValue(engine, self.restart()); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::restart(). This method takes no "@| "arguments."); } return retval; } QScriptValue QTime_start(QScriptContext *context, QScriptEngine *) { if(context->argumentCount() == 0) { QTime self = getself(context); self.start(); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::start(). This method takes no arguments."); } return QScriptValue(); } @ The slightly inappropriately named |setHMS()| method changes the current value of the time and returns a boolean to indicate if the new time value is valid. @= QScriptValue QTime_setHMS(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 3 || context->argumentCount() == 4) { QTime self = getself(context); int arg1 = 0; int arg2 = 0; int arg3 = 0; int arg4 = 0; switch(context->argumentCount())@/ {@t\1@>@/ case 4:@/ arg4 = argument(3, context); case 3:@/ arg3 = argument(2, context); arg2 = argument(1, context); arg1 = argument(0, context); default:@/ break;@t\2@>@/ } retval = QScriptValue(engine, self.setHMS(arg1, arg2, arg3, arg4)); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::setHMS(). This method takes three or "@| "four integer arguments."); } return retval; } @ The |toString()| method returns a string representation of the time. See the Qt documentation for instructions on creating a valid format string. @= QScriptValue QTime_toString(QScriptContext *context, QScriptEngine *engine) { QScriptValue retval; if(context->argumentCount() == 1) { QTime self = getself(context); retval = QScriptValue(engine, self.toString(argument(0, context))); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::toString(). This method takes one QString "@| "as an argument."); } return retval; } @ The |currentTime()| and |fromString()| methods return a new |QTime| object. These methods make no reference to the any other existing |QTime|. @= QScriptValue QTime_currentTime(QScriptContext *, QScriptEngine *engine) { QScriptValue object; object = engine->toScriptValue(QTime::currentTime()); setQTimeProperties(object, engine); return object; } QScriptValue QTime_fromString(QScriptContext *context, QScriptEngine *engine) { QScriptValue object; if(context->argumentCount() == 2) { QString time = argument(0, context); QString format = argument(1, context); object = engine->toScriptValue(QTime::fromString(time, format)); setQTimeProperties(object, engine); } else { context->throwError("Incorrect number of arguments passed to "@| "QTime::fromString(). This method takes two "@| "string arguments."); } return object; } @ In order to pass |QTime| objects back from a script, we also need to overload |argument()| for this type. @= template<> QTime argument(int arg, QScriptContext *context) { return qscriptvalue_cast(context->argument(arg)); } @* Scripting QColor. \noindent |QColor| support is limited to creating colors from strings to pass to objects expecting a color. @= QScriptValue constructQColor(QScriptContext *context, QScriptEngine *engine); @ We must tell the script engine about the constructor. This is not done in quite the same way as is done for |QObject| derived types. @= constructor = engine->newFunction(constructQColor); engine->globalObject().setProperty("QColor", constructor); @ The constructor is trivial. @= QScriptValue constructQColor(QScriptContext *context, QScriptEngine *engine) { QScriptValue object = engine->toScriptValue(QColor(argument(0, context))); return object; } @* Scripting QBrush. \noindent |QBrush| support is limited to creating brushes from color strings to pass to objects expecting a brush. @= QScriptValue constructQBrush(QScriptContext *context, QScriptEngine *engine); @ The script is informed of the constructor. @= constructor = engine->newFunction(constructQBrush); engine->globalObject().setProperty("QBrush", constructor); @ The constructor is trivial. @= QScriptValue constructQBrush(QScriptContext *context, QScriptEngine *engine) { QBrush theBrush = QBrush(QColor(argument(0, context))); QScriptValue object = engine->toScriptValue(theBrush); return object; } @* Scripting Item View Classes. \noindent |QAbstractScrollArea| is a |QFrame| that serves as the base class for classes such as |QGraphicsView| and |QAbstractItemView|. Objects from this class are not created directly. @= void setQAbstractScrollAreaProperties(QScriptValue value, QScriptEngine *engine); @ The implementation of this is simple. @= void setQAbstractScrollAreaProperties(QScriptValue value, QScriptEngine *engine) { setQFrameProperties(value, engine); } @ This class is used by the |QAbstractItemView| class. This is another class that we do not need a script constructor for. @= void setQAbstractItemViewProperties(QScriptValue value, QScriptEngine *engine); @ This function has another simple implementation. @= void setQAbstractItemViewProperties(QScriptValue value, QScriptEngine *engine) { setQAbstractScrollAreaProperties(value, engine); } @ The |QGraphicsView| and |QTableView| classes form the base of \pn{} classes. @= void setQGraphicsViewProperties(QScriptValue value, QScriptEngine *engine); void setQTableViewProperties(QScriptValue value, QScriptEngine *engine); @ Again, the implementations are boring. @= void setQGraphicsViewProperties(QScriptValue value, QScriptEngine *engine) { setQAbstractScrollAreaProperties(value, engine); } void setQTableViewProperties(QScriptValue value, QScriptEngine *engine) { setQAbstractItemViewProperties(value, engine); } @* Scripting Button Classes. \noindent \pn{} provides an |AnnotationButton| class which is a special kind of |QPushButton| which in turn comes from |QAbstractButton|. While |AnnotationButton| can be used in exactly the same way as a |QPushButton|, if an annotation is not needed, there is little reason not to use the base class. @= void setQAbstractButtonProperties(QScriptValue value, QScriptEngine *engine); void setQPushButtonProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQPushButton(QScriptContext *context, QScriptEngine *engine); @ The constructor for |QPushButton| should be passed to the scripting engine. @= constructor = engine->newFunction(constructQPushButton); value = engine->newQMetaObject(&QPushButton::staticMetaObject, constructor); engine->globalObject().setProperty("QPushButton", value); @ The implementation should seem familiar. @= QScriptValue constructQPushButton(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new QPushButton()); setQPushButtonProperties(object, engine); return object; } void setQPushButtonProperties(QScriptValue value, QScriptEngine *engine) { setQAbstractButtonProperties(value, engine); } void setQAbstractButtonProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); } @* Scripting QSqlQuery. \noindent With this class exposed to the host environment, it becomes possible for script code to execute SQL queries and evaluate the result. Rather than use |QSqlQuery| directly, however, we use a proxy \nfnote{Erich Gamma, Richard Helm, Raph Johnson, and John Vlissides,\par\indent\underbar{Design Patterns: elements of reusable object-oriented software} (1995) pp. 207--217} class. This class obtains its own database connection and handles properly closing and removing these connections when the query object is destroyed. @= class SqlQueryConnection : public QSqlQuery@/ { public:@/ SqlQueryConnection(const QString &query = QString()); ~SqlQueryConnection(); QSqlQuery* operator->() const; private:@/ QString connection; QSqlQuery *q; }; @ The constructor can be somewhat simplified from the four forms of |QSqlQuery|. We are not interested in creating an object from a |QSqlResult| or from another |QSqlQuery|. The database connection is managed by the class itself so the constructor only needs an optional string containing a query. This is used to initialize a real |QSqlQuery| object. @= SqlQueryConnection::SqlQueryConnection(const QString &query) { QSqlDatabase database = AppInstance->database(); database.open(); q = new QSqlQuery(query, database); connection = database.connectionName(); } @ The destructor handles removing the |QSqlQuery| and the database connection associated with it. The extra brackets introduce a new scope for the |QSqlDatabase| so that it is out of scope when the connection is removed. @= SqlQueryConnection::~SqlQueryConnection() { delete q; { QSqlDatabase database = QSqlDatabase::database(connection); database.close(); } QSqlDatabase::removeDatabase(connection); } @ For all other functionality, we simply forward the request to our |QSqlQuery| object. @= QSqlQuery* SqlQueryConnection::operator->() const { return q; } @ In order to use this new class in the host environment, a number of functions are needed. @= void setQSqlQueryProperties(QScriptValue value, QScriptEngine *engine); QScriptValue constructQSqlQuery(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_bind(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_bindDeviceData(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_bindFileData(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_exec(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_executedQuery(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_invalidate(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_next(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_prepare(QScriptContext *context, QScriptEngine *engine); QScriptValue QSqlQuery_value(QScriptContext *context, QScriptEngine *engine); @ For conceptual convenience we simply pretend that we are working with a real |QSqlQuery| object. @= constructor = engine->newFunction(constructQSqlQuery); engine->globalObject().setProperty("QSqlQuery", constructor); @ With connection creation no longer needed in the constructor, all that is needed is object creation and applying the appropriate properties to the script value. @= QScriptValue constructQSqlQuery(QScriptContext *, QScriptEngine *engine) { SqlQueryConnection *obj = new SqlQueryConnection(); QScriptValue object = engine->toScriptValue(obj); setQSqlQueryProperties(object, engine); return object; } @ As this class does not derive from |QObject|, we must wrap all of the methods we might want to use. @= void setQSqlQueryProperties(QScriptValue value, QScriptEngine *engine) { value.setProperty("bind", engine->newFunction(QSqlQuery_bind)); value.setProperty("bindFileData", engine->newFunction(QSqlQuery_bindFileData)); value.setProperty("bindDeviceData", engine->newFunction(QSqlQuery_bindDeviceData)); value.setProperty("exec", engine->newFunction(QSqlQuery_exec)); value.setProperty("executedQuery", engine->newFunction(QSqlQuery_executedQuery)); value.setProperty("invalidate", engine->newFunction(QSqlQuery_invalidate)); value.setProperty("next", engine->newFunction(QSqlQuery_next)); value.setProperty("prepare", engine->newFunction(QSqlQuery_prepare)); value.setProperty("value", engine->newFunction(QSqlQuery_value)); } @ Most of these properties are wrappers around existing |QSqlQuery| functions. @= QScriptValue QSqlQuery_exec(QScriptContext *context, QScriptEngine *engine) { QSqlQuery *q = getself(context)->operator->(); QScriptValue retval; if(context->argumentCount() == 1) { retval = QScriptValue(engine, q->exec(argument(0, context))); } else { retval = QScriptValue(engine, q->exec()); } if(q->lastError().isValid()) { qDebug() << q->lastQuery(); qDebug() << q->lastError().text(); } return retval; } QScriptValue QSqlQuery_executedQuery(QScriptContext *context, QScriptEngine *) { QSqlQuery *query = getself(context)->operator->(); return QScriptValue(query->lastQuery()); } QScriptValue QSqlQuery_next(QScriptContext *context, QScriptEngine *engine) { QSqlQuery *query = getself(context)->operator->(); return QScriptValue(engine, query->next()); } QScriptValue QSqlQuery_value(QScriptContext *context, QScriptEngine *engine) { QSqlQuery *query = getself(context)->operator->(); return QScriptValue(engine, query->value(argument(0, context)).toString()); } @ For prepared queries, we support binding variables available to the script, data available in a named file, or data from any open |QIODevice|. @= QScriptValue QSqlQuery_prepare(QScriptContext *context, QScriptEngine *engine) { QSqlQuery *query = getself(context)->operator->(); return QScriptValue(engine, query->prepare(argument(0, context))); } QScriptValue QSqlQuery_bind(QScriptContext *context, QScriptEngine *) { QSqlQuery *query = getself(context)->operator->(); query->bindValue(argument(0, context), argument(1, context)); return QScriptValue(); } QScriptValue QSqlQuery_bindFileData(QScriptContext *context, QScriptEngine *) { QSqlQuery *query = getself(context)->operator->(); QString placeholder = argument(0, context); QString filename = argument(1, context); QFile file(filename); QByteArray data; if(file.open(QIODevice::ReadOnly)) { data = file.readAll(); file.close(); } query->bindValue(placeholder, data); return QScriptValue(); } QScriptValue QSqlQuery_bindDeviceData(QScriptContext *context, QScriptEngine *) { QSqlQuery *query = getself(context)->operator->(); QString placeholder = argument(0, context); QIODevice *device = argument(1, context); device->reset(); QByteArray data; data = device->readAll(); query->bindValue(placeholder, data); return QScriptValue(); } @ To avoid leaking database connections, we add the |invalidate()| property which destroys our object. The object on which this method is called must not be used after calling this method. In script code this will typically be used as in the following example: {\tt query = query.invalidate();} @= QScriptValue QSqlQuery_invalidate(QScriptContext *context, QScriptEngine *) { SqlQueryConnection *query = getself(context); delete query; return QScriptValue::UndefinedValue; } @* Other scripting functions. \noindent There are a few functions that are exposed to the scripting engine that are not associated with any class. Two functions are used for extracting information from file names. Another is used to construct array values from SQL array values. There is also a function for setting the default font for the application or some part of the application. @= QScriptValue baseName(QScriptContext *context, QScriptEngine *engine); QScriptValue dir(QScriptContext *context, QScriptEngine *engine); QScriptValue sqlToArray(QScriptContext *context, QScriptEngine *engine); QScriptValue setFont(QScriptContext *context, QScriptEngine *engine); QScriptValue annotationFromRecord(QScriptContext *context, QScriptEngine *engine); QScriptValue setTabOrder(QScriptContext *context, QScriptEngine *engine); QScriptValue saveFileFromDatabase(QScriptContext *context, QScriptEngine *engine); QScriptValue scriptTr(QScriptContext *context, QScriptEngine *engine); @ These functions are passed to the scripting engine. @= engine->globalObject().setProperty("baseName", engine->newFunction(baseName)); engine->globalObject().setProperty("dir", engine->newFunction(dir)); engine->globalObject().setProperty("sqlToArray", engine->newFunction(sqlToArray)); engine->globalObject().setProperty("setFont", engine->newFunction(setFont)); engine->globalObject().setProperty("annotationFromRecord", engine->newFunction(annotationFromRecord)); engine->globalObject().setProperty("setTabOrder", engine->newFunction(setTabOrder)); engine->globalObject().setProperty("saveFileFromDatabase", engine->newFunction(saveFileFromDatabase)); engine->globalObject().setProperty("TTR", engine->newFunction(scriptTr)); @ These functions are not part of an object. They expect a string specifying the path to a file and return a string with either the name of the file without the path and extension or the path of the directory containing the file. @= QScriptValue baseName(QScriptContext *context, QScriptEngine *engine) { QFileInfo info(argument(0, context)); QScriptValue retval(engine, info.baseName()); return retval; } QScriptValue dir(QScriptContext *context, QScriptEngine *engine) { QFileInfo info(argument(0, context)); QDir dir = info.dir(); QScriptValue retval(engine, dir.path()); return retval; } @ This function takes a file ID and a file name and copies file data stored in the database out to the file system. @= QScriptValue saveFileFromDatabase(QScriptContext *context, QScriptEngine *) { SqlQueryConnection h; QSqlQuery *query = h.operator->(); QString q = "SELECT file FROM files WHERE id = :file"; query->prepare(q); query->bindValue(":file", argument(0, context)); query->exec(); query->next(); QByteArray array = query->value(0).toByteArray(); QFile file(argument(1, context)); file.open(QIODevice::WriteOnly); file.write(array); file.close(); return QScriptValue(); } @ This function takes a string representing a SQL array and returns an array value. @= QScriptValue sqlToArray(QScriptContext *context, QScriptEngine *engine) { QString source = argument(0, context); source.remove(0, 1); source.chop(1); QStringList elements = source.split(","); QString element; QScriptValue dest = engine->newArray(elements.size()); int i = 0; foreach(element, elements) { if(element.startsWith("\"") && element.endsWith("\"")) { element.chop(1); element = element.remove(0, 1); } dest.setProperty(i, QScriptValue(engine, element)); i++; } return dest; } @ This function can be used to set the default font for the application or on a per-class hierarchy basis. @= QScriptValue setFont(QScriptContext *context, QScriptEngine *) { QString font = argument(0, context); QString classname; if(context->argumentCount() > 1) { classname = argument(1, context); QApplication::setFont(QFont(font), classname.toLatin1().constData()); } else { QApplication::setFont(QFont(font)); } return QScriptValue(); } @ This function was briefly used prior to adding support for |QXmlQuery| in the host environment. The function is now depreciated and should not be used. @= QScriptValue annotationFromRecord(QScriptContext *context, QScriptEngine *) { SqlQueryConnection h; QSqlQuery *query = h.operator->(); QString q = "SELECT file FROM files WHERE id = :file"; query->prepare(q); query->bindValue(":file", argument(0, context)); query->exec(); query->next(); QByteArray array = query->value(0).toByteArray(); QBuffer buffer(&array); buffer.open(QIODevice::ReadOnly); QXmlQuery xquery; xquery.bindVariable("profile", &buffer); QString xq; xq = "for $b in doc($profile) //tuple where exists($b/annotation) return $b"; xquery.setQuery(xq); QString result; xquery.evaluateTo(&result); return QScriptValue(result); } @ This function can be used to change the tab order for controls in Typica. Changes to the example configuration in \pn{} 1.4 made the default handling of tab controls in the logging window unacceptable. @= QScriptValue setTabOrder(QScriptContext *context, QScriptEngine *) { QWidget::setTabOrder(argument(0, context), argument(1, context)); return QScriptValue(); } @ This function is used to allow text that must be placed in scripts to be translated into other languages. @= QScriptValue scriptTr(QScriptContext *context, QScriptEngine *) { return QScriptValue(QCoreApplication::translate( "configuration", argument(1, context).toUtf8().data())); } @** Application Configuration. \noindent While \pn{} is intended as a data logging application, the diversity of equipment and supporting technology precludes the option of providing a single interface for common tasks. It is important that the application can be configured to work with different roasting equipment, databases, and the like. To accomplish this, \pn{} utilizes an XML description of the desired application configuration and provides an ECMA-262 host environment which allows application dataflow to be configured. The scripting environment provides access to elements of the XML file and also allows access to most of the application classes. A selection of classes provided by Qt is also available. See the section on The Scripting Engine for more details. \danger While the code is the ultimate documentation of what is possible with this interface, additional documentation should be provided to document the meaning of supported elements and the objects available through the scripting engine.\endanger The application configuration is loaded when the program is started. Starting with version 1.4, we check for a command line option with the path to the configuration file and load that instead of prompting for the information if possible. Starting with version 1.8, if there is not a -c argument, Typica will first search a small number of locations relative to the executable. @= QStringList arguments = QCoreApplication::arguments(); int position = arguments.indexOf("-c"); QString filename = QString(); if(position != -1) { if(arguments.size() >= position + 1) { filename = arguments.at(position + 1); } } else { QDir checkPath(QCoreApplication::applicationDirPath() + "/../config/"); if(checkPath.exists("config.xml")) { filename = checkPath.filePath("config.xml"); } else { checkPath = QDir(QCoreApplication::applicationDirPath() + "/config/"); if(checkPath.exists("config.xml")) { filename = checkPath.filePath("config.xml"); } } } if(filename.isEmpty()) { filename = QFileDialog::getOpenFileName(NULL, "Open Configuration File", settings.value("config", "").toString()); } QDir directory; if(!filename.isEmpty()) { QFile file(filename); QFileInfo info(filename); directory = info.dir(); QTextCodec::setCodecForTr(QTextCodec::codecForName("utf-8")); QTranslator *configtr = new QTranslator; if(configtr->load(QString("config.%1").arg(QLocale::system().name()), QString("%1/Translations").arg(directory.canonicalPath()))) { QCoreApplication::installTranslator(configtr); } settings.setValue("config", directory.path()); if(file.open(QIODevice::ReadOnly)) { app.configuration()->setContent(&file, true); } } else { return 1; } @@; @ The {\tt } element can contain an arbitrary number of {\tt } elements. These elements should not appear in the DOM. Instead, the element should be replaced by the content of the specified document. @= QDomElement root = app.configuration()->documentElement(); QDomNodeList children = root.childNodes(); QString replacementDoc; QDomDocument includedDoc; QDomDocumentFragment fragment; for(int i = 0; i < children.size(); i++) { QDomNode currentNode = children.at(i); QDomElement currentElement; if(currentNode.nodeName() == "include") { currentElement = currentNode.toElement(); if(currentElement.hasAttribute("src")) { replacementDoc = directory.path(); replacementDoc.append('/'); replacementDoc.append(currentElement.attribute("src")); QFile doc(replacementDoc); if(doc.open(QIODevice::ReadOnly)) { includedDoc.setContent(&doc, true); fragment = includedDoc.createDocumentFragment(); fragment.appendChild(includedDoc.documentElement()); root.replaceChild(fragment, currentNode); doc.close(); } } } } @ Simply loading the configuration document does not display a user interface or set up any objects that allow the program to do anything. To do this, a script obtained from the configuration document is run. The root element of the document should be {\tt }. This element should have a number of child elements including {\tt } elements which describe the various windows that can be opened in the application and {\tt } elements containing script code. These {\tt } elements can occur in a number of different contexts including within {\tt } elements which would indicate that such scripts should be evaluated when the window being described is created. After the configuration document is loaded, all {\tt } elements that are direct children of the {\tt } element are concatenated and the script is run. Before the script is run and user interface elements are drawn, we also check for {\tt