1    /*
2    Copyright 2000 by Ralph Hartley
3    This software is licenced under the terms of the
4    Gnu Public Licence
5    */
6    import java.util.*;
7    import java.awt.*;
8    import java.awt.event.*;
9    import java.awt.geom.*;
10   import javax.swing.*;
11   
12   /**
13    * Editable cuve, a basic drawing primative.<br>
14    * Edit by adding and moving anchor points. The spline curve can be drawn with line types,
15    * or it can define an area.
16    */
17   public class Curve extends Symbol implements AlignmentListener,Addable,DragableSymbol,Region,Modable,Styleable {
18    
19     private static final long serialVersionUID = Version.getSUID();
20   
21     static int SELTOL = 8;
22   
23     /**
24      * The anchor points of the curve, int order.
25      */
26     Vector points = new Vector();
27   
28     /** The path followed byte the curve.<br>
29      * Must be recomputed before being used.
30      */
31     transient GeneralPath path = null;
32     Vector atribs = new Vector();
33     transient Vector subpath = new Vector();
34     transient Vector subpathatt = new Vector();
35   
36     transient Shape selectregion;
37   
38     CurveAtt wholeatt = new CurveAtt(null,Color.black,false,null);
39     Paint paint=null;
40     Fill fill=null;
41   
42     boolean closed = false;
43     boolean hasarrows = false;
44   
45     public Curve(Point2D pos,View view,Object arg) {
46     }
47   
48     static boolean debug = false;
49   
50     /**
51      * Prepare all points for survey update.
52      */
53     public void prepareForSurveyUpdate(java.util.List segments) {
54   //    System.out.println("curve prep");
55       if (name.compareTo("DryCurve")==0) {
56         System.out.println("prepare segs = "+segments);
57         debug = true;
58       }
59       for (Iterator it = points.iterator();it.hasNext();)
60         ((Symbol)it.next()).prepareForSurveyUpdate(segments);
61       debug = false;
62     }
63   
64     /**
65      * Tell all points to use a survey update that just happened.
66      */
67     public void useSurveyUpdate() {
68       if (name.compareTo("DryCurve")==0) {
69         System.out.println("use");
70         debug = true;
71       }
72       for (Iterator it = points.iterator();it.hasNext();)
73         ((Symbol)it.next()).useSurveyUpdate();
74       build();
75       debug = false;
76     }
77   
78     public int getLevel() {return(super.getLevel()+4);}
79   
80     transient PointSym editpoint = null;
81     transient JRadioButton inbutton = null;
82     transient JRadioButton outbutton = null;
83     transient JRadioButton notinout = null;
84     transient Comp parent = null;
85     transient LinePicker style = null;
86   
87     public void getPropertyEdit(Object[] edits,int slot,java.util.Set sub, Symbol par) {
88       if (sub.size()==1 && points.contains((PointSym)sub.toArray()[0])) {
89         editpoint = (PointSym)sub.toArray()[0];
90         edits[slot] = editpoint.pointPropertyEdit();
91         edits[slot-1] = ((Anchor)editpoint).anchorPropertyEdit();
92       }
93       else {
94         edits[slot] = new javax.swing.JLabel("No point selected.");
95         ((Component)edits[slot]).setName("Point");
96         edits[slot-1] = new javax.swing.JLabel("No point selected.");
97         ((Component)edits[slot-1]).setName("Arrow");
98       }
99   
100      style = new LinePicker(this,sub);
101      edits[slot-2] = style;
102      style.setName("Line Style");
103  
104      javax.swing.Box box = new javax.swing.Box(BoxLayout.Y_AXIS);
105      edits[slot-3] = box;
106      box.setName("Curve");
107  
108  //    System.out.println("curve parent = "+par);
109  //    new Throwable().printStackTrace(System.out);
110      if (par!=null && (par instanceof Comp)) {
111        parent = (Comp)par;
112  
113        ButtonGroup group = new ButtonGroup();
114  
115        notinout = new JRadioButton("Niether");
116        group.add(notinout);
117        box.add(notinout);
118  
119        inbutton = new JRadioButton("Inside");
120        group.add(inbutton);
121        box.add(inbutton);
122  
123        outbutton = new JRadioButton("Outside");
124        group.add(outbutton);
125        box.add(outbutton);
126  
127        if (parent.regions==null) parent.regions = new HashMap();
128        Integer stat = (Integer)parent.regions.get(this);
129  
130        if (stat == null) notinout.setSelected(true);
131        else if (stat.intValue() == IN) inbutton.setSelected(true);
132        else if (stat.intValue() == OUT) outbutton.setSelected(true);
133        parent.bounds=null;
134      } 
135  
136      super.getPropertyEdit(edits,slot-4,sub,parent);
137    }
138    
139    public void acceptPropertyEdit() {
140      if (parent!=null) {
141        if (notinout.isSelected()) parent.regions.remove(this);
142        else if (inbutton.isSelected()) parent.regions.put(this, new Integer(IN));
143        else if (outbutton.isSelected()) parent.regions.put(this, new Integer(OUT));
144        inbutton = null;
145        outbutton = null;
146        notinout = null;
147        parent = null;
148      }
149  
150      style.apply();
151      style = null;
152  
153      if (editpoint!=null) {
154        editpoint.acceptPropertyEdit();
155        editpoint = null;
156        path=null;
157  
158        hasarrows = false;
159        for (int i=0;i<points.size();i++)
160          if (((Anchor)points.elementAt(i)).arrow!=null)
161            hasarrows = true;
162  
163      }
164      super.acceptPropertyEdit();
165    }
166  
167    public void abandonPropertyEdit() {
168      editpoint = null;
169      style = null;
170      inbutton = null;
171      outbutton = null;
172      notinout = null;
173      parent = null;
174      super.abandonPropertyEdit();
175    }
176  
177    transient Rectangle2D bounds = null;
178  
179    public Rectangle2D getApproxBounds(AffineTransform trans) {
180      if (path==null) bounds = null;
181  
182      if (bounds==null)
183        bounds = getBounds(new AffineTransform());
184  
185      if (bounds==null) return(new Rectangle2D.Double());
186  
187      return (trans.createTransformedShape(bounds).getBounds2D());
188    }
189  
190    public Rectangle2D getBounds(AffineTransform trans) {
191      if (path==null) build();
192      if (path==null) return(null);
193  
194      return(trans.createTransformedShape(path).getBounds2D());
195  /*    if (points.size()==0) return(null);
196  
197      Point2D.Double pos = new Point2D.Double();
198      ((PointSym)points.elementAt(0)).position.transformOrigin(trans,pos);
199  //    trans.transform(
200      Point2D.Double min = (Point2D.Double)pos.clone();
201      Point2D.Double max = (Point2D.Double)pos.clone();
202  
203      for (int i=1;i<(points.size());i++) {
204        ((PointSym)points.elementAt(i)).position.transformOrigin(trans,pos);
205  //      trans.transform(((PointSym)points.elementAt(i)).pos,pos);;
206        if (pos.x<min.x) min.x = pos.x;
207        if (pos.y<min.y) min.y = pos.y;
208        if (pos.x>max.x) max.x = pos.x;
209        if (pos.y>max.y) max.y = pos.y;
210      }
211      return(new Rectangle2D.Double(min.x,min.y,max.x-min.x,max.y-min.y));
212  */
213    }
214  
215    public boolean selectProbe(Point2D pos,View view){
216      if (path==null) build();
217      if (path!=null /* && getApproxBounds(view.trans).contains(pos)*/)
218      return((new BasicStroke((float)(SELTOL/view.scale))).createStrokedShape(path).contains(pos));
219      else return(false);
220    }
221  
222    public boolean valid() {
223      return(points.size()>=2);
224    }
225  
226    public void showSelected(View view) {
227      for (int i=0;i<(points.size());i++)
228        ((PointSym)points.elementAt(i)).paint(view);    
229    }
230  
231    transient Point2D dragpos = null;
232  
233    public void startDrag(int command,Point2D pos,View view) {
234      dragpos = pos;
235      path = null;
236    }
237  
238    /**
239     * Drag the curve as a whole.<br>
240     */
241    public void drag(int command,Point2D pos,View view) {
242      for (int i=0;i<points.size();i++) {
243        Anchor point = (Anchor)points.elementAt(i);    
244        point.paint(view);    
245        point.guideUpdate();
246        point.position.setLocation(point.position.getTranslateX()+pos.getX()-dragpos.getX(),
247          		    point.position.getTranslateY()+pos.getY()-dragpos.getY());
248  //      point.guide[0].setLocation(point.guide[0].getX()+pos.getX()-dragpos.getX(),
249  //        			 point.guide[0].getY()+pos.getY()-dragpos.getY());
250  //      point.guide[1].setLocation(point.guide[1].getX()+pos.getX()-dragpos.getX(),
251  //        			 point.guide[1].getY()+pos.getY()-dragpos.getY());
252        point.guidestate = Anchor.LOCAL;
253        point.paint(view);
254      }
255      dragpos = pos;
256    }
257  
258    public void endDrag(int comand,Point2D pos,View view){
259      for (int i=0;i<points.size();i++)
260        ((Anchor)points.elementAt(i)).position.notifyChange();    
261    }
262  
263  /*  public void setAlignment(Alignment source,java.util.HashSet sub){
264      for (Iterator it=sub.iterator();it.hasNext();) {
265        Symbol cur = (Symbol)it.next();
266        if (points.contains(cur)) {
267          Anchor point = (Anchor)cur;
268          point.addAlignee(this);
269          point.setAlignment(source,null);     
270          if (!point.hasAlignment()) 
271            point.removeAlignee(this);
272        }
273      }
274    }
275  */
276  
277    public void alignmentChanged() {
278      path = null;
279    }
280  
281    static double GLENGTH = 0.4;
282  
283    public Collection getSelectable(){
284      return(points);
285    }
286  
287    public Class addType() {
288      return(Anchor.class);
289    }
290  
291    public void add(Symbol newmember,Set subs,View view) {
292      path = null;
293      Anchor p = (Anchor) newmember;
294      int pos = points.size();
295  
296      if (subs.size()==1) {
297        int k = points.indexOf(subs.toArray()[0]);
298        if (k>=0) pos = k+1;
299      }
300      else if (subs.size()==2) {
301        Object[] arr = subs.toArray();
302        int k = points.indexOf(arr[0]);
303        int j = points.indexOf(arr[1]);
304        if (j>k) {
305          int tmp = j;
306          j=k;
307          k=tmp;
308        }
309        if (j>=0 && k>=0 && ((j+1==k) || (closed && j==pos-1 && k==0)))
310          pos = j+1;
311      }
312  
313      if (points.size()>=1) {
314        if (pos>=atribs.size()) atribs.add(wholeatt);
315        else atribs.add(pos,wholeatt);
316        Point2D last = ((PointSym)points.elementAt(points.size()-1)).position.getTranslation();
317        p.guide[0] = new Point2D.Double(GLENGTH*last.getX() + (1-GLENGTH)*p.position.getTranslateX(),
318          			      GLENGTH*last.getY() + (1-GLENGTH)*p.position.getTranslateY());
319        p.guidestate = Anchor.GLOBAL;
320      }
321      if (points.size()>=1 &&
322          pos == points.size() &&
323          ((Anchor)points.elementAt(0)).position.distance(p.position.getTranslateX(),p.position.getTranslateY()) 
324          < Size.handle.getSize(view.mapscale)/view.scale) {
325        closed = true;
326      } else {
327        p.getAlignTransform().addAlignmentListener(this);
328        points.add(pos,p);
329      }
330    }
331  
332    public void delete(Symbol oldmember) {
333      int ind = points.indexOf(oldmember);
334      if (ind>=0) {
335        points.removeElementAt(ind);
336        if (closed || ind<points.size()) atribs.removeElementAt(ind);
337      }
338      path = null;
339    }
340  
341    transient GeneralPath changepath = null;
342    transient Shape transformedchangepath = null;
343  
344    public void change(Set changelist,Point2D pos,View view) {    
345      Graphics2D g = (Graphics2D)view.draw;
346  
347      int i;
348      boolean changed = false;
349      boolean[] changedsegs = new boolean[points.size()];
350      for (i=0;i<points.size();i++) changedsegs[i]=false;
351  
352      for (Iterator it=changelist.iterator();it.hasNext();) {
353        Symbol item = (Symbol)it.next();
354        if (item instanceof Anchor) {
355          int index = points.indexOf(item);
356          if (index>=0) {
357            if (index<points.size()-1 || closed) changedsegs[(index+1)%points.size()] = true;
358            if (index>0 || closed) changedsegs[index] = true;
359            changed = true;
360          }
361        }
362      }
363  
364      if (changed) { 
365  
366        if (changepath!=null)
367          g.draw(transformedchangepath);
368  
369          int lastdone = -1;
370          changepath = new GeneralPath();
371  
372          for (i=0;i<points.size();i++)
373            if (changedsegs[i]) {
374              Anchor first = (Anchor)points.elementAt((i-1+points.size())%points.size());
375              Anchor second = (Anchor)points.elementAt(i);
376              if (lastdone!=i-1) changepath.moveTo((float)first.position.getTranslateX(),(float)first.position.getTranslateY());
377              first.guideUpdate();
378              second.guideUpdate();
379              changepath.append(new LazyCubic(first.position,first.guide[1],
380          				    second.guide[0],second.position),false);
381              lastdone = i;
382            }
383  
384          transformedchangepath = view.trans.createTransformedShape(changepath);
385          g.draw(transformedchangepath);
386      }
387      path = null;
388    }
389  
390    public void build() {
391      bounds = null;
392      subpath = new Vector();
393      subpathatt = new Vector();
394      if (points.size()>=2) {
395        CurveAtt current = (CurveAtt) atribs.elementAt(0);
396        boolean changes = true;
397        int firstchange = 0;
398        int max = points.size();
399        if (!closed) max--;
400  
401        GeneralPath newsubpath=null;
402  
403        path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
404  
405        for (firstchange=1;firstchange<max && current.equals(atribs.elementAt(firstchange));firstchange++);
406        if (firstchange==max) {
407          changes=false;
408          firstchange = 0;
409          newsubpath = path;
410        } else {
411          newsubpath = new GeneralPath(GeneralPath.WIND_NON_ZERO);
412        }
413  
414        if (!closed) firstchange = 0;
415        
416        current = (CurveAtt) atribs.elementAt(firstchange);
417  
418        subpath.add(newsubpath);
419        subpathatt.add(current);
420  
421        for (int i=0;i<max;i++) {
422          Anchor first = (Anchor)points.elementAt((i+firstchange)%points.size());
423          first.guideUpdate();
424          Anchor second = (Anchor)points.elementAt((i+firstchange+1)%points.size());
425          second.guideUpdate();
426          LazyCubic newpiece = new LazyCubic(
427            first.position,
428            first.guide[1],
429            second.guide[0],
430            second.position);
431          path.append(newpiece,true);
432          if (changes) {
433            if (!current.equals(atribs.elementAt((i+firstchange)%points.size()))) {
434              current = (CurveAtt) atribs.elementAt((i+firstchange)%points.size());
435              newsubpath = new GeneralPath(GeneralPath.WIND_NON_ZERO);
436              subpath.add(newsubpath);
437              subpathatt.add(current);
438            }
439            newsubpath.append(newpiece,true);
440          }
441        }
442      }
443      else {
444        path=null;
445      }
446    }
447  
448    public void flip(Set subsel) {
449      path = null;
450      if (subsel== null ||subsel.isEmpty()) {
451        wholeatt.flip ^= true;
452        for (Iterator it = atribs.iterator();it.hasNext();)
453          ((CurveAtt)it.next()).flip ^= true;;
454      }
455      else {
456        int max = points.size()-1;
457        if (closed) max++;
458        for (int i=0;i<max;i++){
459          if (subsel.contains(points.elementAt(i)) &&
460              subsel.contains(points.elementAt((i+1)%points.size()))) {
461            atribs.setElementAt(new CurveAtt((CurveAtt)atribs.elementAt(i)),i);
462            ((CurveAtt)atribs.elementAt(i)).flip ^= true;
463          }
464        }
465      }
466    }
467  
468    public void setLineType(LineType type,Size thick,Color color,Set subsel) {
469      path = null;
470  
471      if (subsel== null || subsel.isEmpty()) {
472  //Next three lines may be redundant.
473        wholeatt.type = type;
474        wholeatt.color = color;
475        wholeatt.thickness = thick;
476        for (Iterator it = atribs.iterator();it.hasNext();) {
477          CurveAtt segatt = (CurveAtt)it.next();
478          segatt.type = type;
479          segatt.color = color;
480          segatt.thickness = thick;
481        }
482      }
483      else {
484        int max = points.size()-1;
485        if (closed) max++;
486        for (int i=0;i<max;i++){
487          if (subsel.contains(points.elementAt(i)) &&
488              subsel.contains(points.elementAt((i+1)%points.size()))) {
489            atribs.setElementAt(new CurveAtt((CurveAtt)atribs.elementAt(i)),i);
490            ((CurveAtt)atribs.elementAt(i)).type = type;
491            ((CurveAtt)atribs.elementAt(i)).color = color;
492            ((CurveAtt)atribs.elementAt(i)).thickness = thick;
493          }
494        }
495      }
496  
497      CurveAtt newatt = new CurveAtt(wholeatt);
498      newatt.type = type;
499      newatt.thickness = thick;
500      newatt.color = color;
501      if (!newatt.equals(wholeatt)) wholeatt = newatt;
502    }
503  
504    public void setFill(Color c) {
505      paint = c;
506      fill = null;
507    }
508  
509    public void setFill(Fill fill) {
510      this.fill = fill;
511      paint = null;
512    }
513  
514    public Area getIn(View view) {
515      if (!getApproxBounds(view.trans).intersects(view.getClipBounds())) return(new Area());
516      if (path==null) return(new Area());
517  
518      return(new Area(view.trans.createTransformedShape(path)));
519    }
520  
521    public void paint(View view) {
522      if (!view.visible.isMember(this)) return;
523  
524      if (path==null) build();
525  
526      if (!getApproxBounds(view.trans).intersects(view.getClipBounds())) return;
527  
528      if (path!=null) {
529        Graphics2D gr = (Graphics2D)view.draw;
530  
531        if (fill!=null|| paint!=null) {
532          Area inshape = getIn(view);
533          Paint pa = gr.getPaint();
534  
535          if (fill!=null) gr.setPaint(fill.getPaint(view,inshape));
536  
537          if (paint!=null) gr.setPaint(paint);
538          
539          gr.fill(inshape);
540  
541          gr.setPaint(pa);
542        }
543  
544  //      Color oldcolor = gr.getColor();
545  
546        for (int i=0;i<subpath.size();i++) {
547          CurveAtt current = (CurveAtt)subpathatt.elementAt(i);
548          if (current.type!=null) {
549            LineType type = current.type;
550            if (view.mod == Stacking.OVER) {
551              if (type.name.startsWith("Wall"))
552                type = new LineType("temp",LineType.DASHED);
553              else type = null;
554            }
555            else if (view.mod == Stacking.UNDER) {
556              if (type.name.startsWith("Wall"))
557                type = new LineType("temp",LineType.DOTTED);
558              else type = null;
559            }
560            
561            if (type!=null) {
562              SavableStroke stroke = type.getStroke(current.thickness.getSize(view.mapscale));
563              if (stroke!=null) {
564                Stroke st = view.draw.getStroke();
565                Color color = view.draw.getColor();
566                view.draw.setStroke(stroke);
567                view.draw.setColor(current.color);
568                stroke.setFlip(current.flip);
569                Shape shape = view.trans.createTransformedShape((GeneralPath)subpath.elementAt(i));
570                view.draw.draw(shape);
571                view.draw.setStroke(st);
572                view.draw.setColor(color);
573              }
574            }
575          }
576        }
577  
578        if (view.mod==0) showArrows(view);
579  
580      }
581      changepath=null;
582    }
583  
584    void showArrows(View view) {
585      if (hasarrows) {
586        for (int i=0;i<points.size();i++) {
587        Anchor point = (Anchor)points.elementAt(i);
588        CurveAtt props = ((closed||i<points.size()-1)?(CurveAtt)atribs.elementAt(i):(CurveAtt)atribs.elementAt(i-1));
589        if (point.arrow!=null)
590          point.arrow.draw(view,point,(point.arrowreverse?0:1),props);
591        }
592      }
593    }
594  
595    public void showAlignee(View view) {
596      for (Iterator it = points.iterator();it.hasNext();) {
597        ((AlignmentListener)it.next()).showAlignee(view);
598      }
599    }
600  
601    public Point2D showAligner(View view) {
602      return(null);
603    }  
604  }
605