Icon representing a recipe

Recipe: Options Dialog v1.0 -- Brow42

created by brow42

Profile


Name
Options Dialog v1.0 -- Brow42
ID
37119
Shared with
Public
Parent
None
Children
None
Created on
March 06, 2012 at 15:25 PM UTC
Updated on
March 06, 2012 at 15:25 PM UTC
Description

A library of extra dialog functions to automate creating, filling defaults, and reading back values, using tables.

Best for


Code


--[[ =================================== * Option Dialogs * Original Author: Brow42 * Version 1.0 Jan. 6 2012 * This allows you to define a dialog with a table instead of * explicit function calls, set its values with another table, * and read its values into yet a third table. * A self-contained function for text-only message dialogs is * included. This is good for help and error messages. * New functions: * dialog.AddFromTable(dialog,field table, data table) * dialog.ReadFields(dialog,field table, data table) * dialog.MessageBox(text or text table,title text,okay button text, cancel button text) --]] -- =================================== Begin Defaults option={} option.fuze = true option.bandstr = 3.0 option.range = {} -- only segments in this range will be banded { low, high } -- ================================== End Defaults -- ================================== Begin New Dialog Library Functions --[[ Throws up a dialog containing just text, provided as a string or table of strings in the first argument. Title and buttons are optional. Return value is 1 if the first button is clicked and 0 if the second button is clicked or the window is closed. --]] function dialog.MessageBox(msg,title,buttontext1, buttontext0) title = title or '' local d = dialog.CreateDialog(title) if type(msg) == 'string' then d['1'] = dialog.AddLabel(msg) else for i = 1,#msg do d[tostring(i)] = dialog.AddLabel(msg[i]) end end buttontext1 = buttontext1 or 'Ok' d.button = dialog.AddButton(buttontext1,1) if buttontext0 then d.button0 = dialog.AddButton(buttontext0,0) end return dialog.Show(d) end --[[ Add items to the dialog from a table in the order given. The each table entry is the dialog control function, the control label, the data key name, and the control options. If this function fails, it gives an error saying which table item it failed on. d = a created dialog, fields = table of dialog controls as described, object = data table --]] function dialog.AddFromTable(d,fields,object) local func, label, id, value -- renamed field parameters for clarity -- function wrappers to catch errors, explicitly named for clarity function _AddLabel(args) d[id] = dialog.AddLabel(label) end function _AddCheckbox(args) d[id] = dialog.AddCheckbox(label,value) end function _AddSlider(args) d[id] = dialog.AddSlider(label,value,args[4],args[5],args[6]) end function _AddTextbox(args) d[id] = dialog.AddTextbox(label,value) end for i = 1, #fields do local rc = true local err rc,err = pcall( function() func,label,id = fields[i][1],fields[i][2],fields[i][3] end) value = object[id] if func == dialog.AddLabel then id='_AL'..tostring(i) rc,err = pcall(_AddLabel,fields[i]) else -- this is a crasher for AddTextbox but not an error for AddLabel! if value == nil then error('Missing data field for field #'..tostring(i).. '\n(Did you forget to pass a string?)') end if func == dialog.AddCheckbox then rc,err = pcall(_AddCheckbox,fields[i]) elseif func == dialog.AddSlider then rc,err = pcall(_AddSlider,fields[i]) elseif func == dialog.AddTextbox then rc,err = pcall(_AddTextbox,fields[i]) else error('Unknown control in dialog field #'..tostring(i)) end end -- if AddLabel if rc == false then error('Error setting dialog field #'..tostring(i)..(err and ('\n'..err) or '')) end end end --[[ This just copies the data from the dialog to the data table. --]] function dialog.ReadFields(d,fields,object) for i = 1,#fields do if fields[i][1]~= dialog.AddLabel then object[fields[i][3]] = d[fields[i][3]].value end end end -- ================================== End New Dialog Library Functions -- ================================== Begin Demo -- This function defines an optional help screen function DoInfoDialog() local msg = { ' This allows you to define a dialog with a table instead of', 'explicit function calls, set its values with another table,', 'and read its values into yet a third table.', ' A self-contained function for text-only message', 'dialogs is included.', ' Options:', ' * Fuze -- Do a Fuze Y/N', ' * Band Strength -- 0.1 to 10.0', ' * Range -- two numbers, e.g. 10 20', 'Range segment numbers must be > 0 and increasing,', 'otherwise it is an error.' } dialog.MessageBox(msg,'About Dialog Options Demo','Back') end -- This function defines our dialog and calls the functions to set and read values -- First arg is the current option state and optional second arg is a remembered -- default. function DoOptionsDialog(options,defaults) -- Short names for the dialog controls -- this is not an invitation to override, they are not used as functions local label = dialog.AddLabel -- no data member for this local box = dialog.AddCheckbox local slider = dialog.AddSlider -- 4 control options for this local text = dialog.AddTextbox -- Lay out the dialog here -- { { dialog control, textlabel, data member name, control options }, ... } -- all data member names must be unique local fields ={ {label,'Pick one or more options:'}, {box, 'Do a Fuze', 'fuze'}, {label,''}, {slider,'Band Strength:','bandstr',0.1,10.0,1}, {text,'Range (inclusive):','range'} } local d = dialog.CreateDialog('Dialog Options Demo') -- Make sure your text fields are text! if #options.range == 2 then options.range = tostring(options.range[1])..' '..tostring(options.range[2]) else options.range='' end -- Generate the dialog in order of the table -- You can still do this by hand for a custom layout if you wish dialog.AddFromTable(d,fields,options) -- last arg = table to read from -- You can add more items from a different table here as long -- as there is no name conflict in the two tables -- Add the button now d.ok = dialog.AddButton('Start!',1) -- 1 = okay d.cancel = dialog.AddButton('Cancel',0) -- 0 = quit d.help = dialog.AddButton('About',-1) -- < 0 = show options again, optional button if defaults then d.reset = dialog.AddButton('Reset',-2) end -- if defaults provided, allow reset local button = dialog.Show(d) -- Check for reset first...if you have multiple option tables you have to reset them all if button == -2 then for i,v in pairs(defaults) do options[i]=v end return -1 end -- < 0 = redraw -- Check for cancel to skip the error checking if button == 0 then return 0 end -- 0 = cancel -- Otherwise read in the values to update with the user's entry in case we have to redraw -- Read the fields into (a possibly different or empty table) table dialog.ReadFields(d,fields,options) -- last arg = table to save to -- Read in your other fields/tables here -- Convert your text fields -- For this text field, we want to pull out the numbers into a table range={} for num in string.gfind(options.range,'%d+') do table.insert(range,tonumber(num)) end options.range = range -- Check user input here if #options.range > 0 and (#options.range ~= 2 or options.range[1] < 1 or options.range[1] > options.range[2]) then -- error dialog and signal an error (which will cause options dialog to be redisplayed) dialog.MessageBox({'Range must be two numbers > 1 in ascending order','separated by non-numeric characters'},'Input Error','Oops!') return -1 end if button == -1 then DoInfoDialog() end return button end -- Recursive table printer function PrintTable(tab,indent) indent = indent or '' for i,v in pairs(tab) do if type(v) == 'table' then do print(indent,i) PrintTable(v,indent..' ') end else print(indent,i,v) end end end defaults={} for i,v in pairs(option) do defaults[i]=v end -- save the initial options as defaults, deep copy repeat -- Loop if something other than okay or cancel local rc = DoOptionsDialog(option,defaults) if rc == 0 then print('Canceled') return end -- cancel until rc == 1 -- okay print('Printing Option Table') PrintTable(option) dialog.MessageBox('Options have been written to program output','Script Completed')

Comments


brow42 Lv 1

Often in a GUI you have a data object sitting behind the graphical object. This lets you set up a table of controls and field names, and a function will add all the controls AND set their values to the current values in the data object. When the user hits a button (other than cancel or reset) another function reads back all the values.

To use this library, you define a table where each entry contains all that you would normally do to make a control, along with the data key name. Then you decide which buttons you want. After you get the data back, you should do some error checking on the inputs. If there's an error, you can just redisplay the dialog with the current values so the user can correct them. If you have text fields, you may need to convert those strings to and from what you want them to mean.

The dialog code works nicely if used correctly, but is a little intolerant of passing nil values (client crashes). I've tried to catch some errors so that you'll get a standard error message. I'm an inexperienced Lua programmer so I can't guarantee the robustness.

The script above includes a demo program with one of each control, and Okay, Cancel, Help, and Reset buttons. The text field expects 2 numbers and makes an error dialog if it doesn't pass the user input check. You'll want to strip out the demo program or just rewrite it for your purposes. I think the demo contains most of what a basic options dialog would need.

Please send me bug reports so I can fix them (or trap the crashers)!

brow42 Lv 1

--[[ 
  ===================================
  * Option Dialogs
  * Original Author: Brow42
  * Version 1.0 Jan. 6 2012

  * This allows you to define a dialog with a table instead of
  * explicit function calls, set its values with another table,
  * and read its values into yet a third table.
  * A self-contained function for text-only message dialogs is
  * included. This is good for help and error messages.
  * New functions:
  * dialog.AddFromTable(dialog,field table, data table)
  * dialog.ReadFields(dialog,field table, data table)
  * dialog.MessageBox(text or text table,title text,okay button text, cancel button text)
--]]
-- ================================== Begin New Dialog Library Functions
--[[
  Throws up a dialog containing just text, provided as a string or table
  of strings in the first argument. Title and buttons are optional. Return
  value is 1 if the first button is clicked and 0 if the second button is clicked
  or the window is closed.
--]]
function dialog.MessageBox(msg,title,buttontext1, buttontext0)
    title = title or ''
    local d = dialog.CreateDialog(title)
    if type(msg) == 'string' then d['1'] = dialog.AddLabel(msg)
    else for i = 1,#msg do
            d[tostring(i)] = dialog.AddLabel(msg[i])
        end
    end
    buttontext1 = buttontext1 or 'Ok'
    d.button = dialog.AddButton(buttontext1,1)
    if buttontext0 then d.button0 = dialog.AddButton(buttontest0,0) end
    return dialog.Show(d)
end

--[[
  Add items to the dialog from a table in the order given. The each table entry
  is the dialog control function, the control label, the data key name, and the control options.
  If this function fails, it gives an error saying which table item it failed on.
  d = a created dialog, fields = table of dialog controls as described, object = data table
--]]
function dialog.AddFromTable(d,fields,object)
    local  func, label, id, value -- renamed field parameters for clarity
    -- function wrappers to catch errors, explicitly named for clarity
    function _AddLabel(args)  d[id] = dialog.AddLabel(label) end
    function _AddCheckbox(args) d[id] = dialog.AddCheckbox(label,value) end
    function _AddSlider(args) d[id] = dialog.AddSlider(label,value,args[4],args[5],args[6]) end
    function _AddTextbox(args) d[id] = dialog.AddTextbox(label,value) end

    for i = 1, #fields do
        local rc = true
        local err
        rc,err = pcall( function() func,label,id = fields[i][1],fields[i][2],fields[i][3] end)
        value = object[id]
        if func == dialog.AddLabel then id='_AL'..tostring(i) rc,err = pcall(_AddLabel,fields[i])
        else
            -- this is a crasher for AddTextbox but not an error for AddLabel!
            if value == nil then error('Missing data field for field #'..tostring(i)..'\n(Did you forget to pass a string?)') end 
            if func == dialog.AddCheckbox then rc,err = pcall(_AddCheckbox,fields[i])
            elseif func == dialog.AddSlider then rc,err = pcall(_AddSlider,fields[i])
            elseif func == dialog.AddTextbox then rc,err = pcall(_AddTextbox,fields[i])
            else error('Unknown control in dialog field #'..tostring(i)) end
        end -- if AddLabel
        if rc == false then
            error('Error setting dialog field #'..tostring(i)..(err and ('\n'..err) or ''))
        end
    end
end

--[[
  This just copies the data from the dialog to the data table.
--]]
function dialog.ReadFields(d,fields,object)
    for i = 1,#fields do
        if fields[i][1]~= dialog.AddLabel then
            object[fields[i][3]] = d[fields[i][3]].value
        end
    end
end
-- ================================== End New Dialog Library Functions

brow42 Lv 1

Please note the typo in MessageBox activated if you use 2 buttons:
if buttontext0 then d.button0 = dialog.AddButton(buttontest0,0) end

buttontest should be buttontext