Browse Source

ENH: Added ctkMacroWrapPythonQt

This macro provides two ways of wrapping CTK libraries:
 1) A light wrapping
 2) A Full wrapping

Using PyhtonQt, all slots and methods added to the meta-object system
using Q_PROPERTY will be automatically available in Python. Nevertheless,
such mechanism prevent us from instantiating object or even calling
method which are not slot.



The Light wrapping takes care of wrapping both the constructor and
and destructor of class embedding the Q_OBJECT macro. Doing so allows
to instantiate and delete object from python. To enable the light wrapping,
an option entitled CTK_WRAP_PYTHONQT_LIGHT will be available after
the option CTK_LIB_Scripting/Python/Core is enabled.
Note also that the light wrapping is very fast (compare to the full wrapping).

Two log files (a)ctkMacroWrapPythonQt_log.txt and (b)ctkScriptWrapPythonQt_Light_log.txt
will be created in the current binary directory of a given library.
(a) will contain the list of file and the reason why a given class hasn't been wrapped.
(b) will contain the list of class and the constructor signature that will be wrapped.



On the other hand, the full wrapping provides a wrapping of all classes
and methods. Classes don't need to embed the Q_OBJECT macro. It relies
on the PythonQtGenerator tools which is already used by PythonQt to generate
the wrapping of the QtModules (QtGui, QtUitools, QtXml, ..). PythonQtGenerated
has been CMake'ified and will be automatically added as an external project if
CTK_WRAP_PYTHONQT_FULL is enabled. Note also that the typesystem
files used by PythonQtGenerated, which are automatically generated (within
the script ctkScriptWrapPythonQt_Full), specify just the list of classes
 to wrap (See snipped below). It means object type unknown from the meta-object system
(either returned or accepted as a method parameter) won't be handled
properly.
See http://www.pyside.org/docs/apiextractor/typesystem.html
See http://doc.trolltech.com/qtjambi-4.5.2_01/com/trolltech/qt/qtjambi-typesystem.html

Example of generated typesystem file:
 <typesystem package="org.commontk.LibName">
   <object-type name="ctkClass1"/>
   <object-type name="ctkClass2"/>
 </typesystem>



Note also that the macro will automatically exclude a file if:
 - It isn't a regular header (having .h extension)
 - It's a Pimpl header (having _p.h extenstion)
 - It contains virtual pure method

... and if CTK_WRAP_PYTHONQT_LIGHT is enabled, if:
 - It doesn't contain the Q_OBJECT macro
 - The constructor signature doesn't not match one of the following form:
     - myclass(QObject* parent)
     - myclass(QObject* parent = 0)
     - myclass(QObject* parent, Type * arg = 0)
     - myclass(QObject* parent = 0, Type * arg = 0)
     - myclass(QWidget* parent)
     - myclass(QWidget* parent = 0)
     - myclass(QWidget* parent, Type * arg = 0)
     - myclass(QWidget* parent = 0, Type * arg = 0)
     - myclass()


The option CTK_WRAP_PYTHONQT_{LIGHT or FULL} will affect all libraries.
As of today, there is no way of selectively enabled Light or Full wrapping at
either a library or file level.

The ctkMacroWrapPythonQt macro is invoked from ctkMacroBuildLib and the
variable CTK_WRAPPED_LIBRARIES_PYTHONQT is iteratively updated and will
contain the list of all CTK libraries that are wrapped with PythonQt.

If an application wants to be able to load the wrappers associated with
CTK libraries, it could linked against one or all static libraries listed by
CTK_WRAPPED_LIBRARIES_PYTHONQT. Note that the suffix PythonQt should be
added to the listed library names.

Then, the method allowing to initialize the wrapper could be called, for example,
from the overloaded method preInitialization of a custom PythonManager class
derived from ctkAbstractPythonManager.

The signature of the wrapper initialization method has the following form:
  PythonQt_init_org_commontk_<CTK_LIB_NAME>(PyObject* module)

The signature of the macro is the following:
  ctkMacroWrapPythonQt(WRAPPING_NAMESPACE TARGET SRCS_LIST_NAME SOURCES IS_WRAP_FULL)

and its different parameters are:
   WRAPPING_NAMESPACE: Namespace that should contain the library. For example: org.commontk
   TARGET ...........: Name of the wrapped library. For example: CTKWidget
   SRCS_LIST_NAME ...: Name of the variable that should contain the generated wrapper source.
                       For example: KIT_PYTHONQT_SRCS
   SOURCES ..........: List of source files that should be wrapped.
   IS_WRAP_FULL .....: Indicate if a Full wrapping if desired.
Jean-Christophe Fillion-Robin 15 years ago
parent
commit
fff5abe7a7

+ 12 - 0
CMake/ctkMacroBuildLib.cmake

@@ -131,6 +131,18 @@ MACRO(ctkMacroBuildLib)
     DESTINATION ${CTK_INSTALL_INCLUDE_DIR} COMPONENT Development
     )
 
+  IF(CTK_WRAP_PYTHONQT_LIGHT OR CTK_WRAP_PYTHONQT_FULL)
+    ctkMacroWrapPythonQt("org.commontk" ${lib_name}
+      KIT_PYTHONQT_SRCS "${MY_SRCS}" ${CTK_WRAP_PYTHONQT_FULL})
+    ADD_LIBRARY(${lib_name}PythonQt STATIC ${KIT_PYTHONQT_SRCS})
+    TARGET_LINK_LIBRARIES(${lib_name}PythonQt ${lib_name})
+
+    # Update list of libraries wrapped with PythonQt
+    SET(CTK_WRAPPED_LIBRARIES_PYTHONQT
+      ${CTK_WRAPPED_LIBRARIES_PYTHONQT} ${lib_name}
+      CACHE INTERNAL "CTK libraries wrapped using PythonQt" FORCE)
+  ENDIF()
+
 ENDMACRO()
 
 

+ 305 - 0
CMake/ctkMacroWrapPythonQt.cmake

@@ -0,0 +1,305 @@
+###########################################################################
+#
+#  Library:   CTK
+# 
+#  Copyright (c) 2010  Kitware Inc.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.commontk.org/LICENSE
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# 
+###########################################################################
+
+#
+# ctkMacroWrapPythonQt
+#
+
+#
+# Depends on:
+#  PythonQt 
+#  PythonQtGenerator (Only if IS_WRAP_FULL is TRUE)
+#  PythonInterp (See function reSearchFile)
+#
+
+#
+# The different parameters are:
+#
+#    WRAPPING_NAMESPACE: Namespace that should contain the library. For example: org.commontk
+#         
+#    TARGET ...........: Name of the wrapped library. For example: CTKWidget
+# 
+#    SRCS_LIST_NAME ...: Name of the variable that should contain the generated wrapper source.
+#                        For example: KIT_PYTHONQT_SRCS
+#
+#    SOURCES ..........: List of source files that should be wrapped.
+#
+#    IS_WRAP_FULL .....: Indicate if a Full wrapping if desired.
+#
+
+#
+# LOG FILE:
+#   File ctkMacroWrapPythonQt_log.txt will be created in the current directory. 
+#   It will contain the list of file and the reason why a given class hasn't been wrapped.
+#
+
+set(verbose 0)
+
+#
+# Convenient function allowing to log the reason why a given class hasn't been wrapped
+# If verbose=1, it will also be displayed on the standard output
+#
+FUNCTION(ctkMacroWrapPythonQt_log msg)
+  IF(verbose)
+    MESSAGE(${msg})
+  ENDIF()
+  FILE(APPEND "${CMAKE_CURRENT_BINARY_DIR}/ctkMacroWrapPythonQt_log.txt" "${msg}\n")
+ENDFUNCTION()
+
+#
+# Convenient function allowing to invoke re.search(regex, string) using the given interpreter.
+# Note that is_matching will be set to True if there is a match
+#
+FUNCTION(ctkMacroWrapPythonQt_reSearchFile python_exe regex file is_matching)
+
+  set(python_cmd "import re\; f = open('${file}', 'r')\;
+res = re.search\(\"${regex}\", f.read(), re.MULTILINE\)\;
+if res == None: print \"FALSE\"
+else: print \"TRUE\"
+")
+  #message("python_cmd: ${python_cmd}")
+
+  EXECUTE_PROCESS(
+    COMMAND ${python_exe} -c ${python_cmd};
+    RESULT_VARIABLE result
+    OUTPUT_VARIABLE output
+    ERROR_VARIABLE error
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+  
+  IF(result)
+    MESSAGE(SEND_ERROR "reSearchFile - Problem with regex: ${regex}\n${error}")
+  ENDIF()
+  SET(is_matching ${output} PARENT_SCOPE)
+ENDFUNCTION()
+
+MACRO(ctkMacroWrapPythonQt WRAPPING_NAMESPACE TARGET SRCS_LIST_NAME SOURCES IS_WRAP_FULL)
+  
+  # Sanity check
+  IF(IS_WRAP_FULL AND NOT EXISTS "${PYTHONQTGENERATOR_EXECUTABLE}")
+    MESSAGE(SEND_ERROR "PYTHONQTGENERATOR_EXECUTABLE not specified or inexistent when calling ctkMacroWrapPythonQt")
+  ENDIF()
+  
+  find_package(PythonInterp)
+  IF(NOT EXISTS "${PYTHON_EXECUTABLE}")
+    MESSAGE(SEND_ERROR "PYTHON_EXECUTABLE not specified or inexistent when calling ctkMacroWrapPythonQt")
+  ENDIF()
+  
+  # Clear log file
+  FILE(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ctkMacroWrapPythonQt_log.txt" "")
+  
+  # Convert wrapping namespace to subdir
+  STRING(REPLACE "." "_" WRAPPING_NAMESPACE_UNDERSCORE ${WRAPPING_NAMESPACE})
+  
+  SET(SOURCES_TO_WRAP)
+  # For each class
+  FOREACH(FILE ${SOURCES})
+  
+    SET(skip_wrapping FALSE)
+    
+    IF(NOT skip_wrapping)
+      # Skip wrapping if file is NOT regular header
+      IF(NOT ${FILE} MATCHES "^.*\\.[hH]$")
+        SET(skip_wrapping TRUE)
+        ctkMacroWrapPythonQt_log("${FILE}: skipping - Not a regular header")
+      ENDIF()
+    ENDIF()
+    
+    IF(NOT skip_wrapping)
+      # Skip wrapping if file is a pimpl header
+      IF(${FILE} MATCHES "^.*_[pP]\\.[hH]$")
+        SET(skip_wrapping TRUE)
+        ctkMacroWrapPythonQt_log("${FILE}: skipping - Pimpl header (*._p.h)")
+      ENDIF()
+    ENDIF()
+    
+    IF(NOT skip_wrapping)
+      # Skip wrapping if file should excluded
+      SET(skip_wrapping TRUE)
+      GET_SOURCE_FILE_PROPERTY(TMP_WRAP_EXCLUDE ${FILE} WRAP_EXCLUDE)
+      IF(NOT TMP_WRAP_EXCLUDE)
+        SET(skip_wrapping FALSE)
+      ENDIF()
+      IF(skip_wrapping)
+        ctkMacroWrapPythonQt_log("${FILE}: skipping - WRAP_EXCLUDE")
+      ENDIF()
+    ENDIF()
+    
+    # what is the filename without the extension
+    GET_FILENAME_COMPONENT(TMP_FILENAME ${FILE} NAME_WE)
+    
+    # Extract classname - NOTE: We assume the filename matches the associated class
+    SET(className ${TMP_FILENAME})
+    
+    IF(NOT skip_wrapping)
+      # Skip wrapping if IS_WRAP_FULL=FALSE and if file do NOT contain Q_OBJECT
+      IF(NOT IS_WRAP_FULL)
+        file(READ ${CMAKE_CURRENT_SOURCE_DIR}/${FILE} file_content)
+        IF(NOT "${file_content}" MATCHES "Q_OBJECT")
+          SET(skip_wrapping TRUE)
+          ctkMacroWrapPythonQt_log("${FILE}: skipping - No Q_OBJECT macro")
+        ENDIF()
+      ENDIF()
+    ENDIF()
+    
+    IF(NOT skip_wrapping)
+      # Skip wrapping if IS_WRAP_FULL=FALSE and if constructor doesn't match:
+      #    my_class()
+      #    my_class(QObject* newParent ...)
+      #    my_class(QWidget* newParent ...)
+      IF(NOT IS_WRAP_FULL)
+        # Constructor with either QWidget or QObject as first parameter
+        SET(regex "[^~]${className}[\\s\\n]*\\([\\s\\n]*((QObject|QWidget)[\\s\\n]*\\*[\\s\\n]*\\w+[\\s\\n]*(\\=[\\s\\n]*(0|NULL)|,.*\\=.*\\)|\\)|\\)))")
+        ctkMacroWrapPythonQt_reSearchFile(${PYTHON_EXECUTABLE} ${regex} ${CMAKE_CURRENT_SOURCE_DIR}/${FILE} is_matching)
+        IF(NOT is_matching)
+          SET(skip_wrapping TRUE)
+          ctkMacroWrapPythonQt_log("${FILE}: skipping - Missing expected constructor signature")
+        ENDIF()
+      ENDIF()
+    ENDIF()
+    
+    IF(NOT skip_wrapping)
+      # Skip wrapping if object has a virtual pure method 
+      SET(regex "virtual[\\w\\n\\s\\(\\)]+\\=[\\s\\n]*(0|NULL)[\\s\\n]*\\x3b")
+      ctkMacroWrapPythonQt_reSearchFile(${PYTHON_EXECUTABLE} ${regex} ${CMAKE_CURRENT_SOURCE_DIR}/${FILE} is_matching)
+      IF(is_matching)
+        SET(skip_wrapping TRUE)
+        ctkMacroWrapPythonQt_log("${FILE}: skipping - Contains a virtual pure method")
+      ENDIF()
+    ENDIF()
+    
+    # if we should wrap it
+    IF (NOT skip_wrapping)
+      
+      # the input file might be full path so handle that
+      GET_FILENAME_COMPONENT(TMP_FILEPATH ${FILE} PATH)
+      
+      # compute the input filename
+      IF (TMP_FILEPATH)
+        SET(TMP_INPUT ${TMP_FILEPATH}/${TMP_FILENAME}.h) 
+      ELSE (TMP_FILEPATH)
+        SET(TMP_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/${TMP_FILENAME}.h)
+      ENDIF (TMP_FILEPATH)
+        
+      LIST(APPEND SOURCES_TO_WRAP ${TMP_INPUT})
+        
+    ENDIF()
+  ENDFOREACH()
+  
+  # PythonQtGenerator expects a colon ':' separated list
+  SET(INCLUDE_DIRS_TO_WRAP)
+  FOREACH(include ${CTK_BASE_INCLUDE_DIRS})
+    SET(INCLUDE_DIRS_TO_WRAP "${INCLUDE_DIRS_TO_WRAP}:${include}")
+  ENDFOREACH()
+  
+  # Prepare custom_command argument
+  SET(SOURCES_TO_WRAP_ARG)
+  FOREACH(source ${SOURCES_TO_WRAP})
+    SET(SOURCES_TO_WRAP_ARG "${SOURCES_TO_WRAP_ARG}^^${source}")
+  ENDFOREACH()
+  
+  # Define wrap type and wrap intermediate directory
+  SET(wrap_type "Light")
+  SET(wrap_int_dir generated_cpp/${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}/)
+  SET(extra_files )
+  IF(${IS_WRAP_FULL})
+    SET(wrap_type "Full")
+    SET(extra_files ${wrap_int_dir}ctkPythonQt_${TARGET}_masterinclude.h)
+  ENDIF()
+  #message("wrap_type:${wrap_type} - wrap_int_dir:${wrap_int_dir}")
+  
+  # Create intermediate output directory
+  EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${wrap_int_dir})
+  
+  set(wrapper_init_cpp_filename ${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp)
+  set(wrapper_init_cpp_file ${CMAKE_CURRENT_BINARY_DIR}/${wrap_int_dir}${wrapper_init_cpp_filename})
+  
+  # Custom command allow to generate ${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp and 
+  # associated wrappers ${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}{0-N}.cpp
+  ADD_CUSTOM_COMMAND(
+    OUTPUT ${wrap_int_dir}${wrapper_init_cpp_filename} ${extra_files}
+    DEPENDS ${pythonqtgenerator_executable_depends} ${SOURCES_TO_WRAP} ${CTK_CMAKE_DIR}/ctkScriptWrapPythonQt_${wrap_type}.cmake
+    COMMAND ${CMAKE_COMMAND}
+      -DPYTHONQTGENERATOR_EXECUTABLE:FILEPATH=${PYTHONQTGENERATOR_EXECUTABLE}
+      -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE}
+      -DWRAPPING_NAMESPACE:STRING=${WRAPPING_NAMESPACE}
+      -DTARGET:STRING=${TARGET}
+      -DSOURCES:STRING=${SOURCES_TO_WRAP_ARG}
+      -DINCLUDE_DIRS:STRING=${INCLUDE_DIRS_TO_WRAP}
+      -DOUTPUT_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}
+      -DWRAP_INT_DIR:STRING=${wrap_int_dir}
+      -DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}
+      -P ${CTK_CMAKE_DIR}/ctkScriptWrapPythonQt_${wrap_type}.cmake
+    COMMENT "PythonQt ${wrap_type} Wrapping - Generating ${wrapper_init_cpp_filename}"
+    VERBATIM
+    )
+    
+  # Prepare custom_command argument
+  SET(moc_flags_arg)
+  FOREACH(flag ${moc_flags})
+    SET(moc_flags_arg "${moc_flags_arg}^^${flag}")
+  ENDFOREACH()
+  
+  # Use Moc to process the wrapper headers
+  QT4_GET_MOC_FLAGS(moc_flags)
+  
+  SET(wrapper_master_moc_filename moc_${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_all.cpp)
+  SET(wrapper_master_moc_file ${CMAKE_CURRENT_BINARY_DIR}/${wrap_int_dir}${wrapper_master_moc_filename})
+  
+  # Custom command allowing to call moc to process the wrapper headers
+  ADD_CUSTOM_COMMAND(
+    OUTPUT ${wrap_int_dir}${wrapper_master_moc_filename}
+    DEPENDS ${wrap_int_dir}${wrapper_init_cpp_filename} ${extra_files} ${CTK_CMAKE_DIR}/ctkScriptMocPythonQtWrapper.cmake
+    COMMAND ${CMAKE_COMMAND}
+      -DWRAPPING_NAMESPACE:STRING=${WRAPPING_NAMESPACE}
+      -DTARGET:STRING=${TARGET}
+      -DMOC_FLAGS:STRING=${moc_flags_arg}
+      -DWRAP_INT_DIR:STRING=${wrap_int_dir}
+      -DWRAPPER_MASTER_MOC_FILE:STRING=${wrapper_master_moc_file}
+      -DOUTPUT_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}
+      -DQT_MOC_EXECUTABLE:FILEPATH=${QT_MOC_EXECUTABLE}
+      -P ${CTK_CMAKE_DIR}/ctkScriptMocPythonQtWrapper.cmake
+    COMMENT "PythonQt ${wrap_type} Wrapping - Moc'ing ${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET} wrapper headers"
+    VERBATIM
+    )
+  
+  #The following files are generated
+  SET_SOURCE_FILES_PROPERTIES(
+    ${wrap_int_dir}${wrapper_init_cpp_filename}
+    ${wrap_int_dir}${wrapper_master_moc_filename}
+    PROPERTIES GENERATED TRUE)
+    
+  # Create the Init File
+  SET(${SRCS_LIST_NAME} 
+    ${${SRCS_LIST_NAME}}
+    ${wrap_int_dir}${wrapper_init_cpp_filename}
+    ${wrap_int_dir}${wrapper_master_moc_filename})
+  
+  #
+  # Let's include the headers associated with PythonQt
+  #
+  FIND_PACKAGE(PythonQt)
+  IF(NOT PYTHONQT_FOUND)
+    MESSAGE(FATAL_ERROR "error: PythonQt package is required to build ${TARGET}PythonQt")
+  ENDIF()
+  INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIRS} ${PYTHONQT_INCLUDE_DIR})
+
+ENDMACRO()
+

