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