#charset "us-ascii"
#include <tads.h>
#include "advlite.h"
/*
* Custom Banner version 1.2
* by Eric Eve
*
* Version date: 13-Sep-06
* Adapted for adv3Lite: 28-Dec-24
*
* This file implements a CustomBannerWindow class that vastly eases
* the process of setting up banners and displaying material in them.
* e.g. to set up a graphics banner to display pictures, starting with
* pic1.jpg at startup, but not appearing at all on an interpreter that
* can't display JPEGs you could define:
*
* pictureWindow: CustomBannerWindow
* canDisplay = (systemInfo(SysInfoJpeg))
* bannerArgs = [nil, BannerAfter, statuslineBanner, BannerTypeText,
* BannerAlignTop, 10, BannerSizeAbsolute, BannerStyleBorder]
* currentContents = '<img src="pic1.jpg">'
* ;
*
* Then to change the picture dislayed at a later point, call:
*
* pictureWindow.updateContents('<img src="pic2.jpg">');
*
* And everything else, including getting everything right on RESTART, UNDO
* and RESTORE should be taken care of.
*/
/* ------------------------------------------------------------------------ */
/*
* A CustomBannerWindow, like a BannerWindow, corrsponds to an on-screen
* banner. The purpose of CustomBannerWindow is to eliminate most of the
* busy-work that a game author would otherwise have to take care of in
* displaying and manipulating banners.
*
* As with BannerWinnow, merely creating a CustomBannerWindow does not
* display the banner. However, any CustomBannerWindows in existence at
* the start of the game will be added to the screen display, unless the
* condition specified in their shouldDisplay() method prevents initialization.
*
* The one property that must be defined on each instance of a CustomBannerWindow
* is bannerArgs, which takes the form:
*
* bannerArgs = [parent, where, other, windowType, align,
* size, sizeUnits, styleFlags]
*
* where each list element has the same meaning at the corresponding argument
* to BannerWindow.showBanner()
*
* This merely ensures that the CustomBannerWindow is added to the screen's
* banner window layout. To have the CustomBannerWindow display some content
* when first added to the screen layout, override its current contents property:
*
* currentContents = 'My initial contents'
*
* To change what's displayed in a CustomBannerWindow from game code, call its
* updateContents() method, e.g.:
*
* pictureWindow.updateContents('<img src="pics/troll.jpg">');
*
* To redisplay the current contents, call the showCurrentContents() method.
* By default a call to updateContents() or showCurrentContents() clears the
* window before displaying the new content. To have the additional content
* added to the existing content, change the clearBeforeUpdate property to nil.
*
* You can control whether the game uses this banner at all by overriding
* the canDisplay property. The main purpose of this property is to easily allow
* a game to run on different types of interpreter. For example, if your banner is
* meant to display pictures in the JPEG format, there's no point having it included
* in the screen layout of an interpreter that can't display JPEGs, and attempts to
* update its contents with a new picture should do nothing. In which case we could
* define:
*
* canDisplay = (systemInfo(SysInfoJpeg))
*
* Calls to CustomBannerWindow methods like updateContents() and clearWindow()
* should be safe on an interpreter for which shouldDisplay returns nil, since
* by default these will do nothing beyond updating the banner's currentContents
* property. This makes it easier to write game code that is suitable for all
* classes of interpreter
*
* To have a CustomBannerWindow resize to contents each time its contents are
* displayed, set its autoSize property to true.
*
* If you do not want a CustomBannerWindow you have defined not to be dispayed
* at game startup, set its isActive property to nil. Call the activate()
* method to add it to the screen layout and the deactivate() method to remove
* it, or any other CustomBannerWindow, from the screen display.
*
* Obviously, it is then the game author's responsibility to ensure that no
* other banner window that's meant to persist after pictureWindow is deactivated
* depends on pictureWindow for its existence; i.e. that we're not deactivating
* the parent of some other banner window(s) we want to keep or subsequently
* activate, or the sibling of any banner window that's subsequently going to
* defined in relation to us.
*/
class CustomBannerWindow: BannerWindow
/*
* The list of any banner windows that must be set up before me,
* either one of them is my parent, or because I'm going
* to be placed before or after them with BannerBefore or BannerAfter.
*
* If bannerArgs has been set up with the list of showBanner arguments,
* then we can derive this information automatically
*/
initBeforeMe()
{
/*
* If our bannerArgs property contains a list of the right length, i.e. 8
* elements, then the first and third elements of the list (our parent, and
* the sibling we're to be placed before or after) must be initialized before
* we are. If either of these is nil, no harm is done, since initBannerWindow()
* will simply skip the nil value.
*
* Moreover, if our sibling is in the list, we don't need our parent as well,
* since either our sibling or one of its siblings will initialize our parent.
*/
local lst = [];
if (propType(&bannerArgs) == TypeList && bannerArgs.length() == 8)
lst = bannerArgs[3] ? [bannerArgs[3]] : [bannerArgs[1]];
initBeforeMe = lst;
return lst;
}
/*
* A condition to test whether this banner window should actually display.
* Normally this would test for the interpreter class if this would
* affect whether we wanted this banner to be created. For example, if
* we were going to use this banner window to display a JPEG picture, we
* might not this window to display at all if the interpreter we're running
* on can't display JPEGS, so we might write:
*
* canDisplay = (systemInfo(SysInfoJpeg))
*
* If your complete system of CustomBanners depends on the same condition
* (e.g. you don't want any CustomBanners if the interpreter we're running
* on can't display JPEGs, then it's probably easiest to modify CustomBanner
* and override scanDisplay on the modified class.
*
* By default, we simply check that the interpreter we're running on
* can display banners.
*/
canDisplay = (systemInfo(SysInfoBanners))
shouldDisplay = (canDisplay && isActive)
/*
* The standard use of initBannerWindow is first to ensure that any
* banner windows whose existence we presuppose have themselves been
* initialized, and then to set up our own window on screen.
* This function should be used for initializing banner window *layout*,
* not content.
*/
initBannerWindow()
{
/*
* If we shouldn't display on this class of interpreter, don't
* initialize us.
*/
if(!shouldDisplay)
return nil;
/*
* If we've already been initialized, there's nothing left to do.
*/
if(inited_)
return true;
/*
* Initialize all the bannner windows on whose existence our own
* depends. If one of them can't be initialized, neither can we,
* in which case return nil to show that our initialization failed.
* If, however, the parent or sibling banner window we want initialized
* before us is not a CustomBannerWindow, then its initBannerWindow()
* won't have a return value, in which case we ignore the fact that
* it returns nil
*/
foreach(local ban in initBeforeMe)
if(ban && !ban.initBannerWindow() && ban.ofKind(CustomBannerWindow))
return nil;
/*
* Create my banner window on screen; if this fails return nil
* to indicate that the window could not be created
*/
return (inited_ = initBannerLayout());
}
/*
* Initialize my onscreen layout, normally through a call to showBanner(),
* whose return value this method should return, e.g.:
*
* initBannerLayout()
* {
* return showBanner(nil, BannerAfter, statuslineBanner,
* BannerTypeText, BannerAlignTop, 1, BannerSizeAbsolute,
* BannerStyleBorder);
* }
*
* By default we simply call initBannerLayout() using our bannerArgs.
*/
initBannerLayout()
{
return showBanner(bannerArgs...);
}
/*
* The list of args used to define our screen layout, as they would be passed
* to showBanner. This is used both by initBannerLayout and initBeforeMe.
*
* The args should be listed in the form
*
* bannerArgs = [parent, where, other, windowType, align, size, sizeUnits, styleFlags]
*
* e.g.
* bannerArgs = [nil, BannerAfter, statuslineBanner,
* BannerTypeText, BannerAlignTop, 1, BannerSizeAbsolute,
* BannerStyleBorder]
*
*/
bannerArgs = nil
/*
* The current contents to be displayed in this window, which could be
* a string of text, or the HTML string to display a picture.
*
* currentContents can be overridden to hold the initial contents
* we want this banner to display, but it should not otherwise be
* directly written to in game code. To display new contents in the
* banner, use updateContents() instead.
*/
currentContents = ''
/*
* Is this banner currently active? Set to nil if you don't want to this
* CustomBannerWindow to be active at startup; thereafter use the deactivate()
* and activate() methods
*/
isActive = true
/*
* deactivate a currently active banner; this removes it from the screen
* and prevents writing anything further to it. Be careful to respect the
* dependency order of banner windows when activating and deactivating
*
* The argument is optional. If it is the constant true then the currentContents
* will be set to an empty string (''). If it is a string, then the currentContents
* will be set to that string (ready to be displayed when the banner is reactivated).
*/
deactivate([args])
{
removeBanner();
isActive = nil;
if(args.length > 0)
{
local arg = args[1];
switch(dataType(arg))
{
case TypeTrue:
currentContents = '';
break;
case TypeSString:
currentContents = arg;
break;
}
}
}
/*
* Activate a currently inactive banner; this restores it to the screen.
* The argument is optional; if present and true then activate(true)
* displays the current contents of the banner window after activating it.
* If the first argument is a string then the string is displayed in the banner.
*/
activate([args])
{
if(isActive)
return;
isActive = true;
initBannerWindow();
if(args.length() > 0 && args[1] != nil)
{
if(dataType(args[1]) == TypeSString)
updateContents(args...);
else
showCurrentContents();
}
}
removeBanner()
{
/*
* If I'm removed I can't be inited_ any more, and I'll need to be regarded
* as not inited_ in the event of being redisplayed in the future.
*/
inited_ = nil;
inherited;
}
/*
* Set this flag to true to clear the contents of the window before displaying
* the new contents, e.g. to display a new picture that replaces the old one.
*/
clearBeforeUpdate = true
/*
* Set this to true to have this banner size to contents each time its
* contents are displayed. Note that not all interpreters support the size to
* contents so you should still set an appropriate initial size, and, where
* appropriate, call setSize() with the isAdvisory flag set.
*/
autoSize = nil
/*
* Update the contents of this banner window. This is the method to
* call to change what a banner displays.
*
* The second argument is optional. If present it overrides the
* setting of clearBeforeUpdate: updateContents(cont, true) will
* clear the banner before the update, whereas updateContents(cont, nil)
* will not, whatever the value of clearBeforeUpdate.
*/
updateContents(cont, [args])
{
/*
* Update our current contents. Note that this takes place even if
* shouldDisplay is nil, so that if, for example, we are updated on
* a text-only interpreter on which this banner is not displayed,
* and the game is saved there and subsequently restored on a full HTML
* interpreter in which we are displayed, the HTML interpreter will know
* what contents it needs to display in us.
*/
currentContents = cont;
showCurrentContents(args...);
}
/* Show the current contents of this banner window */
showCurrentContents([args])
{
local clr;
if(args.length > 0)
clr = (args[1] != nil);
else
clr = clearBeforeUpdate;
if(clr)
clearWindow();
writeToBanner(currentContents);
if(autoSize)
sizeToContents();
}
/* This is called on each CustomBannerWindow after a Restore. */
restoreBannerDisplay()
{
/*
* It's possible a game was saved in a text-mode terp and
* restored in an HTML one. In which case we need to initialize
* this banner before attempting to display anything
*/
if(shouldDisplay && handle_ == nil)
{
if(!initBannerWindow())
return;
}
/* redisplay my contents after a restore */
showCurrentContents();
}
/*
* Alternatively we might have been saved in a terp that does
* use this banner and restored in one that doesn't, in which
* case we should remove ourselves. This is called on each BannerWindow
* after a restore, but before bannerTracker.restoreDisplayState().
*/
restoreRemove()
{
if(!shouldDisplay)
removeBanner();
}
/* show my initial contents on startup */
initBannerDisplay() { showCurrentContents(); }
/*
* We provide overrides for all the various banner manipulation methods
* that game code might call, in order to make it safe to call them even
* our shouldDisplay method returns nil and we don't - or shouldn't - exist.
* For each of these xxxYyy methods we provide an altXxxyyy method that is
* called when shouldDisplay is nil (e.g. because we're using a window to
* display graphics on an interpreter that doesn't have graphics capablities).
* By default these altXxxYyy methods do nothing, which in many cases will
* be fine, but if you do want something else to happen you can override
* the appropriate altXxxYyy method accordingly (e.g. to show a message in
* the main game window instead of this banner). This should make it easier
* to structure the rest of your game code without needing to worry about
* what happens on interpreters which don't display your banners.
*/
clearWindow()
{
if(shouldDisplay)
inherited();
else
altClearWindow();
}
altClearWindow() { }
/* write to me, but only if I should display */
writeToBanner(txt)
{
if(shouldDisplay)
inherited(txt);
else
altWriteToBanner(txt);
}
/*
* altWriteToBanner(txt) is called when our game code tries to display
* something in this banner, but our shouldDisplay method has ruled out
* displaying this banner. In this case we might want to write something
* to the main display instead. By default we do nothing here, but
* individual instances and/or subclasses can override this method as
* required.
*/
altWriteToBanner(txt) { }
/*
* We don't provide alternative methods for the setSize and sizeToContents
* methods, since there would almost certainly be nothing for them to do.
* We simply do nothing if shouldDisplay is nil.
*/
setSize(size, sizeUnits, isAdvisory)
{
if(shouldDisplay)
inherited(size, sizeUnits, isAdvisory);
}
sizeToContents()
{
/* size our system-level window to our contents */
if(shouldDisplay)
bannerSizeToContents(handle_);
}
captureOutput(func)
{
if(shouldDisplay)
inherited(func);
else
altCaptureOutput(func);
}
/* Simply execute the callback without changing the output stream */
altCaptureOutput(func) { (func)(); }
setOutputStream()
{
if(shouldDisplay)
/* set my stream as the default */
return outputManager.setOutputStream(outputStream_);
else
return altSetOutputStream();
}
/*
* Our caller, or rather our caller's caller, will expect us to return
* the current output stream, which means we must be sure to do this
* whatever else we do.
*/
altSetOutputStream() { return outputManager.curOutputStream; }
flushBanner()
{
if(shouldDisplay)
inherited();
else
altFlushBanner();
}
altFlushBanner() { }
setTextColor(fg, bg)
{
if(shouldDisplay)
inherited(fg, bg);
else
altSetTextColor(fg, bg);
}
altSetTextColor(fg, bg) { }
setScreenColor(color)
{
if(shouldDisplay)
inherited(color);
else
altSetScreenColor(color);
}
altSetScreenColor(color) { }
cursorTo(row, col)
{
if(shouldDisplay)
inherited(row, col);
else
altCursorTo(row, col);
}
/*
* If this banner isn't displaying we can't do anything directly comparable
* to setting the cursot to a particular column and row in it, but we might
* want to do something else instead, like inserting so many blank lines in
* the main window.
*/
altCursorTo(row, col) { }
;
/*
* Initialize or reinitialize what all CustomBanners display at startup or
* after an UNDO
*/
customBannerInit: InitObject, PostUndoObject
execBeforeMe = [bannerInit]
execute()
{
/* first ensure that all banner windows that need to exist do exist */
forEachInstance(CustomBannerWindow, new function(win) {
if(win.shouldDisplay && win.handle_ == nil)
win.initBannerWindow();
} );
/* then show the current contents of every active banner */
forEachInstance(CustomBannerWindow, {win: win.showCurrentContents() } );
}
;
/*
* Reinitialize what all the CustomBanners display on restoring. This requires
* a different procedure since we can't be sure that we're being restored on
* the same class of interpreter as we were saved on.
*/
customBannerRestore: PostRestoreObject
execBeforeMe = [bannerTracker]
execute()
{
/*
* If we save in one terp, restore in the second terp, save in the second
* terp, then restore in the first terp, when different rules apply about
* displaying banners in the two terps, then windows removed in the second
* terp could still be marked as inited_ in the restore file that comes
* back to the first terp. To get round this, on restoration we ensure
* that each CustomBanner's inited_ property in fact corresponds to whether
* it has an active handle_, otherwise the attempt to reinitialize missing
* banners might fail.
*/
forEachInstance(CustomBannerWindow, {win: win.inited_ = (win.handle_ != nil) } );
forEachInstance(CustomBannerWindow, {win: win.restoreBannerDisplay() } );
}
;
customBannerRestoreRemove: PostRestoreObject
execAfterMe = [bannerTracker]
execute()
{
forEachInstance(CustomBannerWindow, {win: win.restoreRemove() } );
}
;
/*
* If we display a menu then we need to remove any active banners from the
* screen before the menu displays and restore them to the screen on exiting
* from the menu
*/
modify MenuItem
display()
{
/*
* First we store a list of all the banners that are currently
* displaying
*/
local vec = new Vector(10);
forEachInstance(CustomBannerWindow, new function(ban) {
if(ban.shouldDisplay)
vec.append(ban); } );
/* deactive all active banners */
foreach(local ban in vec)
ban.deactivate();
try
{
/* carry out the inherited menu display */
inherited();
}
/*
* Restore all the banners in our list of banners that were previously
* displayed. To ensure that they are activated in the right order
* we make what may be several passes through the list. On each pass
* we activate only those banners that don't depend on any inactive
* banners for their activation. Each time we activate a banner, we
* remove it from the list. On the next pass through the list any
* banners that depended on banners we have just activated may now themselves
* be activated, so we can carry on until every banner has been activated
* and removed from the list.
*/
finally
{
while(vec.length())
{
local bannerRemoved = nil;
foreach(local ban in vec)
{
if(ban.bannerArgs[1] != nil && ban.bannerArgs[1].handle_ == nil)
continue;
if(ban.bannerArgs[3] != nil && ban.bannerArgs[3].handle_ == nil)
continue;
ban.activate(true);
vec.removeElement(ban);
bannerRemoved = true;
}
/*
* If we didn't remove any banners on this pass through, we're
* potentially in an infinite loop, so we'd better break out
* of it.
*/
if(!bannerRemoved)
break;
}
}
}
;
Adv3Lite Library Reference Manual
Generated on 26/02/2025 from adv3Lite version 2.2