#!/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, InstConfigView, ConfigEdit # # 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 shutil import argparse import ConfigParser import urwid import copy from collections import defaultdict ## 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 # 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 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) # imp2opt_dict: Maps each implementation to an option or None, # {imp: opt/None} imp2opt_dict = {} 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 if self.file_parser.has_option(s, 'id'): id = self.file_parser.get(s, 'id') self.opt_dict[s]['id'] = id self.opt_dict[s]['enabled'] = stateval self.opt_dict[s]['permanent'] = permanent self.opt_dict[s]['imptype'] = imptype if selected_imp in self.imp2opt_dict: self.opt_dict[s]['selected_imp'] = "none" else: self.opt_dict[s]['selected_imp'] = selected_imp print 'Add imp2opt_dict[{0}] = {1}'.format(selected_imp,s) self.imp2opt_dict[selected_imp] = s 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') if 'none' not in self.imp_dict[key]: self.imp_dict[key].append('none') self.imp_dict[key].append(s) if s not in self.imp2opt_dict: print 'Add imp2opt_dict[{0}] = none'.format(s) self.imp2opt_dict[s] = "none" 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): shutil.copy(self.config_filename, self.config_filename + ".1") def write_config_file(self): for item,dict in self.opt_dict.iteritems(): if 'permanent' in dict and dict['permanent'] == True: 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) for imp,opt in self.imp2opt_dict.iteritems(): if imp != 'none' and opt != 'none' and 'id' in self.opt_dict[opt]: self.file_parser.set(imp, 'id', self.opt_dict[opt]['id']) 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 # 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 = imp_items[:1] + sorted(imp_items[1:]) 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 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)