20 #undef CLASSDESC_ARITIES 21 #define CLASSDESC_ARITIES 0x3F 27 #include <cairo_base.h> 31 #include "eventInterface.rcd" 40 void Canvas::controlMouseDown(
float x,
float y)
43 if (itemFocus && itemFocus->group.lock() == model)
44 selection.toggleItemMembership(itemFocus);
50 if (!itemFocus || !selection.contains(itemFocus))
55 void Canvas::mouseDownCommon(
float x,
float y)
59 if ((itemFocus=itemAt(x,y)))
61 clickType=itemFocus->clickType(x,y);
64 case ClickType::onPort:
66 if ((fromPort=itemFocus->closestOutPort(x,y)))
73 case ClickType::onItem:
74 moveOffsX=x-itemFocus->x();
75 moveOffsY=y-itemFocus->y();
77 case ClickType::outside:
79 if (lassoMode==LassoMode::none)
80 lassoMode=LassoMode::lasso;
82 case ClickType::inItem:
83 itemFocus->onMouseDown(x,y);
85 case ClickType::onResize:
86 lassoMode=LassoMode::itemResize;
88 lasso.x0 = x>itemFocus->x()? itemFocus->left(): itemFocus->right();
89 lasso.y0 = y>itemFocus->y()? itemFocus->top(): itemFocus->bottom();
94 case ClickType::legendMove:
case ClickType::legendResize:
95 if (
auto p=itemFocus->plotWidgetCast())
102 wireFocus=model->findAny(&Group::wires,
103 [&](
const WirePtr& i){
return i->near(x,y);});
105 handleSelected=wireFocus->nearestHandle(x,y);
107 if (lassoMode==LassoMode::none)
108 lassoMode=LassoMode::lasso;
111 if (lassoMode==LassoMode::lasso)
118 shared_ptr<Port> Canvas::closestInPort(
float x,
float y)
const 120 shared_ptr<Port> closestPort;
121 auto minD=numeric_limits<float>::max();
122 model->recursiveDo(&GroupItems::items,
123 [&](
const Items&, Items::const_iterator i)
125 if ((*i)->group.lock()->displayContents())
126 for (
size_t pi=0; pi<(*i)->portsSize(); ++pi)
128 auto p=(*i)->ports(pi).lock();
129 const float d=
sqr(p->x()-x)+
sqr(p->y()-y);
141 void Canvas::mouseUp(
float x,
float y)
145 if (itemFocus && clickType==ClickType::inItem)
147 itemFocus->onMouseUp(x,y);
153 if (
auto to=closestInPort(x,y)) {
154 model->addWire(
static_cast<shared_ptr<Port>&
>(fromPort),to);
157 if (to->item().tooltip().empty() && to->item().ravelCast())
158 if (
auto v=fromPort->item().variableCast())
159 to->item().tooltip(v->name());
167 wireFocus->editHandle(handleSelected,x,y);
171 case LassoMode::lasso:
172 select({lasso.x0,lasso.y0,x,y});
175 case LassoMode::itemResize:
184 lassoMode=LassoMode::none;
187 itemIndicator.reset();
193 void Canvas::mouseMoveOnItem(
float x,
float y)
195 updateRegion=
LassoBox(itemFocus->x(),itemFocus->y(),x,y);
197 if (selection.empty() || !selection.contains(itemFocus))
198 itemFocus->moveTo(x-moveOffsX, y-moveOffsY);
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);
210 if (
auto g=itemFocus->group.lock())
211 if (g==model || !g->contains(itemFocus->x(),itemFocus->y()))
213 if (
auto toGroup=model->minimalEnclosingGroup
214 (itemFocus->x(),itemFocus->y(),itemFocus->x(),itemFocus->y(),itemFocus.get()))
216 if (g.get()==toGroup)
return;
218 if (
auto g=dynamic_cast<Group*>(itemFocus.get()))
219 if (g->higher(*toGroup))
222 toGroup->addItem(itemFocus);
223 toGroup->splitBoundaryCrossingWires();
224 g->splitBoundaryCrossingWires();
229 model->addItem(itemFocus);
230 model->splitBoundaryCrossingWires();
231 g->splitBoundaryCrossingWires();
234 if (
auto g=itemFocus->group.lock())
236 g->checkAddIORegion(itemFocus);
239 void Canvas::mouseMove(
float x,
float y)
242 if (rotatingItem && item)
244 item->rotate(
Point{x,y},rotateOrigin);
253 case ClickType::onItem:
254 mouseMoveOnItem(x,y);
256 case ClickType::inItem:
257 if (itemFocus->onMouseMotion(x,y))
260 case ClickType::legendMove:
case ClickType::legendResize:
261 if (
auto p=itemFocus->plotWidgetCast())
267 case ClickType::onResize:
276 else if (fromPort.get())
284 wireFocus->editHandle(handleSelected,x,y);
287 else if (lassoMode==LassoMode::lasso || (lassoMode==LassoMode::itemResize && item.get()))
295 auto setFlagAndRequestRedraw=[&](
bool& flag,
bool cond) {
296 if (flag==cond)
return;
301 bool mouseFocusSet=
false;
302 model->recursiveDo(&Group::items, [&](
Items&,Items::iterator& i)
304 (*i)->disableDelayedTooltip();
309 if (!(*i)->visible() &&
311 (*i)->mouseFocus=
false;
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))
326 model->recursiveDo(&Group::groups, [&](
Groups&,Groups::iterator& i)
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)
334 (*i)->onResizeHandles=onResize;
339 model->recursiveDo(&Group::wires, [&](
Wires&,Wires::iterator& i)
341 const bool mf=(*i)->near(x,y);
342 if (mf!=(*i)->mouseFocus)
351 minsky().resetAt=std::chrono::system_clock::now()+std::chrono::milliseconds(1500);
357 if (
auto item=itemAt(args.
x,args.
y))
368 void Canvas::displayDelayedTooltip(
float x,
float y)
370 if (
auto item=itemAt(x,y))
372 item->displayDelayedTooltip(x,y);
381 auto topLevel = model->minimalEnclosingGroup(lasso.
x0,lasso.
y0,lasso.
x1,lasso.
y1);
383 if (!topLevel) topLevel=&*model;
385 for (
auto& i: topLevel->items)
387 selection.ensureItemInserted(i);
389 for (
auto& i: topLevel->groups)
391 selection.ensureGroupInserted(i);
397 int Canvas::ravelsSelected()
const 399 int ravelsSelected = 0;
400 for (
auto& i: selection.items) {
401 if (dynamic_pointer_cast<Ravel>(i))
406 return ravelsSelected;
413 auto minD=numeric_limits<float>::max();
414 model->recursiveDo(&GroupItems::items,
415 [&](
const Items&, Items::const_iterator i)
417 const float d=
sqr((*i)->x()-x)+
sqr((*i)->y()-y);
418 if (d<
minD && (*i)->visible() && (*i)->contains(x,y))
427 (&Group::groups, [&](
const GroupPtr& i)
428 {
return i->visible() && i->clickType(x,y)!=ClickType::outside;});
432 bool Canvas::getWireAt(
float x,
float y)
434 wire=model->findAny(&Group::wires,
435 [&](
const WirePtr& i){
return i->near(x,y);});
439 void Canvas::groupSelection()
442 for (
auto& i: selection.items)
444 for (
auto& i: selection.groups)
446 r->splitBoundaryCrossingWires();
447 r->resizeOnContents();
450 void Canvas::lockRavelsInSelection()
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))
458 ravelsToLock.push_back(r);
460 lockGroup=r->lockGroup;
461 if (lockGroup!=r->lockGroup)
462 conflictingLockGroups=
true;
464 if (ravelsToLock.size()<2)
466 if (!lockGroup || conflictingLockGroups)
468 for (
auto& r: ravelsToLock)
470 lockGroup->addRavel(r);
472 r->lockGroup=lockGroup;
474 if (lockGroup) lockGroup->initialBroadcast();
478 void Canvas::unlockRavelsInSelection()
480 for (
auto& i: selection.items)
481 if (
auto r=dynamic_cast<Ravel*>(i.get()))
485 void Canvas::deleteItem()
489 model->deleteItem(*item);
494 void Canvas::deleteWire()
498 model->removeWire(*
wire);
505 void Canvas::delHandle(
float x,
float y)
507 wireFocus=model->findAny(&Group::wires,
508 [&](
const WirePtr& i){
return i->near(x,y);});
511 wireFocus->deleteHandle(x,y);
517 void Canvas::removeItemFromItsGroup()
520 if (
auto g=item->group.lock())
522 if (
auto parent=g->group.lock())
524 itemFocus=parent->addItem(item);
525 if (
auto v=itemFocus->variableCast())
526 v->controller.reset();
527 g->splitBoundaryCrossingWires();
533 void Canvas::selectAllVariables()
536 auto var=item->variableCast();
538 if (
auto i=dynamic_cast<IntOp*>(item.get()))
543 (&GroupItems::items, [&](
const Items&,Items::const_iterator i)
545 if (
auto v=(*i)->variableCast())
546 if (v->valueId()==
var->valueId())
548 selection.ensureItemInserted(*i);
555 void Canvas::renameAllInstances(
const string& newName)
557 auto var=item->variableCast();
559 if (
auto i=dynamic_cast<IntOp*>(item.get()))
565 model->renameAllInstances(
valueId, newName);
569 void Canvas::ungroupItem()
571 if (
auto g=dynamic_cast<Group*>(item.get()))
573 if (
auto p=g->group.lock())
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());
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());
594 existingParms.clear();
603 void Canvas::renameItem(
const std::string& newName)
606 if (
auto var=item->variableCast())
611 var->retype(VariableType::flow);
615 void Canvas::copyItem()
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();
628 if (
auto v=item->variableCast())
629 if (v->rawName()[0]!=
':')
642 newItem.reset(item->clone());
644 if (
auto v=item->variableCast())
645 if (v->controller.lock())
646 newItem->rotation(defaultRotation);
649 setItemFocus(model->addItem(newItem));
650 model->normaliseGroupRefs(model);
654 void Canvas::zoomToFit()
656 if (frameArgs().parentWindowId.empty())
return;
658 for (
auto& i: model->items) i->updateBoundingBox();
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;
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());
672 const float zoomFactor=std::min(frameArgs().childWidth/(x1-x0+inOffset+outOffset),
673 frameArgs().childHeight/(y1-y0));
675 model->zoom(model->x(),model->y(),zoomFactor);
678 model->contentBounds(x0,y0,x1,y1);
679 float ioOffset=notFlipped? x0: x1;
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());
692 void Canvas::openGroupInCanvas(
const ItemPtr& item)
694 if (
auto g=dynamic_pointer_cast<Group>(item))
696 if (
auto parent=model->group.lock())
697 model->setZoom(parent->zoomFactor());
711 void Canvas::copyVars(
const std::vector<VariablePtr>& v)
713 float maxWidth=0, totalHeight=0;
714 vector<float> widths, heights;
721 widths.push_back(rv.
width());
722 heights.push_back(rv.
height());
723 maxWidth=max(maxWidth, widths.back());
724 totalHeight+=heights.back();
726 float y=v[0]->y() - totalHeight;
727 for (
size_t i=0; i<v.size(); ++i)
730 const ItemPtr ni(v[i]->clone());
731 (ni->variableCast())->rotation(0);
732 ni->moveTo(v[0]->x()+maxWidth-v[i]->zoomFactor()*widths[i],
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));
741 if (!selection.empty()) setItemFocus(selection.items[0]);
742 else setItemFocus(
nullptr);
743 }
else throw error(
"no flow or stock variables to copy");
746 void Canvas::zoomToDisplay()
748 if (
auto g=dynamic_cast<Group*>(item.get()))
750 model->zoom(g->x(),g->y(),1.1/(g->relZoom*g->zoomFactor()));
755 bool Canvas::selectVar(
float x,
float y)
759 if (
auto v=item->select(x,y))
768 bool Canvas::findVariableDefinition()
770 if (
auto iv=item->variableCast())
772 if (iv->type()==VariableType::constant ||
773 iv->type()==VariableType::parameter || iv->inputWired())
774 return (itemIndicator=item).get();
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())
783 if (v->valueId()==iv->valueId())
786 else if (
auto o=dynamic_cast<IntOp*>(i.get()))
787 return o->intVar->valueId()==iv->valueId();
790 return itemIndicator.get();
795 bool Canvas::redraw(
int x0,
int y0,
int width,
int height)
799 updateRegion.x1=x0+width;
800 updateRegion.y1=y0+height;
804 return redrawUpdateRegion();
807 catch (std::exception& ex)
809 cerr << ex.what() << endl;
821 bool Canvas::redraw()
824 return redraw(-1e9,-1e9,2e9,2e9);
827 bool Canvas::redrawUpdateRegion()
829 bool didDrawSomething =
false;
830 if (!surface().
get()) {
831 return didDrawSomething;
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);
838 cairo_set_line_width(cairo, 1);
841 (&GroupItems::items, [&](
const Items&, Items::const_iterator i)
844 if (it.visible() && updateRegion.intersects(it))
846 didDrawSomething =
true;
847 const CairoSave cs(cairo);
848 cairo_identity_matrix(cairo);
849 cairo_translate(cairo,it.x(), it.y());
854 catch (
const std::exception& ex)
856 cerr << ex.what() << endl;
864 (&GroupItems::groups, [&](
const Groups&, Groups::const_iterator i)
867 if (it.visible() && updateRegion.intersects(it))
869 didDrawSomething =
true;
870 const CairoSave cs(cairo);
871 cairo_identity_matrix(cairo);
872 cairo_translate(cairo,it.x(), it.y());
881 (&GroupItems::wires, [&](
const Wires&, Wires::const_iterator i)
892 didDrawSomething =
true;
893 cairo_move_to(cairo,fromPort->x(),fromPort->y());
894 cairo_line_to(cairo,termX,termY);
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);
908 if (lassoMode!=LassoMode::none)
910 didDrawSomething =
true;
911 cairo_rectangle(cairo,lasso.x0,lasso.y0,lasso.x1-lasso.x0,lasso.y1-lasso.y0);
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());
924 return didDrawSomething;
927 void Canvas::recentre()
929 const SurfacePtr tmp(surface());
930 surface().reset(
new Surface(cairo_recording_surface_create(CAIRO_CONTENT_COLOR,
nullptr)));
932 model->moveTo(model->x()-surface()->left(), model->y()-surface()->top());
937 void Canvas::reportDrawTime(
double t)
943 void Canvas::renderToPNGCropped(
const std::string& filename,
const ZoomCrop& z)
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)));
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);
958 void Canvas::applyDefaultPlotOptions() {
959 if (
auto p=item->plotWidgetCast()) {
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);
969 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
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky's state cha...
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