diff --git a/exec/slyedcfg.js b/exec/slyedcfg.js index 2def6620fe08329abac9ce6240f32741214fede7..76ab5021c95e462a40d5408666bf80b51dc59a78 100644 --- a/exec/slyedcfg.js +++ b/exec/slyedcfg.js @@ -25,9 +25,33 @@ var gHelpWrapWidth = uifc.screen_width - 10; // Read the SlyEdit configuration file var gCfgInfo = readSlyEditCfgFile(); -// Show the main menu and go from there -doMainMenu(); - +// Show the main menu and go from there. +// This is in a loop so that if the user aborts from confirming to save +// settings, they'll return to the main menu. +var anyOptionChanged = false; +var continueOn = true; +while (continueOn) +{ + anyOptionChanged = doMainMenu(anyOptionChanged) || anyOptionChanged; + // If any configuration option changed, prompt the user & save if the user wants to + if (anyOptionChanged) + { + var userChoice = promptYesNo("Save configuration?", true, WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC); + if (typeof(userChoice) === "boolean") + { + if (userChoice) + { + if (saveSlyEditCfgFile()) + uifc.msg("Changes were successfully saved (to mods dir)"); + else + uifc.msg("Failed to save settings!"); + } + continueOn = false; + } + } + else + continueOn = false; +} /////////////////////////////////////////////////////////// @@ -36,8 +60,7 @@ doMainMenu(); function doMainMenu() { // Create a CTX to specify the current selected item index - if (doMainMenu.ctx == undefined) - doMainMenu.ctx = uifc.list.CTX(); + var ctx = uifc.list.CTX(); var helpText = "Behavior: Behavior settings\r\nIce Colors: Ice-related color settings\r\nDCT Colors: DCT-related color settings"; // Selection var winMode = WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC; @@ -47,8 +70,8 @@ function doMainMenu() while (continueOn && !js.terminated) { uifc.help_text = word_wrap(helpText, gHelpWrapWidth); - var selection = uifc.list(winMode, menuTitle, ["Behavior", "Ice Colors", "DCT Colors"], doMainMenu.ctx); - doMainMenu.ctx.cur = selection; // Remember the current selected item + var selection = uifc.list(winMode, menuTitle, ["Behavior", "Ice Colors", "DCT Colors"], ctx); + ctx.cur = selection; // Remember the current selected item switch (selection) { case -1: // ESC @@ -65,19 +88,7 @@ function doMainMenu() break; } } - - // If any configuration option changed, prompt the user & save if the user wants to - if (anyOptionChanged) - { - var userChoice = promptYesNo("Save configuration?", true, WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC); - if (typeof(userChoice) === "boolean" && userChoice) - { - if (saveSlyEditCfgFile()) - uifc.msg("Changes were successfully saved (to mods dir)"); - else - uifc.msg("Failed to save settings!"); - } - } + return anyOptionChanged; } // Allows the user to change behavior settings. @@ -112,8 +123,9 @@ function doBehaviorMenu() "taglinePrefix", "dictionaryFilenames" ]; - // Menu item text for the toggle options: - var toggleOptItems = [ + // Menu item text for the options: + var optionStrs = [ + // Toggle options: "Display end info screen", "Enable user input timeout", "Re-wrap quote lines", @@ -127,20 +139,34 @@ function doBehaviorMenu() "Enable taglines", "Shuffle taglines", "Double-quotes around tag lines", - "Allow/enable spell check" + "Allow/enable spell check", + // Other options: + "User input timeout (MS)", + "Enable text replacements", + "Tagline filename", + "Tagline prefix", + "Dictionary filenames" ]; // Build the array of items to be displayed on the menu var menuItems = []; // Toggle (on/off) settings - for (var i = 0; i < 14; ++i) - menuItems.push(formatCfgMenuText(itemTextMaxLen, toggleOptItems[i], gCfgInfo.cfgSections.BEHAVIOR[cfgOptProps[i]])); + var optionIdx = 0 + for (; optionIdx < 14; ++optionIdx) + menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[optionIdx], gCfgInfo.cfgSections.BEHAVIOR[cfgOptProps[optionIdx]])); // Text input settings, etc. - menuItems.push(formatCfgMenuText(itemTextMaxLen, "User input timeout (MS)", gCfgInfo.cfgSections.BEHAVIOR.inputTimeoutMS)); + menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[optionIdx++], gCfgInfo.cfgSections.BEHAVIOR.inputTimeoutMS)); // Text replacements can be a boolean true/false or "regex" - menuItems.push(formatCfgMenuText(itemTextMaxLen, "Enable text replacements", getTxtReplacementsVal())); - menuItems.push(formatCfgMenuText(itemTextMaxLen, "Tagline filename", gCfgInfo.cfgSections.BEHAVIOR.tagLineFilename)); - menuItems.push(formatCfgMenuText(itemTextMaxLen, "Tagline prefix", gCfgInfo.cfgSections.BEHAVIOR.taglinePrefix)); - menuItems.push("Dictionary filenames"); // dictionaryFilenames + menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[optionIdx++], getTxtReplacementsVal())); + menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[optionIdx++], gCfgInfo.cfgSections.BEHAVIOR.tagLineFilename)); + menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[optionIdx++], gCfgInfo.cfgSections.BEHAVIOR.taglinePrefix)); + //menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[optionIdx++], gCfgInfo.cfgSections.BEHAVIOR.dictionaryFilenames.substr(0,30))); + menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[optionIdx++], gCfgInfo.cfgSections.BEHAVIOR.dictionaryFilenames)); + + // A dictionary of help text for each option, indexed by the option name from the configuration file + if (doBehaviorMenu.optHelp == undefined) + doBehaviorMenu.optHelp = getOptionHelpText(); + if (doBehaviorMenu.mainScreenHelp == undefined) + doBehaviorMenu.mainScreenHelp = getBehaviorScreenHelp(doBehaviorMenu.optHelp, cfgOptProps); // Create a CTX to specify the current selected item index if (doBehaviorMenu.ctx == undefined) @@ -152,59 +178,20 @@ function doBehaviorMenu() var continueOn = true; while (continueOn && !js.terminated) { - uifc.help_text = getBehaviorScreenHelp(); + uifc.help_text = doBehaviorMenu.mainScreenHelp; var optionMenuSelection = uifc.list(winMode, menuTitle, menuItems, doBehaviorMenu.ctx); doBehaviorMenu.ctx.cur = optionMenuSelection; // Remember the current selected item - switch (optionMenuSelection) + if (optionMenuSelection == -1) // ESC + continueOn = false; + else { - case -1: // ESC - continueOn = false; - break; - // 0-13 are boolean values - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - case 9: - case 10: - case 11: - case 12: - case 13: - gCfgInfo.cfgSections.BEHAVIOR[cfgOptProps[optionMenuSelection]] = !gCfgInfo.cfgSections.BEHAVIOR[cfgOptProps[optionMenuSelection]]; - anyOptionChanged = true; - menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, toggleOptItems[optionMenuSelection], gCfgInfo.cfgSections.BEHAVIOR[cfgOptProps[optionMenuSelection]]); - // With a separate window to prompt for toggling the item: - /* - if (inputCfgObjBoolean(cfgOptProps[optionMenuSelection], toggleOptItems[optionMenuSelection])) - { - anyOptionChanged = true; - menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, toggleOptItems[optionMenuSelection], gCfgInfo.cfgSections.BEHAVIOR[cfgOptProps[optionMenuSelection]]); - } - */ - break; - case 14: // User input timeout (MS) - uifc.help_text = "The user inactivity timeout, in milliseconds"; - var userInput = uifc.input(WIN_MID, "User input timeout (MS)", gCfgInfo.cfgSections.BEHAVIOR.inputTimeoutMS.toString(), 0, K_NUMBER|K_EDIT); - if (typeof(userInput) === "string" && userInput.length > 0) - { - var value = parseInt(userInput); - if (!isNaN(value) && value > 0 && gCfgInfo.cfgSections.BEHAVIOR.inputTimeoutMS != value) - { - gCfgInfo.cfgSections.BEHAVIOR.inputTimeoutMS = value; - anyOptionChanged = true; - menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, "User input timeout (MS)", value); - } - } - break; - case 15: // Enable text replacements (yes/no/regex) - var helpText = "Whether or not to enable text replacements (AKA macros). Can be "; - helpText += "true, false, or 'regex' to use regular expressions."; - uifc.help_text = word_wrap(helpText, gHelpWrapWidth); + var optName = cfgOptProps[optionMenuSelection]; + var itemType = typeof(gCfgInfo.cfgSections.BEHAVIOR[optName]); + uifc.help_text = doBehaviorMenu.optHelp[optName]; + if (optName == "enableTextReplacements") + { + // Enable text replacements - This is above boolean because this + // could be mistaken for a boolean if it's true or false // Store the current value wo we can see if it changes var valBackup = gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements; // Prompt the user @@ -213,7 +200,7 @@ function doBehaviorMenu() ctx.cur = gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements ? 0 : 1; else if (gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements.toLowerCase() === "regex") ctx.cur = 2; - var txtReplacementsSelection = uifc.list(winMode, "Enable text replacements", ["Yes", "No", "Regex"], ctx); + var txtReplacementsSelection = uifc.list(winMode, optionStrs[optionMenuSelection], ["Yes", "No", "Regex"], ctx); switch (txtReplacementsSelection) { case 0: @@ -231,35 +218,59 @@ function doBehaviorMenu() anyOptionChanged = true; menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, "Enable text replacements", getTxtReplacementsVal()); } - break; - case 16: // Tagline filename - var helpText = "The name of the file where tag lines are stored. This file is loaded from the sbbs ctrl directory."; - uifc.help_text = word_wrap(helpText, gHelpWrapWidth); - var userInput = uifc.input(WIN_MID, "Tagline filename", gCfgInfo.cfgSections.BEHAVIOR.tagLineFilename, 60, K_EDIT); + } + else if (itemType === "boolean") + { + gCfgInfo.cfgSections.BEHAVIOR[optName] = !gCfgInfo.cfgSections.BEHAVIOR[optName]; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgSections.BEHAVIOR[optName]); + } + else if (itemType === "number") + { + var promptStr = optionStrs[optionMenuSelection]; + var initialVal = gCfgInfo.cfgSections.BEHAVIOR[optName].toString(); + var minVal = 1; + var userInput = uifc.input(WIN_MID, promptStr, initialVal, 0, K_NUMBER|K_EDIT); + if (typeof(userInput) === "string" && userInput.length > 0) + { + var value = parseInt(userInput); + if (!isNaN(value) && value >= minVal && gCfgInfo.cfgSections.BEHAVIOR[optName] != value) + { + gCfgInfo.cfgSections.BEHAVIOR[optName] = value; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgSections.BEHAVIOR[optName]); + } + } + } + else if (optName == "tagLineFilename") + { + // Tagline filename + var userInput = uifc.input(WIN_MID, optionStrs[optionMenuSelection], gCfgInfo.cfgSections.BEHAVIOR.tagLineFilename, 60, K_EDIT); if (typeof(userInput) === "string" && userInput != gCfgInfo.cfgSections.BEHAVIOR.tagLineFilename) { gCfgInfo.cfgSections.BEHAVIOR.tagLineFilename = userInput; anyOptionChanged = true; menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, "Tagline filename", userInput); } - break; - case 17: // Tagline prefix - var helpText = "Text to add to the front of a tagline when adding it to the message. This "; - helpText += "can be blank (nothing after the =) if no prefix is desired."; - uifc.help_text = word_wrap(helpText, gHelpWrapWidth); - var userInput = uifc.input(WIN_MID, "Tagline prefix", gCfgInfo.cfgSections.BEHAVIOR.taglinePrefix, 0, K_EDIT); + } + else if (optName == "taglinePrefix") + { + // Tagline prefix + var userInput = uifc.input(WIN_MID, optionStrs[optionMenuSelection], gCfgInfo.cfgSections.BEHAVIOR.taglinePrefix, 0, K_EDIT); if (typeof(userInput) === "string" && userInput != gCfgInfo.cfgSections.BEHAVIOR.taglinePrefix) { gCfgInfo.cfgSections.BEHAVIOR.tagLineFilename = userInput; anyOptionChanged = true; menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, "Tagline prefix", userInput); } - break; - case 18: // Dictionary filenames + } + else if (optName == "dictionaryFilenames") + { + // Dictionary filenames var userInput = promptDictionaries(); anyOptionChanged = (userInput != gCfgInfo.cfgSections.BEHAVIOR.dictionaryFilenames); gCfgInfo.cfgSections.BEHAVIOR.dictionaryFilenames = userInput; - break; + } } } @@ -529,64 +540,85 @@ function promptYesNo(pQuestion, pInitialVal, pWinMode) /////////////////////////////////////////////////// // Help text functions -// Returns help text for the behavior configuration screen -function getBehaviorScreenHelp() +// Returns a dictionary of help text, indexed by the option name from the configuration file +function getOptionHelpText() { - if (getBehaviorScreenHelp.help == undefined) - { - getBehaviorScreenHelp.help = "This screen allows you to configure behavior options for SlyEdit.\r\n\r\n"; + var optionHelpText = {}; + optionHelpText["displayEndInfoScreen"] = "Display end info screen: Whether or not to display editor info when exiting"; - getBehaviorScreenHelp.help += "Display end info screen: Whether or not to display editor info when exiting\r\n\r\n"; + optionHelpText["userInputTimeout"] = "User input timeout: Whether or not to enable user inactivity timeout"; - getBehaviorScreenHelp.help += "User input timeout: Whether or not to enable user inactivity timeout\r\n\r\n"; + optionHelpText["reWrapQuoteLines"] = "Re-wrap quote lines: If true, quote lines will be re-wrapped so that they are complete "; + optionHelpText["reWrapQuoteLines"] += "but still look good when quoted. If this option is disabled, then quote lines will "; + optionHelpText["reWrapQuoteLines"] += "simply be trimmed to fit into the message."; - getBehaviorScreenHelp.help += "Re-wrap quote lines: If true, quote lines will be re-wrapped so that they are complete "; - getBehaviorScreenHelp.help += "but still look good when quoted. If this option is disabled, then quote lines will "; - getBehaviorScreenHelp.help += "simply be trimmed to fit into the message.\r\n\r\n"; - - getBehaviorScreenHelp.help += "Use author initials in quoted lines: Whether or not to prefix the quote "; - getBehaviorScreenHelp.help += "lines with the last author's initials. Users can change this for themselves too.\r\n\r\n"; + optionHelpText["useQuoteLineInitials"] = "Use author initials in quoted lines: Whether or not to prefix the quote "; + optionHelpText["useQuoteLineInitials"] += "lines with the last author's initials. Users can change this for themselves too."; - getBehaviorScreenHelp.help += "Indent quoted lines with author initials: When prefixing quote lines with the last author's initials, "; - getBehaviorScreenHelp.help += "whether or not to indent the quote lines with a space. Users can change this for themselves too.\r\n\r\n"; + optionHelpText["indentQuoteLinesWithInitials"] = "Indent quoted lines with author initials: When prefixing quote lines with the last author's initials, "; + optionHelpText["indentQuoteLinesWithInitials"] += "whether or not to indent the quote lines with a space. Users can change this for themselves too."; - getBehaviorScreenHelp.help += "Allow editing quote lines: Whether or not to allow editing quote lines\r\n\r\n"; + optionHelpText["allowEditQuoteLines"] = "Allow editing quote lines: Whether or not to allow editing quote lines"; - getBehaviorScreenHelp.help += "Allow user settings: Whether or not to allow users to change their user settings.\r\n\r\n"; + optionHelpText["allowUserSettings"] = "Allow user settings: Whether or not to allow users to change their user settings."; - getBehaviorScreenHelp.help += "Allow color selection: Whether or not to let the user change the text color\r\n\r\n"; + optionHelpText["allowColorSelection"] = "Allow color selection: Whether or not to let the user change the text color"; - getBehaviorScreenHelp.help += "Save colors as ANSI: Whether or not to save message color/attribute codes as ANSI "; - getBehaviorScreenHelp.help += "(if not, they will be saved as Synchronet attribute codes)\r\n\r\n"; + optionHelpText["saveColorsAsANSI"] = "Save colors as ANSI: Whether or not to save message color/attribute codes as ANSI "; + optionHelpText["saveColorsAsANSI"] += "(if not, they will be saved as Synchronet attribute codes)"; - getBehaviorScreenHelp.help += "Allow cross-posting: Whether or not to allow cross-posting to multiple sub-boards\r\n\r\n"; + optionHelpText["allowCrossPosting"] = "Allow cross-posting: Whether or not to allow cross-posting to multiple sub-boards"; - getBehaviorScreenHelp.help += "Enable taglines: Whether or not to enable the option to add a tagline.\r\n\r\n"; + optionHelpText["enableTaglines"] = "Enable taglines: Whether or not to enable the option to add a tagline."; - getBehaviorScreenHelp.help += "Shuffle taglines: Whether or not to shuffle (randomize) the list of taglines when they are "; - getBehaviorScreenHelp.help += "displayed for the user to choose from\r\n\r\n"; + optionHelpText["shuffleTaglines"] = "Shuffle taglines: Whether or not to shuffle (randomize) the list of taglines when they are "; + optionHelpText["shuffleTaglines"] += "displayed for the user to choose from"; - getBehaviorScreenHelp.help += "Double-quotes around tag lines: Whether or not to add double-quotes around taglines\r\n\r\n"; + optionHelpText["quoteTaglines"] = "Double-quotes around tag lines: Whether or not to add double-quotes around taglines"; - getBehaviorScreenHelp.help += "Allow/enable spell check: Whether or not to allow spell check\r\n\r\n"; + optionHelpText["allowSpellCheck"] = "Allow/enable spell check: Whether or not to allow spell check"; - getBehaviorScreenHelp.help += "User input timeout (MS): The user inactivity timeout, in milliseconds\r\n\r\n"; + optionHelpText["inputTimeoutMS"] = "User input timeout (MS): The user inactivity timeout, in milliseconds"; - getBehaviorScreenHelp.help += "Enable text replacements: Whether or not to enable text replacements (AKA macros). Can be "; - getBehaviorScreenHelp.help += "true, false, or 'regex' to use regular expressions.\r\n\r\n"; + optionHelpText["enableTextReplacements"] = "Enable text replacements: Whether or not to enable text replacements (AKA macros). Can be "; + optionHelpText["enableTextReplacements"] += "true, false, or 'regex' to use regular expressions."; - getBehaviorScreenHelp.help += "Tagline filename: The name of the file where tag lines are stored. This file is loaded "; - getBehaviorScreenHelp.help += "from the sbbs ctrl directory.\r\n\r\n"; + optionHelpText["tagLineFilename"] = "Tagline filename: The name of the file where tag lines are stored. This file is loaded "; + optionHelpText["tagLineFilename"] += "from the sbbs ctrl directory."; - getBehaviorScreenHelp.help += "Tagline prefix: Text to add to the front of a tagline when adding it to the message. This "; - getBehaviorScreenHelp.help += "can be blank (nothing after the =) if no prefix is desired.\r\n\r\n"; + optionHelpText["taglinePrefix"] = "Tagline prefix: Text to add to the front of a tagline when adding it to the message. This "; + optionHelpText["taglinePrefix"] += "can be blank (nothing after the =) if no prefix is desired."; - getBehaviorScreenHelp.help += "Dictionary filenames: These are dictionaries to use for spell check. "; - getBehaviorScreenHelp.help += "The dictionary filenames are in the format dictionary_<language>.txt, where "; - getBehaviorScreenHelp.help += "<language> is the language name. The dictionary files are located in either "; - getBehaviorScreenHelp.help += "sbbs/mods, sbbs/ctrl, or the same directory as SlyEdit. Users can change "; - getBehaviorScreenHelp.help += "this for themselves too."; + optionHelpText["dictionaryFilenames"] = "Dictionary filenames: These are dictionaries to use for spell check. "; + optionHelpText["dictionaryFilenames"] += "The dictionary filenames are in the format dictionary_<language>.txt, where "; + optionHelpText["dictionaryFilenames"] += "<language> is the language name. The dictionary files are located in either "; + optionHelpText["dictionaryFilenames"] += "sbbs/mods, sbbs/ctrl, or the same directory as SlyEdit. Users can change "; + optionHelpText["dictionaryFilenames"] += "this for themselves too."; + // Word-wrap the help text items + for (var prop in optionHelpText) + optionHelpText[prop] = word_wrap(optionHelpText[prop], gHelpWrapWidth); + + return optionHelpText; +} + +// Returns help text for the behavior configuration screen +// +// Parameters: +// pOptionHelpText: An object of help text for each option, indexed by the option name from the configuration file +// pCfgOptProps: An array specifying the properties to include in the help text and their order +// +// Return value: Help text for the behavior options screen +function getBehaviorScreenHelp(pOptionHelpText, pCfgOptProps) +{ + if (getBehaviorScreenHelp.help == undefined) + { + getBehaviorScreenHelp.help = "This screen allows you to configure behavior options for SlyEdit.\r\n\r\n"; + for (var i = 0; i < pCfgOptProps.length; ++i) + { + var optName = pCfgOptProps[i]; + getBehaviorScreenHelp.help += pOptionHelpText[optName] + "\r\n\r\n"; + } getBehaviorScreenHelp.help = word_wrap(getBehaviorScreenHelp.help, gHelpWrapWidth); } return getBehaviorScreenHelp.help; @@ -720,10 +752,8 @@ function readSlyEditCfgFile() // Return value: Boolean - Whether or not the save fully succeeded function saveSlyEditCfgFile() { - var saveSucceeded = false; - // If SlyEdit.cfg doesn't exist in the sbbs/mods directory, then copy it - // from sbbs/ctrl + // from sbbs/ctrl to sbbs/mods // gCfgInfo.cfgFilename contains the full path & filename of the configuration // file var originalCfgFilename = ""; @@ -734,185 +764,26 @@ function saveSlyEditCfgFile() var modsSlyEditCfgFilename = system.mods_dir + gSlyEdCfgFileName; var modsSlyEditCfgFileExists = file_exists(modsSlyEditCfgFilename); if (!modsSlyEditCfgFileExists && file_exists(originalCfgFilename)) - modsSlyEditCfgFileExists = file_copy(originalCfgFilename, modsSlyEditCfgFilename); - - var cfgFile = new File(modsSlyEditCfgFilename); - if (modsSlyEditCfgFileExists) { - if (cfgFile.open("r+")) // Reading and writing (file must exist) - { - for (var settingName in gCfgInfo.cfgSections.BEHAVIOR) - cfgFile.iniSetValue("BEHAVIOR", settingName, gCfgInfo.cfgSections.BEHAVIOR[settingName]); - for (var settingName in gCfgInfo.cfgSections.ICE_COLORS) - cfgFile.iniSetValue("ICE_COLORS", settingName, gCfgInfo.cfgSections.ICE_COLORS[settingName]); - for (var settingName in gCfgInfo.cfgSections.DCT_COLORS) - cfgFile.iniSetValue("DCT_COLORS", settingName, gCfgInfo.cfgSections.DCT_COLORS[settingName]); - - cfgFile.close(); - saveSucceeded = true; - } + if (!file_copy(originalCfgFilename, modsSlyEditCfgFilename)) + return false; } - else + + // Open the configuration file and save the current settings to it + var saveSucceeded = false; + var cfgFile = new File(modsSlyEditCfgFilename); + if (cfgFile.open("r+")) // Reading and writing (file must exist) { - // Creae a new SlyEdit.cfg in sbbs/mods - if (cfgFile.open("w")) - { - saveSucceeded = true; - // Behavior section - if (!cfgFile.writeln("[BEHAVIOR]")) - saveSucceeded = false; - for (var settingName in gCfgInfo.cfgSections.BEHAVIOR) - { - // Write any comments for this setting - var comments = getIniFileCommentsForOpt(settingName, "BEHAVIOR"); - for (var i = 0; i < comments.length; ++i) - { - if (!cfgFile.writeln(comments[i])) - saveSucceeded = false; - } - // Write the setting - var settingLine = settingName + "=" + gCfgInfo.cfgSections.BEHAVIOR[settingName]; - if (!cfgFile.writeln(settingLine)) - saveSucceeded = false; - } - // ICE_COLORS section - if (!cfgFile.writeln("[ICE_COLORS]")) - saveSucceeded = false; - for (var settingName in gCfgInfo.cfgSections.ICE_COLORS) - { - // Write any comments for this setting - var comments = getIniFileCommentsForOpt(settingName, "ICE_COLORS"); - for (var i = 0; i < comments.length; ++i) - { - if (!cfgFile.writeln(comments[i])) - saveSucceeded = false; - } - // Write the setting - var settingLine = settingName + "=" + gCfgInfo.cfgSections.ICE_COLORS[settingName]; - if (!cfgFile.writeln(settingLine)) - saveSucceeded = false; - } - // DCT_COLORS section - if (!cfgFile.writeln("[DCT_COLORS]")) - saveSucceeded = false; - for (var settingName in gCfgInfo.cfgSections.DCT_COLORS) - { - // Write any comments for this setting - var comments = getIniFileCommentsForOpt(settingName, "DCT_COLORS"); - for (var i = 0; i < comments.length; ++i) - { - if (!cfgFile.writeln(comments[i])) - saveSucceeded = false; - } - // Write the setting - var settingLine = settingName + "=" + gCfgInfo.cfgSections.DCT_COLORS[settingName]; - if (!cfgFile.writeln(settingLine)) - saveSucceeded = false; - } + for (var settingName in gCfgInfo.cfgSections.BEHAVIOR) + cfgFile.iniSetValue("BEHAVIOR", settingName, gCfgInfo.cfgSections.BEHAVIOR[settingName]); + for (var settingName in gCfgInfo.cfgSections.ICE_COLORS) + cfgFile.iniSetValue("ICE_COLORS", settingName, gCfgInfo.cfgSections.ICE_COLORS[settingName]); + for (var settingName in gCfgInfo.cfgSections.DCT_COLORS) + cfgFile.iniSetValue("DCT_COLORS", settingName, gCfgInfo.cfgSections.DCT_COLORS[settingName]); - cfgFile.close(); - } + cfgFile.close(); + saveSucceeded = true; } return saveSucceeded; } - -// Returns an array of INI file comments for a particular option (and section name) -function getIniFileCommentsForOpt(pOptName, pSectionName) -{ - var commentLines = []; - if (pSectionName == "BEHAVIOR") - { - if (pOptName == "reWrapQuoteLines") - { - commentLines.push("; If the reWrapQuoteLines option is set to true, quote lines will be re-wrapped"); - commentLines.push("; so that they are complete but still look good when quoted. If this option is"); - commentLines.push("; disabled, then quote lines will simply be trimmed to fit into the message."); - } - else if (pOptName == "allowColorSelection") - commentLines.push("; Whether or not to let the user change the text color"); - else if (pOptName == "saveColorsAsANSI") - { - commentLines.push("; Whether or not to save message color/attribute codes as ANSI (if not, they"); - commentLines.push("; will be saved as Synchronet attribute codes)"); - } - else if (pOptName == "allowCrossPosting") - commentLines.push("; Whether or not to allow cross-posting"); - else if (pOptName == "enableTextReplacements") - { - commentLines.push("; Whether or not to enable text replacements (AKA macros)."); - commentLines.push("; enableTextReplacements can have one of the following values:"); - commentLines.push("; false : Text replacement is disabled"); - commentLines.push("; true : Text replacement is enabled and performed as literal search and replace"); - commentLines.push("; regex : Text replacement is enabled using regular expressions"); - } - else if (pOptName == "tagLineFilename") - commentLines.push("; The name of the file where tag lines are stored"); - else if (pOptName == "taglinePrefix") - { - commentLines.push("; Text to add to the front of a tagline when adding it to the message."); - commentLines.push("; This can be blank (nothing after the =) if no prefix is desired."); - } - else if (pOptName == "quoteTaglines") - commentLines.push("; Whether or not to add double-quotes around taglines"); - else if (pOptName == "shuffleTaglines") - { - commentLines.push("; Whether or not to shuffle (randomize) the list of taglines when they are"); - commentLines.push("; displayed for the user to choose from"); - } - else if (pOptName == "allowUserSettings") - commentLines.push("; Whether or not to allow users to change their user settings."); - //; The following settings serve as defaults for the user settings, which - //; each user can change for themselves: - else if (pOptName == "useQuoteLineInitials") - { - commentLines.push("; Whether or not to prefix the quote lines with the last author's initials"); - commentLines.push("; This also has a user option that takes precedence over this setting."); - } - else if (pOptName == "indentQuoteLinesWithInitials") - { - commentLines.push("; When prefixing quote lines with the last author's initials, whether or not"); - commentLines.push("; to indent the quote lines with a space."); - commentLines.push("; This also has a user option that takes precedence over this setting."); - } - else if (pOptName == "enableTaglines") - { - commentLines.push("; Whether or not to enable the option to add a tagline"); - commentLines.push("; This also has a user option that takes precedence over this setting."); - } - else if (pOptName == "allowEditQuoteLine") - { - commentLines.push("; Whether or not to allow editing quote lines"); - commentLines.push("; This also has a user option that takes precedence over this setting."); - } - else if (pOptName == "allowSpellCheck") - { - commentLines.push("; Whether or not to allow spell check"); - commentLines.push("; This also has a user option that takes precedence over this setting."); - } - else if (pOptName == "dictionaryFilenames") - { - commentLines.push("; Dictionary filenames (used for spell check): This is a comma-separated list of"); - commentLines.push("; dictionary filenames. The dictionary filenames are in the format"); - commentLines.push("; dictionary_<language>.txt, where <language> is the language name. In this"); - commentLines.push("; list, the filenames can be in that format, or just <language>.txt, or just"); - commentLines.push("; <language>. Leave blank to use all dictionary files that exist on the"); - commentLines.push("; system. The dictionary files are located in either sbbs/mods, sbbs/ctrl, or"); - commentLines.push("; the same directory as SlyEdit."); - commentLines.push("; This also has a user option that takes precedence over this setting."); - } - } - else if (pSectionName == "ICE_COLORS") - { - if (pOptName == "ThemeFilename") - commentLines.push("; The filename of the theme file (no leading path necessary)"); - else if (pOptName == "menuOptClassicColors") - commentLines.push("; Whether or not to use all classic IceEdit colors (true/false)"); - } - else if (pSectionName == "DCT_COLORS") - { - if (pOptName == "ThemeFilename") - commentLines.push("; The filename of the theme file (no leading path necessary)"); - } - return commentLines; -} \ No newline at end of file diff --git a/xtrn/DDMsgReader/ddmr_cfg.js b/xtrn/DDMsgReader/ddmr_cfg.js new file mode 100644 index 0000000000000000000000000000000000000000..fa572bff46ddb260aa62a666f9813aacfeaefe97 --- /dev/null +++ b/xtrn/DDMsgReader/ddmr_cfg.js @@ -0,0 +1,765 @@ +// Configurator for Digital Distortion Message Reader: This is a menu-driven configuration +// program/script for Digital Distortion Message reader. If you're running DDMsgReader from +// xtrn/DDMsgReader (the standard location), then any changes are saved to DDMsgReader.cfg in +// sbbs/mods, so that custom changes don't get overridden due to an update. +// If you have DDMsgReader in a directory other than xtrn/DDMsgReader, then the changes to +// DDMsgReader.cfg will be saved in that directory (assuming you're running ddmr_cfg.js from +// that same directory). +// Currently for DDMsgReader 1.85. +// +// If you're running DDMsgReader from xtrn/DDMsgReader (the standard location) and you want +// to save the configuration file there (rather than sbbs/mods), you can use one of the +// following command-line options: noMods, -noMods, no_mods, or -no_mods + +"use strict"; + + +require("sbbsdefs.js", "P_NONE"); +require("uifcdefs.js", "UIFC_INMSG"); + + +if (!uifc.init("DigDist. Message Reader 1.85 Configurator")) +{ + print("Failed to initialize uifc"); + exit(1); +} +js.on_exit("uifc.bail()"); + + +// DDMsgReader base configuration filename, and help text wrap width +var gDDMRCfgFileName = "DDMsgReader.cfg"; +var gHelpWrapWidth = uifc.screen_width - 10; + +// When saving the configuration file, always save it in the same directory +// as DDMsgReader (or this script); don't copy to mods +var gAlwaysSaveCfgInOriginalDir = false; + +// Parse command-line arguments +parseCmdLineArgs(); + +// Read the DDMsgReader configuration file +var gCfgInfo = readDDMsgReaderCfgFile(); + +// Show the main menu and go from there. +// This is in a loop so that if the user aborts from confirming to save +// settings, they'll return to the main menu. +var anyOptionChanged = false; +var continueOn = true; +while (continueOn) +{ + anyOptionChanged = doMainMenu() || anyOptionChanged; + // If any option changed, then let the user save the configuration if they want to + if (anyOptionChanged) + { + var userChoice = promptYesNo("Save configuration?", true, WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC); + if (typeof(userChoice) === "boolean") + { + if (userChoice) + { + var saveRetObj = saveDDMsgReaderCfgFile(); + if (saveRetObj.saveSucceeded) + { + var msg = "Changes were successfully saved"; + if (saveRetObj.savedToModsDir) + msg += " (saved to the mods dir)"; + uifc.msg(msg); + } + else + uifc.msg("Failed to save settings!"); + } + continueOn = false; + } + } + else + continueOn = false; +} + + + +/////////////////////////////////////////////////////////// +// Functions + +function doMainMenu() +{ + // For menu item text formatting + var itemTextMaxLen = 50; + + // Configuration option object properties + // cfgOptProps must correspond exactly with optionStrs & menuItems + var cfgOptProps = [ + "listInterfaceStyle", // String (Lightbar/Traditional) + "reverseListOrder", // Boolean + "readerInterfaceStyle", // String (Scrollable/Traditional) + //"readerInterfaceStyleForANSIMessages", // String (Scrollable/Traditional) + "displayBoardInfoInHeader", // Boolean + "promptToContinueListingMessages", // Boolean + "promptConfirmReadMessage", // Boolean + "msgListDisplayTime", // String (written/imported) + "msgAreaList_lastImportedMsg_time", // String (written/imported) + "startMode", // String (Reader/List) + "tabSpaces", // Number + "pauseAfterNewMsgScan", // Boolean + "readingPostOnSubBoardInsteadOfGoToNext", // Boolean + "areaChooserHdrFilenameBase", // String + "areaChooserHdrMaxLines", // Number + "displayAvatars", // Boolean + "rightJustifyAvatars", // Boolean + "msgListSort", // String (Written/Received) + "convertYStyleMCIAttrsToSync", // Boolean + "prependFowardMsgSubject", // Boolean + "enableIndexedModeMsgListCache", // Boolean + "quickUserValSetIndex", // Number (can be -1) + "saveAllHdrsWhenSavingMsgToBBSPC", // Boolean + "useIndexedModeForNewscan", // Boolean + "themeFilename" // String + ]; + // Strings for the options to display on the menu + var optionStrs = [ + "List Interface Style", + "Reverse List Order", + "Reader Interface Style", + //"readerInterfaceStyleForANSIMessages", + "Display Board Info In Header", + "Prompt to Continue Listing Messages", + "Prompt to Confirm Reading Message", + "Message List Display Time", + "Message Area List: Last Imported Message Time", + "Start Mode", + "Number of Spaces for Tabs", + "Pause After New Message Scan", + "Reading Post On Sub-Board Instead Of Go To Next", + "Area Chooser Header Filename Base", + "Area Chooser Header Max # of Lines", + "Display Avatars", + "Right-Justify Avatars", + "Message List Sort", + "Convert Y-Style MCI Attributes To Sync", + "Prepend Forwarded Message Subject with \"Fwd\"", + "Enable Indexed Mode Message List Cache", + "Quick User Val Set Index", + "Save All Headers When Saving Message To BBS PC", + "Use Indexed Mode For Newscan", + "Theme Filename" + ]; + // Build an array of formatted string to be displayed on the menu + // (the value formatting will depend on the variable type) + var menuItems = []; + for (var i = 0; i < cfgOptProps.length; ++i) + { + var propName = cfgOptProps[i]; + menuItems.push(formatCfgMenuText(itemTextMaxLen, optionStrs[i], gCfgInfo.cfgOptions[propName])); + } + + // Help text + // A dictionary of help text for each option, indexed by the option name from the configuration file + if (doMainMenu.optHelp == undefined) + doMainMenu.optHelp = getOptionHelpText(); + if (doMainMenu.mainScreenHelp == undefined) + doMainMenu.mainScreenHelp = getMainHelp(doMainMenu.optHelp, cfgOptProps); + + // Create a CTX to specify the current selected item index + if (doMainMenu.ctx == undefined) + doMainMenu.ctx = uifc.list.CTX(); + // Selection + var winMode = WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC; + var menuTitle = "DD Message Reader Behavior Configuration"; + var anyOptionChanged = false; + var continueOn = true; + while (continueOn && !js.terminated) + { + uifc.help_text = doMainMenu.mainScreenHelp; + var optionMenuSelection = uifc.list(winMode, menuTitle, menuItems, doMainMenu.ctx); + doMainMenu.ctx.cur = optionMenuSelection; // Remember the current selected item + if (optionMenuSelection == -1) // ESC + continueOn = false; + else + { + var optName = cfgOptProps[optionMenuSelection]; + var itemType = typeof(gCfgInfo.cfgOptions[optName]); + uifc.help_text = doMainMenu.optHelp[optName]; + if (optName == "quickUserValSetIndex") + { + // User quick-validation set index + var selectedValSetIdx = promptQuickValSet(); + if (selectedValSetIdx > -1) + { + gCfgInfo.cfgOptions.quickUserValSetIndex = selectedValSetIdx; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgOptions[optName]); + } + } + else if (itemType === "boolean") + { + gCfgInfo.cfgOptions[optName] = !gCfgInfo.cfgOptions[optName]; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgOptions[optName]); + } + else if (itemType === "number") + { + var promptStr = optionStrs[optionMenuSelection]; + var initialVal = gCfgInfo.cfgOptions[optName].toString(); + var minVal = 1; + var userInput = uifc.input(WIN_MID, promptStr, initialVal, 0, K_NUMBER|K_EDIT); + if (typeof(userInput) === "string" && userInput.length > 0) + { + var value = parseInt(userInput); + if (!isNaN(value) && value >= minVal && gCfgInfo.cfgOptions[optName] != value) + { + gCfgInfo.cfgOptions[optName] = value; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgOptions[optName]); + } + } + } + else + { + if (optName == "areaChooserHdrFilenameBase") + { + // Area chooser header filename base + var promptStr = optionStrs[optionMenuSelection]; + var userInput = uifc.input(WIN_MID, promptStr, gCfgInfo.cfgOptions[optName], 60, K_EDIT); + if (typeof(userInput) === "string" && userInput.length > 0) + { + gCfgInfo.cfgOptions[optName] = userInput; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgOptions[optName]); + } + } + else if (optName == "themeFilename") + { + // Theme filename + var userInput = promptThemeFilename(); + if (typeof(userInput) === "string" && userInput != gCfgInfo.cfgOptions[optName]) + { + gCfgInfo.cfgOptions[optName] = userInput; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgOptions[optName]); + } + } + else + { + // Multiple-choice + var options = []; + if (optName == "listInterfaceStyle") + options = ["Lightbar", "Traditional"]; + else if (optName == "readerInterfaceStyle") + options = ["Scrollable", "Traditional"]; + else if (optName == "msgListDisplayTime" || optName == "msgAreaList_lastImportedMsg_time") + options = ["Written", "Imported"]; + else if (optName == "startMode") + options = ["Reader", "List"]; + else if (optName == "msgListSort") + options = ["Written", "Received"]; + var promptStr = optionStrs[optionMenuSelection]; + var userChoice = promptMultipleChoice(promptStr, options, gCfgInfo.cfgOptions[optName]); + if (userChoice != null && userChoice != undefined && userChoice != gCfgInfo.cfgOptions[optName]) + { + gCfgInfo.cfgOptions[optName] = userChoice; + anyOptionChanged = true; + menuItems[optionMenuSelection] = formatCfgMenuText(itemTextMaxLen, optionStrs[optionMenuSelection], gCfgInfo.cfgOptions[optName]); + } + } + } + } + } + + return anyOptionChanged; +} + +// Formats text for a behavior option +// +// Parameters: +// pItemTextMaxLen: The maximum length for menu item text +// pItemText: The text of the item to be displayed on the menu +// pVal: The value of the option +// +// Return value: The formatted text for the menu item, with a Yes/No value indicating whether it's enabled +function formatCfgMenuText(pItemTextMaxLen, pItemText, pVal) +{ + if (formatCfgMenuText.formatStr == undefined) + formatCfgMenuText.formatStr = "%-" + pItemTextMaxLen + "s %s"; + // Determine what should be displayed for the value + var valType = typeof(pVal); + var value = ""; + if (valType === "boolean") + value = pVal ? "Yes" : "No"; + else + value = pVal.toString(); + return format(formatCfgMenuText.formatStr, pItemText.substr(0, pItemTextMaxLen), value); +} + + +// Prompts the user for which dictionaries to use (for spell check) +function promptThemeFilename() +{ + // Find theme filenames. There should be a DefaultTheme.cfg; also, look + // for theme .cfg filenames starting with DDMR_Theme_ + var defaultThemeFilename = js.exec_dir + "DefaultTheme.cfg"; + var themeFilenames = []; + if (file_exists(defaultThemeFilename)) + themeFilenames.push(defaultThemeFilename); + themeFilenames = themeFilenames.concat(directory(js.exec_dir + "DDMR_Theme_*.cfg")); + + // Abbreviated theme file names: Get just the filename without the full path, + // and remove the trailing .cfg + var abbreviatedThemeFilenames = []; + for (var i = 0; i < themeFilenames.length; ++i) + { + var themeFilename = file_getname(themeFilenames[i]).replace(/\.cfg$/, ""); + abbreviatedThemeFilenames.push(themeFilename); + } + // Add an option at the end to let the user type it themselves + abbreviatedThemeFilenames.push("Type your own filename"); + + // Create a context, and look for the current theme filename & set the + // current index + var ctx = uifc.list.CTX(); + if (gCfgInfo.cfgOptions.themeFilename.length > 0) + { + var themeFilenameUpper = gCfgInfo.cfgOptions.themeFilename.toUpperCase(); + for (var i = 0; i < themeFilenames.length; ++i) + { + if (themeFilenames[i].toUpperCase() == themeFilenameUpper) + { + ctx.cur = i; + break; + } + } + } + // User input + var chosenThemeFilename = null; + var selection = uifc.list(WIN_MID, "Theme Filename", abbreviatedThemeFilenames, ctx); + if (selection == -1) + { + // User quit/aborted; do nothing + } + // Last item: Let the user input the filename themselves + else if (selection == abbreviatedThemeFilenames.length-1) + { + var userInput = uifc.input(WIN_MID, "Theme filename", "", 0, K_EDIT); + if (typeof(userInput) === "string") + chosenThemeFilename = userInput; + } + else + chosenThemeFilename = file_getname(themeFilenames[selection]); + + return chosenThemeFilename; +} + + +// Prompts the user to select one of multiple values for an option +// +// Parameters: +// pPrompt: The prompt text +// pChoices: An array of the choices +// pCurrentVal: The current value (to set the index in the menu) +// +// Return value: The user's chosen value, or null if the user aborted +function promptMultipleChoice(pPrompt, pChoices, pCurrentVal) +{ + //uifc.help_text = pHelpText; + // Create a context object with the current value index + var currentValUpper = pCurrentVal.toUpperCase(); + var ctx = uifc.list.CTX(); + for (var i = 0; i < pChoices.length; ++i) + { + if (pChoices[i].toUpperCase() == currentValUpper) + { + ctx.cur = i; + break; + } + } + // Prompt the user and return their chosen value + var chosenValue = null; + //var winMode = WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC; + var winMode = WIN_MID; + var userSelection = uifc.list(winMode, pPrompt, pChoices, ctx); + if (userSelection >= 0 && userSelection < pChoices.length) + chosenValue = pChoices[userSelection]; + return chosenValue; +} + +// Prompts the user Yes/No for a boolean response +// +// Parameters: +// pQuestion: The question/prompt for the user +// pInitialVal: Boolean - Whether the initial selection in the menu should +// be Yes (true) or No (false) +// pWinMode: Optional window mode bits. If not specified, WIN_MID will be used. +// +// Return value: Boolean true (yes), false (no), or null if the user aborted +function promptYesNo(pQuestion, pInitialVal, pWinMode) +{ + var chosenVal = null; + var winMode = typeof(pWinMode) === "number" ? pWinMode : WIN_MID; + // Create a CTX to specify the current selected item index + var ctx = uifc.list.CTX(); + ctx.cur = typeof(pInitialVal) === "boolean" && pInitialVal ? 0 : 1; + switch (uifc.list(winMode, pQuestion, ["Yes", "No"], ctx)) + { + case -1: // User quit/aborted - Leave chosenVal as null + break; + case 0: // User chose Yes + chosenVal = true; + break; + case 1: // User chose No + chosenVal = false; + break; + default: + break; + } + return chosenVal; +} + +// Prompts the user for a quick-validation set. Returns the index of the +// validation set, or -1 if none chosen. +function promptQuickValSet() +{ + var chosenQuickValSetIdx = -1; + + var promptStr = "Quick-Validation Values"; + // Create a context object with the current value index + var ctx = uifc.list.CTX(); + if (gCfgInfo.cfgOptions.quickUserValSetIndex >= 0 && gCfgInfo.cfgOptions.quickUserValSetIndex < quickValSets.length) + ctx.cur = gCfgInfo.cfgOptions.quickUserValSetIndex; + + // Choices: If main.ini exists (i.e., Synchronet 3.20+), then get the + // quick validation sets from that. Otherwise, just make a list of indexes 0-9. + var menuStrs = []; + if (file_exists(system.ctrl_dir + "main.ini")) + { + var quickValSets = getQuickValidationVals(); + var formatStr = "%-d: SL: %-3d F1: %s"; + for (var i = 0; i < quickValSets.length; ++i) + menuStrs.push(format(formatStr, i, quickValSets[i].level, getUserValFlagsStr(quickValSets[i].flags1))); + } + else + { + for (var i = 0; i < 10; ++i) + menuStrs.push(i.toString()); + } + + // Prompt the user and return their chosen value + var chosenValue = null; + //var winMode = WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC; + var winMode = WIN_MID; + var userSelection = uifc.list(winMode, promptStr, menuStrs, ctx); + if (userSelection >= 0 && userSelection < quickValSets.length) + chosenQuickValSetIdx = userSelection; + return chosenQuickValSetIdx; +} + + +/////////////////////////////////////////////////// +// Help text functions + + +// Returns a dictionary of help text, indexed by the option name from the configuration file +function getOptionHelpText() +{ + var optionHelpText = {}; + optionHelpText["listInterfaceStyle"] = "List Interface Style: Either Lightbar or Traditional"; + + optionHelpText["reverseListOrder"] = "Reverse List Order: Whether or not message lists are to be shown in reverse order. This is a default for "; + optionHelpText["reverseListOrder"] += "a user setting."; + + optionHelpText["readerInterfaceStyle"] = "Reader Interface Style: This can be either Scrollable (allowing scrolling up & down) or Traditional. "; + optionHelpText["readerInterfaceStyle"] += "The scrollable interface only works if the user's terminal supports ANSI. The reader will fall back "; + optionHelpText["readerInterfaceStyle"] += "to a traditional interface if the usser's terminal doesn't support ANSI."; + + //optionHelpText["readerInterfaceStyleForANSIMessages"] = ""; + + optionHelpText["displayBoardInfoInHeader"] = "Display Board Info In Header: Whether or not to display the message group and sub-board lines in the "; + optionHelpText["displayBoardInfoInHeader"] += "header at the top of the screen (an additional 2 lines)."; + + optionHelpText["promptToContinueListingMessages"] = "Prompt to Continue Listing Messages: Whether or not to ask the user if they want to continue listing "; + optionHelpText["promptToContinueListingMessages"] += "messages after they read a message"; + + optionHelpText["promptConfirmReadMessage"] = "Prompt to Confirm Reading Message: Whether or not to prompt the user to confirm to read a message "; + optionHelpText["promptConfirmReadMessage"] += "when a message is selected from the message list"; + + optionHelpText["msgListDisplayTime"] = "Message List Display Time: Whether to display the message import time or the written time in the "; + optionHelpText["msgListDisplayTime"] += "message lists. Valid values are imported and written"; + + optionHelpText["msgAreaList_lastImportedMsg_time"] = "Message Area List Last Imported Message Time: For the last message time in the message lists, "; + optionHelpText["msgAreaList_lastImportedMsg_time"] += "whether to use message import time or written time. Valid values are imported and written"; + + optionHelpText["startMode"] = "Start Mode: Specifies the default startup mode (Reader/Read or Lister/List)"; + + optionHelpText["tabSpaces"] = "Number of Spaces for Tabs: This specifies how many spaces to use for tabs (if a message has tabs)"; + + optionHelpText["pauseAfterNewMsgScan"] = "Pause After Nes Message Scan: Whether or not to pause at the end of a newscan"; + + optionHelpText["readingPostOnSubBoardInsteadOfGoToNext"] = "Reading Post On Sub-Board Instead Of Go To Next: When reading messages (but not for a newscan, etc.): "; + optionHelpText["readingPostOnSubBoardInsteadOfGoToNext"] += "Whether or not to ask the user whether to post on the sub-board in reader mode after reading the last "; + optionHelpText["readingPostOnSubBoardInsteadOfGoToNext"] += "message instead of prompting to go to the next sub-board. This is like the stock Synchronet behavior."; + + optionHelpText["areaChooserHdrFilenameBase"] = "Area Chooser Header Filename Base: If you'd like to have an ANSI displayed above the lists of the area "; + optionHelpText["areaChooserHdrFilenameBase"] += "chooser, you can specify the 'base' of the filename (without the .ans/.asc) here. The file must be in the "; + optionHelpText["areaChooserHdrFilenameBase"] += "same directory as DDMsgReader.js."; + + optionHelpText["areaChooserHdrMaxLines"] = "Area Chooser Header Max # of Lines: The maximum number of lines to use from the area chooser header file"; + + optionHelpText["displayAvatars"] = "Display Avatars: Whether or not to display user avatars (the small user-specified ANSIs) when reading messages"; + + optionHelpText["rightJustifyAvatars"] = "Right-Justify Avatars: Whether or not to display user avatars on the right. If false, they will be displayed "; + optionHelpText["rightJustifyAvatars"] += "on the left."; + + optionHelpText["msgListSort"] = "Message List Sort: How to sort the message lists - Either Received (by date/time received, which is faster), "; + optionHelpText["msgListSort"] += "or Written (by date/time written, which takes time due to sorting)"; + + optionHelpText["convertYStyleMCIAttrsToSync"] = "Convert Y-Style MCI Attributes to Sync: Whether or not to convert Y-Style MCI attribute/color codes to "; + optionHelpText["convertYStyleMCIAttrsToSync"] += "Synchronet attribute codes (if disabled, these codes will appear as-is rather than as the colors or "; + optionHelpText["convertYStyleMCIAttrsToSync"] += "attributes they represent)"; + + optionHelpText["prependFowardMsgSubject"] = "Prepend Forwarded Message Subject with \"Fwd\" Whether or not to prepend the subject for forwarded messages "; + optionHelpText["prependFowardMsgSubject"] += "with \"Fwd: \""; + + optionHelpText["enableIndexedModeMsgListCache"] = "Enable Indexed Mode Message List Cache: For indexed reader mode, whether or not to enable caching the message "; + optionHelpText["enableIndexedModeMsgListCache"] += "header lists for performance"; + + optionHelpText["quickUserValSetIndex"] = "Quick User Val Set Index: An index of a quick-validation set from SCFG > System > Security Options > "; + optionHelpText["quickUserValSetIndex"] += "Quick-Validation Values to be used by the sysop to quick-validate a local user who has posted a message while "; + optionHelpText["quickUserValSetIndex"] += "reading the message. This index is 0-based, as they appear in SCFG. Normally there are 10 quick-validation "; + optionHelpText["quickUserValSetIndex"] += "values, so valid values for this index are 0 through 9. If you would rather DDMsgReader display a menu of "; + optionHelpText["quickUserValSetIndex"] += "quick-validation sets, you can set this to an invalid index (such as -1)."; + + optionHelpText["saveAllHdrsWhenSavingMsgToBBSPC"] = "Save All Headers When Saving Message to BBS PC: As the sysop, you can save messages to the BBS PC. This "; + optionHelpText["saveAllHdrsWhenSavingMsgToBBSPC"] += "option specifies whether or not to save all the message headers along with the message. If disabled, "; + optionHelpText["saveAllHdrsWhenSavingMsgToBBSPC"] += "only a few relevant headers will be saved (such as From, To, Subject, and message time)."; + + optionHelpText["useIndexedModeForNewscan"] = "Used Indexed Mode for Newscan: Whether or not to use indexed mode for message newscans (not for new-to-you "; + optionHelpText["useIndexedModeForNewscan"] += "scans). This is a default for a user setting. When indexed mode is enabled for newscans, the reader displays "; + optionHelpText["useIndexedModeForNewscan"] += "a menu showing each sub-board and the number of new messages and total messages in each. When disabled, "; + optionHelpText["useIndexedModeForNewscan"] += "the reader will do a traditional newscan where it will scan through the sub-boards and go into reader "; + optionHelpText["useIndexedModeForNewscan"] += "mode when there are new messages in a sub-board."; + + optionHelpText["themeFilename"] = "Theme filename: The name of a file for a color theme to use"; + + // Word-wrap the help text items + for (var prop in optionHelpText) + optionHelpText[prop] = word_wrap(optionHelpText[prop], gHelpWrapWidth); + + return optionHelpText; +} + +// Returns help text for the main configuration screen (behavior settings) +// +// Parameters: +// pOptionHelpText: An object of help text for each option, indexed by the option name from the configuration file +// pCfgOptProps: An array specifying the properties to include in the help text and their order +// +// Return value: Help text for the main configuration screen (behavior settings) +function getMainHelp(pOptionHelpText, pCfgOptProps) +{ + var helpText = "This screen allows you to configure behavior options for Digital Distortion Message Reader.\r\n\r\n"; + for (var i = 0; i < pCfgOptProps.length; ++i) + { + var optName = pCfgOptProps[i]; + helpText += pOptionHelpText[optName] + "\r\n\r\n"; + } + return word_wrap(helpText, gHelpWrapWidth); +} + + +/////////////////////////////////////////////////// +// Non-UI utility functions + +// Returns an array of the quick-validation sets configured in +// SCFG > System > Security > Quick-Validation Values. This reads +// from main.ini, which exists with Synchronet 3.20 and newer. +// In SCFG: +// +// Level 60 | +// Flag Set #1 | +// Flag Set #2 | +// Flag Set #3 | +// Flag Set #4 | +// Exemptions | +// Restrictions | +// Extend Expiration 0 days | +// Additional Credits 0 | +// +// Each object in the returned array will have the following properties: +// level (numeric) +// expire +// flags1 +// flags2 +// flags3 +// flags4 +// credits +// exemptions +// restrictions +function getQuickValidationVals() +{ + var validationValSets = []; + // In SCFG > System > Security > Quick-Validation Values, there are 10 sets of + // validation values. These are in main.ini as [valset:0] through [valset:9] + // This reads from main.ini, which exists with Synchronet 3.20 and newer. + //system.version_num >= 32000 + var mainIniFile = new File(system.ctrl_dir + "main.ini"); + if (mainIniFile.open("r")) + { + for (var i = 0; i < 10; ++i) + { + // Flags: + // AZ is 0x2000001 + // A is 1 + // Z is 0x2000000 + var valSection = mainIniFile.iniGetObject(format("valset:%d", i)); + if (valSection != null) + validationValSets.push(valSection); + } + mainIniFile.close(); + } + return validationValSets; +} +// Generates a string based on user quick-validation flags +// +// Parameters: +// pFlags: A bitfield of user quick-validation flags +// +// Return value: A string representing the quick-validation flags (as would appear in SCFG) +function getUserValFlagsStr(pFlags) +{ + var flagLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + var flagsStr = ""; + for (var i = 0; i < flagLetters.length; ++i) + flagsStr += (Boolean((1 << i) & pFlags) ? flagLetters[i] : " "); + return truncsp(flagsStr); +} + +// Parses command-line arguments & sets any applicable values +function parseCmdLineArgs() +{ + for (var i = 0; i < argv.length; ++i) + { + var argUpper = argv[i].toUpperCase(); + if (argUpper == "NOMODS" || argUpper == "NO_MODS" || argUpper == "-NOMODS" || argUpper == "-NO_MODS") + gAlwaysSaveCfgInOriginalDir = true; + } +} + +// Reads the DDMsgReader configuration file +// +// Return value: An object with the following properties: +// cfgFilename: The full path & filename of the DDMsgReader configuration file that was read +// cfgOptions: A dictionary of the configuration options and their values +function readDDMsgReaderCfgFile() +{ + var retObj = { + cfgFilename: "", + cfgOptions: {} + }; + + // Determine the location of the configuration file. First look for it + // in the sbbs/mods directory, then sbbs/ctrl, then in the same directory + // as this script. + var cfgFilename = file_cfgname(system.mods_dir, gDDMRCfgFileName); + if (!file_exists(cfgFilename)) + cfgFilename = file_cfgname(system.ctrl_dir, gDDMRCfgFileName); + if (!file_exists(cfgFilename)) + cfgFilename = file_cfgname(js.exec_dir, gDDMRCfgFileName); + retObj.cfgFilename = cfgFilename; + + // Open and read the configuration file + var cfgFile = new File(retObj.cfgFilename); + if (cfgFile.open("r")) + { + retObj.cfgOptions = cfgFile.iniGetObject(); + cfgFile.close(); + } + + // In case some settings weren't loaded, add defaults + if (!retObj.cfgOptions.hasOwnProperty("listInterfaceStyle")) + retObj.cfgOptions.listInterfaceStyle = "Lightbar"; + if (!retObj.cfgOptions.hasOwnProperty("reverseListOrder")) + retObj.cfgOptions.reverseListOrder = false; + if (!retObj.cfgOptions.hasOwnProperty("readerInterfaceStyle")) + retObj.cfgOptions.readerInterfaceStyle = "Scrollable"; + if (!retObj.cfgOptions.hasOwnProperty("readerInterfaceStyleForANSIMessages")) + retObj.cfgOptions.readerInterfaceStyleForANSIMessages = "Scrollable"; + if (!retObj.cfgOptions.hasOwnProperty("displayBoardInfoInHeader")) + retObj.cfgOptions.displayBoardInfoInHeader = false; + if (!retObj.cfgOptions.hasOwnProperty("promptToContinueListingMessages")) + retObj.cfgOptions.promptToContinueListingMessages = false; + if (!retObj.cfgOptions.hasOwnProperty("promptConfirmReadMessage")) + retObj.cfgOptions.promptConfirmReadMessage = false; + if (!retObj.cfgOptions.hasOwnProperty("msgListDisplayTime")) + retObj.cfgOptions.msgListDisplayTime = "written"; + if (!retObj.cfgOptions.hasOwnProperty("msgAreaList_lastImportedMsg_time")) + retObj.cfgOptions.msgAreaList_lastImportedMsg_time = "written"; + if (!retObj.cfgOptions.hasOwnProperty("startMode")) + retObj.cfgOptions.startMode = "Reader"; + if (!retObj.cfgOptions.hasOwnProperty("tabSpaces")) + retObj.cfgOptions.tabSpaces = 3; + if (!retObj.cfgOptions.hasOwnProperty("pauseAfterNewMsgScan")) + retObj.cfgOptions.pauseAfterNewMsgScan = true; + if (!retObj.cfgOptions.hasOwnProperty("readingPostOnSubBoardInsteadOfGoToNext")) + retObj.cfgOptions.readingPostOnSubBoardInsteadOfGoToNext = false; + if (!retObj.cfgOptions.hasOwnProperty("areaChooserHdrFilenameBase")) + retObj.cfgOptions.areaChooserHdrFilenameBase = ""; + if (!retObj.cfgOptions.hasOwnProperty("areaChooserHdrMaxLines")) + retObj.cfgOptions.areaChooserHdrMaxLines = 12; + if (!retObj.cfgOptions.hasOwnProperty("displayAvatars")) + retObj.cfgOptions.displayAvatars = true; + if (!retObj.cfgOptions.hasOwnProperty("rightJustifyAvatars")) + retObj.cfgOptions.rightJustifyAvatars = true; + if (!retObj.cfgOptions.hasOwnProperty("msgListSort")) + retObj.cfgOptions.msgListSort = "Received"; + if (!retObj.cfgOptions.hasOwnProperty("convertYStyleMCIAttrsToSync")) + retObj.cfgOptions.convertYStyleMCIAttrsToSync = true; + if (!retObj.cfgOptions.hasOwnProperty("prependFowardMsgSubject")) + retObj.cfgOptions.prependFowardMsgSubject = true; + if (!retObj.cfgOptions.hasOwnProperty("enableIndexedModeMsgListCache")) + retObj.cfgOptions.enableIndexedModeMsgListCache = true; + if (!retObj.cfgOptions.hasOwnProperty("quickUserValSetIndex")) + retObj.cfgOptions.quickUserValSetIndex = -1; + if (!retObj.cfgOptions.hasOwnProperty("saveAllHdrsWhenSavingMsgToBBSPC")) + retObj.cfgOptions.saveAllHdrsWhenSavingMsgToBBSPC = false; + if (!retObj.cfgOptions.hasOwnProperty("useIndexedModeForNewscan")) + retObj.cfgOptions.useIndexedModeForNewscan = false; + if (!retObj.cfgOptions.hasOwnProperty("themeFilename")) + retObj.cfgOptions.themeFilename = "DefaultTheme.cfg"; + + return retObj; +} + +// Saves the DDMsgReader configuration file using the settings in gCfgInfo +// +// Return value: An object with the following properties: +// saveSucceeded: Boolean - Whether or not the save fully succeeded +// savedToModsDir: Boolean - Whether or not the .cfg file was saved to the mods directory +function saveDDMsgReaderCfgFile() +{ + var retObj = { + saveSucceeded: false, + savedToModsDir: false + }; + + // If the configuration file was loaded from the standard location in + // the Git repository (xtrn/DDMsgReader), and the option to always save + // in the original directory is not set, then the configuration file + // should be copied to sbbs/mods to avoid custom settings being overwritten + // with an update. + var defaultDirRE = new RegExp("xtrn[\\\\/]DDMsgReader[\\\\/]" + gDDMRCfgFileName + "$"); + var cfgFilename = gCfgInfo.cfgFilename; + if (defaultDirRE.test(cfgFilename) && !gAlwaysSaveCfgInOriginalDir) + { + cfgFilename = system.mods_dir + gDDMRCfgFileName; + if (!file_copy(gCfgInfo.cfgFilename, cfgFilename)) + return false; + retObj.savedToModsDir = true; + } + + var cfgFile = new File(cfgFilename); + if (cfgFile.open("r+")) // Reading and writing (file must exist) + { + for (var settingName in gCfgInfo.cfgOptions) + cfgFile.iniSetValue(null, settingName, gCfgInfo.cfgOptions[settingName]); + cfgFile.close(); + retObj.saveSucceeded = true; + } + + return retObj; +}