ctkWrapPythonQt.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import errno
  2. import os
  3. import re
  4. from string import Template
  5. PYTHONQT_WRAPPER_WITH_PARENT = Template("""
  6. //-----------------------------------------------------------------------------
  7. class PythonQtWrapper_${className} : public QObject
  8. {
  9. Q_OBJECT
  10. public:
  11. public Q_SLOTS:
  12. ${className}* new_${className}(${parentClassName}* parent = 0)
  13. {
  14. return new ${className}(parent);
  15. }
  16. void delete_${className}(${className}* obj) { delete obj; }
  17. };
  18. """)
  19. PYTHONQT_WRAPPER_WITHOUT_PARENT = Template("""
  20. //-----------------------------------------------------------------------------
  21. class PythonQtWrapper_${className} : public QObject
  22. {
  23. Q_OBJECT
  24. public:
  25. public Q_SLOTS:
  26. ${className}* new_${className}()
  27. {
  28. return new ${className}();
  29. }
  30. void delete_${className}(${className}* obj) { delete obj; }
  31. };
  32. """)
  33. def _mkdir_p(path):
  34. """See """
  35. try:
  36. os.makedirs(path)
  37. except OSError as exc:
  38. if exc.errno == errno.EEXIST and os.path.isdir(path):
  39. pass
  40. else: raise
  41. def ctk_wrap_pythonqt(target, namespace, output_dir, input_files, extra_verbose):
  42. if extra_verbose:
  43. print("target: %s" % target)
  44. print("namespace: %s" % namespace)
  45. print("output_dir: %s" % output_dir)
  46. print("input_files: %s" % input_files)
  47. _mkdir_p(output_dir)
  48. includes = []
  49. pythonqtWrappers = []
  50. registerclasses = []
  51. namespace = namespace.replace('.', '_')
  52. for input_file in input_files:
  53. filename = os.path.basename(input_file)
  54. if extra_verbose:
  55. print("Wrapping %s" % filename)
  56. # what is the filename without the extension
  57. filename_we = os.path.splitext(filename)[0]
  58. # Extract classname - NOTE: We assume the filename matches the associated class
  59. className = filename_we
  60. if extra_verbose:
  61. print("\tclassName:%s" % className)
  62. # Extract parent classname
  63. parentClassName = None
  64. # Read input files
  65. with open(input_file) as f:
  66. content = f.read()
  67. # Skip wrapping if file do NOT contain Q_OBJECT
  68. if 'Q_OBJECT' not in content:
  69. if extra_verbose:
  70. print("\tskipping - No Q_OBJECT macro")
  71. continue
  72. # Skip wrapping if constructor doesn't match:
  73. # my_class()
  74. # my_class(QObject* newParent ...)
  75. # my_class(QWidget* newParent ...)
  76. # Constructor with either QWidget or QObject as first parameter
  77. regex = r"[^~]%s[\s\n]*\([\s\n]*((QObject|QWidget)[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\)|\)))" % className
  78. res = re.search(regex, content, re.MULTILINE)
  79. if res is None:
  80. if extra_verbose:
  81. print("\tskipping - Missing expected constructor signature")
  82. continue
  83. # Skip wrapping if object has a virtual pure method
  84. # "x3b" is the unicode for semicolon
  85. regex = r"virtual[\w\n\s\*\(\)]+\=[\s\n]*(0|NULL)[\s\n]*\x3b"
  86. res = re.search(regex, content, re.MULTILINE)
  87. if res is not None:
  88. if extra_verbose:
  89. print("skipping - Contains a virtual pure method")
  90. continue
  91. if parentClassName is None:
  92. # Does constructor signature is of the form: myclass()
  93. regex = r"[^~]%s[\s\n]*\([\s\n]*\)" % className
  94. res = re.search(regex, content, re.MULTILINE)
  95. if res is not None:
  96. parentClassName = ""
  97. if extra_verbose:
  98. print("\tconstructor of the form: %s()" % className)
  99. if parentClassName is None:
  100. # Does constructor signature is of the form: myclass(QObject * parent ...)
  101. regex = r"%s[\s\n]*\([\s\n]*QObject[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\))" % className
  102. res = re.search(regex, content, re.MULTILINE)
  103. if res is not None:
  104. parentClassName = "QObject"
  105. if extra_verbose:
  106. print("\tconstructor of the form: %s(QObject * parent ... )" % className)
  107. if parentClassName is None:
  108. # Does constructor signature is of the form: myclass(QWidget * parent ...)
  109. regex = r"%s[\s\n]*\([\s\n]*QWidget[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\))" % className
  110. res = re.search(regex, content, re.MULTILINE)
  111. if res is not None:
  112. parentClassName = "QWidget"
  113. if extra_verbose:
  114. print("\tconstructor of the form: %s(QWidget * parent ... )" % className)
  115. if parentClassName is not None:
  116. includes.append('#include "%s.h"' % filename_we)
  117. # Generate PythonQtWrapper class
  118. if parentClassName == "QObject" or parentClassName == "QWidget":
  119. pythonqtWrappers.append(
  120. PYTHONQT_WRAPPER_WITH_PARENT.substitute(className = className, parentClassName = parentClassName))
  121. elif parentClassName == "":
  122. pythonqtWrappers.append(PYTHONQT_WRAPPER_WITHOUT_PARENT.substitute(className = className))
  123. else: # Case parentClassName is None
  124. raise Exception("Problem wrapping %s" % input_file)
  125. # Generate code allowing to register the class metaobject and its associated "light" wrapper
  126. registerclasses.append(
  127. Template("""
  128. PythonQt::self()->registerClass(
  129. &${className}::staticMetaObject, "${target}",
  130. PythonQtCreateObject<PythonQtWrapper_${className}>);
  131. """).substitute(className = className, target = target))
  132. output_header = output_dir + "/" + namespace + "_" + target + ".h"
  133. if extra_verbose:
  134. print("output_header: %s" % output_header)
  135. # Write master include file
  136. with open(output_header, "w") as f:
  137. f.write(Template(
  138. """
  139. //
  140. // File auto-generated by ctkWrapPythonQt.py
  141. //
  142. #ifndef __${namespace}_${target}_h
  143. #define __${namespace}_${target}_h
  144. #include <QWidget>
  145. ${includes}
  146. ${pythonqtWrappers}
  147. #endif
  148. """).substitute(namespace = namespace, target = target, includes = '\n'.join(includes), pythonqtWrappers = '\n'.join(pythonqtWrappers)))
  149. output_cpp = output_dir + "/" + namespace + "_" + target + "_init.cpp"
  150. if extra_verbose:
  151. print("output_cpp: %s" % output_cpp)
  152. with open(output_cpp , "w") as f:
  153. # Write wrapper header
  154. f.write(Template(
  155. """
  156. //
  157. // File auto-generated by ctkWrapPythonQt.py
  158. //
  159. #include <PythonQt.h>
  160. #include "${namespace}_${target}.h"
  161. void PythonQt_init_${namespace}_${target}(PyObject* module)
  162. {
  163. Q_UNUSED(module);
  164. ${registerclasses}
  165. }
  166. """).substitute(namespace = namespace, target = target, registerclasses = '\n'.join(registerclasses)))
  167. if __name__ == '__main__':
  168. from optparse import OptionParser
  169. usage = "usage: %prog [options] <output_file> <input_file> [<input_file1> [...]]"
  170. parser = OptionParser(usage=usage)
  171. parser.add_option("-t", "--target",
  172. dest="target", action="store", type="string",
  173. help="Name of the associated library")
  174. parser.add_option("-n", "--namespace",
  175. dest="namespace", action="store", type="string",
  176. help="Wrapping namespace")
  177. parser.add_option("--output-dir",
  178. dest="output_dir", action="store", type="string",
  179. help="Output directory")
  180. parser.add_option("-v", "--verbose",
  181. dest="verbose", action="store_true",
  182. help="Print verbose information")
  183. parser.add_option("--extra-verbose",
  184. dest="extra_verbose", action="store_true",
  185. help="Print extra verbose information")
  186. (options, args) = parser.parse_args()
  187. #if len(args) < 2:
  188. # parser.error("arguments '%s' are required !" % '<output_file> <input_file>')
  189. if options.extra_verbose:
  190. options.verbose = True
  191. ctk_wrap_pythonqt(options.target, options.namespace, options.output_dir, args, options.extra_verbose)
  192. if options.verbose:
  193. print("Wrapped %d files" % len(args))