f8dde16a7e
Delegates the refuse-gate to ledgerDegraded(), lifts its console message into a pure testable fold, fixes stale doc line citations and an inaccurate outcome-enum comment, and splits the rename counter into collision-vs-sanitize.
247 lines
8.6 KiB
C++
247 lines
8.6 KiB
C++
// panel_window.cpp — window-lifecycle seam of the docked bank panel. Owns the SWELL
|
|
// dialog (docked via DockWindowAddEx / undocked via DockWindowRemove), the dialog
|
|
// proc routing to the input/drag/render/audition seams, the OS drop-target opt-in
|
|
// (WM_DROPFILES -> ingest), and the shared PanelState blob's definition. The panel
|
|
// never inserts into the arrange. Dock title + persisted-position identstr both
|
|
// come from app_version (channel-qualified).
|
|
//
|
|
// main.cpp owns the API pointers; here they are extern. DAW-verified, not unit tested.
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "shell/panel/panel_state.h"
|
|
#include "shell/panel/panel_window.h"
|
|
|
|
#include "shell/panel/draw_kit.h"
|
|
#include "shell/actions/ingest.h"
|
|
#include "shell/actions/package_import_action.h"
|
|
#include "core/package/import_plan.h"
|
|
#include "core/version/app_version.h"
|
|
#include "shell/persist/session.h" // ReaSamplerSession::ledgerStatus() — panel_state.h only forward-declares it
|
|
|
|
#ifdef _WIN32
|
|
#include <windowsx.h> // GET_X_LPARAM / GET_Y_LPARAM (SWELL supplies them on mac/linux)
|
|
#include <shellapi.h> // DragAcceptFiles / DragQueryFile / DragFinish
|
|
#endif
|
|
|
|
#include "resource.h"
|
|
|
|
#define REAPERAPI_MINIMAL
|
|
#define REAPERAPI_WANT_DockWindowAddEx
|
|
#define REAPERAPI_WANT_DockWindowActivate
|
|
#define REAPERAPI_WANT_DockWindowRemove
|
|
#define REAPERAPI_WANT_GetMainHwnd
|
|
#define REAPERAPI_WANT_ShowConsoleMsg
|
|
#include "reaper_plugin_functions.h"
|
|
|
|
// main.cpp owns the module instance handle.
|
|
extern REAPER_PLUGIN_HINSTANCE g_hInst;
|
|
|
|
namespace reasampler::panel {
|
|
|
|
// Defined here — the lifecycle seam owns the state's lifetime.
|
|
PanelState g_panel;
|
|
|
|
namespace {
|
|
|
|
bool isPackagePath(const std::string& path) {
|
|
static const std::string kExt = ".rsbank";
|
|
if (path.size() <= kExt.size()) return false;
|
|
std::string tail = path.substr(path.size() - kExt.size());
|
|
for (char& c : tail)
|
|
if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
|
|
return tail == kExt;
|
|
}
|
|
|
|
// DragQueryFile(hDrop, 0xFFFFFFFF, ...) returns the file count; each path is then
|
|
// queried by index (length first, excludes NUL, then a sized buffer). DragFinish
|
|
// always frees the shell-allocated drop buffer. A .rsbank is a whole bank, not audio,
|
|
// so it routes to the import verb (one new bank each); everything else keeps the
|
|
// existing ingest route — multi-file drop imports all into the active bank (bank-fill
|
|
// only, no assignment to any live instance).
|
|
void handleDropFiles(HDROP hDrop) {
|
|
std::vector<std::string> paths;
|
|
std::vector<std::string> packages;
|
|
const UINT count = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
|
|
paths.reserve(count);
|
|
for (UINT i = 0; i < count; ++i) {
|
|
const UINT len = DragQueryFile(hDrop, i, nullptr, 0);
|
|
if (len == 0) continue;
|
|
std::vector<char> buf(static_cast<std::size_t>(len) + 1, '\0');
|
|
DragQueryFile(hDrop, i, buf.data(), static_cast<UINT>(buf.size()));
|
|
std::string p(buf.data());
|
|
if (p.empty()) continue;
|
|
if (isPackagePath(p)) packages.push_back(std::move(p));
|
|
else paths.push_back(std::move(p));
|
|
}
|
|
DragFinish(hDrop);
|
|
if (g_panel.session && !packages.empty()) {
|
|
// One refusal block for the whole drop, not one per dropped .rsbank: the gate
|
|
// decision is the same for all N (session state does not change mid-drop), so
|
|
// checking it here first avoids doImportBankPackageFile's own per-file gate
|
|
// check printing the identical console block N times.
|
|
const package::LedgerRefusal refusal =
|
|
package::importLedgerRefusal(g_panel.session->ledgerStatus());
|
|
if (refusal != package::LedgerRefusal::None) {
|
|
ShowConsoleMsg(
|
|
package::ledgerRefusalMessage(refusal, version::extStateNamespace()).c_str());
|
|
} else {
|
|
for (const std::string& pkg : packages)
|
|
doImportBankPackageFile(*g_panel.session, pkg);
|
|
}
|
|
}
|
|
if (!paths.empty()) ingestDroppedFiles(paths);
|
|
}
|
|
|
|
WDL_DLGRET dlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
|
switch (msg) {
|
|
case WM_DROPFILES:
|
|
handleDropFiles(reinterpret_cast<HDROP>(wParam));
|
|
return 0;
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hwnd, &ps);
|
|
paintPanel(hwnd, hdc);
|
|
EndPaint(hwnd, &ps);
|
|
return 0;
|
|
}
|
|
case WM_LBUTTONDOWN: {
|
|
SetFocus(hwnd);
|
|
handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
return 0;
|
|
}
|
|
case WM_MOUSEMOVE:
|
|
onMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
return 0;
|
|
case WM_LBUTTONUP:
|
|
onLBtnUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
return 0;
|
|
case WM_RBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
handleRightClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
return 0;
|
|
case WM_CAPTURECHANGED:
|
|
// Capture lost (pointer left pre-threshold, or another window stole it
|
|
// mid-drag) — cancel the drag as a no-op, mirroring onLBtnUp's reset.
|
|
if (g_panel.dragArmed || g_panel.dragging) {
|
|
resetDragState();
|
|
SetCursor(LoadCursor(nullptr, IDC_ARROW));
|
|
invalidatePanel();
|
|
}
|
|
return 0;
|
|
case WM_MOUSEWHEEL: {
|
|
// Unlike button messages, WM_MOUSEWHEEL carries SCREEN coords in lParam
|
|
// (Win32 and SWELL agree), so convert to client space first. Wheel delta
|
|
// is the HIWORD of wParam. Consume (return 1) only when the footer acts.
|
|
POINT pt{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
|
|
ScreenToClient(hwnd, &pt);
|
|
const int delta = static_cast<short>(HIWORD(wParam));
|
|
return handleWheel(pt.x, pt.y, delta) ? 1 : 0;
|
|
}
|
|
case WM_DESTROY:
|
|
if (GetCapture() == hwnd) ReleaseCapture();
|
|
stopAudition();
|
|
g_panel.selection = Selection{};
|
|
resetDragState();
|
|
g_panel.hovered = Hover{};
|
|
g_panel.tooltipShown = false;
|
|
g_panel.hwnd = nullptr;
|
|
g_panel.open = false;
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void openPanel() {
|
|
if (g_panel.open && g_panel.hwnd) {
|
|
DockWindowActivate(g_panel.hwnd);
|
|
return;
|
|
}
|
|
initPreview();
|
|
|
|
// Idempotent — a reopen after closePanel (fonts left alive) is a cheap no-op;
|
|
// torn down once at bankPanelShutdown.
|
|
kitFontsInit();
|
|
|
|
g_panel.hwnd = CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_BANK_PANEL),
|
|
GetMainHwnd(), dlgProc, 0);
|
|
if (!g_panel.hwnd) return;
|
|
|
|
// identstr is a REAPER-global collision surface keying the persisted dock
|
|
// position — channel-qualified so beta doesn't fight over stable's slot.
|
|
DockWindowAddEx(g_panel.hwnd, dockTitle().c_str(), dockIdent().c_str(), true);
|
|
DockWindowActivate(g_panel.hwnd);
|
|
g_panel.open = true;
|
|
|
|
// DragAcceptFiles is native Win32 (shellapi.h); SWELL doesn't expose it, so the
|
|
// accept opt-in is Windows-only. DragQueryFile/DragFinish ARE SWELL-provided,
|
|
// so a drop delivered by other means would still ingest.
|
|
#ifdef _WIN32
|
|
DragAcceptFiles(g_panel.hwnd, TRUE);
|
|
#endif
|
|
|
|
registerAccel();
|
|
|
|
reconcileShownBank();
|
|
refreshFingerprint();
|
|
}
|
|
|
|
void closePanel() {
|
|
if (GetCapture() == g_panel.hwnd) ReleaseCapture();
|
|
stopAudition();
|
|
g_panel.selection = Selection{};
|
|
resetDragState();
|
|
unregisterAccel();
|
|
if (g_panel.hwnd) {
|
|
DockWindowRemove(g_panel.hwnd);
|
|
DestroyWindow(g_panel.hwnd);
|
|
g_panel.hwnd = nullptr;
|
|
}
|
|
g_panel.open = false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace reasampler::panel
|
|
|
|
namespace reasampler {
|
|
|
|
void bankPanelInit(ReaSamplerSession* session) {
|
|
panel::g_panel.session = session;
|
|
}
|
|
|
|
// Alt+D hides the docker without destroying the window, leaving HWND/g_panel.open
|
|
// live but IsWindowVisible false — the live query is the source of truth for
|
|
// toggle decisions and the Actions-list checkmark.
|
|
static bool panelEffectivelyVisible() {
|
|
return panel::g_panel.hwnd && IsWindowVisible(panel::g_panel.hwnd);
|
|
}
|
|
|
|
void bankPanelToggle() {
|
|
if (panelEffectivelyVisible())
|
|
panel::closePanel();
|
|
else
|
|
panel::openPanel();
|
|
}
|
|
|
|
bool bankPanelIsOpen() {
|
|
return panelEffectivelyVisible();
|
|
}
|
|
|
|
void bankPanelInvalidate() {
|
|
if (panel::g_panel.hwnd) InvalidateRect(panel::g_panel.hwnd, nullptr, FALSE);
|
|
}
|
|
|
|
void bankPanelShutdown() {
|
|
panel::closePanel();
|
|
panel::deinitPreview();
|
|
kitFontsShutdown();
|
|
panel::g_panel.cache.clear();
|
|
panel::g_panel.session = nullptr;
|
|
}
|
|
|
|
} // namespace reasampler
|