####################################################################################################### # # The wxPyWf is a set of classes to facilitate the building of quick wxPython GUI apps using wftk. # More information at http://www.vivtek.com/wftk/doc/code/python # # Copyright (c) 2003-2007, Vivtek, and released under the GPL. # ####################################################################################################### |
import wftk import repmgr_cli from wxPython.wx import * from wxPython.html import * |
Then there are some useful little extras, like easy user notification and the like, here.
See Utility functions |
The frame class includes very arbitrary window layouts consisting of any collection of splitters and objects of other window classes; these subclasses may be any wxPython class or a wxPyWf panel. A frame may also have a menu, of course.
There is a difference (confusing to me) between a frame and a dialog. A frame is simply a window with the titlebar at the top, perhaps a menu bar, and perhaps a status bar. The client area of the frame may then contain anything at all. A dialog, on the other hand, is expected to contain a set of controls. A panel is a non-framed window which acts as a dialog in that it is a container for controls. And a frame can contain a panel -- but the key is that it need not. It will more often subdivide its window into pieces using splitters and the like. We can think of a dialog as a special case of a frame which contains only a panel and nothing else (and the GUI also does some convenience processing for us involving results retrieval -- but as wxpywf does even more in that regard, this is nearly a moot point.)
A frame also provides the point of reference of a list of command handlers; every item defined in the frame passes command events to the frame for handling. The frame is responsible for doing the appropriate thing.
class frame(wxFrame): def __init__(self, parent, defn, starter, cli_list=[]): h = starter['h'] if not h: h = 500 # TODO: user config -- not just in app, but in Registry h = int(h) w = starter['w'] if not w: w = 500 w = int(w) self.defn = starter self.repos = defn self._handlers = cli_list + [gui_cli(self), repmgr_cli.cli(None)] for cli in self._handlers: cli.set_frame(self) wxFrame.__init__(self, parent, -1, self.defn['title'], size = (w, h), style=wxDEFAULT_FRAME_STYLE|wxNO_FULL_REPAINT_ON_RESIZE) self.CreateStatusBar (1, wxST_SIZEGRIP) # Only if specified? self._context = wftk.xml (" |
def do (self, context, cmd): for h in self._handlers: rslt = h.do(self._context, cmd) if rslt: break |
A panel (or dialog) defines a set of input widgets and can be written to (an XMLOBJ is given to specify the values of all the fields) or read from (an XMLOBJ is extracted from the current values of the fields.) It may also have arbitrary sets of buttons; each button has a command against a CLI list, and the parameter set to that command is the XMLOBJ value of the panel/dialog. If there's no explicit panel definition, the class will make up a default; if given a list defn structure, the default is more likely to be a good one.
The panel writes its fields to a record XML passed in (if not specified, it uses the frame's content member); changes are made to most controls when the selection changes; text fields might end up being updated only when a button is clicked, or something.
Layout is either explicit, with positions for each widget on the panel (TODO: implement), or by means of boxes. If using boxes, the boxes and the widgets are defined at the same level, and each widget then specifies the box it belongs to. The same goes for groups. Everything in the panel, that is, is defined at the same element level, as a direct child of the panel definition element.
class panel(wxPanel): def __init__ (self, defn, frame, parent, rec=None): if rec == None: rec = frame._context self.defn = defn self.frame = frame self.parent = parent self.rec = rec self.boundfields = {} wxPanel.__init__(self, parent, -1) self.dialog = false self.setup() def setup (self, bindtoframe=true): # Define the layout boxes self.boxes = {} self.topbox = None for ch in self.defn.elements(): if ch.is_a('box'): dir = wxHORIZONTAL if first_char_of(ch['dir']) == 'v': dir = wxVERTICAL self.boxes[ch['id']] = wxBoxSizer(dir) if not ch['box']: self.topbox = self.boxes[ch['id']] # Define the controls on the panel self.controls = {} for ch in self.defn.elements(): if ch.is_a('radio-group'): id = wxNewId() ch['ctl-id'] = id (labels, values) = self.ScanControlValues (ch, 'radio') control = wxRadioBox(self, id, ch['label'], majorDimension=1, choices = labels) if not self.dialog: EVT_RADIOBOX(self, id, self.OnControlChange) self.controls[ch['ctl-id']] = (control, ch, values) try: control.SetSelection(values.index(self.rec[ch['field']])) except: pass if not self.dialog: self.rec[ch['field']] = values[control.GetSelection()] if bindtoframe: self.frame.boundfields[ch['field']] = self self.boundfields[ch['field']] = ch['ctl-id'] elif ch.is_a('listbox'): id = wxNewId() ch['ctl-id'] = id (labels, values) = self.ScanControlValues (ch, 'value') control = wxListBox(self, id, choices = labels) if not self.dialog: EVT_LISTBOX(self, id, self.OnControlChange) self.controls[ch['ctl-id']] = (control, ch, values) try: control.SetSelection(values.index(self.rec[ch['field']])) except: pass if not self.dialog:self.rec[ch['field']] = values[control.GetSelection()] if bindtoframe: self.frame.boundfields[ch['field']] = self self.boundfields[ch['field']] = ch['ctl-id'] elif ch.is_a('button'): id = -1 if self.dialog: if ch['value'] == 'ok': id = wxID_OK elif ch['value'] == 'cancel': id = wxID_CANCEL if id == -1: id = wxNewId() ch['ctl-id'] = id control = wxButton (self, id, ch['label']) if ch['cmd']: EVT_BUTTON(self, id, self.OnControlChange) self.controls[ch['ctl-id']] = (control, ch, ch['cmd']) elif ch.is_a('static'): id = wxNewId() ch['ctl-id'] = id if ch['format'] == 'yes': control = wxStaticText (self, -1, self.rec.format(ch.stringcontenthtml())) else: control = wxStaticText (self, -1, ch.stringcontenthtml()) self.controls[ch['ctl-id']] = (control, ch, None) elif ch.is_a('text'): id = wxNewId() ch['ctl-id'] = id style = 0 if ch['multiline'] == 'yes': style = style + wxTE_MULTILINE if ch['password'] == 'yes': style = style + wxTE_PASSWORD if ch['readonly'] == 'yes': style = style + wxTE_READONLY if ch['format'] == 'yes': control = wxTextCtrl (self, id, value = self.rec.format(ch.stringcontenthtml()), style=style) else: control = wxTextCtrl (self, id, value = ch.stringcontenthtml(), style=style) self.controls[ch['ctl-id']] = (control, ch, None) if ch['field']: if self.rec[ch['field']] == None: self.rec[ch['field']] = '' control.SetValue(self.rec[ch['field']]) if bindtoframe: self.frame.boundfields[ch['field']] = self self.boundfields[ch['field']] = ch['ctl-id'] elif ch.is_a('checkbox'): id = wxNewId() ch['ctl-id'] = id if ch['format'] == 'yes': control = wxCheckBox (self, id, self.rec.format(ch['label'])) else: control = wxCheckBox (self, id, ch['label']) EVT_CHECKBOX (self, id, self.OnControlChange) on = ch['on'] if not on: on = '1' off = ch['off'] if not off: off = '0' self.controls[ch['ctl-id']] = (control, ch, [off, on]) if ch['field']: if self.rec[ch['field']] == None: self.rec[ch['field']] = '' control.SetValue(self.rec[ch['field']] == on) if bindtoframe: self.frame.boundfields[ch['field']] = self self.boundfields[ch['field']] = ch['ctl-id'] # Lay out the controls for ch in self.defn.elements(): if ch.is_a('box'): if ch['box']: self.boxes[ch['box']].Add(self.boxes[ch['id']], option=0) else: if ch['box']: self.boxes[ch['box']].Add(self.controls[ch['ctl-id']][0], option=0) if self.topbox: self.SetSizer(self.topbox) if self.dialog: self.topbox.Fit(self) self.SetAutoLayout(true) def new_listbox_values(self, field, new_values, labels=None): defn = self.defn.search('listbox', 'field', field) if not defn: raise KeyError, "no listbox %s defined" % field (control, ch, values) = self.controls[defn['ctl-id']] if labels: labels = [labels[v] for v in new_values] else: labels = new_values control.Clear() control.InsertItems(labels, 0) control.SetDimensions(-1, -1, -1, -1, sizeFlags=wxSIZE_AUTO_WIDTH) self.controls[defn['ctl-id']] = (control, ch, new_values) def ScanControlValues(self, defn, child): labels = [] values = [] count = 0 for r in defn.elements(): if r.is_a(child): v = r['value'] if not v: v = count values = values + [v] l = r['label'] if not l: l = r['value'] labels = labels + [l] count = count + 1 return (labels, values) def OnControlChange(self, event): type = event.GetEventType() id = `event.GetId()` (control, ch, values) = self.controls[id] if type == wxEVT_COMMAND_RADIOBOX_SELECTED or type == wxEVT_COMMAND_LISTBOX_SELECTED: self.rec[ch['field']] = values[control.GetSelection()] elif type == wxEVT_COMMAND_BUTTON_CLICKED: self.frame.do(self.frame._context, values) elif type == wxEVT_COMMAND_CHECKBOX_CLICKED: self.rec[ch['field']] = values[int(control.GetValue())] def setvalue(self, field, value): try: (control, ch, values) = self.controls[self.boundfields[field]] except: self.rec[field] = value return if ch.is_a('radio-group') or ch.is_a('listbox'): try: index = values.index(value) except: return control.SetSelection(index) self.rec[field] = value elif ch.is_a('text'): control.SetValue(value) self.rec[field] = value elif ch.is_a('checkbox'): control.SetValue(value == values[0]) self.rec[field] = value def writerec(self): for field in self.boundfields.keys(): (control, ch, values) = self.controls[self.boundfields[field]] if ch.is_a('radio-group') or ch.is_a('listbox'): self.rec[field] = values[control.GetSelection()] elif ch.is_a('text'): self.rec[field] = control.GetValue() elif ch.is_a('checkbox'): self.rec[field] = values[control.GetValue()] |
class dialog(wxDialog, panel): def __init__(self, defn, frame, parent, rec=None, title=None, bindtoframe=None): if rec == None: rec = frame._context if bindtoframe == None: bindtoframe = true else: if bindtoframe == None: bindtoframe = false self.defn = defn self.frame = frame self.parent = parent self.rec = rec self.boundfields = {} if title == None: title=defn['title'] wxDialog.__init__ (self, parent, -1, title=title) self.dialog = true self.setup(bindtoframe) self.Layout() # Thanks to http://aspn.activestate.com/ASPN/Mail/Message/wxPython-users/630396 def call_dialog (defn, frame, parent, rec=None, title=None): if title == None: title=defn['title'] dlg = dialog(defn, frame, parent, rec, title) ctl = dlg.ShowModal() retval = '' if ctl != wxID_CANCEL: dlg.writerec() (control, ch, values) = dlg.controls[`ctl`] retval = ch['value'] return retval |
def first_char_of (str): try: return str[0] except: pass return '' class tabset(wxNotebook): def __init__ (self, defn, frame, parent): self.defn = defn self.frame = frame self.parent = parent self.id = wxNewId() defn['id'] = self.id s = 0 if first_char_of(defn['edge']) == 'l': s = wxNB_LEFT if first_char_of(defn['edge']) == 'r': s = wxNB_RIGHT if first_char_of(defn['edge']) == 'b': s = wxNB_BOTTOM wxNotebook.__init__(self, parent, self.id, style=s) for ch in self.defn.elements(): if ch.is_a('tab'): win = define(ch.elements()[0], frame, self) if win: self.AddPage (win, ch['label']) if self.defn['field']: self.frame.boundfields[self.defn['field']] = self EVT_NOTEBOOK_PAGE_CHANGED(parent, self.id, self.OnPageChanged) def OnPageChanged(self, event): sel = self.GetSelection() self.frame._context[self.defn['field']] = self.GetPageText(sel) #event.skip() def setvalue(self, field, value): for i in range(self.GetPageCount()): if value == self.GetPageText(i): self.SetSelection(i) self.frame._context[field] = value return |
class html_window(wxHtmlWindow): def __init__ (self, defn, frame, parent): self.defn = defn self.frame = frame self.parent = parent self.id = wxNewId() defn['id'] = self.id wxHtmlWindow.__init__(self, parent, self.id) # Write XHTML content into HTML window as initial value self.SetPage (self.defn.stringcontenthtml()) if defn['field']: self.frame.boundfields[defn['field']] = self def setvalue(self, field, value): self.SetPage (value) def OnLinkClicked(self, linkinfo): where = string.lower(linkinfo.GetHref()) if where[0:5] == 'http:': os.startfile(where) if where[0:7] == 'mailto:': os.startfile(where) if where[0:4] == 'cmd:': self.frame.do (self.frame._context, where[4:]) |
class splitter(wxSplitterWindow): def __init__ (self, defn, frame, parent): self.defn = defn self.frame = frame self.parent = parent self.id = wxNewId() defn['id'] = self.id wxSplitterWindow.__init__(self, parent, self.id) p1 = None p2 = None for ch in self.defn.elements(): win = define(ch, frame, self) if win: if p1: p2 = win else: p1 = win if p2: break min = defn['minpanesize'] if min: self.SetMinimumPaneSize(int(min)) if first_char_of(defn['split']) == 'v': self.SplitVertically(p1, p2) else: self.SplitHorizontally(p1, p2) pos = defn['sashpos'] if pos or pos == 0: self.SetSashPosition(int(pos)) |
class filedroptarget(wxFileDropTarget): def __init__(self, cmd, parent, frame): wxFileDropTarget.__init__(self) self.parent = parent self.frame = frame self.cmd = cmd def OnDropFiles(self, x, y, filenames): cmd = self.cmd % ' '.join(map((lambda x: '"%s"' % x), filenames)) self.frame.do (self.frame._context, cmd) |
class menu(wftk.xml): |
_iterparent
attribute is used for the iteration functions below.
def __init__ (self, defn, window, context, handler): self._window = window self._context = context if handler==None: self._handlers = [] elif type(handler) == type([]): self._handlers = handler else: self._handlers = [handler] self.defn = defn.new_copy() self.refresh() self._window.SetMenuBar (self._menubar) |
def menu_handler (self, event): d = self.defn.search(None, "id", `event.GetId()`) cmd = d['cmd'] self.do(self._context, cmd) def do(self, context, cmd): for h in self._handlers: rslt = h.do(self._context, cmd) if rslt: break |
def refresh (self): self._menubar = wxMenuBar() for e in self.defn.elements(): self.init_menu (self._menubar, e) def init_menu (self, parent, d): if d.is_a('menu'): if not d['id']: d['id'] = wxNewId() d.menu=wxMenu() if d.parent().is_a ('menubar'): parent.Append (d.menu, d['label']) else: parent.AppendMenu (int(d['id']), d['label'], d.menu, d['help']) pass for e in d.elements(): self.init_menu (d.menu, e) return if d.is_a('item'): if not d['id']: d['id'] = wxNewId() parent.Append (int(d['id']), d['label'], d['help']) EVT_MENU(self._window, int(d['id']), self.menu_handler) return if d.is_a('separator'): parent.AppendSeparator() return if d.is_a('break'): parent.Break() |
class list(wxListCtrl): def __init__ (self, defn, frame, parent): self.defn = defn self.id = wxNewId() self.defn['id'] = self.id self.frame = frame self.parent = parent self.list = defn['list'] wxListCtrl.__init__(self, self.parent, self.id, style=wxLC_REPORT|wxSUNKEN_BORDER) self.field = self.defn['field'] if self.field: self.frame.boundfields[self.defn['field']] = self count = 0 self.cols = [] for col in self.defn.elements(): if col.is_a('col'): self.InsertColumn(count, col['label']) self.cols = self.cols + [col['field']] count = count + 1 EVT_LIST_ITEM_SELECTED(frame, self.id, self.select) EVT_LIST_ITEM_ACTIVATED(frame, self.id, self.select) #EVT_LIST_DELETE_ITEM(frame, self.id, self.delete) #EVT_LIST_COL_CLICK(frame, self.id, self.select) def select(self, event): self.cur_item = event.m_itemIndex if self.field: self.frame._context[self.field] = self.keys[self.GetItemData(self.cur_item)] #cmd = "view %s %s" % (self.list, self.keys[self.cur_item]) #for h in self.frame._handlers: # rslt = h.do(self._context, cmd) # if rslt: break def activate(self, event): pass def delete(self, event): delitem = event.m_itemIndex # send command? what? TODO: need an ondelete="" command, I suppose. Then a way to determine which index is which record. def setvalue (self, field, value): pass def clear (self): self.numitem = 0 self.DeleteAllItems() self.keys = [] if self.field: self.frame._context[self.field] = '' def autosize (self): for count in range(len(self.cols)): self.SetColumnWidth(count, wxLIST_AUTOSIZE) def add_line(self, rec, key): field = 0 for f in self.cols: if field == 0: self.InsertStringItem (self.numitem, rec[f]) else: self.SetStringItem (self.numitem, field, rec[f]) field = field + 1 self.SetItemData (self.numitem, len(self.keys)) self.keys.append (key) self.numitem = self.numitem + 1 return (self.numitem - 1) def reload (self, rec_list): self.clear() for (rec, key) in rec_list: self.add_line (rec, key) self.autosize() |
def notify_user (line, frame=None, title="Notification"): # TODO: if the frame is a wftk frame, the definition and its application title can be used to override the title. dlg = wxMessageDialog(frame, line, title, wxOK | wxICON_INFORMATION) #wxYES_NO | wxNO_DEFAULT | wxCANCEL | wxICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def ask_user (line, frame=None, title="Yes or no?"): # TODO: if the frame is a wftk frame, the definition and its application title can be used to override the title. dlg = wxMessageDialog(frame, line, title, wxYES_NO | wxICON_INFORMATION) ret = dlg.ShowModal() dlg.Destroy() return ret |
class gui_cli(wftk.cli): def __init__ (self, parent): self.parent=parent self.mode = 0 self.commands={} self.commands['exit'] = ['exit', self.exit, 0, 0, ''] self.commands['set'] = ['set', self.set, 2, 2, '[field] [value]'] def exit(self, context, action, obj): self.parent.Close() def set(self, context, action, obj): field = action['parm(0)'] value = action['parm(1)'] try: self.parent[field] = value except: context[field] = value |
def define(defn, frame, parent): if (defn.is_a ('tabset') or defn.is_a ('notebook')): return tabset(defn, frame, parent) if (defn.is_a ('html')): return html_window(defn, frame, parent) if (defn.is_a ('splitter')): return splitter (defn, frame, parent) if (defn.is_a ('panel')): return panel (defn, frame, parent) if (defn.is_a ('list')): return list (defn, frame, parent) if (defn.is_a ('text')): return text (defn, frame, parent) return None # If we don't define it, we ignore it. |
This code and documentation are released under the terms of the GNU license. They are copyright (c) 2003-2007, Vivtek. All rights reserved except those explicitly granted under the terms of the GNU license. This presentation was prepared with LPML. Try literate programming. You'll like it. |