mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 21:10:04 +00:00
Make placefile settings view editable
This commit is contained in:
parent
3ff34caa02
commit
7c21ccaf41
6 changed files with 234 additions and 77 deletions
|
|
@ -66,7 +66,7 @@ public:
|
||||||
PlacefileManager::PlacefileManager() : p(std::make_unique<Impl>(this)) {}
|
PlacefileManager::PlacefileManager() : p(std::make_unique<Impl>(this)) {}
|
||||||
PlacefileManager::~PlacefileManager() = default;
|
PlacefileManager::~PlacefileManager() = default;
|
||||||
|
|
||||||
bool PlacefileManager::PlacefileEnabled(const std::string& name)
|
bool PlacefileManager::placefile_enabled(const std::string& name)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(p->placefileRecordLock_);
|
std::shared_lock lock(p->placefileRecordLock_);
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ bool PlacefileManager::PlacefileEnabled(const std::string& name)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlacefileManager::PlacefileThresholded(const std::string& name)
|
bool PlacefileManager::placefile_thresholded(const std::string& name)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(p->placefileRecordLock_);
|
std::shared_lock lock(p->placefileRecordLock_);
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ bool PlacefileManager::PlacefileThresholded(const std::string& name)
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<const gr::Placefile>
|
std::shared_ptr<const gr::Placefile>
|
||||||
PlacefileManager::Placefile(const std::string& name)
|
PlacefileManager::placefile(const std::string& name)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(p->placefileRecordLock_);
|
std::shared_lock lock(p->placefileRecordLock_);
|
||||||
|
|
||||||
|
|
@ -103,6 +103,60 @@ PlacefileManager::Placefile(const std::string& name)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlacefileManager::set_placefile_enabled(const std::string& name,
|
||||||
|
bool enabled)
|
||||||
|
{
|
||||||
|
std::shared_lock lock(p->placefileRecordLock_);
|
||||||
|
|
||||||
|
auto it = p->placefileRecordMap_.find(name);
|
||||||
|
if (it != p->placefileRecordMap_.cend())
|
||||||
|
{
|
||||||
|
it->second->enabled_ = enabled;
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
Q_EMIT PlacefileEnabled(name, enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlacefileManager::set_placefile_thresholded(const std::string& name,
|
||||||
|
bool thresholded)
|
||||||
|
{
|
||||||
|
std::shared_lock lock(p->placefileRecordLock_);
|
||||||
|
|
||||||
|
auto it = p->placefileRecordMap_.find(name);
|
||||||
|
if (it != p->placefileRecordMap_.cend())
|
||||||
|
{
|
||||||
|
it->second->thresholded_ = thresholded;
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
Q_EMIT PlacefileUpdated(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlacefileManager::set_placefile_url(const std::string& name,
|
||||||
|
const std::string& newUrl)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(p->placefileRecordLock_);
|
||||||
|
|
||||||
|
auto it = p->placefileRecordMap_.find(name);
|
||||||
|
auto itNew = p->placefileRecordMap_.find(newUrl);
|
||||||
|
if (it != p->placefileRecordMap_.cend() &&
|
||||||
|
itNew == p->placefileRecordMap_.cend())
|
||||||
|
{
|
||||||
|
auto placefileRecord = it->second;
|
||||||
|
placefileRecord->name_ = newUrl;
|
||||||
|
placefileRecord->placefile_ = nullptr;
|
||||||
|
p->placefileRecordMap_.erase(it);
|
||||||
|
p->placefileRecordMap_.emplace(newUrl, placefileRecord);
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
Q_EMIT PlacefileRenamed(name, newUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::shared_ptr<gr::Placefile>>
|
std::vector<std::shared_ptr<gr::Placefile>>
|
||||||
PlacefileManager::GetActivePlacefiles()
|
PlacefileManager::GetActivePlacefiles()
|
||||||
{
|
{
|
||||||
|
|
@ -112,7 +166,7 @@ PlacefileManager::GetActivePlacefiles()
|
||||||
|
|
||||||
for (const auto& record : p->placefileRecords_)
|
for (const auto& record : p->placefileRecords_)
|
||||||
{
|
{
|
||||||
if (record->enabled_)
|
if (record->enabled_ && record->placefile_ != nullptr)
|
||||||
{
|
{
|
||||||
placefiles.emplace_back(record->placefile_);
|
placefiles.emplace_back(record->placefile_);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,13 @@ public:
|
||||||
explicit PlacefileManager();
|
explicit PlacefileManager();
|
||||||
~PlacefileManager();
|
~PlacefileManager();
|
||||||
|
|
||||||
bool PlacefileEnabled(const std::string& name);
|
bool placefile_enabled(const std::string& name);
|
||||||
bool PlacefileThresholded(const std::string& name);
|
bool placefile_thresholded(const std::string& name);
|
||||||
std::shared_ptr<const gr::Placefile> Placefile(const std::string& name);
|
std::shared_ptr<const gr::Placefile> placefile(const std::string& name);
|
||||||
|
|
||||||
|
void set_placefile_enabled(const std::string& name, bool enabled);
|
||||||
|
void set_placefile_thresholded(const std::string& name, bool thresholded);
|
||||||
|
void set_placefile_url(const std::string& name, const std::string& newUrl);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets a list of active placefiles
|
* @brief Gets a list of active placefiles
|
||||||
|
|
@ -37,6 +41,8 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void PlacefileEnabled(const std::string& name, bool enabled);
|
void PlacefileEnabled(const std::string& name, bool enabled);
|
||||||
|
void PlacefileRenamed(const std::string& oldName,
|
||||||
|
const std::string& newName);
|
||||||
void PlacefileUpdated(const std::string& name);
|
void PlacefileUpdated(const std::string& name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,16 @@ public:
|
||||||
PlacefileModel::PlacefileModel(QObject* parent) :
|
PlacefileModel::PlacefileModel(QObject* parent) :
|
||||||
QAbstractTableModel(parent), p(std::make_unique<PlacefileModelImpl>())
|
QAbstractTableModel(parent), p(std::make_unique<PlacefileModelImpl>())
|
||||||
{
|
{
|
||||||
|
connect(p->placefileManager_.get(),
|
||||||
|
&manager::PlacefileManager::PlacefileEnabled,
|
||||||
|
this,
|
||||||
|
&PlacefileModel::HandlePlacefileUpdate);
|
||||||
|
|
||||||
|
connect(p->placefileManager_.get(),
|
||||||
|
&manager::PlacefileManager::PlacefileRenamed,
|
||||||
|
this,
|
||||||
|
&PlacefileModel::HandlePlacefileRenamed);
|
||||||
|
|
||||||
connect(p->placefileManager_.get(),
|
connect(p->placefileManager_.get(),
|
||||||
&manager::PlacefileManager::PlacefileUpdated,
|
&manager::PlacefileManager::PlacefileUpdated,
|
||||||
this,
|
this,
|
||||||
|
|
@ -65,7 +75,12 @@ Qt::ItemFlags PlacefileModel::flags(const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
case static_cast<int>(Column::Enabled):
|
case static_cast<int>(Column::Enabled):
|
||||||
case static_cast<int>(Column::Thresholds):
|
case static_cast<int>(Column::Thresholds):
|
||||||
flags |= Qt::ItemFlag::ItemIsUserCheckable;
|
flags |= Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case static_cast<int>(Column::Placefile):
|
||||||
|
flags |= Qt::ItemFlag::ItemIsEditable;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -76,6 +91,14 @@ Qt::ItemFlags PlacefileModel::flags(const QModelIndex& index) const
|
||||||
|
|
||||||
QVariant PlacefileModel::data(const QModelIndex& index, int role) const
|
QVariant PlacefileModel::data(const QModelIndex& index, int role) const
|
||||||
{
|
{
|
||||||
|
static const QString enabledString = QObject::tr("Enabled");
|
||||||
|
static const QString disabledString = QObject::tr("Disabled");
|
||||||
|
|
||||||
|
static const QString thresholdsEnabledString =
|
||||||
|
QObject::tr("Thresholds Enabled");
|
||||||
|
static const QString thresholdsDisabledString =
|
||||||
|
QObject::tr("Thresholds Disabled");
|
||||||
|
|
||||||
if (!index.isValid() || index.row() < 0 ||
|
if (!index.isValid() || index.row() < 0 ||
|
||||||
static_cast<std::size_t>(index.row()) >= p->placefileNames_.size())
|
static_cast<std::size_t>(index.row()) >= p->placefileNames_.size())
|
||||||
{
|
{
|
||||||
|
|
@ -84,41 +107,54 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const
|
||||||
|
|
||||||
const auto& placefileName = p->placefileNames_.at(index.row());
|
const auto& placefileName = p->placefileNames_.at(index.row());
|
||||||
|
|
||||||
if (role == Qt::ItemDataRole::DisplayRole ||
|
switch (index.column())
|
||||||
role == Qt::ItemDataRole::ToolTipRole)
|
|
||||||
{
|
{
|
||||||
static const QString enabledString = QObject::tr("Enabled");
|
case static_cast<int>(Column::Enabled):
|
||||||
static const QString disabledString = QObject::tr("Disabled");
|
if (role == Qt::ItemDataRole::ToolTipRole)
|
||||||
|
|
||||||
static const QString thresholdsEnabledString =
|
|
||||||
QObject::tr("Thresholds Enabled");
|
|
||||||
static const QString thresholdsDisabledString =
|
|
||||||
QObject::tr("Thresholds Disabled");
|
|
||||||
|
|
||||||
switch (index.column())
|
|
||||||
{
|
{
|
||||||
case static_cast<int>(Column::Enabled):
|
return p->placefileManager_->placefile_enabled(placefileName) ?
|
||||||
if (role == Qt::ItemDataRole::ToolTipRole)
|
enabledString :
|
||||||
{
|
disabledString;
|
||||||
return p->placefileManager_->PlacefileEnabled(placefileName) ?
|
}
|
||||||
enabledString :
|
else if (role == Qt::ItemDataRole::CheckStateRole)
|
||||||
disabledString;
|
{
|
||||||
}
|
return static_cast<int>(
|
||||||
break;
|
p->placefileManager_->placefile_enabled(placefileName) ?
|
||||||
|
Qt::CheckState::Checked :
|
||||||
|
Qt::CheckState::Unchecked);
|
||||||
|
}
|
||||||
|
else if (role == types::ItemDataRole::SortRole)
|
||||||
|
{
|
||||||
|
return p->placefileManager_->placefile_enabled(placefileName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case static_cast<int>(Column::Thresholds):
|
case static_cast<int>(Column::Thresholds):
|
||||||
if (role == Qt::ItemDataRole::ToolTipRole)
|
if (role == Qt::ItemDataRole::ToolTipRole)
|
||||||
{
|
{
|
||||||
return p->placefileManager_->PlacefileThresholded(placefileName) ?
|
return p->placefileManager_->placefile_thresholded(placefileName) ?
|
||||||
thresholdsEnabledString :
|
thresholdsEnabledString :
|
||||||
thresholdsDisabledString;
|
thresholdsDisabledString;
|
||||||
}
|
}
|
||||||
break;
|
else if (role == Qt::ItemDataRole::CheckStateRole)
|
||||||
|
{
|
||||||
|
return static_cast<int>(
|
||||||
|
p->placefileManager_->placefile_thresholded(placefileName) ?
|
||||||
|
Qt::CheckState::Checked :
|
||||||
|
Qt::CheckState::Unchecked);
|
||||||
|
}
|
||||||
|
else if (role == types::ItemDataRole::SortRole)
|
||||||
|
{
|
||||||
|
return p->placefileManager_->placefile_thresholded(placefileName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case static_cast<int>(Column::Placefile):
|
case static_cast<int>(Column::Placefile):
|
||||||
|
if (role == Qt::ItemDataRole::DisplayRole ||
|
||||||
|
role == Qt::ItemDataRole::ToolTipRole)
|
||||||
{
|
{
|
||||||
std::string description = placefileName;
|
std::string description = placefileName;
|
||||||
auto placefile = p->placefileManager_->Placefile(placefileName);
|
auto placefile = p->placefileManager_->placefile(placefileName);
|
||||||
if (placefile != nullptr)
|
if (placefile != nullptr)
|
||||||
{
|
{
|
||||||
std::string title = placefile->title();
|
std::string title = placefile->title();
|
||||||
|
|
@ -130,47 +166,15 @@ QVariant PlacefileModel::data(const QModelIndex& index, int role) const
|
||||||
|
|
||||||
return QString::fromStdString(description);
|
return QString::fromStdString(description);
|
||||||
}
|
}
|
||||||
|
else if (role == Qt::ItemDataRole::EditRole ||
|
||||||
default:
|
role == types::ItemDataRole::SortRole)
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (role == types::ItemDataRole::SortRole)
|
|
||||||
{
|
|
||||||
switch (index.column())
|
|
||||||
{
|
{
|
||||||
case static_cast<int>(Column::Enabled):
|
|
||||||
return p->placefileManager_->PlacefileEnabled(placefileName);
|
|
||||||
|
|
||||||
case static_cast<int>(Column::Thresholds):
|
|
||||||
return p->placefileManager_->PlacefileThresholded(placefileName);
|
|
||||||
|
|
||||||
case static_cast<int>(Column::Placefile):
|
|
||||||
return QString::fromStdString(placefileName);
|
return QString::fromStdString(placefileName);
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else if (role == Qt::ItemDataRole::CheckStateRole)
|
|
||||||
{
|
|
||||||
switch (index.column())
|
|
||||||
{
|
|
||||||
case static_cast<int>(Column::Enabled):
|
|
||||||
return static_cast<int>(
|
|
||||||
p->placefileManager_->PlacefileEnabled(placefileName) ?
|
|
||||||
Qt::CheckState::Checked :
|
|
||||||
Qt::CheckState::Unchecked);
|
|
||||||
|
|
||||||
case static_cast<int>(Column::Thresholds):
|
default:
|
||||||
return static_cast<int>(
|
break;
|
||||||
p->placefileManager_->PlacefileThresholded(placefileName) ?
|
|
||||||
Qt::CheckState::Checked :
|
|
||||||
Qt::CheckState::Unchecked);
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
@ -240,6 +244,82 @@ QVariant PlacefileModel::headerData(int section,
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PlacefileModel::setData(const QModelIndex& index,
|
||||||
|
const QVariant& value,
|
||||||
|
int role)
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 ||
|
||||||
|
static_cast<std::size_t>(index.row()) >= p->placefileNames_.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& placefileName = p->placefileNames_.at(index.row());
|
||||||
|
|
||||||
|
switch (index.column())
|
||||||
|
{
|
||||||
|
case static_cast<int>(Column::Enabled):
|
||||||
|
if (role == Qt::ItemDataRole::CheckStateRole)
|
||||||
|
{
|
||||||
|
p->placefileManager_->set_placefile_enabled(placefileName,
|
||||||
|
value.toBool());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case static_cast<int>(Column::Thresholds):
|
||||||
|
if (role == Qt::ItemDataRole::CheckStateRole)
|
||||||
|
{
|
||||||
|
p->placefileManager_->set_placefile_thresholded(placefileName,
|
||||||
|
value.toBool());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case static_cast<int>(Column::Placefile):
|
||||||
|
if (role == Qt::ItemDataRole::EditRole)
|
||||||
|
{
|
||||||
|
p->placefileManager_->set_placefile_url(
|
||||||
|
placefileName, value.toString().toStdString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlacefileModel::HandlePlacefileRenamed(const std::string& oldName,
|
||||||
|
const std::string& newName)
|
||||||
|
{
|
||||||
|
auto it =
|
||||||
|
std::find(p->placefileNames_.begin(), p->placefileNames_.end(), oldName);
|
||||||
|
|
||||||
|
if (it != p->placefileNames_.end())
|
||||||
|
{
|
||||||
|
// Placefile exists, mark row as updated
|
||||||
|
const int row = std::distance(p->placefileNames_.begin(), it);
|
||||||
|
QModelIndex topLeft = createIndex(row, kFirstColumn);
|
||||||
|
QModelIndex bottomRight = createIndex(row, kLastColumn);
|
||||||
|
|
||||||
|
// Rename placefile
|
||||||
|
*it = newName;
|
||||||
|
|
||||||
|
Q_EMIT dataChanged(topLeft, bottomRight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Placefile is new, append row
|
||||||
|
const int newIndex = static_cast<int>(p->placefileNames_.size());
|
||||||
|
beginInsertRows(QModelIndex(), newIndex, newIndex);
|
||||||
|
p->placefileNames_.push_back(newName);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PlacefileModel::HandlePlacefileUpdate(const std::string& name)
|
void PlacefileModel::HandlePlacefileUpdate(const std::string& name)
|
||||||
{
|
{
|
||||||
auto it =
|
auto it =
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,13 @@ public:
|
||||||
Qt::Orientation orientation,
|
Qt::Orientation orientation,
|
||||||
int role = Qt::DisplayRole) const override;
|
int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
bool setData(const QModelIndex& index,
|
||||||
|
const QVariant& value,
|
||||||
|
int role = Qt::EditRole) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void HandlePlacefileRenamed(const std::string& oldName,
|
||||||
|
const std::string& newName);
|
||||||
void HandlePlacefileUpdate(const std::string& name);
|
void HandlePlacefileUpdate(const std::string& name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -84,12 +84,14 @@ public:
|
||||||
*/
|
*/
|
||||||
std::vector<std::shared_ptr<DrawItem>> GetDrawItems();
|
std::vector<std::shared_ptr<DrawItem>> GetDrawItems();
|
||||||
|
|
||||||
|
std::string name() const;
|
||||||
std::string title() const;
|
std::string title() const;
|
||||||
std::unordered_map<std::size_t, std::shared_ptr<Font>> fonts();
|
std::unordered_map<std::size_t, std::shared_ptr<Font>> fonts();
|
||||||
std::shared_ptr<Font> font(std::size_t i);
|
std::shared_ptr<Font> font(std::size_t i);
|
||||||
|
|
||||||
static std::shared_ptr<Placefile> Load(const std::string& filename);
|
static std::shared_ptr<Placefile> Load(const std::string& filename);
|
||||||
static std::shared_ptr<Placefile> Load(std::istream& is);
|
static std::shared_ptr<Placefile> Load(const std::string& name,
|
||||||
|
std::istream& is);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class Impl;
|
class Impl;
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ public:
|
||||||
static void ProcessEscapeCharacters(std::string& s);
|
static void ProcessEscapeCharacters(std::string& s);
|
||||||
static void TrimQuotes(std::string& s);
|
static void TrimQuotes(std::string& s);
|
||||||
|
|
||||||
|
std::string name_ {};
|
||||||
std::string title_ {};
|
std::string title_ {};
|
||||||
std::chrono::seconds refresh_ {-1};
|
std::chrono::seconds refresh_ {-1};
|
||||||
|
|
||||||
|
|
@ -85,6 +86,11 @@ std::vector<std::shared_ptr<Placefile::DrawItem>> Placefile::GetDrawItems()
|
||||||
return p->drawItems_;
|
return p->drawItems_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Placefile::name() const
|
||||||
|
{
|
||||||
|
return p->name_;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Placefile::title() const
|
std::string Placefile::title() const
|
||||||
{
|
{
|
||||||
return p->title_;
|
return p->title_;
|
||||||
|
|
@ -110,13 +116,16 @@ std::shared_ptr<Placefile> Placefile::Load(const std::string& filename)
|
||||||
{
|
{
|
||||||
logger_->debug("Loading placefile: {}", filename);
|
logger_->debug("Loading placefile: {}", filename);
|
||||||
std::ifstream f(filename, std::ios_base::in);
|
std::ifstream f(filename, std::ios_base::in);
|
||||||
return Load(f);
|
return Load(filename, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Placefile> Placefile::Load(std::istream& is)
|
std::shared_ptr<Placefile> Placefile::Load(const std::string& name,
|
||||||
|
std::istream& is)
|
||||||
{
|
{
|
||||||
std::shared_ptr<Placefile> placefile = std::make_shared<Placefile>();
|
std::shared_ptr<Placefile> placefile = std::make_shared<Placefile>();
|
||||||
|
|
||||||
|
placefile->p->name_ = name;
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
while (scwx::util::getline(is, line))
|
while (scwx::util::getline(is, line))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue