diff --git a/exec/slyedcfg.js b/exec/slyedcfg.js new file mode 100644 index 0000000000000000000000000000000000000000..2def6620fe08329abac9ce6240f32741214fede7 --- /dev/null +++ b/exec/slyedcfg.js @@ -0,0 +1,918 @@ +// SlyEdit configurator: This is a menu-driven configuration program/script for SlyEdit. +// Any changes are saved to SlyEdit.cfg in sbbs/mods, so that custom changes don't get +// overridden with SlyEdit.cfg in sbbs/ctrl due to an update. +// Currently for SlyEdit 1.87. + +"use strict"; + + +require("sbbsdefs.js", "P_NONE"); +require("uifcdefs.js", "UIFC_INMSG"); + + +if (!uifc.init("SlyEdit 1.87 Configurator")) +{ + print("Failed to initialize uifc"); + exit(1); +} +js.on_exit("uifc.bail()"); + + +// SlyEdit base configuration filename, and help text wrap width +var gSlyEdCfgFileName = "SlyEdit.cfg"; +var gHelpWrapWidth = uifc.screen_width - 10; + +// Read the SlyEdit configuration file +var gCfgInfo = readSlyEditCfgFile(); + +// Show the main menu and go from there +doMainMenu(); + + + +/////////////////////////////////////////////////////////// +// Functions + +function doMainMenu() +{ + // Create a CTX to specify the current selected item index + if (doMainMenu.ctx == undefined) + doMainMenu.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; + var menuTitle = "SlyEdit Configuration"; + var anyOptionChanged = false; + var continueOn = true; + 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 + switch (selection) + { + case -1: // ESC + continueOn = false; + break; + case 0: // Behavior + anyOptionChanged = doBehaviorMenu() || anyOptionChanged; + break; + case 1: // Ice colors + anyOptionChanged = doIceColors() || anyOptionChanged; + break; + case 2: // DCT colors + anyOptionChanged = doDCTColors() || anyOptionChanged; + 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!"); + } + } +} + +// Allows the user to change behavior settings. +// Returns whether any option changed (boolean). +function doBehaviorMenu() +{ + // For menu item text formatting + var itemTextMaxLen = 40; + + // For options in the SlyEdit configuration, the order of + // cfgOptProps must correspond exactly with menuItems + // Configuration object properties (true/false properties are first): + var cfgOptProps = [ + "displayEndInfoScreen", + "userInputTimeout", + "reWrapQuoteLines", + "useQuoteLineInitials", + "indentQuoteLinesWithInitials", + "allowEditQuoteLines", + "allowUserSettings", + "allowColorSelection", + "saveColorsAsANSI", + "allowCrossPosting", + "enableTaglines", + "shuffleTaglines", + "quoteTaglines", + "allowSpellCheck", + + "inputTimeoutMS", + "enableTextReplacements", + "tagLineFilename", + "taglinePrefix", + "dictionaryFilenames" + ]; + // Menu item text for the toggle options: + var toggleOptItems = [ + "Display end info screen", + "Enable user input timeout", + "Re-wrap quote lines", + "Use author initials in quoted lines", + "Indent quoted lines with author initials", + "Allow editing quote lines", + "Allow user settings", + "Allow color selection", + "Save colors as ANSI", + "Allow cross-posting", + "Enable taglines", + "Shuffle taglines", + "Double-quotes around tag lines", + "Allow/enable spell check" + ]; + // 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]])); + // Text input settings, etc. + menuItems.push(formatCfgMenuText(itemTextMaxLen, "User input timeout (MS)", 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 + + // Create a CTX to specify the current selected item index + if (doBehaviorMenu.ctx == undefined) + doBehaviorMenu.ctx = uifc.list.CTX(); + // Selection + var winMode = WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC; + var menuTitle = "SlyEdit Behavior Configuration"; + var anyOptionChanged = false; + var continueOn = true; + while (continueOn && !js.terminated) + { + uifc.help_text = getBehaviorScreenHelp(); + var optionMenuSelection = uifc.list(winMode, menuTitle, menuItems, doBehaviorMenu.ctx); + doBehaviorMenu.ctx.cur = optionMenuSelection; // Remember the current selected item + switch (optionMenuSelection) + { + 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); + // Store the current value wo we can see if it changes + var valBackup = gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements; + // Prompt the user + var ctx = uifc.list.CTX(); + if (typeof(gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements) === "boolean") + 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); + switch (txtReplacementsSelection) + { + case 0: + gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements = true; + break; + case 1: + gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements = false; + break; + case 2: + gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements = "regex"; + break; + } + if (gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements != valBackup) + { + 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); + 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); + 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 + var userInput = promptDictionaries(); + anyOptionChanged = (userInput != gCfgInfo.cfgSections.BEHAVIOR.dictionaryFilenames); + gCfgInfo.cfgSections.BEHAVIOR.dictionaryFilenames = userInput; + break; + } + } + + return anyOptionChanged; +} + +function doIceColors() +{ + // Create a CTX to specify the current selected item index for this + // menu and the theme file menu + if (doIceColors.ctx == undefined) + doIceColors.ctx = uifc.list.CTX(); + if (doIceColors.theme_ctx == undefined) + doIceColors.theme_ctx = uifc.list.CTX(); + // Format string for the menu items, and menu items + var formatStr = "%-30s %s"; + var menuItems = [ + format(formatStr, "Menu option classic colors", gCfgInfo.cfgSections.ICE_COLORS.menuOptClassicColors ? "Yes" : "No"), + format(formatStr, "Theme file", gCfgInfo.cfgSections.ICE_COLORS.ThemeFilename.replace(/\.cfg$/, "")) + ]; + + var helpText = "Menu option classic colors: Whether to use 100% classic colors for menu options\r\n\r\n"; + helpText += "Theme file: The color theme configuration filename (can be in mods or ctrl)"; + + // Selection + var winMode = WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC; + var anyOptionChanged = false; + var continueOn = true; + while (continueOn && !js.terminated) + { + uifc.help_text = word_wrap(helpText, gHelpWrapWidth); + var selection = uifc.list(winMode, "SlyEdit Ice Colors", menuItems, doIceColors.ctx); + doIceColors.ctx.cur = selection; // Remember the current selected item + switch (selection) + { + case -1: // ESC + continueOn = false; + break; + case 0: // Toggle menu option classic colors + gCfgInfo.cfgSections.ICE_COLORS.menuOptClassicColors = !gCfgInfo.cfgSections.ICE_COLORS.menuOptClassicColors; + menuItems[0] = format(formatStr, "Menu option classic colors", gCfgInfo.cfgSections.ICE_COLORS.menuOptClassicColors ? "Yes" : "No"); + anyOptionChanged = true; + break; + case 1: // Theme file + var dictFilenames = getAbbreviatedThemeFilenameList("Ice"); + var themeSelection = uifc.list(winMode, "SlyEdit Ice Theme", dictFilenames, doIceColors.theme_ctx); + doIceColors.theme_ctx.cur = themeSelection; // Remember the current selected item + if (themeSelection >= 0 && themeSelection < dictFilenames.length) + { + gCfgInfo.cfgSections.ICE_COLORS.ThemeFilename = dictFilenames[themeSelection] + ".cfg"; + menuItems[1] = format(formatStr, "Theme file", dictFilenames[themeSelection]); + anyOptionChanged = true; + } + break; + } + } + return anyOptionChanged; +} + +function doDCTColors() +{ + // Create a CTX to specify the current selected item index for this + // menu and the theme file menu + if (doDCTColors.ctx == undefined) + doDCTColors.ctx = uifc.list.CTX(); + if (doDCTColors.theme_ctx == undefined) + doDCTColors.theme_ctx = uifc.list.CTX(); + // Format string for the menu items, and menu items + var formatStr = "%-30s %s"; + var menuItems = [ + format(formatStr, "Theme file", gCfgInfo.cfgSections.DCT_COLORS.ThemeFilename.replace(/\.cfg$/, "")) + ]; + + var helpText = "Theme file: The color theme configuration filename (can be in mods or ctrl)"; + + // Selection + var winMode = WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC; + var anyOptionChanged = false; + var continueOn = true; + while (continueOn && !js.terminated) + { + uifc.help_text = word_wrap(helpText, gHelpWrapWidth); + var selection = uifc.list(winMode, "SlyEdit DCT Colors", menuItems, doDCTColors.ctx); + doDCTColors.ctx.cur = selection; // Remember the current selected item + switch (selection) + { + case -1: // ESC + continueOn = false; + break; + case 0: // Theme file + var dictFilenames = getAbbreviatedThemeFilenameList("DCT"); + var themeSelection = uifc.list(winMode, "SlyEdit DCT Theme", dictFilenames, doDCTColors.theme_ctx); + doDCTColors.theme_ctx.cur = themeSelection; // Remember the current selected item + if (themeSelection >= 0 && themeSelection < dictFilenames.length) + { + gCfgInfo.cfgSections.DCT_COLORS.ThemeFilename = dictFilenames[themeSelection] + ".cfg"; + menuItems[0] = format(formatStr, "Theme file", dictFilenames[themeSelection]); + anyOptionChanged = true; + } + break; + } + } + return anyOptionChanged; +} + +// Gets a value for the text replacements option. This could be a boolean true/false +// or the string "regex". +function getTxtReplacementsVal() +{ + var txtReplacementsItemVal = ""; + if (gCfgInfo.cfgSections.BEHAVIOR.textReplacementsUseRegex) + txtReplacementsItemVal = "Regex"; + else + txtReplacementsItemVal = gCfgInfo.cfgSections.BEHAVIOR.enableTextReplacements; + return txtReplacementsItemVal; +} + +// 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); +} + +// For a boolean configuration option, this prompts the user +// for a yes/no response, with option display string +// +// Return value: Boolean - Whether or not the option changed from +// the current configuration +function inputCfgObjBoolean(pCfgProp, pDisplayStr) +{ + var optChanged = false; + var toggleOpt = promptYesNo(pDisplayStr, gCfgInfo.cfgSections.BEHAVIOR[pCfgProp]); + if (typeof(toggleOpt) === "boolean") + { + optChanged = (gCfgInfo.cfgSections.BEHAVIOR[pCfgProp] != toggleOpt); + gCfgInfo.cfgSections.BEHAVIOR[pCfgProp] = toggleOpt; + } + return optChanged; +} + +// Prompts the user for which dictionaries to use (for spell check) +function promptDictionaries() +{ + // Find the dictionary filenames in sbbs/ctrl + var dictFilenames = directory(system.ctrl_dir + "dictionary_*.txt"); + if (dictFilenames.length == 0) + { + uifc.msg("There are no dictionaries available in ctrl/"); + return ""; + } + + // Abbreviated dictionary names: Get just the filename without the full path, + // and remove the trailing .txt and leading dictionary_ + var abbreviatedDictNames = []; + for (var i = 0; i < dictFilenames.length; ++i) + { + var dictFilename = file_getname(dictFilenames[i]).replace(/\.txt$/, ""); + dictFilename = dictFilename.replace(/^dictionary_/, ""); + abbreviatedDictNames.push(dictFilename); + } + + // Split the current list of dictionaries into an array + var existingDictionaries = gCfgInfo.cfgSections.BEHAVIOR.dictionaryFilenames.split(","); + // A map of abbreviated dictionary names and booleans for their enabled status + var dictToggleVals = {}; + for (var i = 0; i < abbreviatedDictNames.length; ++i) + dictToggleVals[abbreviatedDictNames[i]] = (existingDictionaries.indexOf(abbreviatedDictNames[i]) > -1); + + // Menu items: Abbreviated dictionary names with "Yes" or "No" + var dictNameWidth = 30; + var formatStr = "%-" + dictNameWidth + "s %s"; + var dictMenuItems = []; + for (var i = 0; i < abbreviatedDictNames.length; ++i) + { + var enabledStr = dictToggleVals[abbreviatedDictNames[i]] ? "Yes" : "No"; + dictMenuItems.push(format(formatStr, abbreviatedDictNames[i], enabledStr)); + } + + // Create a CTX to keep track of the selected item index + var ctx = uifc.list.CTX(); + // Help text + var helpText = "Here, you can enable which dictionaries to use for spell check. "; + helpText += "The dictionary filenames are in the format dictionary_<language>.txt, where "; + helpText += "<language> is the language name. The dictionary files are located in either "; + helpText += "sbbs/mods, sbbs/ctrl, or the same directory as SlyEdit."; + uifc.help_text = word_wrap(helpText, gHelpWrapWidth); + // User input loop + var continueOn = true; + while (continueOn) + { + var selection = uifc.list(WIN_MID, "Dictionaries", dictMenuItems, ctx); + if (selection == -1) // User quit/aborted + continueOn = false; + else + { + var abbreviatedDictName = abbreviatedDictNames[selection]; + dictToggleVals[abbreviatedDictName] = !dictToggleVals[abbreviatedDictName]; + var enabledStr = dictToggleVals[abbreviatedDictName] ? "Yes" : "No"; + dictMenuItems[selection] = format(formatStr, abbreviatedDictName, enabledStr); + ctx.cur = selection; // Remember the current selected item index + } + } + + // Build a comma-separated list of the Abbreviated dictionary names and return it + var dictNames = ""; + for (var dictName in dictToggleVals) + { + if (dictToggleVals[dictName]) + { + if (dictNames != "") + dictNames += ","; + dictNames += dictName; + } + } + return dictNames; +} + + +// 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; +} + +/////////////////////////////////////////////////// +// Help text functions + +// Returns help text for the behavior configuration screen +function getBehaviorScreenHelp() +{ + if (getBehaviorScreenHelp.help == undefined) + { + getBehaviorScreenHelp.help = "This screen allows you to configure behavior options for SlyEdit.\r\n\r\n"; + + getBehaviorScreenHelp.help += "Display end info screen: Whether or not to display editor info when exiting\r\n\r\n"; + + getBehaviorScreenHelp.help += "User input timeout: Whether or not to enable user inactivity timeout\r\n\r\n"; + + 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"; + + 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"; + + getBehaviorScreenHelp.help += "Allow editing quote lines: Whether or not to allow editing quote lines\r\n\r\n"; + + getBehaviorScreenHelp.help += "Allow user settings: Whether or not to allow users to change their user settings.\r\n\r\n"; + + getBehaviorScreenHelp.help += "Allow color selection: Whether or not to let the user change the text color\r\n\r\n"; + + 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"; + + getBehaviorScreenHelp.help += "Allow cross-posting: Whether or not to allow cross-posting to multiple sub-boards\r\n\r\n"; + + getBehaviorScreenHelp.help += "Enable taglines: Whether or not to enable the option to add a tagline.\r\n\r\n"; + + 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"; + + getBehaviorScreenHelp.help += "Double-quotes around tag lines: Whether or not to add double-quotes around taglines\r\n\r\n"; + + getBehaviorScreenHelp.help += "Allow/enable spell check: Whether or not to allow spell check\r\n\r\n"; + + getBehaviorScreenHelp.help += "User input timeout (MS): The user inactivity timeout, in milliseconds\r\n\r\n"; + + 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"; + + 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"; + + 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"; + + 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."; + + getBehaviorScreenHelp.help = word_wrap(getBehaviorScreenHelp.help, gHelpWrapWidth); + } + return getBehaviorScreenHelp.help; +} + + +/////////////////////////////////////////////////// +// Non-UI utility functions + + +// Gets an array of abbreviated color configuration filenames. +// Assumes they're in the format Sly<OPT>Colors_*.cfg, where +// OPT is either Ice or DCT +function getAbbreviatedThemeFilenameList(pIceOrDCT) +{ + // The theme files may be in sbbs/ctrl or sbbs/mods. Create a dictionary + // of the filenames to store unique filenames from both directories. + var dictFilenames = {}; + var dictFilenamesArray = directory(system.ctrl_dir + "Sly" + pIceOrDCT + "Colors_*.cfg"); + dictFilenamesArray = dictFilenamesArray.concat(directory(system.mods_dir + "Sly" + pIceOrDCT + "Colors_*.cfg")); + for (var i = 0; i < dictFilenamesArray.length; ++i) + dictFilenames[dictFilenamesArray[i]] = true; + + // For each dictionary filename, get just the filename without the + // leading path and remove the trailing .cfg, and build a dictionary + // of these abbreviated filenames. + var abbreviatedDictFilenames = []; + for (var filename in dictFilenames) + { + var abbreviatedFilename = file_getname(filename.replace(/\.cfg$/, "")); + abbreviatedDictFilenames.push(abbreviatedFilename); + } + return abbreviatedDictFilenames; +} + +// For configuration files, this function returns a fully-pathed filename. +// This function first checks to see if the file exists in the sbbs/mods +// directory, then the sbbs/ctrl directory, and if the file is not found there, +// this function defaults to the given default path. +// +// Parameters: +// pFilename: The name of the file to look for +function genFullPathCfgFilename(pFilename) +{ + var fullyPathedFilename = system.mods_dir + pFilename; + if (!file_exists(fullyPathedFilename)) + fullyPathedFilename = system.ctrl_dir + pFilename; + if (!file_exists(fullyPathedFilename)) + fullyPathedFilename = js.exec_dir + pFilename; + return fullyPathedFilename; +} + +// Reads the SlyEdit configuration file +// +// Return value: An object with the following properties: +// cfgFilename: The full path & filename of the SlyEdit configuration file that was read +// cfgSections: A dictionary of the INI sections (BEHAVIOR, ICE_COLORS, DCT_COLORS) +function readSlyEditCfgFile() +{ + var retObj = { + cfgFilename: genFullPathCfgFilename(gSlyEdCfgFileName), + cfgSections: {} + }; + + var cfgFile = new File(retObj.cfgFilename); + if (cfgFile.open("r")) + { + var iniSectionNames = cfgFile.iniGetSections(); + for (var i = 0; i < iniSectionNames.length; ++i) + retObj.cfgSections[iniSectionNames[i]] = cfgFile.iniGetObject(iniSectionNames[i]); + cfgFile.close(); + } + // In case some settings weren't loaded, add defaults + if (!retObj.cfgSections.hasOwnProperty("BEHAVIOR")) + retObj.cfgSections.BEHAVIOR = {}; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("displayEndInfoScreen")) + retObj.cfgSections.BEHAVIOR.displayEndInfoScreen = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("userInputTimeout")) + retObj.cfgSections.BEHAVIOR.userInputTimeout = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("inputTimeoutMS")) + retObj.cfgSections.BEHAVIOR.inputTimeoutMS = 30000; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("reWrapQuoteLines")) + retObj.cfgSections.BEHAVIOR.reWrapQuoteLines = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("allowColorSelection")) + retObj.cfgSections.BEHAVIOR.allowColorSelection = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("saveColorsAsANSI")) + retObj.cfgSections.BEHAVIOR.saveColorsAsANSI = false; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("allowCrossPosting")) + retObj.cfgSections.BEHAVIOR.allowCrossPosting = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("enableTextReplacements")) + retObj.cfgSections.BEHAVIOR.enableTextReplacements = false; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("tagLineFilename")) + retObj.cfgSections.BEHAVIOR.tagLineFilename = "SlyEdit_Taglines.txt"; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("taglinePrefix")) + retObj.cfgSections.BEHAVIOR.taglinePrefix = "..."; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("quoteTaglines")) + retObj.cfgSections.BEHAVIOR.quoteTaglines = false; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("shuffleTaglines")) + retObj.cfgSections.BEHAVIOR.shuffleTaglines = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("allowUserSettings")) + retObj.cfgSections.BEHAVIOR.allowUserSettings = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("useQuoteLineInitials")) + retObj.cfgSections.BEHAVIOR.useQuoteLineInitials = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("indentQuoteLinesWithInitials")) + retObj.cfgSections.BEHAVIOR.indentQuoteLinesWithInitials = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("enableTaglines")) + retObj.cfgSections.BEHAVIOR.enableTaglines = false; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("allowEditQuoteLines")) + retObj.cfgSections.BEHAVIOR.allowEditQuoteLines = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("allowSpellCheck")) + retObj.cfgSections.BEHAVIOR.allowSpellCheck = true; + if (!retObj.cfgSections.BEHAVIOR.hasOwnProperty("dictionaryFilenames")) + retObj.cfgSections.BEHAVIOR.dictionaryFilenames = "en,en-US-supplemental"; + + if (!retObj.cfgSections.hasOwnProperty("ICE_COLORS")) + retObj.cfgSections.ICE_COLORS = {}; + if (!retObj.cfgSections.ICE_COLORS.hasOwnProperty("menuOptClassicColors")) + retObj.cfgSections.ICE_COLORS.menuOptClassicColors = true; + if (!retObj.cfgSections.ICE_COLORS.hasOwnProperty("ThemeFilename")) + retObj.cfgSections.ICE_COLORS.ThemeFilename = "SlyIceColors_BlueIce.cfg"; + + if (!retObj.cfgSections.hasOwnProperty("DCT_COLORS")) + retObj.cfgSections.DCT_COLORS = {}; + if (!retObj.cfgSections.ICE_COLORS.hasOwnProperty("ThemeFilename")) + retObj.cfgSections.DCT_COLORS.ThemeFilename = "SlyDCTColors_Default.cfg"; + return retObj; +} + +// Saves the SlyEdit configuration file using the settings in gCfgInfo +// +// 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 + // gCfgInfo.cfgFilename contains the full path & filename of the configuration + // file + var originalCfgFilename = ""; + if (gCfgInfo.cfgFilename.length > 0) + originalCfgFilename = gCfgInfo.cfgFilename; + else + originalCfgFilename = system.ctrl_dir + gSlyEdCfgFileName; + 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; + } + } + else + { + // 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; + } + + cfgFile.close(); + } + } + + 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