From 86295157320340cfab3fba102cbdcde98d742eda Mon Sep 17 00:00:00 2001 From: andreidanila1 Date: Thu, 1 Aug 2024 14:19:09 +0300 Subject: [PATCH] plugingenerator: Added option to generate scopy sdk project. You can now generate an out of tree (oot) plugin support project within plugin generator. To work, the scopy sdk project requires importing the libraries and dependencies that were generated with cpack. Signed-off-by: andreidanila1 --- tools/plugingenerator/config.json | 5 + tools/plugingenerator/plugin_generator.py | 149 +++++- .../templates/cmakelists_template.mako | 13 + .../sdk/sdk_cmake_func_template.mako | 25 + .../templates/sdk/sdk_cmake_template.mako | 65 +++ .../templates/sdk/sdk_cmakein_template.mako | 6 + .../templates/sdk/sdk_header_template.mako | 111 ++++ .../templates/sdk/sdk_src_template.mako | 494 ++++++++++++++++++ 8 files changed, 857 insertions(+), 11 deletions(-) create mode 100644 tools/plugingenerator/templates/sdk/sdk_cmake_func_template.mako create mode 100644 tools/plugingenerator/templates/sdk/sdk_cmake_template.mako create mode 100644 tools/plugingenerator/templates/sdk/sdk_cmakein_template.mako create mode 100644 tools/plugingenerator/templates/sdk/sdk_header_template.mako create mode 100644 tools/plugingenerator/templates/sdk/sdk_src_template.mako diff --git a/tools/plugingenerator/config.json b/tools/plugingenerator/config.json index 3ba3c4c9ca..dcc0a46306 100644 --- a/tools/plugingenerator/config.json +++ b/tools/plugingenerator/config.json @@ -1,4 +1,9 @@ { + "sdk":{ + "enable": true, + "deps_path": "", + "project_path": "" + }, "plugin": { "dir_name": "new", "plugin_name": "newplugin", diff --git a/tools/plugingenerator/plugin_generator.py b/tools/plugingenerator/plugin_generator.py index 7e176c0d54..c6c0a824f6 100644 --- a/tools/plugingenerator/plugin_generator.py +++ b/tools/plugingenerator/plugin_generator.py @@ -25,11 +25,6 @@ pathToScopy = args.scopy_path else: pathToScopy = os.path.dirname(os.path.dirname(os.getcwd())) - -pluginsPath = os.path.join(pathToScopy, "plugins") -if not os.path.exists(pluginsPath): - print("Couldn't find " + pluginsPath + " path!") - exit(1) pathToConfigFile = "" if args.config_file_path: @@ -49,6 +44,7 @@ filesGenerated = [] directoriesGenerated = [] +sdkSupport = generatorOptions["sdk"]["enable"] pluginDirName = generatorOptions["plugin"]["dir_name"] pluginName = generatorOptions["plugin"]["plugin_name"] pluginDisplayName = generatorOptions["plugin"]["plugin_display_name"] @@ -57,6 +53,32 @@ pluginExportMacro = "SCOPY_" + pluginName.upper() + "_EXPORT" print("Starting file generation:") + +pluginsPath = os.path.join(pathToScopy, "plugins") +if sdkSupport: + sdkPath = generatorOptions["sdk"]["project_path"] + if not sdkPath: + sdkPath = os.path.join(pathToScopy, "ScopySDK") + else: + sdkPath = os.path.join(sdkPath, "ScopySDK") + try: + os.mkdir(sdkPath, mode) + directoriesGenerated.append(sdkPath) + except FileExistsError: + print(sdkPath + " directory already exists!") + + pluginsPath = os.path.join(sdkPath, "plugin") + try: + os.mkdir(pluginsPath, mode) + directoriesGenerated.append(pluginsPath) + except FileExistsError: + print(pluginsPath + " directory already exists!") + + +if not os.path.exists(pluginsPath): + print("Couldn't find " + pluginsPath + " path!") + exit(1) + #################################################### Plugin dir ############################################# newPluginPath = os.path.join(pluginsPath, pluginDirName) if not os.path.exists(newPluginPath): @@ -98,6 +120,107 @@ else: print(pluginSrcConfigPath + " file already exists!") +####################################################### sdk ################################################## +if sdkSupport: + sdkCmakeFuncFilePath = os.path.join(sdkPath, "SdkSupport.cmake") + if not os.path.exists(sdkCmakeFuncFilePath): + sdkCmakeFuncTemplate = Template( + filename="templates/sdk/sdk_cmake_func_template.mako" + ) + sdkCmakeFuncContent = sdkCmakeFuncTemplate.render() + sdkCmakeFuncFile = open(sdkCmakeFuncFilePath, "w") + sdkCmakeFuncFile.write(sdkCmakeFuncContent) + sdkCmakeFuncFile.close() + filesGenerated.append(sdkCmakeFuncFilePath) + else: + print(sdkCmakeFuncFilePath + " file already exists!") + + sdkCmakeFilePath = os.path.join(sdkPath, "CMakeLists.txt") + if not os.path.exists(sdkCmakeFilePath): + sdkCmakeTemplate = Template( + filename="templates/sdk/sdk_cmake_template.mako" + ) + sdkCmakeContent = sdkCmakeTemplate.render( + deps_path=generatorOptions["sdk"]["deps_path"], + plugin_dir=pluginDirName, + plugin_name=pluginName + ) + sdkCmakeFile = open(sdkCmakeFilePath, "w") + sdkCmakeFile.write(sdkCmakeContent) + sdkCmakeFile.close() + filesGenerated.append(sdkCmakeFilePath) + else: + print(sdkCmakeFilePath + " file already exists!") + + sdkIncludePath = os.path.join(sdkPath, "include") + try: + os.mkdir(sdkIncludePath, mode) + directoriesGenerated.append(sdkIncludePath) + except FileExistsError: + print(sdkIncludePath + " directory already exists!") + + sdkHeaderFilePath = os.path.join(sdkIncludePath, "sdkwindow.h") + if not os.path.exists(sdkHeaderFilePath): + sdkHeaderTemplate = Template( + filename="templates/sdk/sdk_header_template.mako" + ) + sdkHeaderContent = sdkHeaderTemplate.render() + sdkHeaderFile = open(sdkHeaderFilePath, "w") + sdkHeaderFile.write(sdkHeaderContent) + sdkHeaderFile.close() + filesGenerated.append(sdkHeaderFilePath) + else: + print(sdkHeaderFilePath + " file already exists!") + + sdkCmakeInFilePath = os.path.join(sdkIncludePath, "sdk-util_config.h.cmakein") + if not os.path.exists(sdkCmakeInFilePath): + sdkCmakeInTemplate = Template( + filename="templates/sdk/sdk_cmakein_template.mako" + ) + sdkCmakeInTemplate = sdkCmakeInTemplate.render() + sdkCmakeInFile = open(sdkCmakeInFilePath, "w") + sdkCmakeInFile.write(sdkCmakeInTemplate) + sdkCmakeInFile.close() + filesGenerated.append(sdkCmakeInFilePath) + else: + print(sdkCmakeInFilePath + " file already exists!") + + sdkSrcPath = os.path.join(sdkPath, "src") + try: + os.mkdir(sdkSrcPath, mode) + directoriesGenerated.append(sdkSrcPath) + except FileExistsError: + print(sdkSrcPath + " directory already exists!") + + sdkSrcFilePath = os.path.join(sdkSrcPath, "main.cpp") + if not os.path.exists(sdkSrcFilePath): + sdkSrcTemplate = Template( + filename="templates/sdk/sdk_src_template.mako" + ) + sdkSrcContent = sdkSrcTemplate.render() + sdkSrcFile = open(sdkSrcFilePath, "w") + sdkSrcFile.write(sdkSrcContent) + sdkSrcFile.close() + filesGenerated.append(sdkSrcFilePath) + + sdkResPath = os.path.join(sdkPath, "res") + try: + os.mkdir(sdkResPath, mode) + directoriesGenerated.append(sdkResPath) + except FileExistsError: + print(sdkResPath + " directory already exists!") + shutil.copy(pathToScopy+"/gui/res/stylesheets/default.qss",sdkResPath) + sdkResQrc = os.path.join(sdkResPath, "resources.qrc") + if not os.path.exists(sdkResQrc): + resFile = open(sdkResQrc, "w") + resFile.write("\n") + resFile.write(" \n") + resFile.write(" default.qss\n") + resFile.write(" \n") + resFile.write("") + resFile.close() + filesGenerated.append(sdkResQrc) + ##################################################### Include ################################################ includePath = os.path.join(newPluginPath, "include") try: @@ -171,8 +294,9 @@ + str(generatorOptions["test"]["cmake_min_required"]) + ")\n\n" ) - if generatorOptions["test"]["tst_pluginloader"]: - testCmakeFile.write("include(ScopyTest)\n\nsetup_scopy_tests(pluginloader)") + if not sdkSupport: + if generatorOptions["test"]["tst_pluginloader"]: + testCmakeFile.write("include(ScopyTest)\n\nsetup_scopy_tests(pluginloader)") filesGenerated.append(testCmakePath) else: print(testCmakePath + " file already exists!") @@ -214,13 +338,16 @@ print(resQrc + " file already exists!") ##################################################### Plugin CMakeLists ######################################### + if generatorOptions["plugin"]["cmakelists"]: cmakeListsPath = os.path.join(newPluginPath, "CMakeLists.txt") cmakeTemplate = Template(filename="templates/cmakelists_template.mako") + cmakeContent = cmakeTemplate.render( - scopy_module=pluginName, - plugin_display_name=pluginDisplayName, - plugin_description=pluginDecription, config=generatorOptions["cmakelists"] + sdk_en=sdkSupport, + scopy_module=pluginName, + plugin_display_name=pluginDisplayName, + plugin_description=pluginDecription, config=generatorOptions["cmakelists"] ) if not os.path.exists(cmakeListsPath): @@ -247,7 +374,7 @@ filesGenerated.append(cmakeinFilePath) else: - print(cmakeinFilePath + " file already exists!") + print(cmakeinFilePath + " file already exists!") ##################################################### Plugin ToolList ######################################### diff --git a/tools/plugingenerator/templates/cmakelists_template.mako b/tools/plugingenerator/templates/cmakelists_template.mako index dede1117ef..a3f047b8cd 100644 --- a/tools/plugingenerator/templates/cmakelists_template.mako +++ b/tools/plugingenerator/templates/cmakelists_template.mako @@ -65,6 +65,17 @@ configure_file( target_include_directories(${"${PROJECT_NAME}"} INTERFACE ${"${CMAKE_CURRENT_SOURCE_DIR}"}/include) target_include_directories(${"${PROJECT_NAME}"} PRIVATE ${"${CMAKE_CURRENT_SOURCE_DIR}"}/include/${"${SCOPY_MODULE}"}) +% if oot_en: +target_include_directories(${"${PROJECT_NAME}"} PRIVATE ${"${SDK_DEPS_INCLUDE}"}) + +include(${"${CMAKE_SOURCE_DIR}"}/SdkSupport.cmake) +inlcude_dirs(${"${SDK_DEPS_INCLUDE}"}) + +target_link_libraries(${"${PROJECT_NAME}"} PUBLIC Qt::Widgets Qt::Core) + +link_libs(${"${SDK_DEPS_LIB}"}) + +% else: target_include_directories(${"${PROJECT_NAME}"} PUBLIC scopy-pluginbase scopy-gui) target_link_libraries( @@ -76,6 +87,8 @@ target_link_libraries( scopy-iioutil ) +% endif + if(${"${CMAKE_SYSTEM_NAME}"} MATCHES "Windows") configureinstallersettings(${"${SCOPY_MODULE}"} ${"${PLUGIN_DESCRIPTION}"} FALSE) endif() diff --git a/tools/plugingenerator/templates/sdk/sdk_cmake_func_template.mako b/tools/plugingenerator/templates/sdk/sdk_cmake_func_template.mako new file mode 100644 index 0000000000..1ffb4998c5 --- /dev/null +++ b/tools/plugingenerator/templates/sdk/sdk_cmake_func_template.mako @@ -0,0 +1,25 @@ +# Define a function to include all directories recursively +function(inlcude_dirs root_dir) + # Find all files and directories recursively + file(GLOB_RECURSE all_items LIST_DIRECTORIES true ""${"${root_dir}"}/"") + # Loop through each item found + foreach(item ${"${all_items}"}) + # Check if the item is a directory + if(IS_DIRECTORY ${"${item}"}) + message(${"${item}"}) + target_include_directories(${"${PROJECT_NAME}"} PRIVATE ${"${item}"}) + endif() + endforeach() +endfunction() + +# Define a function to link all .so files from a root directory +function(link_libs root_dir) + # Find all .so files from root_dir + file(GLOB all_libs "${"${root_dir}"}/*.so") + # Loop through each library found + foreach(lib ${"${all_libs}"}) + # Link libraries + message(${"${lib}"}) + target_link_libraries(${"${PROJECT_NAME}"} PRIVATE ${"${lib}"}) + endforeach() +endfunction() \ No newline at end of file diff --git a/tools/plugingenerator/templates/sdk/sdk_cmake_template.mako b/tools/plugingenerator/templates/sdk/sdk_cmake_template.mako new file mode 100644 index 0000000000..c421b78cd0 --- /dev/null +++ b/tools/plugingenerator/templates/sdk/sdk_cmake_template.mako @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.9) + +# Project name +set(TARGET_NAME "ScopySDK") + +project(${"${TARGET_NAME}"} VERSION 0.0.1 DESCRIPTION "Project Description") + +# Make sure CMake will take care of moc for us +set(CMAKE_AUTOMOC ON) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(SDK_DEPS_PATH ${deps_path}) +if(NOT DEFINED SDK_DEPS_PATH) + message(FATAL_ERROR "SDK_DEPS_PATH is required!") +else() + if(NOT EXISTS ${"${SDK_DEPS_PATH}"}) + message(FATAL_ERROR "The path=" \"${"${SDK_DEPS_PATH}"}\" " to the dependencies doesn't exist!") + endif() +endif() +set(SDK_DEPS_INCLUDE ${"${SDK_DEPS_PATH}"}/usr/local/include) +if(NOT EXISTS ${"${SDK_DEPS_INCLUDE}"}) + message(FATAL_ERROR "The path=" \"${"${SDK_DEPS_INCLUDE}"}\" " to the headers doesn't exist!") +endif() + +set(SDK_DEPS_LIB ${"${SDK_DEPS_PATH}"}/usr/local/lib) +if(NOT EXISTS ${"${SDK_DEPS_LIB}"}) + message(FATAL_ERROR "The path=" \"${"${SDK_DEPS_LIB}"}\" " to the libraries doesn't exist!") +endif() + +set(PLUGIN_INSTALL_PATH ${"${CMAKE_CURRENT_BINARY_DIR}"}/plugin/${plugin_dir}/libscopy-${plugin_name}.so) + +find_package(QT NAMES Qt5 REQUIRED COMPONENTS Widgets) +find_package(Qt${"${QT_VERSION_MAJOR}"} REQUIRED COMPONENTS Widgets Core) + +file(GLOB SRC_LIST src/*.cpp) +file(GLOB HEADER_LIST include/*.h include/*.hpp) + +configure_file(include/sdk-util_config.h.cmakein ${"${CMAKE_CURRENT_SOURCE_DIR}"}/include/sdk-util_config.h @ONLY) + +set(PROJECT_SOURCES ${"${SRC_LIST}"} ${"${HEADER_LIST}"} ${"${CMAKE_CURRENT_SOURCE_DIR}"}/include/sdk-util_config.h) +find_path(IIO_INCLUDE_DIRS iio.h REQUIRED) +find_library(IIO_LIBRARIES NAMES iio libiio REQUIRED) + +add_subdirectory(plugin/${plugin_dir}) + +qt_add_resources(PROJ_RES res/resources.qrc) + +add_executable(${"${TARGET_NAME}"} ${"${PROJECT_SOURCES}"} ${"${PROJ_RES}"}) + +include(${"${CMAKE_CURRENT_SOURCE_DIR}"}/SdkSupport.cmake) + +target_include_directories(${"${TARGET_NAME}"} PRIVATE ${"${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS}"}) +target_include_directories(${"${TARGET_NAME}"} PRIVATE ${"${CMAKE_SOURCE_DIR}"}/include) +target_include_directories(${"${TARGET_NAME}"} INTERFACE ${"${IIO_INCLUDE_DIRS}"}) + +target_include_directories(${"${TARGET_NAME}"} PUBLIC ${"${SDK_DEPS_INCLUDE}"} ${"${IIO_INCLUDE_DIRS}"}) + +inlcude_dirs(${"${SDK_DEPS_INCLUDE}"}) +# Add any extra libs to link also. +link_libs(${"${SDK_DEPS_LIB}"}) +target_link_libraries( + ${"${TARGET_NAME}"} PRIVATE Qt${"${QT_VERSION_MAJOR}"}::Widgets Qt${"${QT_VERSION_MAJOR}"}::Core ${"${IIO_LIBRARIES}"} +) diff --git a/tools/plugingenerator/templates/sdk/sdk_cmakein_template.mako b/tools/plugingenerator/templates/sdk/sdk_cmakein_template.mako new file mode 100644 index 0000000000..1b8ec71831 --- /dev/null +++ b/tools/plugingenerator/templates/sdk/sdk_cmakein_template.mako @@ -0,0 +1,6 @@ +#ifndef SDK_UTIL_H_CMAKEIN +#define SDK_UTIL_H_CMAKEIN + +#define PLUGIN_INSTALL_PATH "@PLUGIN_INSTALL_PATH@" + +#endif // SDK_UTIL_H_CMAKEIN diff --git a/tools/plugingenerator/templates/sdk/sdk_header_template.mako b/tools/plugingenerator/templates/sdk/sdk_header_template.mako new file mode 100644 index 0000000000..e6db7658d1 --- /dev/null +++ b/tools/plugingenerator/templates/sdk/sdk_header_template.mako @@ -0,0 +1,111 @@ +#include "plugin.h" +#include "toolmenuentry.h" +#include +#include +#include +#include +#include +#include + +class MainWidget; +class PluginManager; + +class SDKWindow : public QMainWindow +{ + Q_OBJECT +public: + SDKWindow(QWidget *parent = nullptr); + ~SDKWindow(); + +public Q_SLOTS: + void onConnect(); + void onDisconnect(); +Q_SIGNALS: + void sigLabelTextUpdated(std::string_view); + +private: + QTabWidget *m_tabWidget; + QWidget *m_prefPage; + QList m_toolList; + MainWidget *m_mainWidget = nullptr; + + void initMainWindow(); + void updateLabelText(); + void initPreferencesPage(); + void addPluginPrefPage(); + void removePluginPrefPage(); + QLabel *createTabLabel(QString name); + QWidget *addHorizontalTab(QWidget *w, QLabel *lbl); +}; + +class MainWidget : public QWidget +{ + Q_OBJECT +public: + MainWidget(QWidget *parent); + ~MainWidget(); + + QWidget *pluginPrefPage(); + QList getPluginTools(); + +Q_SIGNALS: + void connected(); + void disconnected(); + +private Q_SLOTS: + void onConnect(); + void onDisconnect(); + void browseFile(QLineEdit *pluginPathEdit); + +private: + void unloadPluginDetails(); + void loadPluginDetails(); + void btnsActivation(); + bool validConnection(QString uri, QString cat); + bool isCompatible(QString uri, QString cat); + + PluginManager *m_pluginManager; + QLineEdit *m_uriEdit; + QLineEdit *m_pluginPathEdit; + QPushButton *m_connBtn; + QPushButton *m_loadBtn; + QPushButton *m_unloadBtn; + QWidget *m_pluginIcon; + scopy::MenuCombo *m_deviceTypeCb; + QScrollArea *m_scrollArea; +}; + +class PluginManager : public QObject +{ + Q_OBJECT +public: + PluginManager(QString pluginPath, QObject *parent = nullptr); + ~PluginManager(); + + scopy::Plugin *plugin() const; + bool pluginCompatibility(QString param, QString category); + +Q_SIGNALS: + void requestTool(QString toolId); + +private: + void initPlugin(); + void loadPlugin(QString file); + bool pluginCategory(QString category); + + scopy::Plugin *m_plugin = nullptr; +}; + +class ConnectionStrategy : public QObject +{ + Q_OBJECT +public: + ConnectionStrategy(QString uri, QObject *parent = nullptr); + ~ConnectionStrategy(); + bool validConnection(QString cat); + +private: + bool iioConn(); + bool testConn(); + QString m_uri; +}; diff --git a/tools/plugingenerator/templates/sdk/sdk_src_template.mako b/tools/plugingenerator/templates/sdk/sdk_src_template.mako new file mode 100644 index 0000000000..08359c8fb3 --- /dev/null +++ b/tools/plugingenerator/templates/sdk/sdk_src_template.mako @@ -0,0 +1,494 @@ +#include "sdkwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gui/stylehelper.h" +#include "sdk-util_config.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QCoreApplication::setOrganizationName("ADI"); + QCoreApplication::setOrganizationDomain("analog.com"); + QCoreApplication::setApplicationName("Scopy-SDK"); + QSettings::setDefaultFormat(QSettings::IniFormat); + + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + app.setStyleSheet(Util::loadStylesheetFromFile(":/default.qss")); + SDKWindow test; + test.show(); + int ret = app.exec(); + if(ret == 0) { + qInfo() << "SDK support finished successfully!"; + } + return ret; +} + +SDKWindow::SDKWindow(QWidget *parent) + : QMainWindow(parent) +{ + setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + setMinimumSize(1280, 720); + layout()->setMargin(9); + layout()->setSpacing(6); + scopy::StyleHelper::GetInstance()->initColorMap(); + m_mainWidget = new MainWidget(this); + initMainWindow(); + initPreferencesPage(); + + addHorizontalTab(m_mainWidget, createTabLabel("Main")); + addHorizontalTab(new QWidget(), createTabLabel("About")); + addHorizontalTab(m_prefPage, createTabLabel("Preferences")); + connect(m_mainWidget, &MainWidget::connected, this, &SDKWindow::onConnect); + connect(m_mainWidget, &MainWidget::disconnected, this, &SDKWindow::onDisconnect); +} + +SDKWindow::~SDKWindow() +{ + if(m_mainWidget) { + delete m_mainWidget; + m_mainWidget = nullptr; + } +} + +void SDKWindow::onConnect() +{ + addPluginPrefPage(); + QList tools = m_mainWidget->getPluginTools(); + for(const scopy::ToolMenuEntry *t : qAsConst(tools)) { + if(!t->tool()) + continue; + QLabel *lbl = createTabLabel(t->name()); + QWidget *tab = addHorizontalTab(t->tool(), lbl); + connect(t, &scopy::ToolMenuEntry::updateToolEntry, this, [lbl, t]() { + if(lbl->text().compare(t->name()) != 0) { + lbl->setText(t->name()); + } + }); + m_toolList.append(tab); + } +} + +void SDKWindow::onDisconnect() +{ + removePluginPrefPage(); + for(auto t : qAsConst(m_toolList)) { + int idx = m_tabWidget->indexOf(t); + m_tabWidget->removeTab(idx); + m_toolList.removeOne(t); + } +} + +void SDKWindow::initMainWindow() +{ + QWidget *centralWidget = new QWidget(this); + centralWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + centralWidget->setMinimumSize(1280, 720); + setCentralWidget(centralWidget); + QHBoxLayout *lay = new QHBoxLayout(centralWidget); + lay->setMargin(9); + lay->setSpacing(6); + + m_tabWidget = new QTabWidget(centralWidget); + m_tabWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_tabWidget->setTabPosition(QTabWidget::TabPosition::West); + scopy::StyleHelper::BackgroundPage(m_tabWidget, "sdkTable"); + + lay->addWidget(m_tabWidget); +} + +QWidget *SDKWindow::addHorizontalTab(QWidget *w, QLabel *lbl) +{ + QWidget *pane = new QWidget(m_tabWidget); + pane->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + QHBoxLayout *lay = new QHBoxLayout(pane); + lay->setMargin(10); + pane->setLayout(lay); + + QScrollArea *scrollArea = new QScrollArea(pane); + scrollArea->setWidget(w); + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + lay->addWidget(scrollArea); + + // Hackish - so we don't override paint event + m_tabWidget->addTab(pane, ""); + QTabBar *tabbar = m_tabWidget->tabBar(); + tabbar->setTabButton(tabbar->count() - 1, QTabBar::LeftSide, lbl); + return pane; +} +void SDKWindow::initPreferencesPage() +{ + m_prefPage = new QWidget(this); + QGridLayout *lay = new QGridLayout(m_prefPage); + lay->setSpacing(0); + lay->setMargin(0); + QLabel *title = new QLabel(m_prefPage); + title->setText("Plugin Preferences"); + scopy::StyleHelper::MenuHeaderLabel(title); + lay->addWidget(title, 0, 0, Qt::AlignTop | Qt::AlignLeft); + lay->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), 2, 0); +} + +void SDKWindow::addPluginPrefPage() +{ + QWidget *pluginPref = m_mainWidget->pluginPrefPage(); + if(pluginPref) { + QGridLayout *lay = dynamic_cast(m_prefPage->layout()); + lay->addWidget(pluginPref, 1, 0); + } +} + +void SDKWindow::removePluginPrefPage() +{ + QGridLayout *lay = dynamic_cast(m_prefPage->layout()); + QLayoutItem *it = lay->itemAtPosition(1, 0); + if(!it) { + return; + } + QWidget *pluginPref = it->widget(); + if(pluginPref) { + m_prefPage->layout()->removeWidget(pluginPref); + delete pluginPref; + } +} + +QLabel *SDKWindow::createTabLabel(QString name) +{ + QLabel *lbl = new QLabel(); + scopy::StyleHelper::TabWidgetLabel(lbl, "tabWidgetLabel"); + lbl->setText(name); + return lbl; +} + +MainWidget::MainWidget(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + this->setMinimumWidth(1024); + QGridLayout *lay = new QGridLayout(this); + lay->setMargin(9); + lay->setSpacing(6); + + // load section + m_loadBtn = new QPushButton("Load plugin", this); + scopy::StyleHelper::BlueGrayButton(m_loadBtn); + m_loadBtn->setFixedWidth(128); + + m_pluginPathEdit = new QLineEdit(this); + m_pluginPathEdit->setText(PLUGIN_INSTALL_PATH); + m_pluginPathEdit->setPlaceholderText(PLUGIN_INSTALL_PATH); + connect(m_loadBtn, &QPushButton::clicked, this, [this]() { browseFile(m_pluginPathEdit); }); + + // connect section + m_uriEdit = new QLineEdit(this); + m_uriEdit->setPlaceholderText("URI"); + m_uriEdit->setText("ip:127.0.0.1"); + + m_deviceTypeCb = new scopy::MenuCombo("category", this); + m_deviceTypeCb->setFixedHeight(45); + m_deviceTypeCb->combo()->addItem("iio"); + m_deviceTypeCb->combo()->addItem("test"); + + m_connBtn = new QPushButton("Connect", this); + scopy::StyleHelper::BlueGrayButton(m_connBtn); + m_connBtn->setCheckable(true); + m_connBtn->setFixedWidth(128); + connect(m_connBtn, &QPushButton::clicked, this, &MainWidget::onConnect); + + // unload + m_unloadBtn = new QPushButton("Unload", this); + scopy::StyleHelper::BlueGrayButton(m_unloadBtn); + m_unloadBtn->setFixedWidth(128); + m_unloadBtn->setEnabled(false); + connect(m_unloadBtn, &QPushButton::clicked, this, &MainWidget::onDisconnect); + + // plugin info + QWidget *pluginPage = new QWidget(this); + pluginPage->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + pluginPage->setLayout(new QVBoxLayout(pluginPage)); + pluginPage->layout()->setMargin(0); + + m_scrollArea = new QScrollArea(pluginPage); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + pluginPage->layout()->addWidget(m_scrollArea); + + m_pluginIcon = new QWidget(this); + m_pluginIcon->setLayout(new QHBoxLayout(m_pluginIcon)); + m_pluginIcon->layout()->setMargin(0); + m_pluginIcon->setFixedSize(100, 100); + + lay->addWidget(m_pluginPathEdit, 0, 0); + lay->addWidget(m_uriEdit, 1, 0); + lay->addWidget(m_deviceTypeCb, 2, 0); + lay->addWidget(pluginPage, 3, 0); + + lay->addWidget(m_loadBtn, 0, 1); + lay->addWidget(m_connBtn, 1, 1); + lay->addWidget(m_unloadBtn, 2, 1); + lay->addWidget(m_pluginIcon, 3, 1, Qt::AlignCenter | Qt::AlignBottom); +} + +MainWidget::~MainWidget() +{ + if(m_pluginManager && !m_connBtn->isEnabled()) { + onDisconnect(); + } +} + +QWidget *MainWidget::pluginPrefPage() +{ + QWidget *prefPage = nullptr; + m_pluginManager->plugin()->initPreferences(); + if(m_pluginManager->plugin()->loadPreferencesPage()) { + prefPage = m_pluginManager->plugin()->preferencesPage(); + } + return prefPage; +} + +QList MainWidget::getPluginTools() { return m_pluginManager->plugin()->toolList(); } + +void MainWidget::onConnect() +{ + QString uri = m_uriEdit->text(); + QString cat = m_deviceTypeCb->combo()->currentText(); + QString pluginPath = m_pluginPathEdit->text(); + if(!validConnection(uri, cat)) { + m_connBtn->setChecked(false); + return; + } + m_pluginManager = new PluginManager(pluginPath, this); + if(!isCompatible(uri, cat) || !m_pluginManager->plugin()->onConnect()) { + m_connBtn->setChecked(false); + delete m_pluginManager; + m_pluginManager = nullptr; + return; + } + btnsActivation(); + loadPluginDetails(); + Q_EMIT connected(); +} + +void MainWidget::onDisconnect() +{ + btnsActivation(); + unloadPluginDetails(); + m_pluginManager->plugin()->onDisconnect(); + m_pluginManager->plugin()->unload(); + m_connBtn->setChecked(false); + delete m_pluginManager; + m_pluginManager = nullptr; + Q_EMIT disconnected(); +} + +void MainWidget::browseFile(QLineEdit *pluginPathEdit) +{ + QString filePath = + QFileDialog::getOpenFileName(this, "Open a file", "directoryToOpen", + "All (*);;XML Files (*.xml);;Text Files (*.txt);;BIN Files (*.bin)"); + pluginPathEdit->setText(filePath); +} + +void MainWidget::unloadPluginDetails() +{ + QWidget *pluginPage = m_pluginManager->plugin()->page(); + if(pluginPage) { + m_scrollArea->takeWidget(); + } + QWidget *pluginIcon = m_pluginManager->plugin()->icon(); + if(pluginIcon) { + m_pluginIcon->layout()->removeWidget(pluginIcon); + delete pluginIcon; + } +} + +void MainWidget::loadPluginDetails() +{ + if(m_pluginManager->plugin()->loadPage()) { + m_scrollArea->setWidget(m_pluginManager->plugin()->page()); + } + if(m_pluginManager->plugin()->loadIcon()) { + m_pluginIcon->layout()->addWidget(m_pluginManager->plugin()->icon()); + } +} + +void MainWidget::btnsActivation() +{ + m_unloadBtn->setEnabled(!m_unloadBtn->isEnabled()); + m_connBtn->setEnabled(!m_connBtn->isEnabled()); +} + +bool MainWidget::validConnection(QString uri, QString cat) +{ + ConnectionStrategy *connStrategy = new ConnectionStrategy(uri, this); + bool validConn = false; + validConn = connStrategy->validConnection(cat); + if(!validConn) { + m_uriEdit->clear(); + m_uriEdit->setText("Cannot connect to URI!"); + } + connStrategy->deleteLater(); + return validConn; +} + +bool MainWidget::isCompatible(QString uri, QString cat) +{ + bool compatible = false; + compatible = m_pluginManager->pluginCompatibility(uri, cat); + if(!compatible) { + m_uriEdit->clear(); + m_uriEdit->setText("The plugin is not compatible with the device or doesn't exist!"); + } + return compatible; +} + +PluginManager::PluginManager(QString pluginPath, QObject *parent) + : QObject(parent) +{ + loadPlugin(pluginPath); +} + +PluginManager::~PluginManager() +{ + if(m_plugin) { + delete m_plugin; + m_plugin = nullptr; + } +} + +void PluginManager::initPlugin() +{ + if(!m_plugin) + return; + m_plugin->preload(); + m_plugin->initMetadata(); + m_plugin->init(); + m_plugin->loadToolList(); + m_plugin->loadExtraButtons(); + m_plugin->postload(); +} + +bool PluginManager::pluginCategory(QString category) +{ + if(!m_plugin) + return false; + if(category.isEmpty()) // no category selected + return true; + if(!m_plugin->metadata().contains("category")) // plugin metadata does not have category + return true; + QJsonValue categoryVal = m_plugin->metadata().value("category"); + if(categoryVal.isString()) // single category + return category == m_plugin->metadata().value("category").toString(); + if(categoryVal.isArray()) { // list category + QJsonArray catArray = categoryVal.toArray(); + for(const auto &v : catArray) { + if(!v.isString()) { + continue; + } + if(v.toString() == category) { + return true; + } + } + } + return false; +} + +bool PluginManager::pluginCompatibility(QString param, QString category) +{ + bool isCategory = pluginCategory(category); + if(!isCategory) + return false; + if(!m_plugin->compatible(param, category)) { + return false; + } + m_plugin->setParam(param, category); + m_plugin->setEnabled(true); + initPlugin(); + return true; +} + +scopy::Plugin *PluginManager::plugin() const { return m_plugin; } + +void PluginManager::loadPlugin(QString file) +{ + bool ret; + scopy::Plugin *original = nullptr; + scopy::Plugin *clone = nullptr; + + if(!QFile::exists(file)) + return; + + if(!QLibrary::isLibrary(file)) + return; + + QPluginLoader qp(file); + ret = qp.load(); + if(!ret) { + qWarning() << "Cannot load library " + qp.fileName() + "- err: " + qp.errorString(); + return; + } + + QObject *inst = qp.instance(); + if(!inst) { + qWarning() << "Cannot create QObject instance from loaded library"; + return; + } + + original = qobject_cast(qp.instance()); + if(!original) { + qWarning() << "Loaded library instance is not a Plugin*"; + return; + } + + clone = original->clone(this); + if(!clone) { + qWarning() << "clone method does not clone the object"; + return; + } + + QString cloneName; + cloneName = clone->name(); + + if(cloneName == "") + return; + + m_plugin = clone; +} + +ConnectionStrategy::ConnectionStrategy(QString uri, QObject *parent) + : m_uri(uri) + , QObject(parent) +{} + +ConnectionStrategy::~ConnectionStrategy() {} + +bool ConnectionStrategy::validConnection(QString cat) +{ + bool valid = false; + if(cat.compare("iio") == 0) { + valid = iioConn(); + } else { + valid = testConn(); + } + return valid; +} + +bool ConnectionStrategy::iioConn() +{ + iio_context *ctx = iio_create_context_from_uri(m_uri.toStdString().c_str()); + if(!ctx) { + return false; + } + iio_context_destroy(ctx); + return true; +} + +bool ConnectionStrategy::testConn() { return true; }