#charset "us-ascii"
/*
* Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved.
*
* TADS 3 Library - browser (Web UI) input/output manager
*
* This module defines the low-level functions for handling input and
* output via the Web UI.
*
* The functions in this module are designed primarily for internal use
* within the library itself. Games should use the higher level objects
* and functions defined in input.t and output.t instead of directly
* calling the functions defined here. The reason for separating these
* functions is to make the UI selection pluggable, so that the same game
* can be compiled for either the traditional UI or the Web UI simply by
* plugging in the correct i/o module.
*/
/* include the library header */
#include "adv3.h"
/* ------------------------------------------------------------------------ */
/*
* Browser globals
*/
transient browserGlobals: object
/* the HTTPServer object for the browser UI session */
httpServer = nil
/*
* Log file handle. For a LogTypeTranscript file, this is a
* LogConsole object; for other types, it's a regular file handle.
*/
logFile = nil
/* logging type (LogTypeXxx from tadsio.h, or nil if not logging) */
logFileType = nil
;
/* ------------------------------------------------------------------------ */
/*
* Initialize the user interface. The library calls this once at the
* start of the interpreter session to set up the UI. For the Web UI, we
* create the HTTP server and send connection instructions to the client.
*/
initUI()
{
/*
* Set up the HTTP server. Listen on the launch address, which is
* the address that the client used to reach the external Web server
* that launched the interpreter. For local stand-alone launches,
* the launch address is nil, so the HTTP server will listen on
* localhost, which is just what we need in order to connect to the
* local UI.
*/
local srv = browserGlobals.httpServer = new HTTPServer(
getLaunchHostAddr(), nil, 1024*1024);
/* send connection instructions to the client */
webSession.connectUI(srv);
}
/*
* Initialize the display. We call this when we first enter the
* interpreter, and again at each RESTART, to set up the main game
* window's initial layout. We set up the conventional IF screen layout,
* with the status line across the top and the transcript/command window
* filling the rest of the display.
*/
initDisplay()
{
/* set up the command window and status line */
webMainWin.createFrame(commandWin, 'command',
'0, statusline.bottom, 100%, 100%');
webMainWin.createFrame(statuslineBanner, 'statusline',
'0, 0, 100%, content.height');
/* capture the title string */
local title = mainOutputStream.captureOutput(
{: gameMain.setGameTitle() });
/* parse out the contents of the
tag */
if (rexSearch('[<]title[>](.*)[<]/title[>]', title))
title = rexGroup(1)[3];
/* initialize the statusline window object */
statuslineBanner.init();
statusLine.statusDispMode = StatusModeBrowser;
/* set the title */
webMainWin.setTitle(title);
/* get the session parameters from the arguments */
local arg = libGlobal.getCommandSwitch('-gameid=');
if (arg != nil && arg != '')
webSession.launcherGameID = arg;
arg = libGlobal.getCommandSwitch('-storagesid=');
if (arg != nil && arg != '')
webSession.storageSID = arg;
arg = libGlobal.getCommandSwitch('-username=');
if (arg != nil && arg != '')
webSession.launcherUsername = arg;
}
/*
* Shut down the user interface. The library calls this when the game is
* about to terminate.
*/
terminateUI()
{
/* if we have an HTTP server, shut it down */
if (browserGlobals.httpServer != nil)
{
/* flush our windows */
webMainWin.flushWin();
commandWin.sendWinEvent('');
/* end any scripting */
aioSetLogFile(nil, LogTypeTranscript);
aioSetLogFile(nil, LogTypeCommand);
/*
* keep running for a few more minutes, to give clients a chance
* to perform final tasks like downloading log files
*/
ClientSession.shutdownWait(5*60*1000);
/* send the shutdown message */
eventPage.sendEvent('');
/* wait a short time for clients to process the shutdown event */
ClientSession.shutdownWait(5*1000);
/* shut down the http server */
browserGlobals.httpServer.shutdown(true);
}
}
/* ------------------------------------------------------------------------ */
/*
* Check to see if we're in HTML mode
*/
checkHtmlMode()
{
/*
* The web UI is always in HTML mode. This is regardless of the
* interpreter class, because that only tells us about the
* interpreter's own native UI. The actual user interface in Web UI
* mode runs in a separate Web browser app, which is inherently HTML
* capable.
*/
return true;
}
/* ------------------------------------------------------------------------ */
/*
* Write text to the main game window
*/
aioSay(txt)
{
/* write the text to the main command window */
commandWin.write(txt);
/* if we're logging a full transcript, write the text */
if (browserGlobals.logFileType == LogTypeTranscript)
browserGlobals.logFile.writeToStream(txt);
}
/* ------------------------------------------------------------------------ */
/*
* Is a script file active?
*/
readingScript()
{
return setScriptFile(ScriptReqGetStatus) != nil;
}
/*
* Is an event script active?
*/
readingEventScript()
{
local s = setScriptFile(ScriptReqGetStatus);
return (s != nil && (s & ScriptFileEvent) != 0);
}
/* ------------------------------------------------------------------------ */
/*
* Get a line of input from the keyboard, with timeout
*/
aioInputLineTimeout(timeout)
{
/* check for script input */
local scriptMode = setScriptFile(ScriptReqGetStatus);
if (scriptMode != nil)
{
/* we're in a script, so use the regular input line reader */
local e = inputLineTimeout(timeout);
/*
* If it's not an end-of-file indication, return the event. An
* EOF means that there are no more events in the script, so
* return to reading from the live client UI.
*/
if (e[1] != InEvtEof)
{
/* echo the input if we're not in quiet mode */
if (e[1] == InEvtLine && !(scriptMode & ScriptFileQuiet))
aioSay(e[2].htmlify() + '\n');
/* log and return the event */
return aioLogInputEvent(e);
}
}
/*
* read an input line event from the main command window, log it, and
* return it
*/
return aioLogInputEvent(commandWin.getInputLine(timeout));
}
/*
* Cancel a suspended input line
*/
aioInputLineCancel(reset)
{
/* cancel the input line in the command window */
commandWin.cancelInputLine(reset);
}
/* ------------------------------------------------------------------------ */
/*
* Read an input event
*/
aioInputEvent(timeout)
{
/* check for script input */
if (readingEventScript())
{
/* we're in a script, so use the regular input line reader */
local e = inputEvent(timeout);
/*
* If it's not an end-of-file indication, return the event. An
* EOF means that there are no more events in the script, so
* return to reading from the live client UI.
*/
if (e[1] != InEvtEof)
return aioLogInputEvent(e);
}
/* read an event from the main command window, log it, and return it */
return aioLogInputEvent(webMainWin.getInputEvent(timeout));
}
/* ------------------------------------------------------------------------ */
/*
* Show a "More" prompt
*/
aioMorePrompt()
{
/* show a More prompt in the main command window */
commandWin.showMorePrompt();
}
/* ------------------------------------------------------------------------ */
/*
* Clear the screen
*/
aioClearScreen()
{
/* clear the main transcript window */
commandWin.clearWindow();
}
/* ------------------------------------------------------------------------ */
/*
* Show a file selector dialog
*/
aioInputFile(prompt, dialogType, fileType, flags)
{
/*
* First, try reading from the local console. Even though we're
* using the Web UI, there are two special cases where the input will
* come from the local (server-side) console instead of from the
* browser UI:
*
* 1. We're reading from an event script. In this case, regardless
* of the UI mode, the interpreter reads from a server-side file and
* parses the results into an inputFile() result, bypassing any UI
* interaction.
*
* 2. We're running in the Web UI's local stand-alone configuration,
* where the browser is actually an integrated window within the
* interpreter. This configuration simulates the traditional UI by
* running everything locally - the client and server are running on
* the same machine, so there's really no distinction between
* client-side and server-side. Because everything's local, files
* are local, so we want to display traditional local file selector
* dialogs. The stand-alone interpreter does this for us via the
* standard inputFile() function when it detects this configuration.
*
* If neither of these special cases apply, inputFile() will return
* an error to let us know that it can't show a file dialog in the
* current configuration, so we'll continue on to showing the dialog
* on the client side via the Web UI.
*/
local f = inputFile(prompt, dialogType, fileType, flags);
/* if that failed, forget the result */
if (f[1] == InFileFailure)
f = nil;
/* if we got a file, check for warnings */
if (f != nil && f.length() >= 4 && f[4] != nil)
{
/* keep going until we get a definitive answer */
for (local done = nil ; !done ; )
{
/* show the warning dialog */
local d = webMainWin.getInputDialog(
InDlgIconWarning,
libMessages.inputFileScriptWarning(f[4], f[2]),
libMessages.inputFileScriptWarningButtons, 1, 3);
/* check the result */
switch (d)
{
case 0:
case 3:
/* dialog error or Cancel Script - stop the script */
setScriptFile(nil);
/* return a Cancel result */
return [InFileCancel];
case 1:
/* "Yes" - proceed */
done = true;
break;
case 2:
/* Choose New File button - show a file dialog */
local fNew = webMainWin.getInputFile(
prompt, dialogType, fileType, flags);
switch (fNew[1])
{
case InFileSuccess:
/* success - use the new file, and we're done */
f = fNew;
done = true;
break;
case InFileCancel:
/* cancel - repeat the prompt */
break;
case InFileFailure:
/* dialog error - cancel the script */
setScriptFile(nil);
return [InFileCancel];
}
}
}
}
/*
* if we didn't get a result from a script or from the local console,
* tell the client UI to display its file dialog
*/
if (f == nil)
f = webMainWin.getInputFile(prompt, dialogType, fileType, flags);
/* log a synthetic event, if applicable */
aioLogInputEvent(
['',
f[1] != InFileSuccess ? '' :
dataType(f[2]) == TypeObject && !f[2].ofKind(FileName) ? 't' :
f[2]]);
/* return the file information */
return f;
}
/* ------------------------------------------------------------------------ */
/*
* Show an input dialog
*/
aioInputDialog(icon, prompt, buttons, defaultButton, cancelButton)
{
/* check for script input */
local d = nil;
if (readingEventScript())
{
/* we're in a script, so use the regular dialog event reader */
d = inputDialog(icon, prompt, buttons, defaultButton, cancelButton);
/* if it failed, forget the result */
if (d == 0)
d = nil;
}
/* if we didn't get script input, show the dialog via the client UI */
if (d == nil)
d = webMainWin.getInputDialog(icon, prompt, buttons,
defaultButton, cancelButton);
/* log a synthetic