Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

551
LINES

< > BotCompany Repo | #1007303 // CirclesAndLines [include]

JavaX fragment (include)

1  
sclass BaseBase {
2  
  S globalID = aGlobalID();
3  
  S text;
4  
  S textForRender() { ret text; }
5  
}
6  
7  
sbool traits_multiLine = true;
8  
9  
sclass Base extends BaseBase {
10  
  new L<S> traits;
11  
  bool hasTrait(S t) { ret containsIC(traits(), t); }
12  
  L<S> traits() { if (nempty(text) && neq(first(traits), text)) traits.add(0, text); ret traits; }
13  
  void addTraits(L<S> l) { setAddAll(traits(), l); }
14  
  void addTrait(S t) { if (nempty(t)) setAdd(traits(), t); }
15  
  
16  
  S textForRender() {
17  
    L<S> traits = traits();
18  
    if (traits_multiLine) ret lines_rtrim(traits);
19  
    if (l(traits) <= 1) ret first(traits);
20  
    ret first(traits) + " [" + join(", ", dropFirst(traits)) + "]";
21  
  }
22  
  
23  
  void setText(S text) {
24  
    this.text = text;
25  
    traits = ll(text);
26  
  }
27  
}
28  
29  
sclass CirclesAndLines {
30  
  new L<Circle> circles;
31  
  new L<Line> lines;
32  
  Class<? extends Arrow> arrowClass = Arrow;
33  
  Class<? extends Circle> circleClass = Circle;
34  
  S title;
35  
  S globalID = aGlobalID();
36  
  long created = nowUnlessLoading();
37  
  transient Lock lock = fairLock();
38  
  transient S defaultImageID = #1007372;
39  
  transient double imgZoom = 1; // zoom for the circle images
40  
  transient Pt translate;
41  
  Circle hoverCircle; // which one we are hovering over
42  
  transient O onUserMadeArrow, onUserMadeCircle, onLayoutChange;
43  
  transient O onFullLayoutChange, onDeleteCircle, onDeleteLine;
44  
  transient O onRenameCircle, onRenameLine, onStructureChange;
45  
  transient BufferedImage imageForUserMadeNodes;
46  
  static int maxDistanceToLine = 20; // for clicking
47  
  transient S backgroundImageID = defaultBackgroundImageID;
48  
  static S defaultBackgroundImageID = #1007195;
49  
  static Color defaultLineColor = Color.white;
50  
  static bool debugRender;
51  
  static O staticPopupExtender;
52  
  transient double scale = 1; // zoom whole image
53  
  transient bool recordHistory = true;
54  
  L history;
55  
56  
  // auto-visualize
57  
  Circle circle_autoVis(S text, S visualizationText, double x, double y) {
58  
    ret addAndReturn(circles,
59  
      nu(circleClass, +x, +y, +text,
60  
        quickvis := visualizationText,
61  
        img := processImage(quickVisualizeOr(visualizationText, defaultImageID))));
62  
  }
63  
  
64  
  S makeVisualizationText(S text) {
65  
    ret possibleGlobalID(text) ? "" : text;
66  
  }
67  
68  
  Circle circle_autoVis(S text, double x, double y) {
69  
    ret circle_autoVis(text, makeVisualizationText(text), x, y);
70  
  }
71  
  
72  
  Circle circle(BufferedImage img, double x, double y, S text) {
73  
    ret addAndReturn(circles, nu(circleClass, +x, +y, +text, img := processImage(img));
74  
  }
75  
  
76  
  Circle circle(S text, BufferedImage img, double x, double y) {
77  
    ret circle(img, x, y, text);
78  
  }
79  
  
80  
  Circle circle(S text, double x, double y) {
81  
    ret addAndReturn(circles, nu(circleClass, +x, +y, +text, img := processImage(imageForUserMadeNodes());
82  
  }
83  
  
84  
  Circle addCircle(S imageID, double x, double y) {
85  
    ret addCircle(imageID, x, y, "");
86  
  }
87  
  
88  
  Circle addCircle(S imageID, double x, double y, S text) {
89  
    ret addAndReturn(circles, nu(circleClass, +x, +y, +text, img := processImage(loadImage2(imageID))));
90  
  }
91  
  
92  
  Arrow findArrow(Circle a, Circle b) {
93  
    for (Line l : getWhere(lines, +a, +b))
94  
      if (l instanceof Arrow)
95  
        ret (Arrow) l;
96  
    null;
97  
  }
98  
  
99  
  Line addLine(Circle a, Circle b) {
100  
    Line line = findWhere(lines, +a, +b);
101  
    if (line == null)
102  
      lines.add(line = nu(Line, +a, +b));
103  
    ret line;
104  
  }
105  
  
106  
  Arrow arrow(Circle a, S text, Circle b) {
107  
    ret addArrow(a, b, text);
108  
  }
109  
  
110  
  Arrow addArrow(Circle a, Circle b) {
111  
    ret addArrow(a, b, "");
112  
  }
113  
  
114  
  Arrow addArrow(Circle a, Circle b, S text) {
115  
    ret addAndReturn(lines, nu(arrowClass, +a, +b, +text));
116  
  }
117  
  
118  
  BufferedImage makeImage(int w, int h) {
119  
    BufferedImage bg = renderTiledBackground(backgroundImageID, w, h, ptX(translate), ptY(translate));
120  
    if (!lock.tryLock()) null;
121  
    try {
122  
      if (scale != 1)
123  
        createGraphics_modulate(bg, voidfunc(Graphics2D g) {
124  
          g.scale(scale, scale);
125  
        });
126  
      
127  
      // Lines
128  
      
129  
      if (debugRender)
130  
        print("Have " + n(lines, "line"));
131  
      
132  
      // flipMap is false for bidirectional connections
133  
      
134  
      HashMap<Pair<Circle>, Line> hasLine = new HashMap;
135  
      new HashMap<Line, Bool> flipMap;
136  
      for (Line l : lines) {
137  
        hasLine.put(pair(l.a, l.b), l);
138  
        Line x = hasLine.get(pair(l.b, l.a));
139  
        if (x != null) {
140  
          if (debugRender)
141  
            print("flipMap " + l.a.text + " / " + l.b.text);
142  
          flipMap.put(x, false);
143  
          flipMap.put(l, false);
144  
        }
145  
      }
146  
147  
      for (Line l : lines) {
148  
        DoublePt a = translateDoublePt(translate, l.a.doublePt(w, h, this));
149  
        DoublePt b = translateDoublePt(translate, l.b.doublePt(w, h, this));
150  
        if (debugRender)
151  
          print("Line " + a + " " + b);
152  
        if (l instanceof Arrow)
153  
          drawThoughtArrow(bg, l.a.img(this), iround(a.x), iround(a.y), l.b.img(this), iround(b.x), iround(b.y), l.color);
154  
        else
155  
          drawThoughtLine(bg, l.a.img(this), iround(a.x), iround(a.y), l.b.img(this), iround(b.x), iround(b.y), l.color);
156  
        S text = l.textForRender();
157  
        if (nempty(text)) {
158  
          Bool flip = flipMap.get(l);
159  
          drawOutlineTextAlongLine_flip.set(flip);
160  
          
161  
          // mark bidirectional arrow labels with direction for clarity
162  
          if (flip != null) text += " " + unicode_blackRightArrow();
163  
          
164  
          drawThoughtLineText_multiLine(bg, l.a.img(this), iround(a.x), iround(a.y), l.b.img(this), iround(b.x), iround(b.y), text, Color.white /*l.color*/);
165  
        }
166  
      }
167  
      
168  
      // Circles
169  
      
170  
      for (Circle c : circles) {
171  
        DoublePt p = translateDoublePt(translate, c.doublePt(w, h, this));
172  
        drawThoughtCircle(bg, c.img(this), p.x, p.y);
173  
      }
174  
      
175  
      for (Circle c : circles) {
176  
        DoublePt p = translateDoublePt(translate, c.doublePt(w, h, this));
177  
        S text = c.textForRender();
178  
        if (nempty(text))
179  
          drawThoughtCircleText(bg, c.img(this), p, text);
180  
          
181  
        if (c == hoverCircle)
182  
          drawThoughtCirclePlus(bg, c.img(this), p.x, p.y);
183  
      }
184  
    } finally {
185  
      lock.unlock();
186  
      createGraphics_modulate(bg, null);
187  
    }
188  
    ret bg;
189  
  }
190  
  
191  
  Canvas showAsFrame(int w, int h) {
192  
    Canvas canvas = showAsFrame();
193  
    frameInnerSize(canvas, w, h);
194  
    centerFrame(getFrame(canvas));
195  
    ret canvas;
196  
  }
197  
  
198  
  Canvas showAsFrame() {
199  
    ret (Canvas) swing(func {
200  
      Canvas canvas = makeCanvas();
201  
      showCenterFrame(canvas);
202  
      ret canvas;
203  
    });
204  
  }
205  
  
206  
  Canvas makeCanvas() {
207  
    fO makeImg = func(int w, int h) { makeImage(w, h) };
208  
    final Canvas canvas = jcanvas(makeImg);
209  
    disableImageSurfaceSelector(canvas);
210  
    new CircleDragger(this, canvas, r { updateCanvas(canvas, makeImg) });
211  
    
212  
    componentPopupMenu(canvas, voidfunc(JPopupMenu menu) {
213  
      // POPUP MENU START
214  
      Pt p = pointFromEvent(canvas, componentPopupMenu_mouseEvent.get());
215  
      JMenu imageMenu = jmenu("Image");
216  
      moveAllMenuItems(menu, imageMenu);
217  
218  
      addMenuItem(menu, "New Circle...", r { newCircle(canvas) });
219  
      
220  
      final Line l = findLine(canvas, p);
221  
      if (l != null) {
222  
        addMenuItem(menu, "Rename Relation...", r {
223  
          renameLine(canvas, l)
224  
        });
225  
        
226  
        addMenuItem(menu, "Delete Relation", r {
227  
          deleteLine(l);
228  
          canvas.update();
229  
        });
230  
      }
231  
      
232  
      final Circle c = findCircle(canvas, p);
233  
      if (c != null) {
234  
        addMenuItem(menu, "Rename Circle...", r { renameCircle(canvas, c) });
235  
        addMenuItem(menu, "Delete Circle", r {
236  
          deleteCircle(c);
237  
          canvas.update();
238  
        });
239  
        
240  
        if (c.img != null || c.quickvis != null)
241  
          addMenuItem(menu, "Delete Image", r {
242  
            c.img = null;
243  
            c.quickvis = null;
244  
            canvas.update();
245  
          });
246  
          
247  
        if (neqic(c.text, c.quickvis))
248  
          addMenuItem(menu, "Visualize", r {
249  
            thread "Visualizing" {
250  
              quickVisualize(c.text);
251  
              print("Quickvis done");
252  
              swing {
253  
                c.img = null;
254  
                c.quickvis = c.text;
255  
                canvas.update();
256  
                pcallF(onRenameCircle, c); schange();
257  
              }
258  
            }
259  
          });
260  
      }
261  
      
262  
      addMenuItem(menu, "Copy structure to clipboard", r {
263  
        copyTextToClipboard(cal_simplifiedStructure(CirclesAndLines.this))
264  
      });
265  
266  
      addMenuItem(menu, "Paste structure", r {
267  
        S text = getTextFromClipboard();
268  
        if (nempty(text)) {
269  
          copyCAL(cal_unstructure(text), CirclesAndLines.this);
270  
          canvas.update();
271  
          schange();
272  
        }
273  
      });
274  
      
275  
      pcallF(staticPopupExtender, CirclesAndLines.this, canvas, menu);
276  
      
277  
      addMenuItem(menu, imageMenu);
278  
279  
      // POPUP MENU END
280  
    });
281  
    
282  
    ret canvas;
283  
  }
284  
  
285  
  void newCircle(final Canvas canvas) {
286  
    final JTextField text = jtextfield();
287  
    showFormTitled("New Circle", "Text", text, r-thread { loading {
288  
      S theText = getTextTrim(text);
289  
      makeCircle(theText);
290  
      canvas.update();
291  
    }});
292  
  }
293  
  
294  
  Canvas show() { ret showAsFrame(); }
295  
  Canvas show(int w, int h) { ret showAsFrame(w, h); }
296  
  
297  
  Circle findCircle(S text) {
298  
    for (Circle c : circles) if (eq(c.text, text)) ret c;
299  
    for (Circle c : circles) if (eqic(c.text, text)) ret c;
300  
    null;
301  
  }
302  
  
303  
  void renameCircle(final Canvas canvas, final Circle c) {
304  
    final JTextField tf = jtextfield(c.text);
305  
    showFormTitled("Rename circle",
306  
      "Old name", jlabel(c.text),
307  
      "New name", tf,
308  
      r {
309  
        c.setText(getTextTrim(tf));
310  
        canvas.update();
311  
        pcallF(onRenameCircle, c); schange();
312  
      });
313  
  }
314  
  
315  
  void renameLine(final Canvas canvas, final Line l) {
316  
    final JTextField tf = jtextfield(l.text);
317  
    showFormTitled("Rename relation",
318  
      "Old name", jlabel(l.text),
319  
      "New name", tf,
320  
      r {
321  
        l.setText(getTextTrim(tf));
322  
        canvas.update();
323  
        pcallF(onRenameLine, l); schange();
324  
      });
325  
  }
326  
  
327  
  void clear {
328  
    clearAll(circles, lines);
329  
  }
330  
  
331  
  // only finds actually containing circles
332  
  Circle findCircle(ImageSurface canvas, Pt p) {
333  
    p = untranslatePt(translate, p);
334  
    new Lowest<Circle> best;
335  
    for (Circle c : circles)
336  
      if (c.contains(this, canvas, p))
337  
        best.put(c, pointDistance(p, c.pt2(this, canvas)));
338  
    ret best!;
339  
  }
340  
  
341  
  Circle findNearestCircle(ImageSurface canvas, Pt p) {
342  
    new Lowest<Circle> best;
343  
    for (Circle c : circles)
344  
      if (c.contains(this, canvas, p))
345  
        best.put(c, pointDistance(p, c.pt2(this, canvas)));
346  
    ret best.get();
347  
  }
348  
  
349  
  BufferedImage processImage(BufferedImage img) {
350  
    ret scaleImage(img, imgZoom);
351  
  }
352  
  
353  
  void deleteCircle(Circle c) {
354  
    for (Line l : cloneList(lines))
355  
      if (l.a == c || l.b == c) deleteLine(l);
356  
    circles.remove(c);
357  
    pcallF(onDeleteCircle, c); schange();
358  
  }
359  
  
360  
  void deleteLine(Line l) {
361  
    lines.remove(l);
362  
    pcallF(onDeleteLine, l); schange();
363  
  }
364  
  
365  
  void openPlusDialog(final Circle c, final ImageSurface canvas) {
366  
    if (c == null) ret;
367  
    
368  
    final JTextField tfFrom = jtextfield(c.text);
369  
    final JTextField tfRel = jtextfield(web_defaultRelationName());
370  
    final JComboBox tfTo = autoComboBox(collect(circles, 'text));
371  
    
372  
    showFormTitled("Add connection",
373  
      "From node", tfFrom,
374  
      "Connection name", tfRel,
375  
      "To node", tfTo,
376  
      func {
377  
        S sA = getTextTrim(tfFrom);
378  
        Circle a = eq(sA, c.text) ? c : findOrMakeCircle(sA);
379  
        if (a == null) { messageBox("Not found: " + getTextTrim(tfFrom)); false; }
380  
        Circle b = findOrMakeCircle(getTextTrim(tfTo));
381  
        if (b == null) { messageBox("Not found: " + getTextTrim(tfTo)); false; }
382  
        if (a == b) { infoBox("Can't connect circle to itself for now"); false; }
383  
        Arrow arrow = arrow(a, getTextTrim(tfRel), b);
384  
        ((Canvas) canvas).update();
385  
        pcallF(onUserMadeArrow, arrow); schange();
386  
        null;
387  
      });
388  
    awtLater(tfRel, 100, r { requestFocus(tfRel) });
389  
  }
390  
  
391  
  void schange() {
392  
    pcallF(onStructureChange);
393  
    logQuoted("user-web-edits", now() + " " + cal_structure(this));
394  
    markWebsPosted();
395  
  }
396  
  
397  
  Circle findOrMakeCircle(S text) {
398  
    Circle c = findCircle(text);
399  
    if (c != null) ret c;
400  
    ret makeCircle(text);
401  
  }
402  
403  
  Circle makeCircle(S text) {
404  
    Circle c = circle(imageForUserMadeNodes(), random(0.1, 0.9), random(0.1, 0.9), text);
405  
    pcallF(onUserMadeCircle, c); schange();
406  
    historyLog(lisp("Made circle", text));
407  
    ret c;
408  
  }
409  
410  
  BufferedImage imageForUserMadeNodes() {  
411  
    if (imageForUserMadeNodes == null)
412  
      imageForUserMadeNodes = whiteImage(20, 20);
413  
    ret imageForUserMadeNodes;
414  
  }
415  
  
416  
  Line findLine(Canvas is, Pt p) {
417  
    p = untranslatePt(translate, p);
418  
    new Lowest<Line> best;
419  
    for (Line line : lines) {
420  
      double d = distancePointToLineSegment(line.a.pt(this, is), line.b.pt(this, is), p);
421  
      if (d <= maxDistanceToLine)
422  
        best.put(line, d);
423  
    }
424  
    ret best!;
425  
  }
426  
  
427  
  Pt pointFromEvent(ImageSurface canvas, MouseEvent e) {
428  
    ret scalePt(canvas.pointFromEvent(e), 1/scale);
429  
  }
430  
  
431  
  void historyLog(O o) {
432  
    if (!recordHistory) ret;
433  
    if (history == null) history = new L;
434  
    history.add(o);
435  
  }
436  
} // end of class CirclesAndLines
437  
438  
sclass Circle extends Base {
439  
  transient BufferedImage img;
440  
  double x, y;
441  
  //static BufferedImage defaultImage;
442  
  S quickvis;
443  
  
444  
  BufferedImage img(CirclesAndLines cal) {
445  
    if (img != null) ret img;
446  
    if (nempty(quickvis)) img = quickVisualize(quickvis);
447  
    //if (defaultImage == null) defaultImage = loadImage2(#1007372);
448  
    ret cal.imageForUserMadeNodes();
449  
  }
450  
  
451  
  Pt pt2(CirclesAndLines cal, ImageSurface is) {
452  
    ret pt2(is.getWidth(), is.getHeight(), cal);
453  
  }
454  
  
455  
  Pt pt(CirclesAndLines cal, ImageSurface is) {
456  
    ret pt(is.getWidth(), is.getHeight(), cal);
457  
  }
458  
  
459  
  Pt pt(int w, int h, CirclesAndLines cal) {
460  
    ret new Pt(iround(x*w/cal.scale), iround(y*h/cal.scale));
461  
  }
462  
  
463  
  Pt pt2(int w, int h, CirclesAndLines cal) {
464  
    ret new Pt(iround(x*w), iround(y*h));
465  
  }
466  
  
467  
  DoublePt doublePt(int w, int h, CirclesAndLines cal) {
468  
    ret new DoublePt(x*w/cal.scale, y*h/cal.scale);
469  
  }
470  
  
471  
  bool contains(CirclesAndLines cal, ImageSurface is, Pt p) {
472  
    ret pointDistance(p, pt2(cal, is)) <= iround(thoughtCircleSize(img(cal))*cal.scale)/2+1;
473  
  }
474  
}
475  
476  
sclass Line extends Base {
477  
  Circle a, b;
478  
  transient Color color = CirclesAndLines.defaultLineColor;
479  
  
480  
  Line setColor(Color color) {
481  
    this.color = color;
482  
    this;
483  
  }
484  
}
485  
486  
Line > Arrow {}
487  
488  
sclass CircleDragger extends MouseAdapter {
489  
  CirclesAndLines cal;
490  
  ImageSurface is;
491  
  O update;
492  
  int dx, dy;
493  
  Circle circle;
494  
  Pt startPoint;
495  
  
496  
  *(CirclesAndLines *cal, ImageSurface *is, O *update) {
497  
    if (containsInstance(is.tools, CircleDragger)) ret;
498  
    is.tools.add(this);
499  
    is.addMouseListener(this);
500  
    is.addMouseMotionListener(this);
501  
  }
502  
503  
  public void mouseMoved(MouseEvent e) {
504  
    Pt p = is.pointFromEvent(e);
505  
    Circle c = cal.findCircle(is, p);
506  
    if (c != cal.hoverCircle) {
507  
      cal.hoverCircle = c;
508  
      callF(update);
509  
    }
510  
  }
511  
  
512  
  public void mousePressed(MouseEvent e) {
513  
    if (e.getButton() == MouseEvent.BUTTON1) {
514  
      Pt p = is.pointFromEvent(e);
515  
      startPoint = p;
516  
      circle = cal.findCircle(is, p);
517  
      if (circle != null) {
518  
        dx = p.x-iround(circle.x*is.getWidth());
519  
        dy = p.y-iround(circle.y*is.getHeight());
520  
        //printVars("mousePressed", +dx, +dy, +p, cx := circle.x, cy := circle.y, w := is.getWidth(), h := is.getHeight());
521  
      } else { 
522  
        Pt t = unnull(cal.translate);
523  
        dx = p.x-t.x;
524  
        dy = p.y-t.y;
525  
      }
526  
    }
527  
  }
528  
529  
  public void mouseDragged(MouseEvent e) {
530  
    if (startPoint == null) ret;
531  
    Pt p = is.pointFromEvent(e);
532  
    if (circle != null) {
533  
      circle.x = (p.x-dx)/(double) is.getWidth();
534  
      circle.y = (p.y-dy)/(double) is.getHeight();
535  
      //printVars("mouseDragged", +dx, +dy, +p, cx := circle.x, cy := circle.y, w := is.getWidth(), h := is.getHeight());
536  
      pcallF(cal.onLayoutChange, circle);
537  
      callF(update);
538  
    } else {
539  
      cal.translate = new Pt(p.x-dx, p.y-dy);
540  
      callF(update);
541  
    }
542  
  }
543  
544  
  public void mouseReleased(MouseEvent e) {
545  
    mouseDragged(e);
546  
    if (eq(is.pointFromEvent(e), startPoint))
547  
      cal.openPlusDialog(circle, is);
548  
    circle = null;
549  
    startPoint = null;
550  
  }
551  
}

Author comment

Began life as a copy of #1007298

download  show line numbers  debug dex  old transpilations   

Travelled to 17 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, jtubtzbbkimh, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, ppjhyzlbdabe, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv

No comments. add comment

Snippet ID: #1007303
Snippet name: CirclesAndLines [include]
Eternal ID of this version: #1007303/197
Text MD5: bdaf89feb430d4dbb3ef2323baace056
Author: stefan
Category: javax / gui
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-02-20 13:22:40
Source code size: 16898 bytes / 551 lines
Pitched / IR pitched: No / No
Views / Downloads: 1101 / 5017
Version history: 196 change(s)
Referenced in: [show references]