New C++ class AppConfig for maintaining the config.ini
New helper function for generating a unified "generated by slic3r" header.
This commit is contained in:
parent
835e5b71a8
commit
1fee3633a0
@ -162,6 +162,8 @@ add_library(libslic3r STATIC
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_library(libslic3r_gui STATIC
|
add_library(libslic3r_gui STATIC
|
||||||
|
${LIBDIR}/slic3r/GUI/AppConfig.cpp
|
||||||
|
${LIBDIR}/slic3r/GUI/AppConfig.hpp
|
||||||
${LIBDIR}/slic3r/GUI/3DScene.cpp
|
${LIBDIR}/slic3r/GUI/3DScene.cpp
|
||||||
${LIBDIR}/slic3r/GUI/3DScene.hpp
|
${LIBDIR}/slic3r/GUI/3DScene.hpp
|
||||||
${LIBDIR}/slic3r/GUI/GLShader.cpp
|
${LIBDIR}/slic3r/GUI/GLShader.cpp
|
||||||
@ -279,6 +281,7 @@ set(XS_XSP_FILES
|
|||||||
${XSP_DIR}/GCodeSender.xsp
|
${XSP_DIR}/GCodeSender.xsp
|
||||||
${XSP_DIR}/Geometry.xsp
|
${XSP_DIR}/Geometry.xsp
|
||||||
${XSP_DIR}/GUI.xsp
|
${XSP_DIR}/GUI.xsp
|
||||||
|
${XSP_DIR}/GUI_AppConfig.xsp
|
||||||
${XSP_DIR}/GUI_3DScene.xsp
|
${XSP_DIR}/GUI_3DScene.xsp
|
||||||
${XSP_DIR}/GUI_Preset.xsp
|
${XSP_DIR}/GUI_Preset.xsp
|
||||||
${XSP_DIR}/Layer.xsp
|
${XSP_DIR}/Layer.xsp
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include "Config.hpp"
|
#include "Config.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ctime>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <exception> // std::runtime_error
|
#include <exception> // std::runtime_error
|
||||||
@ -422,13 +422,7 @@ void ConfigBase::save(const std::string &file) const
|
|||||||
{
|
{
|
||||||
boost::nowide::ofstream c;
|
boost::nowide::ofstream c;
|
||||||
c.open(file, std::ios::out | std::ios::trunc);
|
c.open(file, std::ios::out | std::ios::trunc);
|
||||||
{
|
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||||
std::time_t now;
|
|
||||||
time(&now);
|
|
||||||
char buf[sizeof "0000-00-00 00:00:00"];
|
|
||||||
strftime(buf, sizeof(buf), "%F %T", gmtime(&now));
|
|
||||||
c << "# generated by Slic3r " << SLIC3R_VERSION << " on " << buf << std::endl;
|
|
||||||
}
|
|
||||||
for (const std::string &opt_key : this->keys())
|
for (const std::string &opt_key : this->keys())
|
||||||
c << opt_key << " = " << this->serialize(opt_key) << std::endl;
|
c << opt_key << " = " << this->serialize(opt_key) << std::endl;
|
||||||
c.close();
|
c.close();
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "GCode/PrintExtents.hpp"
|
#include "GCode/PrintExtents.hpp"
|
||||||
#include "GCode/WipeTowerPrusaMM.hpp"
|
#include "GCode/WipeTowerPrusaMM.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@ -11,7 +12,6 @@
|
|||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/algorithm/string/find.hpp>
|
#include <boost/algorithm/string/find.hpp>
|
||||||
#include <boost/date_time/local_time/local_time.hpp>
|
|
||||||
#include <boost/foreach.hpp>
|
#include <boost/foreach.hpp>
|
||||||
|
|
||||||
#include <boost/nowide/iostream.hpp>
|
#include <boost/nowide/iostream.hpp>
|
||||||
@ -462,15 +462,7 @@ bool GCode::_do_export(Print &print, FILE *file)
|
|||||||
m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
|
m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
|
||||||
|
|
||||||
// Write information on the generator.
|
// Write information on the generator.
|
||||||
{
|
fprintf(file, "# %s\n\n", Slic3r::header_slic3r_generated().c_str());
|
||||||
const auto now = boost::posix_time::second_clock::local_time();
|
|
||||||
const auto date = now.date();
|
|
||||||
fprintf(file, "; generated by Slic3r %s on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
|
|
||||||
SLIC3R_VERSION,
|
|
||||||
// Local date in an ANSII format.
|
|
||||||
int(now.date().year()), int(now.date().month()), int(now.date().day()),
|
|
||||||
int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
|
|
||||||
}
|
|
||||||
// Write notes (content of the Print Settings tab -> Notes)
|
// Write notes (content of the Print Settings tab -> Notes)
|
||||||
{
|
{
|
||||||
std::list<std::string> lines;
|
std::list<std::string> lines;
|
||||||
|
@ -30,6 +30,12 @@ extern std::string encode_path(const char *src);
|
|||||||
extern std::string decode_path(const char *src);
|
extern std::string decode_path(const char *src);
|
||||||
extern std::string normalize_utf8_nfc(const char *src);
|
extern std::string normalize_utf8_nfc(const char *src);
|
||||||
|
|
||||||
|
// Timestamp formatted for header_slic3r_generated().
|
||||||
|
extern std::string timestamp_str();
|
||||||
|
// Standard "generated by Slic3r version xxx timestamp xxx" header string,
|
||||||
|
// to be placed at the top of Slic3r generated files.
|
||||||
|
inline std::string header_slic3r_generated() { return std::string("generated by Slic3r " SLIC3R_VERSION " on ") + timestamp_str(); }
|
||||||
|
|
||||||
// Compute the next highest power of 2 of 32-bit v
|
// Compute the next highest power of 2 of 32-bit v
|
||||||
// http://graphics.stanford.edu/~seander/bithacks.html
|
// http://graphics.stanford.edu/~seander/bithacks.html
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <locale>
|
#include <locale>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
#include <boost/log/core.hpp>
|
#include <boost/log/core.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
@ -7,6 +8,7 @@
|
|||||||
#include <boost/locale.hpp>
|
#include <boost/locale.hpp>
|
||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/date_time/local_time/local_time.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/nowide/integration/filesystem.hpp>
|
#include <boost/nowide/integration/filesystem.hpp>
|
||||||
#include <boost/nowide/convert.hpp>
|
#include <boost/nowide/convert.hpp>
|
||||||
@ -233,4 +235,24 @@ std::string normalize_utf8_nfc(const char *src)
|
|||||||
return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8);
|
return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string timestamp_str()
|
||||||
|
{
|
||||||
|
#if 1
|
||||||
|
std::time_t now;
|
||||||
|
time(&now);
|
||||||
|
char buf[sizeof "0000-00-00 00:00:00"];
|
||||||
|
strftime(buf, sizeof(buf), "%F %T", gmtime(&now));
|
||||||
|
#else
|
||||||
|
const auto now = boost::posix_time::second_clock::local_time();
|
||||||
|
const auto date = now.date();
|
||||||
|
char buf[2048];
|
||||||
|
sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d",
|
||||||
|
SLIC3R_VERSION,
|
||||||
|
// Local date in an ANSII format.
|
||||||
|
int(now.date().year()), int(now.date().month()), int(now.date().day()),
|
||||||
|
int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
|
||||||
|
#endif
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
}; // namespace Slic3r
|
}; // namespace Slic3r
|
||||||
|
@ -54,6 +54,7 @@ REGISTER_CLASS(Surface, "Surface");
|
|||||||
REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
|
REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
|
||||||
REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2");
|
REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2");
|
||||||
REGISTER_CLASS(TriangleMesh, "TriangleMesh");
|
REGISTER_CLASS(TriangleMesh, "TriangleMesh");
|
||||||
|
REGISTER_CLASS(AppConfig, "GUI::AppConfig");
|
||||||
REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader");
|
REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader");
|
||||||
REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume");
|
REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume");
|
||||||
REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");
|
REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");
|
||||||
|
@ -373,19 +373,6 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PresetCollection::select_by_name_ui(char *name, wxItemContainer *ui)
|
|
||||||
{
|
|
||||||
this->select_preset_by_name(name, true);
|
|
||||||
//FIXME this is not finished yet.
|
|
||||||
//this->update_platter_ui(wxChoice *ui)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PresetCollection::select_by_name_ui(char *name, wxChoice *ui)
|
|
||||||
{
|
|
||||||
return this->select_by_name_ui(name, dynamic_cast<wxItemContainer*>(ui));
|
|
||||||
}
|
|
||||||
|
|
||||||
PresetBundle::PresetBundle() :
|
PresetBundle::PresetBundle() :
|
||||||
prints(Preset::TYPE_PRINT, print_options()),
|
prints(Preset::TYPE_PRINT, print_options()),
|
||||||
filaments(Preset::TYPE_FILAMENT, filament_options()),
|
filaments(Preset::TYPE_FILAMENT, filament_options()),
|
||||||
@ -423,6 +410,20 @@ PresetBundle::~PresetBundle()
|
|||||||
delete bitmap.second;
|
delete bitmap.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PresetBundle::setup_directories()
|
||||||
|
{
|
||||||
|
boost::filesystem::path dir = boost::filesystem::canonical(Slic3r::data_dir());
|
||||||
|
if (! boost::filesystem::is_directory(dir))
|
||||||
|
throw std::runtime_error(std::string("datadir does not exist: ") + Slic3r::data_dir());
|
||||||
|
std::initializer_list<const char*> names = { "print", "filament", "printer" };
|
||||||
|
for (const char *name : names) {
|
||||||
|
boost::filesystem::path subdir = (dir / subdir).make_preferred();
|
||||||
|
if (! boost::filesystem::is_directory(subdir) &&
|
||||||
|
! boost::filesystem::create_directory(subdir))
|
||||||
|
throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PresetBundle::load_presets(const std::string &dir_path)
|
void PresetBundle::load_presets(const std::string &dir_path)
|
||||||
{
|
{
|
||||||
this->prints .load_presets(dir_path, "print");
|
this->prints .load_presets(dir_path, "print");
|
||||||
@ -431,6 +432,36 @@ void PresetBundle::load_presets(const std::string &dir_path)
|
|||||||
this->update_multi_material_filament_presets();
|
this->update_multi_material_filament_presets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load selections (current print, current filaments, current printer) from config.ini
|
||||||
|
// This is done just once on application start up.
|
||||||
|
void PresetBundle::load_selections(const AppConfig &config)
|
||||||
|
{
|
||||||
|
prints.select_preset_by_name(config.get("presets", "print"), true);
|
||||||
|
filaments.select_preset_by_name(config.get("presets", "filament"), true);
|
||||||
|
this->set_filament_preset(0, filaments.get_selected_preset().name);
|
||||||
|
for (int i = 1; i < 1000; ++ i) {
|
||||||
|
char name[64];
|
||||||
|
sprintf(name, "filament_%d", i);
|
||||||
|
if (! config.has("presets", name))
|
||||||
|
break;
|
||||||
|
this->set_filament_preset(i, name);
|
||||||
|
}
|
||||||
|
printers.select_preset_by_name(config.get("presets", "printer"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export selections (current print, current filaments, current printer) into config.ini
|
||||||
|
void PresetBundle::export_selections(AppConfig &config)
|
||||||
|
{
|
||||||
|
config.set("presets", "print", prints .get_selected_preset().name);
|
||||||
|
config.set("presets", "filament", filaments.get_selected_preset().name);
|
||||||
|
for (int i = 1; i < 1000; ++ i) {
|
||||||
|
char name[64];
|
||||||
|
sprintf(name, "filament_%d", i);
|
||||||
|
config.set("presets", name, filament_presets[i]);
|
||||||
|
}
|
||||||
|
config.set("presets", "printer", printers .get_selected_preset().name);
|
||||||
|
}
|
||||||
|
|
||||||
bool PresetBundle::load_compatible_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible)
|
bool PresetBundle::load_compatible_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible)
|
||||||
{
|
{
|
||||||
bool loaded_compatible = m_bitmapCompatible ->LoadFile(
|
bool loaded_compatible = m_bitmapCompatible ->LoadFile(
|
||||||
@ -579,7 +610,7 @@ std::string PresetCollection::name() const
|
|||||||
// of the local configuration directory.
|
// of the local configuration directory.
|
||||||
size_t PresetBundle::load_configbundle(const std::string &path)
|
size_t PresetBundle::load_configbundle(const std::string &path)
|
||||||
{
|
{
|
||||||
// 1) Read the complete config file into the boost::property_tree.
|
// 1) Read the complete config file into a boost::property_tree.
|
||||||
namespace pt = boost::property_tree;
|
namespace pt = boost::property_tree;
|
||||||
pt::ptree tree;
|
pt::ptree tree;
|
||||||
boost::nowide::ifstream ifs(path);
|
boost::nowide::ifstream ifs(path);
|
||||||
@ -679,13 +710,7 @@ void PresetBundle::export_configbundle(const std::string &path, const DynamicPri
|
|||||||
c.open(path, std::ios::out | std::ios::trunc);
|
c.open(path, std::ios::out | std::ios::trunc);
|
||||||
|
|
||||||
// Put a comment at the first line including the time stamp and Slic3r version.
|
// Put a comment at the first line including the time stamp and Slic3r version.
|
||||||
{
|
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||||
std::time_t now;
|
|
||||||
time(&now);
|
|
||||||
char buf[sizeof "0000-00-00 00:00:00"];
|
|
||||||
strftime(buf, sizeof(buf), "%F %T", gmtime(&now));
|
|
||||||
c << "# generated by Slic3r " << SLIC3R_VERSION << " on " << buf << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export the print, filament and printer profiles.
|
// Export the print, filament and printer profiles.
|
||||||
for (size_t i_group = 0; i_group < 3; ++ i_group) {
|
for (size_t i_group = 0; i_group < 3; ++ i_group) {
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include "../../libslic3r/libslic3r.h"
|
#include "../../libslic3r/libslic3r.h"
|
||||||
#include "../../libslic3r/PrintConfig.hpp"
|
#include "../../libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
#include "AppConfig.hpp"
|
||||||
|
|
||||||
class wxBitmap;
|
class wxBitmap;
|
||||||
class wxChoice;
|
class wxChoice;
|
||||||
class wxBitmapComboBox;
|
class wxBitmapComboBox;
|
||||||
@ -164,10 +166,6 @@ public:
|
|||||||
// Without force, the selection is only updated if the index changes.
|
// Without force, the selection is only updated if the index changes.
|
||||||
// With force, the changes are reverted if the new index is the same as the old index.
|
// With force, the changes are reverted if the new index is the same as the old index.
|
||||||
bool select_preset_by_name(const std::string &name, bool force);
|
bool select_preset_by_name(const std::string &name, bool force);
|
||||||
// Select a profile by its name, update selection at the UI component.
|
|
||||||
// Return true if the selection changed.
|
|
||||||
bool select_by_name_ui(char *name, wxItemContainer *ui);
|
|
||||||
bool select_by_name_ui(char *name, wxChoice *ui);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PresetCollection();
|
PresetCollection();
|
||||||
@ -204,9 +202,17 @@ public:
|
|||||||
PresetBundle();
|
PresetBundle();
|
||||||
~PresetBundle();
|
~PresetBundle();
|
||||||
|
|
||||||
|
void setup_directories();
|
||||||
|
|
||||||
// Load ini files of all types (print, filament, printer) from the provided directory path.
|
// Load ini files of all types (print, filament, printer) from the provided directory path.
|
||||||
void load_presets(const std::string &dir_path);
|
void load_presets(const std::string &dir_path);
|
||||||
|
|
||||||
|
// Load selections (current print, current filaments, current printer) from config.ini
|
||||||
|
// This is done just once on application start up.
|
||||||
|
void load_selections(const AppConfig &config);
|
||||||
|
// Export selections (current print, current filaments, current printer) into config.ini
|
||||||
|
void export_selections(AppConfig &config);
|
||||||
|
|
||||||
PresetCollection prints;
|
PresetCollection prints;
|
||||||
PresetCollection filaments;
|
PresetCollection filaments;
|
||||||
PresetCollection printers;
|
PresetCollection printers;
|
||||||
|
@ -51,8 +51,6 @@
|
|||||||
%code%{ RETVAL = THIS->update_dirty_ui((wxChoice*)wxPli_sv_2_object(aTHX_ ui, "Wx::Choice")); %};
|
%code%{ RETVAL = THIS->update_dirty_ui((wxChoice*)wxPli_sv_2_object(aTHX_ ui, "Wx::Choice")); %};
|
||||||
|
|
||||||
bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %};
|
bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %};
|
||||||
bool select_by_name_ui(char *name, SV *ui)
|
|
||||||
%code%{ RETVAL = THIS->select_by_name_ui(name, (wxChoice*)wxPli_sv_2_object(aTHX_ ui, "Wx::Choice")); %};
|
|
||||||
|
|
||||||
void save_current_preset(char *new_name);
|
void save_current_preset(char *new_name);
|
||||||
void delete_current_preset();
|
void delete_current_preset();
|
||||||
@ -93,10 +91,14 @@ PresetCollection::presets_hash()
|
|||||||
PresetBundle();
|
PresetBundle();
|
||||||
~PresetBundle();
|
~PresetBundle();
|
||||||
|
|
||||||
|
void setup_directories();
|
||||||
void load_presets(const char *dir_path);
|
void load_presets(const char *dir_path);
|
||||||
size_t load_configbundle(const char *path);
|
size_t load_configbundle(const char *path);
|
||||||
void set_default_suppressed(bool default_suppressed);
|
void set_default_suppressed(bool default_suppressed);
|
||||||
|
|
||||||
|
void load_selections (AppConfig *config) %code%{ THIS->load_selections(*config); %};
|
||||||
|
void export_selections(AppConfig *config) %code%{ THIS->export_selections(*config); %};
|
||||||
|
|
||||||
Ref<PresetCollection> print() %code%{ RETVAL = &THIS->prints; %};
|
Ref<PresetCollection> print() %code%{ RETVAL = &THIS->prints; %};
|
||||||
Ref<PresetCollection> filament() %code%{ RETVAL = &THIS->filaments; %};
|
Ref<PresetCollection> filament() %code%{ RETVAL = &THIS->filaments; %};
|
||||||
Ref<PresetCollection> printer() %code%{ RETVAL = &THIS->printers; %};
|
Ref<PresetCollection> printer() %code%{ RETVAL = &THIS->printers; %};
|
||||||
|
@ -210,6 +210,9 @@ PrintObjectSupportMaterial* O_OBJECT_SLIC3R
|
|||||||
Ref<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
|
Ref<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
|
||||||
Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
|
Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T
|
||||||
|
|
||||||
|
AppConfig* O_OBJECT_SLIC3R
|
||||||
|
Ref<AppConfig> O_OBJECT_SLIC3R_T
|
||||||
|
|
||||||
GLShader* O_OBJECT_SLIC3R
|
GLShader* O_OBJECT_SLIC3R
|
||||||
Ref<GLShader> O_OBJECT_SLIC3R_T
|
Ref<GLShader> O_OBJECT_SLIC3R_T
|
||||||
GLVolume* O_OBJECT_SLIC3R
|
GLVolume* O_OBJECT_SLIC3R
|
||||||
|
@ -191,6 +191,8 @@
|
|||||||
%typemap{ModelInstancePtrs*};
|
%typemap{ModelInstancePtrs*};
|
||||||
%typemap{Ref<ModelInstancePtrs>}{simple};
|
%typemap{Ref<ModelInstancePtrs>}{simple};
|
||||||
%typemap{Clone<ModelInstancePtrs>}{simple};
|
%typemap{Clone<ModelInstancePtrs>}{simple};
|
||||||
|
%typemap{AppConfig*};
|
||||||
|
%typemap{Ref<AppConfig>}{simple};
|
||||||
%typemap{GLShader*};
|
%typemap{GLShader*};
|
||||||
%typemap{Ref<GLShader>}{simple};
|
%typemap{Ref<GLShader>}{simple};
|
||||||
%typemap{GLVolume*};
|
%typemap{GLVolume*};
|
||||||
|
Loading…
Reference in New Issue
Block a user