From fb8e947602c2888b0f14332e669f847db7eaa172 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Wed, 10 Jan 2024 16:39:17 +1100 Subject: [PATCH 01/12] Preserve initial values of all variables except parameters that are pasted. Parameters take on values from pasted parameters. For #1677. --- model/minsky.cc | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/model/minsky.cc b/model/minsky.cc index 61208d618..1ba9a4025 100644 --- a/model/minsky.cc +++ b/model/minsky.cc @@ -217,19 +217,23 @@ namespace minsky void Minsky::paste() try { + map existingParms; + // preserve initial conditions. + for (auto& [valueId,vv]: variableValues) + existingParms.emplace(valueId,vv->init); + istringstream is(clipboard.getClipboard()); xml_unpack_t unpacker(is); schema3::Minsky m(unpacker); GroupPtr g(new Group); g->self=g; m.populateGroup(*g); - // stash values of parameters in copied group, as they are reset for some unknown reason later on. for ticket 1258 - map existingParms; - for (auto& i: g->items) { - auto v=i->variableCast(); - if (v && v->type()==VariableType::parameter) + + // stash values of parameters in the copied group, for ticket 1258 + for (auto& i: g->items) + if (auto v=i->variableCast(); v && v->type()==VariableType::parameter) existingParms.emplace(v->valueId(),v->init()); - } + // Default pasting no longer occurs as grouped items or as a group within a group. Fix for tickets 1080/1098 canvas.selection.clear(); // The following is only necessary if one pastes into an existing model. For ticket 1258 @@ -284,18 +288,14 @@ namespace minsky canvas.model->moveContents(*g); // leave newly ungrouped items in selection - for (auto& i: copyOfItems) { + for (auto& i: copyOfItems) canvas.selection.ensureItemInserted(i); - // ensure that initial values of pasted parameters are correct. for ticket 1258 - if (auto v=i->variableCast()) - if (v->type()==VariableType::parameter && !existingParms.empty()) - { - auto it=existingParms.find(v->valueId()); - if (it!=existingParms.end()) v->init(it->second); - } - } - - if (!existingParms.empty()) existingParms.clear(); + + // ensure that initial values of pasted parameters are correct. for ticket 1258 + for (auto& p: existingParms) + if (auto vv=variableValues.find(p.first); vv!=variableValues.end()) + vv->second->init=p.second; + existingParms.clear(); // Attach mouse focus only to first visible item in selection. For ticket 1098. for (auto& i: canvas.selection.items) From 5e6a85ba97fb4bb4949908e95ccbd8fa775131cd Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 11 Jan 2024 09:51:03 +1100 Subject: [PATCH 02/12] Update ecolab ref. --- ecolab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecolab b/ecolab index 9bb347237..3ad41fb1f 160000 --- a/ecolab +++ b/ecolab @@ -1 +1 @@ -Subproject commit 9bb347237e40e5cb0014e709310967230a5af786 +Subproject commit 3ad41fb1f8525e7e2152d3b3689b62a06b4e1afb From c38ee37a04d4d53e286db25571883ef322e61c7d Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 11 Jan 2024 16:02:21 +1100 Subject: [PATCH 03/12] Default I/O variables are now input* and output*. Double clicking on an I/O variable opens an edit dialog. For #1683. --- .../minsky-electron/src/app/managers/CommandsManager.ts | 7 +++++-- model/group.cc | 4 ++-- model/group.h | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts b/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts index ed74e272b..65f29b43e 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts @@ -716,9 +716,12 @@ export class CommandsManager { break; - case ClassType.Group: + case ClassType.Group: + if (await CommandsManager.selectVar(mouseX,mouseY)) + await CommandsManager.editVar(); + else await CommandsManager.editItem(ClassType.Group); - break; + break; case ClassType.Item: await CommandsManager.postNote('item'); diff --git a/model/group.cc b/model/group.cc index 88a4c8e8c..352a510c8 100644 --- a/model/group.cc +++ b/model/group.cc @@ -516,10 +516,10 @@ namespace minsky } } - VariablePtr Group::addIOVar() + VariablePtr Group::addIOVar(const char* prefix) { VariablePtr v(VariableType::flow, - uqName(cminsky().variableValues.newName(to_string(size_t(this))+":"))); + uqName(cminsky().variableValues.newName(to_string(size_t(this))+":"+prefix))); addItem(v,true); createdIOvariables.push_back(v); v->rotation(rotation()); diff --git a/model/group.h b/model/group.h index 6f63cdba3..3bfc90ae4 100644 --- a/model/group.h +++ b/model/group.h @@ -219,7 +219,7 @@ namespace minsky class Group: public ItemT, public GroupItems, public CallableFunction { bool m_displayContentsChanged=true; - VariablePtr addIOVar(); + VariablePtr addIOVar(const char*); protected: /// returns the smallest group whose icon completely encloses the /// rectangle given by the argument. If no candidate group found, @@ -290,8 +290,8 @@ namespace minsky /// check if item is a variable and located in an I/O region, and add it if it is void checkAddIORegion(const ItemPtr& x); - void addInputVar() {inVariables.push_back(addIOVar());} - void addOutputVar() {outVariables.push_back(addIOVar());} + void addInputVar() {inVariables.push_back(addIOVar("input"));} + void addOutputVar() {outVariables.push_back(addIOVar("output"));} /// remove item from group, and also all attached wires. void deleteItem(const Item&); From ec97566356e0b073af6128fff35dd2bd2b7246bd Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 11 Jan 2024 16:59:32 +1100 Subject: [PATCH 04/12] Make sliders active on publication tab. For #1691. --- model/pubTab.cc | 66 ++++++++++++++++++++++++++++++++----------------- model/pubTab.h | 3 ++- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/model/pubTab.cc b/model/pubTab.cc index eb784ae3a..78fc942ba 100644 --- a/model/pubTab.cc +++ b/model/pubTab.cc @@ -19,6 +19,7 @@ #include "lasso.h" #include "minsky.h" +#include "cairoItems.h" #include "pubTab.h" #include "pubTab.xcd" #include "pubTab.rcd" @@ -102,13 +103,13 @@ namespace minsky } catch (...) {} } - if (resizing) + if (clickType==ClickType::onResize) { cairo_rectangle(cairo,std::min(lasso.x0,lasso.x1), std::min(lasso.y0,lasso.y1), abs(lasso.x0-lasso.x1), abs(lasso.y0-lasso.y1)); cairo_stroke(cairo); } - return !items.empty() || resizing; + return !items.empty() || clickType!=ClickType::outside; } PubItem* PubTab::m_getItemAt(float x, float y) @@ -124,16 +125,24 @@ namespace minsky x-=offsx; y-=offsy; item=m_getItemAt(x,y); if (item) - if (auto p=item->itemCoords(x,y); - item->itemRef->clickType(p.x(),p.y())==ClickType::onResize) - { - resizing=true; - auto scale=item->zoomFactor/item->itemRef->zoomFactor(); - lasso.x0=x>item->x? x-item->itemRef->width()*scale: x+item->itemRef->width()*scale; - lasso.y0=y>item->y? y-item->itemRef->height()*scale: y+item->itemRef->height()*scale; - lasso.x1=x; - lasso.y1=y; - } + { + auto p=item->itemCoords(x,y); + clickType=item->itemRef->clickType(p.x(),p.y()); + switch (clickType) + { + case ClickType::onResize: + { + auto scale=item->zoomFactor/item->itemRef->zoomFactor(); + lasso.x0=x>item->x? x-item->itemRef->width()*scale: x+item->itemRef->width()*scale; + lasso.y0=y>item->y? y-item->itemRef->height()*scale: y+item->itemRef->height()*scale; + lasso.x1=x; + lasso.y1=y; + } + break; + default: + break; + } + } } void PubTab::mouseUp(float x, float y) @@ -145,7 +154,7 @@ namespace minsky return; } mouseMove(x,y); - if (item && resizing) + if (item && clickType==ClickType::onResize) { item->zoomFactor=std::min( abs(lasso.x1-lasso.x0)/item->itemRef->width(), @@ -155,7 +164,7 @@ namespace minsky } minsky().pushHistory(); item=nullptr; - resizing=false; + clickType=ClickType::outside; rotating=false; } @@ -174,16 +183,27 @@ namespace minsky { item->rotation=(180/M_PI)*atan2(x-rx, y-ry); } - else if (resizing) - { - lasso.x1=x; - lasso.y1=y; - } else - { - item->x=x; - item->y=y; - } + switch (clickType) + { + case ClickType::onResize: + lasso.x1=x; + lasso.y1=y; + break; + case ClickType::onSlider: + if (auto v=item->itemRef->variableCast()) + { + RenderVariable rv(*v); + double rw=fabs(v->zoomFactor()*(rv.width()iWidth()? 0.5*v->iWidth() : rv.width())*cos(v->rotation()*M_PI/180)); + double sliderPos=(x-item->x)* (v->sliderMax-v->sliderMin)/rw+0.5*(v->sliderMin+v->sliderMax); + double sliderHatch=sliderPos-fmod(sliderPos,v->sliderStep); // matches slider's hatch marks to sliderStep value. for ticket 1258 + v->sliderSet(sliderHatch); + } + break; + default: + item->x=x; + item->y=y; + } } else // indicate mouse focus diff --git a/model/pubTab.h b/model/pubTab.h index b238c160f..de4fa58af 100644 --- a/model/pubTab.h +++ b/model/pubTab.h @@ -52,7 +52,8 @@ namespace minsky CLASSDESC_ACCESS(PubTab); PubItem* item=nullptr; // weak reference for moving items PubItem* m_getItemAt(float x, float y); - bool resizing=false, rotating=false; + bool rotating=false; + ClickType::Type clickType=ClickType::outside; float rx=0, ry=0; ///< reference position for rotating LassoBox lasso; public: From 21fa4642c4e2b6286705c1bb9a16f01b3fd79271 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 11 Jan 2024 17:22:07 +1100 Subject: [PATCH 05/12] Refactor editor mode in Godleys and Ravels to use common virtual functions in Item. For #1694. --- model/godleyIcon.h | 4 ++-- model/item.h | 5 +++++ model/ravelWrap.cc | 10 +++++----- model/ravelWrap.h | 7 +++++-- schema/schema3.cc | 4 ++-- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/model/godleyIcon.h b/model/godleyIcon.h index 6401fe0d7..ac07725a5 100644 --- a/model/godleyIcon.h +++ b/model/godleyIcon.h @@ -65,8 +65,8 @@ namespace minsky ~GodleyIcon() {Item::removeControlledItems();} /// indicate whether icon is in editor mode or icon mode - bool editorMode() const {return m_editorMode;} - void toggleEditorMode(); + bool editorMode() const override {return m_editorMode;} + void toggleEditorMode() override; /// enable/disable drawing buttons in table on canvas display bool buttonDisplay() const; diff --git a/model/item.h b/model/item.h index 06b35c760..61478b826 100644 --- a/model/item.h +++ b/model/item.h @@ -334,6 +334,11 @@ namespace minsky /// enable extended tooltip help message appropriate for mouse at (x,y) virtual void displayDelayedTooltip(float x, float y) {} virtual void disableDelayedTooltip() {} + + /// some items have an editor mode attribute + virtual bool editorMode() const {return false;} + virtual void toggleEditorMode() {} + /// compute the dimensional units /// @param check - if true, then perform consistency checks /// @throw if check=true and dimensions inconsistent diff --git a/model/ravelWrap.cc b/model/ravelWrap.cc index 979ca0666..00890853f 100644 --- a/model/ravelWrap.cc +++ b/model/ravelWrap.cc @@ -68,12 +68,12 @@ namespace minsky } if (minsky().model->findAny(&GroupItems::items, [](const ItemPtr& i){return i->ravelCast();})) return; // early return if at least 1 ravel already present - editorMode=true; // first ravel is in editor mode + m_editorMode=true; // first ravel is in editor mode } void Ravel::draw(cairo_t* cairo) const { - double z=zoomFactor(), r=editorMode? 1.1*z*wrappedRavel.radius(): 30*z; + double z=zoomFactor(), r=m_editorMode? 1.1*z*wrappedRavel.radius(): 30*z; m_ports[0]->moveTo(x()+1.1*r, y()); m_ports[1]->moveTo(x()-1.1*r, y()); if (mouseFocus) @@ -81,7 +81,7 @@ namespace minsky drawPorts(cairo); displayTooltip(cairo,tooltip.empty()? explanation: tooltip); // Resize handles always visible on mousefocus. For ticket 92. - if (editorMode) drawResizeHandles(cairo); + if (m_editorMode) drawResizeHandles(cairo); } cairo_rectangle(cairo,-r,-r,2*r,2*r); cairo_rectangle(cairo,-1.1*r,-1.1*r,2.2*r,2.2*r); @@ -105,7 +105,7 @@ namespace minsky cairo::CairoSave cs(cairo); cairo_rectangle(cairo,-r,-r,2*r,2*r); cairo_clip(cairo); - if (editorMode) + if (m_editorMode) { cairo_scale(cairo,z,z); CairoRenderer cr(cairo); @@ -130,7 +130,7 @@ namespace minsky bool Ravel::inItem(float xx, float yy) const { - if (editorMode) + if (m_editorMode) { float r=1.1*zoomFactor()*wrappedRavel.radius(); return std::abs(xx-x())<=r && std::abs(yy-y())<=r; diff --git a/model/ravelWrap.h b/model/ravelWrap.h index 5fc3a9f0b..50f02b1db 100644 --- a/model/ravelWrap.h +++ b/model/ravelWrap.h @@ -61,6 +61,9 @@ namespace minsky using HandleState=ravel::HandleState; /// position of the "move" handle, as a proportion of radius const double moveX=0.5, moveY=0.5, moveSz=0.1; + + /// indicate whether icon is in editor mode or icon mode + bool m_editorMode=false; //std::string m_filename; std::string explanation; // explanation of Ravel bits displayed as tooltip ravel::HandleSort::Order previousOrder=ravel::HandleSort::staticForward; @@ -202,8 +205,8 @@ namespace minsky Units units(bool) const override; /// indicate whether icon is in editor mode or icon mode - bool editorMode=false; - void toggleEditorMode() {editorMode=!editorMode;updateBoundingBox();} + bool editorMode() const override {return m_editorMode;} + void toggleEditorMode() override {m_editorMode=!m_editorMode;updateBoundingBox();} }; diff --git a/schema/schema3.cc b/schema/schema3.cc index eff276070..f75ddd96e 100644 --- a/schema/schema3.cc +++ b/schema/schema3.cc @@ -180,7 +180,7 @@ namespace schema3 { items.back().ravelState=s; items.back().dimensions=r->axisDimensions; - items.back().editorMode=r->editorMode; + items.back().editorMode=r->editorMode(); } } if (auto* l=dynamic_cast(i)) @@ -514,7 +514,7 @@ namespace schema3 if (y.dimensions) x1->axisDimensions=*y.dimensions; - if (y.editorMode && *y.editorMode!=x1->editorMode) + if (y.editorMode && *y.editorMode!=x1->editorMode()) x1->toggleEditorMode(); } if (auto* x1=dynamic_cast(&x)) From 094142c8287365f16be8e781c2772f06380a5be1 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 11 Jan 2024 17:50:54 +1100 Subject: [PATCH 06/12] Toggle editor mode on pub tab. For #1694. --- .../src/app/managers/ContextMenuManager.ts | 7 +++ gui-js/libs/shared/src/lib/backend/minsky.ts | 12 +++++- model/pubTab.cc | 43 ++++++++++++++----- model/pubTab.h | 8 +++- 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts b/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts index a86528b62..fa7295a1d 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/ContextMenuManager.ts @@ -71,6 +71,13 @@ export class ContextMenuManager { }, enabled: await pubTab.getItemAt(this.x,this.y), }), + new MenuItem({ + label: 'Toggle editor mode', + click: () => { + pubTab.toggleEditorModeAt(this.x,this.y); + }, + enabled: await pubTab.getItemAt(this.x,this.y), + }), new MenuItem({ label: 'Remove item', click: () => { diff --git a/gui-js/libs/shared/src/lib/backend/minsky.ts b/gui-js/libs/shared/src/lib/backend/minsky.ts index 5eb09be3e..4ceb9c338 100644 --- a/gui-js/libs/shared/src/lib/backend/minsky.ts +++ b/gui-js/libs/shared/src/lib/backend/minsky.ts @@ -70,6 +70,7 @@ export class Item extends CppClass { async drawResizeHandles(a1: minsky__dummy): Promise {return this.$callMethod('drawResizeHandles',a1);} async drawSelected(a1: minsky__dummy): Promise {return this.$callMethod('drawSelected',a1);} async dummyDraw(): Promise {return this.$callMethod('dummyDraw');} + async editorMode(): Promise {return this.$callMethod('editorMode');} async ensureBBValid(): Promise {return this.$callMethod('ensureBBValid');} async flip(): Promise {return this.$callMethod('flip');} async height(): Promise {return this.$callMethod('height');} @@ -111,6 +112,7 @@ export class Item extends CppClass { async select(a1: number,a2: number): Promise {return this.$callMethod('select',a1,a2);} async selected(...args: boolean[]): Promise {return this.$callMethod('selected',...args);} async throw_error(a1: string): Promise {return this.$callMethod('throw_error',a1);} + async toggleEditorMode(): Promise {return this.$callMethod('toggleEditorMode');} async tooltip(...args: string[]): Promise {return this.$callMethod('tooltip',...args);} async top(): Promise {return this.$callMethod('top');} async units(a1: boolean): Promise {return this.$callMethod('units',a1);} @@ -231,6 +233,7 @@ export class VariableBase extends Item { async drawResizeHandles(a1: minsky__dummy): Promise {return this.$callMethod('drawResizeHandles',a1);} async drawSelected(a1: minsky__dummy): Promise {return this.$callMethod('drawSelected',a1);} async dummyDraw(): Promise {return this.$callMethod('dummyDraw');} + async editorMode(): Promise {return this.$callMethod('editorMode');} async enableSlider(...args: boolean[]): Promise {return this.$callMethod('enableSlider',...args);} async engExp(): Promise {return this.$callMethod('engExp');} async ensureBBValid(): Promise {return this.$callMethod('ensureBBValid');} @@ -304,6 +307,7 @@ export class VariableBase extends Item { async sliderVisible(): Promise {return this.$callMethod('sliderVisible');} async temp(): Promise {return this.$callMethod('temp');} async throw_error(a1: string): Promise {return this.$callMethod('throw_error',a1);} + async toggleEditorMode(): Promise {return this.$callMethod('toggleEditorMode');} async toggleLocal(): Promise {return this.$callMethod('toggleLocal');} async tooltip(...args: string[]): Promise {return this.$callMethod('tooltip',...args);} async top(): Promise {return this.$callMethod('top');} @@ -1082,6 +1086,7 @@ export class Group extends Item { async drawSelected(a1: minsky__dummy): Promise {return this.$callMethod('drawSelected',a1);} async dummyDraw(): Promise {return this.$callMethod('dummyDraw');} async edgeScale(): Promise {return this.$callMethod('edgeScale');} + async editorMode(): Promise {return this.$callMethod('editorMode');} async empty(): Promise {return this.$callMethod('empty');} async ensureBBValid(): Promise {return this.$callMethod('ensureBBValid');} async findGroup(a1: Group): Promise {return this.$callMethod('findGroup',a1);} @@ -1162,6 +1167,7 @@ export class Group extends Item { async summariseGodleys(): Promise {return this.$callMethod('summariseGodleys');} async throw_error(a1: string): Promise {return this.$callMethod('throw_error',a1);} async title(...args: string[]): Promise {return this.$callMethod('title',...args);} + async toggleEditorMode(): Promise {return this.$callMethod('toggleEditorMode');} async tooltip(...args: string[]): Promise {return this.$callMethod('tooltip',...args);} async top(): Promise {return this.$callMethod('top');} async uniqueItems(...args: any[]): Promise {return this.$callMethod('uniqueItems',...args);} @@ -1723,6 +1729,7 @@ export class PubItem extends CppClass { super(prefix); this.itemRef=new Item(this.$prefix()+'.itemRef'); } + async editorMode(...args: boolean[]): Promise {return this.$callMethod('editorMode',...args);} async itemCoords(a1: number,a2: number): Promise {return this.$callMethod('itemCoords',a1,a2);} async rotation(...args: number[]): Promise {return this.$callMethod('rotation',...args);} async x(...args: number[]): Promise {return this.$callMethod('x',...args);} @@ -1755,6 +1762,7 @@ export class PubTab extends RenderNativeWindow { async removeItemAt(a1: number,a2: number): Promise {return this.$callMethod('removeItemAt',a1,a2);} async removeSelf(): Promise {return this.$callMethod('removeSelf');} async rotateItemAt(a1: number,a2: number): Promise {return this.$callMethod('rotateItemAt',a1,a2);} + async toggleEditorModeAt(a1: number,a2: number): Promise {return this.$callMethod('toggleEditorModeAt',a1,a2);} async zoom(a1: number,a2: number,a3: number): Promise {return this.$callMethod('zoom',a1,a2,a3);} async zoomFactor(): Promise {return this.$callMethod('zoomFactor');} } @@ -1786,7 +1794,7 @@ export class Ravel extends Item { async displayDelayedTooltip(a1: number,a2: number): Promise {return this.$callMethod('displayDelayedTooltip',a1,a2);} async displayFilterCaliper(): Promise {return this.$callMethod('displayFilterCaliper');} async draw(a1: minsky__dummy): Promise {return this.$callMethod('draw',a1);} - async editorMode(...args: boolean[]): Promise {return this.$callMethod('editorMode',...args);} + async editorMode(): Promise {return this.$callMethod('editorMode');} async exportAsCSV(a1: string): Promise {return this.$callMethod('exportAsCSV',a1);} async getState(): Promise {return this.$callMethod('getState');} async handleDescription(a1: number): Promise {return this.$callMethod('handleDescription',a1);} @@ -1979,6 +1987,7 @@ export class Selection extends CppClass { async drawSelected(a1: minsky__dummy): Promise {return this.$callMethod('drawSelected',a1);} async dummyDraw(): Promise {return this.$callMethod('dummyDraw');} async edgeScale(): Promise {return this.$callMethod('edgeScale');} + async editorMode(): Promise {return this.$callMethod('editorMode');} async empty(): Promise {return this.$callMethod('empty');} async ensureBBValid(): Promise {return this.$callMethod('ensureBBValid');} async ensureGroupInserted(a1: Group): Promise {return this.$callMethod('ensureGroupInserted',a1);} @@ -2060,6 +2069,7 @@ export class Selection extends CppClass { async summariseGodleys(): Promise {return this.$callMethod('summariseGodleys');} async throw_error(a1: string): Promise {return this.$callMethod('throw_error',a1);} async title(...args: string[]): Promise {return this.$callMethod('title',...args);} + async toggleEditorMode(): Promise {return this.$callMethod('toggleEditorMode');} async toggleItemMembership(a1: Item): Promise {return this.$callMethod('toggleItemMembership',a1);} async tooltip(...args: string[]): Promise {return this.$callMethod('tooltip',...args);} async top(): Promise {return this.$callMethod('top');} diff --git a/model/pubTab.cc b/model/pubTab.cc index eb784ae3a..547dff9b5 100644 --- a/model/pubTab.cc +++ b/model/pubTab.cc @@ -29,6 +29,24 @@ using namespace ecolab::cairo; namespace minsky { + namespace { + struct EnsureEditorMode + { + PubItem& item; + bool editorModeToggled; + EnsureEditorMode(PubItem& item): item(item), editorModeToggled(item.editorMode!=item.itemRef->editorMode()) + { + if (editorModeToggled) + item.itemRef->toggleEditorMode(); + } + ~EnsureEditorMode() + { + if (editorModeToggled) + item.itemRef->toggleEditorMode(); + } + }; + } + Point PubItem::itemCoords(float x, float y) const { if (!itemRef) return {0,0}; @@ -98,6 +116,7 @@ namespace minsky cairo_rotate(cairo,(M_PI/180)*i.rotation-i.itemRef->rotation()); try { + EnsureEditorMode ensureEditorMode(i); i.itemRef->draw(cairo); } catch (...) {} @@ -124,16 +143,19 @@ namespace minsky x-=offsx; y-=offsy; item=m_getItemAt(x,y); if (item) - if (auto p=item->itemCoords(x,y); - item->itemRef->clickType(p.x(),p.y())==ClickType::onResize) - { - resizing=true; - auto scale=item->zoomFactor/item->itemRef->zoomFactor(); - lasso.x0=x>item->x? x-item->itemRef->width()*scale: x+item->itemRef->width()*scale; - lasso.y0=y>item->y? y-item->itemRef->height()*scale: y+item->itemRef->height()*scale; - lasso.x1=x; - lasso.y1=y; - } + { + EnsureEditorMode editorMode(*item); + if (auto p=item->itemCoords(x,y); + item->itemRef->clickType(p.x(),p.y())==ClickType::onResize) + { + resizing=true; + auto scale=item->zoomFactor/item->itemRef->zoomFactor(); + lasso.x0=x>item->x? x-item->itemRef->width()*scale: x+item->itemRef->width()*scale; + lasso.y0=y>item->y? y-item->itemRef->height()*scale: y+item->itemRef->height()*scale; + lasso.x1=x; + lasso.y1=y; + } + } } void PubTab::mouseUp(float x, float y) @@ -147,6 +169,7 @@ namespace minsky mouseMove(x,y); if (item && resizing) { + EnsureEditorMode editorMode(*item); item->zoomFactor=std::min( abs(lasso.x1-lasso.x0)/item->itemRef->width(), abs(lasso.y1-lasso.y0)/item->itemRef->height()); diff --git a/model/pubTab.h b/model/pubTab.h index b238c160f..ca36a6e84 100644 --- a/model/pubTab.h +++ b/model/pubTab.h @@ -30,11 +30,13 @@ namespace minsky { public: PubItem()=default; - PubItem(const ItemPtr& item): itemRef(item) {} + PubItem(const ItemPtr& item): + itemRef(item), editorMode(item? item->editorMode(): false) {} ItemPtr itemRef; float x=100,y=100; float zoomFactor=1; double rotation=0; + bool editorMode=false; /// given (x,y) in PubTab, returns coordinates within item Point itemCoords(float x, float y) const; }; @@ -67,6 +69,10 @@ namespace minsky void removeSelf(); void removeItemAt(float x, float y); void rotateItemAt(float x, float y); + void toggleEditorModeAt(float x, float y) { + if (auto i=m_getItemAt(x-offsx,y-offsy)) + i->editorMode=!i->editorMode; + } bool getItemAt(float x, float y) override {return m_getItemAt(x-offsx,y-offsy);} void mouseDown(float x, float y) override; void controlMouseDown(float x, float y) override {panning=true; PannableTab::mouseDown(x,y);} From de1885a2ed981dd2f5d75ff276d8c1cd2b66ef86 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Fri, 12 Jan 2024 11:39:19 +1100 Subject: [PATCH 07/12] Fix up resizing protocol on pub tab. For #1694. --- gui-js/libs/shared/src/lib/backend/minsky.ts | 2 ++ model/pubTab.cc | 25 ++++++++++++++------ model/pubTab.h | 3 ++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/gui-js/libs/shared/src/lib/backend/minsky.ts b/gui-js/libs/shared/src/lib/backend/minsky.ts index 4ceb9c338..03aeede8a 100644 --- a/gui-js/libs/shared/src/lib/backend/minsky.ts +++ b/gui-js/libs/shared/src/lib/backend/minsky.ts @@ -1735,6 +1735,8 @@ export class PubItem extends CppClass { async x(...args: number[]): Promise {return this.$callMethod('x',...args);} async y(...args: number[]): Promise {return this.$callMethod('y',...args);} async zoomFactor(...args: number[]): Promise {return this.$callMethod('zoomFactor',...args);} + async zoomX(...args: number[]): Promise {return this.$callMethod('zoomX',...args);} + async zoomY(...args: number[]): Promise {return this.$callMethod('zoomY',...args);} } export class PubTab extends RenderNativeWindow { diff --git a/model/pubTab.cc b/model/pubTab.cc index 547dff9b5..5f9b6b416 100644 --- a/model/pubTab.cc +++ b/model/pubTab.cc @@ -34,15 +34,24 @@ namespace minsky { PubItem& item; bool editorModeToggled; - EnsureEditorMode(PubItem& item): item(item), editorModeToggled(item.editorMode!=item.itemRef->editorMode()) + LassoBox origBox; + float origIWidth, origIHeight; + EnsureEditorMode(PubItem& item): + item(item), editorModeToggled(item.editorMode!=item.itemRef->editorMode()), + origIWidth(item.itemRef? item.itemRef->iWidth(): 0), + origIHeight(item.itemRef? item.itemRef->iHeight(): 0) { if (editorModeToggled) item.itemRef->toggleEditorMode(); + item.itemRef->iWidth(item.zoomX*origIWidth); + item.itemRef->iHeight(item.zoomY*origIHeight); } ~EnsureEditorMode() { if (editorModeToggled) item.itemRef->toggleEditorMode(); + item.itemRef->iWidth(origIWidth); + item.itemRef->iHeight(origIHeight); } }; } @@ -108,6 +117,7 @@ namespace minsky CairoSave cs(cairo); cairo_translate(cairo, offsx, offsy); cairo_scale(cairo, m_zoomFactor, m_zoomFactor); + cairo_set_line_width(cairo, 1); for (auto& i: items) { CairoSave cs(cairo); @@ -133,8 +143,11 @@ namespace minsky PubItem* PubTab::m_getItemAt(float x, float y) { for (auto& i: items) - if (i.itemRef->contains(i.itemCoords(x,y))) + { + EnsureEditorMode e(i); + if (i.itemRef->contains(i.itemCoords(x,y))) return &i; + } return nullptr; } @@ -144,7 +157,7 @@ namespace minsky item=m_getItemAt(x,y); if (item) { - EnsureEditorMode editorMode(*item); + EnsureEditorMode e(*item); if (auto p=item->itemCoords(x,y); item->itemRef->clickType(p.x(),p.y())==ClickType::onResize) { @@ -169,10 +182,8 @@ namespace minsky mouseMove(x,y); if (item && resizing) { - EnsureEditorMode editorMode(*item); - item->zoomFactor=std::min( - abs(lasso.x1-lasso.x0)/item->itemRef->width(), - abs(lasso.y1-lasso.y0)/item->itemRef->height()); + item->zoomX=abs(lasso.x1-lasso.x0)/(item->itemRef->width()*item->zoomFactor); + item->zoomY=abs(lasso.y1-lasso.y0)/(item->itemRef->height()*item->zoomFactor); item->x=0.5*(lasso.x0+lasso.x1); item->y=0.5*(lasso.y0+lasso.y1); } diff --git a/model/pubTab.h b/model/pubTab.h index ca36a6e84..767fe74a6 100644 --- a/model/pubTab.h +++ b/model/pubTab.h @@ -35,9 +35,10 @@ namespace minsky ItemPtr itemRef; float x=100,y=100; float zoomFactor=1; + float zoomX=1, zoomY=1; double rotation=0; bool editorMode=false; - /// given (x,y) in PubTab, returns coordinates within item + /// given (x,y) in PubTab, returns coordinates within item. Note must be wrapped by EnsureEditorMode Point itemCoords(float x, float y) const; }; From 1a92f1cb9d08dd7fb2efb27b285e33efefbb9c35 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Fri, 12 Jan 2024 12:05:26 +1100 Subject: [PATCH 08/12] Persistence of new pubItem attributes. For #1694. --- RESTService/typescriptAPI.cc | 1 + gui-js/libs/shared/src/lib/backend/minsky.ts | 1 + model/pubTab.cc | 2 + model/pubTab.h | 17 ++++--- schema/publication.h | 49 ++++++++++++++++++++ schema/schema3.cc | 10 +--- schema/schema3.h | 17 ------- 7 files changed, 62 insertions(+), 35 deletions(-) create mode 100644 schema/publication.h diff --git a/RESTService/typescriptAPI.cc b/RESTService/typescriptAPI.cc index f3c8fa26a..dd5a0592d 100644 --- a/RESTService/typescriptAPI.cc +++ b/RESTService/typescriptAPI.cc @@ -47,6 +47,7 @@ #include "polyRESTProcessBase.tcd" #include "port.h" #include "port.tcd" +#include "publication.tcd" #include "pubTab.tcd" #include "ravelState.tcd" #include "renderNativeWindow.tcd" diff --git a/gui-js/libs/shared/src/lib/backend/minsky.ts b/gui-js/libs/shared/src/lib/backend/minsky.ts index 03aeede8a..996937504 100644 --- a/gui-js/libs/shared/src/lib/backend/minsky.ts +++ b/gui-js/libs/shared/src/lib/backend/minsky.ts @@ -1730,6 +1730,7 @@ export class PubItem extends CppClass { this.itemRef=new Item(this.$prefix()+'.itemRef'); } async editorMode(...args: boolean[]): Promise {return this.$callMethod('editorMode',...args);} + async item(...args: number[]): Promise {return this.$callMethod('item',...args);} async itemCoords(a1: number,a2: number): Promise {return this.$callMethod('itemCoords',a1,a2);} async rotation(...args: number[]): Promise {return this.$callMethod('rotation',...args);} async x(...args: number[]): Promise {return this.$callMethod('x',...args);} diff --git a/model/pubTab.cc b/model/pubTab.cc index 5f9b6b416..781a83719 100644 --- a/model/pubTab.cc +++ b/model/pubTab.cc @@ -20,6 +20,7 @@ #include "lasso.h" #include "minsky.h" #include "pubTab.h" +#include "publication.rcd" #include "pubTab.xcd" #include "pubTab.rcd" #include "pannableTab.rcd" @@ -226,5 +227,6 @@ namespace minsky requestRedraw(); } } +CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(schema3::PublicationItem); CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::PubTab); CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::PubItem); diff --git a/model/pubTab.h b/model/pubTab.h index 767fe74a6..d8427a189 100644 --- a/model/pubTab.h +++ b/model/pubTab.h @@ -22,23 +22,22 @@ #include "item.h" #include "pannableTab.h" +#include "publication.h" #include "renderNativeWindow.h" namespace minsky { - class PubItem + class PubItem: public schema3::PublicationItem { public: PubItem()=default; - PubItem(const ItemPtr& item): - itemRef(item), editorMode(item? item->editorMode(): false) {} + PubItem(const ItemPtr& item): itemRef(item) { + if (item) editorMode=item->editorMode(); + } + PubItem(const ItemPtr& item, const schema3::PublicationItem& state): + itemRef(item), schema3::PublicationItem(state) {} ItemPtr itemRef; - float x=100,y=100; - float zoomFactor=1; - float zoomX=1, zoomY=1; - double rotation=0; - bool editorMode=false; - /// given (x,y) in PubTab, returns coordinates within item. Note must be wrapped by EnsureEditorMode + /// given (x,y) in PubTab, returns coordinates within item. Nb: must be wrapped by EnsureEditorMode Point itemCoords(float x, float y) const; }; diff --git a/schema/publication.h b/schema/publication.h new file mode 100644 index 000000000..8b45ca230 --- /dev/null +++ b/schema/publication.h @@ -0,0 +1,49 @@ +/* + @copyright Steve Keen 2024 + @author Russell Standish + This file is part of Minsky. + + Minsky is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Minsky is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Minsky. If not, see . +*/ + +#ifndef PUBLICATION_H +#define PUBLICATION_H +#include +#include +namespace schema3 +{ + struct PublicationItem + { + int item=-1; + float x=100,y=100; + float zoomFactor=1; + float zoomX=1, zoomY=1; + double rotation=0; + bool editorMode=false; + PublicationItem()=default; + PublicationItem(int item, const PublicationItem& x) {*this=x; this->item=item;} + }; + + struct PublicationTab + { + std::string name; + std::vector items; + float x=100,y=100,zoomFactor=1; + }; +} +using classdesc::xsd_generate; +using classdesc::xml_pack; +#include "publication.cd" +#include "publication.xcd" +#endif diff --git a/schema/schema3.cc b/schema/schema3.cc index f75ddd96e..71fd6d220 100644 --- a/schema/schema3.cc +++ b/schema/schema3.cc @@ -427,20 +427,12 @@ namespace schema3 for (auto& pub: publicationTabs) { pubTabs.emplace_back(pub.name); - pubTabs.back().offsx=pub.x; pubTabs.back().offsy=pub.y; pubTabs.back().m_zoomFactor=pub.zoomFactor; for (auto& item: pub.items) if (itemMap[item.item]) - { - pubTabs.back().items.emplace_back(itemMap[item.item]); - auto& newItem=pubTabs.back().items.back(); - newItem.x=item.x; - newItem.y=item.y; - newItem.zoomFactor=item.zoomFactor; - newItem.rotation=item.rotation; - } + pubTabs.back().items.emplace_back(itemMap[item.item], item); } if (pubTabs.empty()) pubTabs.emplace_back("Publication"); } diff --git a/schema/schema3.h b/schema/schema3.h index f5bd03c0c..243c23363 100644 --- a/schema/schema3.h +++ b/schema/schema3.h @@ -233,23 +233,6 @@ namespace schema3 void populatePhillipsDiagram(minsky::PhillipsDiagram&) const; }; - struct PublicationItem - { - int item=-1; - float x=100,y=100; - float zoomFactor=1; - double rotation=0; - PublicationItem()=default; - PublicationItem(int id, const minsky::PubItem& p): item(id), x(p.x), y(p.y), zoomFactor(p.zoomFactor), rotation(p.rotation) {} - }; - - struct PublicationTab - { - std::string name; - std::vector items; - float x=100,y=100,zoomFactor=1; - }; - struct MinskyImpl; ///< working structure, not serialised class Minsky From 84f60d1fd8b0a5bdf5635013458f3c71c625899a Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Fri, 12 Jan 2024 12:11:58 +1100 Subject: [PATCH 09/12] Fix CodeQL nit. --- model/pubTab.cc | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/model/pubTab.cc b/model/pubTab.cc index 78fc942ba..e96295582 100644 --- a/model/pubTab.cc +++ b/model/pubTab.cc @@ -128,19 +128,13 @@ namespace minsky { auto p=item->itemCoords(x,y); clickType=item->itemRef->clickType(p.x(),p.y()); - switch (clickType) + if (clickType==ClickType::onResize) { - case ClickType::onResize: - { - auto scale=item->zoomFactor/item->itemRef->zoomFactor(); - lasso.x0=x>item->x? x-item->itemRef->width()*scale: x+item->itemRef->width()*scale; - lasso.y0=y>item->y? y-item->itemRef->height()*scale: y+item->itemRef->height()*scale; - lasso.x1=x; - lasso.y1=y; - } - break; - default: - break; + auto scale=item->zoomFactor/item->itemRef->zoomFactor(); + lasso.x0=x>item->x? x-item->itemRef->width()*scale: x+item->itemRef->width()*scale; + lasso.y0=y>item->y? y-item->itemRef->height()*scale: y+item->itemRef->height()*scale; + lasso.x1=x; + lasso.y1=y; } } } From 7712d0cb362da17150f3464783898f49f2941a4f Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Fri, 12 Jan 2024 12:27:31 +1100 Subject: [PATCH 10/12] Fix regression tests. --- test/testModel.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testModel.cc b/test/testModel.cc index d57bc1fba..1d8591422 100644 --- a/test/testModel.cc +++ b/test/testModel.cc @@ -108,7 +108,7 @@ SUITE(Group) TEST_FIXTURE(TestFixture, accessibleVars) { vector globalAccessibleVars{"c"}; - vector group0AccessibleVars{"1",":c","a","b"}; + vector group0AccessibleVars{":c","a","b","output1"}; group0->makeSubroutine(); CHECK_EQUAL(globalAccessibleVars.size(), model->accessibleVars().size()); CHECK_ARRAY_EQUAL(globalAccessibleVars, model->accessibleVars(), globalAccessibleVars.size()); From 1e732e8676043643ac8bff30abac9b967ae40f1c Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Fri, 12 Jan 2024 16:46:05 +1100 Subject: [PATCH 11/12] Fix warnings treated as errors. --- model/pubTab.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/pubTab.h b/model/pubTab.h index d8427a189..a41609a67 100644 --- a/model/pubTab.h +++ b/model/pubTab.h @@ -35,7 +35,7 @@ namespace minsky if (item) editorMode=item->editorMode(); } PubItem(const ItemPtr& item, const schema3::PublicationItem& state): - itemRef(item), schema3::PublicationItem(state) {} + schema3::PublicationItem(state), itemRef(item) {} ItemPtr itemRef; /// given (x,y) in PubTab, returns coordinates within item. Nb: must be wrapped by EnsureEditorMode Point itemCoords(float x, float y) const; From 8ad6c04ddc1e3b66553a3853a0bbd54e27491698 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Fri, 12 Jan 2024 18:19:41 +1100 Subject: [PATCH 12/12] Fix up persistence of publication tab notes. For #1697. --- schema/schema3.cc | 71 +++++++++++++++++++++++++++-------------------- schema/schema3.h | 2 +- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/schema/schema3.cc b/schema/schema3.cc index eff276070..14dd657c5 100644 --- a/schema/schema3.cc +++ b/schema/schema3.cc @@ -1,4 +1,4 @@ -/* + /* @copyright Steve Keen 2017 @author Russell Standish This file is part of Minsky. @@ -283,7 +283,7 @@ namespace schema3 struct MinskyImpl { - IdMap itemMap; // for serialisation + IdMap itemMap, pubItemMap; // for serialisation map reverseItemMap; // for deserialisation }; @@ -414,37 +414,15 @@ namespace schema3 publicationTabs.emplace_back(); publicationTabs.back().name=i.name; for (auto& j: i.items) - publicationTabs.back().items.emplace_back(itemMap[j.itemRef.get()], j); + { + // add locally added notes on publication tab to items list in schema + if (!itemMap.count(j.itemRef.get())) + itemMap.emplaceIf(publicationItems,j.itemRef.get()); + publicationTabs.back().items.emplace_back(itemMap[j.itemRef.get()], j); + } } } - void Minsky::populatePublicationTabs(std::vector& pubTabs) const - { - assert(impl.get()); - auto& itemMap=impl->reverseItemMap; - - pubTabs.clear(); - for (auto& pub: publicationTabs) - { - pubTabs.emplace_back(pub.name); - pubTabs.back().offsx=pub.x; - pubTabs.back().offsy=pub.y; - pubTabs.back().m_zoomFactor=pub.zoomFactor; - - for (auto& item: pub.items) - if (itemMap[item.item]) - { - pubTabs.back().items.emplace_back(itemMap[item.item]); - auto& newItem=pubTabs.back().items.back(); - newItem.x=item.x; - newItem.y=item.y; - newItem.zoomFactor=item.zoomFactor; - newItem.rotation=item.rotation; - } - } - if (pubTabs.empty()) pubTabs.emplace_back("Publication"); - } - void Minsky::populateMinsky(minsky::Minsky& m) const { minsky::LocalMinsky lm(m); @@ -608,6 +586,39 @@ namespace schema3 LockGroupFactory(): shared_ptr(new minsky::RavelLockGroup) {} }; + void Minsky::populatePublicationTabs(std::vector& pubTabs) const + { + assert(impl.get()); + auto& itemMap=impl->reverseItemMap; + + // add in publication tab only items + MinskyItemFactory factory; + for (auto& i: publicationItems) + if (auto newItem=itemMap[i.id]=minsky::ItemPtr(factory.create(i.type))) + populateItem(*newItem,i); + + pubTabs.clear(); + for (auto& pub: publicationTabs) + { + pubTabs.emplace_back(pub.name); + pubTabs.back().offsx=pub.x; + pubTabs.back().offsy=pub.y; + pubTabs.back().m_zoomFactor=pub.zoomFactor; + + for (auto& item: pub.items) + if (itemMap.count(item.item)) + { + pubTabs.back().items.emplace_back(itemMap[item.item]); + auto& newItem=pubTabs.back().items.back(); + newItem.x=item.x; + newItem.y=item.y; + newItem.zoomFactor=item.zoomFactor; + newItem.rotation=item.rotation; + } + } + if (pubTabs.empty()) pubTabs.emplace_back("Publication"); + } + void Minsky::populateGroup(minsky::Group& g) const { assert(impl.get()); auto& itemMap=impl->reverseItemMap; diff --git a/schema/schema3.h b/schema/schema3.h index f5bd03c0c..3c3dfe950 100644 --- a/schema/schema3.h +++ b/schema/schema3.h @@ -271,7 +271,7 @@ namespace schema3 minsky::ConversionsMap conversions; PhillipsDiagram phillipsDiagram; std::vector publicationTabs; - + std::vector publicationItems; Minsky(): schemaVersion(0) {makeImpl();} // schemaVersion defined on read in ~Minsky();