/*
** menus.t allows you to add simple menus to your HTML TADS game.
** For an example of their use, see the Arrival or Common Ground
** source (in if-archive/games/tads/source). Feel free to use in
** your own TADS 2 games.
**
** Copyright (c) 1998, 1999 Stephen Granade. All rights reserved.
**
** Special thanks to Mike Roberts, whose help with this module was
** invaluable.
*/

#pragma C+

// The keys used to navigate the menus are held in the keyList array of the
// menuItem. Each navigation option is bound to two keys. The #defines below
// are for accessing the keys associated with the navigation options (quit,
// previous menu, up a selection, down a selection, select a menu item).
// Since each navigation option is bound to two keys, the #defines consist
// of odd numbers.
#define M_QUIT      1
#define M_PREV      3
#define M_UP        5
#define M_DOWN      7
#define M_SEL       9

// The menuItem is the topmost object in a menu tree.
class menuItem: object
    title = ""                  // The name of the menu
    myContents = []             // The submenuItems, topicItems, and longTopicItems
    bgcolor = 'statusbg'        // Background color of the menu
    fgcolor = 'statustext'      // Foreground color of the menu
    indent = '10'               // # of spaces to indent the menu's contents
    allowHTML = true            // Assume the game is running in HTML mode
                                // (i.e. "\H+" has been printed)
    keyList = ['q' '' 'p' '[left]' 'u' '[up]' 'd' '[down]' 'ENTER' '\n'
        '[right]' ' ']

    // topBanner creates the HTML TADS banner for the menu. The banner contains
    // the title of the menu on the left and the navigation keys on the right
    topBanner = {
        "<banner id=MenuTitle><body bgcolor=\"<<self.fgcolor>>\"
            text=\"<<self.bgcolor>>\">";
        "<<self.title>>
            <tab align=\"right\">\^<<self.keyList[M_QUIT]>>=quit \^<<
            self.keyList[M_PREV]>>=previous menu<br>
            <tab align=\"right\">\^<<self.keyList[M_UP]>>=up \^<<
            self.keyList[M_DOWN]>>=down \^<<self.keyList[M_SEL]>>=select";
        "</banner>";
    }

    // Call menu.display when you're ready to show the menu.
    display = {
        local i, selection, len = length(self.myContents), key = '', loc;

        // If we are running on an HTML TADS interpreter and the game uses
        // HTML formatting, use HTML tags to make the menus prettier
        if (systemInfo(__SYSINFO_SYSINFO) == true &&
            systemInfo(__SYSINFO_HTML) == 1 && self.allowHTML) {
            self.displayHTML;
            return;
        }

        // For standard TADS, print the title, then the menu options
        // (numbered), then ask the player to make a selection.
        do {
            "\b\(<<self.title>>\)\b";

            for (i = 1; i <= len; i++) {
                if (i < 10) "\ ";
                "<<i>>.\ <<(self.myContents[i]).title>>\n";
            }
            "\bSelect a topic number, or press &rsquo;<<
                self.keyList[M_QUIT]>>&lsquo; to quit.\ ";

            do {                                // The input loop
                key = lower(inputkey());        // Get the key
                loc = find(self.keyList, key);  // Get the key's pos in keyList
                selection = cvtnum(key);        // Turn key into a #, if possible
                if (loc && (loc % 2 == 0))      // If loc is even, make it odd
                    loc--;
            } while (selection == 0 && loc != M_QUIT);

            "<<key>>\n";                        // Print the selection
            // If selection is a number, then the player selected that menu
            // option. Call that submenu or topic's display routine. If the
            // routine returns nil, the player selected QUIT, so we should
            // quit as well.
            if (selection != 0) {
                if (!((self.myContents[selection]).display(self)))
                    loc = M_QUIT;
            }
        } while (loc != M_QUIT && loc != M_PREV);   // Loop until player quits
    }

    displayHTML = {
        local i, selection = 1, len = length(self.myContents), key = '', loc,
            events;

        // Erase the status line and print the topmost menu banner
        self.removeStatusLine;
        self.topBanner;

        // For HTML TADS, set up a separate menu item banner. In it,
        // print the menu's contents. Next to the current selection, print
        // a greater-than sign. Each of the menu items is an HREF, so the
        // player can select it using the mouse
        do {
            "<banner border id=MenuBody><body bgcolor=\"<<self.bgcolor>>\"
                text=\"<<self.fgcolor>>\">";
            "<table><tr><td width=\"<<self.indent>>\"> </td><td>";
            for (i = 1; i <= len; i++) {
                // To get the alignment right, we have to print '>' on each
                // and every line. However, we print it in the background
                // color to make it invisible everywhere but in front of the
                // current selection.
                if (selection != i)
                    "<font color=\"<<self.bgcolor>>\">&gt;</font>";
                else "&gt;";
                // Make each selection a plain (i.e. unhilighted) HREF
                "<a plain href=\"<<i>>\">";
                (self.myContents[i]).title;
                "</a><br>";
            }
            "</td></tr></table>";
            "</banner>";

            do {                            // The input loop. For HTML TADS,
                events = inputevent();      // we look at events rather than
                                            // just keystrokes
                if (events[1] == INPUT_EVENT_HREF) {    // For HREFs, set the
                    selection = cvtnum(events[2]);      // selection # to the
                    loc = M_SEL;                        // HREF's value
                }
                else {
                    key = lower(events[2]); // Otherwise, assume the event is
                                            // a keystroke and get the key
                    loc = find(self.keyList, key);  // Find key's pos in keyList
                    if (loc && (loc % 2 == 0))  // If loc is even, make it odd
                        loc--;
                }
                // First off, handle arrow keys
                if (loc == M_UP && selection != 1)
                    selection--;
                else if (loc == M_DOWN && selection != len)
                    selection++;
            } while (loc == nil);   // Loop until the user presses a valid key

            if (loc >= M_SEL) {     // The player made a selection, so show it
                if (!((self.myContents[selection]).displayHTML(self)))
                    loc = M_QUIT;
            }
        } while (loc != M_QUIT && loc != M_PREV);

        // Clean up after ourselves
        "<banner remove id=MenuTitle><banner remove id=MenuBody>";
    }

    removeStatusLine = {
        "<banner remove id=StatusLine>";
    }
;

// submenuItem is exactly like menuItem, except that it refers to the top-
// level menu item to get the keyList
class submenuItem: object
    title = ""
    myContents = []
    bgcolor = 'statusbg'
    fgcolor = 'statustext'
    indent = '10'

    display(topMenu) = {
        local i, selection, len = length(self.myContents), key = '', loc;

        do {
            "\b\(<<self.title>>\)\b";
            for (i = 1; i <= len; i++) {
                if (i < 10) "\ ";
                "<<i>>.\ <<(self.myContents[i]).title>>\n";
            }
            "\bSelect a topic number, or press &rsquo;<<
                topMenu.keyList[M_QUIT]>>&lsquo; to quit,
                &rsquo;<<topMenu.keyList[M_PREV]>>&lsquo; to go to the
                previous menu.\ ";

            do {
                key = lower(inputkey());
                loc = find(topMenu.keyList, key);
                selection = cvtnum(key);
                if (loc && (loc % 2 == 0))
                    loc--;
            } while (selection == 0 && loc != M_QUIT && loc != M_PREV);
            "<<key>>\n";
            if (selection != 0) {
                if (!((self.myContents[selection]).display(topMenu)))
                    return nil;
            }
        } while (loc != M_QUIT && loc != M_PREV);

        return (loc == M_PREV);
    }

    displayHTML(topMenu) = {
        local i, selection = 1, len = length(self.myContents), key = '', loc,
            events;

        while (true) {
            do {
                "<banner border id=MenuBody><body bgcolor=\"<<self.bgcolor>>\"
                    text=\"<<self.fgcolor>>\">";
                "<table><tr><td width=\"<<self.indent>>\"> </td><td>";
                for (i = 1; i <= len; i++) {
                    if (selection != i)
                        "<font color=\"<<self.bgcolor>>\">&gt;</font>";
                    else "&gt;";
                    (self.myContents[i]).title;
                    "<br>";
                }
                "</td></tr></table>";
                "</banner>";

                do {
                    events = inputevent();
                    if (events[1] == INPUT_EVENT_HREF) {
                        selection = cvtnum(events[2]);
                        loc = M_SEL;
                    }
                    else {
                        key = lower(events[2]);
                        loc = find(topMenu.keyList, key);
                        if (loc && (loc % 2 == 0))
                            loc--;
                    }
                    if (loc == M_UP && selection != 1)
                        selection--;
                    else if (loc == M_DOWN && selection != len)
                        selection++;
                } while (loc == nil);
            } while (loc == M_UP || loc == M_DOWN);

            if (loc >= M_SEL) {
                if (!((self.myContents[selection]).displayHTML(topMenu)))
                    return nil;
            }
            else return (loc == M_PREV);
        }
    }
;

