banner.t | documentation |
#charset "us-ascii" /* * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. * * TADS 3 Library: banner manager * * This module defines the banner manager, which provides high-level * services to create and manipulate banner windows. * * A "banner" is an independent window shown within the interpreter's * main application display frame (which might be the entire screen on a * character-mode terminal, or could be a window in a GUI system). The * game can control the creation and destruction of banner windows, and * can control their placement and size. * * This implementation is based in part on Steve Breslin's banner * manager, used by permission. */ #include "adv3.h" /* ------------------------------------------------------------------------ */ /* * A BannerWindow corresponds to an on-screen banner. For each banner * window a game wants to display, the game must create an object of this * class. * * Note that merely creating a BannerWindow object doesn't actually * display a banner window. Once a BannerWindow is created, the game * must call the object's showBanner() method to create the on-screen * window for the banner. * * BannerWindow instances are intended to be persistent (not transient). * The banner manager keeps track of each banner window that's actually * being displayed separately via an internal transient object; the game * doesn't need to worry about these tracking objects, since the banner * manager automatically handles them. */ class BannerWindow: OutputStreamWindow /* * Construct the object. * * 'id' is a globally unique identifying string for the banner. When * we dynamically create a banner object, we have to provide a unique * identifying string, so that we can correlate transient on-screen * banners with the banners in a saved state when restoring the saved * state. * * Note that no ID string is needed for BannerWindow objects defined * statically at compile-time, because the object itself ('self') is * a suitably unique and stable identifier. */ construct(id) { /* remember my unique identifier */ id_ = id; } /* * Show the banner. The game should call this method when it first * wants to display the banner. * * 'parent' is the parent banner; this is an existing BannerWindow * object. If 'parent' is nil, then the parent is the main game * text window. The new window's display space is obtained by * carving space out of the parent's area, according to the * alignment and size values specified. * * 'where' and 'other' give the position of the banner among the * children of the given parent. 'where' is one of the constants * BannerFirst, BannerLast, BannerBefore, or BannerAfter. If * 'where' is BannerBefore or BannerAfter, 'other' gives the * BannerWindow object to be used as the reference point in the * parent's child list; 'other' is ignored in other cases. Note * that 'other' must always be another child of the same parent; if * it's not, then we act as though 'where' were given as BannerLast. * * 'windowType' is a BannerTypeXxx constant giving the new window's * type. * * 'align' is a BannerAlignXxx constant giving the alignment of the * new window. 'size' is an integer giving the size of the banner, * in units specified by 'sizeUnits', which is a BannerSizeXxx * constant. If 'size' is nil, it indicates that the caller doesn't * care about the size, usually because the caller will be resizing * the banner soon anyway; the banner will initially have zero size * in this case if we create a new window, or will retain the * existing size if there's already a system window. * * 'styleFlags' is a combination of BannerStyleXxx constants * (combined with the bitwise OR operator, '|'), giving the requested * display style of the new banner window. * * Note that if we already have a system banner window, and the * existing banner window has the same characteristics as the new * creation parameters, we'll simply re-use the existing window * rather than closing and re-creating it; this reduces unnecessary * redrawing in cases where the window isn't changing. If the caller * explicitly wants to create a new window even if we already have a * window, the caller should simply call removeBanner() before * calling this routine. */ showBanner(parent, where, other, windowType, align, size, sizeUnits, styleFlags) { local parentID; local otherID; /* note the ID's of the parent window and the insertion point */ parentID = (parent != nil ? parent.getBannerID() : nil); otherID = (other != nil ? other.getBannerID() : nil); /* * if we have an 'other' specified, its parent must match our * proposed parent; otherwise, ignore 'other' and insert at the * end of the parent list */ if (other != nil && other.parentID_ != parentID) { other = nil; where = BannerLast; } /* if we already have an existing banner window, check for a match */ if (handle_ != nil) { local t; local match; /* presume we won't find an exact match */ match = nil; /* we already have a window - get the UI tracker object */ if ((t = bannerUITracker.getTracker(self)) != nil) { /* check the placement, window type, alignment, and style */ match = (t.windowType_ == windowType && t.parentID_ == parentID && t.align_ == align && t.styleFlags_ == styleFlags && bannerUITracker.orderMatches(t, where, otherID)); } /* * if it doesn't match the existing window, close it, so that * we will open a brand new window with the new * characteristics */ if (!match) removeBanner(); } /* if the system-level banner doesn't already exist, create it */ if (handle_ == nil) { /* create my system-level banner window */ if (!createSystemBanner(parent, where, other, windowType, align, size, sizeUnits, styleFlags)) { /* we couldn't create the system banner - give up */ return nil; } /* create our output stream */ createOutputStream(); /* add myself to the UI tracker's active banner list */ bannerUITracker.addBanner(handle_, outputStream_, getBannerID(), parentID, where, other, windowType, align, styleFlags); } else { /* * Our system-level window already exists, so we don't need * to create a new one. However, our size could be * different, so explicitly set the requested size if it * doesn't match our recorded size. If the size is given as * nil, leave the size as it is; a nil size indicates that * the caller doesn't care about the size (probably because * the caller is going to change the size shortly anyway), * so we can avoid unnecessary redrawing by leaving the size * as it is for now. */ if (size != nil && (size != size_ || sizeUnits != sizeUnits_)) bannerSetSize(handle_, size, sizeUnits, nil); } /* * remember the creation parameters, so that we can re-create the * banner with the same characteristics in the future if we * should need to restore the banner from a saved position */ parentID_ = parentID; windowType_ = windowType; align_ = align; size_ = size; sizeUnits_ = sizeUnits; styleFlags_ = styleFlags; /* * Add myself to the persistent banner tracker's active list. Do * this even if we already had a system handle, since we might be * initializing the window as part of a persistent restore * operation, in which case the persistent tracking object might * not yet exist. (This seems backwards: if we're restoring a * persistent state, surely the persistent tracker would already * exist. In fact, the case we're really handling is where the * window is open in the transient UI, because it was already * open in the ongoing session; but the persistent state we're * restoring doesn't include the window. This is most likely to * occur after a RESTART, since we could have a window that is * always opened immediately at start-up and thus will be in the * transient state up to and through the RESTART, but is only * created as part of the initialization process.) */ bannerTracker.addBanner(self, parent, where, other); /* indicate success */ return true; } /* * Remove the banner. This removes the banner's on-screen window. * The BannerWindow object itself remains valid, but after this * method returns, the BannerWindow no longer has an associated * display window. * * Note that any child banners of ours will become undisplayable * after we're gone. A child banner depends upon its parent to * obtain display space, so once the parent is gone, its children no * longer have any way to obtain any display space. Our children * remain valid objects even after we're closed, but they won't be * visible on the display. */ removeBanner() { /* if I don't have a system-level handle, there's nothing to do */ if (handle_ == nil) return; /* remove my system-level banner window */ bannerDelete(handle_); /* our system-level window is gone, so forget its handle */ handle_ = nil; /* we only need an output stream when we're active */ outputStream_ = nil; /* remove myself from the UI trackers's active list */ bannerUITracker.removeBanner(getBannerID()); /* remove myself from the persistent banner tracker's active list */ bannerTracker.removeBanner(self); } /* write the given text to the banner */ writeToBanner(txt) { /* write the text to our underlying output stream */ outputStream_.writeToStream(txt); } /* flush any pending output to the banner */ flushBanner() { bannerFlush(handle_); } /* * Set the banner window to a specific size. 'size' is the new * size, in units given by 'sizeUnits', which is a BannerSizeXxx * constant. * * 'isAdvisory' is true or nil; if true, it indicates that the size * setting is purely advisory, and that a sizeToContents() call will * eventually follow to set the actual size. When 'isAdvisory is * true, the interpreter is free to ignore the request if * sizeToContents() */ setSize(size, sizeUnits, isAdvisory) { /* set the underlying system window size */ bannerSetSize(handle_, size, sizeUnits, isAdvisory); /* * remember my new size in case we have to re-create the banner * from a saved state */ size_ = size; sizeUnits_ = sizeUnits; } /* * Size the banner to its current contents. Note that some systems * do not support this operation, so callers should always make an * advisory call to setSize() first to set a size based on the * expected content size. */ sizeToContents() { /* size our system-level window to our contents */ bannerSizeToContents(handle_); } /* * Clear my banner window. This clears out all of the contents of * our on-screen display area. */ clearWindow() { /* clear our system-level window */ bannerClear(handle_); } /* set the text color in the banner */ setTextColor(fg, bg) { bannerSetTextColor(handle_, fg, bg); } /* set the screen color in the banner window */ setScreenColor(color) { bannerSetScreenColor(handle_, color); } /* * Move the cursor to the given row/column position. This can only * be used with text-grid banners; for ordinary text banners, this * has no effect. */ cursorTo(row, col) { bannerGoTo(handle_, row, col); } /* * Get the banner identifier. If our 'id_' property is set to nil, * we'll assume that we're a statically-defined object, in which case * 'self' is a suitable identifier. Otherwise, we'll return the * identifier string. */ getBannerID() { return id_ != nil ? id_ : self; } /* * Restore this banner. This is called after a RESTORE or UNDO * operation that finds that this banner was being displayed at the * time the state was saved but is not currently displayed in the * active UI. We'll show the banner using the characteristics saved * persistently. */ showForRestore(parent, where, other) { /* show myself, using my saved characteristics */ showBanner(parent, where, other, windowType_, align_, size_, sizeUnits_, styleFlags_); /* update my contents */ updateForRestore(); } /* * Create the system-level banner window. This can be customized as * needed, although this default implementation should be suitable * for most instances. * * Returns true if we are successful in creating the system window, * nil if we fail. */ createSystemBanner(parent, where, other, windowType, align, size, sizeUnits, styleFlags) { /* create the system-level window */ handle_ = bannerCreate(parent != nil ? parent.handle_ : nil, where, other != nil ? other.handle_ : nil, windowType, align, size, sizeUnits, styleFlags); /* if we got a valid handle, we succeeded */ return (handle_ != nil); } /* create our banner output stream */ createOutputStreamObj() { return new transient BannerOutputStream(handle_); } /* * Update my contents after being restored. By default, this does * nothing; instances might want to override this to refresh the * contents of the banner if the banner is normally updated only in * response to specific events. Note that it's not necessary to do * anything here if the banner will soon be updated automatically as * part of normal processing; for example, the status line banner is * updated at each new command line via a prompt-daemon, so there's * no need for the status line banner to do anything here. */ updateForRestore() { /* do nothing by default; subclasses can override as needed */ } /* * Initialize the banner window. This is called during * initialization (when first starting the game, or when resetting * with RESTART). If the banner is to be displayed from the start of * the game, this can set up the on-screen display. * * Note that we might already have an on-screen handle when this is * called. This indicates that we're restarting an ongoing session, * and that this banner already existed in the session before the * RESTART operation. If desired, we can attach ourselves to the * existing on-screen banner, avoiding the redrawing that would occur * if we created a new window. * * If this window depends upon another window for its layout order * placement (i.e., we'll call showBanner() with another BannerWindow * given as the 'other' parameter), then this routine should call the * other window's initBannerWindow() method before creating its own * window, to ensure that the other window has a system window and * thus will be meaningful to establish the layout order. * * Overriding implementations should check the 'inited_' property. * If this property is true, then it can be assumed that we've * already been initialized and don't require further initialization. * This routine can be called multiple times because dependent * windows might call us directly, before we're called for our * regular initialization. */ initBannerWindow() { /* by default, simply note that we've been initialized */ inited_ = true; } /* flag: this banner has been initialized with initBannerWindow() */ inited_ = nil /* * The creator-assigned ID string to identify the banner * persistently. This is only needed for banners created * dynamically; for BannerWindow objects defined statically at * compile time, simply leave this value as nil, and we'll use the * object itself as the identifier. */ id_ = nil /* the handle to my system-level banner window */ handle_ = nil /* * Creation parameters. We store these when we create the banner, * and update them as needed when the banner's display attributes * are changed. */ parentID_ = nil windowType_ = nil align_ = nil size_ = nil sizeUnits_ = nil styleFlags_ = nil ; /* ------------------------------------------------------------------------ */ /* * Banner Output Stream. This is a specialization of OutputStream that * writes to a banner window. */ class BannerOutputStream: OutputStream /* construct */ construct(handle) { /* inherit base class constructor */ inherited(); /* remember my banner window handle */ handle_ = handle; } /* execute preinitialization */ execute() { /* * We shouldn't need to do anything during pre-initialization, * since we should always be constructed dynamically by a * BannerWindow. Don't even inherit the base class * initialization, since it could clear out state that we want to * keep through a restart, restore, etc. */ } /* write text from the stream to the interpreter I/O system */ writeFromStream(txt) { /* write the text to the underlying system banner window */ bannerSay(handle_, txt); } /* our system-level banner window handle */ handle_ = nil ; /* ------------------------------------------------------------------------ */ /* * The banner UI tracker. This object keeps track of the current user * interface display state; this object is transient because the * interpreter's user interface is not part of the persistence * mechanism. */ transient bannerUITracker: object /* add a banner to the active display list */ addBanner(handle, ostr, id, parentID, where, other, windowType, align, styleFlags) { local uiWin; local parIdx; local idx; /* create a transient BannerUIWindow object to track the banner */ uiWin = new transient BannerUIWindow(handle, ostr, id, parentID, windowType, align, styleFlags); /* * Find the parent in the list. If there's no parent, the * parent is the main window; consider it to be at imaginary * index zero in the list. */ parIdx = (parentID == nil ? 0 : activeBanners_.indexWhich({x: x.id_ == parentID})); /* insert the banner at the proper point in our list */ switch(where) { case BannerFirst: /* * insert as the first child of the parent - put it * immediately after the parent in the list */ activeBanners_.insertAt(parIdx + 1, uiWin); break; case BannerLast: ins_last: /* * Insert as the last child of the parent: insert * immediately after the last window that descends from the * parent. */ activeBanners_.insertAt(skipDescendants(parIdx), uiWin); break; case BannerBefore: case BannerAfter: /* find the reference point ID in our list */ idx = activeBanners_.indexWhich( {x: x.id_ == other.getBannerID()}); /* * if we didn't find the reference point, or the reference * point item doesn't have the same parent as the new item, * then ignore the reference point and instead insert at the * end of the parent's child list */ if (idx == nil || activeBanners_[idx].parentID_ != parentID) goto ins_last; /* * if inserting after, skip the reference item and all * of its descendants */ if (where == BannerAfter) idx = skipDescendants(idx); /* insert at the position we found */ activeBanners_.insertAt(idx, uiWin); break; } } /* * Given an index in our list of active windows, skip the given item * and all items whose windows are descended from this window. * We'll leave the index positioned on the next entry in the list * that isn't a descendant of the window at the given index. Note * that this skips not only children but grandchildren (and so on) * as well. */ skipDescendants(idx) { local parentID; /* * if the index is zero, it's the main window; all windows are * children of the root window, so return the next index after * the last item */ if (idx == 0) return activeBanners_.length() + 1; /* note ID of the parent item */ parentID = activeBanners_[idx].id_; /* skip the parent item */ ++idx; /* keep going as long as we see children of the parent */ while (idx <= activeBanners_.length() && activeBanners_[idx].parentID_ == parentID) { /* * This is a child of the given parent, so we must skip it; * we must also skip its descendants, since they're all * indirectly descendants of the original parent. So, * simply skip this item and its descendants with a * recursive call to this routine. */ idx = skipDescendants(idx); } /* return the new index */ return idx; } /* remove a banner from the active display list */ removeBanner(id) { local idx; /* find the entry with the given ID, and remove it */ if ((idx = activeBanners_.indexWhich({x: x.id_ == id})) != nil) { local lastIdx; /* * After removing an item, its children are no longer * displayable, because a child obtains display space from * its parent. So, we must remove any children of this item * at the same time we remove the item itself. Find the * index of the next item after all of our descendants, so * that we can remove the item and its children all at once. * An item and its descendants are always contiguous in our * list, since we store children immediately after their * parents, so we can simply remove the range of items from * the specified item to its last descendant. * * Note that skipDescendants() returns the index of the * first item that is NOT a descendant; so, decrement the * result so that we end up with the index of the last * descendant. */ lastIdx = skipDescendants(idx) - 1; /* remove the item and all of its children */ activeBanners_.removeRange(idx, lastIdx); } } /* get the BannerUIWindow tracker object for a given BannerWindow */ getTracker(win) { local id; /* get the window's ID */ id = win.getBannerID(); /* return the tracker with the same ID as the given BannerWindow */ return activeBanners_.valWhich({x: x.id_ == id}); } /* check a BannerUIWindow to see if it matches the given layout order */ orderMatches(uiWin, where, otherID) { local idx; local otherIdx; local parentID; local parIdx; /* get the list index of the given window */ idx = activeBanners_.indexOf(uiWin); /* get the list index of the reference point window */ otherIdx = (otherID != nil ? activeBanners_.indexWhich({x: x.id_ == otherID}) : nil); /* * find the parent item (using imaginary index zero for the * root, which we can think of as being just before the first * item in the list) */ parentID = uiWin.parentID_; parIdx = (parentID == nil ? 0 : activeBanners_.indexWhich({x: x.id_ == parentID})); /* * if 'other' is specified, it has to have our same parent; if * it has a different parent, it's not a match */ if (otherID != nil && parentID != activeBanners_[otherIdx].parentID_) return nil; /* * if there's no such window in the list, it can't match the * given placement no matter what the given placement is, as it * has no placement */ if (idx == nil) return nil; /* check the requested layout order */ switch (where) { case BannerFirst: /* make sure it's immediately after the parent */ return idx == parIdx + 1; case BannerLast: /* * Make sure it's the last child of the parent. To do this, * make sure that the next item after this item's last * descendant is the same as the next item after the * parent's last descendant. */ return skipDescendants(idx) == skipDescendants(parIdx); case BannerBefore: /* * we want this item to come before 'other', so make sure * the next item after all of this item's descendants is * 'other' */ return skipDescendants(idx) == otherIdx; case BannerAfter: /* * we want this item to come just after 'other', so make * sure that the next item after all of the descendants of * 'other' is this item */ return skipDescendants(otherIdx) == idx; default: /* other layout orders are invalid */ return nil; } } /* * The vector of banners currently on the screen. This is a list of * transient BannerUIWindow objects, stored in the same order as the * banner layout list. */ activeBanners_ = static new transient Vector(32) ; /* * A BannerUIWindow object. This keeps track of the transient UI state * of a banner window while it appears on the screen. We create only * transient instances of this class, since it tracks what's actually * displayed at any given time. */ class BannerUIWindow: object /* construct */ construct(handle, ostr, id, parentID, windowType, align, styleFlags) { /* remember the banner's data */ handle_ = handle; outputStream_ = ostr; id_ = id; parentID_ = parentID; windowType_ = windowType; align_ = align; styleFlags_ = styleFlags; } /* the system-level banner handle */ handle_ = nil /* the banner's ID */ id_ = nil /* the parent banner's ID (nil if this is a top-level banner) */ parentID_ = nil /* * The banner's output stream. Output streams are always transient, * so hang on to each active banner's stream so that we can plug it * back in on restore. */ outputStream_ = nil /* creation parameters of the banner */ windowType_ = nil align_ = nil styleFlags_ = nil /* * Scratch-pad for our association to our BannerWindow object. We * only use this during the RESTORE process, to tie the transient * object back to the proper persistent object. */ win_ = nil ; /* * The persistent banner tracker. This keeps track of the active banner * windows persistently. Whenever we save or restore the game's state, * this object will be saved or restored along with the state. When we * restore a previously saved state, we can look at this object to * determine which banners were active at the time the state was saved, * and use this information to restore the same active banners in the * user interface. * * This is a post-restore and post-undo object, so we're notified via our * execute() method whenever we restore a saved state using RESTORE or * UNDO. When we restore a saved state, we'll restore the banner display * conditions as they existed in the saved state. */ bannerTracker: PostRestoreObject, PostUndoObject /* add a banner to the active display list */ addBanner(win, parent, where, other) { local parIdx; local otherIdx; /* * Don't add it if it's already in the list. If we're restoring * the banner from persistent state, it'll already be in the * active list, since the active list is the set of windows * we're restoring in the first place. */ if (activeBanners_.indexOf(win) != nil) return; /* find the parent among the existing windows */ parIdx = (parent == nil ? 0 : activeBanners_.indexOf(parent)); /* note the index of 'other' */ otherIdx = (other == nil ? nil : activeBanners_.indexOf(other)); /* insert the banner at the proper point in our list */ switch(where) { case BannerFirst: /* insert immediately after the parent */ activeBanners_.insertAt(parIdx + 1, win); break; case BannerLast: ins_last: /* insert after the parent's last descendant */ activeBanners_.insertAt(skipDescendants(parIdx), win); break; case BannerBefore: case BannerAfter: /* * if we didn't find the reference point, insert at the end * of the parent's child list */ if (otherIdx == nil) goto ins_last; /* * if inserting after, skip the reference item and all of * its descendants */ if (where == BannerAfter) otherIdx = skipDescendants(otherIdx); /* insert at the position we found */ activeBanners_.insertAt(otherIdx, win); break; } } /* * Skip all descendants of the window at the given index. */ skipDescendants(idx) { local parentID; /* index zero is the root item, so skip the entire list */ if (idx == 0) return activeBanners_.length() + 1; /* note the parent item */ parentID = activeBanners_[idx].getBannerID(); /* skip the parent item */ ++idx; /* keep going as long as we see children of the parent */ while (idx < activeBanners_.length() && activeBanners_[idx].parentID_ == parentID) { /* this is a child, so skip it and all of its descendants */ idx = skipDescendants(idx); } /* return the new index */ return idx; } /* remove a banner from the active list */ removeBanner(win) { local idx; local lastIdx; /* get the index of the item to remove */ idx = activeBanners_.indexOf(win); /* if we didn't find it, ignore the request */ if (idx == nil) return; /* find the index of its last descendant */ lastIdx = skipDescendants(idx) - 1; /* * remove the item and all of its descendants - child items * cannot be displayed once their parents are gone, so we can * remove all of this item's children, all of their children, * and so on, as they are becoming undisplayable */ activeBanners_.removeRange(idx, lastIdx); } /* * The list of active banners. This is a list of BannerWindow * objects, stored in banner layout list order. */ activeBanners_ = static new Vector(32) /* receive RESTORE/UNDO notification */ execute() { /* restore the display state for a non-initial state */ restoreDisplayState(nil); } /* * Restore the saved banner display state, so that the banner layout * looks the same as it did when we saved the persistent state. This * should be called after restoring a saved state, undoing to a * savepoint, or initializing (when first starting the game or when * restarting). */ restoreDisplayState(initing) { local uiVec; local uiIdx; local origActive; /* get the list of banners active in the UI */ uiVec = bannerUITracker.activeBanners_; /* * First, go through all of the persistent BannerWindow objects. * For each one whose ID shows up in the active UI display list, * tell the BannerWindow object its current UI handle. */ forEachInstance(BannerWindow, function(cur) { local uiCur; /* find this banner in the active UI list */ uiCur = uiVec.valWhich({x: x.id_ == cur.getBannerID()}); /* * if the window exists in the active UI list, note the * current system handle for the window; otherwise, we have * no system window, so set the handle to nil */ if (uiCur != nil) { /* note the current system banner handle */ cur.handle_ = uiCur.handle_; /* re-establish the banner's active output stream */ cur.outputStream_ = uiCur.outputStream_; /* tie the transient record to the current 'cur' */ uiCur.win_ = cur; } else { /* it's not shown, so it has no system banner handle */ cur.handle_ = nil; /* it has no output stream */ cur.outputStream_ = nil; } }); /* * * 'initing' indicates whether we're initializing (startup or * RESTART) or doing something else (RESTORE, UNDO). When * initializing, if there are any banners on-screen, we'll give * their associated BannerWindow objects (if any) a chance to set * up their initial conditions; this allows us to avoid * unnecessary redrawing if we have banners that we'd immediately * set up to the same conditions anyway, since we can just keep * the existing banners rather than removing and re-creating * them. * * So, if we're initializing, tell each banner that it's time to * set up its initial display. */ if (initing) forEachInstance(BannerWindow, {cur: cur.initBannerWindow()}); /* * scan the active UI list, and close each window that isn't * still open in the saved state */ foreach (local uiCur in uiVec) { /* if this window isn't in the active list, close it */ if (activeBanners_.indexWhich( {x: x.getBannerID() == uiCur.id_}) == nil) { /* * There's no banner in the persistent list with this * ID, so this window is not part of the state we're * restoring. Close the window. If we have an * associated BannerWindow object, close through the * window object; otherwise, close the system handle * directly. */ if (uiCur.win_ != nil) { /* we have a BannerWindow - close it */ uiCur.win_.removeBanner(); } else { /* there's no BannerWindow - close the system window */ bannerDelete(uiCur.handle_); /* remove the UI tracker object */ uiVec.removeElement(uiCur); } } } /* start at the first banner actually displayed right now */ uiIdx = 1; /* * make a copy of the original active list - we might modify the * actual active list in the course of restoring things, so make * a copy that we can refer to as we reconstruct the original * list */ origActive = activeBanners_.toList(); /* * Scan the saved list of banners, and restore each one. Note * that by restoring windows in the order in which they appear * in the list, we ensure that we always restore a parent before * restoring any of its children, since a child always follows * its parent in the list. */ for (local curIdx = 1, local aLen = origActive.length() ; curIdx <= aLen ; ++curIdx) { local redisp; local cur; /* get the current item */ cur = origActive[curIdx]; /* presume we will have to redisplay this banner */ redisp = true; /* * If this banner matches the current banner in the active * UI display list, and the characteristics match, we need * do nothing, as we're already displaying this banner * properly. If the current active UI banner doesn't match, * then we need to insert this saved banner at the current * active UI position. */ if (uiVec.length() >= uiIdx) { local uiCur; /* get this current UI display item (a BannerUIWindow) */ uiCur = uiVec[uiIdx]; /* check for a match to 'cur' */ if (uiCur.id_ == cur.getBannerID() && uiCur.parentID_ == cur.parentID_ && uiCur.windowType_ == cur.windowType_ && uiCur.align_ == cur.align_ && uiCur.styleFlags_ == cur.styleFlags_) { /* * This saved banner ('cur') exactly matches the * active UI banner ('uiCur') at the same position * in the layout list. Therefore, we do not need to * redisplay 'cur'. */ redisp = nil; } } /* if we need to redisplay 'cur', do so */ if (redisp) { local prvIdx; local where; local other; local parent; /* * If 'cur' is already being displayed, we must remove * it before showing it anew. This is the only way to * ensure that we display it with the proper * characteristics, since the characteristics of the * current instance of its window don't match up to what * we want to restore. */ if (cur.handle_ != nil) cur.removeBanner(); /* * Figure out how to specify this window's display list * position. A display list position is always * specified relative to the parent's child list, so * figure out where we go in our parent's list. Scan * backwards in the active list for the nearest previous * window with the same parent. If we find one, insert * the new window after that prior sibling; otherwise, * insert as the first child of our parent. Presume * that we'll fail to find a prior sibling, then search * for it and search for our parent. */ where = BannerFirst; other = nil; for (prvIdx = curIdx - 1 ; prvIdx > 0 ; --prvIdx) { local prv; /* note this item */ prv = origActive[prvIdx]; /* * If this item has our same parent, and we haven't * already found a prior sibling, this is our most * recent prior sibling, so note it. */ if (where == BannerFirst && prv.parentID_ == cur.parentID_) { /* insert after this prior sibling */ where = BannerAfter; other = prv; } /* if this is our parent, note it */ if (prv.getBannerID() == cur.parentID_) { /* * note the parent BannerWindow object - we'll * need it to specify our window display * position */ parent = prv; /* * Children of a given parent always come after * the parent in the display list, so there's no * possibility of finding another sibling. * There's also obviously no possibility of * finding another parent. So, our work here is * done; we can stop scanning. */ break; } } /* show the window */ cur.showForRestore(parent, where, other); } else { /* * the banner is already showing, so we don't need to * redisplay it; simply notify it that a Restore * operation has taken place so that it can do any * necessary updates */ cur.updateForRestore(); } /* * 'cur' should now be displayed, but we might have failed * to re-create it. If we did show the window, we can * advance to the next slot in the UI list, since this * window will necessarily be at the current spot in the UI * list. */ if (cur.handle_ != nil) { /* * We know that this window is now the entry in the * active UI list at the current index we're looking at. * Move on to the next position in the active list for * the next saved window. */ ++uiIdx; } } } ; /* * Initialization object - this will be called when we start the game the * first time or RESTART within a session. We'll restore the display * state to the initial conditions. */ bannerInit: InitObject execute() { /* restore banner displays to their initial conditions */ bannerTracker.restoreDisplayState(true); } ;
TADS 3 Library Manual
Generated on 5/16/2013 from TADS version 3.1.3
Generated on 5/16/2013 from TADS version 3.1.3