mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-10 16:27:54 -06:00
Changing of a type of a volume in the object list
This commit is contained in:
parent
a9e7b5c645
commit
4eae6c0189
4 changed files with 96 additions and 25 deletions
|
@ -36,11 +36,11 @@ ObjectList::ObjectList(wxWindow* parent) :
|
||||||
CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(var("wand.png")), wxBITMAP_TYPE_PNG);
|
CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(var("wand.png")), wxBITMAP_TYPE_PNG);
|
||||||
}
|
}
|
||||||
|
|
||||||
init_icons();
|
|
||||||
|
|
||||||
// create control
|
// create control
|
||||||
create_objects_ctrl();
|
create_objects_ctrl();
|
||||||
|
|
||||||
|
init_icons();
|
||||||
|
|
||||||
// describe control behavior
|
// describe control behavior
|
||||||
Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) {
|
Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) {
|
||||||
selection_changed();
|
selection_changed();
|
||||||
|
@ -224,6 +224,7 @@ void ObjectList::init_icons()
|
||||||
m_bmp_vector.push_back(&m_bmp_modifiermesh); // Add modifier
|
m_bmp_vector.push_back(&m_bmp_modifiermesh); // Add modifier
|
||||||
m_bmp_vector.push_back(&m_bmp_support_enforcer); // Add support enforcer
|
m_bmp_vector.push_back(&m_bmp_support_enforcer); // Add support enforcer
|
||||||
m_bmp_vector.push_back(&m_bmp_support_blocker); // Add support blocker
|
m_bmp_vector.push_back(&m_bmp_support_blocker); // Add support blocker
|
||||||
|
m_objects_model->SetVolumeBitmaps(m_bmp_vector);
|
||||||
|
|
||||||
// init icon for manifold warning
|
// init icon for manifold warning
|
||||||
m_bmp_manifold_warning = wxBitmap(from_u8(var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG);
|
m_bmp_manifold_warning = wxBitmap(from_u8(var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG);
|
||||||
|
@ -569,10 +570,10 @@ wxMenu* ObjectList::create_add_part_popupmenu()
|
||||||
const int obj_types_count = menu_object_types_items.size();
|
const int obj_types_count = menu_object_types_items.size();
|
||||||
const int generics_count = 4;
|
const int generics_count = 4;
|
||||||
|
|
||||||
wxWindowID config_id_base = wxWindow::NewControlId(menu_object_types_items.size() + 4 + 2);
|
wxWindowID config_id_base = NewControlId(menu_object_types_items.size() + 4 + 2);
|
||||||
|
|
||||||
// Add first 4 menu items
|
// Add first 4 menu items
|
||||||
int i = 0;
|
int i;
|
||||||
for (i = 0; i < obj_types_count - 1; i++) {
|
for (i = 0; i < obj_types_count - 1; i++) {
|
||||||
auto& item = menu_object_types_items[i];
|
auto& item = menu_object_types_items[i];
|
||||||
auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item));
|
auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item));
|
||||||
|
@ -631,24 +632,33 @@ wxMenu* ObjectList::create_add_part_popupmenu()
|
||||||
wxMenu* ObjectList::create_part_settings_popupmenu()
|
wxMenu* ObjectList::create_part_settings_popupmenu()
|
||||||
{
|
{
|
||||||
wxMenu *menu = new wxMenu;
|
wxMenu *menu = new wxMenu;
|
||||||
wxWindowID config_id_base = wxWindow::NewControlId(2);
|
wxWindowID config_id_base = NewControlId(3);
|
||||||
|
|
||||||
auto menu_item = menu_item_split(menu, config_id_base);
|
auto menu_item = menu_item_split(menu, config_id_base);
|
||||||
menu->Append(menu_item);
|
menu->Append(menu_item);
|
||||||
menu_item->Enable(is_splittable_object(true));
|
menu_item->Enable(is_splittable_object(true));
|
||||||
|
|
||||||
|
// Append change part type
|
||||||
menu->AppendSeparator();
|
menu->AppendSeparator();
|
||||||
|
menu->Append(new wxMenuItem(menu, config_id_base + 1, _(L("Change type"))));
|
||||||
|
|
||||||
// Append settings popupmenu
|
// Append settings popupmenu
|
||||||
menu->Append(menu_item_settings(menu, config_id_base + 1, true));
|
menu->AppendSeparator();
|
||||||
|
menu_item = menu_item_settings(menu, config_id_base + 2, true);
|
||||||
|
menu->Append(menu_item);
|
||||||
|
menu_item->Enable(get_selected_model_volume()->type() <= ModelVolume::PARAMETER_MODIFIER);
|
||||||
|
|
||||||
menu->Bind(wxEVT_MENU, [config_id_base, menu, this](wxEvent &event) {
|
menu->Bind(wxEVT_MENU, [config_id_base, menu, this](wxEvent &event) {
|
||||||
switch (event.GetId() - config_id_base) {
|
switch (event.GetId() - config_id_base) {
|
||||||
case 0:
|
case 0:
|
||||||
split(true);
|
split(true);
|
||||||
break;
|
break;
|
||||||
default:{
|
case 1:
|
||||||
|
change_part_type();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
get_settings_choice(menu, event.GetId(), true);
|
get_settings_choice(menu, event.GetId(), true);
|
||||||
break; }
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -691,7 +701,7 @@ void ObjectList::load_subobject(int type)
|
||||||
parts_changed(obj_idx);
|
parts_changed(obj_idx);
|
||||||
|
|
||||||
for (int i = 0; i < part_names.size(); ++i) {
|
for (int i = 0; i < part_names.size(); ++i) {
|
||||||
const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), *m_bmp_vector[type]);
|
const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), /**m_bmp_vector[*/type/*]*/);
|
||||||
|
|
||||||
if (i == part_names.size() - 1)
|
if (i == part_names.size() - 1)
|
||||||
select_item(sel_item);
|
select_item(sel_item);
|
||||||
|
@ -790,8 +800,7 @@ void ObjectList::load_lambda(const std::string& type_name)
|
||||||
m_parts_changed = true;
|
m_parts_changed = true;
|
||||||
parts_changed(m_selected_object_id);
|
parts_changed(m_selected_object_id);
|
||||||
|
|
||||||
select_item(m_objects_model->AddVolumeChild(GetSelection(),
|
select_item(m_objects_model->AddVolumeChild(GetSelection(), name, ModelVolume::PARAMETER_MODIFIER/*m_bmp_modifiermesh*/));
|
||||||
name, m_bmp_modifiermesh));
|
|
||||||
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
||||||
selection_changed();
|
selection_changed();
|
||||||
#endif //no __WXOSX__ //__WXMSW__
|
#endif //no __WXOSX__ //__WXMSW__
|
||||||
|
@ -908,7 +917,7 @@ void ObjectList::split(const bool split_part)
|
||||||
|
|
||||||
for (auto id = 0; id < model_object->volumes.size(); id++)
|
for (auto id = 0; id < model_object->volumes.size(); id++)
|
||||||
m_objects_model->AddVolumeChild(parent, model_object->volumes[id]->name,
|
m_objects_model->AddVolumeChild(parent, model_object->volumes[id]->name,
|
||||||
model_object->volumes[id]->is_modifier() ? m_bmp_modifiermesh : m_bmp_solidmesh,
|
model_object->volumes[id]->is_modifier() ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART,
|
||||||
model_object->volumes[id]->config.has("extruder") ?
|
model_object->volumes[id]->config.has("extruder") ?
|
||||||
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
|
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
|
||||||
false);
|
false);
|
||||||
|
@ -918,7 +927,7 @@ void ObjectList::split(const bool split_part)
|
||||||
else {
|
else {
|
||||||
for (auto id = 0; id < model_object->volumes.size(); id++)
|
for (auto id = 0; id < model_object->volumes.size(); id++)
|
||||||
m_objects_model->AddVolumeChild(item, model_object->volumes[id]->name,
|
m_objects_model->AddVolumeChild(item, model_object->volumes[id]->name,
|
||||||
m_bmp_solidmesh,
|
ModelVolume::MODEL_PART,
|
||||||
model_object->volumes[id]->config.has("extruder") ?
|
model_object->volumes[id]->config.has("extruder") ?
|
||||||
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
|
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
|
||||||
false);
|
false);
|
||||||
|
@ -929,7 +938,7 @@ void ObjectList::split(const bool split_part)
|
||||||
parts_changed(m_selected_object_id);
|
parts_changed(m_selected_object_id);
|
||||||
|
|
||||||
// restores selection
|
// restores selection
|
||||||
_3DScene::get_canvas(wxGetApp().canvas3D())->get_selection().add_object(m_selected_object_id);
|
// _3DScene::get_canvas(wxGetApp().canvas3D())->get_selection().add_object(m_selected_object_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjectList::get_volume_by_item(const bool split_part, const wxDataViewItem& item, ModelVolume*& volume)
|
bool ObjectList::get_volume_by_item(const bool split_part, const wxDataViewItem& item, ModelVolume*& volume)
|
||||||
|
@ -979,7 +988,7 @@ void ObjectList::part_settings_changed()
|
||||||
|
|
||||||
void ObjectList::parts_changed(int obj_idx)
|
void ObjectList::parts_changed(int obj_idx)
|
||||||
{
|
{
|
||||||
wxGetApp().plater()->changed_object(get_selected_obj_idx());
|
wxGetApp().plater()->changed_object(obj_idx);
|
||||||
m_parts_changed = false;
|
m_parts_changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1071,7 +1080,7 @@ void ObjectList::add_object_to_list(size_t obj_idx)
|
||||||
for (auto id = 0; id < model_object->volumes.size(); id++)
|
for (auto id = 0; id < model_object->volumes.size(); id++)
|
||||||
m_objects_model->AddVolumeChild(item,
|
m_objects_model->AddVolumeChild(item,
|
||||||
model_object->volumes[id]->name,
|
model_object->volumes[id]->name,
|
||||||
m_bmp_solidmesh,
|
ModelVolume::MODEL_PART,
|
||||||
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value,
|
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value,
|
||||||
false);
|
false);
|
||||||
Expand(item);
|
Expand(item);
|
||||||
|
@ -1296,5 +1305,40 @@ void ObjectList::fix_multiselection_conflicts()
|
||||||
m_prevent_list_events = false;
|
m_prevent_list_events = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModelVolume* ObjectList::get_selected_model_volume()
|
||||||
|
{
|
||||||
|
auto item = GetSelection();
|
||||||
|
if (!item || m_objects_model->GetItemType(item) != itVolume)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const auto vol_idx = m_objects_model->GetVolumeIdByItem(item);
|
||||||
|
const auto obj_idx = get_selected_obj_idx();
|
||||||
|
if (vol_idx < 0 || obj_idx < 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return (*m_objects)[obj_idx]->volumes[vol_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectList::change_part_type()
|
||||||
|
{
|
||||||
|
ModelVolume* volume = get_selected_model_volume();
|
||||||
|
if (!volume)
|
||||||
|
return;
|
||||||
|
const auto type = volume->type();
|
||||||
|
|
||||||
|
const wxString names[] = { "Part", "Modifier", "Support Enforcer", "Support Blocker" };
|
||||||
|
|
||||||
|
auto new_type = wxGetSingleChoiceIndex("Type: ", _(L("Select type of part")), wxArrayString(4, names), type);
|
||||||
|
|
||||||
|
if (new_type == type || new_type < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
volume->set_type(static_cast<ModelVolume::Type>(new_type));
|
||||||
|
m_objects_model->SetVolumeType(GetSelection(), new_type);
|
||||||
|
|
||||||
|
m_parts_changed = true;
|
||||||
|
parts_changed(get_selected_obj_idx());
|
||||||
|
}
|
||||||
|
|
||||||
} //namespace GUI
|
} //namespace GUI
|
||||||
} //namespace Slic3r
|
} //namespace Slic3r
|
|
@ -145,6 +145,9 @@ public:
|
||||||
void select_all();
|
void select_all();
|
||||||
// correct current selections to avoid of the possible conflicts
|
// correct current selections to avoid of the possible conflicts
|
||||||
void fix_multiselection_conflicts();
|
void fix_multiselection_conflicts();
|
||||||
|
|
||||||
|
ModelVolume* get_selected_model_volume();
|
||||||
|
void change_part_type();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <wx/numformatter.h>
|
#include <wx/numformatter.h>
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
|
#include "Model.hpp"
|
||||||
|
|
||||||
wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
|
wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
|
||||||
std::function<void(wxCommandEvent& event)> cb, const std::string& icon, wxEvtHandler* event_handler)
|
std::function<void(wxCommandEvent& event)> cb, const std::string& icon, wxEvtHandler* event_handler)
|
||||||
|
@ -400,10 +401,9 @@ bool PrusaObjectDataViewModelNode::update_settings_digest(const std::vector<std:
|
||||||
|
|
||||||
m_opt_categories = categories;
|
m_opt_categories = categories;
|
||||||
m_name = wxEmptyString;
|
m_name = wxEmptyString;
|
||||||
// m_icon = m_empty_icon;
|
|
||||||
m_bmp = m_empty_bmp;
|
m_bmp = m_empty_bmp;
|
||||||
|
|
||||||
std::map<std::string, wxBitmap>& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON;//Slic3r::GUI::get_category_icon();
|
std::map<std::string, wxBitmap>& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON;
|
||||||
|
|
||||||
for (auto& cat : m_opt_categories)
|
for (auto& cat : m_opt_categories)
|
||||||
m_name += cat + "; ";
|
m_name += cat + "; ";
|
||||||
|
@ -453,19 +453,18 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name)
|
||||||
|
|
||||||
wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item,
|
wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item,
|
||||||
const wxString &name,
|
const wxString &name,
|
||||||
const wxBitmap& icon,
|
const int volume_type,
|
||||||
const int extruder/* = 0*/,
|
const int extruder/* = 0*/,
|
||||||
const bool create_frst_child/* = true*/)
|
const bool create_frst_child/* = true*/)
|
||||||
{
|
{
|
||||||
PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID();
|
PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID();
|
||||||
if (!root) return wxDataViewItem(0);
|
if (!root) return wxDataViewItem(0);
|
||||||
|
|
||||||
const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder);
|
wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder);
|
||||||
|
|
||||||
if (create_frst_child && root->m_volumes_cnt == 0)
|
if (create_frst_child && root->m_volumes_cnt == 0)
|
||||||
{
|
{
|
||||||
const auto bmp_solid_mesh = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);
|
const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0);
|
||||||
const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, bmp_solid_mesh, extruder_str, 0);
|
|
||||||
root->Append(node);
|
root->Append(node);
|
||||||
// notify control
|
// notify control
|
||||||
const wxDataViewItem child((void*)node);
|
const wxDataViewItem child((void*)node);
|
||||||
|
@ -474,7 +473,10 @@ wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &pa
|
||||||
root->m_volumes_cnt++;
|
root->m_volumes_cnt++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, root->m_volumes_cnt);
|
// if (volume_type >= Slic3r::ModelVolume::SUPPORT_ENFORCER)
|
||||||
|
// extruder_str = "";
|
||||||
|
|
||||||
|
const auto node = new PrusaObjectDataViewModelNode(root, name, *m_volume_bmps[volume_type], extruder_str, root->m_volumes_cnt);
|
||||||
root->Append(node);
|
root->Append(node);
|
||||||
// notify control
|
// notify control
|
||||||
const wxDataViewItem child((void*)node);
|
const wxDataViewItem child((void*)node);
|
||||||
|
@ -563,6 +565,10 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item)
|
||||||
auto id = node_parent->GetChildren().Index(node);
|
auto id = node_parent->GetChildren().Index(node);
|
||||||
auto idx = node->GetIdx();
|
auto idx = node->GetIdx();
|
||||||
node_parent->GetChildren().Remove(node);
|
node_parent->GetChildren().Remove(node);
|
||||||
|
|
||||||
|
if (node->m_type == itVolume)
|
||||||
|
node_parent->m_volumes_cnt--;
|
||||||
|
|
||||||
if (id > 0) {
|
if (id > 0) {
|
||||||
if(id == node_parent->GetChildCount()) id--;
|
if(id == node_parent->GetChildCount()) id--;
|
||||||
ret_item = wxDataViewItem(node_parent->GetChildren().Item(id));
|
ret_item = wxDataViewItem(node_parent->GetChildren().Item(id));
|
||||||
|
@ -692,6 +698,9 @@ void PrusaObjectDataViewModel::DeleteChildren(wxDataViewItem& parent)
|
||||||
auto item = wxDataViewItem(node);
|
auto item = wxDataViewItem(node);
|
||||||
children.RemoveAt(id);
|
children.RemoveAt(id);
|
||||||
|
|
||||||
|
if (node->m_type == itVolume)
|
||||||
|
root->m_volumes_cnt--;
|
||||||
|
|
||||||
// free the node
|
// free the node
|
||||||
delete node;
|
delete node;
|
||||||
|
|
||||||
|
@ -1050,6 +1059,16 @@ void PrusaObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item,
|
||||||
ItemChanged(item);
|
ItemChanged(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrusaObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const int type)
|
||||||
|
{
|
||||||
|
if (!item.IsOk() || GetItemType(item) != itVolume)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();
|
||||||
|
node->SetBitmap(*m_volume_bmps[type]);
|
||||||
|
ItemChanged(item);
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// PrusaDataViewBitmapText
|
// PrusaDataViewBitmapText
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
|
@ -424,7 +424,9 @@ private:
|
||||||
|
|
||||||
class PrusaObjectDataViewModel :public wxDataViewModel
|
class PrusaObjectDataViewModel :public wxDataViewModel
|
||||||
{
|
{
|
||||||
std::vector<PrusaObjectDataViewModelNode*> m_objects;
|
std::vector<PrusaObjectDataViewModelNode*> m_objects;
|
||||||
|
std::vector<wxBitmap*> m_volume_bmps;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PrusaObjectDataViewModel();
|
PrusaObjectDataViewModel();
|
||||||
~PrusaObjectDataViewModel();
|
~PrusaObjectDataViewModel();
|
||||||
|
@ -432,7 +434,7 @@ public:
|
||||||
wxDataViewItem Add(const wxString &name);
|
wxDataViewItem Add(const wxString &name);
|
||||||
wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item,
|
wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item,
|
||||||
const wxString &name,
|
const wxString &name,
|
||||||
const wxBitmap& icon,
|
const int volume_type,
|
||||||
const int extruder = 0,
|
const int extruder = 0,
|
||||||
const bool create_frst_child = true);
|
const bool create_frst_child = true);
|
||||||
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
|
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
|
||||||
|
@ -491,6 +493,9 @@ public:
|
||||||
wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const;
|
wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const;
|
||||||
bool IsSettingsItem(const wxDataViewItem &item) const;
|
bool IsSettingsItem(const wxDataViewItem &item) const;
|
||||||
void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector<std::string>& categories);
|
void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector<std::string>& categories);
|
||||||
|
|
||||||
|
void SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; }
|
||||||
|
void SetVolumeType(const wxDataViewItem &item, const int type);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue