20 #undef CLASSDESC_ARITIES 21 #define CLASSDESC_ARITIES 0x3F 27 #include <cairo_base.h> 31 #include "eventInterface.rcd" 34 #include "renderNativeWindow.xcd" 42 void Canvas::controlMouseDown(
float x,
float y)
45 if (itemFocus && itemFocus->group.lock() == model)
46 selection.toggleItemMembership(itemFocus);
49 void Canvas::mouseDown(
float x,
float y)
52 if (!itemFocus || !selection.contains(itemFocus))
57 void Canvas::mouseDownCommon(
float x,
float y)
61 if ((itemFocus=itemAt(x,y)))
63 clickType=itemFocus->clickType(x,y);
66 case ClickType::onPort:
68 if ((fromPort=itemFocus->closestOutPort(x,y)))
75 case ClickType::onItem:
76 moveOffsX=x-itemFocus->x();
77 moveOffsY=y-itemFocus->y();
79 case ClickType::outside:
81 if (lassoMode==LassoMode::none)
82 lassoMode=LassoMode::lasso;
84 case ClickType::inItem:
85 itemFocus->onMouseDown(x,y);
87 case ClickType::onResize:
88 lassoMode=LassoMode::itemResize;
90 lasso.x0 = x>itemFocus->x()? itemFocus->left(): itemFocus->right();
91 lasso.y0 = y>itemFocus->y()? itemFocus->top(): itemFocus->bottom();
96 case ClickType::legendMove:
case ClickType::legendResize:
97 if (
auto p=itemFocus->plotWidgetCast())
104 wireFocus=model->findAny(&Group::wires,
105 [&](
const WirePtr& i){
return i->near(x,y);});
107 handleSelected=wireFocus->nearestHandle(x,y);
109 if (lassoMode==LassoMode::none)
110 lassoMode=LassoMode::lasso;
113 if (lassoMode==LassoMode::lasso)
120 shared_ptr<Port> Canvas::closestInPort(
float x,
float y)
const 122 shared_ptr<Port> closestPort;
123 auto minD=numeric_limits<float>::max();
124 model->recursiveDo(&GroupItems::items,
125 [&](
const Items&, Items::const_iterator i)
127 if ((*i)->group.lock()->displayContents())
128 for (
size_t pi=0; pi<(*i)->portsSize(); ++pi)
130 auto p=(*i)->ports(pi).lock();
131 const float d=
sqr(p->x()-x)+
sqr(p->y()-y);
143 void Canvas::mouseUp(
float x,
float y)
147 if (itemFocus && clickType==ClickType::inItem)
149 itemFocus->onMouseUp(x,y);
155 if (
auto to=closestInPort(x,y)) {
156 model->addWire(
static_cast<shared_ptr<Port>&
>(fromPort),to);
159 if (to->item().tooltip().empty() && to->item().ravelCast())
160 if (
auto v=fromPort->item().variableCast())
161 to->item().tooltip(v->name());
169 wireFocus->editHandle(handleSelected,x,y);
173 case LassoMode::lasso:
174 select({lasso.x0,lasso.y0,x,y});
177 case LassoMode::itemResize:
186 lassoMode=LassoMode::none;
189 itemIndicator.reset();
195 void Canvas::mouseMoveOnItem(
float x,
float y)
197 updateRegion=
LassoBox(itemFocus->x(),itemFocus->y(),x,y);
199 if (selection.empty() || !selection.contains(itemFocus))
200 itemFocus->moveTo(x-moveOffsX, y-moveOffsY);
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);
212 if (
auto g=itemFocus->group.lock())
213 if (g==model || !g->contains(itemFocus->x(),itemFocus->y()))
215 if (
auto toGroup=model->minimalEnclosingGroup
216 (itemFocus->x(),itemFocus->y(),itemFocus->x(),itemFocus->y(),itemFocus.get()))
218 if (g.get()==toGroup)
return;
220 if (
auto g=dynamic_cast<Group*>(itemFocus.get()))
221 if (g->higher(*toGroup))
224 toGroup->addItem(itemFocus);
225 toGroup->splitBoundaryCrossingWires();
226 g->splitBoundaryCrossingWires();
231 model->addItem(itemFocus);
232 model->splitBoundaryCrossingWires();
233 g->splitBoundaryCrossingWires();
236 if (
auto g=itemFocus->group.lock())
238 g->checkAddIORegion(itemFocus);
241 void Canvas::mouseMove(
float x,
float y)
244 if (rotatingItem && item)
246 item->rotate(
Point{x,y},rotateOrigin);
255 case ClickType::onItem:
256 mouseMoveOnItem(x,y);
258 case ClickType::inItem:
259 if (itemFocus->onMouseMotion(x,y))
262 case ClickType::legendMove:
case ClickType::legendResize:
263 if (
auto p=itemFocus->plotWidgetCast())
269 case ClickType::onResize:
278 else if (fromPort.get())
286 wireFocus->editHandle(handleSelected,x,y);
289 else if (lassoMode==LassoMode::lasso || (lassoMode==LassoMode::itemResize && item.get()))
297 auto setFlagAndRequestRedraw=[&](
bool& flag,
bool cond) {
298 if (flag==cond)
return;
303 bool mouseFocusSet=
false;
304 model->recursiveDo(&Group::items, [&](
Items&,Items::iterator& i)
306 (*i)->disableDelayedTooltip();
311 if (!(*i)->visible() &&
313 (*i)->mouseFocus=
false;
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))
328 model->recursiveDo(&Group::groups, [&](
Groups&,Groups::iterator& i)
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)
336 (*i)->onResizeHandles=onResize;
341 model->recursiveDo(&Group::wires, [&](
Wires&,Wires::iterator& i)
343 const bool mf=(*i)->near(x,y);
344 if (mf!=(*i)->mouseFocus)
353 minsky().resetAt=std::chrono::system_clock::now()+std::chrono::milliseconds(1500);
359 if (
auto item=itemAt(args.
x,args.
y))
370 void Canvas::displayDelayedTooltip(
float x,
float y)
372 if (
auto item=itemAt(x,y))
374 item->displayDelayedTooltip(x,y);
383 auto topLevel = model->minimalEnclosingGroup(lasso.
x0,lasso.
y0,lasso.
x1,lasso.
y1);
385 if (!topLevel) topLevel=&*model;
387 for (
auto& i: topLevel->items)
389 selection.ensureItemInserted(i);
391 for (
auto& i: topLevel->groups)
393 selection.ensureGroupInserted(i);
399 int Canvas::ravelsSelected()
const 401 int ravelsSelected = 0;
402 for (
auto& i: selection.items) {
403 if (dynamic_pointer_cast<Ravel>(i))
408 return ravelsSelected;
415 auto minD=numeric_limits<float>::max();
416 model->recursiveDo(&GroupItems::items,
417 [&](
const Items&, Items::const_iterator i)
419 const float d=
sqr((*i)->x()-x)+
sqr((*i)->y()-y);
420 if (d<
minD && (*i)->visible() && (*i)->contains(x,y))
429 (&Group::groups, [&](
const GroupPtr& i)
430 {
return i->visible() && i->clickType(x,y)!=ClickType::outside;});
434 bool Canvas::getWireAt(
float x,
float y)
436 wire=model->findAny(&Group::wires,
437 [&](
const WirePtr& i){
return i->near(x,y);});
441 void Canvas::groupSelection()
444 for (
auto& i: selection.items)
446 for (
auto& i: selection.groups)
448 r->splitBoundaryCrossingWires();
449 r->resizeOnContents();
452 void Canvas::lockRavelsInSelection()
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))
460 ravelsToLock.push_back(r);
462 lockGroup=r->lockGroup;
463 if (lockGroup!=r->lockGroup)
464 conflictingLockGroups=
true;
466 if (ravelsToLock.size()<2)
468 if (!lockGroup || conflictingLockGroups)
470 for (
auto& r: ravelsToLock)
472 lockGroup->addRavel(r);
474 r->lockGroup=lockGroup;
476 if (lockGroup) lockGroup->initialBroadcast();
480 void Canvas::unlockRavelsInSelection()
482 for (
auto& i: selection.items)
483 if (
auto r=dynamic_cast<Ravel*>(i.get()))
487 void Canvas::deleteItem()
491 model->deleteItem(*item);
496 void Canvas::deleteWire()
500 model->removeWire(*
wire);
507 void Canvas::delHandle(
float x,
float y)
509 wireFocus=model->findAny(&Group::wires,
510 [&](
const WirePtr& i){
return i->near(x,y);});
513 wireFocus->deleteHandle(x,y);
519 void Canvas::removeItemFromItsGroup()
522 if (
auto g=item->group.lock())
524 if (
auto parent=g->group.lock())
526 itemFocus=parent->addItem(item);
527 if (
auto v=itemFocus->variableCast())
528 v->controller.reset();
529 g->splitBoundaryCrossingWires();
535 void Canvas::selectAllVariables()
538 auto var=item->variableCast();
540 if (
auto i=dynamic_cast<IntOp*>(item.get()))
545 (&GroupItems::items, [&](
const Items&,Items::const_iterator i)
547 if (
auto v=(*i)->variableCast())
548 if (v->valueId()==
var->valueId())
550 selection.ensureItemInserted(*i);
557 void Canvas::renameAllInstances(
const string& newName)
559 auto var=item->variableCast();
561 if (
auto i=dynamic_cast<IntOp*>(item.get()))
567 model->renameAllInstances(
valueId, newName);
571 void Canvas::ungroupItem()
573 if (
auto g=dynamic_cast<Group*>(item.get()))
575 if (
auto p=g->group.lock())
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());
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());
596 existingParms.clear();
605 void Canvas::renameItem(
const std::string& newName)
608 if (
auto var=item->variableCast())
613 var->retype(VariableType::flow);
617 void Canvas::copyItem()
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();
630 if (
auto v=item->variableCast())
631 if (v->rawName()[0]!=
':')
644 newItem.reset(item->clone());
646 if (
auto v=item->variableCast())
647 if (v->controller.lock())
648 newItem->rotation(defaultRotation);
651 setItemFocus(model->addItem(newItem));
652 model->normaliseGroupRefs(model);
656 void Canvas::zoomToFit()
658 if (frameArgs().parentWindowId.empty())
return;
660 for (
auto& i: model->items) i->updateBoundingBox();
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;
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());
674 const float zoomFactor=std::min(frameArgs().childWidth/(x1-x0+inOffset+outOffset),
675 frameArgs().childHeight/(y1-y0));
677 model->zoom(model->x(),model->y(),zoomFactor);
680 model->contentBounds(x0,y0,x1,y1);
681 float ioOffset=notFlipped? x0: x1;
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());
694 void Canvas::openGroupInCanvas(
const ItemPtr& item)
696 if (
auto g=dynamic_pointer_cast<Group>(item))
698 if (
auto parent=model->group.lock())
699 model->setZoom(parent->zoomFactor());
713 void Canvas::copyVars(
const std::vector<VariablePtr>& v)
715 float maxWidth=0, totalHeight=0;
716 vector<float> widths, heights;
723 widths.push_back(rv.
width());
724 heights.push_back(rv.
height());
725 maxWidth=max(maxWidth, widths.back());
726 totalHeight+=heights.back();
728 float y=v[0]->y() - totalHeight;
729 for (
size_t i=0; i<v.size(); ++i)
732 const ItemPtr ni(v[i]->clone());
733 (ni->variableCast())->rotation(0);
734 ni->moveTo(v[0]->x()+maxWidth-v[i]->zoomFactor()*widths[i],
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));
743 if (!selection.empty()) setItemFocus(selection.items[0]);
744 else setItemFocus(
nullptr);
745 }
else throw error(
"no flow or stock variables to copy");
748 void Canvas::zoomToDisplay()
750 if (
auto g=dynamic_cast<Group*>(item.get()))
752 model->zoom(g->x(),g->y(),1.1/(g->relZoom*g->zoomFactor()));
757 bool Canvas::selectVar(
float x,
float y)
761 if (
auto v=item->select(x,y))
770 bool Canvas::findVariableDefinition()
772 if (
auto iv=item->variableCast())
774 if (iv->type()==VariableType::constant ||
775 iv->type()==VariableType::parameter || iv->inputWired())
776 return (itemIndicator=item).get();
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())
785 if (v->valueId()==iv->valueId())
788 else if (
auto o=dynamic_cast<IntOp*>(i.get()))
789 return o->intVar->valueId()==iv->valueId();
792 return itemIndicator.get();
797 bool Canvas::redraw(
int x0,
int y0,
int width,
int height)
801 updateRegion.x1=x0+width;
802 updateRegion.y1=y0+height;
806 return redrawUpdateRegion();
809 catch (std::exception& ex)
811 cerr << ex.what() << endl;
823 bool Canvas::redraw()
826 return redraw(-1e9,-1e9,2e9,2e9);
829 bool Canvas::redrawUpdateRegion()
831 bool didDrawSomething =
false;
832 if (!surface().
get()) {
833 return didDrawSomething;
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);
840 cairo_set_line_width(cairo, 1);
843 (&GroupItems::items, [&](
const Items&, Items::const_iterator i)
846 if (it.visible() && updateRegion.intersects(it))
848 didDrawSomething =
true;
849 const CairoSave cs(cairo);
850 cairo_identity_matrix(cairo);
851 cairo_translate(cairo,it.x(), it.y());
856 catch (
const std::exception& ex)
858 cerr << ex.what() << endl;
866 (&GroupItems::groups, [&](
const Groups&, Groups::const_iterator i)
869 if (it.visible() && updateRegion.intersects(it))
871 didDrawSomething =
true;
872 const CairoSave cs(cairo);
873 cairo_identity_matrix(cairo);
874 cairo_translate(cairo,it.x(), it.y());
883 (&GroupItems::wires, [&](
const Wires&, Wires::const_iterator i)
894 didDrawSomething =
true;
895 cairo_move_to(cairo,fromPort->x(),fromPort->y());
896 cairo_line_to(cairo,termX,termY);
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);
910 if (lassoMode!=LassoMode::none)
912 didDrawSomething =
true;
913 cairo_rectangle(cairo,lasso.x0,lasso.y0,lasso.x1-lasso.x0,lasso.y1-lasso.y0);
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());
926 return didDrawSomething;
929 void Canvas::recentre()
931 const SurfacePtr tmp(surface());
932 surface().reset(
new Surface(cairo_recording_surface_create(CAIRO_CONTENT_COLOR,
nullptr)));
934 model->moveTo(model->x()-surface()->left(), model->y()-surface()->top());
939 void Canvas::reportDrawTime(
double t)
945 void Canvas::renderToPNGCropped(
const std::string& filename,
const ZoomCrop& z)
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)));
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);
960 void Canvas::applyDefaultPlotOptions() {
961 if (
auto p=item->plotWidgetCast()) {
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);
971 void Canvas::setItemFromItemFocus()
#define M_PI
some useful geometry types, defined from boost::geometry
std::vector< WirePtr > Wires
void copy() const
copy items in current selection into clipboard
bool intersects(const Item &item) const
returns whether item's icon overlaps the lasso
bool pushHistory()
push current model state onto history if it differs from previous
represents rectangular region of a lasso operation
std::shared_ptr< Item > ItemPtr
std::shared_ptr< Wire > WirePtr
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
void markEdited()
indicate model has been changed since last saved
bool flipped(double rotation)
returns if the angle (in degrees) is in the second or third quadrant
bool reset_flag() const
true if reset needs to be called prior to numerical integration
void renameAllInstances(const std::string &newName)
rename all instances of variable as item to newName
long undo(int changes=1)
restore model to state changes ago
std::vector< ItemPtr > Items
virtual void bookmarkRefresh()
refresh the bookmark menu after changes
std::vector< GroupPtr > Groups
boost::geometry::model::d2::point_xy< float > Point
VariablePtr definingVar(const std::string &valueId) const
returns reference to variable defining (ie input wired) for valueId
const Minsky & cminsky()
const version to help in const correctness
double minD(const Item &item1, const Item &item2)
virtual void resetScroll()
reset main window scroll bars after model has been panned
std::shared_ptr< Group > GroupPtr
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::EventInterface)
int maxWaitMS
maximum wait in millisecond between redrawing canvas during simulation
float height() const
half height of unrotated image
float width() const
half width of unrotated image