diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 1923e0f..2953553 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -2,7 +2,8 @@ include_directories(include) FILE (GLOB_RECURSE app_SRCS include/*.h - src/*.cpp + src/MainWindow.cpp + src/toolbar.cpp resources/*.qrc ) @@ -10,10 +11,12 @@ QT5_WRAP_UI(UiGenerated_SRCS ui/MainWindow.ui ) +add_library(sciqlopapp ${UiGenerated_SRCS} ${app_SRCS}) + if(ENABLE_WIN32_CONSOLE) - add_executable(sciqlopapp ${app_SRCS} ${UiGenerated_SRCS}) + add_executable(sciqlop src/Main.cpp) else() - add_executable(sciqlopapp WIN32 ${app_SRCS} ${UiGenerated_SRCS}) + add_executable(sciqlop WIN32 src/Main.cpp) endif() if(NOT BUILD_SHARED_LIBS) @@ -39,9 +42,20 @@ target_link_libraries(sciqlopapp sciqlopcore ) -install(TARGETS sciqlopapp DESTINATION ${CMAKE_INSTALL_BINDIR}) +target_link_libraries(sciqlop + sciqlopapp +) + +install(TARGETS sciqlopapp + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + +install(TARGETS sciqlop DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES resources/SciQLOP.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications/) install(FILES resources/SciQLOP.appdata.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo/) install(FILES resources/sciqlopLOGO.svg DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/SciQLOP/icons/) +add_subdirectory(PySide2-bindings) diff --git a/app/PySide2-bindings/CMakeLists.txt b/app/PySide2-bindings/CMakeLists.txt new file mode 100644 index 0000000..01f4988 --- /dev/null +++ b/app/PySide2-bindings/CMakeLists.txt @@ -0,0 +1,79 @@ +find_package(PythonLibs 3 REQUIRED) +find_package(PythonInterp 3 REQUIRED) +find_package(PySide2 REQUIRED) +find_package(Shiboken2 REQUIRED) +include(PythonInfo) +find_python_site_packages(PYTHON_SITE_PACKAGES) + +set(BINDINGS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(BINDINGS_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}") + +configure_file("${BINDINGS_SRC_DIR}/bindings.xml" "${BINDINGS_BUILD_DIR}/bindings.xml" COPYONLY) +configure_file("${BINDINGS_SRC_DIR}/main.py" "${BINDINGS_BUILD_DIR}/main.py" COPYONLY) + +execute_process(COMMAND "${PYTHON_EXECUTABLE}" "${BINDINGS_SRC_DIR}/src_list.py" cmake "${BINDINGS_BUILD_DIR}" OUTPUT_VARIABLE BINDINGS_SOURCE) + +set_property(SOURCE ${BINDINGS_SOURCE} PROPERTY SKIP_AUTOGEN ON) + +list(APPEND BINDINGS_INCLUDE_DIRS + ${PYTHON_INCLUDE_DIRS} + ${Qt5Core_INCLUDE_DIRS} + ${Qt5Widgets_INCLUDE_DIRS} + ${Qt5Gui_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../../gui/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../core/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../core/external/TimeSeries/include + ) +list(REMOVE_DUPLICATES BINDINGS_INCLUDE_DIRS) +foreach(DIR ${BINDINGS_INCLUDE_DIRS}) + list(APPEND BINDINGS_INCLUDE_DIRS_ARGS "-I${DIR}") +endforeach() + +set(SHIBOKEN_OPTIONS --generator-set=shiboken + --enable-parent-ctor-heuristic + --enable-return-value-heuristic + --use-isnull-as-nb_nonzero + --avoid-protected-hack + --enable-pyside-extensions + -std=c++17) +add_custom_command( + OUTPUT ${BINDINGS_SOURCE} + COMMAND Shiboken2::shiboken2 ${SHIBOKEN_OPTIONS} + ${BINDINGS_INCLUDE_DIRS_ARGS} + --typesystem-paths=${PYSIDE_TYPESYSTEMS} + --output-directory=${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/bindings.h ${CMAKE_CURRENT_SOURCE_DIR}/bindings.xml + + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/bindings.xml" + IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/bindings.h" + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + COMMENT "Generating Python bindings with shiboken2") + +include_directories( + ${PYSIDE_INCLUDE_DIR}/QtCore + ${PYSIDE_INCLUDE_DIR}/QtGui + ${PYSIDE_INCLUDE_DIR}/QtWidgets) + +include_directories( + ${PYTHON_SITE_PACKAGES}/numpy/core/include/ + ${PYTHON_INCLUDE_DIRS} + ${SHIBOKEN_INCLUDE_DIR} + ${PYSIDE_INCLUDE_DIR} + ${PYSIDE_INCLUDE_DIR}/QtCore + ${PYSIDE_INCLUDE_DIR}/QtGui + ${PYSIDE_INCLUDE_DIR}/QtWidgets) + +add_library(SciQLopBindings MODULE ${BINDINGS_SOURCE} numpy_wrappers.h) +set_target_properties( + SciQLopBindings + PROPERTIES + PREFIX "" + OUTPUT_NAME "SciQLopBindings" + ) +target_link_libraries(SciQLopBindings sciqlopapp) +target_link_libraries(SciQLopBindings Shiboken2::libshiboken) +target_link_libraries(SciQLopBindings PySide2::pyside2) + +add_executable(debug_sciqlop_app main.cpp ) +find_package (Python3 COMPONENTS Development) +target_link_libraries(debug_sciqlop_app PRIVATE Python3::Python) diff --git a/app/PySide2-bindings/PyDataProvider.h b/app/PySide2-bindings/PyDataProvider.h new file mode 100644 index 0000000..7be30d1 --- /dev/null +++ b/app/PySide2-bindings/PyDataProvider.h @@ -0,0 +1,16 @@ +#pragma once +#include + +class PyDataProvider : public IDataProvider +{ +public: + PyDataProvider() {} + + virtual TimeSeries::ITimeSerie getData(const std::string& key, double start_time, double stop_time) + {} + + virtual TimeSeries::ITimeSerie* getData(const DataProviderParameters& parameters) + { + return nullptr; + } +}; diff --git a/app/PySide2-bindings/bindings.h b/app/PySide2-bindings/bindings.h new file mode 100644 index 0000000..5644a3c --- /dev/null +++ b/app/PySide2-bindings/bindings.h @@ -0,0 +1,12 @@ +#ifndef SCIQLOP_BINDINGS_H +#define SCIQLOP_BINDINGS_H +#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a))) +#include "../include/MainWindow.h" +#include "PyDataProvider.h" +#include "numpy_wrappers.h" +#include +#include +#include + + +#endif // SCIQLOP_BINDINGS_H diff --git a/app/PySide2-bindings/bindings.xml b/app/PySide2-bindings/bindings.xml new file mode 100644 index 0000000..3f8a933 --- /dev/null +++ b/app/PySide2-bindings/bindings.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + %INTYPE::size_type vectorSize = %in.size(); + PyObject* %out = PyList_New((int) vectorSize); + for (%INTYPE::size_type idx = 0; idx < vectorSize; ++idx) { + %INTYPE_0 cppItem(%in[idx]); + PyList_SET_ITEM(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem)); + } + return %out; + + + + Shiboken::AutoDecRef seq(PySequence_Fast(%in, 0)); + int vectorSize = PySequence_Fast_GET_SIZE(seq.object()); + %out.reserve(vectorSize); + for (int idx = 0; idx < vectorSize; ++idx ) { + PyObject* pyItem = PySequence_Fast_GET_ITEM(seq.object(), idx); + %OUTTYPE_0 cppItem = %CONVERTTOCPP[%OUTTYPE_0](pyItem); + %out.push_back(cppItem); + } + + + + + + + + + + + + + + + + return %in.py_object(); + + + + %out = %OUTTYPE(%in); + + + + + + + + + %BEGIN_ALLOW_THREADS + %0 = new ScalarTimeSerieWrapper(); + %0.set_data(%1.to_std_vect(),%2.to_std_vect()); + %END_ALLOW_THREADS + + + + + %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(); + %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); + + + + + diff --git a/app/PySide2-bindings/main.cpp b/app/PySide2-bindings/main.cpp new file mode 100644 index 0000000..79f878d --- /dev/null +++ b/app/PySide2-bindings/main.cpp @@ -0,0 +1,26 @@ +#include +#include +#define PY_SSIZE_T_CLEAN +#define Py_DEBUG +#include + +int main(int argc, char** argv) +{ + wchar_t* program = Py_DecodeLocale(argv[0], NULL); + if (program == NULL) + { + fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); + exit(1); + } + Py_SetProgramName(program); /* optional but recommended */ + Py_Initialize(); + std::ifstream t(argv[1]); + std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); + PyRun_SimpleString(str.data()); + if (Py_FinalizeEx() < 0) + { + exit(120); + } + PyMem_RawFree(program); + return 0; +} diff --git a/app/PySide2-bindings/main.py b/app/PySide2-bindings/main.py new file mode 100644 index 0000000..2d3d247 --- /dev/null +++ b/app/PySide2-bindings/main.py @@ -0,0 +1,102 @@ +# This Python file uses the following encoding: utf-8 +import os +print(os.getcwd()) +import sys +from PySide2.QtWidgets import QApplication, QMainWindow, QDockWidget +from PySide2.QtCore import QSize, Qt +from PySide2 import QtGui +import os +from SciQLopBindings import SqpApplication, MainWindow, init_resources, load_plugins, SqpApplication_ctor +from qtconsole.rich_ipython_widget import RichJupyterWidget +from qtconsole.inprocess import QtInProcessKernelManager + + +class IPythonWidget(RichJupyterWidget): + """Live IPython console widget. + + .. image:: img/IPythonWidget.png + + :param custom_banner: Custom welcome message to be printed at the top of + the console. + """ + + def __init__(self, parent=None, custom_banner=None, *args, **kwargs): + if parent is not None: + kwargs["parent"] = parent + super(IPythonWidget, self).__init__(*args, **kwargs) + if custom_banner is not None: + self.banner = custom_banner + self.setWindowTitle(self.banner) + self.kernel_manager = kernel_manager = QtInProcessKernelManager() + kernel_manager.start_kernel() + self.kernel_client = kernel_client = self._kernel_manager.client() + kernel_client.start_channels() + + def stop(): + kernel_client.stop_channels() + kernel_manager.shutdown_kernel() + self.exit_requested.connect(stop) + + def sizeHint(self): + """Return a reasonable default size for usage in :class:`PlotWindow`""" + return QSize(500, 300) + + def pushVariables(self, variable_dict): + """ Given a dictionary containing name / value pairs, push those + variables to the IPython console widget. + + :param variable_dict: Dictionary of variables to be pushed to the + console's interactive namespace (```{variable_name: object, …}```) + """ + self.kernel_manager.kernel.shell.push(variable_dict) + + +class IPythonDockWidget(QDockWidget): + """Dock Widget including a :class:`IPythonWidget` inside + a vertical layout. + + .. image:: img/IPythonDockWidget.png + + :param available_vars: Dictionary of variables to be pushed to the + console's interactive namespace: ``{"variable_name": object, …}`` + :param custom_banner: Custom welcome message to be printed at the top of + the console + :param title: Dock widget title + :param parent: Parent :class:`qt.QMainWindow` containing this + :class:`qt.QDockWidget` + """ + def __init__(self, parent=None, available_vars=None, custom_banner=None, + title="Console"): + super(IPythonDockWidget, self).__init__(title, parent) + + self.ipyconsole = IPythonWidget(custom_banner=custom_banner) + + self.layout().setContentsMargins(0, 0, 0, 0) + self.setWidget(self.ipyconsole) + + if available_vars is not None: + self.ipyconsole.pushVariables(available_vars) + self.ipyconsole.pushVariables({"blah":self}) + + def showEvent(self, event): + """Make sure this widget is raised when it is shown + (when it is first created as a tab in PlotWindow or when it is shown + again after hiding). + """ + self.raise_() + +def print_process_id(): + print ('Process ID is:', os.getpid()) + + +if __name__ == "__main__": + init_resources() + app = SqpApplication_ctor() + QtGui.qApp = app + load_plugins(app) + main_window = MainWindow() + term = IPythonDockWidget(available_vars={"app":app, "main_window":main_window}, custom_banner="SciQLop IPython Console ") + main_window.addDockWidget(Qt.BottomDockWidgetArea, term) + main_window.show() + sys.exit(app.exec_()) + diff --git a/app/PySide2-bindings/numpy_wrappers.cpp b/app/PySide2-bindings/numpy_wrappers.cpp new file mode 100644 index 0000000..10ba171 --- /dev/null +++ b/app/PySide2-bindings/numpy_wrappers.cpp @@ -0,0 +1,19 @@ +#include "numpy_wrappers.h" + +// ScalarTimeSerie ScalarTimeSerie_from_np(PyObject* time, PyObject* values) +//{ +// assert(time); +// assert(values); +// assert(PyArray_NDIM(time) == 1); +// assert(PyArray_NDIM(values) == 1); +// assert(PyArray_ISFLOAT(time)); +// assert(PyArray_ISFLOAT(values)); +// assert(PyArray_DIM(time, 0) == PyArray_DIM(values, 0)); +// assert(PyArray_IS_C_CONTIGUOUS(time)); +// assert(PyArray_IS_C_CONTIGUOUS(values)); +// int size = PyArray_DIM(time, 0); +// ScalarTimeSerie ts(size); +// for (int i = 0; i < size; i++) +// { +// } +//} diff --git a/app/PySide2-bindings/numpy_wrappers.h b/app/PySide2-bindings/numpy_wrappers.h new file mode 100644 index 0000000..fb94e5e --- /dev/null +++ b/app/PySide2-bindings/numpy_wrappers.h @@ -0,0 +1,178 @@ +#ifndef NUMPY_WRAPPERS_H +#define NUMPY_WRAPPERS_H +#include +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#if defined(slots) && (defined(__GNUC__) || defined(_MSC_VER) || defined(__clang__)) +#pragma push_macro("slots") +#undef slots +extern "C" +{ +/* + * Python 2 uses the "register" keyword, which is deprecated in C++ 11 + * and forbidden in C++17. + */ +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-register" +#endif + +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +} +#else +#include +#include +#endif +#include + +inline int init_numpy() +{ + import_array(); // PyError if not successful + return 0; +} +const static int numpy_initialized = init_numpy(); +template +struct PyObjectWrapper +{ +private: + PyObject* _py_obj; + + void inc_refcount() + { + if (_py_obj) + Py_IncRef(_py_obj); + } + void dec_refcount() + { + if (_py_obj) + Py_DecRef(_py_obj); + } + +public: + PyObjectWrapper() : _py_obj { nullptr } {} + PyObjectWrapper(const PyObjectWrapper& other) : _py_obj { other._py_obj } { inc_refcount(); }; + PyObjectWrapper(PyObjectWrapper&& other) : _py_obj { other._py_obj } + { + other._py_obj = nullptr; + } + PyObjectWrapper(PyObject* obj) : _py_obj { obj } { inc_refcount(); } + ~PyObjectWrapper() { dec_refcount(); } + PyObjectWrapper& operator=(PyObjectWrapper&& other) + { + this->_py_obj = other._py_obj; + other._py_obj = nullptr; + return *this; + } + PyObjectWrapper& operator=(const PyObjectWrapper& other) + { + dec_refcount(); + this->_py_obj = other._py_obj; + inc_refcount(); + return *this; + } + + PyObject* py_object() { return _py_obj; } + inline dest_type* get() { return reinterpret_cast(_py_obj); } + inline bool is_null() { return _py_obj == nullptr; } +}; + +struct NpArray +{ +private: + PyObjectWrapper _py_obj; + NpArray(NpArray& other) = delete; + NpArray(const NpArray& other) = delete; + NpArray(const NpArray&& other) = delete; + +public: + static bool isNpArray(PyObject* obj) + { + return obj && PyArray_Check(reinterpret_cast(obj)) + && PyArray_IS_C_CONTIGUOUS(reinterpret_cast(obj)); + } + NpArray() : _py_obj { nullptr } {} + NpArray(NpArray&& other) : _py_obj { std::move(other._py_obj) } {} + explicit NpArray(PyObject* obj) : _py_obj { obj } + { + std::cout << "NpArray ctor" << std::endl; + assert(isNpArray(obj)); + assert(PyArray_ISFLOAT(_py_obj.get())); + } + + NpArray& operator=(const NpArray& other) + { + this->_py_obj = other._py_obj; + return *this; + } + + NpArray& operator=(NpArray&& other) + { + this->_py_obj = std::move(other._py_obj); + return *this; + } + + std::vector shape() + { + std::vector shape; + if (!_py_obj.is_null()) + { + if (int ndim = PyArray_NDIM(_py_obj.get()); ndim > 0) + { + if (ndim < 10) + { + shape.resize(ndim); + std::copy_n(PyArray_SHAPE(_py_obj.get()), ndim, std::begin(shape)); + } + } + } + return shape; + } + + std::size_t flat_size() + { + auto s = this->shape(); + return std::accumulate(std::cbegin(s), std::cend(s), 0); + } + + double data(std::size_t pos) + { + if (!_py_obj.is_null()) + { + return reinterpret_cast(PyArray_DATA(_py_obj.get()))[pos]; + } + return nan("NAN"); + } + + std::vector to_std_vect() + { + auto sz = flat_size(); + std::vector v(sz); + auto d_ptr = reinterpret_cast(PyArray_DATA(_py_obj.get())); + std::copy(d_ptr, d_ptr + sz, std::begin(v)); + return v; + } + + PyObject* py_object() { return _py_obj.py_object(); } +}; + +inline int test_np_array(NpArray& arr) +{ + auto shape = arr.shape(); + std::cout << "len(shape)=" << shape.size() << std::endl; + std::for_each(std::cbegin(shape), std::cend(shape), [](auto sz) { + static int i = 0; + std::cout << "shape[" << i++ << "]=" << sz << std::endl; + }); + auto flatsize = std::accumulate(std::cbegin(shape), std::cend(shape), 0); + for (auto i = 0; i < flatsize; i++) + { + std::cout << "data[" << i << "]=" << arr.data(i) << std::endl; + } + return 1; +} + +#endif //#ifndef NUMPY_WRAPPERS_H diff --git a/app/PySide2-bindings/src_list.py b/app/PySide2-bindings/src_list.py new file mode 100755 index 0000000..b34a7ea --- /dev/null +++ b/app/PySide2-bindings/src_list.py @@ -0,0 +1,49 @@ +#!/bin/env python3 +# taken from https://github.com/radareorg/cutter/blob/master/src/bindings/src_list.py + + +import os +import re +import sys + + +script_path = os.path.dirname(os.path.realpath(__file__)) + + +def get_cpp_files_gen(args, include_package=True): + with open(os.path.join(script_path, "bindings.xml"),'r') as f: + txt = f.read() + package = re.findall(" 0: + cpp_files_gen = [os.path.join(args[0], f) for f in cpp_files_gen] + + return cpp_files_gen + + +def cmd_cmake(args): + sys.stdout.write(";".join(get_cpp_files_gen(args))) + + +def cmd_qmake(args): + sys.stdout.write("\n".join(get_cpp_files_gen(args)) + "\n") + + +def cmd_meson(args): + sys.stdout.write(";".join(get_cpp_files_gen(args, include_package=False))) + + +cmds = {"cmake": cmd_cmake, "qmake": cmd_qmake, "meson": cmd_meson} + +if len(sys.argv) < 2 or sys.argv[1] not in cmds: + print(f"""usage: {sys.argv[0]} [{"/".join(cmds.keys())}] [base path]""") + exit(1) +cmds[sys.argv[1]](sys.argv[2:]) diff --git a/app/include/MainWindow.h b/app/include/MainWindow.h index 08f87a3..d418326 100644 --- a/app/include/MainWindow.h +++ b/app/include/MainWindow.h @@ -22,7 +22,8 @@ #ifndef SCIQLOP_MAINWINDOW_H #define SCIQLOP_MAINWINDOW_H -#include +#include +#include #include #include #include @@ -30,31 +31,33 @@ #include #include #include +#include +#include #include #include -Q_DECLARE_LOGGING_CATEGORY(LOG_MainWindow) - -namespace Ui { +namespace Ui +{ class MainWindow; } // namespace Ui -class MainWindow : public QMainWindow { +class MainWindow : public QMainWindow +{ Q_OBJECT public: - explicit MainWindow(QWidget *parent = nullptr); + explicit MainWindow(QWidget* parent = nullptr); virtual ~MainWindow() override; public slots: protected: - void changeEvent(QEvent *e) override; - void closeEvent(QCloseEvent *event) override; + void changeEvent(QEvent* e) override; + void closeEvent(QCloseEvent* event) override; - void keyPressEvent(QKeyEvent *event) override; + void keyPressEvent(QKeyEvent* event) override; private: std::unique_ptr m_Ui; @@ -65,4 +68,53 @@ private: spimpl::unique_impl_ptr impl; }; +inline void init_resources() +{ +#ifdef QT_STATICPLUGIN +#ifndef SQP_NO_PLUGINS + Q_IMPORT_PLUGIN(PythonProviders) + Q_INIT_RESOURCE(python_providers); +#endif +#endif + Q_INIT_RESOURCE(sqpguiresources); + SqpApplication::setOrganizationName("LPP"); + SqpApplication::setOrganizationDomain("lpp.fr"); + SqpApplication::setApplicationName("SciQLop"); + + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +} + +inline void load_plugins(const SqpApplication& a) +{ + // Loads plugins + auto pluginDir = QDir { a.applicationDirPath() }; + auto pluginLookupPath = { +#if _WIN32 || _WIN64 + a.applicationDirPath() + "/SciQLop" +#else + a.applicationDirPath() + "/../lib64/SciQLop", + a.applicationDirPath() + "/../lib64/sciqlop", + a.applicationDirPath() + "/../lib/SciQLop", + a.applicationDirPath() + "/../lib/sciqlop", +#endif + }; + +#if _WIN32 || _WIN64 + pluginDir.mkdir(PLUGIN_DIRECTORY_NAME); + pluginDir.cd(PLUGIN_DIRECTORY_NAME); +#endif + + PluginManager pluginManager {}; + + for (auto&& path : pluginLookupPath) + { + QDir directory { path }; + if (directory.exists()) + { + pluginManager.loadPlugins(directory); + } + } + pluginManager.loadStaticPlugins(); +} + #endif // SCIQLOP_MAINWINDOW_H diff --git a/app/src/Main.cpp b/app/src/Main.cpp index fbd08ba..edb9e56 100644 --- a/app/src/Main.cpp +++ b/app/src/Main.cpp @@ -31,7 +31,6 @@ #include -Q_LOGGING_CATEGORY(LOG_Main, "Main") namespace { @@ -43,56 +42,10 @@ const auto PLUGIN_DIRECTORY_NAME = QStringLiteral("plugins"); int main(int argc, char* argv[]) { -#ifdef QT_STATICPLUGIN -#ifndef SQP_NO_PLUGINS - Q_IMPORT_PLUGIN(PythonProviders) - Q_INIT_RESOURCE(python_providers); -#endif -#endif - Q_INIT_RESOURCE(sqpguiresources); - - SqpApplication::setOrganizationName("LPP"); - SqpApplication::setOrganizationDomain("lpp.fr"); - SqpApplication::setApplicationName("SciQLop"); - - QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - + init_resources(); SqpApplication a { argc, argv }; - + load_plugins(a); MainWindow w; w.show(); - - // Loads plugins - auto pluginDir = QDir { a.applicationDirPath() }; - auto pluginLookupPath = { -#if _WIN32 || _WIN64 - a.applicationDirPath() + "/SciQLop" -#else - a.applicationDirPath() + "/../lib64/SciQLop", - a.applicationDirPath() + "/../lib64/sciqlop", - a.applicationDirPath() + "/../lib/SciQLop", - a.applicationDirPath() + "/../lib/sciqlop", -#endif - }; - -#if _WIN32 || _WIN64 - pluginDir.mkdir(PLUGIN_DIRECTORY_NAME); - pluginDir.cd(PLUGIN_DIRECTORY_NAME); -#endif - - PluginManager pluginManager {}; - - for (auto&& path : pluginLookupPath) - { - QDir directory { path }; - if (directory.exists()) - { - qCDebug(LOG_Main()) - << QObject::tr("Plugin directory: %1").arg(directory.absolutePath()); - pluginManager.loadPlugins(directory); - } - } - pluginManager.loadStaticPlugins(); - return a.exec(); } diff --git a/cmake/FindPySide2.cmake b/cmake/FindPySide2.cmake new file mode 100644 index 0000000..d8842ae --- /dev/null +++ b/cmake/FindPySide2.cmake @@ -0,0 +1,84 @@ + +set(_module PySide2) + +find_package(${_module} ${${_module}_FIND_VERSION} CONFIG QUIET) +set(_lib_target ${_module}::pyside2) + +if(NOT ${_module}_FOUND) + include(PythonInfo) + find_python_site_packages(PYTHON_SITE_PACKAGES) + get_python_extension_suffix(PYTHON_EXTENSION_SUFFIX) + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "if True: + from PySide2 import _config + print(_config.pyside_library_soversion.split('.')[0]) + " + OUTPUT_VARIABLE ${_module}_FIND_VERSION_MAJOR + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "if True: + from PySide2 import _config + print(_config.pyside_library_soversion.split('.')[1]) + " + OUTPUT_VARIABLE ${_module}_FIND_VERSION_MINOR + OUTPUT_STRIP_TRAILING_WHITESPACE) + + find_library(PYSIDE_LIBRARY + NAMES + "pyside2${PYTHON_EXTENSION_SUFFIX}" + "libpyside2${PYTHON_EXTENSION_SUFFIX}" + "pyside2${PYTHON_EXTENSION_SUFFIX}.${${_module}_FIND_VERSION_MAJOR}.${${_module}_FIND_VERSION_MINOR}" + "libpyside2${PYTHON_EXTENSION_SUFFIX}.${${_module}_FIND_VERSION_MAJOR}.${${_module}_FIND_VERSION_MINOR}" + PATHS "${PYTHON_SITE_PACKAGES}/PySide2") + + find_path(PYSIDE_INCLUDE_DIR + pyside.h + PATHS "${PYTHON_SITE_PACKAGES}/PySide2/include") + + find_path(PYSIDE_TYPESYSTEMS + typesystem_core.xml + PATHS "${PYTHON_SITE_PACKAGES}/PySide2/typesystems") +endif() + +if(TARGET ${_lib_target}) + get_target_property(_is_imported ${_lib_target} IMPORTED) + if(_is_imported) + get_target_property(_imported_location ${_lib_target} IMPORTED_LOCATION) + if(NOT _imported_location) + message(STATUS "Target ${_lib_target} does not specify its IMPORTED_LOCATION! Trying to find it ourselves...") + set(_find_args) + if(${_module}_CONFIG) + get_filename_component(_pyside2_lib_dir "${${_module}_CONFIG}/../../../" ABSOLUTE) + set(_find_args PATHS "${_pyside2_lib_dir}") + endif() + find_library(PYSIDE_LIBRARY + NAMES + "pyside2${PYTHON_CONFIG_SUFFIX}" + "pyside2${PYTHON_CONFIG_SUFFIX}.${${_module}_FIND_VERSION_MAJOR}.${${_module}_FIND_VERSION_MINOR}" + ${_find_args}) + if(NOT PYSIDE_LIBRARY) + set(_message_type WARNING) + if(${_module}_FIND_REQUIRED) + set(_message_type FATAL_ERROR) + endif() + message(${_message_type} "Failed to manually find library for ${_module}") + return() + endif() + message(STATUS "IMPORTED_LOCATION for ${_lib_target} found: ${PYSIDE_LIBRARY}") + set_target_properties(${_lib_target} PROPERTIES IMPORTED_LOCATION "${PYSIDE_LIBRARY}") + endif() + endif() +else() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(${_module} + FOUND_VAR ${_module}_FOUND + REQUIRED_VARS PYSIDE_LIBRARY PYSIDE_INCLUDE_DIR PYSIDE_TYPESYSTEMS + VERSION_VAR ${_module}_VERSION) + + add_library(${_module}::pyside2 INTERFACE IMPORTED) + set_target_properties(${_module}::pyside2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PYSIDE_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${PYSIDE_LIBRARY}") +endif() + +mark_as_advanced(PYSIDE_INCLUDE_DIR PYSIDE_LIBRARY PYSIDE_BINARY) diff --git a/cmake/FindShiboken2.cmake b/cmake/FindShiboken2.cmake new file mode 100644 index 0000000..2cae518 --- /dev/null +++ b/cmake/FindShiboken2.cmake @@ -0,0 +1,106 @@ + +set(_module Shiboken2) + +find_package(${_module} ${${_module}_FIND_VERSION} CONFIG QUIET) +set(_executable_target ${_module}::shiboken2) +set(_lib_target ${_module}::libshiboken) + +if(NOT ${_module}_FOUND) + include(PythonInfo) + find_python_site_packages(PYTHON_SITE_PACKAGES) + get_python_extension_suffix(PYTHON_EXTENSION_SUFFIX) + + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "if True: + from shiboken2 import _config + print(_config.shiboken_library_soversion.split('.')[0]) + " + OUTPUT_VARIABLE ${_module}_FIND_VERSION_MAJOR + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "if True: + from shiboken2 import _config + print(_config.shiboken_library_soversion.split('.')[1]) + " + OUTPUT_VARIABLE ${_module}_FIND_VERSION_MINOR + OUTPUT_STRIP_TRAILING_WHITESPACE) + + find_library(SHIBOKEN_LIBRARY + NAMES + "libshiboken2${PYTHON_EXTENSION_SUFFIX}.${${_module}_FIND_VERSION_MAJOR}.${${_module}_FIND_VERSION_MINOR}" + "shiboken2${PYTHON_EXTENSION_SUFFIX}" + PATHS "${PYTHON_SITE_PACKAGES}/shiboken2") + + find_path(SHIBOKEN_INCLUDE_DIR + shiboken.h + PATHS "${PYTHON_SITE_PACKAGES}/shiboken2_generator/include") + + find_file(SHIBOKEN_BINARY + shiboken2 + PATHS "${PYTHON_SITE_PACKAGES}/shiboken2_generator") +endif() + +if(TARGET ${_executable_target}) + get_target_property(_is_imported ${_executable_target} IMPORTED) + if(_is_imported) + get_target_property(_imported_location ${_executable_target} IMPORTED_LOCATION) + if(NOT _imported_location) + message(STATUS "Target ${_executable_target} does not specify its IMPORTED_LOCATION! Trying to find it ourselves...") + find_file(SHIBOKEN_BINARY + shiboken2 + PATHS "${SHIBOKEN_SHARED_LIBRARY_DIR}/../bin" + NO_DEFAULT_PATH) + if(NOT SHIBOKEN_BINARY) + set(_message_type WARNING) + if(${_module}_FIND_REQUIRED) + set(_message_type FATAL_ERROR) + endif() + message(${_message_type} "Failed to manually find executable for ${_module}") + return() + endif() + message(STATUS "IMPORTED_LOCATION for ${_executable_target} found: ${SHIBOKEN_BINARY}") + set_target_properties(${_executable_target} PROPERTIES IMPORTED_LOCATION "${SHIBOKEN_BINARY}") + endif() + endif() + + get_target_property(_is_imported ${_lib_target} IMPORTED) + if(_is_imported) + get_target_property(_imported_location ${_lib_target} IMPORTED_LOCATION) + if(NOT _imported_location) + message(STATUS "Target ${_lib_target} does not specify its IMPORTED_LOCATION! Trying to find it ourselves...") + find_library(SHIBOKEN_LIBRARY + NAMES + "shiboken2${SHIBOKEN_PYTHON_EXTENSION_SUFFIX}" + "shiboken2${SHIBOKEN_PYTHON_EXTENSION_SUFFIX}.${${_module}_FIND_VERSION_MAJOR}.${${_module}_FIND_VERSION_MINOR}" + PATHS "${SHIBOKEN_SHARED_LIBRARY_DIR}") + if(NOT SHIBOKEN_LIBRARY) + set(_message_type WARNING) + if(${_module}_FIND_REQUIRED) + set(_message_type FATAL_ERROR) + endif() + message(${_message_type} "Failed to manually find library for ${_module}") + return() + endif() + message(STATUS "IMPORTED_LOCATION for ${_lib_target} found: ${SHIBOKEN_LIBRARY}") + set_target_properties(${_lib_target} PROPERTIES IMPORTED_LOCATION "${SHIBOKEN_LIBRARY}") + endif() + endif() +else() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(${_module} + FOUND_VAR ${_module}_FOUND + REQUIRED_VARS SHIBOKEN_LIBRARY SHIBOKEN_INCLUDE_DIR SHIBOKEN_BINARY + VERSION_VAR ${_module}_VERSION) + + add_library(${_module}::libshiboken INTERFACE IMPORTED) + set_target_properties(${_module}::libshiboken PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SHIBOKEN_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${SHIBOKEN_LIBRARY}") + + add_executable(${_module}::shiboken2 IMPORTED) + set_target_properties(${_module}::shiboken2 PROPERTIES + IMPORTED_LOCATION "${SHIBOKEN_BINARY}") +endif() + +mark_as_advanced(SHIBOKEN_INCLUDE_DIR SHIBOKEN_LIBRARY SHIBOKEN_BINARY) + diff --git a/cmake/PythonInfo.cmake b/cmake/PythonInfo.cmake new file mode 100644 index 0000000..c60f4cd --- /dev/null +++ b/cmake/PythonInfo.cmake @@ -0,0 +1,32 @@ + +function(find_python_site_packages VAR) + if(Python_SITELIB) + set("${VAR}" "${Python_SITELIB}" PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "if True: + from distutils import sysconfig + print(sysconfig.get_python_lib(prefix=None, plat_specific=True))" + OUTPUT_VARIABLE "${VAR}" + OUTPUT_STRIP_TRAILING_WHITESPACE) + set("${VAR}" "${${VAR}}" PARENT_SCOPE) +endfunction() + +function(get_python_extension_suffix VAR) + # from PySide2 CMakeLists.txt + # Result of imp.get_suffixes() depends on the platform, but generally looks something like: + # [('.cpython-34m-x86_64-linux-gnu.so', 'rb', 3), ('.cpython-34m.so', 'rb', 3), + # ('.abi3.so', 'rb', 3), ('.so', 'rb', 3), ('.py', 'r', 1), ('.pyc', 'rb', 2)] + # We pick the first most detailed one, strip of the file extension part. + + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "if True: + import sysconfig + print(sysconfig.get_config_var('EXT_SUFFIX')) + " + OUTPUT_VARIABLE "${VAR}" + OUTPUT_STRIP_TRAILING_WHITESPACE) + set("${VAR}" "${${VAR}}" PARENT_SCOPE) +endfunction() diff --git a/gui/include/SqpApplication.h b/gui/include/SqpApplication.h index ba5dc59..cc2c417 100644 --- a/gui/include/SqpApplication.h +++ b/gui/include/SqpApplication.h @@ -110,4 +110,11 @@ private: spimpl::unique_impl_ptr impl; }; +inline SqpApplication* SqpApplication_ctor() +{ + static int argc; + static char** argv; + return new SqpApplication(argc, argv); +} + #endif // SCIQLOP_SQPAPPLICATION_H diff --git a/gui/src/Catalogue2/eventsmodel.cpp b/gui/src/Catalogue2/eventsmodel.cpp index 0b53932..968a7cd 100644 --- a/gui/src/Catalogue2/eventsmodel.cpp +++ b/gui/src/Catalogue2/eventsmodel.cpp @@ -112,16 +112,6 @@ void EventsModel::sort(int column, Qt::SortOrder order) [inverse = order != Qt::SortOrder::AscendingOrder]( const std::unique_ptr& a, const std::unique_ptr& b) { - static int i = 0; - i++; - if (!a->event()) - { - throw; - } - if (!b->event()) - { - throw; - } return (a->event()->name < b->event()->name) xor inverse; }); break; @@ -130,45 +120,13 @@ void EventsModel::sort(int column, Qt::SortOrder order) [inverse = order != Qt::SortOrder::AscendingOrder]( const std::unique_ptr& a, const std::unique_ptr& b) { - static int i = 0; - i++; - if (!a) - { - throw; - } - if (!b) - { - throw; - } - if (!a->event()) - { - throw; - } - if (!b->event()) + if (auto t1 = a->event()->startTime(); auto t2 = b->event()->startTime()) { - throw; + if (t1 and t2) + return bool((t1.value() < t2.value()) xor inverse); } - return (a->event()->name < b->event()->name) xor inverse; + return true; }); - // std::sort(std::begin(_items), std::end(_items), - // [inverse = order != Qt::SortOrder::AscendingOrder]( - // const std::unique_ptr& a, - // const std::unique_ptr& b) { - // static int i = 0; - // i++; - // if (a and b) - // { - // if (a->type == ItemType::Event and b->type == ItemType::Event) - // { - // if (auto e1 = a->event(); auto e2 = b->event()) - // { - // return bool((e1->startTime() < e2->startTime()) xor - // inverse); - // } - // } - // } - // return false; - // }); break; case EventsModel::Columns::TEnd: std::sort(std::begin(_items), std::end(_items), diff --git a/gui/tests/CMakeLists.txt b/gui/tests/CMakeLists.txt index 8fb8381..7f07c10 100644 --- a/gui/tests/CMakeLists.txt +++ b/gui/tests/CMakeLists.txt @@ -2,9 +2,9 @@ include(sciqlop_tests) subdirs(GUITestUtils) declare_test(simple_graph simple_graph simple_graph/main.cpp "sciqlopgui;TestUtils;GUITestUtils;Qt5::Test") declare_test(multiple_sync_graph multiple_sync_graph multiple_sync_graph/main.cpp "sciqlopgui;TestUtils;GUITestUtils;Qt5::Test") - +declare_test(catalogue_browser catalogue_browser catalogue/browser/main.cpp "sciqlopgui;TestUtils;GUITestUtils;Qt5::Test") if(NOT WIN32) declare_manual_test(event_list event_list catalogue/event_list/main.cpp "sciqlopgui;TestUtils;GUITestUtils;Qt5::Test") declare_manual_test(repository_list repository_list catalogue/repository_list/main.cpp "sciqlopgui;TestUtils;GUITestUtils;Qt5::Test") - declare_manual_test(catalogue_browser catalogue_browser catalogue/browser/main.cpp "sciqlopgui;TestUtils;GUITestUtils;Qt5::Test") -endif() \ No newline at end of file + declare_manual_test(catalogue_browser_m catalogue_browser_m catalogue/browser/main.cpp "sciqlopgui;TestUtils;GUITestUtils;Qt5::Test") +endif() diff --git a/gui/tests/catalogue/browser/main.cpp b/gui/tests/catalogue/browser/main.cpp index d5825a5..bad4f37 100644 --- a/gui/tests/catalogue/browser/main.cpp +++ b/gui/tests/catalogue/browser/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -14,6 +15,36 @@ #include #include +template +auto build_CatalogueBrowser_test() +{ + sqpApp->catalogueController().add("test"); + sqpApp->catalogueController().add("stuff"); + sqpApp->catalogueController().add("default"); + sqpApp->catalogueController().add("new catalogue", "default"); + auto catalogue = sqpApp->catalogueController().add("new catalogue2", "default"); + for (auto _ : std::array()) + { + static int i = 0; + auto event = CatalogueController::make_event_ptr(); + event->name = std::string("Event ") + std::to_string(i++); + event->tags = { "tag1", "tag2" }; + event->products = { CatalogueController::Event_t::Product_t { + std::string("Product2") + std::to_string(rand() % 30), + static_cast(1532357932 + rand() % 100), + static_cast(1532358932 + rand() % 100) }, + CatalogueController::Event_t::Product_t { + std::string("Product2") + std::to_string(rand() % 30), + static_cast(1532357932 + rand() % 200), + static_cast(1532358932 + rand() % 200) }, + CatalogueController::Event_t::Product_t { + std::string("Product2") + std::to_string(rand() % 30), + static_cast(1532357932 + rand() % 70), + static_cast(1532358932 + rand() % 70) } }; + catalogue->add(event); + } + return std::make_unique(); +} class A_CatalogueBrowser : public QObject { @@ -22,48 +53,33 @@ public: explicit A_CatalogueBrowser(QObject* parent = Q_NULLPTR) : QObject(parent) {} private slots: + void can_sort_events() + { + auto w = build_CatalogueBrowser_test(); + QVERIFY(prepare_gui_test(w.get())); + // GET_CHILD_WIDGET_FOR_GUI_TESTS((*w.get()),,,) + for (int i = 0; i < 1000000; i++) + { + QThread::usleep(100); + QCoreApplication::processEvents(); + } + } }; -// QT_BEGIN_NAMESPACE -// QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS -// QT_END_NAMESPACE -// int main(int argc, char* argv[]) -//{ -// SqpApplication app { argc, argv }; -// app.setAttribute(Qt::AA_Use96Dpi, true); -// QTEST_DISABLE_KEYPAD_NAVIGATION; -// QTEST_ADD_GPU_BLACKLIST_SUPPORT; -// An_EventList tc; -// QTEST_SET_MAIN_SOURCE_PATH; -// return QTest::qExec(&tc, argc, argv); -//} - -#include "main.moc" - - +QT_BEGIN_NAMESPACE +QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS +QT_END_NAMESPACE int main(int argc, char* argv[]) { Q_INIT_RESOURCE(sqpguiresources); - QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - SqpApplication a { argc, argv }; - CataloguesBrowser w; - sqpApp->catalogueController().add("test"); - sqpApp->catalogueController().add("stuff"); - sqpApp->catalogueController().add("default"); - sqpApp->catalogueController().add("new catalogue", "default"); - auto catalogue = sqpApp->catalogueController().add("new catalogue2", "default"); - for (auto _ : std::array()) - { - static int i = 0; - auto event = CatalogueController::make_event_ptr(); - event->name = std::string("Event ") + std::to_string(i++); - event->tags = {"tag1", "tag2"}; - event->products = { CatalogueController::Event_t::Product_t { "Product1", 10., 11. }, - CatalogueController::Event_t::Product_t { "Product2", 11., 12. }, - CatalogueController::Event_t::Product_t { "Product3", 10.2, 11. } }; - catalogue->add(event); - } - w.show(); - return a.exec(); + SqpApplication app { argc, argv }; + app.setAttribute(Qt::AA_Use96Dpi, true); + QTEST_DISABLE_KEYPAD_NAVIGATION; + QTEST_ADD_GPU_BLACKLIST_SUPPORT; + A_CatalogueBrowser tc; + QTEST_SET_MAIN_SOURCE_PATH; + return QTest::qExec(&tc, argc, argv); } + +#include "main.moc" diff --git a/plugins/python_providers/src/python_providers.cpp b/plugins/python_providers/src/python_providers.cpp index 4a2b328..4229ff2 100644 --- a/plugins/python_providers/src/python_providers.cpp +++ b/plugins/python_providers/src/python_providers.cpp @@ -27,10 +27,10 @@ public: PythonProvider(const PythonProvider& other) : _pythonFunction { other._pythonFunction } {} - std::shared_ptr clone() const override - { - return std::make_shared(*this); - } +// std::shared_ptr clone() const override +// { +// return std::make_shared(*this); +// } virtual TimeSeries::ITimeSerie* getData(const DataProviderParameters& parameters) override { auto product = parameters.m_Data.value("PRODUCT", "").toString().toStdString();