[HDRmerge] fixed crash resulting users renaming images from

camera default.

           Minor code cleanup
This commit is contained in:
wpferguson
2021-03-19 23:52:28 -04:00
parent 7810791991
commit beafb6c21b

View File

@@ -55,45 +55,45 @@ local CURR_API_STRING = dt.configuration.api_version_string
-- Tell gettext where to find the .mo file translating messages for a particular domain -- Tell gettext where to find the .mo file translating messages for a particular domain
local gettext = dt.gettext local gettext = dt.gettext
gettext.bindtextdomain('HDRMerge',dt.configuration.config_dir..'/lua/locale/') gettext.bindtextdomain('HDRMerge', dt.configuration.config_dir..'/lua/locale/')
local function _(msgid) local function _(msgid)
return gettext.dgettext('HDRMerge', msgid) return gettext.dgettext('HDRMerge', msgid)
end end
local temp local temp
local HDRM = { --HDRMerge Program Table local HDRM = { --HDRMerge Program Table
name = 'HDRMerge', name = 'HDRMerge',
bin = '', bin = '',
first_run = true, first_run = true,
install_error = false, install_error = false,
arg_string = '', arg_string = '',
images_string = '', images_string = '',
args = { args = {
bps ={text = '-b ', style = 'integer'}, bps = {text = '-b ', style = 'integer'},
size ={text = '-p ', style = 'string'}, size = {text = '-p ', style = 'string'},
batch ={text = '-B ', style = 'bool'}, batch = {text = '-B ', style = 'bool'},
gap ={text = '-g ', style = 'integer'} gap = {text = '-g ', style = 'integer'}
} }
} }
local GUI = { --GUI Elements Table local GUI = { --GUI Elements Table
HDR = { HDR = {
bps ={}, bps ={},
size ={}, size ={},
batch ={}, batch ={},
gap ={} gap ={}
}, },
Target = { Target = {
style ={}, style ={},
copy_tags ={}, copy_tags ={},
add_tags ={} add_tags ={}
}, },
run = {}, run = {},
stack = {}, stack = {},
options = {}, options = {},
exes = { exes = {
HDRMerge = {}, HDRMerge = {},
update = {}, update = {},
} }
} }
HDRM.module_installed = false HDRM.module_installed = false
@@ -104,184 +104,184 @@ HDRM.event_registered = false
local styles = dt.styles local styles = dt.styles
local styles_count = 1 -- 'none' = 1 local styles_count = 1 -- 'none' = 1
for _,i in pairs(dt.styles) do for _,i in pairs(dt.styles) do
if type(i) == 'userdata' then styles_count = styles_count + 1 end if type(i) == 'userdata' then styles_count = styles_count + 1 end
end end
local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive) local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive)
if test >= low and test <= high then if test >= low and test <= high then
return true return true
else else
return false return false
end end
end end
local function GetFileName(full_path) --Parses a full path (path/filename_identifier.extension) into individual parts local function GetFileName(full_path) --Parses a full path (path/filename_identifier.extension) into individual parts
--[[Input: Folder1/Folder2/Folder3/Img_0001.CR2 --[[Input: Folder1/Folder2/Folder3/Img_0001.CR2
Returns: Returns:
path: Folder1/Folder2/Folder3/ path: Folder1/Folder2/Folder3/
filename: Img_0001 filename: Img_0001
identifier: 0001 identifier: 0001
extension: .CR2 extension: .CR2
EX: EX:
path_1, file_1, id_1, ext_1 = GetFileName(full_path_1) path_1, file_1, id_1, ext_1 = GetFileName(full_path_1)
]] ]]
local path = string.match(full_path, '.*[\\/]') local path = string.match(full_path, '.*[\\/]')
local filename = string.gsub(string.match(full_path, '[%w-_]*%.') , '%.' , '' ) local filename = string.gsub(string.match(full_path, '[%w-_]*%.') , '%.' , '' )
local identifier = string.match(filename, '%d*$') local identifier = string.match(filename, '%d*$')
local extension = string.match(full_path, '%.%w*') local extension = string.match(full_path, '%.%w*')
return path, filename, identifier, extension return path, filename, identifier, extension
end end
local function CleanSpaces(text) --removes spaces from the front and back of passed in text local function CleanSpaces(text) --removes spaces from the front and back of passed in text
text = string.gsub(text,'^%s*','') text = string.gsub(text,'^%s*','')
text = string.gsub(text,'%s*$','') text = string.gsub(text,'%s*$','')
return text return text
end end
local function BuildExecuteCmd(prog_table) --creates a program command using elements of the passed in program table local function BuildExecuteCmd(prog_table) --creates a program command using elements of the passed in program table
local result = CleanSpaces(prog_table.bin)..' '..CleanSpaces(prog_table.arg_string)..' '..CleanSpaces(prog_table.images_string) local result = CleanSpaces(prog_table.bin)..' '..CleanSpaces(prog_table.arg_string)..' '..CleanSpaces(prog_table.images_string)
return result return result
end end
local function PreCall(prog_tbl) --looks to see if this is the first call, if so checks to see if program is installed properly local function PreCall(prog_tbl) --looks to see if this is the first call, if so checks to see if program is installed properly
for _,prog in pairs(prog_tbl) do for _,prog in pairs(prog_tbl) do
if prog.first_run then if prog.first_run then
prog.bin = df.check_if_bin_exists(prog.name) prog.bin = df.check_if_bin_exists(prog.name)
if not prog.bin then if not prog.bin then
prog.install_error = true prog.install_error = true
dt.preferences.write(mod, 'bin_exists', 'bool', false) dt.preferences.write(mod, 'bin_exists', 'bool', false)
else else
prog.bin = CleanSpaces(prog.bin) prog.bin = CleanSpaces(prog.bin)
end end
prog.first_run = false prog.first_run = false
end
end
if not dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 2
dt.print(_('please update you binary locatoin'))
end end
end
if not dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 2
dt.print(_('please update you binary location'))
end
end end
local function ExeUpdate(prog_tbl) local function ExeUpdate(prog_tbl)
dt.preferences.write(mod, 'bin_exists', 'bool', true) dt.preferences.write(mod, 'bin_exists', 'bool', true)
for _,prog in pairs(prog_tbl) do for _,prog in pairs(prog_tbl) do
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value) dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
prog.bin = df.check_if_bin_exists(prog.name) prog.bin = df.check_if_bin_exists(prog.name)
if not prog.bin then if not prog.bin then
prog.install_error = true prog.install_error = true
dt.preferences.write(mod, 'bin_exists', 'bool', false) dt.preferences.write(mod, 'bin_exists', 'bool', false)
else
prog.bin = CleanSpaces(prog.bin)
end
prog.first_run = false
end
if dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 1
dt.print(_('update successful'))
else else
dt.print(_('update unsuccessful, please try again')) prog.bin = CleanSpaces(prog.bin)
end end
prog.first_run = false
end
if dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 1
dt.print(_('update successful'))
else
dt.print(_('update unsuccessful, please try again'))
end
end end
local function UpdateActivePreference() --sliders & entry boxes do not have a click/changed callback, so their values must be saved to the active preference local function UpdateActivePreference() --sliders & entry boxes do not have a click/changed callback, so their values must be saved to the active preference
temp = GUI.HDR.gap.value temp = GUI.HDR.gap.value
dt.preferences.write(mod, 'active_gap', 'integer', temp) dt.preferences.write(mod, 'active_gap', 'integer', temp)
temp = GUI.Target.add_tags.text temp = GUI.Target.add_tags.text
dt.preferences.write(mod, 'active_add_tags', 'string', temp) dt.preferences.write(mod, 'active_add_tags', 'string', temp)
end end
local function main() local function main()
PreCall({HDRM}) --check if furst run then check if install OK PreCall({HDRM}) --check if furst run then check if install OK
if HDRM.install_error then if HDRM.install_error then
dt.print_error(_('HDRMerge install issue')) dt.print_error(_('HDRMerge install issue'))
dt.print(_('HDRMerge install issue, please ensure the binary path is proper')) dt.print(_('HDRMerge install issue, please ensure the binary path is proper'))
return return
end
images = dt.gui.selection() --get selected images
if #images < 2 then --ensure enough images selected
dt.print(_('not enough images selected, select at least 2 images to merge'))
return
end
UpdateActivePreference() --save current gui elements to active preference so those values will be pre-loaded at next startup
--create image string and output path
HDRM.images_string = ''
local out_path = ''
local smallest_id = math.huge
local smallest_name = ''
local largest_id = 0
local source_raw = {}
for _,image in pairs(images) do --loop to concat the images string, also track the image indexes for use in creating the final image name (eg; IMG_1034-1037.dng)
local curr_image = image.path..os_path_seperator..image.filename
HDRM.images_string = HDRM.images_string..df.sanitize_filename(curr_image)..' '
out_path = image.path
_unused, source_name, source_id = GetFileName(image.filename)
source_id = tonumber(source_id) or 0
if source_id < smallest_id then
smallest_id = source_id
smallest_name = source_name
source_raw = image
end end
images = dt.gui.selection() --get selected images if source_id > largest_id then largest_id = source_id end
if #images < 2 then --ensure enough images selected end
dt.print(_('not enough images selected, select at least 2 images to merge')) out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng'
return out_path = df.create_unique_filename(out_path)
--create argument string
HDRM.arg_string = HDRM.args.bps.text..GUI.HDR.bps.value..' '..HDRM.args.size.text..GUI.HDR.size.value..' '
if GUI.HDR.batch.value then
HDRM.arg_string = HDRM.arg_string..HDRM.args.batch.text..HDRM.args.gap.text..math.floor(GUI.HDR.gap.value)..' -a'
else
HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path)
end
-- create run command and execute
local run_cmd = BuildExecuteCmd(HDRM)
resp = dsys.external_command(run_cmd)
if resp == 0 and not GUI.HDR.batch.value then
local imported = dt.database.import(out_path) -- import the new file
if GUI.Target.style.selected > 1 then -- apply selected style
local set_style = styles[GUI.Target.style.selected - 1]
dt.styles.apply(set_style , imported)
end end
if GUI.Target.copy_tags.value then -- copy tags from the original file (ignore 'darktable' generated tags)
UpdateActivePreference() --save current gui elements to active preference so those values will be pre-loaded at next startup local all_tags = dt.tags.get_tags(source_raw)
for _,tag in pairs(all_tags) do
--create image string and output path if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end
HDRM.images_string = '' end
local out_path = ''
local smallest_id = math.huge
local smallest_name = ''
local largest_id = 0
local source_raw = {}
for _,image in pairs(images) do --loop to concat the images string, also track the image indexes for use in creating the final image name (eg; IMG_1034-1037.dng)
local curr_image = image.path..os_path_seperator..image.filename
HDRM.images_string = HDRM.images_string..df.sanitize_filename(curr_image)..' '
out_path = image.path
_, source_name, source_id = GetFileName(image.filename)
source_id = tonumber(source_id)
if source_id < smallest_id then
smallest_id = source_id
smallest_name = source_name
source_raw = image
end
if source_id > largest_id then largest_id = source_id end
end end
out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng' local set_tag = GUI.Target.add_tags.text
out_path = df.create_unique_filename(out_path) if set_tag ~= nil then -- add additional user-specified tags
for tag in string.gmatch(set_tag, '[^,]+') do
--create argument string tag = CleanSpaces(tag)
HDRM.arg_string = HDRM.args.bps.text..GUI.HDR.bps.value..' '..HDRM.args.size.text..GUI.HDR.size.value..' ' tag = dt.tags.create(tag)
if GUI.HDR.batch.value then dt.tags.attach(tag, imported)
HDRM.arg_string = HDRM.arg_string..HDRM.args.batch.text..HDRM.args.gap.text..math.floor(GUI.HDR.gap.value)..' -a' end
else
HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path)
end
-- create run command and execute
local run_cmd = BuildExecuteCmd(HDRM)
resp = dsys.external_command(run_cmd)
if resp == 0 and not GUI.HDR.batch.value then
local imported = dt.database.import(out_path) -- import the new file
if GUI.Target.style.selected > 1 then -- apply selected style
local set_style = styles[GUI.Target.style.selected - 1]
dt.styles.apply(set_style , imported)
end
if GUI.Target.copy_tags.value then -- copy tags from the original file (ignore 'darktable' generated tags)
local all_tags = dt.tags.get_tags(source_raw)
for _,tag in pairs(all_tags) do
if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end
end
end
local set_tag = GUI.Target.add_tags.text
if set_tag ~= nil then -- add additional user-specified tags
for tag in string.gmatch(set_tag, '[^,]+') do
tag = CleanSpaces(tag)
tag = dt.tags.create(tag)
dt.tags.attach(tag, imported)
end
end
dt.print(_('HDRMerge completed successfully'))
else
dt.print_error(_('HDRMerge failed'))
dt.print(_('HDRMerge failed'))
end end
dt.print(_('HDRMerge completed successfully'))
else
dt.print_error(_('HDRMerge failed'))
dt.print(_('HDRMerge failed'))
end
end end
local function install_module() local function install_module()
if not HDRM.module_installed then if not HDRM.module_installed then
dt.register_lib( -- register HDRMerge module dt.register_lib( -- register HDRMerge module
'HDRMerge_Lib', -- Module name 'HDRMerge_Lib', -- Module name
_('HDRMerge'), -- name _('HDRMerge'), -- name
true, -- expandable true, -- expandable
true, -- resetable true, -- resetable
{[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers {[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers
dt.new_widget('box'){ dt.new_widget('box'){
orientation = 'vertical', orientation = 'vertical',
GUI.stack GUI.stack
} }
) )
HDRM.module_installed = true HDRM.module_installed = true
end end
@@ -289,149 +289,149 @@ end
-- GUI Elements -- -- GUI Elements --
local lbl_hdr = dt.new_widget('section_label'){ local lbl_hdr = dt.new_widget('section_label'){
label = _('HDRMerge options') label = _('HDRMerge options')
} }
temp = dt.preferences.read(mod, 'active_bps_ind', 'integer') temp = dt.preferences.read(mod, 'active_bps_ind', 'integer')
if not InRange(temp, 1, 3) then temp = 3 end if not InRange(temp, 1, 3) then temp = 3 end
GUI.HDR.bps = dt.new_widget('combobox'){ GUI.HDR.bps = dt.new_widget('combobox'){
label = _('bits per sample'), label = _('bits per sample'),
tooltip =_('number of bits per sample in the output image'), tooltip =_('number of bits per sample in the output image'),
selected = temp, selected = temp,
'16','24','32', '16','24','32',
changed_callback = function(self) changed_callback = function(self)
dt.preferences.write(mod, 'active_bps', 'integer', self.value) dt.preferences.write(mod, 'active_bps', 'integer', self.value)
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected) dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
end, end,
reset_callback = function(self) reset_callback = function(self)
self.selected = 3 self.selected = 3
dt.preferences.write(mod, 'active_bps', 'integer', self.value) dt.preferences.write(mod, 'active_bps', 'integer', self.value)
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected) dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
end end
} }
temp = dt.preferences.read(mod, 'active_size_ind', 'integer') temp = dt.preferences.read(mod, 'active_size_ind', 'integer')
if not InRange(temp, 1, 3) then temp = 2 end if not InRange(temp, 1, 3) then temp = 2 end
GUI.HDR.size = dt.new_widget('combobox'){ GUI.HDR.size = dt.new_widget('combobox'){
label = _('embedded preview size'), label = _('embedded preview size'),
tooltip =_('size of the embedded preview in output image'), tooltip =_('size of the embedded preview in output image'),
selected = temp, selected = temp,
_('none'),_('half'),_('full'), _('none'),_('half'),_('full'),
changed_callback = function(self) changed_callback = function(self)
dt.preferences.write(mod, 'active_size', 'string', self.value) dt.preferences.write(mod, 'active_size', 'string', self.value)
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected) dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
end, end,
reset_callback = function(self) reset_callback = function(self)
self.selected = 2 self.selected = 2
dt.preferences.write(mod, 'active_size', 'string', self.value) dt.preferences.write(mod, 'active_size', 'string', self.value)
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected) dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
end end
} }
GUI.HDR.batch = dt.new_widget('check_button'){ GUI.HDR.batch = dt.new_widget('check_button'){
label = _('batch mode'), label = _('batch mode'),
value = dt.preferences.read(mod, 'active_batch', 'bool'), value = dt.preferences.read(mod, 'active_batch', 'bool'),
tooltip = _('enable batch mode operation \nNOTE: resultant files will NOT be auto-imported'), tooltip = _('enable batch mode operation \nNOTE: resultant files will NOT be auto-imported'),
clicked_callback = function(self) clicked_callback = function(self)
dt.preferences.write(mod, 'active_batch', 'bool', self.value) dt.preferences.write(mod, 'active_batch', 'bool', self.value)
GUI.HDR.gap.sensitive = self.value GUI.HDR.gap.sensitive = self.value
end, end,
reset_callback = function(self) self.value = false end reset_callback = function(self) self.value = false end
} }
temp = dt.preferences.read(mod, 'active_gap', 'integer') temp = dt.preferences.read(mod, 'active_gap', 'integer')
if not InRange(temp, 1, 3600) then temp = 3 end if not InRange(temp, 1, 3600) then temp = 3 end
GUI.HDR.gap = dt.new_widget('slider'){ GUI.HDR.gap = dt.new_widget('slider'){
label = _('batch gap [sec.]'), label = _('batch gap [sec.]'),
tooltip = _('gap, in seconds, between batch mode groups'), tooltip = _('gap, in seconds, between batch mode groups'),
soft_min = 1, soft_min = 1,
soft_max = 30, soft_max = 30,
hard_min = 1, hard_min = 1,
hard_max = 3600, hard_max = 3600,
step = 1, step = 1,
digits = 0, digits = 0,
value = temp, value = temp,
sensitive = GUI.HDR.batch.value, sensitive = GUI.HDR.batch.value,
reset_callback = function(self) reset_callback = function(self)
self.value = 3 self.value = 3
end end
} }
local lbl_import = dt.new_widget('section_label'){ local lbl_import = dt.new_widget('section_label'){
label = _('import options') label = _('import options')
} }
GUI.Target.style = dt.new_widget('combobox'){ GUI.Target.style = dt.new_widget('combobox'){
label = _('apply style on import'), label = _('apply style on import'),
tooltip = _('Apply selected style on auto-import to newly created image'), tooltip = _('Apply selected style on auto-import to newly created image'),
selected = 1, selected = 1,
_('none'), _('none'),
changed_callback = function(self) changed_callback = function(self)
dt.preferences.write(mod, 'active_style', 'string', self.value) dt.preferences.write(mod, 'active_style', 'string', self.value)
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected) dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
end, end,
reset_callback = function(self) reset_callback = function(self)
self.selected = 1 self.selected = 1
dt.preferences.write(mod, 'active_style', 'string', self.value) dt.preferences.write(mod, 'active_style', 'string', self.value)
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected) dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
end end
} }
for k=1, (styles_count-1) do for k=1, (styles_count-1) do
GUI.Target.style[k+1] = styles[k].name GUI.Target.style[k+1] = styles[k].name
end end
temp = dt.preferences.read(mod, 'active_style_ind', 'integer') temp = dt.preferences.read(mod, 'active_style_ind', 'integer')
if not InRange(temp, 1, styles_count) then temp = 1 end if not InRange(temp, 1, styles_count) then temp = 1 end
GUI.Target.style.selected = temp GUI.Target.style.selected = temp
GUI.Target.copy_tags = dt.new_widget('check_button'){ GUI.Target.copy_tags = dt.new_widget('check_button'){
label = _('copy tags'), label = _('copy tags'),
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'), value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
tooltip = _('copy tags from first source image'), tooltip = _('copy tags from first source image'),
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end, clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
reset_callback = function(self) self.value = true end reset_callback = function(self) self.value = true end
} }
temp = dt.preferences.read(mod, 'active_add_tags', 'string') temp = dt.preferences.read(mod, 'active_add_tags', 'string')
if temp == '' then temp = nil end if temp == '' then temp = nil end
GUI.Target.add_tags = dt.new_widget('entry'){ GUI.Target.add_tags = dt.new_widget('entry'){
tooltip = _('Additional tags to be added on import. Seperate with commas, all spaces will be removed'), tooltip = _('Additional tags to be added on import. Seperate with commas, all spaces will be removed'),
text = temp, text = temp,
placeholder = _('Enter tags, seperated by commas'), placeholder = _('Enter tags, seperated by commas'),
editable = true editable = true
} }
GUI.run = dt.new_widget('button'){ GUI.run = dt.new_widget('button'){
label = _('merge'), label = _('merge'),
tooltip =_('run HDRMerge with the above specified settings'), tooltip =_('run HDRMerge with the above specified settings'),
clicked_callback = function() main() end clicked_callback = function() main() end
} }
GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){ GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){
title = _('Select HDRmerge executable'), title = _('Select HDRmerge executable'),
value = df.get_executable_path_preference(HDRM.name), value = df.get_executable_path_preference(HDRM.name),
is_directory = false is_directory = false
} }
GUI.exes.update = dt.new_widget('button'){ GUI.exes.update = dt.new_widget('button'){
label = _('update'), label = _('update'),
tooltip =_('update the binary path with current value'), tooltip =_('update the binary path with current value'),
clicked_callback = function() ExeUpdate({HDRM}) end clicked_callback = function() ExeUpdate({HDRM}) end
} }
GUI.options = dt.new_widget('box'){ GUI.options = dt.new_widget('box'){
orientation = 'vertical', orientation = 'vertical',
lbl_hdr, lbl_hdr,
GUI.HDR.bps, GUI.HDR.bps,
GUI.HDR.size, GUI.HDR.size,
GUI.HDR.batch, GUI.HDR.batch,
GUI.HDR.gap, GUI.HDR.gap,
lbl_import, lbl_import,
GUI.Target.style, GUI.Target.style,
GUI.Target.copy_tags, GUI.Target.copy_tags,
GUI.Target.add_tags, GUI.Target.add_tags,
GUI.run GUI.run
} }
local exes_box = dt.new_widget('box'){ local exes_box = dt.new_widget('box'){
orientation = 'vertical', orientation = 'vertical',
GUI.exes.HDRMerge, GUI.exes.HDRMerge,
GUI.exes.update GUI.exes.update
} }
GUI.stack = dt.new_widget('stack'){ GUI.stack = dt.new_widget('stack'){
GUI.options, GUI.options,
exes_box exes_box
} }
if dt.preferences.read(mod, 'bin_exists', 'bool') then if dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 1 GUI.stack.active = 1
else else
GUI.stack.active = 2 GUI.stack.active = 2
end end
if dt.gui.current_view().id == "lighttable" then if dt.gui.current_view().id == "lighttable" then