diff --git a/CMakeLists.txt b/CMakeLists.txt index dcc00352..0a342391 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,14 @@ option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON) option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON) option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON) +# Compiler arguments for each platform +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup") +else () + set(CMAKE_CXX_FLAGS "-O3 -std=c++17") +endif () # Core of SDR++ add_subdirectory("core") @@ -137,15 +145,6 @@ if (OPT_BUILD_RECORDER) add_subdirectory("recorder") endif (OPT_BUILD_RECORDER) - -if (MSVC) - set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") -elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup") -else () - set(CMAKE_CXX_FLAGS "-O3 -std=c++17") -endif () - add_executable(sdrpp "src/main.cpp" "win32/resources.rc") target_link_libraries(sdrpp PRIVATE sdrpp_core) diff --git a/core/src/gui/widgets/waterfall.cpp b/core/src/gui/widgets/waterfall.cpp index ad36cd04..48edbff0 100644 --- a/core/src/gui/widgets/waterfall.cpp +++ b/core/src/gui/widgets/waterfall.cpp @@ -205,6 +205,7 @@ namespace ImGui { if (IS_IN_AREA(mPos, wfMin, wfMax) && !gui::mainWindow.lockWaterfallControls) { for (auto const& [name, vfo] : vfos) { window->DrawList->AddRectFilled(vfo->wfRectMin, vfo->wfRectMax, vfo->color); + if (!vfo->lineVisible) { continue; } window->DrawList->AddLine(vfo->wfLineMin, vfo->wfLineMax, (name == selectedVFO) ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255)); } } @@ -714,7 +715,22 @@ namespace ImGui { window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 )); window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0); - if (!gui::mainWindow.lockWaterfallControls) { processInputs(); } + if (!gui::mainWindow.lockWaterfallControls) { + inputHandled = false; + InputHandlerArgs args; + args.fftRectMin = fftAreaMin; + args.fftRectMax = fftAreaMax; + args.freqScaleRectMin = freqAreaMin; + args.freqScaleRectMax = freqAreaMax; + args.waterfallRectMin = wfMin; + args.waterfallRectMax = wfMax; + args.lowFreq = lowerFreq; + args.highFreq = upperFreq; + args.freqToPixelRatio = (double)dataWidth / viewBandwidth; + args.pixelToFreqRatio = viewBandwidth / (double)dataWidth; + onInputProcess.emit(args); + if (!inputHandled) { processInputs(); } + } updateAllVFOs(true); @@ -1001,6 +1017,7 @@ namespace ImGui { vfo->wfLbwSelMax = ImVec2(vfo->wfRectMin.x + 2, vfo->wfRectMax.y); vfo->wfRbwSelMin = ImVec2(vfo->wfRectMax.x - 2, vfo->wfRectMin.y); vfo->wfRbwSelMax = ImVec2(vfo->wfRectMax.x + 2, vfo->wfRectMax.y); + vfo->redrawRequired = false; } } @@ -1102,27 +1119,40 @@ namespace ImGui { int left = roundf((((lowerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0)); int right = roundf((((upperOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0)); + // Check weather the line is visible if (left >= 0 && left < dataWidth && reference == REF_LOWER) { - lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9); - lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9); lineVisible = true; } else if (center >= 0 && center < dataWidth && reference == REF_CENTER) { - lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9); - lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9); lineVisible = true; } else if (right >= 0 && right < dataWidth && reference == REF_UPPER) { - lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9); - lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9); lineVisible = true; } else { lineVisible = false; } + // Calculate the position of the line + if (reference == REF_LOWER) { + lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9); + lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9); + } + else if (reference == REF_CENTER) { + lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9); + lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9); + } + else if (reference == REF_UPPER) { + lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9); + lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9); + } + + int _left = left; + int _right = right; left = std::clamp(left, 0, dataWidth - 1); right = std::clamp(right, 0, dataWidth - 1); + if (left != _left) { leftClamped = true; } + if (right != _right) { rightClamped = true; } rectMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10); rectMax = ImVec2(widgetPos.x + 51 + right, widgetPos.y + fftHeight + 10); @@ -1141,11 +1171,11 @@ namespace ImGui { if (!gui::mainWindow.lockWaterfallControls) { ImVec2 mousePos = ImGui::GetMousePos(); - if (reference != REF_LOWER && !bandwidthLocked) { + if (reference != REF_LOWER && !bandwidthLocked && !leftClamped) { if (IS_IN_AREA(mousePos, lbwSelMin, lbwSelMax)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } else if (IS_IN_AREA(mousePos, wfLbwSelMin, wfLbwSelMax)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } } - if (reference != REF_UPPER && !bandwidthLocked) { + if (reference != REF_UPPER && !bandwidthLocked && !rightClamped) { if (IS_IN_AREA(mousePos, rbwSelMin, rbwSelMax)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } else if (IS_IN_AREA(mousePos, wfRbwSelMin, wfRbwSelMax)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } } diff --git a/core/src/gui/widgets/waterfall.h b/core/src/gui/widgets/waterfall.h index dfa453d4..e36b5727 100644 --- a/core/src/gui/widgets/waterfall.h +++ b/core/src/gui/widgets/waterfall.h @@ -35,6 +35,9 @@ namespace ImGui { double snapInterval = 5000; int reference = REF_CENTER; + bool leftClamped; + bool rightClamped; + ImVec2 rectMin; ImVec2 rectMax; ImVec2 lineMin; @@ -154,6 +157,22 @@ namespace ImGui { Event onFFTRedraw; + struct InputHandlerArgs { + ImVec2 fftRectMin; + ImVec2 fftRectMax; + ImVec2 freqScaleRectMin; + ImVec2 freqScaleRectMax; + ImVec2 waterfallRectMin; + ImVec2 waterfallRectMax; + double lowFreq; + double highFreq; + double freqToPixelRatio; + double pixelToFreqRatio; + }; + + bool inputHandled = false; + Event onInputProcess; + enum { REF_LOWER, REF_CENTER, diff --git a/frequency_manager/src/main.cpp b/frequency_manager/src/main.cpp index 92d7312c..41000f20 100644 --- a/frequency_manager/src/main.cpp +++ b/frequency_manager/src/main.cpp @@ -57,14 +57,18 @@ public: fftRedrawHandler.ctx = this; fftRedrawHandler.handler = fftRedraw; + inputHandler.ctx = this; + inputHandler.handler = fftInput; gui::menu.registerEntry(name, menuHandler, this, NULL); gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler); + gui::waterfall.onInputProcess.bindHandler(&inputHandler); } ~FrequencyManagerModule() { gui::menu.removeEntry(name); gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler); + gui::waterfall.onInputProcess.unbindHandler(&inputHandler); } void enable() { @@ -85,19 +89,28 @@ private: if (freq >= 1000000.0) { sprintf(str, "%.06lf", freq / 1000000.0); int len = strlen(str) - 1; - while ((str[len] == '0' || str[len] == '.') && len > 0) { len--; } + while ((str[len] == '0' || str[len] == '.') && len > 0) { + len--; + if (str[len] == '.') { len--; break; } + } return std::string(str).substr(0, len + 1) + "MHz"; } else if (freq >= 1000.0) { sprintf(str, "%.06lf", freq / 1000.0); int len = strlen(str) - 1; - while ((str[len] == '0' || str[len] == '.') && len > 0) { len--; } + while ((str[len] == '0' || str[len] == '.') && len > 0) { + len--; + if (str[len] == '.') { len--; break; } + } return std::string(str).substr(0, len + 1) + "KHz"; } else { sprintf(str, "%.06lf", freq); int len = strlen(str) - 1; - while ((str[len] == '0' || str[len] == '.') && len > 0) { len--; } + while ((str[len] == '0' || str[len] == '.') && len > 0) { + len--; + if (str[len] == '.') { len--; break; } + } return std::string(str).substr(0, len + 1) + "Hz"; } } @@ -109,7 +122,6 @@ private: gui::waterfall.centerFreqMoved = true; } else { - tuner::tune(tuner::TUNER_MODE_NORMAL, vfoName, bm.frequency); if (core::modComManager.interfaceExists(vfoName)) { if (core::modComManager.getModuleName(vfoName) == "radio") { int mode = bm.mode; @@ -117,6 +129,7 @@ private: // TODO: Set bandwidth as well } } + tuner::tune(tuner::TUNER_MODE_NORMAL, vfoName, bm.frequency); } } @@ -531,6 +544,61 @@ private: } } + bool mouseAlreadyDown = false; + static void fftInput(ImGui::WaterFall::InputHandlerArgs args, void* ctx) { + FrequencyManagerModule* _this = (FrequencyManagerModule*)ctx; + if (!_this->showBookmarksOnFFT) { return; } + + // First check that the mouse clicked outside of any label. Also get the bookmark that's hovered + bool inALabel = false; + FrequencyBookmark hoveredBookmark; + std::string hoveredBookmarkName; + for (auto const [_name, bm] : _this->bookmarks) { + double centerXpos = args.fftRectMin.x + std::round((bm.frequency - args.lowFreq) * args.freqToPixelRatio); + ImVec2 nameSize = ImGui::CalcTextSize(_name.c_str()); + ImVec2 rectMin = ImVec2(centerXpos-(nameSize.x/2)-5, args.fftRectMin.y); + ImVec2 rectMax = ImVec2(centerXpos+(nameSize.x/2)+5, args.fftRectMin.y+nameSize.y); + ImVec2 clampedRectMin = ImVec2(std::clamp(rectMin.x, args.fftRectMin.x, args.fftRectMax.x), rectMin.y); + ImVec2 clampedRectMax = ImVec2(std::clamp(rectMax.x, args.fftRectMin.x, args.fftRectMax.x), rectMax.y); + + if (ImGui::IsMouseHoveringRect(clampedRectMin, clampedRectMax)) { + inALabel = true; + hoveredBookmark = bm; + hoveredBookmarkName = _name; + break; + } + } + + // Check if mouse was already down + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !inALabel) { + _this->mouseAlreadyDown = true; + } + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + _this->mouseAlreadyDown = false; + } + + // If yes, cancel + if (_this->mouseAlreadyDown || !inALabel) { return; } + + gui::waterfall.inputHandled = true; + + double centerXpos = args.fftRectMin.x + std::round((hoveredBookmark.frequency - args.lowFreq) * args.freqToPixelRatio); + ImVec2 nameSize = ImGui::CalcTextSize(hoveredBookmarkName.c_str()); + ImVec2 rectMin = ImVec2(centerXpos-(nameSize.x/2)-5, args.fftRectMin.y); + ImVec2 rectMax = ImVec2(centerXpos+(nameSize.x/2)+5, args.fftRectMin.y+nameSize.y); + ImVec2 clampedRectMin = ImVec2(std::clamp(rectMin.x, args.fftRectMin.x, args.fftRectMax.x), rectMin.y); + ImVec2 clampedRectMax = ImVec2(std::clamp(rectMax.x, args.fftRectMin.x, args.fftRectMax.x), rectMax.y); + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + applyBookmark(hoveredBookmark, gui::waterfall.selectedVFO); + } + + ImGui::BeginTooltip(); + ImGui::Text("Bandwidth: %s", freqToStr(hoveredBookmark.bandwidth).c_str()); + ImGui::Text("Mode: %s", demodModeList[hoveredBookmark.mode]); + ImGui::EndTooltip(); + } + json exportedBookmarks; bool importOpen = false; bool exportOpen = false; @@ -586,6 +654,7 @@ private: bool showBookmarksOnFFT = false; EventHandler fftRedrawHandler; + EventHandler inputHandler; std::map bookmarks;