Merge dev into phase-b-multibank (integrate parallel M7/8 + Phase D work before dev promotion)
# Conflicts: # CLAUDE.md # CMakeLists.txt # src/actions.cpp # src/bank_panel.cpp # src/persist.h
This commit is contained in:
+116
-2
@@ -26,9 +26,11 @@
|
||||
|
||||
#include "bank_book.h" // BankBook, nextBankId, TransferResult, kPoolBankId (B1)
|
||||
#include "bank_panel.h" // selection seam + full-height toggles (B3/B4)
|
||||
#include "item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
|
||||
#include "lane_keys.h" // isOnManualLane — the single managed/manual predicate
|
||||
#include "persist.h" // ReaSamplerSession (owns book() + view() model)
|
||||
#include "track_guid.h" // shared MediaTrack* -> canonical GUID key
|
||||
#include "view.h" // applyMode (D2 shell)
|
||||
#include "view.h" // applyMode + mintManagedLanes (D2 shell)
|
||||
#include "view_mode_model.h"
|
||||
|
||||
#include "reaper_plugin.h" // reaper_plugin_info_t, gaccel_register_t (full defs)
|
||||
@@ -36,6 +38,10 @@
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_CountSelectedTracks
|
||||
#define REAPERAPI_WANT_GetSelectedTrack
|
||||
#define REAPERAPI_WANT_CountSelectedMediaItems
|
||||
#define REAPERAPI_WANT_GetSelectedMediaItem
|
||||
#define REAPERAPI_WANT_GetMediaItemTrack
|
||||
#define REAPERAPI_WANT_GetMediaTrackInfo_Value
|
||||
#define REAPERAPI_WANT_EnumProjects
|
||||
#define REAPERAPI_WANT_Main_SaveProject
|
||||
#define REAPERAPI_WANT_ShowConsoleMsg
|
||||
@@ -43,6 +49,8 @@
|
||||
#define REAPERAPI_WANT_ShowMessageBox
|
||||
#define REAPERAPI_WANT_genGuid
|
||||
#define REAPERAPI_WANT_guidToString
|
||||
#define REAPERAPI_WANT_Undo_BeginBlock2
|
||||
#define REAPERAPI_WANT_Undo_EndBlock2
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler {
|
||||
@@ -59,6 +67,12 @@ constexpr const char* kIdTagDesign = "CEREBELLUM_REASAMPLER_VIEW_TAG_DESIGN";
|
||||
constexpr const char* kIdTagArrange = "CEREBELLUM_REASAMPLER_VIEW_TAG_ARRANGE";
|
||||
constexpr const char* kIdUntag = "CEREBELLUM_REASAMPLER_VIEW_UNTAG";
|
||||
constexpr const char* kIdShowBoth = "CEREBELLUM_REASAMPLER_VIEW_SHOW_BOTH";
|
||||
// D2 Wave 3-B item-level mode moves — the item analog of the track tag family. Same
|
||||
// FOREVER-STABLE contract: minted into a persistent command id, user keybindings key off
|
||||
// each — NEVER change these strings after ship.
|
||||
constexpr const char* kIdMoveItemsDesign = "CEREBELLUM_REASAMPLER_VIEW_MOVE_ITEMS_DESIGN";
|
||||
constexpr const char* kIdMoveItemsArrange = "CEREBELLUM_REASAMPLER_VIEW_MOVE_ITEMS_ARRANGE";
|
||||
constexpr const char* kIdUntagItems = "CEREBELLUM_REASAMPLER_VIEW_UNTAG_ITEMS";
|
||||
|
||||
// The live session the actions mutate. Set once by designViewRegisterActions and
|
||||
// read by the hookcommand handler. Not owned here (main.cpp owns g_session).
|
||||
@@ -72,6 +86,9 @@ int g_cmdTagDesign = 0;
|
||||
int g_cmdTagArrange = 0;
|
||||
int g_cmdUntag = 0;
|
||||
int g_cmdShowBoth = 0;
|
||||
int g_cmdMoveItemsDesign = 0;
|
||||
int g_cmdMoveItemsArrange = 0;
|
||||
int g_cmdUntagItems = 0;
|
||||
|
||||
// gaccel storage must outlive registration — REAPER holds each pointer until we
|
||||
// mirror-unregister it. One per action.
|
||||
@@ -82,6 +99,9 @@ gaccel_register_t g_accelTagDesign{};
|
||||
gaccel_register_t g_accelTagArrange{};
|
||||
gaccel_register_t g_accelUntag{};
|
||||
gaccel_register_t g_accelShowBoth{};
|
||||
gaccel_register_t g_accelMoveItemsDesign{};
|
||||
gaccel_register_t g_accelMoveItemsArrange{};
|
||||
gaccel_register_t g_accelUntagItems{};
|
||||
|
||||
// Mints a command id from a stable string and registers its gaccel (Actions-list
|
||||
// entry with `desc`). Returns the command id (0 on failure). The gaccel storage is
|
||||
@@ -120,6 +140,37 @@ void reapplyActiveMode() {
|
||||
applyMode(g_session->view(), g_session->view().activeModeId(), nullptr);
|
||||
}
|
||||
|
||||
// Track fixed-lane mode value (I_FREEMODE=2). Mirrors the shell's constant; used only to
|
||||
// decide whether an item's lane name is meaningful for the manual-lane read.
|
||||
constexpr int kFreeModeFixedLanes = 2;
|
||||
|
||||
// Collects the current media-item selection as the pure decision's input: each selected
|
||||
// item's GUID plus whether it sits on a MANUAL lane (⇒ EXEMPT — never retagged/re-laned).
|
||||
// The manual-lane read follows the shared pure predicate exactly as the shell's readers
|
||||
// do: only on a fixed-lane track (I_FREEMODE==2) is the item's lane name read; on a normal
|
||||
// track isOnManualLane returns false for the empty name, so the P_LANENAME read is skipped.
|
||||
// Items whose GUID cannot be read are dropped (an empty GUID must never be retagged).
|
||||
std::vector<RetagItem> selectedRetagItems() {
|
||||
std::vector<RetagItem> items;
|
||||
const int n = CountSelectedMediaItems(nullptr); // nullptr = active project
|
||||
items.reserve(static_cast<std::size_t>(n < 0 ? 0 : n));
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MediaItem* it = GetSelectedMediaItem(nullptr, i);
|
||||
if (!it) continue;
|
||||
std::string g = itemGuid(it);
|
||||
if (g.empty()) continue;
|
||||
|
||||
MediaTrack* tr = GetMediaItemTrack(it);
|
||||
const bool fixedLane =
|
||||
tr && static_cast<int>(GetMediaTrackInfo_Value(tr, "I_FREEMODE")) == kFreeModeFixedLanes;
|
||||
// Only read the lane name on a fixed-lane track; the pure predicate handles the
|
||||
// normal-track case (returns false) so we pass an empty name and skip the read.
|
||||
const std::string laneNm = fixedLane ? itemLaneName(tr, it) : std::string{};
|
||||
items.push_back(RetagItem{std::move(g), isOnManualLane(fixedLane, laneNm)});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// Persists both the bank and the Design-View model to the active project's ext
|
||||
// state. Called after every state-changing Design View action so the view model
|
||||
// is not lost across save/close/reopen. Marking the project dirty is correct —
|
||||
@@ -218,6 +269,48 @@ void doShowBoth() {
|
||||
persistViewState();
|
||||
}
|
||||
|
||||
// -- Item-level mode moves (D2 Wave 3-B) -----------------------------------
|
||||
//
|
||||
// Retag the current ITEM selection to `targetMode` (empty ⇒ untag → Arrange default),
|
||||
// then re-drive the minting + apply path so each moved item lands on its target mode's
|
||||
// managed lane and the active-mode lane visibility is reasserted. The pure planItemRetag
|
||||
// decides which selected items to retag (manual-lane items are EXEMPT — never retagged,
|
||||
// never re-laned), upholding the managed-lanes-only invariant even under this explicit
|
||||
// user action. The whole structural act is wrapped in ONE Undo block with a descriptive
|
||||
// label (the inner blocks mintManagedLanes / applyMode open nest harmlessly under it).
|
||||
//
|
||||
// UNDO/SAVE ordering: persistViewState may pop a Save-As dialog (Main_SaveProject) which
|
||||
// must NOT sit inside the Undo block, so we close the block first, then persist — the same
|
||||
// separation the track actions rely on (they persist outside applyMode's own block).
|
||||
void doMoveItems(const std::string& targetMode) {
|
||||
const std::vector<RetagItem> selected = selectedRetagItems();
|
||||
const std::vector<ItemRetagOp> ops = planItemRetag(selected, targetMode);
|
||||
if (ops.empty()) return; // nothing selected, or every selected item was exempt/empty
|
||||
|
||||
MembershipIndex& membership = g_session->view().membership();
|
||||
|
||||
Undo_BeginBlock2(nullptr);
|
||||
// Apply the pure decision's membership writes: tag into targetMode, or untag.
|
||||
for (const ItemRetagOp& op : ops) {
|
||||
if (op.untag) membership.untag(op.guid);
|
||||
else membership.tag(op.guid, op.modeId);
|
||||
}
|
||||
// Re-drive the SAME minting/apply path auto-tag uses: mint/split lanes for any track
|
||||
// whose items now span modes and assign each moved item to its mode's managed lane,
|
||||
// then reassert the active mode's lane visibility. Manual lanes stay untouched
|
||||
// (mintManagedLanes reports their items exempt and never mints over them).
|
||||
mintManagedLanes(g_session->view(), nullptr);
|
||||
reapplyActiveMode();
|
||||
|
||||
const std::string label =
|
||||
targetMode.empty()
|
||||
? std::string("ReaSampler: untag selected items")
|
||||
: std::string("ReaSampler: move selected items -> ") + targetMode;
|
||||
Undo_EndBlock2(nullptr, label.c_str(), -1);
|
||||
|
||||
persistViewState();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void designViewRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* session) {
|
||||
@@ -239,6 +332,14 @@ void designViewRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* ses
|
||||
"ReaSampler: untag selected tracks");
|
||||
g_cmdShowBoth = registerAction(rec, kIdShowBoth, g_accelShowBoth,
|
||||
"ReaSampler: show both for selected tracks");
|
||||
|
||||
// Item-level mode moves (D2 W3-B): the item analog of the track tag family.
|
||||
g_cmdMoveItemsDesign = registerAction(rec, kIdMoveItemsDesign, g_accelMoveItemsDesign,
|
||||
"ReaSampler: move selected items -> Design");
|
||||
g_cmdMoveItemsArrange = registerAction(rec, kIdMoveItemsArrange, g_accelMoveItemsArrange,
|
||||
"ReaSampler: move selected items -> Arrange");
|
||||
g_cmdUntagItems = registerAction(rec, kIdUntagItems, g_accelUntagItems,
|
||||
"ReaSampler: untag selected items");
|
||||
}
|
||||
|
||||
bool designViewHandleCommand(int command) {
|
||||
@@ -253,12 +354,25 @@ bool designViewHandleCommand(int command) {
|
||||
if (command == g_cmdUntag) { doUntag(); return true; }
|
||||
if (command == g_cmdShowBoth) { doShowBoth(); return true; }
|
||||
|
||||
// Item-level moves. Move -> Arrange and Untag items collapse to the same act (an
|
||||
// empty target ⇒ untag ⇒ Arrange default), mirroring the track-level pairing above.
|
||||
if (command == g_cmdMoveItemsDesign) { doMoveItems(kDesignModeId); return true; }
|
||||
if (command == g_cmdMoveItemsArrange) { doMoveItems(std::string{}); return true; }
|
||||
if (command == g_cmdUntagItems) { doMoveItems(std::string{}); return true; }
|
||||
|
||||
return false; // not ours — caller's hookcommand keeps looking
|
||||
}
|
||||
|
||||
void designViewUnregisterActions(reaper_plugin_info_t* rec) {
|
||||
// Mirror-unregister with '-'-prefixed strings, per the contract's unload rule.
|
||||
// gaccel first, then the command_id string (reverse of registration order).
|
||||
// gaccel first, then the command_id string (reverse of registration order — the item
|
||||
// moves registered last, so they tear down first).
|
||||
rec->Register("-gaccel", (void*)&g_accelUntagItems);
|
||||
rec->Register("-command_id", (void*)kIdUntagItems);
|
||||
rec->Register("-gaccel", (void*)&g_accelMoveItemsArrange);
|
||||
rec->Register("-command_id", (void*)kIdMoveItemsArrange);
|
||||
rec->Register("-gaccel", (void*)&g_accelMoveItemsDesign);
|
||||
rec->Register("-command_id", (void*)kIdMoveItemsDesign);
|
||||
rec->Register("-gaccel", (void*)&g_accelShowBoth);
|
||||
rec->Register("-command_id", (void*)kIdShowBoth);
|
||||
rec->Register("-gaccel", (void*)&g_accelUntag);
|
||||
|
||||
Reference in New Issue
Block a user