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..62c79b0871 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,11 +338,14 @@ 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, + sdk_en=sdkSupport, + scopy_module=pluginName, plugin_display_name=pluginDisplayName, plugin_description=pluginDecription, config=generatorOptions["cmakelists"] ) 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; }