diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index b5395011..d5348a38 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -1283,14 +1283,39 @@ void MainWindow::DockingArea() } ImGui::DockBuilderDockWindow(group->GetID().c_str(), node->ID); - //Add a new waveform area for our stream to the new group - LogTrace("Making new area for %s in %s\n", - request.m_stream.GetName().c_str(), - group->GetID().c_str()); - auto area = make_shared(request.m_stream, group, this); - if(request.m_ramp != "") - area->GetDisplayedChannel(0)->m_colorRamp = request.m_ramp; - group->AddArea(area); + //Add a new waveform area for our stream/streamGroup to the new group + if(request.m_stream) + { + LogTrace("Making new area for %s in %s\n", + request.m_stream.GetName().c_str(), + group->GetID().c_str()); + auto area = make_shared(request.m_stream, group, this); + if(request.m_ramp != "") + area->GetDisplayedChannel(0)->m_colorRamp = request.m_ramp; + group->AddArea(area); + } + else if(request.m_streamGroup) + { + std::shared_ptr area; + bool first = true; + for(auto channel : request.m_streamGroup->m_channels) + { + StreamDescriptor s(channel, 0); + LogTrace("Making new area for %s in %s\n", + s.GetName().c_str(), + group->GetID().c_str()); + if(first || !request.m_singleArea) + { + area = make_shared(s, group, this); + group->AddArea(area); + first = false; + } + else + { + area->AddStream(s); + } + } + } } //Finish up diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index 348441a4..5773afc0 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -70,30 +70,68 @@ class SplitGroupRequest , m_direction(direction) , m_stream(stream) , m_ramp(ramp) + , m_streamGroup(nullptr) { auto schan = dynamic_cast(stream.m_channel); if(schan) schan->AddRef(); } + SplitGroupRequest(std::shared_ptr group, ImGuiDir direction, std::shared_ptr streamGroup, bool singleArea) + : m_group(group) + , m_direction(direction) + , m_streamGroup(streamGroup) + , m_singleArea(singleArea) + { + if(streamGroup) + { + for(auto chan : streamGroup->m_channels) + { + chan->AddRef(); + } + } + } + SplitGroupRequest(const SplitGroupRequest& rhs) : m_group(rhs.m_group) , m_direction(rhs.m_direction) , m_stream(rhs.m_stream) , m_ramp(rhs.m_ramp) + , m_streamGroup(rhs.m_streamGroup) + , m_singleArea(rhs.m_singleArea) { - auto schan = dynamic_cast(rhs.m_stream.m_channel); - if(schan) - schan->AddRef(); + if(rhs.m_stream) + { + auto schan = dynamic_cast(rhs.m_stream.m_channel); + if(schan) + schan->AddRef(); + } + else if(rhs.m_streamGroup) + { + for(auto chan : rhs.m_streamGroup->m_channels) + { + chan->AddRef(); + } + } } SplitGroupRequest& operator=(const SplitGroupRequest& /*rhs*/) =delete; ~SplitGroupRequest() { - auto schan = dynamic_cast(m_stream.m_channel); - if(schan) - schan->Release(); + if(m_stream) + { + auto schan = dynamic_cast(m_stream.m_channel); + if(schan) + schan->Release(); + } + else if(m_streamGroup) + { + for(auto chan : m_streamGroup->m_channels) + { + chan->Release(); + } + } } std::shared_ptr m_group; @@ -102,6 +140,9 @@ class SplitGroupRequest ///@brief Color ramp request (may be blank if unspecified) std::string m_ramp; + + std::shared_ptr m_streamGroup; + bool m_singleArea; }; /** @@ -142,6 +183,9 @@ class MainWindow : public VulkanWindow std::string colorRamp) { m_splitRequests.push_back(SplitGroupRequest(group, direction, stream, colorRamp)); } + void QueueSplitGroup(std::shared_ptr group, ImGuiDir direction, std::shared_ptr streamGroup, bool singleArea) + { m_splitRequests.push_back(SplitGroupRequest(group, direction, streamGroup, singleArea)); } + void ShowChannelProperties(OscilloscopeChannel* channel); void ShowInstrumentProperties(std::shared_ptr instrument); void ShowTriggerProperties(); diff --git a/src/ngscopeclient/StreamBrowserDialog.cpp b/src/ngscopeclient/StreamBrowserDialog.cpp index 6a283862..85660581 100644 --- a/src/ngscopeclient/StreamBrowserDialog.cpp +++ b/src/ngscopeclient/StreamBrowserDialog.cpp @@ -416,8 +416,10 @@ bool StreamBrowserDialog::renderOnOffToggle(const char* label, bool alignRight, @param inst the instrument to render the progress channel for @param chan the channel to render the progress for @param isLast true if it is the last channel of the instrument + + @return Returns true if the progress bar has been rendered */ -void StreamBrowserDialog::renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast) +bool StreamBrowserDialog::renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast) { static const char* const download[] = {"DOWNLOADING", "DOWNLOAD" ,"DL","D", NULL}; @@ -516,7 +518,7 @@ void StreamBrowserDialog::renderDownloadProgress(std::shared_ptr ins } if (!shouldRender) - return; + return false; /// @brief Width used to display progress bars (e.g. download progress bar) #define PROGRESS_BAR_WIDTH 80 @@ -548,10 +550,11 @@ void StreamBrowserDialog::renderDownloadProgress(std::shared_ptr ins ImGui::ProgressBar(chan->GetDownloadProgress(), ImVec2(PROGRESS_BAR_WIDTH, ImGui::GetFontSize())); } - return; + return true; } } // well, shoot -- I guess there wasn't enough room to do *anything* useful! + return true; } /** @@ -1058,10 +1061,11 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument } } bool result; + bool changed; if(allOn || someOn) { result = true; - renderToggle( + changed = renderToggle( "###psuon", true, allOn ? @@ -1071,9 +1075,9 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument else { result = false; - renderOnOffToggle("###psuon", true, result); + changed = renderOnOffToggle("###psuon", true, result); } - if(result != allOn) + if(changed) { if(psu->SupportsMasterOutputSwitching()) psu->SetMasterPowerEnable(result); @@ -1135,12 +1139,74 @@ void StreamBrowserDialog::renderInstrumentNode(shared_ptr instrument renderChannelNode(instrument,i,(i == lastEnabledChannelIndex)); } int bankNumber = 1; + bool bankIsOpen; for(auto bank : digitalBanks) { // Iterate on digital banks if(bank.size() > 1) { // Only show Digital Bank node if there is more than on channel in the bank string nodeName = "Digital Bank " + to_string(bankNumber); - if(ImGui::TreeNodeEx(nodeName.c_str())) + bankIsOpen = ImGui::TreeNodeEx(nodeName.c_str()); + + // Add dragdrop source for this bank + if(ImGui::BeginDragDropSource()) + { + m_streamGroupDesciptor = make_shared(nodeName, bank); + auto ptr = m_streamGroupDesciptor.get(); + ImGui::SetDragDropPayload("StreamGroup", &ptr, sizeof(m_streamGroupDesciptor)); + ImGui::TextUnformatted(m_streamGroupDesciptor->GetName().c_str()); + ImGui::EndDragDropSource(); + } + else + DoItemHelp(); + /* Currently, enable/disable state is coupled to node reference counting, so we can't let the user manually enable/disable channels + // Add Banck on/off toggle + startBadgeLine(); + bool allOn = true; + bool someOn = false; + for(auto channel : bank) + { // Iterate on bank's channel + size_t i = channel->GetIndex(); + if(scope->IsChannelEnabled(i)) + { + someOn = true; + } + else + { + allOn = false; + } + } + bool result; + bool changed; + string toggleId = "###"+nodeName+"on"; + if(allOn || someOn) + { + result = true; + changed = renderToggle( + toggleId.c_str(), + 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,"DISABLE","ENABLED",3); + } + else + { + result = false; + changed = renderToggle( + toggleId.c_str(), + true, + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_off_badge_color")), + result,"DISABLED","ENABLE",3); + } + if(changed) + { + for(auto channel : bank) + { // Iterate on bank's channel + size_t i = channel->GetIndex(); + result ? scope->EnableChannel(i) : scope->DisableChannel(i); + } + } + */ + if(bankIsOpen) { ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); for(auto channel : bank) @@ -1525,17 +1591,53 @@ void StreamBrowserDialog::renderChannelNode(shared_ptr instrument, s startBadgeLine(); if (scopechan) { + bool chanEnabled = scopechan->IsEnabled(); + //"trigger" badge on trigger inputs to show they're not displayable channels if(scopechan->GetType(0) == Stream::STREAM_TYPE_TRIGGER) renderBadge(ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_disabled_badge_color")), "TRIG ONLY", "TRIG","--", nullptr); + /* Currently, enable/disable state is coupled to node reference counting, so we can't let the user manually enable/disable channels // Scope channel - else if (!scopechan->IsEnabled()) - renderBadge(ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_disabled_badge_color")), "DISABLED", "DISA","--", nullptr); + else if (!chanEnabled) + { + if(renderToggle( + "###scopeChanEnable", + true, + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_disabled_badge_color")), + chanEnabled, + "DISABLED", + "ENABLE", + 3)) + { + if(chanEnabled) + scope->EnableChannel(channelIndex); + } + //renderBadge(ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_disabled_badge_color")), "DISABLED", "DISA","--", nullptr); + } + */ //Download in progress else - renderDownloadProgress(instrument, channel, isLast); + { + if(!renderDownloadProgress(instrument, channel, isLast)) + { // No download in progress, we can show the ENABLE/DISABLE toggle + /* Currently, enable/disable state is coupled to node reference counting, so we can't let the user manually enable/disable channels + if(renderToggle( + "###scopeChanEnable", + true, + ImGui::ColorConvertU32ToFloat4(prefs.GetColor("Appearance.Stream Browser.instrument_on_badge_color")), + chanEnabled, + "DISABLE", + "ENABLED", + 3)) + { + if(!chanEnabled) + scope->DisableChannel(channelIndex); + } + */ + } + } } else if(psu) { diff --git a/src/ngscopeclient/StreamBrowserDialog.h b/src/ngscopeclient/StreamBrowserDialog.h index b4b280a3..50092af5 100644 --- a/src/ngscopeclient/StreamBrowserDialog.h +++ b/src/ngscopeclient/StreamBrowserDialog.h @@ -155,7 +155,7 @@ 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 renderDownloadProgress(std::shared_ptr inst, InstrumentChannel *chan, bool isLast); + bool 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); void renderDmmProperties(std::shared_ptr dmm, MultimeterChannel* dmmchan, bool isMain, bool &clicked, bool &hovered); @@ -189,6 +189,9 @@ class StreamBrowserDialog : public Dialog ///@brief Map of instruments to timebase settings std::map, std::shared_ptr > m_timebaseConfig; + ///@brief Reference to currently dragged StreamGroupDesciptor + std::shared_ptr m_streamGroupDesciptor; + ///@brief Helper to render a small button that's non-interactive void SmallDisabledButton(const char* label) { diff --git a/src/ngscopeclient/WaveformArea.cpp b/src/ngscopeclient/WaveformArea.cpp index 82bd2382..3184bc79 100644 --- a/src/ngscopeclient/WaveformArea.cpp +++ b/src/ngscopeclient/WaveformArea.cpp @@ -3340,7 +3340,8 @@ void WaveformArea::EdgeDropArea(const string& name, ImVec2 start, ImVec2 size, I bool isWaveform = payload->IsDataType("Waveform"); bool isStream = payload->IsDataType("Stream"); - if(!isWaveform && !isStream) + bool isStreamGroup = payload->IsDataType("StreamGroup"); + if(!isWaveform && !isStream && !isStreamGroup) return; //Add drop target @@ -3380,6 +3381,20 @@ void WaveformArea::EdgeDropArea(const string& name, ImVec2 start, ImVec2 size, I } } + auto sgpay = ImGui::AcceptDragDropPayload("StreamGroup", ImGuiDragDropFlags_AcceptPeekOnly); + if(sgpay) + { + StreamGroupDescriptor* streamGroup = *reinterpret_cast(sgpay->Data); + bool singleArea = ImGui::IsKeyDown(ImGuiKey_LeftShift)||ImGui::IsKeyDown(ImGuiKey_RightShift); + hover = true; + + //Add request to split our current group + if(payload->IsDelivery()) + { + LogTrace("splitting\n"); + m_parent->QueueSplitGroup(m_group, splitDir, streamGroup->shared_from_this(), singleArea); + } + } ImGui::EndDragDropTarget(); } @@ -3474,7 +3489,8 @@ void WaveformArea::CenterLeftDropArea(ImVec2 start, ImVec2 size) return; bool isWaveform = payload->IsDataType("Waveform"); bool isStream = payload->IsDataType("Stream"); - if(!isWaveform && !isStream) + bool isStreamGroup = payload->IsDataType("StreamGroup"); + if(!isWaveform && !isStream &&!isStreamGroup) return; //Peek the payload. If not compatible, don't even display the target @@ -3539,12 +3555,35 @@ void WaveformArea::CenterLeftDropArea(ImVec2 start, ImVec2 size) //Reject streams not compatible with this plot //TODO: display nice error message if not - if(!IsCompatible(stream)) + if(!IsCompatible(stream)||IsShowing(stream)) ok = false; else if(payload->IsDelivery()) AddStream(stream); } + //Accept stream group + auto sgpay = ImGui::AcceptDragDropPayload("StreamGroup", + ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect); + if(sgpay) + { + hover = true; + StreamGroupDescriptor* streamGroup = *reinterpret_cast(sgpay->Data); + //Reject streams not compatible with this plot + //TODO: display nice error message if not + ok = false; + for(auto channel : streamGroup->m_channels) + { + StreamDescriptor s(channel, 0); + if(IsCompatible(s) && !IsShowing(s)) + { // At least one stream is compatible + ok = true; + if(payload->IsDelivery()) + { + AddStream(s); + } + } + } + } ImGui::EndDragDropTarget(); } @@ -3605,7 +3644,8 @@ void WaveformArea::CenterRightDropArea(ImVec2 start, ImVec2 size) return; bool isWaveform = payload->IsDataType("Waveform"); bool isStream = payload->IsDataType("Stream"); - if(!isWaveform && !isStream) + bool isStreamGroup = payload->IsDataType("StreamGroup"); + if(!isWaveform && !isStream && !isStreamGroup) return; //Add drop target @@ -3662,6 +3702,35 @@ void WaveformArea::CenterRightDropArea(ImVec2 start, ImVec2 size) } } + //Accept drag/drop payloads for digital banks + auto sgpay = ImGui::AcceptDragDropPayload("StreamGroup", ImGuiDragDropFlags_AcceptPeekOnly); + if(sgpay) + { + hover = true; + StreamGroupDescriptor* streamGroup = *reinterpret_cast(sgpay->Data); + + if( (streamGroup->GetXAxisUnits() == m_group->GetXAxisUnit()) && payload->IsDelivery() ) + { + std::shared_ptr area; + bool first = true; + bool singleArea = ImGui::IsKeyDown(ImGuiKey_LeftShift)||ImGui::IsKeyDown(ImGuiKey_RightShift); + for(auto channel : streamGroup->m_channels) + { + StreamDescriptor s(channel, 0); + if(first || !singleArea) + { + area = make_shared(s, m_group, m_parent); + m_group->AddArea(area); + first = false; + } + else + { + area->AddStream(s); + } + } + } + } + ImGui::EndDragDropTarget(); } @@ -4422,6 +4491,19 @@ bool WaveformArea::IsCompatible(StreamDescriptor desc) return true; } +/** + @brief Checks if provided stream descriptor is already present in this waveform area + */ +bool WaveformArea::IsShowing(StreamDescriptor desc) +{ + for(auto channel : m_displayedChannels) + { + if(channel->GetStream() == desc) + return true; + } + return false; +} + /** @brief Checks if this area is currently displaying a provided stream */ diff --git a/src/ngscopeclient/WaveformArea.h b/src/ngscopeclient/WaveformArea.h index 963114d7..4f28ca4f 100644 --- a/src/ngscopeclient/WaveformArea.h +++ b/src/ngscopeclient/WaveformArea.h @@ -488,6 +488,8 @@ class WaveformArea : public SerializableObject bool IsCompatible(StreamDescriptor desc); + bool IsShowing(StreamDescriptor desc); + void RemoveStream(size_t i); void ClearPersistence();