6
* Custom Banner version 1.2
9
* Version date: 13-Sep-06
11
* This file implements a CustomBannerWindow class that vastly eases
12
* the process of setting up banners and displaying material in them.
13
* e.g. to set up a graphics banner to display pictures, starting with
14
* pic1.jpg at startup, but not appearing at all on an interpreter that
15
* can't display JPEGs you could define:
17
* pictureWindow: CustomBanner
18
* canDisplay = (systemInfo(SysInfoJpeg))
19
* bannerArgs = [nil, BannerAfter, statuslineBanner, BannerTypeText,
20
* BannerAlignTop, 10, BannerSizeAbsolute, BannerStyleBorder]
21
* currentContents = '<img src="pic1.jpg">'
24
* Then to change the picture dislayed at a later point, call:
26
* pictureWindow.updateContents('<img src="pic2.jpg">');
28
* And everything else, including getting everything right on RESTART, UNDO
29
* and RESTORE should be taken care of.
32
/* ------------------------------------------------------------------------ */
34
* A CustomBannerWindow, like a BannerWindow, corrsponds to an on-screen
35
* banner. The purpose of CustomBannerWindow is to eliminate most of the
36
* busy-work that a game author would otherwise have to take care of in
37
* displaying and manipulating banners.
39
* As with BannerWinnow, merely creating a CustomBannerWindow does not
40
* display the banner. However, any CustomBannerWindows in existence at
41
* the start of the game will be added to the screen display, unless the
42
* condition specified in their shouldDisplay() method prevents initialization.
44
* The one property that must be defined on each instance of a CustomBannerWindow
45
* is bannerArgs, which takes the form:
47
* bannerArgs = [parent, where, other, windowType, align,
48
* size, sizeUnits, styleFlags]
50
* where each list element has the same meaning at the corresponding argument
51
* to BannerWindow.showBanner()
53
* This merely ensures that the CustomBannerWindow is added to the screen's
54
* banner window layout. To have the CustomBannerWindow display some content
55
* when first added to the screen layout, override its current contents property:
57
* currentContents = 'My initial contents'
59
* To change what's displayed in a CustomBannerWindow from game code, call its
60
* updateContents() method, e.g.:
62
* pictureWindow.updateContents('<img src="pics/troll.jpg">');
64
* To redisplay the current contents, call the showCurrentContents() method.
65
* By default a call to updateContents() or showCurrentContents() clears the
66
* window before displaying the new content. To have the additional content
67
* added to the existing content, change the clearBeforeUpdate property to nil.
69
* You can control whether the game uses this banner at all by overriding
70
* the canDisplay property. The main purpose of this property is to easily allow
71
* a game to run on different types of interpreter. For example, if your banner is
72
* meant to display pictures in the JPEG format, there's no point having it included
73
* in the screen layout of an interpreter that can't display JPEGs, and attempts to
74
* update its contents with a new picture should do nothing. In which case we could
77
* canDisplay = (systemInfo(SysInfoJpeg))
79
* Calls to CustomBannerWindow methods like updateContents() and clearWindow()
80
* should be safe on an interpreter for which shouldDisplay returns nil, since
81
* by default these will do nothing beyond updating the banner's currentContents
82
* property. This makes it easier to write game code that is suitable for all
83
* classes of interpreter
85
* To have a CustomBannerWindow resize to contents each time its contents are
86
* displayed, set its autoSize property to true.
88
* If you do not want a CustomBannerWindow you have defined not to be dispayed
89
* at game startup, set its isActive property to nil. Call the activate()
90
* method to add it to the screen layout and the deactivate() method to remove
91
* it, or any other CustomBannerWindow, from the screen display.
93
* Obviously, it is then the game author's responsibility to ensure that no
94
* other banner window that's meant to persist after pictureWindow is deactivated
95
* depends on pictureWindow for its existence; i.e. that we're not deactivating
96
* the parent of some other banner window(s) we want to keep or subsequently
97
* activate, or the sibling of any banner window that's subsequently going to
98
* defined in relation to us.
101
class CustomBannerWindow: BannerWindow
104
* The list of any banner windows that must be set up before me,
105
* either one of them is my parent, or because I'm going
106
* to be placed before or after them with BannerBefore or BannerAfter.
108
* If bannerArgs has been set up with the list of showBanner arguments,
109
* then we can derive this information automatically
114
* If our bannerArgs property contains a list of the right length, i.e. 8
115
* elements, then the first and third elements of the list (our parent, and
116
* the sibling we're to be placed before or after) must be initialized before
117
* we are. If either of these is nil, no harm is done, since initBannerWindow()
118
* will simply skip the nil value.
120
* Moreover, if our sibling is in the list, we don't need our parent as well,
121
* since either our sibling or one of its siblings will initialize our parent.
126
if (propType(&bannerArgs) == TypeList && bannerArgs.length() == 8)
127
lst = bannerArgs[3] ? [bannerArgs[3]] : [bannerArgs[1]];
136
* A condition to test whether this banner window should actually display.
137
* Normally this would test for the interpreter class if this would
138
* affect whether we wanted this banner to be created. For example, if
139
* we were going to use this banner window to display a JPEG picture, we
140
* might not this window to display at all if the interpreter we're running
141
* on can't display JPEGS, so we might write:
143
* canDisplay = (systemInfo(SysInfoJpeg))
145
* If your complete system of CustomBanners depends on the same condition
146
* (e.g. you don't want any CustomBanners if the interpreter we're running
147
* on can't display JPEGs, then it's probably easiest to modify CustomBanner
148
* and override scanDisplay on the modified class.
150
* By default, we simply check that the interpreter we're running on
151
* can display banners.
153
canDisplay = (systemInfo(SysInfoBanners))
155
shouldDisplay = (canDisplay && isActive)
158
* The standard use of initBannerWindow is first to ensure that any
159
* banner windows whose existence we presuppose have themselves been
160
* initialized, and then to set up our own window on screen.
161
* This function should be used for initializing banner window *layout*,
169
* If we shouldn't display on this class of interpreter, don't
176
* If we've already been initialized, there's nothing left to do.
183
* Initialize all the bannner windows on whose existence our own
184
* depends. If one of them can't be initialized, neither can we,
185
* in which case return nil to show that our initialization failed.
186
* If, however, the parent or sibling banner window we want initialized
187
* before us is not a CustomBannerWindow, then its initBannerWindow()
188
* won't have a return value, in which case we ignore the fact that
192
foreach(local ban in initBeforeMe)
193
if(ban && !ban.initBannerWindow() && ban.ofKind(CustomBannerWindow))
197
* Create my banner window on screen; if this fails return nil
198
* to indicate that the window could not be created
201
return (inited_ = initBannerLayout());
206
* Initialize my onscreen layout, normally through a call to showBanner(),
207
* whose return value this method should return, e.g.:
211
* return showBanner(nil, BannerAfter, statuslineBanner,
212
* BannerTypeText, BannerAlignTop, 1, BannerSizeAbsolute,
213
* BannerStyleBorder);
216
* By default we simply call initBannerLayout() using our bannerArgs.
221
return showBanner(bannerArgs...);
227
* The list of args used to define our screen layout, as they would be passed
228
* to showBanner. This is used both by initBannerLayout and initBeforeMe.
230
* The args should be listed in the form
232
* bannerArgs = [parent, where, other, windowType, align, size, sizeUnits, styleFlags]
235
* bannerArgs = [nil, BannerAfter, statuslineBanner,
236
* BannerTypeText, BannerAlignTop, 1, BannerSizeAbsolute,
244
* The current contents to be displayed in this window, which could be
245
* a string of text, or the HTML string to display a picture.
247
* currentContents can be overridden to hold the initial contents
248
* we want this banner to display, but it should not otherwise be
249
* directly written to in game code. To display new contents in the
250
* banner, use updateContents() instead.
255
* Is this banner currently active? Set to nil if you don't want to this
256
* CustomBannerWindow to be active at startup; thereafter use the deactivate()
257
* and activate() methods
263
* deactivate a currently active banner; this removes it from the screen
264
* and prevents writing anything further to it. Be careful to respect the
265
* dependency order of banner windows when activating and deactivating
267
* The argument is optional. If it is the constant true then the currentContents
268
* will be set to an empty string (''). If it is a string, then the currentContents
269
* will be set to that string (ready to be displayed when the banner is reactivated).
280
switch(dataType(arg))
283
currentContents = '';
286
currentContents = arg;
294
* Activate a currently inactive banner; this restores it to the screen.
295
* The argument is optional; if present and true then activate(true)
296
* displays the current contents of the banner window after activating it.
297
* If the first argument is a string then the string is displayed in the banner.
307
if(args.length() > 0 && args[1] != nil)
309
if(dataType(args[1]) == TypeSString)
310
updateContents(args...);
312
showCurrentContents();
319
* If I'm removed I can't be inited_ any more, and I'll need to be regarded
320
* as not inited_ in the event of being redisplayed in the future.
330
* Set this flag to true to clear the contents of the window before displaying
331
* the new contents, e.g. to display a new picture that replaces the old one.
333
clearBeforeUpdate = true
336
* Set this to true to have this banner size to contents each time its
337
* contents are displayed. Note that not all interpreters support the size to
338
* contents so you should still set an appropriate initial size, and, where
339
* appropriate, call setSize() with the isAdvisory flag set.
346
* Update the contents of this banner window. This is the method to
347
* call to change what a banner displays.
349
* The second argument is optional. If present it overrides the
350
* setting of clearBeforeUpdate: updateContents(cont, true) will
351
* clear the banner before the update, whereas updateContents(cont, nil)
352
* will not, whatever the value of clearBeforeUpdate.
355
updateContents(cont, [args])
358
* Update our current contents. Note that this takes place even if
359
* shouldDisplay is nil, so that if, for example, we are updated on
360
* a text-only interpreter on which this banner is not displayed,
361
* and the game is saved there and subsequently restored on a full HTML
362
* interpreter in which we are displayed, the HTML interpreter will know
363
* what contents it needs to display in us.
365
currentContents = cont;
367
showCurrentContents(args...);
370
/* Show the current contents of this banner window */
372
showCurrentContents([args])
376
clr = (args[1] != nil);
378
clr = clearBeforeUpdate;
383
writeToBanner(currentContents);
390
/* This is called on each CustomBannerWindow after a Restore. */
392
restoreBannerDisplay()
395
* It's possible a game was saved in a text-mode terp and
396
* restored in an HTML one. In which case we need to initialize
397
* this banner before attempting to display anything
400
if(shouldDisplay && handle_ == nil)
402
if(!initBannerWindow())
407
/* redisplay my contents after a restore */
408
showCurrentContents();
412
* Alternatively we might have been saved in a terp that does
413
* use this banner and restored in one that doesn't, in which
414
* case we should remove ourselves. This is called on each BannerWindow
415
* after a restore, but before bannerTracker.restoreDisplayState().
425
/* show my initial contents on startup */
427
initBannerDisplay() { showCurrentContents(); }
430
* We provide overrides for all the various banner manipulation methods
431
* that game code might call, in order to make it safe to call them even
432
* our shouldDisplay method returns nil and we don't - or shouldn't - exist.
433
* For each of these xxxYyy methods we provide an altXxxyyy method that is
434
* called when shouldDisplay is nil (e.g. because we're using a window to
435
* display graphics on an interpreter that doesn't have graphics capablities).
436
* By default these altXxxYyy methods do nothing, which in many cases will
437
* be fine, but if you do want something else to happen you can override
438
* the appropriate altXxxYyy method accordingly (e.g. to show a message in
439
* the main game window instead of this banner). This should make it easier
440
* to structure the rest of your game code without needing to worry about
441
* what happens on interpreters which don't display your banners.
454
/* write to me, but only if I should display */
461
altWriteToBanner(txt);
465
* altWriteToBanner(txt) is called when our game code tries to display
466
* something in this banner, but our shouldDisplay method has ruled out
467
* displaying this banner. In this case we might want to write something
468
* to the main display instead. By default we do nothing here, but
469
* individual instances and/or subclasses can override this method as
473
altWriteToBanner(txt) { }
477
* We don't provide alternative methods for the setSize and sizeToContents
478
* methods, since there would almost certainly be nothing for them to do.
479
* We simply do nothing if shouldDisplay is nil.
482
setSize(size, sizeUnits, isAdvisory)
485
inherited(size, sizeUnits, isAdvisory);
490
/* size our system-level window to our contents */
492
bannerSizeToContents(handle_);
500
altCaptureOutput(func);
503
/* Simply execute the callback without changing the output stream */
505
altCaptureOutput(func) { (func)(); }
510
/* set my stream as the default */
511
return outputManager.setOutputStream(outputStream_);
513
return altSetOutputStream();
517
* Our caller, or rather our caller's caller, will expect us to return
518
* the current output stream, which means we must be sure to do this
519
* whatever else we do.
522
altSetOutputStream() { return outputManager.curOutputStream; }
539
altSetTextColor(fg, bg);
542
altSetTextColor(fg, bg) { }
544
setScreenColor(color)
549
altSetScreenColor(color);
552
altSetScreenColor(color) { }
559
altCursorTo(row, col);
563
* If this banner isn't displaying we can't do anything directly comparable
564
* to setting the cursot to a particular column and row in it, but we might
565
* want to do something else instead, like inserting so many blank lines in
568
altCursorTo(row, col) { }
574
* Initialize or reinitialize what all CustomBanners display at startup or
578
customBannerInit: InitObject, PostUndoObject
579
execBeforeMe = [bannerInit]
583
/* first ensure that all banner windows that need to exist do exist */
585
// forEachInstance(CustomBannerWindow, new function(win) {
586
// if(win.shouldDisplay && win.handle_ == nil)
587
// win.initBannerWindow();
590
/* then show the current contents of every active banner */
592
forEachInstance(CustomBannerWindow, {win: win.showCurrentContents() } );
597
* Reinitialize what all the CustomBanners display on restoring. This requires
598
* a different procedure since we can't be sure that we're being restored on
599
* the same class of interpreter as we were saved on.
602
customBannerRestore: PostRestoreObject
603
execBeforeMe = [bannerTracker]
609
* If we save in one terp, restore in the second terp, save in the second
610
* terp, then restore in the first terp, when different rules apply about
611
* displaying banners in the two terps, then windows removed in the second
612
* terp could still be marked as inited_ in the restore file that comes
613
* back to the first terp. To get round this, on restoration we ensure
614
* that each CustomBanner's inited_ property in fact corresponds to whether
615
* it has an active handle_, otherwise the attempt to reinitialize missing
616
* banners might fail.
618
forEachInstance(CustomBannerWindow, {win: win.inited_ = (win.handle_ != nil) } );
620
forEachInstance(CustomBannerWindow, {win: win.restoreBannerDisplay() } );
625
customBannerRestoreRemove: PostRestoreObject
626
execAfterMe = [bannerTracker]
630
forEachInstance(CustomBannerWindow, {win: win.restoreRemove() } );
635
* If we display a menu then we need to remove any active banners from the
636
* screen before the menu displays and restore them to the screen on exiting
643
* First we store a list of all the banners that are currently
646
local vec = new Vector(10);
648
forEachInstance(CustomBannerWindow, new function(ban) {
649
if(ban.shouldDisplay)
650
vec.append(ban); } );
652
/* deactive all active banners */
654
foreach(local ban in vec)
659
/* carry out the inherited menu display */
664
* Restore all the banners in our list of banners that were previously
665
* displayed. To ensure that they are activated in the right order
666
* we make what may be several passes through the list. On each pass
667
* we activate only those banners that don't depend on any inactive
668
* banners for their activation. Each time we activate a banner, we
669
* remove it from the list. On the next pass through the list any
670
* banners that depended on banners we have just activated may now themselves
671
* be activated, so we can carry on until every banner has been activated
672
* and removed from the list.
678
local bannerRemoved = nil;
680
foreach(local ban in vec)
684
if(ban.bannerArgs[1] != nil && ban.bannerArgs[1].handle_ == nil)
687
if(ban.bannerArgs[3] != nil && ban.bannerArgs[3].handle_ == nil)
691
vec.removeElement(ban);
692
bannerRemoved = true;
696
* If we didn't remove any banners on this pass through, we're
697
* potentially in an infinite loop, so we'd better break out