// topicItem displays a series of entries successively. It's intended to be
// used for displaying a group of hints. Unlike [sub]menuItem, myContents
// contains a list of strings to be displayed
class topicItem: object
    title = ""              // The name of this topic
    myContents = []         // A list of strings to be displayed
    bgcolor = 'statusbg'    // The background color
    fgcolor = 'statustext'  // The foreground color
    indent = '30'           // How far to indent all of the strings
    lastDisplayed = 1       // The last string displayed
    chunkSize = 6           // How many strings to display at once, max
                            // (only valid under HTML TADS)
    goodbye = '[The End]'   // The ending phrase

    display(topMenu) = {
        local i, len = length(self.myContents), key = '', loc;

        "\b\(<<self.title>>\)\b";
        // Print all of the strings up to and including lastDisplayed. Also,
        // append "[#/#]" after them to show which hint out of how many
        // each is.
        for (i = 1; i <= self.lastDisplayed; i++) {
            "<<self.myContents[i]>> [<<i>>/<<len>>]\b";
            // If we're at the end, let the player know by printing the
            // goodbye message
            if (i == len)
                "<<self.goodbye>>\n";
        }

        while (true) {
            key = lower(inputkey());
            loc = find(topMenu.keyList, key);
            if (loc && (loc % 2 == 0))
                loc--;
            if (loc == M_QUIT)
                return nil;
            if (loc == M_PREV || self.lastDisplayed == len)
                 return true;
            self.lastDisplayed++;
            "<<self.myContents[self.lastDisplayed]>> [<<
                self.lastDisplayed>>/<<len>>]\b";
            if (self.lastDisplayed == len)
                "<<self.goodbye>>\n";
        }
    }

    displayHTML(topMenu) = {
        local i, selection = 1, len = length(self.myContents), key = '', loc,
            firstTime = true, topTopic = 1, chunk = self.chunkSize, events;

        while (true) {
            // Set up the banner
            "<banner border id=MenuBody><body bgcolor=\"<<self.bgcolor>>\"
                text=\"<<self.fgcolor>>\">";
            "<table><tr><td width=\"<<self.indent>>\"> </td><td>";
            // The firstTime variable flags whether or not this is our first
            // trip through this loop. If it's not, we need to adjust the
            // # of displayed hints, among other things.
            if (firstTime)
                i = 1;
            else {
                // Only display a # of strings equal to or less than chunkSize
                if (topTopic != 1 &&
                    self.lastDisplayed - topTopic < self.chunkSize)
                    i = topTopic;
                else i = self.lastDisplayed - self.chunkSize + 1;
                if (i < 1)
                    i = 1;
            }
            while (i <= self.lastDisplayed) {
                // If we display a chunk's worth of strings and this is our
                // first time through the loop we pause, then keep printing
                // out the rest of the strings
                if (i > chunk && firstTime) {
                    "</td></tr></table>";
                    "</banner>";
                    events = inputevent();
                    if (events[1] == INPUT_EVENT_HREF) {    // There should be
                        selection = cvtnum(events[2]);      // no HREF events,
                        loc = M_SEL;                    // but just in case...
                    }
                    else {
                        key = lower(events[2]);
                        loc = find(topMenu.keyList, key);
                        if (loc && (loc % 2 == 0))
                            loc--;
                    }
                    if (loc == M_QUIT || loc == M_PREV)
                        return (loc != M_QUIT);
                    "<banner border id=MenuBody><body
                        bgcolor=\"<<self.bgcolor>>\"
                        text=\"<<self.fgcolor>>\">";
                    "<table><tr><td width=\"<<self.indent>>\"> </td><td>";
                    // Increment the counter which keeps track of the string
                    // # at which the next chunk ends. Also bump up the
                    // current top topic string #.
                    chunk += self.chunkSize;
                    topTopic = i;
                }
                "<<self.myContents[i]>> [<<i>>/<<len>>]<BR>";
                if (i == len)
                    say(self.goodbye);
                i++;
            }
            "</td></tr></table>";
            "</banner>";

            key = lower(inputkey());
            loc = find(topMenu.keyList, key);
            if (loc == M_QUIT || loc == M_PREV ||
                self.lastDisplayed == len) {
                return (loc != M_QUIT);
            }
            self.lastDisplayed++;
            firstTime = nil;    // This is no longer our first time through
                                // the loop
        }
    }
;

// longTopicItems are used to print out big long gobs of text on a subject.
// Use it for printing long treatises on your design philosophy and the like.
class longTopicItem: object
    title = ""
    myContents = ''             // This can be a string or a routine
    goodbye = '[The End]'       // The goodbye message

    display(topMenu) = {
        local key, loc;

        clearscreen();
        "<CENTER>\(<<self.title>>\)</CENTER>\b";

        // Simply dump out our contents, then print the goodbye message.
        "<<self.myContents>>\b<<self.goodbye>>";

        key = lower(inputkey());
        loc = find(topMenu.keyList, key);
        if (loc && (loc % 2 == 0))
            loc--;
        clearscreen();
        return (loc != M_QUIT);
    }

    displayHTML(topMenu) = {
        local ret;

        // longTopicItems are displayed the same in HTML TADS as they are
        // in standard TADS, barring some banner fiddling.
        ret = self.display(topMenu);
        topMenu.topBanner;
        return ret;
    }
;

