feat(actions): item-level Design/Arrange move + untag actions; W3-A polish
Add three MIDI-bindable item actions mirroring the track tag family, routed through the shared hookcommand: retag selected items' membership then re-drive mint/apply so each lands on its mode's managed lane (manual-lane items exempt), all in one undo block. Extract shared item_read seam, simplify applyMintPlan's lane-count pass, and guard reconcile against unregistered-mode lanes.
This commit is contained in:
+24
-27
@@ -18,6 +18,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "item_read.h"
|
||||
#include "lane_keys.h"
|
||||
#include "track_guid.h"
|
||||
#include "view_tree.h"
|
||||
@@ -42,7 +43,6 @@
|
||||
#define REAPERAPI_WANT_GetTrackMediaItem
|
||||
#define REAPERAPI_WANT_GetMediaItemInfo_Value
|
||||
#define REAPERAPI_WANT_SetMediaItemInfo_Value
|
||||
#define REAPERAPI_WANT_GetSetMediaItemInfo_String
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler {
|
||||
@@ -289,21 +289,8 @@ bool applyLaneOps(const std::vector<std::pair<std::string, MediaTrack*>>& handle
|
||||
// planLaneMinting; this shell only reads live per-item mode+lane state, calls the
|
||||
// decision, and applies the resulting REAPER + ownership-index writes.
|
||||
|
||||
// An item's canonical GUID string via GetSetMediaItemInfo_String("GUID"). Empty on
|
||||
// failure. Mirrors bank_panel.cpp's itemGuid — the same read seam for item identity.
|
||||
std::string itemGuidString(MediaItem* it) {
|
||||
char buf[64] = {0};
|
||||
if (!GetSetMediaItemInfo_String(it, "GUID", buf, false)) return {};
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// The durable P_LANENAME of the lane item `it` currently sits on, for a track known to
|
||||
// be a fixed-lane track. Empty if unnamed/unavailable. Same derivation as bank_panel's
|
||||
// itemLaneName; kept local so view.cpp stays self-contained.
|
||||
std::string itemLaneNameOf(MediaTrack* tr, MediaItem* it) {
|
||||
const int laneIdx = static_cast<int>(GetMediaItemInfo_Value(it, "I_FIXEDLANE"));
|
||||
return laneName(tr, laneIdx);
|
||||
}
|
||||
// Item GUID + fixed-lane name reads come from the shared item_read seam (item_read.h):
|
||||
// itemGuid(it) and itemLaneName(tr, it). view.cpp no longer carries its own copies.
|
||||
|
||||
// Maps every item GUID on `tr` to its MediaItem* handle, in one pass. The assign pass
|
||||
// resolves plan item GUIDs back to handles through this map rather than re-scanning the
|
||||
@@ -314,7 +301,7 @@ std::map<std::string, MediaItem*> itemHandlesByGuid(MediaTrack* tr) {
|
||||
for (int i = 0; i < itemCount; ++i) {
|
||||
MediaItem* it = GetTrackMediaItem(tr, i);
|
||||
if (!it) continue;
|
||||
std::string ig = itemGuidString(it);
|
||||
std::string ig = itemGuid(it);
|
||||
if (!ig.empty()) byGuid.emplace(std::move(ig), it);
|
||||
}
|
||||
return byGuid;
|
||||
@@ -354,7 +341,7 @@ std::vector<LaneTrack> readLaneTracks(
|
||||
for (int i = 0; i < itemCount; ++i) {
|
||||
MediaItem* it = GetTrackMediaItem(tr, i);
|
||||
if (!it) continue;
|
||||
const std::string ig = itemGuidString(it);
|
||||
const std::string ig = itemGuid(it);
|
||||
if (ig.empty()) continue;
|
||||
LaneItem li;
|
||||
li.guid = ig;
|
||||
@@ -362,7 +349,7 @@ std::vector<LaneTrack> readLaneTracks(
|
||||
// Manual-lane exemption: only meaningful on a fixed-lane track. The shared
|
||||
// pure predicate decides; on a normal track it returns false regardless of
|
||||
// name, so we pass an empty name and skip the P_LANENAME read.
|
||||
const std::string ln = fixedLane ? itemLaneNameOf(tr, it) : std::string{};
|
||||
const std::string ln = fixedLane ? itemLaneName(tr, it) : std::string{};
|
||||
li.onManualLane = isOnManualLane(fixedLane, ln);
|
||||
lt.items.push_back(std::move(li));
|
||||
}
|
||||
@@ -425,24 +412,23 @@ bool applyMintPlan(ViewModeModel& model, const LaneMintPlan& plan,
|
||||
// Ensure enough lanes for the managed set WITHOUT shrinking: a track may already
|
||||
// carry the user's manual lanes, so only GROW the count, never reduce it (which
|
||||
// would delete a user lane). The managed lanes we mint occupy the tail ordinals.
|
||||
const int haveLanes = static_cast<int>(GetMediaTrackInfo_Value(tr, "I_NUMFIXEDLANES"));
|
||||
// laneCount tracks the live I_NUMFIXEDLANES as we grow it: read ONCE here, then
|
||||
// each mint appends at laneCount and bumps it. No per-mint I_NUMFIXEDLANES re-read
|
||||
// is needed — nextOrdinal and laneCount are the same running value.
|
||||
int laneCount = static_cast<int>(GetMediaTrackInfo_Value(tr, "I_NUMFIXEDLANES"));
|
||||
|
||||
// Which managed keys are already present on this track (durable-name reconcile).
|
||||
std::map<std::string, int> present = managedLaneOrdinals(tr);
|
||||
|
||||
// Mint each managed lane that is not already present, appending at the tail so an
|
||||
// existing manual lane is never overwritten. Record ownership in the model.
|
||||
int nextOrdinal = haveLanes;
|
||||
for (const LaneMint* m : mintsByTrack[split.trackGuid]) {
|
||||
model.lanes().setManaged(m->trackGuid, m->laneKey, m->modeId); // ownership
|
||||
if (present.count(m->laneKey)) continue; // already minted — idempotent
|
||||
|
||||
// Grow the lane count to include the new tail ordinal, then stamp its name.
|
||||
const int laneIdx = nextOrdinal++;
|
||||
if (laneIdx >= static_cast<int>(GetMediaTrackInfo_Value(tr, "I_NUMFIXEDLANES"))) {
|
||||
SetMediaTrackInfo_Value(tr, "I_NUMFIXEDLANES",
|
||||
static_cast<double>(laneIdx + 1));
|
||||
}
|
||||
// Append at the current tail ordinal, grow the tracked count, stamp its name.
|
||||
const int laneIdx = laneCount++;
|
||||
SetMediaTrackInfo_Value(tr, "I_NUMFIXEDLANES", static_cast<double>(laneCount));
|
||||
char parm[32];
|
||||
std::snprintf(parm, sizeof(parm), "P_LANENAME:%d", laneIdx);
|
||||
std::vector<char> name(m->laneKey.begin(), m->laneKey.end());
|
||||
@@ -658,6 +644,17 @@ void reconcileManagedLanes(ViewModeModel& model, ReaProject* proj) {
|
||||
if (!key) continue; // manual/unnamed lane — leave off the index
|
||||
std::optional<std::string> mode = modeIdFromLaneName(name);
|
||||
if (!mode) continue; // prefix-only/illegal name — skip defensively
|
||||
|
||||
// UNREGISTERED-MODE GUARD: the durable name encodes a mode id, but that mode
|
||||
// may no longer be a registered Mode (e.g. a mode removed from the registry
|
||||
// after the project was saved with lanes minted for it). Recording it MANAGED
|
||||
// would make the toggle planner drive a lane keyed to a mode that can never be
|
||||
// the active mode — the lane would stay silenced+hidden forever, orphaning its
|
||||
// items with no way for the user to reach them. So we do NOT record it: the
|
||||
// lane is left off the ownership index and thus treated as manual-by-default
|
||||
// (never driven). Its durable name is preserved on the track, so if the mode is
|
||||
// ever re-registered a later reconcile recovers the ownership cleanly.
|
||||
if (!model.modes().contains(*mode)) continue;
|
||||
model.lanes().setManaged(guid, *key, *mode);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user