diff --git a/modules/ECMAddQtDesignerPlugin.cmake b/modules/ECMAddQtDesignerPlugin.cmake new file mode 100644 --- /dev/null +++ b/modules/ECMAddQtDesignerPlugin.cmake @@ -0,0 +1,444 @@ +#.rst: +# ECMAddQtDesignerPlugin +# ------------------ +# +# This module provides the ``ecm_add_qtdesignerplugin`` function for generating +# Qt Designer plugins for custom widgets. Each of those widgets is described +# using a second function ``ecm_qtdesignerplugin_widget``. +# +# :: +# +# ecm_add_qtdesignerplugin( +# NAME +# WIDGETS [ [...]] +# LINK_LIBRARIES [ [...]] +# INSTALL_DESTINATION +# [OUTPUT_NAME ] +# [DEFAULT_GROUP ] +# [INCLUDE_FILES [ [...]]] +# [COMPONENT ] +# ) +# +# :: +# +# ecm_qtdesignerplugin_widget( +# [CLASSNAME ] +# [INCLUDE_FILE ] +# [CONTAINER] +# [ICON ] +# [TOOLTIP ] +# [WHATSTHIS ] +# [GROUP ] +# [CREATE_WIDGET_CODE ] +# [INITIALIZE_CODE ] +# [IMPL_CLASSNAME ] +# [CONSTRUCTOR_ARGS_CODE ] +# ) +# +# +# +# Example usage: +# +# .. code-block:: cmake +# +# ecm_qtdesignerplugin_widget(FooWidget +# TOOLTIP "Enables to browse foo." +# GROUP "Views (Foo)" +# ) +# +# ecm_qtdesignerplugin_widget(BarWidget +# TOOLTIP "Displays bars." +# GROUP "Display (Foo)" +# ) +# +# ecm_add_qtdesignerplugin(foowidgets +# NAME FooWidgets +# OUTPUT_NAME foo2widgets +# LINK_LIBRARIES +# Foo::Widgets +# WIDGETS +# FooWidget +# BarWidget +# INSTALL_DESTINATION "${KDE_INSTALL_QTPLUGINDIR}/designer" +# COMPONENT Devel +# ) +# +# Since 5.62.0. + +#============================================================================= +# Copyright 2019 Friedrich W. H. Kossebau +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +include(CMakeParseArguments) + + +function(ecm_qtdesignerplugin_widget widget) + set(options + CONTAINER + ) + set(oneValueArgs + CLASSNAME + INCLUDE_FILE + ICON + TOOLTIP + WHATSTHIS + GROUP + CREATE_WIDGET_CODE + INITIALIZE_CODE + DOM_XML + IMPL_CLASSNAME + CONSTRUCTOR_ARGS_CODE + ) + set(multiValueArgs + ) + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARGS_CLASSNAME) + set(ARGS_CLASSNAME "${widget}") + endif() + if(NOT ARGS_INCLUDE_FILE) + string(TOLOWER ${ARGS_CLASSNAME} ARGS_INCLUDE_FILE) + set(ARGS_INCLUDE_FILE "${ARGS_INCLUDE_FILE}.h") + endif() + if(NOT ARGS_TOOLTIP) + set(ARGS_TOOLTIP "${ARGS_CLASSNAME} Widget") + endif() + if(NOT ARGS_WHATSTHIS) + set(ARGS_WHATSTHIS "${ARGS_TOOLTIP}") + endif() + if(ARGS_CONTAINER) + set(_is_container TRUE) + else() + set(_is_container FALSE) + endif() + if(NOT ARGS_CREATE_WIDGET_CODE) + if(NOT ARGS_IMPL_CLASSNAME) + set(ARGS_IMPL_CLASSNAME "${ARGS_CLASSNAME}") + endif() + if(NOT ARGS_CONSTRUCTOR_ARGS_CODE) + set(ARGS_CONSTRUCTOR_ARGS_CODE "(parent)") + endif() + set(ARGS_CREATE_WIDGET_CODE " return new ${ARGS_IMPL_CLASSNAME}${ARGS_CONSTRUCTOR_ARGS_CODE}") + endif() + if(ARGS_ICON) + if (NOT IS_ABSOLUTE ${ARGS_ICON}) + set(ARGS_ICON "${CMAKE_CURRENT_SOURCE_DIR}/${ARGS_ICON}") + endif() + if(NOT EXISTS ARGS_ICON) + message(FATAL_ERROR "No such file as passed with ICON when calling ecm_qtdesignerplugin_widget(): ${ARGS_ICON}") + endif() + else() + string(TOLOWER ${ARGS_CLASSNAME} _icon) + set(_icon "${CMAKE_CURRENT_SOURCE_DIR}/pics/${_icon}.png") + message(STATUS "Looking for ${_icon}") + if(EXISTS "${_icon}") + set(ARGS_ICON "${_icon}") + message(STATUS "Found ${ARGS_ICON}") + endif() + endif() + + + set(ECM_QTDESIGNERPLUGIN_${widget}_CLASSNAME "${ARGS_CLASSNAME}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_INCLUDE_FILE "${ARGS_INCLUDE_FILE}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_TOOLTIP "${ARGS_TOOLTIP}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_WHATSTHIS "${ARGS_WHATSTHIS}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_GROUP "${ARGS_GROUP}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_ICON "${ARGS_ICON}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_IS_CONTAINER "${_is_container}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_CREATE_WIDGET_CODE "${ARGS_CREATE_WIDGET_CODE}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_INITIALIZE_CODE "${ARGS_INITIALIZE_CODE}" PARENT_SCOPE) + set(ECM_QTDESIGNERPLUGIN_${widget}_DOM_XML "${ARGS_DOM_XML}" PARENT_SCOPE) +endfunction() + +# helper method +function(_ecm_qtdesignerplugin_write_widget designer_src_file widget default_group rc_icon_dir) + set(_classname "${ECM_QTDESIGNERPLUGIN_${widget}_CLASSNAME}") + set(_factory_classname "${_classname}QtDesignerWidgetCollection") + set(ECM_QTDESIGNERPLUGIN_${widget}_FACTORY_CLASSNAME "${_factory_classname}" PARENT_SCOPE) + if(ECM_QTDESIGNERPLUGIN_${widget}_IS_CONTAINER) + set(_is_container "true") + else() + set(_is_container "false") + endif() + set(_group ${ECM_QTDESIGNERPLUGIN_${widget}_GROUP}) + if(NOT _group) + set(_group "${default_group}") + endif() + set(_dom_xml "${ECM_QTDESIGNERPLUGIN_${widget}_DOM_XML}") + if(_dom_xml) + string(REPLACE "\"" "\\\"" _dom_xml "${_dom_xml}") + set(_dom_xml_method " QString domXml() const override { return QStringLiteral(\"${_dom_xml}\"); }") + else() + set(_dom_xml_method) + endif() + set(_icon "${ECM_QTDESIGNERPLUGIN_${widget}_ICON}") + if(_icon) + get_filename_component(_icon_filename ${_icon} NAME) + set(_icon_construct "QIcon(QStringLiteral(\":${rc_icon_dir}/${_icon_filename}\"))") + else() + set(_icon_construct "QIcon()") + endif() + set(_initialize_code "${ECM_QTDESIGNERPLUGIN_${widget}_INITIALIZE_CODE}") + if(NOT _initialize_code) + set(_initialize_code +" Q_UNUSED(core); + if (mInitialized) return; + mInitialized = true; +" + ) + endif() + + file(APPEND ${designer_src_file} " +class ${_factory_classname} + : public QObject + , public QDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) + +public: + explicit ${_factory_classname}(QObject *parent = nullptr) + : QObject(parent) + , mInitialized(false) + {} + + ~${_factory_classname}() override {} + +public: // QDesignerCustomWidgetInterface API + bool isInitialized() const override { return mInitialized; } +${_dom_xml_method} + bool isContainer() const override { return ${_is_container}; } + QIcon icon() const override { return ${_icon_construct}; } + QString group() const override { return QStringLiteral(\"${_group}\"); } + QString includeFile() const override { return QStringLiteral(\"${ECM_QTDESIGNERPLUGIN_${widget}_INCLUDE_FILE}\"); } + QString name() const override { return QStringLiteral(\"${_classname}\"); } + QString toolTip() const override { return QStringLiteral(\"${ECM_QTDESIGNERPLUGIN_${widget}_TOOLTIP}\"); } + QString whatsThis() const override { return QStringLiteral(\"${ECM_QTDESIGNERPLUGIN_${widget}_WHATSTHIS}\"); } + + QWidget* createWidget(QWidget* parent) override + { +${ECM_QTDESIGNERPLUGIN_${widget}_CREATE_WIDGET_CODE}; + } + + void initialize(QDesignerFormEditorInterface* core) override + { +${_initialize_code} + } + +private: + bool mInitialized; +}; +" + ) +endfunction() + + +# This needs to be a macro not a function because of the nested +# find_package() call, which will set some variables. +macro(ecm_add_qtdesignerplugin target) + set(options + ) + set(oneValueArgs + NAME + OUTPUT_NAME + INSTALL_DESTINATION + DEFAULT_GROUP + COMPONENT + ) + set(multiValueArgs + WIDGETS + LINK_LIBRARIES + INCLUDE_FILES + ) + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT ARGS_NAME) + set(ARGS_NAME "${target}") + endif() + if(NOT ARGS_DEFAULT_GROUP) + set(ARGS_DEFAULT_GROUP "Custom") + endif() + + # TODO: see when to find designer and when only uiplugin + if(NOT Qt5Designer_FOUND) + find_package(Qt5Designer ${REQUIRED_QT_VERSION} QUIET CONFIG) + set_package_properties(Qt5Designer PROPERTIES + PURPOSE "Required to build Qt Designer plugins" + TYPE REQUIRED + ) + endif() + if(NOT Qt5Designer_VERSION_STRING VERSION_LESS 5.5.0 + AND NOT Qt5UiPlugin_FOUND) + find_package(Qt5UiPlugin ${REQUIRED_QT_VERSION} QUIET CONFIG) + set_package_properties(Qt5UiPlugin PROPERTIES + PURPOSE "Required to build Qt Designer plugins" + TYPE REQUIRED + ) + endif() + if (Qt5UiPlugin_FOUND) + # in some old versions Qt5UiPlugin does not set its _INCLUDE_DIRS variable. Fill it manually + get_target_property(Qt5UiPlugin_INCLUDE_DIRS Qt5::UiPlugin INTERFACE_INCLUDE_DIRECTORIES) + endif() + + # setup plugin only if designer/uiplugin libs were found, as we do not abort hard the cmake run otherwise + if(Qt5Designer_FOUND) + # TODO: move into target-prefixed subdir + set(_generation_dir ${CMAKE_CURRENT_BINARY_DIR}) + set(_rc_icon_dir "/${ARGS_NAME}/designer") + + set(_designer_src_file "${_generation_dir}/designerplugin.cpp") + set(_srcs ${_designer_src_file}) + + set(_icons) + foreach(_widget ${ARGS_WIDGETS}) + message(STATUS "ICON for ${_widget}: ${ECM_QTDESIGNERPLUGIN_${_widget}_ICON}") + list(APPEND _icons "${ECM_QTDESIGNERPLUGIN_${_widget}_ICON}") + endforeach() + + if (_icons) + set(_rc_file "${_generation_dir}/designerplugin.rc") + qt5_add_resources(_srcs ${_rc_file}) + file(WRITE ${_rc_file} +" + + +" + ) + foreach(_icon ${_icons}) + get_filename_component(_icon_filename ${_icon} NAME) + file(APPEND ${_rc_file} "${_icon}\n") + endforeach() + file(APPEND ${_rc_file} +" + +" + ) + + endif() + + # setup plugin binary + add_library(${target} MODULE ${_srcs}) + if(TARGET Qt5::UiPlugin) + list(APPEND ARGS_LINK_LIBRARIES Qt5::UiPlugin) + else() + # For Qt <5.9 include dir variables needed + target_include_directories(${target} + PRIVATE ${Qt5UiPlugin_INCLUDE_DIRS} + PRIVATE ${Qt5Designer_INCLUDE_DIRS} + ) + endif() + if(NOT WIN32) + # Since there are no libraries provided by this module, + # there is no point including the build tree in RPath, + # and then having to edit it at install time. + set_target_properties(${target} PROPERTIES + SKIP_BUILD_RPATH TRUE + BUILD_WITH_INSTALL_RPATH TRUE + ) + endif() + if (ARGS_OUTPUT_NAME) + set_target_properties(${target} PROPERTIES + OUTPUT_NAME ${ARGS_OUTPUT_NAME} + ) + endif() + target_link_libraries(${target} ${ARGS_LINK_LIBRARIES}) + + if (DEFINED ARGS_COMPONENT) + set(_component COMPONENT ${ARGS_COMPONENT}) + else() + set(_component) + endif() + + install(TARGETS ${target} DESTINATION ${ARGS_INSTALL_DESTINATION} ${_component}) + + # generate source file + set(_collection_classname "${ARGS_NAME}QtDesignerWidgetCollection") + + set(_include_files + QDesignerCustomWidgetCollectionInterface + QDesignerCustomWidgetInterface + QObject + QIcon + QString + ${ARGS_INCLUDE_FILES} + ) + # TODO: filter for duplicates + foreach(_widget ${ARGS_WIDGETS}) + list(APPEND _include_files ${ECM_QTDESIGNERPLUGIN_${_widget}_INCLUDE_FILE}) + endforeach() + + file(WRITE ${_designer_src_file} "// DO NOT EDIT! Generated from ecm_add_qtdesignerplugin()\n\n") + foreach(_include_file ${_include_files}) + file(APPEND ${_designer_src_file} "#include <${_include_file}>\n") + endforeach() + foreach(_widget ${ARGS_WIDGETS}) + _ecm_qtdesignerplugin_write_widget(${_designer_src_file} ${_widget} ${ARGS_DEFAULT_GROUP} ${_rc_icon_dir}) + endforeach() + file(APPEND ${_designer_src_file} " +class ${_collection_classname} + : public QObject + , public QDesignerCustomWidgetCollectionInterface +{ + Q_OBJECT + Q_INTERFACES( + QDesignerCustomWidgetCollectionInterface + ) + + Q_PLUGIN_METADATA(IID \"org.qt-project.Qt.QDesignerCustomWidgetInterface\") + +public: + explicit ${_collection_classname}(QObject* parent = nullptr); + +public: // QDesignerCustomWidgetCollectionInterface API + QList customWidgets() const override; + +private: + QList m_widgetFactories; +}; + +${_collection_classname}::${_collection_classname}(QObject* parent) + : QObject(parent) +{ + m_widgetFactories = QList{ +" + ) + foreach(_widget ${ARGS_WIDGETS}) + file(APPEND ${_designer_src_file} " new ${ECM_QTDESIGNERPLUGIN_${_widget}_FACTORY_CLASSNAME}(this),\n") + endforeach() + file(APPEND ${_designer_src_file} " + }; +} + +QList ${_collection_classname}::customWidgets() const +{ + return m_widgetFactories; +} + +#include \"designerplugin.moc\" +" + ) + endif() +endmacro()