cfad47cfa3/t3compiler/tads3/lib/adv3/status.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library - Status Line
7
 *   
8
 *   This module defines the framework for displaying the status line,
9
 *   which is the area conventionally displayed at the top of the screen
10
 *   showing information such as the current location, score (if scoring is
11
 *   used at all), and number of turns.  
12
 */
13
14
/* include the library header */
15
#include "adv3.h"
16
17
18
/* ------------------------------------------------------------------------ */
19
/* 
20
 *   in case the 'score' module isn't included, make sure we can refer to
21
 *   totalScore as a property 
22
 */
23
property totalScore;
24
25
26
/* ------------------------------------------------------------------------ */
27
/*
28
 *   The banner window for the status line.  
29
 */
30
statuslineBanner: BannerWindow
31
    removeBanner()
32
    {
33
        /* inherit the base handling */
34
        inherited();
35
36
        /* 
37
         *   notify the statusLine object that it needs to refigure the
38
         *   display mode 
39
         */
40
        statusLine.statusDispMode = nil;
41
    }
42
43
    /* initialize */
44
    initBannerWindow()
45
    {
46
        /* if we're already initialized, do nothing */
47
        if (inited_)
48
            return;
49
50
        /* inherit the default handling (to set our 'inited_' flag) */
51
        inherited();
52
        
53
        /* tell the status line to initialize its banner window */
54
        statusLine.initBannerWindow(self);
55
    }
56
;
57
58
59
/* ------------------------------------------------------------------------ */
60
/*
61
 *   A special OutputStream for the <BANNER> tag contents.  This is really
62
 *   just part of the main output stream, but we use a separate output
63
 *   stream object so that we have our own separate stream state variables
64
 *   (for paragraph breaking and so forth).  
65
 */
66
transient statusTagOutputStream: OutputStream
67
    /*
68
     *   We're really part of the main window's output stream as far as the
69
     *   underlying interpreter I/O system is concerned, so we have to
70
     *   coordinate with the main game window's input manager. 
71
     */
72
    myInputManager = inputManager
73
74
    /* we sit atop the system-level main console output stream */
75
    writeFromStream(txt)
76
    {
77
        /* write the text directly to the main output stream */
78
        tadsSay(txt);
79
    }
80
;
81
82
/*
83
 *   A special OutputStream for the left half of the status line (the
84
 *   short description area) in text mode.  We use a separate stream for
85
 *   this because we must write the text using the output mode switching
86
 *   for the status line.
87
 *   
88
 *   We only use this stream when we use the old-style text-mode status
89
 *   line interface, which explicitly separates the status line into a
90
 *   left part and a right part.  When we have the banner API available in
91
 *   the interpreter, we'll use banners instead, since banners give us
92
 *   much more flexibility.  
93
 */
94
transient statusLeftOutputStream: OutputStream
95
    /* we sit atop the system-level main console output stream */
96
    writeFromStream(txt)
97
    {
98
        /* write the text directly to the main output stream */
99
        tadsSay(txt);
100
    }
101
;
102
103
/*
104
 *   A special OutputStream for the right half of the status line (the
105
 *   score/turn count area) in text mode.  We use a separate stream for
106
 *   this because we have to write this text with the special
107
 *   statusRight() intrinsic in text mode.
108
 *   
109
 *   We only use this stream when we use the old-style text-mode status
110
 *   line interface, which explicitly separates the status line into a
111
 *   left part and a right part.  When we have the banner API available in
112
 *   the interpreter, we'll use banners instead, since banners give us
113
 *   much more flexibility.  
114
 */
115
transient statusRightOutputStream: OutputStream
116
    /*
117
     *   Write from the stream.  We simply buffer up text until we're
118
     *   asked to display the final data. 
119
     */
120
    writeFromStream(txt)
121
    {
122
        /* buffer the text */
123
        buf_ += txt;
124
    }
125
126
    /*
127
     *   Flush the buffer.  This writes whatever we've buffered up to the
128
     *   right half of the text-mode status line. 
129
     */
130
    flushStream()
131
    {
132
        /* write the text to the system console */
133
        statusRight(buf_);
134
135
        /* we no longer have anything buffered */
136
        buf_ = '';
137
    }
138
139
    /* our buffered text */
140
    buf_ = ''
