fix save simconf (thanks mkk). put template code for custom function plots
This commit is contained in:
parent
d7c35a0a3d
commit
d6d932e730
|
|
@ -311,10 +311,8 @@ static int waves_callback(int event, int mx, int my, KeySym key, int button, int
|
|||
gr->gx2 = gr->master_gx2;
|
||||
gr->gw = gr->master_gw;
|
||||
setup_graph_data(i, xctx->graph_flags, 1, gr); /* skip flag set, no reload x1 and x2 fields */
|
||||
/* if no dataset given assume 0 for graph scaling calculations */
|
||||
if(gr->dataset == -1) dataset = 0;
|
||||
else if(gr->dataset <= xctx->graph_datasets) dataset =gr->dataset;
|
||||
else dataset = 0;
|
||||
if(gr->dataset >= 0 && gr->dataset < xctx->graph_datasets) dataset =gr->dataset;
|
||||
else dataset = -1;
|
||||
/* destroy / show measurement widget */
|
||||
if(i == xctx->graph_master) {
|
||||
if(xctx->graph_flags & 64) {
|
||||
|
|
@ -629,6 +627,7 @@ static int waves_callback(int event, int mx, int my, KeySym key, int button, int
|
|||
if(xctx->graph_left) {
|
||||
if(i == xctx->graph_master) {
|
||||
if(!gr->digital) {
|
||||
int dset;
|
||||
int i, j;
|
||||
double v;
|
||||
double min=0.0, max=0.0;
|
||||
|
|
@ -640,11 +639,16 @@ static int waves_callback(int event, int mx, int my, KeySym key, int button, int
|
|||
nptr = NULL;
|
||||
j = get_raw_index(ntok);
|
||||
if(j >= 0) {
|
||||
for(i = 0; i < xctx->graph_npoints[dataset]; i++) {
|
||||
v = get_raw_value(dataset, j, i);
|
||||
if(first || v < min) {min = v; first = 0;}
|
||||
if(first || v > max) {max = v; first = 0;}
|
||||
}
|
||||
int ofs = 0;
|
||||
for(dset = 0 ; dset < xctx->graph_datasets; dset++) {
|
||||
for(i = ofs; i < ofs + xctx->graph_npoints[dset]; i++) {
|
||||
if(dataset >= 0 && dataset != dset) continue;
|
||||
v = xctx->graph_values[j][i];
|
||||
if(first || v < min) {min = v; first = 0;}
|
||||
if(first || v > max) {max = v; first = 0;}
|
||||
}
|
||||
ofs += xctx->graph_npoints[dset];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(max == min) max += 0.01;
|
||||
|
|
@ -663,9 +667,10 @@ static int waves_callback(int event, int mx, int my, KeySym key, int button, int
|
|||
}
|
||||
}
|
||||
} else {
|
||||
int dset = dataset == -1 ? 0 : dataset;
|
||||
if(r->sel || !(r->flags & 2) || i == xctx->graph_master) {
|
||||
xx1 = get_raw_value(dataset, 0, 0);
|
||||
xx2 = get_raw_value(dataset, 0, xctx->graph_npoints[dataset] -1);
|
||||
xx1 = get_raw_value(dset, 0, 0);
|
||||
xx2 = get_raw_value(dset, 0, xctx->graph_npoints[dset] -1);
|
||||
my_strdup(1409, &r->prop_ptr, subst_token(r->prop_ptr, "x1", dtoa(xx1)));
|
||||
my_strdup(1412, &r->prop_ptr, subst_token(r->prop_ptr, "x2", dtoa(xx2)));
|
||||
need_redraw = 1;
|
||||
|
|
@ -678,9 +683,10 @@ static int waves_callback(int event, int mx, int my, KeySym key, int button, int
|
|||
|
||||
if(xctx->graph_values) {
|
||||
if(r->sel || !(r->flags & 2) || i == xctx->graph_master) {
|
||||
int dset = dataset == -1 ? 0 : dataset;
|
||||
delta = gr->gw;
|
||||
wwx1 = get_raw_value(dataset, 0, 0);
|
||||
wwx2 = get_raw_value(dataset, 0, xctx->graph_npoints[dataset] - 1);
|
||||
wwx1 = get_raw_value(dset, 0, 0);
|
||||
wwx2 = get_raw_value(dset, 0, xctx->graph_npoints[dset] - 1);
|
||||
ccx = (gr->x2 - gr->x1) / (wwx2 - wwx1);
|
||||
ddx = gr->x1 - wwx1 * ccx;
|
||||
p = (xctx->mousex_snap - ddx) / ccx;
|
||||
|
|
|
|||
20
src/draw.c
20
src/draw.c
|
|
@ -2431,7 +2431,7 @@ void draw_graph(int i, const int flags, Graph_ctx *gr)
|
|||
XPoint *point = NULL;
|
||||
int dataset = gr->dataset;
|
||||
int digital = gr->digital;
|
||||
|
||||
if(idx == xctx->graph_nvars -1) plot_raw_custom_data(sweep_idx);
|
||||
ofs = 0;
|
||||
start = (gr->gx1 <= gr->gx2) ? gr->gx1 : gr->gx2;
|
||||
end = (gr->gx1 <= gr->gx2) ? gr->gx2 : gr->gx1;
|
||||
|
|
@ -2687,7 +2687,6 @@ void draw_image(int draw, xRect *r, double *x1, double *y1, double *x2, double *
|
|||
filterdata = (char *)base64_decode(attr, strlen(attr), &filtersize);
|
||||
filter_data(filterdata, filtersize, (char **)&closure.buffer, &data_size, filter);
|
||||
my_free(1488, &filterdata);
|
||||
my_free(1485, &filter);
|
||||
} else {
|
||||
closure.buffer = base64_decode(attr, strlen(attr), &data_size);
|
||||
}
|
||||
|
|
@ -2723,7 +2722,6 @@ void draw_image(int draw, xRect *r, double *x1, double *y1, double *x2, double *
|
|||
emb_ptr->image = cairo_image_surface_create_from_png_stream(png_reader, &closure);
|
||||
image_data = base64_encode((unsigned char *)filterdata, filtersize, &olength, 0);
|
||||
my_free(1489, &filterdata);
|
||||
my_free(1487, &filter);
|
||||
} else {
|
||||
closure.buffer = NULL;
|
||||
closure.size = 0;
|
||||
|
|
@ -2737,10 +2735,20 @@ void draw_image(int draw, xRect *r, double *x1, double *y1, double *x2, double *
|
|||
/* put base64 encoded data to rect image_data attrinute */
|
||||
my_strdup2(1473, &r->prop_ptr, subst_token(r->prop_ptr, "image_data", image_data));
|
||||
my_free(1474, &image_data);
|
||||
if(cairo_surface_status(emb_ptr->image) != CAIRO_STATUS_SUCCESS) return;
|
||||
if(cairo_surface_status(emb_ptr->image) != CAIRO_STATUS_SUCCESS) {
|
||||
my_free(442, &filter);
|
||||
return;
|
||||
}
|
||||
dbg(1, "draw_image(): length3 = %d\n", closure.pos);
|
||||
} else return;
|
||||
if(cairo_surface_status(emb_ptr->image) != CAIRO_STATUS_SUCCESS) return;
|
||||
} else {
|
||||
my_free(453, &filter);
|
||||
return;
|
||||
}
|
||||
if(cairo_surface_status(emb_ptr->image) != CAIRO_STATUS_SUCCESS) {
|
||||
my_free(434, &filter);
|
||||
return;
|
||||
}
|
||||
my_free(1485, &filter);
|
||||
ptr = get_tok_value(r->prop_ptr, "alpha", 0);
|
||||
alpha = 1.0;
|
||||
if(ptr[0]) alpha = atof(ptr);
|
||||
|
|
|
|||
75
src/save.c
75
src/save.c
|
|
@ -226,33 +226,31 @@ static void read_binary_block(FILE *fd)
|
|||
{
|
||||
int p, v;
|
||||
double *tmp;
|
||||
size_t size = 0;
|
||||
int offset = 0;
|
||||
int ac = 0;
|
||||
|
||||
if(xctx->graph_sim_type == 3) ac = 1; /* AC analysis, complex numbers twice the size */
|
||||
|
||||
for(p = 0 ; p < xctx->graph_datasets; p++) {
|
||||
size += xctx->graph_nvars * xctx->graph_npoints[p];
|
||||
offset += xctx->graph_npoints[p];
|
||||
}
|
||||
|
||||
/* read buffer */
|
||||
tmp = my_calloc(1405, xctx->graph_nvars, (sizeof(double *) ));
|
||||
tmp = my_calloc(1405, xctx->graph_nvars - 1, (sizeof(double *) ));
|
||||
/* allocate storage for binary block */
|
||||
if(!xctx->graph_values) xctx->graph_values = my_calloc(118, xctx->graph_nvars, sizeof(SPICE_DATA *));
|
||||
for(p = 0 ; p < xctx->graph_nvars; p++) {
|
||||
my_realloc(372,
|
||||
&xctx->graph_values[p], (size + xctx->graph_npoints[xctx->graph_datasets]) * sizeof(SPICE_DATA));
|
||||
&xctx->graph_values[p], (offset + xctx->graph_npoints[xctx->graph_datasets]) * sizeof(SPICE_DATA));
|
||||
}
|
||||
/* read binary block */
|
||||
for(p = 0; p < xctx->graph_npoints[xctx->graph_datasets]; p++) {
|
||||
if(fread(tmp, sizeof(double) , xctx->graph_nvars, fd) != xctx->graph_nvars) {
|
||||
if(fread(tmp, sizeof(double) , xctx->graph_nvars - 1, fd) != xctx->graph_nvars - 1) {
|
||||
dbg(0, "Warning: binary block is not of correct size\n");
|
||||
}
|
||||
/* assign to xschem struct, memory aligned per variable, for cache locality */
|
||||
if(ac) {
|
||||
for(v = 0; v < xctx->graph_nvars; v += 2) { /*AC analysis: calculate magnitude */
|
||||
for(v = 0; v < xctx->graph_nvars - 1; v += 2) { /*AC analysis: calculate magnitude */
|
||||
if( v == 0 ) /* log scale x */
|
||||
xctx->graph_values[v][offset + p] = log10(sqrt( tmp[v] * tmp[v] + tmp[v + 1] * tmp[v + 1]));
|
||||
else /* dB */
|
||||
|
|
@ -261,7 +259,7 @@ static void read_binary_block(FILE *fd)
|
|||
xctx->graph_values[v + 1] [offset + p] = atan2(tmp[v + 1], tmp[v]) * 180.0 / XSCH_PI;
|
||||
}
|
||||
}
|
||||
else for(v = 0; v < xctx->graph_nvars; v++) {
|
||||
else for(v = 0; v < xctx->graph_nvars - 1; v++) {
|
||||
xctx->graph_values[v][offset + p] = tmp[v];
|
||||
}
|
||||
}
|
||||
|
|
@ -304,15 +302,21 @@ static int read_dataset(FILE *fd)
|
|||
/* after this line comes the binary blob made of nvars * npoints * sizeof(double) bytes */
|
||||
if(!strcmp(line, "Binary:\n")) {
|
||||
int npoints = xctx->graph_npoints[xctx->graph_datasets];
|
||||
/* name of custom data column */
|
||||
if(!xctx->graph_names[xctx->graph_nvars - 1])
|
||||
my_strcat(542, &xctx->graph_names[xctx->graph_nvars - 1], "%custom%");
|
||||
int_hash_lookup(xctx->raw_table, xctx->graph_names[xctx->graph_nvars - 1],
|
||||
xctx->graph_nvars - 1, XINSERT_NOREPLACE);
|
||||
|
||||
if(xctx->graph_sim_type) {
|
||||
done_header = 1;
|
||||
read_binary_block(fd);
|
||||
dbg(1, "read_dataset(): read binary block, nvars=%d npoints=%d\n", xctx->graph_nvars, npoints);
|
||||
dbg(1, "read_dataset(): read binary block, nvars=%d npoints=%d\n", xctx->graph_nvars - 1, npoints);
|
||||
xctx->graph_datasets++;
|
||||
exit_status = 1;
|
||||
} else {
|
||||
dbg(1, "read_dataset(): skip binary block, nvars=%d npoints=%d\n", xctx->graph_nvars, npoints);
|
||||
fseek(fd, xctx->graph_nvars * npoints * sizeof(double), SEEK_CUR); /* skip binary block */
|
||||
dbg(1, "read_dataset(): skip binary block, nvars=%d npoints=%d\n", xctx->graph_nvars - 1, npoints);
|
||||
fseek(fd, (xctx->graph_nvars - 1) * npoints * sizeof(double), SEEK_CUR); /* skip binary block */
|
||||
}
|
||||
done_points = 0;
|
||||
}
|
||||
|
|
@ -342,6 +346,7 @@ static int read_dataset(FILE *fd)
|
|||
else if(!strncmp(line, "No. Variables:", 14)) {
|
||||
sscanf(line, "No. Variables: %d", &xctx->graph_nvars);
|
||||
if(xctx->graph_sim_type == 3) xctx->graph_nvars <<= 1; /* mag and phase */
|
||||
xctx->graph_nvars++; /* add extra column for custom calculated data */
|
||||
}
|
||||
else if(!done_points && !strncmp(line, "No. Points:", 11)) {
|
||||
my_realloc(1415, &xctx->graph_npoints, (xctx->graph_datasets+1) * sizeof(int));
|
||||
|
|
@ -373,7 +378,7 @@ static int read_dataset(FILE *fd)
|
|||
}
|
||||
}
|
||||
dbg(1, "read_dataset(): datasets=%d, last npoints=%d, nvars=%d\n",
|
||||
xctx->graph_datasets, xctx->graph_npoints[xctx->graph_datasets-1], xctx->graph_nvars);
|
||||
xctx->graph_datasets, xctx->graph_npoints[xctx->graph_datasets-1], xctx->graph_nvars - 1);
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
|
|
@ -397,6 +402,7 @@ void free_rawfile(int dr)
|
|||
my_free(528, &xctx->graph_values);
|
||||
}
|
||||
if(xctx->graph_npoints) my_free(1413, &xctx->graph_npoints);
|
||||
xctx->graph_allpoints = 0;
|
||||
if(xctx->raw_schname) my_free(1393, &xctx->raw_schname);
|
||||
xctx->graph_datasets = 0;
|
||||
xctx->graph_nvars = 0;
|
||||
|
|
@ -468,8 +474,13 @@ int read_rawfile(const char *f)
|
|||
fd = fopen(f, fopen_read_mode);
|
||||
if(fd) {
|
||||
if((res = read_dataset(fd)) == 1) {
|
||||
int i;
|
||||
dbg(0, "Raw file data read\n");
|
||||
my_strdup2(1394, &xctx->raw_schname, xctx->sch[xctx->currsch]);
|
||||
xctx->graph_allpoints = 0;
|
||||
for(i = 0; i < xctx->graph_datasets; i++) {
|
||||
xctx->graph_allpoints += xctx->graph_npoints[i];
|
||||
}
|
||||
draw();
|
||||
} else {
|
||||
dbg(0, "read_rawfile(): no useful data found\n");
|
||||
|
|
@ -496,15 +507,51 @@ int get_raw_index(const char *node)
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void plot_raw_custom_data(int sweep_idx)
|
||||
{
|
||||
int p, ofs = 0;
|
||||
/* xctx->graph_datasets */
|
||||
int idx = xctx->graph_nvars -1;
|
||||
int dset;
|
||||
int ret;
|
||||
char cmd[100];
|
||||
/* SPICE_DATA *x = xctx->graph_values[sweep_idx]; */
|
||||
SPICE_DATA *y = xctx->graph_values[idx];
|
||||
for(dset = 0 ; dset < xctx->graph_datasets; dset++) {
|
||||
for(p = ofs ; p < ofs + xctx->graph_npoints[dset]; p++) {
|
||||
my_snprintf(cmd, S(cmd), "customfunc %d %d", p, dset);
|
||||
ret = Tcl_GlobalEval(interp, cmd);
|
||||
if(ret == TCL_OK) {
|
||||
y[p] = atof(tclresult());
|
||||
} else {
|
||||
fprintf(errfp, "plot_raw_custom_data(): evaluation of script: %s failed\n", cmd);
|
||||
fprintf(errfp, " : %s\n", Tcl_GetStringResult(interp));
|
||||
Tcl_ResetResult(interp);
|
||||
y[p] = 0.0;
|
||||
}
|
||||
/* y[p] = sin(dset * x[p] * 1e7); */
|
||||
}
|
||||
ofs += xctx->graph_npoints[dset];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double get_raw_value(int dataset, int idx, int point)
|
||||
{
|
||||
int i, ofs;
|
||||
ofs = 0;
|
||||
if(xctx->graph_values) {
|
||||
for(i = 0; i < dataset; i++) {
|
||||
ofs += xctx->graph_npoints[i];
|
||||
if(dataset == -1) {
|
||||
if(point < xctx->graph_allpoints)
|
||||
return xctx->graph_values[idx][point];
|
||||
} else {
|
||||
for(i = 0; i < dataset; i++) {
|
||||
ofs += xctx->graph_npoints[i];
|
||||
}
|
||||
if(ofs + point < xctx->graph_allpoints)
|
||||
return xctx->graph_values[idx][ofs + point];
|
||||
}
|
||||
return xctx->graph_values[idx][ofs + point];
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2002,7 +2002,6 @@ int xschem(ClientData clientdata, Tcl_Interp *interp, int argc, const char * arg
|
|||
if(!strcmp(argv[1], "raw_query"))
|
||||
{
|
||||
int i;
|
||||
int dataset = 0;
|
||||
cmd_found = 1;
|
||||
Tcl_ResetResult(interp);
|
||||
if(argc > 2 && !strcmp(argv[2], "loaded")) {
|
||||
|
|
@ -2010,11 +2009,13 @@ int xschem(ClientData clientdata, Tcl_Interp *interp, int argc, const char * arg
|
|||
} else if(xctx->graph_values) {
|
||||
/* xschem rawfile_query value v(ldcp) 123 */
|
||||
if(argc > 4 && !strcmp(argv[2], "value")) {
|
||||
int dataset = -1;
|
||||
int point = atoi(argv[4]);
|
||||
const char *node = argv[3];
|
||||
int idx = -1;
|
||||
if(argc > 5) dataset = atoi(argv[5]);
|
||||
if(point >= 0 && point < xctx->graph_npoints[dataset]) {
|
||||
if( (dataset >= 0 && point >= 0 && point < xctx->graph_npoints[dataset]) ||
|
||||
(point >= 0 && point < xctx->graph_allpoints)) {
|
||||
if(isonlydigit(node)) {
|
||||
int i = atoi(node);
|
||||
if(i >= 0 && i < xctx->graph_nvars) {
|
||||
|
|
@ -2024,7 +2025,7 @@ int xschem(ClientData clientdata, Tcl_Interp *interp, int argc, const char * arg
|
|||
idx = get_raw_index(node);
|
||||
}
|
||||
if(idx >= 0) {
|
||||
double val = get_raw_value(dataset, idx, point);
|
||||
double val = get_raw_value(dataset, idx, point);
|
||||
Tcl_AppendResult(interp, dtoa(val), NULL);
|
||||
}
|
||||
}
|
||||
|
|
@ -2038,7 +2039,7 @@ int xschem(ClientData clientdata, Tcl_Interp *interp, int argc, const char * arg
|
|||
} else if(argc > 3 && !strcmp(argv[2], "values")) {
|
||||
/* xschem raw_query values ldcp [dataset] */
|
||||
int idx;
|
||||
int p;
|
||||
int p, dataset = 0;
|
||||
idx = get_raw_index(argv[3]);
|
||||
if(argc > 4) dataset = atoi(argv[4]);
|
||||
if(idx >= 0) {
|
||||
|
|
@ -2050,11 +2051,13 @@ int xschem(ClientData clientdata, Tcl_Interp *interp, int argc, const char * arg
|
|||
} else if(argc > 2 && !strcmp(argv[2], "datasets")) {
|
||||
Tcl_AppendResult(interp, itoa(xctx->graph_datasets), NULL);
|
||||
} else if(argc > 2 && !strcmp(argv[2], "points")) {
|
||||
int i, s = 0;
|
||||
for(i = 0; i < xctx->graph_datasets; i++) {
|
||||
s += xctx->graph_npoints[i];
|
||||
int dset = -1;
|
||||
if(argc > 3) dset = atoi(argv[3]);
|
||||
if(dset == -1) Tcl_AppendResult(interp, itoa(xctx->graph_allpoints), NULL);
|
||||
else {
|
||||
if(dset >= 0 && dset < xctx->graph_datasets)
|
||||
Tcl_AppendResult(interp, itoa(xctx->graph_npoints[dset]), NULL);
|
||||
}
|
||||
Tcl_AppendResult(interp, itoa(s), NULL);
|
||||
} else if(argc > 2 && !strcmp(argv[2], "vars")) {
|
||||
Tcl_AppendResult(interp, itoa(xctx->graph_nvars), NULL);
|
||||
} else if(argc > 2 && !strcmp(argv[2], "list")) {
|
||||
|
|
|
|||
|
|
@ -438,6 +438,7 @@ static void alloc_xschem_data(const char *top_path, const char *win_path)
|
|||
xctx->graph_values = NULL;
|
||||
xctx->graph_nvars = 0;
|
||||
xctx->graph_npoints = NULL;
|
||||
xctx->graph_allpoints = 0;
|
||||
xctx->graph_datasets = 0;
|
||||
xctx->graph_master = 0;
|
||||
xctx->graph_cursor1_x = 0;
|
||||
|
|
|
|||
|
|
@ -881,6 +881,7 @@ typedef struct {
|
|||
SPICE_DATA **graph_values;
|
||||
int graph_nvars;
|
||||
int *graph_npoints;
|
||||
int graph_allpoints; /* all points of all datasets combined */
|
||||
int graph_datasets;
|
||||
double graph_cursor1_x;
|
||||
double graph_cursor2_x;
|
||||
|
|
@ -1027,6 +1028,7 @@ extern int get_raw_index(const char *node);
|
|||
extern void free_rawfile(int dr);
|
||||
extern int read_rawfile(const char *f);
|
||||
extern double get_raw_value(int dataset, int idx, int point);
|
||||
extern void plot_raw_custom_data(int sweep_idx);
|
||||
extern int schematic_waves_loaded(void);
|
||||
extern int edit_wave_attributes(int what, int i, Graph_ctx *gr);
|
||||
extern void draw_graph(int i, int flags, Graph_ctx *gr);
|
||||
|
|
|
|||
|
|
@ -788,6 +788,19 @@ proc simconf_reset {} {
|
|||
}
|
||||
}
|
||||
|
||||
proc simconf_saveconf {scrollframe} {
|
||||
global sim USER_CONF_DIR
|
||||
foreach tool $sim(tool_list) {
|
||||
for {set i 0} { $i < $sim($tool,n)} {incr i} {
|
||||
set sim($tool,$i,cmd) [${scrollframe}.center.$tool.r.$i.cmd get 1.0 {end - 1 chars}]
|
||||
}
|
||||
}
|
||||
# destroy .sim
|
||||
# xschem set semaphore [expr {[xschem get semaphore] -1}]
|
||||
save_sim_defaults ${USER_CONF_DIR}/simrc
|
||||
# puts "saving simrc"
|
||||
}
|
||||
|
||||
proc simconf {} {
|
||||
global sim USER_CONF_DIR simconf_default_geometry
|
||||
|
||||
|
|
@ -875,18 +888,7 @@ To reset to default use the corresponding button or just delete the ~/.xschem/si
|
|||
file manually.
|
||||
} ro
|
||||
}
|
||||
button .sim.bottom.ok -text {Save Configuration to file} -command {
|
||||
foreach tool $sim(tool_list) {
|
||||
for {set i 0} { $i < $sim($tool,n)} {incr i} {
|
||||
set sim($tool,$i,cmd) [${scrollframe}.center.$tool.r.$i.cmd get 1.0 {end - 1 chars}]
|
||||
}
|
||||
}
|
||||
# destroy .sim
|
||||
# xschem set semaphore [expr {[xschem get semaphore] -1}]
|
||||
save_sim_defaults ${USER_CONF_DIR}/simrc
|
||||
# puts "saving simrc"
|
||||
}
|
||||
|
||||
button .sim.bottom.ok -text {Save Configuration to file} -command "simconf_saveconf $scrollframe"
|
||||
button .sim.bottom.reset -text {Reset to default} -command {
|
||||
simconf_reset
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue