More refactoring, grids can now be nested properly...
...allowing better placement of widgets.
This commit is contained in:
parent
2563ef1135
commit
b6b02b26e2
5 changed files with 317 additions and 170 deletions
|
@ -12,10 +12,61 @@
|
||||||
left = "center"
|
left = "center"
|
||||||
|
|
||||||
width = 600
|
width = 600
|
||||||
height = 150
|
height = 200
|
||||||
|
|
||||||
window_definition = "default"
|
window_definition = "default"
|
||||||
|
|
||||||
|
[grid]
|
||||||
|
|
||||||
|
[row]
|
||||||
|
#fixme rename scale to grow_factor
|
||||||
|
scale = 0
|
||||||
|
|
||||||
|
[column]
|
||||||
|
scale = 1
|
||||||
|
|
||||||
|
border = "all"
|
||||||
|
border_size = 5
|
||||||
|
horizontal_alignment = "left"
|
||||||
|
horizontal_grow = "true" #FIXME should not be needed
|
||||||
|
[label]
|
||||||
|
label_definition = "default"
|
||||||
|
|
||||||
|
label = _ "Connect to Server"
|
||||||
|
[/label]
|
||||||
|
|
||||||
|
[/column]
|
||||||
|
|
||||||
|
[/row]
|
||||||
|
|
||||||
|
[row]
|
||||||
|
scale = 0
|
||||||
|
|
||||||
|
[column]
|
||||||
|
scale = 1
|
||||||
|
|
||||||
|
border = "all"
|
||||||
|
border_size = 5
|
||||||
|
horizontal_alignment = "left"
|
||||||
|
horizontal_grow = "true" #FIXME should not be needed
|
||||||
|
[label]
|
||||||
|
label_definition = "default"
|
||||||
|
|
||||||
|
label = _ "You will now connect to a server to download add-ons."
|
||||||
|
[/label]
|
||||||
|
|
||||||
|
[/column]
|
||||||
|
|
||||||
|
[/row]
|
||||||
|
|
||||||
|
[row]
|
||||||
|
scale = 1
|
||||||
|
|
||||||
|
[column]
|
||||||
|
scale = 1
|
||||||
|
|
||||||
|
horizontal_grow = "true"
|
||||||
|
|
||||||
[grid]
|
[grid]
|
||||||
|
|
||||||
[row]
|
[row]
|
||||||
|
@ -55,6 +106,21 @@
|
||||||
|
|
||||||
[/row]
|
[/row]
|
||||||
|
|
||||||
|
[/grid]
|
||||||
|
|
||||||
|
[/column]
|
||||||
|
|
||||||
|
[/row]
|
||||||
|
|
||||||
|
[row]
|
||||||
|
scale = 0
|
||||||
|
|
||||||
|
[column]
|
||||||
|
scale = 1
|
||||||
|
horizontal_grow = "true"
|
||||||
|
|
||||||
|
[grid]
|
||||||
|
|
||||||
[row]
|
[row]
|
||||||
scale = 0
|
scale = 0
|
||||||
|
|
||||||
|
@ -127,6 +193,12 @@
|
||||||
|
|
||||||
[/grid]
|
[/grid]
|
||||||
|
|
||||||
|
[/column]
|
||||||
|
|
||||||
|
[/row]
|
||||||
|
|
||||||
|
[/grid]
|
||||||
|
|
||||||
[/resolution]
|
[/resolution]
|
||||||
|
|
||||||
[/window]
|
[/window]
|
||||||
|
|
|
@ -214,6 +214,8 @@ tpoint tgrid::get_best_size() const
|
||||||
|
|
||||||
void tgrid::set_size(const SDL_Rect& rect)
|
void tgrid::set_size(const SDL_Rect& rect)
|
||||||
{
|
{
|
||||||
|
log_scope2(gui, "Grid: set size");
|
||||||
|
|
||||||
twidget::set_size(rect);
|
twidget::set_size(rect);
|
||||||
|
|
||||||
const tpoint orig(rect.x, rect.y);
|
const tpoint orig(rect.x, rect.y);
|
||||||
|
@ -240,6 +242,7 @@ void tgrid::set_size(const SDL_Rect& rect)
|
||||||
col_width_ = best_col_width_;
|
col_width_ = best_col_width_;
|
||||||
|
|
||||||
// expand it.
|
// expand it.
|
||||||
|
if(size.x > best_size.x) {
|
||||||
const unsigned w = size.x - best_size.x;
|
const unsigned w = size.x - best_size.x;
|
||||||
unsigned w_size = std::accumulate(col_scaling_.begin(), col_scaling_.end(), 0);
|
unsigned w_size = std::accumulate(col_scaling_.begin(), col_scaling_.end(), 0);
|
||||||
DBG_G << "Grid: extra width " << w << " will be divided amount " << w_size << " units in " << cols_ << " columns.\n";
|
DBG_G << "Grid: extra width " << w << " will be divided amount " << w_size << " units in " << cols_ << " columns.\n";
|
||||||
|
@ -260,8 +263,9 @@ void tgrid::set_size(const SDL_Rect& rect)
|
||||||
<< col_scaling_[i] << " set width to " << col_width_[i] << ".\n";
|
<< col_scaling_[i] << " set width to " << col_width_[i] << ".\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(size.y > best_size.y) {
|
||||||
const unsigned h = size.y - best_size.y;
|
const unsigned h = size.y - best_size.y;
|
||||||
unsigned h_size = std::accumulate(row_scaling_.begin(), row_scaling_.end(), 0);
|
unsigned h_size = std::accumulate(row_scaling_.begin(), row_scaling_.end(), 0);
|
||||||
DBG_G << "Grid: extra height " << h << " will be divided amount " << h_size << " units in " << rows_ << " rows.\n";
|
DBG_G << "Grid: extra height " << h << " will be divided amount " << h_size << " units in " << rows_ << " rows.\n";
|
||||||
|
@ -281,7 +285,7 @@ void tgrid::set_size(const SDL_Rect& rect)
|
||||||
DBG_G << "Grid: row " << i << " with scale factor "
|
DBG_G << "Grid: row " << i << " with scale factor "
|
||||||
<< row_scaling_[i] << " set height to " << row_height_[i] << ".\n";
|
<< row_scaling_[i] << " set height to " << row_height_[i] << ".\n";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layout(orig);
|
layout(orig);
|
||||||
return;
|
return;
|
||||||
|
@ -344,6 +348,21 @@ twidget* tgrid::get_widget_by_id(const std::string& id)
|
||||||
return twidget::get_widget_by_id(id);
|
return twidget::get_widget_by_id(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tgrid::draw(surface& surface)
|
||||||
|
{
|
||||||
|
for(iterator itor = begin(); itor != end(); ++itor) {
|
||||||
|
if(! *itor || !itor->dirty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_scope2(gui_draw, "Grid: draw child.");
|
||||||
|
|
||||||
|
itor->draw(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_dirty(false);
|
||||||
|
}
|
||||||
|
|
||||||
void tgrid::load_config()
|
void tgrid::load_config()
|
||||||
{
|
{
|
||||||
for(std::vector<tchild>::iterator itor = children_.begin();
|
for(std::vector<tchild>::iterator itor = children_.begin();
|
||||||
|
|
|
@ -102,7 +102,7 @@ public:
|
||||||
twidget* get_widget_by_id(const std::string& id);
|
twidget* get_widget_by_id(const std::string& id);
|
||||||
|
|
||||||
//! Inherited from twidget.
|
//! Inherited from twidget.
|
||||||
void draw(surface& surface) { /* FIXME IMPLEMENT */ }
|
void draw(surface& surface);
|
||||||
|
|
||||||
//! Inherited from twidget.
|
//! Inherited from twidget.
|
||||||
void load_config();
|
void load_config();
|
||||||
|
@ -298,6 +298,10 @@ public:
|
||||||
void set_col_scaling(const unsigned col, const unsigned scale)
|
void set_col_scaling(const unsigned col, const unsigned scale)
|
||||||
{ grid_.set_col_scaling(col, scale); }
|
{ grid_.set_col_scaling(col, scale); }
|
||||||
|
|
||||||
|
//! Inherited from twidget.
|
||||||
|
//FIXME we also need to load our own config
|
||||||
|
void draw(surface& surface) { grid_.draw(surface); }
|
||||||
|
|
||||||
//! Inherited from twidget.
|
//! Inherited from twidget.
|
||||||
//FIXME we also need to load our own config
|
//FIXME we also need to load our own config
|
||||||
void load_config() { grid_.load_config(); }
|
void load_config() { grid_.load_config(); }
|
||||||
|
|
|
@ -49,54 +49,90 @@
|
||||||
|
|
||||||
namespace gui2 {
|
namespace gui2 {
|
||||||
|
|
||||||
struct tbuilder_button : public tbuilder_widget
|
struct tbuilder_control : public tbuilder_widget
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
tbuilder_control();
|
||||||
|
public:
|
||||||
|
|
||||||
|
tbuilder_control(const config& cfg);
|
||||||
|
|
||||||
|
//! Parameters for the control.
|
||||||
|
std::string id;
|
||||||
|
std::string definition;
|
||||||
|
t_string label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tbuilder_button : public tbuilder_control
|
||||||
{
|
{
|
||||||
|
|
||||||
private:
|
private:
|
||||||
tbuilder_button();
|
tbuilder_button();
|
||||||
public:
|
public:
|
||||||
tbuilder_button(const config& cfg) :
|
tbuilder_button(const config& cfg);
|
||||||
tbuilder_widget(cfg),
|
|
||||||
retval_(0)
|
|
||||||
{ read_extra(cfg); }
|
|
||||||
|
|
||||||
twidget* build () const;
|
twidget* build () const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int retval_;
|
int retval_;
|
||||||
|
|
||||||
//! After reading the general part in the constructor read extra data.
|
|
||||||
void read_extra(const config& cfg);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct tbuilder_label : public tbuilder_widget
|
struct tbuilder_label : public tbuilder_control
|
||||||
{
|
{
|
||||||
|
|
||||||
private:
|
private:
|
||||||
tbuilder_label();
|
tbuilder_label();
|
||||||
public:
|
public:
|
||||||
tbuilder_label(const config& cfg) :
|
tbuilder_label(const config& cfg) :
|
||||||
tbuilder_widget(cfg)
|
tbuilder_control(cfg)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
twidget* build () const;
|
twidget* build () const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct tbuilder_text_box : public tbuilder_widget
|
struct tbuilder_text_box : public tbuilder_control
|
||||||
{
|
{
|
||||||
|
|
||||||
private:
|
private:
|
||||||
tbuilder_text_box();
|
tbuilder_text_box();
|
||||||
public:
|
public:
|
||||||
tbuilder_text_box(const config& cfg) :
|
tbuilder_text_box(const config& cfg) :
|
||||||
tbuilder_widget(cfg)
|
tbuilder_control(cfg)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
twidget* build () const;
|
twidget* build () const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct tbuilder_grid : public tbuilder_widget
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
tbuilder_grid();
|
||||||
|
|
||||||
|
public:
|
||||||
|
tbuilder_grid(const config& cfg);
|
||||||
|
unsigned rows;
|
||||||
|
unsigned cols;
|
||||||
|
|
||||||
|
//! The scale factor for the rows / columns.
|
||||||
|
std::vector<unsigned> row_scale;
|
||||||
|
std::vector<unsigned> col_scale;
|
||||||
|
|
||||||
|
//! The flags per grid cell.
|
||||||
|
std::vector<unsigned> flags;
|
||||||
|
|
||||||
|
//! The border size per grid cell.
|
||||||
|
std::vector<unsigned> border_size;
|
||||||
|
|
||||||
|
//! The widgets per grid cell.
|
||||||
|
std::vector<tbuilder_widget_ptr> widgets;
|
||||||
|
|
||||||
|
twidget* build () const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! After reading the general part in the constructor read extra data.
|
||||||
|
void read_extra(const config& cfg);
|
||||||
|
};
|
||||||
|
|
||||||
twindow build(CVideo& video, const std::string& type)
|
twindow build(CVideo& video, const std::string& type)
|
||||||
{
|
{
|
||||||
|
@ -107,21 +143,23 @@ twindow build(CVideo& video, const std::string& type)
|
||||||
twindow window(video, 100, 100, definition->width, definition->height); // FIXME use proper origin
|
twindow window(video, 100, 100, definition->width, definition->height); // FIXME use proper origin
|
||||||
// twindow window(video, 0, 0, definition->width, definition->height); // FIXME use proper origin
|
// twindow window(video, 0, 0, definition->width, definition->height); // FIXME use proper origin
|
||||||
|
|
||||||
const unsigned rows = definition->grid.rows;
|
log_scope2(gui, "Window builder: building grid for window");
|
||||||
const unsigned cols = definition->grid.cols;
|
|
||||||
|
const unsigned rows = definition->grid->rows;
|
||||||
|
const unsigned cols = definition->grid->cols;
|
||||||
|
|
||||||
window.set_rows_cols(rows, cols);
|
window.set_rows_cols(rows, cols);
|
||||||
|
|
||||||
for(unsigned x = 0; x < rows; ++x) {
|
for(unsigned x = 0; x < rows; ++x) {
|
||||||
window.set_row_scaling(x, definition->grid.row_scale[x]);
|
window.set_row_scaling(x, definition->grid->row_scale[x]);
|
||||||
for(unsigned y = 0; y < cols; ++y) {
|
for(unsigned y = 0; y < cols; ++y) {
|
||||||
|
|
||||||
if(x == 0) {
|
if(x == 0) {
|
||||||
window.set_col_scaling(y, definition->grid.col_scale[y]);
|
window.set_col_scaling(y, definition->grid->col_scale[y]);
|
||||||
}
|
}
|
||||||
|
|
||||||
twidget* widget = definition->grid.widgets[x * cols + y]->build();
|
twidget* widget = definition->grid->widgets[x * cols + y]->build();
|
||||||
window.add_child(widget, x, y, definition->grid.flags[x * cols + y], definition->grid.border_size[x * cols + y]);
|
window.add_child(widget, x, y, definition->grid->flags[x * cols + y], definition->grid->border_size[x * cols + y]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +213,7 @@ twindow_builder::tresolution::tresolution(const config& cfg) :
|
||||||
width(lexical_cast_default<unsigned>(cfg["width"])),
|
width(lexical_cast_default<unsigned>(cfg["width"])),
|
||||||
height(lexical_cast_default<unsigned>(cfg["height"])),
|
height(lexical_cast_default<unsigned>(cfg["height"])),
|
||||||
definition(cfg["window_definition"]),
|
definition(cfg["window_definition"]),
|
||||||
grid(cfg.child("grid"))
|
grid(0) //new tbuilder_grid(cfg.child("grid")))
|
||||||
{
|
{
|
||||||
/*WIKI
|
/*WIKI
|
||||||
* [resolution]
|
* [resolution]
|
||||||
|
@ -193,6 +231,10 @@ twindow_builder::tresolution::tresolution(const config& cfg) :
|
||||||
* [/resolution]
|
* [/resolution]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
VALIDATE(cfg.child("grid"), _("No grid defined."));
|
||||||
|
|
||||||
|
grid = new tbuilder_grid(*(cfg.child("grid")));
|
||||||
|
|
||||||
DBG_G_P << "Window builder: parsing resolution "
|
DBG_G_P << "Window builder: parsing resolution "
|
||||||
<< window_width << ',' << window_height << '\n';
|
<< window_width << ',' << window_height << '\n';
|
||||||
|
|
||||||
|
@ -255,14 +297,19 @@ static unsigned read_flags(const config& cfg)
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
twindow_builder::tresolution::tgrid::tgrid(const config* cfg) :
|
tbuilder_grid::tbuilder_grid(const config& cfg) :
|
||||||
|
tbuilder_widget(cfg),
|
||||||
rows(0),
|
rows(0),
|
||||||
cols(0),
|
cols(0),
|
||||||
|
row_scale(),
|
||||||
|
col_scale(),
|
||||||
|
flags(),
|
||||||
|
border_size(),
|
||||||
widgets()
|
widgets()
|
||||||
{
|
{
|
||||||
VALIDATE(cfg, _("No grid defined."));
|
log_scope2(gui_parse, "Window builder: parsing a grid");
|
||||||
|
|
||||||
const config::child_list& row_cfgs = cfg->get_children("row");
|
const config::child_list& row_cfgs = cfg.get_children("row");
|
||||||
for(std::vector<config*>::const_iterator row_itor = row_cfgs.begin();
|
for(std::vector<config*>::const_iterator row_itor = row_cfgs.begin();
|
||||||
row_itor != row_cfgs.end(); ++row_itor) {
|
row_itor != row_cfgs.end(); ++row_itor) {
|
||||||
|
|
||||||
|
@ -286,6 +333,8 @@ twindow_builder::tresolution::tgrid::tgrid(const config* cfg) :
|
||||||
widgets.push_back(new tbuilder_label(*((**col_itor).child("label"))));
|
widgets.push_back(new tbuilder_label(*((**col_itor).child("label"))));
|
||||||
} else if((**col_itor).child("text_box")) {
|
} else if((**col_itor).child("text_box")) {
|
||||||
widgets.push_back(new tbuilder_text_box(*((**col_itor).child("text_box"))));
|
widgets.push_back(new tbuilder_text_box(*((**col_itor).child("text_box"))));
|
||||||
|
} else if((**col_itor).child("grid")) {
|
||||||
|
widgets.push_back(new tbuilder_grid(*((**col_itor).child("grid"))));
|
||||||
} else {
|
} else {
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
@ -307,7 +356,8 @@ twindow_builder::tresolution::tgrid::tgrid(const config* cfg) :
|
||||||
<< rows << " rows and " << cols << " columns.\n";
|
<< rows << " rows and " << cols << " columns.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
tbuilder_widget::tbuilder_widget(const config& cfg) :
|
tbuilder_control::tbuilder_control(const config& cfg) :
|
||||||
|
tbuilder_widget(cfg),
|
||||||
id(cfg["id"]),
|
id(cfg["id"]),
|
||||||
definition(cfg["button_definition"]),
|
definition(cfg["button_definition"]),
|
||||||
label(cfg["label"])
|
label(cfg["label"])
|
||||||
|
@ -318,7 +368,7 @@ tbuilder_widget::tbuilder_widget(const config& cfg) :
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DBG_G_P << "Window builder: found widget with id '"
|
DBG_G_P << "Window builder: found control with id '"
|
||||||
<< id << "' and definition '" << definition << "'.\n";
|
<< id << "' and definition '" << definition << "'.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,9 +392,10 @@ twidget* tbuilder_button::build() const
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tbuilder_button::read_extra(const config& cfg)
|
tbuilder_button::tbuilder_button(const config& cfg) :
|
||||||
|
tbuilder_control(cfg),
|
||||||
|
retval_(lexical_cast_default<int>(cfg["return_value"]))
|
||||||
{
|
{
|
||||||
retval_ = lexical_cast_default<int>(cfg["return_value"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
twidget* tbuilder_label::build() const
|
twidget* tbuilder_label::build() const
|
||||||
|
@ -376,5 +427,34 @@ twidget* tbuilder_text_box::build() const
|
||||||
return text_box;
|
return text_box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
twidget* tbuilder_grid::build() const
|
||||||
|
{
|
||||||
|
tgrid *grid = new tgrid(0, 0, 0, 0);
|
||||||
|
|
||||||
|
grid->set_rows_cols(rows, cols);
|
||||||
|
|
||||||
|
log_scope2(gui, "Window builder: building grid");
|
||||||
|
|
||||||
|
DBG_G << "Window builder: grid has " << rows << " rows and "
|
||||||
|
<< cols << " columns.\n";
|
||||||
|
|
||||||
|
for(unsigned x = 0; x < rows; ++x) {
|
||||||
|
grid->set_row_scaling(x, row_scale[x]);
|
||||||
|
for(unsigned y = 0; y < cols; ++y) {
|
||||||
|
|
||||||
|
if(x == 0) {
|
||||||
|
grid->set_col_scaling(y, col_scale[y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_G << "Window builder: adding child at " << x << ',' << y << ".\n";
|
||||||
|
|
||||||
|
twidget* widget = widgets[x * cols + y]->build();
|
||||||
|
grid->add_child(widget, x, y, flags[x * cols + y], border_size[x * cols + y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace gui2
|
} // namespace gui2
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ class CVideo;
|
||||||
|
|
||||||
namespace gui2 {
|
namespace gui2 {
|
||||||
|
|
||||||
|
class tbuilder_grid;
|
||||||
class twidget;
|
class twidget;
|
||||||
class twindow;
|
class twindow;
|
||||||
|
|
||||||
|
@ -39,12 +40,8 @@ private:
|
||||||
tbuilder_widget();
|
tbuilder_widget();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
tbuilder_widget(const config& cfg);
|
tbuilder_widget(const config& /*cfg*/) {}
|
||||||
|
|
||||||
//! Parameters for the widget.
|
|
||||||
std::string id;
|
|
||||||
std::string definition;
|
|
||||||
t_string label;
|
|
||||||
|
|
||||||
virtual twidget* build() const = 0;
|
virtual twidget* build() const = 0;
|
||||||
virtual ~tbuilder_widget() {}
|
virtual ~tbuilder_widget() {}
|
||||||
|
@ -79,33 +76,8 @@ public:
|
||||||
|
|
||||||
std::string definition;
|
std::string definition;
|
||||||
|
|
||||||
struct tgrid
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
tgrid();
|
|
||||||
|
|
||||||
public:
|
tbuilder_grid* grid;
|
||||||
tgrid(const config* cfg);
|
|
||||||
|
|
||||||
unsigned rows;
|
|
||||||
unsigned cols;
|
|
||||||
|
|
||||||
//! The scale factor for the rows / columns.
|
|
||||||
std::vector<unsigned> row_scale;
|
|
||||||
std::vector<unsigned> col_scale;
|
|
||||||
|
|
||||||
//! The flags per grid cell.
|
|
||||||
std::vector<unsigned> flags;
|
|
||||||
|
|
||||||
//! The border size per grid cell.
|
|
||||||
std::vector<unsigned> border_size;
|
|
||||||
|
|
||||||
//! The widgets per grid cell.
|
|
||||||
std::vector<tbuilder_widget_ptr> widgets;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
tgrid grid;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<tresolution> resolutions;
|
std::vector<tresolution> resolutions;
|
||||||
|
|
Loading…
Add table
Reference in a new issue