Minsky
item.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2015
3  @author Russell Standish
4  This file is part of Minsky.
5 
6  Minsky is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Minsky is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Minsky. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "cairoItems.h"
21 #include "minsky.h"
22 #include "item.h"
23 #include "group.h"
24 #include "zoom.h"
25 #include "variable.h"
26 #include "latexMarkup.h"
27 #include "geometry.h"
28 #include "selection.h"
29 #include "lasso.h"
30 #include <pango.h>
31 #include <cairo_base.h>
32 #include "item.rcd"
33 #include "noteBase.rcd"
34 #include "noteBase.xcd"
35 #include "polyRESTProcessBase.h"
36 #include "minsky_epilogue.h"
37 #include <exception>
38 
39 using ecolab::Pango;
40 using ecolab::cairo::CairoSave;
41 using namespace std;
42 
43 namespace minsky
44 {
45 
46  void BoundingBox::update(const Item& x)
47  {
48  const ecolab::cairo::Surface surf
49  (cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA,NULL));
50  auto savedMouseFocus=x.mouseFocus;
51  x.mouseFocus=false; // do not mark up icon with tooltips etc, which might invalidate this calc
52  x.onResizeHandles=false;
53  double stashedZf=1;
54  if (auto parent=x.group.lock())
55  {
56  stashedZf=parent->relZoom;
57  parent->relZoom/=x.zoomFactor(); // undo any zooming coming from owning group
58  }
59  try
60  {
61  const cairo::CairoSave cs(surf.cairo());
62  cairo_rotate(surf.cairo(),-x.rotation()*M_PI/180);
63  x.draw(surf.cairo());
64  }
65 #ifndef NDEBUG
66  catch (const std::exception& e)
67  {cerr<<"illegal exception caught in draw(): "<<e.what()<<endl;}
68  catch (...) {cerr<<"illegal exception caught in draw()";}
69 #else
70  catch(...) {}
71 #endif
72  x.mouseFocus=savedMouseFocus;
73  if (auto parent=x.group.lock())
74  parent->relZoom=stashedZf;
75 
76  double l,t,w,h;
77  cairo_recording_surface_ink_extents(surf.surface(),
78  &l,&t,&w,&h);
79  // note (0,0) is relative to the (x,y) of icon.
80  m_left=l;
81  m_right=(l+w);
82  m_top=t;
83  m_bottom=(t+h);
84  }
85 
86  void Item::throw_error(const std::string& msg) const
87  {
88  cminsky().displayErrorItem(*this);
89  throw runtime_error(msg);
90  }
91 
92  std::pair<double,bool> Item::rotationAsRadians() const
93  {
94  // if rotation is in 1st or 3rd quadrant, rotate as
95  // normal, otherwise flip the text so it reads L->R
96  return {rotation() * M_PI / 180.0, flipped(rotation())};
97  }
98 
99 
100  void ItemExclude::rotate(const Point& mouse, const Point& orig)
101  {
102  constexpr double degrees=180.0/M_PI;
103  m_rotation=atan2(mouse.y()-orig.y(),mouse.x()-orig.x())*degrees;
104  }
105 
106 
107  float Item::x() const
108  {
109  if (auto g=group.lock())
110  return zoomFactor()*m_x+g->x();
111  return m_x;
112  }
113 
114  float Item::y() const
115  {
116  if (auto g=group.lock())
117  return zoomFactor()*m_y+g->y();
118  return m_y;
119  }
120 
121  float Item::zoomFactor() const
122  {
123  if (auto g=group.lock())
124  return g->zoomFactor()*g->relZoom;
125  return 1;
126  }
127 
128  float Item::scaleFactor() const
129  {
130  return m_sf;
131  }
132 
133  float Item::scaleFactor(const float& sf) {
134  m_sf=sf;
135  bb.update(*this);
136  return m_sf;
137  }
138 
139  void Item::deleteAttachedWires()
140  {
141  for (auto& p: m_ports)
142  p->deleteWires();
143  }
144 
145  namespace
146  {
147  inline bool near(float x0, float y0, float x1, float y1, float d)
148  {
149  return abs(x0-x1)<d && abs(y0-y1)<d;
150  }
151  }
152 
153  std::vector<Point> Item::corners() const
154  {
155  ensureBBValid();
156  auto left=x()+bb.left()*zoomFactor(), right=x()+bb.right()*zoomFactor();
157  auto top=y()+bb.top()*zoomFactor(), bottom=y()+bb.bottom()*zoomFactor();
158  memoisedRotator.update(rotation(),x(),y());
159  return {memoisedRotator(left,top),memoisedRotator(left,bottom),
160  memoisedRotator(right,bottom),memoisedRotator(right,top)};
161  }
162 
163  float Item::left() const
164  {
165  auto left=x();
166  for (auto& p: corners())
167  if (p.x()<left) left=p.x();
168  return left;
169  }
170  float Item::right() const
171  {
172  auto right=x();
173  for (auto& p: corners())
174  if (p.x()>right) right=p.x();
175  return right;
176  }
177  float Item::top() const
178  {
179  auto top=y();
180  for (auto& p: corners())
181  if (p.y()<top) top=p.y();
182  return top;
183  }
184  float Item::bottom() const
185  {
186  auto bottom=y();
187  for (auto& p: corners())
188  if (p.y()>bottom) bottom=p.y();
189  return bottom;
190  }
191 
192  void Item::adjustBookmark() const
193  {
194  if (auto g=group.lock())
195  {
196  auto& bookmarks=g->bookmarks;
197  if (bookmark)
198  g->addBookmarkXY(left(),top(),bookmarkId());
199  else
200  bookmarks.erase(bookmarkId());
202  }
203  }
204 
205  Point BottomRightResizerItem::resizeHandleCoords() const
206  {
207  // ensure resize handle is always active on the same corner of variable/items. for ticket 1232
208  ensureBBValid();
209  memoisedRotator.update(rotation(),x(),y());
210  auto left=x()+bb.left()*zoomFactor(), right=x()+bb.right()*zoomFactor();
211  auto top=y()+bb.top()*zoomFactor(), bottom=y()+bb.bottom()*zoomFactor();
212  switch (quadrant(rotation()))
213  {
214  case 0:
215  return memoisedRotator(right,bottom);
216  case 1:
217  return memoisedRotator(right,top);
218  case 2:
219  return memoisedRotator(left,top);
220  case 3:
221  return memoisedRotator(left,bottom);
222  default:
223  assert(false);
224  return {};
225  }
226  }
227 
228  bool Item::onResizeHandle(float x, float y) const
229  {
230  float rhSize=resizeHandleSize();
231  auto cnrs=corners();
232  return any_of(cnrs.begin(), cnrs.end(), [&](const Point& p)
233  {return near(x,y,p.x(),p.y(),rhSize);});
234  }
235 
236  bool BottomRightResizerItem::onResizeHandle(float x, float y) const
237  {
238  const Point p=resizeHandleCoords();
239  return near(x,y,p.x(),p.y(),resizeHandleSize());
240  }
241 
242  bool Item::onItem(float x, float y) const
243  {
244  const Rotate r(-rotation(),this->x(),this->y());
245  return bb.contains(
246  (r.x(x,y)-this->x())/zoomFactor(),
247  (r.y(x,y)-this->y())/zoomFactor());
248  }
249 
250  bool Item::visible() const
251  {
252  auto g=group.lock();
253  return (!g || g->displayContents());
254  }
255 
256  void Item::moveTo(float x, float y)
257  {
258  // cowardly refuse to move to a nonsense coordinate
259  if (!isfinite(x)||!isfinite(y)) return;
260  if (auto g=group.lock())
261  {
262  const float invZ=1/zoomFactor();
263  m_x=(x-g->x())*invZ;
264  m_y=(y-g->y())*invZ;
265  }
266  else
267  {
268  m_x=x;
269  m_y=y;
270  }
271  if (bookmark) adjustBookmark();
272  assert(abs(x-this->x())<1 && abs(y-this->y())<1);
273  }
274 
275  ClickType::Type Item::clickType(float x, float y) const
276  {
277  // if selecting a contained variable, the delegate to that
278  if (auto item=select(x,y))
279  return item->clickType(x,y);
280 
281  // firstly, check whether a port has been selected
282  for (auto& p: m_ports)
283  {
284  if (hypot(x-p->x(), y-p->y()) < portRadius*zoomFactor())
285  return ClickType::onPort;
286  }
287 
288  if (onResizeHandle(x,y)) return ClickType::onResize;
289  if (inItem(x,y)) return ClickType::inItem;
290  if (onItem(x,y)) return ClickType::onItem;
291  return ClickType::outside;
292  }
293 
294  void Item::drawPorts(cairo_t* cairo) const
295  {
296  const CairoSave cs(cairo);
297  cairo_new_path(cairo);
298  for (auto& p: m_ports)
299  {
300  cairo_new_sub_path(cairo);
301  cairo_arc(cairo, p->x()-x(), p->y()-y(), portRadius*zoomFactor(), 0, 2*M_PI);
302  }
303  cairo_set_source_rgb(cairo, 0,0,0);
304  cairo_set_line_width(cairo,1);
305  cairo_stroke(cairo);
306  }
307 
308  void Item::drawSelected(cairo_t* cairo)
309  {
310  // implemented by filling the clip region with a transparent grey
311  const CairoSave cs(cairo);
312  cairo_set_source_rgba(cairo, 0.5,0.5,0.5,0.4);
313  cairo_paint(cairo);
314  }
315 
316  void Item::drawResizeHandle(cairo_t* cairo, double x, double y, double sf, double angle)
317  {
318  const cairo::CairoSave cs(cairo);
319  cairo_translate(cairo,x,y);
320  cairo_rotate(cairo,angle);
321  cairo_scale(cairo,sf,sf);
322  cairo_move_to(cairo,-1,-.2);
323  cairo_line_to(cairo,-1,-1);
324  cairo_line_to(cairo,1,1);
325  cairo_line_to(cairo,1,0.2);
326  cairo_move_to(cairo,-1,-1);
327  cairo_line_to(cairo,-.2,-1);
328  cairo_move_to(cairo,.2,1);
329  cairo_line_to(cairo,1,1);
330  }
331 
332  // Refactor resize() code for all canvas items here. For feature 25 and 94
333  void Item::resize(const LassoBox& b)
334  {
335  // Set initial iWidth() and iHeight() to initial Pango determined values. This resize method is not very reliable. Probably a Pango issue.
336  const float w=iWidth(width()), h=iHeight(height()), invZ=1/zoomFactor();
337  moveTo(0.5*(b.x0+b.x1), 0.5*(b.y0+b.y1));
338  iWidth(abs(b.x1-b.x0)*invZ);
339  iHeight(abs(b.y1-b.y0)*invZ);
340  scaleFactor(std::max(1.0f,std::min(iWidth()/w,iHeight()/h)));
341  }
342 
343  void Item::drawResizeHandles(cairo_t* cairo) const
344  {
345  auto sf=resizeHandleSize();
346  double angle=0.5*M_PI;
347  for (auto& p: corners())
348  {
349  angle+=0.5*M_PI;
350  drawResizeHandle(cairo,p.x()-x(),p.y()-y(),sf,angle);
351  }
352  cairo_stroke(cairo);
353  }
354 
355  void BottomRightResizerItem::drawResizeHandles(cairo_t* cairo) const
356  {
357  const Point p=resizeHandleCoords();
358  drawResizeHandle(cairo,p.x()-x(),p.y()-y(),resizeHandleSize(),0);
359  cairo_stroke(cairo);
360  }
361 
362  // default is just to display the detailed text (ie a "note")
363  void Item::draw(cairo_t* cairo) const
364  {
365  auto [angle,flipped]=rotationAsRadians();
366  const Rotate r(rotation()+(flipped? 180:0),0,0);
367  Pango pango(cairo);
368  const float z=zoomFactor();
369  pango.angle=angle+(flipped? M_PI: 0);
370  pango.setFontSize(12.0*scaleFactor()*z);
371  pango.setMarkup(latexToPango(detailedText()));
372  // parameters of icon in userspace (unscaled) coordinates
373  const float w=0.5*pango.width()+2*z;
374  const float h=0.5*pango.height()+4*z;
375 
376  cairo_move_to(cairo,r.x(-w+1,-h+2), r.y(-w+1,-h+2));
377  pango.show();
378 
379  if (mouseFocus) {
380  displayTooltip(cairo,tooltip());
381  }
382  if (onResizeHandles) drawResizeHandles(cairo);
383  cairo_move_to(cairo,r.x(-w,-h), r.y(-w,-h));
384  cairo_line_to(cairo,r.x(w,-h), r.y(w,-h));
385  cairo_line_to(cairo,r.x(w,h), r.y(w,h));
386  cairo_line_to(cairo,r.x(-w,h), r.y(-w,h));
387  cairo_close_path(cairo);
388  cairo_clip(cairo);
389  if (selected) drawSelected(cairo);
390  }
391 
392  void Item::dummyDraw() const
393  {
394  const ecolab::cairo::Surface s(cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA,NULL));
395  draw(s.cairo());
396  }
397 
398  void Item::displayTooltip(cairo_t* cairo, const std::string& tooltip) const
399  {
400  const string unitstr=units().latexStr();
401  if (!tooltip.empty() || !unitstr.empty())
402  {
403  const cairo::CairoSave cs(cairo);
404  Pango pango(cairo);
405  string toolTipText=latexToPango(tooltip);
406  if (!unitstr.empty())
407  toolTipText+=" Units:"+latexToPango(unitstr);
408  pango.setMarkup(toolTipText);
409  const float z=zoomFactor();
410  cairo_translate(cairo,z*(0.5*bb.width())+10,
411  z*(-0.5*bb.height())-20);
412  cairo_rectangle(cairo,0,0,pango.width(),pango.height());
413  cairo_set_source_rgb(cairo,1,1,1);
414  cairo_fill_preserve(cairo);
415  cairo_set_source_rgb(cairo,0,0,0);
416  pango.show();
417  cairo_stroke(cairo);
418  }
419  }
420 
421  shared_ptr<Port> Item::closestOutPort(float x, float y) const
422  {
423  if (auto v=select(x,y))
424  return v->closestOutPort(x,y);
425  return portsSize()>0 && !ports(0).lock()->input()? ports(0).lock(): nullptr;
426  }
427 
428  shared_ptr<Port> Item::closestInPort(float x, float y) const
429  {
430  if (auto v=select(x,y))
431  return v->closestInPort(x,y);
432  shared_ptr<Port> r;
433  for (size_t i=0; i<m_ports.size(); ++i)
434  if (m_ports[i]->input() &&
435  (!r || sqr(m_ports[i]->x()-x)+sqr(m_ports[i]->y()-y) <
436  sqr(r->x()-x)+sqr(r->y()-y)))
437  r=m_ports[i];
438  return r;
439  }
440 
441  void ItemExclude::removeControlledItems()
442  {
443  if (auto g=group.lock())
444  removeControlledItems(*g);
445  }
446 
447  ItemPtr Item::itemPtrFromThis() const
448  {
449  if (auto g=group.lock())
450  return g->findItem(*this);
451  return {};
452  }
453 
454 }
455 
#define M_PI
some useful geometry types, defined from boost::geometry
Definition: geometry.h:29
function f
Definition: canvas.m:1
std::string latexToPango(const char *s)
Definition: latexMarkup.h:30
Definition: input.py:1
represents whether a mouse click is on the item, on an output port (for wiring, or is actually outsid...
Definition: item.h:63
void displayErrorItem(const Item &op) const
indicate operation item has error, if visible, otherwise contining group
Definition: minsky.cc:1230
minsky::Minsky minsky
Definition: pyminsky.cc:28
bool onResizeHandles
set to true to indicate mouse is ovcaler resize handles
Definition: item.h:175
bool near(float x0, float y0, float x1, float y1, float d)
Definition: item.cc:147
represents rectangular region of a lasso operation
Definition: lasso.h:28
STL namespace.
float y(float x, float y) const
Definition: geometry.h:60
std::shared_ptr< Item > ItemPtr
Definition: item.h:57
rotate (x,y) by rot (in degrees) around the origin (x0, y0) can be used for rotating multiple points ...
Definition: geometry.h:44
virtual float zoomFactor() const
Definition: item.cc:121
bool flipped(double rotation)
returns if the angle (in degrees) is in the second or third quadrant
Definition: geometry.h:102
int quadrant(double x)
return quadrant x is in: 0=[-45,45),1=[45,135), etc
Definition: geometry.h:96
virtual void draw(cairo_t *cairo) const
draw this item into a cairo context
Definition: item.cc:363
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
virtual void bookmarkRefresh()
refresh the bookmark menu after changes
Definition: minsky.h:450
boost::geometry::model::d2::point_xy< float > Point
Definition: geometry.h:34
const Minsky & cminsky()
const version to help in const correctness
Definition: minsky.h:549
T sqr(T x)
Definition: geometry.h:36
float x0
Definition: lasso.h:30
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::NoteBase)
bool mouseFocus
true if target of a mouseover
Definition: noteBase.h:31
float y1
Definition: lasso.h:30
float x1
Definition: lasso.h:30
float y0
Definition: lasso.h:30
Definition: group.tcl:84
constexpr float portRadius
radius of circle marking ports at zoom=1
Definition: item.h:69
float x(float x, float y) const
Definition: geometry.h:59
double rotation() const
Definition: item.h:211