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.awt.event.*;
7    import java.awt.*;
8    import javax.swing.*;
9    import java.awt.geom.*;
10   
11   /**
12    *A control point on a Curve.
13    *Consists of a point and two tangent directions determined by guide points.
14    */
15   public class Anchor extends PointSym implements DragableSymbol {
16   
17     private static final long serialVersionUID = Version.getSUID();
18   
19   //  static int NONE = -3;
20     /**Indicates smooth mode.
21      *Where the guide points are slaved to each other so that the curve stays smooth*/
22   
23     static int SMOOTH = -2;
24   
25     /**Indicates that the anchor as a whole is selected.
26      *As when the central point is clicked */
27     static int ALL = -1;
28   
29     /**Indictes that the first guide point is selected. */
30     static int GUIDE0 = 0;
31   
32     /**Indictes that the second guide point is selected. */
33     static int GUIDE1 = 1;
34   
35     /** Indicates guide is valid but not guidelocal */
36     static int GLOBAL = 0;
37   
38     /** Indicates guidelocal is valid but not guide */
39     static int LOCAL = 1;
40   
41     /** Indicates guide and guidelocal are valid and consistant */
42     static int CONSISTANT = 2;
43   
44     /**The two guide points.
45      *must be transient because Point2D is not Serializable*/
46     transient public Point2D[] guide = new Point2D[2];
47   
48     /**The two guide points in local coords.
49      *must be transient because Point2D is not Serializable
50      *Except for storage in a file, guide is derived from guidelocal */
51     transient public Point2D[] guidelocal = new Point2D[2];
52    
53     /** Indicates which form of the guide point is official.
54      * Posible values are LOCAL, GLOBAL, or CONSISTANT.
55      * Before using guide points use guideUpdate() to make it CONSISTANT.
56      */
57     transient int guidestate = LOCAL;
58   
59   //  transient double[] length = new double[2];
60     /**If true, curve has a kink at this point. */
61     boolean kink=false;
62   
63     /** The arrow head to draw at this point.
64      */
65     public Arrow arrow = null;
66   
67     /** Reverse the direction of the arrow?.
68      */
69     public boolean arrowreverse = false;
70   
71     /** The selection state of this anchor.
72      * Possible values are SMOOTH, ALL, GUIDE0, and GUIDE1.
73      */
74     int selmode;
75   
76     /**Read the anchor and re-constitute its transient variables.
77      *That is, its goude points. */
78     private void readObject(java.io.ObjectInputStream stream)
79         throws java.io.IOException,java.lang.ClassNotFoundException {
80       stream.defaultReadObject();
81       guide = new Point2D[2];
82       guidelocal = new Point2D[2];
83       guidelocal[0] = new Point2D.Double();
84       guidelocal[1] = new Point2D.Double();
85       guide[0] = new Point2D.Double(stream.readDouble(),stream.readDouble());
86       guide[1] = new Point2D.Double(stream.readDouble(),stream.readDouble());
87       guidestate = GLOBAL;
88     }
89   
90     /**Writes the anchor and the positions of its guide points. */
91     private void writeObject(java.io.ObjectOutputStream stream)
92         throws java.io.IOException {
93       stream.defaultWriteObject();
94       guideUpdate();
95       stream.writeDouble(guide[0].getX());
96       stream.writeDouble(guide[0].getY());
97       stream.writeDouble(guide[1].getX());
98       stream.writeDouble(guide[1].getY());
99     }
100  
101    /** Take notice of change in position,<br>
102     * caused by alignment.
103     */
104    public void alignmentChanged() {
105      guidestate = LOCAL;
106    }
107  
108    Point2D.Double morphpos0 = null;
109    Point2D.Double morphpos1 = null;
110  
111    public void prepareForSurveyUpdate(java.util.List segments) {
112  //    System.out.println("anchor prep");
113  //    if (true) return;
114      guideUpdate();
115      super.prepareForSurveyUpdate(segments);
116      if (morphsegment!=null) {
117        morphpos0 = new Point2D.Double();
118        morphsegment.map.btrans(guide[0],morphpos0);
119        morphpos1 = new Point2D.Double();
120        morphsegment.map.btrans(guide[1],morphpos1);
121      }
122    }
123  
124    /**
125     *Called when the survey has changed.<br>
126     */
127    public void useSurveyUpdate() {
128  //    guidestate = LOCAL;
129  //    if (true) return;
130  
131      if (morphsegment!=null) {
132        Point2D.Double res = new Point2D.Double();
133        morphsegment.map.ftrans(morphpos0,res);
134        guide[0]=res;
135        morphpos0 = null;
136        res = new Point2D.Double();
137        morphsegment.map.ftrans(morphpos1,res);
138        guide[1] = res;
139        morphpos1 = null;
140      }
141  
142      super.useSurveyUpdate();
143      guidestate = GLOBAL;
144  
145  
146  
147  /*
148      guideUpdate();
149      guidestate = GLOBAL;
150      position.setTransform(1,0,position.getTranslateX(),position.getTranslateY());
151      guideUpdate();
152  */
153    }
154  
155    /** sets the local and  global coords to match. 
156     *Which is taken as primary depends on guidestate */
157    public void guideUpdate() {
158      if (guidestate==GLOBAL) {
159        try {
160          position.inverseTransform(guide[0],guidelocal[0]);
161          position.inverseTransform(guide[1],guidelocal[1]);
162        } catch (NoninvertibleTransformException ex) {
163          ErrorLog.log("Internal error in guideUpdate");
164          ErrorLog.exception(ex);
165          return;
166        }
167      }
168      else if (guidestate==LOCAL) {
169        position.transform(guidelocal[0],guide[0]);
170        position.transform(guidelocal[1],guide[1]);
171      }
172      guidestate=CONSISTANT;
173    }
174  
175    /**Creates a new anchor at the specified location.
176     *The new anchor will be in SMOOTH mode, and its guide points
177     *will be at its central point (the place that was clicked) */
178    public Anchor(Point2D where,View view,Object arg) {
179      super(where,view,arg);
180      guidelocal[0]= new Point2D.Double(0,0);
181      guidelocal[1]= new Point2D.Double(0,0);
182      guide[0]= (Point2D)position.getTranslation();
183      guide[1]= (Point2D)position.getTranslation();
184  //    guide[0]= new Point2D.Double(0,0);
185  //    guide[1]= new Point2D.Double(0,0);
186      guidestate = GLOBAL;
187      selmode = SMOOTH;
188    }
189  /*
190    public void setAlignment(Alignment source,java.util.HashSet sub){
191      guideUpdate();
192      position.addAlignmentListener(this);
193      position.setAlignment(source,null);     
194      if (!position.hasAlignment()) 
195        position.removeAlignee(this);
196  //    System.out.println("anchor = "+this);
197  //    position.listAlignees();
198    }
199  */
200  
201    public int getLevel() {return(super.getLevel()+1);}
202  
203    public void getPropertyEdit(Object[] edits,int slot,java.util.Set sub, Symbol parent) {
204      edits[slot] = anchorPropertyEdit();
205  //    ((Component)edits[slot]).setName("Arrow");
206      super.getPropertyEdit(edits,slot-1,sub,parent);
207    }
208  
209    transient JComboBox arrowfield;
210    transient JCheckBox reversebox;
211  
212    java.awt.Container anchorPropertyEdit() {
213      JPanel res = new JPanel();
214      res.setName("Arrow");
215      res.setLayout(new BoxLayout(res,BoxLayout.Y_AXIS));
216  
217      arrowfield = new JComboBox(Arrow.getAll(Arrow.class));
218      arrowfield.setSelectedItem(arrow);
219      res.add(arrowfield);
220      reversebox = new JCheckBox("Reverse",arrowreverse);
221      res.add(reversebox);
222  
223      return(res);
224    }
225  
226    public void acceptPropertyEdit() {
227      if (arrowfield!=null) {
228  
229        arrow = (Arrow)arrowfield.getSelectedItem();
230        if (arrow==Arrow.none) arrow = null;
231        arrowreverse = reversebox.isSelected();
232  
233        arrowfield = null;
234        reversebox = null;
235      }
236      super.acceptPropertyEdit();
237    }
238  
239    public void abandonPropertyEdit() {
240      arrowfield = null;
241      reversebox = null;
242      super.abandonPropertyEdit();
243    }
244  
245    /**Determines if a mouse press selects this anchor.
246     *If so remembers which part of the anchor was pressed (by setting selmode), so that
247     *the same part can be dragged if needed*/
248    public boolean selectProbe(Point2D where,View view){
249      double BOXSIZE = Size.handle.getSize(view.mapscale);
250      if (position.distance(where)<BOXSIZE/view.scale) {
251        selmode = ALL;
252        return(true);
253      }
254      guideUpdate();
255      if (guide[0].distance(where)<BOXSIZE/view.scale) {
256        selmode=GUIDE0;
257        return(true);
258      }
259      if (guide[1].distance(where)<BOXSIZE/view.scale) {
260        selmode=GUIDE1;
261        return(true);
262      }
263      return(false);
264    }
265  
266    /**Start dragging this Anchor. */
267    public void startDrag(int command,Point2D where,View view) {
268      if ((command&CompPane.SMOOTH)!=0) {
269        if (selmode== ALL && kink) {
270          selmode = SMOOTH;
271          kink = false;
272        }
273        else if (selmode>=0) kink = true;
274      }
275      dragpoint = where;
276    }
277  
278    /**Drag the Anchor.
279     *Which part will actually move depends on the valeue of selmode*/
280    public void drag(int command,Point2D where,View view) {
281      showSelected(view);
282      if (selmode==SMOOTH) {   //smoothing a corner
283        guide[1].setLocation(where.getX()-dragpoint.getX()+guide[1].getX(),
284          		   where.getY()-dragpoint.getY()+guide[1].getY());
285        guide[0].setLocation(2*position.getTranslateX()-guide[1].getX(),
286          		   2*position.getTranslateY()-guide[1].getY());
287        guidestate = GLOBAL;
288      }
289      else if (selmode==ALL) {
290        guidestate = LOCAL;
291        position.setLocation(position.getTranslateX()+(where.getX()-dragpoint.getX())/position.scale,
292          	           position.getTranslateY()+(where.getY()-dragpoint.getY())/position.scale);
293      }
294      else if (selmode>=GUIDE0) {
295        Point2D drager = guide[selmode-GUIDE0];
296        Point2D dragee = guide[1-(selmode-GUIDE0)];
297        double length1 = position.distance(dragee);
298        drager.setLocation(drager.getX()+where.getX()-dragpoint.getX(),
299          		 drager.getY()+where.getY()-dragpoint.getY());
300        double length0 = position.distance(drager);
301        if (!kink)
302          dragee.setLocation(position.getTranslateX()+length1*(position.getTranslateX()-drager.getX())/length0,
303          		   position.getTranslateY()+length1*(position.getTranslateY()-drager.getY())/length0);
304        guidestate = GLOBAL;
305      }
306      dragpoint = where;
307      showSelected(view);
308    }
309  
310    /**
311     *Ends drag.
312     *Alerts alignees that may depend on this point.
313     */
314    public void endDrag(int comand,Point2D pos,View view){
315      position.notifyChange();
316    }
317  
318  
319    /**
320     *Draw the drag box of the Anchor as selected.
321     *The box is shown filled with its guide points. Draw the guide point that can be
322     *dragged (if any) as a filled circle.
323     */
324    public void showSelected(View view) {
325      guideUpdate();
326  
327      Point2D tpos = new Point2D.Double();
328      Point2D tguide = new Point2D.Double();
329  
330      int BOXSIZE = (int)Size.handle.getSize(view.mapscale);
331  
332      position.transformOrigin(view.trans,tpos);
333  //    view.trans.transform(position,tpos);
334      view.draw.fillRect((int)tpos.getX()-BOXSIZE,(int)tpos.getY()-BOXSIZE,2*BOXSIZE,2*BOXSIZE);
335  
336      for (int i=0;i<2;i++) {
337        view.trans.transform(guide[i],tguide);
338  
339        view.draw.drawLine((int)tpos.getX(),(int)tpos.getY(),(int)tguide.getX(),(int)tguide.getY());
340  
341        if (selmode==i)
342          view.draw.fillOval((int)tguide.getX()-BOXSIZE,(int)tguide.getY()-BOXSIZE,2*BOXSIZE,2*BOXSIZE);
343        else
344          view.draw.drawOval((int)tguide.getX()-BOXSIZE,(int)tguide.getY()-BOXSIZE,2*BOXSIZE,2*BOXSIZE);
345      }
346    }
347  
348    /**Paint the Anchor.
349     *The only visible part of an anchor is it's drag box. When not
350     *selected anchors are only visible by their effect on the curve*/
351    public void paint(View view) {
352      guideUpdate();
353      if (!view.visible.isMember(this)) return;
354      int BOXSIZE = (int)Size.handle.getSize(view.mapscale);
355      Point2D tpos = new Point2D.Double();
356      position.transformOrigin(view.trans,tpos);
357  //    view.trans.transform(position,tpos);
358      view.draw.drawRect((int)tpos.getX()-BOXSIZE,(int)tpos.getY()-BOXSIZE,2*BOXSIZE,2*BOXSIZE);
359      view.trans.transform(guide[0],tpos);
360      view.draw.drawOval((int)tpos.getX()-BOXSIZE,(int)tpos.getY()-BOXSIZE,2*BOXSIZE,2*BOXSIZE);
361      view.trans.transform(guide[1],tpos);
362      view.draw.drawOval((int)tpos.getX()-BOXSIZE,(int)tpos.getY()-BOXSIZE,2*BOXSIZE,2*BOXSIZE);
363    }
364  }
365  
366  
367