mirror of
https://gitlab.com/Syping/luaengineapp.git
synced 2025-09-15 21:21:41 +02:00
Initial commit
This commit is contained in:
commit
107204e6c5
80 changed files with 24976 additions and 0 deletions
395
src/luaenginegui/luaengine/LuaEngineGui.cpp
Normal file
395
src/luaenginegui/luaengine/LuaEngineGui.cpp
Normal file
|
@ -0,0 +1,395 @@
|
|||
/*****************************************************************************
|
||||
* luaEngine Lua Engine for Qt
|
||||
* Copyright (C) 2018 Syping
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*****************************************************************************/
|
||||
|
||||
#include "LuaEngineGui.h"
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <QMetaObject>
|
||||
#include <QMetaMethod>
|
||||
#include <QTextStream>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QMenuBar>
|
||||
#include <QProcess>
|
||||
#include <QObject>
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
#include <QLabel>
|
||||
|
||||
LuaEngineGui::LuaEngineGui(QObject *parent, bool loadBaseLibraries) : LuaEngine(parent, loadBaseLibraries)
|
||||
{
|
||||
L = luaState();
|
||||
pushClass(L);
|
||||
pushEngine();
|
||||
}
|
||||
|
||||
void LuaEngineGui::pushEngine()
|
||||
{
|
||||
pushPointer("__LuaEngineGui", (void*)this);
|
||||
}
|
||||
|
||||
void LuaEngineGui::pushClass(lua_State *L_p)
|
||||
{
|
||||
// MessageBox
|
||||
pushFunction(L_p, "showMessageBox", showMessageBox);
|
||||
pushVariant(L_p, "InfoMessageBox", 0);
|
||||
pushVariant(L_p, "WarningMessageBox", 1);
|
||||
pushVariant(L_p, "CriticalMessageBox", 2);
|
||||
pushVariant(L_p, "QuestionMessageBox", 3);
|
||||
|
||||
// Gui
|
||||
pushVariant(L_p, "GuiExecuted", "GuiExecuted");
|
||||
|
||||
// Widget
|
||||
pushFunction(L_p, "closeWidget", closeWidget);
|
||||
pushFunction(L_p, "showWidget", showWidget);
|
||||
pushFunction(L_p, "setWidgetLayout", setWidgetLayout);
|
||||
pushVariant(L_p, "ShowCurrent", 0);
|
||||
pushVariant(L_p, "ShowNormal", 1);
|
||||
pushVariant(L_p, "ShowMinimised", 2);
|
||||
pushVariant(L_p, "ShowMaximised", 3);
|
||||
pushVariant(L_p, "ShowFullscreen", 4);
|
||||
|
||||
// Label
|
||||
pushFunction(L_p, "createLabel", createLabel);
|
||||
|
||||
// Label
|
||||
pushFunction(L_p, "createLayout", createLayout);
|
||||
pushFunction(L_p, "layoutAddWidget", layoutAddWidget);
|
||||
pushVariant(L_p, "HorizontalLayout", 0);
|
||||
pushVariant(L_p, "VerticalLayout", 1);
|
||||
|
||||
// Main Window
|
||||
pushFunction(L_p, "createMainWindow", createMainWindow);
|
||||
pushFunction(L_p, "createCentralWidget", createCentralWidget);
|
||||
|
||||
// Menu
|
||||
pushFunction(L_p, "createMenu", createMenu);
|
||||
pushFunction(L_p, "createMenuBar", createMenuBar);
|
||||
pushFunction(L_p, "createMenuEntry", createMenuEntry);
|
||||
|
||||
// Connect
|
||||
pushFunction(L_p, "connect", connect_p);
|
||||
}
|
||||
|
||||
void LuaEngineGui::pushClass(LuaEngine *luaEngine)
|
||||
{
|
||||
pushClass(luaEngine->luaState());
|
||||
}
|
||||
|
||||
int LuaEngineGui::showMessageBox(lua_State *L_p)
|
||||
{
|
||||
QVariantList args = getArguments(L_p);
|
||||
if (args.length() >= 2) {
|
||||
QWidget *parent = nullptr;
|
||||
QString boxMessage = args.at(1).toString();
|
||||
QString boxTitle = "LuaEngine";
|
||||
if (args.length() >= 3) {
|
||||
boxTitle = args.at(2).toString();
|
||||
if (args.length() >= 4) {
|
||||
if ((QMetaType::Type)args.at(3).type() == QMetaType::Void || (QMetaType::Type)args.at(3).type() == QMetaType::VoidStar) {
|
||||
if (((QObject*)qvariant_cast<void*>(args.at(3)))->inherits("QWidget")) {
|
||||
parent = (QWidget*)qvariant_cast<void*>(args.at(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int returnInt = 0;
|
||||
switch (args.at(0).toInt()) {
|
||||
case 1:
|
||||
QMessageBox::warning(parent, boxTitle, boxMessage);
|
||||
break;
|
||||
case 2:
|
||||
QMessageBox::critical(parent, boxTitle, boxMessage);
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
bool returnCode = false;
|
||||
if (QMessageBox::Yes == QMessageBox::question(parent, boxTitle, boxMessage)) {
|
||||
returnCode = true;
|
||||
}
|
||||
pushVariant(L_p, returnCode);
|
||||
returnInt = 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
QMessageBox::information(parent, boxTitle, boxMessage);
|
||||
}
|
||||
return returnInt;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int LuaEngineGui::closeWidget(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 1) {
|
||||
void *pointer = getPointer(L_p, 1);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QWidget")) {
|
||||
QWidget *widget = (QWidget*)pointer;
|
||||
widget->close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::showWidget(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 1) {
|
||||
void *pointer = getPointer(L_p, 1);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QWidget")) {
|
||||
QWidget *widget = (QWidget*)pointer;
|
||||
int showMode = 0;
|
||||
if (getArgumentCount(L_p) >= 2) {
|
||||
showMode = getVariant(L_p, 2).toInt();
|
||||
}
|
||||
switch (showMode) {
|
||||
case 1:
|
||||
widget->showNormal();
|
||||
break;
|
||||
case 2:
|
||||
widget->showMinimized();
|
||||
break;
|
||||
case 3:
|
||||
widget->showMaximized();
|
||||
break;
|
||||
case 4:
|
||||
widget->showFullScreen();
|
||||
break;
|
||||
default:
|
||||
widget->show();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::layoutAddWidget(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 2) {
|
||||
void *pointer = getPointer(L_p, 1);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QLayout")) {
|
||||
QLayout *layout = (QLayout*)pointer;
|
||||
void *w_pointer = getPointer(L_p, 2);
|
||||
if (w_pointer!= NULL && ((QObject*)w_pointer)->inherits("QWidget")) {
|
||||
layout->addWidget((QWidget*)w_pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::createLabel(lua_State *L_p)
|
||||
{
|
||||
QVariantList args = getArguments(L_p);
|
||||
QWidget *parent = nullptr;
|
||||
QString labelText = "LuaEngine";
|
||||
if (args.length() >= 1) {
|
||||
labelText = args.at(0).toString();
|
||||
if (args.length() >= 2) {
|
||||
if ((QMetaType::Type)args.at(1).type() == QMetaType::Void || (QMetaType::Type)args.at(1).type() == QMetaType::VoidStar) {
|
||||
if (((QObject*)qvariant_cast<void*>(args.at(1)))->inherits("QWidget")) {
|
||||
parent = (QWidget*)qvariant_cast<void*>(args.at(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QLabel *label = new QLabel(parent);
|
||||
label->setObjectName(nameForPointer(label));
|
||||
label->setText(labelText);
|
||||
pushPointer(L_p, label);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaEngineGui::createLayout(lua_State *L_p)
|
||||
{
|
||||
QVariantList args = getArguments(L_p);
|
||||
QWidget *parent = nullptr;
|
||||
if (args.length() >= 1) {
|
||||
if (args.length() >= 2) {
|
||||
if ((QMetaType::Type)args.at(1).type() == QMetaType::Void || (QMetaType::Type)args.at(1).type() == QMetaType::VoidStar) {
|
||||
if (((QObject*)qvariant_cast<void*>(args.at(1)))->inherits("QWidget")) {
|
||||
parent = (QWidget*)qvariant_cast<void*>(args.at(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int layoutType = args.at(0).toInt();
|
||||
switch (layoutType) {
|
||||
case 0:
|
||||
{
|
||||
QHBoxLayout *layout = new QHBoxLayout(parent);
|
||||
layout->setObjectName(nameForPointer(layout));
|
||||
pushPointer(L_p, layout);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(parent);
|
||||
layout->setObjectName(nameForPointer(layout));
|
||||
pushPointer(L_p, layout);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaEngineGui::createMainWindow(lua_State *L_p)
|
||||
{
|
||||
QVariantList args = getArguments(L_p);
|
||||
QWidget *parent = nullptr;
|
||||
QString windowTitle = "LuaEngine";
|
||||
if (args.length() >= 1) {
|
||||
windowTitle = args.at(0).toString();
|
||||
if (args.length() >= 2) {
|
||||
if ((QMetaType::Type)args.at(1).type() == QMetaType::Void || (QMetaType::Type)args.at(1).type() == QMetaType::VoidStar) {
|
||||
if (((QObject*)qvariant_cast<void*>(args.at(1)))->inherits("QWidget")) {
|
||||
parent = (QWidget*)qvariant_cast<void*>(args.at(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QMainWindow *mainWindow = new QMainWindow(parent);
|
||||
mainWindow->setObjectName(nameForPointer(mainWindow));
|
||||
mainWindow->setWindowTitle(windowTitle);
|
||||
pushPointer(L_p, mainWindow);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaEngineGui::createCentralWidget(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 1) {
|
||||
void *pointer = getPointer(L_p, 1);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QMainWindow")) {
|
||||
QWidget *centralWidget = new QWidget((QWidget*)pointer);
|
||||
centralWidget->setObjectName(nameForPointer(centralWidget));
|
||||
((QMainWindow*)pointer)->setCentralWidget(centralWidget);
|
||||
pushPointer(L_p, centralWidget);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::createMenu(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 2) {
|
||||
void *pointer = getPointer(L_p, 2);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QMenuBar")) {
|
||||
QMenu *menu = new QMenu((QWidget*)pointer);
|
||||
menu->setObjectName(nameForPointer(menu));
|
||||
menu->setTitle(getVariant(L_p, 1).toString());
|
||||
((QMenuBar*)pointer)->addAction(menu->menuAction());
|
||||
pushPointer(L_p, menu);
|
||||
return 1;
|
||||
}
|
||||
else if (pointer != NULL && ((QObject*)pointer)->inherits("QMenu")) {
|
||||
QMenu *menu = new QMenu((QWidget*)pointer);
|
||||
menu->setObjectName(nameForPointer(menu));
|
||||
menu->setTitle(getVariant(L_p, 1).toString());
|
||||
((QMenu*)pointer)->addAction(menu->menuAction());
|
||||
pushPointer(L_p, menu);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::createMenuBar(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 1) {
|
||||
void *pointer = getPointer(L_p, 1);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QMainWindow")) {
|
||||
QMenuBar *menuBar = new QMenuBar((QWidget*)pointer);
|
||||
menuBar->setObjectName(nameForPointer(menuBar));
|
||||
((QMainWindow*)pointer)->setMenuBar(menuBar);
|
||||
pushPointer(L_p, menuBar);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::createMenuEntry(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 2) {
|
||||
void *pointer = getPointer(L_p, 2);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QMenu")) {
|
||||
QAction *action = new QAction(getVariant(L_p, 1).toString(), (QObject*)pointer);
|
||||
action->setObjectName(nameForPointer(action));
|
||||
((QMenu*)pointer)->addAction(action);
|
||||
pushPointer(L_p, action);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::setWidgetLayout(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 2) {
|
||||
void *pointer = getPointer(L_p, 1);
|
||||
if (pointer != NULL && ((QObject*)pointer)->inherits("QWidget")) {
|
||||
QWidget *widget = (QWidget*)pointer;
|
||||
void *l_pointer = getPointer(L_p, 2);
|
||||
if (l_pointer!= NULL && ((QObject*)l_pointer)->inherits("QLayout")) {
|
||||
widget->setLayout((QLayout*)l_pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LuaEngineGui::connect_p(lua_State *L_p)
|
||||
{
|
||||
if (getArgumentCount(L_p) >= 3) {
|
||||
QObject *object = (QObject*)getPointer(L_p, 1);
|
||||
QString signalString = getVariant(L_p, 2).toString();
|
||||
int signalIndex = object->metaObject()->indexOfSignal(signalString.toUtf8().data());
|
||||
if (signalIndex != -1) {
|
||||
LuaEngineGui *engine = (LuaEngineGui*)getPointer(L_p, "__LuaEngineGui");
|
||||
int slotIndex = engine->metaObject()->indexOfSlot("luaTriggerSlot()");
|
||||
if (slotIndex != -1) {
|
||||
QMetaMethod signal = object->metaObject()->method(signalIndex);
|
||||
QMetaMethod slot = engine->metaObject()->method(slotIndex);
|
||||
QString funcStorage;
|
||||
QTextStream(&funcStorage) << "__ConnectFunc_" << object << "_" << signal.name();
|
||||
pushVariant(L_p, funcStorage.toUtf8().data(), getVariant(L_p, 3).toString());
|
||||
QObject::connect(object, signal, engine, slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LuaEngineGui::luaTriggerSlot()
|
||||
{
|
||||
QMetaMethod signal = sender()->metaObject()->method(senderSignalIndex());
|
||||
QString funcStorage;
|
||||
QTextStream(&funcStorage) << "__ConnectFunc_" << sender() << "_" << signal.name();
|
||||
QString luaConnectFunc = getVariant(funcStorage.toUtf8().data()).toString();
|
||||
executeLuaFunction(luaConnectFunc.toUtf8().data(), QVariant::fromValue((void*)sender()));
|
||||
}
|
||||
|
||||
QString LuaEngineGui::nameForPointer(void *pointer)
|
||||
{
|
||||
QString nameStorage;
|
||||
QTextStream(&nameStorage) << "LuaEngineGui_" << pointer;
|
||||
return nameStorage;
|
||||
}
|
56
src/luaenginegui/luaengine/LuaEngineGui.h
Normal file
56
src/luaenginegui/luaengine/LuaEngineGui.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*****************************************************************************
|
||||
* luaEngine Lua Engine for Qt
|
||||
* Copyright (C) 2018 Syping
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef LUAENGINEGUI_H
|
||||
#define LUAENGINEGUI_H
|
||||
|
||||
#include "LuaEngineGui_global.h"
|
||||
#include "LuaEngine.h"
|
||||
#include <QMetaMethod>
|
||||
#include <QObject>
|
||||
|
||||
class LUAENGINEGUISHARED_EXPORT LuaEngineGui : public LuaEngine
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LuaEngineGui(QObject *parent = nullptr, bool loadBaseLibraries = true);
|
||||
void pushEngine();
|
||||
static void pushClass(lua_State *L_p);
|
||||
static void pushClass(LuaEngine *luaEngine);
|
||||
static int showMessageBox(lua_State *L_p);
|
||||
static int closeWidget(lua_State *L_p);
|
||||
static int showWidget(lua_State *L_p);
|
||||
static int layoutAddWidget(lua_State *L_p);
|
||||
static int createLabel(lua_State *L_p);
|
||||
static int createLayout(lua_State *L_p);
|
||||
static int createMainWindow(lua_State *L_p);
|
||||
static int createCentralWidget(lua_State *L_p);
|
||||
static int createMenu(lua_State *L_p);
|
||||
static int createMenuBar(lua_State *L_p);
|
||||
static int createMenuEntry(lua_State *L_p);
|
||||
static int setWidgetLayout(lua_State *L_p);
|
||||
static int connect_p(lua_State *L_p);
|
||||
|
||||
public slots:
|
||||
void luaTriggerSlot();
|
||||
|
||||
private:
|
||||
lua_State *L;
|
||||
static QString nameForPointer(void *pointer);
|
||||
};
|
||||
|
||||
#endif // LUAENGINEGUI_H
|
29
src/luaenginegui/luaengine/LuaEngineGui_global.h
Normal file
29
src/luaenginegui/luaengine/LuaEngineGui_global.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*****************************************************************************
|
||||
* luaEngine Lua Engine for Qt
|
||||
* Copyright (C) 2018 Syping
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef LUAENGINEGUI_GLOBAL_H
|
||||
#define LUAENGINEGUI_GLOBAL_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#ifdef LUAENGINEGUI_LIBRARY
|
||||
#define LUAENGINEGUISHARED_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define LUAENGINEGUISHARED_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
|
||||
#endif // LUAENGINEGUI_GLOBAL_H
|
39
src/luaenginegui/luaenginegui.pro
Normal file
39
src/luaenginegui/luaenginegui.pro
Normal file
|
@ -0,0 +1,39 @@
|
|||
#/*****************************************************************************
|
||||
#* luaEngine Lua Engine for Qt
|
||||
#* Copyright (C) 2018 Syping
|
||||
#*
|
||||
#* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#* you may not use this file except in compliance with the License.
|
||||
#* You may obtain a copy of the License at
|
||||
#*
|
||||
#* http://www.apache.org/licenses/LICENSE-2.0
|
||||
#*
|
||||
#* Unless required by applicable law or agreed to in writing, software
|
||||
#* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#* See the License for the specific language governing permissions and
|
||||
#* limitations under the License.
|
||||
#*****************************************************************************/
|
||||
|
||||
QT += core network gui widgets
|
||||
CONFIG += c++11
|
||||
|
||||
TARGET = luaenginegui
|
||||
TEMPLATE = lib
|
||||
|
||||
DEFINES += LUAENGINEGUI_LIBRARY
|
||||
|
||||
CONFIG(debug, debug|release): win32: LIBS += -L$$OUT_PWD/../luaengine/debug -lluaengine
|
||||
CONFIG(release, debug|release): win32: LIBS += -L$$OUT_PWD/../luaengine/release -lluaengine
|
||||
unix: LIBS += -L$$OUT_PWD/../luaengine -L$$OUT_PWD/../luaenginegui -lluaengine
|
||||
|
||||
INCLUDEPATH += \
|
||||
../luaengine/lua \
|
||||
../luaengine/luaengine
|
||||
|
||||
SOURCES += \
|
||||
luaengine/LuaEngineGui.cpp
|
||||
|
||||
HEADERS += \
|
||||
luaengine/LuaEngineGui.h \
|
||||
luaengine/LuaEngineGui_global.h
|
Loading…
Add table
Add a link
Reference in a new issue