""" FieldSpecEditor.py
This application will read in a field spec XML file and allow the user
to edit any of the fields in it.
"""
import os
import sys
import wx
import wx.grid as gridlib
import dabo
import dabo.common.fieldSpecParser as fieldSpecParser
import dabo.dEvents as dEvents
dabo.ui.loadUI("wx")
class FieldSpecGrid(gridlib.Grid):
""" This grid is responsible for displaying the current settings of the
fieldSpecs.xml file for a table in the current app. Each row in the grid
will contain the settings for a field in the table, which control how the
field is displayed in the app, or whether it is displayed at all.
This grid is also responsible for reversing the process when requested
by its form: given the current state of its cell contents, it will generate
a dictionary like the one it was created from. The form will use this
generated dict to save the modified settings back into XML form.
"""
def __init__(self, parent, tblDict=None):
gridlib.Grid.__init__(self, parent, -1)
# Cell renderer and editor classes
strRend = gridlib.GridCellStringRenderer
strEdt = gridlib.GridCellTextEditor
boolRend = gridlib.GridCellBoolRenderer
boolEdt = gridlib.GridCellBoolEditor
numRend = gridlib.GridCellNumberRenderer
numEdt = gridlib.GridCellNumberEditor
floatRend = gridlib.GridCellFloatRenderer
floatEdt = gridlib.GridCellFloatEditor
self.fldInfo = {
"type": {"type": "string", "col": 1, "colheading": "Field Type",
"rend": strRend, "edt": None},
"caption": {"type": "string", "col": 0, "colheading": "Caption",
"rend": strRend, "edt": strEdt},
"searchInclude": {"type": "bool", "col": 2, "colheading": "Search Page?",
"rend": boolRend, "edt": boolEdt},
"searchOrder": {"type": "int", "col": 3, "colheading": "Search Order",
"rend": numRend, "edt": numEdt},
"listColWidth": {"type": "int", "col": 4, "colheading": "List Width",
"rend": numRend, "edt": numEdt},
"listInclude": {"type": "bool", "col": 5, "colheading": "List Page?",
"rend": boolRend, "edt": boolEdt},
"listOrder": {"type": "int", "col": 6, "colheading": "List Order",
"rend": numRend, "edt": numEdt},
"editInclude": {"type": "bool", "col": 7, "colheading": "Edit Page?",
"rend": boolRend, "edt": boolEdt},
"editOrder": {"type": "int", "col": 8, "colheading": "Edit Order",
"rend": numRend, "edt": numEdt},
"editReadOnly": {"type": "bool", "col": 9, "colheading": "Read-Only?",
"rend": boolRend, "edt": boolEdt}
}
if tblDict is not None:
rows = len(tblDict)
cols = len(tblDict[tblDict.keys()[0]])
self.CreateGrid(rows, cols)
row = 0
colLabelSet = False
for fld in tblDict.keys():
self.SetRowLabelValue(row, fld)
fldData = tblDict[fld]
for prop in fldData.keys():
col = self.fldInfo[prop]["col"]
if col < 0:
# Not edited
continue
if not colLabelSet:
self.SetColLabelValue(col, self.fldInfo[prop]["colheading"])
val = fldData[prop]
typ = self.fldInfo[prop]["type"]
self.SetCellValue(row, col, val)
if self.fldInfo[prop]["rend"] is not None:
rend = self.fldInfo[prop]["rend"]()
self.SetCellRenderer(row, col, rend)
if self.fldInfo[prop]["edt"] is not None:
edtClass = self.fldInfo[prop]["edt"]
edtr = edtClass()
self.SetCellEditor(row, col, edtr)
row += 1
colLabelSet = True
font = self.GetFont()
font.SetWeight(wx.BOLD)
attr = gridlib.GridCellAttr()
attr.SetFont(font)
attr.SetBackgroundColour(wx.LIGHT_GREY)
attr.SetReadOnly(True)
attr.SetAlignment(wx.RIGHT, -1)
# self.SetColAttr(0, attr)
# There is a bug in wxGTK for this method...
self.AutoSizeColumns(True)
self.AutoSizeRows(True)
def getCurrentValues(self):
""" Returns a dictionary containing the current state of the edited values
for each field in the grid.
"""
ret = {}
# Get a list of props and their column numbers
propCols = {}
props = self.fldInfo.keys()
for k in props:
propCols[k] = self.fldInfo[k]["col"]
for row in range(self.GetNumberRows()):
fldName = self.GetRowLabelValue(row)
ret[fldName] = {}
for prop in props:
ret[fldName][prop] = self.GetCellValue(row, propCols[prop])
return ret
class FieldEditorForm(dabo.ui.dForm):
def __init__(self, fieldSpecFile=None):
FieldEditorForm.doDefault()
self.Caption = "Field Spec Editor"
self.debug = True # output verbose
self.specFile = fieldSpecFile
# Dict of available preview forms
self.previewForms = {}
# See if we've checked if the user wants to save their changes
self.askedToSave = False
self.Width = 950
self.Height = 440
# This is the main dictionary that holds all the field spec data.
# There will be an element for each table in the app. Each of those
# elements will be a dictionary, with one element for each field.
# Each field will have several properties that affect how it appears
# (or doesn't appear) on each part of the form.
self.mainDict = {}
# As the user selects different tables and fields, these will be
# updated to reflect the current selection.
self.currTableDict = {}
self.currFieldDict = {}
# Read in the field spec file
if self.specFile is None:
# See if it was passed as a parameter
try:
self.specFile = sys.argv[1]
except: pass
if self.specFile is None:
wildcard = "XML Files (*.xml)|*.xml|" \
"All files (*.*)|*.*"
openDlg = wx.FileDialog(None, "Select a file...", wildcard=wildcard,
defaultDir=os.getcwd(), style=wx.OPEN ) #| wx.HIDE_READONLY)
res = openDlg.ShowModal()
if res == wx.ID_OK:
self.specFile = openDlg.GetPath()
# Development shortcuts!
# if os.path.exists("/home/ed/projects/demo/webtest"):
# self.specFile = "/home/ed/projects/demo/webtest/fieldSpecs.xml"
# else:
# self.specFile = "/Volumes/ED/projects/demo/dabotest/fieldSpecs.xml"
if self.specFile is None:
dabo.errorLog.write("No field spec file specified; exiting.")
return
# Read the XML into a local dictionary
self.mainDict = fieldSpecParser.importFieldSpecs(self.specFile)
# Create the form controls. The design will be one page per table.
# Each page will contain a list/grid with a summary for each field.
# Selecting a row in the grid updates a set of editable controls so
# that a user can change the settings for any field.
self.instantiateControls()
def initEvents(self):
FieldEditorForm.doDefault()
self.bindEvent(dEvents.Close, self.__onClose)
def __onClose(self, evt):
if not self.askedToSave:
currDict = self.getCurrentValues()
if currDict != self.mainDict:
resp = dabo.ui.dMessageBox.areYouSure(message="Do you want to save your changes?",
title = "Save Changes")
if resp is None:
# User pressed 'Cancel'
evt.Continue = False
return
if resp:
self.save(currDict)
self.askedToSave = True
def getCurrentValues(self):
ret = {}
for pgNum in range(self.pgf.PageCount):
pg = self.pgf.GetPage(pgNum)
tbl = pg.Caption
grd = [k for k in pg.GetChildren() if isinstance(k, wx.grid.Grid)][0]
grd.SaveEditControlValue()
ret[tbl] = grd.getCurrentValues()
return ret
def saveTmp(self, file):
holdSpecFile = self.specFile
self.specFile = file
self.save()
self.specFile = holdSpecFile
def save(self, vals=None):
if vals is None:
vals = self.getCurrentValues()
tblTemplate = self.getTableTemplate()
fldTemplate = self.getFieldTemplate()
tableXML = ""
for tbl in vals.keys():
tblData = vals[tbl]
flds = tblData.keys()
fieldXML = ""
for fld in flds:
fldData = tblData[fld]
# Make these record zeros for False instead of the default ""
sInc = fldData["searchInclude"]
if not sInc:
sInc = "0"
eInc = fldData["editInclude"]
if not eInc:
eInc = "0"
lInc = fldData["listInclude"]
if not lInc:
lInc = "0"
fldVals = (fld, fldData["type"], fldData["caption"], sInc,
fldData["searchOrder"], lInc,
fldData["listColWidth"], fldData["listOrder"], eInc,
fldData["editReadOnly"], fldData["editOrder"])
fieldXML += fldTemplate % fldVals
tableXML += tblTemplate % (tbl, fieldXML)
xml = self.getBaseXML() % tableXML
open(self.specFile, "w").write(xml)
return
def instantiateControls(self):
labelWidth = 150
labelAlignment = wx.ALIGN_RIGHT
# Create the pageframe
self.pgf = dabo.ui.dPageFrame(self)
tbls = self.mainDict.keys()
self.pgf.PageCount = len(tbls)
pgNum = 0
# Small defaults
self.minHt = 10
self.minWd = 10
for tbl in tbls:
pg = self.pgf.GetPage(pgNum)
pg.Caption = tbl
pgNum += 1
lbl = dabo.ui.dLabel(pg)
lbl.Caption = "Editing Form for table: " + tbl
lbl.FontSize = 18
lbl.FontBold = True
lbl.ForeColor = (0,0,128)
grd = FieldSpecGrid(pg, self.mainDict[tbl])
pgsz = wx.BoxSizer(wx.VERTICAL)
pgsz.Add(lbl, 0, wx.EXPAND | wx.ALIGN_CENTER)
pgsz.Add( (10, 10), 0)
pgsz.Add(grd, 1, wx.EXPAND)
pg.SetSizer(pgsz)
pgsz.Layout()
btn = dabo.ui.dCommandButton(self)
btn.Caption = " Preview Form "
self.Bind(wx.EVT_BUTTON, self.onPreview, id=btn.GetId() )
sz = wx.BoxSizer(wx.VERTICAL)
sz.Add(self.pgf, 1, wx.EXPAND)
sz.Add(btn, 0, wx.ALIGN_CENTRE)
self.SetSizer(sz)
sz.Layout()
def onPreview(self, evt):
tbl = self.pgf.GetPageText(self.pgf.GetSelection())
tmpSpecFile = "./.tmpSpec.xml"
self.saveTmp(tmpSpecFile)
create = True
if self.previewForms.has_key(tbl):
if isinstance(self.previewForms[tbl], dabo.ui.dDataNavForm):
create = False
if create:
self.previewForms[tbl] = dabo.ui.dDataNavForm(self, previewMode=True, tbl=tbl)
prev = self.previewForms[tbl]
prev.Caption = tbl + " - Preview"
prev.setFieldSpecs(tmpSpecFile, tbl)
prev.Show(True)
prev.Raise()
def getBaseXML(self):
ret = """