From bdce41689afc1a2c2774668badbe209eb599b234 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Oct 2026 22:16:17 +0000 Subject: [PATCH 1/6] Initial plan From 03c5a2576eae3b3363377fc4d5c1c9e824559c3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Oct 2026 22:22:57 +0000 Subject: [PATCH 2/6] Fix Godley popup undo and redo history Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- .../src/app/managers/GodleyMenuManager.ts | 14 ++-- .../godley-widget-view.component.ts | 5 ++ model/godleyIcon.cc | 3 + model/godleyTableWindow.cc | 64 ++++++++++++++++--- test/00/godleyTable.sh | 25 ++++++++ 5 files changed, 98 insertions(+), 13 deletions(-) diff --git a/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts b/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts index c837ce598..26a184a4e 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts @@ -20,7 +20,7 @@ export class GodleyMenuManager { const godley = new GodleyIcon(minsky.namedItems.elem(itemInfo.id)); const menu = Menu.buildFromTemplate([ scope.getGodleyFileMenuItem(window, godley), - scope.getGodleyEditMenuItem(godley), + scope.getGodleyEditMenuItem(window, godley), scope.getGodleyViewMenuItem(window), scope.getGodleyOptionsMenuItem(window), new MenuItem({ @@ -188,6 +188,7 @@ export class GodleyMenuManager { } private static getGodleyEditMenuItem( + window: Electron.BrowserWindow, godley: GodleyIcon ) { return new MenuItem({ @@ -196,12 +197,18 @@ export class GodleyMenuManager { { label: 'Undo', accelerator: 'CmdOrCtrl + z', - click: () => {godley.popup.undo(1);}, + click: async () => { + await godley.popup.undo(1); + GodleyMenuManager.refresh(window); + }, }, { label: 'Redo', accelerator: 'CmdOrCtrl + y', - click: () => {godley.popup.undo(-1);}, + click: async () => { + await godley.popup.undo(-1); + GodleyMenuManager.refresh(window); + }, }, { label: 'Title', @@ -281,4 +288,3 @@ export class GodleyMenuManager { menu.popup(); } } - diff --git a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts index c37714693..ef12d4c47 100644 --- a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts +++ b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts @@ -256,6 +256,7 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn async onRowAdd(i) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.insertRow(i+3); await this.hardRefresh(); } @@ -268,24 +269,28 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn async onRowMove(i, n) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.moveRow(i+2,n); await this.hardRefresh(); } async onColumnAdd(i) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.insertCol(i+2); await this.hardRefresh(); } async onColumnDelete(i) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.deleteCol(i+2); await this.hardRefresh(); } async onColumnMove(i, n) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.moveCol(i+1,n); await this.hardRefresh(); } diff --git a/model/godleyIcon.cc b/model/godleyIcon.cc index 7661d5f0b..eff02cb63 100644 --- a/model/godleyIcon.cc +++ b/model/godleyIcon.cc @@ -224,6 +224,7 @@ namespace minsky void GodleyIcon::setCell(int row, int col, const string& newVal) { if (!table.cellInTable(row,col)) return; + popup.pushHistory(); // if this operation is clearing an initial condition cell, set it to 0 string& c=table.cell(row,col); if (newVal.empty() && !c.empty() && table.initialConditionRow(row)) @@ -241,6 +242,7 @@ namespace minsky { if (row>0 && row<=table.rows()) { + popup.pushHistory(); table.deleteRow(row-1); // if shared column data is deleted, remove it from the other tables too for (size_t col=1; col0) m_godleyIcon.table.insertCol(c+1); + if (c>0) + { + pushHistory(); + m_godleyIcon.table.insertCol(c+1); + } } void GodleyTableEditor::importStockVar(const string& name, double x) @@ -870,6 +879,7 @@ namespace minsky { if (c>0 && size_t(c)=0) - m_godleyIcon.table.deleteCol(c+1); + { + pushHistory(); + m_godleyIcon.table.deleteCol(c+1); + } } void GodleyTableEditor::addFlow(double y) @@ -897,8 +910,11 @@ namespace minsky void GodleyTableEditor::addFlowByRow(int r) { - if (r>0) - m_godleyIcon.table.insertRow(r+1); + if (r>0) + { + pushHistory(); + m_godleyIcon.table.insertRow(r+1); + } } void GodleyTableEditor::deleteFlow(double y) @@ -910,7 +926,10 @@ namespace minsky void GodleyTableEditor::deleteFlowByRow(int r) { if (r>1) // Cannot delete flow in Initial Conditions row. For ticket 1064 - m_godleyIcon.deleteRow(r+1); + { + pushHistory(); + m_godleyIcon.deleteRow(r+1); + } } namespace { @@ -1013,25 +1032,52 @@ namespace { void GodleyTableEditor::pushHistory() { - while (history.size()>maxHistory) history.pop_front(); + if (historyPtr0 && history[historyPtr-1]==m_godleyIcon.table) + return; + history.erase(history.begin()+historyPtr, history.end()); + } // Perform deep comparison of Godley tables in history to avoid spurious noAssetClass columns from arising during undo. For ticket 1118. if (history.empty() || !(history.back()==m_godleyIcon.table)) { history.push_back(m_godleyIcon.table); } + while (history.size()>maxHistory) + { + history.pop_front(); + if (historyPtr>0) --historyPtr; + } historyPtr=history.size(); } void GodleyTableEditor::undo(int changes) { - if (historyPtr==history.size()) - pushHistory(); - historyPtr-=changes; + const bool pendingChange=historyPtr==0 || + !(history[historyPtr-1]==m_godleyIcon.table); + if (pendingChange) + { + pushHistory(); + if (changes<0) return; + } + if (changes>0 && static_cast(changes)>=historyPtr) + return; + if (changes<0 && + static_cast(-static_cast(changes))> + history.size()-historyPtr) + return; + if (changes>0) + historyPtr-=changes; + else if (changes<0) + historyPtr+=-static_cast(changes); if (historyPtr > 0 && historyPtr <= history.size()) { auto& d=history[historyPtr-1]; // Perform deep comparison of Godley tables in history to avoid spurious noAssetClass columns from arising during undo. For ticket 1118. if (d.getData().empty()) return; // should not happen m_godleyIcon.table=d; + m_godleyIcon.update(); + adjustWidgets(); + requestRedrawCanvas(); } } diff --git a/test/00/godleyTable.sh b/test/00/godleyTable.sh index b6c20e8af..bb2477eab 100755 --- a/test/00/godleyTable.sh +++ b/test/00/godleyTable.sh @@ -75,6 +75,31 @@ assert minsky.canvas.item.table.rowSum(1)=='0' assert minsky.canvas.item.table.rowSum(2)=='b' assert minsky.canvas.item.table.getVariables()()==['a', 'b'] +# popup undo/redo tracks cell edits and discards stale redo states +originalCell = item.table.getCell(2, 1) +item.setCell(2, 1, 'changed') +item.popup.undo(1) +assert item.table.getCell(2, 1)==originalCell +item.popup.undo(-1) +assert item.table.getCell(2, 1)=='changed' +item.popup.undo(1) +item.setCell(2, 1, 'branched') +item.popup.undo(-1) +assert item.table.getCell(2, 1)=='branched' +item.popup.undo(1) +assert item.table.getCell(2, 1)==originalCell +item.popup.undo(-1) +assert item.table.getCell(2, 1)=='branched' + +# row edits made through the popup are also undoable +rows = item.table.rows() +item.popup.addFlowByRow(rows-1) +assert item.table.rows()==rows+1 +item.popup.undo(1) +assert item.table.rows()==rows +item.popup.undo(-1) +assert item.table.rows()==rows+1 + minsky.canvas.deleteItem() assert minsky.model.numItems()==0 EOF From 4268541643f105fc29ebada294e9a875e3db054c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Oct 2026 00:34:59 +0000 Subject: [PATCH 3/6] Keep Godley redo history after table refresh Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- model/godleyTableWindow.cc | 4 +++- test/00/godleyTable.sh | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/model/godleyTableWindow.cc b/model/godleyTableWindow.cc index 88b9c05e4..d2c6bd230 100644 --- a/model/godleyTableWindow.cc +++ b/model/godleyTableWindow.cc @@ -1074,8 +1074,10 @@ namespace { auto& d=history[historyPtr-1]; // Perform deep comparison of Godley tables in history to avoid spurious noAssetClass columns from arising during undo. For ticket 1118. if (d.getData().empty()) return; // should not happen - m_godleyIcon.table=d; + const GodleyTable restoredTable=d; + m_godleyIcon.table=restoredTable; m_godleyIcon.update(); + m_godleyIcon.table=restoredTable; adjustWidgets(); requestRedrawCanvas(); } diff --git a/test/00/godleyTable.sh b/test/00/godleyTable.sh index bb2477eab..5d69abe62 100755 --- a/test/00/godleyTable.sh +++ b/test/00/godleyTable.sh @@ -91,6 +91,15 @@ assert item.table.getCell(2, 1)==originalCell item.popup.undo(-1) assert item.table.getCell(2, 1)=='branched' +# updating after undo must not change a restored blank initial condition +item.popup.pushHistory() +item.table.setCell(1, 1, '') +item.setCell(2, 1, 'changed') +item.popup.undo(1) +assert item.table.getCell(1, 1)=='' +item.popup.undo(-1) +assert item.table.getCell(2, 1)=='changed' + # row edits made through the popup are also undoable rows = item.table.rows() item.popup.addFlowByRow(rows-1) From dd37bd63a25986e631d8c76664890c5108cfa8ac Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Tue, 20 Oct 2026 14:34:43 +1100 Subject: [PATCH 4/6] bugfix: undo of godley table clearing initial conditions should clear stock var's initial conditions. --- gui-js/package-lock.json | 63 -------------------------------------- model/godleyIcon.cc | 8 ++--- model/godleyIcon.h | 3 +- model/godleyTableWindow.cc | 3 +- 4 files changed, 7 insertions(+), 70 deletions(-) diff --git a/gui-js/package-lock.json b/gui-js/package-lock.json index 01b266512..339a74ff4 100644 --- a/gui-js/package-lock.json +++ b/gui-js/package-lock.json @@ -14170,21 +14170,6 @@ } } }, - "node_modules/html-encoding-sniffer/node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -16826,21 +16811,6 @@ } } }, - "node_modules/jsdom/node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/jsdom/node_modules/lru-cache": { "version": "11.5.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", @@ -24986,21 +24956,6 @@ } } }, - "node_modules/whatwg-url/node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", @@ -25308,24 +25263,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yaml": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", - "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yargs": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", diff --git a/model/godleyIcon.cc b/model/godleyIcon.cc index eff02cb63..508076c33 100644 --- a/model/godleyIcon.cc +++ b/model/godleyIcon.cc @@ -304,7 +304,7 @@ namespace minsky return r; } - void GodleyIcon::update() + void GodleyIcon::update(bool preserveTable) { updateVars(m_stockVars, table.getColumnVariables(), VariableType::stock); updateVars(m_flowVars, table.getVariables(), VariableType::flow); @@ -332,16 +332,16 @@ namespace minsky fc1.coef*=fc.coef; v.init(fc1.str()); } - v.godleyOverridden=true; } + else if (preserveTable) + v.init(""); else { // populate cell with current variable's initial value const FlowCoef fc(v.init()); table.cell(r,c)=fc.str(); - v.godleyOverridden=true; } - + v.godleyOverridden=true; } if (m_variableDisplay) diff --git a/model/godleyIcon.h b/model/godleyIcon.h index 69e52604a..32acd5f82 100644 --- a/model/godleyIcon.h +++ b/model/godleyIcon.h @@ -121,7 +121,8 @@ namespace minsky std::vector summarise() const; /// updates the variable lists with the Godley table - void update(); + /// @param preserveTable - do no populate table's initial conditions from variable init conditions + void update(bool preserveTable=false); GodleyIcon* clone() const override { auto r=new GodleyIcon(*this); diff --git a/model/godleyTableWindow.cc b/model/godleyTableWindow.cc index d2c6bd230..b3e37dd4e 100644 --- a/model/godleyTableWindow.cc +++ b/model/godleyTableWindow.cc @@ -1076,8 +1076,7 @@ namespace { if (d.getData().empty()) return; // should not happen const GodleyTable restoredTable=d; m_godleyIcon.table=restoredTable; - m_godleyIcon.update(); - m_godleyIcon.table=restoredTable; + m_godleyIcon.update(true); adjustWidgets(); requestRedrawCanvas(); } From dbec42327fafdb03009226b22a543567cedbf26d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Oct 2026 04:01:46 +0000 Subject: [PATCH 5/6] Commit active Godley edit before undo Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- .../src/app/events/electron.events.ts | 9 ++++++++ .../src/app/managers/GodleyMenuManager.ts | 21 ++++++++++--------- .../shared/src/lib/constants/constants.ts | 2 ++ .../godley-widget-view.component.ts | 4 ++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts index 73e725692..34c16da71 100644 --- a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts +++ b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts @@ -150,6 +150,15 @@ ipcMain.handle( } ); +ipcMain.handle( + events.GODLEY_POPUP_UNDO, + async (event, payload: { itemId: string; changes: number }) => { + const godley = new GodleyIcon(minsky.namedItems.elem(payload.itemId)); + await godley.popup.undo(payload.changes); + event.sender.send(events.GODLEY_POPUP_REFRESH); + } +); + ipcMain.on( events.REFRESH_ALL_GODLEY_POPUPS, async () => { WindowManager.refreshAllGodleyPopups(); diff --git a/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts b/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts index 26a184a4e..8950cfaf3 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts @@ -20,7 +20,7 @@ export class GodleyMenuManager { const godley = new GodleyIcon(minsky.namedItems.elem(itemInfo.id)); const menu = Menu.buildFromTemplate([ scope.getGodleyFileMenuItem(window, godley), - scope.getGodleyEditMenuItem(window, godley), + scope.getGodleyEditMenuItem(window, godley, itemInfo.id), scope.getGodleyViewMenuItem(window), scope.getGodleyOptionsMenuItem(window), new MenuItem({ @@ -189,7 +189,8 @@ export class GodleyMenuManager { private static getGodleyEditMenuItem( window: Electron.BrowserWindow, - godley: GodleyIcon + godley: GodleyIcon, + itemId: string ) { return new MenuItem({ label: 'Edit', @@ -197,18 +198,18 @@ export class GodleyMenuManager { { label: 'Undo', accelerator: 'CmdOrCtrl + z', - click: async () => { - await godley.popup.undo(1); - GodleyMenuManager.refresh(window); - }, + click: () => window.webContents?.send(events.GODLEY_POPUP_UNDO_REQUEST, { + itemId, + changes: 1, + }), }, { label: 'Redo', accelerator: 'CmdOrCtrl + y', - click: async () => { - await godley.popup.undo(-1); - GodleyMenuManager.refresh(window); - }, + click: () => window.webContents?.send(events.GODLEY_POPUP_UNDO_REQUEST, { + itemId, + changes: -1, + }), }, { label: 'Title', diff --git a/gui-js/libs/shared/src/lib/constants/constants.ts b/gui-js/libs/shared/src/lib/constants/constants.ts index 5471e1d10..b473fbd87 100644 --- a/gui-js/libs/shared/src/lib/constants/constants.ts +++ b/gui-js/libs/shared/src/lib/constants/constants.ts @@ -35,6 +35,8 @@ export const events = { GET_CURRENT_WINDOW: 'get-current-window', GET_PREFERENCES: 'get-preferences', GODLEY_POPUP_REFRESH: 'godley-popup-refresh', + GODLEY_POPUP_UNDO: 'godley-popup-undo', + GODLEY_POPUP_UNDO_REQUEST: 'godley-popup-undo-request', GODLEY_VIEW_MOUSEDOWN: 'godley-view-mousedown', GODLEY_VIEW_IMPORT_STOCK: 'godley-view-import-stock', HELP_FOR: 'help-for', diff --git a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts index ef12d4c47..f01e2991d 100644 --- a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts +++ b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts @@ -104,6 +104,10 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn this.windowId = (await this.electronService.getCurrentWindow()).id; if(!this.canvasMode) { + this.electronService.on(events.GODLEY_POPUP_UNDO_REQUEST, async (e, payload) => { + await this.finishEditing(); + await this.electronService.invoke(events.GODLEY_POPUP_UNDO, payload); + }); this.electronService.on(events.GODLEY_POPUP_REFRESH, async e => { await this.hardRefresh(); }); From 486227f2424778ceca42ca168da5a22031307ebb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Oct 2026 04:03:09 +0000 Subject: [PATCH 6/6] Make Godley cross-column moves a single undo step Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- model/godleyIcon.cc | 12 +++++++++--- model/godleyIcon.h | 1 + test/testMinsky.cc | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/model/godleyIcon.cc b/model/godleyIcon.cc index 508076c33..189c792be 100644 --- a/model/godleyIcon.cc +++ b/model/godleyIcon.cc @@ -222,9 +222,15 @@ namespace minsky } void GodleyIcon::setCell(int row, int col, const string& newVal) + { + setCellImpl(row, col, newVal, true); + } + + void GodleyIcon::setCellImpl(int row, int col, const string& newVal, + bool recordHistory) { if (!table.cellInTable(row,col)) return; - popup.pushHistory(); + if (recordHistory) popup.pushHistory(); // if this operation is clearing an initial condition cell, set it to 0 string& c=table.cell(row,col); if (newVal.empty() && !c.empty() && table.initialConditionRow(row)) @@ -263,8 +269,8 @@ namespace minsky // create a copy here, as setCell may resize the array, changing // what the reference referred to. const string oldVal=table.cell(args.srcRow, args.srcCol); - setCell(args.destRow, args.destCol, oldVal); - setCell(args.srcRow, args.srcCol, ""); + setCellImpl(args.destRow, args.destCol, oldVal, false); + setCellImpl(args.srcRow, args.srcCol, "", false); } else // within a column, just move the data without affecting any linked column { diff --git a/model/godleyIcon.h b/model/godleyIcon.h index 32acd5f82..17b5a5b81 100644 --- a/model/godleyIcon.h +++ b/model/godleyIcon.h @@ -57,6 +57,7 @@ namespace minsky } double titleOffs() const {return !table.title.empty()? 15*zoomFactor(): 0;} + void setCellImpl(int row, int col, const string& val, bool recordHistory); public: static SVGRenderer svgRenderer; ///< SVG icon to display when not in editor mode diff --git a/test/testMinsky.cc b/test/testMinsky.cc index 5e8aa0551..61ff2f006 100644 --- a/test/testMinsky.cc +++ b/test/testMinsky.cc @@ -959,6 +959,25 @@ TEST(TensorOps, evalOpEvaluate) EXPECT_EQ("",godley1.cell(2,2)); } + TEST_F(MinskySuite, godleyCrossColumnMoveUndo) + { + auto g=new GodleyIcon; + model->addItem(g); + g->table.resize(2,3); + g->table.cell(0,1)="a"; + g->table.cell(0,2)="b"; + g->table.cell(1,1)="flow"; + g->popup.pushHistory(); + + g->moveCell({1,1,1,2}); + EXPECT_EQ("",g->table.cell(1,1)); + EXPECT_EQ("flow",g->table.cell(1,2)); + + g->popup.undo(1); + EXPECT_EQ("flow",g->table.cell(1,1)); + EXPECT_EQ("",g->table.cell(1,2)); + } + TEST_F(MinskySuite,godleyRowSums) { auto g1=new GodleyIcon; model->addItem(g1);