Repository navigation
Add configurable device aliases to Qt dropdowns #1768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| /* Device Dropdown Aliases | ||
| * | ||
| * From: https://github.com/PokemonAutomation/ | ||
| * | ||
| */ | ||
|
|
||
| #include "Common/Cpp/Json/JsonValue.h" | ||
| #include "CommonFramework/GlobalSettingsPanel.h" | ||
| #include "CommonFramework/AudioPipeline/Backends/AudioDeviceInfo.h" | ||
| #include "CommonFramework/VideoPipeline/Backends/CameraImplementations.h" | ||
| #include "DeviceDropdownAliases.h" | ||
|
|
||
| namespace PokemonAutomation{ | ||
| namespace{ | ||
|
|
||
| const EnumDropdownDatabase<DeviceAliasType>& categories(){ | ||
| static const EnumDropdownDatabase<DeviceAliasType> database{ | ||
| {DeviceAliasType::VIDEO, "Video", "Video"}, | ||
| {DeviceAliasType::AUDIO, "Audio", "Audio"}, | ||
| }; | ||
| return database; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| DeviceAliasRow::DeviceAliasRow(EditableTableOption& parent) | ||
| : EditableTableRow(parent) | ||
| , category(categories(), LockMode::UNLOCK_WHILE_RUNNING, DeviceAliasType::VIDEO) | ||
| , device_id(false, LockMode::UNLOCK_WHILE_RUNNING, "", "Device ID") | ||
| , device_name(false, LockMode::READ_ONLY, "", "") | ||
| , alias(false, LockMode::UNLOCK_WHILE_RUNNING, "", "Use original name") | ||
| { | ||
| PA_ADD_OPTION(category); | ||
| PA_ADD_OPTION(device_id); | ||
| PA_ADD_OPTION(device_name); | ||
| PA_ADD_OPTION(alias); | ||
| } | ||
|
|
||
| std::unique_ptr<EditableTableRow> DeviceAliasRow::clone() const{ | ||
| auto row = std::make_unique<DeviceAliasRow>(parent()); | ||
| row->category.set(category); | ||
| row->device_id.set(device_id); | ||
| row->device_name.set(device_name); | ||
| row->alias.set(alias); | ||
| return row; | ||
| } | ||
|
|
||
| DeviceDropdownAliases::DeviceDropdownAliases() | ||
| : EditableTableOption_t<DeviceAliasRow>( | ||
| "<b>Device Aliases:</b><br>" | ||
| "Choose names for cameras and audio devices. " | ||
| "Connected devices are added when Global Settings opens.<br>" | ||
| "An empty alias uses the original name.", | ||
| LockMode::UNLOCK_WHILE_RUNNING, | ||
| true, | ||
| 10 | ||
| ) | ||
| {} | ||
|
|
||
| std::vector<std::string> DeviceDropdownAliases::make_header() const{ | ||
| return {"Type", "Device ID", "Original Name", "Alias"}; | ||
| } | ||
|
|
||
| std::optional<std::string> DeviceDropdownAliases::lookup(DeviceAliasType category, const std::string& device_id) const{ | ||
| std::optional<std::string> result; | ||
| EditableTableOption::run_on_all_rows<DeviceAliasRow>([&](const DeviceAliasRow& row){ | ||
| if (row.category != category || std::string(row.device_id) != device_id){ | ||
| return false; | ||
| } | ||
| std::string alias = row.alias; | ||
| if (!alias.empty()){ | ||
| result = std::move(alias); | ||
| } | ||
| return true; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AI review: Minor: the lookup stops at the first row matching (category, device ID), even when that row's alias is empty. If the settings JSON ever ends up with two rows for the same device (only possible by hand-editing the JSON, since |
||
| }); | ||
| return result; | ||
| } | ||
|
|
||
| void DeviceDropdownAliases::add_device(DeviceAliasType category, const std::string& id, const std::string& name){ | ||
| bool found = false; | ||
| run_on_all_rows([&](DeviceAliasRow& row){ | ||
| if (row.category != category || std::string(row.device_id) != id){ | ||
| return false; | ||
| } | ||
| row.device_name.set(name); | ||
| found = true; | ||
| return true; | ||
| }); | ||
| if (!found){ | ||
| auto row = std::make_unique<DeviceAliasRow>(*this); | ||
| row->category.set(category); | ||
| row->device_id.set(id); | ||
| row->device_name.set(name); | ||
| append_row(std::move(row)); | ||
| } | ||
| } | ||
|
|
||
| void DeviceDropdownAliases::add_connected_devices(){ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AI review: Design question: auto-added rows are persisted into the settings JSON (via |
||
| for (const CameraInfo& info : get_all_cameras()){ | ||
| add_device(DeviceAliasType::VIDEO, info.device_name(), get_camera_name(info)); | ||
| } | ||
| for (const AudioDeviceInfo& info : AudioDeviceInfo::all_input_devices()){ | ||
| add_device(DeviceAliasType::AUDIO, info.device_name(), info.original_name()); | ||
| } | ||
| for (const AudioDeviceInfo& info : AudioDeviceInfo::all_output_devices()){ | ||
| add_device(DeviceAliasType::AUDIO, info.device_name(), info.original_name()); | ||
| } | ||
| } | ||
|
|
||
| DeviceAliasOptions::DeviceAliasOptions() | ||
| : BatchOption(LockMode::UNLOCK_WHILE_RUNNING, false) | ||
| { | ||
| PA_ADD_OPTION(m_aliases); | ||
| } | ||
|
|
||
| JsonValue DeviceAliasOptions::to_json() const{ | ||
| return m_aliases.to_json(); | ||
| } | ||
| void DeviceAliasOptions::load_json(const JsonValue& json){ | ||
| m_aliases.load_json(json); | ||
| } | ||
|
|
||
| void DeviceAliasOptions::add_connected_devices(){ | ||
| m_aliases.add_connected_devices(); | ||
| } | ||
| std::optional<std::string> DeviceAliasOptions::lookup(DeviceAliasType category, const std::string& device_id) const{ | ||
| return m_aliases.lookup(category, device_id); | ||
| } | ||
|
|
||
| std::optional<std::string> device_dropdown_name(DeviceAliasType category, const std::string& device_id){ | ||
| return GlobalSettings::instance().DEVICE_ALIASES->lookup(category, device_id); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| /* Device Dropdown Aliases | ||
| * | ||
| * From: https://github.com/PokemonAutomation/ | ||
| * | ||
| */ | ||
|
|
||
| #ifndef PokemonAutomation_DeviceDropdownAliases_H | ||
| #define PokemonAutomation_DeviceDropdownAliases_H | ||
|
|
||
| #include <optional> | ||
| #include <string> | ||
| #include "Common/Cpp/Options/EditableTableOption.h" | ||
| #include "Common/Cpp/Options/EnumDropdownOption.h" | ||
| #include "Common/Cpp/Options/BatchOption.h" | ||
| #include "Common/Cpp/Options/StringOption.h" | ||
|
|
||
| namespace PokemonAutomation{ | ||
|
|
||
| enum class DeviceAliasType{ | ||
| VIDEO, | ||
| AUDIO, | ||
| }; | ||
|
|
||
| class DeviceAliasRow : public EditableTableRow{ | ||
| public: | ||
| DeviceAliasRow(EditableTableOption& parent); | ||
| virtual std::unique_ptr<EditableTableRow> clone() const override; | ||
|
|
||
| EnumDropdownCell<DeviceAliasType> category; | ||
| StringCell device_id; | ||
| StringCell device_name; | ||
| StringCell alias; | ||
| }; | ||
|
|
||
| class DeviceDropdownAliases : public EditableTableOption_t<DeviceAliasRow>{ | ||
| public: | ||
| DeviceDropdownAliases(); | ||
|
|
||
| virtual std::vector<std::string> make_header() const override; | ||
| void add_connected_devices(); | ||
| std::optional<std::string> lookup(DeviceAliasType category, const std::string& device_id) const; | ||
|
|
||
| private: | ||
| void add_device(DeviceAliasType category, const std::string& id, const std::string& name); | ||
| }; | ||
|
|
||
| class DeviceAliasOptions : public BatchOption{ | ||
| public: | ||
| DeviceAliasOptions(); | ||
|
|
||
| virtual JsonValue to_json() const override; | ||
| virtual void load_json(const JsonValue& json) override; | ||
|
|
||
| void add_connected_devices(); | ||
| std::optional<std::string> lookup(DeviceAliasType category, const std::string& device_id) const; | ||
|
|
||
| private: | ||
| DeviceDropdownAliases m_aliases; | ||
| }; | ||
|
|
||
| // Presentation only: never use this result for device selection or serialization. | ||
| // Returns std::nullopt when no non-empty alias is configured. | ||
| // Configured in Global Settings. Device IDs remain unchanged. | ||
| std::optional<std::string> device_dropdown_name( | ||
| DeviceAliasType category, | ||
| const std::string& device_id | ||
| ); | ||
|
|
||
| } | ||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /* Camera Info | ||
| * | ||
| * From: https://github.com/PokemonAutomation/ | ||
| * | ||
| */ | ||
|
|
||
| #include "CommonFramework/Options/DeviceDropdownAliases.h" | ||
| #include "Backends/CameraImplementations.h" | ||
| #include "CameraInfo.h" | ||
|
|
||
| namespace PokemonAutomation{ | ||
|
|
||
| std::string CameraInfo::display_name() const{ | ||
| if (!*this){ | ||
| return std::string(); | ||
| } | ||
| if (auto alias = device_dropdown_name(DeviceAliasType::VIDEO, m_device_name)){ | ||
| return *alias; | ||
| } | ||
| return get_camera_name(*this); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AI review: Note: this runs synchronously on the GUI thread every time Global Settings is opened.
AudioDeviceInfo::all_input_devices()probes every input device's supported formats (andget_all_cameras()enumerates the video backend), so on a machine with many virtual audio devices or a stuck USB camera this can stall the UI for several seconds while the panel is being constructed. Consider deferring it (e.g. a one-shotQTimer::singleShotafter the panel is shown) or enumerating on a worker thread.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is fine I think. We already have this delay when loading any program. If it becomes a problem in the future, we can add an async refresher like what the video sources already do.