| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library: banner manager |
| | 7 | * |
| | 8 | * This module defines the banner manager, which provides high-level |
| | 9 | * services to create and manipulate banner windows. |
| | 10 | * |
| | 11 | * A "banner" is an independent window shown within the interpreter's |
| | 12 | * main application display frame (which might be the entire screen on a |
| | 13 | * character-mode terminal, or could be a window in a GUI system). The |
| | 14 | * game can control the creation and destruction of banner windows, and |
| | 15 | * can control their placement and size. |
| | 16 | * |
| | 17 | * This implementation is based in part on Steve Breslin's banner |
| | 18 | * manager, used by permission. |
| | 19 | */ |
| | 20 | |
| | 21 | #include "adv3.h" |
| | 22 | |
| | 23 | /* ------------------------------------------------------------------------ */ |
| | 24 | /* |
| | 25 | * A BannerWindow corresponds to an on-screen banner. For each banner |
| | 26 | * window a game wants to display, the game must create an object of this |
| | 27 | * class. |
| | 28 | * |
| | 29 | * Note that merely creating a BannerWindow object doesn't actually |
| | 30 | * display a banner window. Once a BannerWindow is created, the game |
| | 31 | * must call the object's showBanner() method to create the on-screen |
| | 32 | * window for the banner. |
| | 33 | * |
| | 34 | * BannerWindow instances are intended to be persistent (not transient). |
| | 35 | * The banner manager keeps track of each banner window that's actually |
| | 36 | * being displayed separately via an internal transient object; the game |
| | 37 | * doesn't need to worry about these tracking objects, since the banner |
| | 38 | * manager automatically handles them. |
| | 39 | */ |
| | 40 | class BannerWindow: object |
| | 41 | /* |
| | 42 | * Construct the object. |
| | 43 | * |
| | 44 | * 'id' is a globally unique identifying string for the banner. When |
| | 45 | * we dynamically create a banner object, we have to provide a unique |
| | 46 | * identifying string, so that we can correlate transient on-screen |
| | 47 | * banners with the banners in a saved state when restoring the saved |
| | 48 | * state. |
| | 49 | * |
| | 50 | * Note that no ID string is needed for BannerWindow objects defined |
| | 51 | * statically at compile-time, because the object itself ('self') is |
| | 52 | * a suitably unique and stable identifier. |
| | 53 | */ |
| | 54 | construct(id) |
| | 55 | { |
| | 56 | /* remember my unique identifier */ |
| | 57 | id_ = id; |
| | 58 | } |
| | 59 | |
| | 60 | /* |
| | 61 | * Show the banner. The game should call this method when it first |
| | 62 | * wants to display the banner. |
| | 63 | * |
| | 64 | * 'parent' is the parent banner; this is an existing BannerWindow |
| | 65 | * object. If 'parent' is nil, then the parent is the main game |
| | 66 | * text window. The new window's display space is obtained by |
| | 67 | * carving space out of the parent's area, according to the |
| | 68 | * alignment and size values specified. |
| | 69 | * |
| | 70 | * 'where' and 'other' give the position of the banner among the |
| | 71 | * children of the given parent. 'where' is one of the constants |
| | 72 | * BannerFirst, BannerLast, BannerBefore, or BannerAfter. If |
| | 73 | * 'where' is BannerBefore or BannerAfter, 'other' gives the |
| | 74 | * BannerWindow object to be used as the reference point in the |
| | 75 | * parent's child list; 'other' is ignored in other cases. Note |
| | 76 | * that 'other' must always be another child of the same parent; if |
| | 77 | * it's not, then we act as though 'where' were given as BannerLast. |
| | 78 | * |
| | 79 | * 'windowType' is a BannerTypeXxx constant giving the new window's |
| | 80 | * type. |
| | 81 | * |
| | 82 | * 'align' is a BannerAlignXxx constant giving the alignment of the |
| | 83 | * new window. 'size' is an integer giving the size of the banner, |
| | 84 | * in units specified by 'sizeUnits', which is a BannerSizeXxx |
| | 85 | * constant. If 'size' is nil, it indicates that the caller doesn't |
| | 86 | * care about the size, usually because the caller will be resizing |
| | 87 | * the banner soon anyway; the banner will initially have zero size |
| | 88 | * in this case if we create a new window, or will retain the |
| | 89 | * existing size if there's already a system window. |
| | 90 | * |
| | 91 | * 'styleFlags' is a combination of BannerStyleXxx constants |
| | 92 | * (combined with the bitwise OR operator, '|'), giving the requested |
| | 93 | * display style of the new banner window. |
| | 94 | * |
| | 95 | * Note that if we already have a system banner window, and the |
| | 96 | * existing banner window has the same characteristics as the new |
| | 97 | * creation parameters, we'll simply re-use the existing window |
| | 98 | * rather than closing and re-creating it; this reduces unnecessary |
| | 99 | * redrawing in cases where the window isn't changing. If the caller |
| | 100 | * explicitly wants to create a new window even if we already have a |
| | 101 | * window, the caller should simply call removeBanner() before |
| | 102 | * calling this routine. |
| | 103 | */ |
| | 104 | showBanner(parent, where, other, windowType, |
| | 105 | align, size, sizeUnits, styleFlags) |
| | 106 | { |
| | 107 | local parentID; |
| | 108 | local otherID; |
| | 109 | |
| | 110 | /* note the ID's of the parent window and the insertion point */ |
| | 111 | parentID = (parent != nil ? parent.getBannerID() : nil); |
| | 112 | otherID = (other != nil ? other.getBannerID() : nil); |
| | 113 | |
| | 114 | /* |
| | 115 | * if we have an 'other' specified, its parent must match our |
| | 116 | * proposed parent; otherwise, ignore 'other' and insert at the |
| | 117 | * end of the parent list |
| | 118 | */ |
| | 119 | if (other != nil && other.parentID_ != parentID) |
| | 120 | { |
| | 121 | other = nil; |
| | 122 | where = BannerLast; |
| | 123 | } |
| | 124 | |
| | 125 | /* if we already have an existing banner window, check for a match */ |
| | 126 | if (handle_ != nil) |
| | 127 | { |
| | 128 | local t; |
| | 129 | local match; |
| | 130 | |
| | 131 | /* presume we won't find an exact match */ |
| | 132 | match = nil; |
| | 133 | |
| | 134 | /* we already have a window - get the UI tracker object */ |
| | 135 | if ((t = bannerUITracker.getTracker(self)) != nil) |
| | 136 | { |
| | 137 | /* check the placement, window type, alignment, and style */ |
| | 138 | match = (t.windowType_ == windowType |
| | 139 | && t.parentID_ == parentID |
| | 140 | && t.align_ == align |
| | 141 | && t.styleFlags_ == styleFlags |
| | 142 | && bannerUITracker.orderMatches(t, where, otherID)); |
| | 143 | } |
| | 144 | |
| | 145 | /* |
| | 146 | * if it doesn't match the existing window, close it, so that |
| | 147 | * we will open a brand new window with the new |
| | 148 | * characteristics |
| | 149 | */ |
| | 150 | if (!match) |
| | 151 | removeBanner(); |
| | 152 | } |
| | 153 | |
| | 154 | /* if the system-level banner doesn't already exist, create it */ |
| | 155 | if (handle_ == nil) |
| | 156 | { |
| | 157 | /* create my system-level banner window */ |
| | 158 | if (!createSystemBanner(parent, where, other, windowType, align, |
| | 159 | size, sizeUnits, styleFlags)) |
| | 160 | { |
| | 161 | /* we couldn't create the system banner - give up */ |
| | 162 | return nil; |
| | 163 | } |
| | 164 | |
| | 165 | /* create our output stream */ |
| | 166 | createOutputStream(); |
| | 167 | |
| | 168 | /* add myself to the UI tracker's active banner list */ |
| | 169 | bannerUITracker.addBanner(handle_, outputStream_, getBannerID(), |
| | 170 | parentID, where, other, windowType, |
| | 171 | align, styleFlags); |
| | 172 | } |
| | 173 | else |
| | 174 | { |
| | 175 | /* |
| | 176 | * Our system-level window already exists, so we don't need |
| | 177 | * to create a new one. However, our size could be |
| | 178 | * different, so explicitly set the requested size if it |
| | 179 | * doesn't match our recorded size. If the size is given as |
| | 180 | * nil, leave the size as it is; a nil size indicates that |
| | 181 | * the caller doesn't care about the size (probably because |
| | 182 | * the caller is going to change the size shortly anyway), |
| | 183 | * so we can avoid unnecessary redrawing by leaving the size |
| | 184 | * as it is for now. |
| | 185 | */ |
| | 186 | if (size != nil && (size != size_ || sizeUnits != sizeUnits_)) |
| | 187 | bannerSetSize(handle_, size, sizeUnits, nil); |
| | 188 | } |
| | 189 | |
| | 190 | /* |
| | 191 | * remember the creation parameters, so that we can re-create the |
| | 192 | * banner with the same characteristics in the future if we |
| | 193 | * should need to restore the banner from a saved position |
| | 194 | */ |
| | 195 | parentID_ = parentID; |
| | 196 | windowType_ = windowType; |
| | 197 | align_ = align; |
| | 198 | size_ = size; |
| | 199 | sizeUnits_ = sizeUnits; |
| | 200 | styleFlags_ = styleFlags; |
| | 201 | |
| | 202 | /* |
| | 203 | * Add myself to the persistent banner tracker's active list. Do |
| | 204 | * this even if we already had a system handle, since we might be |
| | 205 | * initializing the window as part of a persistent restore |
| | 206 | * operation, in which case the persistent tracking object might |
| | 207 | * not yet exist. (This seems backwards: if we're restoring a |
| | 208 | * persistent state, surely the persistent tracker would already |
| | 209 | * exist. In fact, the case we're really handling is where the |
| | 210 | * window is open in the transient UI, because it was already |
| | 211 | * open in the ongoing session; but the persistent state we're |
| | 212 | * restoring doesn't include the window. This is most likely to |
| | 213 | * occur after a RESTART, since we could have a window that is |
| | 214 | * always opened immediately at start-up and thus will be in the |
| | 215 | * transient state up to and through the RESTART, but is only |
| | 216 | * created as part of the initialization process.) |
| | 217 | */ |
| | 218 | bannerTracker.addBanner(self, parent, where, other); |
| | 219 | |
| | 220 | /* indicate success */ |
| | 221 | return true; |
| | 222 | } |
| | 223 | |
| | 224 | /* |
| | 225 | * Remove the banner. This removes the banner's on-screen window. |
| | 226 | * The BannerWindow object itself remains valid, but after this |
| | 227 | * method returns, the BannerWindow no longer has an associated |
| | 228 | * display window. |
| | 229 | * |
| | 230 | * Note that any child banners of ours will become undisplayable |
| | 231 | * after we're gone. A child banner depends upon its parent to |
| | 232 | * obtain display space, so once the parent is gone, its children no |
| | 233 | * longer have any way to obtain any display space. Our children |
| | 234 | * remain valid objects even after we're closed, but they won't be |
| | 235 | * visible on the display. |
| | 236 | */ |
| | 237 | removeBanner() |
| | 238 | { |
| | 239 | /* if I don't have a system-level handle, there's nothing to do */ |
| | 240 | if (handle_ == nil) |
| | 241 | return; |
| | 242 | |
| | 243 | /* remove my system-level banner window */ |
| | 244 | bannerDelete(handle_); |
| | 245 | |
| | 246 | /* our system-level window is gone, so forget its handle */ |
| | 247 | handle_ = nil; |
| | 248 | |
| | 249 | /* we only need an output stream when we're active */ |
| | 250 | outputStream_ = nil; |
| | 251 | |
| | 252 | /* remove myself from the UI trackers's active list */ |
| | 253 | bannerUITracker.removeBanner(getBannerID()); |
| | 254 | |
| | 255 | /* remove myself from the persistent banner tracker's active list */ |
| | 256 | bannerTracker.removeBanner(self); |
| | 257 | } |
| | 258 | |
| | 259 | /* write the given text to the banner */ |
| | 260 | writeToBanner(txt) |
| | 261 | { |
| | 262 | /* write the text to our underlying output stream */ |
| | 263 | outputStream_.writeToStream(txt); |
| | 264 | } |
| | 265 | |
| | 266 | /* |
| | 267 | * Invoke the given callback function, setting the default output |
| | 268 | * stream to the banner's output stream for the duration of the |
| | 269 | * call. This allows invoking any code that writes to the current |
| | 270 | * default output stream and displaying the result in the banner. |
| | 271 | */ |
| | 272 | captureOutput(func) |
| | 273 | { |
| | 274 | local oldStr; |
| | 275 | |
| | 276 | /* make my output stream the global default */ |
| | 277 | oldStr = outputManager.setOutputStream(outputStream_); |
| | 278 | |
| | 279 | /* make sure we restore the default output stream on the way out */ |
| | 280 | try |
| | 281 | { |
| | 282 | /* invoke the callback function */ |
| | 283 | (func)(); |
| | 284 | } |
| | 285 | finally |
| | 286 | { |
| | 287 | /* restore the original default output stream */ |
| | 288 | outputManager.setOutputStream(oldStr); |
| | 289 | } |
| | 290 | } |
| | 291 | |
| | 292 | /* |
| | 293 | * Make my output stream the default in the output manager. Returns |
| | 294 | * the previous default output stream; the caller can note the return |
| | 295 | * value and use it later to restore the original output stream via a |
| | 296 | * call to outputManager.setOutputStream(), if desired. |
| | 297 | */ |
| | 298 | setOutputStream() |
| | 299 | { |
| | 300 | /* set my stream as the default */ |
| | 301 | return outputManager.setOutputStream(outputStream_); |
| | 302 | } |
| | 303 | |
| | 304 | /* flush any pending output to the banner */ |
| | 305 | flushBanner() { bannerFlush(handle_); } |
| | 306 | |
| | 307 | /* |
| | 308 | * Set the banner window to a specific size. 'size' is the new |
| | 309 | * size, in units given by 'sizeUnits', which is a BannerSizeXxx |
| | 310 | * constant. |
| | 311 | * |
| | 312 | * 'isAdvisory' is true or nil; if true, it indicates that the size |
| | 313 | * setting is purely advisory, and that a sizeToContents() call will |
| | 314 | * eventually follow to set the actual size. When 'isAdvisory is |
| | 315 | * true, the interpreter is free to ignore the request if |
| | 316 | * sizeToContents() |
| | 317 | */ |
| | 318 | setSize(size, sizeUnits, isAdvisory) |
| | 319 | { |
| | 320 | /* set the underlying system window size */ |
| | 321 | bannerSetSize(handle_, size, sizeUnits, isAdvisory); |
| | 322 | |
| | 323 | /* |
| | 324 | * remember my new size in case we have to re-create the banner |
| | 325 | * from a saved state |
| | 326 | */ |
| | 327 | size_ = size; |
| | 328 | sizeUnits_ = sizeUnits; |
| | 329 | } |
| | 330 | |
| | 331 | /* |
| | 332 | * Size the banner to its current contents. Note that some systems |
| | 333 | * do not support this operation, so callers should always make an |
| | 334 | * advisory call to setSize() first to set a size based on the |
| | 335 | * expected content size. |
| | 336 | */ |
| | 337 | sizeToContents() |
| | 338 | { |
| | 339 | /* size our system-level window to our contents */ |
| | 340 | bannerSizeToContents(handle_); |
| | 341 | } |
| | 342 | |
| | 343 | /* |
| | 344 | * Clear my banner window. This clears out all of the contents of |
| | 345 | * our on-screen display area. |
| | 346 | */ |
| | 347 | clearWindow() |
| | 348 | { |
| | 349 | /* clear our system-level window */ |
| | 350 | bannerClear(handle_); |
| | 351 | } |
| | 352 | |
| | 353 | /* set the text color in the banner */ |
| | 354 | setTextColor(fg, bg) { bannerSetTextColor(handle_, fg, bg); } |
| | 355 | |
| | 356 | /* set the screen color in the banner window */ |
| | 357 | setScreenColor(color) { bannerSetScreenColor(handle_, color); } |
| | 358 | |
| | 359 | /* |
| | 360 | * Move the cursor to the given row/column position. This can only |
| | 361 | * be used with text-grid banners; for ordinary text banners, this |
| | 362 | * has no effect. |
| | 363 | */ |
| | 364 | cursorTo(row, col) { bannerGoTo(handle_, row, col); } |
| | 365 | |
| | 366 | /* |
| | 367 | * Get the banner identifier. If our 'id_' property is set to nil, |
| | 368 | * we'll assume that we're a statically-defined object, in which case |
| | 369 | * 'self' is a suitable identifier. Otherwise, we'll return the |
| | 370 | * identifier string. |
| | 371 | */ |
| | 372 | getBannerID() { return id_ != nil ? id_ : self; } |
| | 373 | |
| | 374 | /* |
| | 375 | * Restore this banner. This is called after a RESTORE or UNDO |
| | 376 | * operation that finds that this banner was being displayed at the |
| | 377 | * time the state was saved but is not currently displayed in the |
| | 378 | * active UI. We'll show the banner using the characteristics saved |
| | 379 | * persistently. |
| | 380 | */ |
| | 381 | showForRestore(parent, where, other) |
| | 382 | { |
| | 383 | /* show myself, using my saved characteristics */ |
| | 384 | showBanner(parent, where, other, windowType_, align_, |
| | 385 | size_, sizeUnits_, styleFlags_); |
| | 386 | |
| | 387 | /* update my contents */ |
| | 388 | updateForRestore(); |
| | 389 | } |
| | 390 | |
| | 391 | /* |
| | 392 | * Create our output stream. We'll create a BannerOutputStream and |
| | 393 | * set it up with our default output filters. Subclasses can |
| | 394 | * override this as needed to customize the output stream. |
| | 395 | */ |
| | 396 | createOutputStream() |
| | 397 | { |
| | 398 | /* create a banner output stream */ |
| | 399 | outputStream_ = new transient BannerOutputStream(handle_); |
| | 400 | |
| | 401 | /* set up the default filters */ |
| | 402 | outputStream_.addOutputFilter(typographicalOutputFilter); |
| | 403 | outputStream_.addOutputFilter(new transient ParagraphManager()); |
| | 404 | outputStream_.addOutputFilter(styleTagFilter); |
| | 405 | outputStream_.addOutputFilter(langMessageBuilder); |
| | 406 | } |
| | 407 | |
| | 408 | /* |
| | 409 | * Create the system-level banner window. This can be customized as |
| | 410 | * needed, although this default implementation should be suitable |
| | 411 | * for most instances. |
| | 412 | * |
| | 413 | * Returns true if we are successful in creating the system window, |
| | 414 | * nil if we fail. |
| | 415 | */ |
| | 416 | createSystemBanner(parent, where, other, windowType, align, |
| | 417 | size, sizeUnits, styleFlags) |
| | 418 | { |
| | 419 | /* create the system-level window */ |
| | 420 | handle_ = bannerCreate(parent != nil ? parent.handle_ : nil, |
| | 421 | where, other != nil ? other.handle_ : nil, |
| | 422 | windowType, align, size, sizeUnits, |
| | 423 | styleFlags); |
| | 424 | |
| | 425 | /* if we got a valid handle, we succeeded */ |
| | 426 | return (handle_ != nil); |
| | 427 | } |
| | 428 | |
| | 429 | /* |
| | 430 | * Update my contents after being restored. By default, this does |
| | 431 | * nothing; instances might want to override this to refresh the |
| | 432 | * contents of the banner if the banner is normally updated only in |
| | 433 | * response to specific events. Note that it's not necessary to do |
| | 434 | * anything here if the banner will soon be updated automatically as |
| | 435 | * part of normal processing; for example, the status line banner is |
| | 436 | * updated at each new command line via a prompt-daemon, so there's |
| | 437 | * no need for the status line banner to do anything here. |
| | 438 | */ |
| | 439 | updateForRestore() |
| | 440 | { |
| | 441 | /* do nothing by default; subclasses can override as needed */ |
| | 442 | } |
| | 443 | |
| | 444 | /* |
| | 445 | * Initialize the banner window. This is called during |
| | 446 | * initialization (when first starting the game, or when resetting |
| | 447 | * with RESTART). If the banner is to be displayed from the start of |
| | 448 | * the game, this can set up the on-screen display. |
| | 449 | * |
| | 450 | * Note that we might already have an on-screen handle when this is |
| | 451 | * called. This indicates that we're restarting an ongoing session, |
| | 452 | * and that this banner already existed in the session before the |
| | 453 | * RESTART operation. If desired, we can attach ourselves to the |
| | 454 | * existing on-screen banner, avoiding the redrawing that would occur |
| | 455 | * if we created a new window. |
| | 456 | * |
| | 457 | * If this window depends upon another window for its layout order |
| | 458 | * placement (i.e., we'll call showBanner() with another BannerWindow |
| | 459 | * given as the 'other' parameter), then this routine should call the |
| | 460 | * other window's initBannerWindow() method before creating its own |
| | 461 | * window, to ensure that the other window has a system window and |
| | 462 | * thus will be meaningful to establish the layout order. |
| | 463 | * |
| | 464 | * Overriding implementations should check the 'inited_' property. |
| | 465 | * If this property is true, then it can be assumed that we've |
| | 466 | * already been initialized and don't require further initialization. |
| | 467 | * This routine can be called multiple times because dependent |
| | 468 | * windows might call us directly, before we're called for our |
| | 469 | * regular initialization. |
| | 470 | */ |
| | 471 | initBannerWindow() |
| | 472 | { |
| | 473 | /* by default, simply note that we've been initialized */ |
| | 474 | inited_ = true; |
| | 475 | } |
| | 476 | |
| | 477 | /* flag: this banner has been initialized with initBannerWindow() */ |
| | 478 | inited_ = nil |
| | 479 | |
| | 480 | /* |
| | 481 | * The creator-assigned ID string to identify the banner |
| | 482 | * persistently. This is only needed for banners created |
| | 483 | * dynamically; for BannerWindow objects defined statically at |
| | 484 | * compile time, simply leave this value as nil, and we'll use the |
| | 485 | * object itself as the identifier. |
| | 486 | */ |
| | 487 | id_ = nil |
| | 488 | |
| | 489 | /* the handle to my system-level banner window */ |
| | 490 | handle_ = nil |
| | 491 | |
| | 492 | /* |
| | 493 | * My output stream - this is a transient OutputStream instance. |
| | 494 | * We'll automatically create an output stream when we show the |
| | 495 | * banner. |
| | 496 | */ |
| | 497 | outputStream_ = nil |
| | 498 | |
| | 499 | /* |
| | 500 | * Creation parameters. We store these when we create the banner, |
| | 501 | * and update them as needed when the banner's display attributes |
| | 502 | * are changed. |
| | 503 | */ |
| | 504 | parentID_ = nil |
| | 505 | windowType_ = nil |
| | 506 | align_ = nil |
| | 507 | size_ = nil |
| | 508 | sizeUnits_ = nil |
| | 509 | styleFlags_ = nil |
| | 510 | ; |
| | 511 | |
| | 512 | /* ------------------------------------------------------------------------ */ |
| | 513 | /* |
| | 514 | * Banner Output Stream. This is a specialization of OutputStream that |
| | 515 | * writes to a banner window. |
| | 516 | */ |
| | 517 | class BannerOutputStream: OutputStream |
| | 518 | /* construct */ |
| | 519 | construct(handle) |
| | 520 | { |
| | 521 | /* inherit base class constructor */ |
| | 522 | inherited(); |
| | 523 | |
| | 524 | /* remember my banner window handle */ |
| | 525 | handle_ = handle; |
| | 526 | } |
| | 527 | |
| | 528 | /* execute preinitialization */ |
| | 529 | execute() |
| | 530 | { |
| | 531 | /* |
| | 532 | * We shouldn't need to do anything during pre-initialization, |
| | 533 | * since we should always be constructed dynamically by a |
| | 534 | * BannerWindow. Don't even inherit the base class |
| | 535 | * initialization, since it could clear out state that we want to |
| | 536 | * keep through a restart, restore, etc. |
| | 537 | */ |
| | 538 | } |
| | 539 | |
| | 540 | /* write text from the stream to the interpreter I/O system */ |
| | 541 | writeFromStream(txt) |
| | 542 | { |
| | 543 | /* write the text to the underlying system banner window */ |
| | 544 | bannerSay(handle_, txt); |
| | 545 | } |
| | 546 | |
| | 547 | /* our system-level banner window handle */ |
| | 548 | handle_ = nil |
| | 549 | ; |
| | 550 | |
| | 551 | |
| | 552 | /* ------------------------------------------------------------------------ */ |
| | 553 | /* |
| | 554 | * The banner UI tracker. This object keeps track of the current user |
| | 555 | * interface display state; this object is transient because the |
| | 556 | * interpreter's user interface is not part of the persistence |
| | 557 | * mechanism. |
| | 558 | */ |
| | 559 | transient bannerUITracker: object |
| | 560 | /* add a banner to the active display list */ |
| | 561 | addBanner(handle, ostr, id, parentID, where, other, |
| | 562 | windowType, align, styleFlags) |
| | 563 | { |
| | 564 | local uiWin; |
| | 565 | local parIdx; |
| | 566 | local idx; |
| | 567 | |
| | 568 | /* create a transient BannerUIWindow object to track the banner */ |
| | 569 | uiWin = new transient BannerUIWindow(handle, ostr, id, parentID, |
| | 570 | windowType, align, styleFlags); |
| | 571 | |
| | 572 | /* |
| | 573 | * Find the parent in the list. If there's no parent, the |
| | 574 | * parent is the main window; consider it to be at imaginary |
| | 575 | * index zero in the list. |
| | 576 | */ |
| | 577 | parIdx = (parentID == nil |
| | 578 | ? 0 : activeBanners_.indexWhich({x: x.id_ == parentID})); |
| | 579 | |
| | 580 | /* insert the banner at the proper point in our list */ |
| | 581 | switch(where) |
| | 582 | { |
| | 583 | case BannerFirst: |
| | 584 | /* |
| | 585 | * insert as the first child of the parent - put it |
| | 586 | * immediately after the parent in the list |
| | 587 | */ |
| | 588 | activeBanners_.insertAt(parIdx + 1, uiWin); |
| | 589 | break; |
| | 590 | |
| | 591 | case BannerLast: |
| | 592 | ins_last: |
| | 593 | /* |
| | 594 | * Insert as the last child of the parent: insert |
| | 595 | * immediately after the last window that descends from the |
| | 596 | * parent. |
| | 597 | */ |
| | 598 | activeBanners_.insertAt(skipDescendants(parIdx), uiWin); |
| | 599 | break; |
| | 600 | |
| | 601 | case BannerBefore: |
| | 602 | case BannerAfter: |
| | 603 | /* find the reference point ID in our list */ |
| | 604 | idx = activeBanners_.indexWhich( |
| | 605 | {x: x.id_ == other.getBannerID()}); |
| | 606 | |
| | 607 | /* |
| | 608 | * if we didn't find the reference point, or the reference |
| | 609 | * point item doesn't have the same parent as the new item, |
| | 610 | * then ignore the reference point and instead insert at the |
| | 611 | * end of the parent's child list |
| | 612 | */ |
| | 613 | if (idx == nil || activeBanners_[idx].parentID_ != parentID) |
| | 614 | goto ins_last; |
| | 615 | |
| | 616 | /* |
| | 617 | * if inserting after, skip the reference item and all |
| | 618 | * of its descendants |
| | 619 | */ |
| | 620 | if (where == BannerAfter) |
| | 621 | idx = skipDescendants(idx); |
| | 622 | |
| | 623 | /* insert at the position we found */ |
| | 624 | activeBanners_.insertAt(idx, uiWin); |
| | 625 | break; |
| | 626 | } |
| | 627 | } |
| | 628 | |
| | 629 | /* |
| | 630 | * Given an index in our list of active windows, skip the given item |
| | 631 | * and all items whose windows are descended from this window. |
| | 632 | * We'll leave the index positioned on the next entry in the list |
| | 633 | * that isn't a descendant of the window at the given index. Note |
| | 634 | * that this skips not only children but grandchildren (and so on) |
| | 635 | * as well. |
| | 636 | */ |
| | 637 | skipDescendants(idx) |
| | 638 | { |
| | 639 | local parentID; |
| | 640 | |
| | 641 | /* |
| | 642 | * if the index is zero, it's the main window; all windows are |
| | 643 | * children of the root window, so return the next index after |
| | 644 | * the last item |
| | 645 | */ |
| | 646 | if (idx == 0) |
| | 647 | return activeBanners_.length() + 1; |
| | 648 | |
| | 649 | /* note ID of the parent item */ |
| | 650 | parentID = activeBanners_[idx].id_; |
| | 651 | |
| | 652 | /* skip the parent item */ |
| | 653 | ++idx; |
| | 654 | |
| | 655 | /* keep going as long as we see children of the parent */ |
| | 656 | while (idx <= activeBanners_.length() |
| | 657 | && activeBanners_[idx].parentID_ == parentID) |
| | 658 | { |
| | 659 | /* |
| | 660 | * This is a child of the given parent, so we must skip it; |
| | 661 | * we must also skip its descendants, since they're all |
| | 662 | * indirectly descendants of the original parent. So, |
| | 663 | * simply skip this item and its descendants with a |
| | 664 | * recursive call to this routine. |
| | 665 | */ |
| | 666 | idx = skipDescendants(idx); |
| | 667 | } |
| | 668 | |
| | 669 | /* return the new index */ |
| | 670 | return idx; |
| | 671 | } |
| | 672 | |
| | 673 | /* remove a banner from the active display list */ |
| | 674 | removeBanner(id) |
| | 675 | { |
| | 676 | local idx; |
| | 677 | |
| | 678 | /* find the entry with the given ID, and remove it */ |
| | 679 | if ((idx = activeBanners_.indexWhich({x: x.id_ == id})) != nil) |
| | 680 | { |
| | 681 | local lastIdx; |
| | 682 | |
| | 683 | /* |
| | 684 | * After removing an item, its children are no longer |
| | 685 | * displayable, because a child obtains display space from |
| | 686 | * its parent. So, we must remove any children of this item |
| | 687 | * at the same time we remove the item itself. Find the |
| | 688 | * index of the next item after all of our descendants, so |
| | 689 | * that we can remove the item and its children all at once. |
| | 690 | * An item and its descendants are always contiguous in our |
| | 691 | * list, since we store children immediately after their |
| | 692 | * parents, so we can simply remove the range of items from |
| | 693 | * the specified item to its last descendant. |
| | 694 | * |
| | 695 | * Note that skipDescendants() returns the index of the |
| | 696 | * first item that is NOT a descendant; so, decrement the |
| | 697 | * result so that we end up with the index of the last |
| | 698 | * descendant. |
| | 699 | */ |
| | 700 | lastIdx = skipDescendants(idx) - 1; |
| | 701 | |
| | 702 | /* remove the item and all of its children */ |
| | 703 | activeBanners_.removeRange(idx, lastIdx); |
| | 704 | } |
| | 705 | } |
| | 706 | |
| | 707 | /* get the BannerUIWindow tracker object for a given BannerWindow */ |
| | 708 | getTracker(win) |
| | 709 | { |
| | 710 | local id; |
| | 711 | |
| | 712 | /* get the window's ID */ |
| | 713 | id = win.getBannerID(); |
| | 714 | |
| | 715 | /* return the tracker with the same ID as the given BannerWindow */ |
| | 716 | return activeBanners_.valWhich({x: x.id_ == id}); |
| | 717 | } |
| | 718 | |
| | 719 | /* check a BannerUIWindow to see if it matches the given layout order */ |
| | 720 | orderMatches(uiWin, where, otherID) |
| | 721 | { |
| | 722 | local idx; |
| | 723 | local otherIdx; |
| | 724 | local parentID; |
| | 725 | local parIdx; |
| | 726 | |
| | 727 | /* get the list index of the given window */ |
| | 728 | idx = activeBanners_.indexOf(uiWin); |
| | 729 | |
| | 730 | /* get the list index of the reference point window */ |
| | 731 | otherIdx = (otherID != nil |
| | 732 | ? activeBanners_.indexWhich({x: x.id_ == otherID}) : nil); |
| | 733 | |
| | 734 | /* |
| | 735 | * find the parent item (using imaginary index zero for the |
| | 736 | * root, which we can think of as being just before the first |
| | 737 | * item in the list) |
| | 738 | */ |
| | 739 | parentID = uiWin.parentID_; |
| | 740 | parIdx = (parentID == nil |
| | 741 | ? 0 : activeBanners_.indexWhich({x: x.id_ == parentID})); |
| | 742 | |
| | 743 | /* |
| | 744 | * if 'other' is specified, it has to have our same parent; if |
| | 745 | * it has a different parent, it's not a match |
| | 746 | */ |
| | 747 | if (otherID != nil && parentID != activeBanners_[otherIdx].parentID_) |
| | 748 | return nil; |
| | 749 | |
| | 750 | /* |
| | 751 | * if there's no such window in the list, it can't match the |
| | 752 | * given placement no matter what the given placement is, as it |
| | 753 | * has no placement |
| | 754 | */ |
| | 755 | if (idx == nil) |
| | 756 | return nil; |
| | 757 | |
| | 758 | /* check the requested layout order */ |
| | 759 | switch (where) |
| | 760 | { |
| | 761 | case BannerFirst: |
| | 762 | /* make sure it's immediately after the parent */ |
| | 763 | return idx == parIdx + 1; |
| | 764 | |
| | 765 | case BannerLast: |
| | 766 | /* |
| | 767 | * Make sure it's the last child of the parent. To do this, |
| | 768 | * make sure that the next item after this item's last |
| | 769 | * descendant is the same as the next item after the |
| | 770 | * parent's last descendant. |
| | 771 | */ |
| | 772 | return skipDescendants(idx) == skipDescendants(parIdx); |
| | 773 | |
| | 774 | case BannerBefore: |
| | 775 | /* |
| | 776 | * we want this item to come before 'other', so make sure |
| | 777 | * the next item after all of this item's descendants is |
| | 778 | * 'other' |
| | 779 | */ |
| | 780 | return skipDescendants(idx) == otherIdx; |
| | 781 | |
| | 782 | case BannerAfter: |
| | 783 | /* |
| | 784 | * we want this item to come just after 'other', so make |
| | 785 | * sure that the next item after all of the descendants of |
| | 786 | * 'other' is this item |
| | 787 | */ |
| | 788 | return skipDescendants(otherIdx) == idx; |
| | 789 | |
| | 790 | default: |
| | 791 | /* other layout orders are invalid */ |
| | 792 | return nil; |
| | 793 | } |
| | 794 | } |
| | 795 | |
| | 796 | /* |
| | 797 | * The vector of banners currently on the screen. This is a list of |
| | 798 | * transient BannerUIWindow objects, stored in the same order as the |
| | 799 | * banner layout list. |
| | 800 | */ |
| | 801 | activeBanners_ = static new transient Vector(32) |
| | 802 | ; |
| | 803 | |
| | 804 | /* |
| | 805 | * A BannerUIWindow object. This keeps track of the transient UI state |
| | 806 | * of a banner window while it appears on the screen. We create only |
| | 807 | * transient instances of this class, since it tracks what's actually |
| | 808 | * displayed at any given time. |
| | 809 | */ |
| | 810 | class BannerUIWindow: object |
| | 811 | /* construct */ |
| | 812 | construct(handle, ostr, id, parentID, windowType, align, styleFlags) |
| | 813 | { |
| | 814 | /* remember the banner's data */ |
| | 815 | handle_ = handle; |
| | 816 | outputStream_ = ostr; |
| | 817 | id_ = id; |
| | 818 | parentID_ = parentID; |
| | 819 | windowType_ = windowType; |
| | 820 | align_ = align; |
| | 821 | styleFlags_ = styleFlags; |
| | 822 | } |
| | 823 | |
| | 824 | /* the system-level banner handle */ |
| | 825 | handle_ = nil |
| | 826 | |
| | 827 | /* the banner's ID */ |
| | 828 | id_ = nil |
| | 829 | |
| | 830 | /* the parent banner's ID (nil if this is a top-level banner) */ |
| | 831 | parentID_ = nil |
| | 832 | |
| | 833 | /* |
| | 834 | * The banner's output stream. Output streams are always transient, |
| | 835 | * so hang on to each active banner's stream so that we can plug it |
| | 836 | * back in on restore. |
| | 837 | */ |
| | 838 | outputStream_ = nil |
| | 839 | |
| | 840 | /* creation parameters of the banner */ |
| | 841 | windowType_ = nil |
| | 842 | align_ = nil |
| | 843 | styleFlags_ = nil |
| | 844 | |
| | 845 | /* |
| | 846 | * Scratch-pad for our association to our BannerWindow object. We |
| | 847 | * only use this during the RESTORE process, to tie the transient |
| | 848 | * object back to the proper persistent object. |
| | 849 | */ |
| | 850 | win_ = nil |
| | 851 | ; |
| | 852 | |
| | 853 | /* |
| | 854 | * The persistent banner tracker. This keeps track of the active banner |
| | 855 | * windows persistently. Whenever we save or restore the game's state, |
| | 856 | * this object will be saved or restored along with the state. When we |
| | 857 | * restore a previously saved state, we can look at this object to |
| | 858 | * determine which banners were active at the time the state was saved, |
| | 859 | * and use this information to restore the same active banners in the |
| | 860 | * user interface. |
| | 861 | * |
| | 862 | * This is a post-restore and post-undo object, so we're notified via our |
| | 863 | * execute() method whenever we restore a saved state using RESTORE or |
| | 864 | * UNDO. When we restore a saved state, we'll restore the banner display |
| | 865 | * conditions as they existed in the saved state. |
| | 866 | */ |
| | 867 | bannerTracker: PostRestoreObject, PostUndoObject |
| | 868 | /* add a banner to the active display list */ |
| | 869 | addBanner(win, parent, where, other) |
| | 870 | { |
| | 871 | local parIdx; |
| | 872 | local otherIdx; |
| | 873 | |
| | 874 | /* |
| | 875 | * Don't add it if it's already in the list. If we're restoring |
| | 876 | * the banner from persistent state, it'll already be in the |
| | 877 | * active list, since the active list is the set of windows |
| | 878 | * we're restoring in the first place. |
| | 879 | */ |
| | 880 | if (activeBanners_.indexOf(win) != nil) |
| | 881 | return; |
| | 882 | |
| | 883 | /* find the parent among the existing windows */ |
| | 884 | parIdx = (parent == nil ? 0 : activeBanners_.indexOf(parent)); |
| | 885 | |
| | 886 | /* note the index of 'other' */ |
| | 887 | otherIdx = (other == nil ? nil : activeBanners_.indexOf(other)); |
| | 888 | |
| | 889 | /* insert the banner at the proper point in our list */ |
| | 890 | switch(where) |
| | 891 | { |
| | 892 | case BannerFirst: |
| | 893 | /* insert immediately after the parent */ |
| | 894 | activeBanners_.insertAt(parIdx + 1, win); |
| | 895 | break; |
| | 896 | |
| | 897 | case BannerLast: |
| | 898 | ins_last: |
| | 899 | /* insert after the parent's last descendant */ |
| | 900 | activeBanners_.insertAt(skipDescendants(parIdx), win); |
| | 901 | break; |
| | 902 | |
| | 903 | case BannerBefore: |
| | 904 | case BannerAfter: |
| | 905 | /* |
| | 906 | * if we didn't find the reference point, insert at the end |
| | 907 | * of the parent's child list |
| | 908 | */ |
| | 909 | if (otherIdx == nil) |
| | 910 | goto ins_last; |
| | 911 | |
| | 912 | /* |
| | 913 | * if inserting after, skip the reference item and all of |
| | 914 | * its descendants |
| | 915 | */ |
| | 916 | if (where == BannerAfter) |
| | 917 | otherIdx = skipDescendants(otherIdx); |
| | 918 | |
| | 919 | /* insert at the position we found */ |
| | 920 | activeBanners_.insertAt(otherIdx, win); |
| | 921 | break; |
| | 922 | } |
| | 923 | } |
| | 924 | |
| | 925 | /* |
| | 926 | * Skip all descendants of the window at the given index. |
| | 927 | */ |
| | 928 | skipDescendants(idx) |
| | 929 | { |
| | 930 | local parentID; |
| | 931 | |
| | 932 | /* index zero is the root item, so skip the entire list */ |
| | 933 | if (idx == 0) |
| | 934 | return activeBanners_.length() + 1; |
| | 935 | |
| | 936 | /* note the parent item */ |
| | 937 | parentID = activeBanners_[idx].getBannerID(); |
| | 938 | |
| | 939 | /* skip the parent item */ |
| | 940 | ++idx; |
| | 941 | |
| | 942 | /* keep going as long as we see children of the parent */ |
| | 943 | while (idx < activeBanners_.length() |
| | 944 | && activeBanners_[idx].parentID_ == parentID) |
| | 945 | { |
| | 946 | /* this is a child, so skip it and all of its descendants */ |
| | 947 | idx = skipDescendants(idx); |
| | 948 | } |
| | 949 | |
| | 950 | /* return the new index */ |
| | 951 | return idx; |
| | 952 | } |
| | 953 | |
| | 954 | /* remove a banner from the active list */ |
| | 955 | removeBanner(win) |
| | 956 | { |
| | 957 | local idx; |
| | 958 | local lastIdx; |
| | 959 | |
| | 960 | /* get the index of the item to remove */ |
| | 961 | idx = activeBanners_.indexOf(win); |
| | 962 | |
| | 963 | /* if we didn't find it, ignore the request */ |
| | 964 | if (idx == nil) |
| | 965 | return; |
| | 966 | |
| | 967 | /* find the index of its last descendant */ |
| | 968 | lastIdx = skipDescendants(idx) - 1; |
| | 969 | |
| | 970 | /* |
| | 971 | * remove the item and all of its descendants - child items |
| | 972 | * cannot be displayed once their parents are gone, so we can |
| | 973 | * remove all of this item's children, all of their children, |
| | 974 | * and so on, as they are becoming undisplayable |
| | 975 | */ |
| | 976 | activeBanners_.removeRange(idx, lastIdx); |
| | 977 | } |
| | 978 | |
| | 979 | /* |
| | 980 | * The list of active banners. This is a list of BannerWindow |
| | 981 | * objects, stored in banner layout list order. |
| | 982 | */ |
| | 983 | activeBanners_ = static new Vector(32) |
| | 984 | |
| | 985 | /* receive RESTORE/UNDO notification */ |
| | 986 | execute() |
| | 987 | { |
| | 988 | /* restore the display state for a non-initial state */ |
| | 989 | restoreDisplayState(nil); |
| | 990 | } |
| | 991 | |
| | 992 | /* |
| | 993 | * Restore the saved banner display state, so that the banner layout |
| | 994 | * looks the same as it did when we saved the persistent state. This |
| | 995 | * should be called after restoring a saved state, undoing to a |
| | 996 | * savepoint, or initializing (when first starting the game or when |
| | 997 | * restarting). |
| | 998 | */ |
| | 999 | restoreDisplayState(initing) |
| | 1000 | { |
| | 1001 | local uiVec; |
| | 1002 | local uiIdx; |
| | 1003 | local origActive; |
| | 1004 | |
| | 1005 | /* get the list of banners active in the UI */ |
| | 1006 | uiVec = bannerUITracker.activeBanners_; |
| | 1007 | |
| | 1008 | /* |
| | 1009 | * First, go through all of the persistent BannerWindow objects. |
| | 1010 | * For each one whose ID shows up in the active UI display list, |
| | 1011 | * tell the BannerWindow object its current UI handle. |
| | 1012 | */ |
| | 1013 | forEachInstance(BannerWindow, new function(cur) |
| | 1014 | { |
| | 1015 | local uiCur; |
| | 1016 | |
| | 1017 | /* find this banner in the active UI list */ |
| | 1018 | uiCur = uiVec.valWhich({x: x.id_ == cur.getBannerID()}); |
| | 1019 | |
| | 1020 | /* |
| | 1021 | * if the window exists in the active UI list, note the |
| | 1022 | * current system handle for the window; otherwise, we have |
| | 1023 | * no system window, so set the handle to nil |
| | 1024 | */ |
| | 1025 | if (uiCur != nil) |
| | 1026 | { |
| | 1027 | /* note the current system banner handle */ |
| | 1028 | cur.handle_ = uiCur.handle_; |
| | 1029 | |
| | 1030 | /* re-establish the banner's active output stream */ |
| | 1031 | cur.outputStream_ = uiCur.outputStream_; |
| | 1032 | |
| | 1033 | /* tie the transient record to the current 'cur' */ |
| | 1034 | uiCur.win_ = cur; |
| | 1035 | } |
| | 1036 | else |
| | 1037 | { |
| | 1038 | /* it's not shown, so it has no system banner handle */ |
| | 1039 | cur.handle_ = nil; |
| | 1040 | |
| | 1041 | /* it has no output stream */ |
| | 1042 | cur.outputStream_ = nil; |
| | 1043 | } |
| | 1044 | }); |
| | 1045 | |
| | 1046 | /* |
| | 1047 | * |
| | 1048 | * 'initing' indicates whether we're initializing (startup or |
| | 1049 | * RESTART) or doing something else (RESTORE, UNDO). When |
| | 1050 | * initializing, if there are any banners on-screen, we'll give |
| | 1051 | * their associated BannerWindow objects (if any) a chance to set |
| | 1052 | * up their initial conditions; this allows us to avoid |
| | 1053 | * unnecessary redrawing if we have banners that we'd immediately |
| | 1054 | * set up to the same conditions anyway, since we can just keep |
| | 1055 | * the existing banners rather than removing and re-creating |
| | 1056 | * them. |
| | 1057 | * |
| | 1058 | * So, if we're initializing, tell each banner that it's time to |
| | 1059 | * set up its initial display. |
| | 1060 | */ |
| | 1061 | if (initing) |
| | 1062 | forEachInstance(BannerWindow, {cur: cur.initBannerWindow()}); |
| | 1063 | |
| | 1064 | /* |
| | 1065 | * scan the active UI list, and close each window that isn't |
| | 1066 | * still open in the saved state |
| | 1067 | */ |
| | 1068 | foreach (local uiCur in uiVec) |
| | 1069 | { |
| | 1070 | /* if this window isn't in the active list, close it */ |
| | 1071 | if (activeBanners_.indexWhich( |
| | 1072 | {x: x.getBannerID() == uiCur.id_}) == nil) |
| | 1073 | { |
| | 1074 | /* |
| | 1075 | * There's no banner in the persistent list with this |
| | 1076 | * ID, so this window is not part of the state we're |
| | 1077 | * restoring. Close the window. If we have an |
| | 1078 | * associated BannerWindow object, close through the |
| | 1079 | * window object; otherwise, close the system handle |
| | 1080 | * directly. |
| | 1081 | */ |
| | 1082 | if (uiCur.win_ != nil) |
| | 1083 | { |
| | 1084 | /* we have a BannerWindow - close it */ |
| | 1085 | uiCur.win_.removeBanner(); |
| | 1086 | } |
| | 1087 | else |
| | 1088 | { |
| | 1089 | /* there's no BannerWindow - close the system window */ |
| | 1090 | bannerDelete(uiCur.handle_); |
| | 1091 | |
| | 1092 | /* remove the UI tracker object */ |
| | 1093 | uiVec.removeElement(uiCur); |
| | 1094 | } |
| | 1095 | } |
| | 1096 | } |
| | 1097 | |
| | 1098 | /* start at the first banner actually displayed right now */ |
| | 1099 | uiIdx = 1; |
| | 1100 | |
| | 1101 | /* |
| | 1102 | * make a copy of the original active list - we might modify the |
| | 1103 | * actual active list in the course of restoring things, so make |
| | 1104 | * a copy that we can refer to as we reconstruct the original |
| | 1105 | * list |
| | 1106 | */ |
| | 1107 | origActive = activeBanners_.toList(); |
| | 1108 | |
| | 1109 | /* |
| | 1110 | * Scan the saved list of banners, and restore each one. Note |
| | 1111 | * that by restoring windows in the order in which they appear |
| | 1112 | * in the list, we ensure that we always restore a parent before |
| | 1113 | * restoring any of its children, since a child always follows |
| | 1114 | * its parent in the list. |
| | 1115 | */ |
| | 1116 | for (local curIdx = 1, local aLen = origActive.length() ; |
| | 1117 | curIdx <= aLen ; ++curIdx) |
| | 1118 | { |
| | 1119 | local redisp; |
| | 1120 | local cur; |
| | 1121 | |
| | 1122 | /* get the current item */ |
| | 1123 | cur = origActive[curIdx]; |
| | 1124 | |
| | 1125 | /* presume we will have to redisplay this banner */ |
| | 1126 | redisp = true; |
| | 1127 | |
| | 1128 | /* |
| | 1129 | * If this banner matches the current banner in the active |
| | 1130 | * UI display list, and the characteristics match, we need |
| | 1131 | * do nothing, as we're already displaying this banner |
| | 1132 | * properly. If the current active UI banner doesn't match, |
| | 1133 | * then we need to insert this saved banner at the current |
| | 1134 | * active UI position. |
| | 1135 | */ |
| | 1136 | if (uiVec.length() >= uiIdx) |
| | 1137 | { |
| | 1138 | local uiCur; |
| | 1139 | |
| | 1140 | /* get this current UI display item (a BannerUIWindow) */ |
| | 1141 | uiCur = uiVec[uiIdx]; |
| | 1142 | |
| | 1143 | /* check for a match to 'cur' */ |
| | 1144 | if (uiCur.id_ == cur.getBannerID() |
| | 1145 | && uiCur.parentID_ == cur.parentID_ |
| | 1146 | && uiCur.windowType_ == cur.windowType_ |
| | 1147 | && uiCur.align_ == cur.align_ |
| | 1148 | && uiCur.styleFlags_ == cur.styleFlags_) |
| | 1149 | { |
| | 1150 | /* |
| | 1151 | * This saved banner ('cur') exactly matches the |
| | 1152 | * active UI banner ('uiCur') at the same position |
| | 1153 | * in the layout list. Therefore, we do not need to |
| | 1154 | * redisplay 'cur'. |
| | 1155 | */ |
| | 1156 | redisp = nil; |
| | 1157 | } |
| | 1158 | } |
| | 1159 | |
| | 1160 | /* if we need to redisplay 'cur', do so */ |
| | 1161 | if (redisp) |
| | 1162 | { |
| | 1163 | local prvIdx; |
| | 1164 | local where; |
| | 1165 | local other; |
| | 1166 | local parent; |
| | 1167 | |
| | 1168 | /* |
| | 1169 | * If 'cur' is already being displayed, we must remove |
| | 1170 | * it before showing it anew. This is the only way to |
| | 1171 | * ensure that we display it with the proper |
| | 1172 | * characteristics, since the characteristics of the |
| | 1173 | * current instance of its window don't match up to what |
| | 1174 | * we want to restore. |
| | 1175 | */ |
| | 1176 | if (cur.handle_ != nil) |
| | 1177 | cur.removeBanner(); |
| | 1178 | |
| | 1179 | /* |
| | 1180 | * Figure out how to specify this window's display list |
| | 1181 | * position. A display list position is always |
| | 1182 | * specified relative to the parent's child list, so |
| | 1183 | * figure out where we go in our parent's list. Scan |
| | 1184 | * backwards in the active list for the nearest previous |
| | 1185 | * window with the same parent. If we find one, insert |
| | 1186 | * the new window after that prior sibling; otherwise, |
| | 1187 | * insert as the first child of our parent. Presume |
| | 1188 | * that we'll fail to find a prior sibling, then search |
| | 1189 | * for it and search for our parent. |
| | 1190 | */ |
| | 1191 | where = BannerFirst; |
| | 1192 | other = nil; |
| | 1193 | for (prvIdx = curIdx - 1 ; prvIdx > 0 ; --prvIdx) |
| | 1194 | { |
| | 1195 | local prv; |
| | 1196 | |
| | 1197 | /* note this item */ |
| | 1198 | prv = origActive[prvIdx]; |
| | 1199 | |
| | 1200 | /* |
| | 1201 | * If this item has our same parent, and we haven't |
| | 1202 | * already found a prior sibling, this is our most |
| | 1203 | * recent prior sibling, so note it. |
| | 1204 | */ |
| | 1205 | if (where == BannerFirst |
| | 1206 | && prv.parentID_ == cur.parentID_) |
| | 1207 | { |
| | 1208 | /* insert after this prior sibling */ |
| | 1209 | where = BannerAfter; |
| | 1210 | other = prv; |
| | 1211 | } |
| | 1212 | |
| | 1213 | /* if this is our parent, note it */ |
| | 1214 | if (prv.getBannerID() == cur.parentID_) |
| | 1215 | { |
| | 1216 | /* |
| | 1217 | * note the parent BannerWindow object - we'll |
| | 1218 | * need it to specify our window display |
| | 1219 | * position |
| | 1220 | */ |
| | 1221 | parent = prv; |
| | 1222 | |
| | 1223 | /* |
| | 1224 | * Children of a given parent always come after |
| | 1225 | * the parent in the display list, so there's no |
| | 1226 | * possibility of finding another sibling. |
| | 1227 | * There's also obviously no possibility of |
| | 1228 | * finding another parent. So, our work here is |
| | 1229 | * done; we can stop scanning. |
| | 1230 | */ |
| | 1231 | break; |
| | 1232 | } |
| | 1233 | } |
| | 1234 | |
| | 1235 | /* show the window */ |
| | 1236 | cur.showForRestore(parent, where, other); |
| | 1237 | } |
| | 1238 | else |
| | 1239 | { |
| | 1240 | /* |
| | 1241 | * the banner is already showing, so we don't need to |
| | 1242 | * redisplay it; simply notify it that a Restore |
| | 1243 | * operation has taken place so that it can do any |
| | 1244 | * necessary updates |
| | 1245 | */ |
| | 1246 | cur.updateForRestore(); |
| | 1247 | } |
| | 1248 | |
| | 1249 | /* |
| | 1250 | * 'cur' should now be displayed, but we might have failed |
| | 1251 | * to re-create it. If we did show the window, we can |
| | 1252 | * advance to the next slot in the UI list, since this |
| | 1253 | * window will necessarily be at the current spot in the UI |
| | 1254 | * list. |
| | 1255 | */ |
| | 1256 | if (cur.handle_ != nil) |
| | 1257 | { |
| | 1258 | /* |
| | 1259 | * We know that this window is now the entry in the |
| | 1260 | * active UI list at the current index we're looking at. |
| | 1261 | * Move on to the next position in the active list for |
| | 1262 | * the next saved window. |
| | 1263 | */ |
| | 1264 | ++uiIdx; |
| | 1265 | } |
| | 1266 | } |
| | 1267 | } |
| | 1268 | ; |
| | 1269 | |
| | 1270 | /* |
| | 1271 | * Initialization object - this will be called when we start the game the |
| | 1272 | * first time or RESTART within a session. We'll restore the display |
| | 1273 | * state to the initial conditions. |
| | 1274 | */ |
| | 1275 | bannerInit: InitObject |
| | 1276 | execute() |
| | 1277 | { |
| | 1278 | /* restore banner displays to their initial conditions */ |
| | 1279 | bannerTracker.restoreDisplayState(true); |
| | 1280 | } |
| | 1281 | ; |