mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-08-03 06:08:39 +00:00
游戏控制功能更新
This commit is contained in:
parent
52df6c289e
commit
70ebf39a2b
3 changed files with 278 additions and 88 deletions
|
@ -1,6 +1,9 @@
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QCursor>
|
||||||
|
|
||||||
#include "inputconvertgame.h"
|
#include "inputconvertgame.h"
|
||||||
|
|
||||||
InputConvertGame::InputConvertGame()
|
InputConvertGame::InputConvertGame(QObject* parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,10 +16,22 @@ InputConvertGame::~InputConvertGame()
|
||||||
void InputConvertGame::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
|
void InputConvertGame::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||||
{
|
{
|
||||||
updateSize(frameSize, showSize);
|
updateSize(frameSize, showSize);
|
||||||
|
|
||||||
|
// mouse move
|
||||||
|
if (processMouseMove(from)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mouse click
|
||||||
|
if (processMouseClick(from)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputConvertGame::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
|
void InputConvertGame::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(from);
|
||||||
updateSize(frameSize, showSize);
|
updateSize(frameSize, showSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,66 +42,16 @@ void InputConvertGame::keyEvent(const QKeyEvent *from, const QSize& frameSize, c
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// steer wheel
|
||||||
if (isSteerWheelKeys(from)) {
|
if (isSteerWheelKeys(from)) {
|
||||||
processSteerWheel(from);
|
processSteerWheel(from);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidMotioneventAction action;
|
// key click
|
||||||
// pos
|
if (processKeyClick(from)) {
|
||||||
QPointF pos;
|
|
||||||
// id
|
|
||||||
int id = 0;
|
|
||||||
|
|
||||||
if (from->key() == Qt::Key_A) {
|
|
||||||
id = 0;
|
|
||||||
pos.setX(showSize.width() * 0.25f);
|
|
||||||
pos.setY(showSize.height() * 0.25f);
|
|
||||||
} else if (from->key() == Qt::Key_S) {
|
|
||||||
id = 1;
|
|
||||||
pos.setX(showSize.width() * 0.35f);
|
|
||||||
pos.setY(showSize.height() * 0.35f);
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// action
|
|
||||||
switch (from->type()) {
|
|
||||||
case QEvent::KeyPress:
|
|
||||||
action = AMOTION_EVENT_ACTION_DOWN;
|
|
||||||
break;
|
|
||||||
case QEvent::KeyRelease:
|
|
||||||
action = AMOTION_EVENT_ACTION_UP;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert pos
|
|
||||||
pos.setX(pos.x() * frameSize.width() / showSize.width());
|
|
||||||
pos.setY(pos.y() * frameSize.height() / showSize.height());
|
|
||||||
|
|
||||||
// set data
|
|
||||||
ControlEvent* controlEvent = new ControlEvent(ControlEvent::CET_TOUCH);
|
|
||||||
if (!controlEvent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
controlEvent->setTouchEventData(id, action, QRect(pos.toPoint(), frameSize));
|
|
||||||
sendControlEvent(controlEvent);
|
|
||||||
|
|
||||||
if (QEvent::KeyPress == from->type() && from->key() == Qt::Key_S) {
|
|
||||||
// set data
|
|
||||||
ControlEvent* controlEvent2 = new ControlEvent(ControlEvent::CET_TOUCH);
|
|
||||||
if (!controlEvent2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pos.setX(pos.x() + 50);
|
|
||||||
pos.setY(pos.y() + 50);
|
|
||||||
controlEvent2->setTouchEventData(id, AMOTION_EVENT_ACTION_MOVE, QRect(pos.toPoint(), frameSize));
|
|
||||||
sendControlEvent(controlEvent2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputConvertGame::updateSize(const QSize &frameSize, const QSize &showSize)
|
void InputConvertGame::updateSize(const QSize &frameSize, const QSize &showSize)
|
||||||
|
@ -174,48 +139,247 @@ bool InputConvertGame::isSteerWheelKeys(const QKeyEvent *from)
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#include <QDebug>
|
|
||||||
void InputConvertGame::processSteerWheel(const QKeyEvent *from)
|
void InputConvertGame::processSteerWheel(const QKeyEvent *from)
|
||||||
{
|
{
|
||||||
QPointF movePos = m_steerWheelPos;
|
int keyPress1 = -1;
|
||||||
int moveKey = 0;
|
int keyPress2 = -1;
|
||||||
|
int keysNum = updateSteerWheelKeysPress(from, keyPress1, keyPress2);
|
||||||
|
bool needMove = false;
|
||||||
if (QEvent::KeyPress == from->type()) {
|
if (QEvent::KeyPress == from->type()) {
|
||||||
m_steerWheelKeysNum++;
|
if (1 == keysNum) {
|
||||||
if (1 == m_steerWheelKeysNum) {
|
m_steerWheelFirstTouchKey = from->key();
|
||||||
int id = attachTouchID(from->key());
|
int id = attachTouchID(m_steerWheelFirstTouchKey);
|
||||||
if (-1 == id) {
|
if (-1 == id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_steerWheelFirstTouchKey = from->key();
|
|
||||||
sendTouchDownEvent(id, m_steerWheelPos);
|
sendTouchDownEvent(id, m_steerWheelPos);
|
||||||
moveKey = from->key();
|
needMove = true;
|
||||||
} else if (2 == m_steerWheelKeysNum) {
|
} else if (2 == keysNum) {
|
||||||
moveKey = from->key();
|
needMove = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (QEvent::KeyRelease == from->type()){
|
} else if (QEvent::KeyRelease == from->type()){
|
||||||
m_steerWheelKeysNum--;
|
if (0 == keysNum) {
|
||||||
if (0 == m_steerWheelKeysNum) {
|
|
||||||
sendTouchUpEvent(getTouchID(m_steerWheelFirstTouchKey), m_steerWheelPos);
|
sendTouchUpEvent(getTouchID(m_steerWheelFirstTouchKey), m_steerWheelPos);
|
||||||
detachTouchID(from->key());
|
detachTouchID(m_steerWheelFirstTouchKey);
|
||||||
m_steerWheelFirstTouchKey = 0;
|
m_steerWheelFirstTouchKey = 0;
|
||||||
return;
|
} else if (1 == keysNum) {
|
||||||
} else if (1 == m_steerWheelKeysNum) {
|
needMove = true;
|
||||||
moveKey = m_steerWheelFirstTouchKey;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (needMove) {
|
||||||
if (moveKey == m_steerWheelKeys[SWD_UP]) {
|
steerWheelMove(keysNum, keyPress1, keyPress2);
|
||||||
movePos.setY(movePos.y() - m_steerWheelOffset);
|
}
|
||||||
} else if (moveKey == m_steerWheelKeys[SWD_RIGHT]) {
|
}
|
||||||
movePos.setX(movePos.x() + m_steerWheelOffset);
|
|
||||||
} else if (moveKey == m_steerWheelKeys[SWD_DOWN]) {
|
int InputConvertGame::updateSteerWheelKeysPress(const QKeyEvent *from, int& keyPress1, int& keyPress2)
|
||||||
movePos.setY(movePos.y() + m_steerWheelOffset);
|
{
|
||||||
} else if (moveKey == m_steerWheelKeys[SWD_LEFT]) {
|
bool keyPress = false;
|
||||||
movePos.setX(movePos.x() - m_steerWheelOffset);
|
if (QEvent::KeyPress == from->type()) {
|
||||||
}
|
keyPress = true;
|
||||||
if (0 != moveKey) {
|
} else if (QEvent::KeyRelease == from->type()) {
|
||||||
qDebug() << "move pos" << movePos;
|
keyPress = false;
|
||||||
sendTouchMoveEvent(getTouchID(m_steerWheelFirstTouchKey), movePos);
|
}
|
||||||
|
if (from->key() == m_steerWheelKeys[SWD_UP]) {
|
||||||
|
m_steerWheelKeysPress[SWD_UP] = keyPress;
|
||||||
|
} else if (from->key() == m_steerWheelKeys[SWD_RIGHT]) {
|
||||||
|
m_steerWheelKeysPress[SWD_RIGHT] = keyPress;
|
||||||
|
} else if (from->key() == m_steerWheelKeys[SWD_DOWN]) {
|
||||||
|
m_steerWheelKeysPress[SWD_DOWN] = keyPress;
|
||||||
|
} else if (from->key() == m_steerWheelKeys[SWD_LEFT]) {
|
||||||
|
m_steerWheelKeysPress[SWD_LEFT] = keyPress;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
keyPress1 = -1;
|
||||||
|
keyPress2 = -1;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (true == m_steerWheelKeysPress[i]) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (-1 == keyPress1) {
|
||||||
|
keyPress1 = i;
|
||||||
|
} else if (-1 == keyPress2) {
|
||||||
|
keyPress2 = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputConvertGame::steerWheelMove(int keysNum, int keyPress1, int keyPress2)
|
||||||
|
{
|
||||||
|
if (1 != keysNum && 2 != keysNum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QPointF movePos = m_steerWheelPos;
|
||||||
|
switch (keysNum) {
|
||||||
|
case 2:
|
||||||
|
if (keyPress2 == SWD_UP) {
|
||||||
|
movePos.setY(movePos.y() - m_steerWheelOffset.y());
|
||||||
|
} else if (keyPress2 == SWD_RIGHT) {
|
||||||
|
movePos.setX(movePos.x() + m_steerWheelOffset.x());
|
||||||
|
} else if (keyPress2 == SWD_DOWN) {
|
||||||
|
movePos.setY(movePos.y() + m_steerWheelOffset.y());
|
||||||
|
} else if (keyPress2 == SWD_LEFT) {
|
||||||
|
movePos.setX(movePos.x() - m_steerWheelOffset.x());
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
if (keyPress1 == SWD_UP) {
|
||||||
|
movePos.setY(movePos.y() - m_steerWheelOffset.y());
|
||||||
|
} else if (keyPress1 == SWD_RIGHT) {
|
||||||
|
movePos.setX(movePos.x() + m_steerWheelOffset.x());
|
||||||
|
} else if (keyPress1 == SWD_DOWN) {
|
||||||
|
movePos.setY(movePos.y() + m_steerWheelOffset.y());
|
||||||
|
} else if (keyPress1 == SWD_LEFT) {
|
||||||
|
movePos.setX(movePos.x() - m_steerWheelOffset.x());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sendTouchMoveEvent(getTouchID(m_steerWheelFirstTouchKey), movePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputConvertGame::processKeyClick(const QKeyEvent *from)
|
||||||
|
{
|
||||||
|
QPointF clickPos;
|
||||||
|
switch (from->key()) {
|
||||||
|
case Qt::Key_Space: // 跳
|
||||||
|
clickPos = QPointF(0.96f, 0.7f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_M: // 地图
|
||||||
|
clickPos = QPointF(0.98f, 0.03f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_Tab: // 背包
|
||||||
|
clickPos = QPointF(0.06f, 0.9f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_Z: // 趴
|
||||||
|
clickPos = QPointF(0.95f, 0.9f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_C: // 蹲
|
||||||
|
clickPos = QPointF(0.86f, 0.92f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_R: // 换弹
|
||||||
|
clickPos = QPointF(0.795f, 0.93f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_Alt: // 小眼睛
|
||||||
|
clickPos = QPointF(0.8f, 0.31f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_F: // 捡东西1
|
||||||
|
clickPos = QPointF(0.7f, 0.34f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_G: // 捡东西2
|
||||||
|
clickPos = QPointF(0.7f, 0.44f);
|
||||||
|
break;
|
||||||
|
case Qt::Key_H: // 捡东西3
|
||||||
|
clickPos = QPointF(0.7f, 0.54f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QEvent::KeyPress == from->type()) {
|
||||||
|
int id = attachTouchID(from->key());
|
||||||
|
sendTouchDownEvent(id, clickPos);
|
||||||
|
} else if (QEvent::KeyRelease == from->type()) {
|
||||||
|
sendTouchUpEvent(getTouchID(from->key()), clickPos);
|
||||||
|
detachTouchID(from->key());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputConvertGame::processMouseClick(const QMouseEvent *from)
|
||||||
|
{
|
||||||
|
QPointF clickPos;
|
||||||
|
if (Qt::LeftButton == from->button()) {
|
||||||
|
clickPos = QPointF(0.86f, 0.72f);
|
||||||
|
} else if (Qt::RightButton == from->button()){
|
||||||
|
clickPos = QPointF(0.96f, 0.52f);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QEvent::MouseButtonPress == from->type() || QEvent::MouseButtonDblClick == from->type()) {
|
||||||
|
int id = attachTouchID(from->button());
|
||||||
|
sendTouchDownEvent(id, clickPos);
|
||||||
|
} else if (QEvent::MouseButtonRelease == from->type()) {
|
||||||
|
sendTouchUpEvent(getTouchID(from->button()), clickPos);
|
||||||
|
detachTouchID(from->button());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputConvertGame::processMouseMove(const QMouseEvent *from)
|
||||||
|
{
|
||||||
|
if (QEvent::MouseMove != from->type()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseMoveStartTouch();
|
||||||
|
startMouseMoveTimer();
|
||||||
|
|
||||||
|
// move
|
||||||
|
// pos
|
||||||
|
QPointF pos = from->localPos();
|
||||||
|
// convert pos
|
||||||
|
pos.setX(pos.x() / m_showSize.width());
|
||||||
|
pos.setY(pos.y() / m_showSize.height());
|
||||||
|
sendTouchMoveEvent(getTouchID(Qt::ExtraButton24), pos);
|
||||||
|
|
||||||
|
if (pos.x() < 0.1 || pos.x() > 0.9 || pos.y() < 0.1 || pos.y() > 0.9) {
|
||||||
|
mouseMoveStopTouch();
|
||||||
|
|
||||||
|
QPoint localPos = QPoint(m_showSize.width()*m_mouseMoveStartPos.x(), m_showSize.height()*m_mouseMoveStartPos.y());
|
||||||
|
QPoint posOffset = from->localPos().toPoint() - localPos;
|
||||||
|
QPoint globalPos = from->globalPos();
|
||||||
|
|
||||||
|
globalPos += posOffset;
|
||||||
|
QCursor::setPos(globalPos);
|
||||||
|
|
||||||
|
mouseMoveStartTouch();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputConvertGame::startMouseMoveTimer()
|
||||||
|
{
|
||||||
|
stopMouseMoveTimer();
|
||||||
|
m_mouseMoveTimer = startTimer(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputConvertGame::stopMouseMoveTimer()
|
||||||
|
{
|
||||||
|
if (0 != m_mouseMoveTimer) {
|
||||||
|
killTimer(m_mouseMoveTimer);
|
||||||
|
m_mouseMoveTimer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputConvertGame::mouseMoveStartTouch()
|
||||||
|
{
|
||||||
|
if (!m_mouseMovePress) {
|
||||||
|
int id = attachTouchID(Qt::ExtraButton24);
|
||||||
|
sendTouchDownEvent(id, m_mouseMoveStartPos);
|
||||||
|
m_mouseMovePress = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputConvertGame::mouseMoveStopTouch()
|
||||||
|
{
|
||||||
|
if (m_mouseMovePress) {
|
||||||
|
sendTouchUpEvent(getTouchID(Qt::ExtraButton24), m_mouseMoveStartPos);
|
||||||
|
detachTouchID(Qt::ExtraButton24);
|
||||||
|
m_mouseMovePress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputConvertGame::timerEvent(QTimerEvent *event)
|
||||||
|
{
|
||||||
|
if (m_mouseMoveTimer == event->timerId()) {
|
||||||
|
stopMouseMoveTimer();
|
||||||
|
mouseMoveStopTouch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
#include "inputconvertbase.h"
|
#include "inputconvertbase.h"
|
||||||
|
|
||||||
#define MULTI_TOUCH_MAX_NUM 10
|
#define MULTI_TOUCH_MAX_NUM 10
|
||||||
class InputConvertGame : public InputConvertBase
|
class InputConvertGame : public QObject, public InputConvertBase
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
InputConvertGame();
|
InputConvertGame(QObject* parent = Q_NULLPTR);
|
||||||
virtual ~InputConvertGame();
|
virtual ~InputConvertGame();
|
||||||
|
|
||||||
void mouseEvent(const QMouseEvent* from, const QSize& frameSize, const QSize& showSize);
|
void mouseEvent(const QMouseEvent* from, const QSize& frameSize, const QSize& showSize);
|
||||||
|
@ -31,6 +32,22 @@ protected:
|
||||||
// steer wheel
|
// steer wheel
|
||||||
bool isSteerWheelKeys(const QKeyEvent* from);
|
bool isSteerWheelKeys(const QKeyEvent* from);
|
||||||
void processSteerWheel(const QKeyEvent* from);
|
void processSteerWheel(const QKeyEvent* from);
|
||||||
|
int updateSteerWheelKeysPress(const QKeyEvent* from, int& keyPress1, int& keyPress2);
|
||||||
|
void steerWheelMove(int keysNum, int keyPress1, int keyPress2);
|
||||||
|
|
||||||
|
// click
|
||||||
|
bool processKeyClick(const QKeyEvent* from);
|
||||||
|
|
||||||
|
// mouse
|
||||||
|
bool processMouseClick(const QMouseEvent* from);
|
||||||
|
bool processMouseMove(const QMouseEvent* from);
|
||||||
|
void startMouseMoveTimer();
|
||||||
|
void stopMouseMoveTimer();
|
||||||
|
void mouseMoveStartTouch();
|
||||||
|
void mouseMoveStopTouch();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void timerEvent(QTimerEvent *event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum SteerWheelDirection {
|
enum SteerWheelDirection {
|
||||||
|
@ -47,11 +64,17 @@ private:
|
||||||
int multiTouchID[MULTI_TOUCH_MAX_NUM] = { 0 };
|
int multiTouchID[MULTI_TOUCH_MAX_NUM] = { 0 };
|
||||||
|
|
||||||
QPointF m_steerWheelPos = {0.16f, 0.75f};
|
QPointF m_steerWheelPos = {0.16f, 0.75f};
|
||||||
float m_steerWheelOffset = 0.1f;
|
QPointF m_steerWheelOffset = {0.1f, 0.2f};
|
||||||
// order by SteerWheelDirection(up right down left)
|
// order by SteerWheelDirection(up right down left)
|
||||||
int m_steerWheelKeys[4] = {Qt::Key_W, Qt::Key_D, Qt::Key_S, Qt::Key_A};
|
int m_steerWheelKeys[4] = {Qt::Key_W, Qt::Key_D, Qt::Key_S, Qt::Key_A};
|
||||||
|
bool m_steerWheelKeysPress[4] = { false };
|
||||||
int m_steerWheelKeysNum = 0;
|
int m_steerWheelKeysNum = 0;
|
||||||
int m_steerWheelFirstTouchKey = 0;
|
int m_steerWheelFirstTouchKey = 0;
|
||||||
|
|
||||||
|
// mouse move
|
||||||
|
QPointF m_mouseMoveStartPos = {0.57f, 0.26f};
|
||||||
|
bool m_mouseMovePress = false;
|
||||||
|
int m_mouseMoveTimer = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INPUTCONVERTGAME_H
|
#endif // INPUTCONVERTGAME_H
|
||||||
|
|
|
@ -11,6 +11,9 @@ VideoForm::VideoForm(QWidget *parent) :
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
setAttribute(Qt::WA_DeleteOnClose);
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
setMouseTracking(true);
|
||||||
|
ui->videoWidget->setMouseTracking(true);
|
||||||
|
|
||||||
m_server = new Server();
|
m_server = new Server();
|
||||||
m_frames.init();
|
m_frames.init();
|
||||||
m_decoder.setFrames(&m_frames);
|
m_decoder.setFrames(&m_frames);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue