| | 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 | |