]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/tools/pywxrc.py
Fixed Cut command, added support for MenuBar inside Frame/Dialog.
[wxWidgets.git] / wxPython / wx / tools / pywxrc.py
1 #----------------------------------------------------------------------
2 # Name: wx.tools.pywxrc
3 # Purpose: XML resource compiler
4 #
5 # Author: Robin Dunn
6 # Based on wxrc.cpp by Vaclav Slavik, Eduardo Marques
7 # Ported to Python in order to not require yet another
8 # binary in wxPython distributions
9 #
10 # RCS-ID: $Id$
11 # Copyright: (c) 2004 by Total Control Software, 2000 Vaclav Slavik
12 # Licence: wxWindows license
13 #----------------------------------------------------------------------
14
15 """
16 pywxrc -- XML resource compiler
17
18 Usage: wxrc [-h] [-v] [-e] [-c] [-p] [-g] [-n <str>] [-o <str>] input file(s)...
19 -h, --help show help message
20 -v, --verbose be verbose
21 -e, --extra-cpp-code output C++ header file with XRC derived classes
22 -c, --cpp-code output C++ source rather than .xrs file
23 -p, --python-code output wxPython source rather than .rsc file
24 -g, --gettext output list of translatable strings (to stdout or file if -o used)
25 -n, --function str C++/Python function name (with -c or -p) [InitXmlResource]
26 -o, --output str output file [resource.xrs/cpp/py]
27 """
28
29 import sys, os, getopt, glob
30 import wx
31 import wx.xrc
32
33
34 #----------------------------------------------------------------------
35
36 class XRCWidgetData:
37 def __init__(self, vname, vclass):
38 self.name = vname
39 self.klass = vclass
40 def GetName(self):
41 return self.name
42 def GetClass(self):
43 return self.klass
44
45
46 #----------------------------------------------------------------------
47
48 class XRCWndClassData:
49 def __init__(self, className, parentClassName, node):
50 self.className = className
51 self.parentClassName = parentClassName
52 self.BrowseXmlNode(node.GetChildren())
53 self.wdata = []
54
55
56 def BrowseXmlNode(self, node):
57 while node:
58 if node.GetName() == "object" and node.HasProp("class") and node.HasProp("name"):
59 classVal = node.GetPropVal("class", "")
60 nameVal = node.GetPropVal("name", "")
61 self.wdata.append(XRCWidgetData(nameVal, classVal))
62 children = node.GetChildren()
63 if children:
64 self.BrowseXmlNode(children)
65 node = node.GetNext()
66
67
68 def GetWidgetData(self):
69 return self.wdata
70
71
72 def IsRealClass(self, name):
73 if name in ['tool', 'unknown', 'notebookpage', 'separator',
74 'sizeritem', 'wxMenuItem']:
75 return False
76 else:
77 return True
78
79
80 def GenerateHeaderCode(self, file):
81 file.write("class %s : public %s {\nprotected:\n" % (self.className, self.parentClassName))
82
83 for w in self.wdata:
84 if not self.IsRealClass(w.GetClass()):
85 continue
86 if not w.GetName():
87 continue
88 file.write(" " + w.GetClass() + "* " + w.GetName() + ";\n")
89
90 file.write("\nprivate:\n void InitWidgetsFromXRC(){\n",
91 + " wxXmlResource::Get()->LoadObject(this,NULL,\""
92 + self.className
93 + "\",\""
94 + self.parentClassName
95 + "\");\n");
96
97 for w in self.wdata:
98 if not self.IsRealClass(w.GetClass()):
99 continue
100 if not w.GetName():
101 continue
102 file.write( " "
103 + w.GetName()
104 + " = XRCCTRL(*this,\""
105 + w.GetName()
106 + "\","
107 + w.GetClass()
108 + ");\n")
109
110 file.write(" }\n")
111 file.write("public:\n"
112 + self.className
113 + "::"
114 + self.className
115 + "(){\n"
116 + " InitWidgetsFromXRC();\n"
117 + " }\n"
118 + "};\n")
119
120
121
122 #----------------------------------------------------------------------
123
124
125 class XmlResApp:
126 def __init__(self):
127 self.flagVerbose = False
128 self.flagCPP = False
129 self.flagH = False
130 self.flagPython = False
131 self.flagGettext = False
132 self.parOutput = ""
133 self.parFuncname = "InitXmlResource"
134 self.parFiles = []
135 self.aXRCWndClassData = []
136
137
138 #--------------------------------------------------
139 def main(self, args):
140 try:
141 opts, args = getopt.getopt(args, "hvecpgn:o:",
142 "help verbose extra-cpp-code cpp-code python-code gettext function= output=".split())
143 except getopt.GetoptError:
144 print __doc__
145 sys.exit(1)
146
147 for opt, val in opts:
148 if opt in ["-h", "--help"]:
149 print __doc__
150 sys.exit(1)
151
152 if opt in ["-v", "--verbose"]:
153 self.flagVerbose = True
154
155 if opt in ["-e", "--extra-cpp-code"]:
156 self.flagH = True
157
158 if opt in ["-c", "--cpp-code"]:
159 self.flagCPP = True
160
161 if opt in ["-p", "--python-code"]:
162 self.flagPython = True
163
164 if opt in ["-g", "--gettext"]:
165 self.flagGettext = True
166
167 if opt in ["-n", "--function"]:
168 self.parFuncname = val
169
170 if opt in ["-o", "--output"]:
171 self.parOutput = val
172
173 if self.flagCPP + self.flagPython + self.flagGettext == 0:
174 print __doc__
175 print "\nYou must specify one of -c, -p or -g!\n"
176 sys.exit(1)
177
178 if self.flagCPP + self.flagPython + self.flagGettext > 1:
179 print __doc__
180 print "\n-c, -p and -g are mutually exclusive, specify only 1!\n"
181 sys.exit(1)
182
183
184 if self.parOutput:
185 self.parOutput = os.path.normpath(self.parOutput)
186 self.parOutputPath = os.path.split(self.parOutput)[0]
187 else:
188 self.parOutputPath = "."
189 if self.flagCPP:
190 self.parOutput = "resource.cpp"
191 elif self.flagPython:
192 self.parOutput = "resource.py"
193 elif self.flagGettext:
194 self.parOutput = ""
195 else:
196 self.parOutput = "resource.xrs"
197
198 if not args:
199 print __doc__
200 sys.exit(1)
201 for arg in args:
202 self.parFiles += glob.glob(arg)
203
204 self.retCode = 0
205 if self.flagGettext:
206 self.OutputGettext()
207 else:
208 self.CompileRes()
209
210
211
212 #--------------------------------------------------
213 def CompileRes(self):
214 files = self.PrepareTempFiles()
215 try:
216 os.unlink(self.parOutput)
217 except OSError:
218 pass
219
220 if not self.retCode:
221 if self.flagCPP:
222 self.MakePackageCPP(files)
223 if self.flagH:
224 self.GenCPPHeader()
225
226 elif self.flagPython:
227 self.MakePackagePython(files)
228
229 else:
230 self.MakePackageZIP(files)
231
232 self.DeleteTempFiles(files)
233
234
235 #--------------------------------------------------
236 def OutputGettext(self):
237 pass
238
239
240 #--------------------------------------------------
241 def GetInternalFileName(self, name, flist):
242 name2 = name;
243 name2 = name2.replace(":", "_")
244 name2 = name2.replace("/", "_")
245 name2 = name2.replace("\\", "_")
246 name2 = name2.replace("*", "_")
247 name2 = name2.replace("?", "_")
248
249 s = os.path.split(self.parOutput)[1] + "$" + name2
250
251 if os.path.exists(s) and s not in flist:
252 i = 0
253 while True:
254 s = os.path.split(self.parOutput)[1] + ("$%s%03d" % (name2, i))
255 if not os.path.exists(s) or s in flist:
256 break
257 return s;
258
259
260 #--------------------------------------------------
261 def PrepareTempFiles(self):
262 flist = []
263 for f in self.parFiles:
264 if self.flagVerbose:
265 print "processing %s..." % f
266
267 doc = wx.xrc.EmptyXmlDocument()
268
269 if not doc.Load(f):
270 print "Error parsing file", f
271 self.retCode = 1
272 continue
273
274 path, name = os.path.split(f)
275 name, ext = os.path.splitext(name)
276
277 self.FindFilesInXML(doc.GetRoot(), flist, path)
278 if self.flagH:
279 node = doc.GetRoot().GetChildren()
280 while node:
281 if node.GetName() == "object" and node.HasProp("class") and node.HasProp("name"):
282 classVal = node.GetPropVal("class", "")
283 nameVal = node.GetPropVal("name", "")
284 self.aXRCWndClassData.append(XRCWidgetData(nameVal, classVal))
285 node = node.GetNext()
286 internalName = self.GetInternalFileName(f, flist)
287
288 doc.Save(os.path.join(self.parOutputPath, internalName))
289 flist.append(internalName)
290
291 return flist
292
293
294 #--------------------------------------------------
295 # Does 'node' contain filename information at all?
296 def NodeContainsFilename(self, node):
297 # Any bitmaps:
298 if node.GetName() == "bitmap":
299 return True
300
301 if node.GetName() == "icon":
302 return True
303
304 # URLs in wxHtmlWindow:
305 if node.GetName() == "url":
306 return True
307
308 # wxBitmapButton:
309 parent = node.GetParent()
310 if parent != None and \
311 parent.GetPropVal("class", "") == "wxBitmapButton" and \
312 (node.GetName() == "focus" or node.etName() == "disabled" or
313 node.GetName() == "selected"):
314 return True
315
316 # wxBitmap or wxIcon toplevel resources:
317 if node.GetName() == "object":
318 klass = node.GetPropVal("class", "")
319 if klass == "wxBitmap" or klass == "wxIcon":
320 return True
321
322 return False
323
324 #--------------------------------------------------
325 # find all files mentioned in structure, e.g. <bitmap>filename</bitmap>
326 def FindFilesInXML(self, node, flist, inputPath):
327 # Is 'node' XML node element?
328 if node is None: return
329 if node.GetType() != wx.xrc.XML_ELEMENT_NODE: return
330
331 containsFilename = self.NodeContainsFilename(node);
332
333 n = node.GetChildren()
334 while n:
335 if (containsFilename and
336 (n.GetType() == wx.xrc.XML_TEXT_NODE or
337 n.GetType() == wx.xrc.XML_CDATA_SECTION_NODE)):
338
339 if os.path.isabs(n.GetContent()) or inputPath == "":
340 fullname = n.GetContent()
341 else:
342 fullname = os.path.join(inputPath, n.GetContent())
343
344 if self.flagVerbose:
345 print "adding %s..." % fullname
346
347 filename = self.GetInternalFileName(n.GetContent(), flist)
348 n.SetContent(filename)
349
350 if filename not in flist:
351 flist.append(filename)
352
353 inp = open(fullname)
354 out = open(os.path.join(self.parOutputPath, filename), "w")
355 out.write(inp.read())
356
357 # subnodes:
358 if n.GetType() == wx.xrc.XML_ELEMENT_NODE:
359 self.FindFilesInXML(n, flist, inputPath);
360
361 n = n.GetNext()
362
363
364
365 #--------------------------------------------------
366 def DeleteTempFiles(self, flist):
367 for f in flist:
368 os.unlink(os.path.join(self.parOutputPath, f))
369
370
371 #--------------------------------------------------
372 def MakePackageZIP(self, flist):
373 files = " ".join(flist)
374
375 if self.flagVerbose:
376 print "compressing %s..." % self.parOutput
377
378 cwd = os.getcwd()
379 os.chdir(self.parOutputPath)
380 cmd = "zip -9 -j "
381 if not self.flagVerbose:
382 cmd += "-q "
383 cmd += self.parOutput + " " + files
384
385 from distutils.spawn import spawn
386 try:
387 spawn(cmd.split())
388 success = True
389 except:
390 success = False
391
392 os.chdir(cwd)
393
394 if not success:
395 print "Unable to execute zip program. Make sure it is in the path."
396 print "You can download it at http://www.cdrom.com/pub/infozip/"
397 self.retCode = 1
398
399
400 #--------------------------------------------------
401 def FileToCppArray(self, filename, num):
402 output = []
403 buffer = open(filename, "rb").read()
404 lng = len(buffer)
405
406 output.append("static size_t xml_res_size_%d = %d;\n" % (num, lng))
407 output.append("static unsigned char xml_res_file_%d[] = {\n" % num)
408 # we cannot use string literals because MSVC is dumb wannabe compiler
409 # with arbitrary limitation to 2048 strings :(
410
411 linelng = 0
412 for i in xrange(lng):
413 tmp = "%i" % ord(buffer[i])
414 if i != 0: output.append(',')
415 if linelng > 70:
416 linelng = 0
417 output.append("\n")
418
419 output.append(tmp)
420 linelng += len(tmp)+1
421
422 output.append("};\n\n")
423
424 return "".join(output)
425
426
427
428 #--------------------------------------------------
429 def MakePackageCPP(self, flist):
430 file = open(self.parOutput, "wt")
431
432 if self.flagVerbose:
433 print "creating C++ source file %s..." % self.parOutput
434
435 file.write("""\
436 //
437 // This file was automatically generated by wxrc, do not edit by hand.
438 //
439
440 #include <wx/wxprec.h>
441
442 #ifdef __BORLANDC__
443 #pragma hdrstop
444 #endif
445
446 #ifndef WX_PRECOMP
447 #include <wx/wx.h>
448 #endif
449
450 #include <wx/filesys.h>
451 #include <wx/fs_mem.h>
452 #include <wx/xrc/xmlres.h>
453 #include <wx/xrc/xh_all.h>
454
455 """)
456
457 num = 0
458 for f in flist:
459 file.write(self.FileToCppArray(os.path.join(self.parOutputPath, f), num))
460 num += 1
461
462
463 file.write("void " + self.parFuncname + "()\n")
464 file.write("""\
465 {
466
467 // Check for memory FS. If not present, load the handler:
468 {
469 wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/dummy_file\"), wxT(\"dummy one\"));
470 wxFileSystem fsys;
471 wxFSFile *f = fsys.OpenFile(wxT(\"memory:XRC_resource/dummy_file\"));
472 wxMemoryFSHandler::RemoveFile(wxT(\"XRC_resource/dummy_file\"));
473 if (f) delete f;
474 else wxFileSystem::AddHandler(new wxMemoryFSHandler);
475 }
476 """);
477
478 for i in range(len(flist)):
479 file.write(" wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/" + flist[i])
480 file.write("\"), xml_res_file_%i, xml_res_size_%i);\n" %(i, i))
481
482
483 for i in range(len(self.parFiles)):
484 file.write(" wxXmlResource::Get()->Load(wxT(\"memory:XRC_resource/" +
485 self.GetInternalFileName(self.parFiles[i], flist) +
486 "\"));\n")
487
488 file.write("}\n")
489
490
491 #--------------------------------------------------
492 def GenCPPHeader(self):
493 path, name = os.path.split(self.parOutput)
494 name, ext = os.path.splitext(name)
495 heaFileName = name+'.h'
496
497 file = open(heaFileName, "wt")
498 file.write("""\
499 //
500 // This file was automatically generated by wxrc, do not edit by hand.
501 //
502 """);
503 file.write("#ifndef __" + name + "_h__\n")
504 file.write("#define __" + name + "_h__\n")
505
506 for data in self.aXRCWndClassData:
507 data.GenerateHeaderCode(file)
508
509 file.write("\nvoid \n" + self.parFuncname + "();\n#endif\n")
510
511
512 #--------------------------------------------------
513 def FileToPythonArray(self, filename, num):
514 output = []
515 buffer = open(filename, "rb").read()
516 lng = len(buffer)
517
518 output.append(" xml_res_file_%d = '''\\\n" % num)
519
520 linelng = 0
521 for i in xrange(lng):
522 s = buffer[i]
523 c = ord(s)
524 if s == '\n':
525 tmp = s
526 linelng = 0
527 elif c < 32 or c > 127 or s == "'":
528 tmp = "\\x%02x" % c
529 elif s == "\\":
530 tmp = "\\\\"
531 else:
532 tmp = s
533
534 if linelng > 70:
535 linelng = 0
536 output.append("\\\n")
537
538 output.append(tmp)
539 linelng += len(tmp)
540
541 output.append("'''\n\n")
542
543 return "".join(output)
544
545 #--------------------------------------------------
546 def MakePackagePython(self, flist):
547 file = open(self.parOutput, "wt")
548
549 if self.flagVerbose:
550 print "creating Python source file %s..." % self.parOutput
551
552 file.write("""\
553 #
554 # This file was automatically generated by wxrc, do not edit by hand.
555 #
556
557 import wx
558 import wx.xrc
559
560 """)
561 file.write("def " + self.parFuncname + "():\n")
562
563 num = 0
564 for f in flist:
565 file.write(self.FileToPythonArray(os.path.join(self.parOutputPath, f), num))
566 num += 1
567
568 file.write("""
569
570 # check if the memory filesystem handler has been loaded yet, and load it if not
571 wx.MemoryFSHandler.AddFile('XRC_resource/dummy_file', 'dummy value')
572 fsys = wx.FileSystem()
573 f = fsys.OpenFile('memory:XRC_resource/dummy_file')
574 wx.MemoryFSHandler.RemoveFile('XRC_resource/dummy_file')
575 if f is not None:
576 f.Destroy()
577 else:
578 wx.FileSystem.AddHandler(wx.MemoryFSHandler())
579
580 # load all the strings as memory files and load into XmlRes
581 """)
582
583 for i in range(len(flist)):
584 file.write(" wx.MemoryFSHandler.AddFile('XRC_resource/" + flist[i] +
585 "', xml_res_file_%i)\n" % i)
586
587 for pf in self.parFiles:
588 file.write(" wx.xrc.XmlResource.Get().Load('memory:XRC_resource/" +
589 self.GetInternalFileName(pf, flist) + "')\n")
590
591
592 #--------------------------------------------------
593 def OutputGettext(self):
594 strings = self.FindStrings()
595
596 if not self.parOutput:
597 out = sys.stdout
598 else:
599 out = open(self.parOutput, "wt")
600
601 for st in strings:
602 out.write("_(\"%s\")\n" % st)
603
604
605
606 #--------------------------------------------------
607 def FindStrings(self):
608 strings = []
609 for pf in self.parFiles:
610 if self.flagVerbose:
611 print "processing %s..." % pf
612
613 doc = wx.xrc.EmptyXmlDocument()
614 if not doc.Load(pf):
615 print "Error parsing file", pf
616 self.retCode = 1
617 continue
618
619 strings += self.FindStringsInNode(doc.GetRoot())
620
621 return strings
622
623
624 #--------------------------------------------------
625 def ConvertText(self, st):
626 st2 = ""
627 dt = list(st)
628
629 skipNext = False
630 for i in range(len(dt)):
631 if skipNext:
632 skipNext = False
633 continue
634
635 if dt[i] == '_':
636 if dt[i+1] == '_':
637 st2 += '_'
638 skipNext = True
639 else:
640 st2 += '&'
641 elif dt[i] == '\n':
642 st2 += '\\n'
643 elif dt[i] == '\t':
644 st2 += '\\t'
645 elif dt[i] == '\r':
646 st2 += '\\r'
647 elif dt[i] == '\\':
648 if dt[i+1] not in ['n', 't', 'r']:
649 st2 += '\\\\'
650 else:
651 st2 += '\\'
652 elif dt[i] == '"':
653 st2 += '\\"'
654 else:
655 st2 += dt[i]
656
657 return st2
658
659
660
661 #--------------------------------------------------
662 def FindStringsInNode(self, parent):
663 def is_number(st):
664 try:
665 i = int(st)
666 return True
667 except ValueError:
668 return False
669
670 strings = []
671 if parent is None:
672 return strings;
673 child = parent.GetChildren()
674
675 while child:
676 if ((parent.GetType() == wx.xrc.XML_ELEMENT_NODE) and
677 # parent is an element, i.e. has subnodes...
678 (child.GetType() == wx.xrc.XML_TEXT_NODE or
679 child.GetType() == wx.xrc.XML_CDATA_SECTION_NODE) and
680 # ...it is textnode...
681 (
682 parent.GetName() == "label" or
683 (parent.GetName() == "value" and
684 not is_number(child.GetContent())) or
685 parent.GetName() == "help" or
686 parent.GetName() == "longhelp" or
687 parent.GetName() == "tooltip" or
688 parent.GetName() == "htmlcode" or
689 parent.GetName() == "title" or
690 parent.GetName() == "item"
691 )):
692 # ...and known to contain translatable string
693 if (not self.flagGettext or
694 parent.GetPropVal("translate", "1") != "0"):
695
696 strings.append(self.ConvertText(child.GetContent()))
697
698 # subnodes:
699 if child.GetType() == wx.xrc.XML_ELEMENT_NODE:
700 strings += self.FindStringsInNode(child)
701
702 child = child.GetNext()
703
704 return strings
705
706 #---------------------------------------------------------------------------
707
708 def main():
709 XmlResApp().main(sys.argv[1:])
710
711
712 if __name__ == "__main__":
713 main()
714