文章目录
- 一、前言
- 二、基本策略
- 三、效果展示
- 四、关键代码
- 4.1、主程序
- 4.2、插件管理器
- 4.3、插件A
一、前言
插件大致可分为:功能性插件、界面插件;一个软件由一堆插件堆起来,必然难以避免插件间相互引用,例如:插件A调用了插件C中的功能,那么插件A就依赖插件C,所以在程序启动加载插件时,就应该先加载插件C,再加载插件A。
如果插件A是用于初始化的模块,插件C是用于检测软件序列合法性的模块,如果先加载插件A,并直接调用功能模块,那么检测软件序列合法性这部分功能就是缺失的,所以必须要检测插件间的依赖关系,并排列插件加载顺序,才能使得软件功能正常。
但是插件依赖,不能形成死锁,例如:有向环
- 图1:先加载插件B和插件C,再加载插件A,即可;
- 图2:先加载插件C,再加载插件B,再加载插件A,即可;
- 图3:先加载插件C,再加载插件B,再加载插件A,即可;
- 图4:加载插件A需要插件B,加载插件B需要插件C,加载插件C需要插件A,就形成死锁了;
二、基本策略
插件元数据中有一项:dependencies
,这个就是用来标识该插件依赖的插件,例如:插件A依赖插件B、插件C
{
"author" : "Wang_JC",
"date" : "2022/02/16",
"name" : "pluginA",
"version" : "1.0.0",
"des" : "这是一个插件A",
"dependencies" : ["pluginB:1.0.0","pluginC:1.0.0"],
"menu" : "menuFile",
"action" : ["action_A1","action_A2","action_A3"],
"widget" : "tabWidget"
}
我们在主程序中嗅探、自动加载插件:
在加载插件的时候,我们可以读取插件元数据中的插件依赖信息,然后检测依赖;如果存在依赖,那就暂不加载;如果不存在,那就直接加载。
检测插件是否存在依赖:可以遍历当前插件依赖项
等到自动加载插件全部结束后,我们再检查依赖插件栈内是否有还未加载的插件,有的话,再手动加载一下:
三、效果展示
弄了4个插件:A、B、C、D,依赖关系如下:
- 插件A:依赖插件B和插件C
- 插件B:无依赖
- 插件C:无依赖
- 插件D:无依赖
所以加载插件时,插件B和插件C必须要在插件A之间加载,插件D无所谓
四、关键代码
4.1、主程序
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDebug>
#include "PluginManager.h"
#include <QDir>
#include <QMenuBar>
#include <QToolBar>
#include <QTabWidget>
#include <QWidget>
#include <QHBoxLayout>
#include <QLabel>
#include "./Widget/speeddashbroad.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void Init_UI();
void recvMsgFromManager(PluginMetaData metaData);
QMenuBar* menuBar;
QMenu* menuFile;
QMenu* menuEdit;
QMenu* menuView;
QMenu* menuTool;
QTabWidget* tabWidget;
public:
SpeedDashBroad* sdb;
signals:
void sendMsgToManager(PluginMetaData);
private slots:
void slot_PluginAction_MenuBar(QString menu,QStringList actionList); //添加插件菜单栏Action
void slot_PluginsAction_trigger(bool isChecked); //响应插件Action
void slot_PluginWidget(QPluginLoader*,QString); //添加插件widget
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
Init_UI();
//传递主程序指针给插件管理器
PluginManager::instance()->m_mainWin = this;
//绑定主程序和插件管理器消息信号槽
connect(this,&MainWindow::sendMsgToManager,PluginManager::instance(),&PluginManager::recMsgFromPlugin);
//嗅探到的所有插件
qDebug()<<"嗅探插件: "<<PluginManager::instance()->CK_allPluginsName().keys();
//加载所有插件
foreach(QString pluginName , PluginManager::instance()->CK_allPluginsName().keys()) {
PluginManager::instance()->loadPlugin(PluginManager::instance()->CK_allPluginsName().value(pluginName));
}
//加载所有插件
// PluginManager::instance()->loadAllPlugins();
//加载存在依赖稍候处理的插件
while(!PluginManager::instance()->m_remainPlugin.isEmpty()) {
QString plugin_filePath = PluginManager::instance()->m_remainPlugin.pop();
qDebug()<<endl<<endl<<"加载存在依赖稍候处理的插件: "<<plugin_filePath;
PluginManager::instance()->loadPlugin(plugin_filePath);
}
//加载其中某个插件
// PluginManager::instance()->loadPlugin(PluginManager::instance()->CK_allPluginsName().value("pluginA"));
// PluginManager::instance()->loadPlugin(PluginManager::instance()->CK_allPluginsName().value("pluginB"));
// PluginManager::instance()->loadPlugin(PluginManager::instance()->CK_allPluginsName().value("pluginC"));
// PluginManager::instance()->loadPlugin(PluginManager::instance()->CK_allPluginsName().value("pluginD"));
// QPluginLoader *loader1 = PluginManager::instance()->getPlugin("pluginA");
// PluginInterface* pluginInterface1 = qobject_cast<PluginInterface *>(loader1->instance());
// pluginInterface1->showSomeThing("新增接口API");
// QPluginLoader *loader2 = PluginManager::instance()->getPlugin("pluginB");
// PluginInterface* pluginInterface2 = qobject_cast<PluginInterface *>(loader2->instance());
// pluginInterface2->showSomeThing("新增接口API");
// //通信测试
// //================================================================================
// QPluginLoader *loader1 = PluginManager::instance()->getPlugin("pluginA");
// if(loader1) {
// PluginInterface *pluginA = dynamic_cast<PluginInterface*>(loader1->instance());
// if(pluginA) {
// PluginMetaData m;
// m.dest = "pluginB";
// m.from = "pluginA";
// m.msg = "插件A发给插件B的消息";
// pluginA->sendMsgToManager(m);
// }
// }
// QPluginLoader *loader2 = PluginManager::instance()->getPlugin("pluginB");
// if(loader2) {
// PluginInterface *pluginB = dynamic_cast<PluginInterface*>(loader2->instance());
// if(pluginB) {
// PluginMetaData m;
// m.dest = "pluginA";
// m.from = "pluginB";
// m.msg = "插件B发给插件A的消息";
// pluginB->sendMsgToManager(m);
// }
// }
// //------------------------------
// if(loader2) {
// PluginInterface *pluginB = dynamic_cast<PluginInterface*>(loader2->instance());
// if(pluginB) {
// PluginMetaData m1;
// m1.dest = "mainWin";
// m1.from = "pluginB";
// m1.msg = "插件B发给主程序的消息";
// pluginB->sendMsgToManager(m1);
// }
// }
// //------------------------------
// PluginMetaData m2;
// m2.dest = "pluginA";
// m2.from = "mainWin";
// m2.msg = "主程序发给插件A的消息";
// this->sendMsgToManager(m2);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::Init_UI()
{
QWidget* p = takeCentralWidget(); //删除中央窗体
if (p) {
delete p;
}
setDockNestingEnabled(true); //允许嵌套dock
//-------------------------------------------------
menuBar = new QMenuBar(this);
menuFile = new QMenu("文件", this);
menuBar->addMenu(menuFile);
this->setMenuBar(menuBar);
menuEdit = new QMenu("编辑", this);
menuBar->addMenu(menuEdit);
this->setMenuBar(menuBar);
menuView = new QMenu("视图", this);
menuBar->addMenu(menuView);
this->setMenuBar(menuBar);
menuTool = new QMenu("工具", this);
menuBar->addMenu(menuTool);
this->setMenuBar(menuBar);
connect(PluginManager::instance(),&PluginManager::sig_actions,this,&MainWindow::slot_PluginAction_MenuBar);
//-------------------------------------------------
tabWidget = new QTabWidget(this);
tabWidget->setMinimumSize(1000, 800); // 设置最小宽高
setCentralWidget(tabWidget); // 指定为中心窗口
connect(PluginManager::instance(),&PluginManager::sig_widget,this,&MainWindow::slot_PluginWidget);
//-------------------------------------------------
sdb = new SpeedDashBroad;
tabWidget->addTab(sdb,"表盘");
}
void MainWindow::slot_PluginAction_MenuBar(QString menu,QStringList actionList)
{
QAction * action = nullptr;
if(menu == QString::fromLocal8Bit("menuFile")) {
for(int i=0; i<actionList.size(); ++i) {
action = new QAction(QIcon(), actionList.at(i), this);
menuFile->addAction(action);
connect(action,&QAction::triggered,this,&MainWindow::slot_PluginsAction_trigger);
}
}
if(menu == QString::fromLocal8Bit("menuEdit")) {
for(int i=0; i<actionList.size(); ++i) {
action = new QAction(QIcon(), actionList.at(i), this);
menuEdit->addAction(action);
connect(action,&QAction::triggered,this,&MainWindow::slot_PluginsAction_trigger);
}
}
if(menu == QString::fromLocal8Bit("menuView")) {
for(int i=0; i<actionList.size(); ++i) {
action = new QAction(QIcon(), actionList.at(i), this);
menuView->addAction(action);
connect(action,&QAction::triggered,this,&MainWindow::slot_PluginsAction_trigger);
}
}
if(menu == QString::fromLocal8Bit("menuTool")) {
for(int i=0; i<actionList.size(); ++i) {
action = new QAction(QIcon(), actionList.at(i), this);
menuTool->addAction(action);
connect(action,&QAction::triggered,this,&MainWindow::slot_PluginsAction_trigger);
}
}
}
void MainWindow::recvMsgFromManager(PluginMetaData metaData)
{
qDebug()<<"主程序接收到消息:"<<metaData.msg;
}
void MainWindow::slot_PluginsAction_trigger(bool isChecked)
{
QAction* action = qobject_cast<QAction*>(sender());
for(int i=0; i<PluginManager::instance()->_actionMap.size(); ++i) { //遍历插件管理器action映射表
if(PluginManager::instance()->_actionMap.at(i).action == action->text()) { //映射表中匹配到Action对应的方法
PluginInterface* plugin = qobject_cast<PluginInterface *>(PluginManager::instance()->_actionMap.at(i).plugin->instance()); //获取该action对应的接口指针
if(plugin) {
for(int j=0; j<plugin->_actionName.size(); ++j) { //遍历该接口指针内的action名字
if(plugin->_actionName[j] == action->text()) {
plugin->_actionFunction[j](true);
break;
}
}
}
break;
}
}
}
void MainWindow::slot_PluginWidget(QPluginLoader* loader,QString widget)
{
if(widget == QString::fromLocal8Bit("tabWidget")) {
PluginInterface* pluginInterface = qobject_cast<PluginInterface *>(loader->instance());
QString pluginName = pluginInterface->_Plugin_Name;
QWidget* widget = pluginInterface->_widget;
if(widget) {
tabWidget->addTab(widget,pluginName);
}
}
//也可以预留布局接入点,插件UI嵌入,看自己需求
if(widget == QString::fromLocal8Bit("xxxLayout")) {
}
}
4.2、插件管理器
#ifndef PLUGINMANAGER_H
#define PLUGINMANAGER_H
#include "../Plugin_Interface/PluginInterface.h"
#include <QObject>
#include <QPluginLoader>
#include <QVariant>
#include <QAction>
#include <QStack>
class MainWindow;
typedef struct manager_action_map
{
QString action;
QPluginLoader* plugin;
}MANAGER_ACTION_MAP;
class PluginManager : public QObject
{
Q_OBJECT
public:
explicit PluginManager(QObject *parent = nullptr);
~PluginManager();
static PluginManager *instance(){
if(m_instance==nullptr)
m_instance=new PluginManager();
return m_instance;
}
MainWindow* m_mainWin;
QStack<QString> m_remainPlugin;
public:
QList<MANAGER_ACTION_MAP> _actionMap;
void deal_metaData(QPluginLoader* loader,QJsonObject& json);
public:
//扫描JSON文件中的插件元数据
void scanMetaData(const QString &filepath,QJsonObject& json);
//加载所有插件
void loadAllPlugins();
//加载其中某个插件
void loadPlugin(const QString &filepath);
//卸载所有插件
void unloadAllPlugins();
//卸载某个插件
void unloadPlugin(const QString &filepath);
//获取所有插件名称
QList<QVariant> allPluginsName();
//获取所有插件
QList<QPluginLoader *> allPlugins();
//获取某个插件名称
QVariant getPluginName(QPluginLoader *loader);
//根据名称获得插件
QPluginLoader* getPlugin(const QString &name);
//获取库中所有插件名称
QHash<QString,QString> CK_allPluginsName();
signals:
void sig_actions(QString,QStringList);
void sig_widget(QPluginLoader*,QString);
public slots:
void recMsgFromPlugin(PluginMetaData);
void slot_test();
private:
static PluginManager *m_instance;
class PluginsManagerPrivate;
PluginsManagerPrivate *managerPrivate;
};
#endif // PLUGINMANAGER_H
#include "PluginManager.h"
#include <QDir>
#include <QCoreApplication>
#include <QJsonArray>
#include <QDebug>
#include "mainwindow.h"
PluginManager* PluginManager::m_instance=nullptr;
class PluginManager::PluginsManagerPrivate
{
public:
PluginsManagerPrivate()
{
m_names.clear();
m_versions.clear();
m_dependencies.clear();
m_loaders.clear();
m_dependencies_temp.clear();
}
~PluginsManagerPrivate(){}
QHash<QString, QVariant> m_names; //插件路径--插件名称
QHash<QString, QVariant> m_versions; //插件路径--插件版本
QHash<QString, QVariantList> m_dependencies; //插件路径--插件额外依赖的其他插件
QHash<QString, QPluginLoader *> m_loaders; //插件路径--QPluginLoader实例
QHash<QString, QVariantList> m_dependencies_temp;
bool check(const QString &filepath) //插件依赖检测
{
//qDebug()<<QString("=============== bool check(%1) ===============").arg(filepath);
bool status = true;
foreach (QVariant item, m_dependencies_temp.value(filepath)) {
QString dependencyPluginInfo = item.toString();
// 依赖的插件名称、版本、路径
QStringList List = dependencyPluginInfo.split(':');
QString name_str = List[0];
QString version_str = List[1];
QString path = m_names.key(name_str);
qDebug()<<"=== 插件依赖信息 ===";
qDebug()<<"name_str: "<<name_str;
qDebug()<<"version_str: "<<version_str;
qDebug()<<"path: "<<path;
QVariant name = QVariant(name_str);
QVariant version = QVariant(version_str);
/********** 检测插件是否依赖于其他插件 **********/
// 先检测插件名称
if (!m_names.values().contains(name)) {
qDebug() << "=== 插件" << filepath <<" 缺少依赖插件:" << name.toString();
status = false;
continue;
}
// 再检测插件版本
if (m_versions.value(path) != version) {
qDebug() << "=== 依赖插件: " << name.toString() << "当前版本为: "
<< m_versions.value(m_names.key(name)).toString() << "但是需要依赖插件版本为: " << version.toString();
status = false;
continue;
}
// 然后,检测被依赖的插件是否还依赖于另外的插件
if (!check(path)) {
qDebug() << "=== 依赖插件:" << name.toString() << "又依赖: " << path;
status = false;
continue;
}
}
//qDebug()<<"status: "<<status;
return status;
}
};
PluginManager::PluginManager(QObject *parent) : QObject(parent)
{
managerPrivate = new PluginsManagerPrivate;
}
PluginManager::~PluginManager()
{
delete managerPrivate;
}
void PluginManager::loadAllPlugins()
{
QDir pluginsDir(qApp->applicationDirPath()); //pluginsDir: "../build-xxx-debug/debug"
if(pluginsDir.dirName().toLower() == "debug" ||
pluginsDir.dirName().toLower() == "release") {
pluginsDir.cdUp(); //pluginsDir: "../build-xxx-debug"
pluginsDir.cdUp(); //pluginsDir: "../"
}
pluginsDir.cd("plugins");
QFileInfoList pluginsInfo = pluginsDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
//加载插件
for(QFileInfo fileinfo : pluginsInfo)
loadPlugin(fileinfo.absoluteFilePath());
}
void PluginManager::scanMetaData(const QString &filepath,QJsonObject& json)
{
//判断是否为库(后缀有效性)
if(!QLibrary::isLibrary(filepath))
return;
if(managerPrivate->m_names.keys().contains(filepath)) {
//qDebug()<<QString("插件: %1 已加载,退出!!!").arg(filepath);
return;
}
//获取元数据
QPluginLoader *loader = new QPluginLoader(filepath);
//qDebug()<<loader->metaData().keys();
json = loader->metaData().value("MetaData").toObject();
// for(int i=0; i<json.keys().size(); ++i) {
// qDebug()<<json.keys().at(i)<< " : "<<json.value(json.keys().at(i));
// }
managerPrivate->m_names.insert(filepath, json.value("name").toVariant());
managerPrivate->m_versions.insert(filepath, json.value("version").toVariant());
managerPrivate->m_dependencies.insert(filepath, json.value("dependencies").toArray().toVariantList());
//qDebug()<<"dependencies: "<<json.value("dependencies").toArray().toVariantList();
//qDebug()<<"managerPrivate->m_dependencies: "<<managerPrivate->m_dependencies.values();
delete loader;
loader = nullptr;
}
void PluginManager::loadPlugin(const QString &filepath)
{
//库文件类型检测
if(!QLibrary::isLibrary(filepath))
return;
//读取当前插件依赖
qDebug()<<"===========================================================================";
QPluginLoader *loader_temp = new QPluginLoader(filepath);
QJsonObject json_temp = loader_temp->metaData().value("MetaData").toObject();
managerPrivate->m_dependencies_temp.insert(filepath, json_temp.value("dependencies").toArray().toVariantList());
//qDebug()<<"managerPrivate->m_dependencies_temp: "<<managerPrivate->m_dependencies_temp;
delete loader_temp;
loader_temp = nullptr;
qDebug()<<"@@@ 当前加载插件: "<<filepath;
//检测依赖
if(!managerPrivate->check(filepath)) {
qDebug()<<"当前插件存在依赖,入栈,稍候处理: "<<filepath;
m_remainPlugin.push(filepath);
//清空当前插件依赖
managerPrivate->m_dependencies_temp.clear();
return;
}
//清空当前插件依赖
managerPrivate->m_dependencies_temp.clear();
//检测当前插件是否已加载
if(managerPrivate->m_loaders.keys().contains(filepath)) {
qDebug()<<"当前插件已加载!!!";
return;
}
//加载插件
QPluginLoader *loader = new QPluginLoader(filepath);
if(loader->load()) {
PluginInterface *plugin = qobject_cast<PluginInterface *>(loader->instance());
if(plugin) {
//检测元信息
QJsonObject json;
scanMetaData(filepath,json);
deal_metaData(loader,json);
managerPrivate->m_loaders.insert(filepath, loader);
connect(loader->instance(),SIGNAL(sendMsgToManager(PluginMetaData)),
this,SLOT(recMsgFromPlugin(PluginMetaData)));
//绑定
if(plugin->_Plugin_Name == QString::fromLocal8Bit("PluginA")) {
//qDebug()<<"plugin->_Plugin_Name: "<<plugin->_Plugin_Name;
connect(loader->instance(),SIGNAL(sig_test()),
this,SLOT(slot_test()));
}
plugin->Info(QString(" %1 加载成功!").arg(plugin->_Plugin_Name));
}else {
delete loader;
loader = nullptr;
}
}else{
qDebug()<<"loadPlugin:"<<filepath<<loader->errorString();
}
}
void PluginManager::unloadAllPlugins()
{
for(QString filepath : managerPrivate->m_loaders.keys())
unloadPlugin(filepath);
}
void PluginManager::unloadPlugin(const QString &filepath)
{
if(!managerPrivate->m_loaders.keys().contains(filepath)) {
return;
}
QPluginLoader *loader = managerPrivate->m_loaders.value(filepath);
//卸载插件,并从内部数据结构中移除
if(loader->unload()) {
PluginInterface *plugin = qobject_cast<PluginInterface *>(loader->instance());
if(plugin) {
plugin->Info("插件卸载成功!");
managerPrivate->m_loaders.remove(filepath);
delete loader;
loader = nullptr;
}
}
}
QList<QPluginLoader *> PluginManager::allPlugins()
{
return managerPrivate->m_loaders.values();
}
QList<QVariant> PluginManager::allPluginsName()
{
return managerPrivate->m_names.values();
}
QVariant PluginManager::getPluginName(QPluginLoader *loader)
{
if(loader)
return managerPrivate->m_names.value(managerPrivate->m_loaders.key(loader));
else
return "";
}
QPluginLoader *PluginManager::getPlugin(const QString &name)
{
return managerPrivate->m_loaders.value(managerPrivate->m_names.key(name));
}
void PluginManager::recMsgFromPlugin(PluginMetaData metaData)
{
//和主程序通信
//------------------------------------------------------------
if(metaData.dest == QString::fromLocal8Bit("mainWin")) {
if(m_mainWin) {
m_mainWin->recvMsgFromManager(metaData);
}
return;
}
//和插件通信
//------------------------------------------------------------
auto loader = getPlugin(metaData.dest); //目标插件
if(loader) {
auto interface = qobject_cast<PluginInterface*>(loader->instance());
if(interface) {
interface->recMsgFromManager(metaData); //转发给对应的插件
}
}
}
void PluginManager::slot_test()
{
//qDebug()<<"触发槽函数: PluginManager::slot_test()";
}
QHash<QString,QString> PluginManager::CK_allPluginsName()
{
QDir pluginsDir(qApp->applicationDirPath()); //pluginsDir: "../build-xxx-debug/debug"
if(pluginsDir.dirName().toLower() == "debug" ||
pluginsDir.dirName().toLower() == "release") {
pluginsDir.cdUp(); //pluginsDir: "../build-xxx-debug"
pluginsDir.cdUp(); //pluginsDir: "../"
}
pluginsDir.cd("plugins");
QFileInfoList pluginsInfo = pluginsDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
//库中插件
QHash<QString,QString> pluginNames;
for(QFileInfo fileinfo : pluginsInfo){
if(fileinfo.fileName().contains(".dll")) {
QString pluginName = fileinfo.fileName().mid(0,fileinfo.fileName().size()-4);
QString pluginPath = fileinfo.filePath();
pluginNames.insert(pluginName,pluginPath);
}
}
return pluginNames;
}
void PluginManager::deal_metaData(QPluginLoader* loader,QJsonObject& json)
{
QString name;
if(json.keys().contains("name")) {
QJsonValue JValue = json.value("name").toString();
name = JValue.toString();
}
//------------------------------------------------------------
QString menu;
QStringList actionList;
if(json.keys().contains("menu")) {
QJsonValue JValue = json.value("menu").toString();
menu = JValue.toString();
}
if(json.keys().contains("action")) {
QJsonArray JArray = json.value("action").toArray();
for(int i=0;i<JArray.size();++i) {
actionList << JArray.at(i).toString();
MANAGER_ACTION_MAP manager_action_map;
manager_action_map.action = JArray.at(i).toString();
manager_action_map.plugin = loader;
_actionMap.push_back(manager_action_map);
}
}
QStringList dependencies_List;
if(json.keys().contains("dependencies")) {
QJsonArray JArray = json.value("dependencies").toArray();
for(int i=0;i<JArray.size();++i) {
dependencies_List << JArray.at(i).toString();
}
}
//qDebug()<<"dependencies_List: "<<dependencies_List;
//------------------------------------------------------------
if(!menu.isEmpty() && !actionList.empty()) {
emit sig_actions(menu,actionList);
}
//------------------------------------------------------------
//------------------------------------------------------------
QString widget;
if(json.keys().contains("widget")) {
QJsonValue JValue = json.value("widget").toString();
widget = JValue.toString();
if(!widget.isEmpty()) {
sig_widget(loader,widget); //发送:插件对象、主界面预留接入点
}
}
//------------------------------------------------------------
}
4.3、插件A
pluginA.json
{
"author" : "Wang_JC",
"date" : "2022/02/16",
"name" : "pluginA",
"version" : "1.0.0",
"des" : "这是一个插件A",
"dependencies" : ["pluginB:1.0.0","pluginC:1.0.0"],
"menu" : "menuFile",
"action" : ["action_A1","action_A2","action_A3"],
"widget" : "tabWidget"
}