| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library - footnotes |
| | 7 | * |
| | 8 | * This module defines objects related to footnotes. |
| | 9 | */ |
| | 10 | |
| | 11 | /* include the library header */ |
| | 12 | #include "adv3.h" |
| | 13 | |
| | 14 | |
| | 15 | /* ------------------------------------------------------------------------ */ |
| | 16 | /* |
| | 17 | * Footnote - this allows footnote references to be generated in |
| | 18 | * displayed text, and the user to retrieve the contents of the footnote |
| | 19 | * on demand. |
| | 20 | * |
| | 21 | * Create an instance of Footnote for each footnote. For each footnote |
| | 22 | * object, define the "desc" property as a double-quoted string (or |
| | 23 | * method) displaying the footnote's contents. |
| | 24 | * |
| | 25 | * To display a footnote reference in a passage of text, call |
| | 26 | * <<x.noteRef>>, where x is the footnote object in question. That's all |
| | 27 | * you have to do - we'll automatically assign the footnote a sequential |
| | 28 | * number (so that footnote references are always seen by the player in |
| | 29 | * ascending order, regardless of the order in which the player |
| | 30 | * encounters the sources of the footnotes), and the NOTE command will |
| | 31 | * automatically figure out which footnote object is involved for a given |
| | 32 | * footnote number. |
| | 33 | * |
| | 34 | * This class also serves as a daemon notification object to receive |
| | 35 | * per-command daemon calls. The first time we show a footnote |
| | 36 | * reference, we'll show an explanation of how footnotes work. |
| | 37 | */ |
| | 38 | class Footnote: object |
| | 39 | /* |
| | 40 | * Display the contents of the footnote - this will be called when |
| | 41 | * the user asks to show the footnote with the "NOTE n" command. |
| | 42 | * Each instance must provide suitable text here. |
| | 43 | */ |
| | 44 | desc = "" |
| | 45 | |
| | 46 | /* |
| | 47 | * Get a reference to the footnote for use in a passage of text. |
| | 48 | * This returns a single-quoted string to display as a reference to |
| | 49 | * the footnote. |
| | 50 | */ |
| | 51 | noteRef |
| | 52 | { |
| | 53 | /* |
| | 54 | * If the sensory context is blocking output, do not consider |
| | 55 | * this a reference to the footnote at all, since the player |
| | 56 | * won't see it. |
| | 57 | */ |
| | 58 | if (senseContext.isBlocking) |
| | 59 | return ''; |
| | 60 | |
| | 61 | /* |
| | 62 | * if we haven't already assigned a number to this footnote, |
| | 63 | * assign one now |
| | 64 | */ |
| | 65 | if (footnoteNum == nil) |
| | 66 | { |
| | 67 | /* |
| | 68 | * Allocate a new footnote number and remember it as our |
| | 69 | * own. Note that we want the last footnote number for all |
| | 70 | * footnotes, so use the Footnote class property |
| | 71 | * lastFootnote. |
| | 72 | */ |
| | 73 | footnoteNum = ++(Footnote.lastFootnote); |
| | 74 | |
| | 75 | /* |
| | 76 | * add myself to the class's list of numbered notes, so we |
| | 77 | * can find this footnote easily again given its number |
| | 78 | */ |
| | 79 | Footnote.numberedFootnotes.append(self); |
| | 80 | |
| | 81 | /* note that we've generated a footnote reference */ |
| | 82 | Footnote.everShownFootnote = true; |
| | 83 | } |
| | 84 | |
| | 85 | /* |
| | 86 | * If we're allowed to show footnotes, return the library |
| | 87 | * message text to display given the note number. If all |
| | 88 | * footnotes are being hidden, or if we're only showing new |
| | 89 | * footnotes and we've already read this one, return an empty |
| | 90 | * string. |
| | 91 | */ |
| | 92 | switch(footnoteSettings.showFootnotes) |
| | 93 | { |
| | 94 | case FootnotesFull: |
| | 95 | /* we're showing all footnotes unconditionally */ |
| | 96 | return gLibMessages.footnoteRef(footnoteNum); |
| | 97 | |
| | 98 | case FootnotesMedium: |
| | 99 | /* we're only showing unread footnotes */ |
| | 100 | return footnoteRead ? '' : gLibMessages.footnoteRef(footnoteNum); |
| | 101 | |
| | 102 | case FootnotesOff: |
| | 103 | /* we're hiding all footnotes unconditionally */ |
| | 104 | return ''; |
| | 105 | } |
| | 106 | |
| | 107 | /* |
| | 108 | * in case the status is invalid and we fall through, return an |
| | 109 | * empty string as a last resort |
| | 110 | */ |
| | 111 | return ''; |
| | 112 | } |
| | 113 | |
| | 114 | /* |
| | 115 | * Display a footnote given its number. If there is no such |
| | 116 | * footnote, we'll display an error message saying so. (This is a |
| | 117 | * class method, so it should be called directly on Footnote, not on |
| | 118 | * instances of Footnote.) |
| | 119 | */ |
| | 120 | showFootnote(num) |
| | 121 | { |
| | 122 | /* |
| | 123 | * if there's a footnote for this number, display it; otherwise, |
| | 124 | * display an error explaining that the footnote number is |
| | 125 | * invalid |
| | 126 | */ |
| | 127 | if (num >= 1 && num <= lastFootnote) |
| | 128 | { |
| | 129 | local fn; |
| | 130 | |
| | 131 | /* |
| | 132 | * it's a valid footnote number - get the footnote object |
| | 133 | * from our vector of footnotes, simply using the footnote |
| | 134 | * number as an index into the vector |
| | 135 | */ |
| | 136 | fn = numberedFootnotes[num]; |
| | 137 | |
| | 138 | /* show its description by calling 'desc' method */ |
| | 139 | fn.desc; |
| | 140 | |
| | 141 | /* note that this footnote text has been read */ |
| | 142 | fn.footnoteRead = true; |
| | 143 | } |
| | 144 | else |
| | 145 | { |
| | 146 | /* there is no such footnote */ |
| | 147 | gLibMessages.noSuchFootnote(num); |
| | 148 | } |
| | 149 | } |
| | 150 | |
| | 151 | /* SettingsItem tracking our current status */ |
| | 152 | footnoteSettings = footnoteSettingsItem |
| | 153 | |
| | 154 | /* |
| | 155 | * my footnote number - this is assigned the first time I'm |
| | 156 | * referenced; initially we have no number, since we don't want to |
| | 157 | * assign a number until the note is first referenced |
| | 158 | */ |
| | 159 | footnoteNum = nil |
| | 160 | |
| | 161 | /* |
| | 162 | * Flag: this footnote's full text has been displayed. This refers |
| | 163 | * to the text of the footnote itself, not the reference, so this is |
| | 164 | * only set when the "FOOTNOTE n" command is used to read this |
| | 165 | * footnote. |
| | 166 | */ |
| | 167 | footnoteRead = nil |
| | 168 | |
| | 169 | /* |
| | 170 | * Static property: the highest footnote number currently in use. |
| | 171 | * We start this at zero, because zero is never a valid footnote |
| | 172 | * number. |
| | 173 | */ |
| | 174 | lastFootnote = 0 |
| | 175 | |
| | 176 | /* |
| | 177 | * Static property: a vector of all footnotes which have had numbers |
| | 178 | * assigned. We use this to find a footnote object given its note |
| | 179 | * number. |
| | 180 | */ |
| | 181 | numberedFootnotes = static new Vector(20) |
| | 182 | |
| | 183 | /* static property: we've never shown a footnote reference before */ |
| | 184 | everShownFootnote = nil |
| | 185 | |
| | 186 | /* static property: per-command-prompt daemon entrypoint */ |
| | 187 | checkNotification() |
| | 188 | { |
| | 189 | /* |
| | 190 | * If we've ever shown a footnote, show the footnote |
| | 191 | * notification now. Note that we know we've never shown a |
| | 192 | * notification before simply because we're still running - we |
| | 193 | * remove this daemon as soon as it shows its notification. |
| | 194 | */ |
| | 195 | if (everShownFootnote) |
| | 196 | { |
| | 197 | /* show the first footnote notification */ |
| | 198 | gLibMessages.firstFootnote(); |
| | 199 | |
| | 200 | /* |
| | 201 | * We only want to show this notification once in the whole |
| | 202 | * game, so we can cancel this daemon now. Since we're the |
| | 203 | * event that's running, we can just tell the event manager |
| | 204 | * to remove the current event from receiving further |
| | 205 | * notifications. |
| | 206 | */ |
| | 207 | eventManager.removeCurrentEvent(); |
| | 208 | } |
| | 209 | } |
| | 210 | ; |
| | 211 | |
| | 212 | /* our FOOTNOTES settings item */ |
| | 213 | footnoteSettingsItem: SettingsItem |
| | 214 | /* our current status - the factory default is "medium" */ |
| | 215 | showFootnotes = FootnotesMedium |
| | 216 | |
| | 217 | /* our config file variable ID */ |
| | 218 | settingID = 'adv3.footnotes' |
| | 219 | |
| | 220 | /* show our short description */ |
| | 221 | settingDesc = (gLibMessages.shortFootnoteStatus(showFootnotes)) |
| | 222 | |
| | 223 | /* get the setting's external file string representation */ |
| | 224 | settingToText() |
| | 225 | { |
| | 226 | switch(showFootnotes) |
| | 227 | { |
| | 228 | case FootnotesMedium: |
| | 229 | return 'medium'; |
| | 230 | |
| | 231 | case FootnotesFull: |
| | 232 | return 'full'; |
| | 233 | |
| | 234 | default: |
| | 235 | return 'off'; |
| | 236 | } |
| | 237 | } |
| | 238 | |
| | 239 | settingFromText(str) |
| | 240 | { |
| | 241 | /* convert to lower-case and strip off spaces */ |
| | 242 | if (rexMatch('<space>*(<alpha>+)', str.toLower()) != nil) |
| | 243 | str = rexGroup(1)[3]; |
| | 244 | |
| | 245 | /* check the keyword */ |
| | 246 | switch (str) |
| | 247 | { |
| | 248 | case 'off': |
| | 249 | showFootnotes = FootnotesOff; |
| | 250 | break; |
| | 251 | |
| | 252 | case 'medium': |
| | 253 | showFootnotes = FootnotesMedium; |
| | 254 | break; |
| | 255 | |
| | 256 | case 'full': |
| | 257 | showFootnotes = FootnotesFull; |
| | 258 | break; |
| | 259 | } |
| | 260 | } |
| | 261 | ; |
| | 262 | |
| | 263 | /* pre-initialization - set up the footnote explanation daemon */ |
| | 264 | PreinitObject |
| | 265 | execute() |
| | 266 | { |
| | 267 | /* since we're available, register as the global footnote handler */ |
| | 268 | libGlobal.footnoteClass = Footnote; |
| | 269 | |
| | 270 | /* initialize the footnote notification daemon */ |
| | 271 | new PromptDaemon(Footnote, &checkNotification); |
| | 272 | } |
| | 273 | ; |
| | 274 | |