# Windows dialog .RC file parser, by Adam Walker. # This module is part of the spambayes project, which is Copyright 2003 # The Python Software Foundation and is covered by the Python Software # Foundation license. __author__="Adam Walker" import sys, os, shlex import win32con #import win32gui import commctrl _controlMap = {"DEFPUSHBUTTON":0x80, "PUSHBUTTON":0x80, "Button":0x80, "GROUPBOX":0x80, "Static":0x82, "CTEXT":0x82, "RTEXT":0x82, "LTEXT":0x82, "LISTBOX":0x83, "SCROLLBAR":0x84, "COMBOBOX":0x85, "EDITTEXT":0x81, } _addDefaults = {"EDITTEXT":win32con.WS_BORDER, "GROUPBOX":win32con.BS_GROUPBOX, "LTEXT":win32con.SS_LEFT, "DEFPUSHBUTTON":win32con.BS_DEFPUSHBUTTON, "CTEXT":win32con.SS_CENTER, "RTEXT":win32con.SS_RIGHT} defaultControlStyle = win32con.WS_CHILD | win32con.WS_VISIBLE class DialogDef: name = "" id = 0 style = 0 styleEx = None caption = "" font = "MS Sans Serif" fontSize = 8 x = 0 y = 0 w = 0 h = 0 template = None def __init__(self, n, i): self.name = n self.id = i self.styles = [] self.stylesEx = [] self.controls = [] #print "dialog def for ",self.name, self.id def createDialogTemplate(self): t = None self.template = [[self.caption, (self.x,self.y,self.w,self.h), self.style, self.styleEx, (self.fontSize, self.font)]] # Add the controls for control in self.controls: self.template.append(control.createDialogTemplate()) return self.template class ControlDef: id = "" controlType = "" subType = "" idNum = 0 style = defaultControlStyle label = "" x = 0 y = 0 w = 0 h = 0 def __init__(self): self.styles = [] def toString(self): s = "" return s def createDialogTemplate(self): ct = self.controlType if "CONTROL"==ct: ct = self.subType if ct in _addDefaults: self.style |= _addDefaults[ct] if ct in _controlMap: ct = _controlMap[ct] t = [ct, self.label, self.idNum, (self.x, self.y, self.w, self.h), self.style] #print t return t class RCParser: next_id = 1001 dialogs = {} _dialogs = {} debugEnabled = False; token = "" def __init__(self): self.ids = {"IDOK":1, "IDCANCEL":2, "IDC_STATIC": -1} self.names = {1:"IDOK", 2:"IDCANCEL", -1:"IDC_STATIC"} self.bitmaps = {} def debug(self, *args): if self.debugEnabled: print args def getToken(self): self.token = self.lex.get_token() self.debug("getToken returns:", self.token) if self.token=="": self.token = None return self.token def getCommaToken(self): tok = self.getToken() assert tok == ",", "Token '%s' should be a comma!" % tok def loadDialogs(self, rcFileName): """ RCParser.loadDialogs(rcFileName) -> None Load the dialog information into the parser. Dialog Definations can then be accessed using the "dialogs" dictionary member (name->DialogDef). The "ids" member contains the dictionary of id->name. The "names" member contains the dictionary of name->id """ hFileName = rcFileName[:-2]+"h" try: h = open(hFileName, "rU") self.parseH(h) h.close() except OSError: print "No .h file. ignoring." f = open(rcFileName) self.open(f) self.getToken() while self.token!=None: self.parse() self.getToken() f.close() def open(self, file): self.lex = shlex.shlex(file) self.lex.commenters = "//#" def parseH(self, file): lex = shlex.shlex(file) lex.commenters = "//" token = " " while token is not None: token = lex.get_token() if token == "" or token is None: token = None else: if token=='define': n = lex.get_token() i = int(lex.get_token()) self.ids[n] = i if self.names.has_key(i): # ignore AppStudio special ones. if not n.startswith("_APS_"): print "Duplicate id",i,"for",n,"is", self.names[i] else: self.names[i] = n if self.next_id<=i: self.next_id = i+1 def parse(self): deep = 0 if self.token == None: more == None elif "BEGIN" == self.token: deep = 1 while deep!=0: self.getToken() if "BEGIN" == self.token: deep += 1 elif "END" == self.token: deep -= 1 elif "IDD_" == self.token[:4]: possibleDlgName = self.token #print "possible dialog:", possibleDlgName self.getToken() if "DIALOG" == self.token or "DIALOGEX" == self.token: self.dialog(possibleDlgName) elif "IDB_" == self.token[:4]: possibleBitmap = self.token self.getToken() if "BITMAP" == self.token: self.getToken() if self.token=="MOVEABLE": self.getToken() # PURE self.getToken() # bmpname bmf = self.token[1:-1] # quotes self.bitmaps[possibleBitmap] = bmf print "BITMAP", possibleBitmap, bmf #print win32gui.LoadImage(0, bmf, win32con.IMAGE_BITMAP,0,0,win32con.LR_DEFAULTCOLOR|win32con.LR_LOADFROMFILE) def addId(self, id_name): if id_name in self.ids: id = self.ids[id_name] else: id = self.next_id self.next_id += 1 self.ids[id_name] = id self.names[id] = id_name return id def lang(self): while self.token[0:4]=="LANG" or self.token[0:7]=="SUBLANG" or self.token==',': self.getToken(); def dialog(self, name): dlg = DialogDef(name,self.addId(name)) assert len(dlg.controls)==0 self._dialogs[name] = dlg extras = [] self.getToken() while not self.token.isdigit(): self.debug("extra", self.token) extras.append(self.token) self.getToken() dlg.x = int(self.token) self.getCommaToken() self.getToken() # number dlg.y = int(self.token) self.getCommaToken() self.getToken() # number dlg.w = int(self.token) self.getCommaToken() self.getToken() # number dlg.h = int(self.token) self.getToken() while not (self.token==None or self.token=="" or self.token=="END"): if self.token=="STYLE": self.dialogStyle(dlg) elif self.token=="EXSTYLE": self.dialogExStyle(dlg) elif self.token=="CAPTION": self.dialogCaption(dlg) elif self.token=="FONT": self.dialogFont(dlg) elif self.token=="BEGIN": self.controls(dlg) else: break self.dialogs[name] = dlg.createDialogTemplate() def dialogStyle(self, dlg): dlg.style, dlg.styles = self.styles( [], win32con.WS_VISIBLE | win32con.DS_SETFONT) def dialogExStyle(self, dlg): self.getToken() dlg.styleEx, dlg.stylesEx = self.styles( [], 0) def styles(self, defaults, defaultStyle): list = defaults style = defaultStyle if "STYLE"==self.token: self.getToken() i = 0 Not = False while ((i%2==1 and ("|"==self.token or "NOT"==self.token)) or (i%2==0)) and not self.token==None: Not = False; if "NOT"==self.token: Not = True self.getToken() i += 1 if self.token!="|": if self.token in win32con.__dict__: value = getattr(win32con,self.token) else: if self.token in commctrl.__dict__: value = getattr(commctrl,self.token) else: value = 0 if Not: list.append("NOT "+self.token) self.debug("styles add Not",self.token, value) style &= ~value else: list.append(self.token) self.debug("styles add", self.token, value) style |= value self.getToken() self.debug("style is ",style) return style, list def dialogCaption(self, dlg): if "CAPTION"==self.token: self.getToken() self.token = self.token[1:-1] self.debug("Caption is:",self.token) dlg.caption = self.token self.getToken() def dialogFont(self, dlg): if "FONT"==self.token: self.getToken() dlg.fontSize = int(self.token) self.getCommaToken() self.getToken() # Font name dlg.font = self.token[1:-1] # it's quoted self.getToken() while "BEGIN"!=self.token: self.getToken() def controls(self, dlg): if self.token=="BEGIN": self.getToken() while self.token!="END": control = ControlDef() control.controlType = self.token; #print self.token self.getToken() if self.token[0:1]=='"': control.label = self.token[1:-1] self.getCommaToken() self.getToken() elif self.token.isdigit(): control.label = self.token self.getCommaToken() self.getToken() # msvc seems to occasionally replace "IDC_STATIC" with -1 if self.token=='-': if self.getToken() != '1': raise RuntimeError, \ "Negative literal in rc script (other than -1) - don't know what to do" self.token = "IDC_STATIC" control.id = self.token control.idNum = self.addId(control.id) self.getCommaToken() if control.controlType == "CONTROL": self.getToken() control.subType = self.token[1:-1] # Styles self.getCommaToken() self.getToken() control.style, control.styles = self.styles([], defaultControlStyle) #self.getToken() #, # Rect control.x = int(self.getToken()) self.getCommaToken() control.y = int(self.getToken()) self.getCommaToken() control.w = int(self.getToken()) self.getCommaToken() self.getToken() control.h = int(self.token) self.getToken() if self.token==",": self.getToken() control.style, control.styles = self.styles([], defaultControlStyle) #print control.toString() dlg.controls.append(control) def ParseDialogs(rc_file): rcp = RCParser() try: rcp.loadDialogs(rc_file) except: lex = getattr(rcp, "lex", None) if lex: print "ERROR parsing dialogs at line", lex.lineno print "Next 10 tokens are:" for i in range(10): print lex.get_token(), print raise return rcp if __name__=='__main__': rc_file = os.path.join(os.path.dirname(__file__), "dialogs.rc") d = ParseDialogs(rc_file) import pprint for id, ddef in d.dialogs.items(): print "Dialog %s (%d controls)" % (id, len(ddef)) pprint.pprint(ddef) print