Minsky
godleyTableWindow.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2018
3  @author Russell Standish
4  This file is part of Minsky.
5 
6  Minsky is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Minsky is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Minsky. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "cairoItems.h"
21 #include "minsky.h"
22 #include "godleyTableWindow.h"
23 #include "selection.h"
24 #include "latexMarkup.h"
25 #include <pango.h>
26 
27 #include "assetClass.rcd"
28 #include "godleyTableWindow.rcd"
29 #include "godleyTableWindow.xcd"
30 #include "minsky_epilogue.h"
31 
32 #include <boost/locale.hpp>
33 
34 using namespace std;
35 using namespace minsky;
36 using ecolab::Pango;
37 using namespace ecolab::cairo;
38 using namespace boost::locale::conv;
39 
40 #include <cairo/cairo-ps.h>
41 #include <cairo/cairo-pdf.h>
42 #include <cairo/cairo-svg.h>
43 
44 namespace
45 {
46  string capitalise(string x)
47  {
48  if (!x.empty()) x[0]=toupper(x[0]);
49  return x;
50  }
51 
52  struct Colour
53  {
54  double r,g,b;
55  } assetColour[] = {
56  {0,0,0},
57  {0,0,0},
58  {1,0,0},
59  {.6,.5,0}
60  };
61 
62  void showAsset(Pango& pango, cairo_t* cairo, GodleyAssetClass::AssetClass assetClass)
63  {
64  const CairoSave cs(cairo);
65  auto& colour=assetColour[assetClass];
66  cairo_set_source_rgb(cairo,colour.r,colour.g,colour.b);
67  pango.show();
68  }
69 
70 }
71 
72 namespace minsky
73 {
74  template <>
76  {
77  const int button=x/buttonSpacing;
78  switch (button)
79  {
80  case 0:
81  godleyIcon.table.insertRow(idx+1);
82  break;
83  case 1:
84  if (pos!=first && pos!=firstAndLast) godleyIcon.deleteRow(idx+1); // Initial conditions row cannot be deleted, even when it is the only row in the table. For ticket 1064
85  break;
86  case 2:
87  if (pos==second) // Third button of second row cannot swap initial conditions and second row. For ticket 1064
88  godleyIcon.table.moveRow(idx,1);
89  else if (pos!=first && pos!=firstAndLast) // Third button cannot swap column headings and initial conditions row values. For ticket 1064
90  godleyIcon.table.moveRow(idx,-1);
91  break;
92  case 3:
93  if (pos==middle) // Fourth button on first and second row cannot move initial conditions row. For ticket 1064
94  godleyIcon.table.moveRow(idx,1);
95  break;
96  }
97  try {godleyIcon.update();} // Update current Godley icon and table after button widget invoke. for ticket 1059.
98  catch (...) {}
99  }
100 
101  template <>
103  {
104  const int button=x/buttonSpacing;
105  if (!cminsky().multipleEquities() && godleyIcon.table.singleEquity()) { // no column widgets on equity column in single equity column mode
106  if (pos!=last)
107  switch (button)
108  {
109  case 0:
110  godleyIcon.table.insertCol(idx+1);
111  break;
112  case 1:
113  godleyIcon.table.deleteCol(idx+1);
114  break;
115  case 2:
116  if (pos==first)
117  godleyIcon.table.moveCol(idx,1);
118  else if (pos!=first)
119  godleyIcon.table.moveCol(idx,-1);
120  break;
121  case 3:
122  if (pos==middle)
123  godleyIcon.table.moveCol(idx,1);
124  break;
125  }
126  } else {
127  switch (button)
128  {
129  case 0:
130  godleyIcon.table.insertCol(idx+1);
131  break;
132  case 1:
133  godleyIcon.table.deleteCol(idx+1);
134  break;
135  case 2:
136  if (pos==first)
137  godleyIcon.table.moveCol(idx,1);
138  else
139  godleyIcon.table.moveCol(idx,-1);
140  break;
141  case 3:
142  if (pos==middle)
143  godleyIcon.table.moveCol(idx,1);
144  break;
145  }
146  }
147  try {godleyIcon.update();} // Update current Godley icon and table after button widget invoke. for ticket 1059.
148  catch (...) {}
149  }
150 
151  bool GodleyTableEditor::selectedCellInTable() const
152  {
153  return m_godleyIcon.table.cellInTable(selectedRow, selectedCol);
154  }
155 
156  void GodleyTableEditor::draw(cairo_t *cairo)
157  {
158  const CairoSave cs(cairo);
159  cairo_scale(cairo,zoomFactor,zoomFactor);
160  Pango pango(cairo);
161  pango.setMarkup("Flows ↓ / Stock Vars →");
162  rowHeight=pango.height()+2;
163  const double tableHeight=(m_godleyIcon.table.rows()-scrollRowStart+1)*rowHeight;
164  double x=leftTableOffset;
165  double lastAssetBoundary=x;
166  auto assetClass=GodleyAssetClass::noAssetClass;
167  // only recalculate colmn widths when no cell is selected.
168  const bool resizeGrid=selectedCol<0 || selectedRow<0 || motionRow>=0 || motionCol>=0;
169  if (resizeGrid)
170  colLeftMargin.clear();
171 
172  for (unsigned col=0; col<m_godleyIcon.table.cols(); ++col)
173  {
174  // omit stock columns less than scrollColStart
175  if (col>0 && col<scrollColStart) continue;
176  // vertical lines & asset type tag
177  if (assetClass!=m_godleyIcon.table._assetClass(col))
178  {
179  if (assetClass!=GodleyAssetClass::noAssetClass)
180  {
181  pango.setMarkup(capitalise(enumKey<GodleyAssetClass::AssetClass>(assetClass)));
182  // increase column by enough to fit asset class label
183  if (x < pango.width()+lastAssetBoundary+3)
184  x=pango.width()+lastAssetBoundary+3;
185  cairo_move_to(cairo,0.5*(x+lastAssetBoundary-pango.width()),0);
186  showAsset(pango, cairo, assetClass);
187  }
188  lastAssetBoundary=x;
189 
190  assetClass=m_godleyIcon.table._assetClass(col);
191  cairo_move_to(cairo,x+3,topTableOffset);
192  cairo_rel_line_to(cairo,0,tableHeight);
193  }
194  cairo_move_to(cairo,x,topTableOffset);
195  cairo_rel_line_to(cairo,0,tableHeight);
196  cairo_set_line_width(cairo,0.5);
197  cairo_stroke(cairo);
198 
199  if (drawButtons && col>0 && col<colWidgets.size())
200  {
201  const CairoSave cs(cairo);
202  cairo_move_to(cairo, x, columnButtonsOffset);
203  colWidgets[col].draw(cairo);
204  }
205 
206  if (col>1)
207  {
208  cairo_move_to(cairo,x-pulldownHot,topTableOffset);
209  pango.setMarkup("▼");
210  pango.show();
211  }
212 
213  double y=topTableOffset;
214  double colWidth=minColumnWidth;
215  for (unsigned row=0; row<m_godleyIcon.table.rows(); ++row)
216  {
217  if (row>0 && row<scrollRowStart) continue;
218 
219  if (drawButtons && col==0 && row>0 && row<rowWidgets.size())
220  {
221  const CairoSave cs(cairo);
222  cairo_move_to(cairo, 0, y);
223  rowWidgets[row].draw(cairo);
224  }
225 
226  const CairoSave cs(cairo);
227  if (row!=0 || col!=0)
228  {
229  // Make sure non-utf8 chars converted to utf8 as far as possible. for ticket 1166.
230  string text=utf_to_utf<char>(m_godleyIcon.table.cell(row,col));
231  if (!text.empty())
232  {
233  string value;
234  FlowCoef fc(text);
235  if (cminsky().displayValues && col!=0) // Do not add value "= 0.0" to first column. For tickets 1064/1274
236  try
237  {
238  auto vv=cminsky().variableValues
239  [valueId(m_godleyIcon.group.lock(),utf_to_utf<char>(fc.name))];
240  if (vv->idx()>=0)
241  {
242  const double val=fc.coef*vv->value();
243  auto ee=engExp(val);
244  if (ee.engExp==-3) ee.engExp=0;
245  value=" = "+mantissa(val,ee)+expMultiplier(ee.engExp);
246  }
247  }
248  catch (const std::exception& ex)
249  {
250  value=string("= Err: ")+ex.what();
251  // highlight error in red
252  cairo_set_source_rgb(cairo,1,0,0);
253  }
254 
255  // the active cell renders as bare LaTeX code for
256  // editing, all other cells rendered as LaTeX
257  if ((int(row)!=selectedRow || int(col)!=selectedCol) && !m_godleyIcon.table.initialConditionRow(row))
258  {
259  if (row>0 && col>0)
260  { // handle DR/CR mode and colouring of text
261  if (fc.coef<0)
262  cairo_set_source_rgb(cairo,1,0,0);
263  if (cminsky().displayStyle==GodleyTable::DRCR)
264  {
265  if (assetClass==GodleyAssetClass::asset ||
266  assetClass==GodleyAssetClass::noAssetClass)
267  text = (fc.coef<0)?"CR ":"DR ";
268  else
269  text = (fc.coef<0)?"DR ":"CR ";
270  fc.coef=abs(fc.coef);
271  text+=latexToPango(fc.str());
272  }
273  else
274  text = latexToPango(text);
275  }
276  else // is flow tag, stock var or initial condition
277  text = latexToPango(text);
278  text+=value;
279  }
280  else
281  //Display values of parameters used as initial conditions in Godley tables. for ticket 1126.
282  if (m_godleyIcon.table.initialConditionRow(row) && cminsky().displayValues) text=defang(text+=value);
283  else text=defang(text);
284  }
285  pango.setMarkup(text);
286  }
287  // allow extra space for the ▼ in row 0
288  colWidth=max(colWidth,pango.width() + (row==0? pulldownHot:0));
289  cairo_move_to(cairo,x+3,y);
290  pango.show();
291  y+=rowHeight;
292  }
293  colWidth+=5;
294 
295  if (resizeGrid)
296  {
297  colLeftMargin.push_back(x);
298  x+=colWidth;
299  }
300  else if (col+1<colLeftMargin.size())
301  x=colLeftMargin[col+1];
302  }
303 
304  // display pulldown for last column
305  cairo_move_to(cairo,x-pulldownHot,topTableOffset);
306  pango.setMarkup("▼");
307  pango.show();
308 
309 
310  pango.setMarkup
311  (capitalise(enumKey<GodleyAssetClass::AssetClass>(assetClass)));
312  // increase column by enough to fit asset class label
313  if (x < pango.width()+lastAssetBoundary+3)
314  x=pango.width()+lastAssetBoundary+3;
315  cairo_move_to(cairo,0.5*(x+lastAssetBoundary-pango.width()),0);
316  showAsset(pango, cairo, assetClass);
317  // final column vertical line
318  colLeftMargin.push_back(x);
319  cairo_move_to(cairo,x,topTableOffset);
320  cairo_rel_line_to(cairo,0,tableHeight);
321  cairo_move_to(cairo,x+3,topTableOffset);
322  cairo_rel_line_to(cairo,0,tableHeight);
323  cairo_set_line_width(cairo,0.5);
324  cairo_stroke(cairo);
325 
326  cairo_move_to(cairo,x-pulldownHot,topTableOffset);
327 
328  // now row sum column
329  x+=3;
330  double y=topTableOffset;
331  cairo_move_to(cairo,x,0); // display A-L-E above the final column. for ticket 1285
332  pango.setMarkup("A-L-E");
333  pango.show();
334  double colWidth=pango.width();
335 
336  for (unsigned row=0; row<m_godleyIcon.table.rows(); ++row) // perform row sum on stock var heading column too. for ticket 1285
337  {
338  if (row >0 && row<scrollRowStart) continue;
339  pango.setMarkup(latexToPango(m_godleyIcon.rowSum(row)));
340  colWidth=max(colWidth,pango.width());
341  cairo_move_to(cairo,x,y);
342  pango.show();
343  y+=rowHeight;
344  }
345 
346  x+=colWidth;
347  y=topTableOffset;
348  for (unsigned row=0; row<=m_godleyIcon.table.rows(); ++row)
349  {
350  // horizontal lines
351  if (row>0 && row<scrollRowStart) continue;
352  cairo_move_to(cairo,leftTableOffset,y);
353  cairo_line_to(cairo,x,y);
354  cairo_set_line_width(cairo,0.5);
355  cairo_stroke(cairo);
356  y+=rowHeight;
357  }
358 
359  // final vertical line
360  colLeftMargin.push_back(x);
361  cairo_move_to(cairo,x,topTableOffset);
362  cairo_rel_line_to(cairo,0,tableHeight);
363  cairo_set_line_width(cairo,0.5);
364  cairo_stroke(cairo);
365 
366  // indicate cell mouse is hovering over
367  if ((hoverRow>0 || hoverCol>0) &&
368  size_t(hoverRow)<m_godleyIcon.table.rows() &&
369  size_t(hoverCol)<m_godleyIcon.table.cols())
370  {
371  const CairoSave cs(cairo);
372  cairo_rectangle(cairo,
373  colLeftMargin[hoverCol],hoverRow*rowHeight+topTableOffset,
374  colLeftMargin[hoverCol+1]-colLeftMargin[hoverCol],rowHeight);
375  cairo_set_line_width(cairo,1);
376  cairo_stroke(cairo);
377  }
378 
379  // indicate selected cells
380  {
381  const CairoSave cs(cairo);
382  if (selectedRow==0 || (selectedRow>=int(scrollRowStart) && selectedRow<int(m_godleyIcon.table.rows())))
383  {
384  size_t i=0, j=0;
385  if (selectedRow>=int(scrollRowStart)) j=selectedRow-scrollRowStart+1;
386 
387  if (motionCol>=0 && selectedRow==0 && selectedCol>0) // whole col being moved
388  {
389  highlightColumn(cairo,selectedCol);
390  highlightColumn(cairo,motionCol);
391  }
392  else if (motionRow>=0 && selectedCol==0 && selectedRow>0) // whole Row being moved
393  {
394  highlightRow(cairo,selectedRow);
395  highlightRow(cairo,motionRow);
396  }
397  else if (selectedCol==0 || /* selecting individual cell */
398  (selectedCol>=int(scrollColStart) && selectedCol<int(m_godleyIcon.table.cols())))
399  {
400  if ((selectedRow>1 || selectedRow <0) || selectedCol!=0) // can't select flows/stockVars or Initial Conditions labels
401  {
402  if (selectedCol>=int(scrollColStart)) i=selectedCol-scrollColStart+1;
403  const double xx=colLeftMargin[i], yy=j*rowHeight+topTableOffset;
404  {
405  const cairo::CairoSave cs(cairo);
406  cairo_set_source_rgba(cairo,1,1,1,1);
407  cairo_rectangle(cairo,xx,yy,colLeftMargin[i+1]-xx,rowHeight);
408  cairo_fill_preserve(cairo);
409  cairo_set_source_rgba(cairo,1,.55,0,1);
410  cairo_set_line_width(cairo,2);
411  cairo_stroke(cairo);
412  }
413  pango.setMarkup(defang(m_godleyIcon.table.cell(selectedRow,selectedCol)));
414  cairo_move_to(cairo,xx,yy);
415  pango.show();
416 
417  // show insertion cursor
418  cairo_move_to(cairo,xx+pango.idxToPos(insertIdx),yy);
419  cairo_rel_line_to(cairo,0,rowHeight);
420  cairo_set_line_width(cairo,1);
421  cairo_stroke(cairo);
422  if (motionRow>0 && motionCol>0)
423  highlightCell(cairo,motionRow,motionCol);
424  if (selectIdx!=insertIdx)
425  {
426  // indicate some text has been selected
427  cairo_rectangle(cairo,xx+pango.idxToPos(insertIdx),yy,
428  pango.idxToPos(selectIdx)-pango.idxToPos(insertIdx),rowHeight);
429  cairo_set_source_rgba(cairo,0.5,0.5,0.5,0.5);
430  cairo_fill(cairo);
431  }
432  }
433  }
434  }
435  }
436  }
437 
438  double GodleyTableEditor::height() const
439  {
440  return godleyIcon().table.rows()*rowHeight;
441  }
442 
443  int GodleyTableEditor::colX(double x) const
444  {
445  if (colLeftMargin.size()<2 || x<colLeftMargin[0]) return -1;
446  if (x<colLeftMargin[1]) return 0;
447  auto p=std::upper_bound(colLeftMargin.begin(), colLeftMargin.end(), x);
448  size_t r=p-colLeftMargin.begin()-2+scrollColStart;
449  if (r>m_godleyIcon.table.cols()-1) r=-1; // out of bounds, invalidate. Also exclude A-L-E column. For ticket 1163.
450  return r;
451  }
452 
453  int GodleyTableEditor::rowY(double y) const
454  {
455  int c=(y-topTableOffset)/rowHeight;
456  if (c>0) c+=scrollRowStart-1;
457  if (c<0 || size_t(c)>m_godleyIcon.table.rows()) c=-1; // out of bounds, invalidate
458  return c;
459  }
460 
461  int GodleyTableEditor::textIdx(double x) const
462  {
463  const cairo::Surface surf(cairo_recording_surface_create(CAIRO_CONTENT_COLOR,NULL));
464  Pango pango(surf.cairo());
465  if (selectedCellInTable() && (selectedRow!=1 || selectedCol!=0)) // No text index needed for a cell that is immutable. For ticket 1064
466  {
467  // Make sure non-utf8 chars converted to utf8 as far as possible. for ticket 1166.
468  auto& str=m_godleyIcon.table.cell(selectedRow,selectedCol);
469  str=utf_to_utf<char>(str);
470  pango.setMarkup(defang(str));
471  int j=0;
472  if (selectedCol>=int(scrollColStart)) j=selectedCol-scrollColStart+1;
473  x-=colLeftMargin[j]+2;
474  x*=zoomFactor;
475  if (x>0 && str.length())
476  {
477  auto p=pango.posToIdx(x);
478  if (p<str.length())
479  return p+numBytes(str[p]);
480  }
481  }
482  return 0;
483  }
484 
485  void GodleyTableEditor::mouseDown(double x, double y)
486  {
487  // catch exception, as the intention here is to allow the user to fix a problem
488  try {update();}
489  catch (...) {}
490  button1=true;
491  x/=zoomFactor;
492  y/=zoomFactor;
493  requestRedrawCanvas();
494  switch (clickType(x,y))
495  {
496  case rowWidget:
497  {
498  const unsigned r=rowY(y);
499  if (r<rowWidgets.size())
500  {
501  rowWidgets[r].invoke(x);
502  adjustWidgets();
503  selectedCol=selectedRow=-1;
504  }
505  return;
506  }
507  case colWidget:
508  {
509  const unsigned c=colX(x);
510  const unsigned visibleCol=c-scrollColStart+1;
511  if (c<colWidgets.size() && visibleCol < colLeftMargin.size())
512  {
513  colWidgets[c].invoke(x-colLeftMargin[visibleCol]);
514  adjustWidgets();
515  selectedCol=selectedRow=-1;
516  }
517  return;
518  }
519  case background:
520  selectIdx=insertIdx=0;
521  selectedCol=selectedRow=-1;
522  break;
523  default:
524  if (selectedRow>=0 && selectedCol>=0)
525  { // if cell already selected, deselect to allow the chance to redraw
526  selectedCol=selectedRow=-1;
527  break;
528  }
529  selectedCol=colX(x);
530  selectedRow=rowY(y);
531  if (selectedCellInTable() && (selectedRow!=1 || selectedCol!=0)) // Cannot save text in cell(1,0). For ticket 1064
532  {
533  // Make sure non-utf8 chars converted to utf8 as far as possible. for ticket 1166.
534  auto& str=m_godleyIcon.table.cell(selectedRow,selectedCol);
535  str=utf_to_utf<char>(str);
536  m_godleyIcon.table.savedText=str;
537  selectIdx=insertIdx = textIdx(x);
538  }
539  else
540  selectIdx=insertIdx=0;
541  break;
542  }
543  }
544 
545  void GodleyTableEditor::mouseUp(double x, double y)
546  {
547  button1=false;
548  x/=zoomFactor;
549  y/=zoomFactor;
550  const int c=colX(x), r=rowY(y);
551  motionRow=motionCol=-1;
552  // Cannot swap cell(1,0) with another. For ticket 1064. Also cannot move cells outside an existing Godley table to create new rows or columns. For ticket 1066.
553  if ((selectedCol==0 && selectedRow==1) || (c==0 && r==1) || size_t(selectedRow)>=(m_godleyIcon.table.rows()) || size_t(r)>=(m_godleyIcon.table.rows()) || size_t(c)>=(m_godleyIcon.table.cols()) || size_t(selectedCol)>=(m_godleyIcon.table.cols()))
554  return;
555  if (selectedRow==0)
556  {
557  // Disallow moving flow labels column and prevent columns from moving when import stockvar dropdown button is pressed in empty column. For tickets 1053/1064/1066
558  if (c>0 && size_t(c)<m_godleyIcon.table.cols() && selectedCol>0 && size_t(selectedCol)<m_godleyIcon.table.cols() && c!=selectedCol && !(colLeftMargin[c+1]-x < pulldownHot))
559  m_godleyIcon.table.moveCol(selectedCol,c-selectedCol);
560  }
561  else if (r>0 && selectedCol==0)
562  {
563  if (r!=selectedRow && !m_godleyIcon.table.initialConditionRow(selectedRow) && !m_godleyIcon.table.initialConditionRow(r)) // Cannot move Intitial Conditions row. For ticket 1064.
564  m_godleyIcon.table.moveRow(selectedRow,r-selectedRow);
565  }
566  else if ((c!=selectedCol || r!=selectedRow) && c>0 && r>0)
567  {
568  swap(m_godleyIcon.table.cell(selectedRow,selectedCol), m_godleyIcon.table.cell(r,c));
569  minsky().balanceDuplicateColumns(m_godleyIcon,selectedCol);
570  minsky().balanceDuplicateColumns(m_godleyIcon,c);
571  selectedCol=-1;
572  selectedRow=-1;
573  }
574  else if (selectIdx!=insertIdx)
575  copy();
576  requestRedrawCanvas();
577  }
578 
579  void GodleyTableEditor::mouseMoveB1(double x, double y)
580  {
581  x/=zoomFactor;
582  y/=zoomFactor;
583  motionCol=colX(x), motionRow=rowY(y);
584  if (motionCol==selectedCol && motionRow==selectedRow)
585  selectIdx=textIdx(x);
586  requestRedrawCanvas();
587  }
588 
589  void GodleyTableEditor::mouseMove(double x, double y)
590  {
591  if (button1)
592  {
593  mouseMoveB1(x,y);
594  return;
595  }
596  x/=zoomFactor;
597  y/=zoomFactor;
598  // clear any existing marks
599  for (auto& i: rowWidgets) i.hover(-1);
600  for (auto& i: colWidgets) i.hover(-1);
601  hoverRow=hoverCol=-1;
602  switch (clickType(x,y))
603  {
604  case rowWidget:
605  {
606  const unsigned r=rowY(y);
607  if (r<rowWidgets.size())
608  rowWidgets[r].hover(x);
609  requestRedrawCanvas();
610  break;
611  }
612  case colWidget:
613  {
614  const unsigned c=colX(x);
615  if (c<colWidgets.size())
616  colWidgets[c].hover(x-colLeftMargin[c]);
617  requestRedrawCanvas();
618  break;
619  }
620  case background:
621  break;
622  default:
623  hoverRow=rowY(y);
624  if (hoverRow>0) hoverRow-=scrollRowStart-1;
625  hoverCol=colX(x);
626  if (hoverCol>0) hoverCol-=scrollColStart-1;
627  break;
628  }
629  }
630 
631  inline constexpr char control(char x) {return x-'`';}
632 
633  void GodleyTableEditor::keyPress(int keySym, const std::string& utf8)
634  {
635 
636  auto& table=m_godleyIcon.table;
637  if (selectedCellInTable() && (selectedCol!=0 || selectedRow!=1)) // Cell (1,0) is off-limits. For ticket 1064
638  {
639  auto& str=table.cell(selectedRow,selectedCol);
640  str=utf_to_utf<char>(str);
641  if (utf8.length() && (keySym<0x7f || (0xffaa <= keySym && keySym <= 0xffbf))) // Enable numeric keypad key presses. For ticket 1136
642  // all printing and control characters have keysym
643  // <0x80. But some keys (eg tab, backspace and escape
644  // are mapped to control characters
645  if (unsigned(utf8[0])>=' ' && utf8[0]!=0x7f)
646  {
647  delSelection();
648  if (insertIdx>=str.length()) insertIdx=str.length();
649  str.insert(insertIdx,utf8);
650  selectIdx=insertIdx+=utf8.length();
651  }
652  else
653  {
654  switch (utf8[0]) // process control characters
655  {
656  case control('x'):
657  cut();
658  break;
659  case control('c'):
660  copy();
661  break;
662  case control('v'):
663  paste();
664  break;
665  case control('h'): case 0x7f:
666  handleDelete();
667  break;
668  }
669  }
670  else
671  {
672  switch (keySym)
673  {
674  case 0xff08: // backspace
675  handleBackspace();
676  break;
677  case 0xffff: // delete
678  handleDelete();
679  break;
680  case 0xff1b: // escape
681  if (selectedRow>=0 && size_t(selectedRow)<=table.rows() &&
682  selectedCol>=0 && size_t(selectedCol)<=table.cols())
683  table.cell(selectedRow, selectedCol)=table.savedText;
684  selectedRow=selectedCol=-1;
685  break;
686  case 0xff0d: //return
687  case 0xff8d: //enter added for ticket 1122
688  update();
689  selectedRow=selectedCol=-1;
690  break;
691  case 0xff51: //left arrow
692  if (insertIdx>0) insertIdx=prevIndex(str, insertIdx);
693  else navigateLeft();
694  break;
695  case 0xff53: //right arrow
696  if (insertIdx<str.length()) insertIdx+=numBytes(str[insertIdx]);
697  else navigateRight();
698  break;
699  case 0xff09: // tab
700  navigateRight();
701  break;
702  case 0xfe20: // back tab
703  navigateLeft();
704  break;
705  case 0xff54: // down
706  navigateDown();
707  break;
708  case 0xff52: // up
709  navigateUp();
710  break;
711  default:
712  return; // key not handled, just return without resetting selection
713  }
714  selectIdx=insertIdx;
715  }
716  }
717  else // nothing selected
718  {
719  // if one of the navigation keys pressed, move to the first/last etc cell
720  switch (keySym)
721  {
722  case 0xff09: case 0xff53: // tab, right
723  selectedRow=0; selectedCol=1; break;
724  case 0xfe20: // back tab
725  selectedRow=table.rows()-1; selectedCol=table.cols()-1; break;
726  case 0xff51: //left arrow
727  selectedRow=0; selectedCol=table.cols()-1; break;
728  case 0xff54: // down
729  selectedRow=2; selectedCol=0; break; // Start from second row because Initial Conditions cell (1,0) can no longer be selected. For ticket 1064
730  case 0xff52: // up
731  selectedRow=table.rows()-1; selectedCol=0; break;
732  default:
733  return; // early return, no need to redraw
734  }
735  }
736  requestRedrawCanvas();
737  }
738 
739  void GodleyTableEditor::delSelection()
740  {
741  if (selectedCellInTable() && insertIdx!=selectIdx)
742  {
743  auto& str=m_godleyIcon.table.cell(selectedRow,selectedCol);
744  str.erase(min(insertIdx,selectIdx),abs(int(insertIdx)-int(selectIdx)));
745  selectIdx=insertIdx=min(insertIdx,selectIdx);
746  }
747  }
748 
749  void GodleyTableEditor::handleBackspace()
750  {
751  if (!selectedCellInTable()) return;
752  auto& table=m_godleyIcon.table;
753  auto& str=table.cell(selectedRow,selectedCol);
754  if (insertIdx!=selectIdx)
755  delSelection();
756  else if (insertIdx>0 && insertIdx<=str.length())
757  {
758  insertIdx=prevIndex(str, insertIdx);
759  str.erase(insertIdx,numBytes(str[insertIdx]));
760  }
761  selectIdx=insertIdx;
762  }
763 
764  void GodleyTableEditor::handleDelete()
765  {
766  if (!selectedCellInTable()) return;
767  auto& table=m_godleyIcon.table;
768  auto& str=table.cell(selectedRow,selectedCol);
769  if (insertIdx!=selectIdx)
770  delSelection();
771  else if (insertIdx<str.length())
772  str.erase(insertIdx,numBytes(str[insertIdx]));
773  selectIdx=insertIdx;
774  }
775 
777  {
778  if (!selectedCellInTable()) return;
779  copy();
780  if (selectIdx==insertIdx)
781  // delete entire cell
782  m_godleyIcon.table.cell(selectedRow,selectedCol).clear();
783  else
784  delSelection();
785  requestRedrawCanvas();
786  }
787 
788  void GodleyTableEditor::copy()
789  {
790  if (!selectedCellInTable()) return;
791  auto& str=m_godleyIcon.table.cell(selectedRow,selectedCol);
792  if (selectIdx!=insertIdx)
793  cminsky().clipboard.putClipboard
794  (str.substr(min(selectIdx,insertIdx), abs(int(selectIdx)-int(insertIdx))));
795  else
796  cminsky().clipboard.putClipboard(str);
797  }
798 
799  void GodleyTableEditor::paste()
800  {
801  if (!selectedCellInTable()) return;
802  delSelection();
803  auto& str=m_godleyIcon.table.cell(selectedRow,selectedCol);
804  auto stringToInsert=cminsky().clipboard.getClipboard();
805  // only insert first line
806  auto p=stringToInsert.find('\n');
807  if (p!=string::npos)
808  stringToInsert=stringToInsert.substr(0,p-1);
809  str.insert(insertIdx,stringToInsert);
810  selectIdx=insertIdx+=stringToInsert.length();
811  requestRedrawCanvas();
812  }
813 
814  GodleyTableEditor::ClickType GodleyTableEditor::clickType(double x, double y) const
815  {
816  const int c=colX(x), r=rowY(y);
817 
818  if (x<leftTableOffset && r>0)
819  return rowWidget;
820  if (y<topTableOffset && y>columnButtonsOffset && c>0)
821  return colWidget;
822 
823  if (r==0)
824  {
825  if (colLeftMargin[c+1]-x < pulldownHot)
826  return importStock;
827  return row0;
828  }
829  if (c==0)
830  return col0;
831 
832  if (c>0 && c<int(m_godleyIcon.table.cols()))
833  if (r>0 && r<int(m_godleyIcon.table.rows()))
834  return internal;
835 
836  return background;
837  }
838 
839  std::set<string> GodleyTableEditor::matchingTableColumns(double x)
840  {
841  const int col=colXZoomed(x);
842  return matchingTableColumnsByCol(col);
843  }
844 
845  std::set<string> GodleyTableEditor::matchingTableColumnsByCol(int col)
846  {
847  if (col<0||col>=static_cast<int>(godleyIcon().table.cols())) return {};
848  return minsky().matchingTableColumns(godleyIcon(), godleyIcon().table._assetClass(col));
849  }
850 
851  void GodleyTableEditor::addStockVar(double x)
852  {
853  const int c=colXZoomed(x);
854  addStockVarByCol(c);
855  }
856 
857  void GodleyTableEditor::addStockVarByCol(int c)
858  {
859  if (c>0) m_godleyIcon.table.insertCol(c+1);
860  }
861 
862  void GodleyTableEditor::importStockVar(const string& name, double x)
863  {
864  const int c=colXZoomed(x);
865  importStockVarByCol(name, c);
866  }
867 
868  void GodleyTableEditor::importStockVarByCol(const string& name, int c)
869  {
870  if (c>0 && size_t(c)<m_godleyIcon.table.cols())
871  {
872  m_godleyIcon.table.cell(0,c)=name;
873  minsky().importDuplicateColumn(m_godleyIcon.table, c);
874  adjustWidgets();
875  update(); //TODO I don't know why this is insufficient to update icon on canvas
876  }
877  }
878 
879  void GodleyTableEditor::deleteStockVar(double x)
880  {
881  const int c=colXZoomed(x);
882  deleteStockVarByCol(c);
883  }
884 
885  void GodleyTableEditor::deleteStockVarByCol(int c)
886  {
887  if (c>=0)
888  m_godleyIcon.table.deleteCol(c+1);
889  }
890 
891  void GodleyTableEditor::addFlow(double y)
892  {
893  const int r=rowYZoomed(y);
894  addFlowByRow(r);
895  }
896 
897  void GodleyTableEditor::addFlowByRow(int r)
898  {
899  if (r>0)
900  m_godleyIcon.table.insertRow(r+1);
901  }
902 
903  void GodleyTableEditor::deleteFlow(double y)
904  {
905  const int r=rowYZoomed(y);
906  deleteFlowByRow(r);
907  }
908 
909  void GodleyTableEditor::deleteFlowByRow(int r)
910  {
911  if (r>1) // Cannot delete flow in Initial Conditions row. For ticket 1064
912  m_godleyIcon.deleteRow(r+1);
913  }
914 
915 namespace {
917  {
918  string tmpStr="This will convert "+var+" from "+classdesc::enumKey<GodleyAssetClass::AssetClass>(oldAC)+" to "+classdesc::enumKey<GodleyAssetClass::AssetClass>(targetAC)+". Are you sure?";
919  return tmpStr;
920  }
921 }
922 
923  string GodleyTableEditor::moveAssetClass(double x, double y)
924  {
925  x/=zoomFactor;
926  y/=zoomFactor;
927  const unsigned c=colX(x);
928  string tmpStr;
929  if (c>=m_godleyIcon.table.cols()) return tmpStr;
930  if (clickType(x,y)==colWidget) {
931  const unsigned visibleCol=c-scrollColStart+1;
932  if (c<colWidgets.size() && visibleCol < colLeftMargin.size())
933  {
934  auto moveVar=m_godleyIcon.table.cell(0,c);
935  auto oldAssetClass=m_godleyIcon.table._assetClass(c);
936  auto targetAssetClassPlus=m_godleyIcon.table._assetClass(c+1);
937  auto targetAssetClassMinus=m_godleyIcon.table._assetClass(c-1);
938  if (colWidgets[c].button(x-colLeftMargin[visibleCol])==3 && oldAssetClass!=GodleyAssetClass::equity) {
939  if (targetAssetClassPlus!=oldAssetClass && !moveVar.empty() && targetAssetClassPlus!=GodleyAssetClass::equity && targetAssetClassPlus!=GodleyAssetClass::noAssetClass)
940  tmpStr=constructMessage(targetAssetClassPlus,oldAssetClass,moveVar);
941  else if (targetAssetClassPlus==GodleyAssetClass::noAssetClass && !moveVar.empty())
942  tmpStr="Cannot convert stock variable to an equity class";
943  }
944  else if (colWidgets[c].button(x-colLeftMargin[visibleCol])==2 && oldAssetClass==GodleyAssetClass::asset && oldAssetClass!=GodleyAssetClass::equity && targetAssetClassMinus!=GodleyAssetClass::asset) {
945  if (targetAssetClassPlus!=oldAssetClass && !moveVar.empty() && targetAssetClassPlus!=GodleyAssetClass::equity && targetAssetClassPlus!=GodleyAssetClass::noAssetClass)
946  tmpStr=constructMessage(targetAssetClassPlus,oldAssetClass,moveVar);
947  else if ((targetAssetClassPlus==GodleyAssetClass::equity || targetAssetClassPlus==GodleyAssetClass::noAssetClass) && !moveVar.empty())
948  tmpStr="Cannot convert stock variable to an equity class";
949  }
950  else if (colWidgets[c].button(x-colLeftMargin[visibleCol])==2 && oldAssetClass!=GodleyAssetClass::equity) {
951  if (targetAssetClassMinus!=oldAssetClass && !moveVar.empty())
952  tmpStr=constructMessage(targetAssetClassMinus,oldAssetClass,moveVar);
953  }
954  }
955  }
956  return tmpStr;
957  }
958 
959  string GodleyTableEditor::swapAssetClass(double x, double)
960  {
961  x/=zoomFactor;
962  const int c=colX(x);
963  string tmpStr;
964  if (selectedRow==0 && size_t(selectedCol)<m_godleyIcon.table.cols())
965  {
966  // clickType triggers pango error which causes this condition to be skipped and thus column gets moved to Equity, which should not be the case
967  if (c>0 && selectedCol>0 && c!=selectedCol) {
968  auto swapVar=m_godleyIcon.table.cell(0,selectedCol);
969  auto oldAssetClass=m_godleyIcon.table._assetClass(selectedCol);
970  auto targetAssetClass=m_godleyIcon.table._assetClass(c);
971  if (!swapVar.empty() && !(colLeftMargin[c+1]-x < pulldownHot)) { // ImportVar dropdown button should not trigger this condition. For ticket 1162
972  if (targetAssetClass!=oldAssetClass && targetAssetClass!=GodleyAssetClass::equity && targetAssetClass!=GodleyAssetClass::noAssetClass)
973  tmpStr=constructMessage(targetAssetClass,oldAssetClass,swapVar);
974  else if ((targetAssetClass==GodleyAssetClass::equity || targetAssetClass==GodleyAssetClass::noAssetClass) || oldAssetClass==GodleyAssetClass::noAssetClass)
975  tmpStr="Cannot convert stock variable to an equity class";
976  }
977  }
978  }
979  return tmpStr;
980  }
981 
982  void GodleyTableEditor::highlightColumn(cairo_t* cairo, unsigned col)
983  {
984  if (col<scrollColStart) return;
985  const double x=colLeftMargin[col-scrollColStart+1];
986  const double width=colLeftMargin[col-scrollColStart+2]-x;
987  const double tableHeight=(m_godleyIcon.table.rows()-scrollRowStart+1)*rowHeight;
988  cairo_rectangle(cairo,x,topTableOffset,width,tableHeight);
989  cairo_set_source_rgba(cairo,1,1,1,0.5);
990  cairo_fill(cairo);
991  }
992 
993  void GodleyTableEditor::highlightRow(cairo_t* cairo, unsigned row)
994  {
995  if (row<scrollRowStart) return;
996  const double y=(row-scrollRowStart+1)*rowHeight+topTableOffset;
997  cairo_rectangle(cairo,leftTableOffset,y,colLeftMargin.back()-leftTableOffset,rowHeight);
998  cairo_set_source_rgba(cairo,1,1,1,0.5);
999  cairo_fill(cairo);
1000  }
1001 
1002  void GodleyTableEditor::highlightCell(cairo_t* cairo, unsigned row, unsigned col)
1003  {
1004  if (row<scrollRowStart || col<scrollColStart) return;
1005  const double x=colLeftMargin[col-scrollColStart+1];
1006  const double width=colLeftMargin[col-scrollColStart+2]-x;
1007  const double y=(row-scrollRowStart+1)*rowHeight+topTableOffset;
1008  cairo_rectangle(cairo,x,y,width,rowHeight);
1009  cairo_set_source_rgba(cairo,1,1,1,0.5);
1010  cairo_fill(cairo);
1011  }
1012 
1013  void GodleyTableEditor::pushHistory()
1014  {
1015  while (history.size()>maxHistory) history.pop_front();
1016  // Perform deep comparison of Godley tables in history to avoid spurious noAssetClass columns from arising during undo. For ticket 1118.
1017  if (history.empty() || !(history.back()==m_godleyIcon.table)) {
1018  history.push_back(m_godleyIcon.table);
1019  }
1020  historyPtr=history.size();
1021  }
1022 
1023  void GodleyTableEditor::undo(int changes)
1024  {
1025  if (historyPtr==history.size())
1026  pushHistory();
1027  historyPtr-=changes;
1028  if (historyPtr > 0 && historyPtr <= history.size())
1029  {
1030  auto& d=history[historyPtr-1];
1031  // Perform deep comparison of Godley tables in history to avoid spurious noAssetClass columns from arising during undo. For ticket 1118.
1032  if (d.getData().empty()) return; // should not happen
1033  m_godleyIcon.table=d;
1034  }
1035  }
1036 
1037  void GodleyTableEditor::adjustWidgets()
1038  {
1039  rowWidgets.clear();
1040  for (size_t i=0; i<m_godleyIcon.table.rows(); ++i)
1041  rowWidgets.emplace_back(m_godleyIcon, i);
1042  colWidgets.clear();
1043  for (size_t i=0; i<m_godleyIcon.table.cols(); ++i)
1044  colWidgets.emplace_back(m_godleyIcon, i);
1045  // nb first column/row is actually 1 - 0th element actually
1046  // just ignored
1047  if (rowWidgets.size()==2)
1048  rowWidgets[1].pos=firstAndLast;
1049  else if (rowWidgets.size()==3)
1050  {
1051  rowWidgets[1].pos=first;
1052  rowWidgets.back().pos=second; // Position to avoid Initial Conditions row from being moved. For ticket 1064
1053  }
1054  else if (rowWidgets.size()>3)
1055  {
1056  rowWidgets[1].pos=first;
1057  rowWidgets[2].pos=second; // Position to avoid Initial Conditions row from being moved. For ticket 1064
1058  rowWidgets.back().pos=last;
1059  }
1060  if (colWidgets.size()==2)
1061  colWidgets[1].pos=firstAndLast;
1062  else if (colWidgets.size()>2)
1063  {
1064  colWidgets[1].pos=first;
1065  colWidgets.back().pos=last;
1066  }
1067  }
1068 
1069  void GodleyTableEditor::update()
1070  {
1071  if (selectedCol>0 && selectedCol<int(m_godleyIcon.table.cols()))
1072  {
1073  if (selectedRow==0)
1074  {
1075  // rename all instances of the stock variable if updated. For ticket #956
1076  // find stock variable if it exists
1077  for (const auto& sv: m_godleyIcon.stockVars())
1078  if (sv->valueId()==m_godleyIcon.valueId(m_godleyIcon.table.savedText))
1079  {
1080  auto savedItem=minsky().canvas.item;
1081  minsky().canvas.item=sv;
1082  auto newName=utf_to_utf<char>(m_godleyIcon.table.cell(selectedRow,selectedCol));
1083  if (!newName.empty())
1084  minsky().canvas.renameAllInstances(newName);
1085  savedItem.swap(minsky().canvas.item);
1086  }
1087  minsky().importDuplicateColumn(m_godleyIcon.table, selectedCol);
1088  }
1089  else
1090  {
1091  if (m_godleyIcon.table.initialConditionRow(selectedRow))
1092  {
1093  // if the contents of the cell are cleared, set the cell to "0". For #1181
1094  if (!m_godleyIcon.table.savedText.empty() && m_godleyIcon.table.cell(selectedRow,selectedCol).empty())
1095  m_godleyIcon.table.cell(selectedRow,selectedCol)="0";
1096  }
1097  minsky().balanceDuplicateColumns(m_godleyIcon,selectedCol);
1098  }
1099  // get list of GodleyIcons first, rather than doing recursiveDo, as update munges the items vectors
1100  auto godleyTables=minsky().model->findItems
1101  ([](const ItemPtr& i){return dynamic_cast<GodleyIcon*>(i.get());});
1102  for (auto& i: godleyTables)
1103  if (auto* g=dynamic_cast<GodleyIcon*>(i.get()))
1104  g->update();
1105  }
1108  }
1109 
1110  void GodleyTableEditor::checkCell00()
1111  {
1112  if (selectedCol==0 && (selectedRow==0 || selectedRow ==1))
1113  // (0,0) cell not editable
1114  {
1115  selectedCol=-1;
1116  selectedRow=-1;
1117  }
1118  }
1119 
1120  void GodleyTableEditor::navigateRight()
1121  {
1122  if (selectedCol>=0)
1123  {
1124  update();
1125  selectedCol++;
1126  insertIdx=0;
1127  if (selectedCol>=int(m_godleyIcon.table.cols()))
1128  {
1129  if (selectedRow>0) selectedCol=0; // Minor fix: Make sure tabbing and right arrow traverse all editable cells.
1130  else selectedCol=1;
1131  navigateDown();
1132  }
1133  checkCell00();
1134  }
1135  }
1136 
1137  void GodleyTableEditor::navigateLeft()
1138  {
1139  if (selectedCol>=0)
1140  {
1141  update();
1142  selectedCol--;
1143  if (selectedCol<0)
1144  {
1145  selectedCol=m_godleyIcon.table.cols()-1;
1146  navigateUp();
1147  }
1148  checkCell00();
1149  insertIdx=godleyIcon().table.cellInTable(selectedRow, selectedCol)?
1150  godleyIcon().table.cell(selectedRow, selectedCol).length(): 0;
1151  }
1152  }
1153 
1154  void GodleyTableEditor::navigateUp()
1155  {
1156  update();
1157  if (selectedRow>=0)
1158  selectedRow=(selectedRow-1)%m_godleyIcon.table.rows();
1159  checkCell00();
1160  }
1161 
1162  void GodleyTableEditor::navigateDown()
1163  {
1164  update();
1165  if (selectedRow>=0)
1166  selectedRow=(selectedRow+1)%m_godleyIcon.table.rows();
1167  checkCell00();
1168  }
1169 
1170  template <ButtonWidgetEnums::RowCol rowCol>
1171  void ButtonWidget<rowCol>::drawButton(cairo_t* cairo, const std::string& label, double r, double g, double b, int idx)
1172  {
1173  // stash current point for drawing a box
1174  double x0, y0;
1175  cairo_get_current_point(cairo,&x0, &y0);
1176 
1177  const CairoSave cs(cairo);
1178  Pango pango(cairo);
1179  // increase text size a bit for the buttons
1180  pango.setFontSize(0.8*buttonSpacing);
1181  pango.setMarkup(label);
1182  cairo_set_source_rgb(cairo,r,g,b);
1183  pango.show();
1184 
1185  // draw box around button
1186  cairo_rectangle(cairo, x0, y0+0.2*pango.height(), buttonSpacing, buttonSpacing);
1187  if (idx==m_mouseOver)
1188  cairo_set_source_rgb(cairo,0,0,0); // draw in black if mouse over button
1189  else
1190  cairo_set_source_rgb(cairo,0.5,0.5,0.5); // draw in grey
1191  cairo_set_line_width(cairo,1);
1192  cairo_stroke(cairo);
1193  cairo_move_to(cairo,x0+buttonSpacing,y0);
1194  }
1195 
1196  template <ButtonWidgetEnums::RowCol rowCol>
1197  void ButtonWidget<rowCol>::draw(cairo_t* cairo)
1198  {
1199  const CairoSave cs(cairo);
1200  int idx=0;
1201  if (rowCol==row || (!cminsky().multipleEquities() && godleyIcon.table.singleEquity())) { // no column widgets on equity column in single equity column mode
1202  if (rowCol == row || (rowCol == col && pos!=last))
1203  drawButton(cairo,"+",0,1,0,idx++);
1204  if ((rowCol == row && pos!=first && pos!=firstAndLast) || (rowCol == col && pos!=last)) // no delete button for first row containing initial conditions. For ticket 1064
1205  drawButton(cairo,"—",1,0,0,idx++);
1206  if ((rowCol == row && pos!=first && pos!=second && pos!=firstAndLast) || (rowCol == col && pos!=first && pos!=last)) // no move up button for first row containing initial conditions. For ticket 1064
1207  drawButton(cairo,rowCol==row? "↑": "←",0,0,0,idx++);
1208  if ((pos!=first && pos!=last && pos!=firstAndLast) || (rowCol == col && pos!=last)) // no move down button for first row containing initial conditions. For ticket 1064
1209  drawButton(cairo,rowCol==row? "↓": "→",0,0,0,idx++);
1210  } else {
1211  drawButton(cairo,"+",0,1,0,idx++);
1212  if ((pos!=first && pos!=firstAndLast) || rowCol == col) // no delete button for first row containing initial conditions. For ticket 1064
1213  drawButton(cairo,"—",1,0,0,idx++);
1214  if (pos!=first && pos!=second && pos!=firstAndLast) // no move up button for first row containing initial conditions. For ticket 1064
1215  drawButton(cairo,rowCol==row? "↑": "←",0,0,0,idx++);
1216  if ((pos!=first && pos!=last && pos!=firstAndLast) || (rowCol == col && pos!=last)) // no move down button for first row containing initial conditions. For ticket 1064
1217  drawButton(cairo,rowCol==row? "↓": "→",0,0,0,idx++);
1218  }
1219 
1220  }
1221 
1222  template class ButtonWidget<ButtonWidgetEnums::row>;
1223  template class ButtonWidget<ButtonWidgetEnums::col>;
1224 }
std::string expMultiplier(int exp)
string defang(char c)
Definition: latexMarkup.cc:842
void importDuplicateColumn(GodleyTable &srcTable, int srcCol)
find any duplicate column, and use it as a source column for balanceDuplicateColumns ...
Definition: minsky.cc:618
string constructMessage(GodleyAssetClass::AssetClass &targetAC, GodleyAssetClass::AssetClass &oldAC, string &var)
void balanceDuplicateColumns(const GodleyIcon &srcTable, int srcCol)
makes all duplicated columns consistent with srcTable, srcCol
Definition: minsky.cc:753
void redrawAllGodleyTables()
request all Godley table windows to redraw
Definition: minsky.cc:1703
std::string latexToPango(const char *s)
Definition: latexMarkup.h:30
supports +/-/←/→/↓/↑ widget
moveAssetClassid x y X Y
Definition: godley.tcl:156
EngNotation engExp(double value)
return formatted mantissa and exponent in engineering format
unsigned numBytes(unsigned char x)
a wrapper around std::ofstream that checks the write succeeded, throwing an exception if not ...
Definition: str.h:100
VariableValues variableValues
Definition: minsky.h:200
minsky::Minsky minsky
Definition: pyminsky.cc:28
std::string name
Definition: flowCoef.h:30
bool displayValues
Definition: minsky.h:431
STL namespace.
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::GodleyTableWindow)
std::shared_ptr< Item > ItemPtr
Definition: item.h:57
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
Definition: valueId.cc:75
undodelta
Definition: minsky.tcl:747
importStockVarid var x
Definition: godley.tcl:188
std::string str() const
Definition: flowCoef.cc:81
void renameAllInstances(const std::string &newName)
rename all instances of variable as item to newName
Definition: canvas.cc:555
mouseDownid x y X Y
Definition: godley.tcl:137
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
Canvas canvas
Definition: minsky.h:256
size_t prevIndex(const std::string &str, size_t index)
return index of previous character to index
Definition: str.h:112
std::string str(T x)
utility function to create a string representation of a numeric type
Definition: str.h:33
const Minsky & cminsky()
const version to help in const correctness
Definition: minsky.h:549
std::string mantissa(double value, const EngNotation &, int digits=3)
struct anonymous_namespace{godleyTableWindow.cc}::Colour assetColour[]
void showAsset(Pango &pango, cairo_t *cairo, GodleyAssetClass::AssetClass assetClass)
represents a numerical coefficient times a variable (a "flow")
Definition: flowCoef.h:27
void requestRedraw()
request a redraw on the screen
Definition: canvas.h:292
constexpr char control(char x)
std::set< string > matchingTableColumns(const GodleyIcon &currTable, GodleyAssetClass::AssetClass ac)
Definition: minsky.cc:567
cut
Definition: minsky.tcl:756
GroupPtr model
Definition: minsky.h:255
ItemPtr item
item or wire obtained by get*At() calls
Definition: canvas.h:175
swapAssetClassid x y
Definition: godley.tcl:172