/*---------------------------------------------------------*\ | ManualDevicesSettingsPage.cpp | | | | User interface for OpenRGB Manually Added Devices | | settings page | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-only | \*---------------------------------------------------------*/ #include "ManualDevicesSettingsPage.h" #include "ui_ManualDevicesSettingsPage.h" #include "NanoleafSettingsEntry.h" #include "ResourceManager.h" #include "SettingsManager.h" #include static void ManualDevicesPageReloadCallback(void* this_ptr) { ManualDevicesSettingsPage * this_obj = (ManualDevicesSettingsPage *)this_ptr; QMetaObject::invokeMethod(this_obj, "reloadList", Qt::QueuedConnection); } ManualDevicesSettingsPage::ManualDevicesSettingsPage(QWidget *parent) : QWidget(parent), ui(new Ui::ManualDevicesSettingsPage) { ui->setupUi(this); ResourceManager::get()->RegisterDetectionEndCallback(&ManualDevicesPageReloadCallback, this); addDeviceMenu = new QMenu(this); ui->addDeviceButton->setMenu(addDeviceMenu); connect(addDeviceMenu, &QMenu::triggered, this, &ManualDevicesSettingsPage::onAddDeviceItemSelected); QMenu* saveButtonMenu = new QMenu(this); saveButtonMenu->addAction(ui->ActionSaveAndRescan); saveButtonMenu->addAction(ui->ActionSaveNoRescan); ui->saveConfigurationButton->setMenu(saveButtonMenu); ui->saveConfigurationButton->setDefaultAction(ui->ActionSaveAndRescan); reloadList(); } ManualDevicesSettingsPage::~ManualDevicesSettingsPage() { ResourceManager::get()->UnregisterDetectionEndCallback(&ManualDevicesPageReloadCallback, this); clearList(); delete ui; } void ManualDevicesSettingsPage::changeEvent(QEvent *event) { if(event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); reloadMenu(); } } void ManualDevicesSettingsPage::on_removeDeviceButton_clicked() { int cur_row = ui->deviceList->currentRow(); if(cur_row < 0) { return; } QListWidgetItem* item = ui->deviceList->takeItem(cur_row); ui->deviceList->removeItemWidget(item); delete item; BaseManualDeviceEntry* entry = entries[cur_row]; entries.erase(entries.begin() + cur_row); delete entry; setUnsavedChanges(true); } void ManualDevicesSettingsPage::saveSettings() { SettingsManager* sm = ResourceManager::get()->GetSettingsManager(); json result; /*-----------------------------------------------------------------------------*\ | First, cache the data that will be stored as JSON | | We wrap data into arrays by type (except Nanoleaf) | | Nanoleaf stores it's data as an object with "location" as key for some reason | \*-----------------------------------------------------------------------------*/ for(std::size_t idx = 0; idx < entries.size(); idx++) { std::string section = entries[idx]->getSettingsSection(); if(section == "NanoleafDevices") { NanoleafSettingsEntry* entry = dynamic_cast(entries[idx]); result[section]["devices"][entry->getLocation()] = entries[idx]->saveSettings(); } else if(section == "PhilipsHueDevices") { result[section]["bridges"].push_back(entries[idx]->saveSettings()); } else { result[section]["devices"].push_back(entries[idx]->saveSettings()); } } /*---------------------------------------------------------*\ | Transfer data from the cached object into config sections | | Use ALL possible settings entry names, so that those with | | all entries deleted are cleared properly | \*---------------------------------------------------------*/ std::vector blocks = ManualDevicesTypeManager::get()->getRegisteredTypes(); for(std::size_t i = 0; i < blocks.size(); i++) { sm->SetSettings(blocks[i].settingsSection, result[blocks[i].settingsSection]); } sm->SaveSettings(); setUnsavedChanges(false); } void ManualDevicesSettingsPage::reloadMenu() { std::vector names = ManualDevicesTypeManager::get()->getRegisteredTypeNames(); addDeviceMenu->clear(); for(std::size_t i = 0; i < names.size(); i++) { QAction* action = addDeviceMenu->addAction(qApp->translate("ManualDevice", names[i].c_str())); action->setData(QString::fromStdString(names[i])); } } void ManualDevicesSettingsPage::reloadList() { clearList(); addDeviceMenu->clear(); std::vector blocks = ManualDevicesTypeManager::get()->getRegisteredTypes(); for(std::size_t i = 0; i < blocks.size(); i++) { addEntries(blocks[i]); /*------------------------------------------------------------*\ | While we have all the data at hand, load in the menu as well | \*------------------------------------------------------------*/ QAction* action = addDeviceMenu->addAction(qApp->translate("ManualDevice", blocks[i].name.c_str())); action->setData(QString::fromStdString(blocks[i].name)); } /*---------------------------------------------------------*\ | Refresh button state | \*---------------------------------------------------------*/ setUnsavedChanges(false); on_deviceList_itemSelectionChanged(); } void ManualDevicesSettingsPage::onTextEditChanged() { setUnsavedChanges(true); } void ManualDevicesSettingsPage::clearList() { std::vector entries_copy; entries_copy.swap(entries); ui->deviceList->clear(); for(std::size_t i = 0; i < entries_copy.size(); i++) { delete entries_copy[i]; } entries_copy.clear(); setUnsavedChanges(true); } void ManualDevicesSettingsPage::setUnsavedChanges(bool v) { unsavedChanges = v; if(v) { ui->saveConfigurationButton->setStyleSheet("font: bold"); } else { ui->saveConfigurationButton->setStyleSheet(""); } } bool ManualDevicesSettingsPage::checkValidToSave() { for(std::size_t i = 0; i < entries.size(); i++) { if(!entries[i]->isDataValid()) { return false; } } return true; } QListWidgetItem* ManualDevicesSettingsPage::addEntry(BaseManualDeviceEntry* entry) { if(!entry) { return nullptr; } /*---------------------------------------------------------*\ | Find EVERY QLineEdit in the entry and get notified if ANY | | text in them changes - for validation | | Validation mostly affects the "Save" button state | \*---------------------------------------------------------*/ QList textEditList = entry->findChildren(QString(), Qt::FindChildrenRecursively); for(qsizetype i = 0; i < textEditList.size(); i++) { connect(textEditList[i], &QLineEdit::textChanged, this, &ManualDevicesSettingsPage::onTextEditChanged); } QListWidgetItem* item = new QListWidgetItem; item->setSizeHint(entry->sizeHint()); ui->deviceList->addItem(item); ui->deviceList->setItemWidget(item, entry); ui->deviceList->show(); entries.push_back(entry); /*---------------------------------------------------------*\ | New entries generally indicate unsaved changes | \*---------------------------------------------------------*/ setUnsavedChanges(true); return item; } void ManualDevicesSettingsPage::addEntries(const ManualDeviceTypeBlock& block) { /*---------------------------------------------------------*\ | Spawn list entries for all config entries of one type | \*---------------------------------------------------------*/ json settings = ResourceManager::get()->GetSettingsManager()->GetSettings(block.settingsSection); const char* array_name = "devices"; if(block.settingsSection == "PhilipsHueDevices") { array_name = "bridges"; } if(settings.contains(array_name)) { json& array_ref = settings[array_name]; if(!array_ref.is_array() && !array_ref.is_object()) { return; } /*-----------------------------------------------------------------*\ | Nanoleaf stores it's data as objects with location field as "key" | | everything else is arrays | | For uniformity, use iterators, as if it's always an object | \*-----------------------------------------------------------------*/ for(json::const_iterator iter = array_ref.begin(); iter != array_ref.end(); ++iter) { if(!iter.value().empty()) { BaseManualDeviceEntry* entry = block.spawn(iter.value()); /*---------------------------------------------------*\ | Note: spawn functions are allowed to return nullptr | \*---------------------------------------------------*/ if(entry) { addEntry(entry); } } } } } void ManualDevicesSettingsPage::onAddDeviceItemSelected(QAction* action) { /*---------------------------------------------------------*\ | Spawn new entry based on name | \*---------------------------------------------------------*/ std::string entryName = action->data().toString().toStdString(); BaseManualDeviceEntry* entry = ManualDevicesTypeManager::get()->spawnByTypeName(entryName, json()); /*---------------------------------------------------------*\ | Note: spawn functions are allowed to return nullptr | \*---------------------------------------------------------*/ if(entry) { QListWidgetItem* item = addEntry(entry); /*-----------------------------------------------------*\ | Scroll to the newly added entry (last one in the list | \*-----------------------------------------------------*/ if(item) { ui->deviceList->scrollToItem(item); } } } void ManualDevicesSettingsPage::on_deviceList_itemSelectionChanged() { /*---------------------------------------------------------*\ | Enable Remove button if any entry is selected | \*---------------------------------------------------------*/ int cur_row = ui->deviceList->currentRow(); bool anySelected = (cur_row >= 0); ui->removeDeviceButton->setEnabled(anySelected); } void ManualDevicesSettingsPage::on_ActionSaveNoRescan_triggered() { saveSettings(); } void ManualDevicesSettingsPage::on_ActionSaveAndRescan_triggered() { saveSettings(); /*---------------------------------------------------------*\ | Trigger rescan | \*---------------------------------------------------------*/ ResourceManager::get()->DetectDevices(); }