From 5d3ecc64003919d3cb3c9e7a144d8434a3c376ff Mon Sep 17 00:00:00 2001 From: Mark Cannon Date: Tue, 28 Dec 2010 05:15:47 -0500 Subject: [PATCH] Added documentation for ThemePrefs and ThemePrefsRows, updated some code and added sanity checks into ThemePrefsRows, added localization support for error messages in ThemePrefs and ThemePrefsRows. --- Docs/Themerdocs/ThemePrefs.txt | 88 +++++++++++ Docs/Themerdocs/ThemePrefsRows.txt | 145 ++++++++++++++++++ Themes/_fallback/Languages/en.ini | 12 +- Themes/_fallback/Scripts/03 ThemePrefs.lua | 31 ++-- .../_fallback/Scripts/03 ThemePrefsRows.lua | 55 +++++-- 5 files changed, 307 insertions(+), 24 deletions(-) create mode 100644 Docs/Themerdocs/ThemePrefs.txt create mode 100644 Docs/Themerdocs/ThemePrefsRows.txt diff --git a/Docs/Themerdocs/ThemePrefs.txt b/Docs/Themerdocs/ThemePrefs.txt new file mode 100644 index 0000000000..d0307503ba --- /dev/null +++ b/Docs/Themerdocs/ThemePrefs.txt @@ -0,0 +1,88 @@ +ThemePrefs, v0.7 +---------------- + +ThemePrefs is a system for handling theme-specific preferences in a standard, +efficient, and predictable way. All you do, basically, is declare a table of +preferences and options and use GetThemePref(name) to get your preference +values as needed. This system will handle all disk I/O as well. + +Additionally, it can work in combination with ThemePrefsRows to generate +OptionsRows for all preferences, saving you (most of) the trouble with +integrating it into your theme. More on that later. + +The prefs are written, using IniFile, into Save/ThemePrefs.ini, written under +the section corresponding to your theme name (if ThemeInfo exists, it will +use the Name field listed there; otherwise, it will use the folder name). + +---------------------- +Section 1: Declaration +---------------------- + +To declare preferences for your theme to use, make a table with named +tables in it; its name is used for the preference, and the tables hold +the options for that preference. All you need to know for now is that +Default is required; for more options, check ThemePrefsRows. + +After the table is declared, pass it to the function ThemePrefs.InitAll(). +(If you're planning not to use any OptionsRows at all, you can just use +ThemePrefs.Init() to save some processing time, but seriously give the +ThemePrefsRows system a look before you disregard it.) + +[code] +local Prefs = +{ + BoolPref = { Default = false }, + IntPref = { Default = 3 }, +} + +ThemePrefs.InitAll( Prefs ) +[/code] + +If your theme is "default", this will generate a ThemePrefs.ini like so: + +[default] +BoolPref=false +IntPref=3 + +---------------- +Section 2: Usage +---------------- + +You can access the value with ThemePrefs.Get(name) or GetThemePref(name): +both calls work identically. The return type will be based on whatever you set +it to, but the subsystem will always try number, then bool, then string, in +that order. Use true and false for bools, not 1 and 0. + +For example, if you wanted to branch something in the metrics based on +the value of BoolPref (which is a boolean), you could use the following: + +NextScreen=GetThemePref('BoolPref') and "ScreenTrue" or "ScreenFalse" + +If you want to set a preference value for some reason, you can use either +ThemePrefs.Set(name, value) or SetThemePref(name, value), but the point of +this API is to simplify and standardize prefs handling, so you really should +not need it. (Use ThemePrefsRows instead.) + +----------------------- +Section 3: Localization +----------------------- + +ThemePrefs has two strings that can be localized, to be placed under +[ThemePrefs] in the appropriate language INI. + +IniFileMissing - displayed when IniFile.lua is not found, which means + ThemePrefs cannot be loaded from or written to disk + +UnknownPreference - displayed when an attempt is made to read or write + a preference that does not exist in the system. Uses %s for the + name of the unknown preference. + +--------------------- +Section 4: Disclaimer +--------------------- + +Though this is working fine so far, it is still experimental! If you run +into problems, or have suggestions, comments, questions, etc., please talk +to "vyhd" in #sm-ssc on irc.badnik.net, as it's his code to maintain; he +hates subjecting other people to his code (and occasionally vice versa), +but will gladly address concerns that come up. diff --git a/Docs/Themerdocs/ThemePrefsRows.txt b/Docs/Themerdocs/ThemePrefsRows.txt new file mode 100644 index 0000000000..42a9d6bf14 --- /dev/null +++ b/Docs/Themerdocs/ThemePrefsRows.txt @@ -0,0 +1,145 @@ +ThemePrefsRows, v0.5 +-------------------- + +ThemePrefsRows is an optional system that works with ThemePrefs to generate +Lua-based OptionsRows for every preference used in the system. All you do is +declare a table of preferences and pass it to ThemePrefs.InitAll(), which will +also initialize ThemePrefs. Note that you do need to add an entry for each +preference in [OptionTitles] and [OptionExplanations] to give the row its name +and describe what it does; I hope to find a (clean) way to do it from Lua. + +---------------------- +Section 1: Declaration +---------------------- + +Declaring preferences is the same basic concept as ThemePrefs; see that doc +for the basics. So I'll go into the stuff that's important to this API. + +As mentioned in ThemePrefs, the table contains options. The possible options +and their types are outlaid here. (The pref's type is defined by Default.) + +Default: whatever the value is set to by default. For sanity's sake, you + should have this in your Choices table. + +Choices (table): an indexed array containing all the possible OptionsRow + choices. If you're using a Values table, the array values should be + strings. If not, you can use any type that can cast to a string. + +Values (table): indexed array of the pref's type containing the value used when + the Choices entry with the same index is chosen in the row. If that + confused you, don't worry: it'll make more sense in the example. + This is optional, if your pref type can cast to strings. + +Params (table): optional modifications to the OptionsRow. I'll cover this in + more detail in a later section. You probably won't need it. + +To make this clearer, have an example of a valid prefs table: + +[code] +local prefs = +{ + BoolPref = + { + Default = false, + Choices = { "On", "Off" }, + Values = { true, false }, + }, + + IntPref = + { + Default = 3, + Choices = { 1, 2, 3, 4, 5 }, + Params = { SelectType = "ShowOneInRow" } + } +} + +ThemePrefs.InitAll( prefs ) +[/code] + +So when "On" is selected, BoolPref will be set to true, and selecting "Off" +will set it false. Simple enough, yeah? This will scale to any type you like. +Enumerations, strings, whatever. + +----------------- +Section 2: Params +----------------- + +We did promise a more in-depth look at Params, so here it is. + +The following arguments and values are currently accepted for Params: + +LayoutType (string): "ShowAllInRow" (default), "ShowOneInRow". + +ExportOnChange (bool): currently disabled in the source, but available to + set in case it's enabled later. If true, the Save function (which + handles actually setting the preference) is called whenever a value + is changed rather than when the screen changes. + +EnabledForPlayers (function): has 'self' as an argument, returns a table of + PlayerNumbers who are allowed to select stuff. Not important yet. + +ReloadRowMessages (table): contains an indexed array of strings. Whenever + any of the messages in this table are broadcast, the Load function + (which loads the list of possible options and sets the selected + value/s) is called again. + +LoadSelections (function): takes self, list, pn, overrides the default + function. This is advanced usage, so you should probably know + what you're doing to use it. Probably unnecessary. + +SaveSelections (function): takes self, list, pn, and overrides the default + function. This is advanced as well, for the same reasons. Fortunately, + you probably won't need it. + +---------------- +Section 3: Usage +---------------- + +To get the OptionsRow for the preference you want, use ThemePrefRow(name) +or ThemePrefsRows.GetRow(name); they're the same function. You'd use this +as, for example in the metrics: + +Line1="lua,ThemePrefRow('BoolPref')" + +Unfortunately, you do need to do some more work with the Language INIs too. +For each declared preference, you need an entry under [OptionTitles] for its +title and an entry under [OptionExplanations] for its description. The key for +both is the name of the preference. With the above example, that'd be e.g. + +[OptionTitles] +(...) +BoolPref=BoolPref +IntPref=IntPref + +[OptionExplanations] +(...) +BoolPref=Toggles a simple boolean preference between true and false. +IntPref=Sets an integer value between 1 and 5. + +----------------------- +Section 4: Localization +----------------------- + +ThemePrefsRows has three themable strings: + +NoDefaultInValues - if the default value isn't actually in choices or values, + this is displayed. Takes %s for the affected preference's name. + +TypeMismatch - if the default type and a value type mismatch, this is shown. + Takes %s, %d, and %s for the default's type, the index of the + mismatching value, and the value's type respectively. + +ChoicesSizeMismatch - if the Choices and Values arrays have different + lengths, this is displayed. Takes %d and %d for the size of + Choices and Values, respectively. + +--------------------- +Section 5: Disclaimer +--------------------- + +This isn't as tested as I'd like. For this version, it remains experimental. +Quite experimental. If you run into problems, please let me know so I can +fix them. + +Please direct all questions, comments, complaints, bug reports, etc. to "vyhd" +in #sm-ssc on irc.badnik.net or whatever other form of communication you like. diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index f77b4611b7..33b28c115a 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -1947,6 +1947,7 @@ W5=Boo Held=Held MaxCombo=Max Combo Miss=Miss + [NativeLanguageNames] Afar=Afar Abkhazian=аҧсуа бызшәа @@ -2274,4 +2275,13 @@ Karaoke=Karaoke Lights_Cabinet=Lights [ScreenHowToInstallSongs] -HeaderText=Installing Songs \ No newline at end of file +HeaderText=Installing Songs + +[ThemePrefs] +IniFileMissing=IniFile missing. Can't read or write from disk :( +UnknownPreference=Unknown preference "%s" + +[ThemePrefsRows] +ChoicesSizeMismatch=Choices and Values have different sizes (%d, %d) +NoDefaultInValues=Preference "%s" has no values matching default +TypeMismatch=Type mismatch between default (%s) and value %d (%s) diff --git a/Themes/_fallback/Scripts/03 ThemePrefs.lua b/Themes/_fallback/Scripts/03 ThemePrefs.lua index 0bf146e460..0a61da5741 100644 --- a/Themes/_fallback/Scripts/03 ThemePrefs.lua +++ b/Themes/_fallback/Scripts/03 ThemePrefs.lua @@ -3,12 +3,16 @@ ThemePrefs: handles the underlying structure for ThemePrefs, so any themes built off of this can simply declare their prefs and default values, and access them through this system. -v0.7: Dec. 15, 2010. Initial version. +v0.7.1: Dec. 28, 2010. Added language support. +v0.7.0: Dec. 15, 2010. Initial version. + +vyhd wrote this for sm-ssc. <3 you guys --]] --- If we don't have IniFile, we can't read to or write from disk -if not IniFile then - Warn( "ThemePrefs: IniFile missing. Can't read/write from disk :(" ) +-- local function to handle themed error strings +-- (and to ensure we're getting all of them from the same section) +local function GetString( name ) + return THEME:GetString( "ThemePrefs", name ) end function PrintTable( tbl ) @@ -74,6 +78,9 @@ ThemePrefs = -- Only read from disk once, when _fallback calls this; we just -- need the base set once to add prefs onto. Init = function( prefs, bLoadFromDisk ) + -- If we don't have IniFile, we can't read/write from/to disk + if not IniFile then Warn( GetString("IniFileMissing") ) end + Trace( ("ThemePrefs.Init(prefs, %s)"):format(tostring(bLoadFromDisk)) ) if bLoadFromDisk then Trace( "ThemePrefs.Init: loading from disk" ) @@ -83,20 +90,20 @@ ThemePrefs = Trace( "ThemePrefs.Init: not loading from disk" ) -- create the section if it doesn't exist - local name = GetThemeName() - PrefsTable[name] = PrefsTable[name] and PrefsTable[name] or { } + local section = GetThemeName() + PrefsTable[section] = PrefsTable[section] and PrefsTable[section] or { } - Trace( "Using section " .. name ) + Trace( "Using section " .. section ) -- if the key doesn't exist, add it with our default value for k, tbl in pairs(prefs) do - if not PrefsTable[name][k] then + if not PrefsTable[section][k] then Trace( k .. " doesn't exist, creating" ) - PrefsTable[name][k] = tbl.Default + PrefsTable[section][k] = tbl.Default end end - PrintTable( PrefsTable[name] ) + PrintTable( PrefsTable[section] ) end, Load = function() @@ -117,7 +124,7 @@ ThemePrefs = Trace( ("ThemePrefs.Get(%s)"):format(name) ) local tbl = ResolveTable(name) if tbl then return tbl[name] end - Warn( ("GetThemePref: unknown preference \"%s\""):format(name) ) + Warn( "Get: "..GetString("UnknownPreference"):format(name) ) return nil end, @@ -125,7 +132,7 @@ ThemePrefs = Trace( ("ThemePrefs.Set(%s, %s)"):format(name, tostring(value)) ) local tbl = ResolveTable(name) if tbl then tbl[name] = value; NeedsSaved = true; return end - Warn( ("SetThemePref: unknown preference \"%s\""):format(name) ) + Warn( "Set: "..GetString("UnknownPreference"):format(name) ) end, }; diff --git a/Themes/_fallback/Scripts/03 ThemePrefsRows.lua b/Themes/_fallback/Scripts/03 ThemePrefsRows.lua index 5fb7f9044e..26d102d1c7 100644 --- a/Themes/_fallback/Scripts/03 ThemePrefsRows.lua +++ b/Themes/_fallback/Scripts/03 ThemePrefsRows.lua @@ -3,8 +3,11 @@ ThemePrefsRows: you give it the choices, values, and params, and it'll generate the rest; quirky behavior to be outlined below. Documentation will be provided once this system is stabilized. -v0.6: Dec. 27, 2010. Fix Choices not necessarily being strings. -v0.5: Dec. 15, 2010. Initial version. Not very well tested. +v0.5.2: Dec. 28, 2010. Throw an error for default/value type mismatches. +v0.5.1: Dec. 27, 2010. Fix Choices not necessarily being strings. +v0.5.0: Dec. 15, 2010. Initial version. Not very well tested. + +vyhd wrote this for sm-ssc --]] -- unless overridden, these parameters will be used for the OptionsRow @@ -23,7 +26,12 @@ local DefaultParams = SaveSelections = nil, } -local function DefaultLoadSelections( pref, default, choices, values ) +-- local alias to simplify error reporting +local GetString( name ) + return THEME:GetString( "ThemePrefsRows", name ) +end + +local function DefaultLoad( pref, default, choices, values ) return function(self, list, pn) local val = ThemePrefs.Get( pref ) @@ -37,13 +45,13 @@ local function DefaultLoadSelections( pref, default, choices, values ) if values[i] == default then list[i] = true return end end - -- set to the first value and throw a warning - Warn( ("LoadSelections: preference \"%s\"'s default not in Values"):format(pref) ) + -- set to the first value and output a warning + Warn( GetString("NoDefaultInValues"):format(pref) ) list[1] = true end end -local function DefaultSaveSelections( pref, choices, values ) +local function DefaultSave( pref, choices, values ) local msg = "ThemePrefChanged" local params = { Name = pref } @@ -55,14 +63,31 @@ local function DefaultSaveSelections( pref, choices, values ) end end +-- This function checks for mismatches between the default value and the +-- values table passed to the ThemePrefRow, e.g. it will return false if +-- you have a boolean default and an integer value. I'm somewhat stricter +-- about types than Lua is because I don't like the unpredictability and +-- complexity of coercing values transparently in a bunch of places. +local function TypesMatch( Values, Default ) + local DefaultType = type(Default) + + for i, value in ipairs(Values) do + local ValueType = type(value) + if ValueType ~= DefaultType then + Warn( GetString("TypeMismatch"):format(DefaultType, i, ValueType) ) + return false + end + end + + return true +end local function CreateThemePrefRow( pref, tbl ) - Trace( "CreateThemePrefRow( " .. pref .. " )" ) - -- can't make an option handler without options if not tbl.Choices then return nil end local Choices = tbl.Choices + local Default = tbl.Default local Values = tbl.Values and tbl.Values or Choices local Params = tbl.Params and tbl.Params or { } @@ -71,6 +96,15 @@ local function CreateThemePrefRow( pref, tbl ) Choices[i] = tostring( Chances[i] ) end + -- check to see that Values and Choices have the same length + if #Choices ~= #Values then + Warn( GetString("ChoicesSizeMismatch") ) + return nil + end + + -- check to see that everything in Values matches the type of Default + if not TypesMatch( Values, Default ) then return nil end + -- set the name and choices here; we'll do the rest below local Handler = { Name = pref, Choices = Choices } @@ -83,11 +117,11 @@ local function CreateThemePrefRow( pref, tbl ) -- if we don't have LoadSelections and SaveSelections, make them if not Handler.LoadSelections then - Handler.LoadSelections = DefaultLoadSelections( pref, tbl.Default, Choices, Values ) + Handler.LoadSelections = DefaultLoad( pref, Default, Choices, Values ) end if not Handler.SaveSelections then - Handler.SaveSelections = DefaultSaveSelections( pref, Choices, Values ) + Handler.SaveSelections = DefaultSave( pref, Choices, Values ) end return Handler @@ -125,7 +159,6 @@ ThemePrefRow = ThemePrefsRows.GetRow ThemePrefs.InitAll = function( prefs ) Trace( "ThemePrefs.InitAll( prefs )" ) - -- HACK: for now, don't worry about extraneous file I/O ThemePrefs.Init( prefs, true ) ThemePrefsRows.Init( prefs ) end