28 #include <cairo_base.h> 30 #include "godleyIcon.rcd" 33 #include <boost/locale.hpp> 49 {assert(x&&y);
return x->valueId() < y->valueId();}
57 cairo(cairo), x(x), y(y) {}
61 for (GodleyIcon::Variables::const_iterator v=vars.begin();
64 const ecolab::cairo::CairoSave cs(cairo);
67 cairo_translate(cairo, vv.
x()-x, vv.
y()-y);
75 float& height,
float& width)
78 for (
const auto& v: vars)
82 if (h>height) height=h;
83 const float w=2*rv.
width();
89 bool GodleyIcon::inItem(
float xx,
float yy)
const 91 return abs(xx-x())<0.5*width()-
border && abs(yy-y())<0.5*height()-
border;
95 const vector<string>& varNames,
99 set<VariablePtr, OrderByName> oldVars(vars.begin(), vars.end());
100 set<string> alreadyAdded;
103 shared_ptr<GodleyIcon>
self;
104 if (
auto g=
group.lock())
105 self=dynamic_pointer_cast<GodleyIcon>(g->findItem(*
this));
107 for (vector<string>::const_iterator nm=varNames.begin(); nm!=varNames.end(); ++nm)
110 auto myGroup=
group.lock();
111 if (myGroup) myGroup->addItem(newVar);
112 auto v=oldVars.find(newVar);
114 if (v==oldVars.end())
117 if (!alreadyAdded.contains(newVar->valueId()))
120 vars.push_back(newVar);
121 alreadyAdded.insert(newVar->valueId());
122 if (varType==VariableType::stock)
123 newVar->setUnits(currency);
127 myGroup->removeItem(*newVar);
134 alreadyAdded.insert(newVar->valueId());
136 if (myGroup) myGroup->removeItem(*newVar);
138 if (myGroup) myGroup->addItem(vars.back(),
true);
139 vars.back()->controller=
self;
144 if (
auto g=
group.lock())
145 for (
const auto& v: oldVars)
149 void GodleyIcon::toggleVariableDisplay()
151 m_variableDisplay=!m_variableDisplay;
155 void GodleyIcon::toggleEditorMode()
157 m_editorMode=!m_editorMode;
161 bool GodleyIcon::buttonDisplay()
const {
return m_editorMode && editor.drawButtons;}
162 void GodleyIcon::toggleButtons()
164 if (editor.drawButtons)
165 editor.disableButtons();
167 editor.enableButtons();
171 void GodleyIcon::scaleIcon(
float w,
float h)
174 scaleFactor(min(w/(leftMargin()+iWidth()*zoomFactor()),h/(bottomMargin()+iHeight()*zoomFactor())));
179 const float invZ=1.0/(this->zoomFactor());
180 auto bw=abs(b.
x0-b.
x1), bh=abs(b.
y0-b.
y1);
181 if (bw<=leftMargin() || bh<=bottomMargin())
return;
182 moveTo(0.5*(b.
x0+b.
x1), 0.5*(b.
y0+b.
y1));
183 iWidth((bw-leftMargin())*invZ);
184 iHeight((bh-bottomMargin())*invZ);
191 for (
auto& i: m_flowVars)
194 i->deleteAttachedWires();
196 assert(item.use_count()!=1);
199 for (
auto& i: m_stockVars)
202 i->deleteAttachedWires();
204 assert(item.use_count()!=1);
209 void GodleyIcon::setCell(
int row,
int col,
const string& newVal)
211 if (!table.cellInTable(row,col))
return;
213 string& c=table.cell(row,col);
214 if (newVal.empty() && !c.empty() && table.initialConditionRow(row))
225 void GodleyIcon::deleteRow(
unsigned row)
227 if (row>0 && row<=table.rows())
229 table.deleteRow(row-1);
231 for (
size_t col=1; col<table.cols(); ++col)
247 const string oldVal=table.cell(args.
srcRow, args.
srcCol);
258 map<string,double> GodleyIcon::flowSignature(
unsigned col)
const 260 map<string,double> r;
261 for (
size_t row=1; row<table.rows() && col<table.cols(); ++row)
262 if (!table.initialConditionRow(row))
264 const FlowCoef fc(table.cell(row,col));
265 if (!fc.name.empty())
271 vector<Summary> GodleyIcon::summarise()
const 274 for (
auto& i: stockVars())
275 if (
auto vv=i->vValue())
277 r.push_back(vv->summary());
278 r.back().godley=table.title.empty()? id(): table.title;
280 for (
auto& i: flowVars())
281 if (
auto vv=i->vValue())
283 r.push_back(vv->summary());
284 r.back().godley=table.title.empty()? id(): table.title;
289 void GodleyIcon::update()
291 updateVars(m_stockVars, table.getColumnVariables(), VariableType::stock);
292 updateVars(m_flowVars, table.getVariables(), VariableType::flow);
295 for (
size_t r=1; r<table.rows(); ++r)
296 if (table.initialConditionRow(r))
297 for (
size_t c=1; c<table.cols(); ++c)
299 const string name=
trimWS(table.cell(0,c));
304 const string::size_type start=table.cell(r,c).find_first_not_of(
' ');
305 if (start!=string::npos)
307 const FlowCoef fc(table.cell(r,c).substr(start));
311 if (initVar->inputWired() && initVar->type()==VariableType::flow)
312 if (
auto* lhsVar=initVar->ports(1).lock()->wires()[0]->from()->item().variableCast()) {
313 FlowCoef fc1(lhsVar->vValue()->init());
323 table.cell(r,c)=fc.
str();
329 if (m_variableDisplay)
332 float stockH=0, flowH=0;
340 const float iw=this->iWidth(), ih=this->iHeight();
341 this->iWidth(max(iw, 1.8
f*stockH));
342 this->iHeight(max(ih, 1.8
f*flowH));
346 for (
auto& v: m_stockVars)
347 if (
auto p=v->ports(0).lock())
348 if (!p->wires().empty())
350 if (
auto g=
group.lock())
352 auto newVar=g->addItem(v->clone());
353 if (
auto w=p->wires().front())
354 if (
auto from=newVar->ports(0).lock())
356 w->moveToPorts(from, w->to());
361 m_variableDisplay=
true;
362 throw_error(
"Cowardly refusing to hide a wired variable");
365 for (
auto& v: m_flowVars)
366 if (
auto p=v->ports(1).lock())
367 if (!p->wires().empty())
369 if (
auto g=
group.lock())
371 auto newVar=g->addItem(v->clone());
372 if (
auto w=p->wires().front())
373 if (
auto to=newVar->ports(1).lock())
375 w->moveToPorts(w->from(), to);
380 m_variableDisplay=
true;
381 throw_error(
"Cowardly refusing to hide a wired variable");
389 void GodleyIcon::positionVariables()
const 392 const float z=this->zoomFactor()*scaleFactor();
393 float x= this->x() - 0.5*iWidth()*z+0.5*leftMargin();
394 float y= this->y() - 0.5*bottomMargin()-0.15*iHeight()*z;
395 for (
const auto& v: m_flowVars)
399 v->moveTo(x+v->x() - (m_variableDisplay? v->right(): v->left()), y);
402 x= this->x() + 0.55*leftMargin()-0.45*iWidth()*z;
403 y= this->y() + 0.5*iHeight()*z-0.5*bottomMargin();
405 for (
const auto& v: m_stockVars)
409 v->moveTo(x, y + v->y() - (m_variableDisplay? v->top(): v->bottom()));
414 ItemPtr GodleyIcon::select(
float x,
float y)
const 416 if (m_variableDisplay)
418 for (
const auto& v: m_flowVars)
419 if (v->contains(x,y))
421 for (
const auto& v: m_stockVars)
422 if (v->contains(x,y))
428 bool GodleyIcon::wiresAttached()
const 431 for (
auto& v: m_flowVars)
432 if (
auto p=v->ports(1).lock())
433 r|=p->wires().size();
434 for (
auto& v: m_stockVars)
435 if (
auto p=v->ports(0).lock())
436 r|=p->wires().size();
441 void GodleyIcon::draw(cairo_t* cairo)
const 444 const float z=zoomFactor()*scaleFactor();
445 float w=iWidth()*z+leftMargin(), h=iHeight()*z+bottomMargin(), left=-0.5*w, top=-0.5*h;
450 const CairoSave cs(cairo);
451 cairo_rectangle(cairo, left, top, w, h);
453 cairo_stroke_preserve(cairo);
456 const cairo::CairoSave cs(cairo);
457 cairo_set_source_rgba(cairo,0.5,0.5,0.5,0.5);
458 cairo_set_fill_rule(cairo,CAIRO_FILL_RULE_EVEN_ODD);
461 cairo_new_path(cairo);
462 cairo_rectangle(cairo, left, top, w, h);
464 cairo_translate(cairo,left+
border*z+leftMargin(),top+
border*z+titleOffs());
467 const Surface surf(cairo_recording_surface_create(CAIRO_CONTENT_COLOR,
nullptr));
470 const_cast<GodleyTableEditor&
>(editor).zoomFactor=min((w-leftMargin()-2*
border*z)/surf.width(),(h-bottomMargin()-2*
border*z-titleOffs())/surf.height());
480 const CairoSave cs(cairo);
481 cairo_translate(cairo,left+leftMargin(),top);
482 cairo_scale(cairo, (w-leftMargin())/svgRenderer.width(), (h-bottomMargin())/svgRenderer.height());
483 svgRenderer.render(cairo);
484 titley=top+0.1*(h-bottomMargin());
487 if (!table.title.empty())
489 const CairoSave cs(cairo);
491 pango.setMarkup(
"<b>"+
latexToPango(table.title)+
"</b>");
492 pango.setFontSize(titleOffs());
493 cairo_move_to(cairo,-0.5*(pango.width()-leftMargin()), titley);
499 if (m_variableDisplay && (!m_editorMode || wiresAttached()))
502 const DrawVars drawVars(cairo,x(),y());
503 drawVars(m_flowVars);
504 drawVars(m_stockVars);
510 displayTooltip(cairo,tooltip());
511 drawResizeHandles(cairo);
514 cairo_rectangle(cairo, left,top, w, h);
522 string GodleyIcon::rowSum(
int row)
const 526 map<string,VariablePtr> stockVars;
527 for (
const auto& i: m_stockVars)
528 stockVars[i->valueId()]=i;
529 double sum=0, absSum=0;
530 for (
size_t c=1; c<table.cols(); ++c)
533 if (i!=stockVars.end())
535 sum+=(table.signConventionReversed(c)? -1: 1)*i->second->value();
536 absSum+=abs(i->second->value());
539 if (sum<1E-4*absSum) sum=0;
542 return table.rowSum(row);
545 Units GodleyIcon::stockVarUnits(
const string& stockName,
bool check)
const 549 for (; stockCol<table.cols(); ++stockCol)
550 if (
valueId(table.cell(0,stockCol))==valId)
553 if (stockCol>=table.cols())
return {};
555 bool foundFlow=
false;
557 for (
unsigned row=1; row<table.rows(); ++row)
559 if (table.initialConditionRow(row))
continue;
560 const FlowCoef fc(table.cell(row,stockCol));
565 for (
const auto& v: flowVars())
566 if (v->valueId()==vid)
568 auto flowUnits=v->units(check);
569 if (check && foundFlow && units!=flowUnits)
570 throw_error(
"incompatible units: "+flowUnits.str()+
"≠"+units.
str()+
" on stock "+stockName);
583 for (
const auto& i: flowVars())
585 for (
const auto& i: stockVars())
591 if (m_editorMode)
return Item::clickType(x,y);
592 const double dx=fabs(x-this->x()), dy=fabs(y-this->y());
593 auto z=zoomFactor()*scaleFactor();
594 const double w=0.5*iWidth()*z, h=0.5*iHeight()*z;
595 if (onResizeHandle(x,y))
return ClickType::onResize;
597 if (
auto item=select(x,y))
598 return item->clickType(x,y);
599 if (dx < w && dy < h)
600 return ClickType::onItem;
601 return ClickType::outside;
604 float GodleyIcon::toEditorX(
float xx)
const 605 {
return xx-x()+0.5f*width()-2*
border*zoomFactor()-leftMargin();}
606 float GodleyIcon::toEditorY(
float yy)
const 607 {
return yy-y()+0.5f*height()-2*
border*zoomFactor()-titleOffs();}
609 void GodleyIcon::onMouseDown(
float x,
float y)
610 {
if (m_editorMode) editor.mouseDown(toEditorX(x),toEditorY(y));}
612 void GodleyIcon::onMouseUp(
float x,
float y)
613 {
if (m_editorMode) editor.mouseUp(toEditorX(x),toEditorY(y));}
615 bool GodleyIcon::onMouseMotion(
float x,
float y)
617 if (m_editorMode) editor.mouseMoveB1(toEditorX(x),toEditorY(y));
621 bool GodleyIcon::onMouseOver(
float x,
float y)
623 if (m_editorMode) editor.mouseMove(toEditorX(x),toEditorY(y));
627 void GodleyIcon::onMouseLeave()
631 editor.mouseMove(-1,-1);
633 editor.selectedCol=-1;
634 editor.selectedRow=-1;
639 bool GodleyIcon::onKeyPress(
int keySym,
const std::string& utf8,
int)
641 if (m_editorMode) editor.keyPress(keySym, utf8);
void importDuplicateColumn(GodleyTable &srcTable, int srcCol)
find any duplicate column, and use it as a source column for balanceDuplicateColumns ...
represents items that have been selected
void balanceDuplicateColumns(const GodleyIcon &srcTable, int srcCol)
makes all duplicated columns consistent with srcTable, srcCol
std::string latexToPango(const char *s)
VariableValues variableValues
DrawVars(cairo_t *cairo, float x, float y)
represents rectangular region of a lasso operation
std::shared_ptr< Item > ItemPtr
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
const std::string & init() const
move the contents of cell at (srcRow, srcCol) to (destRow, destCol).
void operator()(const GodleyIcon::Variables &vars) const
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky's state cha...
std::string trimWS(const std::string &s)
represents the units (in sense of dimensional analysis) of a variable.
std::string str(T x)
utility function to create a string representation of a numeric type
VariablePtr definingVar(const std::string &valueId) const
returns reference to variable defining (ie input wired) for valueId
const Minsky & cminsky()
const version to help in const correctness
bool operator()(const VariablePtr &x, const VariablePtr &y) const
void ensureItemInserted(const ItemPtr &item)
check if item already present, and if not, inserts item delegates to ensureGroupInserted if passed a ...
represents a numerical coefficient times a variable (a "flow")
std::vector< VariablePtr > Variables
void convertVarType(const std::string &name, VariableType::Type type)
Converts variable(s) named by name into a variable of type type.
void draw(cairo_t *) const override
Minsky & minsky()
global minsky object
void accumulateWidthHeight(const GodleyIcon::Variables &vars, float &height, float &width)
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::GodleyIcon)
float height() const
half height of unrotated image
ItemPtr removeItem(const Item &)
float width() const
half width of unrotated image