fix: guard persistBankOp/persistBook against a null session; rename promptText to promptBankName
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
// bank_actions.cpp — the multi-bank bindable action family (Phase B3; Q-W4 split of
|
// bank_actions.cpp — the multi-bank bindable action family (Phase B3; Q-W4 split of
|
||||||
// actions.cpp). See bank_actions.h.
|
// actions.cpp). See bank_actions.h.
|
||||||
//
|
//
|
||||||
// Q-W4 dedupe: each mutating handler is a THIN UX SKIN — text prompts (promptText),
|
// Q-W4 dedupe: each mutating handler is a THIN UX SKIN — text prompts (promptBankName),
|
||||||
// name resolution, and console feedback — over the promptless bankOp* inner verbs
|
// name resolution, and console feedback — over the promptless bankOp* inner verbs
|
||||||
// homed in panel_bank_ops (model op + persistBankOp, one bank op = one Ctrl-Z). The
|
// homed in panel_bank_ops (model op + persistBankOp, one bank op = one Ctrl-Z). The
|
||||||
// book's rules (pool privileges, collapse-by-hash, active-fallback-to-pool) all live
|
// book's rules (pool privileges, collapse-by-hash, active-fallback-to-pool) all live
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
#include "core/model/bank_book.h" // BankBook, nextBankId, kPoolBankId (B1)
|
#include "core/model/bank_book.h" // BankBook, nextBankId, kPoolBankId (B1)
|
||||||
#include "persist.h" // ReaSamplerSession (owns book())
|
#include "persist.h" // ReaSamplerSession (owns book())
|
||||||
#include "shell/panel/panel_bank_ops.h" // bankOp* inner verbs + promptText + selection seam
|
#include "shell/panel/panel_bank_ops.h" // bankOp* inner verbs + promptBankName + selection seam
|
||||||
#include "shell/panel/panel_layout.h" // full-height toggles (B3)
|
#include "shell/panel/panel_layout.h" // full-height toggles (B3)
|
||||||
|
|
||||||
#define REAPERAPI_MINIMAL
|
#define REAPERAPI_MINIMAL
|
||||||
@@ -115,7 +115,7 @@ std::string bankIdByDisplayName(const std::string& name) {
|
|||||||
// create then fails and the user is told the name is taken.
|
// create then fails and the user is told the name is taken.
|
||||||
void doBankCreate() {
|
void doBankCreate() {
|
||||||
std::string name;
|
std::string name;
|
||||||
if (!promptText("ReaSampler: create bank", "Bank name:", "", name)) return;
|
if (!promptBankName("ReaSampler: create bank", "Bank name:", "", name)) return;
|
||||||
if (bankOpCreate(name).empty()) {
|
if (bankOpCreate(name).empty()) {
|
||||||
ShowConsoleMsg(
|
ShowConsoleMsg(
|
||||||
("ReaSampler: could not create bank \"" + name +
|
("ReaSampler: could not create bank \"" + name +
|
||||||
@@ -129,7 +129,7 @@ void doBankCreate() {
|
|||||||
// self-contained; the panel renames in place on a tab.
|
// self-contained; the panel renames in place on a tab.
|
||||||
void doBankRename() {
|
void doBankRename() {
|
||||||
std::string which;
|
std::string which;
|
||||||
if (!promptText("ReaSampler: rename bank", "Bank to rename (current name):", "",
|
if (!promptBankName("ReaSampler: rename bank", "Bank to rename (current name):", "",
|
||||||
which))
|
which))
|
||||||
return;
|
return;
|
||||||
const std::string id = bankIdByDisplayName(which);
|
const std::string id = bankIdByDisplayName(which);
|
||||||
@@ -138,7 +138,7 @@ void doBankRename() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string newName;
|
std::string newName;
|
||||||
if (!promptText("ReaSampler: rename bank", "New name:", which, newName)) return;
|
if (!promptBankName("ReaSampler: rename bank", "New name:", which, newName)) return;
|
||||||
if (!bankOpRename(id, newName)) {
|
if (!bankOpRename(id, newName)) {
|
||||||
// The verb rejects the pool (un-renamable) or a name already used by another
|
// The verb rejects the pool (un-renamable) or a name already used by another
|
||||||
// bank (unique display names, trimmed + case-insensitive).
|
// bank (unique display names, trimmed + case-insensitive).
|
||||||
@@ -154,7 +154,7 @@ void doBankRename() {
|
|||||||
// panel confirm (naming evacuate inline, with a one-click evacuate) lives in the panel.
|
// panel confirm (naming evacuate inline, with a one-click evacuate) lives in the panel.
|
||||||
void doBankDelete() {
|
void doBankDelete() {
|
||||||
std::string which;
|
std::string which;
|
||||||
if (!promptText("ReaSampler: delete bank", "Bank to delete:", "", which)) return;
|
if (!promptBankName("ReaSampler: delete bank", "Bank to delete:", "", which)) return;
|
||||||
const std::string id = bankIdByDisplayName(which);
|
const std::string id = bankIdByDisplayName(which);
|
||||||
if (id.empty()) {
|
if (id.empty()) {
|
||||||
ShowConsoleMsg(("ReaSampler: no bank named \"" + which + "\".\n").c_str());
|
ShowConsoleMsg(("ReaSampler: no bank named \"" + which + "\".\n").c_str());
|
||||||
@@ -194,7 +194,7 @@ void doBankDelete() {
|
|||||||
// intended "keep the samples" companion to delete.
|
// intended "keep the samples" companion to delete.
|
||||||
void doBankEvacuate() {
|
void doBankEvacuate() {
|
||||||
std::string which;
|
std::string which;
|
||||||
if (!promptText("ReaSampler: evacuate bank", "Bank to evacuate to the pool:", "",
|
if (!promptBankName("ReaSampler: evacuate bank", "Bank to evacuate to the pool:", "",
|
||||||
which))
|
which))
|
||||||
return;
|
return;
|
||||||
const std::string id = bankIdByDisplayName(which);
|
const std::string id = bankIdByDisplayName(which);
|
||||||
@@ -245,7 +245,7 @@ void doBankTransferSelected(bool copy) {
|
|||||||
const char* verb = copy ? "copy" : "move";
|
const char* verb = copy ? "copy" : "move";
|
||||||
const std::string title = std::string("ReaSampler: ") + verb + " selected samples";
|
const std::string title = std::string("ReaSampler: ") + verb + " selected samples";
|
||||||
std::string destName;
|
std::string destName;
|
||||||
if (!promptText(title.c_str(), "Destination bank:", "", destName)) return;
|
if (!promptBankName(title.c_str(), "Destination bank:", "", destName)) return;
|
||||||
const std::string destId = bankIdByDisplayName(destName);
|
const std::string destId = bankIdByDisplayName(destName);
|
||||||
if (destId.empty()) {
|
if (destId.empty()) {
|
||||||
ShowConsoleMsg(("ReaSampler: no bank named \"" + destName + "\".\n").c_str());
|
ShowConsoleMsg(("ReaSampler: no bank named \"" + destName + "\".\n").c_str());
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ std::vector<const Bank*> namedBanks() {
|
|||||||
void doCreateBank() {
|
void doCreateBank() {
|
||||||
if (!book()) return;
|
if (!book()) return;
|
||||||
std::string name;
|
std::string name;
|
||||||
if (!promptText("ReaSampler: create bank", "Bank name:", "", name)) return;
|
if (!promptBankName("ReaSampler: create bank", "Bank name:", "", name)) return;
|
||||||
const std::string id = bankOpCreate(name);
|
const std::string id = bankOpCreate(name);
|
||||||
if (id.empty()) {
|
if (id.empty()) {
|
||||||
ShowMessageBox("A bank with that name already exists.",
|
ShowMessageBox("A bank with that name already exists.",
|
||||||
@@ -115,7 +115,7 @@ void doRenameBank(const std::string& bankId) {
|
|||||||
if (!bk || bk->isPool()) return;
|
if (!bk || bk->isPool()) return;
|
||||||
const std::string current = bk->displayName; // copy before any mutation
|
const std::string current = bk->displayName; // copy before any mutation
|
||||||
std::string newName;
|
std::string newName;
|
||||||
if (!promptText("ReaSampler: rename bank", "New name:", current, newName)) return;
|
if (!promptBankName("ReaSampler: rename bank", "New name:", current, newName)) return;
|
||||||
if (!bankOpRename(bankId, newName)) {
|
if (!bankOpRename(bankId, newName)) {
|
||||||
ShowMessageBox("Another bank already uses that name.",
|
ShowMessageBox("Another bank already uses that name.",
|
||||||
"ReaSampler: rename bank", 0);
|
"ReaSampler: rename bank", 0);
|
||||||
@@ -417,9 +417,13 @@ namespace {
|
|||||||
// (the change stays valid for the session and persists on the user's next save).
|
// (the change stays valid for the session and persists on the user's next save).
|
||||||
// Deliberately NO Save-As prompt; do not "align" with persistViewState's prompt
|
// Deliberately NO Save-As prompt; do not "align" with persistViewState's prompt
|
||||||
// idiom. Returns whether a persist actually happened, so persistBankOp can discard
|
// idiom. Returns whether a persist actually happened, so persistBankOp can discard
|
||||||
// its undo block when nothing was written. Session pointer is live for the whole
|
// its undo block when nothing was written. Guards a null session pointer (false,
|
||||||
// extension lifetime (bankPanelInit at load, before any action registers).
|
// no-op) — see persistBankOp's guard below for why this is defensive rather than
|
||||||
bool persistBook() { return panel::g_panel.session->saveToActiveProject(); }
|
// dead code.
|
||||||
|
bool persistBook() {
|
||||||
|
if (!panel::g_panel.session) return false; // no live session: nothing to persist
|
||||||
|
return panel::g_panel.session->saveToActiveProject();
|
||||||
|
}
|
||||||
|
|
||||||
// Mints a fresh, genuine REAPER GUID string as a stable bank id (the B2/model
|
// Mints a fresh, genuine REAPER GUID string as a stable bank id (the B2/model
|
||||||
// design: ids are caller-supplied and stable; the model stays pure and mints none).
|
// design: ids are caller-supplied and stable; the model stays pure and mints none).
|
||||||
@@ -438,8 +442,8 @@ std::string mintBankId() {
|
|||||||
// COMMA GUARD: GetUserInputs splits returned values on a separator defaulting to ',',
|
// COMMA GUARD: GetUserInputs splits returned values on a separator defaulting to ',',
|
||||||
// so the return separator is overridden to \x1f (un-typeable) via the documented
|
// so the return separator is overridden to \x1f (un-typeable) via the documented
|
||||||
// `separator=X` trailing pseudo-caption (SDK ~3806) — any printable name round-trips.
|
// `separator=X` trailing pseudo-caption (SDK ~3806) — any printable name round-trips.
|
||||||
bool promptText(const char* title, const char* caption, const std::string& initial,
|
bool promptBankName(const char* title, const char* caption, const std::string& initial,
|
||||||
std::string& out) {
|
std::string& out) {
|
||||||
std::vector<char> buf(512, '\0');
|
std::vector<char> buf(512, '\0');
|
||||||
// Pre-fill: GetUserInputs seeds the field from the retvals buffer's initial value.
|
// Pre-fill: GetUserInputs seeds the field from the retvals buffer's initial value.
|
||||||
std::snprintf(buf.data(), buf.size(), "%s", initial.c_str());
|
std::snprintf(buf.data(), buf.size(), "%s", initial.c_str());
|
||||||
@@ -472,7 +476,14 @@ bool promptText(const char* title, const char* caption, const std::string& initi
|
|||||||
// change stands and persists on the user's next save; it just earns no undo point until
|
// change stands and persists on the user's next save; it just earns no undo point until
|
||||||
// there is a project to persist into (undo of an unsaved bank op has nothing to roll
|
// there is a project to persist into (undo of an unsaved bank op has nothing to roll
|
||||||
// back to anyway). The Begin/End must still be balanced, hence the close-either-way.
|
// back to anyway). The Begin/End must still be balanced, hence the close-either-way.
|
||||||
|
//
|
||||||
|
// NULL-SESSION GUARD: this is a public API (panel_bank_ops.h) with callers outside
|
||||||
|
// this TU (e.g. panel_drag.cpp), not all of which are guaranteed to have re-checked
|
||||||
|
// the session pointer immediately beforehand. Bail out BEFORE Undo_BeginBlock2 — no
|
||||||
|
// block is opened, so there is nothing to balance and no risk of an unbalanced
|
||||||
|
// Begin/End pair.
|
||||||
void persistBankOp(const char* label, bool bumpGeneration) {
|
void persistBankOp(const char* label, bool bumpGeneration) {
|
||||||
|
if (!panel::g_panel.session) return; // no live session: no-op, no undo point opened
|
||||||
Undo_BeginBlock2(nullptr);
|
Undo_BeginBlock2(nullptr);
|
||||||
// S9: bump the bank-generation counter INSIDE the block, before persistBook(), so the
|
// S9: bump the bank-generation counter INSIDE the block, before persistBook(), so the
|
||||||
// fresh generation rides the same ext-state write the persist makes (persistBook() ->
|
// fresh generation rides the same ext-state write the persist makes (persistBook() ->
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ bool bankOpRemove(const std::vector<std::string>& sampleIds,
|
|||||||
// overridden to \x1f (un-typeable) via the documented `separator=X` pseudo-caption,
|
// overridden to \x1f (un-typeable) via the documented `separator=X` pseudo-caption,
|
||||||
// so any printable name — commas included — round-trips whole (SDK ~3806/3808).
|
// so any printable name — commas included — round-trips whole (SDK ~3806/3808).
|
||||||
// One home (Q-W4) for the former actions.cpp/panel_bank_ops.cpp twins.
|
// One home (Q-W4) for the former actions.cpp/panel_bank_ops.cpp twins.
|
||||||
bool promptText(const char* title, const char* caption, const std::string& initial,
|
bool promptBankName(const char* title, const char* caption, const std::string& initial,
|
||||||
std::string& out);
|
std::string& out);
|
||||||
|
|
||||||
// Persists a completed bank-index verb as a single REAPER undo point (R-B).
|
// Persists a completed bank-index verb as a single REAPER undo point (R-B).
|
||||||
// Wraps the session persist (SetProjExtState) in a Begin/End block with
|
// Wraps the session persist (SetProjExtState) in a Begin/End block with
|
||||||
@@ -78,6 +78,12 @@ bool promptText(const char* title, const char* caption, const std::string& initi
|
|||||||
// rejected ops (duplicate name, un-deletable pool, etc.) must return before reaching
|
// rejected ops (duplicate name, un-deletable pool, etc.) must return before reaching
|
||||||
// here so no empty undo point is ever opened for a no-op.
|
// here so no empty undo point is ever opened for a no-op.
|
||||||
//
|
//
|
||||||
|
// NULL-SESSION GUARD: this is a public API with callers outside panel_bank_ops.cpp
|
||||||
|
// (e.g. panel_drag.cpp). If the panel's session pointer is absent (no live session),
|
||||||
|
// this is a no-op — no undo block is opened. Today every real caller only reaches
|
||||||
|
// here via a prior session-backed check, so the guard is not yet reachable in
|
||||||
|
// practice; it exists to make the function safe to call standalone.
|
||||||
|
//
|
||||||
// S9 bank-generation bump: pass `bumpGeneration = true` for a verb that changes what a
|
// S9 bank-generation bump: pass `bumpGeneration = true` for a verb that changes what a
|
||||||
// live instance would PLAY — move / copy / remove / evacuate / delete-with-members. Leave
|
// live instance would PLAY — move / copy / remove / evacuate / delete-with-members. Leave
|
||||||
// it false (the default) for a PURELY ORGANIZATIONAL verb — create / rename / activate /
|
// it false (the default) for a PURELY ORGANIZATIONAL verb — create / rename / activate /
|
||||||
|
|||||||
Reference in New Issue
Block a user