141
;
142
143
144
/* ------------------------------------------------------------------------ */
145
/*
146
 *   Statusline modes.  We have three different ways to display the
147
 *   statusline, depending on the level of support in the interpreter.
148
 *   
149
 *   StatusModeApi - use the banner API.  This is preferred method, because
150
 *   it gives us uniform capabilities on text and graphical interpreters,
151
 *   and provides an output stream for the statusline that is fully
152
 *   independent on the main game window's output stream.
153
 *   
154
 *   StatusModeTag - use the <BANNER> tag.  This is the method we must use
155
 *   for HTML-enabled interpreters that don't support the banner API.  This
156
 *   gives us the full formatting capabilities of HTML, but isn't as good
157
 *   as StatusModeApi because we have to share our output stream with the
158
 *   main game window.
159
 *   
160
 *   StatusModeText - use the old-style dedicated statusline in a text-only
161
 *   interpreter.  This is the least desirable method, because it gives us
162
 *   a rigid format for the statusline (exactly one line high, no control
163
 *   over colors, and with the strict left/right bifurcation).  We'll only
164
 *   use this method if we're on a text-only interpreter that doesn't
165
 *   support the banner API.  
166
 */
167
enum StatusModeApi, StatusModeTag, StatusModeText;
168
169
/* ------------------------------------------------------------------------ */
170
/*
171
 *   Status line - this is an abstract object that controls the status line
172
 *   display.  
173
 *   
174
 *   We provide two main methods: showStatusHtml, which shows the status
175
 *   line in HTML format, and showStatusText, which shows the status line
176
 *   in plain text mode.  To display the status line, we invoke one or the
177
 *   other of these methods, according to the current mode, to display the
178
 *   statusline.  The default implementations of these methods generate the
179
 *   appropriate formatting codes for a statusline with a left part and a
180
 *   right part, calling showStatusLeft and showStatusRight, respectively,
181
 *   to display the text for the parts.
182
 *   
183
 *   Games can customize the statusline at two levels.  At the simpler
184
 *   level, a game can modify showStatusLeft and/or showStatusRight to
185
 *   change the text displayed on the left and/or right of the statusline.
186
 *   Since these two methods are used regardless of the statusline style of
187
 *   the underlying interpreter, games don't have to worry about the
188
 *   different modes when overriding these.
189
 *   
190
 *   At the more complex level, a game can modify showStatusHtml and/or
191
 *   showStatusText.  Modifying these routines provides complete control
192
 *   over the formatting of the entir status line.  If a game wants to use
193
 *   something other than the traditional left/right display, it must
194
 *   modify these methods.
195
 *   
196
 *   This object is transient, because the statusline style is a function
197
 *   of the interpreter we're currently running on, and thus isn't suitable
198
 *   for saving persistently.
199
 */
200
transient statusLine: object
201
    /* 
202
     *   Show the status line, in HTML or text mode, as appropriate.  By
203
     *   default, the library sets this up as a "prompt daemon," which
204
     *   means that this will be called automatically just before each
205
     *   command line is read.  
206
     */
207
    showStatusLine()
208
    {
209
        local oldStr;
210
211
        /* if the status line isn't active, or there's no PC, skip this */
212
        if (statusDispMode == nil || gPlayerChar == nil)
213
            return;
214
215
        /* 
216
         *   showing the status line doesn't normally change any game
217
         *   state, so we can turn on the sense cache while generating the
218
         *   display 
219
         */
220
        libGlobal.enableSenseCache();
221
222
        /* 
223
         *   Enter status-line mode.  This will do whatever is required for
224
         *   our current status-line display style to prepare the output
225
         *   manager so that any text we display to the default output
226
         *   stream is displayed on the status line. 
227
         */
228
        oldStr = beginStatusLine();
229
230
        /* make sure we restore statusline mode before we're done */
231
        try
232
        {
233
            /*
234
             *   Generate a text or HTML status line, as appropriate.  If
235
             *   we're in <BANNER> tag mode or banner API mode, use HTML to
236
             *   format the contents of the status line; if we're using the
237
             *   old-style text mode, use plain text, since the formatting
238
             *   is rigidly defined in this mode.  
239
             */
240
            if (statusDispMode != StatusModeText)
241
            {
242
                /* show the HTML status line */
243
                showStatusHtml();
244
            }
245
            else
246
            {
247
                /* show the status line in plain text mode */
248
                showStatusText();
249
            }
250
        }
251
        finally
252
        {
253
            /* end status-line mode */
254
            endStatusLine(oldStr);
255
256
            /* turn off sense caching */
257
            libGlobal.disableSenseCache();
258
        }
259
    }
260
261
    /* prompt-daemon showing the status line */
262
    showStatusLineDaemon()
263
    {
264
        /* show the status line as normal */
265
        showStatusLine();
266
267
        /* 
268
         *   Explicitly flush the status line if it's in a banner window.
269
         *   This will ensure that we'll redraw the status line on each
270
         *   turn if we're reading an input script, which is nice because
271
         *   it provides a visual indication that something's happening. 
272
         */
273
        if (statusDispMode == StatusModeApi)
274
            statuslineBanner.flushBanner();
275
    }
276
277
    /*
278
     *   Show the status line in HTML format.  Our default implementation
279
     *   shows the traditional two-part (left/right) status line, using
280
     *   showStatusLeft() and showStatusRight() to display the parts.  
281
     */
