Minsky
canvas.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2017
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 #undef CLASSDESC_ARITIES
21 #define CLASSDESC_ARITIES 0x3F
22 #include "minsky.h"
23 #include "geometry.h"
24 #include "canvas.h"
25 #include "cairoItems.h"
26 #include "ravelWrap.h"
27 #include <cairo_base.h>
28 
29 #include "canvas.rcd"
30 #include "canvas.xcd"
31 #include "eventInterface.rcd"
32 #include "nobble.h"
33 #include "minsky_epilogue.h"
34 using namespace std;
35 using namespace ecolab::cairo;
36 using namespace minsky;
37 
38 namespace minsky
39 {
40  void Canvas::controlMouseDown(float x, float y)
41  {
42  mouseDownCommon(x,y);
43  if (itemFocus && itemFocus->group.lock() == model)
44  selection.toggleItemMembership(itemFocus);
45  }
46 
47  void Canvas::mouseDown(float x, float y)
48  {
49  mouseDownCommon(x,y);
50  if (!itemFocus || !selection.contains(itemFocus))
51  selection.clear(); // clicking outside a selection clears it
52  }
53 
54 
55  void Canvas::mouseDownCommon(float x, float y)
56  {
57  wireFocus.reset();
58  // firstly, see if the user is selecting an item
59  if ((itemFocus=itemAt(x,y)))
60  {
61  clickType=itemFocus->clickType(x,y);
62  switch (clickType)
63  {
64  case ClickType::onPort:
65  // items all have their output port first, if they have an output port at all.
66  if ((fromPort=itemFocus->closestOutPort(x,y)))
67  {
68  termX=x;
69  termY=y;
70  itemFocus.reset();
71  }
72  break;
73  case ClickType::onItem:
74  moveOffsX=x-itemFocus->x();
75  moveOffsY=y-itemFocus->y();
76  break;
77  case ClickType::outside:
78  itemFocus.reset();
79  if (lassoMode==LassoMode::none)
80  lassoMode=LassoMode::lasso;
81  break;
82  case ClickType::inItem:
83  itemFocus->onMouseDown(x,y);
84  break;
85  case ClickType::onResize:
86  lassoMode=LassoMode::itemResize;
87  // set x0,y0 to the opposite corner of (x,y)
88  lasso.x0 = x>itemFocus->x()? itemFocus->left(): itemFocus->right();
89  lasso.y0 = y>itemFocus->y()? itemFocus->top(): itemFocus->bottom();
90  lasso.x1=x;
91  lasso.y1=y;
92  item=itemFocus;
93  break;
94  case ClickType::legendMove: case ClickType::legendResize:
95  if (auto p=itemFocus->plotWidgetCast())
96  p->mouseDown(x,y);
97  break;
98  }
99  }
100  else
101  {
102  wireFocus=model->findAny(&Group::wires,
103  [&](const WirePtr& i){return i->near(x,y);});
104  if (wireFocus)
105  handleSelected=wireFocus->nearestHandle(x,y);
106  else
107  if (lassoMode==LassoMode::none)
108  lassoMode=LassoMode::lasso;
109  }
110 
111  if (lassoMode==LassoMode::lasso)
112  {
113  lasso.x0=x;
114  lasso.y0=y;
115  }
116  }
117 
118  shared_ptr<Port> Canvas::closestInPort(float x, float y) const
119  {
120  shared_ptr<Port> closestPort;
121  auto minD=numeric_limits<float>::max();
122  model->recursiveDo(&GroupItems::items,
123  [&](const Items&, Items::const_iterator i)
124  {
125  if ((*i)->group.lock()->displayContents())
126  for (size_t pi=0; pi<(*i)->portsSize(); ++pi)
127  {
128  auto p=(*i)->ports(pi).lock();
129  const float d=sqr(p->x()-x)+sqr(p->y()-y);
130  if (d<minD)
131  {
132  minD=d;
133  closestPort=p;
134  }
135  }
136  return false;
137  });
138  return closestPort;
139  }
140 
141  void Canvas::mouseUp(float x, float y)
142  {
143  mouseMove(x,y);
144 
145  if (itemFocus && clickType==ClickType::inItem)
146  {
147  itemFocus->onMouseUp(x,y);
148  itemFocus.reset(); // prevent spurious mousemove events being processed
149  minsky().requestReset();
150  }
151  if (fromPort.get())
152  {
153  if (auto to=closestInPort(x,y)) {
154  model->addWire(static_cast<shared_ptr<Port>&>(fromPort),to);
155 
156  // populate the destination tooltip if a Ravel
157  if (to->item().tooltip().empty() && to->item().ravelCast())
158  if (auto v=fromPort->item().variableCast())
159  to->item().tooltip(v->name());
160 
161  minsky().requestReset();
162  }
163  fromPort.reset();
164  }
165 
166  if (wireFocus)
167  wireFocus->editHandle(handleSelected,x,y);
168 
169  switch (lassoMode)
170  {
171  case LassoMode::lasso:
172  select({lasso.x0,lasso.y0,x,y});
173  requestRedraw();
174  break;
175  case LassoMode::itemResize:
176  if (item)
177  {
178  item->resize(lasso);
179  requestRedraw();
180  }
181  break;
182  default: break;
183  }
184  lassoMode=LassoMode::none;
185 
186 
187  itemIndicator.reset();
188  rotatingItem=false;
189  itemFocus.reset();
190  wireFocus.reset();
191  }
192 
193  void Canvas::mouseMoveOnItem(float x, float y)
194  {
195  updateRegion=LassoBox(itemFocus->x(),itemFocus->y(),x,y);
196  // move item relatively to avoid accidental moves on double click
197  if (selection.empty() || !selection.contains(itemFocus))
198  itemFocus->moveTo(x-moveOffsX, y-moveOffsY);
199  else
200  {
201  // move the whole selection
202  auto deltaX=x-moveOffsX-itemFocus->x(), deltaY=y-moveOffsY-itemFocus->y();
203  for (auto& i: selection.items)
204  i->moveTo(i->x()+deltaX, i->y()+deltaY);
205  for (auto& i: selection.groups)
206  i->moveTo(i->x()+deltaX, i->y()+deltaY);
207  }
208  requestRedraw();
209  // check if the move has moved outside or into a group
210  if (auto g=itemFocus->group.lock())
211  if (g==model || !g->contains(itemFocus->x(),itemFocus->y()))
212  {
213  if (auto toGroup=model->minimalEnclosingGroup
214  (itemFocus->x(),itemFocus->y(),itemFocus->x(),itemFocus->y(),itemFocus.get()))
215  {
216  if (g.get()==toGroup) return;
217  // prevent moving a group inside itself
218  if (auto g=dynamic_cast<Group*>(itemFocus.get()))
219  if (g->higher(*toGroup))
220  return;
221  selection.clear(); // prevent old wires from being held onto
222  toGroup->addItem(itemFocus);
223  toGroup->splitBoundaryCrossingWires();
224  g->splitBoundaryCrossingWires();
225  }
226  else if (g!=model)
227  {
228  selection.clear(); // prevent old wires from being held onto
229  model->addItem(itemFocus);
230  model->splitBoundaryCrossingWires();
231  g->splitBoundaryCrossingWires();
232  }
233  }
234  if (auto g=itemFocus->group.lock())
235  if (g!=model)
236  g->checkAddIORegion(itemFocus);
237  }
238 
239  void Canvas::mouseMove(float x, float y)
240  try
241  {
242  if (rotatingItem && item)
243  {
244  item->rotate(Point{x,y},rotateOrigin);
245  requestRedraw();
246  return;
247  }
248 
249  if (itemFocus)
250  {
251  switch (clickType)
252  {
253  case ClickType::onItem:
254  mouseMoveOnItem(x,y);
255  return;
256  case ClickType::inItem:
257  if (itemFocus->onMouseMotion(x,y))
258  requestRedraw();
259  return;
260  case ClickType::legendMove: case ClickType::legendResize:
261  if (auto p=itemFocus->plotWidgetCast())
262  {
263  p->mouseMove(x,y);
264  requestRedraw();
265  }
266  return;
267  case ClickType::onResize:
268  lasso.x1=x;
269  lasso.y1=y;
270  requestRedraw();
271  break;
272  default:
273  break;
274  }
275  }
276  else if (fromPort.get())
277  {
278  termX=x;
279  termY=y;
280  requestRedraw();
281  }
282  else if (wireFocus)
283  {
284  wireFocus->editHandle(handleSelected,x,y);
285  requestRedraw();
286  }
287  else if (lassoMode==LassoMode::lasso || (lassoMode==LassoMode::itemResize && item.get()))
288  {
289  lasso.x1=x;
290  lasso.y1=y;
291  requestRedraw();
292  }
293  else
294  {
295  auto setFlagAndRequestRedraw=[&](bool& flag, bool cond) {
296  if (flag==cond) return;
297  flag=cond;
298  requestRedraw();
299  };
300  // set mouse focus to display ports etc.
301  bool mouseFocusSet=false; // ensure only one item is focused by mouse.
302  model->recursiveDo(&Group::items, [&](Items&,Items::iterator& i)
303  {
304  (*i)->disableDelayedTooltip();
305  // with coupled integration variables, we
306  // do not want to set mousefocus, as this
307  // draws unnecessary port circles on the
308  // variable
309  if (!(*i)->visible() &&
310  dynamic_cast<Variable<VariableBase::integral>*>(i->get()))
311  (*i)->mouseFocus=false;
312  else
313  {
314  auto ct=(*i)->clickType(x,y);
315  setFlagAndRequestRedraw((*i)->mouseFocus, !mouseFocusSet && ct!=ClickType::outside);
316  if ((*i)->mouseFocus) mouseFocusSet=true;
317  setFlagAndRequestRedraw((*i)->onResizeHandles, ct==ClickType::onResize);
318  setFlagAndRequestRedraw((*i)->onBorder, ct==ClickType::onItem);
319  if (ct==ClickType::outside)
320  (*i)->onMouseLeave();
321  else if ((*i)->onMouseOver(x,y))
322  requestRedraw();
323  }
324  return false;
325  });
326  model->recursiveDo(&Group::groups, [&](Groups&,Groups::iterator& i)
327  {
328  auto ct=(*i)->clickType(x,y);
329  setFlagAndRequestRedraw((*i)->mouseFocus, !mouseFocusSet && ct!=ClickType::outside);
330  if ((*i)->mouseFocus) mouseFocusSet=true;
331  const bool onResize = ct==ClickType::onResize;
332  if (onResize!=(*i)->onResizeHandles)
333  {
334  (*i)->onResizeHandles=onResize;
335  requestRedraw();
336  }
337  return false;
338  });
339  model->recursiveDo(&Group::wires, [&](Wires&,Wires::iterator& i)
340  {
341  const bool mf=(*i)->near(x,y);
342  if (mf!=(*i)->mouseFocus)
343  {
344  (*i)->mouseFocus=mf;
345  requestRedraw();
346  }
347  return false;
348  });
349  }
350  if (minsky().reset_flag())
351  minsky().resetAt=std::chrono::system_clock::now()+std::chrono::milliseconds(1500); // postpone reset whilst mousing
352  }
353  catch (...) {/* absorb any exceptions, as they're not useful here */}
354 
355  bool Canvas::keyPress(const KeyPressArgs& args)
356  {
357  if (auto item=itemAt(args.x,args.y))
358  if (item->onKeyPress(args.keySym, args.utf8, args.state))
359  {
360  minsky().markEdited(); // keyPresses don't set the dirty flag by default
361  requestRedraw();
362  return true;
363  }
364  return false;
365  }
366 
367 
368  void Canvas::displayDelayedTooltip(float x, float y)
369  {
370  if (auto item=itemAt(x,y))
371  {
372  item->displayDelayedTooltip(x,y);
373  requestRedraw();
374  }
375  }
376 
377  void Canvas::select(const LassoBox& lasso)
378  {
379  selection.clear();
380 
381  auto topLevel = model->minimalEnclosingGroup(lasso.x0,lasso.y0,lasso.x1,lasso.y1);
382 
383  if (!topLevel) topLevel=&*model;
384 
385  for (auto& i: topLevel->items)
386  if (i->visible() && lasso.intersects(*i))
387  selection.ensureItemInserted(i);
388 
389  for (auto& i: topLevel->groups)
390  if (i->visible() && lasso.intersects(*i))
391  selection.ensureGroupInserted(i);
392 
393  // X11 behaviour, selecting automatically copies.
394  minsky().copy();
395  }
396 
397  int Canvas::ravelsSelected() const
398  {
399  int ravelsSelected = 0;
400  for (auto& i: selection.items) {
401  if (dynamic_pointer_cast<Ravel>(i))
402  {
403  ravelsSelected++;
404  }
405  }
406  return ravelsSelected;
407  }
408 
409  ItemPtr Canvas::itemAt(float x, float y)
410  {
411  // Fix for library dependency problem with items during Travis build
412  ItemPtr item;
413  auto minD=numeric_limits<float>::max();
414  model->recursiveDo(&GroupItems::items,
415  [&](const Items&, Items::const_iterator i)
416  {
417  const float d=sqr((*i)->x()-x)+sqr((*i)->y()-y);
418  if (d<minD && (*i)->visible() && (*i)->contains(x,y))
419  {
420  minD=d;
421  item=*i;
422  }
423  return false;
424  });
425  if (!item)
426  item=model->findAny
427  (&Group::groups, [&](const GroupPtr& i)
428  {return i->visible() && i->clickType(x,y)!=ClickType::outside;});
429  return item;
430  }
431 
432  bool Canvas::getWireAt(float x, float y)
433  {
434  wire=model->findAny(&Group::wires,
435  [&](const WirePtr& i){return i->near(x,y);});
436  return wire.get();
437  }
438 
439  void Canvas::groupSelection()
440  {
441  const GroupPtr r=model->addGroup(new Group);
442  for (auto& i: selection.items)
443  r->addItem(i);
444  for (auto& i: selection.groups)
445  r->addItem(i);
446  r->splitBoundaryCrossingWires();
447  r->resizeOnContents();
448  }
449 
450  void Canvas::lockRavelsInSelection()
451  {
452  vector<shared_ptr<Ravel> > ravelsToLock;
453  shared_ptr<RavelLockGroup> lockGroup;
454  bool conflictingLockGroups=false;
455  for (auto& i: selection.items)
456  if (auto r=dynamic_pointer_cast<Ravel>(i))
457  {
458  ravelsToLock.push_back(r);
459  if (!lockGroup)
460  lockGroup=r->lockGroup;
461  if (lockGroup!=r->lockGroup)
462  conflictingLockGroups=true;
463  }
464  if (ravelsToLock.size()<2)
465  return;
466  if (!lockGroup || conflictingLockGroups)
467  lockGroup.reset(new RavelLockGroup);
468  for (auto& r: ravelsToLock)
469  {
470  lockGroup->addRavel(r);
471  r->leaveLockGroup();
472  r->lockGroup=lockGroup;
473  }
474  if (lockGroup) lockGroup->initialBroadcast();
475  selection.clear();
476  }
477 
478  void Canvas::unlockRavelsInSelection()
479  {
480  for (auto& i: selection.items)
481  if (auto r=dynamic_cast<Ravel*>(i.get()))
482  r->leaveLockGroup();
483  }
484 
485  void Canvas::deleteItem()
486  {
487  if (item)
488  {
489  model->deleteItem(*item);
490  minsky().requestReset();
491  }
492  }
493 
494  void Canvas::deleteWire()
495  {
496  if (wire)
497  {
498  model->removeWire(*wire);
499  wire.reset();
500  minsky().requestReset();
501  }
502  }
503 
504  // For ticket 1092. Reinstate delete handle user interaction
505  void Canvas::delHandle(float x, float y)
506  {
507  wireFocus=model->findAny(&Group::wires,
508  [&](const WirePtr& i){return i->near(x,y);});
509  if (wireFocus)
510  {
511  wireFocus->deleteHandle(x,y);
512  wireFocus.reset(); // prevent accidental moves of handle
513  }
514  requestRedraw();
515  }
516 
517  void Canvas::removeItemFromItsGroup()
518  {
519  if (item)
520  if (auto g=item->group.lock())
521  {
522  if (auto parent=g->group.lock())
523  {
524  itemFocus=parent->addItem(item);
525  if (auto v=itemFocus->variableCast())
526  v->controller.reset();
527  g->splitBoundaryCrossingWires();
528  }
529  // else item already at toplevel
530  }
531  }
532 
533  void Canvas::selectAllVariables()
534  {
535  selection.clear();
536  auto var=item->variableCast();
537  if (!var)
538  if (auto i=dynamic_cast<IntOp*>(item.get()))
539  var=i->intVar.get();
540  if (var)
541  {
542  model->recursiveDo
543  (&GroupItems::items, [&](const Items&,Items::const_iterator i)
544  {
545  if (auto v=(*i)->variableCast())
546  if (v->valueId()==var->valueId())
547  {
548  selection.ensureItemInserted(*i);
549  }
550  return false;
551  });
552  }
553  }
554 
555  void Canvas::renameAllInstances(const string& newName)
556  {
557  auto var=item->variableCast();
558  if (!var)
559  if (auto i=dynamic_cast<IntOp*>(item.get()))
560  var=i->intVar.get();
561  if (var)
562  {
563  // cache name and valueId for later use as var gets invalidated in the recursiveDo
564  auto valueId=var->valueId();
565  model->renameAllInstances(valueId, newName);
566  }
567  }
568 
569  void Canvas::ungroupItem()
570  {
571  if (auto g=dynamic_cast<Group*>(item.get()))
572  {
573  if (auto p=g->group.lock())
574  {
575  if (!g->empty()) // minskly crashes if group empty and ungrouped subsequently. for ticket 1243
576  {
577  // stash values of parameters in copied group, as they are reset for some unknown reason later on. for tickets 1243/1258
578  map<string,string> existingParms;
579  for (auto& i: g->items) {
580  auto v=i->variableCast();
581  if (v && v->type()==VariableType::parameter)
582  existingParms.emplace(v->valueId(),v->init());
583  }
584 
585  // blow up containe items so they appear in same relative locationsat parent scalefactor
586  auto scaleFactor=1/g->relZoom;
587  for (auto& i: g->items)
588  i->moveTo((i->x()-g->x())*scaleFactor+g->x(), (i->y()-g->y())*scaleFactor+g->y());
589  for (auto& i: g->groups)
590  i->moveTo((i->x()-g->x())*scaleFactor+g->x(), (i->y()-g->y())*scaleFactor+g->y());
591  p->moveContents(*g);
592  deleteItem();
593 
594  existingParms.clear();
595 
596  } else deleteItem();
597  }
598 
599  // else item is toplevel which can't be ungrouped
600  }
601  }
602 
603  void Canvas::renameItem(const std::string& newName)
604  {
605  if (!item) return;
606  if (auto var=item->variableCast())
607  {
608  var->name(newName);
609  // check stock variables are defined, convert to flow if not
610  if (var->isStock() && !cminsky().definingVar(var->valueId()))
611  var->retype(VariableType::flow);
612  }
613  }
614 
615  void Canvas::copyItem()
616  {
617  if (item)
618  {
619  ItemPtr newItem;
620  // cannot duplicate an integral, just its variable
621  if (auto intop=dynamic_cast<IntOp*>(item.get()))
622  newItem.reset(intop->intVar->clone());
623  else if (auto group=dynamic_cast<Group*>(item.get()))
624  newItem=group->copy();
625  else
626  {
627  if (item->ioVar())
628  if (auto v=item->variableCast())
629  if (v->rawName()[0]!=':')
630  try
631  {
632  minsky().pushHistory(); // save current state in case of failure
633  // attempt to make variable outer scoped. For #1100
634  minsky().canvas.renameAllInstances(":"+v->rawName());
635  }
636  catch (...)
637  {
638  minsky().undo(); // back out of change
639  throw;
640  }
641 
642  newItem.reset(item->clone());
643  // if copied from a Godley table or I/O var, set orientation to default
644  if (auto v=item->variableCast())
645  if (v->controller.lock())
646  newItem->rotation(defaultRotation);
647 
648  }
649  setItemFocus(model->addItem(newItem));
650  model->normaliseGroupRefs(model);
651  }
652  }
653 
654  void Canvas::zoomToFit()
655  {
656  if (frameArgs().parentWindowId.empty()) return; // no window to fit to, so do nothing
657  // recompute all bounding boxes - why is this needed?
658  for (auto& i: model->items) i->updateBoundingBox();
659 
660  double x0,x1,y0,y1;
661  model->contentBounds(x0,y0,x1,y1);
662  float inOffset=0, outOffset=0;
663  const bool notFlipped=!flipped(model->rotation());
664  const float flip=notFlipped? 1: -1;
665 
666  // we need to move the io variables
667  for (auto& v: model->inVariables)
668  inOffset=std::max(inOffset, v->width());
669  for (auto& v: model->outVariables)
670  outOffset=std::max(outOffset, v->width());
671 
672  const float zoomFactor=std::min(frameArgs().childWidth/(x1-x0+inOffset+outOffset),
673  frameArgs().childHeight/(y1-y0));
674 
675  model->zoom(model->x(),model->y(),zoomFactor);
676 
677 
678  model->contentBounds(x0,y0,x1,y1);
679  float ioOffset=notFlipped? x0: x1;
680 
681  // we need to move the io variables
682  for (auto& v: model->inVariables)
683  v->moveTo(ioOffset-flip*v->width(),v->y());
684  ioOffset=notFlipped? x1: x0;
685  for (auto& v: model->outVariables)
686  v->moveTo(ioOffset+flip*v->width(),v->y());
687 
688  recentre();
689  requestRedraw();
690  }
691 
692  void Canvas::openGroupInCanvas(const ItemPtr& item)
693  {
694  if (auto g=dynamic_pointer_cast<Group>(item))
695  {
696  if (auto parent=model->group.lock())
697  model->setZoom(parent->zoomFactor());
698  model=g;
699  zoomToFit();
700 
702  this->item.reset();
703  itemFocus.reset();
704  wire.reset();
705  selection.clear();
706  fromPort.reset();
707  requestRedraw();
708  }
709  }
710 
711  void Canvas::copyVars(const std::vector<VariablePtr>& v)
712  {
713  float maxWidth=0, totalHeight=0;
714  vector<float> widths, heights;
715  // Throw error if no stock/flow vars on Godley icon. For ticket 1039
716  if (!v.empty()) {
717  selection.clear();
718  for (auto& i: v)
719  {
720  const RenderVariable rv(*i);
721  widths.push_back(rv.width());
722  heights.push_back(rv.height());
723  maxWidth=max(maxWidth, widths.back());
724  totalHeight+=heights.back();
725  }
726  float y=v[0]->y() - totalHeight;
727  for (size_t i=0; i<v.size(); ++i)
728  {
729  // Stock and flow variables on Godley icons should not be copied as groups. For ticket 1039
730  const ItemPtr ni(v[i]->clone());
731  (ni->variableCast())->rotation(0);
732  ni->moveTo(v[0]->x()+maxWidth-v[i]->zoomFactor()*widths[i],
733  y+heights[i]);
734  // variables need to refer to outer scope
735  if ((ni->variableCast())->name()[0]!=':')
736  (ni->variableCast())->name(':'+(ni->variableCast())->name());
737  y+=2*v[i]->zoomFactor()*heights[i];
738  selection.insertItem(model->addItem(ni));
739  }
740  // Item focus put on one of the copied vars that are still in selection. For ticket 1039
741  if (!selection.empty()) setItemFocus(selection.items[0]);
742  else setItemFocus(nullptr);
743  } else throw error("no flow or stock variables to copy");
744  }
745 
746  void Canvas::zoomToDisplay()
747  {
748  if (auto g=dynamic_cast<Group*>(item.get()))
749  {
750  model->zoom(g->x(),g->y(),1.1/(g->relZoom*g->zoomFactor()));
751  minsky().resetScroll();
752  }
753  }
754 
755  bool Canvas::selectVar(float x, float y)
756  {
757  if (item)
758  {
759  if (auto v=item->select(x,y))
760  {
761  item=v;
762  return true;
763  }
764  }
765  return false;
766  }
767 
768  bool Canvas::findVariableDefinition()
769  {
770  if (auto iv=item->variableCast())
771  {
772  if (iv->type()==VariableType::constant ||
773  iv->type()==VariableType::parameter || iv->inputWired())
774  return (itemIndicator=item).get();
775 
776  itemIndicator=model->findAny
777  (&GroupItems::items, [&](const ItemPtr& i) {
778  if (auto v=i->variableCast())
779  return v->inputWired() && v->valueId()==iv->valueId();
780  if (auto g=dynamic_cast<GodleyIcon*>(i.get()))
781  for (auto& v: g->stockVars())
782  {
783  if (v->valueId()==iv->valueId())
784  return true;
785  }
786  else if (auto o=dynamic_cast<IntOp*>(i.get()))
787  return o->intVar->valueId()==iv->valueId();
788  return false;
789  });
790  return itemIndicator.get();
791  }
792  return false;
793  }
794 
795  bool Canvas::redraw(int x0, int y0, int width, int height)
796  {
797  updateRegion.x0=x0;
798  updateRegion.y0=y0;
799  updateRegion.x1=x0+width;
800  updateRegion.y1=y0+height;
801  // redraw of canvas may throw if called during a reset operation
802  try
803  {
804  return redrawUpdateRegion();
805  }
806 #ifndef NDEBUG
807  catch (std::exception& ex)
808  {
809  cerr << ex.what() << endl;
810  }
811 #endif
812  catch (...)
813  {
814  // consume exception and try redrawing
815  // this leads to an endless loop...
816  //requestRedraw();
817  }
818  return false;
819  }
820 
821  bool Canvas::redraw()
822  {
823  // nb using maxint here doesn't seem to work
824  return redraw(-1e9,-1e9,2e9,2e9);
825  }
826 
827  bool Canvas::redrawUpdateRegion()
828  {
829  bool didDrawSomething = false;
830  if (!surface().get()) {
831  return didDrawSomething;
832  }
833  m_redrawRequested=false;
834  auto cairo=surface()->cairo();
835  const CairoSave cs(cairo);
836  cairo_rectangle(cairo,updateRegion.x0,updateRegion.y0,updateRegion.x1-updateRegion.x0,updateRegion.y1-updateRegion.y0);
837  cairo_clip(cairo);
838  cairo_set_line_width(cairo, 1);
839  // items
840  model->recursiveDo
841  (&GroupItems::items, [&](const Items&, Items::const_iterator i)
842  {
843  auto& it=**i;
844  if (it.visible() && updateRegion.intersects(it))
845  {
846  didDrawSomething = true;
847  const CairoSave cs(cairo);
848  cairo_identity_matrix(cairo);
849  cairo_translate(cairo,it.x(), it.y());
850  try
851  {
852  it.draw(cairo);
853  }
854  catch (const std::exception& ex)
855  {
856  cerr << ex.what() << endl;
857  }
858  }
859  return false;
860  });
861 
862  // groups
863  model->recursiveDo
864  (&GroupItems::groups, [&](const Groups&, Groups::const_iterator i)
865  {
866  auto& it=**i;
867  if (it.visible() && updateRegion.intersects(it))
868  {
869  didDrawSomething = true;
870  const CairoSave cs(cairo);
871  cairo_identity_matrix(cairo);
872  cairo_translate(cairo,it.x(), it.y());
873  it.draw(cairo);
874  }
875  return false;
876  });
877 
878  // draw all wires - wires will go over the top of any icons. TODO
879  // introduce an ordering concept if needed
880  model->recursiveDo
881  (&GroupItems::wires, [&](const Wires&, Wires::const_iterator i)
882  {
883  const Wire& w=**i;
884  if (w.visible()) {
885  w.draw(cairo);
886  }
887  return false;
888  });
889 
890  if (fromPort.get()) // we're in process of creating a wire
891  {
892  didDrawSomething = true;
893  cairo_move_to(cairo,fromPort->x(),fromPort->y());
894  cairo_line_to(cairo,termX,termY);
895  cairo_stroke(cairo);
896  // draw arrow
897  const CairoSave cs(cairo);
898  cairo_translate(cairo, termX,termY);
899  cairo_rotate(cairo,atan2(termY-fromPort->y(), termX-fromPort->x()));
900  cairo_move_to(cairo,0,0);
901  cairo_line_to(cairo,-5,-3);
902  cairo_line_to(cairo,-3,0);
903  cairo_line_to(cairo,-5,3);
904  cairo_close_path(cairo);
905  cairo_fill(cairo);
906  }
907 
908  if (lassoMode!=LassoMode::none)
909  {
910  didDrawSomething = true;
911  cairo_rectangle(cairo,lasso.x0,lasso.y0,lasso.x1-lasso.x0,lasso.y1-lasso.y0);
912  cairo_stroke(cairo);
913  }
914 
915  if (itemIndicator) // draw a red circle to indicate an error or other marker
916  {
917  const CairoSave cs(surface()->cairo());
918  cairo_set_source_rgb(surface()->cairo(),1,0,0);
919  cairo_arc(surface()->cairo(),itemIndicator->x(),itemIndicator->y(),15,0,2*M_PI);
920  cairo_stroke(surface()->cairo());
921  }
922 
923  surface()->blit();
924  return didDrawSomething;
925  }
926 
927  void Canvas::recentre()
928  {
929  const SurfacePtr tmp(surface());
930  surface().reset(new Surface(cairo_recording_surface_create(CAIRO_CONTENT_COLOR,nullptr)));
931  redraw();
932  model->moveTo(model->x()-surface()->left(), model->y()-surface()->top());
933  surface()=tmp;
934  requestRedraw();
935  }
936 
937  void Canvas::reportDrawTime(double t)
938  {
939  // ensure screen refresh time is less than a third
940  minsky().maxWaitMS=(t>0.03)? 3000*t: 100.0;
941  }
942 
943  void Canvas::renderToPNGCropped(const std::string& filename, const ZoomCrop& z)
944  {
945  model->zoom(model->x(),model->y(),z.zoom);
946  model->moveTo(model->x()-z.left,model->y()-z.top);
947  cairo::SurfacePtr tmp(new cairo::Surface(cairo_image_surface_create(CAIRO_FORMAT_ARGB32,z.width,z.height)));
948  tmp.swap(surface());
949  redraw(0,0,z.width,z.height);
950  tmp.swap(surface());
951  cairo_surface_write_to_png(tmp->surface(),filename.c_str());
952  model->moveTo(model->x()+z.left,model->y()+z.top);
953  model->zoom(model->x(),model->y(),1/z.zoom);
954  }
955 
956 
957 
958  void Canvas::applyDefaultPlotOptions() {
959  if (auto p=item->plotWidgetCast()) {
960  // stash titles to restore later
961  const string title(p->title), xlabel(p->xlabel()),
962  ylabel(p->ylabel()), y1label(p->y1label());
963  defaultPlotOptions.applyPlotOptions(*p);
964  p->title=title, p->xlabel(xlabel),
965  p->ylabel(ylabel), p->y1label(y1label);
966  }
967  }
968 
969  void Canvas::setItemFromItemFocus()
970  {
971  item = itemFocus;
972  }
973 }
974 
#define M_PI
some useful geometry types, defined from boost::geometry
Definition: geometry.h:29
std::vector< WirePtr > Wires
Definition: wire.h:103
void copy() const
copy items in current selection into clipboard
Definition: minsky.cc:191
bool intersects(const Item &item) const
returns whether item&#39;s icon overlaps the lasso
Definition: lasso.h:36
void requestReset()
Definition: minsky.cc:467
bool pushHistory()
push current model state onto history if it differs from previous
Definition: minsky.cc:1265
minsky::Minsky minsky
Definition: pyminsky.cc:28
represents rectangular region of a lasso operation
Definition: lasso.h:28
STL namespace.
std::shared_ptr< Item > ItemPtr
Definition: item.h:57
std::shared_ptr< Wire > WirePtr
Definition: wire.h:102
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
Definition: valueId.cc:75
void markEdited()
indicate model has been changed since last saved
Definition: minsky.h:173
bool flipped(double rotation)
returns if the angle (in degrees) is in the second or third quadrant
Definition: geometry.h:102
bool reset_flag() const
true if reset needs to be called prior to numerical integration
Definition: minsky.h:171
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
long undo(int changes=1)
restore model to state changes ago
Definition: minsky.cc:1401
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
std::vector< ItemPtr > Items
Definition: item.h:366
Canvas canvas
Definition: minsky.h:256
virtual void bookmarkRefresh()
refresh the bookmark menu after changes
Definition: minsky.h:450
std::vector< GroupPtr > Groups
Definition: group.h:54
boost::geometry::model::d2::point_xy< float > Point
Definition: geometry.h:34
VariablePtr definingVar(const std::string &valueId) const
returns reference to variable defining (ie input wired) for valueId
Definition: minsky.cc:205
const Minsky & cminsky()
const version to help in const correctness
Definition: minsky.h:549
double minD(const Item &item1, const Item &item2)
Definition: autoLayout.cc:47
T sqr(T x)
Definition: geometry.h:36
float x0
Definition: lasso.h:30
virtual void resetScroll()
reset main window scroll bars after model has been panned
Definition: minsky.h:453
float y1
Definition: lasso.h:30
float x1
Definition: lasso.h:30
std::shared_ptr< Group > GroupPtr
Definition: port.h:32
float y0
Definition: lasso.h:30
Definition: group.tcl:84
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::EventInterface)
int maxWaitMS
maximum wait in millisecond between redrawing canvas during simulation
Definition: minsky.h:354
float height() const
half height of unrotated image
Definition: cairoItems.h:47
float width() const
half width of unrotated image
Definition: cairoItems.h:45