From 6e3f56cd8c8b14fd5b017d14ac87abc20297cb6e 2019-08-23 13:30:31 From: Alexis Jeandet Date: 2019-08-23 13:30:31 Subject: [PATCH] PySide2 bindings + some GUI clean - simple Python plugin with scalar product works - launcher args are forwarded to SciQLop app, this allow to choose between wayland and xcb for example - removed all not implemented widgets on main GUI - moved all widgets except plots into QDocWidgets Signed-off-by: Alexis Jeandet --- diff --git a/app/PySide2-bindings/CMakeLists.txt b/app/PySide2-bindings/CMakeLists.txt index 01f4988..8ee7931 100644 --- a/app/PySide2-bindings/CMakeLists.txt +++ b/app/PySide2-bindings/CMakeLists.txt @@ -63,7 +63,7 @@ include_directories( ${PYSIDE_INCLUDE_DIR}/QtGui ${PYSIDE_INCLUDE_DIR}/QtWidgets) -add_library(SciQLopBindings MODULE ${BINDINGS_SOURCE} numpy_wrappers.h) +add_library(SciQLopBindings MODULE ${BINDINGS_SOURCE} numpy_wrappers.h PyDataProvider.h) set_target_properties( SciQLopBindings PROPERTIES diff --git a/app/PySide2-bindings/PyDataProvider.h b/app/PySide2-bindings/PyDataProvider.h index 7be30d1..d62c84b 100644 --- a/app/PySide2-bindings/PyDataProvider.h +++ b/app/PySide2-bindings/PyDataProvider.h @@ -1,16 +1,89 @@ #pragma once +#include #include +#include +#include +#include +#include +#include +// must be included last because of Python/Qt definition of slots +#include "numpy_wrappers.h" + +struct Product +{ + QString path; + std::vector components; + QMap metadata; + Product() = default; + explicit Product(const QString& path, const std::vector& components, + const QMap& metadata) + : path { path }, components { components }, metadata { metadata } + { + } + virtual ~Product() = default; +}; class PyDataProvider : public IDataProvider { public: - PyDataProvider() {} + PyDataProvider() + { + auto& dataSourceController = sqpApp->dataSourceController(); + dataSourceController.registerProvider(this); + } + + virtual ~PyDataProvider() {} - virtual TimeSeries::ITimeSerie getData(const std::string& key, double start_time, double stop_time) - {} + virtual QPair getData( + const std::string& key, double start_time, double stop_time) + { + (void)key, (void)start_time, (void)stop_time; + return {}; + } - virtual TimeSeries::ITimeSerie* getData(const DataProviderParameters& parameters) + virtual TimeSeries::ITimeSerie* getData(const DataProviderParameters& parameters) override { + if (parameters.m_Data.contains("name")) + { + auto data = getData(parameters.m_Data["name"].toString().toStdString(), + parameters.m_Range.m_TStart, parameters.m_Range.m_TEnd); + // TODO add shape/type switch + return new ScalarTimeSerie { data.first.to_std_vect(), data.second.to_std_vect() }; + } return nullptr; } + + + inline void register_products(const QVector& products) + { + auto& dataSourceController = sqpApp->dataSourceController(); + auto id = this->id(); + auto data_source_name = this->name(); + std::for_each(std::cbegin(products), std::cend(products), + [&id, &dataSourceController](const Product* product) { + dataSourceController.setDataSourceItem(id, product->path, product->metadata); + }); + } }; + + +struct Providers +{ + Providers() = default; + virtual ~Providers() = default; + inline void register_provider(PyDataProvider* provider) + { + auto& dataSourceController = sqpApp->dataSourceController(); + dataSourceController.setDataProvider( + provider->id(), std::unique_ptr(provider)); + } +}; + + +inline ScalarTimeSerie test_PyDataProvider(PyDataProvider& prov) +{ + auto v = prov.getData("", 0., 0.); + ScalarTimeSerie s; + s.set_data(v.first.to_std_vect(), v.second.to_std_vect()); + return s; +} diff --git a/app/PySide2-bindings/bindings.xml b/app/PySide2-bindings/bindings.xml index 3f8a933..70bac73 100644 --- a/app/PySide2-bindings/bindings.xml +++ b/app/PySide2-bindings/bindings.xml @@ -31,18 +31,36 @@ + + - - + + + + + + + + + + + static int argc; + static char **argv; + Shiboken::listToArgcArgv(%1, &argc, &argv, "PySideApp"); + auto retval = new SqpApplication(argc,argv); + %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](retval); + + - return %in.py_object(); + auto result = %in.py_object(); + return result; @@ -68,5 +86,6 @@ + diff --git a/app/PySide2-bindings/main.cpp b/app/PySide2-bindings/main.cpp index 79f878d..3976308 100644 --- a/app/PySide2-bindings/main.cpp +++ b/app/PySide2-bindings/main.cpp @@ -4,6 +4,17 @@ #define Py_DEBUG #include +wchar_t** decode_argv(int argc, char** argv) +{ + wchar_t** _argv = static_cast(PyMem_Malloc(sizeof(wchar_t*) * argc)); + for (int i = 0; i < argc; i++) + { + wchar_t* arg = Py_DecodeLocale(argv[i], NULL); + _argv[i] = arg; + } + return _argv; +} + int main(int argc, char** argv) { wchar_t* program = Py_DecodeLocale(argv[0], NULL); @@ -12,8 +23,10 @@ int main(int argc, char** argv) fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); exit(1); } + Py_SetProgramName(program); /* optional but recommended */ Py_Initialize(); + PySys_SetArgv(argc, decode_argv(argc, argv)); std::ifstream t(argv[1]); std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); PyRun_SimpleString(str.data()); diff --git a/app/PySide2-bindings/main.py b/app/PySide2-bindings/main.py index 2d3d247..7bd6566 100644 --- a/app/PySide2-bindings/main.py +++ b/app/PySide2-bindings/main.py @@ -6,6 +6,7 @@ from PySide2.QtWidgets import QApplication, QMainWindow, QDockWidget from PySide2.QtCore import QSize, Qt from PySide2 import QtGui import os +sys.path.append(os.getcwd()) from SciQLopBindings import SqpApplication, MainWindow, init_resources, load_plugins, SqpApplication_ctor from qtconsole.rich_ipython_widget import RichJupyterWidget from qtconsole.inprocess import QtInProcessKernelManager @@ -91,7 +92,7 @@ def print_process_id(): if __name__ == "__main__": init_resources() - app = SqpApplication_ctor() + app = SqpApplication_ctor(sys.argv) QtGui.qApp = app load_plugins(app) main_window = MainWindow() diff --git a/app/PySide2-bindings/numpy_wrappers.h b/app/PySide2-bindings/numpy_wrappers.h index fb94e5e..63feb93 100644 --- a/app/PySide2-bindings/numpy_wrappers.h +++ b/app/PySide2-bindings/numpy_wrappers.h @@ -29,6 +29,8 @@ extern "C" #endif #include +#include + inline int init_numpy() { import_array(); // PyError if not successful @@ -39,32 +41,31 @@ template struct PyObjectWrapper { private: - PyObject* _py_obj; - + PyObject* _py_obj = nullptr; void inc_refcount() { - if (_py_obj) - Py_IncRef(_py_obj); + Py_XINCREF(_py_obj); } void dec_refcount() { - if (_py_obj) - Py_DecRef(_py_obj); + Py_XDECREF(_py_obj); + _py_obj = nullptr; } public: PyObjectWrapper() : _py_obj { nullptr } {} - PyObjectWrapper(const PyObjectWrapper& other) : _py_obj { other._py_obj } { inc_refcount(); }; - PyObjectWrapper(PyObjectWrapper&& other) : _py_obj { other._py_obj } + PyObjectWrapper(const PyObjectWrapper& other) : _py_obj { other._py_obj } { inc_refcount(); } + PyObjectWrapper(PyObjectWrapper&& other) : _py_obj { other._py_obj } { inc_refcount(); } + explicit PyObjectWrapper(PyObject* obj) : _py_obj { obj } { - other._py_obj = nullptr; + inc_refcount(); } - PyObjectWrapper(PyObject* obj) : _py_obj { obj } { inc_refcount(); } ~PyObjectWrapper() { dec_refcount(); } PyObjectWrapper& operator=(PyObjectWrapper&& other) { + dec_refcount(); this->_py_obj = other._py_obj; - other._py_obj = nullptr; + inc_refcount(); return *this; } PyObjectWrapper& operator=(const PyObjectWrapper& other) @@ -91,14 +92,14 @@ private: public: static bool isNpArray(PyObject* obj) { - return obj && PyArray_Check(reinterpret_cast(obj)) - && PyArray_IS_C_CONTIGUOUS(reinterpret_cast(obj)); + auto arr = reinterpret_cast(obj); + auto is_c_aray = obj && PyArray_Check(arr) && PyArray_ISCARRAY(arr); + return is_c_aray; } NpArray() : _py_obj { nullptr } {} - NpArray(NpArray&& other) : _py_obj { std::move(other._py_obj) } {} + NpArray(NpArray&& other) : _py_obj { 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())); } @@ -111,7 +112,7 @@ public: NpArray& operator=(NpArray&& other) { - this->_py_obj = std::move(other._py_obj); + this->_py_obj = other._py_obj; return *this; } diff --git a/app/include/toolbar.h b/app/include/toolbar.h index 3ef4693..ef3e794 100644 --- a/app/include/toolbar.h +++ b/app/include/toolbar.h @@ -9,7 +9,7 @@ #include #include -// @TODO remove this, shouldn't need to include SqpApplication to get PlotsInteractionMode +// TODO remove this, shouldn't need to include SqpApplication to get PlotsInteractionMode #include class ToolBar : public QToolBar diff --git a/app/src/MainWindow.cpp b/app/src/MainWindow.cpp index 0c74276..514893f 100644 --- a/app/src/MainWindow.cpp +++ b/app/src/MainWindow.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -48,29 +49,16 @@ Q_LOGGING_CATEGORY(LOG_MainWindow, "MainWindow") -namespace -{ -const auto LEFTMAININSPECTORWIDGETSPLITTERINDEX = 0; -const auto LEFTINSPECTORSIDEPANESPLITTERINDEX = 1; -const auto VIEWPLITTERINDEX = 2; -const auto RIGHTINSPECTORSIDEPANESPLITTERINDEX = 3; -const auto RIGHTMAININSPECTORWIDGETSPLITTERINDEX = 4; -} - class MainWindow::MainWindowPrivate { public: explicit MainWindowPrivate(MainWindow* mainWindow) - : m_LastOpenLeftInspectorSize {} - , m_LastOpenRightInspectorSize {} - , m_GeneralSettingsWidget { new SqpSettingsGeneralWidget { mainWindow } } + : m_GeneralSettingsWidget { new SqpSettingsGeneralWidget { mainWindow } } , m_SettingsDialog { new SqpSettingsDialog { mainWindow } } , m_CatalogExplorer { new CataloguesBrowser { mainWindow } } { } - QSize m_LastOpenLeftInspectorSize; - QSize m_LastOpenRightInspectorSize; /// General settings widget. MainWindow has the ownership SqpSettingsGeneralWidget* m_GeneralSettingsWidget; /// Settings dialog. MainWindow has the ownership @@ -89,67 +77,6 @@ MainWindow::MainWindow(QWidget* parent) m_Ui->setupUi(this); setWindowTitle(QString("SciQLop v%1").arg(SCIQLOP_VERSION)); - m_Ui->splitter->setCollapsible(LEFTINSPECTORSIDEPANESPLITTERINDEX, false); - m_Ui->splitter->setCollapsible(RIGHTINSPECTORSIDEPANESPLITTERINDEX, false); - - // impl->m_CatalogExplorer->setVisualizationWidget(m_Ui->view); - - - auto spacerLeftTop = new QWidget {}; - spacerLeftTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - auto spacerLeftBottom = new QWidget {}; - spacerLeftBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - - auto spacerRightTop = new QWidget {}; - spacerRightTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - auto spacerRightBottom = new QWidget {}; - spacerRightBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - - auto openInspector = [this](bool checked, bool right, auto action) { - action->setIcon( - QIcon { (checked ^ right) ? ":/icones/next.png" : ":/icones/previous.png" }); - - auto& lastInspectorSize - = right ? impl->m_LastOpenRightInspectorSize : impl->m_LastOpenLeftInspectorSize; - - auto nextInspectorSize = right ? m_Ui->rightMainInspectorWidget->size() - : m_Ui->leftMainInspectorWidget->size(); - - // Update of the last opened geometry - if (checked) - { - lastInspectorSize = nextInspectorSize; - } - - auto startSize = lastInspectorSize; - auto endSize = startSize; - endSize.setWidth(0); - - auto splitterInspectorIndex - = right ? RIGHTMAININSPECTORWIDGETSPLITTERINDEX : LEFTMAININSPECTORWIDGETSPLITTERINDEX; - - auto currentSizes = m_Ui->splitter->sizes(); - if (checked) - { - // adjust sizes individually here, e.g. - currentSizes[splitterInspectorIndex] -= lastInspectorSize.width(); - currentSizes[VIEWPLITTERINDEX] += lastInspectorSize.width(); - m_Ui->splitter->setSizes(currentSizes); - } - else - { - // adjust sizes individually here, e.g. - currentSizes[splitterInspectorIndex] += lastInspectorSize.width(); - currentSizes[VIEWPLITTERINDEX] -= lastInspectorSize.width(); - m_Ui->splitter->setSizes(currentSizes); - } - }; - - // //////////////// // // Menu and Toolbar // // //////////////// // @@ -188,8 +115,8 @@ MainWindow::MainWindow(QWidget* parent) // Widgets / controllers connections // DataSource - connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem*)), - m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem*))); + connect(&sqpApp->dataSourceController(), &DataSourceController::dataSourceItemSet, + m_Ui->dataSourceWidget, &DataSourceWidget::addDataSource); // Time // connect(timeWidget, SIGNAL(timeUpdated(DateTimeRange)), &sqpApp->timeController(), diff --git a/app/ui/MainWindow.ui b/app/ui/MainWindow.ui index 899a94f..908b022 100644 --- a/app/ui/MainWindow.ui +++ b/app/ui/MainWindow.ui @@ -16,7 +16,7 @@ true - + true @@ -32,97 +32,6 @@ 16777215 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - Name - - - - - - - - - @@ -130,7 +39,7 @@ 0 0 800 - 27 + 24 @@ -150,6 +59,30 @@ + + + QDockWidget::DockWidgetMovable + + + Products + + + 1 + + + + + + QDockWidget::DockWidgetMovable + + + Variables + + + 1 + + + diff --git a/core b/core index c4e2737..9ff5f48 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit c4e273750e74336d1d125c02989c922f2032e489 +Subproject commit 9ff5f48e3d71ce2d3a8b2156f778f3f9be3ea389 diff --git a/gui/src/DataSource/DataSourceTreeWidgetItem.cpp b/gui/src/DataSource/DataSourceTreeWidgetItem.cpp index d02d738..8815bb2 100644 --- a/gui/src/DataSource/DataSourceTreeWidgetItem.cpp +++ b/gui/src/DataSource/DataSourceTreeWidgetItem.cpp @@ -138,7 +138,7 @@ struct DataSourceTreeWidgetItem::DataSourceTreeWidgetItemPrivate { QString m_Name; /// Actions associated to the item. The parent of the item (QTreeWidget) takes the ownership of /// the actions - QList m_Actions; + QList m_Actions; //TODO check if no memory leak here }; DataSourceTreeWidgetItem::DataSourceTreeWidgetItem(const DataSourceItem *data, int type) diff --git a/gui/src/DataSource/DataSourceWidget.cpp b/gui/src/DataSource/DataSourceWidget.cpp index 7934df4..b4eaeae 100644 --- a/gui/src/DataSource/DataSourceWidget.cpp +++ b/gui/src/DataSource/DataSourceWidget.cpp @@ -8,39 +8,39 @@ #include -namespace { +namespace +{ /// Number of columns displayed in the tree const auto TREE_NB_COLUMNS = 1; /// Header labels for the tree -const auto TREE_HEADER_LABELS = QStringList{QObject::tr("Name")}; +const auto TREE_HEADER_LABELS = QStringList { QObject::tr("Name") }; /** * Creates the item associated to a data source * @param dataSource the data source for which to create the item * @return the new item */ -DataSourceTreeWidgetItem *createTreeWidgetItem(DataSourceItem *dataSource) +DataSourceTreeWidgetItem* createTreeWidgetItem(DataSourceItem* dataSource) { // Creates item for the data source - auto item = new DataSourceTreeWidgetItem{dataSource}; - + auto item = new DataSourceTreeWidgetItem { dataSource }; // Generates items for the children of the data source - for (auto i = 0; i < dataSource->childCount(); ++i) { - item->addChild(createTreeWidgetItem(dataSource->child(i))); - } - + std::for_each(dataSource->cbegin(), dataSource->cend(), + [&item](const std::unique_ptr& child) { + item->addChild(createTreeWidgetItem(child.get())); + }); return item; } } // namespace -DataSourceWidget::DataSourceWidget(QWidget *parent) - : QWidget{parent}, - ui{new Ui::DataSourceWidget}, - m_Root{ - std::make_unique(DataSourceItemType::NODE, QStringLiteral("Sources"))} +DataSourceWidget::DataSourceWidget(QWidget* parent) + : QWidget { parent } + , ui { new Ui::DataSourceWidget } + , m_Root { std::make_unique( + DataSourceItemType::NODE, QStringLiteral("Sources")) } { ui->setupUi(this); @@ -51,7 +51,7 @@ DataSourceWidget::DataSourceWidget(QWidget *parent) // Connection to show a menu when right clicking on the tree connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this, - &DataSourceWidget::onTreeMenuRequested); + &DataSourceWidget::onTreeMenuRequested); // Connection to filter tree connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &DataSourceWidget::filterChanged); @@ -65,14 +65,13 @@ DataSourceWidget::~DataSourceWidget() noexcept delete ui; } -void DataSourceWidget::addDataSource(DataSourceItem *dataSource) noexcept +void DataSourceWidget::addDataSource(DataSourceItem* dataSource) noexcept { // Merges the data source (without taking its root) - if (dataSource) { - for (auto i = 0, count = dataSource->childCount(); i < count; ++i) { - m_Root->merge(*dataSource->child(i)); - } - + if (dataSource) + { + std::for_each(std::cbegin(*dataSource), std::cend(*dataSource), + [this](const auto& child) { this->m_Root->merge(*child.get()); }); updateTreeWidget(); } } @@ -90,33 +89,35 @@ void DataSourceWidget::updateTreeWidget() noexcept ui->treeWidget->sortByColumn(0, Qt::AscendingOrder); } -void DataSourceWidget::filterChanged(const QString &text) noexcept +void DataSourceWidget::filterChanged(const QString& text) noexcept { - auto validateItem = [&text](const DataSourceTreeWidgetItem &item) { - auto regExp = QRegExp{text, Qt::CaseInsensitive, QRegExp::Wildcard}; + auto validateItem = [&text](const DataSourceTreeWidgetItem& item) { + auto regExp = QRegExp { text, Qt::CaseInsensitive, QRegExp::Wildcard }; // An item is valid if any of its metadata validates the text filter auto itemMetadata = item.data()->data(); auto itemMetadataEnd = itemMetadata.cend(); auto acceptFilter - = [®Exp](const auto &variant) { return variant.toString().contains(regExp); }; + = [®Exp](const auto& variant) { return variant.toString().contains(regExp); }; return std::find_if(itemMetadata.cbegin(), itemMetadataEnd, acceptFilter) - != itemMetadataEnd; + != itemMetadataEnd; }; // Applies filter on tree widget DataSourceTreeWidgetHelper::filter(*ui->treeWidget, validateItem); } -void DataSourceWidget::onTreeMenuRequested(const QPoint &pos) noexcept +void DataSourceWidget::onTreeMenuRequested(const QPoint& pos) noexcept { // Retrieves the selected item in the tree, and build the menu from its actions - if (auto selectedItem = dynamic_cast(ui->treeWidget->itemAt(pos))) { - QMenu treeMenu{}; + if (auto selectedItem = dynamic_cast(ui->treeWidget->itemAt(pos))) + { + QMenu treeMenu {}; treeMenu.addActions(selectedItem->actions()); - if (!treeMenu.isEmpty()) { + if (!treeMenu.isEmpty()) + { treeMenu.exec(QCursor::pos()); } } diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 1465322..fc081a5 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -219,7 +219,7 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { /* * I give up on this for now - * @TODO implement this, the difficulty is that selection zones have their own + * TODO implement this, the difficulty is that selection zones have their own * event handling code which seems to rely on QCP GUI event handling propagation * which was a realy bad design choice. */ @@ -506,7 +506,7 @@ void VisualizationGraphWidget::addVariable(std::shared_ptr variable, delete context; }); } - //@TODO this is bad! when variable is moved to another graph it still fires + //TODO this is bad! when variable is moved to another graph it still fires // even if this has been deleted connect(variable.get(), &Variable2::updated, this, &VisualizationGraphWidget::variableUpdated); this->onUpdateVarDisplaying(variable, range); // My bullshit