ctkWrapPythonQt.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. includes.append('#include "%s.h"' % filename_we)
  59. # Extract classname - NOTE: We assume the filename matches the associated class
  60. className = filename_we
  61. if extra_verbose:
  62. print("\tclassName:%s" % className)
  63. # Extract parent classname
  64. parentClassName = None
  65. # Read input files
  66. with open(input_file) as f:
  67. content = f.read()
  68. # Skip wrapping if file do NOT contain Q_OBJECT
  69. if 'Q_OBJECT' not in content:
  70. if extra_verbose:
  71. print("\tskipping - No Q_OBJECT macro")
  72. continue
  73. # Skip wrapping if constructor doesn't match:
  74. # my_class()
  75. # my_class(QObject* newParent ...)
  76. # my_class(QWidget* newParent ...)
  77. # Constructor with either QWidget or QObject as first parameter
  78. regex = r"[^~]%s[\s\n]*\([\s\n]*((QObject|QWidget)[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\)|\)))" % className
  79. res = re.search(regex, content, re.MULTILINE)
  80. if res is None:
  81. if extra_verbose:
  82. print("\tskipping - Missing expected constructor signature")
  83. continue
  84. # Skip wrapping if object has a virtual pure method
  85. # "x3b" is the unicode for semicolon
  86. regex = r"virtual[\w\n\s\*\(\)]+\=[\s\n]*(0|NULL)[\s\n]*\x3b"
  87. res = re.search(regex, content, re.MULTILINE)
  88. if res is not None:
  89. if extra_verbose:
  90. print("skipping - Contains a virtual pure method")
  91. continue
  92. if parentClassName is None:
  93. # Does constructor signature is of the form: myclass()
  94. regex = r"[^~]%s[\s\n]*\([\s\n]*\)" % className
  95. res = re.search(regex, content, re.MULTILINE)
  96. if res is not None:
  97. parentClassName = ""
  98. if extra_verbose:
  99. print("\tconstructor of the form: %s()" % className)
  100. if parentClassName is None:
  101. # Does constructor signature is of the form: myclass(QObject * parent ...)
  102. regex = r"%s[\s\n]*\([\s\n]*QObject[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\))" % className
  103. res = re.search(regex, content, re.MULTILINE)
  104. if res is not None:
  105. parentClassName = "QObject"
  106. if extra_verbose:
  107. print("\tconstructor of the form: %s(QObject * parent ... )" % className)
  108. if parentClassName is None:
  109. # Does constructor signature is of the form: myclass(QWidget * parent ...)
  110. regex = r"%s[\s\n]*\([\s\n]*QWidget[\s\n]*\*[\s\n]*\w+[\s\n]*(\=[\s\n]*(0|NULL)|,.*\=.*\)|\))" % className
  111. res = re.search(regex, content, re.MULTILINE)
  112. if res is not None:
  113. parentClassName = "QWidget"
  114. if extra_verbose:
  115. print("\tconstructor of the form: %s(QWidget * parent ... )" % className)
  116. # Generate PythonQtWrapper class
  117. if parentClassName == "QObject" or parentClassName == "QWidget":
  118. pythonqtWrappers.append(
  119. PYTHONQT_WRAPPER_WITH_PARENT.substitute(className = className, parentClassName = parentClassName))
  120. elif parentClassName == "":
  121. pythonqtWrappers.append(PYTHONQT_WRAPPER_WITHOUT_PARENT.substitute(className = className))
  122. else: # Case parentClassName is None
  123. raise Exception("Problem wrapping %s" % input_file)
  124. # Generate code allowing to register the class metaobject and its associated "light" wrapper
  125. registerclasses.append(
  126. Template("""
  127. PythonQt::self()->registerClass(
  128. &${className}::staticMetaObject, "${target}",
  129. PythonQtCreateObject<PythonQtWrapper_${className}>);
  130. """).substitute(className = className, target = target))
  131. output_header = output_dir + "/" + namespace + "_" + target + ".h"
  132. if extra_verbose:
  133. print("output_header: %s" % output_header)
  134. # Write master include file
  135. with open(output_header, "w") as f:
  136. f.write(Template(
  137. """
  138. //
  139. // File auto-generated by ctkWrapPythonQt.py
  140. //
  141. #ifndef __${namespace}_${target}_h
  142. #define __${namespace}_${target}_h
  143. #include <QWidget>
  144. ${includes}
  145. ${pythonqtWrappers}
  146. #endif
  147. """).substitute(namespace = namespace, target = target, includes = '\n'.join(includes), pythonqtWrappers = '\n'.join(pythonqtWrappers)))
  148. output_cpp = output_dir + "/" + namespace + "_" + target + "_init.cpp"
  149. if extra_verbose:
  150. print("output_cpp: %s" % output_cpp)
  151. with open(output_cpp , "w") as f:
  152. # Write wrapper header
  153. f.write(Template(
  154. """
  155. //
  156. // File auto-generated by ctkWrapPythonQt.py
  157. //
  158. #include <PythonQt.h>
  159. #include "${namespace}_${target}.h"
  160. void PythonQt_init_${namespace}_${target}(PyObject* module)
  161. {
  162. Q_UNUSED(module);
  163. ${registerclasses}
  164. }
  165. """).substitute(namespace = namespace, target = target, registerclasses = '\n'.join(registerclasses)))
  166. if __name__ == '__main__':
  167. from optparse import OptionParser
  168. usage = "usage: %prog [options] <output_file> <input_file> [<input_file1> [...]]"
  169. parser = OptionParser(usage=usage)
  170. parser.add_option("-t", "--target",
  171. dest="target", action="store", type="string",
  172. help="Name of the associated library")
  173. parser.add_option("-n", "--namespace",
  174. dest="namespace", action="store", type="string",
  175. help="Wrapping namespace")
  176. parser.add_option("--output-dir",
  177. dest="output_dir", action="store", type="string",
  178. help="Output directory")
  179. parser.add_option("-v", "--verbose",
  180. dest="verbose", action="store_true",
  181. help="Print verbose information")
  182. parser.add_option("--extra-verbose",
  183. dest="extra_verbose", action="store_true",
  184. help="Print extra verbose information")
  185. (options, args) = parser.parse_args()
  186. #if len(args) < 2:
  187. # parser.error("arguments '%s' are required !" % '<output_file> <input_file>')
  188. if options.extra_verbose:
  189. options.verbose = True
  190. ctk_wrap_pythonqt(options.target, options.namespace, options.output_dir, args, options.extra_verbose)
  191. if options.verbose:
  192. print("Wrapped %d files" % len(args))