diff --git a/ci/.gitlab-ci.yml b/ci/.gitlab-ci.yml new file mode 100644 index 0000000000..92200a8d8e --- /dev/null +++ b/ci/.gitlab-ci.yml @@ -0,0 +1,43 @@ +# gitlab CI config file + +# this image is on dockerhub. Dockerfile is here: https://gitlab.com/PrzemoF/FreeCAD/-/blob/gitlab-v1/ci/Dockerfile +image: freecadci/runner + +stages: # List of stages for jobs, and their order of execution + - build + - test + +before_script: + - apt-get update -yqq + # CCache Config + - mkdir -p ccache + - export CCACHE_BASEDIR=${PWD} + - export CCACHE_DIR=${PWD}/ccache + +cache: + paths: + - ccache/ + +build-job: # This job runs in the build stage, which runs first. + stage: build + + script: + - echo "Compiling the code..." + - mkdir build + - cd build + - ccache cmake ../ + - ccache cmake --build ./ -j$(nproc) + - echo "Compile complete." + + artifacts: + paths: + - build/ + +test-job: # This job runs in the test stage. + stage: test # It only starts when the job in the build stage completes successfully. + script: + - echo "Running unit tests... " + - cd build/bin/ + # Testing currently doesn't work due to problems with libraries ot being visible by the binary. + - ./FreeCADCmd -t 0 + diff --git a/ci/Dockerfile b/ci/Dockerfile new file mode 100644 index 0000000000..fb5196ed4d --- /dev/null +++ b/ci/Dockerfile @@ -0,0 +1,118 @@ +FROM ubuntu:20.04 +MAINTAINER Przemo Firszt +# This is the docker image definition used to build FreeCAD. It's currently accessible on: +# https://hub.docker.com/repository/docker/freecadci/runner +# on under name freecadci/runner when using docker + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -y +RUN apt-get update -y && apt-get install -y gnupg2 +RUN echo "deb http://ppa.launchpad.net/freecad-maintainers/freecad-daily/ubuntu focal main" >> /etc/apt/sources.list.d/freecad-daily.list +RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 83193AA3B52FF6FCF10A1BBF005EAE8119BB5BCA +RUN apt-get update -y + +# those 3 are for debugging purposes only. Not required to build FreeCAD +RUN apt-get install -y \ + vim \ + nano \ + bash + +# Main set of FreeCAD dependencies. To be verified. +RUN apt-get install -y \ + ccache \ + cmake \ + debhelper \ + dh-exec \ + dh-python \ + doxygen \ + git \ + graphviz \ + libboost-date-time-dev \ + libboost-dev \ + libboost-filesystem-dev \ + libboost-filesystem1.71-dev \ + libboost-graph-dev \ + libboost-iostreams-dev \ + libboost-program-options-dev \ + libboost-program-options1.71-dev \ + libboost-python1.71-dev \ + libboost-regex-dev \ + libboost-regex1.71-dev \ + libboost-serialization-dev \ + libboost-system1.71-dev \ + libboost-thread-dev \ + libboost-thread1.71-dev \ + libboost1.71-dev \ + libcoin-dev \ + libdouble-conversion-dev \ + libeigen3-dev \ + libglew-dev \ + libgts-bin \ + libgts-dev \ + libkdtree++-dev \ + liblz4-dev \ + libmedc-dev \ + libmetis-dev \ + libnglib-dev \ + libocct-data-exchange-dev \ + libocct-ocaf-dev \ + libocct-visualization-dev \ + libopencv-dev \ + libproj-dev \ + libpyside2-dev \ + libqt5opengl5 \ + libqt5opengl5-dev \ + libqt5svg5-dev \ + libqt5webkit5 \ + libqt5webkit5-dev \ + libqt5x11extras5-dev \ + libqt5xmlpatterns5-dev \ + libshiboken2-dev \ + libspnav-dev \ + libvtk7-dev \ + libvtk7.1p \ + libvtk7.1p-qt \ + libx11-dev \ + libxerces-c-dev \ + libzipios++-dev \ + lsb-release \ + nastran \ + netgen \ + netgen-headers \ + occt-draw \ + pybind11-dev \ + pyqt5-dev-tools \ + pyside2-tools \ + python3-dev \ + python3-matplotlib \ + python3-pivy \ + python3-ply \ + python3-pyqt5 \ + python3-pyside2.* \ + python3-pyside2.qtcore \ + python3-pyside2.qtgui \ + python3-pyside2.qtsvg \ + python3-pyside2.qtuitools \ + python3-pyside2.qtwidgets \ + python3-pyside2.qtxml \ + python3-requests \ + python3-yaml \ + qt5-default \ + qt5-qmake \ + qtbase5-dev \ + qttools5-dev \ + qtwebengine5-dev \ + swig + +RUN apt-get update -y --fix-missing + +# Clean +RUN apt-get clean \ + && rm /var/lib/apt/lists/* \ + /usr/share/doc/* \ + /usr/share/locale/* \ + /usr/share/man/* \ + /usr/share/info/* -fR + + diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index c93b2b6b03..6af9e09da9 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -69,6 +69,7 @@ #include "DocumentPy.h" #include "View.h" #include "View3DPy.h" +#include "UiLoader.h" #include "WidgetFactory.h" #include "Command.h" #include "Macro.h" diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index 61d636a63b..cf23554b0d 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -52,6 +52,7 @@ #include "SplitView3DInventor.h" #include "ViewProvider.h" #include "WaitCursor.h" +#include "PythonWrapper.h" #include "WidgetFactory.h" #include "Workbench.h" #include "WorkbenchManager.h" diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index e682cc825b..5cbdfe8b00 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1032,6 +1032,8 @@ SET(Widget_CPP_SRCS QuantitySpinBox.cpp SpinBox.cpp Splashscreen.cpp + PythonWrapper.cpp + UiLoader.cpp WidgetFactory.cpp Widgets.cpp Window.cpp @@ -1048,6 +1050,8 @@ SET(Widget_HPP_SRCS QuantitySpinBox_p.h SpinBox.h Splashscreen.h + PythonWrapper.h + UiLoader.h WidgetFactory.h Widgets.h Window.h diff --git a/src/Gui/CommandPyImp.cpp b/src/Gui/CommandPyImp.cpp index b705ec8149..332434ce65 100644 --- a/src/Gui/CommandPyImp.cpp +++ b/src/Gui/CommandPyImp.cpp @@ -31,7 +31,7 @@ #include "MainWindow.h" #include "Selection.h" #include "Window.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" // inclusion of the generated files (generated out of AreaPy.xml) #include "CommandPy.h" diff --git a/src/Gui/ExpressionBindingPy.cpp b/src/Gui/ExpressionBindingPy.cpp index 6899a59177..811bc77df7 100644 --- a/src/Gui/ExpressionBindingPy.cpp +++ b/src/Gui/ExpressionBindingPy.cpp @@ -25,7 +25,7 @@ #endif #include "ExpressionBindingPy.h" #include "ExpressionBinding.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include "QuantitySpinBox.h" #include "InputField.h" #include diff --git a/src/Gui/PropertyPage.cpp b/src/Gui/PropertyPage.cpp index f95379dc55..303b730516 100644 --- a/src/Gui/PropertyPage.cpp +++ b/src/Gui/PropertyPage.cpp @@ -25,7 +25,7 @@ #include "PropertyPage.h" #include "PrefWidgets.h" -#include "WidgetFactory.h" +#include "UiLoader.h" #include using namespace Gui::Dialog; diff --git a/src/Gui/PythonWrapper.cpp b/src/Gui/PythonWrapper.cpp new file mode 100644 index 0000000000..cb4e9ba0aa --- /dev/null +++ b/src/Gui/PythonWrapper.cpp @@ -0,0 +1,647 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +#endif +#include + +// Uncomment this block to remove PySide C++ support and switch to its Python interface +//#undef HAVE_SHIBOKEN +//#undef HAVE_PYSIDE +//#undef HAVE_SHIBOKEN2 +//#undef HAVE_PYSIDE2 + +#ifdef FC_OS_WIN32 +#undef max +#undef min +#ifdef _MSC_VER +#pragma warning( disable : 4099 ) +#pragma warning( disable : 4522 ) +#endif +#endif + +// class and struct used for SbkObject +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmismatched-tags" +# pragma clang diagnostic ignored "-Wunused-parameter" +# if __clang_major__ > 3 +# pragma clang diagnostic ignored "-Wkeyword-macro" +# endif +#elif defined (__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef HAVE_SHIBOKEN +# undef _POSIX_C_SOURCE +# undef _XOPEN_SOURCE +# include +# include +# include +# include +# include +# ifdef HAVE_PYSIDE +# include +# include +PyTypeObject** SbkPySide_QtCoreTypes=nullptr; +PyTypeObject** SbkPySide_QtGuiTypes=nullptr; +# endif +#endif + +#ifdef HAVE_SHIBOKEN2 +# define HAVE_SHIBOKEN +# undef _POSIX_C_SOURCE +# undef _XOPEN_SOURCE +# include +# include +# include +# include +# ifdef HAVE_PYSIDE2 +# define HAVE_PYSIDE + +// Since version 5.12 shiboken offers a method to get wrapper by class name (typeForTypeName) +// This helps to avoid to include the PySide2 headers since MSVC has a compiler bug when +// compiling together with std::bitset (https://bugreports.qt.io/browse/QTBUG-72073) + +// Do not use SHIBOKEN_MICRO_VERSION; it might contain a dot +# define SHIBOKEN_FULL_VERSION QT_VERSION_CHECK(SHIBOKEN_MAJOR_VERSION, SHIBOKEN_MINOR_VERSION, 0) +# if (SHIBOKEN_FULL_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +# define HAVE_SHIBOKEN_TYPE_FOR_TYPENAME +# endif + +# ifndef HAVE_SHIBOKEN_TYPE_FOR_TYPENAME +# include +# include +# include +# endif +# include +PyTypeObject** SbkPySide2_QtCoreTypes=nullptr; +PyTypeObject** SbkPySide2_QtGuiTypes=nullptr; +PyTypeObject** SbkPySide2_QtWidgetsTypes=nullptr; +# endif // HAVE_PYSIDE2 +#endif // HAVE_SHIBOKEN2 + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined (__GNUC__) +# pragma GCC diagnostic pop +#endif + +#include +#include +#include + +#include "PythonWrapper.h" +#include "UiLoader.h" +#include "MetaTypes.h" + + +using namespace Gui; + +#if defined (HAVE_SHIBOKEN) + +/** + Example: + \code + ui = FreeCADGui.UiLoader() + w = ui.createWidget("Gui::InputField") + w.show() + w.property("quantity") + \endcode + */ + +PyObject* toPythonFuncQuantityTyped(Base::Quantity cpx) { + return new Base::QuantityPy(new Base::Quantity(cpx)); +} + +PyObject* toPythonFuncQuantity(const void* cpp) +{ + return toPythonFuncQuantityTyped(*reinterpret_cast(cpp)); +} + +void toCppPointerConvFuncQuantity(PyObject* pyobj,void* cpp) +{ + *((Base::Quantity*)cpp) = *static_cast(pyobj)->getQuantityPtr(); +} + +PythonToCppFunc toCppPointerCheckFuncQuantity(PyObject* obj) +{ + if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) + return toCppPointerConvFuncQuantity; + else + return 0; +} + +void BaseQuantity_PythonToCpp_QVariant(PyObject* pyIn, void* cppOut) +{ + Base::Quantity* q = static_cast(pyIn)->getQuantityPtr(); + *((QVariant*)cppOut) = QVariant::fromValue(*q); +} + +PythonToCppFunc isBaseQuantity_PythonToCpp_QVariantConvertible(PyObject* obj) +{ + if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) + return BaseQuantity_PythonToCpp_QVariant; + return 0; +} + +#if defined (HAVE_PYSIDE) +Base::Quantity convertWrapperToQuantity(const PySide::PyObjectWrapper &w) +{ + PyObject* pyIn = static_cast(w); + if (PyObject_TypeCheck(pyIn, &(Base::QuantityPy::Type))) { + return *static_cast(pyIn)->getQuantityPtr(); + } + + return Base::Quantity(std::numeric_limits::quiet_NaN()); +} +#endif + +void registerTypes() +{ + SbkConverter* convert = Shiboken::Conversions::createConverter(&Base::QuantityPy::Type, + toPythonFuncQuantity); + Shiboken::Conversions::setPythonToCppPointerFunctions(convert, + toCppPointerConvFuncQuantity, + toCppPointerCheckFuncQuantity); + Shiboken::Conversions::registerConverterName(convert, "Base::Quantity"); + + SbkConverter* qvariant_conv = Shiboken::Conversions::getConverter("QVariant"); + if (qvariant_conv) { + // The type QVariant already has a converter from PyBaseObject_Type which will + // come before our own converter. + Shiboken::Conversions::addPythonToCppValueConversion(qvariant_conv, + BaseQuantity_PythonToCpp_QVariant, + isBaseQuantity_PythonToCpp_QVariantConvertible); + } + +#if defined (HAVE_PYSIDE) + QMetaType::registerConverter(&convertWrapperToQuantity); +#endif +} +#endif + +// -------------------------------------------------------- + +namespace Gui { +template +Py::Object qt_wrapInstance(qttype object, const char* className, + const char* shiboken, const char* pyside, + const char* wrap) +{ + PyObject* module = PyImport_ImportModule(shiboken); + if (!module) { + std::string error = "Cannot load "; + error += shiboken; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module mainmod(module, true); + Py::Callable func = mainmod.getDict().getItem(wrap); + + Py::Tuple arguments(2); + arguments[0] = Py::asObject(PyLong_FromVoidPtr((void*)object)); + + module = PyImport_ImportModule(pyside); + if (!module) { + std::string error = "Cannot load "; + error += pyside; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module qtmod(module); + arguments[1] = qtmod.getDict().getItem(className); + return func.apply(arguments); +} + +const char* qt_identifyType(QObject* ptr, const char* pyside) +{ + PyObject* module = PyImport_ImportModule(pyside); + if (!module) { + std::string error = "Cannot load "; + error += pyside; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module qtmod(module); + const QMetaObject* metaObject = ptr->metaObject(); + while (metaObject) { + const char* className = metaObject->className(); + if (qtmod.getDict().hasKey(className)) + return className; + metaObject = metaObject->superClass(); + } + + return nullptr; +} + +void* qt_getCppPointer(const Py::Object& pyobject, const char* shiboken, const char* unwrap) +{ + // https://github.com/PySide/Shiboken/blob/master/shibokenmodule/typesystem_shiboken.xml + PyObject* module = PyImport_ImportModule(shiboken); + if (!module) { + std::string error = "Cannot load "; + error += shiboken; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module mainmod(module, true); + Py::Callable func = mainmod.getDict().getItem(unwrap); + + Py::Tuple arguments(1); + arguments[0] = pyobject; //PySide pointer + Py::Tuple result(func.apply(arguments)); + void* ptr = PyLong_AsVoidPtr(result[0].ptr()); + return ptr; +} + + +template +PyTypeObject *getPyTypeObjectForTypeName() +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) +#if defined (HAVE_SHIBOKEN_TYPE_FOR_TYPENAME) + SbkObjectType* sbkType = Shiboken::ObjectType::typeForTypeName(typeid(qttype).name()); + if (sbkType) + return &(sbkType->type); +#else + return Shiboken::SbkType(); +#endif +#endif + return nullptr; +} +} + +// -------------------------------------------------------- + +PythonWrapper::PythonWrapper() +{ +#if defined (HAVE_SHIBOKEN) + static bool init = false; + if (!init) { + init = true; + registerTypes(); + } +#endif +} + +bool PythonWrapper::toCString(const Py::Object& pyobject, std::string& str) +{ + if (PyUnicode_Check(pyobject.ptr())) { + PyObject* unicode = PyUnicode_AsUTF8String(pyobject.ptr()); + str = PyBytes_AsString(unicode); + Py_DECREF(unicode); + return true; + } + else if (PyBytes_Check(pyobject.ptr())) { + str = PyBytes_AsString(pyobject.ptr()); + return true; + } +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + if (Shiboken::String::check(pyobject.ptr())) { + const char* s = Shiboken::String::toCString(pyobject.ptr()); + if (s) str = s; + return true; + } +#endif + return false; +} + +QObject* PythonWrapper::toQObject(const Py::Object& pyobject) +{ + // http://pastebin.com/JByDAF5Z +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject * type = getPyTypeObjectForTypeName(); + if (type) { + if (Shiboken::Object::checkType(pyobject.ptr())) { + SbkObject* sbkobject = reinterpret_cast(pyobject.ptr()); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + // Access shiboken2/PySide2 via Python + // + void* ptr = qt_getCppPointer(pyobject, "shiboken2", "getCppPointer"); + return reinterpret_cast(ptr); +#endif + +#if 0 // Unwrapping using sip/PyQt + void* ptr = qt_getCppPointer(pyobject, "sip", "unwrapinstance"); + return reinterpret_cast(ptr); +#endif + + return 0; +} + +QGraphicsItem* PythonWrapper::toQGraphicsItem(PyObject* pyPtr) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject* type = getPyTypeObjectForTypeName(); + if (type) { + if (Shiboken::Object::checkType(pyPtr)) { + SbkObject* sbkobject = reinterpret_cast(pyPtr); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + // Access shiboken2/PySide2 via Python + // + void* ptr = qt_getCppPointer(Py::asObject(pyPtr), "shiboken2", "getCppPointer"); + return reinterpret_cast(ptr); +#endif + return nullptr; +} + +Py::Object PythonWrapper::fromQIcon(const QIcon* icon) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + const char* typeName = typeid(*const_cast(icon)).name(); + PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), + const_cast(icon), true, false, typeName); + if (pyobj) + return Py::asObject(pyobj); +#else + // Access shiboken2/PySide2 via Python + // + return qt_wrapInstance(icon, "QIcon", "shiboken2", "PySide2.QtGui", "wrapInstance"); +#endif + throw Py::RuntimeError("Failed to wrap icon"); +} + +QIcon *PythonWrapper::toQIcon(PyObject *pyobj) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject * type = getPyTypeObjectForTypeName(); + if(type) { + if (Shiboken::Object::checkType(pyobj)) { + SbkObject* sbkobject = reinterpret_cast(pyobj); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + Q_UNUSED(pyobj); +#endif + return 0; +} + +Py::Object PythonWrapper::fromQDir(const QDir& dir) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + const char* typeName = typeid(dir).name(); + PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), + const_cast(&dir), false, false, typeName); + if (pyobj) + return Py::asObject(pyobj); +#endif + throw Py::RuntimeError("Failed to wrap icon"); +} + +QDir* PythonWrapper::toQDir(PyObject* pyobj) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject* type = getPyTypeObjectForTypeName(); + if (type) { + if (Shiboken::Object::checkType(pyobj)) { + SbkObject* sbkobject = reinterpret_cast(pyobj); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + Q_UNUSED(pyobj); +#endif + return nullptr; +} + +Py::Object PythonWrapper::fromQObject(QObject* object, const char* className) +{ + if (!object) + return Py::None(); +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // Access shiboken/PySide via C++ + // + PyTypeObject * type = getPyTypeObjectForTypeName(); + if (type) { + SbkObjectType* sbk_type = reinterpret_cast(type); + std::string typeName; + if (className) + typeName = className; + else + typeName = object->metaObject()->className(); + PyObject* pyobj = Shiboken::Object::newObject(sbk_type, object, false, false, typeName.c_str()); + return Py::asObject(pyobj); + } + throw Py::RuntimeError("Failed to wrap object"); +#else + // Access shiboken2/PySide2 via Python + // + return qt_wrapInstance(object, className, "shiboken2", "PySide2.QtCore", "wrapInstance"); +#endif +#if 0 // Unwrapping using sip/PyQt + Q_UNUSED(className); + return qt_wrapInstance(object, "QObject", "sip", "PyQt5.QtCore", "wrapinstance"); +#endif +} + +Py::Object PythonWrapper::fromQWidget(QWidget* widget, const char* className) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // Access shiboken/PySide via C++ + // + PyTypeObject * type = getPyTypeObjectForTypeName(); + if (type) { + SbkObjectType* sbk_type = reinterpret_cast(type); + std::string typeName; + if (className) + typeName = className; + else + typeName = widget->metaObject()->className(); + PyObject* pyobj = Shiboken::Object::newObject(sbk_type, widget, false, false, typeName.c_str()); + return Py::asObject(pyobj); + } + throw Py::RuntimeError("Failed to wrap widget"); + +#else + // Access shiboken2/PySide2 via Python + // + return qt_wrapInstance(widget, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance"); +#endif + +#if 0 // Unwrapping using sip/PyQt + Q_UNUSED(className); + return qt_wrapInstance(widget, "QWidget", "sip", "PyQt5.QtWidgets", "wrapinstance"); +#endif +} + +const char* PythonWrapper::getWrapperName(QObject* obj) const +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + const QMetaObject* meta = obj->metaObject(); + while (meta) { + const char* typeName = meta->className(); + PyTypeObject* exactType = Shiboken::Conversions::getPythonTypeObject(typeName); + if (exactType) + return typeName; + meta = meta->superClass(); + } +#else + QUiLoader ui; + QStringList names = ui.availableWidgets(); + const QMetaObject* meta = obj->metaObject(); + while (meta) { + const char* typeName = meta->className(); + if (names.indexOf(QLatin1String(typeName)) >= 0) + return typeName; + meta = meta->superClass(); + } +#endif + return "QObject"; +} + +bool PythonWrapper::loadCoreModule() +{ +#if defined (HAVE_SHIBOKEN2) && (HAVE_PYSIDE2) + // QtCore + if (!SbkPySide2_QtCoreTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtCore")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); + } +#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // QtCore + if (!SbkPySide_QtCoreTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtCore")); + if (requiredModule.isNull()) + return false; + SbkPySide_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +bool PythonWrapper::loadGuiModule() +{ +#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) + // QtGui + if (!SbkPySide2_QtGuiTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtGui")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); + } +#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // QtGui + if (!SbkPySide_QtGuiTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtGui")); + if (requiredModule.isNull()) + return false; + SbkPySide_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +bool PythonWrapper::loadWidgetsModule() +{ +#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) + // QtWidgets + if (!SbkPySide2_QtWidgetsTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtWidgets")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtWidgetsTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +bool PythonWrapper::loadUiToolsModule() +{ +#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) + // QtUiTools + static PyTypeObject** SbkPySide2_QtUiToolsTypes = nullptr; + if (!SbkPySide2_QtUiToolsTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtUiTools")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtUiToolsTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +void PythonWrapper::createChildrenNameAttributes(PyObject* root, QObject* object) +{ + Q_FOREACH (QObject* child, object->children()) { + const QByteArray name = child->objectName().toLocal8Bit(); + + if (!name.isEmpty() && !name.startsWith("_") && !name.startsWith("qt_")) { + bool hasAttr = PyObject_HasAttrString(root, name.constData()); + if (!hasAttr) { +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + Shiboken::AutoDecRef pyChild(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), child)); + PyObject_SetAttrString(root, name.constData(), pyChild); +#else + const char* className = qt_identifyType(child, "PySide2.QtWidgets"); + if (!className) { + if (qobject_cast(child)) + className = "QWidget"; + else + className = "QObject"; + } + + Py::Object pyChild(qt_wrapInstance(child, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance")); + PyObject_SetAttrString(root, name.constData(), pyChild.ptr()); +#endif + } + createChildrenNameAttributes(root, child); + } + createChildrenNameAttributes(root, child); + } +} + +void PythonWrapper::setParent(PyObject* pyWdg, QObject* parent) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + if (parent) { + Shiboken::AutoDecRef pyParent(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), parent)); + Shiboken::Object::setParent(pyParent, pyWdg); + } +#else + Q_UNUSED(pyWdg); + Q_UNUSED(parent); +#endif +} diff --git a/src/Gui/PythonWrapper.h b/src/Gui/PythonWrapper.h new file mode 100644 index 0000000000..7b5a8929ab --- /dev/null +++ b/src/Gui/PythonWrapper.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_PYTHONWRAPPER_H +#define GUI_PYTHONWRAPPER_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QDir; +QT_END_NAMESPACE + +namespace Gui { + +class GuiExport PythonWrapper +{ +public: + PythonWrapper(); + bool loadCoreModule(); + bool loadGuiModule(); + bool loadWidgetsModule(); + bool loadUiToolsModule(); + + bool toCString(const Py::Object&, std::string&); + QObject* toQObject(const Py::Object&); + QGraphicsItem* toQGraphicsItem(PyObject* ptr); + Py::Object fromQObject(QObject*, const char* className=0); + Py::Object fromQWidget(QWidget*, const char* className=0); + const char* getWrapperName(QObject*) const; + /*! + Create a Python wrapper for the icon. The icon must be created on the heap + and the Python wrapper takes ownership of it. + */ + Py::Object fromQIcon(const QIcon*); + QIcon *toQIcon(PyObject *pyobj); + Py::Object fromQDir(const QDir&); + QDir* toQDir(PyObject* pyobj); + static void createChildrenNameAttributes(PyObject* root, QObject* object); + static void setParent(PyObject* pyWdg, QObject* parent); +}; + +} // namespace Gui + +#endif // GUI_PYTHONWRAPPER_H diff --git a/src/Gui/Qt4All.h b/src/Gui/Qt4All.h index 3dca95bd68..256dd249cc 100644 --- a/src/Gui/Qt4All.h +++ b/src/Gui/Qt4All.h @@ -157,9 +157,6 @@ // QtSvg #include #include -// QtUiTools -#include -#include #include "qmath.h" #include diff --git a/src/Gui/TaskView/TaskDialogPython.cpp b/src/Gui/TaskView/TaskDialogPython.cpp index 82da806206..7415c85799 100644 --- a/src/Gui/TaskView/TaskDialogPython.cpp +++ b/src/Gui/TaskView/TaskDialogPython.cpp @@ -36,7 +36,8 @@ #include #include #include -#include +#include +#include #include #include #include diff --git a/src/Gui/UiLoader.cpp b/src/Gui/UiLoader.cpp new file mode 100644 index 0000000000..b90f3c59db --- /dev/null +++ b/src/Gui/UiLoader.cpp @@ -0,0 +1,316 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +#endif + +#include "UiLoader.h" +#include "PythonWrapper.h" +#include "WidgetFactory.h" +#include + +using namespace Gui; + + +PySideUicModule::PySideUicModule() + : Py::ExtensionModule("PySideUic") +{ + add_varargs_method("loadUiType",&PySideUicModule::loadUiType, + "PySide lacks the \"loadUiType\" command, so we have to convert the ui file to py code in-memory first\n" + "and then execute it in a special frame to retrieve the form_class."); + add_varargs_method("loadUi",&PySideUicModule::loadUi, + "Addition of \"loadUi\" to PySide."); + initialize("PySideUic helper module"); // register with Python +} + +Py::Object PySideUicModule::loadUiType(const Py::Tuple& args) +{ + Base::PyGILStateLocker lock; + PyObject* main = PyImport_AddModule("__main__"); + PyObject* dict = PyModule_GetDict(main); + Py::Dict d(PyDict_Copy(dict), true); + Py::String uiFile(args.getItem(0)); + std::string file = uiFile.as_string(); + std::replace(file.begin(), file.end(), '\\', '/'); + + QString cmd; + QTextStream str(&cmd); + // https://github.com/albop/dolo/blob/master/bin/load_ui.py + str << "import pyside2uic\n" + << "from PySide2 import QtCore, QtGui, QtWidgets\n" + << "import xml.etree.ElementTree as xml\n" + << "try:\n" + << " from cStringIO import StringIO\n" + << "except Exception:\n" + << " from io import StringIO\n" + << "\n" + << "uiFile = \"" << file.c_str() << "\"\n" + << "parsed = xml.parse(uiFile)\n" + << "widget_class = parsed.find('widget').get('class')\n" + << "form_class = parsed.find('class').text\n" + << "with open(uiFile, 'r') as f:\n" + << " o = StringIO()\n" + << " frame = {}\n" + << " pyside2uic.compileUi(f, o, indent=0)\n" + << " pyc = compile(o.getvalue(), '', 'exec')\n" + << " exec(pyc, frame)\n" + << " #Fetch the base_class and form class based on their type in the xml from designer\n" + << " form_class = frame['Ui_%s'%form_class]\n" + << " base_class = eval('QtWidgets.%s'%widget_class)\n"; + + PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); + if (result) { + Py_DECREF(result); + if (d.hasKey("form_class") && d.hasKey("base_class")) { + Py::Tuple t(2); + t.setItem(0, d.getItem("form_class")); + t.setItem(1, d.getItem("base_class")); + return t; + } + } + else { + throw Py::Exception(); + } + + return Py::None(); +} + +Py::Object PySideUicModule::loadUi(const Py::Tuple& args) +{ + Base::PyGILStateLocker lock; + PyObject* main = PyImport_AddModule("__main__"); + PyObject* dict = PyModule_GetDict(main); + Py::Dict d(PyDict_Copy(dict), true); + d.setItem("uiFile_", args[0]); + if (args.size() > 1) + d.setItem("base_", args[1]); + else + d.setItem("base_", Py::None()); + + QString cmd; + QTextStream str(&cmd); +#if 0 + // https://github.com/lunaryorn/snippets/blob/master/qt4/designer/pyside_dynamic.py + str << "from PySide import QtCore, QtGui, QtUiTools\n" + << "import FreeCADGui" + << "\n" + << "class UiLoader(QtUiTools.QUiLoader):\n" + << " def __init__(self, baseinstance):\n" + << " QtUiTools.QUiLoader.__init__(self, baseinstance)\n" + << " self.baseinstance = baseinstance\n" + << " self.ui = FreeCADGui.UiLoader()\n" + << "\n" + << " def createWidget(self, class_name, parent=None, name=''):\n" + << " if parent is None and self.baseinstance:\n" + << " return self.baseinstance\n" + << " else:\n" + << " widget = self.ui.createWidget(class_name, parent, name)\n" + << " if not widget:\n" + << " widget = QtUiTools.QUiLoader.createWidget(self, class_name, parent, name)\n" + << " if self.baseinstance:\n" + << " setattr(self.baseinstance, name, widget)\n" + << " return widget\n" + << "\n" + << "loader = UiLoader(globals()[\"base_\"])\n" + << "widget = loader.load(globals()[\"uiFile_\"])\n" + << "\n"; +#else + str << "from PySide2 import QtCore, QtGui, QtWidgets\n" + << "import FreeCADGui" + << "\n" + << "loader = FreeCADGui.UiLoader()\n" + << "widget = loader.load(globals()[\"uiFile_\"])\n" + << "\n"; +#endif + + PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); + if (result) { + Py_DECREF(result); + if (d.hasKey("widget")) { + return d.getItem("widget"); + } + } + else { + throw Py::Exception(); + } + + return Py::None(); +} + +// ---------------------------------------------------- + +UiLoader::UiLoader(QObject* parent) + : QUiLoader(parent) +{ + // do not use the plugins for additional widgets as we don't need them and + // the application may crash under Linux (tested on Ubuntu 7.04 & 7.10). + clearPluginPaths(); + this->cw = availableWidgets(); +} + +UiLoader::~UiLoader() +{ +} + +QWidget* UiLoader::createWidget(const QString & className, QWidget * parent, + const QString& name) +{ + if (this->cw.contains(className)) + return QUiLoader::createWidget(className, parent, name); + QWidget* w = 0; + if (WidgetFactory().CanProduce((const char*)className.toLatin1())) + w = WidgetFactory().createWidget((const char*)className.toLatin1(), parent); + if (w) w->setObjectName(name); + return w; +} + +// ---------------------------------------------------- + +PyObject *UiLoaderPy::PyMake(struct _typeobject * /*type*/, PyObject * args, PyObject * /*kwds*/) +{ + if (!PyArg_ParseTuple(args, "")) + return 0; + return new UiLoaderPy(); +} + +void UiLoaderPy::init_type() +{ + behaviors().name("UiLoader"); + behaviors().doc("UiLoader to create widgets"); + behaviors().set_tp_new(PyMake); + // you must have overwritten the virtual functions + behaviors().supportRepr(); + behaviors().supportGetattr(); + behaviors().supportSetattr(); + add_varargs_method("load",&UiLoaderPy::load,"load(string, QWidget parent=None) -> QWidget\n" + "load(QIODevice, QWidget parent=None) -> QWidget"); + add_varargs_method("createWidget",&UiLoaderPy::createWidget,"createWidget()"); +} + +UiLoaderPy::UiLoaderPy() +{ +} + +UiLoaderPy::~UiLoaderPy() +{ +} + +Py::Object UiLoaderPy::repr() +{ + std::string s; + std::ostringstream s_out; + s_out << "Ui loader"; + return Py::String(s_out.str()); +} + +Py::Object UiLoaderPy::load(const Py::Tuple& args) +{ + Gui::PythonWrapper wrap; + if (wrap.loadCoreModule()) { + std::string fn; + QFile file; + QIODevice* device = 0; + QWidget* parent = 0; + if (wrap.toCString(args[0], fn)) { + file.setFileName(QString::fromUtf8(fn.c_str())); + if (!file.open(QFile::ReadOnly)) + throw Py::RuntimeError("Cannot open file"); + device = &file; + } + else if (args[0].isString()) { + fn = (std::string)Py::String(args[0]); + file.setFileName(QString::fromUtf8(fn.c_str())); + if (!file.open(QFile::ReadOnly)) + throw Py::RuntimeError("Cannot open file"); + device = &file; + } + else { + QObject* obj = wrap.toQObject(args[0]); + device = qobject_cast(obj); + } + + if (args.size() > 1) { + QObject* obj = wrap.toQObject(args[1]); + parent = qobject_cast(obj); + } + + if (device) { + QWidget* widget = loader.load(device, parent); + if (widget) { + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + + const char* typeName = wrap.getWrapperName(widget); + Py::Object pyWdg = wrap.fromQWidget(widget, typeName); + wrap.createChildrenNameAttributes(*pyWdg, widget); + wrap.setParent(*pyWdg, parent); + return pyWdg; + } + } + else { + throw Py::TypeError("string or QIODevice expected"); + } + } + return Py::None(); +} + +Py::Object UiLoaderPy::createWidget(const Py::Tuple& args) +{ + Gui::PythonWrapper wrap; + + // 1st argument + Py::String str(args[0]); + std::string className; + className = str.as_std_string("utf-8"); + // 2nd argument + QWidget* parent = 0; + if (wrap.loadCoreModule() && args.size() > 1) { + QObject* object = wrap.toQObject(args[1]); + if (object) + parent = qobject_cast(object); + } + + // 3rd argument + std::string objectName; + if (args.size() > 2) { + Py::String str(args[2]); + objectName = str.as_std_string("utf-8"); + } + + QWidget* widget = loader.createWidget(QString::fromLatin1(className.c_str()), parent, + QString::fromLatin1(objectName.c_str())); + if (!widget) { + std::string err = "No such widget class '"; + err += className; + err += "'"; + throw Py::RuntimeError(err); + } + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + + const char* typeName = wrap.getWrapperName(widget); + return wrap.fromQWidget(widget, typeName); +} diff --git a/src/Gui/UiLoader.h b/src/Gui/UiLoader.h new file mode 100644 index 0000000000..e4567c0191 --- /dev/null +++ b/src/Gui/UiLoader.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_UILOADER_H +#define GUI_UILOADER_H + +#include +#include + + +namespace Gui { + +class PySideUicModule : public Py::ExtensionModule +{ + +public: + PySideUicModule(); + virtual ~PySideUicModule() {} + +private: + Py::Object loadUiType(const Py::Tuple& args); + Py::Object loadUi(const Py::Tuple& args); +}; + +/** + * The UiLoader class provides the abitlity to use the widget factory + * framework of FreeCAD within the framework provided by Qt. This class + * extends QUiLoader by the creation of FreeCAD specific widgets. + * @author Werner Mayer + */ +class UiLoader : public QUiLoader +{ +public: + UiLoader(QObject* parent=0); + virtual ~UiLoader(); + + /** + * Creates a widget of the type \a className with the parent \a parent. + * For more details see the documentation to QWidgetFactory. + */ + QWidget* createWidget(const QString & className, QWidget * parent=0, + const QString& name = QString()); +private: + QStringList cw; +}; + +// -------------------------------------------------------------------- + +class UiLoaderPy : public Py::PythonExtension +{ +public: + static void init_type(void); // announce properties and methods + + UiLoaderPy(); + ~UiLoaderPy(); + + Py::Object repr(); + Py::Object createWidget(const Py::Tuple&); + Py::Object load(const Py::Tuple&); + +private: + static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *); + +private: + UiLoader loader; +}; + +} // namespace Gui + +#endif // GUI_UILOADER_H diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index 18cf5985ec..50e8325c3a 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -51,7 +51,7 @@ #include "View3DInventorViewer.h" #include "View3DViewerPy.h" #include "ActiveObjectList.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include diff --git a/src/Gui/ViewProviderPyImp.cpp b/src/Gui/ViewProviderPyImp.cpp index 506050f9fc..db048d382d 100644 --- a/src/Gui/ViewProviderPyImp.cpp +++ b/src/Gui/ViewProviderPyImp.cpp @@ -37,7 +37,7 @@ #include "SoFCDB.h" #include "ViewProvider.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include diff --git a/src/Gui/ViewProviderPythonFeature.cpp b/src/Gui/ViewProviderPythonFeature.cpp index 56c32885de..7b1c946844 100644 --- a/src/Gui/ViewProviderPythonFeature.cpp +++ b/src/Gui/ViewProviderPythonFeature.cpp @@ -57,7 +57,7 @@ #include "Application.h" #include "BitmapFactory.h" #include "Document.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include "View3DInventorViewer.h" #include #include diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index 4ffeeff5db..70c30fdc3e 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -25,15 +25,7 @@ #ifndef _PreComp_ # include # include -# include #endif -#include - -// Uncomment this block to remove PySide C++ support and switch to its Python interface -//#undef HAVE_SHIBOKEN -//#undef HAVE_PYSIDE -//#undef HAVE_SHIBOKEN2 -//#undef HAVE_PYSIDE2 #ifdef FC_OS_WIN32 #undef max @@ -44,570 +36,22 @@ #endif #endif -// class and struct used for SbkObject -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wmismatched-tags" -# pragma clang diagnostic ignored "-Wunused-parameter" -# if __clang_major__ > 3 -# pragma clang diagnostic ignored "-Wkeyword-macro" -# endif -#elif defined (__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-parameter" -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - -#ifdef HAVE_SHIBOKEN -# undef _POSIX_C_SOURCE -# undef _XOPEN_SOURCE -# include -# include -# include -# include -# include -# ifdef HAVE_PYSIDE -# include -# include -PyTypeObject** SbkPySide_QtCoreTypes=nullptr; -PyTypeObject** SbkPySide_QtGuiTypes=nullptr; -# endif -#endif - -#ifdef HAVE_SHIBOKEN2 -# define HAVE_SHIBOKEN -# undef _POSIX_C_SOURCE -# undef _XOPEN_SOURCE -# include -# include -# include -# include -# ifdef HAVE_PYSIDE2 -# define HAVE_PYSIDE - -// Since version 5.12 shiboken offers a method to get wrapper by class name (typeForTypeName) -// This helps to avoid to include the PySide2 headers since MSVC has a compiler bug when -// compiling together with std::bitset (https://bugreports.qt.io/browse/QTBUG-72073) - -// Do not use SHIBOKEN_MICRO_VERSION; it might contain a dot -# define SHIBOKEN_FULL_VERSION QT_VERSION_CHECK(SHIBOKEN_MAJOR_VERSION, SHIBOKEN_MINOR_VERSION, 0) -# if (SHIBOKEN_FULL_VERSION >= QT_VERSION_CHECK(5, 12, 0)) -# define HAVE_SHIBOKEN_TYPE_FOR_TYPENAME -# endif - -# ifndef HAVE_SHIBOKEN_TYPE_FOR_TYPENAME -# include -# include -# include -# endif -# include -PyTypeObject** SbkPySide2_QtCoreTypes=nullptr; -PyTypeObject** SbkPySide2_QtGuiTypes=nullptr; -PyTypeObject** SbkPySide2_QtWidgetsTypes=nullptr; -# endif // HAVE_PYSIDE2 -#endif // HAVE_SHIBOKEN2 - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined (__GNUC__) -# pragma GCC diagnostic pop -#endif - #include #include #include #include #include -#include -#include #include "WidgetFactory.h" +#include "UiLoader.h" +#include "PythonWrapper.h" #include "PrefWidgets.h" #include "PropertyPage.h" using namespace Gui; -#if defined (HAVE_SHIBOKEN) - -/** - Example: - \code - ui = FreeCADGui.UiLoader() - w = ui.createWidget("Gui::InputField") - w.show() - w.property("quantity") - \endcode - */ - -PyObject* toPythonFuncQuantityTyped(Base::Quantity cpx) { - return new Base::QuantityPy(new Base::Quantity(cpx)); -} - -PyObject* toPythonFuncQuantity(const void* cpp) -{ - return toPythonFuncQuantityTyped(*reinterpret_cast(cpp)); -} - -void toCppPointerConvFuncQuantity(PyObject* pyobj,void* cpp) -{ - *((Base::Quantity*)cpp) = *static_cast(pyobj)->getQuantityPtr(); -} - -PythonToCppFunc toCppPointerCheckFuncQuantity(PyObject* obj) -{ - if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) - return toCppPointerConvFuncQuantity; - else - return 0; -} - -void BaseQuantity_PythonToCpp_QVariant(PyObject* pyIn, void* cppOut) -{ - Base::Quantity* q = static_cast(pyIn)->getQuantityPtr(); - *((QVariant*)cppOut) = QVariant::fromValue(*q); -} - -PythonToCppFunc isBaseQuantity_PythonToCpp_QVariantConvertible(PyObject* obj) -{ - if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) - return BaseQuantity_PythonToCpp_QVariant; - return 0; -} - -#if defined (HAVE_PYSIDE) -Base::Quantity convertWrapperToQuantity(const PySide::PyObjectWrapper &w) -{ - PyObject* pyIn = static_cast(w); - if (PyObject_TypeCheck(pyIn, &(Base::QuantityPy::Type))) { - return *static_cast(pyIn)->getQuantityPtr(); - } - - return Base::Quantity(std::numeric_limits::quiet_NaN()); -} -#endif - -void registerTypes() -{ - SbkConverter* convert = Shiboken::Conversions::createConverter(&Base::QuantityPy::Type, - toPythonFuncQuantity); - Shiboken::Conversions::setPythonToCppPointerFunctions(convert, - toCppPointerConvFuncQuantity, - toCppPointerCheckFuncQuantity); - Shiboken::Conversions::registerConverterName(convert, "Base::Quantity"); - - SbkConverter* qvariant_conv = Shiboken::Conversions::getConverter("QVariant"); - if (qvariant_conv) { - // The type QVariant already has a converter from PyBaseObject_Type which will - // come before our own converter. - Shiboken::Conversions::addPythonToCppValueConversion(qvariant_conv, - BaseQuantity_PythonToCpp_QVariant, - isBaseQuantity_PythonToCpp_QVariantConvertible); - } - -#if defined (HAVE_PYSIDE) - QMetaType::registerConverter(&convertWrapperToQuantity); -#endif -} -#endif - -// -------------------------------------------------------- - -namespace Gui { -template -Py::Object qt_wrapInstance(qttype object, const char* className, - const char* shiboken, const char* pyside, - const char* wrap) -{ - PyObject* module = PyImport_ImportModule(shiboken); - if (!module) { - std::string error = "Cannot load "; - error += shiboken; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module mainmod(module, true); - Py::Callable func = mainmod.getDict().getItem(wrap); - - Py::Tuple arguments(2); - arguments[0] = Py::asObject(PyLong_FromVoidPtr((void*)object)); - - module = PyImport_ImportModule(pyside); - if (!module) { - std::string error = "Cannot load "; - error += pyside; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module qtmod(module); - arguments[1] = qtmod.getDict().getItem(className); - return func.apply(arguments); -} - -const char* qt_identifyType(QObject* ptr, const char* pyside) -{ - PyObject* module = PyImport_ImportModule(pyside); - if (!module) { - std::string error = "Cannot load "; - error += pyside; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module qtmod(module); - const QMetaObject* metaObject = ptr->metaObject(); - while (metaObject) { - const char* className = metaObject->className(); - if (qtmod.getDict().hasKey(className)) - return className; - metaObject = metaObject->superClass(); - } - - return nullptr; -} - -void* qt_getCppPointer(const Py::Object& pyobject, const char* shiboken, const char* unwrap) -{ - // https://github.com/PySide/Shiboken/blob/master/shibokenmodule/typesystem_shiboken.xml - PyObject* module = PyImport_ImportModule(shiboken); - if (!module) { - std::string error = "Cannot load "; - error += shiboken; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module mainmod(module, true); - Py::Callable func = mainmod.getDict().getItem(unwrap); - - Py::Tuple arguments(1); - arguments[0] = pyobject; //PySide pointer - Py::Tuple result(func.apply(arguments)); - void* ptr = PyLong_AsVoidPtr(result[0].ptr()); - return ptr; -} - - -template -PyTypeObject *getPyTypeObjectForTypeName() -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) -#if defined (HAVE_SHIBOKEN_TYPE_FOR_TYPENAME) - SbkObjectType* sbkType = Shiboken::ObjectType::typeForTypeName(typeid(qttype).name()); - if (sbkType) - return &(sbkType->type); -#else - return Shiboken::SbkType(); -#endif -#endif - return nullptr; -} -} - -// -------------------------------------------------------- - -PythonWrapper::PythonWrapper() -{ -#if defined (HAVE_SHIBOKEN) - static bool init = false; - if (!init) { - init = true; - registerTypes(); - } -#endif -} - -bool PythonWrapper::toCString(const Py::Object& pyobject, std::string& str) -{ - if (PyUnicode_Check(pyobject.ptr())) { - PyObject* unicode = PyUnicode_AsUTF8String(pyobject.ptr()); - str = PyBytes_AsString(unicode); - Py_DECREF(unicode); - return true; - } - else if (PyBytes_Check(pyobject.ptr())) { - str = PyBytes_AsString(pyobject.ptr()); - return true; - } -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - if (Shiboken::String::check(pyobject.ptr())) { - const char* s = Shiboken::String::toCString(pyobject.ptr()); - if (s) str = s; - return true; - } -#endif - return false; -} - -QObject* PythonWrapper::toQObject(const Py::Object& pyobject) -{ - // http://pastebin.com/JByDAF5Z -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - PyTypeObject * type = getPyTypeObjectForTypeName(); - if (type) { - if (Shiboken::Object::checkType(pyobject.ptr())) { - SbkObject* sbkobject = reinterpret_cast(pyobject.ptr()); - void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); - return reinterpret_cast(cppobject); - } - } -#else - // Access shiboken2/PySide2 via Python - // - void* ptr = qt_getCppPointer(pyobject, "shiboken2", "getCppPointer"); - return reinterpret_cast(ptr); -#endif - -#if 0 // Unwrapping using sip/PyQt - void* ptr = qt_getCppPointer(pyobject, "sip", "unwrapinstance"); - return reinterpret_cast(ptr); -#endif - - return 0; -} - -QGraphicsItem* PythonWrapper::toQGraphicsItem(PyObject* pyPtr) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - PyTypeObject* type = getPyTypeObjectForTypeName(); - if (type) { - if (Shiboken::Object::checkType(pyPtr)) { - SbkObject* sbkobject = reinterpret_cast(pyPtr); - void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); - return reinterpret_cast(cppobject); - } - } -#else - // Access shiboken2/PySide2 via Python - // - void* ptr = qt_getCppPointer(Py::asObject(pyPtr), "shiboken2", "getCppPointer"); - return reinterpret_cast(ptr); -#endif - return nullptr; -} - -Py::Object PythonWrapper::fromQIcon(const QIcon* icon) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - const char* typeName = typeid(*const_cast(icon)).name(); - PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), - const_cast(icon), true, false, typeName); - if (pyobj) - return Py::asObject(pyobj); -#else - // Access shiboken2/PySide2 via Python - // - return qt_wrapInstance(icon, "QIcon", "shiboken2", "PySide2.QtGui", "wrapInstance"); -#endif - throw Py::RuntimeError("Failed to wrap icon"); -} - -QIcon *PythonWrapper::toQIcon(PyObject *pyobj) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - PyTypeObject * type = getPyTypeObjectForTypeName(); - if(type) { - if (Shiboken::Object::checkType(pyobj)) { - SbkObject* sbkobject = reinterpret_cast(pyobj); - void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); - return reinterpret_cast(cppobject); - } - } -#else - Q_UNUSED(pyobj); -#endif - return 0; -} - -Py::Object PythonWrapper::fromQObject(QObject* object, const char* className) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // Access shiboken/PySide via C++ - // - PyTypeObject * type = getPyTypeObjectForTypeName(); - if (type) { - SbkObjectType* sbk_type = reinterpret_cast(type); - std::string typeName; - if (className) - typeName = className; - else - typeName = object->metaObject()->className(); - PyObject* pyobj = Shiboken::Object::newObject(sbk_type, object, false, false, typeName.c_str()); - return Py::asObject(pyobj); - } - throw Py::RuntimeError("Failed to wrap object"); -#else - // Access shiboken2/PySide2 via Python - // - return qt_wrapInstance(object, className, "shiboken2", "PySide2.QtCore", "wrapInstance"); -#endif -#if 0 // Unwrapping using sip/PyQt - Q_UNUSED(className); - return qt_wrapInstance(object, "QObject", "sip", "PyQt5.QtCore", "wrapinstance"); -#endif -} - -Py::Object PythonWrapper::fromQWidget(QWidget* widget, const char* className) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // Access shiboken/PySide via C++ - // - PyTypeObject * type = getPyTypeObjectForTypeName(); - if (type) { - SbkObjectType* sbk_type = reinterpret_cast(type); - std::string typeName; - if (className) - typeName = className; - else - typeName = widget->metaObject()->className(); - PyObject* pyobj = Shiboken::Object::newObject(sbk_type, widget, false, false, typeName.c_str()); - return Py::asObject(pyobj); - } - throw Py::RuntimeError("Failed to wrap widget"); - -#else - // Access shiboken2/PySide2 via Python - // - return qt_wrapInstance(widget, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance"); -#endif - -#if 0 // Unwrapping using sip/PyQt - Q_UNUSED(className); - return qt_wrapInstance(widget, "QWidget", "sip", "PyQt5.QtWidgets", "wrapinstance"); -#endif -} - -const char* PythonWrapper::getWrapperName(QObject* obj) const -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - const QMetaObject* meta = obj->metaObject(); - while (meta) { - const char* typeName = meta->className(); - PyTypeObject* exactType = Shiboken::Conversions::getPythonTypeObject(typeName); - if (exactType) - return typeName; - meta = meta->superClass(); - } -#else - QUiLoader ui; - QStringList names = ui.availableWidgets(); - const QMetaObject* meta = obj->metaObject(); - while (meta) { - const char* typeName = meta->className(); - if (names.indexOf(QLatin1String(typeName)) >= 0) - return typeName; - meta = meta->superClass(); - } -#endif - return "QObject"; -} - -bool PythonWrapper::loadCoreModule() -{ -#if defined (HAVE_SHIBOKEN2) && (HAVE_PYSIDE2) - // QtCore - if (!SbkPySide2_QtCoreTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtCore")); - if (requiredModule.isNull()) - return false; - SbkPySide2_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); - } -#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // QtCore - if (!SbkPySide_QtCoreTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtCore")); - if (requiredModule.isNull()) - return false; - SbkPySide_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); - } -#endif - return true; -} - -bool PythonWrapper::loadGuiModule() -{ -#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) - // QtGui - if (!SbkPySide2_QtGuiTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtGui")); - if (requiredModule.isNull()) - return false; - SbkPySide2_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); - } -#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // QtGui - if (!SbkPySide_QtGuiTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtGui")); - if (requiredModule.isNull()) - return false; - SbkPySide_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); - } -#endif - return true; -} - -bool PythonWrapper::loadWidgetsModule() -{ -#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) - // QtWidgets - if (!SbkPySide2_QtWidgetsTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtWidgets")); - if (requiredModule.isNull()) - return false; - SbkPySide2_QtWidgetsTypes = Shiboken::Module::getTypes(requiredModule); - } -#endif - return true; -} - -void PythonWrapper::createChildrenNameAttributes(PyObject* root, QObject* object) -{ - Q_FOREACH (QObject* child, object->children()) { - const QByteArray name = child->objectName().toLocal8Bit(); - - if (!name.isEmpty() && !name.startsWith("_") && !name.startsWith("qt_")) { - bool hasAttr = PyObject_HasAttrString(root, name.constData()); - if (!hasAttr) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - Shiboken::AutoDecRef pyChild(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), child)); - PyObject_SetAttrString(root, name.constData(), pyChild); -#else - const char* className = qt_identifyType(child, "PySide2.QtWidgets"); - if (!className) { - if (qobject_cast(child)) - className = "QWidget"; - else - className = "QObject"; - } - - Py::Object pyChild(qt_wrapInstance(child, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance")); - PyObject_SetAttrString(root, name.constData(), pyChild.ptr()); -#endif - } - createChildrenNameAttributes(root, child); - } - createChildrenNameAttributes(root, child); - } -} - -void PythonWrapper::setParent(PyObject* pyWdg, QObject* parent) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - if (parent) { - Shiboken::AutoDecRef pyParent(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), parent)); - Shiboken::Object::setParent(pyParent, pyWdg); - } -#else - Q_UNUSED(pyWdg); - Q_UNUSED(parent); -#endif -} - -// ---------------------------------------------------- - Gui::WidgetFactoryInst* Gui::WidgetFactoryInst::_pcSingleton = NULL; WidgetFactoryInst& WidgetFactoryInst::instance() @@ -742,288 +186,6 @@ QWidget* WidgetFactoryInst::createPrefWidget(const char* sName, QWidget* parent, // ---------------------------------------------------- -PySideUicModule::PySideUicModule() - : Py::ExtensionModule("PySideUic") -{ - add_varargs_method("loadUiType",&PySideUicModule::loadUiType, - "PySide lacks the \"loadUiType\" command, so we have to convert the ui file to py code in-memory first\n" - "and then execute it in a special frame to retrieve the form_class."); - add_varargs_method("loadUi",&PySideUicModule::loadUi, - "Addition of \"loadUi\" to PySide."); - initialize("PySideUic helper module"); // register with Python -} - -Py::Object PySideUicModule::loadUiType(const Py::Tuple& args) -{ - Base::PyGILStateLocker lock; - PyObject* main = PyImport_AddModule("__main__"); - PyObject* dict = PyModule_GetDict(main); - Py::Dict d(PyDict_Copy(dict), true); - Py::String uiFile(args.getItem(0)); - std::string file = uiFile.as_string(); - std::replace(file.begin(), file.end(), '\\', '/'); - - QString cmd; - QTextStream str(&cmd); - // https://github.com/albop/dolo/blob/master/bin/load_ui.py - str << "import pyside2uic\n" - << "from PySide2 import QtCore, QtGui, QtWidgets\n" - << "import xml.etree.ElementTree as xml\n" - << "try:\n" - << " from cStringIO import StringIO\n" - << "except Exception:\n" - << " from io import StringIO\n" - << "\n" - << "uiFile = \"" << file.c_str() << "\"\n" - << "parsed = xml.parse(uiFile)\n" - << "widget_class = parsed.find('widget').get('class')\n" - << "form_class = parsed.find('class').text\n" - << "with open(uiFile, 'r') as f:\n" - << " o = StringIO()\n" - << " frame = {}\n" - << " pyside2uic.compileUi(f, o, indent=0)\n" - << " pyc = compile(o.getvalue(), '', 'exec')\n" - << " exec(pyc, frame)\n" - << " #Fetch the base_class and form class based on their type in the xml from designer\n" - << " form_class = frame['Ui_%s'%form_class]\n" - << " base_class = eval('QtWidgets.%s'%widget_class)\n"; - - PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); - if (result) { - Py_DECREF(result); - if (d.hasKey("form_class") && d.hasKey("base_class")) { - Py::Tuple t(2); - t.setItem(0, d.getItem("form_class")); - t.setItem(1, d.getItem("base_class")); - return t; - } - } - else { - throw Py::Exception(); - } - - return Py::None(); -} - -Py::Object PySideUicModule::loadUi(const Py::Tuple& args) -{ - Base::PyGILStateLocker lock; - PyObject* main = PyImport_AddModule("__main__"); - PyObject* dict = PyModule_GetDict(main); - Py::Dict d(PyDict_Copy(dict), true); - d.setItem("uiFile_", args[0]); - if (args.size() > 1) - d.setItem("base_", args[1]); - else - d.setItem("base_", Py::None()); - - QString cmd; - QTextStream str(&cmd); -#if 0 - // https://github.com/lunaryorn/snippets/blob/master/qt4/designer/pyside_dynamic.py - str << "from PySide import QtCore, QtGui, QtUiTools\n" - << "import FreeCADGui" - << "\n" - << "class UiLoader(QtUiTools.QUiLoader):\n" - << " def __init__(self, baseinstance):\n" - << " QtUiTools.QUiLoader.__init__(self, baseinstance)\n" - << " self.baseinstance = baseinstance\n" - << " self.ui = FreeCADGui.UiLoader()\n" - << "\n" - << " def createWidget(self, class_name, parent=None, name=''):\n" - << " if parent is None and self.baseinstance:\n" - << " return self.baseinstance\n" - << " else:\n" - << " widget = self.ui.createWidget(class_name, parent, name)\n" - << " if not widget:\n" - << " widget = QtUiTools.QUiLoader.createWidget(self, class_name, parent, name)\n" - << " if self.baseinstance:\n" - << " setattr(self.baseinstance, name, widget)\n" - << " return widget\n" - << "\n" - << "loader = UiLoader(globals()[\"base_\"])\n" - << "widget = loader.load(globals()[\"uiFile_\"])\n" - << "\n"; -#else - str << "from PySide2 import QtCore, QtGui, QtWidgets\n" - << "import FreeCADGui" - << "\n" - << "loader = FreeCADGui.UiLoader()\n" - << "widget = loader.load(globals()[\"uiFile_\"])\n" - << "\n"; -#endif - - PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); - if (result) { - Py_DECREF(result); - if (d.hasKey("widget")) { - return d.getItem("widget"); - } - } - else { - throw Py::Exception(); - } - - return Py::None(); -} - -// ---------------------------------------------------- - -UiLoader::UiLoader(QObject* parent) - : QUiLoader(parent) -{ - // do not use the plugins for additional widgets as we don't need them and - // the application may crash under Linux (tested on Ubuntu 7.04 & 7.10). - clearPluginPaths(); - this->cw = availableWidgets(); -} - -UiLoader::~UiLoader() -{ -} - -QWidget* UiLoader::createWidget(const QString & className, QWidget * parent, - const QString& name) -{ - if (this->cw.contains(className)) - return QUiLoader::createWidget(className, parent, name); - QWidget* w = 0; - if (WidgetFactory().CanProduce((const char*)className.toLatin1())) - w = WidgetFactory().createWidget((const char*)className.toLatin1(), parent); - if (w) w->setObjectName(name); - return w; -} - -// ---------------------------------------------------- - -PyObject *UiLoaderPy::PyMake(struct _typeobject * /*type*/, PyObject * args, PyObject * /*kwds*/) -{ - if (!PyArg_ParseTuple(args, "")) - return 0; - return new UiLoaderPy(); -} - -void UiLoaderPy::init_type() -{ - behaviors().name("UiLoader"); - behaviors().doc("UiLoader to create widgets"); - behaviors().set_tp_new(PyMake); - // you must have overwritten the virtual functions - behaviors().supportRepr(); - behaviors().supportGetattr(); - behaviors().supportSetattr(); - add_varargs_method("load",&UiLoaderPy::load,"load(string, QWidget parent=None) -> QWidget\n" - "load(QIODevice, QWidget parent=None) -> QWidget"); - add_varargs_method("createWidget",&UiLoaderPy::createWidget,"createWidget()"); -} - -UiLoaderPy::UiLoaderPy() -{ -} - -UiLoaderPy::~UiLoaderPy() -{ -} - -Py::Object UiLoaderPy::repr() -{ - std::string s; - std::ostringstream s_out; - s_out << "Ui loader"; - return Py::String(s_out.str()); -} - -Py::Object UiLoaderPy::load(const Py::Tuple& args) -{ - Gui::PythonWrapper wrap; - if (wrap.loadCoreModule()) { - std::string fn; - QFile file; - QIODevice* device = 0; - QWidget* parent = 0; - if (wrap.toCString(args[0], fn)) { - file.setFileName(QString::fromUtf8(fn.c_str())); - if (!file.open(QFile::ReadOnly)) - throw Py::RuntimeError("Cannot open file"); - device = &file; - } - else if (args[0].isString()) { - fn = (std::string)Py::String(args[0]); - file.setFileName(QString::fromUtf8(fn.c_str())); - if (!file.open(QFile::ReadOnly)) - throw Py::RuntimeError("Cannot open file"); - device = &file; - } - else { - QObject* obj = wrap.toQObject(args[0]); - device = qobject_cast(obj); - } - - if (args.size() > 1) { - QObject* obj = wrap.toQObject(args[1]); - parent = qobject_cast(obj); - } - - if (device) { - QWidget* widget = loader.load(device, parent); - if (widget) { - wrap.loadGuiModule(); - wrap.loadWidgetsModule(); - - const char* typeName = wrap.getWrapperName(widget); - Py::Object pyWdg = wrap.fromQWidget(widget, typeName); - wrap.createChildrenNameAttributes(*pyWdg, widget); - wrap.setParent(*pyWdg, parent); - return pyWdg; - } - } - else { - throw Py::TypeError("string or QIODevice expected"); - } - } - return Py::None(); -} - -Py::Object UiLoaderPy::createWidget(const Py::Tuple& args) -{ - Gui::PythonWrapper wrap; - - // 1st argument - Py::String str(args[0]); - std::string className; - className = str.as_std_string("utf-8"); - // 2nd argument - QWidget* parent = 0; - if (wrap.loadCoreModule() && args.size() > 1) { - QObject* object = wrap.toQObject(args[1]); - if (object) - parent = qobject_cast(object); - } - - // 3rd argument - std::string objectName; - if (args.size() > 2) { - Py::String str(args[2]); - objectName = str.as_std_string("utf-8"); - } - - QWidget* widget = loader.createWidget(QString::fromLatin1(className.c_str()), parent, - QString::fromLatin1(objectName.c_str())); - if (!widget) { - std::string err = "No such widget class '"; - err += className; - err += "'"; - throw Py::RuntimeError(err); - } - wrap.loadGuiModule(); - wrap.loadWidgetsModule(); - - const char* typeName = wrap.getWrapperName(widget); - return wrap.fromQWidget(widget, typeName); -} - -// ---------------------------------------------------- - WidgetFactorySupplier* WidgetFactorySupplier::_pcSingleton = 0L; WidgetFactorySupplier & WidgetFactorySupplier::instance() diff --git a/src/Gui/WidgetFactory.h b/src/Gui/WidgetFactory.h index d1d1ecf6eb..db59c16535 100644 --- a/src/Gui/WidgetFactory.h +++ b/src/Gui/WidgetFactory.h @@ -25,8 +25,6 @@ #define GUI_WIDGETFACTORY_H #include -#include -#include #include #include @@ -35,47 +33,15 @@ #include "PropertyPage.h" #include +QT_BEGIN_NAMESPACE +class QDir; +QT_END_NAMESPACE + namespace Gui { namespace Dialog{ class PreferencePage; } -class GuiExport PythonWrapper -{ -public: - PythonWrapper(); - bool loadCoreModule(); - bool loadGuiModule(); - bool loadWidgetsModule(); - - bool toCString(const Py::Object&, std::string&); - QObject* toQObject(const Py::Object&); - QGraphicsItem* toQGraphicsItem(PyObject* ptr); - Py::Object fromQObject(QObject*, const char* className=0); - Py::Object fromQWidget(QWidget*, const char* className=0); - const char* getWrapperName(QObject*) const; - /*! - Create a Python wrapper for the icon. The icon must be created on the heap - and the Python wrapper takes ownership of it. - */ - Py::Object fromQIcon(const QIcon*); - QIcon *toQIcon(PyObject *pyobj); - static void createChildrenNameAttributes(PyObject* root, QObject* object); - static void setParent(PyObject* pyWdg, QObject* parent); -}; - -class PySideUicModule : public Py::ExtensionModule -{ - -public: - PySideUicModule(); - virtual ~PySideUicModule() {} - -private: - Py::Object loadUiType(const Py::Tuple& args); - Py::Object loadUi(const Py::Tuple& args); -}; - /** * The widget factory provides methods for the dynamic creation of widgets. * To create these widgets once they must be registered to the factory. @@ -107,51 +73,6 @@ inline WidgetFactoryInst& WidgetFactory() // -------------------------------------------------------------------- -/** - * The UiLoader class provides the abitlity to use the widget factory - * framework of FreeCAD within the framework provided by Qt. This class - * extends QUiLoader by the creation of FreeCAD specific widgets. - * @author Werner Mayer - */ -class UiLoader : public QUiLoader -{ -public: - UiLoader(QObject* parent=0); - virtual ~UiLoader(); - - /** - * Creates a widget of the type \a className with the parent \a parent. - * For more details see the documentation to QWidgetFactory. - */ - QWidget* createWidget(const QString & className, QWidget * parent=0, - const QString& name = QString()); -private: - QStringList cw; -}; - -// -------------------------------------------------------------------- - -class UiLoaderPy : public Py::PythonExtension -{ -public: - static void init_type(void); // announce properties and methods - - UiLoaderPy(); - ~UiLoaderPy(); - - Py::Object repr(); - Py::Object createWidget(const Py::Tuple&); - Py::Object load(const Py::Tuple&); - -private: - static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *); - -private: - UiLoader loader; -}; - -// -------------------------------------------------------------------- - /** * The WidgetProducer class is a value-based template class that provides * the ability to create widgets dynamically. diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index e86fff54c2..cfb63ccbb6 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -266,8 +266,10 @@ def get_zip_url(baseurl): def get_readme_url(url): "Returns the location of a readme file" - if "github" in url or "framagit" in url or "gitlab" in url: + if "github" in url or "framagit" in url: return url+"/raw/master/README.md" + elif "gitlab" in url: + return url+"/-/raw/master/README.md" else: print("Debug: addonmanager_utilities.get_readme_url: Unknown git host:", url) return None diff --git a/src/Mod/Mesh/App/CMakeLists.txt b/src/Mod/Mesh/App/CMakeLists.txt index 187b2115de..2d661aea12 100644 --- a/src/Mod/Mesh/App/CMakeLists.txt +++ b/src/Mod/Mesh/App/CMakeLists.txt @@ -372,6 +372,15 @@ SET(Mesh_SRCS Segment.h ) +# Suppress -Wundefined-var-template +if (MINGW AND CMAKE_COMPILER_IS_CLANGXX) + unset(_flag_found CACHE) + check_cxx_compiler_flag("-Wno-undefined-var-template" _flag_found) + if (_flag_found) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-undefined-var-template") + endif() +endif() + if(FREECAD_USE_PCH) add_definitions(-D_PreComp_) GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${Core_SRCS} ${Mesh_SRCS}) diff --git a/src/Mod/Mesh/App/Core/MeshIO.cpp b/src/Mod/Mesh/App/Core/MeshIO.cpp index 7a18de2d54..f0a1e0b7e7 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.cpp +++ b/src/Mod/Mesh/App/Core/MeshIO.cpp @@ -49,6 +49,8 @@ #include #include #include +#include +#include using namespace MeshCore; @@ -1656,31 +1658,88 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) while (std::getline(rstrIn, line)) { upper(ltrim(line)); - if (line.find("GRID*") == 0) { + if (line.empty()) { + // Skip all the following tests } - else if (line.find('*') == 0) { + else if (line.rfind("GRID*", 0) == 0) { + // This element is the 16-digit-precision GRID element, which occupies two lines of the card. Note that + // FreeCAD discards the extra precision, downcasting to an four-byte float. + // + // The two lines are: + // 1 8 24 40 56 + // GRID* Index(16) Blank(16) x(16) y(at least one) + // * z(at least one) + // + // The first character is typically the sign, and may be omitted for positive numbers, + // so it is possible for a field to begin with a blank. Trailing zeros may be omitted, so + // a field may also end with blanks. No space or other delimiter is required between + // the numbers. The following is a valid NASTRAN GRID* element: + // + // GRID* 1 0.1234567890120. + // * 1. + // + if (line.length() < 8 + 16 + 16 + 16 + 1) // Element type(8), index(16), empty(16), x(16), y(>=1) + continue; + auto indexView = std::string_view(&line[8], 16); + auto blankView = std::string_view(&line[8+16], 16); + auto xView = std::string_view(&line[8+16+16], 16); + auto yView = std::string_view(&line[8+16+16+16]); + + std::string line2; + std::getline(rstrIn, line2); + if ((!line2.empty() && line2[0] != '*') || + line2.length() < 9) + continue; // File format error: second line is not a continuation line + auto zView = std::string_view(&line2[8]); + + // We have to strip off any whitespace (technically really just any *trailing* whitespace): + auto indexString = boost::trim_copy(std::string(indexView)); + auto xString = boost::trim_copy(std::string(xView)); + auto yString = boost::trim_copy(std::string(yView)); + auto zString = boost::trim_copy(std::string(zView)); + + auto converter = boost::cnv::spirit(); + auto indexCheck = boost::convert(indexString, converter); + if (!indexCheck.is_initialized()) + // File format error: index couldn't be converted to an integer + continue; + index = indexCheck.get(); + + // Get the high-precision versions first + auto x = boost::convert(xString, converter); + auto y = boost::convert(yString, converter); + auto z = boost::convert(zString, converter); + + if (!x.is_initialized() || !y.is_initialized() || !z.is_initialized()) + // File format error: x, y or z could not be converted + continue; + + // Now drop precision: + mNode[index].x = (float)x.get(); + mNode[index].y = (float)y.get(); + mNode[index].z = (float)z.get(); } - // insert the read-in vertex into a map to preserve the order - else if (line.find("GRID") == 0) { + else if (line.rfind("GRID", 0) == 0) { if (boost::regex_match(line.c_str(), what, rx_p)) { + // insert the read-in vertex into a map to preserve the order index = std::atol(what[1].first)-1; mNode[index].x = (float)std::atof(what[2].first); mNode[index].y = (float)std::atof(what[5].first); mNode[index].z = (float)std::atof(what[8].first); } } - // insert the read-in triangle into a map to preserve the order - else if (line.find("CTRIA3 ") == 0) { + else if (line.rfind("CTRIA3 ", 0) == 0) { if (boost::regex_match(line.c_str(), what, rx_t)) { + // insert the read-in triangle into a map to preserve the order index = std::atol(what[1].first)-1; mTria[index].iV[0] = std::atol(what[3].first)-1; mTria[index].iV[1] = std::atol(what[4].first)-1; mTria[index].iV[2] = std::atol(what[5].first)-1; } } - // insert the read-in quadrangle into a map to preserve the order - else if (line.find("CQUAD4") == 0) { + else if (line.rfind("CQUAD4", 0) == 0) { if (boost::regex_match(line.c_str(), what, rx_q)) { + // insert the read-in quadrangle into a map to preserve the order index = std::atol(what[1].first)-1; mQuad[index].iV[0] = std::atol(what[3].first)-1; mQuad[index].iV[1] = std::atol(what[4].first)-1; diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index 2dcf446018..6d103e7ab0 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -52,7 +52,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.assertFalse(info) def test01(self): - '''Verify chamfer depth and offset for a 90° v-bit.''' + '''Verify chamfer depth and offset for a 90 deg v-bit.''' tool = Path.Tool() tool.FlatRadius = 0 tool.CuttingEdgeAngle = 90 @@ -68,7 +68,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.assertFalse(info) def test02(self): - '''Verify chamfer depth and offset for a 90° v-bit with non 0 flat radius.''' + '''Verify chamfer depth and offset for a 90 deg v-bit with non 0 flat radius.''' tool = Path.Tool() tool.FlatRadius = 0.3 tool.CuttingEdgeAngle = 90 @@ -84,7 +84,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.assertFalse(info) def test03(self): - '''Verify chamfer depth and offset for a 60° v-bit with non 0 flat radius.''' + '''Verify chamfer depth and offset for a 60 deg v-bit with non 0 flat radius.''' tool = Path.Tool() tool.FlatRadius = 10 tool.CuttingEdgeAngle = 60 diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp index b55fba8077..2344a87757 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp @@ -541,20 +541,12 @@ void ConstraintView::updateActiveStatus() void ConstraintView::showConstraints() { - QList items = selectedItems(); - for (auto it : items) { - if (it->checkState() != Qt::Checked) - it->setCheckState(Qt::Checked); - } + Q_EMIT emitShowSelection3DVisibility(); } void ConstraintView::hideConstraints() { - QList items = selectedItems(); - for (auto it : items) { - if (it->checkState() != Qt::Unchecked) - it->setCheckState(Qt::Unchecked); - } + Q_EMIT emitHideSelection3DVisibility(); } void ConstraintView::modifyCurrentItem() @@ -679,6 +671,22 @@ TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) : ui->extendedInformation, SIGNAL(stateChanged(int)), this , SLOT (on_extendedInformation_stateChanged(int)) ); + QObject::connect( + ui->showAllButton, SIGNAL(clicked(bool)), + this , SLOT (on_showAllButton_clicked(bool)) + ); + QObject::connect( + ui->hideAllButton, SIGNAL(clicked(bool)), + this , SLOT (on_hideAllButton_clicked(bool)) + ); + QObject::connect( + ui->listWidgetConstraints, SIGNAL(emitHideSelection3DVisibility()), + this , SLOT (on_listWidgetConstraints_emitHideSelection3DVisibility()) + ); + QObject::connect( + ui->listWidgetConstraints, SIGNAL(emitShowSelection3DVisibility()), + this , SLOT (on_listWidgetConstraints_emitShowSelection3DVisibility()) + ); connectionConstraintsChanged = sketchView->signalConstraintsChanged.connect( boost::bind(&SketcherGui::TaskSketcherConstrains::slotConstraintsChanged, this)); @@ -698,6 +706,85 @@ TaskSketcherConstrains::~TaskSketcherConstrains() connectionConstraintsChanged.disconnect(); } +void TaskSketcherConstrains::changeFilteredVisibility(bool show, ActionTarget target) +{ + assert(sketchView); + const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); + + bool doCommit = false; + + auto selecteditems = ui->listWidgetConstraints->selectedItems(); + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Update constraint's virtual space")); + + for(int i = 0; i < ui->listWidgetConstraints->count(); ++i) + { + QListWidgetItem* item = ui->listWidgetConstraints->item(i); + + bool processItem = false; + + if(target == ActionTarget::All) { + processItem = !item->isHidden(); + } + else if(target == ActionTarget::Selected) { + if(std::find(selecteditems.begin(), selecteditems.end(), item) != selecteditems.end()) + processItem = true; + } + + if(processItem) { // The item is shown in the filtered list + const ConstraintItem *it = dynamic_cast(item); + + if (!it) + continue; + + // must change state is shown and is to be hidden or hidden and must change state is shown + if((it->isInVirtualSpace() == sketchView->getIsShownVirtualSpace() && !show) || + (it->isInVirtualSpace() != sketchView->getIsShownVirtualSpace() && show)) { + + + try { + Gui::cmdAppObjectArgs(sketch, "setVirtualSpace(%d, %s)", + it->ConstraintNbr, + show?"False":"True"); + + doCommit = true; + } + catch (const Base::Exception & e) { + Gui::Command::abortCommand(); + + QMessageBox::critical(Gui::MainWindow::getInstance(), tr("Error"), + QString::fromLatin1("Impossible to update visibility tracking"), QMessageBox::Ok, QMessageBox::Ok); + + return; + } + } + } + } + + if(doCommit) + Gui::Command::commitCommand(); +} + +void TaskSketcherConstrains::on_showAllButton_clicked(bool) +{ + changeFilteredVisibility(true); +} + +void TaskSketcherConstrains::on_hideAllButton_clicked(bool) +{ + changeFilteredVisibility(false); +} + +void TaskSketcherConstrains::on_listWidgetConstraints_emitHideSelection3DVisibility() +{ + changeFilteredVisibility(false, ActionTarget::Selected); +} + +void TaskSketcherConstrains::on_listWidgetConstraints_emitShowSelection3DVisibility() +{ + changeFilteredVisibility(true, ActionTarget::Selected); +} + void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg) { std::string temp; @@ -743,7 +830,13 @@ void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg void TaskSketcherConstrains::on_comboBoxFilter_currentIndexChanged(int) { - slotConstraintsChanged(); + // enforce constraint visibility + bool visibilityTracksFilter = ui->visualisationTrackingFilter->isChecked(); + + if(visibilityTracksFilter) + change3DViewVisibilityToTrackFilter(); // it will call slotConstraintChanged via update mechanism + else + slotConstraintsChanged(); } void TaskSketcherConstrains::on_filterInternalAlignment_stateChanged(int state) @@ -872,6 +965,149 @@ void TaskSketcherConstrains::on_listWidgetConstraints_itemChanged(QListWidgetIte inEditMode = false; } +void TaskSketcherConstrains::change3DViewVisibilityToTrackFilter() +{ + assert(sketchView); + // Build up ListView with the constraints + const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); + const std::vector< Sketcher::Constraint * > &vals = sketch->Constraints.getValues(); + + bool doCommit = false; + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Update constraint's virtual space")); + + for(std::size_t i = 0; i < vals.size(); ++i) { + ConstraintItem * it = static_cast(ui->listWidgetConstraints->item(i)); + + bool visible = !isConstraintFiltered(it); + + // If the constraint is filteredout and it was previously shown in 3D view + if( !visible && it->isInVirtualSpace() == sketchView->getIsShownVirtualSpace()) { + try { + Gui::cmdAppObjectArgs(sketch, "setVirtualSpace(%d, %s)", + it->ConstraintNbr, + "True"); + + doCommit = true; + + } + catch (const Base::Exception & e) { + Gui::Command::abortCommand(); + + QMessageBox::critical(Gui::MainWindow::getInstance(), tr("Error"), + QString::fromLatin1("Impossible to update visibility tracking"), QMessageBox::Ok, QMessageBox::Ok); + + return; + } + } + else if( visible && it->isInVirtualSpace() != sketchView->getIsShownVirtualSpace() ) { + try { + Gui::cmdAppObjectArgs(sketch, "setVirtualSpace(%d, %s)", + it->ConstraintNbr, + "False"); + + doCommit = true; + + } + catch (const Base::Exception & e) { + Gui::Command::abortCommand(); + + QMessageBox::critical(Gui::MainWindow::getInstance(), tr("Error"), + QString::fromLatin1("Impossible to update visibility tracking"), QMessageBox::Ok, QMessageBox::Ok); + + return; + } + } + } + + if(doCommit) + Gui::Command::commitCommand(); + +} + +bool TaskSketcherConstrains::isConstraintFiltered(QListWidgetItem * item) +{ + assert(sketchView); + const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); + const std::vector< Sketcher::Constraint * > &vals = sketch->Constraints.getValues(); + ConstraintItem * it = static_cast(item); + const Sketcher::Constraint * constraint = vals[it->ConstraintNbr]; + + int Filter = ui->comboBoxFilter->currentIndex(); + bool hideInternalAlignment = this->ui->filterInternalAlignment->isChecked(); + + bool visible = true; + bool showAll = (Filter == FilterValue::All); + bool showGeometric = (Filter == FilterValue::Geometric); + bool showDatums = (Filter == FilterValue::Datums); + bool showNamed = (Filter == FilterValue::Named && !(constraint->Name.empty())); + bool showNonDriving = (Filter == FilterValue::NonDriving && !constraint->isDriving); + + switch(constraint->Type) { + case Sketcher::Horizontal: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Horizontal); + break; + case Sketcher::Vertical: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Vertical); + break; + case Sketcher::Coincident: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Coincident); + break; + case Sketcher::PointOnObject: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::PointOnObject); + break; + case Sketcher::Parallel: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Parallel); + break; + case Sketcher::Perpendicular: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Perpendicular); + break; + case Sketcher::Tangent: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Tangent); + break; + case Sketcher::Equal: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Equality); + break; + case Sketcher::Symmetric: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Symmetric); + break; + case Sketcher::Block: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Block); + break; + case Sketcher::Distance: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Distance); + break; + case Sketcher::DistanceX: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::HorizontalDistance); + break; + case Sketcher::DistanceY: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::VerticalDistance); + break; + case Sketcher::Radius: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Radius); + break; + case Sketcher::Weight: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Weight); + break; + case Sketcher::Diameter: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Diameter); + break; + case Sketcher::Angle: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Angle); + break; + case Sketcher::SnellsLaw: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::SnellsLaw); + break; + case Sketcher::InternalAlignment: + visible = (( showAll || showGeometric || showNamed || Filter == FilterValue::InternalAlignment) && + (!hideInternalAlignment || (Filter == FilterValue::InternalAlignment))); + default: + break; + } + + return !visible; +} + void TaskSketcherConstrains::slotConstraintsChanged(void) { assert(sketchView); @@ -906,54 +1142,11 @@ void TaskSketcherConstrains::slotConstraintsChanged(void) ui->listWidgetConstraints->blockSignals(false); /* Update filtering */ - int Filter = ui->comboBoxFilter->currentIndex(); for(std::size_t i = 0; i < vals.size(); ++i) { const Sketcher::Constraint * constraint = vals[i]; ConstraintItem * it = static_cast(ui->listWidgetConstraints->item(i)); - bool visible = true; - /* Filter - 0 <=> All - 1 <=> Normal - 2 <=> Datums - 3 <=> Named - 4 <=> Non-Driving - */ - - bool showNormal = (Filter < 2); - bool showDatums = (Filter < 3); - bool showNamed = (Filter == 3 && !(constraint->Name.empty())); - bool showNonDriving = (Filter == 4 && !constraint->isDriving); - bool hideInternalAlignment = this->ui->filterInternalAlignment->isChecked(); - - switch(constraint->Type) { - case Sketcher::Horizontal: - case Sketcher::Vertical: - case Sketcher::Coincident: - case Sketcher::PointOnObject: - case Sketcher::Parallel: - case Sketcher::Perpendicular: - case Sketcher::Tangent: - case Sketcher::Equal: - case Sketcher::Symmetric: - case Sketcher::Block: - visible = showNormal || showNamed; - break; - case Sketcher::Distance: - case Sketcher::DistanceX: - case Sketcher::DistanceY: - case Sketcher::Radius: - case Sketcher::Weight: - case Sketcher::Diameter: - case Sketcher::Angle: - case Sketcher::SnellsLaw: - visible = (showDatums || showNamed || showNonDriving); - break; - case Sketcher::InternalAlignment: - visible = ((showNormal || showNamed) && !hideInternalAlignment); - default: - break; - } + bool visible = !isConstraintFiltered(it); // block signals as there is no need to invoke the // on_listWidgetConstraints_itemChanged() slot in @@ -964,6 +1157,7 @@ void TaskSketcherConstrains::slotConstraintsChanged(void) it->setHidden(!visible); it->setData(Qt::EditRole, Base::Tools::fromStdString(constraint->Name)); model->blockSignals(block); + } } diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h index 27864995d9..059d29a789 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h @@ -53,6 +53,8 @@ Q_SIGNALS: void onUpdateDrivingStatus(QListWidgetItem *item, bool status); void onUpdateActiveStatus(QListWidgetItem *item, bool status); void emitCenterSelectedItems(); + void emitHideSelection3DVisibility(); + void emitShowSelection3DVisibility(); protected Q_SLOTS: void modifyCurrentItem(); @@ -71,6 +73,38 @@ class TaskSketcherConstrains : public Gui::TaskView::TaskBox, public Gui::Select { Q_OBJECT + enum FilterValue { + All = 0, + Geometric = 1, + Datums = 2, + Named = 3, + NonDriving = 4, + Horizontal = 5, + Vertical = 6, + Coincident = 7, + PointOnObject = 8, + Parallel = 9, + Perpendicular = 10, + Tangent = 11, + Equality = 12, + Symmetric = 13, + Block = 14, + Distance = 15, + HorizontalDistance = 16, + VerticalDistance = 17, + Radius = 18, + Weight = 19, + Diameter = 20, + Angle = 21, + SnellsLaw = 22, + InternalAlignment = 23 + }; + + enum class ActionTarget { + All, + Selected + }; + public: TaskSketcherConstrains(ViewProviderSketch *sketchView); ~TaskSketcherConstrains(); @@ -80,6 +114,9 @@ public: private: void slotConstraintsChanged(void); + bool isConstraintFiltered(QListWidgetItem * item); + void change3DViewVisibilityToTrackFilter(); + void changeFilteredVisibility(bool show, ActionTarget target = ActionTarget::All); public Q_SLOTS: void on_comboBoxFilter_currentIndexChanged(int); @@ -91,6 +128,10 @@ public Q_SLOTS: void on_listWidgetConstraints_emitCenterSelectedItems(void); void on_filterInternalAlignment_stateChanged(int state); void on_extendedInformation_stateChanged(int state); + void on_showAllButton_clicked(bool); + void on_hideAllButton_clicked(bool); + void on_listWidgetConstraints_emitShowSelection3DVisibility(); + void on_listWidgetConstraints_emitHideSelection3DVisibility(); protected: void changeEvent(QEvent *e); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui index 8752bc40dd..11bfdaf064 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui @@ -6,8 +6,8 @@ 0 0 - 212 - 288 + 299 + 388 @@ -19,7 +19,7 @@ 16777215 - 288 + 388 @@ -47,7 +47,7 @@ - Normal + Geometric @@ -65,46 +65,301 @@ Reference + + + Horizontal + + + + + Vertical + + + + + Coincident + + + + + Point on Object + + + + + Parallel + + + + + Perpendicular + + + + + Tangent + + + + + Equality + + + + + Symmetric + + + + + Block + + + + + Distance + + + + + Horizontal Distance + + + + + Vertical Distance + + + + + Radius + + + + + Weight + + + + + Diameter + + + + + Angle + + + + + Snell's Law + + + + + Internal Alignment + + - + + + + 0 + 0 + + + + + 0 + 95 + + - Internal alignments will be hidden + - - Hide internal alignment - - - true - - - HideInternalAlignment - - - Mod/Sketcher - - - - - - - Extended information will be added to the list - - - Extended information - - - false - - - ExtendedConstraintInformation - - - Mod/Sketcher + + 0 + + + + 0 + 0 + + + + View + + + + + 10 + 10 + 125 + 27 + + + + + 0 + 0 + + + + Shows all the constraints in the list + + + Show All + + + + + + 140 + 10 + 125 + 27 + + + + + 0 + 0 + + + + Hides all the constraints in the list + + + Hide All + + + + + + Controls visualisation in the 3D view + + + Automation + + + + + 0 + 0 + 189 + 36 + + + + + 0 + 0 + + + + Constraint visualisation tracks filter selection so that filtered out constraints are hidden + + + Track filter selection + + + false + + + VisualisationTrackingFilter + + + Mod/Sketcher + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Controls widget list behaviour + + + List + + + + + 0 + 30 + 189 + 36 + + + + + 0 + 0 + + + + Extended information will be added to the list + + + Extended information + + + false + + + ExtendedConstraintInformation + + + Mod/Sketcher + + + + + + 0 + 0 + 189 + 36 + + + + + 0 + 0 + + + + Internal alignments will be hidden + + + Hide internal alignment + + + true + + + HideInternalAlignment + + + Mod/Sketcher + + + diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index cfeb1c0e59..3459526a49 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -3455,6 +3455,7 @@ void ViewProviderSketch::drawConstraintIcons() thisIcon.position = absPos; thisIcon.destination = coinIconPtr; thisIcon.infoPtr = infoPtr; + thisIcon.visible = (*it)->isInVirtualSpace == getIsShownVirtualSpace(); if ((*it)->Type==Symmetric) { Base::Vector3d startingpoint = getSketchObject()->getPoint((*it)->First,(*it)->FirstPos); @@ -3539,36 +3540,44 @@ void ViewProviderSketch::combineConstraintIcons(IconQueue iconQueue) iconQueue.pop_back(); // we group only icons not being Symmetry icons, because we want those on the line - if(init.type != QString::fromLatin1("Constraint_Symmetric")){ + // and only icons that are visible + if(init.type != QString::fromLatin1("Constraint_Symmetric") && init.visible){ IconQueue::iterator i = iconQueue.begin(); + + while(i != iconQueue.end()) { - bool addedToGroup = false; + if((*i).visible) { + bool addedToGroup = false; - for(IconQueue::iterator j = thisGroup.begin(); - j != thisGroup.end(); ++j) { - float distSquared = pow(i->position[0]-j->position[0],2) + pow(i->position[1]-j->position[1],2); - if(distSquared <= maxDistSquared && (*i).type != QString::fromLatin1("Constraint_Symmetric")) { - // Found an icon in iconQueue that's close enough to - // a member of thisGroup, so move it into thisGroup - thisGroup.push_back(*i); - i = iconQueue.erase(i); - addedToGroup = true; - break; + for(IconQueue::iterator j = thisGroup.begin(); + j != thisGroup.end(); ++j) { + float distSquared = pow(i->position[0]-j->position[0],2) + pow(i->position[1]-j->position[1],2); + if(distSquared <= maxDistSquared && (*i).type != QString::fromLatin1("Constraint_Symmetric")) { + // Found an icon in iconQueue that's close enough to + // a member of thisGroup, so move it into thisGroup + thisGroup.push_back(*i); + i = iconQueue.erase(i); + addedToGroup = true; + break; + } } - } - if(addedToGroup) { - if(i == iconQueue.end()) - // We just got the last icon out of iconQueue - break; - else - // Start looking through the iconQueue again, in case - // we have an icon that's now close enough to thisGroup - i = iconQueue.begin(); - } else - ++i; + if(addedToGroup) { + if(i == iconQueue.end()) + // We just got the last icon out of iconQueue + break; + else + // Start looking through the iconQueue again, in case + // we have an icon that's now close enough to thisGroup + i = iconQueue.begin(); + } else + ++i; + } + else // if !visible we skip it + i++; } + } if(thisGroup.size() == 1) { diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 2486301a62..598ff154c9 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -370,6 +370,8 @@ protected: /// Angle to rotate an icon double iconRotation; + + bool visible; }; /// Internal type used for drawing constraint icons diff --git a/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp b/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp index 14dc2882c3..93836655b0 100644 --- a/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp +++ b/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp @@ -45,7 +45,7 @@ #include #include #include -#include //for PythonWrappers +#include #include #include diff --git a/src/Mod/Test/UnitTests.py b/src/Mod/Test/UnitTests.py index 5b896da80f..5c6ffea7e6 100644 --- a/src/Mod/Test/UnitTests.py +++ b/src/Mod/Test/UnitTests.py @@ -125,9 +125,10 @@ class UnitBasicCases(unittest.TestCase): try: q2 = FreeCAD.Units.Quantity(t[0]) if math.fabs(q1.Value - q2.Value) > 0.01: - print (q1, " : ", q2, " : ", t, " : ", i, " : ", val) + print (" {} : {} : {} : {} : {}".format(q1, q2, t, i, val).encode("utf-8").strip()) except Exception as e: - print ("{}: {}".format(str(e), t[0])) + s = "{}: {}".format(e, t[0]) + print (" ".join(e).encode("utf-8").strip()) def testVoltage(self): q1 = FreeCAD.Units.Quantity("1e20 V") diff --git a/src/Mod/Web/Gui/AppWebGui.cpp b/src/Mod/Web/Gui/AppWebGui.cpp index c665318dff..a82508b93d 100644 --- a/src/Mod/Web/Gui/AppWebGui.cpp +++ b/src/Mod/Web/Gui/AppWebGui.cpp @@ -27,6 +27,7 @@ # include # include # include +# include #endif #include @@ -63,7 +64,7 @@ public: add_varargs_method("openBrowserWindow",&Module::openBrowserWindow ); add_varargs_method("open",&Module::openBrowser, - "open(string)\n" + "open(htmlcode,baseurl,[title,iconpath])\n" "Load a local (X)HTML file." ); add_varargs_method("insert",&Module::openBrowser, @@ -99,8 +100,9 @@ private: { const char* HtmlCode; const char* BaseUrl; + const char* IconPath; char* TabName = nullptr; - if (! PyArg_ParseTuple(args.ptr(), "ss|et", &HtmlCode, &BaseUrl, "utf-8", &TabName)) + if (! PyArg_ParseTuple(args.ptr(), "ss|ets", &HtmlCode, &BaseUrl, "utf-8", &TabName, &IconPath)) throw Py::Exception(); std::string EncodedName = "Browser"; @@ -114,6 +116,8 @@ private: pcBrowserView->resize(400, 300); pcBrowserView->setHtml(QString::fromUtf8(HtmlCode),QUrl(QString::fromLatin1(BaseUrl))); pcBrowserView->setWindowTitle(QString::fromUtf8(EncodedName.c_str())); + if (IconPath) + pcBrowserView->setWindowIcon(QIcon(QString::fromUtf8(IconPath))); Gui::getMainWindow()->addWindow(pcBrowserView); if (!Gui::getMainWindow()->activeWindow()) Gui::getMainWindow()->setActiveWindow(pcBrowserView); diff --git a/src/Mod/Web/Gui/BrowserView.h b/src/Mod/Web/Gui/BrowserView.h index 67efacdceb..b36e505b32 100644 --- a/src/Mod/Web/Gui/BrowserView.h +++ b/src/Mod/Web/Gui/BrowserView.h @@ -97,7 +97,11 @@ public: bool onMsg(const char* pMsg,const char** ppReturn); bool onHasMsg(const char* pMsg) const; - bool canClose(void); + bool canClose (void); + +#ifdef QTWEBENGINE + void setWindowIcon(const QIcon &icon); +#endif protected Q_SLOTS: void onLoadStarted(); @@ -107,7 +111,6 @@ protected Q_SLOTS: void urlFilter(const QUrl &url); #ifdef QTWEBENGINE void onDownloadRequested(QWebEngineDownloadItem *request); - void setWindowIcon(const QIcon &icon); void onLinkHovered(const QString& url); #else void onDownloadRequested(const QNetworkRequest& request); diff --git a/src/Tools/WinVersion.py b/src/Tools/WinVersion.py index eb02f62229..0e15568759 100644 --- a/src/Tools/WinVersion.py +++ b/src/Tools/WinVersion.py @@ -26,7 +26,7 @@ def main(): output = a git = SubWCRev.GitControl() - if(git.extractInfo(input)): + if(git.extractInfo(input, "")): print(git.hash) print(git.branch) print(git.rev[0:4])