282
    showStatusHtml()
283
    {
284
        /* hyperlink the location name to a "look around" command */
285
        "<a plain href='<<gLibMessages.commandLookAround>>'>";
286
            
287
        /* show the left part of the status line */
288
        showStatusLeft();
289
            
290
        /* set up for the score part on the right half */
291
        "</a><tab align=right><a plain
292
            href='<<gLibMessages.commandFullScore>>'>";
293
        
294
        /* show the right part of the status line */
295
        showStatusRight();
296
        
297
        /* end the score link */
298
        "</a>";
299
        
300
        /* add the status-line exit list, if desired */
301
        if (gPlayerChar.location != nil)
302
            gPlayerChar.location.showStatuslineExits();
303
    }
304
305
    /*
306
     *   Get the estimated HTML-style banner height, in lines of text.
307
     *   This is used to set the status line banner size for platforms
308
     *   where sizing to the exact height of the rendered contents isn't
309
     *   supported.
310
     *   
311
     *   If showStatusHtml() is overridden to display more or fewer lines
312
     *   of text than the basic implementation here, then this routine must
313
     *   be overridden as well to reflect the new height.  
314
     */
315
    getEstimatedHeightHtml()
316
    {
317
        local ht;
318
        
319
        /* 
320
         *   we need one line for the basic display (the location name and
321
         *   score/turn count) 
322
         */
323
        ht = 1;
324
325
        /* add in the estimated height of the exits display, if appropriate */
326
        if (gPlayerChar.location != nil)
327
            ht += gPlayerChar.location.getStatuslineExitsHeight();
328
329
        /* return the result */
330
        return ht;
331
    }
332
333
    /*
334
     *   Show the statusline in text mode.  Our default implementation
335
     *   shows the traditional two-part (left/right) status line, using
336
     *   showStatusLeft() and showStatusRight() to display the parts.  
337
     */
338
    showStatusText()
339
    {
340
        /* show the left part of the display */
341
        showStatusLeft();
342
343
        /* switch to the right-side status stream */
344
        outputManager.setOutputStream(statusRightOutputStream);
345
346
        /* show the right-half text */
347
        showStatusRight();
348
        
349
        /* flush the right-side stream */
350
        statusRightOutputStream.flushStream();
351
    }
352
353
    /*
354
     *   Show the left part of a standard left/right statusline.  By
355
     *   default, we'll show the player character's location, by calling
356
     *   statusName() on the PC's immediate container.  
357
     */
358
    showStatusLeft()
359
    {
360
        local actor;
361
362
        /* get the player character actor */
363
        actor = gPlayerChar;
364
365
        "<.statusroom>";
366
367
        /* show the actor's location's status name */
368
        if (actor != nil && actor.location != nil)
369
            actor.location.statusName(actor);
370
371
        "<./statusroom>";
372
    }
373
374
    /*
375
     *   Show the right part of a standard left/right statusline.  By
376
     *   default, we'll show the current score, a slash, and the number of
377
     *   turns. 
378
     */
379
    showStatusRight()
380
    {
381
        local s;
382
383
        /* if there's a score object, show the score */
384
        if ((s = libGlobal.scoreObj) != nil)
385
        {
386
            /* show the score and the number of turns so far */
387
            "<.statusscore><<s.totalScore>>/<<
388
            libGlobal.totalTurns>><./statusscore>";
389
        }
390
    }
391
392
    /*
393
     *   Set up the status line's color scheme.  This is called each time
394
     *   we redraw the status line to set the background and text colors.
395
     *   We simply show a <BODY> tag that selects the parameterized colors
396
     *   STATUSBG and STATUSTEXT.  (These are called "parameterized" colors
397
     *   because they don't select specific colors, but rather select
398
     *   whatever colors the interpreter wishes to use for the status line.
399
     *   In many cases, the interpreter lets the user select these colors
400
     *   via a Preferences dialog.)  
401
     */
402
    setColorScheme()
403
    {
404
        /* set up the interpreter's standard status line colors */
405
        "<body bgcolor=statusbg text=statustext>";
406
    }
407
408
    /* 
409
     *   Begin status-line mode.  This sets up the output manager so that
410
     *   text written to the default output stream is displayed on the
411
     *   status line.  Returns the original output stream.
412
     */
413
    beginStatusLine()
