like multimeters can set a data type parameter eg datype config_edit.py uses optype instead of type Also the listwalker is now being generated dynamically so that in the future unavailable drivers can be moved from the list of radiobuttons.
412 lines
14 KiB
Python
Executable File
412 lines
14 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# vim: tabstop=8 softtabstop=2 shiftwidth=2 nocin si et ft=python
|
|
|
|
# View Screen has 3 parts
|
|
# (Instrument Configuration), (Configuration Options), (Option Implementation)
|
|
# Uses MVC implemented as InstConfigData<M>, InstConfigView<V>, ConfigEdit<C>
|
|
#
|
|
# InstConfigData <>--- ConfigParser.SafeConfig
|
|
# |--set_cfparse()
|
|
|
|
# ConfigEdit <>--- InstConfigData, PresentationData
|
|
# |--set_cfdata(), set_presdata()
|
|
# |--set_xyz_data() call self.cfgdata.set_xyz() methods
|
|
#
|
|
# urwid.Frame
|
|
# ^
|
|
# InstConfigView <>--- ConfigEdit, PresentationData
|
|
# |--set_cfedit(), set_presdata()
|
|
#
|
|
# PresentationData
|
|
# |--set_cfdata()
|
|
|
|
|
|
import os
|
|
import argparse
|
|
import ConfigParser
|
|
import urwid
|
|
import copy
|
|
from collections import defaultdict
|
|
|
|
|
|
class InstConfigData:
|
|
msg_index = 4
|
|
# configuration_dict: dict of instrument configurations as defined below,
|
|
# {configname: {'enabled':T/F, 'cascade_list':[(option, dflt_imp)]} }
|
|
configuration_dict = defaultdict(dict)
|
|
|
|
# opt_dict: dict of configuration options as defined below,
|
|
# {optname:{'enabled': T/F/Always, 'imptype':optype, 'selected_imp':dflt}}
|
|
opt_dict = defaultdict(dict)
|
|
|
|
# imp_dict: dict of implementations indexed by optype,
|
|
# {optype: [impname] }
|
|
imp_dict = defaultdict(list)
|
|
|
|
def __init__(self):
|
|
return
|
|
|
|
def __get_configurations(self):
|
|
for s in self.file_parser.sections():
|
|
cascade_list = []
|
|
if self.file_parser.has_option(s, 'cascade'):
|
|
enabled = self.file_parser.get(s, 'enabled')
|
|
for cascade_str in self.file_parser.get(s,'cascade').split(','):
|
|
cascade_list.append(tuple(cascade_str.split(':')))
|
|
if enabled.lower() in ['true','always']:
|
|
stateval = True
|
|
else:
|
|
stateval = False
|
|
|
|
self.configuration_dict[s]['enabled'] = stateval
|
|
self.configuration_dict[s]['cascade_list'] = cascade_list
|
|
|
|
def __get_options(self):
|
|
for s in self.file_parser.sections():
|
|
if self.file_parser.has_option(s, 'implementation'):
|
|
selected_imp = self.file_parser.get(s, 'implementation')
|
|
imptype = self.file_parser.get(s, 'optype')
|
|
enabled = self.file_parser.get(s, 'enabled').lower()
|
|
if enabled == 'always':
|
|
stateval = True
|
|
permanent = True
|
|
elif enabled == 'true':
|
|
stateval = True
|
|
permanent = False
|
|
else:
|
|
stateval = False
|
|
permanent = False
|
|
|
|
self.opt_dict[s]['enabled'] = stateval
|
|
self.opt_dict[s]['permanent'] = permanent
|
|
self.opt_dict[s]['imptype'] = imptype
|
|
self.opt_dict[s]['selected_imp'] = selected_imp
|
|
|
|
def __get_implementations(self):
|
|
for s in self.file_parser.sections():
|
|
if self.file_parser.has_option(s, 'imptype'):
|
|
key = self.file_parser.get(s, 'imptype')
|
|
self.imp_dict[key].append(s)
|
|
|
|
def read_config_file(self, config_filename):
|
|
self.config_filename = config_filename
|
|
self.file_parser = ConfigParser.SafeConfigParser()
|
|
self.file_parser.read(config_filename)
|
|
self.__get_options()
|
|
self.__get_implementations()
|
|
self.__get_configurations()
|
|
return
|
|
|
|
def backup_files(self):
|
|
for idx in range(8, 0, -1):
|
|
if os.path.exists(self.config_filename + "." + str(idx)):
|
|
os.rename(self.config_filename + "." + str(idx),
|
|
self.config_filename + "." + str(idx + 1))
|
|
if os.path.exists(self.config_filename):
|
|
os.rename(self.config_filename, self.config_filename + ".1")
|
|
|
|
def write_config_file(self):
|
|
for item,dict in self.opt_dict.iteritems():
|
|
if self.file_parser.get(item, 'enabled').lower() == 'always':
|
|
enabled = 'Always'
|
|
else:
|
|
enabled = dict['enabled'].__str__()
|
|
self.file_parser.set(item, 'enabled', enabled)
|
|
self.file_parser.set(item, 'implementation', dict['selected_imp'])
|
|
self.file_parser.set(item, 'optype', dict['imptype'])
|
|
for item,dict in self.configuration_dict.iteritems():
|
|
enabled = dict['enabled'].__str__()
|
|
self.file_parser.set(item, 'enabled', enabled)
|
|
with open(self.config_filename,'w') as cfile:
|
|
for section in sorted(self.file_parser.sections()):
|
|
cfile.write("[%s]\n" % section)
|
|
for option in sorted(self.file_parser.options(section)):
|
|
cfile.write("%s = %s\n" % (option, self.file_parser.get(section, option)))
|
|
cfile.write("\n")
|
|
#self.file_parser.write(cfile)
|
|
|
|
def cf_statechange(self, checkbox, new_state, udat=None):
|
|
cfg_id = checkbox.get_label()
|
|
self.configuration_dict[cfg_id]['enabled'] = new_state
|
|
|
|
def opt_statechange(self, checkbox, new_state, udat=None):
|
|
opt = checkbox.get_label()
|
|
dbg.msg(3, 'InstConfigData:opt_statechange({0},{1},{2})'.format(opt, new_state, udat))
|
|
self.opt_dict[opt]['enabled'] = new_state
|
|
|
|
def imp_statechange(self, button, new_state, opt):
|
|
selected_imp = button.get_label()
|
|
dbg.msg(self.msg_index, 'InstConfigData:imp_statechange({0},{1},{2})'.format(selected_imp, new_state, opt))
|
|
self.msg_index = (self.msg_index - 3) % 2 + 4
|
|
if new_state == True:
|
|
self.opt_dict[opt]['selected_imp'] = selected_imp
|
|
|
|
|
|
## TODO Configuration Editor
|
|
## Configuration Viewer
|
|
Palette = [
|
|
('body', 'dark cyan', '', 'standout'),
|
|
('focus', 'dark red', '', 'standout'),
|
|
('head', 'light red', 'black'),
|
|
]
|
|
|
|
|
|
#FIXME Replace the [(name,stateval)] list imp_states with list of item names
|
|
class RadioButtonListWalker(urwid.SimpleListWalker):
|
|
button_dict = {}
|
|
def __init__(self, item_states, on_state_change=None, user_data=None):
|
|
radio_grp = []
|
|
mapped_rb_list = []
|
|
for item,stateval in item_states:
|
|
rb = urwid.RadioButton(radio_grp, item, state=stateval, on_state_change=on_state_change, user_data=user_data)
|
|
self.button_dict[item] = rb
|
|
mapped_rb = urwid.AttrMap(rb, 'body', 'focus')
|
|
mapped_rb_list.append(mapped_rb)
|
|
|
|
super(RadioButtonListWalker, self).__init__(mapped_rb_list)
|
|
return
|
|
|
|
|
|
class CheckBoxListWalker(urwid.SimpleListWalker):
|
|
button_dict = {}
|
|
def __init__(self, item_states, on_state_change = None, user_data = None):
|
|
mapped_cb_list = []
|
|
for item,stateval in item_states:
|
|
cb = urwid.CheckBox(item, state = stateval, on_state_change = on_state_change, user_data = user_data)
|
|
self.button_dict[item] = cb
|
|
mapped_cb = urwid.AttrMap(cb, 'body', 'focus')
|
|
mapped_cb_list.append(mapped_cb)
|
|
|
|
super(CheckBoxListWalker, self).__init__(mapped_cb_list)
|
|
return
|
|
|
|
|
|
# Selects listwalker to display for ImpListBox on focus
|
|
class OptionListWalker(CheckBoxListWalker):
|
|
def __init__(self, opt_dict, statechange_cb):
|
|
urwid.register_signal(OptionListWalker, ['focus_change'])
|
|
item_states = [(i,d['enabled']) for i,d in opt_dict.iteritems()]
|
|
item_states.sort()
|
|
|
|
super(OptionListWalker, self).__init__(item_states, statechange_cb)
|
|
return
|
|
|
|
def set_focus(self, pos):
|
|
dbg.msg(0, 'OptionListWalker:set_focus({0}) -> emit focus_change'.format(pos))
|
|
urwid.emit_signal(self, 'focus_change', pos)
|
|
return super(OptionListWalker, self).set_focus(pos)
|
|
|
|
|
|
# ClosedListBox implements a ListBox which prevents selection outside of the
|
|
# list using the 'up' or 'down' keys
|
|
class ClosedListBox(urwid.ListBox):
|
|
|
|
def keypress(self, size, key):
|
|
"""Prevents navigating outside of a ClosedListBox with the up and down arrow keys"""
|
|
pos = self.get_focus()[1]
|
|
ll = len(self.body)
|
|
if (pos <= 0 and key == 'up') or (pos >= ll-1 and key == 'down'):
|
|
return
|
|
else:
|
|
return super(ClosedListBox, self).keypress(size, key)
|
|
|
|
|
|
# List of Checkboxes
|
|
class OptionListBox(ClosedListBox):
|
|
def __init__(self, listwalker):
|
|
super(OptionListBox, self).__init__(listwalker)
|
|
return
|
|
|
|
# Contains OptionListWalker dict indexed by option
|
|
# Contains ImpListBox
|
|
# Connects OptionListWalker 'focus_change' signal to update_imp_lb handler
|
|
# Tracks selected implementation for each option
|
|
# and sets selection on ImpListBox
|
|
class InstConfigManager:
|
|
cf_msg_index = 8
|
|
options = []
|
|
imp_lw_dict = {}
|
|
def __init__(self, cfdat):
|
|
self.cfdat = cfdat
|
|
urwid.register_signal(InstConfigManager, ['focus_change'])
|
|
for opt,dict in cfdat.opt_dict.iteritems():
|
|
self.options.append((opt, dict['imptype']))
|
|
# imp_items = []
|
|
# for imp in cfdat.imp_dict[dict['imptype']]:
|
|
# if imp == dict['selected_imp']:
|
|
# imp_items.append((imp, True))
|
|
# else:
|
|
# imp_items.append((imp, False))
|
|
|
|
# imp_items.sort()
|
|
# self.imp_lw_dict[opt] = RadioButtonListWalker(imp_items, on_state_change=self.imp_statechange, user_data=opt)
|
|
|
|
self.options.sort()
|
|
# imp_items.sort()
|
|
firstopt = self.options[0][0]
|
|
self.imp_lw = self.__gen_imp_listwalker(firstopt)
|
|
# self.imp_lw = RadioButtonListWalker([], on_state_change=self.imp_statechange, user_data=firstopt)
|
|
self.option_lw = OptionListWalker(cfdat.opt_dict, self.opt_statechange)
|
|
self.imp_lb = ImpListBox(self.imp_lw)
|
|
urwid.connect_signal(self.option_lw, 'focus_change', self.update_imp_lb)
|
|
|
|
item_states = [(i,d['enabled']) for i,d in cf_dat.configuration_dict.iteritems()]
|
|
item_states.sort()
|
|
self.cfg_lw = RadioButtonListWalker(item_states, on_state_change = self.cf_statechange)
|
|
self.config_lb = OptionListBox(self.cfg_lw)
|
|
self.opt_lb = OptionListBox(self.option_lw)
|
|
self.opt_lb.set_focus(0)
|
|
return
|
|
|
|
def __gen_imp_listwalker(self, opt):
|
|
imp_items = []
|
|
dict = self.cfdat.opt_dict[opt]
|
|
for imp in self.cfdat.imp_dict[dict['imptype']]:
|
|
if imp == dict['selected_imp']:
|
|
imp_items.append((imp, True))
|
|
else:
|
|
imp_items.append((imp, False))
|
|
|
|
imp_items.sort()
|
|
|
|
return RadioButtonListWalker(imp_items, on_state_change=self.imp_statechange, user_data=opt)
|
|
|
|
|
|
def cf_statechange(self, button, new_state, udat=None):
|
|
self.cfdat.cf_statechange(button, new_state, udat)
|
|
b = button.get_label()
|
|
cascade = self.cfdat.configuration_dict[b]['cascade_list']
|
|
if new_state == True:
|
|
for opt in self.cfdat.opt_dict.keys():
|
|
if self.cfdat.opt_dict[opt]['permanent'] == False:
|
|
self.option_lw.button_dict[opt].set_state(False)
|
|
for opt,imp in cascade:
|
|
self.option_lw.button_dict[opt].set_state(True)
|
|
imp_lw = self.__gen_imp_listwalker(opt)
|
|
imp_lw.button_dict[imp].set_state(True)
|
|
currpos = self.opt_lb.get_focus()[1]
|
|
self.opt_lb.set_focus(currpos)
|
|
|
|
dbg.msg(self.cf_msg_index, 'InstConfigManager:cf_statechange({0},{1},{2}), cascade = {3}'.format(b, new_state, udat, cascade))
|
|
self.cf_msg_index = (self.cf_msg_index - 7) % 2 + 8
|
|
return
|
|
|
|
def opt_statechange(self, button, new_state, udat=None):
|
|
self.cfdat.opt_statechange(button, new_state, udat)
|
|
return
|
|
|
|
def imp_statechange(self, button, new_state, udat=None):
|
|
self.cfdat.imp_statechange(button, new_state, udat)
|
|
return
|
|
|
|
def update_imp_lb(self, pos):
|
|
optname = self.options[pos][0]
|
|
optype = self.options[pos][1]
|
|
mstr = 'InstConfigManager:update_imp_lb({0}) -> select {1}'.format(pos, optype)
|
|
dbg.msg(1, mstr)
|
|
self.imp_lw = self.__gen_imp_listwalker(optname)
|
|
self.imp_lb.use_listwalker(self.imp_lw)
|
|
return
|
|
|
|
|
|
# List of RadioButtons
|
|
class ImpListBox(ClosedListBox):
|
|
def __init__(self, listwalker):
|
|
super(ImpListBox, self).__init__(listwalker)
|
|
return
|
|
|
|
def use_listwalker(self, liswalker):
|
|
self.body.contents[:] = liswalker
|
|
return
|
|
|
|
|
|
class InstConfigView(urwid.Pile):
|
|
|
|
def __init__(self, cf_dat, cf_man, dbmsg):
|
|
self.cf_dat = cf_dat
|
|
self.cf_man = cf_man
|
|
option_ListBoxes = [
|
|
self.cf_man.config_lb,
|
|
self.cf_man.opt_lb,
|
|
self.cf_man.imp_lb,
|
|
dbmsg]
|
|
super(InstConfigView, self).__init__(option_ListBoxes)
|
|
return
|
|
|
|
def keyinput(self, key):
|
|
if key == 'meta q':
|
|
raise urwid.ExitMainLoop()
|
|
elif key == 'w':
|
|
self.cf_dat.backup_files()
|
|
self.cf_dat.write_config_file()
|
|
elif key in ['right', 'tab']:
|
|
if self.get_focus() == self.cf_man.config_lb:
|
|
self.set_focus(self.cf_man.opt_lb)
|
|
elif self.get_focus() == self.cf_man.opt_lb:
|
|
self.set_focus(self.cf_man.imp_lb)
|
|
else:
|
|
self.set_focus(self.cf_man.config_lb)
|
|
elif key in ['left', 'shift tab']:
|
|
if self.get_focus() == self.cf_man.config_lb:
|
|
self.set_focus(self.cf_man.imp_lb)
|
|
elif self.get_focus() == self.cf_man.opt_lb:
|
|
self.set_focus(self.cf_man.config_lb)
|
|
else:
|
|
self.set_focus(self.cf_man.opt_lb)
|
|
return
|
|
|
|
import pdb
|
|
class DEBUG:
|
|
msgTextDict = {}
|
|
msglist = []
|
|
msg_ids = [ 'm0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9' ]
|
|
def __init__(self, enabled=False):
|
|
self.enabled = enabled
|
|
if enabled:
|
|
for msgID in self.msg_ids:
|
|
msgText = urwid.Text(u'Space for message {0}'.format(msgID))
|
|
self.msgTextDict[msgID] = msgText
|
|
self.msglist.append(urwid.AttrMap(msgText, 'body', 'focus'))
|
|
|
|
mlw = urwid.SimpleListWalker(self.msglist)
|
|
self.mlb = urwid.ListBox(mlw)
|
|
return
|
|
|
|
def msg(self, index, msg):
|
|
if self.enabled:
|
|
mid = self.msg_ids[index]
|
|
self.msgTextDict[mid].set_text(msg)
|
|
return
|
|
|
|
|
|
dbg = DEBUG(enabled=True)
|
|
def main(config_ini):
|
|
global cf_dat, cf_man, cf_viewer
|
|
# Make configuration data
|
|
cf_dat = InstConfigData()
|
|
cf_dat.read_config_file(config_ini)
|
|
|
|
# Make configuration editor
|
|
cf_man = InstConfigManager(cf_dat)
|
|
|
|
# Make configuration viewer
|
|
cf_viewer = InstConfigView(cf_dat, cf_man, dbg.mlb)
|
|
urwid.MainLoop(cf_viewer, Palette, unhandled_input=cf_viewer.keyinput).run()
|
|
return
|
|
|
|
if '__main__' == __name__:
|
|
default_ini = "/usr/local/sics/sics_config.ini"
|
|
parser = argparse.ArgumentParser(description = """
|
|
Edit a configuration (*.ini) file using python urwid widget library.
|
|
Options can be enabled or disabled with mouse or spacebar.
|
|
Navigate with arrow keys.
|
|
Press W to save.
|
|
Press Alt-Q to quit.
|
|
The default configuration filename is %s.
|
|
""" % default_ini)
|
|
parser.add_argument("-v", "--verbose", action="store_true", help="give more info in the footer")
|
|
parser.add_argument("path", nargs="?", default = default_ini, help="name of file to edit [%s]" % default_ini)
|
|
args = parser.parse_args()
|
|
default_ini = os.path.abspath(args.path)
|
|
main(default_ini)
|