mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2025-02-20 10:54:14 +01:00
Merge branch 'filnet/OP-843_settings_dialog_show_language_selection_at_top' into next
Conflicts: ground/openpilotgcs/src/plugins/coreplugin/dialogs/settingsdialog.cpp
This commit is contained in:
commit
664e79c9e0
@ -5,13 +5,16 @@
|
||||
<Description>Default configuration</Description>
|
||||
<Details>Default configuration built to work on all screen sizes</Details>
|
||||
<ExpertMode>false</ExpertMode>
|
||||
<LastPreferenceCategory></LastPreferenceCategory>
|
||||
<LastPreferencePage></LastPreferencePage>
|
||||
<SaveSettingsOnExit>true</SaveSettingsOnExit>
|
||||
<SettingsWindowHeight>600</SettingsWindowHeight>
|
||||
<SettingsWindowWidth>800</SettingsWindowWidth>
|
||||
<StyleSheet>default</StyleSheet>
|
||||
<UDPMirror>false</UDPMirror>
|
||||
<Settings>
|
||||
<LastPreferenceCategory>Environment</LastPreferenceCategory>
|
||||
<LastPreferencePage>General</LastPreferencePage>
|
||||
<WindowHeight>600</WindowHeight>
|
||||
<WindowWidth>800</WindowWidth>
|
||||
<SplitterPosition>150</SplitterPosition>
|
||||
</Settings>
|
||||
</General>
|
||||
<IPconnection>
|
||||
<Current>
|
||||
|
@ -39,8 +39,10 @@ class QWidget;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Core {
|
||||
|
||||
class CORE_EXPORT IOptionsPage : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
IOptionsPage(QObject *parent = 0) :
|
||||
QObject(parent),
|
||||
@ -51,6 +53,7 @@ public:
|
||||
{
|
||||
m_icon = icon;
|
||||
}
|
||||
|
||||
QIcon icon()
|
||||
{
|
||||
return m_icon;
|
||||
@ -63,25 +66,36 @@ public:
|
||||
{
|
||||
return "";
|
||||
};
|
||||
|
||||
virtual QString trName() const
|
||||
{
|
||||
return "";
|
||||
};
|
||||
|
||||
virtual QString category() const
|
||||
{
|
||||
return "DefaultCategory";
|
||||
};
|
||||
|
||||
virtual QString trCategory() const
|
||||
{
|
||||
return "DefaultCategory";
|
||||
};
|
||||
|
||||
virtual QWidget *createPage(QWidget *parent) = 0;
|
||||
|
||||
virtual void apply() = 0;
|
||||
virtual void finish() = 0;
|
||||
|
||||
public slots:
|
||||
virtual void updateState()
|
||||
{
|
||||
};
|
||||
|
||||
private:
|
||||
QIcon m_icon;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
#endif // IOPTIONSPAGE_H
|
||||
|
@ -99,6 +99,7 @@ Q_DECLARE_METATYPE(::PageData) SettingsDialog::SettingsDialog(QWidget *parent, c
|
||||
QSettings *settings = ICore::instance()->settings();
|
||||
|
||||
settings->beginGroup("General");
|
||||
settings->beginGroup("Settings");
|
||||
|
||||
// restore last displayed category and page
|
||||
// this is done only if no category or page was provided through the constructor
|
||||
@ -112,30 +113,26 @@ Q_DECLARE_METATYPE(::PageData) SettingsDialog::SettingsDialog(QWidget *parent, c
|
||||
}
|
||||
|
||||
// restore window size
|
||||
int windowWidth = settings->value("SettingsWindowWidth", 0).toInt();
|
||||
int windowHeight = settings->value("SettingsWindowHeight", 0).toInt();
|
||||
int windowWidth = settings->value("WindowWidth", 0).toInt();
|
||||
int windowHeight = settings->value("WindowHeight", 0).toInt();
|
||||
qDebug() << "SettingsDialog window width :" << windowWidth << ", height:" << windowHeight;
|
||||
if (windowWidth > 0 && windowHeight > 0) {
|
||||
resize(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
// restore splitter size
|
||||
int size0 = settings->value("SettingsSplitterSize0", 0).toInt();
|
||||
int size1 = settings->value("SettingsSplitterSize1", 0).toInt();
|
||||
qDebug() << "SettingsDialog splitter size0:" << size0 << ", size1:" << size1;
|
||||
int splitterPosition = settings->value("SplitterPosition", 350).toInt();
|
||||
qDebug() << "SettingsDialog splitter position:" << splitterPosition;
|
||||
QList<int> sizes;
|
||||
if (size0 > 0 && size1 > 0) {
|
||||
sizes << size0 << size1;
|
||||
} else {
|
||||
sizes << 150 << 300;
|
||||
}
|
||||
sizes << splitterPosition << 400;
|
||||
splitter->setSizes(sizes);
|
||||
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
|
||||
// all extra space must go to the option page and none to the tree
|
||||
splitter->setStretchFactor(splitter->indexOf(pageTree), 0);
|
||||
splitter->setStretchFactor(splitter->indexOf(layoutWidget), 1);
|
||||
splitter->setStretchFactor(0, 0);
|
||||
splitter->setStretchFactor(1, 1);
|
||||
|
||||
buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
|
||||
|
||||
@ -146,14 +143,15 @@ Q_DECLARE_METATYPE(::PageData) SettingsDialog::SettingsDialog(QWidget *parent, c
|
||||
connect(this, SIGNAL(settingsDialogShown(Core::Internal::SettingsDialog *)), m_instanceManager,
|
||||
SLOT(settingsDialogShown(Core::Internal::SettingsDialog *)));
|
||||
connect(this, SIGNAL(settingsDialogRemoved()), m_instanceManager, SLOT(settingsDialogRemoved()));
|
||||
connect(this, SIGNAL(categoryItemSelected()), this, SLOT(categoryItemSelectedShowChildInstead()),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
// needs to be queued to be able to change the selection from the selection change signal call
|
||||
connect(this, SIGNAL(categoryItemSelected()), this, SLOT(onCategorySelected()), Qt::QueuedConnection);
|
||||
|
||||
splitter->setCollapsible(0, false);
|
||||
splitter->setCollapsible(1, false);
|
||||
pageTree->header()->setVisible(false);
|
||||
|
||||
connect(pageTree, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(pageSelected()));
|
||||
connect(pageTree, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(onItemSelected()));
|
||||
|
||||
QList<Core::IOptionsPage *> pluginPages;
|
||||
QList<Core::IOptionsPage *> gadgetPages;
|
||||
@ -168,27 +166,27 @@ Q_DECLARE_METATYPE(::PageData) SettingsDialog::SettingsDialog(QWidget *parent, c
|
||||
}
|
||||
}
|
||||
|
||||
// the plugin options page list sorted by untranslated names to facilitate access to the language settings when GCS
|
||||
// is not running in a language understood by the user.
|
||||
// the plugin options page list is sorted by untranslated category and names
|
||||
// this is done to facilitate access to the language settings when GCS is not running in a language understood by the user.
|
||||
qStableSort(pluginPages.begin(), pluginPages.end(), compareOptionsPageByCategoryAndId);
|
||||
// the plugin options page list sorted is sorted by translated names
|
||||
|
||||
// the plugin options page list is sorted by translated names
|
||||
qStableSort(gadgetPages.begin(), gadgetPages.end(), compareOptionsPageByCategoryAndNameTr);
|
||||
|
||||
// will hold the initially selected item if any
|
||||
QTreeWidgetItem *initialItem = 0;
|
||||
|
||||
// add plugin pages
|
||||
foreach(IOptionsPage * page, pluginPages) {
|
||||
foreach(IOptionsPage *page, pluginPages) {
|
||||
QTreeWidgetItem *item = addPage(page);
|
||||
|
||||
// to automatically expand all plugin categories, uncomment next line
|
||||
// item->parent()->setExpanded(true);
|
||||
// automatically expand all plugin categories
|
||||
item->parent()->setExpanded(true);
|
||||
if (page->id() == initialPage && page->category() == initialCategory) {
|
||||
initialItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
// insert separator bewteen plugin and gadget pages
|
||||
// insert separator between plugin and gadget pages
|
||||
QTreeWidgetItem *separator = new QTreeWidgetItem(pageTree);
|
||||
separator->setFlags(separator->flags() & ~Qt::ItemIsSelectable & ~Qt::ItemIsEnabled);
|
||||
separator->setText(0, QString(30, 0xB7));
|
||||
@ -288,18 +286,31 @@ QTreeWidgetItem *SettingsDialog::addPage(IOptionsPage *page)
|
||||
return item;
|
||||
}
|
||||
|
||||
void SettingsDialog::pageSelected()
|
||||
void SettingsDialog::onItemSelected()
|
||||
{
|
||||
QTreeWidgetItem *item = pageTree->currentItem();
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageTree->indexOfTopLevelItem(item) >= 0) {
|
||||
if (item->childCount() == 1) {
|
||||
// single child : category will not be expanded
|
||||
item = item->child(0);
|
||||
}
|
||||
else if (item->childCount() > 1) {
|
||||
// multiple children : expand category and select 1st child
|
||||
emit categoryItemSelected();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// get user data
|
||||
PageData data = item->data(0, Qt::UserRole).value<PageData>();
|
||||
int index = data.index;
|
||||
m_currentCategory = data.category;
|
||||
m_currentPage = data.id;
|
||||
|
||||
// check if we are looking at a place holder or not
|
||||
QWidget *widget = dynamic_cast<QLabel *>(stackedPages->widget(index));
|
||||
if (widget) {
|
||||
@ -310,25 +321,26 @@ void SettingsDialog::pageSelected()
|
||||
IOptionsPage *page = m_pages.at(index);
|
||||
stackedPages->insertWidget(index, page->createPage(stackedPages));
|
||||
}
|
||||
|
||||
IOptionsPage *page = m_pages.at(index);
|
||||
page->updateState();
|
||||
|
||||
stackedPages->setCurrentIndex(index);
|
||||
// If user selects a toplevel item, select the first child for them
|
||||
// I.e. Top level items are not really selectable
|
||||
if ((pageTree->indexOfTopLevelItem(item) >= 0) && (item->childCount() > 0)) {
|
||||
emit categoryItemSelected();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::categoryItemSelectedShowChildInstead()
|
||||
void SettingsDialog::onCategorySelected()
|
||||
{
|
||||
QTreeWidgetItem *item = pageTree->currentItem();
|
||||
|
||||
item->setExpanded(true);
|
||||
pageTree->setCurrentItem(item->child(0), 0, QItemSelectionModel::SelectCurrent);
|
||||
if (item->childCount() > 1) {
|
||||
item->setExpanded(true);
|
||||
pageTree->setCurrentItem(item->child(0), 0, QItemSelectionModel::SelectCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::deletePage()
|
||||
{
|
||||
QTreeWidgetItem *item = pageTree->currentItem();
|
||||
|
||||
PageData data = item->data(0, Qt::UserRole).value<PageData>();
|
||||
QString category = data.category;
|
||||
|
||||
@ -339,9 +351,9 @@ void SettingsDialog::deletePage()
|
||||
parentItem->removeChild(item);
|
||||
if (parentItem->childCount() == 1) {
|
||||
parentItem->child(0)->setHidden(true);
|
||||
pageTree->setCurrentItem(parentItem, 0, QItemSelectionModel::SelectCurrent);
|
||||
}
|
||||
}
|
||||
pageSelected();
|
||||
}
|
||||
|
||||
// TODO duplicates a lot of the addPage code...
|
||||
@ -453,16 +465,18 @@ void SettingsDialog::done(int val)
|
||||
QSettings *settings = ICore::instance()->settings();
|
||||
|
||||
settings->beginGroup("General");
|
||||
settings->beginGroup("Settings");
|
||||
|
||||
settings->setValue("LastPreferenceCategory", m_currentCategory);
|
||||
settings->setValue("LastPreferencePage", m_currentPage);
|
||||
settings->setValue("SettingsWindowWidth", this->width());
|
||||
settings->setValue("SettingsWindowHeight", this->height());
|
||||
QList<int> sizes = splitter->sizes();
|
||||
qDebug() << "SettingsDialog splitter saving size0:" << sizes[0] << ", size1:" << sizes[1];
|
||||
settings->setValue("SettingsSplitterSize0", sizes[0]);
|
||||
settings->setValue("SettingsSplitterSize1", sizes[1]);
|
||||
|
||||
settings->setValue("WindowWidth", this->width());
|
||||
settings->setValue("WindowHeight", this->height());
|
||||
|
||||
QList<int> sizes = splitter->sizes();
|
||||
settings->setValue("SplitterPosition", sizes[0]);
|
||||
|
||||
settings->endGroup();
|
||||
settings->endGroup();
|
||||
|
||||
QDialog::done(val);
|
||||
|
@ -62,11 +62,11 @@ public slots:
|
||||
void done(int);
|
||||
|
||||
private slots:
|
||||
void pageSelected();
|
||||
void onItemSelected();
|
||||
void onCategorySelected();
|
||||
void accept();
|
||||
void reject();
|
||||
void apply();
|
||||
void categoryItemSelectedShowChildInstead();
|
||||
|
||||
private:
|
||||
QList<Core::IOptionsPage *> m_pages;
|
||||
|
@ -303,23 +303,31 @@ void UAVGadgetInstanceManager::removeAllGadgets()
|
||||
}
|
||||
|
||||
|
||||
bool UAVGadgetInstanceManager::canDeleteConfiguration(IUAVGadgetConfiguration *config)
|
||||
bool UAVGadgetInstanceManager::isConfigurationActive(IUAVGadgetConfiguration *config)
|
||||
{
|
||||
// to be able to delete a configuration, no instance must be using it
|
||||
// check if there is gadget currently using the configuration
|
||||
foreach(IUAVGadget * gadget, m_gadgetInstances) {
|
||||
if (gadget->activeConfiguration() == config) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// and it cannot be the only configuration
|
||||
foreach(IUAVGadgetConfiguration * c, m_configurations) {
|
||||
if (c != config && c->classId() == config->classId()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UAVGadgetInstanceManager::DeleteStatus UAVGadgetInstanceManager::canDeleteConfiguration(IUAVGadgetConfiguration *config)
|
||||
{
|
||||
// to be able to delete a configuration, no instance must be using it
|
||||
if (isConfigurationActive(config)) {
|
||||
return UAVGadgetInstanceManager::KO_ACTIVE;
|
||||
}
|
||||
// and it cannot be the only configuration
|
||||
QList<IUAVGadgetConfiguration *> *configs = provisionalConfigurations(config->classId());
|
||||
if (configs->count() <= 1) {
|
||||
return UAVGadgetInstanceManager::KO_LONE;
|
||||
}
|
||||
return UAVGadgetInstanceManager::OK;
|
||||
}
|
||||
|
||||
void UAVGadgetInstanceManager::deleteConfiguration(IUAVGadgetConfiguration *config)
|
||||
{
|
||||
m_provisionalDeletes.append(config);
|
||||
@ -504,6 +512,30 @@ QList<IUAVGadgetConfiguration *> *UAVGadgetInstanceManager::configurations(QStri
|
||||
return configs;
|
||||
}
|
||||
|
||||
QList<IUAVGadgetConfiguration *> *UAVGadgetInstanceManager::provisionalConfigurations(QString classId) const
|
||||
{
|
||||
QList<IUAVGadgetConfiguration *> *configs = new QList<IUAVGadgetConfiguration *>;
|
||||
// add current configurations
|
||||
foreach(IUAVGadgetConfiguration * config, m_configurations) {
|
||||
if (config->classId() == classId) {
|
||||
configs->append(config);
|
||||
}
|
||||
}
|
||||
// add provisional configurations
|
||||
foreach(IUAVGadgetConfiguration * config, m_provisionalConfigs) {
|
||||
if (config->classId() == classId) {
|
||||
configs->append(config);
|
||||
}
|
||||
}
|
||||
// remove provisional configurations
|
||||
foreach(IUAVGadgetConfiguration * config, m_provisionalDeletes) {
|
||||
if (config->classId() == classId) {
|
||||
configs->removeOne(config);
|
||||
}
|
||||
}
|
||||
return configs;
|
||||
}
|
||||
|
||||
int UAVGadgetInstanceManager::indexForConfig(QList<IUAVGadgetConfiguration *> configurations,
|
||||
QString classId, QString configName)
|
||||
{
|
||||
|
@ -41,6 +41,7 @@ class PluginManager;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace Internal {
|
||||
class SettingsDialog;
|
||||
}
|
||||
@ -53,22 +54,30 @@ class IUAVGadgetFactory;
|
||||
class CORE_EXPORT UAVGadgetInstanceManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum DeleteStatus { OK, KO_ACTIVE, KO_LONE };
|
||||
|
||||
explicit UAVGadgetInstanceManager(QObject *parent = 0);
|
||||
~UAVGadgetInstanceManager();
|
||||
|
||||
void readSettings(QSettings *qs);
|
||||
void saveSettings(QSettings *qs);
|
||||
|
||||
IUAVGadget *createGadget(QString classId, QWidget *parent, bool loadDefaultConfiguration = true);
|
||||
void removeGadget(IUAVGadget *gadget);
|
||||
void removeAllGadgets();
|
||||
bool canDeleteConfiguration(IUAVGadgetConfiguration *config);
|
||||
|
||||
bool isConfigurationActive(IUAVGadgetConfiguration *config);
|
||||
DeleteStatus canDeleteConfiguration(IUAVGadgetConfiguration *config);
|
||||
void deleteConfiguration(IUAVGadgetConfiguration *config);
|
||||
void cloneConfiguration(IUAVGadgetConfiguration *config);
|
||||
void applyChanges(IUAVGadgetConfiguration *config);
|
||||
void configurationNameEdited(QString text, bool hasText = true);
|
||||
|
||||
QStringList classIds() const
|
||||
{
|
||||
return m_classIdNameMap.keys();
|
||||
}
|
||||
|
||||
QStringList configurationNames(QString classId) const;
|
||||
QString gadgetName(QString classId) const;
|
||||
QIcon gadgetIcon(QString classId) const;
|
||||
@ -84,10 +93,6 @@ public slots:
|
||||
void settingsDialogRemoved();
|
||||
|
||||
private:
|
||||
IUAVGadgetFactory *factory(QString classId) const;
|
||||
void createOptionsPages();
|
||||
QList<IUAVGadgetConfiguration *> *configurations(QString classId) const;
|
||||
QString suggestName(QString classId, QString name);
|
||||
QList<IUAVGadget *> m_gadgetInstances;
|
||||
QList<IUAVGadgetFactory *> m_factories;
|
||||
QList<IUAVGadgetConfiguration *> m_configurations;
|
||||
@ -100,11 +105,22 @@ private:
|
||||
QList<IOptionsPage *> m_provisionalOptionsPages;
|
||||
Core::Internal::SettingsDialog *m_settingsDialog;
|
||||
ExtensionSystem::PluginManager *m_pm;
|
||||
int indexForConfig(QList<IUAVGadgetConfiguration *> configurations,
|
||||
QString classId, QString configName);
|
||||
|
||||
IUAVGadgetFactory *factory(QString classId) const;
|
||||
|
||||
void createOptionsPages();
|
||||
|
||||
QList<IUAVGadgetConfiguration *> *configurations(QString classId) const;
|
||||
QList<IUAVGadgetConfiguration *> *provisionalConfigurations(QString classId) const;
|
||||
|
||||
QString suggestName(QString classId, QString name);
|
||||
|
||||
int indexForConfig(QList<IUAVGadgetConfiguration *> configurations, QString classId, QString configName);
|
||||
|
||||
void readConfigs_1_1_0(QSettings *qs);
|
||||
void readConfigs_1_2_0(QSettings *qs);
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
#endif // UAVGADGETINSTANCEMANAGER_H
|
||||
|
@ -53,16 +53,6 @@ QWidget *UAVGadgetOptionsPageDecorator::createPage(QWidget *parent)
|
||||
m_page = new Ui_TopOptionsPage();
|
||||
QWidget *w = new QWidget(parent);
|
||||
m_page->setupUi(w);
|
||||
if (m_config->locked()) {
|
||||
m_page->deleteButton->hide();
|
||||
m_page->lockCheckBox->hide();
|
||||
m_page->nameLineEdit->setDisabled(true);
|
||||
}
|
||||
if (!m_instanceManager->canDeleteConfiguration(m_config)) {
|
||||
m_page->deleteButton->setDisabled(true);
|
||||
}
|
||||
m_page->lockCheckBox->hide(); //
|
||||
m_page->nameLineEdit->setText(m_id);
|
||||
|
||||
QWidget *wi = m_optionsPage->createPage(w);
|
||||
wi->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
@ -73,6 +63,8 @@ QWidget *UAVGadgetOptionsPageDecorator::createPage(QWidget *parent)
|
||||
m_page->configurationBox->hide();
|
||||
}
|
||||
|
||||
updateState();
|
||||
|
||||
connect(m_page->cloneButton, SIGNAL(clicked()), this, SLOT(cloneConfiguration()));
|
||||
connect(m_page->deleteButton, SIGNAL(clicked()), this, SLOT(deleteConfiguration()));
|
||||
connect(m_page->nameLineEdit, SIGNAL(textEdited(QString)), this, SLOT(textEdited(QString)));
|
||||
@ -92,6 +84,35 @@ void UAVGadgetOptionsPageDecorator::finish()
|
||||
m_optionsPage->finish();
|
||||
}
|
||||
|
||||
void UAVGadgetOptionsPageDecorator::updateState()
|
||||
{
|
||||
if (m_config->locked()) {
|
||||
m_page->deleteButton->hide();
|
||||
m_page->lockCheckBox->hide();
|
||||
m_page->nameLineEdit->setDisabled(true);
|
||||
}
|
||||
switch(m_instanceManager->canDeleteConfiguration(m_config)) {
|
||||
case UAVGadgetInstanceManager::OK:
|
||||
m_page->deleteButton->setEnabled(true);
|
||||
m_page->deleteButton->setToolTip(tr("Delete this configuration"));
|
||||
break;
|
||||
case UAVGadgetInstanceManager::KO_ACTIVE:
|
||||
m_page->deleteButton->setEnabled(false);
|
||||
m_page->deleteButton->setToolTip(tr("Cannot delete a configuration currently in use"));
|
||||
break;
|
||||
case UAVGadgetInstanceManager::KO_LONE:
|
||||
m_page->deleteButton->setEnabled(false);
|
||||
m_page->deleteButton->setToolTip(tr("Cannot delete the last configuration"));
|
||||
break;
|
||||
default:
|
||||
m_page->deleteButton->setEnabled(false);
|
||||
m_page->deleteButton->setToolTip(tr("DON'T KNOW !"));
|
||||
break;
|
||||
}
|
||||
m_page->lockCheckBox->hide();
|
||||
m_page->nameLineEdit->setText(m_id);
|
||||
}
|
||||
|
||||
void UAVGadgetOptionsPageDecorator::cloneConfiguration()
|
||||
{
|
||||
m_instanceManager->cloneConfiguration(m_config);
|
||||
|
@ -67,6 +67,7 @@ public:
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
void updateState();
|
||||
|
||||
private slots:
|
||||
void cloneConfiguration();
|
||||
|
Loading…
x
Reference in New Issue
Block a user