Commit ba7c3782 authored by mwoehlke's avatar mwoehlke

ENH: Add support for real Qt resources in Python

Add methods to qSlicerCoreApplication to allow Python code to invoke the
raw data overloads of QResource::[un]registerResource (which otherwise
cannot be invoked, as the requisite uchar* overloads are hidden by the
QString overloads). Create a Python script to generate a Python compiled
resource script (similar to pyrcc4 or pyside-rcc, except leveraging Qt's
native rcc rather than reimplementing it). Create CMake functions to
invoke this in an appropriate manner, and to create necessary target
dependencies so that the resulting Python files are created before the
ctkFunctionAddCompilePythonScriptTargets targets try to copy them to
their final build locations.

This system will allow Python loadable modules to use Qt resources by
creating a .qrc file and loading the resources using ':/path' notation,
in the same manner as C++ code (rather than individually copying every
resource file and loading them relative to the module's directory).

git-svn-id: http://svn.slicer.org/Slicer4/trunk@23290 3bd1e089-480b-0410-8dfb-8563597acbee
parent da227822
......@@ -28,6 +28,7 @@
#include <QMessageBox>
#include <QTimer>
#include <QNetworkProxyFactory>
#include <QResource>
#include <QSettings>
#include <QTranslator>
......@@ -118,6 +119,7 @@ qSlicerCoreApplicationPrivate::qSlicerCoreApplicationPrivate(
#ifdef Slicer_BUILD_DICOM_SUPPORT
this->DICOMDatabase = 0;
#endif
this->NextResourceHandle = 0;
}
//-----------------------------------------------------------------------------
......@@ -1561,3 +1563,38 @@ bool qSlicerCoreApplication::loadCaCertificates()
return false;
#endif
}
//----------------------------------------------------------------------------
int qSlicerCoreApplication::registerResource(const QByteArray& data)
{
Q_D(qSlicerCoreApplication);
const int handle = d->NextResourceHandle++;
d->LoadedResources.insert(handle, data);
uchar* pdata = reinterpret_cast<uchar*>(d->LoadedResources[handle].data());
if (!QResource::registerResource(pdata))
{
d->LoadedResources.remove(handle);
return -1;
}
return handle;
}
//----------------------------------------------------------------------------
bool qSlicerCoreApplication::unregisterResource(int handle)
{
Q_D(qSlicerCoreApplication);
if (d->LoadedResources.contains(handle))
{
uchar* pdata = reinterpret_cast<uchar*>(d->LoadedResources[handle].data());
const bool result = QResource::unregisterResource(pdata);
d->LoadedResources.remove(handle);
return result;
}
return false;
}
......@@ -306,12 +306,16 @@ public:
/// \sa QSslSocket::defaultCaCertificates()
static bool loadCaCertificates();
Q_INVOKABLE int registerResource(const QByteArray& data);
public slots:
/// Restart the application with the arguments passed at startup time
/// \sa QCoreApplication::arguments()
static void restart();
bool unregisterResource(int handle);
protected:
///
virtual void handlePreApplicationCommandLineArguments();
......
......@@ -164,6 +164,9 @@ public:
/// Application-wide database instance
ctkDICOMDatabase* DICOMDatabase;
#endif
QHash<int, QByteArray> LoadedResources;
int NextResourceHandle;
};
#endif
......
set_property(GLOBAL PROPERTY _SLICER_PYTHON_RESOURCE_TARGETS)
function(slicerFunctionAddPythonQtResources RESOURCE_NAMES)
set(out_paths)
# Generate compiler resource scripts
foreach(in_path ${ARGN})
set(rc_depends)
if(IS_ABSOLUTE ${in_path})
file(RELATIVE_PATH out_path ${CMAKE_CURRENT_SOURCE_DIR} ${in_path})
get_filename_component(out_path ${CMAKE_CURRENT_BINARY_DIR}/${out_path} PATH)
else()
get_filename_component(out_path ${CMAKE_CURRENT_BINARY_DIR}/${in_path} PATH)
get_filename_component(in_path ${in_path} ABSOLUTE)
endif()
get_filename_component(out_name ${in_path} NAME_WE)
get_filename_component(rc_path ${in_path} PATH)
set(out_path ${out_path}/${out_name}Resources.py)
if(EXISTS ${in_path})
# Parse file for dependencies
file(READ ${in_path} rc_file_contents)
string(REGEX MATCHALL "<file[^<]+" rc_files "${rc_file_contents}")
foreach(rc_file ${rc_files})
string(REGEX REPLACE "^<file[^>]*>" "" rc_file ${rc_file})
if(NOT IS_ABSOLUTE ${rc_file})
set(rc_file ${rc_path}/${rc_file})
endif()
list(APPEND rc_depends ${rc_file})
endforeach()
# Copy the input qrc script to enforce recalculation of dependencies on
# changes to the same
configure_file(
${in_path}
${CMAKE_CURRENT_BINARY_DIR}/.${out_name}Resources.py.deps
COPY_ONLY
)
endif()
# Create command to generate the compiled resource script
add_custom_command(
OUTPUT ${out_path}
COMMAND ${PYTHON_EXECUTABLE}
${Slicer_SOURCE_DIR}/Utilities/Scripts/qrcc.py
--rcc ${QT_RCC_EXECUTABLE}
-o ${out_path}
${in_path}
VERBATIM
WORKING_DIRECTORY ${rc_path}
MAIN_DEPENDENCY ${in_path}
DEPENDS ${rc_depends}
)
list(APPEND out_paths ${out_path})
endforeach()
# Create target to generate resource files
get_filename_component(target ${CMAKE_CURRENT_BINARY_DIR}-Resources NAME)
add_custom_target(${target} DEPENDS ${out_paths})
get_property(resource_targets
GLOBAL PROPERTY _SLICER_PYTHON_RESOURCE_TARGETS
)
list(APPEND resource_targets ${target})
set_property(GLOBAL PROPERTY _SLICER_PYTHON_RESOURCE_TARGETS
${resource_targets}
)
set(${RESOURCE_NAMES} ${out_paths} PARENT_SCOPE)
endfunction()
function(slicerFunctionAddPythonQtResourcesTargets NAME)
get_property(resource_files GLOBAL PROPERTY _SLICER_PYTHON_RESOURCE_TARGETS)
add_custom_target(${NAME} DEPENDS ${resource_files})
endfunction()
......@@ -245,6 +245,7 @@ include(SlicerMacroBuildModuleMRML)
include(SlicerMacroBuildModuleWidgets)
include(SlicerMacroBuildQtModule)
include(SlicerMacroBuildScriptedModule)
include(SlicerFunctionAddPythonQtResources)
include(SlicerMacroExtractRepositoryInfo)
include(vtkMacroKitPythonWrap)
......
......@@ -381,7 +381,7 @@ if(Slicer_BUILD_I18N_SUPPORT)
include(SlicerMacroTranslation)
endif()
include(SlicerFunctionInstallLibrary)
include(SlicerFunctionAddPythonQtResources)
#-----------------------------------------------------------------------------
# Internationalization
......@@ -1159,7 +1159,11 @@ ExternalData_Add_Target(${Slicer_ExternalData_DATA_MANAGEMENT_TARGET})
#-----------------------------------------------------------------------------
# Create targets CopySlicerPython{Resource, Script}Files, CompileSlicerPythonFiles
if(Slicer_USE_PYTHONQT)
ctkFunctionAddCompilePythonScriptTargets(${CTK_COMPILE_PYTHON_SCRIPTS_GLOBAL_TARGET_NAME})
slicerFunctionAddPythonQtResourcesTargets(SlicerPythonResources)
ctkFunctionAddCompilePythonScriptTargets(
${CTK_COMPILE_PYTHON_SCRIPTS_GLOBAL_TARGET_NAME}
DEPENDS SlicerPythonResources
)
endif()
#-----------------------------------------------------------------------------
......
#!/usr/bin/env python
import argparse
import base64
import subprocess
import sys
_header = """
import base64
from __main__ import slicer
qt_resource_handle = None
qt_resource_data = base64.decodestring(\"\"\"
"""
_footer = """
\"\"\")
def qInitResources():
global qt_resource_handle
qt_resource_handle = slicer.app.registerResource(qt_resource_data)
def qCleanupResources():
slicer.app.unregisterResource(qt_resource_handle)
qInitResources()
"""
def compileResources(in_path, out_file, args):
command = [args.rcc, "-binary", in_path]
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=sys.stderr)
data, err = proc.communicate()
if proc.returncode != 0:
sys.exit(proc.returncode)
out_file.write(_header)
out_file.write(base64.encodestring(data).rstrip())
out_file.write(_footer)
def main(argv):
parser = argparse.ArgumentParser(description="PythonQt Resource Compiler")
parser.add_argument("--rcc", default="rcc",
help="location of the Qt resource compiler executable")
parser.add_argument("-o", "--output", dest="out_path", metavar="PATH",
default="-",
help="location to which to write the output Python"
" script (default=stdout)")
parser.add_argument("in_path", metavar="resource_script",
help="input resource script to compile")
args = parser.parse_args(argv)
if args.out_path == "-":
compileResources(args.in_path, sys.stdout, args)
else:
with open(args.out_path, "w") as f:
compileResources(args.in_path, f, args)
if __name__ == "__main__":
main(sys.argv[1:])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment