%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% pdfquiz.tex (Udo Hertrich-Jeromin, Tue Sep 12 23:03:42 BST 2006)
%%
%% commands:
%%
%%  \qtick[0:unchk;1:chk]{app}     - creates a tickbox
%%  \qnuml[1:true;0:false]res(tol) - numerical answer field
%%  \qhint[0:text]{app}            - provides a popup hint
%%  \qeval{app}                    - evaluates the current problem
%%
%%  \qtext(w,h)[id]{app}           - multiline text input
%%  \qline(w)[id]{app}             - line of text input
%%  \qsend[url;"f1",..,"fn"]{app}  - submit form fields,
%%                                   evaluate if \qprbno = 1
%%
%% variables:
%%
%%  \qprbno, \qoptno, \qnmlno      - count problem, tickboxes, num'l boxes
%%  \qmark[wd|ht|ho|vo]            - dimensions and offsets of mark boxes  
%%
%% form fields:
%%
%%  qtotal                         - contains [time,total,marks,penalties]
%%
%%
%% an example and technical details can be found at the end of this file
%%
%% another, much more complete macro package for producing pdf quizzes
%% using La(!)TeX can be found at http://www.acrotex.net
%%
%%
%% todo
%%
%% - investigate possibility of folding solutions
%% - think about saving filled in data & resuming quiz
%% - get rid of that stupid submit error "cannot handle"...
%%
%%
%% version history
%%
%% 06/09/12 - a few bug fixes in; evaluation does now not show
%%    a penalty of 0 marks any more; added documentation
%%
%% 06/02/10 - deal with submissions from browser and reader:
%%    \qsend now submits form field "access"
%%
%% 06/01/09 - initial release, available commands:
%%    \qtick, \qnuml, \qhint, \qeval, \qtext, \qline, \qsend, \qsave;
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\catcode`?=11
%% check for pdftex run:
\def\ifundef#1{\expandafter\ifx\csname#1\endcsname\relax}
\ifundef{pdfoutput}
   \message{ERROR: these macros do, so far, only work for pdftex!}
   \endinput\fi
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% markbox dimensions
\newdimen\qmarkwd \qmarkwd=96pt % marking box width
\newdimen\qmarkht \qmarkht=24pt % marking box height
\newdimen\qmarkho \qmarkho=0pt  % marking box horiz offset
\newdimen\qmarkvo \qmarkvo=0pt  % marking box vert offset
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% text input boxes
% #1/#2 - width/height
% #3 - name
% #4 - default text
% #5 - field flag
% #6/#7 - font size/color (r g b)
\def\q?textbox(#1,#2)[#3]#4\ff#5\da(#6,#7){%
   \vbox to #2{\hbox to #1{\pdfannot
      height 2pt
      width #1
      depth #2
      {
         /Subtype /Widget
         /FT /Tx
         /F 4
         /Ff #5
         /T (#3)
         /V (#4)
         /DA (/Helv #6 Tf #7 rg)
%%% reset no form fields upon cursor exit to accept field value:
         /AA << /X << /S /ResetForm /Fields [] /Flags 0 >> >>
%         /AA <<
%            /X  << /S /Hide /T (#3) /H true  >>
%            /Fo << /S /Hide /T (#3) /H false >>
%            >>
      }\hfill}\vfill}%
   }
%%% flag: 4096 - multiline
\def\qtext(#1,#2)[#3]#4{%
   \q?textbox(#1,#2)[#3]#4\ff4096\da(10,0 0 1)}
%%%
\newdimen\q?lineht \q?lineht=12pt
\def\qline(#1)[#2]#3{%
   \q?textbox(#1,\q?lineht)[#2]#3\ff0\da(10,0 0 1)}
%%% flag: 1 - readonly, 4096 - multiline
\def\q?gradebox[#1]{\relax%
   {\advance\qmarkho by -12pt\advance\qmarkvo by 22pt\rlap{\hglue\qmarkho%
   \vrule height 0pt depth 7pt width 0pt\smash{\hbox{\lower\qmarkvo%
   \q?textbox(\qmarkwd,\qmarkht)[#1]{}\ff4097\da(8,1 0 0)}}}}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% checkbox for options
% #1 - form field name
% #2 - appearance, e.g., "tick here" or "$\bigcirc$" or the like
\def\q?checkbox[#1]#2{\leavevmode\relax%
   \pdfstartlink
      user {/Subtype /Widget
      /BS << /W 1 /S /S >>          % annotation border style (pp490, 495)
      /F 4                          % annotation flag (pp490, 493)
      /T (#1)                       % form field name (p531)
      /FT /Btn                      % form field type (p531)
      /Ff 4                         % form field flag (pp531, 532)
      /V /Off                       % default value (pp531, 539)
%      /MK << /BG [] /BC [1 1 1] /CA (0) >>  % form field colors (p536)
%      /DA (/ZaDb 12 Tf 0 0 1 rg)    % variable text appearance (p534)
      /A << /Type /Action /S /JavaScript /JS (score.tickdecor("#1")) >>
      }\relax#2%
   \pdfendlink
   }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% embed javascript (code that belongs together should be together)
% #1 - script
\def\js?scr#1\endjs{
   \immediate\pdfobj{ << /S /JavaScript /JS (#1) >> }
   \immediate\pdfobj{ << /Names [(Doc Level JS) \the\pdflastobj\space 0 R] >> }
   \pdfnames{/JavaScript \the\pdflastobj\space 0 R}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% trigger js with button press
% #1 - appearance, #2 - script
\def\js?btn#1\js#2\endjs{\leavevmode\relax%
\pdfstartlink
   user {/Subtype /Link
   /C [1 0 0] /Border [0 0 0]
   /H /P % annotation highlighting mode (p512)
   /A << /Type /Action /S /JavaScript /JS (#2) >> }\relax#1%
\pdfendlink}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% appearance and start actions
\pdfcatalog {
   /PageMode /UseNone
   /PageLayout /SinglePage
%%%
   /ViewerPreferences <<
      % would be nice if we could fit the page into the window
      % properly...
      /FitWindow true
      /CenterWindow true
      /HideToolbar false
      /HideMenubar false
      /HideWindowUI false
      /DisplayDocTitle true
      >>
%%%
   /OpenAction <<
      /S /JavaScript /JS (
         %app.alert("debug: the document is opening!");
%%% produce a warning when the reader isn't good enough
         if (typeof app.viewerVersion == "undefined" || app.viewerVersion < 5)
            app.alert("This document needs Acrobat Reader v5 or higher!");
%         if (this.external == false)
%            app.alert("Run inside browser for data submission!");
%            %else
%            %app.alert("debug: ok, running in a browser!");
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% introducing the new object "score" and its properties and methods
% variable naming conventions:
%  M = \qprbno, N = \qoptno, S = [N,\q?scores], G = [N,\q?grades], Vi = #i
%%% we need a way to refer to the doc when "this" referes to another object
var quiz = this;
%%% score is a new object that carries most of the javascript stuff
var score = new Object();
score.evals = [0];
score.total = [0];
score.marks = [0];
score.pnlty = [0];
score.maxevals = 1;
score.otime = new Date();
%%% debugging function
score.debug = function (X,Y)
{
   app.alert("debug: "+X+"; "+Y);
};
%%%
score.tickdecor = function (id)
{
   var fld = quiz.getField(id);
   fld.borderStyle = border.u;
   fld.strokeColor = color.transparent;
   fld.style = style.ch;
   fld.textColor = color.blue;
   fld.textSize = 12;
};
%%% collect penalties before the first evaluation
score.penalize = function (M,marks,text)
{
   if (typeof this.pnlty[M] == "undefined") this.pnlty[M] = 0;
   if (typeof this.evals[M] == "undefined" || this.evals[M] < this.maxevals)
      this.pnlty[M] += marks - 0;
   app.alert(text+" (Penalty: "+marks+" marks)",3);
};
%%% init various variables for eval process
score.initvars = function (M)
{
   if (typeof this.evals[M] == "undefined")
      this.evals[M] = 0;
   if (typeof this.pnlty[M] == "undefined")
      this.pnlty[M] = 0;
   if (this.evals[M] == 0)
   {
      this.total[M] = 0;
      this.marks[M] = 0;
   }
   %app.alert("debug: "+score.evals[M]+score.total[M]+score.pnlty[M]);
};
%%% eval the tickboxes, leave results in score.marks and score.total
score.marktick = function (M,N,S,G)
{
   var qscores = S;
   var qgrades = G;
   var check = [0];
   var total = 0;
   var marks = 0;
   var optfs = "qticktick."+M;
   var mrkfs = "qtickmark."+M;
   %app.alert("debug: "+qscores+"; element: "+qscores[0]);
   for (var j = 1; j <= N; j++)
   {
      var optfld = quiz.getField(optfs+"."+j);
      var mrkfld = quiz.getField(mrkfs+"."+j);
      check[j] = optfld.isBoxChecked(0);
      optfld.readonly = true;
      %app.alert("debug: ("+qscores[j][check[j]]+") "+qgrades[j][check[j]]);
      mrkfld.value = "("+qscores[j][check[j]]+") "+qgrades[j][check[j]];
      mrkfld.display = display.visible;
      marks += qscores[j][check[j]];
      total += Math.max(qscores[j][0],qscores[j][1]);
   }
   this.marks[M] += marks;
   this.total[M] += total;
   %app.alert("debug: "+marks+"/"+total);
};
%%% eval the numerical answers, leave results in score.marks and score.total
score.marknuml = function (M,N,R,S,G)
{
   var qresult = R;
   var qscores = S;
   var qgrades = G;
   var check = [0];
   var total = 0;
   var marks = 0;
   var resfs = "qnumlrslt."+M;
   var mrkfs = "qnumlmark."+M;
   %app.alert("debug: "+qresult);
   for (var j = 1; j <= N; j++)
   {
      var resfld = quiz.getField(resfs+"."+j);
      var mrkfld = quiz.getField(mrkfs+"."+j);
      %app.alert("debug: given "+resfld.value+", should be "+qresult[j][0]);
      if (Math.abs(resfld.value-qresult[j][0]) <= qresult[j][1])
         check[j] = 0;
         else
         check[j] = 1;
      %app.alert("debug: "+check[j]);
      resfld.readonly = true;
      %app.alert("debug: ("+qscores[j][check[j]]+") "+qgrades[j][check[j]]);
      mrkfld.value = "("+qscores[j][check[j]]+") "+qgrades[j][check[j]];
      mrkfld.display = display.visible;
      marks += qscores[j][check[j]];
      total += Math.max(qscores[j][0],qscores[j][1]);
   }
   this.marks[M] += marks;
   this.total[M] += total;
   %app.alert("debug: "+marks+"/"+total);
};
%%% compute and show final mark from score.total, score.marks, score.pnlty
score.showmark = function (M)
{
   var mrkfld = quiz.getField("qevalmark."+M);
   var result = [this.total[M],this.marks[M],this.pnlty[M]];
   var average = Math.round(100*(result[1]-result[2])/result[0]);
   if (average < 0) average = 0;
   if (result[0] > 0)
      if (result[2] > 0)
         mrkfld.value = average+"\% (penalty: "+result[2]+")";
         else
         mrkfld.value = average+"\%";
      else
      mrkfld.value = "nothing to evaluate";
   mrkfld.display = display.visible;
   score.evals[M] += 1;
};
%%% compute total marks and store in a special, invisible markfield
score.evaltotal = function ()
{
   var sumfld = quiz.getField("qtotal");
   var totals = [0,0,0,0];
   totals[0] = this.timing();
   for (var j = 1; j <= this.evals.length-1; j++)
   {
      if (this.evals[j] > 0)
      {
         totals[1] += this.total[j];
         totals[2] += this.marks[j];
         totals[3] += this.pnlty[j];
      };
   };
   %app.alert("debug: "+totals);
   sumfld.value = totals;
   sumfld.display = display.hidden;
};
%%% read previously computed marks from persistent variable
score.initprops = function ()
{
   var qfs = quiz.filesize; % used to identify the quiz
   if (typeof global.scores == "undefined" ||
       global.scores[0][0] != qfs ||
       global.scores[0][1] == "ignore" ||
       app.alert("Load previously saved results?",2,2) == 3)
      global.scores = [[qfs,"ignore"],[0],[0],[0],[0]];
   this.evals = global.scores[1];
   this.total = global.scores[2];
   this.marks = global.scores[3];
   this.pnlty = global.scores[4];
   %app.alert("debug (score.evals): "+global.scores[0]+"/"+this.evals);
   global.setPersistent("scores",true);
   global.scores[0] = [qfs,"ignore"];
   %app.alert("debug: "+this.evals+"; length "+this.evals.length);
   for (var j = 1; j <= this.evals.length-1; j++)
   {
      if (this.evals[j] > 0) this.showmark(j);
   };
};
%%% save obtained marks in a persistent variable
score.saveprops = function ()
{
   var qfs = quiz.filesize;
   global.scores = [[qfs,"load"],this.evals,this.total,this.marks,this.pnlty];
   global.setPersistent("scores",true);
   %app.alert("debug: "+global.scores);
};
%%% compute (and return) minutes elapsed since doc opening
score.timing = function ()
{
   var otr = this.otime.getHours()+":"+this.otime.getMinutes();
   var otm = 60*this.otime.getHours()+this.otime.getMinutes();
   var ctime = new Date();
   var ctm = 60*ctime.getHours()+ctime.getMinutes();
   var min = ctm - otm;
   %app.alert("started "+otr+", that's "+min+"' ago");
   return min;
};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% read in previously saved results
         score.initprops();
%%%
      ) >>
%   /AA << /DC << /Type /Action
%      /S /JavaScript /JS (
%         app.alert("the document is about to close!");
%      ) >> >>
   }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% counting problems, tickboxes, etc...
\newcount\qprbno \qprbno=1
\newcount\qoptno \qoptno=0
\newcount\qnmlno \qnmlno=0
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% tickbox
% #1/#3 - marks for unticked/ticked box
% #2/#4 - grading text for unticked/ticked box
% #5 - appearance
\def\qtick[#1:#2;#3:#4]#5{\leavevmode%
   \global\advance\qoptno by 1%
   \relax% this is needed for a reliable comparison
% builds up an array with the available scores (read by \qeval)
   \ifnum\qoptno=1
      \xdef\q?scores{[#1,#3]}%
      \xdef\q?grades{["#2","#4"]}%
      \else
      \xdef\q?scores{\q?scores,[#1,#3]}%
      \xdef\q?grades{\q?grades,["#2","#4"]}%
   \fi
   %\immediate\write99{debug: \q?scores}%
   %\immediate\write99{debug: \q?grades}%
% creating the tickbox and the marks box
   \hbox{%
   \q?gradebox[qtickmark.\the\qprbno.\the\qoptno]%
   \q?checkbox[qticktick.\the\qprbno.\the\qoptno]{\strut#5}}%
   }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% numerical input
% #1/#2 - correct result/tolerance
% #3/#5 - marks for correct/false result
% #4/#6 - grading text for correct/false result
% #7 - appearance
\def\qnuml[#1:#2;#3:#4]#5(#6){\leavevmode%
   \global\advance\qnmlno by 1%
   \relax%
   \ifnum\qnmlno=1
      \xdef\q?nmlres{[#5,#6]}%
      \xdef\q?nmlscr{[#1,#3]}%
      \xdef\q?nmlmrk{["#2","#4"]}%
      \else
      \xdef\q?nmlres{\q?nmlres,[#5,#6]}%
      \xdef\q?nmlscr{\q?nmlscr,[#1,#3]}%
      \xdef\q?nmlmrk{\q?nmlmrk,["#2","#4"]}%
   \fi
   %\immediate\write99{debug: \q?nmlres}%
   %\immediate\write99{debug: \q?nmlscr}%
   %\immediate\write99{debug: \q?nmlmrk}%
   \lower 0pt\hbox{%
   \q?gradebox[qnumlmark.\the\qprbno.\the\qnmlno]%
   \qline(4em)[qnumlrslt.\the\qprbno.\the\qnmlno]{}}%
   }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% gives a hint
% #1 - penalty marks
% #2 - hint text
% #3 - appearance
\def\qhint[#1:#2]#3{%
   \js?btn#3\js
      score.penalize(\the\qprbno,#1,"#2");
   \endjs}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% the solution arrays need to be initialized
\xdef\q?scores{undefined}
\xdef\q?grades{undefined}
\xdef\q?nmlres{undefined}
\xdef\q?nmlscr{undefined}
\xdef\q?nmlmrk{undefined}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% evaluates the problem
% #1 - appearance
\def\qeval#1{\hbox{%
   \ifnum\qprbno=1\q?gradebox[qtotal]\fi%
   \q?gradebox[qevalmark.\the\qprbno]%
   \js?btn#1\js
      score.initvars(\the\qprbno);
      if (score.evals[\the\qprbno] < score.maxevals)
      {
         score.marktick(\the\qprbno,\the\qoptno,
            [0,\q?scores],[0,\q?grades]);
         score.marknuml(\the\qprbno,\the\qnmlno,
            [0,\q?nmlres],[0,\q?nmlscr],[0,\q?nmlmrk]);
      }
      else
         app.alert("This has already been evaluated!",1);
      score.showmark(\the\qprbno);
      score.evaltotal();
   \endjs}%
   \global\advance\qprbno by 1
   \global\qoptno=0
   \global\qnmlno=0
   }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% submits form fields, if there are no previous evaluations it evaluates
% #1 - url
% #2 - fields, in the form "fld1","fld2",...,"fldN"
% #3 - appearance
\def\qsend[#1;#2]#3{\hbox{%
   \q?gradebox[access]%
   \ifnum\qprbno=1%
      \q?gradebox[qtotal]%
      \q?gradebox[qevalmark.\the\qprbno]%
      \fi%
   \js?btn#3\js
      if (1 == \the\qprbno)
      {
         score.initvars(\the\qprbno);
         if (score.evals[\the\qprbno] < score.maxevals)
         {
            score.marktick(\the\qprbno,\the\qoptno,
               [0,\q?scores],[0,\q?grades]);
            score.marknuml(\the\qprbno,\the\qnmlno,
               [0,\q?nmlres],[0,\q?nmlscr],[0,\q?nmlmrk]);
         }
         else
            app.alert("The quiz has already been evaluated!",1);
         score.showmark(\the\qprbno);
         score.evaltotal();
      };
      var acc = this.getField("access");
      acc.value = "reader";
      if (this.external == true) acc.value = "browser";
      acc.display = display.hidden;
      this.submitForm("#1",false,true,["access",#2]);
%%% would be better to acknowledge receipt on the server side:
      if (this.external == false) app.alert("Thank you!",1);
   \endjs}%
   \global\advance\qprbno by 1
   \global\qoptno=0
   \global\qnmlno=0
   }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% submits form fields
%% #1 - url
%% #2 - fields, in the form "(fld1) (fld2) ... (fldN)"
%% #3 - appearance
%\def\qsend[#1;#2]#3{\leavevmode\relax\hbox{%
%   \pdfstartlink
%      user {/Subtype /Link
%      /C [1 0 0] /Border [0 0 0]
%      /H /P
%      /A
%      <<
%         /S /SubmitForm /F
%         <<
%            /FS /URL /F (#1)
%         >>
%         %/Fields [(Name) (Email) (Average)]
%         /Fields [#2]
%         /Flags 6 % 2^1+2^2
%      >>
%   }\relax#3%
%   \pdfendlink}%
%   }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% make evaluation results persistent until next session
% #1 - appearance
\def\qsave#1{\js?btn#1\js
   score.saveprops();
   app.alert("Your evaluation results will be available next time.",1);
   \endjs}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% get some help
\def\qhelp{\relax}
%
\catcode`?=12
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\JOBpdfquiz{\message{leaving pdfquiz.tex}}
\ifundef{JOB\jobname}\JOBpdfquiz\endinput\fi
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% documentation & sample quiz
% ===========================
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\hsize = 450pt
\vsize = 640pt

