Remove pluginapi.c's dependency on nsis_tchar.h
git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@6531 212acab6-be3b-0410-9dea-997c60f758d6
This commit is contained in:
parent
9950ce1432
commit
913d5a62b2
3 changed files with 77 additions and 61 deletions
|
@ -1,5 +1,8 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <nsis/pluginapi.h> // nsis plugin
|
#include <nsis/pluginapi.h> // nsis plugin
|
||||||
|
#ifndef _TCHAR_DEFINED
|
||||||
|
#include <tchar.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
HINSTANCE g_hInstance;
|
HINSTANCE g_hInstance;
|
||||||
|
|
||||||
|
@ -26,8 +29,8 @@ void __declspec(dllexport) myFunction(HWND hwndParent, int string_size,
|
||||||
|
|
||||||
// do your stuff here
|
// do your stuff here
|
||||||
{
|
{
|
||||||
TCHAR buf[1024];
|
TCHAR buf[3+1024+1]; // A real plugin should use string_size and not 1024!
|
||||||
wsprintf(buf,_T("$0=%s\n"),getuservariable(INST_0));
|
wsprintf(buf,TEXT("$0=%.1024s"),getuservariable(INST_0));
|
||||||
MessageBox(g_hwndParent,buf,0,MB_OK);
|
MessageBox(g_hwndParent,buf,0,MB_OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,31 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include "pluginapi.h"
|
#include "pluginapi.h"
|
||||||
|
|
||||||
#ifdef _countof
|
#ifndef COUNTOF
|
||||||
#define COUNTOF _countof
|
|
||||||
#else
|
|
||||||
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
|
#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// minimal tchar.h emulation
|
||||||
|
#ifndef _T
|
||||||
|
# define _T TEXT
|
||||||
|
#endif
|
||||||
|
#if !defined(TCHAR) && !defined(_TCHAR_DEFINED)
|
||||||
|
# ifdef UNICODE
|
||||||
|
# define TCHAR WCHAR
|
||||||
|
# else
|
||||||
|
# define TCHAR char
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#define isvalidnsisvarindex(varnum) ( ((unsigned int)(varnum)) < (__INST_LAST) )
|
#define isvalidnsisvarindex(varnum) ( ((unsigned int)(varnum)) < (__INST_LAST) )
|
||||||
|
|
||||||
unsigned int g_stringsize;
|
unsigned int g_stringsize;
|
||||||
stack_t **g_stacktop;
|
stack_t **g_stacktop;
|
||||||
TCHAR *g_variables;
|
LPTSTR g_variables;
|
||||||
|
|
||||||
// utility functions (not required but often useful)
|
// utility functions (not required but often useful)
|
||||||
|
|
||||||
int NSISCALL popstring(TCHAR *str)
|
int NSISCALL popstring(LPTSTR str)
|
||||||
{
|
{
|
||||||
stack_t *th;
|
stack_t *th;
|
||||||
if (!g_stacktop || !*g_stacktop) return 1;
|
if (!g_stacktop || !*g_stacktop) return 1;
|
||||||
|
@ -27,7 +36,7 @@ int NSISCALL popstring(TCHAR *str)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NSISCALL popstringn(TCHAR *str, int maxlen)
|
int NSISCALL popstringn(LPTSTR str, int maxlen)
|
||||||
{
|
{
|
||||||
stack_t *th;
|
stack_t *th;
|
||||||
if (!g_stacktop || !*g_stacktop) return 1;
|
if (!g_stacktop || !*g_stacktop) return 1;
|
||||||
|
@ -38,122 +47,122 @@ int NSISCALL popstringn(TCHAR *str, int maxlen)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL pushstring(const TCHAR *str)
|
void NSISCALL pushstring(LPCTSTR str)
|
||||||
{
|
{
|
||||||
stack_t *th;
|
stack_t *th;
|
||||||
if (!g_stacktop) return;
|
if (!g_stacktop) return;
|
||||||
th=(stack_t*)GlobalAlloc(GPTR,(sizeof(stack_t)+(g_stringsize)*sizeof(TCHAR)));
|
th=(stack_t*)GlobalAlloc(GPTR,(sizeof(stack_t)+(g_stringsize)*sizeof(*str)));
|
||||||
lstrcpyn(th->text,str,g_stringsize);
|
lstrcpyn(th->text,str,g_stringsize);
|
||||||
th->next=*g_stacktop;
|
th->next=*g_stacktop;
|
||||||
*g_stacktop=th;
|
*g_stacktop=th;
|
||||||
}
|
}
|
||||||
|
|
||||||
TCHAR* NSISCALL getuservariable(const int varnum)
|
LPTSTR NSISCALL getuservariable(const int varnum)
|
||||||
{
|
{
|
||||||
if (!isvalidnsisvarindex(varnum)) return NULL;
|
if (!isvalidnsisvarindex(varnum)) return NULL;
|
||||||
return g_variables+varnum*g_stringsize;
|
return g_variables+varnum*g_stringsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL setuservariable(const int varnum, const TCHAR *var)
|
void NSISCALL setuservariable(const int varnum, LPCTSTR var)
|
||||||
{
|
{
|
||||||
if (var && isvalidnsisvarindex(varnum))
|
if (var && isvalidnsisvarindex(varnum))
|
||||||
lstrcpy(g_variables + varnum*g_stringsize, var);
|
lstrcpy(g_variables + varnum*g_stringsize, var);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _UNICODE
|
#ifdef _UNICODE
|
||||||
int NSISCALL PopStringA(char* ansiStr)
|
int NSISCALL PopStringA(LPSTR ansiStr)
|
||||||
{
|
{
|
||||||
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t));
|
LPWSTR wideStr = (LPWSTR) GlobalAlloc(GPTR, g_stringsize*sizeof(WCHAR));
|
||||||
int rval = popstring(wideStr);
|
int rval = popstring(wideStr);
|
||||||
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
GlobalFree((HGLOBAL)wideStr);
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NSISCALL PopStringNA(char* ansiStr, int maxlen)
|
int NSISCALL PopStringNA(LPSTR ansiStr, int maxlen)
|
||||||
{
|
{
|
||||||
int realLen = maxlen ? maxlen : g_stringsize;
|
int realLen = maxlen ? maxlen : g_stringsize;
|
||||||
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, realLen*sizeof(wchar_t));
|
LPWSTR wideStr = (LPWSTR) GlobalAlloc(GPTR, realLen*sizeof(WCHAR));
|
||||||
int rval = popstringn(wideStr, realLen);
|
int rval = popstringn(wideStr, realLen);
|
||||||
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, realLen, NULL, NULL);
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, realLen, NULL, NULL);
|
||||||
GlobalFree((HGLOBAL)wideStr);
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL PushStringA(const char* ansiStr)
|
void NSISCALL PushStringA(LPCSTR ansiStr)
|
||||||
{
|
{
|
||||||
wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t));
|
LPWSTR wideStr = (LPWSTR) GlobalAlloc(GPTR, g_stringsize*sizeof(WCHAR));
|
||||||
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
pushstring(wideStr);
|
pushstring(wideStr);
|
||||||
GlobalFree((HGLOBAL)wideStr);
|
GlobalFree((HGLOBAL)wideStr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr)
|
void NSISCALL GetUserVariableW(const int varnum, LPWSTR wideStr)
|
||||||
{
|
{
|
||||||
lstrcpyW(wideStr, getuservariable(varnum));
|
lstrcpyW(wideStr, getuservariable(varnum));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr)
|
void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr)
|
||||||
{
|
{
|
||||||
wchar_t* wideStr = getuservariable(varnum);
|
LPWSTR wideStr = getuservariable(varnum);
|
||||||
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr)
|
void NSISCALL SetUserVariableA(const int varnum, LPCSTR ansiStr)
|
||||||
{
|
{
|
||||||
if (ansiStr && isvalidnsisvarindex(varnum))
|
if (ansiStr && isvalidnsisvarindex(varnum))
|
||||||
{
|
{
|
||||||
wchar_t* wideStr = g_variables + varnum * g_stringsize;
|
LPWSTR wideStr = g_variables + varnum * g_stringsize;
|
||||||
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// ANSI defs
|
// ANSI defs
|
||||||
int NSISCALL PopStringW(wchar_t* wideStr)
|
int NSISCALL PopStringW(LPWSTR wideStr)
|
||||||
{
|
{
|
||||||
char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize);
|
LPSTR ansiStr = (LPSTR) GlobalAlloc(GPTR, g_stringsize);
|
||||||
int rval = popstring(ansiStr);
|
int rval = popstring(ansiStr);
|
||||||
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
GlobalFree((HGLOBAL)ansiStr);
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NSISCALL PopStringNW(wchar_t* wideStr, int maxlen)
|
int NSISCALL PopStringNW(LPWSTR wideStr, int maxlen)
|
||||||
{
|
{
|
||||||
int realLen = maxlen ? maxlen : g_stringsize;
|
int realLen = maxlen ? maxlen : g_stringsize;
|
||||||
char* ansiStr = (char*) GlobalAlloc(GPTR, realLen);
|
LPSTR ansiStr = (LPSTR) GlobalAlloc(GPTR, realLen);
|
||||||
int rval = popstringn(ansiStr, realLen);
|
int rval = popstringn(ansiStr, realLen);
|
||||||
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, realLen);
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, realLen);
|
||||||
GlobalFree((HGLOBAL)ansiStr);
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL PushStringW(wchar_t* wideStr)
|
void NSISCALL PushStringW(LPWSTR wideStr)
|
||||||
{
|
{
|
||||||
char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize);
|
LPSTR ansiStr = (LPSTR) GlobalAlloc(GPTR, g_stringsize);
|
||||||
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
pushstring(ansiStr);
|
pushstring(ansiStr);
|
||||||
GlobalFree((HGLOBAL)ansiStr);
|
GlobalFree((HGLOBAL)ansiStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr)
|
void NSISCALL GetUserVariableW(const int varnum, LPWSTR wideStr)
|
||||||
{
|
{
|
||||||
char* ansiStr = getuservariable(varnum);
|
LPSTR ansiStr = getuservariable(varnum);
|
||||||
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr)
|
void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr)
|
||||||
{
|
{
|
||||||
lstrcpyA(ansiStr, getuservariable(varnum));
|
lstrcpyA(ansiStr, getuservariable(varnum));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr)
|
void NSISCALL SetUserVariableW(const int varnum, LPCWSTR wideStr)
|
||||||
{
|
{
|
||||||
if (wideStr && isvalidnsisvarindex(varnum))
|
if (wideStr && isvalidnsisvarindex(varnum))
|
||||||
{
|
{
|
||||||
char* ansiStr = g_variables + varnum * g_stringsize;
|
LPSTR ansiStr = g_variables + varnum * g_stringsize;
|
||||||
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,7 +170,7 @@ void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr)
|
||||||
|
|
||||||
// playing with integers
|
// playing with integers
|
||||||
|
|
||||||
INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s)
|
INT_PTR NSISCALL nsishelper_str_to_ptr(LPCTSTR s)
|
||||||
{
|
{
|
||||||
INT_PTR v=0;
|
INT_PTR v=0;
|
||||||
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
||||||
|
@ -206,7 +215,7 @@ INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s)
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int NSISCALL myatou(const TCHAR *s)
|
unsigned int NSISCALL myatou(LPCTSTR s)
|
||||||
{
|
{
|
||||||
unsigned int v=0;
|
unsigned int v=0;
|
||||||
|
|
||||||
|
@ -221,7 +230,7 @@ unsigned int NSISCALL myatou(const TCHAR *s)
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NSISCALL myatoi_or(const TCHAR *s)
|
int NSISCALL myatoi_or(LPCTSTR s)
|
||||||
{
|
{
|
||||||
int v=0;
|
int v=0;
|
||||||
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X')))
|
||||||
|
|
|
@ -6,10 +6,10 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
#include "nsis_tchar.h"
|
#include "nsis_tchar.h" // BUGBUG: Why cannot our plugins use the compilers tchar.h?
|
||||||
|
|
||||||
#ifndef NSISCALL
|
#ifndef NSISCALL
|
||||||
# define NSISCALL __stdcall
|
# define NSISCALL WINAPI
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define EXDLL_INIT() { \
|
#define EXDLL_INIT() { \
|
||||||
|
@ -19,7 +19,11 @@ extern "C" {
|
||||||
|
|
||||||
typedef struct _stack_t {
|
typedef struct _stack_t {
|
||||||
struct _stack_t *next;
|
struct _stack_t *next;
|
||||||
TCHAR text[1]; // this should be the length of string_size
|
#ifdef UNICODE
|
||||||
|
WCHAR text[1]; // this should be the length of g_stringsize when allocating
|
||||||
|
#else
|
||||||
|
char text[1];
|
||||||
|
#endif
|
||||||
} stack_t;
|
} stack_t;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
|
@ -54,33 +58,33 @@ __INST_LAST
|
||||||
|
|
||||||
extern unsigned int g_stringsize;
|
extern unsigned int g_stringsize;
|
||||||
extern stack_t **g_stacktop;
|
extern stack_t **g_stacktop;
|
||||||
extern TCHAR *g_variables;
|
extern LPTSTR g_variables;
|
||||||
|
|
||||||
void NSISCALL pushstring(const TCHAR *str);
|
void NSISCALL pushstring(LPCTSTR str);
|
||||||
void NSISCALL pushintptr(INT_PTR value);
|
void NSISCALL pushintptr(INT_PTR value);
|
||||||
#define pushint(v) pushintptr((INT_PTR)(v))
|
#define pushint(v) pushintptr((INT_PTR)(v))
|
||||||
int NSISCALL popstring(TCHAR *str); // 0 on success, 1 on empty stack
|
int NSISCALL popstring(LPTSTR str); // 0 on success, 1 on empty stack
|
||||||
int NSISCALL popstringn(TCHAR *str, int maxlen); // with length limit, pass 0 for g_stringsize
|
int NSISCALL popstringn(LPTSTR str, int maxlen); // with length limit, pass 0 for g_stringsize
|
||||||
INT_PTR NSISCALL popintptr();
|
INT_PTR NSISCALL popintptr();
|
||||||
#define popint() ( (int) popintptr() )
|
#define popint() ( (int) popintptr() )
|
||||||
int NSISCALL popint_or(); // with support for or'ing (2|4|8)
|
int NSISCALL popint_or(); // with support for or'ing (2|4|8)
|
||||||
INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s);
|
INT_PTR NSISCALL nsishelper_str_to_ptr(LPCTSTR s);
|
||||||
#define myatoi(s) ( (int) nsishelper_str_to_ptr(s) ) // converts a string to an integer
|
#define myatoi(s) ( (int) nsishelper_str_to_ptr(s) ) // converts a string to an integer
|
||||||
unsigned int NSISCALL myatou(const TCHAR *s); // converts a string to an unsigned integer, decimal only
|
unsigned int NSISCALL myatou(LPCTSTR s); // converts a string to an unsigned integer, decimal only
|
||||||
int NSISCALL myatoi_or(const TCHAR *s); // with support for or'ing (2|4|8)
|
int NSISCALL myatoi_or(LPCTSTR s); // with support for or'ing (2|4|8)
|
||||||
TCHAR* NSISCALL getuservariable(const int varnum);
|
LPTSTR NSISCALL getuservariable(const int varnum);
|
||||||
void NSISCALL setuservariable(const int varnum, const TCHAR *var);
|
void NSISCALL setuservariable(const int varnum, LPCTSTR var);
|
||||||
|
|
||||||
#ifdef _UNICODE
|
#ifdef UNICODE
|
||||||
#define PopStringW(x) popstring(x)
|
#define PopStringW(x) popstring(x)
|
||||||
#define PushStringW(x) pushstring(x)
|
#define PushStringW(x) pushstring(x)
|
||||||
#define SetUserVariableW(x,y) setuservariable(x,y)
|
#define SetUserVariableW(x,y) setuservariable(x,y)
|
||||||
|
|
||||||
int NSISCALL PopStringA(char* ansiStr);
|
int NSISCALL PopStringA(LPSTR ansiStr);
|
||||||
void NSISCALL PushStringA(const char* ansiStr);
|
void NSISCALL PushStringA(LPCSTR ansiStr);
|
||||||
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr);
|
void NSISCALL GetUserVariableW(const int varnum, LPWSTR wideStr);
|
||||||
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr);
|
void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr);
|
||||||
void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr);
|
void NSISCALL SetUserVariableA(const int varnum, LPCSTR ansiStr);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// ANSI defs
|
// ANSI defs
|
||||||
|
@ -89,11 +93,11 @@ void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr);
|
||||||
#define PushStringA(x) pushstring(x)
|
#define PushStringA(x) pushstring(x)
|
||||||
#define SetUserVariableA(x,y) setuservariable(x,y)
|
#define SetUserVariableA(x,y) setuservariable(x,y)
|
||||||
|
|
||||||
int NSISCALL PopStringW(wchar_t* wideStr);
|
int NSISCALL PopStringW(LPWSTR wideStr);
|
||||||
void NSISCALL PushStringW(wchar_t* wideStr);
|
void NSISCALL PushStringW(LPWSTR wideStr);
|
||||||
void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr);
|
void NSISCALL GetUserVariableW(const int varnum, LPWSTR wideStr);
|
||||||
void NSISCALL GetUserVariableA(const int varnum, char* ansiStr);
|
void NSISCALL GetUserVariableA(const int varnum, LPSTR ansiStr);
|
||||||
void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr);
|
void NSISCALL SetUserVariableW(const int varnum, LPCWSTR wideStr);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue