From 084cba89773b0fe2ee5531e00dfcb97e27717114 Mon Sep 17 00:00:00 2001 From: fredzo Date: Thu, 15 Jan 2026 18:10:12 +0100 Subject: [PATCH 01/10] Return false on CreateAndAddInstrument method if instrument creation failed. --- src/ngscopeclient/Session.cpp | 6 ++++-- src/ngscopeclient/Session.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index 9a1b5b23..c1e6c7f4 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -2899,8 +2899,9 @@ void Session::StartWaveformThreadIfNeeded() /** @brief Creates a new instrument and adds it to the session + @return Returns false if creation failed */ -void Session::CreateAndAddInstrument(const string& driver, SCPITransport* transport, const string& nickname) +bool Session::CreateAndAddInstrument(const string& driver, SCPITransport* transport, const string& nickname) { shared_ptr inst = nullptr; @@ -2953,7 +2954,7 @@ void Session::CreateAndAddInstrument(const string& driver, SCPITransport* transp "Driver error", "Failed to create instrument driver instance of type \"" + driver + "\""); delete transport; - return; + return false; } //Apply preference settings, if any, here @@ -2963,6 +2964,7 @@ void Session::CreateAndAddInstrument(const string& driver, SCPITransport* transp inst->m_nickname = nickname; AddInstrument(inst); + return true; } /** diff --git a/src/ngscopeclient/Session.h b/src/ngscopeclient/Session.h index 22e8c5df..94a95dea 100644 --- a/src/ngscopeclient/Session.h +++ b/src/ngscopeclient/Session.h @@ -621,7 +621,7 @@ class Session const std::vector& GetDriverNamesForType(const std::string& type) { return m_driverNamesByType[type]; } - void CreateAndAddInstrument( + bool CreateAndAddInstrument( const std::string& driver, SCPITransport* transport, const std::string& nickname); From d555a42b67a2777f01c7e04981dbaacb6bbf7a5d Mon Sep 17 00:00:00 2001 From: fredzo Date: Thu, 15 Jan 2026 18:14:34 +0100 Subject: [PATCH 02/10] Fixed recent instrument YAML serialization to keep most recent version of a given nickname when duplicated. When reconnection fails for a recent instrument, respawn a AddInstrumentDialog to allow modifying the connetion parameters. --- src/ngscopeclient/AddInstrumentDialog.cpp | 38 +++++++++++++++++++++-- src/ngscopeclient/AddInstrumentDialog.h | 5 ++- src/ngscopeclient/MainWindow.cpp | 12 ++++--- src/ngscopeclient/MainWindow_Menus.cpp | 23 +++++++++++++- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/ngscopeclient/AddInstrumentDialog.cpp b/src/ngscopeclient/AddInstrumentDialog.cpp index 6b938787..0a9cb558 100644 --- a/src/ngscopeclient/AddInstrumentDialog.cpp +++ b/src/ngscopeclient/AddInstrumentDialog.cpp @@ -45,16 +45,49 @@ AddInstrumentDialog::AddInstrumentDialog( const string& title, const string& nickname, Session& session, - const string& driverType) + const string& driverType, + const std::string& driver, + const std::string& transport, + const std::string& path) : Dialog(title, string("AddInstrument") + to_string_hex(reinterpret_cast(this)), ImVec2(600, 150)) , m_session(session) , m_nickname(nickname) , m_selectedDriver(0) , m_selectedTransport(0) + , m_path(path) { SCPITransport::EnumTransports(m_transports); m_drivers = session.GetDriverNamesForType(driverType); + + if(!driver.empty()) + { + int i = 0; + for(auto driverName: m_drivers) + { + if(driverName == driver) + { + m_selectedDriver = i; + break; + } + i++; + } + } + + + if(!transport.empty()) + { + int i = 0; + for(auto transportName: m_transports) + { + if(transportName == transport) + { + m_selectedTransport = i; + break; + } + i++; + } + } } AddInstrumentDialog::~AddInstrumentDialog() @@ -170,6 +203,5 @@ SCPITransport* AddInstrumentDialog::MakeTransport() bool AddInstrumentDialog::DoConnect(SCPITransport* transport) { - m_session.CreateAndAddInstrument(m_drivers[m_selectedDriver], transport, m_nickname); - return true; + return m_session.CreateAndAddInstrument(m_drivers[m_selectedDriver], transport, m_nickname); } diff --git a/src/ngscopeclient/AddInstrumentDialog.h b/src/ngscopeclient/AddInstrumentDialog.h index 22e86290..ac4f0908 100644 --- a/src/ngscopeclient/AddInstrumentDialog.h +++ b/src/ngscopeclient/AddInstrumentDialog.h @@ -45,7 +45,10 @@ class AddInstrumentDialog : public Dialog const std::string& title, const std::string& nickname, Session& session, - const std::string& driverType); + const std::string& driverType, + const std::string& driver = "", + const std::string& transport = "", + const std::string& path = ""); virtual ~AddInstrumentDialog(); virtual bool DoRender(); diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index c13ded6e..4f644027 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -1567,10 +1567,14 @@ void MainWindow::SaveRecentInstrumentList() continue; //Make a node for the instrument - YAML::Node inode; - inode["path"] = it.first; - inode["timestamp"] = static_cast(it.second); - node[nick] = inode; + int64_t timestamp = static_cast(it.second); + if(!node[nick] || (node[nick]["timestamp"].as() < timestamp)) + { // Only add node if not already present or if other timestamp is older + YAML::Node inode; + inode["path"] = it.first; + inode["timestamp"] = timestamp; + node[nick] = inode; + } } //Write the generated YAML to disk diff --git a/src/ngscopeclient/MainWindow_Menus.cpp b/src/ngscopeclient/MainWindow_Menus.cpp index 1521e155..592cd2d7 100644 --- a/src/ngscopeclient/MainWindow_Menus.cpp +++ b/src/ngscopeclient/MainWindow_Menus.cpp @@ -340,9 +340,30 @@ void MainWindow::DoAddSubMenu( path = path + ":" + fields[j]; } + bool success = true; auto transport = MakeTransport(transname, path); if(transport != nullptr) - m_session.CreateAndAddInstrument(drivername, transport, nick); + { + if(!m_session.CreateAndAddInstrument(drivername, transport, nick)) + { + success = false; + } + } + else + { + success = false; + } + if(!success) + { // Spawn an AddInstrument dialog here, prefilled with intrument informations, to allow changing connection path + m_dialogs.emplace(make_shared( + string("Update ") + typePretty, + nick, + m_session, + typeInternal, + drivername, + transname, + path)); + } } } } From 28b09f77b688e97ebbdeef8a17e825b4bcd68f86 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sun, 18 Jan 2026 00:11:18 +0100 Subject: [PATCH 03/10] Prevent warning dialog from poping up if we are already showing an error dialog. Moved PreloadConfiguration before AddInstrument() for Mockable instruments to allow channel creation before instrument state is initialized. Only create InstrumentThread if Instrument is online. --- src/ngscopeclient/MainWindow.cpp | 3 +++ src/ngscopeclient/Session.cpp | 31 ++++++++++------------- src/ngscopeclient/StreamBrowserDialog.cpp | 30 ++++++++++++++-------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index 4f644027..bcd063f3 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -1781,6 +1781,9 @@ ImGui::MarkdownConfig MainWindow::GetMarkdownConfig() */ void MainWindow::RenderLoadWarningPopup() { + if(!m_errorPopupTitle.empty()) + return; // Already showing Error popup, skip Warning for now + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index c1e6c7f4..5d093b3d 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -47,6 +47,7 @@ #include "../scopehal/SiglentSCPIOscilloscope.h" #include "../scopehal/RigolOscilloscope.h" #include "../scopehal/MockOscilloscope.h" +#include "../scopehal/MockPowerSupply.h" #include "../scopeprotocols/EyePattern.h" #include @@ -1240,6 +1241,9 @@ bool Session::PreLoadOscilloscope(int version, const YAML::Node& node, bool onli //Make any config settings to the instrument from our preference settings ApplyPreferences(scope); + //Run the preload + scope->PreLoadConfiguration(version, node, m_idtable, m_warnings); + //All good. Add to our list of scopes etc AddInstrument(scope, false); m_idtable.emplace(node["id"].as(), (Instrument*)scope.get()); @@ -1248,9 +1252,6 @@ bool Session::PreLoadOscilloscope(int version, const YAML::Node& node, bool onli if(node["triggerdeskew"]) m_scopeDeskewCal[scope] = node["triggerdeskew"].as(); - //Run the preload - scope->PreLoadConfiguration(version, node, m_idtable, m_warnings); - return true; } @@ -1311,13 +1312,13 @@ bool Session::PreLoadVNA(int version, const YAML::Node& node, bool online) //Make any config settings to the instrument from our preference settings ApplyPreferences(scope); + //Run the preload + scope->PreLoadConfiguration(version, node, m_idtable, m_warnings); + //All good. Add to our list of scopes etc AddInstrument(scope, false); m_idtable.emplace(node["id"].as(), (Instrument*)scope.get()); - //Run the preload - scope->PreLoadConfiguration(version, node, m_idtable, m_warnings); - return true; } @@ -1797,9 +1798,8 @@ bool Session::PreLoadPowerSupply(int version, const YAML::Node& node, bool onlin if(!psu) { - /* - //Create the mock scope - scope = new MockOscilloscope( + //Create the mock PSU + psu = make_shared( node["name"].as(), node["vendor"].as(), node["serial"].as(), @@ -1807,21 +1807,18 @@ bool Session::PreLoadPowerSupply(int version, const YAML::Node& node, bool onlin driver, node["args"].as() ); - */ - LogError("offline loading of power supplies not implemented yet\n"); - return true; } //Make any config settings to the instrument from our preference settings //ApplyPreferences(psu); + //Run the preload + psu->PreLoadConfiguration(version, node, m_idtable, m_warnings); + //All good. Add to our list of scopes etc AddInstrument(psu, false); m_idtable.emplace(node["id"].as(), (Instrument*)psu.get()); - //Run the preload - psu->PreLoadConfiguration(version, node, m_idtable, m_warnings); - return true; } @@ -3037,8 +3034,8 @@ void Session::AddInstrument(shared_ptr inst, bool createDialogs) args.awgstate = state; } - //Make the instrument thread - if(si) + //Make the instrument thread (only if Instrument is online) + if(si && !inst->IsOffline()) m_instrumentStates[inst] = make_shared(args); //Spawn dialogs/views if requested diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 9e003280..1647b7d8 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1132,19 +1132,27 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument int bankNumber = 1; for(auto bank : digitalBanks) { // Iterate on digital banks - string nodeName = "Digital Bank " + to_string(bankNumber); - if(ImGui::TreeNodeEx(nodeName.c_str())) - { - ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); - for(auto channel : bank) - { // Iterate on bank's channel - size_t i = channel->GetIndex(); - renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); + if(bank.size() > 1) + { // Only show Digital Bank node if there is more than on channel in the bank + string nodeName = "Digital Bank " + to_string(bankNumber); + if(ImGui::TreeNodeEx(nodeName.c_str())) + { + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + for(auto channel : bank) + { // Iterate on bank's channel + size_t i = channel->GetIndex(); + renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); + } + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); } - ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); - ImGui::TreePop(); + bankNumber++; + } + else if(bank.size() == 1) + { // Only one channel in the bank, render it directly + size_t i = bank[0]->GetIndex(); + renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); } - bankNumber++; } for(size_t i : otherChannels) { // Finally iterate on other channels From d4f2e98631c28cd8301e1823677728521679b87b Mon Sep 17 00:00:00 2001 From: fredzo Date: Sun, 18 Jan 2026 00:41:36 +0100 Subject: [PATCH 04/10] Added offline badge for PSU. --- src/ngscopeclient/Session.cpp | 4 +- src/ngscopeclient/StreamBrowserDialog.cpp | 77 ++++++++++++----------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index 5d093b3d..0cfc36b2 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -3034,8 +3034,8 @@ void Session::AddInstrument(shared_ptr inst, bool createDialogs) args.awgstate = state; } - //Make the instrument thread (only if Instrument is online) - if(si && !inst->IsOffline()) + //Make the instrument thread + if(si) m_instrumentStates[inst] = make_shared(args); //Spawn dialogs/views if requested diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 1647b7d8..6a283862 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1035,49 +1035,54 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument auto psu = dynamic_pointer_cast(instrument); if (psu) { - //Get the state - auto psustate = m_session->GetPSUState(psu); - - bool allOn = false; - bool someOn = false; - if(psu->SupportsMasterOutputSwitching()) - allOn = psustate->m_masterEnable; - else - { - allOn = true; - for(size_t i = 0 ; i < channelCount ; i++) - { - if(psustate->m_channelOn[i]) - someOn = true; - else - allOn = false; - } - } - bool result; - if(allOn || someOn) - { - result = true; - renderToggle( - "###psuon", - true, - allOn ? - ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color")) : - ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_partial_badge_color")), result); - } + if (psu->IsOffline()) + renderBadge(ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_offline_badge_color")), "OFFLINE", "OFFL", NULL); else { - result = false; - renderOnOffToggle("###psuon", true, result); - } - if(result != allOn) - { + //Get the state + auto psustate = m_session->GetPSUState(psu); + + bool allOn = false; + bool someOn = false; if(psu->SupportsMasterOutputSwitching()) - psu->SetMasterPowerEnable(result); + allOn = psustate->m_masterEnable; else { + allOn = true; for(size_t i = 0 ; i < channelCount ; i++) { - psu->SetPowerChannelActive(i,result); + if(psustate->m_channelOn[i]) + someOn = true; + else + allOn = false; + } + } + bool result; + if(allOn || someOn) + { + result = true; + renderToggle( + "###psuon", + true, + allOn ? + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color")) : + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_partial_badge_color")), result); + } + else + { + result = false; + renderOnOffToggle("###psuon", true, result); + } + if(result != allOn) + { + if(psu->SupportsMasterOutputSwitching()) + psu->SetMasterPowerEnable(result); + else + { + for(size_t i = 0 ; i < channelCount ; i++) + { + psu->SetPowerChannelActive(i,result); + } } } } From 0710f2a45bd281867ae981a2ee61dbbfb11abd78 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sun, 18 Jan 2026 10:58:30 +0100 Subject: [PATCH 05/10] Moved to latest scopehal --- lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib b/lib index 9411b419..bc20120f 160000 --- a/lib +++ b/lib @@ -1 +1 @@ -Subproject commit 9411b4192b5f6ff6c243486c32f65d3a906fef8a +Subproject commit bc20120f64bb4210d4cd08f2172cdcd12ec37387 From 1bb8a2689a0c1d1372773d6a3ccc92503d7eddc3 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sun, 18 Jan 2026 11:29:45 +0100 Subject: [PATCH 06/10] Fixed CI compilation. --- lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib b/lib index bc20120f..a112c477 160000 --- a/lib +++ b/lib @@ -1 +1 @@ -Subproject commit bc20120f64bb4210d4cd08f2172cdcd12ec37387 +Subproject commit a112c477dab84a78734f6c39d6bcc97fcaae5ca2 From 472a8551107e8144b0bd26857ba94e99e2e3a40c Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 19 Jan 2026 12:23:57 +0100 Subject: [PATCH 07/10] Added renderBadge method to Dialog. Added String support for EditableProperties. Added rename (change nickname) support to ManageInstrument dialog + update nickname in recent instrument list. Removed (unused) selection on instrument list. --- src/ngscopeclient/Dialog.cpp | 33 +++++++- src/ngscopeclient/Dialog.h | 2 + src/ngscopeclient/MainWindow.cpp | 29 +++++++ src/ngscopeclient/MainWindow.h | 1 + src/ngscopeclient/ManageInstrumentsDialog.cpp | 76 +++++++++++++++---- src/ngscopeclient/ManageInstrumentsDialog.h | 6 +- 6 files changed, 131 insertions(+), 16 deletions(-) diff --git a/src/ngscopeclient/Dialog.cpp b/src/ngscopeclient/Dialog.cpp index 4de5aba4..026af0eb 100644 --- a/src/ngscopeclient/Dialog.cpp +++ b/src/ngscopeclient/Dialog.cpp @@ -237,6 +237,11 @@ bool Dialog::TextInputWithImplicitApply(const string& label, string& currentValu return false; } +bool Dialog::TextInputWithExplicitApply(const string& label, string& currentValue, string& committedValue) +{ + return renderEditablePropertyWithExplicitApply(-1,label,currentValue,committedValue,Unit()/*not used for string*/); +} + bool Dialog::IntInputWithImplicitApply(const string& label, int& currentValue, int& committedValue) { bool dirty = currentValue != committedValue; @@ -488,7 +493,7 @@ template */ bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay, bool explicitApply) { - static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports int64_t, float or double"); + static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports string, int64_t, float or double"); bool use7Segment = false; bool changeFont = false; int64_t displayType = NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT; @@ -528,6 +533,8 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: } if constexpr (std::is_same_v) dirty = unit.PrettyPrintInt64(committedValue) != currentValue; + else if constexpr (std::is_same_v) + dirty = committedValue != currentValue; else dirty = unit.PrettyPrint(committedValue) != currentValue; string editLabel = label+"##Edit"; @@ -674,6 +681,10 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: currentValue = unit.PrettyPrintInt64(committedValue); } + else if constexpr (std::is_same_v) + { + committedValue = currentValue; + } else { committedValue = static_cast(unit.ParseString(currentValue)); @@ -689,6 +700,8 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: { // Restore value if constexpr (std::is_same_v) currentValue = unit.PrettyPrintInt64(committedValue); + else if constexpr (std::is_same_v) + currentValue = committedValue; else currentValue = unit.PrettyPrint(committedValue); if(m_editedItemId == editId) @@ -707,6 +720,7 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, float& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay, bool explicitApply); template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, double& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay, bool explicitApply); template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, int64_t& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay, bool explicitApply); +template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, std::string& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay, bool explicitApply); template /** @@ -731,7 +745,24 @@ bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::str template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, float& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay); template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, double& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay); template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, int64_t& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay); +template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, std::string& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay); +/** + @brief Render a badge with text inside + @param width the width of the badge + @param color the badge color + @param label the text of the badge +*/ +void Dialog::renderBadge(float width, ImVec4 color, const string& label) +{ + float fontSize = ImGui::GetFontSize(); + if(width <= 0) width = 6*fontSize; + ImGui::PushStyleColor(ImGuiCol_ChildBg, color); + ImGui::BeginChild("##badge", ImVec2(width, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); + ImGui::TextUnformatted(label.c_str()); + ImGui::EndChild(); + ImGui::PopStyleColor(); +} /** @brief Segment on/off state for each of the 10 digits + "L" (needed for OL / Overload) diff --git a/src/ngscopeclient/Dialog.h b/src/ngscopeclient/Dialog.h index 84b1008b..fb6cce7f 100644 --- a/src/ngscopeclient/Dialog.h +++ b/src/ngscopeclient/Dialog.h @@ -81,6 +81,7 @@ class Dialog std::string& committedValue); protected: + bool TextInputWithExplicitApply(const std::string& label,std::string& currentValue,std::string& committedValue); bool IntInputWithImplicitApply(const std::string& label, int& currentValue, int& committedValue); bool UnitInputWithExplicitApply( const std::string& label, @@ -93,6 +94,7 @@ class Dialog bool renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, std::optional optcolor = std::nullopt, bool allow7SegmentDisplay = false, bool explicitApply = false); template bool renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, std::optional optcolor = std::nullopt, bool allow7SegmentDisplay = false); + void renderBadge(float width, ImVec4 color, const std::string& label); public: static void Tooltip(const std::string& str, bool allowDisabled = false); diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index bcd063f3..ca4674ad 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -1635,6 +1635,35 @@ void MainWindow::AddToRecentInstrumentList(shared_ptr inst) SaveRecentInstrumentList(); } +void MainWindow::RenameRecentInstrument(std::shared_ptr inst, const std::string& oldName) +{ + if(inst == nullptr) + return; + + LogTrace("Renaming instrument \"%s\" with name \"%s\" in recent instrument list (had %zu)\n", + oldName.c_str(), inst->m_nickname.c_str(), m_recentInstruments.size()); + + auto oldConnectionString = + oldName + ":" + + inst->GetDriverName() + ":" + + inst->GetTransportName() + ":" + + inst->GetTransportConnectionString(); + auto it = m_recentInstruments.find(oldConnectionString); + if (it != m_recentInstruments.end()) + { + auto now = time(NULL); + auto newConnectionString = + inst->m_nickname + ":" + + inst->GetDriverName() + ":" + + inst->GetTransportName() + ":" + + inst->GetTransportConnectionString(); + LogTrace("Replaced connection string %s by %s\n", oldConnectionString.c_str(),newConnectionString.c_str()); + m_recentInstruments.erase(it); + m_recentInstruments.emplace(newConnectionString, now); + SaveRecentInstrumentList(); + } +} + /** @brief Helper function for creating a transport and printing an error if the connection is unsuccessful */ diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index c06bea16..69c28fe2 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -506,6 +506,7 @@ class MainWindow : public VulkanWindow public: void AddToRecentInstrumentList(std::shared_ptr inst); + void RenameRecentInstrument(std::shared_ptr inst, const std::string& oldName); protected: diff --git a/src/ngscopeclient/ManageInstrumentsDialog.cpp b/src/ngscopeclient/ManageInstrumentsDialog.cpp index 626e2ba6..291b9572 100644 --- a/src/ngscopeclient/ManageInstrumentsDialog.cpp +++ b/src/ngscopeclient/ManageInstrumentsDialog.cpp @@ -44,10 +44,10 @@ using namespace std; // Construction / destruction ManageInstrumentsDialog::ManageInstrumentsDialog(Session& session, MainWindow* parent) - : Dialog("Manage Instruments", "Manage Instruments", ImVec2(1000, 300)) + : Dialog("Manage Instruments", "Manage Instruments", ImVec2(1024, 300)) , m_session(session) , m_parent(parent) - , m_selection(nullptr) + //, m_selection(nullptr) { } @@ -97,7 +97,7 @@ bool ManageInstrumentsDialog::DoRender() if(ImGui::CollapsingHeader("All Instruments", ImGuiTreeNodeFlags_DefaultOpen)) { - if(ImGui::BeginTable("alltable", 7, flags)) + if(ImGui::BeginTable("alltable", 8, flags)) { AllInstrumentsTable(); ImGui::EndTable(); @@ -393,45 +393,91 @@ void ManageInstrumentsDialog::RowForNewGroup() void ManageInstrumentsDialog::AllInstrumentsTable() { + auto& prefs = m_session.GetPreferences(); auto insts = m_session.GetInstruments(); float width = ImGui::GetFontSize(); ImGui::TableSetupScrollFreeze(0, 1); //Header row does not scroll - ImGui::TableSetupColumn("Nickname", ImGuiTableColumnFlags_WidthFixed, 6*width); + ImGui::TableSetupColumn("Nickname", ImGuiTableColumnFlags_WidthFixed, 12*width); ImGui::TableSetupColumn("Make", ImGuiTableColumnFlags_WidthFixed, 9*width); - ImGui::TableSetupColumn("Model", ImGuiTableColumnFlags_WidthFixed, 15*width); + ImGui::TableSetupColumn("Model", ImGuiTableColumnFlags_WidthFixed, 12*width); ImGui::TableSetupColumn("Transport", ImGuiTableColumnFlags_WidthFixed, 4*width); - ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthFixed, 25*width); - ImGui::TableSetupColumn("Serial", ImGuiTableColumnFlags_WidthFixed, 8*width); + ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthFixed, 15*width); + ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 5*width); + ImGui::TableSetupColumn("Serial", ImGuiTableColumnFlags_WidthFixed, 7*width); ImGui::TableSetupColumn("Features", ImGuiTableColumnFlags_WidthFixed, 10*width); ImGui::TableHeadersRow(); + size_t instNumber = insts.size(); + size_t instIndex = 0; + m_instrumentCurrentNames.resize(instNumber); + m_instrumentCommittedNames.resize(instNumber); + m_instrumentCurrentPaths.resize(instNumber); + m_instrumentCommittedPaths.resize(instNumber); + for(auto inst : insts) { auto itype = inst->GetInstrumentTypes(); - bool rowIsSelected = (m_selection == inst); + //bool rowIsSelected = (m_selection == inst); ImGui::PushID(inst.get()); ImGui::TableNextRow(ImGuiTableRowFlags_None); - ImGui::TableSetColumnIndex(0); + if(ImGui::TableSetColumnIndex(0)) + { + if(m_instrumentCommittedNames[instIndex].empty()) m_instrumentCommittedNames[instIndex]=inst->m_nickname; + if(m_instrumentCurrentNames[instIndex].empty()) m_instrumentCurrentNames[instIndex]=inst->m_nickname; + ImGui::SetNextItemWidth(12*width); + if(TextInputWithExplicitApply("",m_instrumentCurrentNames[instIndex],m_instrumentCommittedNames[instIndex])) + { + string oldName = inst->m_nickname; + inst->m_nickname = m_instrumentCommittedNames[instIndex]; + auto si = dynamic_pointer_cast(inst); + if(si) + m_parent->RenameRecentInstrument(si,oldName); + } + } + if(ImGui::TableSetColumnIndex(1)) + { + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(inst->GetVendor().c_str()); + } + /*ImGui::TableSetColumnIndex(1); + ImGui::AlignTextToFramePadding(); if(ImGui::Selectable( - inst->m_nickname.c_str(), + inst->GetVendor().c_str(), rowIsSelected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, 0))) { m_selection = dynamic_pointer_cast(inst); rowIsSelected = true; - } - if(ImGui::TableSetColumnIndex(1)) - ImGui::TextUnformatted(inst->GetVendor().c_str()); + }*/ if(ImGui::TableSetColumnIndex(2)) + { + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(inst->GetName().c_str()); + } if(ImGui::TableSetColumnIndex(3)) + { + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(inst->GetTransportName().c_str()); + } if(ImGui::TableSetColumnIndex(4)) + { + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(inst->GetTransportConnectionString().c_str()); + } if(ImGui::TableSetColumnIndex(5)) - ImGui::TextUnformatted(inst->GetSerial().c_str()); + { + ImGui::AlignTextToFramePadding(); + renderBadge(0, + inst->IsOffline() ? ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_offline_badge_color")) : ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color")), + inst->IsOffline() ? "Offline" : "Online"); + } if(ImGui::TableSetColumnIndex(6)) + { + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(inst->GetSerial().c_str()); + } + if(ImGui::TableSetColumnIndex(7)) { string types = ""; @@ -453,8 +499,10 @@ void ManageInstrumentsDialog::AllInstrumentsTable() if(itype & Instrument::INST_BERT) types += "bert "; + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(types.c_str()); } ImGui::PopID(); + instIndex++; } } diff --git a/src/ngscopeclient/ManageInstrumentsDialog.h b/src/ngscopeclient/ManageInstrumentsDialog.h index 9ce9d82e..8af2d1c0 100644 --- a/src/ngscopeclient/ManageInstrumentsDialog.h +++ b/src/ngscopeclient/ManageInstrumentsDialog.h @@ -54,8 +54,12 @@ class ManageInstrumentsDialog : public Dialog Session& m_session; MainWindow* m_parent; + std::vector m_instrumentCommittedNames; + std::vector m_instrumentCurrentNames; + std::vector m_instrumentCommittedPaths; + std::vector m_instrumentCurrentPaths; - std::shared_ptr m_selection; + //std::shared_ptr m_selection; }; class TriggerGroupDragDescriptor From 041600db6469d3e682f06792f5eb22dea3ae5174 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 19 Jan 2026 18:43:49 +0100 Subject: [PATCH 08/10] Added path edition to ManageInstrumentDialog. --- lib | 2 +- src/ngscopeclient/MainWindow.cpp | 29 +++++++++++++++++++ src/ngscopeclient/MainWindow.h | 1 + src/ngscopeclient/ManageInstrumentsDialog.cpp | 25 ++++++++++++++-- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/lib b/lib index a112c477..3cb18ba8 160000 --- a/lib +++ b/lib @@ -1 +1 @@ -Subproject commit a112c477dab84a78734f6c39d6bcc97fcaae5ca2 +Subproject commit 3cb18ba8ae1303ee08fa6098f261ede79c6ac9cc diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index ca4674ad..e92a9ec7 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -1664,6 +1664,35 @@ void MainWindow::RenameRecentInstrument(std::shared_ptr inst, co } } +void MainWindow::RepathRecentInstrument(std::shared_ptr inst, const std::string& oldPath) +{ + if(inst == nullptr) + return; + + LogTrace("Changing path for instrument \"%s\" with path \"%s\" in recent instrument list (had %zu)\n", + oldPath.c_str(), inst->GetTransportConnectionString().c_str(), m_recentInstruments.size()); + + auto oldConnectionString = + inst->m_nickname + ":" + + inst->GetDriverName() + ":" + + inst->GetTransportName() + ":" + + oldPath; + auto it = m_recentInstruments.find(oldConnectionString); + if (it != m_recentInstruments.end()) + { + auto now = time(NULL); + auto newConnectionString = + inst->m_nickname + ":" + + inst->GetDriverName() + ":" + + inst->GetTransportName() + ":" + + inst->GetTransportConnectionString(); + LogTrace("Replaced connection string %s by %s\n", oldConnectionString.c_str(),newConnectionString.c_str()); + m_recentInstruments.erase(it); + m_recentInstruments.emplace(newConnectionString, now); + SaveRecentInstrumentList(); + } +} + /** @brief Helper function for creating a transport and printing an error if the connection is unsuccessful */ diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index 69c28fe2..8fdfb21a 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -507,6 +507,7 @@ class MainWindow : public VulkanWindow public: void AddToRecentInstrumentList(std::shared_ptr inst); void RenameRecentInstrument(std::shared_ptr inst, const std::string& oldName); + void RepathRecentInstrument(std::shared_ptr inst, const std::string& oldPath); protected: diff --git a/src/ngscopeclient/ManageInstrumentsDialog.cpp b/src/ngscopeclient/ManageInstrumentsDialog.cpp index 291b9572..6377e756 100644 --- a/src/ngscopeclient/ManageInstrumentsDialog.cpp +++ b/src/ngscopeclient/ManageInstrumentsDialog.cpp @@ -425,7 +425,7 @@ void ManageInstrumentsDialog::AllInstrumentsTable() if(m_instrumentCommittedNames[instIndex].empty()) m_instrumentCommittedNames[instIndex]=inst->m_nickname; if(m_instrumentCurrentNames[instIndex].empty()) m_instrumentCurrentNames[instIndex]=inst->m_nickname; ImGui::SetNextItemWidth(12*width); - if(TextInputWithExplicitApply("",m_instrumentCurrentNames[instIndex],m_instrumentCommittedNames[instIndex])) + if(TextInputWithExplicitApply("###nickname",m_instrumentCurrentNames[instIndex],m_instrumentCommittedNames[instIndex])) { string oldName = inst->m_nickname; inst->m_nickname = m_instrumentCommittedNames[instIndex]; @@ -462,8 +462,27 @@ void ManageInstrumentsDialog::AllInstrumentsTable() } if(ImGui::TableSetColumnIndex(4)) { - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(inst->GetTransportConnectionString().c_str()); + if(inst->IsOffline()) + { // Only allow changing path if instrument is Offline + if(m_instrumentCommittedPaths[instIndex].empty()) m_instrumentCommittedPaths[instIndex]=inst->GetTransportConnectionString(); + if(m_instrumentCurrentPaths[instIndex].empty()) m_instrumentCurrentPaths[instIndex]=inst->GetTransportConnectionString(); + ImGui::SetNextItemWidth(12*width); + if(TextInputWithExplicitApply("###path",m_instrumentCurrentPaths[instIndex],m_instrumentCommittedPaths[instIndex])) + { + string oldPath = inst->GetTransportConnectionString(); + auto mi = dynamic_pointer_cast(inst); + if(mi) + mi->SetTransportConnectionString(m_instrumentCommittedPaths[instIndex]); + auto si = dynamic_pointer_cast(inst); + if(si) + m_parent->RepathRecentInstrument(si,oldPath); + } + } + else + { + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(inst->GetTransportConnectionString().c_str()); + } } if(ImGui::TableSetColumnIndex(5)) { From ab10e14fb27b02b16ebe49b585c4bd242ec79823 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 19 Jan 2026 22:04:28 +0100 Subject: [PATCH 09/10] Fixed copyright. --- lib | 2 +- src/ngscopeclient/AddInstrumentDialog.cpp | 2 +- src/ngscopeclient/AddInstrumentDialog.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib b/lib index 3cb18ba8..b978fa3d 160000 --- a/lib +++ b/lib @@ -1 +1 @@ -Subproject commit 3cb18ba8ae1303ee08fa6098f261ede79c6ac9cc +Subproject commit b978fa3df600078f0aad1de6fc9a9d36a19e95ea diff --git a/src/ngscopeclient/AddInstrumentDialog.cpp b/src/ngscopeclient/AddInstrumentDialog.cpp index 0a9cb558..89020064 100644 --- a/src/ngscopeclient/AddInstrumentDialog.cpp +++ b/src/ngscopeclient/AddInstrumentDialog.cpp @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 Andrew D. Zonenberg * +* Copyright (c) 2012-2026 Andrew D. Zonenberg * * All rights reserved. * * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * diff --git a/src/ngscopeclient/AddInstrumentDialog.h b/src/ngscopeclient/AddInstrumentDialog.h index ac4f0908..2edd0102 100644 --- a/src/ngscopeclient/AddInstrumentDialog.h +++ b/src/ngscopeclient/AddInstrumentDialog.h @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 Andrew D. Zonenberg * +* Copyright (c) 2012-2026 Andrew D. Zonenberg * * All rights reserved. * * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * From e704c77402aca09189871709e52d6d74d73e77bb Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 19 Jan 2026 22:11:37 +0100 Subject: [PATCH 10/10] Fixed copyright. Fixed intrument list change in ManageInstrumentDialog. --- lib | 2 +- src/ngscopeclient/MainWindow_Menus.cpp | 2 +- src/ngscopeclient/ManageInstrumentsDialog.cpp | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib b/lib index b978fa3d..987b8cf3 160000 --- a/lib +++ b/lib @@ -1 +1 @@ -Subproject commit b978fa3df600078f0aad1de6fc9a9d36a19e95ea +Subproject commit 987b8cf3afdc83b02626904f18451b639d0e52a4 diff --git a/src/ngscopeclient/MainWindow_Menus.cpp b/src/ngscopeclient/MainWindow_Menus.cpp index 592cd2d7..6dbe6553 100644 --- a/src/ngscopeclient/MainWindow_Menus.cpp +++ b/src/ngscopeclient/MainWindow_Menus.cpp @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2025 Andrew D. Zonenberg and contributors * +* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors * * All rights reserved. * * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * diff --git a/src/ngscopeclient/ManageInstrumentsDialog.cpp b/src/ngscopeclient/ManageInstrumentsDialog.cpp index 6377e756..47003e09 100644 --- a/src/ngscopeclient/ManageInstrumentsDialog.cpp +++ b/src/ngscopeclient/ManageInstrumentsDialog.cpp @@ -44,7 +44,7 @@ using namespace std; // Construction / destruction ManageInstrumentsDialog::ManageInstrumentsDialog(Session& session, MainWindow* parent) - : Dialog("Manage Instruments", "Manage Instruments", ImVec2(1024, 300)) + : Dialog("Manage Instruments", "Manage Instruments", ImVec2(1024, 300),&session,parent) , m_session(session) , m_parent(parent) //, m_selection(nullptr) @@ -409,10 +409,17 @@ void ManageInstrumentsDialog::AllInstrumentsTable() size_t instNumber = insts.size(); size_t instIndex = 0; - m_instrumentCurrentNames.resize(instNumber); - m_instrumentCommittedNames.resize(instNumber); - m_instrumentCurrentPaths.resize(instNumber); - m_instrumentCommittedPaths.resize(instNumber); + if(instNumber != m_instrumentCurrentNames.size()) + { // Instrument list has changed, clear cache + m_instrumentCurrentNames.clear(); + m_instrumentCommittedNames.clear(); + m_instrumentCurrentPaths.clear(); + m_instrumentCommittedPaths.clear(); + m_instrumentCurrentNames.resize(instNumber); + m_instrumentCommittedNames.resize(instNumber); + m_instrumentCurrentPaths.resize(instNumber); + m_instrumentCommittedPaths.resize(instNumber); + } for(auto inst : insts) {