prepare for converting keybinds to actions

issue #157
This commit is contained in:
tildearrow 2022-02-10 16:37:17 -05:00
parent bb7cbb9ced
commit 4e936f2c2d
2 changed files with 214 additions and 69 deletions

View file

@ -1081,7 +1081,7 @@ void FurnaceGUI::drawOrders() {
if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) {
if (curOrder==i) {
if (orderEditMode==0) {
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
if (changeAllOrders) {
for (int k=0; k<e->getTotalChannelCount(); k++) {
if (e->song.orders.ord[k][i]<0x7f) e->song.orders.ord[k][i]++;
@ -1090,7 +1090,7 @@ void FurnaceGUI::drawOrders() {
if (e->song.orders.ord[j][i]<0x7f) e->song.orders.ord[j][i]++;
}
e->walkSong(loopOrder,loopRow,loopEnd);
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
} else {
orderCursor=j;
curNibble=false;
@ -1107,7 +1107,7 @@ void FurnaceGUI::drawOrders() {
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (curOrder==i) {
if (orderEditMode==0) {
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
if (changeAllOrders) {
for (int k=0; k<e->getTotalChannelCount(); k++) {
if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--;
@ -1116,7 +1116,7 @@ void FurnaceGUI::drawOrders() {
if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--;
}
e->walkSong(loopOrder,loopRow,loopEnd);
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
} else {
orderCursor=j;
curNibble=false;
@ -1139,39 +1139,39 @@ void FurnaceGUI::drawOrders() {
ImGui::NextColumn();
if (ImGui::Button(ICON_FA_PLUS)) {
// add order row (new)
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->addOrder(false,false);
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
}
if (ImGui::Button(ICON_FA_MINUS)) {
// remove this order row
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->deleteOrder();
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
}
if (ImGui::Button(ICON_FA_FILES_O)) {
// duplicate order row
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->addOrder(true,false);
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
}
if (ImGui::Button(ICON_FA_ANGLE_UP)) {
// move order row up
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->moveOrderUp();
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
}
if (ImGui::Button(ICON_FA_ANGLE_DOWN)) {
// move order row down
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->moveOrderDown();
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
}
if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) {
// duplicate order row at end
prepareUndo(GUI_ACTION_CHANGE_ORDER);
prepareUndo(GUI_UNDO_CHANGE_ORDER);
e->addOrder(true,true);
makeUndo(GUI_ACTION_CHANGE_ORDER);
makeUndo(GUI_UNDO_CHANGE_ORDER);
}
if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) {
// whether to change one or all orders in a row
@ -4615,16 +4615,16 @@ void FurnaceGUI::editAdvance() {
void FurnaceGUI::prepareUndo(ActionType action) {
int order=e->getOrder();
switch (action) {
case GUI_ACTION_CHANGE_ORDER:
case GUI_UNDO_CHANGE_ORDER:
oldOrders=e->song.orders;
oldOrdersLen=e->song.ordersLen;
break;
case GUI_ACTION_PATTERN_EDIT:
case GUI_ACTION_PATTERN_DELETE:
case GUI_ACTION_PATTERN_PULL:
case GUI_ACTION_PATTERN_PUSH:
case GUI_ACTION_PATTERN_CUT:
case GUI_ACTION_PATTERN_PASTE:
case GUI_UNDO_PATTERN_EDIT:
case GUI_UNDO_PATTERN_DELETE:
case GUI_UNDO_PATTERN_PULL:
case GUI_UNDO_PATTERN_PUSH:
case GUI_UNDO_PATTERN_CUT:
case GUI_UNDO_PATTERN_PASTE:
for (int i=0; i<e->getTotalChannelCount(); i++) {
memcpy(oldPat[i],e->song.pat[i].getPattern(e->song.orders.ord[i][order],false),sizeof(DivPattern));
}
@ -4643,7 +4643,7 @@ void FurnaceGUI::makeUndo(ActionType action) {
s.order=order;
s.nibble=curNibble;
switch (action) {
case GUI_ACTION_CHANGE_ORDER:
case GUI_UNDO_CHANGE_ORDER:
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=0; j<128; j++) {
if (oldOrders.ord[i][j]!=e->song.orders.ord[i][j]) {
@ -4660,12 +4660,12 @@ void FurnaceGUI::makeUndo(ActionType action) {
doPush=true;
}
break;
case GUI_ACTION_PATTERN_EDIT:
case GUI_ACTION_PATTERN_DELETE:
case GUI_ACTION_PATTERN_PULL:
case GUI_ACTION_PATTERN_PUSH:
case GUI_ACTION_PATTERN_CUT:
case GUI_ACTION_PATTERN_PASTE:
case GUI_UNDO_PATTERN_EDIT:
case GUI_UNDO_PATTERN_DELETE:
case GUI_UNDO_PATTERN_PULL:
case GUI_UNDO_PATTERN_PUSH:
case GUI_UNDO_PATTERN_CUT:
case GUI_UNDO_PATTERN_PASTE:
for (int i=0; i<e->getTotalChannelCount(); i++) {
DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false);
for (int j=0; j<e->song.patLen; j++) {
@ -4734,7 +4734,7 @@ void FurnaceGUI::doSelectAll() {
void FurnaceGUI::doDelete() {
finishSelection();
prepareUndo(GUI_ACTION_PATTERN_DELETE);
prepareUndo(GUI_UNDO_PATTERN_DELETE);
curNibble=false;
int iCoarse=selStart.xCoarse;
@ -4755,12 +4755,12 @@ void FurnaceGUI::doDelete() {
iFine=0;
}
makeUndo(GUI_ACTION_PATTERN_DELETE);
makeUndo(GUI_UNDO_PATTERN_DELETE);
}
void FurnaceGUI::doPullDelete() {
finishSelection();
prepareUndo(GUI_ACTION_PATTERN_PULL);
prepareUndo(GUI_UNDO_PATTERN_PULL);
curNibble=false;
if (settings.pullDeleteBehavior) {
@ -4794,12 +4794,12 @@ void FurnaceGUI::doPullDelete() {
iFine=0;
}
makeUndo(GUI_ACTION_PATTERN_PULL);
makeUndo(GUI_UNDO_PATTERN_PULL);
}
void FurnaceGUI::doInsert() {
finishSelection();
prepareUndo(GUI_ACTION_PATTERN_PUSH);
prepareUndo(GUI_UNDO_PATTERN_PUSH);
curNibble=false;
int iCoarse=selStart.xCoarse;
@ -4826,12 +4826,12 @@ void FurnaceGUI::doInsert() {
iFine=0;
}
makeUndo(GUI_ACTION_PATTERN_PUSH);
makeUndo(GUI_UNDO_PATTERN_PUSH);
}
void FurnaceGUI::doTranspose(int amount) {
finishSelection();
prepareUndo(GUI_ACTION_PATTERN_DELETE);
prepareUndo(GUI_UNDO_PATTERN_DELETE);
curNibble=false;
int iCoarse=selStart.xCoarse;
@ -4872,14 +4872,14 @@ void FurnaceGUI::doTranspose(int amount) {
iFine=0;
}
makeUndo(GUI_ACTION_PATTERN_DELETE);
makeUndo(GUI_UNDO_PATTERN_DELETE);
}
void FurnaceGUI::doCopy(bool cut) {
finishSelection();
if (cut) {
curNibble=false;
prepareUndo(GUI_ACTION_PATTERN_CUT);
prepareUndo(GUI_UNDO_PATTERN_CUT);
}
clipboard=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)\n%d",DIV_ENGINE_VERSION,selStart.xFine);
@ -4919,13 +4919,13 @@ void FurnaceGUI::doCopy(bool cut) {
SDL_SetClipboardText(clipboard.c_str());
if (cut) {
makeUndo(GUI_ACTION_PATTERN_CUT);
makeUndo(GUI_UNDO_PATTERN_CUT);
}
}
void FurnaceGUI::doPaste() {
finishSelection();
prepareUndo(GUI_ACTION_PATTERN_PASTE);
prepareUndo(GUI_UNDO_PATTERN_PASTE);
char* clipText=SDL_GetClipboardText();
if (clipText!=NULL) {
if (clipText[0]) {
@ -5034,7 +5034,7 @@ void FurnaceGUI::doPaste() {
j++;
}
makeUndo(GUI_ACTION_PATTERN_PASTE);
makeUndo(GUI_UNDO_PATTERN_PASTE);
}
void FurnaceGUI::doUndo() {
@ -5044,18 +5044,18 @@ void FurnaceGUI::doUndo() {
modified=true;
switch (us.type) {
case GUI_ACTION_CHANGE_ORDER:
case GUI_UNDO_CHANGE_ORDER:
e->song.ordersLen=us.oldOrdersLen;
for (UndoOrderData& i: us.ord) {
e->song.orders.ord[i.chan][i.ord]=i.oldVal;
}
break;
case GUI_ACTION_PATTERN_EDIT:
case GUI_ACTION_PATTERN_DELETE:
case GUI_ACTION_PATTERN_PULL:
case GUI_ACTION_PATTERN_PUSH:
case GUI_ACTION_PATTERN_CUT:
case GUI_ACTION_PATTERN_PASTE:
case GUI_UNDO_PATTERN_EDIT:
case GUI_UNDO_PATTERN_DELETE:
case GUI_UNDO_PATTERN_PULL:
case GUI_UNDO_PATTERN_PUSH:
case GUI_UNDO_PATTERN_CUT:
case GUI_UNDO_PATTERN_PASTE:
for (UndoPatternData& i: us.pat) {
DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.oldVal;
@ -5081,18 +5081,18 @@ void FurnaceGUI::doRedo() {
modified=true;
switch (us.type) {
case GUI_ACTION_CHANGE_ORDER:
case GUI_UNDO_CHANGE_ORDER:
e->song.ordersLen=us.newOrdersLen;
for (UndoOrderData& i: us.ord) {
e->song.orders.ord[i.chan][i.ord]=i.newVal;
}
break;
case GUI_ACTION_PATTERN_EDIT:
case GUI_ACTION_PATTERN_DELETE:
case GUI_ACTION_PATTERN_PULL:
case GUI_ACTION_PATTERN_PUSH:
case GUI_ACTION_PATTERN_CUT:
case GUI_ACTION_PATTERN_PASTE:
case GUI_UNDO_PATTERN_EDIT:
case GUI_UNDO_PATTERN_DELETE:
case GUI_UNDO_PATTERN_PULL:
case GUI_UNDO_PATTERN_PUSH:
case GUI_UNDO_PATTERN_CUT:
case GUI_UNDO_PATTERN_PASTE:
for (UndoPatternData& i: us.pat) {
DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.newVal;
@ -5376,7 +5376,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
if (edit) {
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true);
prepareUndo(GUI_ACTION_PATTERN_EDIT);
prepareUndo(GUI_UNDO_PATTERN_EDIT);
if (key==100) { // note off
pat->data[cursor.y][0]=100;
@ -5398,7 +5398,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
pat->data[cursor.y][2]=curIns;
previewNote(cursor.xCoarse,num);
}
makeUndo(GUI_ACTION_PATTERN_EDIT);
makeUndo(GUI_UNDO_PATTERN_EDIT);
editAdvance();
curNibble=false;
} else {
@ -5412,7 +5412,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
try {
int num=valueKeys.at(ev.key.keysym.sym);
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true);
prepareUndo(GUI_ACTION_PATTERN_EDIT);
prepareUndo(GUI_UNDO_PATTERN_EDIT);
if (pat->data[cursor.y][cursor.xFine+1]==-1) pat->data[cursor.y][cursor.xFine+1]=0;
pat->data[cursor.y][cursor.xFine+1]=((pat->data[cursor.y][cursor.xFine+1]<<4)|num)&0xff;
if (cursor.xFine==1) { // instrument
@ -5422,7 +5422,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
pat->data[cursor.y][cursor.xFine+1]=(int)e->song.ins.size()-1;
}
}
makeUndo(GUI_ACTION_PATTERN_EDIT);
makeUndo(GUI_UNDO_PATTERN_EDIT);
if (e->song.ins.size()<16) {
curNibble=false;
editAdvance();
@ -5436,7 +5436,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
} else {
pat->data[cursor.y][cursor.xFine+1]&=15;
}
makeUndo(GUI_ACTION_PATTERN_EDIT);
makeUndo(GUI_UNDO_PATTERN_EDIT);
if (e->getMaxVolumeChan(cursor.xCoarse)<16) {
curNibble=false;
editAdvance();
@ -5445,7 +5445,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
if (!curNibble) editAdvance();
}
} else {
makeUndo(GUI_ACTION_PATTERN_EDIT);
makeUndo(GUI_UNDO_PATTERN_EDIT);
curNibble=!curNibble;
if (!curNibble) editAdvance();
}

View file

@ -117,6 +117,151 @@ enum FurnaceGUIFMAlgs {
FM_ALGS_4OP_OPL
};
enum FurnaceGUIActions {
GUI_ACTION_OPEN,
GUI_ACTION_SAVE,
GUI_ACTION_UNDO,
GUI_ACTION_REDO,
GUI_ACTION_PLAY_TOGGLE,
GUI_ACTION_PLAY,
GUI_ACTION_STOP,
GUI_ACTION_PLAY_REPEAT,
GUI_ACTION_PLAY_CURSOR,
GUI_ACTION_STEP_ONE,
GUI_ACTION_OCTAVE_UP,
GUI_ACTION_OCTAVE_DOWN,
GUI_ACTION_INS_UP,
GUI_ACTION_INS_DOWN,
GUI_ACTION_STEP_UP,
GUI_ACTION_STEP_DOWN,
GUI_ACTION_TOGGLE_EDIT,
GUI_ACTION_METRONOME,
GUI_ACTION_REPEAT_PATTERN,
GUI_ACTION_FOLLOW_ORDERS,
GUI_ACTION_FOLLOW_PATTERN,
GUI_ACTION_PANIC,
GUI_ACTION_WINDOW_EDIT_CONTROLS,
GUI_ACTION_WINDOW_ORDERS,
GUI_ACTION_WINDOW_INS_LIST,
GUI_ACTION_WINDOW_INS_EDIT,
GUI_ACTION_WINDOW_SONG_INFO,
GUI_ACTION_WINDOW_PATTERN,
GUI_ACTION_WINDOW_WAVE_LIST,
GUI_ACTION_WINDOW_WAVE_EDIT,
GUI_ACTION_WINDOW_SAMPLE_LIST,
GUI_ACTION_WINDOW_SAMPLE_EDIT,
GUI_ACTION_WINDOW_ABOUT,
GUI_ACTION_WINDOW_SETTINGS,
GUI_ACTION_WINDOW_MIXER,
GUI_ACTION_WINDOW_DEBUG,
GUI_ACTION_WINDOW_VOL_METER,
GUI_ACTION_WINDOW_STATS,
GUI_ACTION_WINDOW_COMPAT_FLAGS,
GUI_ACTION_WINDOW_PIANO,
GUI_ACTION_WINDOW_NOTES,
GUI_ACTION_WINDOW_CHANNELS,
GUI_ACTION_COLLAPSE_WINDOW,
GUI_ACTION_CLOSE_WINDOW,
GUI_ACTION_PAT_NOTE_UP,
GUI_ACTION_PAT_NOTE_DOWN,
GUI_ACTION_PAT_OCTAVE_UP,
GUI_ACTION_PAT_OCTAVE_DOWN,
GUI_ACTION_PAT_SELECT_ALL,
GUI_ACTION_PAT_CUT,
GUI_ACTION_PAT_COPY,
GUI_ACTION_PAT_PASTE,
GUI_ACTION_PAT_CURSOR_UP,
GUI_ACTION_PAT_CURSOR_DOWN,
GUI_ACTION_PAT_CURSOR_LEFT,
GUI_ACTION_PAT_CURSOR_RIGHT,
GUI_ACTION_PAT_CURSOR_UP_ONE,
GUI_ACTION_PAT_CURSOR_DOWN_ONE,
GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL,
GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL,
GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL,
GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL,
GUI_ACTION_PAT_CURSOR_BEGIN,
GUI_ACTION_PAT_CURSOR_END,
GUI_ACTION_PAT_CURSOR_UP_COARSE,
GUI_ACTION_PAT_CURSOR_DOWN_COARSE,
GUI_ACTION_PAT_SELECTION_UP,
GUI_ACTION_PAT_SELECTION_DOWN,
GUI_ACTION_PAT_SELECTION_LEFT,
GUI_ACTION_PAT_SELECTION_RIGHT,
GUI_ACTION_PAT_SELECTION_UP_ONE,
GUI_ACTION_PAT_SELECTION_DOWN_ONE,
GUI_ACTION_PAT_SELECTION_BEGIN,
GUI_ACTION_PAT_SELECTION_END,
GUI_ACTION_PAT_SELECTION_UP_COARSE,
GUI_ACTION_PAT_SELECTION_DOWN_COARSE,
GUI_ACTION_PAT_DELETE,
GUI_ACTION_PAT_PULL_DELETE,
GUI_ACTION_PAT_INSERT,
GUI_ACTION_PAT_MUTE_CURSOR,
GUI_ACTION_PAT_SOLO_CURSOR,
GUI_ACTION_PAT_NEXT_ORDER,
GUI_ACTION_PAT_PREV_ORDER,
GUI_ACTION_PAT_COLLAPSE,
GUI_ACTION_PAT_INCREASE_COLUMNS,
GUI_ACTION_PAT_DECREASE_COLUMNS,
GUI_ACTION_INS_LIST_ADD,
GUI_ACTION_INS_LIST_DUPLICATE,
GUI_ACTION_INS_LIST_OPEN,
GUI_ACTION_INS_LIST_SAVE,
GUI_ACTION_INS_LIST_MOVE_UP,
GUI_ACTION_INS_LIST_MOVE_DOWN,
GUI_ACTION_INS_LIST_DELETE,
GUI_ACTION_INS_LIST_EDIT,
GUI_ACTION_INS_LIST_UP,
GUI_ACTION_INS_LIST_DOWN,
GUI_ACTION_WAVE_LIST_ADD,
GUI_ACTION_WAVE_LIST_DUPLICATE,
GUI_ACTION_WAVE_LIST_OPEN,
GUI_ACTION_WAVE_LIST_SAVE,
GUI_ACTION_WAVE_LIST_MOVE_UP,
GUI_ACTION_WAVE_LIST_MOVE_DOWN,
GUI_ACTION_WAVE_LIST_DELETE,
GUI_ACTION_WAVE_LIST_EDIT,
GUI_ACTION_WAVE_LIST_UP,
GUI_ACTION_WAVE_LIST_DOWN,
GUI_ACTION_SAMPLE_LIST_ADD,
GUI_ACTION_SAMPLE_LIST_DUPLICATE,
GUI_ACTION_SAMPLE_LIST_OPEN,
GUI_ACTION_SAMPLE_LIST_SAVE,
GUI_ACTION_SAMPLE_LIST_MOVE_UP,
GUI_ACTION_SAMPLE_LIST_MOVE_DOWN,
GUI_ACTION_SAMPLE_LIST_DELETE,
GUI_ACTION_SAMPLE_LIST_EDIT,
GUI_ACTION_SAMPLE_LIST_UP,
GUI_ACTION_SAMPLE_LIST_DOWN,
GUI_ACTION_SAMPLE_LIST_PREVIEW,
GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW,
GUI_ACTION_ORDERS_UP,
GUI_ACTION_ORDERS_DOWN,
GUI_ACTION_ORDERS_LEFT,
GUI_ACTION_ORDERS_RIGHT,
GUI_ACTION_ORDERS_INCREASE,
GUI_ACTION_ORDERS_DECREASE,
GUI_ACTION_ORDERS_EDIT_MODE,
GUI_ACTION_ORDERS_LINK,
GUI_ACTION_ORDERS_ADD,
GUI_ACTION_ORDERS_DUPLICATE,
GUI_ACTION_ORDERS_DEEP_CLONE,
GUI_ACTION_ORDERS_DUPLICATE_END,
GUI_ACTION_ORDERS_DEEP_CLONE_END,
GUI_ACTION_ORDERS_REMOVE,
GUI_ACTION_ORDERS_MOVE_UP,
GUI_ACTION_ORDERS_MOVE_DOWN,
GUI_ACTION_ORDERS_REPLAY,
};
struct SelectionPoint {
int xCoarse, xFine;
int y;
@ -125,13 +270,13 @@ struct SelectionPoint {
};
enum ActionType {
GUI_ACTION_CHANGE_ORDER,
GUI_ACTION_PATTERN_EDIT,
GUI_ACTION_PATTERN_DELETE,
GUI_ACTION_PATTERN_PULL,
GUI_ACTION_PATTERN_PUSH,
GUI_ACTION_PATTERN_CUT,
GUI_ACTION_PATTERN_PASTE
GUI_UNDO_CHANGE_ORDER,
GUI_UNDO_PATTERN_EDIT,
GUI_UNDO_PATTERN_DELETE,
GUI_UNDO_PATTERN_PULL,
GUI_UNDO_PATTERN_PUSH,
GUI_UNDO_PATTERN_CUT,
GUI_UNDO_PATTERN_PASTE
};
struct UndoPatternData {