+ 95 - 0
CMake/ctkScriptMocPythonQtWrapper.cmake

@@ -0,0 +1,95 @@
+###########################################################################
+#
+#  Library:   CTK
+# 
+#  Copyright (c) 2010  Kitware Inc.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.commontk.org/LICENSE
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# 
+###########################################################################
+
+#
+# ctkScriptMocPythonQtWrapper
+#
+
+#
+# This script should be invoked either as a CUSTOM_COMMAND 
+# or from the command line using the following syntax:
+#
+#    cmake -DWRAPPING_NAMESPACE:STRING=org.commontk -DTARGET:STRING=MyLib
+#          -DOUTPUT_DIR:PATH=/path -DINCLUDE_DIRS:STRING=/path1:/path2
+#          -DWRAPPER_MASTER_MOC_FILE:FILEPATH=/path/to/file
+#          -DQT_QMAKE_EXECUTABLE:PATH=/path/to/qt/qmake
+#           -P ctkScriptWrapPythonQt.cmake
+#
+#
+
+# Check for non-defined var
+FOREACH(var WRAPPING_NAMESPACE TARGET MOC_FLAGS WRAPPER_MASTER_MOC_FILE WRAP_INT_DIR)
+  IF(NOT DEFINED ${var})
+    MESSAGE(SEND_ERROR "${var} not specified when calling ctkScriptMocPythonQtWrapper")
+  ENDIF()
+ENDFOREACH()
+
+# Check for non-existing ${var}
+FOREACH(var OUTPUT_DIR QT_MOC_EXECUTABLE)
+  IF(NOT EXISTS ${${var}})
+    MESSAGE(SEND_ERROR "Failed to find ${var} when calling ctkScriptWrapPythonQt")
+  ENDIF()
+ENDFOREACH()
+
+# Convert wrapping namespace to subdir
+STRING(REPLACE "." "_" WRAPPING_NAMESPACE_UNDERSCORE ${WRAPPING_NAMESPACE})
+
+# Convert ^^ separated string to list
+STRING(REPLACE "^^" ";" MOC_FLAGS "${MOC_FLAGS}")
+
+# Clear file where all moc'ified will be appended
+FILE(WRITE ${WRAPPER_MASTER_MOC_FILE} "// 
+// File auto-generated by cmake macro ctkScriptMocPythonQtWrapper\n//\n")
+
+# Collect wrapper headers
+set(glob_expression ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}*.h)
+FILE(GLOB wrapper_headers RELATIVE ${OUTPUT_DIR}/${WRAP_INT_DIR} ${glob_expression})
+
+IF(NOT wrapper_headers)
+  MESSAGE(SEND_ERROR "ctkScriptMocPythonQtWrapper - Failed to glob wrapper headers using expression:[${glob_expression}]")
+ENDIF()
+
+# Moc'ified each one of them
+FOREACH(header ${wrapper_headers})
+
+  # what is the filename without the extension
+  GET_FILENAME_COMPONENT(TMP_FILENAME ${header} NAME_WE)
+  
+  set(moc_options)
+  set(wrapper_h_file ${OUTPUT_DIR}/${WRAP_INT_DIR}/${header})
+  set(wrapper_moc_file ${OUTPUT_DIR}/${WRAP_INT_DIR}/moc_${header}.cpp)
+  #message("wrapper_h_file: ${wrapper_h_file}")
+  #message("wrapper_moc_file: ${wrapper_moc_file}")
+  EXECUTE_PROCESS(
+    COMMAND ${QT_MOC_EXECUTABLE} ${MOC_FLAGS} ${moc_options} -o ${wrapper_moc_file} ${wrapper_h_file}
+    WORKING_DIRECTORY ${OUTPUT_DIR}
+    RESULT_VARIABLE RESULT_VAR
+    ERROR_VARIABLE error
+    )
+  IF(RESULT_VAR)
+    MESSAGE(FATAL_ERROR "Failed to moc'ified .\n${RESULT_VAR}\n${error}")
+  ENDIF()
+  
+  # Append generated moc file to the master file
+  FILE(READ ${wrapper_moc_file} file_content)
+  FILE(APPEND ${WRAPPER_MASTER_MOC_FILE} "${file_content}")
+
+ENDFOREACH()
+