414
    {
415
        local oldStr;
416
417
        /* check what kind of statusline display we're using */
418
        switch(statusDispMode)
419
        {
420
        case StatusModeApi:
421
            /* 
422
             *   We have a banner API window.  Start by clearing the
423
             *   window, so we can completely replace everything in it.  
424
             */
425
            statuslineBanner.clearWindow();
426
427
            /*
428
             *   If the platform doesn't support size-to-contents, then set
429
             *   the height to our best estimate for the size.
430
             *   
431
             *   If we do support size-to-contents, we'll set the height to
432
             *   the exact rendered size when we're done, so we don't need
433
             *   to worry about setting an estimate; indicate this to the
434
             *   interpreter by setting the is-advisory flag to true.  
435
             */
436
            statuslineBanner.setSize(getEstimatedHeightHtml(),
437
                                     BannerSizeAbsolute, true);
438
439
            /* switch to the banner's output stream */
440
            oldStr = statuslineBanner.setOutputStream();
441
442
            /* set up the statusline color in the window */
443
            setColorScheme();
444
445
            /* done */
446
            break;
447
448
        case StatusModeTag:
449
            /* 
450
             *   We're using <BANNER> tags.  Switch to our statusline
451
             *   output stream.  
452
             */
453
            oldStr = outputManager.setOutputStream(statusTagOutputStream);
454
455
            /* set up the <BANNER> tag */
456
            "<banner id=StatusLine height=previous border>";
457
458
            /* set up the color scheme */
459
            setColorScheme();
460
461
            /* done */
462
            break;
463
464
        case StatusModeText:
465
            /* flush the main window */
466
            flushOutput();
467
468
            /* plain text mode - enter text status mode */
469
            statusMode(StatModeStatus);
470
471
            /* switch to the status-left output stream */
472
            oldStr = outputManager.setOutputStream(statusLeftOutputStream);
473
474
            /* done */
475
            break;
476
        }
477
478
        /* return the original output stream */
479
        return oldStr;
480
    }
481
482
    /* end statusline display */
483
    endStatusLine(oldStr)
484
    {
485
        /* restore the old default output stream */
486
        outputManager.setOutputStream(oldStr);
487
488
        /* check the type of statusline we're generating */
489
        switch (statusDispMode)
490
        {
491
        case StatusModeApi:
492
            /* banner API mode - end the last line */
493
            statuslineBanner.writeToBanner('\n');
494
495
            /* 
496
             *   Size the window to its current contents.  This doesn't
497
             *   work everywhere - on a few platforms, it does nothing -
498
             *   but this will give us the optimal size where it's
499
             *   supported.  On platforms that don't support this, it'll do
500
             *   nothing, which means we'll simply be left with the
501
             *   "advisory" size we established earlier.  
502
             */
503
            statuslineBanner.sizeToContents();
504
            break;
505
506
        case StatusModeTag:
507
            /* HTML <BANNER> mode - end the <BANNER> tag */
508
            statusTagOutputStream.writeToStream('</banner>');
509
            break;
510
511
        case StatusModeText:
512
            /* plain text statusline - end status mode */
513
            statusMode(StatModeNormal);
514
        }
515
    }
516
517
    /*
518
     *   Initialize the banner window, given the BannerWindow object
519
     *   representing the status line banner API window.  
520
     */
521
    initBannerWindow(win)
522
    {
523
        /* 
524
         *   Try showing the banner API window.  If that succeeds, use the
525
         *   banner API window, since it's the most portable and flexible
526
         *   way to show the status line.  If we can't create the banner
527
         *   API window, it means we're on an interpreter that doesn't
528
         *   support the banner API, so fall back on one of the older, less
529
         *   flexible mechanisms; which older mechanism we choose depends
530
         *   on what kind of interpreter we're on.
531
         *   
532
         *   Since we create the status line banner during initialization
533
         *   and normally leave it as the first item in the display list at
534
         *   all times, we can attach to an existing status line banner
535
         *   window if there is one.  This will avoid unnecessary redrawing
536
         *   on RESTART.  
537
         */
538
        if (win.showBanner(nil, BannerFirst, nil, BannerTypeText,
539
                           BannerAlignTop, nil, nil,
540
                           BannerStyleBorder | BannerStyleTabAlign))
541
        {
542
            /* 
543
             *   we successfully created the banner window - use the banner
544
             *   API to show the status line 
545
             */
546
            statusDispMode = StatusModeApi;
547
        }
548
        else if (systemInfo(SysInfoInterpClass) == SysInfoIClassHTML)
549
        {
550
            /*
551
             *   We failed to create a banner API window, and we're running
552
             *   on a full HTML interpreter, so use <BANNER> tags to
553
             *   produce the status line.  
554
             */
555
            statusDispMode = StatusModeTag;
556
        }
557
        else
558
        {
559
            /*
560
             *   We failed to create a banner API window, and we're running
561
             *   on a text-only interpreter - use the old-style
562
             *   fixed-format status line mechanism.  
563
             */
564
            statusDispMode = StatusModeText;
565
        }
566
    }
567
568
    /* 
569
     *   The status mode we're using.  If this is nil, it means we haven't
570
     *   chosen a mode yet. 
571
     */
572
    statusDispMode = nil
573
;
574