From d76f076769141c05ffc71a52704cece32acfccf8 Mon Sep 17 00:00:00 2001 From: fredzo Date: Fri, 2 Jan 2026 19:41:25 +0100 Subject: [PATCH 01/49] Fixed AWG shape preview. --- src/ngscopeclient/StreamBrowserDialog.cpp | 57 +++++++++-------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index c64731ba..0f4325af 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -699,22 +699,6 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr auto& prefs = m_session.GetPreferences(); - //Impedance - ImGui::SetNextItemWidth(dwidth); - /* - if(renderCombo( - "Sample Rate", - false, - ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), - m_timebaseConfig[scope]->m_rate, m_timebaseConfig[scope]->m_rateNames)) - { - scope->SetSampleRate(m_timebaseConfig[scope]->m_rates[m_timebaseConfig[scope]->m_rate]); - refresh = true; - } - */ - - //shape = awgState->m_channelShape[channelIndex]; - // Row 1 ImGui::Text("Waveform:"); startBadgeLine(); // Needed for shape combo @@ -738,6 +722,9 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_needsUpdate[channelIndex] = true; } + // Store current Y position for shape preview + float shapePreviewY = ImGui::GetCursorPosY(); + // Row 2 // Frequency label ImGui::SetNextItemWidth(dwidth); @@ -763,7 +750,19 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr DoItemHelp(); HelpMarker("Frequency of the generated waveform"); - /* + // Row 3 + ImGui::SetNextItemWidth(dwidth); + if(UnitInputWithExplicitApply( + "Amplitude", + awgState->m_strAmplitude[channelIndex], + awgState->m_committedAmplitude[channelIndex], + volts)) + { + awg->SetFunctionChannelAmplitude(channelIndex, awgState->m_committedAmplitude[channelIndex]); + awgState->m_needsUpdate[channelIndex] = true; + } + HelpMarker("Peak-to-peak amplitude of the generated waveform"); + // Shape preview startBadgeLine(); auto height = ImGui::GetFontSize() * 2; @@ -772,28 +771,18 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr { // ok, we have enough space draw preview m_badgeXCur -= width; + // save current y position to restore it after drawing the preview + float currentY = ImGui::GetCursorPosY(); + // Continue layout on current line (row 3) ImGui::SameLine(m_badgeXCur); + // But use y position of row 2 + ImGui::SetCursorPosY(shapePreviewY); ImGui::Image( m_parent->GetTextureManager()->GetTexture(m_parent->GetIconForWaveformShape(shape)), ImVec2(width,height)); - // Go back one line since preview spans on two text lines - ImGuiWindow *window = ImGui::GetCurrentWindowRead(); - window->DC.CursorPos.y -= ImGui::GetFontSize(); - } - */ - - // Row 3 - ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithExplicitApply( - "Amplitude", - awgState->m_strAmplitude[channelIndex], - awgState->m_committedAmplitude[channelIndex], - volts)) - { - awg->SetFunctionChannelAmplitude(channelIndex, awgState->m_committedAmplitude[channelIndex]); - awgState->m_needsUpdate[channelIndex] = true; + // Now that we're done with shape preview, restore y position of row 3 + ImGui::SetCursorPosY(currentY); } - HelpMarker("Peak-to-peak amplitude of the generated waveform"); //Row 4 //Offset From 27e5df652000bd5314c8ac766ba8c4d7710b76fe Mon Sep 17 00:00:00 2001 From: fredzo Date: Fri, 2 Jan 2026 23:26:42 +0100 Subject: [PATCH 02/49] Added duty-cycle to AWG in StreamBrowserDialog --- src/ngscopeclient/FunctionGeneratorState.h | 8 ++++ src/ngscopeclient/InstrumentThread.cpp | 1 + src/ngscopeclient/StreamBrowserDialog.cpp | 44 ++++++++++++++++------ 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/ngscopeclient/FunctionGeneratorState.h b/src/ngscopeclient/FunctionGeneratorState.h index 58a8bda3..a0da5fd1 100644 --- a/src/ngscopeclient/FunctionGeneratorState.h +++ b/src/ngscopeclient/FunctionGeneratorState.h @@ -49,6 +49,7 @@ class FunctionGeneratorState m_channelAmplitude = std::make_unique[] >(n); m_channelOffset= std::make_unique[] >(n); m_channelFrequency = std::make_unique[] >(n); + m_channelDutyCycle = std::make_unique[] >(n); m_channelShape = std::make_unique[] >(n); m_channelOutputImpedance = std::make_unique[] >(n); m_channelShapes = std::make_unique[] >(n); @@ -63,6 +64,8 @@ class FunctionGeneratorState m_committedAmplitude = std::make_unique(n); m_strFrequency = std::make_unique(n); m_committedFrequency = std::make_unique(n); + m_strDutyCycle = std::make_unique(n); + m_committedDutyCycle = std::make_unique(n); Unit volts(Unit::UNIT_VOLTS); @@ -72,6 +75,7 @@ class FunctionGeneratorState m_channelAmplitude[i] = 0; m_channelOffset[i] = 0; m_channelFrequency[i] = 0; + m_channelDutyCycle[i] = 0; m_channelShape[i] = FunctionGenerator::WaveShape::SHAPE_SINE; m_channelOutputImpedance[i] = FunctionGenerator::OutputImpedance::IMPEDANCE_HIGH_Z; // Init shape list and names @@ -93,6 +97,7 @@ class FunctionGeneratorState std::unique_ptr[]> m_channelAmplitude; std::unique_ptr[]> m_channelOffset; std::unique_ptr[]> m_channelFrequency; + std::unique_ptr[]> m_channelDutyCycle; std::unique_ptr[]> m_channelShape; std::unique_ptr[]> m_channelOutputImpedance; std::unique_ptr[]> m_channelShapes; @@ -110,6 +115,9 @@ class FunctionGeneratorState std::unique_ptr m_committedFrequency; std::unique_ptr m_strFrequency; + + std::unique_ptr m_committedDutyCycle; + std::unique_ptr m_strDutyCycle; }; #endif diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index e168159b..c66bf584 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -240,6 +240,7 @@ void InstrumentThread(InstrumentThreadArgs args) awgstate->m_channelAmplitude[i] = awg->GetFunctionChannelAmplitude(i); awgstate->m_channelOffset[i] = awg->GetFunctionChannelOffset(i); awgstate->m_channelFrequency[i] = awg->GetFunctionChannelFrequency(i); + awgstate->m_channelDutyCycle[i] = awg->GetFunctionChannelDutyCycle(i); awgstate->m_channelShape[i] = awg->GetFunctionChannelShape(i); awgstate->m_channelOutputImpedance[i] = awg->GetFunctionChannelOutputImpedance(i); session->MarkChannelDirty(awgchan); diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 0f4325af..6ea5355a 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -669,6 +669,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr { Unit volts(Unit::UNIT_VOLTS); Unit hz(Unit::UNIT_HZ); + Unit percent(Unit::UNIT_PERCENT); size_t channelIndex = awgchan->GetIndex(); auto awgState = m_session.GetFunctionGeneratorState(awg); @@ -696,6 +697,12 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_committedFrequency[channelIndex] = freq; awgState->m_strFrequency[channelIndex] = hz.PrettyPrint(freq); } + float dutyCycle = awgState->m_channelDutyCycle[channelIndex]; + if(dutyCycle != awgState->m_committedDutyCycle[channelIndex]) + { + awgState->m_committedDutyCycle[channelIndex] = dutyCycle; + awgState->m_strDutyCycle[channelIndex] = percent.PrettyPrint(dutyCycle); + } auto& prefs = m_session.GetPreferences(); @@ -746,22 +753,23 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr ImGui::EndDragDropSource(); } else - */ DoItemHelp(); - HelpMarker("Frequency of the generated waveform"); + */ + //HelpMarker("Frequency of the generated waveform"); - // Row 3 + //Row 2 + //Duty cycle ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithExplicitApply( - "Amplitude", - awgState->m_strAmplitude[channelIndex], - awgState->m_committedAmplitude[channelIndex], - volts)) + if(UnitInputWithImplicitApply( + "Duty cycle", + awgState->m_strDutyCycle[channelIndex], + awgState->m_committedDutyCycle[channelIndex], + percent)) { - awg->SetFunctionChannelAmplitude(channelIndex, awgState->m_committedAmplitude[channelIndex]); + awg->SetFunctionChannelDutyCycle(channelIndex, awgState->m_committedDutyCycle[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } - HelpMarker("Peak-to-peak amplitude of the generated waveform"); + //HelpMarker("Duty cycle of the generated waveform"); // Shape preview startBadgeLine(); @@ -784,6 +792,19 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr ImGui::SetCursorPosY(currentY); } + // Row 3 + ImGui::SetNextItemWidth(dwidth); + if(UnitInputWithExplicitApply( + "Amplitude", + awgState->m_strAmplitude[channelIndex], + awgState->m_committedAmplitude[channelIndex], + volts)) + { + awg->SetFunctionChannelAmplitude(channelIndex, awgState->m_committedAmplitude[channelIndex]); + awgState->m_needsUpdate[channelIndex] = true; + } + HelpMarker("Peak-to-peak amplitude of the generated waveform"); + //Row 4 //Offset ImGui::SetNextItemWidth(dwidth); @@ -798,8 +819,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr } HelpMarker("DC offset for the waveform above (positive) or below (negative) ground"); - //TODO: Duty cycle - + //Row 5 //Impedance ImGui::SetNextItemWidth(dwidth); FunctionGenerator::OutputImpedance impedance = awgState->m_channelOutputImpedance[channelIndex]; From bb53c34f5e2948dfc5e52adc2e6717c26a044918 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sat, 3 Jan 2026 14:54:43 +0100 Subject: [PATCH 03/49] Deduplicated renderToggle/renderToggleEXT and renderOnOffToggle/renderOnOffToggleEXT methods. --- src/ngscopeclient/StreamBrowserDialog.cpp | 56 ++++++----------------- src/ngscopeclient/StreamBrowserDialog.h | 8 +--- 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 6ea5355a..79a1edc6 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -372,22 +372,6 @@ bool StreamBrowserDialog::renderCombo( return renderCombo(label, alignRight, color, (*selected), values); } -/** - @brief Render a toggle button combo - - @param color the color of the toggle button - @param curValue the value of the toggle button - @return the selected value for the toggle button - - TODO: replace with renderToggleEXT - */ -bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool curValue) -{ - int selection = (int)curValue; - renderCombo(label, alignRight, color, &selection, "OFF", "ON", nullptr); - return (selection == 1); -} - /** @brief Render a toggle button combo @@ -395,7 +379,7 @@ bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec @param curValue the value of the toggle button @return true if selection has changed */ -bool StreamBrowserDialog::renderToggleEXT(const char* label, bool alignRight, ImVec4 color, bool& curValue) +bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool& curValue) { int selection = (int)curValue; bool ret = renderCombo(label, alignRight, color, &selection, "OFF", "ON", nullptr); @@ -403,38 +387,20 @@ bool StreamBrowserDialog::renderToggleEXT(const char* label, bool alignRight, Im return ret; } -/** - @brief Render an on/off toggle button combo - - @param curValue the value of the toggle button - @return the selected value for the toggle button - - TODO: replace with renderOnOffToggleEXT - */ -bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool curValue) -{ - auto& prefs = m_session.GetPreferences(); - ImVec4 color = ImGui::ColorConvertU32ToFloat4( - (curValue ? - prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : - prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); - return renderToggle(label, alignRight, color, curValue); -} - /** @brief Render an on/off toggle button combo @param curValue the value of the toggle button @return true if value has changed */ -bool StreamBrowserDialog::renderOnOffToggleEXT(const char* label, bool alignRight, bool& curValue) +bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue) { auto& prefs = m_session.GetPreferences(); ImVec4 color = ImGui::ColorConvertU32ToFloat4( (curValue ? prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); - return renderToggleEXT(label, alignRight, color, curValue); + return renderToggle(label, alignRight, color, curValue); } /** @@ -927,16 +893,18 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument bool result; if(allOn || someOn) { - result = renderToggle( + 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")), true); + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_partial_badge_color")), result); } else { - result = renderOnOffToggle("###psuon", true, false); + result = false; + renderOnOffToggle("###psuon", true, result); } if(result != allOn) { @@ -1123,7 +1091,7 @@ void StreamBrowserDialog::DoTimebaseSettings(shared_ptr scope) ImGui::SetNextItemWidth(width); bool disabled = !scope->CanInterleave(); ImGui::BeginDisabled(disabled); - if(renderOnOffToggleEXT("Interleaving", false, config->m_interleaving)) + if(renderOnOffToggle("Interleaving", false, config->m_interleaving)) { scope->SetInterleaving(config->m_interleaving); refresh = true; @@ -1351,7 +1319,8 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto psustate = m_session.GetPSUState(psu); bool active = psustate->m_channelOn[channelIndex]; - bool result = renderOnOffToggle("###active", true, active); + bool result = active; + renderOnOffToggle("###active", true, result); if(result != active) psu->SetPowerChannelActive(channelIndex,result); } @@ -1361,7 +1330,8 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto awgstate = m_session.GetFunctionGeneratorState(awg); bool active = awgstate->m_channelActive[channelIndex]; - bool result = renderOnOffToggle("###active", true, active); + bool result = active; + renderOnOffToggle("###active", true, result); if(result != active) { awg->SetFunctionChannelActive(channelIndex,result); diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 011325bc..0f495af7 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -142,17 +142,11 @@ class StreamBrowserDialog : public Dialog int* selected, ...); bool renderToggle( - const char* label, - bool alignRight, - ImVec4 color, - bool curValue); - bool renderToggleEXT( const char* label, bool alignRight, ImVec4 color, bool& curValue); - bool renderOnOffToggle(const char* label, bool alignRight, bool curValue); - bool renderOnOffToggleEXT(const char* label, bool alignRight, bool& curValue); + bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); From 8be968592625353004cba4a977533e1338c104e7 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sat, 3 Jan 2026 16:15:27 +0100 Subject: [PATCH 04/49] Added on and off values optional parameters to renderToggle and renderOnOffToggle methods. --- src/ngscopeclient/StreamBrowserDialog.cpp | 17 +++++++++++++---- src/ngscopeclient/StreamBrowserDialog.h | 7 +++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 79a1edc6..82edafec 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -377,12 +377,18 @@ bool StreamBrowserDialog::renderCombo( @param color the color of the toggle button @param curValue the value of the toggle button + @param valueOff label for value off (optionnal, defaults to "OFF") + @param valueOn label for value on (optionnal, defaults to "ON") + @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) @return true if selection has changed */ -bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool& curValue) +bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo) { int selection = (int)curValue; - bool ret = renderCombo(label, alignRight, color, &selection, "OFF", "ON", nullptr); + std::vector values; + values.push_back(string(valueOff)); + values.push_back(string(valueOn)); + bool ret = renderCombo(label, alignRight, color, selection, values, false, cropTextTo); curValue = (selection == 1); return ret; } @@ -391,16 +397,19 @@ bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec @brief Render an on/off toggle button combo @param curValue the value of the toggle button + @param valueOff label for value off (optionnal, defaults to "OFF") + @param valueOn label for value on (optionnal, defaults to "ON") + @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) @return true if value has changed */ -bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue) +bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo) { auto& prefs = m_session.GetPreferences(); ImVec4 color = ImGui::ColorConvertU32ToFloat4( (curValue ? prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); - return renderToggle(label, alignRight, color, curValue); + return renderToggle(label, alignRight, color, curValue, valueOff, valueOn, cropTextTo); } /** diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 0f495af7..71138c71 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -145,8 +145,11 @@ class StreamBrowserDialog : public Dialog const char* label, bool alignRight, ImVec4 color, - bool& curValue); - bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue); + bool& curValue, + const char* valueOff = "OFF", + const char* valueOn = "ON", + uint8_t cropTextTo = 0); + bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); From 72c354d178dd9de94392bb37f4e28e4724824f36 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sat, 3 Jan 2026 16:27:13 +0100 Subject: [PATCH 05/49] Fixed doc. --- src/ngscopeclient/StreamBrowserDialog.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 82edafec..b1e3c423 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -346,10 +346,12 @@ bool StreamBrowserDialog::renderCombo( /** @brief Render a combo box with provded color and values + @param label Label for the combo box + @param alignRight true if the combo should be aligned to the right @param color the color of the combo box @param selected the selected value index (in/out) @param ... the combo box values - @return true true if the selected value of the combo has been changed + @return true if the selected value of the combo has been changed */ bool StreamBrowserDialog::renderCombo( const char* label, @@ -375,6 +377,8 @@ bool StreamBrowserDialog::renderCombo( /** @brief Render a toggle button combo + @param label Label for the combo box + @param alignRight true if the combo should be aligned to the right @param color the color of the toggle button @param curValue the value of the toggle button @param valueOff label for value off (optionnal, defaults to "OFF") @@ -396,6 +400,8 @@ bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec /** @brief Render an on/off toggle button combo + @param label Label for the combo box + @param alignRight true if the combo should be aligned to the right @param curValue the value of the toggle button @param valueOff label for value off (optionnal, defaults to "OFF") @param valueOn label for value on (optionnal, defaults to "ON") From 63f3d625e7ae6baff4df0f6ec8b15974047d2c3d Mon Sep 17 00:00:00 2001 From: fredzo Date: Sat, 3 Jan 2026 17:26:02 +0100 Subject: [PATCH 06/49] Added 7 segment display methods. --- src/ngscopeclient/Dialog.cpp | 277 +++++++++++++++++++++++++++++++++++ src/ngscopeclient/Dialog.h | 4 + 2 files changed, 281 insertions(+) diff --git a/src/ngscopeclient/Dialog.cpp b/src/ngscopeclient/Dialog.cpp index 92a41104..84379998 100644 --- a/src/ngscopeclient/Dialog.cpp +++ b/src/ngscopeclient/Dialog.cpp @@ -424,3 +424,280 @@ bool Dialog::UnitInputWithExplicitApply( ImGui::EndGroup(); return changed; } + +/** + @brief Segment on/off state for each of the 10 digits + "L" (needed for OL / Overload) + 0b01000000 : Top h segment + 0b00100000 : Top right v seglent + 0b00010000 : Bottom right v segment + 0b00001000 : Bottom h segment + 0b00000100 : Bottom left v segment + 0b00000010 : Top left v segment + 0b00000001 : Center h segment + */ +static char SEGMENTS[] = +{ + 0x7E, // 0 + 0x30, // 1 + 0x6D, // 2 + 0x79, // 3 + 0x33, // 4 + 0x5B, // 5 + 0x5F, // 6 + 0x70, // 7 + 0x7F, // 8 + 0x7B, // 9 + 0x0E, // L +}; + +/** + @brief Render a single digit in 7 segment display style + @param drawList the drawList used for rendering + @param digit the digit to render + @param size the size of the digit + @param position the position of the digit + @param thickness the thickness of a segment + @param colorOn the color for an "on" segment + @param colorOff the color for an "off" segment + */ +void Dialog::Render7SegmentDigit(ImDrawList* drawList, uint8_t digit, ImVec2 size, ImVec2 position, float thickness, ImU32 colorOn, ImU32 colorOff) +{ + // Inspired by https://github.com/ocornut/imgui/issues/3606#issuecomment-736855952 + if(digit > 10) + digit = 10; // 10 is for L of OL (Overload) + size.y += thickness; + ImVec2 halfSize(size.x/2,size.y/2); + ImVec2 centerPosition(position.x+halfSize.x,position.y+halfSize.y); + float w = thickness; + float h = thickness/2; + float segmentSpec[7][4] = + { + {-1, -1, h, h}, // Top h segment + { 1, -1, -h, h}, // Top right v seglent + { 1, 0, -h, -h}, // Bottom right v segment + {-1, 1, h, -w * 1.5f},// Bottom h segment + {-1, 0, h, -h}, // Bottom left v segment + {-1, -1, h, h}, // Top left v segment + {-1, 0, h, -h}, // Center h segment + }; + for(int i = 0; i < 7; i++) + { + ImVec2 topLeft, bottomRight; + if(i % 3 == 0) + { + // Horizontal segment + topLeft = ImVec2(centerPosition.x + segmentSpec[i][0] * halfSize.x + segmentSpec[i][2], centerPosition.y + segmentSpec[i][1] * halfSize.y + segmentSpec[i][3] - h); + bottomRight = ImVec2(topLeft.x + size.x - w, topLeft.y + w); + } + else + { + // Vertical segment + topLeft = ImVec2(centerPosition.x + segmentSpec[i][0] * halfSize.x + segmentSpec[i][2] - h, centerPosition.y + segmentSpec[i][1] * halfSize.y + segmentSpec[i][3]); + bottomRight = ImVec2(topLeft.x + w, topLeft.y + halfSize.y - w); + } + ImVec2 segmentSize = bottomRight - topLeft; + float space = w * 0.6; + float u = space - h; + if(segmentSize.x > segmentSize.y) + { + // Horizontal segment + ImVec2 points[] = + { + {topLeft.x + u, topLeft.y + segmentSize.y * .5f}, + {topLeft.x + space, topLeft.y}, + {bottomRight.x - space, topLeft.y}, + {bottomRight.x - u, topLeft.y + segmentSize.y * .5f}, + {bottomRight.x - space, bottomRight.y}, + {topLeft.x + space, bottomRight.y} + }; + drawList->AddConvexPolyFilled(points, 6, (SEGMENTS[digit] >> (6 - i)) & 1 ? colorOn : colorOff); + } + else + { + // Vertical segment + ImVec2 points[] = { + {topLeft.x + segmentSize.x * .5f, topLeft.y + u}, + {bottomRight.x, topLeft.y + space}, + {bottomRight.x, bottomRight.y - space}, + {bottomRight.x - segmentSize.x * .5f, bottomRight.y - u}, + {topLeft.x, bottomRight.y - space}, + {topLeft.x, topLeft.y + space}}; + drawList->AddConvexPolyFilled(points, 6, (SEGMENTS[digit] >> (6 - i)) & 1 ? colorOn : colorOff); + } + } +} + +// @brief ratio between unit font size and digit size +#define UNIT_SCALE 0.80f + +// @brief ratio between digit width and height +#define DIGIT_WIDTH_RATIO 0.50f + +/** + @brief Render a numeric value with a 7 segment display style + @param value the string representation of the value to display (may include the unit) + @param color the color to use + @param digitHeight the height of a digit + */ +void Dialog::Render7SegmentValue(const std::string& value, ImVec4 color, float digitHeight) +{ + bool ignoredClicked, ignoredHovered; + Render7SegmentValue(value,color,digitHeight,ignoredClicked,ignoredHovered,false); +} + +/** + @brief Render a numeric value with a 7 segment display style + @param value the string representation of the value to display (may include the unit) + @param color the color to use + @param digitHeight the height of a digit + @param clicked output value for clicked state + @param hovered output value for hovered state + @param clickable true (default) if the displayed value should be clickable + */ +void Dialog::Render7SegmentValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable) +{ + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Compute digit width according th height + float digitWidth = digitHeight*DIGIT_WIDTH_RATIO; + + // Compute front and back color + float bgmul = 0.15; + auto bcolor = ImGui::ColorConvertFloat4ToU32(ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w)); + auto fcolor = ImGui::ColorConvertFloat4ToU32(color); + + + // Parse value string to get integer and fractional part + unit + bool inIntPart = true; + bool inFractPart = false; + vector intPart; + vector fractPart; + string unit; + + // TODO : move this to Unit.h + #define UNIT_OVERLOAD_LABEL "Overload" + + if(value == UNIT_OVERLOAD_LABEL) + { + // Overload + intPart.push_back(0); + intPart.push_back(10); // 10 is for L + unit = "Inf."; + } + else + { + // Iterate on each char of the value string + for(const char c : value) + { + if(c >= '0' && c <='9') + { + // This is a numeric digit + if(inIntPart) + intPart.push_back((uint8_t)(c-'0')); + else if(inFractPart) + fractPart.push_back((uint8_t)(c-'0')); + else + unit += c; + } + else if(c == '.' || c == std::use_facet >(std::locale()).decimal_point() || c == ',') + { + // This is the decimal separator + if(inIntPart) + { + inFractPart = true; + inIntPart = false; + } + else + LogWarning("Unexpected decimal separator '%c' in value '%s'.\n",c,value.c_str()); + } + else if(isspace(c) || c == std::use_facet< std::numpunct >(std::locale()).thousands_sep()) + { + // We ingore spaces (except in unit part) + if(inIntPart || inFractPart) {} // Ignore + else + unit += c; + } + else // Anything else + { + // This is the unit + inFractPart = false; + inIntPart = false; + unit += c; + } + } + // Trim the unit string + unit = Trim(unit); + + // Fill fractional part with 2 zeros if it's empty + if(fractPart.empty()) + { + fractPart.push_back(0); + fractPart.push_back(0); + } + } + + // Segment thickness + float thickness = digitHeight/10; + + // Space between digits + float spacing = 0.08 * digitWidth; + + // Size of decimal separator + float dotSize = 2*thickness; + + // Size of unit font and unit text + float unitSize = digitHeight*UNIT_SCALE; + float unitTextWidth = ImGui::GetFont()->CalcTextSizeA(unitSize,FLT_MAX, 0.0f,unit.c_str()).x; + + ImVec2 size(digitWidth*(intPart.size()+fractPart.size())+dotSize+2*spacing+unitTextWidth+thickness, digitHeight); + + if(clickable) + { + bgmul = 0.0f; + ImVec4 buttonColor = ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, 0); + bgmul = 0.2f; + ImVec4 buttonColorHovered = ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w); + bgmul = 0.3f; + ImVec4 buttonColorActive = ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); + clicked |= ImGui::Button(" ",size); + hovered |= ImGui::IsItemHovered(); + ImGui::PopStyleColor(3); + if(hovered) + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + else + ImGui::InvisibleButton("seven", size, ImGuiButtonFlags_EnableNav); + + ImVec2 position = ImGui::GetItemRectMin(); + + // Actual digit width (without space) + float digitActualWidth = digitWidth - spacing; + // Current x position + float x = 0; + + // Integer part + for(size_t i = 0; i < intPart.size(); i++) + { + Render7SegmentDigit(draw_list, intPart[i], ImVec2(digitActualWidth, digitHeight), ImVec2(position.x + x, position.y),thickness,fcolor,bcolor); + x += digitWidth; + } + // Decimal separator + x+= spacing; + draw_list->AddCircleFilled(ImVec2(position.x+x+dotSize/2-spacing/2,position.y+digitHeight-dotSize/2),dotSize/2,fcolor); + x+= dotSize; + x+= spacing; + // Factional part + for(size_t i = 0; i < fractPart.size(); i++) + { + Render7SegmentDigit(draw_list, fractPart[i], ImVec2(digitActualWidth, digitHeight), ImVec2(position.x + x, position.y),thickness,fcolor,bcolor); + x += digitWidth; + } + // Unit + draw_list->AddText(NULL,unitSize, + ImVec2(position.x + x + thickness, position.y), + fcolor, + unit.c_str()); +} \ No newline at end of file diff --git a/src/ngscopeclient/Dialog.h b/src/ngscopeclient/Dialog.h index 43bd4957..88d19d4e 100644 --- a/src/ngscopeclient/Dialog.h +++ b/src/ngscopeclient/Dialog.h @@ -96,6 +96,10 @@ class Dialog void RenderErrorPopup(); void ShowErrorPopup(const std::string& title, const std::string& msg); + void Render7SegmentDigit(ImDrawList* drawList, uint8_t digit, ImVec2 size, ImVec2 position, float thikness, ImU32 colorOn, ImU32 colorOff); + void Render7SegmentValue(const std::string& value, ImVec4 color, float digitHeight); + void Render7SegmentValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable = true); + bool m_open; std::string m_id; std::string m_title; From 6a9adea03b90d9837dc3329fac4ee601a1a10e8a Mon Sep 17 00:00:00 2001 From: fredzo Date: Sat, 3 Jan 2026 17:27:36 +0100 Subject: [PATCH 07/49] Added autorange cache in Multimeter state. --- src/ngscopeclient/InstrumentThread.cpp | 6 ++++++ src/ngscopeclient/MultimeterDialog.cpp | 3 +++ src/ngscopeclient/MultimeterState.h | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index c66bf584..15eb541a 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -176,6 +176,12 @@ void InstrumentThread(InstrumentThreadArgs args) meterstate->m_secondaryMeasurement = chan->GetSecondaryValue(); meterstate->m_firstUpdateDone = true; + if(meterstate->m_needsRangeUpdate.load()) + { // We need to update range state + meterstate->m_autoRange = meter->GetMeterAutoRange(); + meterstate->m_needsRangeUpdate = false; + } + session->MarkChannelDirty(chan); } } diff --git a/src/ngscopeclient/MultimeterDialog.cpp b/src/ngscopeclient/MultimeterDialog.cpp index ec13e382..bef63d2e 100644 --- a/src/ngscopeclient/MultimeterDialog.cpp +++ b/src/ngscopeclient/MultimeterDialog.cpp @@ -137,7 +137,10 @@ bool MultimeterDialog::DoRender() if(ImGui::CollapsingHeader("Configuration", ImGuiTreeNodeFlags_DefaultOpen)) { if(ImGui::Checkbox("Autorange", &m_autorange)) + { m_meter->SetMeterAutoRange(m_autorange); + m_state->m_needsRangeUpdate = true; + } HelpMarker("Enables automatic selection of meter scale ranges."); //Channel selector (hide if we have only one channel) diff --git a/src/ngscopeclient/MultimeterState.h b/src/ngscopeclient/MultimeterState.h index 2ae95c95..bf35a690 100644 --- a/src/ngscopeclient/MultimeterState.h +++ b/src/ngscopeclient/MultimeterState.h @@ -47,11 +47,15 @@ class MultimeterState m_primaryMeasurement = 0; m_secondaryMeasurement = 0; m_firstUpdateDone = false; + m_autoRange = true; + m_needsRangeUpdate = true; } std::atomic m_primaryMeasurement; std::atomic m_secondaryMeasurement; std::atomic m_firstUpdateDone; + std::atomic m_autoRange; + std::atomic m_needsRangeUpdate; }; #endif From f037ca79c12606c2c3b1af38a1c6ed9c09810a73 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sat, 3 Jan 2026 17:30:11 +0100 Subject: [PATCH 08/49] Added DMM support in StreamBrowserDialog. Added 7 segment display for DMM and PSU values in StreamBrowserDialog. --- src/ngscopeclient/PreferenceSchema.cpp | 8 ++ src/ngscopeclient/Session.h | 9 ++ src/ngscopeclient/StreamBrowserDialog.cpp | 158 +++++++++++++++++++++- src/ngscopeclient/StreamBrowserDialog.h | 1 + 4 files changed, 172 insertions(+), 4 deletions(-) diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index 6ae14ff9..e2f4a2cf 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -165,6 +165,10 @@ void PreferenceManager::InitializeDefaults() .Description("Color for icon captions")); auto& stream = appearance.AddCategory("Stream Browser"); + stream.AddPreference( + Preference::Bool("use_7_segment_display", true) + .Label("Use 7 segment style display") + .Description("Use 7 segment style display for DMM and PSU values")); stream.AddPreference( Preference::Real("instrument_badge_latch_duration", 0.4) .Label("Intrument badge latch duration (seconds)") @@ -241,6 +245,10 @@ void PreferenceManager::InitializeDefaults() Preference::Color("psu_meas_label_color", ColorFromString("#00C100")) .Label("PSU measured label color") .Description("Color for PSU 'meas.' label")); + stream.AddPreference( + Preference::Color("psu_7_segment_color", ColorFromString("#B2FFFF")) + .Label("PSU 7 segment display color") + .Description("Color for PSU 7 segment style display")); stream.AddPreference( Preference::Color("awg_hiz_badge_color", ColorFromString("#666600")) .Label("Function Generator HI-Z badge color") diff --git a/src/ngscopeclient/Session.h b/src/ngscopeclient/Session.h index 7a8677d5..d8b00010 100644 --- a/src/ngscopeclient/Session.h +++ b/src/ngscopeclient/Session.h @@ -171,6 +171,15 @@ class Session return m_awgs[awg]; } + /** + @brief Returns a pointer to the state for a Digital Multimeter + */ + std::shared_ptr GetDmmState(std::shared_ptr dmm) + { + std::lock_guard lock(m_scopeMutex); + return m_meters[dmm]; + } + /** @brief Returns a pointer to the existing packet manager for a protocol decode filter */ diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index b1e3c423..14688c0f 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -605,8 +605,17 @@ void StreamBrowserDialog::renderPsuRows( ImGui::PopID(); ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "sV" : "sC"); - clicked |= ImGui::TextLink(setValue); - hovered |= ImGui::IsItemHovered(); + + float height = ImGui::GetFontSize(); + ImVec4 color = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.psu_7_segment_color")); + + if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) + Render7SegmentValue(setValue,color,height,clicked,hovered); + else + { + clicked |= ImGui::TextLink(setValue); + hovered |= ImGui::IsItemHovered(); + } ImGui::PopID(); // Row 2 ImGui::TableNextRow(); @@ -635,8 +644,127 @@ void StreamBrowserDialog::renderPsuRows( ImGui::PopID(); ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "mV" : "mC"); - clicked |= ImGui::TextLink(measuredValue); - hovered |= ImGui::IsItemHovered(); + + if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) + Render7SegmentValue(measuredValue, color,height,clicked,hovered); + else + { + clicked |= ImGui::TextLink(measuredValue); + hovered |= ImGui::IsItemHovered(); + } + ImGui::PopID(); +} + +/** + @brief Render DMM channel properties + @param dmm the DMM to render channel properties for + @param dmmchan the DMM channel to render properties for + @param clicked output param for clicked state + @param hovered output param for hovered state + */ +void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered) +{ + auto& prefs = m_session.GetPreferences(); + size_t streamIndex = isMain ? 0 : 1; + Unit unit = dmmchan->GetYAxisUnits(streamIndex); + float mainValue = dmmchan->GetScalarValue(streamIndex); + string valueText = unit.PrettyPrint(mainValue,dmm->GetMeterDigits()); + ImVec4 color = ImGui::ColorConvertU32ToFloat4(ColorFromString(dmmchan->m_displaycolor)); + string streamName = isMain ? "Main" : "Secondary"; + + ImGui::PushID(streamName.c_str()); + + // Get available operating and current modes + auto modemask = isMain ? dmm->GetMeasurementTypes() : dmm->GetSecondaryMeasurementTypes(); + auto curMode = isMain ? dmm->GetMeterMode() : dmm->GetSecondaryMeterMode(); + + // Stream name + bool open = ImGui::TreeNodeEx(streamName.c_str(), (curMode > 0) ? ImGuiTreeNodeFlags_DefaultOpen : 0); + + // Mode combo + startBadgeLine(); + ImGui::PushID(streamName.c_str()); + vector modeNames; + vector modes; + if(!isMain) + { + // Add None for secondary measurement to be able to disable it + modeNames.push_back("None"); + modes.push_back(Multimeter::MeasurementTypes::NONE); + } + int modeSelector = 0; + for(unsigned int i=0; i<32; i++) + { + auto mode = static_cast(1 << i); + if(modemask & mode) + { + modes.push_back(mode); + modeNames.push_back(dmm->ModeToText(mode)); + if(curMode == mode) + modeSelector = modes.size() - 1; + } + } + + if(renderCombo("#mode", true, color, modeSelector, modeNames,true,3)) + { + curMode = modes[modeSelector]; + if(isMain) + dmm->SetMeterMode(curMode); + else + { + dmm->SetSecondaryMeterMode(curMode); + // Open or close tree node according the secondary measure mode + ImGuiContext& g = *GImGui; + ImGui::TreeNodeSetOpen(g.LastItemData.ID,(curMode > 0)); + } + } + ImGui::PopID(); + + StreamDescriptor s(dmmchan, streamIndex); + if(ImGui::BeginDragDropSource()) + { + if(s.GetType() == Stream::STREAM_TYPE_ANALOG_SCALAR) + ImGui::SetDragDropPayload("Scalar", &s, sizeof(s)); + else + ImGui::SetDragDropPayload("Stream", &s, sizeof(s)); + + ImGui::TextUnformatted(s.GetName().c_str()); + ImGui::EndDragDropSource(); + } + else + DoItemHelp(); + + if(open) + ImGui::TreePop(); + + if(open) + { + if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) + Render7SegmentValue(valueText, color,ImGui::GetFontSize()*2,clicked,hovered); + else + { + clicked |= ImGui::TextLink(valueText.c_str()); + hovered |= ImGui::IsItemHovered(); + } + if(isMain) + { + auto dmmState = m_session.GetDmmState(dmm); + if(dmmState) + { + ImGui::PushID("autorange"); + // For main, also show the autorange combo + startBadgeLine(); + bool autorange = dmmState->m_autoRange.load(); + if(renderOnOffToggle("#autorange",true,autorange,"Manual Range","Autorange",3)) + { + dmm->SetMeterAutoRange(autorange); + dmmState->m_needsRangeUpdate = true; + } + ImGui::PopID(); + } + } + } + ImGui::PopID(); } @@ -1245,11 +1373,13 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto psu = std::dynamic_pointer_cast(instrument); auto scope = std::dynamic_pointer_cast(instrument); auto awg = std::dynamic_pointer_cast(instrument); + auto dmm = std::dynamic_pointer_cast(instrument); bool singleStream = channel->GetStreamCount() == 1; auto scopechan = dynamic_cast(channel); auto psuchan = dynamic_cast(channel); auto awgchan = dynamic_cast(channel); + auto dmmchan = dynamic_cast(channel); bool renderProps = false; bool isDigital = false; if (scopechan) @@ -1406,6 +1536,26 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s renderAwgProperties(awg, awgchan); ImGui::PopID(); } + else if(dmm && dmmchan) + { + ImGui::BeginChild("dmm_params", ImVec2(0, 0), + ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); + // Always 2 streams for dmm channel => render properties on channel node + bool clicked = false; + bool hovered = false; + // Main measurement + renderDmmProperties(dmm,dmmchan,true,clicked,hovered); + // Secondary measurement + renderDmmProperties(dmm,dmmchan,false,clicked,hovered); + if (clicked) + { + m_parent->ShowInstrumentProperties(dmm); + } + if (hovered) + m_parent->AddStatusHelp("mouse_lmb", "Open Multimeter properties"); + + ImGui::EndChild(); + } else { size_t streamCount = channel->GetStreamCount(); diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 71138c71..f88c697e 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -153,6 +153,7 @@ class StreamBrowserDialog : public Dialog void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); + void renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered); // Rendering of an instrument node void renderInstrumentNode(std::shared_ptr instrument); From 221599faa82bc2de1958485440da6245dc11e0a1 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sat, 3 Jan 2026 17:56:19 +0100 Subject: [PATCH 09/49] Force multimter autorange cache update if needed uppon Multimeter dialog open. --- src/ngscopeclient/MultimeterDialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ngscopeclient/MultimeterDialog.cpp b/src/ngscopeclient/MultimeterDialog.cpp index bef63d2e..b0a8184c 100644 --- a/src/ngscopeclient/MultimeterDialog.cpp +++ b/src/ngscopeclient/MultimeterDialog.cpp @@ -75,6 +75,10 @@ MultimeterDialog::MultimeterDialog(shared_ptr meter, shared_ptr< } } + //Check for autorange state change + if(m_state->m_autoRange.load() != m_autorange) + m_state->m_needsRangeUpdate = true; + //Secondary operating modes RefreshSecondaryModeList(); } From 153a909e63d3a5c6b7666a3eba9affd7c990b465 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sun, 4 Jan 2026 00:33:48 +0100 Subject: [PATCH 10/49] Fixed combo rendering glitch: non displayable combo label need 2 "#" not one. --- src/ngscopeclient/StreamBrowserDialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 14688c0f..ea63f12c 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -705,7 +705,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M } } - if(renderCombo("#mode", true, color, modeSelector, modeNames,true,3)) + if(renderCombo("##mode", true, color, modeSelector, modeNames,true,3)) { curMode = modes[modeSelector]; if(isMain) @@ -755,7 +755,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M // For main, also show the autorange combo startBadgeLine(); bool autorange = dmmState->m_autoRange.load(); - if(renderOnOffToggle("#autorange",true,autorange,"Manual Range","Autorange",3)) + if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3)) { dmm->SetMeterAutoRange(autorange); dmmState->m_needsRangeUpdate = true; @@ -823,7 +823,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr FunctionGenerator::WaveShape shape = awgState->m_channelShape[channelIndex]; int shapeIndex = awgState->m_channelShapeIndexes[channelIndex][shape]; if(renderCombo( - "#waveform", + "##waveform", true, ImGui::ColorConvertU32ToFloat4(ColorFromString(awgchan->m_displaycolor)), shapeIndex, awgState->m_channelShapeNames[channelIndex], From a02b85c432765a2b6d3e2a65ff10a15eb1d28c15 Mon Sep 17 00:00:00 2001 From: fredzo Date: Sun, 4 Jan 2026 01:33:20 +0100 Subject: [PATCH 11/49] Added show block border setting for StreamBrowserDialog : enable/disable borders around SteamBrowserDialog blocks (e.g. channel properties). --- src/ngscopeclient/PreferenceSchema.cpp | 4 +++ src/ngscopeclient/StreamBrowserDialog.cpp | 43 +++++++++++++++++------ src/ngscopeclient/StreamBrowserDialog.h | 4 +++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index e2f4a2cf..41d27b87 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -169,6 +169,10 @@ void PreferenceManager::InitializeDefaults() Preference::Bool("use_7_segment_display", true) .Label("Use 7 segment style display") .Description("Use 7 segment style display for DMM and PSU values")); + stream.AddPreference( + Preference::Bool("show_block_border", true) + .Label("Show block border") + .Description("Add a visual border around stream browser blocks (e.g. channel properties)")); stream.AddPreference( Preference::Real("instrument_badge_latch_duration", 0.4) .Label("Intrument badge latch duration (seconds)") diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index ea63f12c..26cb98b9 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1070,6 +1070,7 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument { if(ImGui::TreeNodeEx("Timebase", ImGuiTreeNodeFlags_DefaultOpen)) { + BeginBlock("timebase"); if(scope->HasTimebaseControls()) DoTimebaseSettings(scope); if(scope->HasFrequencyControls()) @@ -1077,6 +1078,7 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument auto spec = dynamic_pointer_cast(scope); if(spec) DoSpectrometerSettings(spec); + EndBlock(); ImGui::TreePop(); } @@ -1498,8 +1500,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if(psu) { // For PSU we will have a special handling for the 4 streams associated to a PSU channel - ImGui::BeginChild("psu_params", ImVec2(0, 0), - ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); + BeginBlock("psu_params"); auto svoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageSetPoint ()); auto mvoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageMeasured()); auto scurrent_txt = Unit(Unit::UNIT_AMPS).PrettyPrint(psuchan->GetCurrentSetPoint ()); @@ -1528,18 +1529,17 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if (hovered) m_parent->AddStatusHelp("mouse_lmb", "Open channel properties"); } - ImGui::EndChild(); + EndBlock(); } else if(awg && awgchan) { - ImGui::PushID("awgparams"); + BeginBlock("awgparams"); renderAwgProperties(awg, awgchan); - ImGui::PopID(); + EndBlock(); } else if(dmm && dmmchan) { - ImGui::BeginChild("dmm_params", ImVec2(0, 0), - ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); + BeginBlock("dmm_params"); // Always 2 streams for dmm channel => render properties on channel node bool clicked = false; bool hovered = false; @@ -1554,7 +1554,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if (hovered) m_parent->AddStatusHelp("mouse_lmb", "Open Multimeter properties"); - ImGui::EndChild(); + EndBlock(); } else { @@ -1637,8 +1637,7 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } if(hasProps) { - ImGui::BeginChild("stream_params", ImVec2(0, 0), - ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); + BeginBlock("stream_params"); Unit unit = channel->GetYAxisUnits(streamIndex); bool clicked = false; @@ -1668,7 +1667,7 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } break; } - ImGui::EndChild(); + EndBlock(); if (clicked) { m_parent->ShowChannelProperties(scopechan); @@ -1789,3 +1788,25 @@ void StreamBrowserDialog::DoItemHelp() if(ImGui::IsItemHovered()) m_parent->AddStatusHelp("mouse_lmb_drag", "Add to filter graph or plot"); } + +void StreamBrowserDialog::BeginBlock(const char* label) +{ + auto& prefs = m_session.GetPreferences(); + ImGuiWindowFlags flags = ImGuiChildFlags_AutoResizeY; + if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); + flags |= ImGuiChildFlags_Borders; + } + ImGui::BeginChild(label, ImVec2(0, 0), flags); +} + +void StreamBrowserDialog::EndBlock() +{ + ImGui::EndChild(); + auto& prefs = m_session.GetPreferences(); + if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) + { + ImGui::PopStyleVar(); + } +} \ No newline at end of file diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index f88c697e..70f11080 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -121,6 +121,10 @@ class StreamBrowserDialog : public Dialog void DoItemHelp(); + // Block handling + void BeginBlock(const char *label); + void EndBlock(); + // Rendeding of StreamBrowserDialog elements void renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered); void startBadgeLine(); From d1d1a67c2b6f4ab285a35abdff70758a66ad87fc Mon Sep 17 00:00:00 2001 From: fredzo Date: Sun, 4 Jan 2026 01:54:00 +0100 Subject: [PATCH 12/49] Refactored renderNumericValue() code. --- src/ngscopeclient/StreamBrowserDialog.cpp | 49 +++++++++++++---------- src/ngscopeclient/StreamBrowserDialog.h | 1 + 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 26cb98b9..4ffd9d57 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -418,6 +418,28 @@ bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, return renderToggle(label, alignRight, color, curValue, valueOff, valueOn, cropTextTo); } +/** + @brief Render a numeric value + @param value the string representation of the value to display (may include the unit) + @param color the color to use + @param digitHeight the height of a digit + @param clicked output value for clicked state + @param hovered output value for hovered state + @param clickable true (default) if the displayed value should be clickable + */ +void StreamBrowserDialog::renderNumericValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable) +{ + auto& prefs = m_session.GetPreferences(); + if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) + Render7SegmentValue(value,color,digitHeight,clicked,hovered); + else + { + clicked |= ImGui::TextLink(value.c_str()); + hovered |= ImGui::IsItemHovered(); + } +} + + /** @brief Render a download progress bar for a given instrument channel @@ -609,13 +631,8 @@ void StreamBrowserDialog::renderPsuRows( float height = ImGui::GetFontSize(); ImVec4 color = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.psu_7_segment_color")); - if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) - Render7SegmentValue(setValue,color,height,clicked,hovered); - else - { - clicked |= ImGui::TextLink(setValue); - hovered |= ImGui::IsItemHovered(); - } + renderNumericValue(setValue,color,height,clicked,hovered); + ImGui::PopID(); // Row 2 ImGui::TableNextRow(); @@ -645,13 +662,8 @@ void StreamBrowserDialog::renderPsuRows( ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "mV" : "mC"); - if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) - Render7SegmentValue(measuredValue, color,height,clicked,hovered); - else - { - clicked |= ImGui::TextLink(measuredValue); - hovered |= ImGui::IsItemHovered(); - } + renderNumericValue(measuredValue, color,height,clicked,hovered); + ImGui::PopID(); } @@ -739,13 +751,8 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M if(open) { - if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) - Render7SegmentValue(valueText, color,ImGui::GetFontSize()*2,clicked,hovered); - else - { - clicked |= ImGui::TextLink(valueText.c_str()); - hovered |= ImGui::IsItemHovered(); - } + renderNumericValue(valueText, color,ImGui::GetFontSize()*2,clicked,hovered); + if(isMain) { auto dmmState = m_session.GetDmmState(dmm); diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 70f11080..b269e642 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -154,6 +154,7 @@ class StreamBrowserDialog : public Dialog const char* valueOn = "ON", uint8_t cropTextTo = 0); bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0); + void renderNumericValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable = true); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); From 606b4203f856f0c2c139b2eb99ad3d0935bcf276 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 01:03:51 +0100 Subject: [PATCH 13/49] First step to editable values in StreamBrowserDialog. --- src/ngscopeclient/PreferenceSchema.cpp | 4 + src/ngscopeclient/StreamBrowserDialog.cpp | 180 ++++++++++++++++++++-- src/ngscopeclient/StreamBrowserDialog.h | 7 + 3 files changed, 181 insertions(+), 10 deletions(-) diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index 41d27b87..a1782c06 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -233,6 +233,10 @@ void PreferenceManager::InitializeDefaults() Preference::Color("instrument_off_badge_color", ColorFromString("#808000ff")) .Label("Instrument off badge color") .Description("Color for instrument 'off' badge")); + stream.AddPreference( + Preference::Color("apply_button_color", ColorFromString("#4CCC4C")) + .Label("Apply button color") + .Description("Color for the apply value button")); stream.AddPreference( Preference::Color("psu_cv_badge_color", ColorFromString("#4CCC4C")) .Label("PSU cv badge color") diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 4ffd9d57..6de39aee 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -304,7 +304,7 @@ bool StreamBrowserDialog::renderCombo( { // Use channel color for shape combo, but darken it to make text readable float bgmul = 0.4; - auto bcolor = ImGui::ColorConvertFloat4ToU32(ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w) ); + auto bcolor = ImGui::ColorConvertFloat4ToU32(ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w)); ImGui::PushStyleColor(ImGuiCol_FrameBg, bcolor); ImGui::PushStyleColor(ImGuiCol_Text, color); } @@ -431,12 +431,164 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, ImVec4 co { auto& prefs = m_session.GetPreferences(); if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) - Render7SegmentValue(value,color,digitHeight,clicked,hovered); + Render7SegmentValue(value,color,digitHeight,clicked,hovered,clickable); else { - clicked |= ImGui::TextLink(value.c_str()); - hovered |= ImGui::IsItemHovered(); + if(clickable) + { + clicked |= ImGui::TextLink(value.c_str()); + hovered |= ImGui::IsItemHovered(); + } + else + { + ImGui::Text(value.c_str()); + } + } +} + +bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) +{ + auto& prefs = m_session.GetPreferences(); + bool changed = false; + bool validateChange = false; + bool cancelEdit = false; + bool keepEditing = false; + bool dirty = unit.PrettyPrint(committedValue) != currentValue; + string editLabel = label+"##Edit"; + ImGuiID editId = ImGui::GetID(editLabel.c_str()); + ImGuiID labelId = ImGui::GetID(label.c_str()); + if(m_editedItemId == editId) + { // Item currently beeing edited + ImGui::BeginGroup(); + + if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) + { // Input validated (but no apply button) + if(!explicitApply) + { // Implcit apply => validate change + validateChange = true; + } + else + { // Explicit apply needed => keep editing + keepEditing = true; + } + } + if(explicitApply) + { // Add Apply button + ImGui::SameLine(); + ImVec4 buttonColorActive = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.apply_button_color")); + float bgmul = 0.8f; + ImVec4 buttonColorHovered = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); + bgmul = 0.7f; + ImVec4 buttonColor = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); + if(!dirty) + ImGui::BeginDisabled(); + if(ImGui::SmallButton("\xE2\x8F\x8E")) // Carriage return symbol + { // Apply button click + validateChange = true; + } + if(!dirty) + ImGui::EndDisabled(); + ImGui::PopStyleColor(3); + } + if(!validateChange) + { + if(keepEditing) + { // Give back focus to test input + ImGui::ActivateItemByID(editId); + } + else if(ImGui::IsKeyPressed(ImGuiKey_Escape)) + { // Detect escape => stop editing + cancelEdit = true; + //Prevent focus from going to parent node + ImGui::ActivateItemByID(0); + } + else if((ImGui::GetActiveID() != editId) && (!explicitApply || !ImGui::IsItemActive() /* This is here to prevent detecting focus lost when apply button is clicked */)) + { // Detect focus lost => stop editing too + if(explicitApply) + { // Cancel on focus lost + cancelEdit = true; + } + else + { // Validate on focus list + validateChange = true; + } + } + } + ImGui::EndGroup(); + } + else + { + if(m_lastEditedItemId == editId) + { // Focus lost + if(explicitApply) + { // Cancel edit + cancelEdit = true; + } + else + { // Validate change + validateChange = true; + } + m_lastEditedItemId = 0; + } + bool clicked = false; + bool hovered = false; + bool use7Segment = false; + if(allow7SegmentDisplay) + { + use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); + } + if(use7Segment) + { + ImGui::PushID(labelId); + Render7SegmentValue(currentValue,color,ImGui::GetFontSize(),clicked,hovered); + ImGui::PopID(); + } + else + { + ImGui::InputText(label.c_str(),¤tValue,ImGuiInputTextFlags_ReadOnly); + clicked |= ImGui::IsItemClicked(); + if(ImGui::IsItemHovered()) + { // Keep hand cursor while read-only + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + hovered = true; + } + } + + if (clicked) + { + m_lastEditedItemId = m_editedItemId; + m_editedItemId = editId; + ImGui::ActivateItemByID(editId); + } + if (hovered) + m_parent->AddStatusHelp("mouse_lmb", "Edit value"); + } + if(validateChange) + { + m_lastEditedItemId = 0; + m_editedItemId = 0; + if(dirty) + { // Content actually changed + committedValue = unit.ParseString(currentValue); + currentValue = unit.PrettyPrint(committedValue); + changed = true; + } + } + else if(cancelEdit) + { // Restore value + currentValue = unit.PrettyPrint(committedValue); + m_lastEditedItemId = 0; + m_editedItemId = 0; } + return changed; +} + +bool StreamBrowserDialog::renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay) +{ + return renderEditableNumericValue(label,currentValue,committedValue,unit,color,allow7SegmentDisplay,true); } @@ -631,7 +783,16 @@ void StreamBrowserDialog::renderPsuRows( float height = ImGui::GetFontSize(); ImVec4 color = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.psu_7_segment_color")); - renderNumericValue(setValue,color,height,clicked,hovered); + Unit unit(isVoltage ? Unit::UNIT_VOLTS : Unit::UNIT_AMPS); + + string setValueString = string(setValue); + // TODO: use PSU state here + float commitValue = unit.ParseString(setValue); + auto dwidth = ImGui::GetFontSize() * 6; + ImGui::SetNextItemWidth(dwidth); + if(renderEditableNumericValueWithExplicitApply("##psuSetValue",setValueString,commitValue,unit,color,true)) + { // TODO Update PSU value + } ImGui::PopID(); // Row 2 @@ -676,7 +837,6 @@ void StreamBrowserDialog::renderPsuRows( */ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered) { - auto& prefs = m_session.GetPreferences(); size_t streamIndex = isMain ? 0 : 1; Unit unit = dmmchan->GetYAxisUnits(streamIndex); float mainValue = dmmchan->GetScalarValue(streamIndex); @@ -851,7 +1011,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr // Row 2 // Frequency label ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithImplicitApply( + if(renderEditableNumericValue( "Frequency", awgState->m_strFrequency[channelIndex], awgState->m_committedFrequency[channelIndex], @@ -876,7 +1036,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr //Row 2 //Duty cycle ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithImplicitApply( + if(renderEditableNumericValue( "Duty cycle", awgState->m_strDutyCycle[channelIndex], awgState->m_committedDutyCycle[channelIndex], @@ -910,7 +1070,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr // Row 3 ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithExplicitApply( + if(renderEditableNumericValueWithExplicitApply( "Amplitude", awgState->m_strAmplitude[channelIndex], awgState->m_committedAmplitude[channelIndex], @@ -924,7 +1084,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr //Row 4 //Offset ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithExplicitApply( + if(renderEditableNumericValueWithExplicitApply( "Offset", awgState->m_strOffset[channelIndex], awgState->m_committedOffset[channelIndex], diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index b269e642..e5db2000 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -155,6 +155,8 @@ class StreamBrowserDialog : public Dialog uint8_t cropTextTo = 0); bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0); void renderNumericValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable = true); + bool renderEditableNumericValue(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); + bool renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); @@ -183,6 +185,11 @@ class StreamBrowserDialog : public Dialog float m_badgeXMin; // left edge over which we must not overrun float m_badgeXCur; // right edge to render the next badge against + ///@brief Id of the item currently beeing edited + ImGuiID m_editedItemId = 0; + ///@brief Id of the last edited item + ImGuiID m_lastEditedItemId = 0; + std::map, bool> m_instrumentDownloadIsSlow; ///@brief Store the last state of an intrument badge (used for badge state latching) std::map, std::pair> m_instrumentLastBadge; From 0dd3917c4eead957e460e9728eb245609cb65f27 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 09:39:35 +0100 Subject: [PATCH 14/49] Fixed apply button behavior. --- src/ngscopeclient/StreamBrowserDialog.cpp | 27 +++++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 6de39aee..5e00cced 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -460,7 +460,11 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s if(m_editedItemId == editId) { // Item currently beeing edited ImGui::BeginGroup(); - + float inputXPos = ImGui::GetCursorPosX(); + ImGuiContext& g = *GImGui; + float inputWidth = g.NextItemData.Width; + // Allow overlap for apply button + ImGui::PushItemFlag(ImGuiItemFlags_AllowOverlap, true); if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) { // Input validated (but no apply button) if(!explicitApply) @@ -472,9 +476,12 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s keepEditing = true; } } + ImGui::PopItemFlag(); if(explicitApply) { // Add Apply button - ImGui::SameLine(); + float buttonWidth = ImGui::GetFontSize() * 2; + // Position the button just before the right side of the text input + ImGui::SameLine(inputXPos+inputWidth-ImGui::GetCursorPosX()-buttonWidth+2*ImGui::GetStyle().ItemInnerSpacing.x); ImVec4 buttonColorActive = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.apply_button_color")); float bgmul = 0.8f; ImVec4 buttonColorHovered = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); @@ -485,7 +492,7 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); if(!dirty) ImGui::BeginDisabled(); - if(ImGui::SmallButton("\xE2\x8F\x8E")) // Carriage return symbol + if(ImGui::Button("\xE2\x8F\x8E")) // Carriage return symbol { // Apply button click validateChange = true; } @@ -568,8 +575,11 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s } if(validateChange) { - m_lastEditedItemId = 0; - m_editedItemId = 0; + if(m_editedItemId == editId) + { + m_lastEditedItemId = 0; + m_editedItemId = 0; + } if(dirty) { // Content actually changed committedValue = unit.ParseString(currentValue); @@ -580,8 +590,11 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s else if(cancelEdit) { // Restore value currentValue = unit.PrettyPrint(committedValue); - m_lastEditedItemId = 0; - m_editedItemId = 0; + if(m_editedItemId == editId) + { + m_lastEditedItemId = 0; + m_editedItemId = 0; + } } return changed; } From 9ec99c57667f0fc7670ffc9da68c5378f6b871e2 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 09:44:25 +0100 Subject: [PATCH 15/49] Added apply button help text. --- src/ngscopeclient/StreamBrowserDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 5e00cced..1c43cb83 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -497,7 +497,13 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s validateChange = true; } if(!dirty) + { ImGui::EndDisabled(); + } + else if(ImGui::IsItemHovered()) + { // Help to explain apply button + m_parent->AddStatusHelp("mouse_lmb", "Apply value changes and send them to the instrument"); + } ImGui::PopStyleColor(3); } if(!validateChange) From 131fb305b38217e8f5ccb65205c8c91a1ddca731 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 18:41:51 +0100 Subject: [PATCH 16/49] Fixed default value for Duty Cycle. --- src/ngscopeclient/FunctionGeneratorState.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ngscopeclient/FunctionGeneratorState.h b/src/ngscopeclient/FunctionGeneratorState.h index a0da5fd1..6dd1e353 100644 --- a/src/ngscopeclient/FunctionGeneratorState.h +++ b/src/ngscopeclient/FunctionGeneratorState.h @@ -90,6 +90,7 @@ class FunctionGeneratorState m_committedAmplitude[i] = FLT_MIN; m_committedOffset[i] = FLT_MIN; m_committedFrequency[i] = FLT_MIN; + m_committedDutyCycle[i] = FLT_MIN; } } From 717b0db6404bbfbe7e1c0bafe8d58c2ec3018e6c Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 18:43:20 +0100 Subject: [PATCH 17/49] Added UI state in PowerSupplyState. --- src/ngscopeclient/PowerSupplyDialog.cpp | 18 ++++++++++++++ src/ngscopeclient/PowerSupplyState.h | 31 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/ngscopeclient/PowerSupplyDialog.cpp b/src/ngscopeclient/PowerSupplyDialog.cpp index 85e6602f..b4aa7b0c 100644 --- a/src/ngscopeclient/PowerSupplyDialog.cpp +++ b/src/ngscopeclient/PowerSupplyDialog.cpp @@ -202,7 +202,11 @@ void PowerSupplyDialog::ChannelSettings(int i, float v, float a, float etime) if(m_psu->SupportsIndividualOutputSwitching()) { if(ImGui::Checkbox("Output Enable", &m_channelUIState[i].m_outputEnabled)) + { m_psu->SetPowerChannelActive(i, m_channelUIState[i].m_outputEnabled); + // Tell intrument thread that the PSU state has to be updated + m_state->m_needsUpdate[i] = true; + } if(shdn) { //TODO: preference for configuring this? @@ -230,7 +234,11 @@ void PowerSupplyDialog::ChannelSettings(int i, float v, float a, float etime) if(ocp) { if(ImGui::Checkbox("Overcurrent Shutdown", &m_channelUIState[i].m_overcurrentShutdownEnabled)) + { m_psu->SetPowerOvercurrentShutdownEnabled(i, m_channelUIState[i].m_overcurrentShutdownEnabled); + // Tell intrument thread that the PSU state has to be updated + m_state->m_needsUpdate[i] = true; + } HelpMarker( "When enabled, the channel will shut down on overcurrent rather than switching to constant current mode.\n" "\n" @@ -241,7 +249,11 @@ void PowerSupplyDialog::ChannelSettings(int i, float v, float a, float etime) if(ss) { if(ImGui::Checkbox("Soft Start", &m_channelUIState[i].m_softStartEnabled)) + { m_psu->SetSoftStartEnabled(i, m_channelUIState[i].m_softStartEnabled); + // Tell intrument thread that the PSU state has to be updated + m_state->m_needsUpdate[i] = true; + } HelpMarker( "Deliberately limit the rise time of the output in order to reduce inrush current when driving " @@ -252,6 +264,8 @@ void PowerSupplyDialog::ChannelSettings(int i, float v, float a, float etime) "Ramp time", m_channelUIState[i].m_setSSRamp, m_channelUIState[i].m_committedSSRamp, fs)) { m_psu->SetSoftStartRampTime(i, m_channelUIState[i].m_committedSSRamp); + // Tell intrument thread that the PSU state has to be updated + m_state->m_needsUpdate[i] = true; } HelpMarker( "Transition time between off and on state when using soft start\n\n" @@ -276,6 +290,8 @@ void PowerSupplyDialog::ChannelSettings(int i, float v, float a, float etime) "Voltage", m_channelUIState[i].m_setVoltage, m_channelUIState[i].m_committedSetVoltage, volts)) { m_psu->SetPowerVoltage(i, m_channelUIState[i].m_committedSetVoltage); + // Tell intrument thread that the PSU state has to be updated + m_state->m_needsUpdate[i] = true; } HelpMarker("Target voltage to be supplied to the load.\n\nChanges are not pushed to hardware until you click Apply."); @@ -284,6 +300,8 @@ void PowerSupplyDialog::ChannelSettings(int i, float v, float a, float etime) "Current", m_channelUIState[i].m_setCurrent, m_channelUIState[i].m_committedSetCurrent, amps)) { m_psu->SetPowerCurrent(i, m_channelUIState[i].m_committedSetCurrent); + // Tell intrument thread that the PSU state has to be updated + m_state->m_needsUpdate[i] = true; } HelpMarker("Maximum current to be supplied to the load.\n\nChanges are not pushed to hardware until you click Apply."); diff --git a/src/ngscopeclient/PowerSupplyState.h b/src/ngscopeclient/PowerSupplyState.h index cc2887e4..ec33db9f 100644 --- a/src/ngscopeclient/PowerSupplyState.h +++ b/src/ngscopeclient/PowerSupplyState.h @@ -52,6 +52,17 @@ class PowerSupplyState m_channelFuseTripped = std::make_unique[] >(n); m_channelOn = std::make_unique[] >(n); + m_needsUpdate = std::make_unique[] >(n); + + m_overcurrentShutdownEnabled = std::make_unique[] >(n); + m_softStartEnabled = std::make_unique[] >(n); + m_committedSetVoltage = std::make_unique(n); + m_setVoltage = std::make_unique(n); + m_committedSetCurrent = std::make_unique(n); + m_setCurrent = std::make_unique(n); + m_committedSSRamp = std::make_unique(n); + m_setSSRamp = std::make_unique(n); + for(size_t i=0; i[]> m_channelFuseTripped; std::unique_ptr[]> m_channelOn; + std::unique_ptr[]> m_needsUpdate; + //UI state for dialogs etc + std::unique_ptr[]> m_overcurrentShutdownEnabled; + std::unique_ptr[]> m_softStartEnabled; + + std::unique_ptr m_committedSetVoltage; + std::unique_ptr m_setVoltage; + std::unique_ptr m_committedSetCurrent; + std::unique_ptr m_setCurrent; + std::unique_ptr m_committedSSRamp; + std::unique_ptr m_setSSRamp; + + std::atomic m_firstUpdateDone; std::atomic m_masterEnable; From 9054b795bd30a5bbd03cb3c1f46451b15ecc8b31 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 18:47:51 +0100 Subject: [PATCH 18/49] Added PowerSupply UI state update in InstrumentThread. --- src/ngscopeclient/InstrumentThread.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index 15eb541a..6c08f4d9 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -146,6 +146,24 @@ void InstrumentThread(InstrumentThreadArgs args) psustate->m_channelFuseTripped[i] = psu->GetPowerOvercurrentShutdownTripped(i); psustate->m_channelOn[i] = psu->GetPowerChannelActive(i); + if(psustate->m_needsUpdate[i]) + { + psustate->m_overcurrentShutdownEnabled[i] = psu->GetPowerOvercurrentShutdownEnabled(i); + psustate->m_softStartEnabled[i] = psu->IsSoftStartEnabled(i); + psustate->m_committedSetVoltage[i] = psu->GetPowerVoltageNominal(i); + psustate->m_committedSetCurrent[i] = psu->GetPowerCurrentNominal(i); + psustate->m_committedSSRamp[i] = psu->GetSoftStartRampTime(i); + Unit volts(Unit::UNIT_VOLTS); + Unit amps(Unit::UNIT_AMPS); + Unit fs(Unit::UNIT_FS); + psustate->m_setVoltage[i] = volts.PrettyPrint(psustate->m_committedSetVoltage[i]); + psustate->m_setCurrent[i] = amps.PrettyPrint(psustate->m_committedSetCurrent[i]); + psustate->m_setSSRamp[i] = fs.PrettyPrint(psustate->m_committedSSRamp[i]); + + psustate->m_needsUpdate[i] = false; + + } + session->MarkChannelDirty(pchan); } @@ -236,8 +254,6 @@ void InstrumentThread(InstrumentThreadArgs args) { if(awgstate->m_needsUpdate[i]) { - Unit volts(Unit::UNIT_VOLTS); - //Skip non-awg channels auto awgchan = dynamic_cast(awg->GetChannel(i)); if(!awgchan) From f285e11969fa833ff04d0e5532892e426a22e66a Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 18:53:10 +0100 Subject: [PATCH 19/49] Added editable values for PSU in StreamBrowserDialog. Fixed RBW handling in StreamBrowserDialog. --- src/ngscopeclient/StreamBrowserDialog.cpp | 113 +++++++++++++++------- src/ngscopeclient/StreamBrowserDialog.h | 8 +- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 1c43cb83..6098e012 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -82,7 +82,7 @@ StreamBrowserTimebaseInfo::StreamBrowserTimebaseInfo(shared_ptr sc Unit hz(Unit::UNIT_HZ); m_rbw = scope->GetResolutionBandwidth(); - m_rbwText = hz.PrettyPrint(m_rbw); + m_rbwText = hz.PrettyPrintInt64(m_rbw); m_span = scope->GetSpan(); m_spanText = hz.PrettyPrint(m_span); @@ -446,14 +446,20 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, ImVec4 co } } -bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) +template +bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableNumericValue only supports float or double"); auto& prefs = m_session.GetPreferences(); bool changed = false; bool validateChange = false; bool cancelEdit = false; bool keepEditing = false; - bool dirty = unit.PrettyPrint(committedValue) != currentValue; + bool dirty; + if constexpr (std::is_same_v) + dirty = unit.PrettyPrintInt64(committedValue) != currentValue; + else + dirty = unit.PrettyPrint(committedValue) != currentValue; string editLabel = label+"##Edit"; ImGuiID editId = ImGui::GetID(editLabel.c_str()); ImGuiID labelId = ImGui::GetID(label.c_str()); @@ -588,14 +594,34 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s } if(dirty) { // Content actually changed - committedValue = unit.ParseString(currentValue); - currentValue = unit.PrettyPrint(committedValue); + if constexpr (std::is_same_v) + { + //Float path if the user input a decimal value like "3.5G" + if(currentValue.find(".") != string::npos) + committedValue = unit.ParseString(currentValue); + //Integer path otherwise for full precision + else + committedValue = unit.ParseStringInt64(currentValue); + + currentValue = unit.PrettyPrintInt64(committedValue); + } + else + { + committedValue = static_cast(unit.ParseString(currentValue)); + if constexpr (std::is_same_v) + currentValue = unit.PrettyPrintInt64(committedValue); + else + currentValue = unit.PrettyPrint(committedValue); + } changed = true; } } else if(cancelEdit) { // Restore value - currentValue = unit.PrettyPrint(committedValue); + if constexpr (std::is_same_v) + currentValue = unit.PrettyPrintInt64(committedValue); + else + currentValue = unit.PrettyPrint(committedValue); if(m_editedItemId == editId) { m_lastEditedItemId = 0; @@ -605,7 +631,8 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s return changed; } -bool StreamBrowserDialog::renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay) +template +bool StreamBrowserDialog::renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay) { return renderEditableNumericValue(label,currentValue,committedValue,unit,color,allow7SegmentDisplay,true); } @@ -762,19 +789,24 @@ void StreamBrowserDialog::renderDownloadProgress(std::shared_ptr ins @param cc true if the PSU channel is in constant current mode, false for constant voltage mode @param chan the PSU channel to render properties for @param setValue the set value text + @param committedValue the last commited value @param measuredValue the measured value text @param clicked output param for clicked state @param hovered output param for hovered state + + @return true if the value has been modified */ -void StreamBrowserDialog::renderPsuRows( +bool StreamBrowserDialog::renderPsuRows( bool isVoltage, bool cc, PowerSupplyChannel* chan, - const char *setValue, - const char *measuredValue, + std::string& currentValue, + float& committedValue, + std::string& measuredValue, bool &clicked, bool &hovered) { + bool changed = false; auto& prefs = m_session.GetPreferences(); // Row 1 ImGui::TableNextRow(); @@ -804,13 +836,11 @@ void StreamBrowserDialog::renderPsuRows( Unit unit(isVoltage ? Unit::UNIT_VOLTS : Unit::UNIT_AMPS); - string setValueString = string(setValue); - // TODO: use PSU state here - float commitValue = unit.ParseString(setValue); auto dwidth = ImGui::GetFontSize() * 6; ImGui::SetNextItemWidth(dwidth); - if(renderEditableNumericValueWithExplicitApply("##psuSetValue",setValueString,commitValue,unit,color,true)) - { // TODO Update PSU value + if(renderEditableNumericValueWithExplicitApply("##psuSetValue",currentValue,committedValue,unit,color,true)) + { + changed = true; } ImGui::PopID(); @@ -845,6 +875,7 @@ void StreamBrowserDialog::renderPsuRows( renderNumericValue(measuredValue, color,height,clicked,hovered); ImGui::PopID(); + return changed; } /** @@ -1325,12 +1356,12 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) // Resolution Bandwidh ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Rbw", p->m_rbwText, p->m_rbw, hz)) + if(renderEditableNumericValue("Rbw", p->m_rbwText, p->m_rbw, hz)) { scope->SetResolutionBandwidth(p->m_rbw); // Update with values from the device p->m_rbw = scope->GetResolutionBandwidth(); - p->m_rbwText = hz.PrettyPrint(p->m_rbw); + p->m_rbwText = hz.PrettyPrintInt64(p->m_rbw); } HelpMarker("Resolution Bandwidth"); @@ -1338,7 +1369,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) bool changed = false; ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Start", p->m_startText, p->m_start, hz)) + if(renderEditableNumericValue("Start", p->m_startText, p->m_start, hz)) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1349,7 +1380,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) HelpMarker("Start of the frequency sweep"); ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Center", p->m_centerText, p->m_center, hz)) + if(renderEditableNumericValue("Center", p->m_centerText, p->m_center, hz)) { scope->SetCenterFrequency(0, p->m_center); changed = true; @@ -1357,7 +1388,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) HelpMarker("Midpoint of the frequency sweep"); ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Span", p->m_spanText, p->m_span, hz)) + if(renderEditableNumericValue("Span", p->m_spanText, p->m_span, hz)) { scope->SetSpan(p->m_span); changed = true; @@ -1365,7 +1396,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) HelpMarker("Width of the frequency sweep"); ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("End", p->m_endText, p->m_end, hz)) + if(renderEditableNumericValue("End", p->m_endText, p->m_end, hz)) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1400,7 +1431,7 @@ void StreamBrowserDialog::DoSpectrometerSettings(shared_ptr sp ImGui::SetNextItemWidth(width); Unit fs(Unit::UNIT_FS); - if(UnitInputWithImplicitApply("Integration time", config->m_integrationText, config->m_integrationTime, fs)) + if(renderEditableNumericValue("Integration time", config->m_integrationText, config->m_integrationTime, fs)) spec->SetIntegrationTime(config->m_integrationTime); HelpMarker("Spectrometer integration / exposure time"); } @@ -1695,25 +1726,35 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s bool cc = false; auto psuState = m_session.GetPSUState(psu); if(psuState) + { cc = psuState->m_channelConstantCurrent[channelIndex].load(); - bool clicked = false; - bool hovered = false; + bool clicked = false; + bool hovered = false; - if (ImGui::BeginTable("table1", 3)) - { - // Voltage - renderPsuRows(true,cc,psuchan,svoltage_txt.c_str(),mvoltage_txt.c_str(),clicked,hovered); - // Current - renderPsuRows(false,cc,psuchan,scurrent_txt.c_str(),mcurrent_txt.c_str(),clicked,hovered); - // End table - ImGui::EndTable(); - if (clicked) + if (ImGui::BeginTable("table1", 3)) { - m_parent->ShowInstrumentProperties(psu); + // Voltage + if(renderPsuRows(true,cc,psuchan,psuState->m_setVoltage[channelIndex],psuState->m_committedSetVoltage[channelIndex],mvoltage_txt,clicked,hovered)) + { // Update set voltage + psu->SetPowerVoltage(channelIndex, psuState->m_committedSetVoltage[channelIndex]); + psuState->m_needsUpdate[channelIndex] = true; + } + // Current + if(renderPsuRows(false,cc,psuchan,psuState->m_setCurrent[channelIndex],psuState->m_committedSetCurrent[channelIndex],mcurrent_txt,clicked,hovered)) + { // Update set current + psu->SetPowerCurrent(channelIndex, psuState->m_committedSetCurrent[channelIndex]); + psuState->m_needsUpdate[channelIndex] = true; + } + // End table + ImGui::EndTable(); + if (clicked) + { + m_parent->ShowInstrumentProperties(psu); + } + if (hovered) + m_parent->AddStatusHelp("mouse_lmb", "Open channel properties"); } - if (hovered) - m_parent->AddStatusHelp("mouse_lmb", "Open channel properties"); } EndBlock(); } diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index e5db2000..98a93837 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -155,10 +155,12 @@ class StreamBrowserDialog : public Dialog uint8_t cropTextTo = 0); bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0); void renderNumericValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable = true); - bool renderEditableNumericValue(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); - bool renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, float& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); + template + bool renderEditableNumericValue(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); + template + bool renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); - void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); + bool renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan, std::string& currentValue, float& committedValue, std::string& measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); void renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered); From 2c253a0fc4b30e78ecb20c3c4fc884ae49f8da85 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 19:07:16 +0100 Subject: [PATCH 20/49] Fixed linux compilation error. --- src/ngscopeclient/StreamBrowserDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 6098e012..b08de925 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -441,7 +441,7 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, ImVec4 co } else { - ImGui::Text(value.c_str()); + ImGui::TextUnformatted(value.c_str()); } } } From 3b33b30c35a65b5a01dcb38a760bf3c795f16db4 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 5 Jan 2026 22:33:58 +0100 Subject: [PATCH 21/49] Added doc. --- src/ngscopeclient/StreamBrowserDialog.cpp | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index b08de925..5ba0e247 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -447,6 +447,19 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, ImVec4 co } template +/** + @brief Render an editable numeric value + @param label the value label (used as a label for the TextInput) + @param currentValue the string representation of the current value + @param comittedValue the last comitted typed (float, double or int64_t) value + @param unit the Unit of the value + @param color the color to use + @param clicked output value for clicked state + @param hovered output value for hovered state + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) + @return true if the value has changed + */ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) { static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableNumericValue only supports float or double"); @@ -632,6 +645,18 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s } template +/** + @brief Render an editable numeric value with explicit apply (if the input value needs to explicitly be applied by clicking the apply button) + @param label the value label (used as a label for the TextInput) + @param currentValue the string representation of the current value + @param comittedValue the last comitted typed (float, double or int64_t) value + @param unit the Unit of the value + @param color the color to use + @param clicked output value for clicked state + @param hovered output value for hovered state + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @return true if the value has changed + */ bool StreamBrowserDialog::renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay) { return renderEditableNumericValue(label,currentValue,committedValue,unit,color,allow7SegmentDisplay,true); From 4ee9e421b15284ce4127f05dadcc44be5514b871 Mon Sep 17 00:00:00 2001 From: fredzo Date: Tue, 6 Jan 2026 00:15:11 +0100 Subject: [PATCH 22/49] Fixed link rendering. --- src/ngscopeclient/StreamBrowserDialog.cpp | 55 ++++++++++++++++++----- src/ngscopeclient/StreamBrowserDialog.h | 2 +- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 5ba0e247..a473df47 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -141,8 +141,7 @@ void StreamBrowserDialog::renderInfoLink(const char *label, const char *linktext ImGui::PushID(label); // Prevent collision if several sibling links have the same linktext ImGui::Text("%s: ", label); ImGui::SameLine(0, 0); - clicked |= ImGui::TextLink(linktext); - hovered |= ImGui::IsItemHovered(); + renderNumericValue(linktext,clicked,hovered); ImGui::PopID(); } @@ -421,27 +420,55 @@ bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, /** @brief Render a numeric value @param value the string representation of the value to display (may include the unit) - @param color the color to use - @param digitHeight the height of a digit @param clicked output value for clicked state @param hovered output value for hovered state + @param color the color to use (defaults to white) + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @param digitHeight the height of a digit (if 0 (defualt), will use ImGui::GetFontSize()) @param clickable true (default) if the displayed value should be clickable */ -void StreamBrowserDialog::renderNumericValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable) +void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color, bool allow7SegmentDisplay, float digitHeight, bool clickable) { - auto& prefs = m_session.GetPreferences(); - if(prefs.GetBool("Appearance.Stream Browser.use_7_segment_display")) + bool use7Segment = false; + if(allow7SegmentDisplay) + { + auto& prefs = m_session.GetPreferences(); + use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); + } + if(use7Segment) + { + if(digitHeight <= 0) digitHeight = ImGui::GetFontSize(); Render7SegmentValue(value,color,digitHeight,clicked,hovered,clickable); + } else { if(clickable) { - clicked |= ImGui::TextLink(value.c_str()); - hovered |= ImGui::IsItemHovered(); + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(value.c_str()); + ImGui::PopStyleColor(); + + clicked |= ImGui::IsItemClicked(); + if(ImGui::IsItemHovered()) + { // Hand cursor + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + // Lighter if hovered + color.x = color.x * 1.2f; + color.y = color.y * 1.2f; + color.z = color.z * 1.2f; + ImGui::SetCursorPos(pos); + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(value.c_str()); + ImGui::PopStyleColor(); + hovered = true; + } } else { + ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::TextUnformatted(value.c_str()); + ImGui::PopStyleColor(); } } } @@ -484,6 +511,7 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s float inputWidth = g.NextItemData.Width; // Allow overlap for apply button ImGui::PushItemFlag(ImGuiItemFlags_AllowOverlap, true); + ImGui::PushStyleColor(ImGuiCol_Text, color); if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) { // Input validated (but no apply button) if(!explicitApply) @@ -495,6 +523,7 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s keepEditing = true; } } + ImGui::PopStyleColor(); ImGui::PopItemFlag(); if(explicitApply) { // Add Apply button @@ -580,11 +609,13 @@ bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, s } else { + ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::InputText(label.c_str(),¤tValue,ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(); clicked |= ImGui::IsItemClicked(); if(ImGui::IsItemHovered()) { // Keep hand cursor while read-only - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); hovered = true; } } @@ -897,7 +928,7 @@ bool StreamBrowserDialog::renderPsuRows( ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "mV" : "mC"); - renderNumericValue(measuredValue, color,height,clicked,hovered); + renderNumericValue(measuredValue,clicked,hovered,color,true); ImGui::PopID(); return changed; @@ -986,7 +1017,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M if(open) { - renderNumericValue(valueText, color,ImGui::GetFontSize()*2,clicked,hovered); + renderNumericValue(valueText,clicked,hovered,color,true,ImGui::GetFontSize()*2); if(isMain) { diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 98a93837..f5d4a0b1 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -154,7 +154,7 @@ class StreamBrowserDialog : public Dialog const char* valueOn = "ON", uint8_t cropTextTo = 0); bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0); - void renderNumericValue(const std::string& value, ImVec4 color, float digitHeight, bool &clicked, bool &hovered, bool clickable = true); + void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); template bool renderEditableNumericValue(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); template From 22ae7b638464e716456e12e5b5b7b27ede60dad9 Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Wed, 7 Jan 2026 01:21:30 +0100 Subject: [PATCH 23/49] First step to adding a "+" button on top right corner of blocks to access channel property dialog. --- src/ngscopeclient/StreamBrowserDialog.cpp | 73 ++++++++++++++++++----- src/ngscopeclient/StreamBrowserDialog.h | 5 +- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index a473df47..e70882b9 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -39,6 +39,9 @@ using namespace std; +#define ELLIPSIS_CHAR "\xE2\x80\xA6" // "..." character +#define PLUS_CHAR "+" + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // StreamBrowserTimebaseInfo @@ -139,9 +142,11 @@ StreamBrowserDialog::~StreamBrowserDialog() void StreamBrowserDialog::renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered) { ImGui::PushID(label); // Prevent collision if several sibling links have the same linktext - ImGui::Text("%s: ", label); - ImGui::SameLine(0, 0); + auto dwidth = ImGui::GetFontSize() * 6; + ImGui::SetNextItemWidth(dwidth); renderNumericValue(linktext,clicked,hovered); + ImGui::SameLine(0, 0); + ImGui::TextUnformatted(label); ImGui::PopID(); } @@ -260,7 +265,8 @@ bool StreamBrowserDialog::renderCombo( const vector &values, bool useColorForText, uint8_t cropTextTo, - bool hideArrow) + bool hideArrow, + int paddingRight) { if(selected >= (int)values.size() || selected < 0) { @@ -273,7 +279,7 @@ bool StreamBrowserDialog::renderCombo( if(alignRight) { - int padding = ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x * 2; + int padding = ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x * 2 + paddingRight; float xsz = ImGui::CalcTextSize(selectedLabel).x + padding; string resizedLabel; if ((m_badgeXCur - xsz) < m_badgeXMin) @@ -287,12 +293,12 @@ bool StreamBrowserDialog::renderCombo( resizedLabel = resizedLabel.substr(0,resizedLabel.size()-1); if(resizedLabel.size() < cropTextTo) break; // We don't want to make the text that short - xsz = ImGui::CalcTextSize((resizedLabel + "...").c_str()).x + padding; + xsz = ImGui::CalcTextSize((resizedLabel + ELLIPSIS_CHAR).c_str()).x + padding; } if((m_badgeXCur - xsz) < m_badgeXMin) return false; // Still no room // We found an acceptable size - resizedLabel = resizedLabel + "..."; + resizedLabel = resizedLabel + ELLIPSIS_CHAR; selectedLabel = resizedLabel.c_str(); } m_badgeXCur -= xsz - ImGui::GetStyle().ItemSpacing.x; @@ -887,7 +893,6 @@ bool StreamBrowserDialog::renderPsuRows( ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "sV" : "sC"); - float height = ImGui::GetFontSize(); ImVec4 color = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.psu_7_segment_color")); Unit unit(isVoltage ? Unit::UNIT_VOLTS : Unit::UNIT_AMPS); @@ -1091,6 +1096,8 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr // Row 1 ImGui::Text("Waveform:"); startBadgeLine(); // Needed for shape combo + // Padding to give space for ChannelProperties dialog button + int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); // Shape combo // Get current shape and shape index FunctionGenerator::WaveShape shape = awgState->m_channelShape[channelIndex]; @@ -1101,7 +1108,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr ImGui::ColorConvertU32ToFloat4(ColorFromString(awgchan->m_displaycolor)), shapeIndex, awgState->m_channelShapeNames[channelIndex], true, - 3)) + 3,true,padding)) { shape = awgState->m_channelShapes[channelIndex][shapeIndex]; awg->SetFunctionChannelShape(channelIndex, shape); @@ -1157,10 +1164,11 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr startBadgeLine(); auto height = ImGui::GetFontSize() * 2; auto width = height * 2; - if ((m_badgeXCur - width) >= m_badgeXMin) + auto totalWidth = width + padding; + if ((m_badgeXCur - totalWidth) >= m_badgeXMin) { // ok, we have enough space draw preview - m_badgeXCur -= width; + m_badgeXCur -= totalWidth; // save current y position to restore it after drawing the preview float currentY = ImGui::GetCursorPosY(); // Continue layout on current line (row 3) @@ -1773,7 +1781,11 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if(psu) { // For PSU we will have a special handling for the 4 streams associated to a PSU channel - BeginBlock("psu_params"); + if(BeginBlock("psu_params",true)) + { + m_parent->ShowInstrumentProperties(psu); + } + auto svoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageSetPoint ()); auto mvoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageMeasured()); auto scurrent_txt = Unit(Unit::UNIT_AMPS).PrettyPrint(psuchan->GetCurrentSetPoint ()); @@ -1816,8 +1828,11 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s } else if(awg && awgchan) { - BeginBlock("awgparams"); - renderAwgProperties(awg, awgchan); + if(BeginBlock("awgparams",true)) + { + m_parent->ShowInstrumentProperties(awg); + } + renderAwgProperties(awg, awgchan); EndBlock(); } else if(dmm && dmmchan) @@ -2072,16 +2087,46 @@ void StreamBrowserDialog::DoItemHelp() m_parent->AddStatusHelp("mouse_lmb_drag", "Add to filter graph or plot"); } -void StreamBrowserDialog::BeginBlock(const char* label) +bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton) { + bool clicked = false; auto& prefs = m_session.GetPreferences(); ImGuiWindowFlags flags = ImGuiChildFlags_AutoResizeY; + bool withBorders = false; if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); flags |= ImGuiChildFlags_Borders; + withBorders = true; } ImGui::BeginChild(label, ImVec2(0, 0), flags); + if(withButton) + { // Create a "+" button on the top right corner of the box + ImVec2 oldPos = ImGui::GetCursorPos(); + float padding = ImGui::GetStyle().FramePadding.x; + float shift = withBorders ? padding*1.5 : 0; + float xsz = ImGui::GetFontSize(); + ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - xsz + shift); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()-shift); + // Use the same color as border for the button + ImVec4 border = ImGui::GetStyle().Colors[ImGuiCol_Border]; + ImVec4 hover = ImVec4(border.x * 1.2f, border.y * 1.2f, border.z * 1.2f, border.w); + ImVec4 active = ImVec4(border.x * 0.9f, border.y * 0.9f, border.z * 0.9f, border.w); + ImGui::PushStyleColor(ImGuiCol_Button, border); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, active); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.6f, 0.5f)); + clicked = ImGui::Button(PLUS_CHAR,ImVec2(xsz, xsz)); + if(ImGui::IsItemHovered()) + { // Hand cursor + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(3); + ImGui::SetCursorPos(oldPos); + } + return clicked; } void StreamBrowserDialog::EndBlock() diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index f5d4a0b1..8d2300e3 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -122,7 +122,7 @@ class StreamBrowserDialog : public Dialog void DoItemHelp(); // Block handling - void BeginBlock(const char *label); + bool BeginBlock(const char *label, bool withButton = false); void EndBlock(); // Rendeding of StreamBrowserDialog elements @@ -138,7 +138,8 @@ class StreamBrowserDialog : public Dialog const std::vector& values, bool useColorForText = false, uint8_t cropTextTo = 0, - bool hideArrow = true); + bool hideArrow = true, + int paddingRight = 0); bool renderCombo( const char* label, bool alignRight, From 19d73a8ed4fe7a31f52a19f460a9bb6e2761e0ae Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Wed, 7 Jan 2026 09:18:54 +0100 Subject: [PATCH 24/49] Added tooltip on properties dialog button. Added properties dialog button to dmm and oscilloscope. --- src/ngscopeclient/StreamBrowserDialog.cpp | 62 +++++++++++------------ src/ngscopeclient/StreamBrowserDialog.h | 9 ++-- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index e70882b9..98536427 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -248,12 +248,14 @@ void StreamBrowserDialog::renderBadge(ImVec4 color, ... /* labels, ending in NUL @brief Render a combo box with provided color and values @param label Label for the combo box + @param alignRight if true @param color the color of the combo box @param selected the selected value index (in/out) @param values the combo box values @param useColorForText if true, use the provided color for text (and a darker version of it for background color) @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space - @param hideArrow True to hide the dropdown arrow + @param hideArrow True to hide the dropdown arrow (defaults to true) + @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) @return true if the selected value of the combo has been changed */ @@ -266,7 +268,7 @@ bool StreamBrowserDialog::renderCombo( bool useColorForText, uint8_t cropTextTo, bool hideArrow, - int paddingRight) + float paddingRight) { if(selected >= (int)values.size() || selected < 0) { @@ -389,15 +391,16 @@ bool StreamBrowserDialog::renderCombo( @param valueOff label for value off (optionnal, defaults to "OFF") @param valueOn label for value on (optionnal, defaults to "ON") @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) + @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) @return true if selection has changed */ -bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo) +bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo, float paddingRight) { int selection = (int)curValue; std::vector values; values.push_back(string(valueOff)); values.push_back(string(valueOn)); - bool ret = renderCombo(label, alignRight, color, selection, values, false, cropTextTo); + bool ret = renderCombo(label, alignRight, color, selection, values, false, cropTextTo, true, paddingRight); curValue = (selection == 1); return ret; } @@ -411,16 +414,17 @@ bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec @param valueOff label for value off (optionnal, defaults to "OFF") @param valueOn label for value on (optionnal, defaults to "ON") @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) + @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) @return true if value has changed */ -bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo) +bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo, float paddingRight) { auto& prefs = m_session.GetPreferences(); ImVec4 color = ImGui::ColorConvertU32ToFloat4( (curValue ? prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); - return renderToggle(label, alignRight, color, curValue, valueOff, valueOn, cropTextTo); + return renderToggle(label, alignRight, color, curValue, valueOff, valueOn, cropTextTo,paddingRight); } /** @@ -933,7 +937,7 @@ bool StreamBrowserDialog::renderPsuRows( ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "mV" : "mC"); - renderNumericValue(measuredValue,clicked,hovered,color,true); + renderNumericValue(measuredValue,clicked,hovered,color,true,0,false); ImGui::PopID(); return changed; @@ -988,7 +992,11 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M } } - if(renderCombo("##mode", true, color, modeSelector, modeNames,true,3)) + // Padding to give space for ChannelProperties dialog button + auto& prefs = m_session.GetPreferences(); + int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); + + if(renderCombo("##mode", true, color, modeSelector, modeNames,true,3,true,padding)) { curMode = modes[modeSelector]; if(isMain) @@ -1033,7 +1041,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M // For main, also show the autorange combo startBadgeLine(); bool autorange = dmmState->m_autoRange.load(); - if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3)) + if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3,padding)) { dmm->SetMeterAutoRange(autorange); dmmState->m_needsRangeUpdate = true; @@ -1781,7 +1789,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if(psu) { // For PSU we will have a special handling for the 4 streams associated to a PSU channel - if(BeginBlock("psu_params",true)) + if(BeginBlock("psu_params",true,"Open PSU channel properties")) { m_parent->ShowInstrumentProperties(psu); } @@ -1816,12 +1824,6 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s } // End table ImGui::EndTable(); - if (clicked) - { - m_parent->ShowInstrumentProperties(psu); - } - if (hovered) - m_parent->AddStatusHelp("mouse_lmb", "Open channel properties"); } } EndBlock(); @@ -1837,7 +1839,10 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s } else if(dmm && dmmchan) { - BeginBlock("dmm_params"); + if(BeginBlock("dmm_params",true,"Open Multimeter properties")) + { + m_parent->ShowInstrumentProperties(dmm); + } // Always 2 streams for dmm channel => render properties on channel node bool clicked = false; bool hovered = false; @@ -1845,12 +1850,6 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s renderDmmProperties(dmm,dmmchan,true,clicked,hovered); // Secondary measurement renderDmmProperties(dmm,dmmchan,false,clicked,hovered); - if (clicked) - { - m_parent->ShowInstrumentProperties(dmm); - } - if (hovered) - m_parent->AddStatusHelp("mouse_lmb", "Open Multimeter properties"); EndBlock(); } @@ -1935,7 +1934,10 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } if(hasProps) { - BeginBlock("stream_params"); + if(BeginBlock("stream_params",true,"Open channel properties")) + { + m_parent->ShowChannelProperties(scopechan); + } Unit unit = channel->GetYAxisUnits(streamIndex); bool clicked = false; @@ -1966,12 +1968,6 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In break; } EndBlock(); - if (clicked) - { - m_parent->ShowChannelProperties(scopechan); - } - if (hovered) - m_parent->AddStatusHelp("mouse_lmb", "Open properties"); } } ImGui::PopID(); @@ -2087,7 +2083,7 @@ void StreamBrowserDialog::DoItemHelp() m_parent->AddStatusHelp("mouse_lmb_drag", "Add to filter graph or plot"); } -bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton) +bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton, const char* tooltip) { bool clicked = false; auto& prefs = m_session.GetPreferences(); @@ -2121,6 +2117,10 @@ bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton) if(ImGui::IsItemHovered()) { // Hand cursor ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if(tooltip) + { + m_parent->AddStatusHelp("mouse_lmb", tooltip); + } } ImGui::PopStyleVar(2); ImGui::PopStyleColor(3); diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 8d2300e3..7142f0fd 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -122,7 +122,7 @@ class StreamBrowserDialog : public Dialog void DoItemHelp(); // Block handling - bool BeginBlock(const char *label, bool withButton = false); + bool BeginBlock(const char *label, bool withButton = false, const char* tooltip = nullptr); void EndBlock(); // Rendeding of StreamBrowserDialog elements @@ -139,7 +139,7 @@ class StreamBrowserDialog : public Dialog bool useColorForText = false, uint8_t cropTextTo = 0, bool hideArrow = true, - int paddingRight = 0); + float paddingRight = 0); bool renderCombo( const char* label, bool alignRight, @@ -153,8 +153,9 @@ class StreamBrowserDialog : public Dialog bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", - uint8_t cropTextTo = 0); - bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0); + uint8_t cropTextTo = 0, + float paddingRight = 0); + bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0, float paddingRight = 0); void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); template bool renderEditableNumericValue(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); From 94acc33fbaae235d2f0d731e63c0880ab4234069 Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Wed, 7 Jan 2026 17:42:23 +0100 Subject: [PATCH 25/49] Fixed negative number rendering in 7segment mode. --- src/ngscopeclient/Dialog.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ngscopeclient/Dialog.cpp b/src/ngscopeclient/Dialog.cpp index 84379998..32797373 100644 --- a/src/ngscopeclient/Dialog.cpp +++ b/src/ngscopeclient/Dialog.cpp @@ -448,6 +448,7 @@ static char SEGMENTS[] = 0x7F, // 8 0x7B, // 9 0x0E, // L + 0x01, // - }; /** @@ -463,7 +464,9 @@ static char SEGMENTS[] = void Dialog::Render7SegmentDigit(ImDrawList* drawList, uint8_t digit, ImVec2 size, ImVec2 position, float thickness, ImU32 colorOn, ImU32 colorOff) { // Inspired by https://github.com/ocornut/imgui/issues/3606#issuecomment-736855952 - if(digit > 10) + if(digit == '-') + digit = 11; // Minus sign + else if(digit > 10) digit = 10; // 10 is for L of OL (Overload) size.y += thickness; ImVec2 halfSize(size.x/2,size.y/2); @@ -599,6 +602,16 @@ void Dialog::Render7SegmentValue(const std::string& value, ImVec4 color, float d else unit += c; } + else if(c == '-') + { + // This is the decimal separator + if(inIntPart) + { + intPart.push_back(c); + } + else + LogWarning("Unexpected sign '%c' in value '%s'.\n",c,value.c_str()); + } else if(c == '.' || c == std::use_facet >(std::locale()).decimal_point() || c == ',') { // This is the decimal separator From a35e1ceb8d728e6fc345736cd5a3e8fc431c7e3d Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Wed, 7 Jan 2026 18:53:43 +0100 Subject: [PATCH 26/49] Added renderReadOnlyProperty() method. Refactoired prperty edit method names. --- src/ngscopeclient/StreamBrowserDialog.cpp | 79 ++++++++++++----------- src/ngscopeclient/StreamBrowserDialog.h | 6 +- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 98536427..f88784ae 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -135,21 +135,6 @@ StreamBrowserDialog::~StreamBrowserDialog() //Helper methods for rendering widgets that appear in the StreamBrowserDialog. -/** - @brief Render a link of the "Sample rate: 4 GSa/s" type that shows up in the - scope properties box. -*/ -void StreamBrowserDialog::renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered) -{ - ImGui::PushID(label); // Prevent collision if several sibling links have the same linktext - auto dwidth = ImGui::GetFontSize() * 6; - ImGui::SetNextItemWidth(dwidth); - renderNumericValue(linktext,clicked,hovered); - ImGui::SameLine(0, 0); - ImGui::TextUnformatted(label); - ImGui::PopID(); -} - /** @brief prepare rendering context to display a badge at the end of current line */ @@ -483,6 +468,29 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &cli } } +/** + @brief Render a read-only instrument property value + @param label the value label (used as a label for the property) + @param currentValue the string representation of the current value +*/ +void StreamBrowserDialog::renderReadOnlyProperty(const string& label, const string& value) +{ + ImGui::PushID(label.c_str()); // Prevent collision if several sibling links have the same linktext + float fontSize = ImGui::GetFontSize(); + auto dwidth = fontSize * 6; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 bg = style.Colors[ImGuiCol_FrameBg]; + ImGui::PushStyleColor(ImGuiCol_ChildBg, bg); + ImGui::BeginChild("##readOnlyValue", ImVec2(dwidth, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); + ImGui::TextUnformatted(value.c_str()); + ImGui::EndChild(); + ImGui::PopStyleColor(); + ImGui::SameLine(); + ImGui::TextUnformatted(label.c_str()); + ImGui::PopID(); +} + + template /** @brief Render an editable numeric value @@ -497,9 +505,9 @@ template @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) @return true if the value has changed */ -bool StreamBrowserDialog::renderEditableNumericValue(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) +bool StreamBrowserDialog::renderEditableProperty(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) { - static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableNumericValue only supports float or double"); + static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports float or double"); auto& prefs = m_session.GetPreferences(); bool changed = false; bool validateChange = false; @@ -698,9 +706,9 @@ template @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format @return true if the value has changed */ -bool StreamBrowserDialog::renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay) +bool StreamBrowserDialog::renderEditablePropertyWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay) { - return renderEditableNumericValue(label,currentValue,committedValue,unit,color,allow7SegmentDisplay,true); + return renderEditableProperty(label,currentValue,committedValue,unit,color,allow7SegmentDisplay,true); } @@ -903,7 +911,7 @@ bool StreamBrowserDialog::renderPsuRows( auto dwidth = ImGui::GetFontSize() * 6; ImGui::SetNextItemWidth(dwidth); - if(renderEditableNumericValueWithExplicitApply("##psuSetValue",currentValue,committedValue,unit,color,true)) + if(renderEditablePropertyWithExplicitApply("##psuSetValue",currentValue,committedValue,unit,color,true)) { changed = true; } @@ -1132,7 +1140,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr // Row 2 // Frequency label ImGui::SetNextItemWidth(dwidth); - if(renderEditableNumericValue( + if(renderEditableProperty( "Frequency", awgState->m_strFrequency[channelIndex], awgState->m_committedFrequency[channelIndex], @@ -1157,7 +1165,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr //Row 2 //Duty cycle ImGui::SetNextItemWidth(dwidth); - if(renderEditableNumericValue( + if(renderEditableProperty( "Duty cycle", awgState->m_strDutyCycle[channelIndex], awgState->m_committedDutyCycle[channelIndex], @@ -1192,7 +1200,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr // Row 3 ImGui::SetNextItemWidth(dwidth); - if(renderEditableNumericValueWithExplicitApply( + if(renderEditablePropertyWithExplicitApply( "Amplitude", awgState->m_strAmplitude[channelIndex], awgState->m_committedAmplitude[channelIndex], @@ -1206,7 +1214,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr //Row 4 //Offset ImGui::SetNextItemWidth(dwidth); - if(renderEditableNumericValueWithExplicitApply( + if(renderEditablePropertyWithExplicitApply( "Offset", awgState->m_strOffset[channelIndex], awgState->m_committedOffset[channelIndex], @@ -1428,7 +1436,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) // Resolution Bandwidh ImGui::SetNextItemWidth(width); - if(renderEditableNumericValue("Rbw", p->m_rbwText, p->m_rbw, hz)) + if(renderEditableProperty("Rbw", p->m_rbwText, p->m_rbw, hz)) { scope->SetResolutionBandwidth(p->m_rbw); // Update with values from the device @@ -1441,7 +1449,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) bool changed = false; ImGui::SetNextItemWidth(width); - if(renderEditableNumericValue("Start", p->m_startText, p->m_start, hz)) + if(renderEditableProperty("Start", p->m_startText, p->m_start, hz)) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1452,7 +1460,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) HelpMarker("Start of the frequency sweep"); ImGui::SetNextItemWidth(width); - if(renderEditableNumericValue("Center", p->m_centerText, p->m_center, hz)) + if(renderEditableProperty("Center", p->m_centerText, p->m_center, hz)) { scope->SetCenterFrequency(0, p->m_center); changed = true; @@ -1460,7 +1468,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) HelpMarker("Midpoint of the frequency sweep"); ImGui::SetNextItemWidth(width); - if(renderEditableNumericValue("Span", p->m_spanText, p->m_span, hz)) + if(renderEditableProperty("Span", p->m_spanText, p->m_span, hz)) { scope->SetSpan(p->m_span); changed = true; @@ -1468,7 +1476,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) HelpMarker("Width of the frequency sweep"); ImGui::SetNextItemWidth(width); - if(renderEditableNumericValue("End", p->m_endText, p->m_end, hz)) + if(renderEditableProperty("End", p->m_endText, p->m_end, hz)) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1503,7 +1511,7 @@ void StreamBrowserDialog::DoSpectrometerSettings(shared_ptr sp ImGui::SetNextItemWidth(width); Unit fs(Unit::UNIT_FS); - if(renderEditableNumericValue("Integration time", config->m_integrationText, config->m_integrationTime, fs)) + if(renderEditableProperty("Integration time", config->m_integrationText, config->m_integrationTime, fs)) spec->SetIntegrationTime(config->m_integrationTime); HelpMarker("Spectrometer integration / exposure time"); } @@ -1940,30 +1948,27 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } Unit unit = channel->GetYAxisUnits(streamIndex); - bool clicked = false; - bool hovered = false; switch (type) { case Stream::STREAM_TYPE_ANALOG: { auto offset_txt = unit.PrettyPrint(scopechan->GetOffset(streamIndex)); auto range_txt = unit.PrettyPrint(scopechan->GetVoltageRange(streamIndex)); - renderInfoLink("Offset", offset_txt.c_str(), clicked, hovered); - renderInfoLink("Vertical range", range_txt.c_str(), clicked, hovered); + renderReadOnlyProperty("Offset", offset_txt); + renderReadOnlyProperty("Vertical range", range_txt); } break; case Stream::STREAM_TYPE_DIGITAL: if(scope) { auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); - renderInfoLink("Threshold", threshold_txt.c_str(), clicked, hovered); + renderReadOnlyProperty("Threshold", threshold_txt); break; } //fall through default: { - clicked = ImGui::TextLink("Properties"); - hovered = ImGui::IsItemHovered(); + ImGui::TextUnformatted("No properties"); } break; } diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 7142f0fd..7318d453 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -126,7 +126,6 @@ class StreamBrowserDialog : public Dialog void EndBlock(); // Rendeding of StreamBrowserDialog elements - void renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered); void startBadgeLine(); void renderBadge(ImVec4 color, ... /* labels, ending in NULL */); void renderInstrumentBadge(std::shared_ptr inst, bool latched, InstrumentBadge badge); @@ -157,10 +156,11 @@ class StreamBrowserDialog : public Dialog float paddingRight = 0); bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0, float paddingRight = 0); void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); + void renderReadOnlyProperty(const std::string& label, const std::string& value); template - bool renderEditableNumericValue(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); + bool renderEditableProperty(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); template - bool renderEditableNumericValueWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); + bool renderEditablePropertyWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); bool renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan, std::string& currentValue, float& committedValue, std::string& measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); From 20ba675464329fb9911e6c6639c3ce6e9de012be Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Wed, 7 Jan 2026 19:47:00 +0100 Subject: [PATCH 27/49] Added width and tooltip to render property methods. --- src/ngscopeclient/StreamBrowserDialog.cpp | 98 ++++++++++------------- src/ngscopeclient/StreamBrowserDialog.h | 6 +- 2 files changed, 47 insertions(+), 57 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index f88784ae..a70bfba8 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -472,32 +472,39 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &cli @brief Render a read-only instrument property value @param label the value label (used as a label for the property) @param currentValue the string representation of the current value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) */ -void StreamBrowserDialog::renderReadOnlyProperty(const string& label, const string& value) +void StreamBrowserDialog::renderReadOnlyProperty(float width, const string& label, const string& value, const char* tooltip) { ImGui::PushID(label.c_str()); // Prevent collision if several sibling links have the same linktext float fontSize = ImGui::GetFontSize(); - auto dwidth = fontSize * 6; + if(width <= 0) width = 6*fontSize; ImGuiStyle& style = ImGui::GetStyle(); ImVec4 bg = style.Colors[ImGuiCol_FrameBg]; ImGui::PushStyleColor(ImGuiCol_ChildBg, bg); - ImGui::BeginChild("##readOnlyValue", ImVec2(dwidth, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); + ImGui::BeginChild("##readOnlyValue", ImVec2(width, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); ImGui::TextUnformatted(value.c_str()); ImGui::EndChild(); ImGui::PopStyleColor(); ImGui::SameLine(); ImGui::TextUnformatted(label.c_str()); ImGui::PopID(); + if(tooltip) + { + HelpMarker(tooltip); + } } template /** @brief Render an editable numeric value + @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) @param label the value label (used as a label for the TextInput) @param currentValue the string representation of the current value @param comittedValue the last comitted typed (float, double or int64_t) value @param unit the Unit of the value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) @param color the color to use @param clicked output value for clicked state @param hovered output value for hovered state @@ -505,7 +512,7 @@ template @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) @return true if the value has changed */ -bool StreamBrowserDialog::renderEditableProperty(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) +bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) { static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports float or double"); auto& prefs = m_session.GetPreferences(); @@ -514,6 +521,9 @@ bool StreamBrowserDialog::renderEditableProperty(const std::string& label, std:: bool cancelEdit = false; bool keepEditing = false; bool dirty; + float fontSize = ImGui::GetFontSize(); + if(width <= 0) width = 6*fontSize; + ImGui::SetNextItemWidth(width); if constexpr (std::is_same_v) dirty = unit.PrettyPrintInt64(committedValue) != currentValue; else @@ -556,17 +566,13 @@ bool StreamBrowserDialog::renderEditableProperty(const std::string& label, std:: ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); - if(!dirty) - ImGui::BeginDisabled(); + ImGui::BeginDisabled(!dirty); if(ImGui::Button("\xE2\x8F\x8E")) // Carriage return symbol { // Apply button click validateChange = true; } - if(!dirty) - { - ImGui::EndDisabled(); - } - else if(ImGui::IsItemHovered()) + ImGui::EndDisabled(); + if(dirty && ImGui::IsItemHovered()) { // Help to explain apply button m_parent->AddStatusHelp("mouse_lmb", "Apply value changes and send them to the instrument"); } @@ -690,25 +696,31 @@ bool StreamBrowserDialog::renderEditableProperty(const std::string& label, std:: m_editedItemId = 0; } } + if(tooltip) + { + HelpMarker(tooltip); + } return changed; } template /** @brief Render an editable numeric value with explicit apply (if the input value needs to explicitly be applied by clicking the apply button) + @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) @param label the value label (used as a label for the TextInput) @param currentValue the string representation of the current value @param comittedValue the last comitted typed (float, double or int64_t) value @param unit the Unit of the value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) @param color the color to use @param clicked output value for clicked state @param hovered output value for hovered state @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format @return true if the value has changed */ -bool StreamBrowserDialog::renderEditablePropertyWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color, bool allow7SegmentDisplay) +bool StreamBrowserDialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay) { - return renderEditableProperty(label,currentValue,committedValue,unit,color,allow7SegmentDisplay,true); + return renderEditableProperty(width,label,currentValue,committedValue,unit,tooltip,color,allow7SegmentDisplay,true); } @@ -909,9 +921,7 @@ bool StreamBrowserDialog::renderPsuRows( Unit unit(isVoltage ? Unit::UNIT_VOLTS : Unit::UNIT_AMPS); - auto dwidth = ImGui::GetFontSize() * 6; - ImGui::SetNextItemWidth(dwidth); - if(renderEditablePropertyWithExplicitApply("##psuSetValue",currentValue,committedValue,unit,color,true)) + if(renderEditablePropertyWithExplicitApply(0,"##psuSetValue",currentValue,committedValue,unit,nullptr,color,true)) { changed = true; } @@ -1038,7 +1048,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M if(open) { - renderNumericValue(valueText,clicked,hovered,color,true,ImGui::GetFontSize()*2); + renderNumericValue(valueText,clicked,hovered,color,true,ImGui::GetFontSize()*2,false); if(isMain) { @@ -1139,12 +1149,11 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr // Row 2 // Frequency label - ImGui::SetNextItemWidth(dwidth); - if(renderEditableProperty( + if(renderEditableProperty(dwidth, "Frequency", awgState->m_strFrequency[channelIndex], awgState->m_committedFrequency[channelIndex], - hz)) + hz/*,"Frequency of the generated waveform"*/)) { awg->SetFunctionChannelFrequency(channelIndex, awgState->m_committedFrequency[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; @@ -1160,21 +1169,18 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr else DoItemHelp(); */ - //HelpMarker("Frequency of the generated waveform"); //Row 2 //Duty cycle - ImGui::SetNextItemWidth(dwidth); - if(renderEditableProperty( + if(renderEditableProperty(dwidth, "Duty cycle", awgState->m_strDutyCycle[channelIndex], awgState->m_committedDutyCycle[channelIndex], - percent)) + percent/*,"Duty cycle of the generated waveform"*/)) { awg->SetFunctionChannelDutyCycle(channelIndex, awgState->m_committedDutyCycle[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } - //HelpMarker("Duty cycle of the generated waveform"); // Shape preview startBadgeLine(); @@ -1199,31 +1205,27 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr } // Row 3 - ImGui::SetNextItemWidth(dwidth); - if(renderEditablePropertyWithExplicitApply( + if(renderEditablePropertyWithExplicitApply(dwidth, "Amplitude", awgState->m_strAmplitude[channelIndex], awgState->m_committedAmplitude[channelIndex], - volts)) + volts,"Peak-to-peak amplitude of the generated waveform")) { awg->SetFunctionChannelAmplitude(channelIndex, awgState->m_committedAmplitude[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } - HelpMarker("Peak-to-peak amplitude of the generated waveform"); //Row 4 //Offset - ImGui::SetNextItemWidth(dwidth); - if(renderEditablePropertyWithExplicitApply( + if(renderEditablePropertyWithExplicitApply(dwidth, "Offset", awgState->m_strOffset[channelIndex], awgState->m_committedOffset[channelIndex], - volts)) + volts,"DC offset for the waveform above (positive) or below (negative) ground")) { awg->SetFunctionChannelOffset(channelIndex, awgState->m_committedOffset[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } - HelpMarker("DC offset for the waveform above (positive) or below (negative) ground"); //Row 5 //Impedance @@ -1435,21 +1437,18 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) Unit hz(Unit::UNIT_HZ); // Resolution Bandwidh - ImGui::SetNextItemWidth(width); - if(renderEditableProperty("Rbw", p->m_rbwText, p->m_rbw, hz)) + if(renderEditableProperty(width,"Rbw", p->m_rbwText, p->m_rbw, hz, "Resolution Bandwidth")) { scope->SetResolutionBandwidth(p->m_rbw); // Update with values from the device p->m_rbw = scope->GetResolutionBandwidth(); p->m_rbwText = hz.PrettyPrintInt64(p->m_rbw); } - HelpMarker("Resolution Bandwidth"); //Frequency bool changed = false; - ImGui::SetNextItemWidth(width); - if(renderEditableProperty("Start", p->m_startText, p->m_start, hz)) + if(renderEditableProperty(width,"Start", p->m_startText, p->m_start, hz, "Start of the frequency sweep")) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1457,26 +1456,20 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) scope->SetSpan(span); changed = true; } - HelpMarker("Start of the frequency sweep"); - ImGui::SetNextItemWidth(width); - if(renderEditableProperty("Center", p->m_centerText, p->m_center, hz)) + if(renderEditableProperty(width,"Center", p->m_centerText, p->m_center, hz, "Midpoint of the frequency sweep")) { scope->SetCenterFrequency(0, p->m_center); changed = true; } - HelpMarker("Midpoint of the frequency sweep"); - ImGui::SetNextItemWidth(width); - if(renderEditableProperty("Span", p->m_spanText, p->m_span, hz)) + if(renderEditableProperty(width,"Span", p->m_spanText, p->m_span, hz, "Width of the frequency sweep")) { scope->SetSpan(p->m_span); changed = true; } - HelpMarker("Width of the frequency sweep"); - ImGui::SetNextItemWidth(width); - if(renderEditableProperty("End", p->m_endText, p->m_end, hz)) + if(renderEditableProperty(width,"End", p->m_endText, p->m_end, hz, "End of the frequency sweep")) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1484,7 +1477,6 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) scope->SetSpan(span); changed = true; } - HelpMarker("End of the frequency sweep"); //Update everything if one setting is changed if(changed) @@ -1508,12 +1500,10 @@ void StreamBrowserDialog::DoSpectrometerSettings(shared_ptr sp auto config = m_timebaseConfig[scope]; auto width = ImGui::GetFontSize() * 5; - ImGui::SetNextItemWidth(width); Unit fs(Unit::UNIT_FS); - if(renderEditableProperty("Integration time", config->m_integrationText, config->m_integrationTime, fs)) + if(renderEditableProperty(width, "Integration time", config->m_integrationText, config->m_integrationTime, fs, "Spectrometer integration / exposure time")) spec->SetIntegrationTime(config->m_integrationTime); - HelpMarker("Spectrometer integration / exposure time"); } /** @@ -1954,15 +1944,15 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In { auto offset_txt = unit.PrettyPrint(scopechan->GetOffset(streamIndex)); auto range_txt = unit.PrettyPrint(scopechan->GetVoltageRange(streamIndex)); - renderReadOnlyProperty("Offset", offset_txt); - renderReadOnlyProperty("Vertical range", range_txt); + renderReadOnlyProperty(0,"Offset", offset_txt); + renderReadOnlyProperty(0,"Vertical range", range_txt, "Vertical range"); } break; case Stream::STREAM_TYPE_DIGITAL: if(scope) { auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); - renderReadOnlyProperty("Threshold", threshold_txt); + renderReadOnlyProperty(0,"Threshold", threshold_txt); break; } //fall through diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 7318d453..8b33ef56 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -156,11 +156,11 @@ class StreamBrowserDialog : public Dialog float paddingRight = 0); bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0, float paddingRight = 0); void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); - void renderReadOnlyProperty(const std::string& label, const std::string& value); + void renderReadOnlyProperty(float width, const std::string& label, const std::string& value, const char* tooltip = nullptr); template - bool renderEditableProperty(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); + bool renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); template - bool renderEditablePropertyWithExplicitApply(const std::string& label, std::string& currentValue, T& committedValue, Unit unit, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); + bool renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); bool renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan, std::string& currentValue, float& committedValue, std::string& measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); From e39f6d75a6b41bcc9febfbc265e6f470974b361d Mon Sep 17 00:00:00 2001 From: fredzo Date: Thu, 8 Jan 2026 13:19:58 +0100 Subject: [PATCH 28/49] Added scopestate to session. First step to channel propoeries editing in StreamBrowserDialog. --- src/ngscopeclient/ChannelPropertiesDialog.cpp | 55 ++++++++++++++++++- src/ngscopeclient/ChannelPropertiesDialog.h | 3 + src/ngscopeclient/InstrumentThread.cpp | 55 +++++++++++++++++++ src/ngscopeclient/Session.cpp | 7 +++ src/ngscopeclient/Session.h | 12 ++++ src/ngscopeclient/StreamBrowserDialog.cpp | 17 ++++-- src/ngscopeclient/ngscopeclient.h | 2 + 7 files changed, 145 insertions(+), 6 deletions(-) diff --git a/src/ngscopeclient/ChannelPropertiesDialog.cpp b/src/ngscopeclient/ChannelPropertiesDialog.cpp index a0d937ab..de7c9a3a 100644 --- a/src/ngscopeclient/ChannelPropertiesDialog.cpp +++ b/src/ngscopeclient/ChannelPropertiesDialog.cpp @@ -43,9 +43,26 @@ using namespace std; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Construction / destruction -ChannelPropertiesDialog::ChannelPropertiesDialog(InstrumentChannel* chan, MainWindow* parent, bool graphEditorMode) +ChannelPropertiesDialog::ChannelPropertiesDialog(InstrumentChannel* chan, MainWindow* parent, bool graphEditorMode) : BaseChannelPropertiesDialog(chan, parent, graphEditorMode) { + // Get oscilloscope state, for that we need to make a shared_ptr out of the base pointer returned by chan->GetInstrument() + Session& session = m_parent->GetSession(); + // We pass an empty Deleter since this pointer's lifecycle is handled elsewhere + std::shared_ptr scopeSharedPointer(chan->GetInstrument(), [](Instrument*){}); + shared_ptr sharedScope = dynamic_pointer_cast(scopeSharedPointer); + if(sharedScope) + { + m_state = session.GetOscillopscopeState(sharedScope); + if(!m_state) + LogError("Could not get OscilloscopeState for scope %s.\n",chan->GetInstrument()->GetName().c_str()); + + } + else + { + LogError("Could not get cast Instrument %s to Oscilloscope.\n",chan->GetInstrument()->GetName().c_str()); + } + auto ochan = dynamic_cast(chan); if(!ochan) LogFatal("ChannelPropertiesDialog expects an OscilloscopeChannel\n"); @@ -309,10 +326,10 @@ bool ChannelPropertiesDialog::DoRender() auto nstreams = m_channel->GetStreamCount(); if(scope) { + auto index = m_channel->GetIndex(); if(ImGui::CollapsingHeader("Input", defaultOpenFlags)) { //Type of probe connected - auto index = m_channel->GetIndex(); string ptype = m_probe; if(ptype == "") ptype = "(not detected)"; @@ -340,6 +357,9 @@ bool ChannelPropertiesDialog::DoRender() //refresh in case scope driver changed the value m_committedThreshold = scope->GetDigitalThreshold(index); m_threshold = yunit.PrettyPrint(m_committedThreshold); + + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; } HelpMarker("Switching threshold for the digital input buffer"); } @@ -354,6 +374,9 @@ bool ChannelPropertiesDialog::DoRender() //refresh in case scope driver changed the value m_committedHysteresis = scope->GetDigitalHysteresis(index); m_hysteresis = yunit.PrettyPrint(m_committedHysteresis); + + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; } HelpMarker("Hysteresis for the digital input buffer"); } @@ -398,6 +421,9 @@ bool ChannelPropertiesDialog::DoRender() m_committedRange[i] = ochan->GetVoltageRange(i); m_range[i] = unit.PrettyPrint(m_committedRange[i]); } + + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; } if(m_probe != "") ImGui::EndDisabled(); @@ -408,7 +434,12 @@ bool ChannelPropertiesDialog::DoRender() { ImGui::SetNextItemWidth(width); if(Combo("Coupling", m_couplingNames, m_coupling)) + { ochan->SetCoupling(m_couplings[m_coupling]); + + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; + } HelpMarker("Coupling configuration for the input"); } @@ -417,7 +448,12 @@ bool ChannelPropertiesDialog::DoRender() { ImGui::SetNextItemWidth(width); if(Combo("Bandwidth", m_bwlNames, m_bwl)) + { ochan->SetBandwidthLimit(m_bwlValues[m_bwl]); + + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; + } HelpMarker("Hardware bandwidth limiter setting"); } } @@ -465,8 +501,13 @@ bool ChannelPropertiesDialog::DoRender() if(scope->CanInvert(index)) { if(ImGui::Checkbox("Invert", &m_inverted)) + { ochan->Invert(m_inverted); + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; + } + HelpMarker( "When checked, input value is multiplied by -1.\n\n" "For a differential probe, this is equivalent to swapping the positive and negative inputs." @@ -558,8 +599,13 @@ bool ChannelPropertiesDialog::DoRender() } ImGui::SetNextItemWidth(width); if(UnitInputWithExplicitApply("Offset", m_offset[i], m_committedOffset[i], unit)) + { ochan->SetOffset(m_committedOffset[i], i); + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; + } + //Same for range auto range = ochan->GetVoltageRange(i); auto srange = unit.PrettyPrint(m_committedRange[i]); @@ -570,8 +616,13 @@ bool ChannelPropertiesDialog::DoRender() } ImGui::SetNextItemWidth(width); if(UnitInputWithExplicitApply("Range", m_range[i], m_committedRange[i], unit)) + { ochan->SetVoltageRange(m_committedRange[i], i); + // Tell intrument thread that the scope state has to be updated + m_state->m_needsUpdate[index] = true; + } + ImGui::PopID(); } } diff --git a/src/ngscopeclient/ChannelPropertiesDialog.h b/src/ngscopeclient/ChannelPropertiesDialog.h index 460ab620..6be9077c 100644 --- a/src/ngscopeclient/ChannelPropertiesDialog.h +++ b/src/ngscopeclient/ChannelPropertiesDialog.h @@ -49,6 +49,9 @@ class ChannelPropertiesDialog : public BaseChannelPropertiesDialog void RefreshInputSettings(Oscilloscope* scope, size_t nchan); + ///@brief Current channel stats, live updated + std::shared_ptr m_state; + std::string m_displayName; std::string m_committedDisplayName; diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index 6c08f4d9..aeb61afb 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -67,6 +67,7 @@ void InstrumentThread(InstrumentThreadArgs args) auto misc = dynamic_pointer_cast(inst); auto psu = dynamic_pointer_cast(inst); auto awg = dynamic_pointer_cast(inst); + auto scopestate = args.oscilloscopestate; auto loadstate = args.loadstate; auto meterstate = args.meterstate; auto bertstate = args.bertstate; @@ -123,6 +124,60 @@ void InstrumentThread(InstrumentThreadArgs args) } triggerUpToDate = false; } + + if(scopestate) + { // Update state + //Read status for channels that need it + for(size_t i=0; iGetChannelCount(); i++) + { + if(scopestate->m_needsUpdate[i]) + { + //Skip non-scope channels + auto scopechan = dynamic_cast(scope->GetChannel(i)); + if(!scopechan) + continue; + + scopestate->m_channelInverted[i] = scope->IsInverted(i); + + bool isDigital = (scopechan->GetType(0) == Stream::STREAM_TYPE_DIGITAL); + if(isDigital) + { + Unit unit = scopechan->GetYAxisUnits(0); + scopestate->m_channelDigitalTrehshold[i] = scope->GetDigitalThreshold(i); + scopestate->m_committedDigitalThreshold[i] = scopestate->m_channelDigitalTrehshold[i]; + scopestate->m_strDigitalThreshold[i] = unit.PrettyPrint(scopestate->m_channelDigitalTrehshold[i]); + } + else + { + scopestate->m_channelAttenuation[i] = scope->GetChannelAttenuation(i); + scopestate->m_channelBandwidthLimit[i] = scope->GetChannelBandwidthLimit(i); + scopestate->m_channelCoupling[i] = scope->GetChannelCoupling(i); + scopestate->m_committedAttenuation[i] = scopestate->m_channelAttenuation[i]; + Unit counts(Unit::UNIT_COUNTS); + scopestate->m_strAttenuation[i] = counts.PrettyPrint(scopestate->m_committedAttenuation[i]); + + size_t nstreams = scopechan->GetStreamCount(); + for(size_t j=0; jGetOffset(j); + float range = scopechan->GetVoltageRange(j); + Unit unit = scopechan->GetYAxisUnits(j); + scopestate->m_channelOffset[i][j] = scopechan->GetOffset(j); + scopestate->m_channelRange[i][j] = scopechan->GetVoltageRange(j); + scopestate->m_committedOffset[i][j] = offset; + scopestate->m_committedRange[i][j] = range; + scopestate->m_strOffset[i][j] = unit.PrettyPrint(offset); + scopestate->m_strRange[i][j] = unit.PrettyPrint(range); + } + } + + session->MarkChannelDirty(scopechan); + + scopestate->m_needsUpdate[i] = false; + } + + } + } } //Always acquire data from non-scope instruments diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index 1de02d46..776864a9 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -239,6 +239,7 @@ void Session::Clear() m_history.clear(); m_oscilloscopes.clear(); + m_oscilloscopesStates.clear(); m_psus.clear(); m_loads.clear(); m_meters.clear(); @@ -2913,6 +2914,9 @@ void Session::AddInstrument(shared_ptr inst, bool createDialogs) if(scope && (types & Instrument::INST_OSCILLOSCOPE)) { m_oscilloscopes.push_back(scope); + auto state = make_shared(scope); + m_oscilloscopesStates[scope] = state; + args.oscilloscopestate = state; if(m_oscilloscopes.size() > 1) m_multiScope = true; } @@ -2970,11 +2974,14 @@ void Session::RemoveInstrument(shared_ptr inst) //Remove instrument-specific state auto psu = dynamic_pointer_cast(inst); + auto scope = dynamic_pointer_cast(inst); auto meter = dynamic_pointer_cast(inst); auto load = dynamic_pointer_cast(inst); auto bert = dynamic_pointer_cast(inst); if(psu) m_psus.erase(psu); + if(scope) + m_oscilloscopesStates.erase(scope); if(meter) m_meters.erase(meter); if(load) diff --git a/src/ngscopeclient/Session.h b/src/ngscopeclient/Session.h index d8b00010..c569fc84 100644 --- a/src/ngscopeclient/Session.h +++ b/src/ngscopeclient/Session.h @@ -144,6 +144,15 @@ class Session MainWindow* GetMainWindow() { return m_mainWindow; } + /** + @brief Returns a pointer to the state for a function generator + */ + std::shared_ptr GetOscillopscopeState(std::shared_ptr scope) + { + std::lock_guard lock(m_scopeMutex); + return m_oscilloscopesStates[scope]; + } + /** @brief Returns a pointer to the state for a BERT */ @@ -432,6 +441,9 @@ class Session ///@brief Oscilloscopes we are currently connected to std::vector> m_oscilloscopes; + ///@brief Oscilloscopes we are currently connected to + std::map, std::shared_ptr > m_oscilloscopesStates; + ///@brief Power supplies we are currently connected to std::map, std::shared_ptr > m_psus; diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index a70bfba8..0a9aec47 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1938,14 +1938,23 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } Unit unit = channel->GetYAxisUnits(streamIndex); + size_t channelIndex = scopechan->GetIndex(); + auto scopeState = m_session.GetOscillopscopeState(scope); + switch (type) { case Stream::STREAM_TYPE_ANALOG: { - auto offset_txt = unit.PrettyPrint(scopechan->GetOffset(streamIndex)); - auto range_txt = unit.PrettyPrint(scopechan->GetVoltageRange(streamIndex)); - renderReadOnlyProperty(0,"Offset", offset_txt); - renderReadOnlyProperty(0,"Vertical range", range_txt, "Vertical range"); + if(renderEditablePropertyWithExplicitApply(0,"Offset",scopeState->m_strOffset[channelIndex][streamIndex],scopeState->m_committedOffset[channelIndex][streamIndex],unit)) + { // Update offset + scopechan->SetOffset(scopeState->m_committedOffset[channelIndex][streamIndex],streamIndex); + scopeState->m_needsUpdate[channelIndex] = true; + } + if(renderEditablePropertyWithExplicitApply(0,"Vertical range",scopeState->m_strRange[channelIndex][streamIndex],scopeState->m_committedRange[channelIndex][streamIndex],unit)) + { // Update offset + scopechan->SetVoltageRange(scopeState->m_committedRange[channelIndex][streamIndex],streamIndex); + scopeState->m_needsUpdate[channelIndex] = true; + } } break; case Stream::STREAM_TYPE_DIGITAL: diff --git a/src/ngscopeclient/ngscopeclient.h b/src/ngscopeclient/ngscopeclient.h index de1e8fe5..6bcd9ee6 100644 --- a/src/ngscopeclient/ngscopeclient.h +++ b/src/ngscopeclient/ngscopeclient.h @@ -44,6 +44,7 @@ #include +#include "OscilloscopeState.h" #include "BERTState.h" #include "PowerSupplyState.h" #include "FunctionGeneratorState.h" @@ -67,6 +68,7 @@ class InstrumentThreadArgs Session* session; //Additional per-instrument-type state we can add + std::shared_ptr oscilloscopestate; std::shared_ptr loadstate; std::shared_ptr meterstate; std::shared_ptr bertstate; From a8271e020c93ad871f47cfab99cc6e77effed3e5 Mon Sep 17 00:00:00 2001 From: fredzo Date: Thu, 8 Jan 2026 23:04:31 +0100 Subject: [PATCH 29/49] Added channel proerties block with attenuation, coupling, bandwitdh and invert settings. Added digital threshold setting. --- src/ngscopeclient/ChannelPropertiesDialog.cpp | 2 +- src/ngscopeclient/InstrumentThread.cpp | 60 +++++++++- src/ngscopeclient/StreamBrowserDialog.cpp | 104 +++++++++++++++++- src/ngscopeclient/StreamBrowserDialog.h | 2 + 4 files changed, 164 insertions(+), 4 deletions(-) diff --git a/src/ngscopeclient/ChannelPropertiesDialog.cpp b/src/ngscopeclient/ChannelPropertiesDialog.cpp index de7c9a3a..2af2eb6a 100644 --- a/src/ngscopeclient/ChannelPropertiesDialog.cpp +++ b/src/ngscopeclient/ChannelPropertiesDialog.cpp @@ -324,9 +324,9 @@ bool ChannelPropertiesDialog::DoRender() //Input settings only make sense if we have an attached scope auto nstreams = m_channel->GetStreamCount(); + auto index = m_channel->GetIndex(); if(scope) { - auto index = m_channel->GetIndex(); if(ImGui::CollapsingHeader("Input", defaultOpenFlags)) { //Type of probe connected diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index aeb61afb..4b38b79f 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -151,7 +151,6 @@ void InstrumentThread(InstrumentThreadArgs args) { scopestate->m_channelAttenuation[i] = scope->GetChannelAttenuation(i); scopestate->m_channelBandwidthLimit[i] = scope->GetChannelBandwidthLimit(i); - scopestate->m_channelCoupling[i] = scope->GetChannelCoupling(i); scopestate->m_committedAttenuation[i] = scopestate->m_channelAttenuation[i]; Unit counts(Unit::UNIT_COUNTS); scopestate->m_strAttenuation[i] = counts.PrettyPrint(scopestate->m_committedAttenuation[i]); @@ -169,6 +168,65 @@ void InstrumentThread(InstrumentThreadArgs args) scopestate->m_strOffset[i][j] = unit.PrettyPrint(offset); scopestate->m_strRange[i][j] = unit.PrettyPrint(range); } + // Get probe name + scopestate->m_probeName[i] = scope->GetProbeName(i); + // Popilate bandwidth limit values + auto limit = scope->GetChannelBandwidthLimit(i); + scopestate->m_bandwidthLimits[i].clear(); + scopestate->m_bandwidthLimitNames[i].clear(); + scopestate->m_bandwidthLimits[i] = scope->GetChannelBandwidthLimiters(i); + Unit hz(Unit::UNIT_HZ); + for(size_t j=0; jm_bandwidthLimits[i].size(); j++) + { + auto b = scopestate->m_bandwidthLimits[i][j]; + if(b == 0) + scopestate->m_bandwidthLimitNames[i].push_back("Full"); + else + scopestate->m_bandwidthLimitNames[i].push_back(hz.PrettyPrint(b*1e6)); + + if(b == limit) + scopestate->m_channelBandwidthLimit[i] = j; + } + + + // Populate coupling values + auto coupling = scope->GetChannelCoupling(i); + scopestate->m_couplings[i].clear(); + scopestate->m_couplingNames[i].clear(); + scopestate->m_couplings[i] = scope->GetAvailableCouplings(i); + for(size_t j=0; jm_couplings[i].size(); j++) + { + auto c = scopestate->m_couplings[i][j]; + + switch(c) + { + case OscilloscopeChannel::COUPLE_DC_50: + scopestate->m_couplingNames[i].push_back("DC 50Ω"); + break; + + case OscilloscopeChannel::COUPLE_AC_50: + scopestate->m_couplingNames[i].push_back("AC 50Ω"); + break; + + case OscilloscopeChannel::COUPLE_DC_1M: + scopestate->m_couplingNames[i].push_back("DC 1MΩ"); + break; + + case OscilloscopeChannel::COUPLE_AC_1M: + scopestate->m_couplingNames[i].push_back("AC 1MΩ"); + break; + + case OscilloscopeChannel::COUPLE_GND: + scopestate->m_couplingNames[i].push_back("Ground"); + break; + + default: + scopestate->m_couplingNames[i].push_back("Invalid"); + break; + } + if(c == coupling) + scopestate->m_channelCoupling[i] = j; + } } session->MarkChannelDirty(scopechan); diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 0a9aec47..269730c8 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1853,6 +1853,16 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s } else { + if(!singleStream) + { + if(BeginBlock("stream_params",true,"Open channel properties")) + { + m_parent->ShowChannelProperties(scopechan); + } + auto scopeState = m_session.GetOscillopscopeState(scope); + renderChannelProperties(scope,scopechan,channelIndex,scopeState); + EndBlock(); + } size_t streamCount = channel->GetStreamCount(); for(size_t j=0; j instrument, s ImGui::PopID(); } +/** + @brief Rendering of channel properties + + @param scope the scope + @param scopechan the scope channel + @param channelIndex the index of the channel + @param scopeState the OscilloscopeState + */ +void StreamBrowserDialog::renderChannelProperties(std::shared_ptr scope, OscilloscopeChannel* scopechan, size_t channelIndex, shared_ptr scopeState) +{ + float fontSize = ImGui::GetFontSize(); + float width = 6*fontSize; + + Unit counts(Unit::UNIT_COUNTS); + if(renderEditableProperty(width,"Attenuation",scopeState->m_strAttenuation[channelIndex],scopeState->m_committedAttenuation[channelIndex],counts, + "Attenuation setting for the probe (for example, 10 for a 10:1 probe)")) + { // Update offset + scopechan->SetAttenuation(scopeState->m_committedAttenuation[channelIndex]); + scopeState->m_needsUpdate[channelIndex] = true; + } + //Only show coupling box if the instrument has configurable coupling + if( (scopeState->m_couplings[channelIndex].size() > 1) && (scopeState->m_probeName[channelIndex] == "") ) + { + ImGui::SetNextItemWidth(width); + if(renderCombo( + "Coupling", + false, + ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), + scopeState->m_channelCoupling[channelIndex], + scopeState->m_couplingNames[channelIndex], + false, + 0, + false)) + { + scope->SetChannelCoupling(channelIndex,scopeState->m_couplings[channelIndex][scopeState->m_channelCoupling[channelIndex]]); + scopeState->m_needsUpdate[channelIndex] = true; + } + HelpMarker("Coupling configuration for the input"); + } + //Bandwidth limiters (only show if more than one value available) + if(scopeState->m_bandwidthLimitNames[channelIndex].size() > 1) + { + ImGui::SetNextItemWidth(width); + if(renderCombo( + "Bandwidth", + false, + ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), + scopeState->m_channelBandwidthLimit[channelIndex], + scopeState->m_bandwidthLimitNames[channelIndex], + false, + 0, + false)) + { + scopechan->SetBandwidthLimit(scopeState->m_bandwidthLimits[channelIndex][scopeState->m_channelBandwidthLimit[channelIndex]]); + scopeState->m_needsUpdate[channelIndex] = true; + } + HelpMarker("Hardware bandwidth limiter setting"); + } + //If the probe supports inversion, show a checkbox for it + if(scope->CanInvert(channelIndex)) + { + ImGui::SetNextItemWidth(width); + if(renderOnOffToggle("Invert",false,scopeState->m_channelInverted[channelIndex])) + { + scope->Invert(channelIndex,scopeState->m_channelInverted[channelIndex]); + scopeState->m_needsUpdate[channelIndex] = true; + } + HelpMarker( + "When ON, input value is multiplied by -1.\n" + "For a differential probe, this is equivalent to swapping the positive and negative inputs." + ); + } + +} + /** @brief Rendering of a stream node @@ -1945,6 +2030,10 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In { case Stream::STREAM_TYPE_ANALOG: { + if(!renderName) + { // No streams => display channel properties here + renderChannelProperties(scope,scopechan,channelIndex,scopeState); + } if(renderEditablePropertyWithExplicitApply(0,"Offset",scopeState->m_strOffset[channelIndex][streamIndex],scopeState->m_committedOffset[channelIndex][streamIndex],unit)) { // Update offset scopechan->SetOffset(scopeState->m_committedOffset[channelIndex][streamIndex],streamIndex); @@ -1960,8 +2049,19 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In case Stream::STREAM_TYPE_DIGITAL: if(scope) { - auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); - renderReadOnlyProperty(0,"Threshold", threshold_txt); + if(scope->IsDigitalThresholdConfigurable()) + { + if(renderEditablePropertyWithExplicitApply(0,"Threshold",scopeState->m_strDigitalThreshold[channelIndex],scopeState->m_committedDigitalThreshold[channelIndex],unit)) + { // Update offset + scopechan->SetDigitalThreshold(scopeState->m_committedDigitalThreshold[channelIndex]); + scopeState->m_needsUpdate[channelIndex] = true; + } + } + else + { + auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); + renderReadOnlyProperty(0,"Threshold", threshold_txt); + } break; } //fall through diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 8b33ef56..1ffc197e 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -176,6 +176,8 @@ class StreamBrowserDialog : public Dialog // Rendering of a channel node void renderChannelNode(std::shared_ptr instrument, size_t channelIndex, bool isLast); + void renderChannelProperties(std::shared_ptr scope, OscilloscopeChannel* scopechan, size_t channelIndex, std::shared_ptr scopeState); + // Rendering of a stream node void renderStreamNode(std::shared_ptr instrument, InstrumentChannel* channel, size_t streamIndex, bool renderName, bool renderProps, bool isLast); From 65c8c731113041d8d0b22a3dee674e96310d4682 Mon Sep 17 00:00:00 2001 From: fredzo Date: Thu, 8 Jan 2026 23:05:44 +0100 Subject: [PATCH 30/49] Added missing file. --- src/ngscopeclient/OscilloscopeState.h | 146 ++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/ngscopeclient/OscilloscopeState.h diff --git a/src/ngscopeclient/OscilloscopeState.h b/src/ngscopeclient/OscilloscopeState.h new file mode 100644 index 00000000..d14cb32c --- /dev/null +++ b/src/ngscopeclient/OscilloscopeState.h @@ -0,0 +1,146 @@ +/*********************************************************************************************************************** +* * +* ngscopeclient * +* * +* Copyright (c) 2012-2024 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 * +* following conditions are met: * +* * +* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * +* following disclaimer. * +* * +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * +* following disclaimer in the documentation and/or other materials provided with the distribution. * +* * +* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * +* derived from this software without specific prior written permission. * +* * +* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * +* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * +* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * +* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * +* POSSIBILITY OF SUCH DAMAGE. * +* * +***********************************************************************************************************************/ + +/** + @file + @author Frederic BORRY + @brief Declaration of OscilloscopeState + */ +#ifndef OscilloscopeState_h +#define OscilloscopeState_h + +/** + @brief Current status of an Oscilloscope + */ +class OscilloscopeState +{ +public: + + OscilloscopeState(std::shared_ptr scope) + { + size_t n = scope->GetChannelCount(); + m_channelInverted = std::make_unique(n); + m_channelOffset = std::make_unique[] >(n); + m_channelRange = std::make_unique[] >(n); + + m_channelDigitalTrehshold = std::make_unique[] >(n); + m_channelAttenuation = std::make_unique[] >(n); + + m_needsUpdate = std::make_unique[] >(n); + + m_probeName = std::make_unique(n); + + m_channelCoupling = std::make_unique(n); + m_couplings = std::make_unique[]>(n); + m_couplingNames = std::make_unique[]>(n); + + m_channelBandwidthLimit = std::make_unique(n); + m_bandwidthLimits = std::make_unique[]>(n); + m_bandwidthLimitNames = std::make_unique[]>(n); + + m_committedOffset = std::make_unique[]>(n); + m_strOffset = std::make_unique[]>(n); + + m_committedRange = std::make_unique[]>(n); + m_strRange = std::make_unique[]>(n); + + m_committedDigitalThreshold = std::make_unique(n); + m_strDigitalThreshold = std::make_unique(n); + + m_committedAttenuation = std::make_unique(n); + m_strAttenuation = std::make_unique(n); + + Unit volts(Unit::UNIT_VOLTS); + + for(size_t i=0; i(scope->GetChannel(i)); + if(chan) + { + size_t nstreams = chan->GetStreamCount(); + for(size_t j=0; j m_channelInverted; + std::unique_ptr[]> m_channelOffset; + std::unique_ptr[]> m_channelRange; + std::unique_ptr[]> m_channelDigitalTrehshold; + std::unique_ptr[]> m_channelAttenuation; + + std::unique_ptr[]> m_needsUpdate; + + //UI state for dialogs etc + std::unique_ptr m_probeName; + + std::unique_ptr m_channelBandwidthLimit; + std::unique_ptr[]> m_bandwidthLimits; + std::unique_ptr[]> m_bandwidthLimitNames; + + std::unique_ptr m_channelCoupling; + std::unique_ptr[]> m_couplings; + std::unique_ptr[]> m_couplingNames; + + std::unique_ptr[]> m_committedOffset; + std::unique_ptr[]> m_strOffset; + + std::unique_ptr[]> m_committedRange; + std::unique_ptr[]> m_strRange; + + std::unique_ptr m_committedDigitalThreshold; + std::unique_ptr m_strDigitalThreshold; + + std::unique_ptr m_committedAttenuation; + std::unique_ptr m_strAttenuation; +}; + +#endif From 38ad1137fb9f4de63b86b5f739e54ca2e8e768d6 Mon Sep 17 00:00:00 2001 From: fredzo Date: Fri, 9 Jan 2026 08:39:06 +0100 Subject: [PATCH 31/49] Added digital bank nodes to gather digital channels. --- src/ngscopeclient/StreamBrowserDialog.cpp | 52 +++++++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 269730c8..dee6f1f6 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1364,6 +1364,9 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument if(instIsOpen) { + vector digitalBanks; + vector analogChannels; + vector otherChannels; size_t lastEnabledChannelIndex = 0; if (scope) { @@ -1381,17 +1384,58 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument ImGui::TreePop(); } + digitalBanks = scope->GetDigitalBanks(); + for(size_t i = 0; iIsChannelEnabled(i)) lastEnabledChannelIndex = i; + auto scopechan = scope->GetChannel(i); + auto streamType = scopechan->GetType(0); + if(streamType != Stream::STREAM_TYPE_DIGITAL) + { + if(streamType == Stream::STREAM_TYPE_ANALOG) + analogChannels.push_back(i); + else + otherChannels.push_back(i); + } } } - for(size_t i=0; i 0) + { // If digital banks are avaialble, gather digital channels in banks + for(size_t i : analogChannels) + { // Iterate on analog channel first + renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); + } + 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)); + } + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + bankNumber++; + } + for(size_t i : otherChannels) + { // Finally iterate on other channels + renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); + } + } + else + { // Display all channels if no digital bank is available + for(size_t i=0; i Date: Fri, 9 Jan 2026 20:15:47 +0100 Subject: [PATCH 32/49] Added instrument state update on config flush. --- src/ngscopeclient/FunctionGeneratorState.h | 9 +++++++++ src/ngscopeclient/MultimeterState.h | 5 +++++ src/ngscopeclient/OscilloscopeState.h | 9 +++++++++ src/ngscopeclient/PowerSupplyState.h | 9 +++++++++ src/ngscopeclient/Session.cpp | 18 ++++++++++++++++++ 5 files changed, 50 insertions(+) diff --git a/src/ngscopeclient/FunctionGeneratorState.h b/src/ngscopeclient/FunctionGeneratorState.h index 6dd1e353..46c42fad 100644 --- a/src/ngscopeclient/FunctionGeneratorState.h +++ b/src/ngscopeclient/FunctionGeneratorState.h @@ -45,6 +45,7 @@ class FunctionGeneratorState FunctionGeneratorState(std::shared_ptr generator) { size_t n = generator->GetChannelCount(); + m_channelNumner = n; m_channelActive = std::make_unique[] >(n); m_channelAmplitude = std::make_unique[] >(n); m_channelOffset= std::make_unique[] >(n); @@ -94,6 +95,12 @@ class FunctionGeneratorState } } + void FlushConfigCache() + { + for(size_t i = 0 ; i < m_channelNumner.load() ; i++) + m_needsUpdate[i] = true; + } + std::unique_ptr[]> m_channelActive; std::unique_ptr[]> m_channelAmplitude; std::unique_ptr[]> m_channelOffset; @@ -107,6 +114,8 @@ class FunctionGeneratorState std::unique_ptr[]> m_needsUpdate; + std::atomic m_channelNumner; + //UI state for dialogs etc std::unique_ptr m_committedOffset; std::unique_ptr m_strOffset; diff --git a/src/ngscopeclient/MultimeterState.h b/src/ngscopeclient/MultimeterState.h index bf35a690..001de1c1 100644 --- a/src/ngscopeclient/MultimeterState.h +++ b/src/ngscopeclient/MultimeterState.h @@ -51,6 +51,11 @@ class MultimeterState m_needsRangeUpdate = true; } + void FlushConfigCache() + { + m_needsRangeUpdate = true; + } + std::atomic m_primaryMeasurement; std::atomic m_secondaryMeasurement; std::atomic m_firstUpdateDone; diff --git a/src/ngscopeclient/OscilloscopeState.h b/src/ngscopeclient/OscilloscopeState.h index d14cb32c..e02ec035 100644 --- a/src/ngscopeclient/OscilloscopeState.h +++ b/src/ngscopeclient/OscilloscopeState.h @@ -45,6 +45,7 @@ class OscilloscopeState OscilloscopeState(std::shared_ptr scope) { size_t n = scope->GetChannelCount(); + m_channelNumner = n; m_channelInverted = std::make_unique(n); m_channelOffset = std::make_unique[] >(n); m_channelRange = std::make_unique[] >(n); @@ -111,6 +112,12 @@ class OscilloscopeState } } + void FlushConfigCache() + { + for(size_t i = 0 ; i < m_channelNumner.load() ; i++) + m_needsUpdate[i] = true; + } + std::unique_ptr m_channelInverted; std::unique_ptr[]> m_channelOffset; std::unique_ptr[]> m_channelRange; @@ -119,6 +126,8 @@ class OscilloscopeState std::unique_ptr[]> m_needsUpdate; + std::atomic m_channelNumner; + //UI state for dialogs etc std::unique_ptr m_probeName; diff --git a/src/ngscopeclient/PowerSupplyState.h b/src/ngscopeclient/PowerSupplyState.h index ec33db9f..733cf1ae 100644 --- a/src/ngscopeclient/PowerSupplyState.h +++ b/src/ngscopeclient/PowerSupplyState.h @@ -45,6 +45,7 @@ class PowerSupplyState PowerSupplyState(size_t n = 0) { m_masterEnable = false; + m_channelNumner = n; m_channelVoltage = std::make_unique[] >(n); m_channelCurrent = std::make_unique[] >(n); @@ -82,6 +83,12 @@ class PowerSupplyState m_firstUpdateDone = false; } + void FlushConfigCache() + { + for(size_t i = 0 ; i < m_channelNumner.load() ; i++) + m_needsUpdate[i] = true; + } + std::unique_ptr[]> m_channelVoltage; std::unique_ptr[]> m_channelCurrent; std::unique_ptr[]> m_channelConstantCurrent; @@ -104,6 +111,8 @@ class PowerSupplyState std::atomic m_firstUpdateDone; std::atomic m_masterEnable; + + std::atomic m_channelNumner; }; #endif diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index 776864a9..8df9f5ee 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -166,6 +166,24 @@ void Session::FlushConfigCache() lock_guard lock(m_scopeMutex); for(auto it : m_instrumentStates) it.first->FlushConfigCache(); + + // Also flush session states + for(auto it : m_psus) + { + it.second->FlushConfigCache(); + } + for(auto it : m_meters) + { + it.second->FlushConfigCache(); + } + for(auto it : m_oscilloscopesStates) + { + it.second->FlushConfigCache(); + } + for(auto it : m_awgs) + { + it.second->FlushConfigCache(); + } } /** From 0af25a75defed9a2ff020f671047c21fb69b8bc0 Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Sat, 10 Jan 2026 01:01:17 +0100 Subject: [PATCH 33/49] Merged code from upstream to open properties on double click + close nodes by default. --- src/ngscopeclient/StreamBrowserDialog.cpp | 56 ++++++++++++++--------- src/ngscopeclient/StreamBrowserDialog.h | 4 +- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index dee6f1f6..39051cee 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.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 * @@ -1714,11 +1714,9 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto awgchan = dynamic_cast(channel); auto dmmchan = dynamic_cast(channel); bool renderProps = false; - bool isDigital = false; if (scopechan) { renderProps = scopechan->IsEnabled(); - isDigital = scopechan->GetType(0) == Stream::STREAM_TYPE_DIGITAL; } else if(awg && awgchan) { @@ -1734,12 +1732,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if(!hasChildren) flags |= ImGuiTreeNodeFlags_Leaf; - //Collapse all scope channel nodes by default to reduce clutter - if(isDigital || scopechan) - {} - - else - flags |= ImGuiTreeNodeFlags_DefaultOpen; + flags |= ImGuiTreeNodeFlags_OpenOnArrow; bool open = ImGui::TreeNodeEx( channel->GetDisplayName().c_str(), @@ -1747,6 +1740,24 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if (channel->m_displaycolor != "") ImGui::PopStyleColor(); + //Open properties dialog on double click + if(ImGui::IsItemHovered()) + { + if(ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + if(scopechan) + m_parent->ShowChannelProperties(scopechan); + else if(psuchan) + m_parent->ShowInstrumentProperties(psu); + else if(awgchan) + m_parent->ShowInstrumentProperties(awg); + else + LogWarning("Don't know how to open channel properties yet\n"); + } + + m_parent->AddStatusHelp("mouse_lmb_double", "Open properties"); + } + //Single stream: drag the stream not the channel if(singleStream) { @@ -1911,7 +1922,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s for(size_t j=0; j @param streamIndex the index of the stream to render @param renderName true if the name of the stream should be rendred as a selectable item @param renderProps true if a properties block should be rendered for this stream - @param isLast true if this is the last stream of the channel */ -void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, InstrumentChannel* channel, size_t streamIndex, bool renderName, bool renderProps, bool isLast) +void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, InstrumentChannel* channel, size_t streamIndex, bool renderName, bool renderProps) { auto scope = std::dynamic_pointer_cast(instrument); auto scopechan = dynamic_cast(channel); @@ -2020,7 +2030,6 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In if (renderName) { ImGui::Selectable(channel->GetStreamName(streamIndex).c_str()); - StreamDescriptor s(channel, streamIndex); if(ImGui::BeginDragDropSource()) { @@ -2044,7 +2053,7 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In if(renderProps && scopechan) { // If no properties are available for this stream, only show a "Properties" link if it is the last stream of the channel/filter - bool hasProps = isLast; + bool hasProps = false; switch (type) { case Stream::STREAM_TYPE_ANALOG: @@ -2110,9 +2119,6 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } //fall through default: - { - ImGui::TextUnformatted("No properties"); - } break; } EndBlock(); @@ -2136,7 +2142,7 @@ void StreamBrowserDialog::renderFilterNode(Filter* filter) ImGui::PushStyleColor(ImGuiCol_Text, ColorFromString(filter->m_displaycolor)); //Don't expand filters with a single stream by default - int flags = 0; + int flags = ImGuiTreeNodeFlags_OpenOnArrow; if(!singleStream) flags |= ImGuiTreeNodeFlags_DefaultOpen; @@ -2144,6 +2150,14 @@ void StreamBrowserDialog::renderFilterNode(Filter* filter) if (filter->m_displaycolor != "") ImGui::PopStyleColor(); + //Open properties dialog on double click + if(ImGui::IsItemHovered()) + { + if(ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + m_parent->ShowChannelProperties(filter); + m_parent->AddStatusHelp("mouse_lmb_double", "Open properties"); + } + //Single stream: drag the stream not the filter if(singleStream) { @@ -2178,10 +2192,8 @@ void StreamBrowserDialog::renderFilterNode(Filter* filter) size_t streamCount = filter->GetStreamCount(); for(size_t j=0; j scope, OscilloscopeChannel* scopechan, size_t channelIndex, std::shared_ptr scopeState); // Rendering of a stream node - void renderStreamNode(std::shared_ptr instrument, InstrumentChannel* channel, size_t streamIndex, bool renderName, bool renderProps, bool isLast); + void renderStreamNode(std::shared_ptr instrument, InstrumentChannel* channel, size_t streamIndex, bool renderName, bool renderProps); // Rendering of an Filter node void renderFilterNode(Filter* filter); From 959800469e88042dc1a51aa40dbf73f999a9a0ed Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Sat, 10 Jan 2026 01:05:42 +0100 Subject: [PATCH 34/49] Merge --- src/ngscopeclient/StreamBrowserDialog.cpp | 975 ++++------------------ src/ngscopeclient/StreamBrowserDialog.h | 38 +- 2 files changed, 175 insertions(+), 838 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 39051cee..b93ca459 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -39,9 +39,6 @@ using namespace std; -#define ELLIPSIS_CHAR "\xE2\x80\xA6" // "..." character -#define PLUS_CHAR "+" - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // StreamBrowserTimebaseInfo @@ -85,7 +82,7 @@ StreamBrowserTimebaseInfo::StreamBrowserTimebaseInfo(shared_ptr sc Unit hz(Unit::UNIT_HZ); m_rbw = scope->GetResolutionBandwidth(); - m_rbwText = hz.PrettyPrintInt64(m_rbw); + m_rbwText = hz.PrettyPrint(m_rbw); m_span = scope->GetSpan(); m_spanText = hz.PrettyPrint(m_span); @@ -135,6 +132,20 @@ StreamBrowserDialog::~StreamBrowserDialog() //Helper methods for rendering widgets that appear in the StreamBrowserDialog. +/** + @brief Render a link of the "Sample rate: 4 GSa/s" type that shows up in the + scope properties box. +*/ +void StreamBrowserDialog::renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered) +{ + ImGui::PushID(label); // Prevent collision if several sibling links have the same linktext + ImGui::Text("%s: ", label); + ImGui::SameLine(0, 0); + clicked |= ImGui::TextLink(linktext); + hovered |= ImGui::IsItemHovered(); + ImGui::PopID(); +} + /** @brief prepare rendering context to display a badge at the end of current line */ @@ -233,14 +244,12 @@ void StreamBrowserDialog::renderBadge(ImVec4 color, ... /* labels, ending in NUL @brief Render a combo box with provided color and values @param label Label for the combo box - @param alignRight if true @param color the color of the combo box @param selected the selected value index (in/out) @param values the combo box values @param useColorForText if true, use the provided color for text (and a darker version of it for background color) @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space - @param hideArrow True to hide the dropdown arrow (defaults to true) - @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) + @param hideArrow True to hide the dropdown arrow @return true if the selected value of the combo has been changed */ @@ -252,8 +261,7 @@ bool StreamBrowserDialog::renderCombo( const vector &values, bool useColorForText, uint8_t cropTextTo, - bool hideArrow, - float paddingRight) + bool hideArrow) { if(selected >= (int)values.size() || selected < 0) { @@ -266,7 +274,7 @@ bool StreamBrowserDialog::renderCombo( if(alignRight) { - int padding = ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x * 2 + paddingRight; + int padding = ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x * 2; float xsz = ImGui::CalcTextSize(selectedLabel).x + padding; string resizedLabel; if ((m_badgeXCur - xsz) < m_badgeXMin) @@ -280,12 +288,12 @@ bool StreamBrowserDialog::renderCombo( resizedLabel = resizedLabel.substr(0,resizedLabel.size()-1); if(resizedLabel.size() < cropTextTo) break; // We don't want to make the text that short - xsz = ImGui::CalcTextSize((resizedLabel + ELLIPSIS_CHAR).c_str()).x + padding; + xsz = ImGui::CalcTextSize((resizedLabel + "...").c_str()).x + padding; } if((m_badgeXCur - xsz) < m_badgeXMin) return false; // Still no room // We found an acceptable size - resizedLabel = resizedLabel + ELLIPSIS_CHAR; + resizedLabel = resizedLabel + "..."; selectedLabel = resizedLabel.c_str(); } m_badgeXCur -= xsz - ImGui::GetStyle().ItemSpacing.x; @@ -296,7 +304,7 @@ bool StreamBrowserDialog::renderCombo( { // Use channel color for shape combo, but darken it to make text readable float bgmul = 0.4; - auto bcolor = ImGui::ColorConvertFloat4ToU32(ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w)); + auto bcolor = ImGui::ColorConvertFloat4ToU32(ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w) ); ImGui::PushStyleColor(ImGuiCol_FrameBg, bcolor); ImGui::PushStyleColor(ImGuiCol_Text, color); } @@ -338,12 +346,10 @@ bool StreamBrowserDialog::renderCombo( /** @brief Render a combo box with provded color and values - @param label Label for the combo box - @param alignRight true if the combo should be aligned to the right @param color the color of the combo box @param selected the selected value index (in/out) @param ... the combo box values - @return true if the selected value of the combo has been changed + @return true true if the selected value of the combo has been changed */ bool StreamBrowserDialog::renderCombo( const char* label, @@ -369,23 +375,30 @@ bool StreamBrowserDialog::renderCombo( /** @brief Render a toggle button combo - @param label Label for the combo box - @param alignRight true if the combo should be aligned to the right @param color the color of the toggle button @param curValue the value of the toggle button - @param valueOff label for value off (optionnal, defaults to "OFF") - @param valueOn label for value on (optionnal, defaults to "ON") - @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) - @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) + @return the selected value for the toggle button + + TODO: replace with renderToggleEXT + */ +bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool curValue) +{ + int selection = (int)curValue; + renderCombo(label, alignRight, color, &selection, "OFF", "ON", nullptr); + return (selection == 1); +} + +/** + @brief Render a toggle button combo + + @param color the color of the toggle button + @param curValue the value of the toggle button @return true if selection has changed */ -bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo, float paddingRight) +bool StreamBrowserDialog::renderToggleEXT(const char* label, bool alignRight, ImVec4 color, bool& curValue) { int selection = (int)curValue; - std::vector values; - values.push_back(string(valueOff)); - values.push_back(string(valueOn)); - bool ret = renderCombo(label, alignRight, color, selection, values, false, cropTextTo, true, paddingRight); + bool ret = renderCombo(label, alignRight, color, &selection, "OFF", "ON", nullptr); curValue = (selection == 1); return ret; } @@ -393,337 +406,37 @@ bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec /** @brief Render an on/off toggle button combo - @param label Label for the combo box - @param alignRight true if the combo should be aligned to the right @param curValue the value of the toggle button - @param valueOff label for value off (optionnal, defaults to "OFF") - @param valueOn label for value on (optionnal, defaults to "ON") - @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) - @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) - @return true if value has changed + @return the selected value for the toggle button + + TODO: replace with renderOnOffToggleEXT */ -bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo, float paddingRight) +bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool curValue) { auto& prefs = m_session.GetPreferences(); ImVec4 color = ImGui::ColorConvertU32ToFloat4( (curValue ? prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); - return renderToggle(label, alignRight, color, curValue, valueOff, valueOn, cropTextTo,paddingRight); -} - -/** - @brief Render a numeric value - @param value the string representation of the value to display (may include the unit) - @param clicked output value for clicked state - @param hovered output value for hovered state - @param color the color to use (defaults to white) - @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format - @param digitHeight the height of a digit (if 0 (defualt), will use ImGui::GetFontSize()) - @param clickable true (default) if the displayed value should be clickable - */ -void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color, bool allow7SegmentDisplay, float digitHeight, bool clickable) -{ - bool use7Segment = false; - if(allow7SegmentDisplay) - { - auto& prefs = m_session.GetPreferences(); - use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); - } - if(use7Segment) - { - if(digitHeight <= 0) digitHeight = ImGui::GetFontSize(); - Render7SegmentValue(value,color,digitHeight,clicked,hovered,clickable); - } - else - { - if(clickable) - { - ImVec2 pos = ImGui::GetCursorPos(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(value.c_str()); - ImGui::PopStyleColor(); - - clicked |= ImGui::IsItemClicked(); - if(ImGui::IsItemHovered()) - { // Hand cursor - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - // Lighter if hovered - color.x = color.x * 1.2f; - color.y = color.y * 1.2f; - color.z = color.z * 1.2f; - ImGui::SetCursorPos(pos); - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(value.c_str()); - ImGui::PopStyleColor(); - hovered = true; - } - } - else - { - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(value.c_str()); - ImGui::PopStyleColor(); - } - } + return renderToggle(label, alignRight, color, curValue); } /** - @brief Render a read-only instrument property value - @param label the value label (used as a label for the property) - @param currentValue the string representation of the current value - @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) -*/ -void StreamBrowserDialog::renderReadOnlyProperty(float width, const string& label, const string& value, const char* tooltip) -{ - ImGui::PushID(label.c_str()); // Prevent collision if several sibling links have the same linktext - float fontSize = ImGui::GetFontSize(); - if(width <= 0) width = 6*fontSize; - ImGuiStyle& style = ImGui::GetStyle(); - ImVec4 bg = style.Colors[ImGuiCol_FrameBg]; - ImGui::PushStyleColor(ImGuiCol_ChildBg, bg); - ImGui::BeginChild("##readOnlyValue", ImVec2(width, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); - ImGui::TextUnformatted(value.c_str()); - ImGui::EndChild(); - ImGui::PopStyleColor(); - ImGui::SameLine(); - ImGui::TextUnformatted(label.c_str()); - ImGui::PopID(); - if(tooltip) - { - HelpMarker(tooltip); - } -} - + @brief Render an on/off toggle button combo -template -/** - @brief Render an editable numeric value - @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) - @param label the value label (used as a label for the TextInput) - @param currentValue the string representation of the current value - @param comittedValue the last comitted typed (float, double or int64_t) value - @param unit the Unit of the value - @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) - @param color the color to use - @param clicked output value for clicked state - @param hovered output value for hovered state - @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format - @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) - @return true if the value has changed + @param curValue the value of the toggle button + @return true if value has changed */ -bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) +bool StreamBrowserDialog::renderOnOffToggleEXT(const char* label, bool alignRight, bool& curValue) { - static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports float or double"); auto& prefs = m_session.GetPreferences(); - bool changed = false; - bool validateChange = false; - bool cancelEdit = false; - bool keepEditing = false; - bool dirty; - float fontSize = ImGui::GetFontSize(); - if(width <= 0) width = 6*fontSize; - ImGui::SetNextItemWidth(width); - if constexpr (std::is_same_v) - dirty = unit.PrettyPrintInt64(committedValue) != currentValue; - else - dirty = unit.PrettyPrint(committedValue) != currentValue; - string editLabel = label+"##Edit"; - ImGuiID editId = ImGui::GetID(editLabel.c_str()); - ImGuiID labelId = ImGui::GetID(label.c_str()); - if(m_editedItemId == editId) - { // Item currently beeing edited - ImGui::BeginGroup(); - float inputXPos = ImGui::GetCursorPosX(); - ImGuiContext& g = *GImGui; - float inputWidth = g.NextItemData.Width; - // Allow overlap for apply button - ImGui::PushItemFlag(ImGuiItemFlags_AllowOverlap, true); - ImGui::PushStyleColor(ImGuiCol_Text, color); - if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) - { // Input validated (but no apply button) - if(!explicitApply) - { // Implcit apply => validate change - validateChange = true; - } - else - { // Explicit apply needed => keep editing - keepEditing = true; - } - } - ImGui::PopStyleColor(); - ImGui::PopItemFlag(); - if(explicitApply) - { // Add Apply button - float buttonWidth = ImGui::GetFontSize() * 2; - // Position the button just before the right side of the text input - ImGui::SameLine(inputXPos+inputWidth-ImGui::GetCursorPosX()-buttonWidth+2*ImGui::GetStyle().ItemInnerSpacing.x); - ImVec4 buttonColorActive = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.apply_button_color")); - float bgmul = 0.8f; - ImVec4 buttonColorHovered = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); - bgmul = 0.7f; - ImVec4 buttonColor = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); - ImGui::BeginDisabled(!dirty); - if(ImGui::Button("\xE2\x8F\x8E")) // Carriage return symbol - { // Apply button click - validateChange = true; - } - ImGui::EndDisabled(); - if(dirty && ImGui::IsItemHovered()) - { // Help to explain apply button - m_parent->AddStatusHelp("mouse_lmb", "Apply value changes and send them to the instrument"); - } - ImGui::PopStyleColor(3); - } - if(!validateChange) - { - if(keepEditing) - { // Give back focus to test input - ImGui::ActivateItemByID(editId); - } - else if(ImGui::IsKeyPressed(ImGuiKey_Escape)) - { // Detect escape => stop editing - cancelEdit = true; - //Prevent focus from going to parent node - ImGui::ActivateItemByID(0); - } - else if((ImGui::GetActiveID() != editId) && (!explicitApply || !ImGui::IsItemActive() /* This is here to prevent detecting focus lost when apply button is clicked */)) - { // Detect focus lost => stop editing too - if(explicitApply) - { // Cancel on focus lost - cancelEdit = true; - } - else - { // Validate on focus list - validateChange = true; - } - } - } - ImGui::EndGroup(); - } - else - { - if(m_lastEditedItemId == editId) - { // Focus lost - if(explicitApply) - { // Cancel edit - cancelEdit = true; - } - else - { // Validate change - validateChange = true; - } - m_lastEditedItemId = 0; - } - bool clicked = false; - bool hovered = false; - bool use7Segment = false; - if(allow7SegmentDisplay) - { - use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); - } - if(use7Segment) - { - ImGui::PushID(labelId); - Render7SegmentValue(currentValue,color,ImGui::GetFontSize(),clicked,hovered); - ImGui::PopID(); - } - else - { - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::InputText(label.c_str(),¤tValue,ImGuiInputTextFlags_ReadOnly); - ImGui::PopStyleColor(); - clicked |= ImGui::IsItemClicked(); - if(ImGui::IsItemHovered()) - { // Keep hand cursor while read-only - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - hovered = true; - } - } - - if (clicked) - { - m_lastEditedItemId = m_editedItemId; - m_editedItemId = editId; - ImGui::ActivateItemByID(editId); - } - if (hovered) - m_parent->AddStatusHelp("mouse_lmb", "Edit value"); - } - if(validateChange) - { - if(m_editedItemId == editId) - { - m_lastEditedItemId = 0; - m_editedItemId = 0; - } - if(dirty) - { // Content actually changed - if constexpr (std::is_same_v) - { - //Float path if the user input a decimal value like "3.5G" - if(currentValue.find(".") != string::npos) - committedValue = unit.ParseString(currentValue); - //Integer path otherwise for full precision - else - committedValue = unit.ParseStringInt64(currentValue); - - currentValue = unit.PrettyPrintInt64(committedValue); - } - else - { - committedValue = static_cast(unit.ParseString(currentValue)); - if constexpr (std::is_same_v) - currentValue = unit.PrettyPrintInt64(committedValue); - else - currentValue = unit.PrettyPrint(committedValue); - } - changed = true; - } - } - else if(cancelEdit) - { // Restore value - if constexpr (std::is_same_v) - currentValue = unit.PrettyPrintInt64(committedValue); - else - currentValue = unit.PrettyPrint(committedValue); - if(m_editedItemId == editId) - { - m_lastEditedItemId = 0; - m_editedItemId = 0; - } - } - if(tooltip) - { - HelpMarker(tooltip); - } - return changed; -} - -template -/** - @brief Render an editable numeric value with explicit apply (if the input value needs to explicitly be applied by clicking the apply button) - @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) - @param label the value label (used as a label for the TextInput) - @param currentValue the string representation of the current value - @param comittedValue the last comitted typed (float, double or int64_t) value - @param unit the Unit of the value - @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) - @param color the color to use - @param clicked output value for clicked state - @param hovered output value for hovered state - @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format - @return true if the value has changed - */ -bool StreamBrowserDialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay) -{ - return renderEditableProperty(width,label,currentValue,committedValue,unit,tooltip,color,allow7SegmentDisplay,true); + ImVec4 color = ImGui::ColorConvertU32ToFloat4( + (curValue ? + prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : + prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); + return renderToggleEXT(label, alignRight, color, curValue); } - /** @brief Render a download progress bar for a given instrument channel @@ -875,24 +588,19 @@ void StreamBrowserDialog::renderDownloadProgress(std::shared_ptr ins @param cc true if the PSU channel is in constant current mode, false for constant voltage mode @param chan the PSU channel to render properties for @param setValue the set value text - @param committedValue the last commited value @param measuredValue the measured value text @param clicked output param for clicked state @param hovered output param for hovered state - - @return true if the value has been modified */ -bool StreamBrowserDialog::renderPsuRows( +void StreamBrowserDialog::renderPsuRows( bool isVoltage, bool cc, PowerSupplyChannel* chan, - std::string& currentValue, - float& committedValue, - std::string& measuredValue, + const char *setValue, + const char *measuredValue, bool &clicked, bool &hovered) { - bool changed = false; auto& prefs = m_session.GetPreferences(); // Row 1 ImGui::TableNextRow(); @@ -916,16 +624,8 @@ bool StreamBrowserDialog::renderPsuRows( ImGui::PopID(); ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "sV" : "sC"); - - ImVec4 color = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.psu_7_segment_color")); - - Unit unit(isVoltage ? Unit::UNIT_VOLTS : Unit::UNIT_AMPS); - - if(renderEditablePropertyWithExplicitApply(0,"##psuSetValue",currentValue,committedValue,unit,nullptr,color,true)) - { - changed = true; - } - + clicked |= ImGui::TextLink(setValue); + hovered |= ImGui::IsItemHovered(); ImGui::PopID(); // Row 2 ImGui::TableNextRow(); @@ -954,121 +654,8 @@ bool StreamBrowserDialog::renderPsuRows( ImGui::PopID(); ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "mV" : "mC"); - - renderNumericValue(measuredValue,clicked,hovered,color,true,0,false); - - ImGui::PopID(); - return changed; -} - -/** - @brief Render DMM channel properties - @param dmm the DMM to render channel properties for - @param dmmchan the DMM channel to render properties for - @param clicked output param for clicked state - @param hovered output param for hovered state - */ -void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered) -{ - size_t streamIndex = isMain ? 0 : 1; - Unit unit = dmmchan->GetYAxisUnits(streamIndex); - float mainValue = dmmchan->GetScalarValue(streamIndex); - string valueText = unit.PrettyPrint(mainValue,dmm->GetMeterDigits()); - ImVec4 color = ImGui::ColorConvertU32ToFloat4(ColorFromString(dmmchan->m_displaycolor)); - string streamName = isMain ? "Main" : "Secondary"; - - ImGui::PushID(streamName.c_str()); - - // Get available operating and current modes - auto modemask = isMain ? dmm->GetMeasurementTypes() : dmm->GetSecondaryMeasurementTypes(); - auto curMode = isMain ? dmm->GetMeterMode() : dmm->GetSecondaryMeterMode(); - - // Stream name - bool open = ImGui::TreeNodeEx(streamName.c_str(), (curMode > 0) ? ImGuiTreeNodeFlags_DefaultOpen : 0); - - // Mode combo - startBadgeLine(); - ImGui::PushID(streamName.c_str()); - vector modeNames; - vector modes; - if(!isMain) - { - // Add None for secondary measurement to be able to disable it - modeNames.push_back("None"); - modes.push_back(Multimeter::MeasurementTypes::NONE); - } - int modeSelector = 0; - for(unsigned int i=0; i<32; i++) - { - auto mode = static_cast(1 << i); - if(modemask & mode) - { - modes.push_back(mode); - modeNames.push_back(dmm->ModeToText(mode)); - if(curMode == mode) - modeSelector = modes.size() - 1; - } - } - - // Padding to give space for ChannelProperties dialog button - auto& prefs = m_session.GetPreferences(); - int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); - - if(renderCombo("##mode", true, color, modeSelector, modeNames,true,3,true,padding)) - { - curMode = modes[modeSelector]; - if(isMain) - dmm->SetMeterMode(curMode); - else - { - dmm->SetSecondaryMeterMode(curMode); - // Open or close tree node according the secondary measure mode - ImGuiContext& g = *GImGui; - ImGui::TreeNodeSetOpen(g.LastItemData.ID,(curMode > 0)); - } - } - ImGui::PopID(); - - StreamDescriptor s(dmmchan, streamIndex); - if(ImGui::BeginDragDropSource()) - { - if(s.GetType() == Stream::STREAM_TYPE_ANALOG_SCALAR) - ImGui::SetDragDropPayload("Scalar", &s, sizeof(s)); - else - ImGui::SetDragDropPayload("Stream", &s, sizeof(s)); - - ImGui::TextUnformatted(s.GetName().c_str()); - ImGui::EndDragDropSource(); - } - else - DoItemHelp(); - - if(open) - ImGui::TreePop(); - - if(open) - { - renderNumericValue(valueText,clicked,hovered,color,true,ImGui::GetFontSize()*2,false); - - if(isMain) - { - auto dmmState = m_session.GetDmmState(dmm); - if(dmmState) - { - ImGui::PushID("autorange"); - // For main, also show the autorange combo - startBadgeLine(); - bool autorange = dmmState->m_autoRange.load(); - if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3,padding)) - { - dmm->SetMeterAutoRange(autorange); - dmmState->m_needsRangeUpdate = true; - } - ImGui::PopID(); - } - } - } - + clicked |= ImGui::TextLink(measuredValue); + hovered |= ImGui::IsItemHovered(); ImGui::PopID(); } @@ -1082,7 +669,6 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr { Unit volts(Unit::UNIT_VOLTS); Unit hz(Unit::UNIT_HZ); - Unit percent(Unit::UNIT_PERCENT); size_t channelIndex = awgchan->GetIndex(); auto awgState = m_session.GetFunctionGeneratorState(awg); @@ -1110,31 +696,39 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_committedFrequency[channelIndex] = freq; awgState->m_strFrequency[channelIndex] = hz.PrettyPrint(freq); } - float dutyCycle = awgState->m_channelDutyCycle[channelIndex]; - if(dutyCycle != awgState->m_committedDutyCycle[channelIndex]) + + auto& prefs = m_session.GetPreferences(); + + //Impedance + ImGui::SetNextItemWidth(dwidth); + /* + if(renderCombo( + "Sample Rate", + false, + ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), + m_timebaseConfig[scope]->m_rate, m_timebaseConfig[scope]->m_rateNames)) { - awgState->m_committedDutyCycle[channelIndex] = dutyCycle; - awgState->m_strDutyCycle[channelIndex] = percent.PrettyPrint(dutyCycle); + scope->SetSampleRate(m_timebaseConfig[scope]->m_rates[m_timebaseConfig[scope]->m_rate]); + refresh = true; } + */ - auto& prefs = m_session.GetPreferences(); + //shape = awgState->m_channelShape[channelIndex]; // Row 1 ImGui::Text("Waveform:"); startBadgeLine(); // Needed for shape combo - // Padding to give space for ChannelProperties dialog button - int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); // Shape combo // Get current shape and shape index FunctionGenerator::WaveShape shape = awgState->m_channelShape[channelIndex]; int shapeIndex = awgState->m_channelShapeIndexes[channelIndex][shape]; if(renderCombo( - "##waveform", + "#waveform", true, ImGui::ColorConvertU32ToFloat4(ColorFromString(awgchan->m_displaycolor)), shapeIndex, awgState->m_channelShapeNames[channelIndex], true, - 3,true,padding)) + 3)) { shape = awgState->m_channelShapes[channelIndex][shapeIndex]; awg->SetFunctionChannelShape(channelIndex, shape); @@ -1144,16 +738,14 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_needsUpdate[channelIndex] = true; } - // Store current Y position for shape preview - float shapePreviewY = ImGui::GetCursorPosY(); - // Row 2 // Frequency label - if(renderEditableProperty(dwidth, + ImGui::SetNextItemWidth(dwidth); + if(UnitInputWithImplicitApply( "Frequency", awgState->m_strFrequency[channelIndex], awgState->m_committedFrequency[channelIndex], - hz/*,"Frequency of the generated waveform"*/)) + hz)) { awg->SetFunctionChannelFrequency(channelIndex, awgState->m_committedFrequency[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; @@ -1167,67 +759,58 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr ImGui::EndDragDropSource(); } else - DoItemHelp(); */ + DoItemHelp(); + HelpMarker("Frequency of the generated waveform"); - //Row 2 - //Duty cycle - if(renderEditableProperty(dwidth, - "Duty cycle", - awgState->m_strDutyCycle[channelIndex], - awgState->m_committedDutyCycle[channelIndex], - percent/*,"Duty cycle of the generated waveform"*/)) - { - awg->SetFunctionChannelDutyCycle(channelIndex, awgState->m_committedDutyCycle[channelIndex]); - awgState->m_needsUpdate[channelIndex] = true; - } - + /* // Shape preview startBadgeLine(); auto height = ImGui::GetFontSize() * 2; auto width = height * 2; - auto totalWidth = width + padding; - if ((m_badgeXCur - totalWidth) >= m_badgeXMin) + if ((m_badgeXCur - width) >= m_badgeXMin) { // ok, we have enough space draw preview - m_badgeXCur -= totalWidth; - // save current y position to restore it after drawing the preview - float currentY = ImGui::GetCursorPosY(); - // Continue layout on current line (row 3) + m_badgeXCur -= width; ImGui::SameLine(m_badgeXCur); - // But use y position of row 2 - ImGui::SetCursorPosY(shapePreviewY); ImGui::Image( m_parent->GetTextureManager()->GetTexture(m_parent->GetIconForWaveformShape(shape)), ImVec2(width,height)); - // Now that we're done with shape preview, restore y position of row 3 - ImGui::SetCursorPosY(currentY); + // Go back one line since preview spans on two text lines + ImGuiWindow *window = ImGui::GetCurrentWindowRead(); + window->DC.CursorPos.y -= ImGui::GetFontSize(); } + */ // Row 3 - if(renderEditablePropertyWithExplicitApply(dwidth, + ImGui::SetNextItemWidth(dwidth); + if(UnitInputWithExplicitApply( "Amplitude", awgState->m_strAmplitude[channelIndex], awgState->m_committedAmplitude[channelIndex], - volts,"Peak-to-peak amplitude of the generated waveform")) + volts)) { awg->SetFunctionChannelAmplitude(channelIndex, awgState->m_committedAmplitude[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } + HelpMarker("Peak-to-peak amplitude of the generated waveform"); //Row 4 //Offset - if(renderEditablePropertyWithExplicitApply(dwidth, + ImGui::SetNextItemWidth(dwidth); + if(UnitInputWithExplicitApply( "Offset", awgState->m_strOffset[channelIndex], awgState->m_committedOffset[channelIndex], - volts,"DC offset for the waveform above (positive) or below (negative) ground")) + volts)) { awg->SetFunctionChannelOffset(channelIndex, awgState->m_committedOffset[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } + HelpMarker("DC offset for the waveform above (positive) or below (negative) ground"); + + //TODO: Duty cycle - //Row 5 //Impedance ImGui::SetNextItemWidth(dwidth); FunctionGenerator::OutputImpedance impedance = awgState->m_channelOutputImpedance[channelIndex]; @@ -1335,18 +918,16 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument bool result; if(allOn || someOn) { - result = true; - renderToggle( + result = 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); + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_partial_badge_color")), true); } else { - result = false; - renderOnOffToggle("###psuon", true, result); + result = renderOnOffToggle("###psuon", true, false); } if(result != allOn) { @@ -1364,15 +945,11 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument if(instIsOpen) { - vector digitalBanks; - vector analogChannels; - vector otherChannels; size_t lastEnabledChannelIndex = 0; if (scope) { if(ImGui::TreeNodeEx("Timebase", ImGuiTreeNodeFlags_DefaultOpen)) { - BeginBlock("timebase"); if(scope->HasTimebaseControls()) DoTimebaseSettings(scope); if(scope->HasFrequencyControls()) @@ -1380,62 +957,20 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument auto spec = dynamic_pointer_cast(scope); if(spec) DoSpectrometerSettings(spec); - EndBlock(); ImGui::TreePop(); } - digitalBanks = scope->GetDigitalBanks(); - for(size_t i = 0; iIsChannelEnabled(i)) lastEnabledChannelIndex = i; - auto scopechan = scope->GetChannel(i); - auto streamType = scopechan->GetType(0); - if(streamType != Stream::STREAM_TYPE_DIGITAL) - { - if(streamType == Stream::STREAM_TYPE_ANALOG) - analogChannels.push_back(i); - else - otherChannels.push_back(i); - } } } - if(digitalBanks.size() > 0) - { // If digital banks are avaialble, gather digital channels in banks - for(size_t i : analogChannels) - { // Iterate on analog channel first - renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); - } - 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)); - } - ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); - ImGui::TreePop(); - } - bankNumber++; - } - for(size_t i : otherChannels) - { // Finally iterate on other channels - renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); - } - } - else - { // Display all channels if no digital bank is available - for(size_t i=0; i scope) Unit hz(Unit::UNIT_HZ); // Resolution Bandwidh - if(renderEditableProperty(width,"Rbw", p->m_rbwText, p->m_rbw, hz, "Resolution Bandwidth")) + ImGui::SetNextItemWidth(width); + if(UnitInputWithImplicitApply("Rbw", p->m_rbwText, p->m_rbw, hz)) { scope->SetResolutionBandwidth(p->m_rbw); // Update with values from the device p->m_rbw = scope->GetResolutionBandwidth(); - p->m_rbwText = hz.PrettyPrintInt64(p->m_rbw); + p->m_rbwText = hz.PrettyPrint(p->m_rbw); } + HelpMarker("Resolution Bandwidth"); //Frequency bool changed = false; - if(renderEditableProperty(width,"Start", p->m_startText, p->m_start, hz, "Start of the frequency sweep")) + ImGui::SetNextItemWidth(width); + if(UnitInputWithImplicitApply("Start", p->m_startText, p->m_start, hz)) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1500,20 +1038,26 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) scope->SetSpan(span); changed = true; } + HelpMarker("Start of the frequency sweep"); - if(renderEditableProperty(width,"Center", p->m_centerText, p->m_center, hz, "Midpoint of the frequency sweep")) + ImGui::SetNextItemWidth(width); + if(UnitInputWithImplicitApply("Center", p->m_centerText, p->m_center, hz)) { scope->SetCenterFrequency(0, p->m_center); changed = true; } + HelpMarker("Midpoint of the frequency sweep"); - if(renderEditableProperty(width,"Span", p->m_spanText, p->m_span, hz, "Width of the frequency sweep")) + ImGui::SetNextItemWidth(width); + if(UnitInputWithImplicitApply("Span", p->m_spanText, p->m_span, hz)) { scope->SetSpan(p->m_span); changed = true; } + HelpMarker("Width of the frequency sweep"); - if(renderEditableProperty(width,"End", p->m_endText, p->m_end, hz, "End of the frequency sweep")) + ImGui::SetNextItemWidth(width); + if(UnitInputWithImplicitApply("End", p->m_endText, p->m_end, hz)) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1521,6 +1065,7 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) scope->SetSpan(span); changed = true; } + HelpMarker("End of the frequency sweep"); //Update everything if one setting is changed if(changed) @@ -1544,10 +1089,12 @@ void StreamBrowserDialog::DoSpectrometerSettings(shared_ptr sp auto config = m_timebaseConfig[scope]; auto width = ImGui::GetFontSize() * 5; + ImGui::SetNextItemWidth(width); Unit fs(Unit::UNIT_FS); - if(renderEditableProperty(width, "Integration time", config->m_integrationText, config->m_integrationTime, fs, "Spectrometer integration / exposure time")) + if(UnitInputWithImplicitApply("Integration time", config->m_integrationText, config->m_integrationTime, fs)) spec->SetIntegrationTime(config->m_integrationTime); + HelpMarker("Spectrometer integration / exposure time"); } /** @@ -1567,7 +1114,7 @@ void StreamBrowserDialog::DoTimebaseSettings(shared_ptr scope) ImGui::SetNextItemWidth(width); bool disabled = !scope->CanInterleave(); ImGui::BeginDisabled(disabled); - if(renderOnOffToggle("Interleaving", false, config->m_interleaving)) + if(renderOnOffToggleEXT("Interleaving", false, config->m_interleaving)) { scope->SetInterleaving(config->m_interleaving); refresh = true; @@ -1706,13 +1253,11 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto psu = std::dynamic_pointer_cast(instrument); auto scope = std::dynamic_pointer_cast(instrument); auto awg = std::dynamic_pointer_cast(instrument); - auto dmm = std::dynamic_pointer_cast(instrument); bool singleStream = channel->GetStreamCount() == 1; auto scopechan = dynamic_cast(channel); auto psuchan = dynamic_cast(channel); auto awgchan = dynamic_cast(channel); - auto dmmchan = dynamic_cast(channel); bool renderProps = false; if (scopechan) { @@ -1731,8 +1276,8 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s int flags = 0; if(!hasChildren) flags |= ImGuiTreeNodeFlags_Leaf; - - flags |= ImGuiTreeNodeFlags_OpenOnArrow; + else + flags |= ImGuiTreeNodeFlags_OpenOnArrow; bool open = ImGui::TreeNodeEx( channel->GetDisplayName().c_str(), @@ -1808,8 +1353,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto psustate = m_session.GetPSUState(psu); bool active = psustate->m_channelOn[channelIndex]; - bool result = active; - renderOnOffToggle("###active", true, result); + bool result = renderOnOffToggle("###active", true, active); if(result != active) psu->SetPowerChannelActive(channelIndex,result); } @@ -1819,8 +1363,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto awgstate = m_session.GetFunctionGeneratorState(awg); bool active = awgstate->m_channelActive[channelIndex]; - bool result = active; - renderOnOffToggle("###active", true, result); + bool result = renderOnOffToggle("###active", true, active); if(result != active) { awg->SetFunctionChannelActive(channelIndex,result); @@ -1842,11 +1385,8 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if(psu) { // For PSU we will have a special handling for the 4 streams associated to a PSU channel - if(BeginBlock("psu_params",true,"Open PSU channel properties")) - { - m_parent->ShowInstrumentProperties(psu); - } - + ImGui::BeginChild("psu_params", ImVec2(0, 0), + ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); auto svoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageSetPoint ()); auto mvoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageMeasured()); auto scurrent_txt = Unit(Unit::UNIT_AMPS).PrettyPrint(psuchan->GetCurrentSetPoint ()); @@ -1855,69 +1395,36 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s bool cc = false; auto psuState = m_session.GetPSUState(psu); if(psuState) - { cc = psuState->m_channelConstantCurrent[channelIndex].load(); - bool clicked = false; - bool hovered = false; + bool clicked = false; + bool hovered = false; - if (ImGui::BeginTable("table1", 3)) + if (ImGui::BeginTable("table1", 3)) + { + // Voltage + renderPsuRows(true,cc,psuchan,svoltage_txt.c_str(),mvoltage_txt.c_str(),clicked,hovered); + // Current + renderPsuRows(false,cc,psuchan,scurrent_txt.c_str(),mcurrent_txt.c_str(),clicked,hovered); + // End table + ImGui::EndTable(); + if (clicked) { - // Voltage - if(renderPsuRows(true,cc,psuchan,psuState->m_setVoltage[channelIndex],psuState->m_committedSetVoltage[channelIndex],mvoltage_txt,clicked,hovered)) - { // Update set voltage - psu->SetPowerVoltage(channelIndex, psuState->m_committedSetVoltage[channelIndex]); - psuState->m_needsUpdate[channelIndex] = true; - } - // Current - if(renderPsuRows(false,cc,psuchan,psuState->m_setCurrent[channelIndex],psuState->m_committedSetCurrent[channelIndex],mcurrent_txt,clicked,hovered)) - { // Update set current - psu->SetPowerCurrent(channelIndex, psuState->m_committedSetCurrent[channelIndex]); - psuState->m_needsUpdate[channelIndex] = true; - } - // End table - ImGui::EndTable(); + m_parent->ShowInstrumentProperties(psu); } + if (hovered) + m_parent->AddStatusHelp("mouse_lmb", "Open channel properties"); } - EndBlock(); + ImGui::EndChild(); } else if(awg && awgchan) { - if(BeginBlock("awgparams",true)) - { - m_parent->ShowInstrumentProperties(awg); - } - renderAwgProperties(awg, awgchan); - EndBlock(); - } - else if(dmm && dmmchan) - { - if(BeginBlock("dmm_params",true,"Open Multimeter properties")) - { - m_parent->ShowInstrumentProperties(dmm); - } - // Always 2 streams for dmm channel => render properties on channel node - bool clicked = false; - bool hovered = false; - // Main measurement - renderDmmProperties(dmm,dmmchan,true,clicked,hovered); - // Secondary measurement - renderDmmProperties(dmm,dmmchan,false,clicked,hovered); - - EndBlock(); + ImGui::PushID("awgparams"); + renderAwgProperties(awg, awgchan); + ImGui::PopID(); } else { - if(!singleStream) - { - if(BeginBlock("stream_params",true,"Open channel properties")) - { - m_parent->ShowChannelProperties(scopechan); - } - auto scopeState = m_session.GetOscillopscopeState(scope); - renderChannelProperties(scope,scopechan,channelIndex,scopeState); - EndBlock(); - } size_t streamCount = channel->GetStreamCount(); for(size_t j=0; j instrument, s ImGui::PopID(); } -/** - @brief Rendering of channel properties - - @param scope the scope - @param scopechan the scope channel - @param channelIndex the index of the channel - @param scopeState the OscilloscopeState - */ -void StreamBrowserDialog::renderChannelProperties(std::shared_ptr scope, OscilloscopeChannel* scopechan, size_t channelIndex, shared_ptr scopeState) -{ - float fontSize = ImGui::GetFontSize(); - float width = 6*fontSize; - - Unit counts(Unit::UNIT_COUNTS); - if(renderEditableProperty(width,"Attenuation",scopeState->m_strAttenuation[channelIndex],scopeState->m_committedAttenuation[channelIndex],counts, - "Attenuation setting for the probe (for example, 10 for a 10:1 probe)")) - { // Update offset - scopechan->SetAttenuation(scopeState->m_committedAttenuation[channelIndex]); - scopeState->m_needsUpdate[channelIndex] = true; - } - //Only show coupling box if the instrument has configurable coupling - if( (scopeState->m_couplings[channelIndex].size() > 1) && (scopeState->m_probeName[channelIndex] == "") ) - { - ImGui::SetNextItemWidth(width); - if(renderCombo( - "Coupling", - false, - ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), - scopeState->m_channelCoupling[channelIndex], - scopeState->m_couplingNames[channelIndex], - false, - 0, - false)) - { - scope->SetChannelCoupling(channelIndex,scopeState->m_couplings[channelIndex][scopeState->m_channelCoupling[channelIndex]]); - scopeState->m_needsUpdate[channelIndex] = true; - } - HelpMarker("Coupling configuration for the input"); - } - //Bandwidth limiters (only show if more than one value available) - if(scopeState->m_bandwidthLimitNames[channelIndex].size() > 1) - { - ImGui::SetNextItemWidth(width); - if(renderCombo( - "Bandwidth", - false, - ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), - scopeState->m_channelBandwidthLimit[channelIndex], - scopeState->m_bandwidthLimitNames[channelIndex], - false, - 0, - false)) - { - scopechan->SetBandwidthLimit(scopeState->m_bandwidthLimits[channelIndex][scopeState->m_channelBandwidthLimit[channelIndex]]); - scopeState->m_needsUpdate[channelIndex] = true; - } - HelpMarker("Hardware bandwidth limiter setting"); - } - //If the probe supports inversion, show a checkbox for it - if(scope->CanInvert(channelIndex)) - { - ImGui::SetNextItemWidth(width); - if(renderOnOffToggle("Invert",false,scopeState->m_channelInverted[channelIndex])) - { - scope->Invert(channelIndex,scopeState->m_channelInverted[channelIndex]); - scopeState->m_needsUpdate[channelIndex] = true; - } - HelpMarker( - "When ON, input value is multiplied by -1.\n" - "For a differential probe, this is equivalent to swapping the positive and negative inputs." - ); - } - -} - /** @brief Rendering of a stream node @@ -2052,7 +1484,6 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In // Channel/stream properties block if(renderProps && scopechan) { - // If no properties are available for this stream, only show a "Properties" link if it is the last stream of the channel/filter bool hasProps = false; switch (type) { @@ -2070,58 +1501,36 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } if(hasProps) { - if(BeginBlock("stream_params",true,"Open channel properties")) - { - m_parent->ShowChannelProperties(scopechan); - } + ImGui::BeginChild("stream_params", ImVec2(0, 0), + ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); Unit unit = channel->GetYAxisUnits(streamIndex); - size_t channelIndex = scopechan->GetIndex(); - auto scopeState = m_session.GetOscillopscopeState(scope); - + bool clicked; + bool hovered; switch (type) { case Stream::STREAM_TYPE_ANALOG: { - if(!renderName) - { // No streams => display channel properties here - renderChannelProperties(scope,scopechan,channelIndex,scopeState); - } - if(renderEditablePropertyWithExplicitApply(0,"Offset",scopeState->m_strOffset[channelIndex][streamIndex],scopeState->m_committedOffset[channelIndex][streamIndex],unit)) - { // Update offset - scopechan->SetOffset(scopeState->m_committedOffset[channelIndex][streamIndex],streamIndex); - scopeState->m_needsUpdate[channelIndex] = true; - } - if(renderEditablePropertyWithExplicitApply(0,"Vertical range",scopeState->m_strRange[channelIndex][streamIndex],scopeState->m_committedRange[channelIndex][streamIndex],unit)) - { // Update offset - scopechan->SetVoltageRange(scopeState->m_committedRange[channelIndex][streamIndex],streamIndex); - scopeState->m_needsUpdate[channelIndex] = true; - } + auto offset_txt = unit.PrettyPrint(scopechan->GetOffset(streamIndex)); + auto range_txt = unit.PrettyPrint(scopechan->GetVoltageRange(streamIndex)); + renderInfoLink("Offset", offset_txt.c_str(), clicked, hovered); + renderInfoLink("Vertical range", range_txt.c_str(), clicked, hovered); } break; case Stream::STREAM_TYPE_DIGITAL: if(scope) { - if(scope->IsDigitalThresholdConfigurable()) - { - if(renderEditablePropertyWithExplicitApply(0,"Threshold",scopeState->m_strDigitalThreshold[channelIndex],scopeState->m_committedDigitalThreshold[channelIndex],unit)) - { // Update offset - scopechan->SetDigitalThreshold(scopeState->m_committedDigitalThreshold[channelIndex]); - scopeState->m_needsUpdate[channelIndex] = true; - } - } - else - { - auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); - renderReadOnlyProperty(0,"Threshold", threshold_txt); - } + auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); + renderInfoLink("Threshold", threshold_txt.c_str(), clicked, hovered); break; } //fall through default: + { + } break; } - EndBlock(); + ImGui::EndChild(); } } ImGui::PopID(); @@ -2242,59 +1651,3 @@ void StreamBrowserDialog::DoItemHelp() if(ImGui::IsItemHovered()) m_parent->AddStatusHelp("mouse_lmb_drag", "Add to filter graph or plot"); } - -bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton, const char* tooltip) -{ - bool clicked = false; - auto& prefs = m_session.GetPreferences(); - ImGuiWindowFlags flags = ImGuiChildFlags_AutoResizeY; - bool withBorders = false; - if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) - { - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); - flags |= ImGuiChildFlags_Borders; - withBorders = true; - } - ImGui::BeginChild(label, ImVec2(0, 0), flags); - if(withButton) - { // Create a "+" button on the top right corner of the box - ImVec2 oldPos = ImGui::GetCursorPos(); - float padding = ImGui::GetStyle().FramePadding.x; - float shift = withBorders ? padding*1.5 : 0; - float xsz = ImGui::GetFontSize(); - ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - xsz + shift); - ImGui::SetCursorPosY(ImGui::GetCursorPosY()-shift); - // Use the same color as border for the button - ImVec4 border = ImGui::GetStyle().Colors[ImGuiCol_Border]; - ImVec4 hover = ImVec4(border.x * 1.2f, border.y * 1.2f, border.z * 1.2f, border.w); - ImVec4 active = ImVec4(border.x * 0.9f, border.y * 0.9f, border.z * 0.9f, border.w); - ImGui::PushStyleColor(ImGuiCol_Button, border); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hover); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, active); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.6f, 0.5f)); - clicked = ImGui::Button(PLUS_CHAR,ImVec2(xsz, xsz)); - if(ImGui::IsItemHovered()) - { // Hand cursor - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - if(tooltip) - { - m_parent->AddStatusHelp("mouse_lmb", tooltip); - } - } - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(3); - ImGui::SetCursorPos(oldPos); - } - return clicked; -} - -void StreamBrowserDialog::EndBlock() -{ - ImGui::EndChild(); - auto& prefs = m_session.GetPreferences(); - if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) - { - ImGui::PopStyleVar(); - } -} \ No newline at end of file diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 6d232eea..cb71d44a 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -121,11 +121,8 @@ class StreamBrowserDialog : public Dialog void DoItemHelp(); - // Block handling - bool BeginBlock(const char *label, bool withButton = false, const char* tooltip = nullptr); - void EndBlock(); - // Rendeding of StreamBrowserDialog elements + void renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered); void startBadgeLine(); void renderBadge(ImVec4 color, ... /* labels, ending in NULL */); void renderInstrumentBadge(std::shared_ptr inst, bool latched, InstrumentBadge badge); @@ -137,8 +134,7 @@ class StreamBrowserDialog : public Dialog const std::vector& values, bool useColorForText = false, uint8_t cropTextTo = 0, - bool hideArrow = true, - float paddingRight = 0); + bool hideArrow = true); bool renderCombo( const char* label, bool alignRight, @@ -149,22 +145,17 @@ class StreamBrowserDialog : public Dialog const char* label, bool alignRight, ImVec4 color, - bool& curValue, - const char* valueOff = "OFF", - const char* valueOn = "ON", - uint8_t cropTextTo = 0, - float paddingRight = 0); - bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0, float paddingRight = 0); - void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); - void renderReadOnlyProperty(float width, const std::string& label, const std::string& value, const char* tooltip = nullptr); - template - bool renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, ImVec4 color = ImVec4(1, 1, 1, 1), 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, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); + bool curValue); + bool renderToggleEXT( + const char* label, + bool alignRight, + ImVec4 color, + bool& curValue); + bool renderOnOffToggle(const char* label, bool alignRight, bool curValue); + bool renderOnOffToggleEXT(const char* label, bool alignRight, bool& curValue); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); - bool renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan, std::string& currentValue, float& committedValue, std::string& measuredValue, bool &clicked, bool &hovered); + void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); - void renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered); // Rendering of an instrument node void renderInstrumentNode(std::shared_ptr instrument); @@ -176,8 +167,6 @@ class StreamBrowserDialog : public Dialog // Rendering of a channel node void renderChannelNode(std::shared_ptr instrument, size_t channelIndex, bool isLast); - void renderChannelProperties(std::shared_ptr scope, OscilloscopeChannel* scopechan, size_t channelIndex, std::shared_ptr scopeState); - // Rendering of a stream node void renderStreamNode(std::shared_ptr instrument, InstrumentChannel* channel, size_t streamIndex, bool renderName, bool renderProps); @@ -191,11 +180,6 @@ class StreamBrowserDialog : public Dialog float m_badgeXMin; // left edge over which we must not overrun float m_badgeXCur; // right edge to render the next badge against - ///@brief Id of the item currently beeing edited - ImGuiID m_editedItemId = 0; - ///@brief Id of the last edited item - ImGuiID m_lastEditedItemId = 0; - std::map, bool> m_instrumentDownloadIsSlow; ///@brief Store the last state of an intrument badge (used for badge state latching) std::map, std::pair> m_instrumentLastBadge; From 2c2a41da33fb00a725a5641bb6084546c9afb19b Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Sat, 10 Jan 2026 01:24:01 +0100 Subject: [PATCH 35/49] Finished merge from upstream. --- src/ngscopeclient/StreamBrowserDialog.cpp | 975 ++++++++++++++++++---- src/ngscopeclient/StreamBrowserDialog.h | 38 +- 2 files changed, 838 insertions(+), 175 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index b93ca459..39051cee 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -39,6 +39,9 @@ using namespace std; +#define ELLIPSIS_CHAR "\xE2\x80\xA6" // "..." character +#define PLUS_CHAR "+" + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // StreamBrowserTimebaseInfo @@ -82,7 +85,7 @@ StreamBrowserTimebaseInfo::StreamBrowserTimebaseInfo(shared_ptr sc Unit hz(Unit::UNIT_HZ); m_rbw = scope->GetResolutionBandwidth(); - m_rbwText = hz.PrettyPrint(m_rbw); + m_rbwText = hz.PrettyPrintInt64(m_rbw); m_span = scope->GetSpan(); m_spanText = hz.PrettyPrint(m_span); @@ -132,20 +135,6 @@ StreamBrowserDialog::~StreamBrowserDialog() //Helper methods for rendering widgets that appear in the StreamBrowserDialog. -/** - @brief Render a link of the "Sample rate: 4 GSa/s" type that shows up in the - scope properties box. -*/ -void StreamBrowserDialog::renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered) -{ - ImGui::PushID(label); // Prevent collision if several sibling links have the same linktext - ImGui::Text("%s: ", label); - ImGui::SameLine(0, 0); - clicked |= ImGui::TextLink(linktext); - hovered |= ImGui::IsItemHovered(); - ImGui::PopID(); -} - /** @brief prepare rendering context to display a badge at the end of current line */ @@ -244,12 +233,14 @@ void StreamBrowserDialog::renderBadge(ImVec4 color, ... /* labels, ending in NUL @brief Render a combo box with provided color and values @param label Label for the combo box + @param alignRight if true @param color the color of the combo box @param selected the selected value index (in/out) @param values the combo box values @param useColorForText if true, use the provided color for text (and a darker version of it for background color) @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space - @param hideArrow True to hide the dropdown arrow + @param hideArrow True to hide the dropdown arrow (defaults to true) + @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) @return true if the selected value of the combo has been changed */ @@ -261,7 +252,8 @@ bool StreamBrowserDialog::renderCombo( const vector &values, bool useColorForText, uint8_t cropTextTo, - bool hideArrow) + bool hideArrow, + float paddingRight) { if(selected >= (int)values.size() || selected < 0) { @@ -274,7 +266,7 @@ bool StreamBrowserDialog::renderCombo( if(alignRight) { - int padding = ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x * 2; + int padding = ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x * 2 + paddingRight; float xsz = ImGui::CalcTextSize(selectedLabel).x + padding; string resizedLabel; if ((m_badgeXCur - xsz) < m_badgeXMin) @@ -288,12 +280,12 @@ bool StreamBrowserDialog::renderCombo( resizedLabel = resizedLabel.substr(0,resizedLabel.size()-1); if(resizedLabel.size() < cropTextTo) break; // We don't want to make the text that short - xsz = ImGui::CalcTextSize((resizedLabel + "...").c_str()).x + padding; + xsz = ImGui::CalcTextSize((resizedLabel + ELLIPSIS_CHAR).c_str()).x + padding; } if((m_badgeXCur - xsz) < m_badgeXMin) return false; // Still no room // We found an acceptable size - resizedLabel = resizedLabel + "..."; + resizedLabel = resizedLabel + ELLIPSIS_CHAR; selectedLabel = resizedLabel.c_str(); } m_badgeXCur -= xsz - ImGui::GetStyle().ItemSpacing.x; @@ -304,7 +296,7 @@ bool StreamBrowserDialog::renderCombo( { // Use channel color for shape combo, but darken it to make text readable float bgmul = 0.4; - auto bcolor = ImGui::ColorConvertFloat4ToU32(ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w) ); + auto bcolor = ImGui::ColorConvertFloat4ToU32(ImVec4(color.x*bgmul, color.y*bgmul, color.z*bgmul, color.w)); ImGui::PushStyleColor(ImGuiCol_FrameBg, bcolor); ImGui::PushStyleColor(ImGuiCol_Text, color); } @@ -346,10 +338,12 @@ bool StreamBrowserDialog::renderCombo( /** @brief Render a combo box with provded color and values + @param label Label for the combo box + @param alignRight true if the combo should be aligned to the right @param color the color of the combo box @param selected the selected value index (in/out) @param ... the combo box values - @return true true if the selected value of the combo has been changed + @return true if the selected value of the combo has been changed */ bool StreamBrowserDialog::renderCombo( const char* label, @@ -375,30 +369,23 @@ bool StreamBrowserDialog::renderCombo( /** @brief Render a toggle button combo + @param label Label for the combo box + @param alignRight true if the combo should be aligned to the right @param color the color of the toggle button @param curValue the value of the toggle button - @return the selected value for the toggle button - - TODO: replace with renderToggleEXT - */ -bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool curValue) -{ - int selection = (int)curValue; - renderCombo(label, alignRight, color, &selection, "OFF", "ON", nullptr); - return (selection == 1); -} - -/** - @brief Render a toggle button combo - - @param color the color of the toggle button - @param curValue the value of the toggle button + @param valueOff label for value off (optionnal, defaults to "OFF") + @param valueOn label for value on (optionnal, defaults to "ON") + @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) + @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) @return true if selection has changed */ -bool StreamBrowserDialog::renderToggleEXT(const char* label, bool alignRight, ImVec4 color, bool& curValue) +bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec4 color, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo, float paddingRight) { int selection = (int)curValue; - bool ret = renderCombo(label, alignRight, color, &selection, "OFF", "ON", nullptr); + std::vector values; + values.push_back(string(valueOff)); + values.push_back(string(valueOn)); + bool ret = renderCombo(label, alignRight, color, selection, values, false, cropTextTo, true, paddingRight); curValue = (selection == 1); return ret; } @@ -406,37 +393,337 @@ bool StreamBrowserDialog::renderToggleEXT(const char* label, bool alignRight, Im /** @brief Render an on/off toggle button combo + @param label Label for the combo box + @param alignRight true if the combo should be aligned to the right @param curValue the value of the toggle button - @return the selected value for the toggle button - - TODO: replace with renderOnOffToggleEXT + @param valueOff label for value off (optionnal, defaults to "OFF") + @param valueOn label for value on (optionnal, defaults to "ON") + @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) + @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) + @return true if value has changed */ -bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool curValue) +bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo, float paddingRight) { auto& prefs = m_session.GetPreferences(); ImVec4 color = ImGui::ColorConvertU32ToFloat4( (curValue ? prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); - return renderToggle(label, alignRight, color, curValue); + return renderToggle(label, alignRight, color, curValue, valueOff, valueOn, cropTextTo,paddingRight); } /** - @brief Render an on/off toggle button combo + @brief Render a numeric value + @param value the string representation of the value to display (may include the unit) + @param clicked output value for clicked state + @param hovered output value for hovered state + @param color the color to use (defaults to white) + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @param digitHeight the height of a digit (if 0 (defualt), will use ImGui::GetFontSize()) + @param clickable true (default) if the displayed value should be clickable + */ +void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color, bool allow7SegmentDisplay, float digitHeight, bool clickable) +{ + bool use7Segment = false; + if(allow7SegmentDisplay) + { + auto& prefs = m_session.GetPreferences(); + use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); + } + if(use7Segment) + { + if(digitHeight <= 0) digitHeight = ImGui::GetFontSize(); + Render7SegmentValue(value,color,digitHeight,clicked,hovered,clickable); + } + else + { + if(clickable) + { + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(value.c_str()); + ImGui::PopStyleColor(); - @param curValue the value of the toggle button - @return true if value has changed + clicked |= ImGui::IsItemClicked(); + if(ImGui::IsItemHovered()) + { // Hand cursor + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + // Lighter if hovered + color.x = color.x * 1.2f; + color.y = color.y * 1.2f; + color.z = color.z * 1.2f; + ImGui::SetCursorPos(pos); + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(value.c_str()); + ImGui::PopStyleColor(); + hovered = true; + } + } + else + { + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(value.c_str()); + ImGui::PopStyleColor(); + } + } +} + +/** + @brief Render a read-only instrument property value + @param label the value label (used as a label for the property) + @param currentValue the string representation of the current value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) +*/ +void StreamBrowserDialog::renderReadOnlyProperty(float width, const string& label, const string& value, const char* tooltip) +{ + ImGui::PushID(label.c_str()); // Prevent collision if several sibling links have the same linktext + float fontSize = ImGui::GetFontSize(); + if(width <= 0) width = 6*fontSize; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 bg = style.Colors[ImGuiCol_FrameBg]; + ImGui::PushStyleColor(ImGuiCol_ChildBg, bg); + ImGui::BeginChild("##readOnlyValue", ImVec2(width, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); + ImGui::TextUnformatted(value.c_str()); + ImGui::EndChild(); + ImGui::PopStyleColor(); + ImGui::SameLine(); + ImGui::TextUnformatted(label.c_str()); + ImGui::PopID(); + if(tooltip) + { + HelpMarker(tooltip); + } +} + + +template +/** + @brief Render an editable numeric value + @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) + @param label the value label (used as a label for the TextInput) + @param currentValue the string representation of the current value + @param comittedValue the last comitted typed (float, double or int64_t) value + @param unit the Unit of the value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) + @param color the color to use + @param clicked output value for clicked state + @param hovered output value for hovered state + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) + @return true if the value has changed */ -bool StreamBrowserDialog::renderOnOffToggleEXT(const char* label, bool alignRight, bool& curValue) +bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports float or double"); auto& prefs = m_session.GetPreferences(); - ImVec4 color = ImGui::ColorConvertU32ToFloat4( - (curValue ? - prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : - prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color"))); - return renderToggleEXT(label, alignRight, color, curValue); + bool changed = false; + bool validateChange = false; + bool cancelEdit = false; + bool keepEditing = false; + bool dirty; + float fontSize = ImGui::GetFontSize(); + if(width <= 0) width = 6*fontSize; + ImGui::SetNextItemWidth(width); + if constexpr (std::is_same_v) + dirty = unit.PrettyPrintInt64(committedValue) != currentValue; + else + dirty = unit.PrettyPrint(committedValue) != currentValue; + string editLabel = label+"##Edit"; + ImGuiID editId = ImGui::GetID(editLabel.c_str()); + ImGuiID labelId = ImGui::GetID(label.c_str()); + if(m_editedItemId == editId) + { // Item currently beeing edited + ImGui::BeginGroup(); + float inputXPos = ImGui::GetCursorPosX(); + ImGuiContext& g = *GImGui; + float inputWidth = g.NextItemData.Width; + // Allow overlap for apply button + ImGui::PushItemFlag(ImGuiItemFlags_AllowOverlap, true); + ImGui::PushStyleColor(ImGuiCol_Text, color); + if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) + { // Input validated (but no apply button) + if(!explicitApply) + { // Implcit apply => validate change + validateChange = true; + } + else + { // Explicit apply needed => keep editing + keepEditing = true; + } + } + ImGui::PopStyleColor(); + ImGui::PopItemFlag(); + if(explicitApply) + { // Add Apply button + float buttonWidth = ImGui::GetFontSize() * 2; + // Position the button just before the right side of the text input + ImGui::SameLine(inputXPos+inputWidth-ImGui::GetCursorPosX()-buttonWidth+2*ImGui::GetStyle().ItemInnerSpacing.x); + ImVec4 buttonColorActive = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.apply_button_color")); + float bgmul = 0.8f; + ImVec4 buttonColorHovered = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); + bgmul = 0.7f; + ImVec4 buttonColor = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); + ImGui::BeginDisabled(!dirty); + if(ImGui::Button("\xE2\x8F\x8E")) // Carriage return symbol + { // Apply button click + validateChange = true; + } + ImGui::EndDisabled(); + if(dirty && ImGui::IsItemHovered()) + { // Help to explain apply button + m_parent->AddStatusHelp("mouse_lmb", "Apply value changes and send them to the instrument"); + } + ImGui::PopStyleColor(3); + } + if(!validateChange) + { + if(keepEditing) + { // Give back focus to test input + ImGui::ActivateItemByID(editId); + } + else if(ImGui::IsKeyPressed(ImGuiKey_Escape)) + { // Detect escape => stop editing + cancelEdit = true; + //Prevent focus from going to parent node + ImGui::ActivateItemByID(0); + } + else if((ImGui::GetActiveID() != editId) && (!explicitApply || !ImGui::IsItemActive() /* This is here to prevent detecting focus lost when apply button is clicked */)) + { // Detect focus lost => stop editing too + if(explicitApply) + { // Cancel on focus lost + cancelEdit = true; + } + else + { // Validate on focus list + validateChange = true; + } + } + } + ImGui::EndGroup(); + } + else + { + if(m_lastEditedItemId == editId) + { // Focus lost + if(explicitApply) + { // Cancel edit + cancelEdit = true; + } + else + { // Validate change + validateChange = true; + } + m_lastEditedItemId = 0; + } + bool clicked = false; + bool hovered = false; + bool use7Segment = false; + if(allow7SegmentDisplay) + { + use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); + } + if(use7Segment) + { + ImGui::PushID(labelId); + Render7SegmentValue(currentValue,color,ImGui::GetFontSize(),clicked,hovered); + ImGui::PopID(); + } + else + { + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::InputText(label.c_str(),¤tValue,ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(); + clicked |= ImGui::IsItemClicked(); + if(ImGui::IsItemHovered()) + { // Keep hand cursor while read-only + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + hovered = true; + } + } + + if (clicked) + { + m_lastEditedItemId = m_editedItemId; + m_editedItemId = editId; + ImGui::ActivateItemByID(editId); + } + if (hovered) + m_parent->AddStatusHelp("mouse_lmb", "Edit value"); + } + if(validateChange) + { + if(m_editedItemId == editId) + { + m_lastEditedItemId = 0; + m_editedItemId = 0; + } + if(dirty) + { // Content actually changed + if constexpr (std::is_same_v) + { + //Float path if the user input a decimal value like "3.5G" + if(currentValue.find(".") != string::npos) + committedValue = unit.ParseString(currentValue); + //Integer path otherwise for full precision + else + committedValue = unit.ParseStringInt64(currentValue); + + currentValue = unit.PrettyPrintInt64(committedValue); + } + else + { + committedValue = static_cast(unit.ParseString(currentValue)); + if constexpr (std::is_same_v) + currentValue = unit.PrettyPrintInt64(committedValue); + else + currentValue = unit.PrettyPrint(committedValue); + } + changed = true; + } + } + else if(cancelEdit) + { // Restore value + if constexpr (std::is_same_v) + currentValue = unit.PrettyPrintInt64(committedValue); + else + currentValue = unit.PrettyPrint(committedValue); + if(m_editedItemId == editId) + { + m_lastEditedItemId = 0; + m_editedItemId = 0; + } + } + if(tooltip) + { + HelpMarker(tooltip); + } + return changed; } +template +/** + @brief Render an editable numeric value with explicit apply (if the input value needs to explicitly be applied by clicking the apply button) + @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) + @param label the value label (used as a label for the TextInput) + @param currentValue the string representation of the current value + @param comittedValue the last comitted typed (float, double or int64_t) value + @param unit the Unit of the value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) + @param color the color to use + @param clicked output value for clicked state + @param hovered output value for hovered state + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @return true if the value has changed + */ +bool StreamBrowserDialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay) +{ + return renderEditableProperty(width,label,currentValue,committedValue,unit,tooltip,color,allow7SegmentDisplay,true); +} + + /** @brief Render a download progress bar for a given instrument channel @@ -588,19 +875,24 @@ void StreamBrowserDialog::renderDownloadProgress(std::shared_ptr ins @param cc true if the PSU channel is in constant current mode, false for constant voltage mode @param chan the PSU channel to render properties for @param setValue the set value text + @param committedValue the last commited value @param measuredValue the measured value text @param clicked output param for clicked state @param hovered output param for hovered state + + @return true if the value has been modified */ -void StreamBrowserDialog::renderPsuRows( +bool StreamBrowserDialog::renderPsuRows( bool isVoltage, bool cc, PowerSupplyChannel* chan, - const char *setValue, - const char *measuredValue, + std::string& currentValue, + float& committedValue, + std::string& measuredValue, bool &clicked, bool &hovered) { + bool changed = false; auto& prefs = m_session.GetPreferences(); // Row 1 ImGui::TableNextRow(); @@ -624,8 +916,16 @@ void StreamBrowserDialog::renderPsuRows( ImGui::PopID(); ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "sV" : "sC"); - clicked |= ImGui::TextLink(setValue); - hovered |= ImGui::IsItemHovered(); + + ImVec4 color = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.psu_7_segment_color")); + + Unit unit(isVoltage ? Unit::UNIT_VOLTS : Unit::UNIT_AMPS); + + if(renderEditablePropertyWithExplicitApply(0,"##psuSetValue",currentValue,committedValue,unit,nullptr,color,true)) + { + changed = true; + } + ImGui::PopID(); // Row 2 ImGui::TableNextRow(); @@ -654,8 +954,121 @@ void StreamBrowserDialog::renderPsuRows( ImGui::PopID(); ImGui::TableSetColumnIndex(2); ImGui::PushID(isVoltage ? "mV" : "mC"); - clicked |= ImGui::TextLink(measuredValue); - hovered |= ImGui::IsItemHovered(); + + renderNumericValue(measuredValue,clicked,hovered,color,true,0,false); + + ImGui::PopID(); + return changed; +} + +/** + @brief Render DMM channel properties + @param dmm the DMM to render channel properties for + @param dmmchan the DMM channel to render properties for + @param clicked output param for clicked state + @param hovered output param for hovered state + */ +void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered) +{ + size_t streamIndex = isMain ? 0 : 1; + Unit unit = dmmchan->GetYAxisUnits(streamIndex); + float mainValue = dmmchan->GetScalarValue(streamIndex); + string valueText = unit.PrettyPrint(mainValue,dmm->GetMeterDigits()); + ImVec4 color = ImGui::ColorConvertU32ToFloat4(ColorFromString(dmmchan->m_displaycolor)); + string streamName = isMain ? "Main" : "Secondary"; + + ImGui::PushID(streamName.c_str()); + + // Get available operating and current modes + auto modemask = isMain ? dmm->GetMeasurementTypes() : dmm->GetSecondaryMeasurementTypes(); + auto curMode = isMain ? dmm->GetMeterMode() : dmm->GetSecondaryMeterMode(); + + // Stream name + bool open = ImGui::TreeNodeEx(streamName.c_str(), (curMode > 0) ? ImGuiTreeNodeFlags_DefaultOpen : 0); + + // Mode combo + startBadgeLine(); + ImGui::PushID(streamName.c_str()); + vector modeNames; + vector modes; + if(!isMain) + { + // Add None for secondary measurement to be able to disable it + modeNames.push_back("None"); + modes.push_back(Multimeter::MeasurementTypes::NONE); + } + int modeSelector = 0; + for(unsigned int i=0; i<32; i++) + { + auto mode = static_cast(1 << i); + if(modemask & mode) + { + modes.push_back(mode); + modeNames.push_back(dmm->ModeToText(mode)); + if(curMode == mode) + modeSelector = modes.size() - 1; + } + } + + // Padding to give space for ChannelProperties dialog button + auto& prefs = m_session.GetPreferences(); + int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); + + if(renderCombo("##mode", true, color, modeSelector, modeNames,true,3,true,padding)) + { + curMode = modes[modeSelector]; + if(isMain) + dmm->SetMeterMode(curMode); + else + { + dmm->SetSecondaryMeterMode(curMode); + // Open or close tree node according the secondary measure mode + ImGuiContext& g = *GImGui; + ImGui::TreeNodeSetOpen(g.LastItemData.ID,(curMode > 0)); + } + } + ImGui::PopID(); + + StreamDescriptor s(dmmchan, streamIndex); + if(ImGui::BeginDragDropSource()) + { + if(s.GetType() == Stream::STREAM_TYPE_ANALOG_SCALAR) + ImGui::SetDragDropPayload("Scalar", &s, sizeof(s)); + else + ImGui::SetDragDropPayload("Stream", &s, sizeof(s)); + + ImGui::TextUnformatted(s.GetName().c_str()); + ImGui::EndDragDropSource(); + } + else + DoItemHelp(); + + if(open) + ImGui::TreePop(); + + if(open) + { + renderNumericValue(valueText,clicked,hovered,color,true,ImGui::GetFontSize()*2,false); + + if(isMain) + { + auto dmmState = m_session.GetDmmState(dmm); + if(dmmState) + { + ImGui::PushID("autorange"); + // For main, also show the autorange combo + startBadgeLine(); + bool autorange = dmmState->m_autoRange.load(); + if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3,padding)) + { + dmm->SetMeterAutoRange(autorange); + dmmState->m_needsRangeUpdate = true; + } + ImGui::PopID(); + } + } + } + ImGui::PopID(); } @@ -669,6 +1082,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr { Unit volts(Unit::UNIT_VOLTS); Unit hz(Unit::UNIT_HZ); + Unit percent(Unit::UNIT_PERCENT); size_t channelIndex = awgchan->GetIndex(); auto awgState = m_session.GetFunctionGeneratorState(awg); @@ -696,39 +1110,31 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_committedFrequency[channelIndex] = freq; awgState->m_strFrequency[channelIndex] = hz.PrettyPrint(freq); } - - auto& prefs = m_session.GetPreferences(); - - //Impedance - ImGui::SetNextItemWidth(dwidth); - /* - if(renderCombo( - "Sample Rate", - false, - ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), - m_timebaseConfig[scope]->m_rate, m_timebaseConfig[scope]->m_rateNames)) + float dutyCycle = awgState->m_channelDutyCycle[channelIndex]; + if(dutyCycle != awgState->m_committedDutyCycle[channelIndex]) { - scope->SetSampleRate(m_timebaseConfig[scope]->m_rates[m_timebaseConfig[scope]->m_rate]); - refresh = true; + awgState->m_committedDutyCycle[channelIndex] = dutyCycle; + awgState->m_strDutyCycle[channelIndex] = percent.PrettyPrint(dutyCycle); } - */ - //shape = awgState->m_channelShape[channelIndex]; + auto& prefs = m_session.GetPreferences(); // Row 1 ImGui::Text("Waveform:"); startBadgeLine(); // Needed for shape combo + // Padding to give space for ChannelProperties dialog button + int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); // Shape combo // Get current shape and shape index FunctionGenerator::WaveShape shape = awgState->m_channelShape[channelIndex]; int shapeIndex = awgState->m_channelShapeIndexes[channelIndex][shape]; if(renderCombo( - "#waveform", + "##waveform", true, ImGui::ColorConvertU32ToFloat4(ColorFromString(awgchan->m_displaycolor)), shapeIndex, awgState->m_channelShapeNames[channelIndex], true, - 3)) + 3,true,padding)) { shape = awgState->m_channelShapes[channelIndex][shapeIndex]; awg->SetFunctionChannelShape(channelIndex, shape); @@ -738,14 +1144,16 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_needsUpdate[channelIndex] = true; } + // Store current Y position for shape preview + float shapePreviewY = ImGui::GetCursorPosY(); + // Row 2 // Frequency label - ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithImplicitApply( + if(renderEditableProperty(dwidth, "Frequency", awgState->m_strFrequency[channelIndex], awgState->m_committedFrequency[channelIndex], - hz)) + hz/*,"Frequency of the generated waveform"*/)) { awg->SetFunctionChannelFrequency(channelIndex, awgState->m_committedFrequency[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; @@ -759,58 +1167,67 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr ImGui::EndDragDropSource(); } else - */ DoItemHelp(); - HelpMarker("Frequency of the generated waveform"); + */ + + //Row 2 + //Duty cycle + if(renderEditableProperty(dwidth, + "Duty cycle", + awgState->m_strDutyCycle[channelIndex], + awgState->m_committedDutyCycle[channelIndex], + percent/*,"Duty cycle of the generated waveform"*/)) + { + awg->SetFunctionChannelDutyCycle(channelIndex, awgState->m_committedDutyCycle[channelIndex]); + awgState->m_needsUpdate[channelIndex] = true; + } - /* // Shape preview startBadgeLine(); auto height = ImGui::GetFontSize() * 2; auto width = height * 2; - if ((m_badgeXCur - width) >= m_badgeXMin) + auto totalWidth = width + padding; + if ((m_badgeXCur - totalWidth) >= m_badgeXMin) { // ok, we have enough space draw preview - m_badgeXCur -= width; + m_badgeXCur -= totalWidth; + // save current y position to restore it after drawing the preview + float currentY = ImGui::GetCursorPosY(); + // Continue layout on current line (row 3) ImGui::SameLine(m_badgeXCur); + // But use y position of row 2 + ImGui::SetCursorPosY(shapePreviewY); ImGui::Image( m_parent->GetTextureManager()->GetTexture(m_parent->GetIconForWaveformShape(shape)), ImVec2(width,height)); - // Go back one line since preview spans on two text lines - ImGuiWindow *window = ImGui::GetCurrentWindowRead(); - window->DC.CursorPos.y -= ImGui::GetFontSize(); + // Now that we're done with shape preview, restore y position of row 3 + ImGui::SetCursorPosY(currentY); } - */ // Row 3 - ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithExplicitApply( + if(renderEditablePropertyWithExplicitApply(dwidth, "Amplitude", awgState->m_strAmplitude[channelIndex], awgState->m_committedAmplitude[channelIndex], - volts)) + volts,"Peak-to-peak amplitude of the generated waveform")) { awg->SetFunctionChannelAmplitude(channelIndex, awgState->m_committedAmplitude[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } - HelpMarker("Peak-to-peak amplitude of the generated waveform"); //Row 4 //Offset - ImGui::SetNextItemWidth(dwidth); - if(UnitInputWithExplicitApply( + if(renderEditablePropertyWithExplicitApply(dwidth, "Offset", awgState->m_strOffset[channelIndex], awgState->m_committedOffset[channelIndex], - volts)) + volts,"DC offset for the waveform above (positive) or below (negative) ground")) { awg->SetFunctionChannelOffset(channelIndex, awgState->m_committedOffset[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; } - HelpMarker("DC offset for the waveform above (positive) or below (negative) ground"); - - //TODO: Duty cycle + //Row 5 //Impedance ImGui::SetNextItemWidth(dwidth); FunctionGenerator::OutputImpedance impedance = awgState->m_channelOutputImpedance[channelIndex]; @@ -918,16 +1335,18 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument bool result; if(allOn || someOn) { - result = renderToggle( + 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")), true); + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_partial_badge_color")), result); } else { - result = renderOnOffToggle("###psuon", true, false); + result = false; + renderOnOffToggle("###psuon", true, result); } if(result != allOn) { @@ -945,11 +1364,15 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument if(instIsOpen) { + vector digitalBanks; + vector analogChannels; + vector otherChannels; size_t lastEnabledChannelIndex = 0; if (scope) { if(ImGui::TreeNodeEx("Timebase", ImGuiTreeNodeFlags_DefaultOpen)) { + BeginBlock("timebase"); if(scope->HasTimebaseControls()) DoTimebaseSettings(scope); if(scope->HasFrequencyControls()) @@ -957,20 +1380,62 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument auto spec = dynamic_pointer_cast(scope); if(spec) DoSpectrometerSettings(spec); + EndBlock(); ImGui::TreePop(); } + digitalBanks = scope->GetDigitalBanks(); + for(size_t i = 0; iIsChannelEnabled(i)) lastEnabledChannelIndex = i; + auto scopechan = scope->GetChannel(i); + auto streamType = scopechan->GetType(0); + if(streamType != Stream::STREAM_TYPE_DIGITAL) + { + if(streamType == Stream::STREAM_TYPE_ANALOG) + analogChannels.push_back(i); + else + otherChannels.push_back(i); + } } } - for(size_t i=0; i 0) + { // If digital banks are avaialble, gather digital channels in banks + for(size_t i : analogChannels) + { // Iterate on analog channel first + renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); + } + 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)); + } + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + bankNumber++; + } + for(size_t i : otherChannels) + { // Finally iterate on other channels + renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); + } + } + else + { // Display all channels if no digital bank is available + for(size_t i=0; i scope) Unit hz(Unit::UNIT_HZ); // Resolution Bandwidh - ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Rbw", p->m_rbwText, p->m_rbw, hz)) + if(renderEditableProperty(width,"Rbw", p->m_rbwText, p->m_rbw, hz, "Resolution Bandwidth")) { scope->SetResolutionBandwidth(p->m_rbw); // Update with values from the device p->m_rbw = scope->GetResolutionBandwidth(); - p->m_rbwText = hz.PrettyPrint(p->m_rbw); + p->m_rbwText = hz.PrettyPrintInt64(p->m_rbw); } - HelpMarker("Resolution Bandwidth"); //Frequency bool changed = false; - ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Start", p->m_startText, p->m_start, hz)) + if(renderEditableProperty(width,"Start", p->m_startText, p->m_start, hz, "Start of the frequency sweep")) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1038,26 +1500,20 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) scope->SetSpan(span); changed = true; } - HelpMarker("Start of the frequency sweep"); - ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Center", p->m_centerText, p->m_center, hz)) + if(renderEditableProperty(width,"Center", p->m_centerText, p->m_center, hz, "Midpoint of the frequency sweep")) { scope->SetCenterFrequency(0, p->m_center); changed = true; } - HelpMarker("Midpoint of the frequency sweep"); - ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("Span", p->m_spanText, p->m_span, hz)) + if(renderEditableProperty(width,"Span", p->m_spanText, p->m_span, hz, "Width of the frequency sweep")) { scope->SetSpan(p->m_span); changed = true; } - HelpMarker("Width of the frequency sweep"); - ImGui::SetNextItemWidth(width); - if(UnitInputWithImplicitApply("End", p->m_endText, p->m_end, hz)) + if(renderEditableProperty(width,"End", p->m_endText, p->m_end, hz, "End of the frequency sweep")) { double mid = (p->m_start + p->m_end) / 2; double span = (p->m_end - p->m_start); @@ -1065,7 +1521,6 @@ void StreamBrowserDialog::DoFrequencySettings(shared_ptr scope) scope->SetSpan(span); changed = true; } - HelpMarker("End of the frequency sweep"); //Update everything if one setting is changed if(changed) @@ -1089,12 +1544,10 @@ void StreamBrowserDialog::DoSpectrometerSettings(shared_ptr sp auto config = m_timebaseConfig[scope]; auto width = ImGui::GetFontSize() * 5; - ImGui::SetNextItemWidth(width); Unit fs(Unit::UNIT_FS); - if(UnitInputWithImplicitApply("Integration time", config->m_integrationText, config->m_integrationTime, fs)) + if(renderEditableProperty(width, "Integration time", config->m_integrationText, config->m_integrationTime, fs, "Spectrometer integration / exposure time")) spec->SetIntegrationTime(config->m_integrationTime); - HelpMarker("Spectrometer integration / exposure time"); } /** @@ -1114,7 +1567,7 @@ void StreamBrowserDialog::DoTimebaseSettings(shared_ptr scope) ImGui::SetNextItemWidth(width); bool disabled = !scope->CanInterleave(); ImGui::BeginDisabled(disabled); - if(renderOnOffToggleEXT("Interleaving", false, config->m_interleaving)) + if(renderOnOffToggle("Interleaving", false, config->m_interleaving)) { scope->SetInterleaving(config->m_interleaving); refresh = true; @@ -1253,11 +1706,13 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto psu = std::dynamic_pointer_cast(instrument); auto scope = std::dynamic_pointer_cast(instrument); auto awg = std::dynamic_pointer_cast(instrument); + auto dmm = std::dynamic_pointer_cast(instrument); bool singleStream = channel->GetStreamCount() == 1; auto scopechan = dynamic_cast(channel); auto psuchan = dynamic_cast(channel); auto awgchan = dynamic_cast(channel); + auto dmmchan = dynamic_cast(channel); bool renderProps = false; if (scopechan) { @@ -1276,8 +1731,8 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s int flags = 0; if(!hasChildren) flags |= ImGuiTreeNodeFlags_Leaf; - else - flags |= ImGuiTreeNodeFlags_OpenOnArrow; + + flags |= ImGuiTreeNodeFlags_OpenOnArrow; bool open = ImGui::TreeNodeEx( channel->GetDisplayName().c_str(), @@ -1353,7 +1808,8 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto psustate = m_session.GetPSUState(psu); bool active = psustate->m_channelOn[channelIndex]; - bool result = renderOnOffToggle("###active", true, active); + bool result = active; + renderOnOffToggle("###active", true, result); if(result != active) psu->SetPowerChannelActive(channelIndex,result); } @@ -1363,7 +1819,8 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto awgstate = m_session.GetFunctionGeneratorState(awg); bool active = awgstate->m_channelActive[channelIndex]; - bool result = renderOnOffToggle("###active", true, active); + bool result = active; + renderOnOffToggle("###active", true, result); if(result != active) { awg->SetFunctionChannelActive(channelIndex,result); @@ -1385,8 +1842,11 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s if(psu) { // For PSU we will have a special handling for the 4 streams associated to a PSU channel - ImGui::BeginChild("psu_params", ImVec2(0, 0), - ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); + if(BeginBlock("psu_params",true,"Open PSU channel properties")) + { + m_parent->ShowInstrumentProperties(psu); + } + auto svoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageSetPoint ()); auto mvoltage_txt = Unit(Unit::UNIT_VOLTS).PrettyPrint(psuchan->GetVoltageMeasured()); auto scurrent_txt = Unit(Unit::UNIT_AMPS).PrettyPrint(psuchan->GetCurrentSetPoint ()); @@ -1395,36 +1855,69 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s bool cc = false; auto psuState = m_session.GetPSUState(psu); if(psuState) + { cc = psuState->m_channelConstantCurrent[channelIndex].load(); - bool clicked = false; - bool hovered = false; + bool clicked = false; + bool hovered = false; - if (ImGui::BeginTable("table1", 3)) - { - // Voltage - renderPsuRows(true,cc,psuchan,svoltage_txt.c_str(),mvoltage_txt.c_str(),clicked,hovered); - // Current - renderPsuRows(false,cc,psuchan,scurrent_txt.c_str(),mcurrent_txt.c_str(),clicked,hovered); - // End table - ImGui::EndTable(); - if (clicked) + if (ImGui::BeginTable("table1", 3)) { - m_parent->ShowInstrumentProperties(psu); + // Voltage + if(renderPsuRows(true,cc,psuchan,psuState->m_setVoltage[channelIndex],psuState->m_committedSetVoltage[channelIndex],mvoltage_txt,clicked,hovered)) + { // Update set voltage + psu->SetPowerVoltage(channelIndex, psuState->m_committedSetVoltage[channelIndex]); + psuState->m_needsUpdate[channelIndex] = true; + } + // Current + if(renderPsuRows(false,cc,psuchan,psuState->m_setCurrent[channelIndex],psuState->m_committedSetCurrent[channelIndex],mcurrent_txt,clicked,hovered)) + { // Update set current + psu->SetPowerCurrent(channelIndex, psuState->m_committedSetCurrent[channelIndex]); + psuState->m_needsUpdate[channelIndex] = true; + } + // End table + ImGui::EndTable(); } - if (hovered) - m_parent->AddStatusHelp("mouse_lmb", "Open channel properties"); } - ImGui::EndChild(); + EndBlock(); } else if(awg && awgchan) { - ImGui::PushID("awgparams"); - renderAwgProperties(awg, awgchan); - ImGui::PopID(); + if(BeginBlock("awgparams",true)) + { + m_parent->ShowInstrumentProperties(awg); + } + renderAwgProperties(awg, awgchan); + EndBlock(); + } + else if(dmm && dmmchan) + { + if(BeginBlock("dmm_params",true,"Open Multimeter properties")) + { + m_parent->ShowInstrumentProperties(dmm); + } + // Always 2 streams for dmm channel => render properties on channel node + bool clicked = false; + bool hovered = false; + // Main measurement + renderDmmProperties(dmm,dmmchan,true,clicked,hovered); + // Secondary measurement + renderDmmProperties(dmm,dmmchan,false,clicked,hovered); + + EndBlock(); } else { + if(!singleStream) + { + if(BeginBlock("stream_params",true,"Open channel properties")) + { + m_parent->ShowChannelProperties(scopechan); + } + auto scopeState = m_session.GetOscillopscopeState(scope); + renderChannelProperties(scope,scopechan,channelIndex,scopeState); + EndBlock(); + } size_t streamCount = channel->GetStreamCount(); for(size_t j=0; j instrument, s ImGui::PopID(); } +/** + @brief Rendering of channel properties + + @param scope the scope + @param scopechan the scope channel + @param channelIndex the index of the channel + @param scopeState the OscilloscopeState + */ +void StreamBrowserDialog::renderChannelProperties(std::shared_ptr scope, OscilloscopeChannel* scopechan, size_t channelIndex, shared_ptr scopeState) +{ + float fontSize = ImGui::GetFontSize(); + float width = 6*fontSize; + + Unit counts(Unit::UNIT_COUNTS); + if(renderEditableProperty(width,"Attenuation",scopeState->m_strAttenuation[channelIndex],scopeState->m_committedAttenuation[channelIndex],counts, + "Attenuation setting for the probe (for example, 10 for a 10:1 probe)")) + { // Update offset + scopechan->SetAttenuation(scopeState->m_committedAttenuation[channelIndex]); + scopeState->m_needsUpdate[channelIndex] = true; + } + //Only show coupling box if the instrument has configurable coupling + if( (scopeState->m_couplings[channelIndex].size() > 1) && (scopeState->m_probeName[channelIndex] == "") ) + { + ImGui::SetNextItemWidth(width); + if(renderCombo( + "Coupling", + false, + ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), + scopeState->m_channelCoupling[channelIndex], + scopeState->m_couplingNames[channelIndex], + false, + 0, + false)) + { + scope->SetChannelCoupling(channelIndex,scopeState->m_couplings[channelIndex][scopeState->m_channelCoupling[channelIndex]]); + scopeState->m_needsUpdate[channelIndex] = true; + } + HelpMarker("Coupling configuration for the input"); + } + //Bandwidth limiters (only show if more than one value available) + if(scopeState->m_bandwidthLimitNames[channelIndex].size() > 1) + { + ImGui::SetNextItemWidth(width); + if(renderCombo( + "Bandwidth", + false, + ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), + scopeState->m_channelBandwidthLimit[channelIndex], + scopeState->m_bandwidthLimitNames[channelIndex], + false, + 0, + false)) + { + scopechan->SetBandwidthLimit(scopeState->m_bandwidthLimits[channelIndex][scopeState->m_channelBandwidthLimit[channelIndex]]); + scopeState->m_needsUpdate[channelIndex] = true; + } + HelpMarker("Hardware bandwidth limiter setting"); + } + //If the probe supports inversion, show a checkbox for it + if(scope->CanInvert(channelIndex)) + { + ImGui::SetNextItemWidth(width); + if(renderOnOffToggle("Invert",false,scopeState->m_channelInverted[channelIndex])) + { + scope->Invert(channelIndex,scopeState->m_channelInverted[channelIndex]); + scopeState->m_needsUpdate[channelIndex] = true; + } + HelpMarker( + "When ON, input value is multiplied by -1.\n" + "For a differential probe, this is equivalent to swapping the positive and negative inputs." + ); + } + +} + /** @brief Rendering of a stream node @@ -1484,6 +2052,7 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In // Channel/stream properties block if(renderProps && scopechan) { + // If no properties are available for this stream, only show a "Properties" link if it is the last stream of the channel/filter bool hasProps = false; switch (type) { @@ -1501,36 +2070,58 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } if(hasProps) { - ImGui::BeginChild("stream_params", ImVec2(0, 0), - ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); + if(BeginBlock("stream_params",true,"Open channel properties")) + { + m_parent->ShowChannelProperties(scopechan); + } Unit unit = channel->GetYAxisUnits(streamIndex); - bool clicked; - bool hovered; + size_t channelIndex = scopechan->GetIndex(); + auto scopeState = m_session.GetOscillopscopeState(scope); + switch (type) { case Stream::STREAM_TYPE_ANALOG: { - auto offset_txt = unit.PrettyPrint(scopechan->GetOffset(streamIndex)); - auto range_txt = unit.PrettyPrint(scopechan->GetVoltageRange(streamIndex)); - renderInfoLink("Offset", offset_txt.c_str(), clicked, hovered); - renderInfoLink("Vertical range", range_txt.c_str(), clicked, hovered); + if(!renderName) + { // No streams => display channel properties here + renderChannelProperties(scope,scopechan,channelIndex,scopeState); + } + if(renderEditablePropertyWithExplicitApply(0,"Offset",scopeState->m_strOffset[channelIndex][streamIndex],scopeState->m_committedOffset[channelIndex][streamIndex],unit)) + { // Update offset + scopechan->SetOffset(scopeState->m_committedOffset[channelIndex][streamIndex],streamIndex); + scopeState->m_needsUpdate[channelIndex] = true; + } + if(renderEditablePropertyWithExplicitApply(0,"Vertical range",scopeState->m_strRange[channelIndex][streamIndex],scopeState->m_committedRange[channelIndex][streamIndex],unit)) + { // Update offset + scopechan->SetVoltageRange(scopeState->m_committedRange[channelIndex][streamIndex],streamIndex); + scopeState->m_needsUpdate[channelIndex] = true; + } } break; case Stream::STREAM_TYPE_DIGITAL: if(scope) { - auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); - renderInfoLink("Threshold", threshold_txt.c_str(), clicked, hovered); + if(scope->IsDigitalThresholdConfigurable()) + { + if(renderEditablePropertyWithExplicitApply(0,"Threshold",scopeState->m_strDigitalThreshold[channelIndex],scopeState->m_committedDigitalThreshold[channelIndex],unit)) + { // Update offset + scopechan->SetDigitalThreshold(scopeState->m_committedDigitalThreshold[channelIndex]); + scopeState->m_needsUpdate[channelIndex] = true; + } + } + else + { + auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); + renderReadOnlyProperty(0,"Threshold", threshold_txt); + } break; } //fall through default: - { - } break; } - ImGui::EndChild(); + EndBlock(); } } ImGui::PopID(); @@ -1651,3 +2242,59 @@ void StreamBrowserDialog::DoItemHelp() if(ImGui::IsItemHovered()) m_parent->AddStatusHelp("mouse_lmb_drag", "Add to filter graph or plot"); } + +bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton, const char* tooltip) +{ + bool clicked = false; + auto& prefs = m_session.GetPreferences(); + ImGuiWindowFlags flags = ImGuiChildFlags_AutoResizeY; + bool withBorders = false; + if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); + flags |= ImGuiChildFlags_Borders; + withBorders = true; + } + ImGui::BeginChild(label, ImVec2(0, 0), flags); + if(withButton) + { // Create a "+" button on the top right corner of the box + ImVec2 oldPos = ImGui::GetCursorPos(); + float padding = ImGui::GetStyle().FramePadding.x; + float shift = withBorders ? padding*1.5 : 0; + float xsz = ImGui::GetFontSize(); + ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x - xsz + shift); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()-shift); + // Use the same color as border for the button + ImVec4 border = ImGui::GetStyle().Colors[ImGuiCol_Border]; + ImVec4 hover = ImVec4(border.x * 1.2f, border.y * 1.2f, border.z * 1.2f, border.w); + ImVec4 active = ImVec4(border.x * 0.9f, border.y * 0.9f, border.z * 0.9f, border.w); + ImGui::PushStyleColor(ImGuiCol_Button, border); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, active); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.6f, 0.5f)); + clicked = ImGui::Button(PLUS_CHAR,ImVec2(xsz, xsz)); + if(ImGui::IsItemHovered()) + { // Hand cursor + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if(tooltip) + { + m_parent->AddStatusHelp("mouse_lmb", tooltip); + } + } + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(3); + ImGui::SetCursorPos(oldPos); + } + return clicked; +} + +void StreamBrowserDialog::EndBlock() +{ + ImGui::EndChild(); + auto& prefs = m_session.GetPreferences(); + if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) + { + ImGui::PopStyleVar(); + } +} \ No newline at end of file diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index cb71d44a..6d232eea 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -121,8 +121,11 @@ class StreamBrowserDialog : public Dialog void DoItemHelp(); + // Block handling + bool BeginBlock(const char *label, bool withButton = false, const char* tooltip = nullptr); + void EndBlock(); + // Rendeding of StreamBrowserDialog elements - void renderInfoLink(const char *label, const char *linktext, bool &clicked, bool &hovered); void startBadgeLine(); void renderBadge(ImVec4 color, ... /* labels, ending in NULL */); void renderInstrumentBadge(std::shared_ptr inst, bool latched, InstrumentBadge badge); @@ -134,7 +137,8 @@ class StreamBrowserDialog : public Dialog const std::vector& values, bool useColorForText = false, uint8_t cropTextTo = 0, - bool hideArrow = true); + bool hideArrow = true, + float paddingRight = 0); bool renderCombo( const char* label, bool alignRight, @@ -145,17 +149,22 @@ class StreamBrowserDialog : public Dialog const char* label, bool alignRight, ImVec4 color, - bool curValue); - bool renderToggleEXT( - const char* label, - bool alignRight, - ImVec4 color, - bool& curValue); - bool renderOnOffToggle(const char* label, bool alignRight, bool curValue); - bool renderOnOffToggleEXT(const char* label, bool alignRight, bool& curValue); + bool& curValue, + const char* valueOff = "OFF", + const char* valueOn = "ON", + uint8_t cropTextTo = 0, + float paddingRight = 0); + bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0, float paddingRight = 0); + void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); + void renderReadOnlyProperty(float width, const std::string& label, const std::string& value, const char* tooltip = nullptr); + template + bool renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, ImVec4 color = ImVec4(1, 1, 1, 1), 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, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); - void renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan,const char *setValue, const char *measuredValue, bool &clicked, bool &hovered); + bool renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan, std::string& currentValue, float& committedValue, std::string& measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); + void renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered); // Rendering of an instrument node void renderInstrumentNode(std::shared_ptr instrument); @@ -167,6 +176,8 @@ class StreamBrowserDialog : public Dialog // Rendering of a channel node void renderChannelNode(std::shared_ptr instrument, size_t channelIndex, bool isLast); + void renderChannelProperties(std::shared_ptr scope, OscilloscopeChannel* scopechan, size_t channelIndex, std::shared_ptr scopeState); + // Rendering of a stream node void renderStreamNode(std::shared_ptr instrument, InstrumentChannel* channel, size_t streamIndex, bool renderName, bool renderProps); @@ -180,6 +191,11 @@ class StreamBrowserDialog : public Dialog float m_badgeXMin; // left edge over which we must not overrun float m_badgeXCur; // right edge to render the next badge against + ///@brief Id of the item currently beeing edited + ImGuiID m_editedItemId = 0; + ///@brief Id of the last edited item + ImGuiID m_lastEditedItemId = 0; + std::map, bool> m_instrumentDownloadIsSlow; ///@brief Store the last state of an intrument badge (used for badge state latching) std::map, std::pair> m_instrumentLastBadge; From 2633bf1df1702ae8e44074ec30a40f8161dc4c4d Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Sat, 10 Jan 2026 02:03:16 +0100 Subject: [PATCH 36/49] Fixed state handling in ChannelPropertyDialog --- src/ngscopeclient/ChannelPropertiesDialog.cpp | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/ngscopeclient/ChannelPropertiesDialog.cpp b/src/ngscopeclient/ChannelPropertiesDialog.cpp index 2af2eb6a..b6f09b97 100644 --- a/src/ngscopeclient/ChannelPropertiesDialog.cpp +++ b/src/ngscopeclient/ChannelPropertiesDialog.cpp @@ -52,16 +52,7 @@ ChannelPropertiesDialog::ChannelPropertiesDialog(InstrumentChannel* chan, MainWi std::shared_ptr scopeSharedPointer(chan->GetInstrument(), [](Instrument*){}); shared_ptr sharedScope = dynamic_pointer_cast(scopeSharedPointer); if(sharedScope) - { m_state = session.GetOscillopscopeState(sharedScope); - if(!m_state) - LogError("Could not get OscilloscopeState for scope %s.\n",chan->GetInstrument()->GetName().c_str()); - - } - else - { - LogError("Could not get cast Instrument %s to Oscilloscope.\n",chan->GetInstrument()->GetName().c_str()); - } auto ochan = dynamic_cast(chan); if(!ochan) @@ -359,7 +350,7 @@ bool ChannelPropertiesDialog::DoRender() m_threshold = yunit.PrettyPrint(m_committedThreshold); // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } HelpMarker("Switching threshold for the digital input buffer"); } @@ -376,7 +367,7 @@ bool ChannelPropertiesDialog::DoRender() m_hysteresis = yunit.PrettyPrint(m_committedHysteresis); // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } HelpMarker("Hysteresis for the digital input buffer"); } @@ -423,7 +414,7 @@ bool ChannelPropertiesDialog::DoRender() } // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } if(m_probe != "") ImGui::EndDisabled(); @@ -438,7 +429,7 @@ bool ChannelPropertiesDialog::DoRender() ochan->SetCoupling(m_couplings[m_coupling]); // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } HelpMarker("Coupling configuration for the input"); } @@ -452,7 +443,7 @@ bool ChannelPropertiesDialog::DoRender() ochan->SetBandwidthLimit(m_bwlValues[m_bwl]); // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } HelpMarker("Hardware bandwidth limiter setting"); } @@ -505,7 +496,7 @@ bool ChannelPropertiesDialog::DoRender() ochan->Invert(m_inverted); // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } HelpMarker( @@ -603,7 +594,7 @@ bool ChannelPropertiesDialog::DoRender() ochan->SetOffset(m_committedOffset[i], i); // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } //Same for range @@ -620,7 +611,7 @@ bool ChannelPropertiesDialog::DoRender() ochan->SetVoltageRange(m_committedRange[i], i); // Tell intrument thread that the scope state has to be updated - m_state->m_needsUpdate[index] = true; + if(m_state) m_state->m_needsUpdate[index] = true; } ImGui::PopID(); From 1fd1a19c411a30958c5b0831ed583ecc6cf70e4c Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Sat, 10 Jan 2026 11:00:03 +0100 Subject: [PATCH 37/49] Fixed node rendering for fiters. --- src/ngscopeclient/StreamBrowserDialog.cpp | 98 ++++++++++++----------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 39051cee..c2faacaf 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1910,13 +1910,16 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s { if(!singleStream) { - if(BeginBlock("stream_params",true,"Open channel properties")) - { - m_parent->ShowChannelProperties(scopechan); - } auto scopeState = m_session.GetOscillopscopeState(scope); - renderChannelProperties(scope,scopechan,channelIndex,scopeState); + if(scopeState) + { + if(BeginBlock("stream_params",true,"Open channel properties")) + { + m_parent->ShowChannelProperties(scopechan); + } + renderChannelProperties(scope,scopechan,channelIndex,scopeState); EndBlock(); + } } size_t streamCount = channel->GetStreamCount(); for(size_t j=0; j instrument, In } if(hasProps) { - if(BeginBlock("stream_params",true,"Open channel properties")) - { - m_parent->ShowChannelProperties(scopechan); - } - - Unit unit = channel->GetYAxisUnits(streamIndex); - size_t channelIndex = scopechan->GetIndex(); auto scopeState = m_session.GetOscillopscopeState(scope); + if(scopeState) + { // For now, only show properties for scope channel / streams + if(BeginBlock("stream_params",true,"Open channel properties")) + { + m_parent->ShowChannelProperties(scopechan); + } - switch (type) - { - case Stream::STREAM_TYPE_ANALOG: - { - if(!renderName) - { // No streams => display channel properties here - renderChannelProperties(scope,scopechan,channelIndex,scopeState); - } - if(renderEditablePropertyWithExplicitApply(0,"Offset",scopeState->m_strOffset[channelIndex][streamIndex],scopeState->m_committedOffset[channelIndex][streamIndex],unit)) - { // Update offset - scopechan->SetOffset(scopeState->m_committedOffset[channelIndex][streamIndex],streamIndex); - scopeState->m_needsUpdate[channelIndex] = true; - } - if(renderEditablePropertyWithExplicitApply(0,"Vertical range",scopeState->m_strRange[channelIndex][streamIndex],scopeState->m_committedRange[channelIndex][streamIndex],unit)) - { // Update offset - scopechan->SetVoltageRange(scopeState->m_committedRange[channelIndex][streamIndex],streamIndex); - scopeState->m_needsUpdate[channelIndex] = true; - } - } - break; - case Stream::STREAM_TYPE_DIGITAL: - if(scope) - { - if(scope->IsDigitalThresholdConfigurable()) + Unit unit = channel->GetYAxisUnits(streamIndex); + size_t channelIndex = scopechan->GetIndex(); + + switch (type) + { + case Stream::STREAM_TYPE_ANALOG: { - if(renderEditablePropertyWithExplicitApply(0,"Threshold",scopeState->m_strDigitalThreshold[channelIndex],scopeState->m_committedDigitalThreshold[channelIndex],unit)) + if(!renderName) + { // No streams => display channel properties here + renderChannelProperties(scope,scopechan,channelIndex,scopeState); + } + if(renderEditablePropertyWithExplicitApply(0,"Offset",scopeState->m_strOffset[channelIndex][streamIndex],scopeState->m_committedOffset[channelIndex][streamIndex],unit)) + { // Update offset + scopechan->SetOffset(scopeState->m_committedOffset[channelIndex][streamIndex],streamIndex); + scopeState->m_needsUpdate[channelIndex] = true; + } + if(renderEditablePropertyWithExplicitApply(0,"Vertical range",scopeState->m_strRange[channelIndex][streamIndex],scopeState->m_committedRange[channelIndex][streamIndex],unit)) { // Update offset - scopechan->SetDigitalThreshold(scopeState->m_committedDigitalThreshold[channelIndex]); + scopechan->SetVoltageRange(scopeState->m_committedRange[channelIndex][streamIndex],streamIndex); scopeState->m_needsUpdate[channelIndex] = true; } } - else + break; + case Stream::STREAM_TYPE_DIGITAL: + if(scope) { - auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); - renderReadOnlyProperty(0,"Threshold", threshold_txt); + if(scope->IsDigitalThresholdConfigurable()) + { + if(renderEditablePropertyWithExplicitApply(0,"Threshold",scopeState->m_strDigitalThreshold[channelIndex],scopeState->m_committedDigitalThreshold[channelIndex],unit)) + { // Update offset + scopechan->SetDigitalThreshold(scopeState->m_committedDigitalThreshold[channelIndex]); + scopeState->m_needsUpdate[channelIndex] = true; + } + } + else + { + auto threshold_txt = unit.PrettyPrint(scope->GetDigitalThreshold(scopechan->GetIndex())); + renderReadOnlyProperty(0,"Threshold", threshold_txt); + } + break; } + //fall through + default: break; - } - //fall through - default: - break; + } + EndBlock(); } - EndBlock(); } } ImGui::PopID(); From 00540b7e7836adb458030896e381948828273bca Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Sun, 11 Jan 2026 19:19:55 +0100 Subject: [PATCH 38/49] Fixed typos. Updated copyright notice. Fixed shared pointer creation in ChannelPropertyDialog. --- src/ngscopeclient/ChannelPropertiesDialog.cpp | 7 +++---- src/ngscopeclient/ChannelPropertiesDialog.h | 2 +- src/ngscopeclient/Dialog.h | 2 +- src/ngscopeclient/FunctionGeneratorState.h | 8 ++++---- src/ngscopeclient/MultimeterDialog.cpp | 2 +- src/ngscopeclient/MultimeterDialog.h | 2 +- src/ngscopeclient/OscilloscopeState.h | 8 ++++---- src/ngscopeclient/PowerSupplyDialog.cpp | 2 +- src/ngscopeclient/PowerSupplyState.h | 8 ++++---- src/ngscopeclient/PreferenceSchema.cpp | 2 +- src/ngscopeclient/Session.cpp | 2 +- src/ngscopeclient/Session.h | 4 ++-- src/ngscopeclient/StreamBrowserDialog.cpp | 4 ++-- src/ngscopeclient/ngscopeclient.h | 2 +- 14 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/ngscopeclient/ChannelPropertiesDialog.cpp b/src/ngscopeclient/ChannelPropertiesDialog.cpp index b6f09b97..92e692a5 100644 --- a/src/ngscopeclient/ChannelPropertiesDialog.cpp +++ b/src/ngscopeclient/ChannelPropertiesDialog.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 * @@ -48,11 +48,10 @@ ChannelPropertiesDialog::ChannelPropertiesDialog(InstrumentChannel* chan, MainWi { // Get oscilloscope state, for that we need to make a shared_ptr out of the base pointer returned by chan->GetInstrument() Session& session = m_parent->GetSession(); - // We pass an empty Deleter since this pointer's lifecycle is handled elsewhere - std::shared_ptr scopeSharedPointer(chan->GetInstrument(), [](Instrument*){}); + std::shared_ptr scopeSharedPointer = chan->GetInstrument()->shared_from_this(); shared_ptr sharedScope = dynamic_pointer_cast(scopeSharedPointer); if(sharedScope) - m_state = session.GetOscillopscopeState(sharedScope); + m_state = session.GetOscilloscopeState(sharedScope); auto ochan = dynamic_cast(chan); if(!ochan) diff --git a/src/ngscopeclient/ChannelPropertiesDialog.h b/src/ngscopeclient/ChannelPropertiesDialog.h index 6be9077c..048e84d3 100644 --- a/src/ngscopeclient/ChannelPropertiesDialog.h +++ b/src/ngscopeclient/ChannelPropertiesDialog.h @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2025 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/Dialog.h b/src/ngscopeclient/Dialog.h index 88d19d4e..42a569a7 100644 --- a/src/ngscopeclient/Dialog.h +++ b/src/ngscopeclient/Dialog.h @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 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/FunctionGeneratorState.h b/src/ngscopeclient/FunctionGeneratorState.h index 46c42fad..1190cd33 100644 --- a/src/ngscopeclient/FunctionGeneratorState.h +++ b/src/ngscopeclient/FunctionGeneratorState.h @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 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 * @@ -45,7 +45,7 @@ class FunctionGeneratorState FunctionGeneratorState(std::shared_ptr generator) { size_t n = generator->GetChannelCount(); - m_channelNumner = n; + m_channelNumber = n; m_channelActive = std::make_unique[] >(n); m_channelAmplitude = std::make_unique[] >(n); m_channelOffset= std::make_unique[] >(n); @@ -97,7 +97,7 @@ class FunctionGeneratorState void FlushConfigCache() { - for(size_t i = 0 ; i < m_channelNumner.load() ; i++) + for(size_t i = 0 ; i < m_channelNumber.load() ; i++) m_needsUpdate[i] = true; } @@ -114,7 +114,7 @@ class FunctionGeneratorState std::unique_ptr[]> m_needsUpdate; - std::atomic m_channelNumner; + std::atomic m_channelNumber; //UI state for dialogs etc std::unique_ptr m_committedOffset; diff --git a/src/ngscopeclient/MultimeterDialog.cpp b/src/ngscopeclient/MultimeterDialog.cpp index b0a8184c..7370ba0b 100644 --- a/src/ngscopeclient/MultimeterDialog.cpp +++ b/src/ngscopeclient/MultimeterDialog.cpp @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 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/MultimeterDialog.h b/src/ngscopeclient/MultimeterDialog.h index 85a69f46..5452c904 100644 --- a/src/ngscopeclient/MultimeterDialog.h +++ b/src/ngscopeclient/MultimeterDialog.h @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 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/OscilloscopeState.h b/src/ngscopeclient/OscilloscopeState.h index e02ec035..48cdeb06 100644 --- a/src/ngscopeclient/OscilloscopeState.h +++ b/src/ngscopeclient/OscilloscopeState.h @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 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 * @@ -45,7 +45,7 @@ class OscilloscopeState OscilloscopeState(std::shared_ptr scope) { size_t n = scope->GetChannelCount(); - m_channelNumner = n; + m_channelNumber = n; m_channelInverted = std::make_unique(n); m_channelOffset = std::make_unique[] >(n); m_channelRange = std::make_unique[] >(n); @@ -114,7 +114,7 @@ class OscilloscopeState void FlushConfigCache() { - for(size_t i = 0 ; i < m_channelNumner.load() ; i++) + for(size_t i = 0 ; i < m_channelNumber.load() ; i++) m_needsUpdate[i] = true; } @@ -126,7 +126,7 @@ class OscilloscopeState std::unique_ptr[]> m_needsUpdate; - std::atomic m_channelNumner; + std::atomic m_channelNumber; //UI state for dialogs etc std::unique_ptr m_probeName; diff --git a/src/ngscopeclient/PowerSupplyDialog.cpp b/src/ngscopeclient/PowerSupplyDialog.cpp index b4aa7b0c..8b895959 100644 --- a/src/ngscopeclient/PowerSupplyDialog.cpp +++ b/src/ngscopeclient/PowerSupplyDialog.cpp @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2024 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/PowerSupplyState.h b/src/ngscopeclient/PowerSupplyState.h index 733cf1ae..8f2be69e 100644 --- a/src/ngscopeclient/PowerSupplyState.h +++ b/src/ngscopeclient/PowerSupplyState.h @@ -2,7 +2,7 @@ * * * glscopeclient * * * -* Copyright (c) 2012-2022 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 * @@ -45,7 +45,7 @@ class PowerSupplyState PowerSupplyState(size_t n = 0) { m_masterEnable = false; - m_channelNumner = n; + m_channelNumber = n; m_channelVoltage = std::make_unique[] >(n); m_channelCurrent = std::make_unique[] >(n); @@ -85,7 +85,7 @@ class PowerSupplyState void FlushConfigCache() { - for(size_t i = 0 ; i < m_channelNumner.load() ; i++) + for(size_t i = 0 ; i < m_channelNumber.load() ; i++) m_needsUpdate[i] = true; } @@ -112,7 +112,7 @@ class PowerSupplyState std::atomic m_masterEnable; - std::atomic m_channelNumner; + std::atomic m_channelNumber; }; #endif diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index a1782c06..2bce82f5 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -2,7 +2,7 @@ * * * ngscopeclient * * * -* Copyright (c) 2012-2025 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/Session.cpp b/src/ngscopeclient/Session.cpp index ed852e36..2ebe17bf 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.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/Session.h b/src/ngscopeclient/Session.h index 7d5fc60b..33f74ec2 100644 --- a/src/ngscopeclient/Session.h +++ b/src/ngscopeclient/Session.h @@ -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 * @@ -147,7 +147,7 @@ class Session /** @brief Returns a pointer to the state for a function generator */ - std::shared_ptr GetOscillopscopeState(std::shared_ptr scope) + std::shared_ptr GetOscilloscopeState(std::shared_ptr scope) { std::lock_guard lock(m_scopeMutex); return m_oscilloscopesStates[scope]; diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index c2faacaf..c2359d59 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1910,7 +1910,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s { if(!singleStream) { - auto scopeState = m_session.GetOscillopscopeState(scope); + auto scopeState = m_session.GetOscilloscopeState(scope); if(scopeState) { if(BeginBlock("stream_params",true,"Open channel properties")) @@ -2073,7 +2073,7 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } if(hasProps) { - auto scopeState = m_session.GetOscillopscopeState(scope); + auto scopeState = m_session.GetOscilloscopeState(scope); if(scopeState) { // For now, only show properties for scope channel / streams if(BeginBlock("stream_params",true,"Open channel properties")) diff --git a/src/ngscopeclient/ngscopeclient.h b/src/ngscopeclient/ngscopeclient.h index 6bcd9ee6..51338e8f 100644 --- a/src/ngscopeclient/ngscopeclient.h +++ b/src/ngscopeclient/ngscopeclient.h @@ -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 * From 178285d088de2a31026fb35ff9d0d7135893e48a Mon Sep 17 00:00:00 2001 From: Frederic Borry Date: Sun, 11 Jan 2026 22:50:01 +0100 Subject: [PATCH 39/49] Added Rise and Fall time to AWG node in StreamBrowserDialog. Removed FunctionGeneratorDialog (replaced by AWG node in StreamBrowserDialog). --- src/ngscopeclient/CMakeLists.txt | 1 - src/ngscopeclient/FunctionGeneratorState.h | 18 ++++++ src/ngscopeclient/InstrumentThread.cpp | 2 + src/ngscopeclient/MainWindow.cpp | 37 ------------- src/ngscopeclient/MainWindow.h | 1 - src/ngscopeclient/MainWindow_Menus.cpp | 50 ----------------- src/ngscopeclient/Session.cpp | 8 --- src/ngscopeclient/StreamBrowserDialog.cpp | 64 +++++++++++++++++----- 8 files changed, 70 insertions(+), 111 deletions(-) diff --git a/src/ngscopeclient/CMakeLists.txt b/src/ngscopeclient/CMakeLists.txt index 2cdccd07..ce5c0c69 100644 --- a/src/ngscopeclient/CMakeLists.txt +++ b/src/ngscopeclient/CMakeLists.txt @@ -73,7 +73,6 @@ add_executable(ngscopeclient FilterGraphWorkspace.cpp FilterPropertiesDialog.cpp FontManager.cpp - FunctionGeneratorDialog.cpp GuiLogSink.cpp HistoryDialog.cpp HistoryManager.cpp diff --git a/src/ngscopeclient/FunctionGeneratorState.h b/src/ngscopeclient/FunctionGeneratorState.h index 1190cd33..392dbe30 100644 --- a/src/ngscopeclient/FunctionGeneratorState.h +++ b/src/ngscopeclient/FunctionGeneratorState.h @@ -51,6 +51,8 @@ class FunctionGeneratorState m_channelOffset= std::make_unique[] >(n); m_channelFrequency = std::make_unique[] >(n); m_channelDutyCycle = std::make_unique[] >(n); + m_channelRiseTime = std::make_unique[] >(n); + m_channelFallTime = std::make_unique[] >(n); m_channelShape = std::make_unique[] >(n); m_channelOutputImpedance = std::make_unique[] >(n); m_channelShapes = std::make_unique[] >(n); @@ -67,6 +69,10 @@ class FunctionGeneratorState m_committedFrequency = std::make_unique(n); m_strDutyCycle = std::make_unique(n); m_committedDutyCycle = std::make_unique(n); + m_strRiseTime = std::make_unique(n); + m_committedRiseTime = std::make_unique(n); + m_strFallTime = std::make_unique(n); + m_committedFallTime = std::make_unique(n); Unit volts(Unit::UNIT_VOLTS); @@ -77,6 +83,8 @@ class FunctionGeneratorState m_channelOffset[i] = 0; m_channelFrequency[i] = 0; m_channelDutyCycle[i] = 0; + m_channelRiseTime[i] = 0; + m_channelFallTime[i] = 0; m_channelShape[i] = FunctionGenerator::WaveShape::SHAPE_SINE; m_channelOutputImpedance[i] = FunctionGenerator::OutputImpedance::IMPEDANCE_HIGH_Z; // Init shape list and names @@ -92,6 +100,8 @@ class FunctionGeneratorState m_committedOffset[i] = FLT_MIN; m_committedFrequency[i] = FLT_MIN; m_committedDutyCycle[i] = FLT_MIN; + m_committedRiseTime[i] = FLT_MIN; + m_committedFallTime[i] = FLT_MIN; } } @@ -106,6 +116,8 @@ class FunctionGeneratorState std::unique_ptr[]> m_channelOffset; std::unique_ptr[]> m_channelFrequency; std::unique_ptr[]> m_channelDutyCycle; + std::unique_ptr[]> m_channelRiseTime; + std::unique_ptr[]> m_channelFallTime; std::unique_ptr[]> m_channelShape; std::unique_ptr[]> m_channelOutputImpedance; std::unique_ptr[]> m_channelShapes; @@ -128,6 +140,12 @@ class FunctionGeneratorState std::unique_ptr m_committedDutyCycle; std::unique_ptr m_strDutyCycle; + + std::unique_ptr m_committedRiseTime; + std::unique_ptr m_strRiseTime; + + std::unique_ptr m_committedFallTime; + std::unique_ptr m_strFallTime; }; #endif diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index c7d2e26c..51b1d077 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -380,6 +380,8 @@ void InstrumentThread(InstrumentThreadArgs args) awgstate->m_channelOffset[i] = awg->GetFunctionChannelOffset(i); awgstate->m_channelFrequency[i] = awg->GetFunctionChannelFrequency(i); awgstate->m_channelDutyCycle[i] = awg->GetFunctionChannelDutyCycle(i); + awgstate->m_channelRiseTime[i] = awg->GetFunctionChannelRiseTime(i); + awgstate->m_channelFallTime[i] = awg->GetFunctionChannelFallTime(i); awgstate->m_channelShape[i] = awg->GetFunctionChannelShape(i); awgstate->m_channelOutputImpedance[i] = awg->GetFunctionChannelOutputImpedance(i); session->MarkChannelDirty(awgchan); diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index d43e7791..0f6f19a1 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -55,7 +55,6 @@ #include "FileBrowser.h" #include "FilterGraphWorkspace.h" #include "FilterPropertiesDialog.h" -#include "FunctionGeneratorDialog.h" #include "HistoryDialog.h" #include "LoadDialog.h" #include "LogViewerDialog.h" @@ -1129,10 +1128,6 @@ void MainWindow::OnDialogClosed(const std::shared_ptr& dlg) if(psuDlg) m_psuDialogs.erase(psuDlg->GetPSU()); - auto genDlg = dynamic_pointer_cast(dlg); - if(genDlg) - m_generatorDialogs.erase(genDlg->GetGenerator()); - auto rgenDlg = dynamic_pointer_cast(dlg); if(rgenDlg) m_rfgeneratorDialogs.erase(rgenDlg->GetGenerator()); @@ -1511,18 +1506,6 @@ void MainWindow::ShowInstrumentProperties(std::shared_ptr instrument m_session.AddMultimeterDialog(dmm); return; } - // AWG - auto awg = dynamic_pointer_cast(instrument); - if(awg) - { - if(m_generatorDialogs.find(awg) != m_generatorDialogs.end()) - { - LogTrace("Generator properties dialog is already open, no action required\n"); - return; - } - AddDialog(make_shared(awg, m_session.GetFunctionGeneratorState(awg), &m_session)); - return; - } // Bert auto bert = dynamic_pointer_cast(instrument); if(bert) @@ -2777,26 +2760,6 @@ bool MainWindow::LoadDialogs(const YAML::Node& node) } } - auto generators = node["generators"]; - if(generators) - { - for(auto it : generators) - { - auto gen = dynamic_cast(m_session.m_idtable.Lookup(it.second.as())); - - if(gen) - { - auto sgen = dynamic_pointer_cast(gen->shared_from_this()); - AddDialog(make_shared(sgen, m_session.GetFunctionGeneratorState(sgen), &m_session)); - } - else - { - ShowErrorPopup("Invalid function generator", "Function generator dialog references nonexistent instrument"); - continue; - } - } - } - auto psus = node["psus"]; if(psus) { diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index aa5725a7..404b3d24 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -259,7 +259,6 @@ class MainWindow : public VulkanWindow void SetupMenu(); void WindowMenu(); void WindowAnalyzerMenu(); - void WindowGeneratorMenu(); void WindowPSUMenu(); void WindowMultimeterMenu(); void DebugMenu(); diff --git a/src/ngscopeclient/MainWindow_Menus.cpp b/src/ngscopeclient/MainWindow_Menus.cpp index 4ea2199c..f8a42d16 100644 --- a/src/ngscopeclient/MainWindow_Menus.cpp +++ b/src/ngscopeclient/MainWindow_Menus.cpp @@ -46,7 +46,6 @@ #include "BERTDialog.h" #include "CreateFilterBrowser.h" #include "FilterGraphEditor.h" -#include "FunctionGeneratorDialog.h" #include "HistoryDialog.h" #include "LoadDialog.h" #include "LogViewerDialog.h" @@ -84,10 +83,6 @@ void MainWindow::AddDialog(shared_ptr dlg) if(bdlg != nullptr) m_bertDialogs[bdlg->GetBERT()] = dlg; - auto fdlg = dynamic_cast(dlg.get()); - if(fdlg != nullptr) - m_generatorDialogs[fdlg->GetGenerator()] = dlg; - auto rdlg = dynamic_cast(dlg.get()); if(rdlg != nullptr) m_rfgeneratorDialogs[rdlg->GetGenerator()] = dlg; @@ -535,7 +530,6 @@ void MainWindow::WindowMenu() if(ImGui::BeginMenu("Window")) { WindowAnalyzerMenu(); - WindowGeneratorMenu(); WindowMultimeterMenu(); WindowPSUMenu(); @@ -689,50 +683,6 @@ void MainWindow::WindowAnalyzerMenu() ImGui::EndDisabled(); } -/** - @brief Run the Window | Generator menu - - This menu is used for connecting to a function generator that is part of an oscilloscope or other instrument. - */ -void MainWindow::WindowGeneratorMenu() -{ - //Make a list of generators - vector< shared_ptr > gens; - auto insts = m_session.GetSCPIInstruments(); - for(auto inst : insts) - { - //Skip anything that's not a function generator - if( (inst->GetInstrumentTypes() & Instrument::INST_FUNCTION) == 0) - continue; - - //Do we already have a dialog open for it? If so, don't make another - auto generator = dynamic_pointer_cast(inst); - if(m_generatorDialogs.find(generator) != m_generatorDialogs.end()) - continue; - - gens.push_back(generator); - } - - ImGui::BeginDisabled(gens.empty()); - if(ImGui::BeginMenu("Generator")) - { - for(auto generator : gens) - { - //Add it to the menu - if(ImGui::MenuItem(generator->m_nickname.c_str())) - { - AddDialog(make_shared( - generator, - m_session.GetFunctionGeneratorState(generator), - &m_session)); - } - } - - ImGui::EndMenu(); - } - ImGui::EndDisabled(); -} - /** @brief Run the Window | Power Supply menu diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index 2ebe17bf..d3f7388b 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -38,7 +38,6 @@ #include "../scopeprotocols/ExportFilter.h" #include "MainWindow.h" #include "BERTDialog.h" -#include "FunctionGeneratorDialog.h" #include "LoadDialog.h" #include "MultimeterDialog.h" #include "PowerSupplyDialog.h" @@ -3039,13 +3038,6 @@ void Session::AddInstrument(shared_ptr inst, bool createDialogs) m_mainWindow->AddDialog(make_shared(psu, args.psustate, this)); if(meter && (types & Instrument::INST_DMM) ) m_mainWindow->AddDialog(make_shared(meter, args.meterstate, this)); - if(generator && (types & Instrument::INST_FUNCTION) ) - { - //If it's also a scope, don't show the generator dialog by default - //TODO: only if generator is currently producing a signal or something? - if(!scope) - m_mainWindow->AddDialog(make_shared(generator, args.awgstate, this)); - } if(load && (types & Instrument::INST_LOAD) ) m_mainWindow->AddDialog(make_shared(load, args.loadstate, this)); if(bert && (types & Instrument::INST_BERT) ) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index c2359d59..21d6bef0 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1083,6 +1083,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr Unit volts(Unit::UNIT_VOLTS); Unit hz(Unit::UNIT_HZ); Unit percent(Unit::UNIT_PERCENT); + Unit fs(Unit::UNIT_FS); size_t channelIndex = awgchan->GetIndex(); auto awgState = m_session.GetFunctionGeneratorState(awg); @@ -1116,14 +1117,24 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_committedDutyCycle[channelIndex] = dutyCycle; awgState->m_strDutyCycle[channelIndex] = percent.PrettyPrint(dutyCycle); } + float riseTime = awgState->m_channelRiseTime[channelIndex]; + if(riseTime != awgState->m_committedRiseTime[channelIndex]) + { + awgState->m_committedRiseTime[channelIndex] = riseTime; + awgState->m_strRiseTime[channelIndex] = fs.PrettyPrint(riseTime); + } + float fallTime = awgState->m_channelFallTime[channelIndex]; + if(fallTime != awgState->m_committedFallTime[channelIndex]) + { + awgState->m_committedFallTime[channelIndex] = fallTime; + awgState->m_strFallTime[channelIndex] = fs.PrettyPrint(fallTime); + } auto& prefs = m_session.GetPreferences(); // Row 1 ImGui::Text("Waveform:"); startBadgeLine(); // Needed for shape combo - // Padding to give space for ChannelProperties dialog button - int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); // Shape combo // Get current shape and shape index FunctionGenerator::WaveShape shape = awgState->m_channelShape[channelIndex]; @@ -1134,7 +1145,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr ImGui::ColorConvertU32ToFloat4(ColorFromString(awgchan->m_displaycolor)), shapeIndex, awgState->m_channelShapeNames[channelIndex], true, - 3,true,padding)) + 3,true)) { shape = awgState->m_channelShapes[channelIndex][shapeIndex]; awg->SetFunctionChannelShape(channelIndex, shape); @@ -1176,7 +1187,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr "Duty cycle", awgState->m_strDutyCycle[channelIndex], awgState->m_committedDutyCycle[channelIndex], - percent/*,"Duty cycle of the generated waveform"*/)) + percent/*,"Duty cycle of the waveform, in percent. Not applicable to all waveform types."*/)) { awg->SetFunctionChannelDutyCycle(channelIndex, awgState->m_committedDutyCycle[channelIndex]); awgState->m_needsUpdate[channelIndex] = true; @@ -1186,11 +1197,10 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr startBadgeLine(); auto height = ImGui::GetFontSize() * 2; auto width = height * 2; - auto totalWidth = width + padding; - if ((m_badgeXCur - totalWidth) >= m_badgeXMin) + if ((m_badgeXCur - width) >= m_badgeXMin) { // ok, we have enough space draw preview - m_badgeXCur -= totalWidth; + m_badgeXCur -= width; // save current y position to restore it after drawing the preview float currentY = ImGui::GetCursorPosY(); // Continue layout on current line (row 3) @@ -1204,7 +1214,32 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr ImGui::SetCursorPosY(currentY); } - // Row 3 + if(awg->HasFunctionRiseFallTimeControls(channelIndex)) + { //Row 3 + //Fall Time + if(renderEditableProperty(dwidth, + "Rise Time", + awgState->m_strRiseTime[channelIndex], + awgState->m_committedRiseTime[channelIndex], + fs)) + { + awg->SetFunctionChannelRiseTime(channelIndex, awgState->m_committedRiseTime[channelIndex]); + awgState->m_needsUpdate[channelIndex] = true; + } + //Row 4 + //Fall Time + if(renderEditableProperty(dwidth, + "Fall Time", + awgState->m_strFallTime[channelIndex], + awgState->m_committedFallTime[channelIndex], + fs)) + { + awg->SetFunctionChannelFallTime(channelIndex, awgState->m_committedFallTime[channelIndex]); + awgState->m_needsUpdate[channelIndex] = true; + } + } + + // Row 5 if(renderEditablePropertyWithExplicitApply(dwidth, "Amplitude", awgState->m_strAmplitude[channelIndex], @@ -1215,7 +1250,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_needsUpdate[channelIndex] = true; } - //Row 4 + //Row 6 //Offset if(renderEditablePropertyWithExplicitApply(dwidth, "Offset", @@ -1227,7 +1262,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_needsUpdate[channelIndex] = true; } - //Row 5 + //Row 7 //Impedance ImGui::SetNextItemWidth(dwidth); FunctionGenerator::OutputImpedance impedance = awgState->m_channelOutputImpedance[channelIndex]; @@ -1243,6 +1278,10 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr "Hi-Z", "50 Ω", nullptr); + HelpMarker( + "Select the expected load impedance.\n\n" + "If set incorrectly, amplitude and offset will be inaccurate due to reflections."); + if(changed) { @@ -1883,10 +1922,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s } else if(awg && awgchan) { - if(BeginBlock("awgparams",true)) - { - m_parent->ShowInstrumentProperties(awg); - } + BeginBlock("awgparams"); renderAwgProperties(awg, awgchan); EndBlock(); } From 56887e3db3a8fd6cb18a23d661b72b3bd1f61049 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 01:17:11 +0100 Subject: [PATCH 40/49] Finished removing FunctionGeneratorDialog. First step to removing MultimeterDialog. --- src/ngscopeclient/CMakeLists.txt | 1 - src/ngscopeclient/FunctionGeneratorDialog.cpp | 299 ------------------ src/ngscopeclient/FunctionGeneratorDialog.h | 108 ------- src/ngscopeclient/FunctionGeneratorState.h | 4 +- src/ngscopeclient/InstrumentThread.cpp | 4 +- src/ngscopeclient/MainWindow.cpp | 36 --- src/ngscopeclient/MainWindow.h | 1 - src/ngscopeclient/MainWindow_Menus.cpp | 5 - src/ngscopeclient/MultimeterDialog.cpp | 4 +- src/ngscopeclient/MultimeterState.h | 8 +- src/ngscopeclient/Session.cpp | 24 +- src/ngscopeclient/Session.h | 1 - src/ngscopeclient/StreamBrowserDialog.cpp | 40 ++- 13 files changed, 45 insertions(+), 490 deletions(-) delete mode 100644 src/ngscopeclient/FunctionGeneratorDialog.cpp delete mode 100644 src/ngscopeclient/FunctionGeneratorDialog.h diff --git a/src/ngscopeclient/CMakeLists.txt b/src/ngscopeclient/CMakeLists.txt index ce5c0c69..a73d4cc7 100644 --- a/src/ngscopeclient/CMakeLists.txt +++ b/src/ngscopeclient/CMakeLists.txt @@ -88,7 +88,6 @@ add_executable(ngscopeclient MeasurementsDialog.cpp MemoryLeakerDialog.cpp MetricsDialog.cpp - MultimeterDialog.cpp NFDFileBrowser.cpp NotesDialog.cpp PacketManager.cpp diff --git a/src/ngscopeclient/FunctionGeneratorDialog.cpp b/src/ngscopeclient/FunctionGeneratorDialog.cpp deleted file mode 100644 index 0d0cd70e..00000000 --- a/src/ngscopeclient/FunctionGeneratorDialog.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/*********************************************************************************************************************** -* * -* ngscopeclient * -* * -* Copyright (c) 2012-2024 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 * -* following conditions are met: * -* * -* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * -* following disclaimer. * -* * -* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * -* following disclaimer in the documentation and/or other materials provided with the distribution. * -* * -* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * -* derived from this software without specific prior written permission. * -* * -* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * -* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * -* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * -* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * -* POSSIBILITY OF SUCH DAMAGE. * -* * -***********************************************************************************************************************/ - -/** - @file - @author Andrew D. Zonenberg - @brief Implementation of FunctionGeneratorDialog - */ - -#include "ngscopeclient.h" -#include "FunctionGeneratorDialog.h" - -using namespace std; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Construction / destruction - -FunctionGeneratorDialog::FunctionGeneratorDialog(shared_ptr generator, std::shared_ptr sessionState, Session* session) - : Dialog( - string("Function Generator: ") + generator->m_nickname, - string("Function Generator: ") + generator->m_nickname, - ImVec2(400, 350)) - , m_session(session) - , m_generator(generator) - , m_state(sessionState) -{ - Unit hz(Unit::UNIT_HZ); - Unit percent(Unit::UNIT_PERCENT); - Unit volts(Unit::UNIT_VOLTS); - Unit fs(Unit::UNIT_FS); - - size_t n = m_generator->GetChannelCount(); - for(size_t i=0; iGetInstrumentTypesForChannel(i) & Instrument::INST_FUNCTION)) - { - //Add dummy placeholder (never used) - m_uiState.push_back(state); - continue; - } - - state.m_outputEnabled = m_generator->GetFunctionChannelActive(i); - - state.m_committedAmplitude = m_generator->GetFunctionChannelAmplitude(i); - state.m_amplitude = volts.PrettyPrint(state.m_committedAmplitude); - - state.m_committedOffset = m_generator->GetFunctionChannelOffset(i); - state.m_offset = volts.PrettyPrint(state.m_committedOffset); - - state.m_committedDutyCycle = m_generator->GetFunctionChannelDutyCycle(i); - state.m_dutyCycle = percent.PrettyPrint(state.m_committedDutyCycle); - - state.m_committedFrequency = m_generator->GetFunctionChannelFrequency(i); - state.m_frequency = hz.PrettyPrint(state.m_committedFrequency); - - state.m_committedRiseTime = m_generator->GetFunctionChannelRiseTime(i); - state.m_riseTime = fs.PrettyPrint(state.m_committedRiseTime); - - state.m_committedFallTime = m_generator->GetFunctionChannelFallTime(i); - state.m_fallTime = fs.PrettyPrint(state.m_committedFallTime); - - //Convert waveform shape to list box index - state.m_waveShapes = m_generator->GetAvailableWaveformShapes(i); - state.m_shapeIndex = 0; - auto shape = m_generator->GetFunctionChannelShape(i); - for(size_t j=0; jGetNameOfShape(state.m_waveShapes[j])); - } - - if(m_generator->GetFunctionChannelOutputImpedance(i) == FunctionGenerator::IMPEDANCE_50_OHM) - state.m_impedanceIndex = 1; - else - state.m_impedanceIndex = 0; - - m_uiState.push_back(state); - } - - m_impedances.push_back(FunctionGenerator::IMPEDANCE_HIGH_Z); - m_impedances.push_back(FunctionGenerator::IMPEDANCE_50_OHM); - m_impedanceNames.push_back("High-Z"); - m_impedanceNames.push_back("50Ω"); -} - -FunctionGeneratorDialog::~FunctionGeneratorDialog() -{ -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Rendering - -bool FunctionGeneratorDialog::DoRender() -{ - //Device information - if(ImGui::CollapsingHeader("Info")) - { - ImGui::BeginDisabled(); - - auto name = m_generator->GetName(); - auto vendor = m_generator->GetVendor(); - auto serial = m_generator->GetSerial(); - auto driver = m_generator->GetDriverName(); - auto transport = m_generator->GetTransport(); - auto tname = transport->GetName(); - auto tstring = transport->GetConnectionString(); - - ImGui::InputText("Make", &vendor[0], vendor.size()); - ImGui::InputText("Model", &name[0], name.size()); - ImGui::InputText("Serial", &serial[0], serial.size()); - ImGui::InputText("Driver", &driver[0], driver.size()); - ImGui::InputText("Transport", &tname[0], tname.size()); - ImGui::InputText("Path", &tstring[0], tstring.size()); - - ImGui::EndDisabled(); - } - - size_t n = m_generator->GetChannelCount(); - for(size_t i=0; iGetInstrumentTypesForChannel(i) & Instrument::INST_FUNCTION)) - continue; - - DoChannel(i); - } - - return true; -} - -/** - @brief Run the UI for a single channel - */ -void FunctionGeneratorDialog::DoChannel(size_t i) -{ - auto chname = m_generator->GetChannel(i)->GetDisplayName(); - - float valueWidth = 200; - - Unit pct(Unit::UNIT_PERCENT); - Unit hz(Unit::UNIT_HZ); - Unit volts(Unit::UNIT_VOLTS); - Unit fs(Unit::UNIT_FS); - - if(ImGui::CollapsingHeader(chname.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) - { - //Check for updates (value changed instrument side since last commit) - //TODO: move to background thread? or just rely on clientside caching to make it fast? - auto freq = m_generator->GetFunctionChannelFrequency(i); - if(freq != m_uiState[i].m_committedFrequency) - { - m_uiState[i].m_committedFrequency = freq; - m_uiState[i].m_frequency = hz.PrettyPrint(freq); - } - - ImGui::PushID(chname.c_str()); - - if(ImGui::Checkbox("Output Enable", &m_uiState[i].m_outputEnabled)) - { - m_generator->SetFunctionChannelActive(i, m_uiState[i].m_outputEnabled); - - // Tell intrument thread that the FunctionGenerator state has to be updated - m_state->m_needsUpdate[i] = true; - } - HelpMarker("Turns the output signal from this channel on or off"); - - if(m_generator->HasFunctionImpedanceControls(i)) - { - ImGui::SetNextItemWidth(valueWidth); - if(Combo("Output Impedance", m_impedanceNames, m_uiState[i].m_impedanceIndex)) - { - auto& state = m_uiState[i]; - m_generator->SetFunctionChannelOutputImpedance(i, m_impedances[state.m_impedanceIndex]); - - //Refresh amplitude and offset when changing impedance - state.m_committedAmplitude = m_generator->GetFunctionChannelAmplitude(i); - state.m_amplitude = volts.PrettyPrint(state.m_committedAmplitude); - state.m_committedOffset = m_generator->GetFunctionChannelOffset(i); - state.m_offset = volts.PrettyPrint(state.m_committedOffset); - - // Tell intrument thread that the FunctionGenerator state has to be updated - m_state->m_needsUpdate[i] = true; - } - HelpMarker( - "Select the expected load impedance.\n\n" - "If set incorrectly, amplitude and offset will be inaccurate due to reflections."); - } - - //Amplitude and offset are potentially damaging operations - //Require the user to explicitly commit changes before they take effect - ImGui::SetNextItemWidth(valueWidth); - if(UnitInputWithExplicitApply("Amplitude", m_uiState[i].m_amplitude, m_uiState[i].m_committedAmplitude, volts)) - { - m_generator->SetFunctionChannelAmplitude(i, m_uiState[i].m_committedAmplitude); - // Tell intrument thread that the FunctionGenerator state has to be updated - m_state->m_needsUpdate[i] = true; - } - HelpMarker("Peak-to-peak amplitude of the generated waveform"); - - ImGui::SetNextItemWidth(valueWidth); - if(UnitInputWithExplicitApply("Offset", m_uiState[i].m_offset, m_uiState[i].m_committedOffset, volts)) - { - m_generator->SetFunctionChannelOffset(i, m_uiState[i].m_committedOffset); - // Tell intrument thread that the FunctionGenerator state has to be updated - m_state->m_needsUpdate[i] = true; - } - HelpMarker("DC offset for the waveform above (positive) or below (negative) ground"); - - //All other settings apply when user presses enter or focus is lost - ImGui::SetNextItemWidth(valueWidth); - if(Combo("Waveform", m_uiState[i].m_waveShapeNames, m_uiState[i].m_shapeIndex)) - { - m_generator->SetFunctionChannelShape(i, m_uiState[i].m_waveShapes[m_uiState[i].m_shapeIndex]); - // Tell intrument thread that the FunctionGenerator state has to be updated - m_state->m_needsUpdate[i] = true; - } - HelpMarker("Select the type of waveform to generate"); - - ImGui::SetNextItemWidth(valueWidth); - if(UnitInputWithImplicitApply("Frequency", m_uiState[i].m_frequency, m_uiState[i].m_committedFrequency, hz)) - { - m_generator->SetFunctionChannelFrequency(i, m_uiState[i].m_committedFrequency); - // Tell intrument thread that the FunctionGenerator state has to be updated - m_state->m_needsUpdate[i] = true; - } - - //Duty cycle controls are not available in all generators - if(m_generator->HasFunctionDutyCycleControls(i)) - { - auto waveformType = m_uiState[i].m_waveShapes[m_uiState[i].m_shapeIndex]; - bool hasDutyCycle = false; - switch(waveformType) - { - case FunctionGenerator::SHAPE_PULSE: - case FunctionGenerator::SHAPE_SQUARE: - case FunctionGenerator::SHAPE_PRBS_NONSTANDARD: - hasDutyCycle = true; - break; - - default: - hasDutyCycle = false; - } - ImGui::SetNextItemWidth(valueWidth); - if(!hasDutyCycle) - ImGui::BeginDisabled(); - if(UnitInputWithImplicitApply("Duty Cycle", m_uiState[i].m_dutyCycle, m_uiState[i].m_committedDutyCycle, pct)) - m_generator->SetFunctionChannelDutyCycle(i, m_uiState[i].m_committedDutyCycle); - if(!hasDutyCycle) - ImGui::EndDisabled(); - HelpMarker("Duty cycle of the waveform, in percent. Not applicable to all waveform types."); - } - - //Rise and fall time controls are not present in all generators - //TODO: not all waveforms make sense to have rise/fall times etiher - if(m_generator->HasFunctionRiseFallTimeControls(i)) - { - ImGui::SetNextItemWidth(valueWidth); - if(UnitInputWithImplicitApply("Rise Time", m_uiState[i].m_riseTime, m_uiState[i].m_committedRiseTime, fs)) - m_generator->SetFunctionChannelRiseTime(i, m_uiState[i].m_committedRiseTime); - - ImGui::SetNextItemWidth(valueWidth); - if(UnitInputWithImplicitApply("Fall Time", m_uiState[i].m_fallTime, m_uiState[i].m_committedFallTime, fs)) - m_generator->SetFunctionChannelFallTime(i, m_uiState[i].m_committedFallTime); - } - - ImGui::PopID(); - } - - //Push config for dedicated generators - if(dynamic_pointer_cast(m_generator) == nullptr) - m_generator->GetTransport()->FlushCommandQueue(); -} diff --git a/src/ngscopeclient/FunctionGeneratorDialog.h b/src/ngscopeclient/FunctionGeneratorDialog.h deleted file mode 100644 index ef07be81..00000000 --- a/src/ngscopeclient/FunctionGeneratorDialog.h +++ /dev/null @@ -1,108 +0,0 @@ -/*********************************************************************************************************************** -* * -* ngscopeclient * -* * -* Copyright (c) 2012-2024 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 * -* following conditions are met: * -* * -* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * -* following disclaimer. * -* * -* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * -* following disclaimer in the documentation and/or other materials provided with the distribution. * -* * -* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * -* derived from this software without specific prior written permission. * -* * -* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * -* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * -* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * -* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * -* POSSIBILITY OF SUCH DAMAGE. * -* * -***********************************************************************************************************************/ - -/** - @file - @author Andrew D. Zonenberg - @brief Declaration of FunctionGeneratorDialog - */ -#ifndef FunctionGeneratorDialog_h -#define FunctionGeneratorDialog_h - -#include "Dialog.h" -#include "RollingBuffer.h" -#include "Session.h" - -class FunctionGeneratorChannelUIState -{ -public: - bool m_outputEnabled; - - std::string m_amplitude; - float m_committedAmplitude; - - std::string m_offset; - float m_committedOffset; - - std::string m_dutyCycle; - float m_committedDutyCycle; - - std::string m_frequency; - float m_committedFrequency; - - std::string m_riseTime; - float m_committedRiseTime; - - std::string m_fallTime; - float m_committedFallTime; - - int m_impedanceIndex; - - int m_shapeIndex; - std::vector m_waveShapes; - std::vector m_waveShapeNames; -}; - -class FunctionGeneratorDialog : public Dialog -{ -public: - FunctionGeneratorDialog(std::shared_ptr gen, std::shared_ptr sessionState, Session* session); - virtual ~FunctionGeneratorDialog(); - - virtual bool DoRender(); - - std::shared_ptr GetGenerator() - { return m_generator; } - -protected: - void DoChannel(size_t i); - - ///@brief Session handle so we can remove the PSU when closed - Session* m_session; - - ///@brief The generator we're controlling - std::shared_ptr m_generator; - - ///@brief Current channel stats, live updated - std::shared_ptr m_state; - - ///@brief UI state for each channel - std::vector m_uiState; - - ///@brief Output impedances - std::vector m_impedances; - - ///@brief Human readable description of each element in m_impedances - std::vector m_impedanceNames; - -}; - - - -#endif diff --git a/src/ngscopeclient/FunctionGeneratorState.h b/src/ngscopeclient/FunctionGeneratorState.h index 392dbe30..b2cf995d 100644 --- a/src/ngscopeclient/FunctionGeneratorState.h +++ b/src/ngscopeclient/FunctionGeneratorState.h @@ -46,7 +46,7 @@ class FunctionGeneratorState { size_t n = generator->GetChannelCount(); m_channelNumber = n; - m_channelActive = std::make_unique[] >(n); + m_channelActive = std::make_unique(n); m_channelAmplitude = std::make_unique[] >(n); m_channelOffset= std::make_unique[] >(n); m_channelFrequency = std::make_unique[] >(n); @@ -111,7 +111,7 @@ class FunctionGeneratorState m_needsUpdate[i] = true; } - std::unique_ptr[]> m_channelActive; + std::unique_ptr m_channelActive; std::unique_ptr[]> m_channelAmplitude; std::unique_ptr[]> m_channelOffset; std::unique_ptr[]> m_channelFrequency; diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index 51b1d077..27a41fe1 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -311,10 +311,10 @@ void InstrumentThread(InstrumentThreadArgs args) meterstate->m_secondaryMeasurement = chan->GetSecondaryValue(); meterstate->m_firstUpdateDone = true; - if(meterstate->m_needsRangeUpdate.load()) + if(meterstate->m_needsUpdate.load()) { // We need to update range state meterstate->m_autoRange = meter->GetMeterAutoRange(); - meterstate->m_needsRangeUpdate = false; + meterstate->m_needsUpdate = false; } session->MarkChannelDirty(chan); diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index 0f6f19a1..c13ded6e 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -61,7 +61,6 @@ #include "ManageInstrumentsDialog.h" #include "MeasurementsDialog.h" #include "MetricsDialog.h" -#include "MultimeterDialog.h" #include "NotesDialog.h" #include "PersistenceSettingsDialog.h" #include "PowerSupplyDialog.h" @@ -1120,10 +1119,6 @@ void MainWindow::ToolbarButtons() void MainWindow::OnDialogClosed(const std::shared_ptr& dlg) { //Handle multi-instance dialogs - auto meterDlg = dynamic_pointer_cast(dlg); - if(meterDlg) - m_meterDialogs.erase(meterDlg->GetMeter()); - auto psuDlg = dynamic_pointer_cast(dlg); if(psuDlg) m_psuDialogs.erase(psuDlg->GetPSU()); @@ -1494,18 +1489,6 @@ void MainWindow::ShowInstrumentProperties(std::shared_ptr instrument AddDialog(make_shared(psu, m_session.GetPSUState(psu), &m_session)); return; } - // Meter - auto dmm = dynamic_pointer_cast(instrument); - if(dmm) - { - if(m_meterDialogs.find(dmm) != m_meterDialogs.end()) - { - LogTrace("Multimeter properties dialog is already open, no action required\n"); - return; - } - m_session.AddMultimeterDialog(dmm); - return; - } // Bert auto bert = dynamic_pointer_cast(instrument); if(bert) @@ -2741,25 +2724,6 @@ bool MainWindow::LoadDialogs(const YAML::Node& node) } } - auto meters = node["meters"]; - if(meters) - { - for(auto it : meters) - { - auto meter = dynamic_cast(m_session.m_idtable.Lookup(it.second.as())); - if(meter) - { - auto smeter = dynamic_pointer_cast(meter->shared_from_this()); - m_session.AddMultimeterDialog(smeter); - } - else - { - ShowErrorPopup("Invalid meter", "Multimeter dialog references nonexistent instrument"); - continue; - } - } - } - auto psus = node["psus"]; if(psus) { diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index 404b3d24..41c35948 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -53,7 +53,6 @@ #include "../scopehal/PacketDecoder.h" class MeasurementsDialog; -class MultimeterDialog; class HistoryDialog; class FileBrowser; class CreateFilterBrowser; diff --git a/src/ngscopeclient/MainWindow_Menus.cpp b/src/ngscopeclient/MainWindow_Menus.cpp index f8a42d16..1ad47365 100644 --- a/src/ngscopeclient/MainWindow_Menus.cpp +++ b/src/ngscopeclient/MainWindow_Menus.cpp @@ -52,7 +52,6 @@ #include "MeasurementsDialog.h" #include "MemoryLeakerDialog.h" #include "MetricsDialog.h" -#include "MultimeterDialog.h" #include "NotesDialog.h" #include "PersistenceSettingsDialog.h" #include "PowerSupplyDialog.h" @@ -71,10 +70,6 @@ void MainWindow::AddDialog(shared_ptr dlg) { m_dialogs.emplace(dlg); - auto mdlg = dynamic_cast(dlg.get()); - if(mdlg != nullptr) - m_meterDialogs[mdlg->GetMeter()] = dlg; - auto pdlg = dynamic_cast(dlg.get()); if(pdlg != nullptr) m_psuDialogs[pdlg->GetPSU()] = dlg; diff --git a/src/ngscopeclient/MultimeterDialog.cpp b/src/ngscopeclient/MultimeterDialog.cpp index 7370ba0b..954ab5d1 100644 --- a/src/ngscopeclient/MultimeterDialog.cpp +++ b/src/ngscopeclient/MultimeterDialog.cpp @@ -77,7 +77,7 @@ MultimeterDialog::MultimeterDialog(shared_ptr meter, shared_ptr< //Check for autorange state change if(m_state->m_autoRange.load() != m_autorange) - m_state->m_needsRangeUpdate = true; + m_state->m_needsUpdate = true; //Secondary operating modes RefreshSecondaryModeList(); @@ -143,7 +143,7 @@ bool MultimeterDialog::DoRender() if(ImGui::Checkbox("Autorange", &m_autorange)) { m_meter->SetMeterAutoRange(m_autorange); - m_state->m_needsRangeUpdate = true; + m_state->m_needsUpdate = true; } HelpMarker("Enables automatic selection of meter scale ranges."); diff --git a/src/ngscopeclient/MultimeterState.h b/src/ngscopeclient/MultimeterState.h index 001de1c1..47d549d5 100644 --- a/src/ngscopeclient/MultimeterState.h +++ b/src/ngscopeclient/MultimeterState.h @@ -44,23 +44,25 @@ class MultimeterState MultimeterState() { + m_started = false; m_primaryMeasurement = 0; m_secondaryMeasurement = 0; m_firstUpdateDone = false; m_autoRange = true; - m_needsRangeUpdate = true; + m_needsUpdate = true; } void FlushConfigCache() { - m_needsRangeUpdate = true; + m_needsUpdate = true; } + bool m_started; std::atomic m_primaryMeasurement; std::atomic m_secondaryMeasurement; std::atomic m_firstUpdateDone; std::atomic m_autoRange; - std::atomic m_needsRangeUpdate; + std::atomic m_needsUpdate; }; #endif diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index d3f7388b..00020ff2 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -39,7 +39,6 @@ #include "MainWindow.h" #include "BERTDialog.h" #include "LoadDialog.h" -#include "MultimeterDialog.h" #include "PowerSupplyDialog.h" #include "RFGeneratorDialog.h" #include "PreferenceTypes.h" @@ -2997,6 +2996,11 @@ void Session::AddInstrument(shared_ptr inst, bool createDialogs) auto state = make_shared(); m_meters[meter] = state; args.meterstate = state; + if(!(scope && (types & Instrument::INST_OSCILLOSCOPE))) + { // This is a standalone multimeter (not in an Oscilloscope) => start it by default + meter->StartMeter(); + m_meters[meter]->m_started = true; + } } if(load && (types & Instrument::INST_LOAD) ) { @@ -3036,8 +3040,6 @@ void Session::AddInstrument(shared_ptr inst, bool createDialogs) { if(psu && (types & Instrument::INST_PSU) ) m_mainWindow->AddDialog(make_shared(psu, args.psustate, this)); - if(meter && (types & Instrument::INST_DMM) ) - m_mainWindow->AddDialog(make_shared(meter, args.meterstate, this)); if(load && (types & Instrument::INST_LOAD) ) m_mainWindow->AddDialog(make_shared(load, args.loadstate, this)); if(bert && (types & Instrument::INST_BERT) ) @@ -3075,7 +3077,13 @@ void Session::RemoveInstrument(shared_ptr inst) if(scope) m_oscilloscopesStates.erase(scope); if(meter) + { + auto state = m_meters[meter]; + // Stop meter if needed + if(state && state->m_started) + meter->StopMeter(); m_meters.erase(meter); + } if(load) m_loads.erase(load); if(bert) @@ -3087,16 +3095,6 @@ void Session::RemoveInstrument(shared_ptr inst) m_instrumentStates.erase(inst); } -/** - @brief Adds a multimeter dialog to the session - - Low level helper, intended to be only used by file loading - */ -void Session::AddMultimeterDialog(shared_ptr meter) -{ - m_mainWindow->AddDialog(make_shared(meter, m_meters[meter], this)); -} - /** @brief Returns a list of all connected SCPI instruments, of any type diff --git a/src/ngscopeclient/Session.h b/src/ngscopeclient/Session.h index 33f74ec2..381a4800 100644 --- a/src/ngscopeclient/Session.h +++ b/src/ngscopeclient/Session.h @@ -131,7 +131,6 @@ class Session bool SerializeSparseWaveform(SparseWaveformBase* wfm, const std::string& path); bool SerializeUniformWaveform(UniformWaveformBase* wfm, const std::string& path); - void AddMultimeterDialog(std::shared_ptr meter); std::shared_ptr AddPacketFilter(PacketDecoder* filter); void AddInstrument(std::shared_ptr inst, bool createDialogs = true); diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 21d6bef0..a9ed130a 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1062,7 +1062,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3,padding)) { dmm->SetMeterAutoRange(autorange); - dmmState->m_needsRangeUpdate = true; + dmmState->m_needsUpdate = true; } ImGui::PopID(); } @@ -1761,6 +1761,10 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s { renderProps = m_session.GetFunctionGeneratorState(awg)->m_channelActive[channelIndex]; } + else if(dmm && dmmchan) + { + renderProps = m_session.GetDmmState(dmm)->m_started; + } bool hasChildren = !singleStream || renderProps; @@ -1788,8 +1792,6 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s m_parent->ShowChannelProperties(scopechan); else if(psuchan) m_parent->ShowInstrumentProperties(psu); - else if(awgchan) - m_parent->ShowInstrumentProperties(awg); else LogWarning("Don't know how to open channel properties yet\n"); } @@ -1856,22 +1858,26 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s { // AWG Channel : get the state auto awgstate = m_session.GetFunctionGeneratorState(awg); - - bool active = awgstate->m_channelActive[channelIndex]; - bool result = active; - renderOnOffToggle("###active", true, result); - if(result != active) + if(renderOnOffToggle("###active", true,awgstate->m_channelActive[channelIndex])) { - awg->SetFunctionChannelActive(channelIndex,result); - auto awgState = m_session.GetFunctionGeneratorState(awg); - if(awgState) - { - // Update state right now to cover from slow intruments - awgState->m_channelActive[channelIndex]=result; - // Tell intrument thread that the FunctionGenerator state has to be updated - awgState->m_needsUpdate[channelIndex] = true; - } + awg->SetFunctionChannelActive(channelIndex,awgstate->m_channelActive[channelIndex]); + // Tell intrument thread that the FunctionGenerator state has to be updated + awgstate->m_needsUpdate[channelIndex] = true; + } + } + else if(dmm && dmmchan) + { + // DMM Channel : get the state + auto dmmstate = m_session.GetDmmState(dmm); + if(renderOnOffToggle("###active", true, dmmstate->m_started)) + { + if(dmmstate->m_started) + dmm->StartMeter(); + else + dmm->StopMeter(); + // Tell intrument thread that the Dmm state has to be updated + dmmstate->m_needsUpdate = true; } } From cafbb36c9d1e21d9e04a91e525a4da508338b937 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 01:32:13 +0100 Subject: [PATCH 41/49] Next step to removing MultimeterDialog. --- src/ngscopeclient/MainWindow.h | 1 - src/ngscopeclient/MainWindow_Menus.cpp | 38 ----------------------- src/ngscopeclient/StreamBrowserDialog.cpp | 13 ++------ 3 files changed, 3 insertions(+), 49 deletions(-) diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index 41c35948..c06bea16 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -259,7 +259,6 @@ class MainWindow : public VulkanWindow void WindowMenu(); void WindowAnalyzerMenu(); void WindowPSUMenu(); - void WindowMultimeterMenu(); void DebugMenu(); void DebugSCPIConsoleMenu(); void HelpMenu(); diff --git a/src/ngscopeclient/MainWindow_Menus.cpp b/src/ngscopeclient/MainWindow_Menus.cpp index 1ad47365..1521e155 100644 --- a/src/ngscopeclient/MainWindow_Menus.cpp +++ b/src/ngscopeclient/MainWindow_Menus.cpp @@ -525,7 +525,6 @@ void MainWindow::WindowMenu() if(ImGui::BeginMenu("Window")) { WindowAnalyzerMenu(); - WindowMultimeterMenu(); WindowPSUMenu(); bool hasLabNotes = m_notesDialog != nullptr; @@ -717,43 +716,6 @@ void MainWindow::WindowPSUMenu() ImGui::EndDisabled(); } -/** - @brief Run the Window | Multimeter menu - */ -void MainWindow::WindowMultimeterMenu() -{ - //This is a bit of a hack but all of the dialogs are gonna get redone eventually so - vector< shared_ptr > meters; - auto insts = m_session.GetScopes(); - for(auto inst : insts) - { - //Skip anything that's not a multimeter - if( (inst->GetInstrumentTypes() & Instrument::INST_DMM) == 0) - continue; - - //Do we already have a dialog open for it? If so, don't make another - auto meter = dynamic_pointer_cast(inst); - if(m_meterDialogs.find(meter) != m_meterDialogs.end()) - continue; - - meters.push_back(meter); - } - - ImGui::BeginDisabled(meters.empty()); - if(ImGui::BeginMenu("Multimeter")) - { - for(auto meter : meters) - { - //Add it to the menu - if(ImGui::MenuItem(meter->m_nickname.c_str())) - m_session.AddInstrument(meter); - } - - ImGui::EndMenu(); - } - ImGui::EndDisabled(); -} - /** @brief Runs the Debug | SCPI Console menu */ diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index a9ed130a..8df5875e 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1010,11 +1010,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M } } - // Padding to give space for ChannelProperties dialog button - auto& prefs = m_session.GetPreferences(); - int padding = prefs.GetBool("Appearance.Stream Browser.show_block_border") ? (ImGui::GetFontSize() - 1) : (1.5 * ImGui::GetFontSize()); - - if(renderCombo("##mode", true, color, modeSelector, modeNames,true,3,true,padding)) + if(renderCombo("##mode", true, color, modeSelector, modeNames,true,3,true)) { curMode = modes[modeSelector]; if(isMain) @@ -1059,7 +1055,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M // For main, also show the autorange combo startBadgeLine(); bool autorange = dmmState->m_autoRange.load(); - if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3,padding)) + if(renderOnOffToggle("##autorange",true,autorange,"Manual Range","Autorange",3)) { dmm->SetMeterAutoRange(autorange); dmmState->m_needsUpdate = true; @@ -1934,10 +1930,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s } else if(dmm && dmmchan) { - if(BeginBlock("dmm_params",true,"Open Multimeter properties")) - { - m_parent->ShowInstrumentProperties(dmm); - } + BeginBlock("dmm_params"); // Always 2 streams for dmm channel => render properties on channel node bool clicked = false; bool hovered = false; From 1c1ff08042035608a26bbadc32bdf65dfc4e691d Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 02:00:20 +0100 Subject: [PATCH 42/49] Merged oscilloscopes and oscilloscopeStates in Session class. --- src/ngscopeclient/Session.cpp | 44 +++++++++++++++++++---------------- src/ngscopeclient/Session.h | 14 ++++++----- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index 00020ff2..63e02746 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -174,7 +174,7 @@ void Session::FlushConfigCache() { it.second->FlushConfigCache(); } - for(auto it : m_oscilloscopesStates) + for(auto it : m_oscilloscopes) { it.second->FlushConfigCache(); } @@ -236,8 +236,13 @@ void Session::Clear() //Delete scopes once we've terminated the threads //Detach waveforms before we destroy the scope, since history owns them //(but make sure they're actually *in* history first!) - m_history.AddHistory(m_oscilloscopes); - for(auto scope : m_oscilloscopes) + std::vector> scopes; + for(auto it : m_oscilloscopes) + { + scopes.push_back(it.first); + } + m_history.AddHistory(scopes); + for(auto scope : scopes) { for(size_t i=0; iGetChannelCount(); i++) { @@ -255,7 +260,6 @@ void Session::Clear() m_history.clear(); m_oscilloscopes.clear(); - m_oscilloscopesStates.clear(); m_psus.clear(); m_loads.clear(); m_meters.clear(); @@ -435,9 +439,9 @@ bool Session::LoadWaveformData(int version, const string& dataDir) } //Load data for each scope - for(size_t i=0; iGetChannelCount(); i++) { auto oc = scope->GetOscilloscopeChannel(i); @@ -2423,9 +2428,9 @@ bool Session::SerializeWaveforms(const string& dataDir) } //Write metadata files (by this point, data directories should have been created) - for(size_t i=0; i inst, bool createDialogs) } if(scope && (types & Instrument::INST_OSCILLOSCOPE)) { - m_oscilloscopes.push_back(scope); auto state = make_shared(scope); - m_oscilloscopesStates[scope] = state; + m_oscilloscopes[scope] = state; args.oscilloscopestate = state; if(m_oscilloscopes.size() > 1) m_multiScope = true; @@ -3075,7 +3079,7 @@ void Session::RemoveInstrument(shared_ptr inst) if(psu) m_psus.erase(psu); if(scope) - m_oscilloscopesStates.erase(scope); + m_oscilloscopes.erase(scope); if(meter) { auto state = m_meters[meter]; @@ -3111,9 +3115,9 @@ set> Session::GetSCPIInstruments() if(s != nullptr) insts.emplace(s); } - for(auto& scope : m_oscilloscopes) + for(auto& it : m_oscilloscopes) { - auto s = dynamic_pointer_cast(scope); + auto s = dynamic_pointer_cast(it.first); if(s != nullptr) insts.emplace(s); } @@ -3155,8 +3159,8 @@ set> Session::GetInstruments() lock_guard lock(m_scopeMutex); set> insts; - for(auto& scope : m_oscilloscopes) - insts.emplace(scope); + for(auto& it : m_oscilloscopes) + insts.emplace(it.first); for(auto& it : m_psus) insts.emplace(it.first); for(auto& it : m_berts) @@ -3246,9 +3250,9 @@ void Session::StopTrigger(bool all) */ bool Session::HasOnlineScopes() { - for(auto scope : m_oscilloscopes) + for(auto it : m_oscilloscopes) { - if(!scope->IsOffline()) + if(!it.first->IsOffline()) return true; } return false; @@ -3663,9 +3667,9 @@ bool Session::OnMemoryPressure(MemoryPressureLevel level, MemoryPressureType typ if(!moreFreed) { std::lock_guard lock(m_scopeMutex); - for(auto scope : m_oscilloscopes) + for(auto it : m_oscilloscopes) { - if(scope->FreeWaveformPools()) + if(it.first->FreeWaveformPools()) moreFreed = true; } } diff --git a/src/ngscopeclient/Session.h b/src/ngscopeclient/Session.h index 381a4800..5132cf8c 100644 --- a/src/ngscopeclient/Session.h +++ b/src/ngscopeclient/Session.h @@ -149,7 +149,7 @@ class Session std::shared_ptr GetOscilloscopeState(std::shared_ptr scope) { std::lock_guard lock(m_scopeMutex); - return m_oscilloscopesStates[scope]; + return m_oscilloscopes[scope]; } /** @@ -247,7 +247,12 @@ class Session const std::vector> GetScopes() { std::lock_guard lock(m_scopeMutex); - return m_oscilloscopes; + std::vector> scopes; + for(auto it : m_oscilloscopes) + { + scopes.push_back(it.first); + } + return scopes; } /** @@ -435,10 +440,7 @@ class Session bool m_modifiedSinceLastSave; ///@brief Oscilloscopes we are currently connected to - std::vector> m_oscilloscopes; - - ///@brief Oscilloscopes we are currently connected to - std::map, std::shared_ptr > m_oscilloscopesStates; + std::map, std::shared_ptr > m_oscilloscopes; ///@brief Power supplies we are currently connected to std::map, std::shared_ptr > m_psus; From 1b28ddbf18fe6fe61ab95bd1d7d1c655745e0f90 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 10:46:39 +0100 Subject: [PATCH 43/49] Made PSU/DMM numeric value a 3 option choice (monospace font (default), 7 segment display or proportional font). --- src/ngscopeclient/PreferenceSchema.cpp | 17 +++++++--- src/ngscopeclient/PreferenceTypes.h | 7 ++++ src/ngscopeclient/StreamBrowserDialog.cpp | 40 +++++++++++++++++++---- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index 2bce82f5..9adb7cda 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -166,9 +166,18 @@ void PreferenceManager::InitializeDefaults() auto& stream = appearance.AddCategory("Stream Browser"); stream.AddPreference( - Preference::Bool("use_7_segment_display", true) - .Label("Use 7 segment style display") - .Description("Use 7 segment style display for DMM and PSU values")); + Preference::Enum("numeric_value_display", NUMERIC_DISPLAY_MONO_FONT) + .Label("Numeric value display") + .Description( + "Select the way numeric values are displayed for DMM and PSU nodes.\n" + "- Console font: use Console font (monospace) defined in General preferences.\n" + "- 7 segment: use 7 segment style display.\n" + "- Default font: use Default font (proportional) defined in General preferences.\n" + ) + .EnumValue("Console font", NUMERIC_DISPLAY_MONO_FONT) + .EnumValue("7 segment", NUMERIC_DISPLAY_7SEGMENT) + .EnumValue("Default font", NUMERIC_DISPLAY_DEFAULT_FONT) + ); stream.AddPreference( Preference::Bool("show_block_border", true) .Label("Show block border") @@ -286,7 +295,7 @@ void PreferenceManager::InitializeDefaults() general.AddPreference( Preference::Font("console_font", FontDescription(FindDataFile("fonts/DejaVuSansMono.ttf"), 13)) .Label("Console font") - .Description("Font used for SCPI console and log viewer")); + .Description("Font used for SCPI console, log viewer and PSU/DMM numeric values in Stream Browser")); auto& graphs = appearance.AddCategory("Graphs"); graphs.AddPreference( diff --git a/src/ngscopeclient/PreferenceTypes.h b/src/ngscopeclient/PreferenceTypes.h index 2c161276..60a6cbfd 100644 --- a/src/ngscopeclient/PreferenceTypes.h +++ b/src/ngscopeclient/PreferenceTypes.h @@ -68,4 +68,11 @@ enum HeadlessStartupMode HEADLESS_STARTUP_C1_ONLY }; +enum NumericValueDisplay +{ + NUMERIC_DISPLAY_MONO_FONT, + NUMERIC_DISPLAY_7SEGMENT, + NUMERIC_DISPLAY_DEFAULT_FONT +}; + #endif diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 8df5875e..38fdc8d8 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -36,6 +36,7 @@ #include "ngscopeclient.h" #include "StreamBrowserDialog.h" #include "MainWindow.h" +#include "PreferenceTypes.h" using namespace std; @@ -425,10 +426,18 @@ bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color, bool allow7SegmentDisplay, float digitHeight, bool clickable) { bool use7Segment = false; + bool changeFont = false; + auto& prefs = m_session.GetPreferences(); + auto displayType = prefs.GetEnumRaw("Appearance.Stream Browser.numeric_value_display"); + FontWithSize font; if(allow7SegmentDisplay) { - auto& prefs = m_session.GetPreferences(); - use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); + use7Segment = displayType == NumericValueDisplay::NUMERIC_DISPLAY_7SEGMENT; + if(!use7Segment) + { + font = m_parent->GetFontPref(displayType == NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT ? "Appearance.General.default_font" : "Appearance.General.console_font"); + changeFont = true; + } } if(use7Segment) { @@ -441,7 +450,9 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &cli { ImVec2 pos = ImGui::GetCursorPos(); ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); ImGui::TextUnformatted(value.c_str()); + if(changeFont) ImGui::PopFont(); ImGui::PopStyleColor(); clicked |= ImGui::IsItemClicked(); @@ -462,7 +473,9 @@ void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &cli else { ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); ImGui::TextUnformatted(value.c_str()); + if(changeFont) ImGui::PopFont(); ImGui::PopStyleColor(); } } @@ -516,6 +529,20 @@ bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& { static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports float or double"); auto& prefs = m_session.GetPreferences(); + auto displayType = prefs.GetEnumRaw("Appearance.Stream Browser.numeric_value_display"); + bool use7Segment = false; + bool changeFont = false; + FontWithSize font; + if(allow7SegmentDisplay) + { + use7Segment = displayType == NumericValueDisplay::NUMERIC_DISPLAY_7SEGMENT; + if(!use7Segment) + { + font = m_parent->GetFontPref(displayType == NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT ? "Appearance.General.default_font" : "Appearance.General.console_font"); + changeFont = true; + } + } + bool changed = false; bool validateChange = false; bool cancelEdit = false; @@ -540,6 +567,7 @@ bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& // Allow overlap for apply button ImGui::PushItemFlag(ImGuiItemFlags_AllowOverlap, true); ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) { // Input validated (but no apply button) if(!explicitApply) @@ -551,6 +579,7 @@ bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& keepEditing = true; } } + if(changeFont) ImGui::PopFont(); ImGui::PopStyleColor(); ImGui::PopItemFlag(); if(explicitApply) @@ -620,11 +649,6 @@ bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& } bool clicked = false; bool hovered = false; - bool use7Segment = false; - if(allow7SegmentDisplay) - { - use7Segment = prefs.GetBool("Appearance.Stream Browser.use_7_segment_display"); - } if(use7Segment) { ImGui::PushID(labelId); @@ -634,7 +658,9 @@ bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& else { ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); ImGui::InputText(label.c_str(),¤tValue,ImGuiInputTextFlags_ReadOnly); + if(changeFont) ImGui::PopFont(); ImGui::PopStyleColor(); clicked |= ImGui::IsItemClicked(); if(ImGui::IsItemHovered()) From fcd130de8801c04a26b1b897989b6fea26c14c1b Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 10:59:05 +0100 Subject: [PATCH 44/49] Fixed UTF8 special characters in cpp file. --- src/ngscopeclient/StreamBrowserDialog.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 38fdc8d8..5a01a6da 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -40,7 +40,8 @@ using namespace std; -#define ELLIPSIS_CHAR "\xE2\x80\xA6" // "..." character +#define ELLIPSIS_CHAR "…" +#define CARRIAGE_RETURN_CHAR "⏎" #define PLUS_CHAR "+" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -596,7 +597,7 @@ bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); ImGui::BeginDisabled(!dirty); - if(ImGui::Button("\xE2\x8F\x8E")) // Carriage return symbol + if(ImGui::Button(CARRIAGE_RETURN_CHAR)) // Carriage return symbol { // Apply button click validateChange = true; } From 7bebb64e2cba55557d1e33db9556a152b83a8817 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 12:02:56 +0100 Subject: [PATCH 45/49] Finished MultimterDialog removal. --- src/ngscopeclient/InstrumentThread.cpp | 3 +- src/ngscopeclient/MultimeterDialog.cpp | 240 ---------------------- src/ngscopeclient/MultimeterDialog.h | 98 --------- src/ngscopeclient/MultimeterState.h | 2 + src/ngscopeclient/StreamBrowserDialog.cpp | 27 +++ 5 files changed, 31 insertions(+), 339 deletions(-) delete mode 100644 src/ngscopeclient/MultimeterDialog.cpp delete mode 100644 src/ngscopeclient/MultimeterDialog.h diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index 27a41fe1..66fe607f 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -312,7 +312,8 @@ void InstrumentThread(InstrumentThreadArgs args) meterstate->m_firstUpdateDone = true; if(meterstate->m_needsUpdate.load()) - { // We need to update range state + { // We need to update dmm state + meterstate->m_selectedChannel = meter->GetCurrentMeterChannel(); meterstate->m_autoRange = meter->GetMeterAutoRange(); meterstate->m_needsUpdate = false; } diff --git a/src/ngscopeclient/MultimeterDialog.cpp b/src/ngscopeclient/MultimeterDialog.cpp deleted file mode 100644 index 954ab5d1..00000000 --- a/src/ngscopeclient/MultimeterDialog.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/*********************************************************************************************************************** -* * -* ngscopeclient * -* * -* 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 * -* following conditions are met: * -* * -* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * -* following disclaimer. * -* * -* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * -* following disclaimer in the documentation and/or other materials provided with the distribution. * -* * -* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * -* derived from this software without specific prior written permission. * -* * -* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * -* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * -* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * -* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * -* POSSIBILITY OF SUCH DAMAGE. * -* * -***********************************************************************************************************************/ - -/** - @file - @author Andrew D. Zonenberg - @brief Implementation of MultimeterDialog - */ - -#include "ngscopeclient.h" -#include "MultimeterDialog.h" - -using namespace std; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Construction / destruction - -MultimeterDialog::MultimeterDialog(shared_ptr meter, shared_ptr state, Session* session) - : Dialog( - string("Multimeter: ") + meter->m_nickname, - string("Multimeter: ") + meter->m_nickname, - ImVec2(500, 400)) - , m_session(session) - , m_tstart(GetTime()) - , m_meter(meter) - , m_state(state) - , m_selectedChannel(m_meter->GetCurrentMeterChannel()) - , m_autorange(m_meter->GetMeterAutoRange()) -{ - m_meter->StartMeter(); - - //Inputs - for(size_t i=0; iGetChannelCount(); i++) - m_channelNames.push_back(m_meter->GetChannel(i)->GetDisplayName()); - - //Primary operating modes - auto modemask = m_meter->GetMeasurementTypes(); - auto primode = m_meter->GetMeterMode(); - m_primaryModeSelector = 0; - for(unsigned int i=0; i<32; i++) - { - auto mode = static_cast(1 << i); - if(modemask & mode) - { - m_primaryModes.push_back(mode); - m_primaryModeNames.push_back(m_meter->ModeToText(mode)); - if(primode == mode) - m_primaryModeSelector = m_primaryModes.size() - 1; - } - } - - //Check for autorange state change - if(m_state->m_autoRange.load() != m_autorange) - m_state->m_needsUpdate = true; - - //Secondary operating modes - RefreshSecondaryModeList(); -} - -MultimeterDialog::~MultimeterDialog() -{ - m_meter->StopMeter(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Rendering - -bool MultimeterDialog::DoRender() -{ - float valueWidth = 10 * ImGui::GetFontSize(); - - //Device information - if(ImGui::CollapsingHeader("Info")) - { - ImGui::BeginDisabled(); - - auto name = m_meter->GetName(); - auto vendor = m_meter->GetVendor(); - auto serial = m_meter->GetSerial(); - auto driver = m_meter->GetDriverName(); - auto transport = m_meter->GetTransport(); - auto tname = transport->GetName(); - auto tstring = transport->GetConnectionString(); - - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText("Make", &vendor[0], vendor.size()); - - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText("Model", &name[0], name.size()); - - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText("Serial", &serial[0], serial.size()); - - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText("Driver", &driver[0], driver.size()); - - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText("Transport", &tname[0], tname.size()); - - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText("Path", &tstring[0], tstring.size()); - - ImGui::EndDisabled(); - } - - //Save history - auto pri = m_state->m_primaryMeasurement.load(); - auto sec = m_state->m_secondaryMeasurement.load(); - bool firstUpdateDone = m_state->m_firstUpdateDone.load(); - bool hasSecondary = m_meter->GetSecondaryMeterMode() != Multimeter::NONE; - - auto primaryMode = m_meter->ModeToText(m_meter->GetMeterMode()); - auto secondaryMode = m_meter->ModeToText(m_meter->GetSecondaryMeterMode()); - - if(ImGui::CollapsingHeader("Configuration", ImGuiTreeNodeFlags_DefaultOpen)) - { - if(ImGui::Checkbox("Autorange", &m_autorange)) - { - m_meter->SetMeterAutoRange(m_autorange); - m_state->m_needsUpdate = true; - } - HelpMarker("Enables automatic selection of meter scale ranges."); - - //Channel selector (hide if we have only one channel) - if(m_meter->GetChannelCount() > 1) - { - ImGui::SetNextItemWidth(valueWidth); - if(Combo("Channel", m_channelNames, m_selectedChannel)) - m_meter->SetCurrentMeterChannel(m_selectedChannel); - - HelpMarker("Select which input channel is being monitored."); - } - - //Primary operating mode selector - ImGui::SetNextItemWidth(valueWidth); - if(Combo("Mode", m_primaryModeNames, m_primaryModeSelector)) - OnPrimaryModeChanged(); - HelpMarker("Select the type of measurement to make."); - - //Secondary operating mode selector - if(m_secondaryModeNames.empty()) - ImGui::BeginDisabled(); - ImGui::SetNextItemWidth(valueWidth); - if(Combo("Secondary Mode", m_secondaryModeNames, m_secondaryModeSelector)) - m_meter->SetSecondaryMeterMode(m_secondaryModes[m_secondaryModeSelector]); - if(m_secondaryModeNames.empty()) - ImGui::EndDisabled(); - - HelpMarker( - "Select auxiliary measurement mode, if supported.\n\n" - "The set of available auxiliary measurements depends on the current primary measurement mode."); - } - - if(ImGui::CollapsingHeader("Measurements", ImGuiTreeNodeFlags_DefaultOpen)) - { - string spri; - string ssec; - - //Hide values until we get first readings back from the meter - if(firstUpdateDone) - { - spri = m_meter->GetMeterUnit().PrettyPrint(pri, m_meter->GetMeterDigits()); - if(hasSecondary) - ssec = m_meter->GetSecondaryMeterUnit().PrettyPrint(sec, m_meter->GetMeterDigits()); - } - - ImGui::BeginDisabled(); - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText(primaryMode.c_str(), &spri[0], spri.size()); - ImGui::EndDisabled(); - HelpMarker("Most recent value for the primary measurement"); - - if(hasSecondary) - { - ImGui::BeginDisabled(); - ImGui::SetNextItemWidth(valueWidth); - ImGui::InputText(secondaryMode.c_str(), &ssec[0], ssec.size()); - ImGui::EndDisabled(); - HelpMarker("Most recent value for the secondary measurement"); - } - } - - return true; -} - -void MultimeterDialog::OnPrimaryModeChanged() -{ - //Push the new mode to the meter - m_meter->SetMeterMode(m_primaryModes[m_primaryModeSelector]); - - //Redo the list of available secondary meter modes - RefreshSecondaryModeList(); -} - -void MultimeterDialog::RefreshSecondaryModeList() -{ - m_secondaryModes.clear(); - m_secondaryModeNames.clear(); - m_secondaryModeSelector = -1; - - auto modemask = m_meter->GetSecondaryMeasurementTypes(); - auto secmode = m_meter->GetSecondaryMeterMode(); - for(unsigned int i=0; i<32; i++) - { - auto mode = static_cast(1 << i); - if(modemask & mode) - { - m_secondaryModes.push_back(mode); - m_secondaryModeNames.push_back(m_meter->ModeToText(mode)); - if(secmode == mode) - m_secondaryModeSelector = m_secondaryModes.size() - 1; - } - } -} diff --git a/src/ngscopeclient/MultimeterDialog.h b/src/ngscopeclient/MultimeterDialog.h deleted file mode 100644 index 5452c904..00000000 --- a/src/ngscopeclient/MultimeterDialog.h +++ /dev/null @@ -1,98 +0,0 @@ -/*********************************************************************************************************************** -* * -* ngscopeclient * -* * -* 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 * -* following conditions are met: * -* * -* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * -* following disclaimer. * -* * -* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * -* following disclaimer in the documentation and/or other materials provided with the distribution. * -* * -* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * -* derived from this software without specific prior written permission. * -* * -* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * -* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * -* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * -* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * -* POSSIBILITY OF SUCH DAMAGE. * -* * -***********************************************************************************************************************/ - -/** - @file - @author Andrew D. Zonenberg - @brief Declaration of MultimeterDialog - */ -#ifndef MultimeterDialog_h -#define MultimeterDialog_h - -#include "Dialog.h" -#include "Session.h" - -class MultimeterDialog : public Dialog -{ -public: - MultimeterDialog(std::shared_ptr meter, std::shared_ptr state, Session* session); - virtual ~MultimeterDialog(); - - virtual bool DoRender(); - - std::shared_ptr GetMeter() - { return m_meter; } - -protected: - void OnPrimaryModeChanged(); - void RefreshSecondaryModeList(); - - ///@brief Session handle so we can remove the PSU when closed - Session* m_session; - - ///@brief Timestamp of when we opened the dialog - double m_tstart; - - ///@brief The meter we're controlling - std::shared_ptr m_meter; - - ///@brief Current channel stats, live updated - std::shared_ptr m_state; - - ///@brief Set of channel names - std::vector m_channelNames; - - ///@brief The currently selected input channel - int m_selectedChannel; - - ///@brief Names of primary channel operating modes - std::vector m_primaryModeNames; - - ///@brief List of primary channel operating modes - std::vector m_primaryModes; - - ///@brief Index of primary mode - int m_primaryModeSelector; - - ///@brief Names of secondary channel operating modes - std::vector m_secondaryModeNames; - - ///@brief List of secondary channel operating modes - std::vector m_secondaryModes; - - ///@brief Index of secondary mode - int m_secondaryModeSelector; - - ///@brief Autorange enable flag - bool m_autorange; -}; - - - -#endif diff --git a/src/ngscopeclient/MultimeterState.h b/src/ngscopeclient/MultimeterState.h index 47d549d5..f4787ede 100644 --- a/src/ngscopeclient/MultimeterState.h +++ b/src/ngscopeclient/MultimeterState.h @@ -45,6 +45,7 @@ class MultimeterState MultimeterState() { m_started = false; + m_selectedChannel = 0; m_primaryMeasurement = 0; m_secondaryMeasurement = 0; m_firstUpdateDone = false; @@ -58,6 +59,7 @@ class MultimeterState } bool m_started; + int m_selectedChannel; std::atomic m_primaryMeasurement; std::atomic m_secondaryMeasurement; std::atomic m_firstUpdateDone; diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 5a01a6da..7918db8b 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1961,6 +1961,33 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s // Always 2 streams for dmm channel => render properties on channel node bool clicked = false; bool hovered = false; + // Channel selection + size_t channelCount = instrument->GetChannelCount(); + if(channelCount > 1) + { + auto dmmState = m_session.GetDmmState(dmm); + if(dmmState) + { + ImGui::SetNextItemWidth(6*ImGui::GetFontSize()); + vector channelNames; + for(size_t i=0; iGetChannelCount(); i++) + channelNames.push_back(dmm->GetChannel(i)->GetDisplayName()); + if(renderCombo( + "Channel", + false, + ImGui::GetStyleColorVec4(ImGuiCol_FrameBg), + dmmState->m_selectedChannel, + channelNames, + false, + 0, + false)) + { + dmm->SetCurrentMeterChannel(dmmState->m_selectedChannel); + dmmState->m_needsUpdate = true; + } + HelpMarker("Select which input channel is being monitored."); + } + } // Main measurement renderDmmProperties(dmm,dmmchan,true,clicked,hovered); // Secondary measurement From 808fc31ddbb1c6a3aea47fbb67f884dfde70d2a3 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 18:14:56 +0100 Subject: [PATCH 46/49] Refactored property editors from StreamBrowserDialog to Dialog class. --- src/ngscopeclient/Dialog.cpp | 367 +++++++++++++++++++- src/ngscopeclient/Dialog.h | 22 +- src/ngscopeclient/PreferenceSchema.cpp | 8 +- src/ngscopeclient/StreamBrowserDialog.cpp | 389 ++-------------------- src/ngscopeclient/StreamBrowserDialog.h | 14 - 5 files changed, 415 insertions(+), 385 deletions(-) diff --git a/src/ngscopeclient/Dialog.cpp b/src/ngscopeclient/Dialog.cpp index 8d35c430..61eb66b4 100644 --- a/src/ngscopeclient/Dialog.cpp +++ b/src/ngscopeclient/Dialog.cpp @@ -35,17 +35,23 @@ #include "ngscopeclient.h" #include "Dialog.h" #include "MainWindow.h" +#include "PreferenceTypes.h" using namespace std; +#define CARRIAGE_RETURN_CHAR "⏎" +#define DEFAULT_APPLY_BUTTON_COLOR "#4CCC4C" + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Construction / destruction -Dialog::Dialog(const string& title, const string& id, ImVec2 defaultSize) +Dialog::Dialog(const string& title, const string& id, ImVec2 defaultSize, Session* session, MainWindow* parent) : m_open(true) , m_id(id) , m_title(title) , m_defaultSize(defaultSize) + , m_session(session) + , m_parent(parent) { } @@ -430,6 +436,365 @@ bool Dialog::UnitInputWithExplicitApply( return changed; } +/** + @brief Render a numeric value + @param value the string representation of the value to display (may include the unit) + @param clicked output value for clicked state + @param hovered output value for hovered state + @param color the color to use (defaults to white) + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @param digitHeight the height of a digit (if 0 (defualt), will use ImGui::GetFontSize()) + @param clickable true (default) if the displayed value should be clickable + */ +void Dialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color, bool allow7SegmentDisplay, float digitHeight, bool clickable) +{ + bool use7Segment = false; + bool changeFont = false; + int64_t displayType = NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT; + if(m_session) + { + auto& prefs = m_session->GetPreferences(); + displayType = prefs.GetEnumRaw("Appearance.Stream Browser.numeric_value_display"); + } + FontWithSize font; + if(allow7SegmentDisplay) + { + use7Segment = (displayType == NumericValueDisplay::NUMERIC_DISPLAY_7SEGMENT); + if(!use7Segment && m_parent) + { + font = m_parent->GetFontPref(displayType == NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT ? "Appearance.General.default_font" : "Appearance.General.console_font"); + changeFont = true; + } + } + if(use7Segment) + { + if(digitHeight <= 0) digitHeight = ImGui::GetFontSize(); + Render7SegmentValue(value,color,digitHeight,clicked,hovered,clickable); + } + else + { + if(clickable) + { + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); + ImGui::TextUnformatted(value.c_str()); + if(changeFont) ImGui::PopFont(); + ImGui::PopStyleColor(); + + clicked |= ImGui::IsItemClicked(); + if(ImGui::IsItemHovered()) + { // Hand cursor + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + // Lighter if hovered + color.x = color.x * 1.2f; + color.y = color.y * 1.2f; + color.z = color.z * 1.2f; + ImGui::SetCursorPos(pos); + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(value.c_str()); + ImGui::PopStyleColor(); + hovered = true; + } + } + else + { + ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); + ImGui::TextUnformatted(value.c_str()); + if(changeFont) ImGui::PopFont(); + ImGui::PopStyleColor(); + } + } +} + +/** + @brief Render a read-only instrument property value + @param label the value label (used as a label for the property) + @param currentValue the string representation of the current value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) +*/ +void Dialog::renderReadOnlyProperty(float width, const string& label, const string& value, const char* tooltip) +{ + ImGui::PushID(label.c_str()); // Prevent collision if several sibling links have the same linktext + float fontSize = ImGui::GetFontSize(); + if(width <= 0) width = 6*fontSize; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 bg = style.Colors[ImGuiCol_FrameBg]; + ImGui::PushStyleColor(ImGuiCol_ChildBg, bg); + ImGui::BeginChild("##readOnlyValue", ImVec2(width, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); + ImGui::TextUnformatted(value.c_str()); + ImGui::EndChild(); + ImGui::PopStyleColor(); + ImGui::SameLine(); + ImGui::TextUnformatted(label.c_str()); + ImGui::PopID(); + if(tooltip) + { + HelpMarker(tooltip); + } +} + + +template +/** + @brief Render an editable numeric value + @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) + @param label the value label (used as a label for the TextInput) + @param currentValue the string representation of the current value + @param comittedValue the last comitted typed (float, double or int64_t) value + @param unit the Unit of the value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) + @param color the color to use + @param clicked output value for clicked state + @param hovered output value for hovered state + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) + @return true if the value has changed + */ +bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) +{ + static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports float or double"); + bool use7Segment = false; + bool changeFont = false; + int64_t displayType = NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT; + ImVec4 buttonColor; + if(m_session) + { + auto& prefs = m_session->GetPreferences(); + displayType = prefs.GetEnumRaw("Appearance.Stream Browser.numeric_value_display"); + buttonColor = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.General.apply_button_color")); + } + else + { + buttonColor = ImGui::ColorConvertU32ToFloat4(ColorFromString(DEFAULT_APPLY_BUTTON_COLOR)); + } + FontWithSize font; + if(allow7SegmentDisplay) + { + use7Segment = (displayType == NumericValueDisplay::NUMERIC_DISPLAY_7SEGMENT); + if(!use7Segment && m_parent) + { + font = m_parent->GetFontPref(displayType == NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT ? "Appearance.General.default_font" : "Appearance.General.console_font"); + changeFont = true; + } + } + + bool changed = false; + bool validateChange = false; + bool cancelEdit = false; + bool keepEditing = false; + bool dirty; + float fontSize = ImGui::GetFontSize(); + if(width <= 0) width = 6*fontSize; + ImGui::SetNextItemWidth(width); + if constexpr (std::is_same_v) + dirty = unit.PrettyPrintInt64(committedValue) != currentValue; + else + dirty = unit.PrettyPrint(committedValue) != currentValue; + string editLabel = label+"##Edit"; + ImGuiID editId = ImGui::GetID(editLabel.c_str()); + ImGuiID labelId = ImGui::GetID(label.c_str()); + if(m_editedItemId == editId) + { // Item currently beeing edited + ImGui::BeginGroup(); + float inputXPos = ImGui::GetCursorPosX(); + ImGuiContext& g = *GImGui; + float inputWidth = g.NextItemData.Width; + // Allow overlap for apply button + ImGui::PushItemFlag(ImGuiItemFlags_AllowOverlap, true); + ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); + if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) + { // Input validated (but no apply button) + if(!explicitApply) + { // Implcit apply => validate change + validateChange = true; + } + else + { // Explicit apply needed => keep editing + keepEditing = true; + } + } + if(changeFont) ImGui::PopFont(); + ImGui::PopStyleColor(); + ImGui::PopItemFlag(); + if(explicitApply) + { // Add Apply button + float buttonWidth = ImGui::GetFontSize() * 2; + // Position the button just before the right side of the text input + ImGui::SameLine(inputXPos+inputWidth-ImGui::GetCursorPosX()-buttonWidth+2*ImGui::GetStyle().ItemInnerSpacing.x); + ImVec4 buttonColorHovered = buttonColor; + float bgmul = 0.8f; + ImVec4 buttonColorDefault = ImVec4(buttonColor.x*bgmul, buttonColor.y*bgmul, buttonColor.z*bgmul, buttonColor.w); + bgmul = 0.7f; + ImVec4 buttonColorActive = ImVec4(buttonColor.x*bgmul, buttonColor.y*bgmul, buttonColor.z*bgmul, buttonColor.w); + ImGui::PushStyleColor(ImGuiCol_Button, buttonColorDefault); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); + ImGui::BeginDisabled(!dirty); + if(ImGui::Button(CARRIAGE_RETURN_CHAR)) // Carriage return symbol + { // Apply button click + validateChange = true; + } + ImGui::EndDisabled(); + if(dirty && ImGui::IsItemHovered()) + { // Help to explain apply button + m_parent->AddStatusHelp("mouse_lmb", "Apply value changes and send them to the instrument"); + } + ImGui::PopStyleColor(3); + } + if(!validateChange) + { + if(keepEditing) + { // Give back focus to test input + ImGui::ActivateItemByID(editId); + } + else if(ImGui::IsKeyPressed(ImGuiKey_Escape)) + { // Detect escape => stop editing + cancelEdit = true; + //Prevent focus from going to parent node + ImGui::ActivateItemByID(0); + } + else if((ImGui::GetActiveID() != editId) && (!explicitApply || !ImGui::IsItemActive() /* This is here to prevent detecting focus lost when apply button is clicked */)) + { // Detect focus lost => stop editing too + if(explicitApply) + { // Cancel on focus lost + cancelEdit = true; + } + else + { // Validate on focus list + validateChange = true; + } + } + } + ImGui::EndGroup(); + } + else + { + if(m_lastEditedItemId == editId) + { // Focus lost + if(explicitApply) + { // Cancel edit + cancelEdit = true; + } + else + { // Validate change + validateChange = true; + } + m_lastEditedItemId = 0; + } + bool clicked = false; + bool hovered = false; + if(use7Segment) + { + ImGui::PushID(labelId); + Render7SegmentValue(currentValue,color,ImGui::GetFontSize(),clicked,hovered); + ImGui::PopID(); + } + else + { + ImGui::PushStyleColor(ImGuiCol_Text, color); + if(changeFont) ImGui::PushFont(font.first, font.second); + ImGui::InputText(label.c_str(),¤tValue,ImGuiInputTextFlags_ReadOnly); + if(changeFont) ImGui::PopFont(); + ImGui::PopStyleColor(); + clicked |= ImGui::IsItemClicked(); + if(ImGui::IsItemHovered()) + { // Keep hand cursor while read-only + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + hovered = true; + } + } + + if (clicked) + { + m_lastEditedItemId = m_editedItemId; + m_editedItemId = editId; + ImGui::ActivateItemByID(editId); + } + if (hovered) + m_parent->AddStatusHelp("mouse_lmb", "Edit value"); + } + if(validateChange) + { + if(m_editedItemId == editId) + { + m_lastEditedItemId = 0; + m_editedItemId = 0; + } + if(dirty) + { // Content actually changed + if constexpr (std::is_same_v) + { + //Float path if the user input a decimal value like "3.5G" + if(currentValue.find(".") != string::npos) + committedValue = unit.ParseString(currentValue); + //Integer path otherwise for full precision + else + committedValue = unit.ParseStringInt64(currentValue); + + currentValue = unit.PrettyPrintInt64(committedValue); + } + else + { + committedValue = static_cast(unit.ParseString(currentValue)); + if constexpr (std::is_same_v) + currentValue = unit.PrettyPrintInt64(committedValue); + else + currentValue = unit.PrettyPrint(committedValue); + } + changed = true; + } + } + else if(cancelEdit) + { // Restore value + if constexpr (std::is_same_v) + currentValue = unit.PrettyPrintInt64(committedValue); + else + currentValue = unit.PrettyPrint(committedValue); + if(m_editedItemId == editId) + { + m_lastEditedItemId = 0; + m_editedItemId = 0; + } + } + if(tooltip) + { + HelpMarker(tooltip); + } + return changed; +} + +template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, float& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply); +template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, double& committedValue, Unit unit, const char* tooltip, ImVec4 color, 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, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply); + +template +/** + @brief Render an editable numeric value with explicit apply (if the input value needs to explicitly be applied by clicking the apply button) + @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) + @param label the value label (used as a label for the TextInput) + @param currentValue the string representation of the current value + @param comittedValue the last comitted typed (float, double or int64_t) value + @param unit the Unit of the value + @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) + @param color the color to use + @param clicked output value for clicked state + @param hovered output value for hovered state + @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format + @return true if the value has changed + */ +bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay) +{ + return renderEditableProperty(width,label,currentValue,committedValue,unit,tooltip,color,allow7SegmentDisplay,true); +} + +template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, float& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay); +template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, double& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay); +template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, int64_t& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay); + + /** @brief Segment on/off state for each of the 10 digits + "L" (needed for OL / Overload) 0b01000000 : Top h segment diff --git a/src/ngscopeclient/Dialog.h b/src/ngscopeclient/Dialog.h index 42a569a7..4b8b7dac 100644 --- a/src/ngscopeclient/Dialog.h +++ b/src/ngscopeclient/Dialog.h @@ -37,13 +37,15 @@ #include "imgui_stdlib.h" +class MainWindow; + /** @brief Generic dialog box or other popup window */ class Dialog { public: - Dialog(const std::string& title, const std::string& id, ImVec2 defaultSize = ImVec2(300, 100) ); + Dialog(const std::string& title, const std::string& id, ImVec2 defaultSize = ImVec2(300, 100),Session* session = nullptr, MainWindow* parent = nullptr); virtual ~Dialog(); virtual bool Render(); @@ -87,6 +89,13 @@ class Dialog std::string& currentValue, float& committedValue, Unit unit); + void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); + void renderReadOnlyProperty(float width, const std::string& label, const std::string& value, const char* tooltip = nullptr); + template + bool renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, ImVec4 color = ImVec4(1, 1, 1, 1), 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, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); + public: static void Tooltip(const std::string& str, bool allowDisabled = false); static void HelpMarker(const std::string& str); @@ -107,6 +116,17 @@ class Dialog std::string m_errorPopupTitle; std::string m_errorPopupMessage; + + ///@brief optionnal reference to session + Session* m_session; + ///@brief optionnal reference to parent MainWindow + MainWindow* m_parent; + + + ///@brief Id of the item currently beeing edited + ImGuiID m_editedItemId = 0; + ///@brief Id of the last edited item + ImGuiID m_lastEditedItemId = 0; }; #endif diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index 9adb7cda..b28b2223 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -242,10 +242,6 @@ void PreferenceManager::InitializeDefaults() Preference::Color("instrument_off_badge_color", ColorFromString("#808000ff")) .Label("Instrument off badge color") .Description("Color for instrument 'off' badge")); - stream.AddPreference( - Preference::Color("apply_button_color", ColorFromString("#4CCC4C")) - .Label("Apply button color") - .Description("Color for the apply value button")); stream.AddPreference( Preference::Color("psu_cv_badge_color", ColorFromString("#4CCC4C")) .Label("PSU cv badge color") @@ -296,6 +292,10 @@ void PreferenceManager::InitializeDefaults() Preference::Font("console_font", FontDescription(FindDataFile("fonts/DejaVuSansMono.ttf"), 13)) .Label("Console font") .Description("Font used for SCPI console, log viewer and PSU/DMM numeric values in Stream Browser")); + general.AddPreference( + Preference::Color("apply_button_color", ColorFromString("#4CCC4C")) + .Label("Apply button color") + .Description("Color for the apply value button")); auto& graphs = appearance.AddCategory("Graphs"); graphs.AddPreference( diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 7918db8b..3c02313f 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -36,12 +36,10 @@ #include "ngscopeclient.h" #include "StreamBrowserDialog.h" #include "MainWindow.h" -#include "PreferenceTypes.h" using namespace std; #define ELLIPSIS_CHAR "…" -#define CARRIAGE_RETURN_CHAR "⏎" #define PLUS_CHAR "+" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -121,9 +119,7 @@ StreamBrowserTimebaseInfo::StreamBrowserTimebaseInfo(shared_ptr sc // Construction / destruction StreamBrowserDialog::StreamBrowserDialog(Session& session, MainWindow* parent) - : Dialog("Stream Browser", "Stream Browser", ImVec2(550, 400)) - , m_session(session) - , m_parent(parent) + : Dialog("Stream Browser", "Stream Browser", ImVec2(550, 400), &session, parent) { } @@ -157,7 +153,7 @@ void StreamBrowserDialog::startBadgeLine() */ void StreamBrowserDialog::renderInstrumentBadge(std::shared_ptr inst, bool latched, InstrumentBadge badge) { - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); double now = GetTime(); if(latched) { @@ -406,7 +402,7 @@ bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec */ bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff, const char* valueOn, uint8_t cropTextTo, float paddingRight) { - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); ImVec4 color = ImGui::ColorConvertU32ToFloat4( (curValue ? prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color") : @@ -414,343 +410,6 @@ bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, return renderToggle(label, alignRight, color, curValue, valueOff, valueOn, cropTextTo,paddingRight); } -/** - @brief Render a numeric value - @param value the string representation of the value to display (may include the unit) - @param clicked output value for clicked state - @param hovered output value for hovered state - @param color the color to use (defaults to white) - @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format - @param digitHeight the height of a digit (if 0 (defualt), will use ImGui::GetFontSize()) - @param clickable true (default) if the displayed value should be clickable - */ -void StreamBrowserDialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color, bool allow7SegmentDisplay, float digitHeight, bool clickable) -{ - bool use7Segment = false; - bool changeFont = false; - auto& prefs = m_session.GetPreferences(); - auto displayType = prefs.GetEnumRaw("Appearance.Stream Browser.numeric_value_display"); - FontWithSize font; - if(allow7SegmentDisplay) - { - use7Segment = displayType == NumericValueDisplay::NUMERIC_DISPLAY_7SEGMENT; - if(!use7Segment) - { - font = m_parent->GetFontPref(displayType == NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT ? "Appearance.General.default_font" : "Appearance.General.console_font"); - changeFont = true; - } - } - if(use7Segment) - { - if(digitHeight <= 0) digitHeight = ImGui::GetFontSize(); - Render7SegmentValue(value,color,digitHeight,clicked,hovered,clickable); - } - else - { - if(clickable) - { - ImVec2 pos = ImGui::GetCursorPos(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - if(changeFont) ImGui::PushFont(font.first, font.second); - ImGui::TextUnformatted(value.c_str()); - if(changeFont) ImGui::PopFont(); - ImGui::PopStyleColor(); - - clicked |= ImGui::IsItemClicked(); - if(ImGui::IsItemHovered()) - { // Hand cursor - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - // Lighter if hovered - color.x = color.x * 1.2f; - color.y = color.y * 1.2f; - color.z = color.z * 1.2f; - ImGui::SetCursorPos(pos); - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(value.c_str()); - ImGui::PopStyleColor(); - hovered = true; - } - } - else - { - ImGui::PushStyleColor(ImGuiCol_Text, color); - if(changeFont) ImGui::PushFont(font.first, font.second); - ImGui::TextUnformatted(value.c_str()); - if(changeFont) ImGui::PopFont(); - ImGui::PopStyleColor(); - } - } -} - -/** - @brief Render a read-only instrument property value - @param label the value label (used as a label for the property) - @param currentValue the string representation of the current value - @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) -*/ -void StreamBrowserDialog::renderReadOnlyProperty(float width, const string& label, const string& value, const char* tooltip) -{ - ImGui::PushID(label.c_str()); // Prevent collision if several sibling links have the same linktext - float fontSize = ImGui::GetFontSize(); - if(width <= 0) width = 6*fontSize; - ImGuiStyle& style = ImGui::GetStyle(); - ImVec4 bg = style.Colors[ImGuiCol_FrameBg]; - ImGui::PushStyleColor(ImGuiCol_ChildBg, bg); - ImGui::BeginChild("##readOnlyValue", ImVec2(width, ImGui::GetFontSize()),false,ImGuiWindowFlags_None); - ImGui::TextUnformatted(value.c_str()); - ImGui::EndChild(); - ImGui::PopStyleColor(); - ImGui::SameLine(); - ImGui::TextUnformatted(label.c_str()); - ImGui::PopID(); - if(tooltip) - { - HelpMarker(tooltip); - } -} - - -template -/** - @brief Render an editable numeric value - @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) - @param label the value label (used as a label for the TextInput) - @param currentValue the string representation of the current value - @param comittedValue the last comitted typed (float, double or int64_t) value - @param unit the Unit of the value - @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) - @param color the color to use - @param clicked output value for clicked state - @param hovered output value for hovered state - @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format - @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) - @return true if the value has changed - */ -bool StreamBrowserDialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) -{ - static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports float or double"); - auto& prefs = m_session.GetPreferences(); - auto displayType = prefs.GetEnumRaw("Appearance.Stream Browser.numeric_value_display"); - bool use7Segment = false; - bool changeFont = false; - FontWithSize font; - if(allow7SegmentDisplay) - { - use7Segment = displayType == NumericValueDisplay::NUMERIC_DISPLAY_7SEGMENT; - if(!use7Segment) - { - font = m_parent->GetFontPref(displayType == NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT ? "Appearance.General.default_font" : "Appearance.General.console_font"); - changeFont = true; - } - } - - bool changed = false; - bool validateChange = false; - bool cancelEdit = false; - bool keepEditing = false; - bool dirty; - float fontSize = ImGui::GetFontSize(); - if(width <= 0) width = 6*fontSize; - ImGui::SetNextItemWidth(width); - if constexpr (std::is_same_v) - dirty = unit.PrettyPrintInt64(committedValue) != currentValue; - else - dirty = unit.PrettyPrint(committedValue) != currentValue; - string editLabel = label+"##Edit"; - ImGuiID editId = ImGui::GetID(editLabel.c_str()); - ImGuiID labelId = ImGui::GetID(label.c_str()); - if(m_editedItemId == editId) - { // Item currently beeing edited - ImGui::BeginGroup(); - float inputXPos = ImGui::GetCursorPosX(); - ImGuiContext& g = *GImGui; - float inputWidth = g.NextItemData.Width; - // Allow overlap for apply button - ImGui::PushItemFlag(ImGuiItemFlags_AllowOverlap, true); - ImGui::PushStyleColor(ImGuiCol_Text, color); - if(changeFont) ImGui::PushFont(font.first, font.second); - if(ImGui::InputText(editLabel.c_str(), ¤tValue, ImGuiInputTextFlags_EnterReturnsTrue)) - { // Input validated (but no apply button) - if(!explicitApply) - { // Implcit apply => validate change - validateChange = true; - } - else - { // Explicit apply needed => keep editing - keepEditing = true; - } - } - if(changeFont) ImGui::PopFont(); - ImGui::PopStyleColor(); - ImGui::PopItemFlag(); - if(explicitApply) - { // Add Apply button - float buttonWidth = ImGui::GetFontSize() * 2; - // Position the button just before the right side of the text input - ImGui::SameLine(inputXPos+inputWidth-ImGui::GetCursorPosX()-buttonWidth+2*ImGui::GetStyle().ItemInnerSpacing.x); - ImVec4 buttonColorActive = ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.apply_button_color")); - float bgmul = 0.8f; - ImVec4 buttonColorHovered = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); - bgmul = 0.7f; - ImVec4 buttonColor = ImVec4(buttonColorActive.x*bgmul, buttonColorActive.y*bgmul, buttonColorActive.z*bgmul, buttonColorActive.w); - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHovered); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); - ImGui::BeginDisabled(!dirty); - if(ImGui::Button(CARRIAGE_RETURN_CHAR)) // Carriage return symbol - { // Apply button click - validateChange = true; - } - ImGui::EndDisabled(); - if(dirty && ImGui::IsItemHovered()) - { // Help to explain apply button - m_parent->AddStatusHelp("mouse_lmb", "Apply value changes and send them to the instrument"); - } - ImGui::PopStyleColor(3); - } - if(!validateChange) - { - if(keepEditing) - { // Give back focus to test input - ImGui::ActivateItemByID(editId); - } - else if(ImGui::IsKeyPressed(ImGuiKey_Escape)) - { // Detect escape => stop editing - cancelEdit = true; - //Prevent focus from going to parent node - ImGui::ActivateItemByID(0); - } - else if((ImGui::GetActiveID() != editId) && (!explicitApply || !ImGui::IsItemActive() /* This is here to prevent detecting focus lost when apply button is clicked */)) - { // Detect focus lost => stop editing too - if(explicitApply) - { // Cancel on focus lost - cancelEdit = true; - } - else - { // Validate on focus list - validateChange = true; - } - } - } - ImGui::EndGroup(); - } - else - { - if(m_lastEditedItemId == editId) - { // Focus lost - if(explicitApply) - { // Cancel edit - cancelEdit = true; - } - else - { // Validate change - validateChange = true; - } - m_lastEditedItemId = 0; - } - bool clicked = false; - bool hovered = false; - if(use7Segment) - { - ImGui::PushID(labelId); - Render7SegmentValue(currentValue,color,ImGui::GetFontSize(),clicked,hovered); - ImGui::PopID(); - } - else - { - ImGui::PushStyleColor(ImGuiCol_Text, color); - if(changeFont) ImGui::PushFont(font.first, font.second); - ImGui::InputText(label.c_str(),¤tValue,ImGuiInputTextFlags_ReadOnly); - if(changeFont) ImGui::PopFont(); - ImGui::PopStyleColor(); - clicked |= ImGui::IsItemClicked(); - if(ImGui::IsItemHovered()) - { // Keep hand cursor while read-only - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - hovered = true; - } - } - - if (clicked) - { - m_lastEditedItemId = m_editedItemId; - m_editedItemId = editId; - ImGui::ActivateItemByID(editId); - } - if (hovered) - m_parent->AddStatusHelp("mouse_lmb", "Edit value"); - } - if(validateChange) - { - if(m_editedItemId == editId) - { - m_lastEditedItemId = 0; - m_editedItemId = 0; - } - if(dirty) - { // Content actually changed - if constexpr (std::is_same_v) - { - //Float path if the user input a decimal value like "3.5G" - if(currentValue.find(".") != string::npos) - committedValue = unit.ParseString(currentValue); - //Integer path otherwise for full precision - else - committedValue = unit.ParseStringInt64(currentValue); - - currentValue = unit.PrettyPrintInt64(committedValue); - } - else - { - committedValue = static_cast(unit.ParseString(currentValue)); - if constexpr (std::is_same_v) - currentValue = unit.PrettyPrintInt64(committedValue); - else - currentValue = unit.PrettyPrint(committedValue); - } - changed = true; - } - } - else if(cancelEdit) - { // Restore value - if constexpr (std::is_same_v) - currentValue = unit.PrettyPrintInt64(committedValue); - else - currentValue = unit.PrettyPrint(committedValue); - if(m_editedItemId == editId) - { - m_lastEditedItemId = 0; - m_editedItemId = 0; - } - } - if(tooltip) - { - HelpMarker(tooltip); - } - return changed; -} - -template -/** - @brief Render an editable numeric value with explicit apply (if the input value needs to explicitly be applied by clicking the apply button) - @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) - @param label the value label (used as a label for the TextInput) - @param currentValue the string representation of the current value - @param comittedValue the last comitted typed (float, double or int64_t) value - @param unit the Unit of the value - @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) - @param color the color to use - @param clicked output value for clicked state - @param hovered output value for hovered state - @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format - @return true if the value has changed - */ -bool StreamBrowserDialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay) -{ - return renderEditableProperty(width,label,currentValue,committedValue,unit,tooltip,color,allow7SegmentDisplay,true); -} - - /** @brief Render a download progress bar for a given instrument channel @@ -789,7 +448,7 @@ void StreamBrowserDialog::renderDownloadProgress(std::shared_ptr ins bool shouldRender = true; bool hasProgress = false; double elapsed = GetTime() - chan->GetDownloadStartTime(); - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); // determine what label we should apply, and while we are at @@ -920,7 +579,7 @@ bool StreamBrowserDialog::renderPsuRows( bool &hovered) { bool changed = false; - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); // Row 1 ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); @@ -1075,7 +734,7 @@ void StreamBrowserDialog::renderDmmProperties(std::shared_ptr dmm, M if(isMain) { - auto dmmState = m_session.GetDmmState(dmm); + auto dmmState = m_session->GetDmmState(dmm); if(dmmState) { ImGui::PushID("autorange"); @@ -1109,7 +768,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr Unit fs(Unit::UNIT_FS); size_t channelIndex = awgchan->GetIndex(); - auto awgState = m_session.GetFunctionGeneratorState(awg); + auto awgState = m_session->GetFunctionGeneratorState(awg); if(!awgState) return; @@ -1153,7 +812,7 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr awgState->m_strFallTime[channelIndex] = fs.PrettyPrint(fallTime); } - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); // Row 1 ImGui::Text("Waveform:"); @@ -1328,13 +987,13 @@ void StreamBrowserDialog::renderAwgProperties(std::shared_ptr void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument) { // Get preferences for colors - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); ImGui::PushID(instrument.get()); bool instIsOpen = ImGui::TreeNodeEx(instrument->m_nickname.c_str(), ImGuiTreeNodeFlags_DefaultOpen); startBadgeLine(); - auto state = m_session.GetInstrumentConnectionState(instrument); + auto state = m_session->GetInstrumentConnectionState(instrument); size_t channelCount = instrument->GetChannelCount(); @@ -1377,7 +1036,7 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument if (psu) { //Get the state - auto psustate = m_session.GetPSUState(psu); + auto psustate = m_session->GetPSUState(psu); bool allOn = false; bool someOn = false; @@ -1759,7 +1418,7 @@ void StreamBrowserDialog::DoTimebaseSettings(shared_ptr scope) void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, size_t channelIndex, bool isLast) { // Get preferences for colors - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); InstrumentChannel* channel = instrument->GetChannel(channelIndex); @@ -1782,11 +1441,11 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s } else if(awg && awgchan) { - renderProps = m_session.GetFunctionGeneratorState(awg)->m_channelActive[channelIndex]; + renderProps = m_session->GetFunctionGeneratorState(awg)->m_channelActive[channelIndex]; } else if(dmm && dmmchan) { - renderProps = m_session.GetDmmState(dmm)->m_started; + renderProps = m_session->GetDmmState(dmm)->m_started; } bool hasChildren = !singleStream || renderProps; @@ -1869,7 +1528,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s { // PSU Channel //Get the state - auto psustate = m_session.GetPSUState(psu); + auto psustate = m_session->GetPSUState(psu); bool active = psustate->m_channelOn[channelIndex]; bool result = active; @@ -1880,7 +1539,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s else if(awg && awgchan) { // AWG Channel : get the state - auto awgstate = m_session.GetFunctionGeneratorState(awg); + auto awgstate = m_session->GetFunctionGeneratorState(awg); if(renderOnOffToggle("###active", true,awgstate->m_channelActive[channelIndex])) { awg->SetFunctionChannelActive(channelIndex,awgstate->m_channelActive[channelIndex]); @@ -1891,7 +1550,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s else if(dmm && dmmchan) { // DMM Channel : get the state - auto dmmstate = m_session.GetDmmState(dmm); + auto dmmstate = m_session->GetDmmState(dmm); if(renderOnOffToggle("###active", true, dmmstate->m_started)) { @@ -1921,7 +1580,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s auto mcurrent_txt = Unit(Unit::UNIT_AMPS).PrettyPrint(psuchan->GetCurrentMeasured ()); bool cc = false; - auto psuState = m_session.GetPSUState(psu); + auto psuState = m_session->GetPSUState(psu); if(psuState) { cc = psuState->m_channelConstantCurrent[channelIndex].load(); @@ -1965,7 +1624,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s size_t channelCount = instrument->GetChannelCount(); if(channelCount > 1) { - auto dmmState = m_session.GetDmmState(dmm); + auto dmmState = m_session->GetDmmState(dmm); if(dmmState) { ImGui::SetNextItemWidth(6*ImGui::GetFontSize()); @@ -1999,7 +1658,7 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s { if(!singleStream) { - auto scopeState = m_session.GetOscilloscopeState(scope); + auto scopeState = m_session->GetOscilloscopeState(scope); if(scopeState) { if(BeginBlock("stream_params",true,"Open channel properties")) @@ -2162,7 +1821,7 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In } if(hasProps) { - auto scopeState = m_session.GetOscilloscopeState(scope); + auto scopeState = m_session->GetOscilloscopeState(scope); if(scopeState) { // For now, only show properties for scope channel / streams if(BeginBlock("stream_params",true,"Open channel properties")) @@ -2312,7 +1971,7 @@ void StreamBrowserDialog::FlushConfigCache() bool StreamBrowserDialog::DoRender() { //Add all instruments - auto insts = m_session.GetInstruments(); + auto insts = m_session->GetInstruments(); for(auto inst : insts) { renderInstrumentNode(inst); @@ -2341,7 +2000,7 @@ void StreamBrowserDialog::DoItemHelp() bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton, const char* tooltip) { bool clicked = false; - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); ImGuiWindowFlags flags = ImGuiChildFlags_AutoResizeY; bool withBorders = false; if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) @@ -2387,7 +2046,7 @@ bool StreamBrowserDialog::BeginBlock(const char* label, bool withButton, const c void StreamBrowserDialog::EndBlock() { ImGui::EndChild(); - auto& prefs = m_session.GetPreferences(); + auto& prefs = m_session->GetPreferences(); if(prefs.GetBool("Appearance.Stream Browser.show_block_border")) { ImGui::PopStyleVar(); diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index 6d232eea..b4b280a3 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -155,12 +155,6 @@ class StreamBrowserDialog : public Dialog uint8_t cropTextTo = 0, float paddingRight = 0); bool renderOnOffToggle(const char* label, bool alignRight, bool& curValue, const char* valueOff = "OFF", const char* valueOn = "ON", uint8_t cropTextTo = 0, float paddingRight = 0); - void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); - void renderReadOnlyProperty(float width, const std::string& label, const std::string& value, const char* tooltip = nullptr); - template - bool renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, ImVec4 color = ImVec4(1, 1, 1, 1), 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, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); void renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); bool renderPsuRows(bool isVoltage, bool cc, PowerSupplyChannel* chan, std::string& currentValue, float& committedValue, std::string& measuredValue, bool &clicked, bool &hovered); void renderAwgProperties(std::shared_ptr awg, FunctionGeneratorChannel* awgchan); @@ -184,18 +178,10 @@ class StreamBrowserDialog : public Dialog // Rendering of an Filter node void renderFilterNode(Filter* filter); - Session& m_session; - MainWindow* m_parent; - ///@brief Positions for badge display float m_badgeXMin; // left edge over which we must not overrun float m_badgeXCur; // right edge to render the next badge against - ///@brief Id of the item currently beeing edited - ImGuiID m_editedItemId = 0; - ///@brief Id of the last edited item - ImGuiID m_lastEditedItemId = 0; - std::map, bool> m_instrumentDownloadIsSlow; ///@brief Store the last state of an intrument badge (used for badge state latching) std::map, std::pair> m_instrumentLastBadge; From f2ec92e9046eb4f132fd4908b1fb255c04b2f9b0 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 19:09:37 +0100 Subject: [PATCH 47/49] Made color optional and default color to text color rather than white for property display. --- src/ngscopeclient/Dialog.cpp | 69 +++++++++++++++--------------------- src/ngscopeclient/Dialog.h | 6 ++-- 2 files changed, 31 insertions(+), 44 deletions(-) diff --git a/src/ngscopeclient/Dialog.cpp b/src/ngscopeclient/Dialog.cpp index 61eb66b4..15587580 100644 --- a/src/ngscopeclient/Dialog.cpp +++ b/src/ngscopeclient/Dialog.cpp @@ -376,6 +376,7 @@ bool Dialog::UnitInputWithImplicitApply( int64_t& committedValue, Unit unit) { + // return renderEditableProperty(-1,label,currentValue,committedValue,unit,); bool dirty = unit.PrettyPrintInt64(committedValue) != currentValue; ImGui::InputText(label.c_str(), ¤tValue); @@ -413,27 +414,7 @@ bool Dialog::UnitInputWithExplicitApply( float& committedValue, Unit unit) { - bool dirty = unit.PrettyPrint(committedValue) != currentValue; - - ImGui::BeginGroup(); - - ImGui::InputText(label.c_str(), ¤tValue); - ImGui::SameLine(); - if(!dirty) - ImGui::BeginDisabled(); - auto applyLabel = string("Apply###Apply") + label; - bool changed = false; - if(ImGui::Button(applyLabel.c_str())) - { - changed = true; - committedValue = unit.ParseString(currentValue); - currentValue = unit.PrettyPrint(committedValue); - } - if(!dirty) - ImGui::EndDisabled(); - - ImGui::EndGroup(); - return changed; + return renderEditablePropertyWithExplicitApply(-1,label,currentValue,committedValue,unit); } /** @@ -441,16 +422,17 @@ bool Dialog::UnitInputWithExplicitApply( @param value the string representation of the value to display (may include the unit) @param clicked output value for clicked state @param hovered output value for hovered state - @param color the color to use (defaults to white) + @param optcolor the optional color to use (defaults to text color) @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format @param digitHeight the height of a digit (if 0 (defualt), will use ImGui::GetFontSize()) @param clickable true (default) if the displayed value should be clickable */ -void Dialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color, bool allow7SegmentDisplay, float digitHeight, bool clickable) +void Dialog::renderNumericValue(const std::string& value, bool &clicked, bool &hovered, std::optional optcolor, bool allow7SegmentDisplay, float digitHeight, bool clickable) { bool use7Segment = false; bool changeFont = false; int64_t displayType = NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT; + ImVec4 color = optcolor ? optcolor.value() : ImGui::GetStyleColorVec4(ImGuiCol_Text); if(m_session) { auto& prefs = m_session->GetPreferences(); @@ -469,6 +451,7 @@ void Dialog::renderNumericValue(const std::string& value, bool &clicked, bool &h if(use7Segment) { if(digitHeight <= 0) digitHeight = ImGui::GetFontSize(); + Render7SegmentValue(value,color,digitHeight,clicked,hovered,clickable); } else @@ -539,26 +522,27 @@ void Dialog::renderReadOnlyProperty(float width, const string& label, const stri template /** @brief Render an editable numeric value - @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) + @param width the width of the input value (if <0 will be ignored, if =0 will default to 6*ImGui::GetStyle()) @param label the value label (used as a label for the TextInput) @param currentValue the string representation of the current value @param comittedValue the last comitted typed (float, double or int64_t) value @param unit the Unit of the value @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) - @param color the color to use + @param optcolor the optional color to use (defaults to text color) @param clicked output value for clicked state @param hovered output value for hovered state @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format @param explicitApply (defaults to false) true if the input value needs to explicitly be applied (by clicking the apply button) @return true if the value has changed */ -bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply) +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 float or double"); + static_assert(std::is_same_v || std::is_same_v || std::is_same_v,"renderEditableProperty only supports int64_t, float or double"); bool use7Segment = false; bool changeFont = false; int64_t displayType = NumericValueDisplay::NUMERIC_DISPLAY_DEFAULT_FONT; ImVec4 buttonColor; + ImVec4 color = optcolor ? optcolor.value() : ImGui::GetStyleColorVec4(ImGuiCol_Text); if(m_session) { auto& prefs = m_session->GetPreferences(); @@ -586,8 +570,11 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: bool keepEditing = false; bool dirty; float fontSize = ImGui::GetFontSize(); - if(width <= 0) width = 6*fontSize; - ImGui::SetNextItemWidth(width); + if(width >= 0) + { + if(width == 0) width = 6*fontSize; + ImGui::SetNextItemWidth(width); + } if constexpr (std::is_same_v) dirty = unit.PrettyPrintInt64(committedValue) != currentValue; else @@ -638,7 +625,7 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: validateChange = true; } ImGui::EndDisabled(); - if(dirty && ImGui::IsItemHovered()) + if(dirty && ImGui::IsItemHovered() && m_parent) { // Help to explain apply button m_parent->AddStatusHelp("mouse_lmb", "Apply value changes and send them to the instrument"); } @@ -713,7 +700,7 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: m_editedItemId = editId; ImGui::ActivateItemByID(editId); } - if (hovered) + if (hovered && m_parent) m_parent->AddStatusHelp("mouse_lmb", "Edit value"); } if(validateChange) @@ -766,33 +753,33 @@ bool Dialog::renderEditableProperty(float width, const std::string& label, std:: return changed; } -template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, float& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply); -template bool Dialog::renderEditableProperty(float width, const std::string& label, std::string& currentValue, double& committedValue, Unit unit, const char* tooltip, ImVec4 color, 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, ImVec4 color, bool allow7SegmentDisplay, bool explicitApply); +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 /** @brief Render an editable numeric value with explicit apply (if the input value needs to explicitly be applied by clicking the apply button) - @param width the width of the input value (if <=0 will default to 6*ImGui::GetStyle()) + @param width the width of the input value (if <0 will be ignored, if =0 will default to 6*ImGui::GetStyle()) @param label the value label (used as a label for the TextInput) @param currentValue the string representation of the current value @param comittedValue the last comitted typed (float, double or int64_t) value @param unit the Unit of the value @param tooltip if not null, will add the provided text as an help marker (defaults to nullptr) - @param color the color to use + @param optcolor the optional color to use (defaults to text color) @param clicked output value for clicked state @param hovered output value for hovered state @param allow7SegmentDisplay (defaults to false) true if the value can be displayed in 7 segment format @return true if the value has changed */ -bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay) +bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip, std::optional optcolor, bool allow7SegmentDisplay) { - return renderEditableProperty(width,label,currentValue,committedValue,unit,tooltip,color,allow7SegmentDisplay,true); + return renderEditableProperty(width,label,currentValue,committedValue,unit,tooltip,optcolor,allow7SegmentDisplay,true); } -template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, float& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay); -template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, double& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay); -template bool Dialog::renderEditablePropertyWithExplicitApply(float width, const std::string& label, std::string& currentValue, int64_t& committedValue, Unit unit, const char* tooltip, ImVec4 color, bool allow7SegmentDisplay); +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); /** diff --git a/src/ngscopeclient/Dialog.h b/src/ngscopeclient/Dialog.h index 4b8b7dac..7c022b50 100644 --- a/src/ngscopeclient/Dialog.h +++ b/src/ngscopeclient/Dialog.h @@ -89,12 +89,12 @@ class Dialog std::string& currentValue, float& committedValue, Unit unit); - void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); + void renderNumericValue(const std::string& value, bool &clicked, bool &hovered, std::optional optcolor = std::nullopt, bool allow7SegmentDisplay = false, float digitHeight = 0, bool clickable = true); void renderReadOnlyProperty(float width, const std::string& label, const std::string& value, const char* tooltip = nullptr); template - bool renderEditableProperty(float width, const std::string& label, std::string& currentValue, T& committedValue, Unit unit, const char* tooltip = nullptr, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false, bool explicitApply = false); + 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, ImVec4 color = ImVec4(1, 1, 1, 1), bool allow7SegmentDisplay = false); + 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); public: static void Tooltip(const std::string& str, bool allowDisabled = false); From 41daa41887d8196198b1cbe4b9e6ebfa9f243333 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 12 Jan 2026 22:24:08 +0100 Subject: [PATCH 48/49] Made digital threshold property implcit apply. Made all explicit apply property inputs use the new design. --- src/ngscopeclient/ChannelPropertiesDialog.cpp | 14 ++++-- src/ngscopeclient/Dialog.cpp | 49 ------------------- src/ngscopeclient/Dialog.h | 2 - src/ngscopeclient/DigitalIOChannelDialog.cpp | 1 + .../DigitalOutputChannelDialog.cpp | 1 + src/ngscopeclient/LoadDialog.cpp | 3 +- src/ngscopeclient/LoadDialog.h | 3 -- src/ngscopeclient/PowerSupplyDialog.cpp | 3 +- src/ngscopeclient/PowerSupplyDialog.h | 3 -- src/ngscopeclient/RFGeneratorDialog.cpp | 3 +- src/ngscopeclient/RFGeneratorDialog.h | 3 -- src/ngscopeclient/StreamBrowserDialog.cpp | 2 +- 12 files changed, 15 insertions(+), 72 deletions(-) diff --git a/src/ngscopeclient/ChannelPropertiesDialog.cpp b/src/ngscopeclient/ChannelPropertiesDialog.cpp index 92e692a5..7e6add57 100644 --- a/src/ngscopeclient/ChannelPropertiesDialog.cpp +++ b/src/ngscopeclient/ChannelPropertiesDialog.cpp @@ -47,11 +47,15 @@ ChannelPropertiesDialog::ChannelPropertiesDialog(InstrumentChannel* chan, MainWi : BaseChannelPropertiesDialog(chan, parent, graphEditorMode) { // Get oscilloscope state, for that we need to make a shared_ptr out of the base pointer returned by chan->GetInstrument() - Session& session = m_parent->GetSession(); - std::shared_ptr scopeSharedPointer = chan->GetInstrument()->shared_from_this(); - shared_ptr sharedScope = dynamic_pointer_cast(scopeSharedPointer); - if(sharedScope) - m_state = session.GetOscilloscopeState(sharedScope); + m_session = &m_parent->GetSession(); + auto instrument = chan->GetInstrument(); + if(instrument) + { + std::shared_ptr scopeSharedPointer = instrument->shared_from_this(); + shared_ptr sharedScope = dynamic_pointer_cast(scopeSharedPointer); + if(sharedScope) + m_state = m_session->GetOscilloscopeState(sharedScope); + } auto ochan = dynamic_cast(chan); if(!ochan) diff --git a/src/ngscopeclient/Dialog.cpp b/src/ngscopeclient/Dialog.cpp index 15587580..4de5aba4 100644 --- a/src/ngscopeclient/Dialog.cpp +++ b/src/ngscopeclient/Dialog.cpp @@ -223,55 +223,6 @@ void Dialog::HelpMarker(const string& header, const vector& bullets) } } -/** - @brief Helper for displaying a floating-point input box with an "apply" button - */ -bool Dialog::FloatInputWithApplyButton(const string& label, float& currentValue, float& committedValue) -{ - ImGui::BeginGroup(); - - bool dirty = currentValue != committedValue; - ImGui::InputFloat(label.c_str(), ¤tValue); - ImGui::SameLine(); - if(!dirty) - ImGui::BeginDisabled(); - auto applyLabel = string("Apply###Apply") + label; - bool changed = false; - if(ImGui::Button(applyLabel.c_str())) - { - changed = true; - committedValue = currentValue; - } - if(!dirty) - ImGui::EndDisabled(); - - ImGui::EndGroup(); - return changed; -} - -bool Dialog::TextInputWithApplyButton(const string& label, string& currentValue, string& committedValue) -{ - ImGui::BeginGroup(); - - bool dirty = currentValue != committedValue; - ImGui::InputText(label.c_str(), ¤tValue); - ImGui::SameLine(); - if(!dirty) - ImGui::BeginDisabled(); - auto applyLabel = string("Apply###Apply") + label; - bool changed = false; - if(ImGui::Button(applyLabel.c_str())) - { - changed = true; - committedValue = currentValue; - } - if(!dirty) - ImGui::EndDisabled(); - - ImGui::EndGroup(); - return changed; -} - bool Dialog::TextInputWithImplicitApply(const string& label, string& currentValue, string& committedValue) { bool dirty = currentValue != committedValue; diff --git a/src/ngscopeclient/Dialog.h b/src/ngscopeclient/Dialog.h index 7c022b50..3801d645 100644 --- a/src/ngscopeclient/Dialog.h +++ b/src/ngscopeclient/Dialog.h @@ -81,8 +81,6 @@ class Dialog std::string& committedValue); protected: - bool FloatInputWithApplyButton(const std::string& label, float& currentValue, float& committedValue); - bool TextInputWithApplyButton(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, diff --git a/src/ngscopeclient/DigitalIOChannelDialog.cpp b/src/ngscopeclient/DigitalIOChannelDialog.cpp index ce23d5f4..d5cee304 100644 --- a/src/ngscopeclient/DigitalIOChannelDialog.cpp +++ b/src/ngscopeclient/DigitalIOChannelDialog.cpp @@ -53,6 +53,7 @@ DigitalIOChannelDialog::DigitalIOChannelDialog(DigitalIOChannel* chan, MainWindo , m_drive("") , m_committedDrive(0) { + m_session = &parent->GetSession(); m_committedDisplayName = m_channel->GetDisplayName(); m_displayName = m_committedDisplayName; diff --git a/src/ngscopeclient/DigitalOutputChannelDialog.cpp b/src/ngscopeclient/DigitalOutputChannelDialog.cpp index 5e2a0377..c9662114 100644 --- a/src/ngscopeclient/DigitalOutputChannelDialog.cpp +++ b/src/ngscopeclient/DigitalOutputChannelDialog.cpp @@ -51,6 +51,7 @@ DigitalOutputChannelDialog::DigitalOutputChannelDialog(DigitalOutputChannel* cha , m_drive("") , m_committedDrive(0) { + m_session = &parent->GetSession(); m_committedDisplayName = m_channel->GetDisplayName(); m_displayName = m_committedDisplayName; diff --git a/src/ngscopeclient/LoadDialog.cpp b/src/ngscopeclient/LoadDialog.cpp index af74463c..93dedc34 100644 --- a/src/ngscopeclient/LoadDialog.cpp +++ b/src/ngscopeclient/LoadDialog.cpp @@ -45,8 +45,7 @@ LoadDialog::LoadDialog(shared_ptr load, shared_ptr state, S : Dialog( string("Load: ") + load->m_nickname, string("Load: ") + load->m_nickname, - ImVec2(500, 400)) - , m_session(session) + ImVec2(500, 400), session) , m_tstart(GetTime()) , m_load(load) , m_state(state) diff --git a/src/ngscopeclient/LoadDialog.h b/src/ngscopeclient/LoadDialog.h index a1ee8227..03d461e4 100644 --- a/src/ngscopeclient/LoadDialog.h +++ b/src/ngscopeclient/LoadDialog.h @@ -155,9 +155,6 @@ class LoadDialog : public Dialog protected: void ChannelSettings(size_t channel); - ///@brief Session handle so we can remove the load when closed - Session* m_session; - ///@brief Timestamp of when we opened the dialog double m_tstart; diff --git a/src/ngscopeclient/PowerSupplyDialog.cpp b/src/ngscopeclient/PowerSupplyDialog.cpp index 8b895959..118d8ea9 100644 --- a/src/ngscopeclient/PowerSupplyDialog.cpp +++ b/src/ngscopeclient/PowerSupplyDialog.cpp @@ -49,8 +49,7 @@ PowerSupplyDialog::PowerSupplyDialog( : Dialog( string("Power Supply: ") + psu->m_nickname, string("Power Supply: ") + psu->m_nickname, - ImVec2(500, 400)) - , m_session(session) + ImVec2(500, 400), session) , m_masterEnable(psu->GetMasterPowerEnable()) , m_tstart(GetTime()) , m_psu(psu) diff --git a/src/ngscopeclient/PowerSupplyDialog.h b/src/ngscopeclient/PowerSupplyDialog.h index 3a4629bc..ea056657 100644 --- a/src/ngscopeclient/PowerSupplyDialog.h +++ b/src/ngscopeclient/PowerSupplyDialog.h @@ -104,9 +104,6 @@ class PowerSupplyDialog : public Dialog void ChannelSettings(int i, float v, float a, float etime); void AsyncLoadState(); - ///@brief Session handle so we can remove the PSU when closed - Session* m_session; - //@brief Global power enable (if we have one) bool m_masterEnable; diff --git a/src/ngscopeclient/RFGeneratorDialog.cpp b/src/ngscopeclient/RFGeneratorDialog.cpp index 6f44efb9..9e56f832 100644 --- a/src/ngscopeclient/RFGeneratorDialog.cpp +++ b/src/ngscopeclient/RFGeneratorDialog.cpp @@ -153,8 +153,7 @@ RFGeneratorDialog::RFGeneratorDialog( : Dialog( string("RF Generator: ") + generator->m_nickname, string("RF Generator: ") + generator->m_nickname, - ImVec2(400, 350)) - , m_session(session) + ImVec2(400, 350),session) , m_generator(generator) { Unit hz(Unit::UNIT_HZ); diff --git a/src/ngscopeclient/RFGeneratorDialog.h b/src/ngscopeclient/RFGeneratorDialog.h index 1ece330b..3f188538 100644 --- a/src/ngscopeclient/RFGeneratorDialog.h +++ b/src/ngscopeclient/RFGeneratorDialog.h @@ -124,9 +124,6 @@ class RFGeneratorDialog : public Dialog void DoChannel(size_t i); - ///@brief Session handle so we can remove the PSU when closed - Session* m_session; - ///@brief The generator we're controlling std::shared_ptr m_generator; diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 3c02313f..ddf58661 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -1857,7 +1857,7 @@ void StreamBrowserDialog::renderStreamNode(shared_ptr instrument, In { if(scope->IsDigitalThresholdConfigurable()) { - if(renderEditablePropertyWithExplicitApply(0,"Threshold",scopeState->m_strDigitalThreshold[channelIndex],scopeState->m_committedDigitalThreshold[channelIndex],unit)) + if(renderEditableProperty(0,"Threshold",scopeState->m_strDigitalThreshold[channelIndex],scopeState->m_committedDigitalThreshold[channelIndex],unit)) { // Update offset scopechan->SetDigitalThreshold(scopeState->m_committedDigitalThreshold[channelIndex]); scopeState->m_needsUpdate[channelIndex] = true; From af7e613dd2aa02d97853e104764b09837742e6ab Mon Sep 17 00:00:00 2001 From: fredzo Date: Tue, 13 Jan 2026 16:24:59 +0100 Subject: [PATCH 49/49] Fixed typos. --- src/ngscopeclient/Dialog.h | 4 ++-- src/ngscopeclient/InstrumentThread.cpp | 2 +- src/ngscopeclient/StreamBrowserDialog.cpp | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ngscopeclient/Dialog.h b/src/ngscopeclient/Dialog.h index 3801d645..84b1008b 100644 --- a/src/ngscopeclient/Dialog.h +++ b/src/ngscopeclient/Dialog.h @@ -115,9 +115,9 @@ class Dialog std::string m_errorPopupTitle; std::string m_errorPopupMessage; - ///@brief optionnal reference to session + ///@brief optional reference to session Session* m_session; - ///@brief optionnal reference to parent MainWindow + ///@brief optional reference to parent MainWindow MainWindow* m_parent; diff --git a/src/ngscopeclient/InstrumentThread.cpp b/src/ngscopeclient/InstrumentThread.cpp index 66fe607f..a1fad710 100644 --- a/src/ngscopeclient/InstrumentThread.cpp +++ b/src/ngscopeclient/InstrumentThread.cpp @@ -174,7 +174,7 @@ void InstrumentThread(InstrumentThreadArgs args) } // Get probe name scopestate->m_probeName[i] = scope->GetProbeName(i); - // Popilate bandwidth limit values + // Populate bandwidth limit values auto limit = scope->GetChannelBandwidthLimit(i); scopestate->m_bandwidthLimits[i].clear(); scopestate->m_bandwidthLimitNames[i].clear(); diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index ddf58661..9e003280 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -371,9 +371,9 @@ bool StreamBrowserDialog::renderCombo( @param alignRight true if the combo should be aligned to the right @param color the color of the toggle button @param curValue the value of the toggle button - @param valueOff label for value off (optionnal, defaults to "OFF") - @param valueOn label for value on (optionnal, defaults to "ON") - @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) + @param valueOff label for value off (optional, defaults to "OFF") + @param valueOn label for value on (optional, defaults to "ON") + @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optional, defaults to 0) @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) @return true if selection has changed */ @@ -394,9 +394,9 @@ bool StreamBrowserDialog::renderToggle(const char* label, bool alignRight, ImVec @param label Label for the combo box @param alignRight true if the combo should be aligned to the right @param curValue the value of the toggle button - @param valueOff label for value off (optionnal, defaults to "OFF") - @param valueOn label for value on (optionnal, defaults to "ON") - @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optionnal, defaults to 0) + @param valueOff label for value off (optional, defaults to "OFF") + @param valueOn label for value on (optional, defaults to "ON") + @param cropTextTo if >0 crop the combo text up to this number of characters to have it fit the available space (optional, defaults to 0) @param paddingRight the padding to leave at the right of the combo when alighRight is true (defaults to 0) @return true if value has changed */