+ 131 - 0
CMake/ctkScriptWrapPythonQt_Full.cmake

@@ -0,0 +1,131 @@
+###########################################################################
+#
+#  Library:   CTK
+# 
+#  Copyright (c) 2010  Kitware Inc.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.commontk.org/LICENSE
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# 
+###########################################################################
+
+#
+# ctkScriptWrapPythonQt_Full
+#
+
+#
+# Depends on:
+#  CTK/CMake/ctkMacroWrapPythonQt.cmake
+#
+
+#
+# This script should be invoked either as a CUSTOM_COMMAND 
+# or from the command line using the following syntax:
+#
+#    cmake -DWRAPPING_NAMESPACE:STRING=org.commontk -DTARGET:STRING=MyLib 
+#          -DSOURCES:STRING="file1^^file2" -DINCLUDE_DIRS:STRING=/path1:/path2
+#          -DWRAP_INT_DIR:STRING=subir/subir/
+#          -DPYTHONQTGENERATOR_EXECUTABLE:FILEPATH=/path/to/exec
+#          -DOUTPUT_DIR:PATH=/path  -DQT_QMAKE_EXECUTABLE:PATH=/path/to/qt/qmake
+#          -P ctkScriptWrapPythonQt_Full.cmake
+#
+
+# Check for non-defined var
+FOREACH(var SOURCES TARGET INCLUDE_DIRS WRAP_INT_DIR WRAPPING_NAMESPACE)
+  IF(NOT DEFINED ${var})
+    MESSAGE(SEND_ERROR "${var} not specified when calling ctkScriptWrapPythonQt")
+  ENDIF()
+ENDFOREACH()
+
+# Check for non-existing ${var}
+FOREACH(var PYTHONQTGENERATOR_EXECUTABLE QT_QMAKE_EXECUTABLE OUTPUT_DIR)
+  IF(NOT EXISTS ${${var}})
+    MESSAGE(SEND_ERROR "Failed to find ${var} when calling ctkScriptWrapPythonQt")
+  ENDIF()
+ENDFOREACH()
+
+# Convert wrapping namespace to subdir
+STRING(REPLACE "." "_" WRAPPING_NAMESPACE_UNDERSCORE ${WRAPPING_NAMESPACE})
+
+# Convert ^^ separated string to list
+STRING(REPLACE "^^" ";" SOURCES "${SOURCES}")
+
+FOREACH(FILE ${SOURCES})
+
+  # what is the filename without the extension
+  GET_FILENAME_COMPONENT(TMP_FILENAME ${FILE} NAME_WE)
+    
+  SET(includes 
+    "${includes}\n#include \"${TMP_FILENAME}.h\"")
+        
+  # Extract classname - NOTE: We assume the filename matches the associated class
+  set(className ${TMP_FILENAME})
+  #message(STATUS "FILE:${FILE}, className:${className}")
+
+  SET(objectTypes "${objectTypes}\n  <object-type name=\"${className}\"/>")
+
+ENDFOREACH()
+
+# Write master include file
+  FILE(WRITE ${OUTPUT_DIR}/${WRAP_INT_DIR}ctkPythonQt_${TARGET}_masterinclude.h "
+#ifndef __ctkPythonQt_${TARGET}_masterinclude_h
+#define __ctkPythonQt_${TARGET}_masterinclude_h
+${includes}
+#endif
+")
+
+# Write Typesystem file
+FILE(WRITE ${OUTPUT_DIR}/${WRAP_INT_DIR}typesystem_${TARGET}.xml "
+<typesystem package=\"${WRAPPING_NAMESPACE}.${TARGET}\">
+  ${objectTypes}
+</typesystem>
+")
+
+# Extract PYTHONQTGENERATOR_DIR 
+GET_FILENAME_COMPONENT(PYTHONQTGENERATOR_DIR ${PYTHONQTGENERATOR_EXECUTABLE} PATH)
+#message(PYTHONQTGENERATOR_DIR:${PYTHONQTGENERATOR_DIR})
+
+# Write Build file
+FILE(WRITE ${OUTPUT_DIR}/${WRAP_INT_DIR}build_${TARGET}.txt "
+<!-- File auto-generated by cmake macro ctkScriptWrapPythonQt_Full -->
+
+<typesystem>
+  <load-typesystem name=\"${PYTHONQTGENERATOR_DIR}/typesystem_core.xml\" generate=\"no\" />
+  <load-typesystem name=\"${PYTHONQTGENERATOR_DIR}/typesystem_gui.xml\" generate=\"no\" />
+  <load-typesystem name=\"${OUTPUT_DIR}/${WRAP_INT_DIR}/typesystem_${TARGET}.xml\" generate=\"yes\" />
+</typesystem>
+")
+
+# Compute QTDIR
+GET_FILENAME_COMPONENT(QTDIR ${QT_QMAKE_EXECUTABLE}/../../ REALPATH)
+SET(ENV{QTDIR} ${QTDIR})
+
+EXECUTE_PROCESS(
+  COMMAND ${PYTHONQTGENERATOR_EXECUTABLE} --debug-level=sparse --include-paths=${INCLUDE_DIRS} --output-directory=${OUTPUT_DIR} ${OUTPUT_DIR}/${WRAP_INT_DIR}ctkPythonQt_${TARGET}_masterinclude.h ${OUTPUT_DIR}/${WRAP_INT_DIR}build_${TARGET}.txt
+  WORKING_DIRECTORY ${PYTHONQTGENERATOR_DIR}
+  RESULT_VARIABLE result
+  #OUTPUT_VARIABLE output
+  ERROR_VARIABLE error
+  OUTPUT_QUIET
+  )
+#message(${error})
+IF(result)
+  MESSAGE(FATAL_ERROR "Failed to generate ${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp\n${error}")
+ENDIF()
+
+# Since PythonQtGenerator or FILE(WRITE ) doesn't 'update the timestamp - Let's touch the files
+EXECUTE_PROCESS(
+  COMMAND ${CMAKE_COMMAND} -E touch 
+    ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp
+    ${OUTPUT_DIR}/${WRAP_INT_DIR}ctkPythonQt_${TARGET}_masterinclude.h
+  )
+

+ 244 - 0
CMake/ctkScriptWrapPythonQt_Light.cmake

@@ -0,0 +1,244 @@
+###########################################################################
+#
+#  Library:   CTK
+# 
+#  Copyright (c) 2010  Kitware Inc.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.commontk.org/LICENSE
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# 
+###########################################################################
+
+#
+# ctkScriptWrapPythonQt_Light
+#
+
+#
+# Depends on:
+#  CTK/CMake/ctkMacroWrapPythonQt.cmake
+#
+
+#
+# This script should be invoked either as a CUSTOM_COMMAND 
+# or from the command line using the following syntax:
+#
+#    cmake -DWRAPPING_NAMESPACE:STRING=org.commontk -DTARGET:STRING=MyLib 
+#          -DSOURCES:STRING="file1^^file2" -DINCLUDE_DIRS:STRING=/path1:/path2
+#          -DWRAP_INT_DIR:STRING=subir/subir/
+#          -DOUTPUT_DIR:PATH=/path  -DQT_QMAKE_EXECUTABLE:PATH=/path/to/qt/qmake
+#          -DPYTHON_EXECUTABLE:FILEPATH=/path/to/python
+#          -P ctkScriptWrapPythonQt_Light.cmake
+#
+
+#
+# LOG FILE:
+#   File ctkScriptWrapPythonQt_Light_log.txt will be created in the current directory. 
+#   It will contain the list of class and the constructor signature that will be wrapped.
+#
+
+set(verbose 0)
+
+#
+# Convenient function allowing to log the reason why a given class hasn't been wrapped
+# If verbose=1, it will also be displayed on the standard output
+#
+FUNCTION(log msg)
+  IF(verbose)
+    MESSAGE(${msg})
+  ENDIF()
+  FILE(APPEND "${CMAKE_CURRENT_BINARY_DIR}/ctkScriptWrapPythonQt_Light_log.txt" "${msg}\n")
+ENDFUNCTION()
+
+#
+# Convenient function allowing to invoke re.search(regex, string) using the given interpreter.
+# Note that is_matching will be set to True if there is a match
+#
+FUNCTION(reSearchFile python_exe regex file is_matching)
+
+  set(python_cmd "import re\; f = open('${file}', 'r')\;
+res = re.search\(\"${regex}\", f.read(), re.MULTILINE\)\;
+if res == None: print \"FALSE\" 
+else: print \"TRUE\"
+")
+  #message("python_cmd: ${python_cmd}")
+
+  EXECUTE_PROCESS(
+    COMMAND ${python_exe} -c ${python_cmd};
+    RESULT_VARIABLE result
+    OUTPUT_VARIABLE output
+    ERROR_VARIABLE error
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+  
+  IF(result)
+    MESSAGE(SEND_ERROR "reSearchFile - Problem with regex: ${regex}\n${error}")
+  ENDIF()
+  #message(${output})
+  SET(is_matching ${output} PARENT_SCOPE)
+  
+ENDFUNCTION()
+
+
+# Check for non-defined var
+FOREACH(var WRAPPING_NAMESPACE TARGET SOURCES INCLUDE_DIRS WRAP_INT_DIR)
+  IF(NOT DEFINED ${var})
+    MESSAGE(SEND_ERROR "${var} not specified when calling ctkScriptWrapPythonQt")
+  ENDIF()
+ENDFOREACH()
+
+# Check for non-existing ${var}
+FOREACH(var QT_QMAKE_EXECUTABLE OUTPUT_DIR PYTHON_EXECUTABLE)
+  IF(NOT EXISTS ${${var}})
+    MESSAGE(SEND_ERROR "Failed to find ${var} when calling ctkScriptWrapPythonQt")
+  ENDIF()
+ENDFOREACH()
+
+# Clear log file
+FILE(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ctkScriptWrapPythonQt_Light_log.txt" "")
+  
+# Convert wrapping namespace to subdir
+STRING(REPLACE "." "_" WRAPPING_NAMESPACE_UNDERSCORE ${WRAPPING_NAMESPACE})
+
+# Convert ^^ separated string to list
+STRING(REPLACE "^^" ";" SOURCES "${SOURCES}")
+
+FOREACH(FILE ${SOURCES})
+
+  # what is the filename without the extension
+  GET_FILENAME_COMPONENT(TMP_FILENAME ${FILE} NAME_WE)
+      
+  SET(includes 
+    "${includes}\n#include \"${TMP_FILENAME}.h\"")
+        
+  # Extract classname - NOTE: We assume the filename matches the associated class
+  set(className ${TMP_FILENAME})
+  #message(STATUS "FILE:${FILE}, className:${className}")
+  
+  # Extract parent classname
+  SET(parentClassName)
+  
+  IF("${parentClassName}" STREQUAL "")
+    # Does constructor signature is of the form: myclass()
+    SET(regex "[^~]${className}[\\s\\n]*\\([\\s\\n]*\\)")
+    reSearchFile(${PYTHON_EXECUTABLE} ${regex} ${FILE} is_matching)
+    IF(is_matching)
+      SET(parentClassName "No")
+      log("${TMP_FILENAME} - constructor of the form: ${className}\(\)")
+    ENDIF()
+  ENDIF()
+  
+  IF("${parentClassName}" STREQUAL "")
+    # Does constructor signature is of the form: myclass(QObject * parent ...)
+    SET(regex "${className}[\\s\\n]*\\([\\s\\n]*QObject[\\s\\n]*\\*[\\s\\n]*\\w+[\\s\\n]*(\\=[\\s\\n]*(0|NULL)|,.*\\=.*\\)|\\))")
+    reSearchFile(${PYTHON_EXECUTABLE} ${regex} ${FILE} is_matching)
+    IF(is_matching)
+      SET(parentClassName "QObject")
+      log("${TMP_FILENAME} - constructor of the form: ${className}\(QObject * parent ... \)")
+    ENDIF()
+  ENDIF()
+  
+  IF("${parentClassName}" STREQUAL "")
+    # Does constructor signature is of the form: myclass(QWidget * parent ...)
+    SET(regex "${className}[\\s\\n]*\\([\\s\\n]*QWidget[\\s\\n]*\\*[\\s\\n]*\\w+[\\s\\n]*(\\=[\\s\\n]*(0|NULL)|,.*\\=.*\\)|\\))")
+    reSearchFile(${PYTHON_EXECUTABLE} ${regex} ${FILE} is_matching)
+    IF(is_matching)
+      SET(parentClassName "QWidget")
+      log("${TMP_FILENAME} - constructor of the form: ${className}\(QWidget * parent ... \)")
+    ENDIF()
+  ENDIF()
+ 
+  # Generate PythonQtWrapper class
+  IF("${parentClassName}" STREQUAL "QObject" OR "${parentClassName}" STREQUAL "QWidget")
+  
+    SET(pythonqtWrappers 
+      "${pythonqtWrappers}
+//-----------------------------------------------------------------------------
+class PythonQtWrapper_${className} : public QObject
+{
+Q_OBJECT
+public:
+public slots:
+  ${className}* new_${className}(${parentClassName}*  parent = 0)
+    {
+    return new ${className}(parent);
+    }
+  void delete_${className}(${className}* obj) { delete obj; }
+};
+")
+
+  ELSEIF("${parentClassName}" STREQUAL "No")
+  
+    SET(pythonqtWrappers 
+      "${pythonqtWrappers}
+//-----------------------------------------------------------------------------
+class PythonQtWrapper_${className} : public QObject
+{
+Q_OBJECT
+public:
+public slots:
+  ${className}* new_${className}()
+    {
+    return new ${className}();
+    }
+  void delete_${className}(${className}* obj) { delete obj; }
+};
+")
+
+  ELSE() # Case parentClassName is empty
+  
+    MESSAGE(WARNING "ctkScriptWrapPythonQt_Light - Problem wrapping ${FILE}")
+    
+  ENDIF()
+
+  # Generate code allowing to register the class metaobject and its associated "light" wrapper
+  SET(registerclasses "${registerclasses}
+  PythonQt::self()->registerClass(
+    &${className}::staticMetaObject, \"${TARGET}\",   
+    PythonQtCreateObject<PythonQtWrapper_${className}>);\n")
+
+ENDFOREACH()
+
+# Write master include file
+FILE(WRITE ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0.h "//
+// File auto-generated by cmake macro ctkScriptWrapPythonQt_Light
+//
+
+#ifndef __${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0_h
+#define __${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0_h
+
+#include <QWidget>
+${includes}
+${pythonqtWrappers}
+#endif
+")
+
+# Write wrapper header
+FILE(WRITE ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp "//
+// File auto-generated by cmake macro ctkScriptWrapPythonQt_Light
+//
+
+#include <PythonQt.h>
+#include \"${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}0.h\"
+
+void PythonQt_init_${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}(PyObject* module)
+{
+  Q_UNUSED(module);
+  ${registerclasses}
+}
+")
+
+# Since FILE(WRITE ) doesn't update the timestamp - Let's touch the files
+EXECUTE_PROCESS(
+  COMMAND ${CMAKE_COMMAND} -E touch 
+    ${OUTPUT_DIR}/${WRAP_INT_DIR}${WRAPPING_NAMESPACE_UNDERSCORE}_${TARGET}_init.cpp
+  )
+

+ 66 - 0
CMakeExternals/PythonQtGenerator.cmake

@@ -0,0 +1,66 @@
+#
+# PythonQtGenerator
+#
+
+SET(PythonQtGenerator_DEPENDS)
+IF(CTK_WRAP_PYTHONQT_FULL)
+  
+  # Sanity checks
+  IF(DEFINED PYTHONQTGENERATOR_EXECUTABLE AND NOT EXISTS ${PYTHONQTGENERATOR_EXECUTABLE})
+    MESSAGE(FATAL_ERROR "PYTHONQTGENERATOR_EXECUTABLE variable is defined but corresponds to non-existing executable")
+  ENDIF()
+  
+  IF(NOT DEFINED PYTHONQTGENERATOR_EXECUTABLE)
+    
+    #
+    # PythonQtGenerator is the tool allowing to generate the PythonQt decorators using 
+    # typesystem xml files. If called without any option, it will generate the bindings for Qt.
+    #
+    # See http://www.pyside.org/docs/apiextractor/typesystem.html
+    # See http://doc.trolltech.com/qtjambi-4.5.2_01/com/trolltech/qt/qtjambi-typesystem.html
+    # See http://qt.gitorious.org/qt-labs/qtscriptgenerator
+    #
+    SET(proj PythonQtGenerator)
+  #   MESSAGE(STATUS "Adding project:${proj}")
+    SET(PythonQtGenerator_DEPENDS ${proj})
+    
+    #
+    # If directory PythonQt already exists, the corresponding project will 
+    # be cloned. Since PythonQt and PythonQtGenerator source code are hosted 
+    # on the same repository, we will assume that if the directory PythonQt 
+    # exists, the generator code will also be available.
+    #
+    IF(EXISTS ${ep_source_dir}/PythonQt)
+      #MESSAGE(STATUS "ExternalProject/PythonQtGenerator: PythonQt already added as ExternalProject")
+      ExternalProject_Add(${proj}
+        DOWNLOAD_COMMAND ""
+        CMAKE_GENERATOR ${gen}
+        INSTALL_COMMAND ""
+        SOURCE_DIR ${ep_source_dir}/PythonQt/generator
+        CMAKE_ARGS
+          ${ep_common_args}
+          -DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}
+        DEPENDS
+          "PythonQt" # To make sure the generator code is checked out, let's depent on PythonQt
+        )
+    ELSE()
+      #MESSAGE(STATUS "ExternalProject/PythonQtGenerator: PythonQt is NOT an ExternalProject")
+      ExternalProject_Add(${proj}
+        GIT_REPOSITORY "${git_protocol}://github.com/commontk/PythonQt.git"
+        GIT_TAG "patched"
+        CMAKE_GENERATOR ${gen}
+        INSTALL_COMMAND ""
+        SOURCE_DIR ${ep_source_dir}/${proj}/generator
+        CMAKE_ARGS
+          ${ep_common_args}
+          -DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}
+        )
+    ENDIF()
+    
+    SET(PYTHONQTGENERATOR_EXECUTABLE ${ep_build_dir}/PythonQtGenerator/PythonQtGenerator)
+    
+    # Since PythonQtGenerator is an executable, there is no need to add its corresponding 
+    # library output directory to CTK_EXTERNAL_LIBRARY_DIRS
+        
+  ENDIF()
+ENDIF()

+ 17 - 1
CMakeLists.txt

@@ -123,10 +123,11 @@ SET(CMAKE_MODULE_PATH
   ${CMAKE_MODULE_PATH})
 
 #-----------------------------------------------------------------------------
-# Clear CTK_BASE_INCLUDE_DIRS and CTK_BASE_LIBRARIES
+# Clear CTK_BASE_INCLUDE_DIRS, CTK_BASE_LIBRARIES and CTK_WRAPPED_LIBRARIES_PYTHONQT
 #
 SET(CTK_BASE_LIBRARIES CACHE INTERNAL "CTK base libraries" FORCE)
 SET(CTK_BASE_INCLUDE_DIRS CACHE INTERNAL "CTK includes" FORCE)
+SET(CTK_WRAPPED_LIBRARIES_PYTHONQT CACHE INTERNAL "CTK libraries wrapped using PythonQt" FORCE)
 
 # Variable use in CTKConfig.cmake.in
 SET(CTK_LIBRARIES CACHE INTERNAL "CTK libraries" FORCE)
@@ -141,6 +142,7 @@ INCLUDE(CMake/ctkMacroBuildPlugin.cmake)
 INCLUDE(CMake/ctkMacroBuildApp.cmake)
 INCLUDE(CMake/ctkMacroBuildQtDesignerPlugin.cmake)
 INCLUDE(CMake/ctkMacroCompilePythonScript.cmake)
+INCLUDE(CMake/ctkMacroWrapPythonQt.cmake)
 INCLUDE(CMake/ctkMacroSetupQt.cmake)
 INCLUDE(CMake/ctkMacroTargetLibraries.cmake) # Import multiple macros
 INCLUDE(CMake/ctkFunctionExtractOptionNameAndValue.cmake)
@@ -354,6 +356,20 @@ FOREACH(app ${CTK_APPLICATIONS})
 ENDFOREACH()
 
 #-----------------------------------------------------------------------------
+# CTK Python wrapping - Propose the option only if CTK_LIB_Scriping/Python/Core is ON
+IF(CTK_LIB_Scripting/Python/Core)
+  OPTION(CTK_WRAP_PYTHONQT_FULL "Wrap CTK classes using Qt meta-object system into Python language" OFF)
+  OPTION(CTK_WRAP_PYTHONQT_LIGHT "Wrap CTK classes using Qt meta-object system into Python language" OFF)
+  IF(CTK_WRAP_PYTHONQT_FULL AND CTK_WRAP_PYTHONQT_LIGHT)
+    MESSAGE(SEND_ERROR "CTK_WRAP_PYTHONQT_{LIGHT,FULL} options are mutually exclusive. Please enable only one !")
+  ENDIF()
+ELSE()
+  # Note that an unset boolean variable is considered to be False
+  UNSET(CTK_WRAP_PYTHONQT_FULL CACHE)
+  UNSET(CTK_WRAP_PYTHONQT_LIGHT CACHE)
+ENDIF()
+
+#-----------------------------------------------------------------------------
 # Generate target_directories list - List of directory corresponding to the different
 # libraries, plugins and applications associated with the corresponding option name.
 #

+ 3 - 0
CTKConfig.cmake.in

@@ -43,6 +43,9 @@
 # List all libraries
 SET(CTK_LIBRARIES @CTK_LIBRARIES@)
 
+# List all CTK libraries wrapped with PythonQt
+SET(CTK_WRAPPED_LIBRARIES_PYTHONQT @CTK_WRAPPED_LIBRARIES_PYTHONQT@)
+
 @CTK_CONFIG_INSTALL_ONLY@
 
 # The CTK include file directories.

+ 7 - 1
SuperBuild.cmake

@@ -114,6 +114,7 @@ SET(external_projects
   Log4Qt
   KWStyle
   PythonQt
+  PythonQtGenerator # Should be added after PythonQt - See comment in CMakeExternals/PythonQtGenerator.cmake
   DCMTK
   ZMQ
   QtMobility
@@ -145,10 +146,11 @@ ExternalProject_Add(${proj}
     ${kwstyle_DEPENDS}
     ${DCMTK_DEPENDS}
     ${PythonQt_DEPENDS}
+    ${PythonQtGenerator_DEPENDS}
     ${ZMQ_DEPENDS}
     ${OpenIGTLink_DEPENDS}
     ${VTK_DEPENDS}
-    ${XIP_DEPENDS}
+    ${XIP_DEPENDS}    
 )
 
 #-----------------------------------------------------------------------------
@@ -179,6 +181,8 @@ SET(ctk_cmake_boolean_args
   WITH_COVERAGE
   DOCUMENTATION_TARGET_IN_ALL
   CTEST_USE_LAUNCHERS
+  CTK_WRAP_PYTHONQT_FULL
+  CTK_WRAP_PYTHONQT_LIGHT
   ${ctk_libs_bool_vars}
   ${ctk_plugins_bool_vars}
   ${ctk_applications_bool_vars}
@@ -217,6 +221,7 @@ ExternalProject_Add(${proj}
     -DCTK_C_FLAGS:STRING=${CTK_C_FLAGS}
     -DCTK_EXTERNAL_LIBRARY_DIRS:STRING=${CTK_EXTERNAL_LIBRARY_DIRS}
     -DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}
+    # External projects
     -DCTKData_DIR:PATH=${CTKData_DIR}
 	  -DZMQ_DIR:PATH=${ZMQ_DIR}                     # FindVTK expects VTK_DIR variable to be defined
 	  -DOpenIGTLink_DIR:PATH=${OpenIGTLink_DIR}     # FindOpenIGTLink expects OpenIGTLink_DIR variable to be defined
@@ -226,6 +231,7 @@ ExternalProject_Add(${proj}
     -DPYTHON_INCLUDE_DIR:PATH=${PYTHON_INCLUDE_DIR}     # FindPythonQt expects PYTHON_INCLUDE_DIR variable to be defined
     -DPYTHON_LIBRARY:FILEPATH=${PYTHON_LIBRARY}         # FindPythonQt expects PYTHON_LIBRARY variable to be defined
     -DPYTHONQT_INSTALL_DIR:PATH=${PYTHONQT_INSTALL_DIR} # FindPythonQt expects PYTHONQT_INSTALL_DIR variable to be defined
+    -DPYTHONQTGENERATOR_EXECUTABLE:FILEPATH=${PYTHONQTGENERATOR_EXECUTABLE} #FindPythonQtGenerator expects PYTHONQTGENERATOR_EXECUTABLE to be defined
     -DLog4Qt_DIR:PATH=${Log4Qt_DIR} # FindLog4Qt expects Log4Qt_DIR variable to be defined
   SOURCE_DIR ${CTK_SOURCE_DIR}
   BINARY_DIR ${CTK_BINARY_DIR}/CTK-build

+ 13 - 0
Utilities/CMake/FindPythonQtGenerator.cmake

@@ -0,0 +1,13 @@
+# Find PythonQtGenerator
+#
+# Sets PYTHONQTGENERATOR_FOUND, PYTHONQTGENERATOR_EXECUTABLE
+#
+
+
+FIND_PROGRAM(PYTHONQTGENERATOR_EXECUTABLE PythonQtGenerator DOC "PythonQt Generator.")
+MARK_AS_ADVANCED(PYTHONQTGENERATOR_EXECUTABLE)
+
+SET(PYTHONQTGENERATOR_FOUND 0)
+IF(EXISTS PYTHONQTGENERATOR_EXECUTABLE)
+  SET(PYTHONQTGENERATOR_FOUND 1)
+ENDIF()