feat(bank_panel): B4 vertical-split UI — LICE tab strip, id-keyed bank ops, move/copy drag
Pool grid on top, LICE-drawn named-banks tab strip below with overflow-scroll (new pure tab_strip seam, unit-tested). Full-height toggles, unmistakable active-bank readout distinct from the shown tab, tab context menu (activate/rename/delete/evacuate/create) with rich confirm-on-non-empty-delete, and move/copy via menu + drag with drop-highlighting. Fold-in: deserialize auto-disambiguates duplicate folded bank names instead of rejecting the book.
This commit is contained in:
@@ -687,6 +687,49 @@ bool Parser::parseBook(std::vector<Bank>& banks, std::string& activeBank) {
|
||||
for (auto& b : parsedBanks)
|
||||
if (b.isPool()) b.displayName = kPoolBankName;
|
||||
|
||||
// --- Coalesce duplicate folded display names (B4 re-review fold-in). --------
|
||||
// The in-model create/rename path enforces unique display names under nameKey,
|
||||
// but a hand-edited .rpp blob can smuggle in two banks whose names fold to the
|
||||
// same key ("Drums" and " drums "). Rejecting the whole book over one collision
|
||||
// would degrade the user's entire library to empty, so instead we AUTO-
|
||||
// DISAMBIGUATE the later duplicate deterministically: scan in parse order, and
|
||||
// the first time a folded key repeats, suffix that bank's display name (" 2",
|
||||
// " 3", …) until its folded key is unique among all names seen so far. The FIRST
|
||||
// bank to carry a key keeps its name verbatim; only subsequent collisions are
|
||||
// renamed. No bank or sample is lost, and ids are untouched. The pool is included
|
||||
// in the seen-set (its "Pool" key is reserved) so a named bank folding to "pool"
|
||||
// is disambiguated away from it, never the reverse.
|
||||
{
|
||||
std::vector<std::string> seenKeys;
|
||||
seenKeys.reserve(parsedBanks.size());
|
||||
for (auto& b : parsedBanks) {
|
||||
if (b.isPool()) { // pool's name is fixed; reserve its key
|
||||
seenKeys.push_back(nameKey(b.displayName));
|
||||
continue;
|
||||
}
|
||||
const auto taken = [&](const std::string& k) {
|
||||
return std::find(seenKeys.begin(), seenKeys.end(), k) != seenKeys.end();
|
||||
};
|
||||
std::string key = nameKey(b.displayName);
|
||||
if (taken(key)) {
|
||||
// Suffix with an ascending integer until the folded key is free. Guard
|
||||
// against a pathological blob whose base name already ends in a number
|
||||
// by folding the candidate each attempt (nameKey normalizes it).
|
||||
const std::string base = b.displayName;
|
||||
for (int n = 2;; ++n) {
|
||||
const std::string candidate = base + " " + std::to_string(n);
|
||||
const std::string candKey = nameKey(candidate);
|
||||
if (!taken(candKey)) {
|
||||
b.displayName = candidate;
|
||||
key = candKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
seenKeys.push_back(key);
|
||||
}
|
||||
}
|
||||
|
||||
banks = std::move(parsedBanks);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user