\parindent = 0pt
\parskip   = 1ex

\def\qfeedback{%
   \edef\url{http://www.maths.bath.ac.uk/\string~masuh/cgi-bin/quiz.pl}
   \par\noindent
   {\it Please enter your comments or questions below
      (only use when running inside a browser):}\hfill\break
   \centerline{\qtext(.9\hsize,5em)[comment]{Your comments.}}\par
   \hbox to \hsize{%
      {\it Name:}\space\underbar{\qline(.4\hsize)[name]{Your name.}}\hfill
      {\it Email:}\space\underbar{\qline(.4\hsize)[email]{Your email.}}%
      }\bigskip\leavevmode\hfill
   \qsend[\url;"name","email","comment"]{{\it Submit}}\par
   }

%%%
\underbar{\bf{pdfquiz} --- a \TeX\ CAA system}
\vskip 1em
{\it Udo Hertrich-Jeromin,
Tue Sep 12 22:22:21 BST 2006
}\vskip 2em
The macro package shall facilitate the creation of interactive
quizzes in .pdf format.
This short documentation describes the new \TeX\ commands provided
by the package and demonstrates their interactive features if the
document is viewed using an appropriate viewer.

\vskip 1em
%%%
\underbar{\bf New \TeX\ commands}

\hangafter=1\hangindent=2em
{\tt\string\qtick[mu:tu;mc:tc]\string{app\string}}
   --- creates a checkbox with appearance {\tt app};
   {\tt mu/mc} are the marks awarded when the box is unchecked/checked and
   {\tt tu/tc} is the corresponding text shown after evaluation.

\hangafter=0\hangindent=2em
{\it Example:\/}
   {\tt\string\qtick[3:not checked;-2:box is checked]%
      \string{\$\string\bigcirc\$\string}}
   creates the following check box:
   \qtick[3:not checked;-2:box is checked]{$\bigcirc$}.

\hangafter=1\hangindent=2em
{\tt\string\qnuml[mi:ti;mo:to]$X$($\Delta$)}
   --- creates a number input field;
   {\tt mi/mo} are the marks awarded if the provided decimal is in/not in
   $[X-\Delta,X+\Delta]$ and
   {\tt ti/to} is the corresponding text shown after evaluation.

\hangafter=0\hangindent=2em
{\it Example:\/}
   {\tt(\string\qnuml[0.5:in interval;1:not in interval]1.3(0.2)\string)}
   creates the following number input field between the parentheses:
   (\qnuml[0.5:number is in interval;1:number is not in interval]1.3(0.2)).

\hangafter=1\hangindent=2em
{\tt\string\qhint[pm:ht]\string{app\string}}
   --- creates a hint with appearance {\tt app};
   {\tt pm} are the penalty marks for requesting the hint and
   {\tt ht} is the text of the hint, shown in a pop up box.

\hangafter=0\hangindent=2em
{\it Example:\/}
   {\tt\string\qhint[0.1:this is a hint]\string{Hint\string}}
   produces:
   \qhint[0.1:this is a hint]{Hint}.

\hangafter=1\hangindent=2em
{\tt\string\qeval\string{app\string}}
   --- creates a button for evaluation with appearance {\tt app}.

\hangafter=0\hangindent=2em
{\it Example:\/}
   {\tt\string\underbar\string{\string\qeval\string{Evaluate\string}\string}}
   creates: \underbar{\qeval{Evaluate}}; click on it to evaluate the boxes
   in the above examples.

\hangafter=1\hangindent=2em
{\it Remark:\/}
   the mark and hint texts are not \TeX\ formatted and should be kept simple.

\hangafter=1\hangindent=2em
{\tt\string\qtext(wd,ht)[id]\string{dt\string}} \&
{\tt\string\qline(wd)[id]\string{dt\string}}
   --- provide text input fields (for example, for a feedback form);
   {\tt wd/ht} specify the width/height of the text box
      (only width in case of the {\tt\string\qline} command),
   {\tt dt} specifies a default text to be shown and
   {\tt id} specifies the name (id) of the text field for
      reference by the {\tt\string\qsend} command.

\hangafter=0\hangindent=2em
{\it Example:\/}
   {\tt\string\underbar\string{\string\qline(20em)[feedback]%
      \string{Your feedback\string}\string}}
   creates the following text input field:
   \underbar{\qline(20em)[feedback]{Your feedback}}.

\hangafter=1\hangindent=2em
{\tt\string\qsend[url;fld]\string{app\string}}
   --- creates a button with appearance {\tt app} for submission of
   the named fields {\tt fld} to a cgi or perl script with URL {\tt url}
   on a web server.

\hangafter=0\hangindent=2em
{\it Example:\/}
   {\tt\string\edef\string\url\string{%
      http://www.maths.bath.ac.uk/\string\string\string~masuh/cgi-bin/quiz.pl%
      \string}} and\break
   {\tt\string\qsend[\string\url;"feedback","qtotal"]\string{submit\string}},
   yields:
   \edef\url{http://www.maths.bath.ac.uk/\string~masuh/cgi-bin/quiz.pl}%
   \qsend[\url;"feedback","qtotal"]{submit}.

\hangafter=0\hangindent=2em
{\it Remark:\/}
   a predefined field ``{\tt qtotal}'' holds an array containing
   (1) the minutes since opening the document,
   (2) the maximally available marks,
   (3) the total marks from all evaluations,
   (4) the total penalties for requesting hints.

\hangafter=1\hangindent=2em
{\tt\string\qsave\string{app\string}}
   --- creates a button with appearance {\tt app} to save the results
   of all evaluations.

\hangafter=0\hangindent=2em
{\it Example:\/}
   {\tt\string\qsave\string{Save results\string}}
   will create:
   \qsave{Save results}.

\hangafter=1\hangindent=2em
{\it Remark:\/}
   The {\tt\string\qsend} and {\tt\string\qsave} commands have not been
   tested properly and may not work as intended.

\vskip 1em
\vfill\eject
%%%
\underbar{\bf New \TeX\ variables}

\hangafter=1\hangindent=2em
{\it Counters:\/}
{\tt\string\qprbno},
{\tt\string\qoptno}, {\tt\string\qnmlno}
--- these count the problem (a new problem starts after each evaluation)
   and, in each problem, the checkboxes and number input fields, respectively.

\hangafter=0\hangindent=2em
{\it Usage:\/}
   These counters can be used to automatically number problems or
   checkboxes within problems;
   internally these counters are used to identify checkboxes and
   numerical input fields --- therefore these counters should not
   be tampered with.

\hangafter=1\hangindent=2em
{\it Dimensions:\/}
{\tt\string\qmarkwd}, {\tt\string\qmarkht},
{\tt\string\qmarkho}, {\tt\string\qmarkvo}
--- these control the size (wifth and height) and offset (horizontal and
   vertical) of the box containing the mark and comment for each checkbox
   or number input box.

\hangafter=0\hangindent=2em
{\it Usage:\/}
   In order to change the global appearance, change the default values
   of these dimensions at the beginning of the file; in order to alter
   the size or position of an individual box, put the new dimensions
   followed by the corresponding {\tt\string\qtick} or {\tt\string\qnuml}
   command in a group.

\vskip 3em
%%%
\underbar{\bf Sample Quiz}

\vskip 1em

\def\option#1{\ifcase#1%
   \qtick[1:correct;0:false]{$\bigcirc$}\or
   \qtick[0:false;1:correct]{$\bigcirc$}\fi}

{\it Problem \the\qprbno\/}: Tick the correct statements.
\par\option1 This statement is true.
\par\option1 This statement is false.
\par\option0 This statement is true and false
   (\qhint[0.5:This is nonsense.]{\underbar{Hint}}).

\hfill\qeval{\underbar{Evaluate}}\par

{\it Problem \the\qprbno\/}: Tick statement number (ii).
{\parindent = 2em
\par\leavevmode\llap{\qtick[1:correct;-1:false]{(\romannumeral\qoptno)}}
   This is statement \#\the\qoptno.
\par\leavevmode\llap{\qtick[-1:false;1:correct]{(\romannumeral\qoptno)}}
   This is statement \#\the\qoptno.
\par\leavevmode\llap{\qtick[1:correct;-1:false]{(\romannumeral\qoptno)}}
   This is statement \#\the\qoptno.
\par}

\hfill\qeval{\underbar{Evaluate}}\par

{\it Problem \the\qprbno\/}: Fill in numbers and tick the box.
\par Fill in $1.3$:
   \underbar{\qnuml[1:correct;0:way off, should be 1.3]1.3(0.001)}.
\par Fill in $1.3$:
   \underbar{\qnuml[1:correct;0:way off, should be 1.3]1.3(0.000)}.
\par Fill in $3.1$:
   \underbar{\qnuml[1:correct;0:way off, should be 1.3]1.3(0.001)}
   and then \qtick[0:false;1:correct]{tick here}.

\hfill\qeval{\underbar{Evaluate}}\par

\vskip 1em

\hfill\qsave{\underbar{Save results}}\par

\vskip 3em

%{\bf Feedback.} \qfeedback
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\bye

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% technical details
% =================
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
tex commands:
=============

\js?scr script \endjs
\js?btn{appearance}\js script \endjs
\q?gradebox[field id]
\q?checkbox[field id]{appearance}
\q?inputbox(width,height)[field id]appearance\ff(pdf flag)

\qtick[0:text;1:text]{appearance} -- depends on:
   \q?gradebox[field id]
   \q?checkbox[field id]{appearance}
\qnuml[0:text;1:text]res(tol) -- depends on:
   \q?gradebox[field id]
   \qline(width)[field id]{appearance}
\qhint[0:text]{appearance} -- depends on:
   \js?btn{appearance}\js script \endjs
\qeval{appearance} -- depends on:
   \q?gradebox[field id]
   \js?btn{appearance}\js script \endjs

\qtext(width,height)[field id]{appearance} -- depends on:
   \q?inputbox(width,height)[field id]appearance\ff(pdf flag)
\qline(width)[field id]{appearance} -- depends on:
   \q?inputbox(width,height)[field id]appearance\ff(pdf flag)
\qsend[url;"fld1 id","fld2 id",...,"fldN id"]{appearance}

\qhelp -- not implemented yet


tex variables:
==============

\qprbno -- counts problems (js: "M")
\qoptno -- counts options per problem (js: "N")
\qnmlno -- counts numerical fields per problem (js: "N")

\qmarkwd -- dimen of the markbox, width  96pt
\qmarkht -- dimen of the markbox, height 24pt
\qmarkho -- offset of the markbox, horiz 0pt-12pt
\qmarkvo -- offset of the markbox, vert  0pt+22pt

\q?lineht -- 12pt: height of an input line

\q?scores -- builds up scores array at compile time (tickboxes)
\q?grades -- builds up grades array at compile time (tickboxes)

\q?nmlres -- builds up result array at compile time (numerical answers)
\q?nmlscr -- builds up scores array at compile time (numerical answers)
\q?nmlmrk -- builds up grades array at compile time (numerical answers)


form field names:
=================

tickfields
   qticktick.N.M
   qtickmark.N.M

numlfields
   qnumlrslt.N.M
   qnumlmark.N.M

evalfields
   qevalmark.N
   qtotal

global js objects & functions:
==============================

obj score

properties (arrays numbered by M = \qprbno):
   score.evals
   score.total
   score.marks
   score.pnlty
   score.maxevals
   score.otime

methods:
   score.tickdecor(id)                            -- used in \qtick
   score.penalize(M)                              -- used in \qhint
   score.initvars(M)                              -- used in \qeval
   score.marktick(M,N,[0,q?scores],[0,q?grades])  -- used in \qeval
   score.marknuml(M,N,[0,q?nmlres],[0,q?nmlscr],[0,q?nmlmrk])
                                                  -- used in \qeval
   score.showmark(M)                              -- used in \qeval
   score.evaltotal()                              -- used in \qeval
   score.initprops()                              -- run at startup
   score.saveprops()                              -- not used, stores data
   score.timing()                                 -- used in evaltotals()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

