1
0
Fork 0
forked from hanez/xtc

Phase 27: strengthen undo transactions and improve editor workflow

This commit is contained in:
Johannes Findeisen 2026-10-27 16:49:30 +01:00
commit c4daef7c42
21 changed files with 460 additions and 27 deletions

View file

@ -27,7 +27,7 @@ namespace {
xtc_backend handle;
xtc_result seek_result = XTC_OK;
xtc_result open_result = XTC_OK, start_result = XTC_OK, stop_result = XTC_OK, close_result = XTC_OK;
xtc_result status_result = XTC_OK, submit_result = XTC_OK;
xtc_result status_result = XTC_OK, submit_result = XTC_OK, reserve_result = XTC_OK;
unsigned int submissions = 0U;
xtc_backend_status engine_status = {};
bool partial_open = false;
@ -94,7 +94,7 @@ void reset()
{
CHECK(alive == 0U);
open_result = start_result = stop_result = close_result = status_result = XTC_OK;
submit_result = seek_result = XTC_OK;
submit_result = seek_result = reserve_result = XTC_OK;
engine_status = {};
engine_status.sample_rate = 48000U;
engine_status.buffer_frames = 1024U;
@ -102,6 +102,11 @@ void reset()
}
}
extern "C" {
xtc_result __real_xtc_history_reserve(xtc_history *history);
xtc_result __wrap_xtc_history_reserve(xtc_history *history) {
return reserve_result == XTC_OK ? __real_xtc_history_reserve(history) : reserve_result;
}
xtc_result xtc_backend_open(const xtc_backend_config *config, xtc_backend **out)
{
CHECK(config->kind == XTC_BACKEND_JACK && config->channel_count == 2U);
@ -334,6 +339,8 @@ int main(int argc, char **argv)
CHECK(sequence->position() == 1920); // Graph edits preserve the transport.
engine_status.sample_position = 0;
std::string take = std::string(directory) + "/take.wav";
size_t before_take_nodes = 0, before_take_edges = 0;
CHECK(xtc_graph_get_counts(canvas->graph(),&before_take_nodes,&before_take_edges) == XTC_OK);
CHECK(window.record_audio(take.c_str()) == XTC_OK && handle.recorder != nullptr);
CHECK(canvas->add_node(XTC_NODE_GAIN) == XTC_ERR_INVALID_STATE);
CHECK(window.save_project(filename.c_str()) == XTC_ERR_INVALID_STATE);
@ -349,6 +356,14 @@ int main(int argc, char **argv)
press(stop);
CHECK(handle.recorder == nullptr);
CHECK(xtc_project_get_counts(sequence->project(), &track_count, &clip_count) == XTC_OK && clip_count == 2);
CHECK(window.undo(false) == XTC_OK);
CHECK(xtc_project_get_counts(sequence->project(),&track_count,&clip_count) == XTC_OK && clip_count == 1);
CHECK(access(take.c_str(),F_OK) == 0); // Undo never deletes the external take.
CHECK(window.undo(false) == XTC_OK);
size_t after_take_nodes = 0, after_take_edges = 0;
CHECK(xtc_graph_get_counts(canvas->graph(),&after_take_nodes,&after_take_edges) == XTC_OK && after_take_nodes+1 == before_take_nodes);
CHECK(window.undo(true) == XTC_OK && window.undo(true) == XTC_OK);
CHECK(xtc_project_get_counts(sequence->project(),&track_count,&clip_count) == XTC_OK && clip_count == 2);
CHECK(sequence->waveform_count() == 1);
// Short clips use their four-pixel draw width for hit testing too.
xtc_clip_info audio_clip{};
@ -656,6 +671,36 @@ int main(int argc, char **argv)
CHECK(xtc_project_get_automation(sequence->project(),0,&point) == XTC_OK && point.step == 1);
unlink("/tmp/xtc-gui-controls-test.xtc"); window.do_callback();
}
{
reset(); MainWindow window(false);
auto *sequence = timeline(&window); auto *canvas = graph_canvas(&window);
CHECK(sequence->add_track() == XTC_OK && sequence->add_midi_clip() == XTC_OK);
const uint64_t track = sequence->selected_track(), clip = sequence->selected_clip();
CHECK(window.open_piano_roll() == XTC_OK);
CHECK(window.piano_roll()->add_note(0,240,60) == XTC_OK);
const uint64_t note = window.piano_roll()->selected_note();
CHECK(window.piano_roll()->add_note(240,240,64) == XTC_OK);
CHECK(window.piano_roll()->select_note(note));
CHECK(window.undo(false) == XTC_OK);
CHECK(sequence->selected_track() == track && sequence->selected_clip() == clip);
CHECK(window.piano_roll()->clip() == clip && window.piano_roll()->selected_note() == note);
const xtc_project *before = sequence->project(); unsigned int bindings[3] = {1,74,11};
unsigned int old_submissions = submissions;
reserve_result = XTC_ERR_OUT_OF_MEMORY;
CHECK(canvas->add_node(XTC_NODE_GAIN) == XTC_ERR_OUT_OF_MEMORY && sequence->project() == before);
CHECK(window.set_control_map(0,bindings) == XTC_ERR_OUT_OF_MEMORY && sequence->project() == before);
CHECK(window.import_audio("/absent/not-loaded.wav") == XTC_ERR_OUT_OF_MEMORY && sequence->project() == before);
CHECK(submissions == old_submissions);
reserve_result = XTC_OK;
CHECK(window.undo(true) == XTC_OK); // Failed commands preserved redo.
size_t count = 0;
CHECK(xtc_project_note_count(sequence->project(),&count) == XTC_OK && count == 2);
CHECK(canvas->add_node(XTC_NODE_GAIN) == XTC_OK);
const size_t node = canvas->selected_node(); int left, top;
CHECK(canvas->node_position(node,left,top) && canvas->select_at(left+50,top+30) && canvas->selected_node() == node);
CHECK(!canvas->select_at(-20,-20) && canvas->selected_node() == SIZE_MAX);
window.do_callback();
}
if (const char *module = std::getenv("XTC_TEST_PLUGIN")) {
reset();
MainWindow window(false);