1    /*
2    Copyright 2001 by Ralph Hartley
3    This software is licenced under the terms of the
4    Gnu Public Licence
5    */
6    import java.awt.geom.*;
7    import java.awt.Graphics2D;
8    import javax.swing.*;
9    import java.util.*;
10   
11   public abstract class Sub extends Symbol implements DragableSymbol,Aligner {
12   
13     private static final long serialVersionUID = Version.getSUID();
14   
15     static double MINR = 2;
16   
17     public ViewTransform relativepos = null;// obsolete 1/27/00 new ViewTransform(ViewTransform.PREMULTIPLY);
18     public PointSym anchorpoint = new PointSym();
19     
20     public SavableRectangle2D box;
21   
22     transient public ViewTransform pos = null;
23     transient public Point2D[] corners;
24   
25     public double handley = 0;
26     public double handlex = 0;
27   
28     transient Point2D origin = new Point2D.Double();
29   
30     transient boolean brandnew = true;
31   //  public boolean lockrotation = false;
32     public boolean editaspect = false;
33   //  public boolean lockscale = false;
34     public boolean editrotation = true;
35     public boolean editscale = true;
36     public boolean showcorners = true;
37   //  public double scale = 1.0;
38   //  public double rotation = 0.0;
39     public boolean sizewidth = true;
40   
41     public double subaspect = 1;
42     public Size size = Size.none;
43   
44     transient boolean located = false;
45   
46     public void copy(Sub that) {
47       handley = that.handley;
48       handlex = that.handlex;
49   
50       editaspect = that.editaspect;
51       editrotation = that.editrotation;
52       editscale = that.editscale;
53       showcorners = that.showcorners;
54       sizewidth = that.sizewidth;
55   
56       subaspect = that.subaspect;
57       size = that.size;
58     }
59   
60     public Sub(){
61       anchorpoint.getAlignTransform().addAlignmentListener(this);
62     }
63   
64     public Sub(Point2D where,View view,Object arg) {
65       anchorpoint.getAlignTransform().addAlignmentListener(this);
66       if (arg instanceof Rectangle2D) {
67         located=false;
68         box = new SavableRectangle2D((Rectangle2D)arg);
69       }
70     }
71   
72   //  public ViewTransform defaultPosition() {
73   //    return(null);
74   //  }
75   
76     public void alignmentChanged(){
77       located = false;
78     }
79   
80   //  transient JCheckBox rotlockf=null;
81   //  transient JCheckBox scalelockf=null;
82     transient JRadioButton widthf=null;
83     transient JRadioButton heightf=null;
84     transient JComboBox sizefield=null;
85   //  transient PrefTrans trans=null;
86     transient JComboBox editmodef = null;
87   
88     transient JRadioButton topf=null;
89     transient JRadioButton botf=null;
90     transient JRadioButton leftf=null;
91     transient JRadioButton rightf=null;
92   
93     /**
94      *Get the edit modes that are supported.
95      */
96     protected Vector getEditModes() {
97       Vector res = new Vector();
98       res.add("Translate");
99       res.add("Rotate");
100  //    if (size==null || size.equals(Size.none)) {
101        res.add("Scale");
102        res.add("Scale & Rotate");
103  //    }
104      return(res);
105    }
106  
107    public int getLevel() {return(super.getLevel()+1);}
108  
109    public void getPropertyEdit(Object[] edits,int slot,Set sub,Symbol parent) {
110      located=false;
111      locate();
112      JPanel res = new JPanel();
113      edits[slot] = res;
114      ((java.awt.Component)edits[slot]).setName("Group");
115      res.setLayout(new BoxLayout(res,BoxLayout.Y_AXIS));
116  
117      JPanel row = new JPanel();
118      row.setLayout(new BoxLayout(row,BoxLayout.X_AXIS));
119  
120      Object[] editmodes = getEditModes().toArray();
121      editmodef = new JComboBox(editmodes);
122      for (int i=0;i<editmodes.length;i++)
123        if ((editscale || ((String)editmodes[i]).indexOf("Scale")<0) &&
124            (editrotation || ((String)editmodes[i]).indexOf("Rotate")<0) &&
125            (editaspect || ((String)editmodes[i]).indexOf("Aspect")<0))
126          editmodef.setSelectedIndex(i);
127  
128      JPanel modebox = new JPanel();
129      modebox.add(new JLabel("Edit Mode"));
130      modebox.add(editmodef);
131      row.add(modebox);
132  
133  //    rotlockf = new JCheckBox("Lock Rotation",lockrotation);
134  //    scalelockf = new JCheckBox("Lock Scale",lockscale);
135  //    row.add(rotlockf);
136  //    row.add(scalelockf);
137      res.add(row);
138  
139  //    trans = new PrefTrans(position,xorig,yorig);
140  //    res.add(trans.getBarePane(null));
141      res.add(anchorpoint.pointPropertyEdit());
142  
143      row = new JPanel();
144      row.setLayout(new BoxLayout(row,BoxLayout.X_AXIS));
145      row.add(new JLabel("Handle:"));
146      JPanel col = new JPanel();
147      col.setLayout(new java.awt.GridLayout(1,6));
148      ButtonGroup group = new ButtonGroup();
149  
150      topf = new JRadioButton("Top",handley==1);
151      group.add(topf);
152      col.add(topf);
153      JRadioButton midf = new JRadioButton("Mid",handley==0);
154      group.add(midf);
155      col.add(midf);
156      botf = new JRadioButton("Bottom",handley==-11);
157      group.add(botf);
158      col.add(botf);
159  
160      group = new ButtonGroup();
161      leftf = new JRadioButton("Left",handlex==-1);
162      group.add(leftf);
163      col.add(leftf);
164      midf = new JRadioButton("Mid",handlex==0);
165      group.add(midf);
166      col.add(midf);
167      rightf = new JRadioButton("Right",handlex==1);
168      group.add(rightf);
169      col.add(rightf);
170  
171      row.add(col);
172      res.add(row);    
173  
174      row = new JPanel();
175      row.setLayout(new BoxLayout(row,BoxLayout.X_AXIS));
176      widthf = new JRadioButton("Width",sizewidth);
177      heightf = new JRadioButton("Height",!sizewidth);
178      
179      row.add(widthf);
180      row.add(heightf);
181  
182      group = new ButtonGroup();
183      group.add(widthf);
184      group.add(heightf);
185  
186      row.add(new JLabel("Size "));
187      sizefield = new JComboBox(Size.getAll(Size.class));
188      sizefield.setSelectedItem(size);
189      row.add(sizefield);
190  
191      res.add(row);
192  
193      super.getPropertyEdit(edits,slot-1,sub,parent);
194    }
195  
196    public void acceptPropertyEdit() {
197      if (widthf!=null) {
198        String editmode = (String)editmodef.getSelectedItem();
199        editscale = editmode.indexOf("Scale")>=0;
200        editrotation = editmode.indexOf("Rotate")>=0;
201        editaspect = editmode.indexOf("Aspect")>=0;
202  
203  //      lockrotation = rotlockf.isSelected();
204  //      lockscale = scalelockf.isSelected();
205  
206        if (topf.isSelected()) handley = 1;
207        else if (botf.isSelected()) handley = -1;
208        else handley = 0;
209  
210        if (leftf.isSelected()) handlex = -1;
211        else if (rightf.isSelected()) handlex = 1;
212        else handlex = 0;
213  
214        sizewidth = widthf.isSelected();
215        size = (Size)sizefield.getSelectedItem();
216        if (size!=null && !size.equals(Size.none)) editscale = false;
217        anchorpoint.acceptPropertyEdit();
218  //      trans.save();
219        clear();
220      }
221      super.acceptPropertyEdit();
222      located = false;
223      reshape();
224  //    positionAnchor();
225    }
226  
227    void clear() {
228      editmodef = null;
229      sizefield = null;
230  //    rotlockf = null;
231  //    scalelockf = null;
232      widthf = null;
233      heightf = null;
234      topf = null;
235      botf = null;
236      leftf = null;
237      rightf = null;
238  //    trans=null;
239    }
240  
241    public void abandonPropertyEdit() {
242      clear();
243      anchorpoint.abandonPropertyEdit();
244      super.abandonPropertyEdit();
245    }
246  
247    void init(Point2D where,View view) {
248      anchorpoint.position.setLocation(where);
249      located=false;
250  
251      locate();
252    }
253  
254    public ViewTransform getAlignTransform() {
255      locate();
256      return(anchorpoint.position);
257    }
258  
259  //  static boolean originpre = true;   //debug only
260  
261    public void locate() {
262      if (located) return;
263      located = true;
264  
265      if (box==null) {
266        box = new SavableRectangle2D(-0.5,-0.5,1,1);
267        reshape();
268      }
269  
270      origin = new Point2D.Double(box.getX()+(handlex+1)*box.getWidth()/2,
271          			box.getY()+(handley+1)*box.getHeight()/2);
272  
273      if (pos==null) pos = new ViewTransform();
274  
275      pos.setToTranslation(-origin.getX(),-origin.getY());
276  //    if (originpre)
277      pos.preConcatenate(anchorpoint.position);
278  //    else
279  //      pos.concatenate(anchorpoint.position);
280  
281      if (corners==null) {
282        corners = new Point2D.Double[4];
283        for (int i=0;i<4;i++) corners[i] = new Point2D.Double();
284      }
285  
286      corners[0].setLocation(box.getMinX(),box.getMinY());
287      corners[1].setLocation(box.getMaxX(),box.getMinY());
288      corners[2].setLocation(box.getMaxX(),box.getMaxY());
289      corners[3].setLocation(box.getMinX(),box.getMaxY());
290  
291      for (int i=0;i<4;i++) pos.transform(corners[i],corners[i]);
292  
293  //    System.out.println("this = "+this+" located at "+pos);
294    }
295  
296  //  void positionAnchor() {
297  //    anchorpoint.position.setLocation(xorig,yorig);
298  //    anchorpoint.position.preConcatenate(position);
299  //    position.transform(anchorpoint.position,anchorpoint.position);
300  //  }
301  
302    private void readObject(java.io.ObjectInputStream stream)
303      throws java.io.IOException,java.lang.ClassNotFoundException {
304      stream.defaultReadObject();
305      brandnew = false;
306  
307      if (relativepos!=null) {
308        anchorpoint.position = relativepos;
309        relativepos = null;
310      }
311  
312      if ((this instanceof Page) &&
313          ((Page)this).aspect != -1 &&
314          ((Page)this).aspect != 0) {
315        subaspect = ((Page)this).aspect;
316        ((Page)this).aspect = -1;
317      }
318  
319      if (subaspect==0)
320        subaspect = 1.0;
321  
322      if (size==null) size=Size.none;
323  
324      located = false;
325    }
326  
327    public Rectangle2D getBounds(AffineTransform trans) {
328      locate();
329  
330      Point2D.Double corn = new Point2D.Double();
331      trans.transform(corners[0],corn);;
332      Point2D.Double min = (Point2D.Double)corn.clone();
333      Point2D.Double max = (Point2D.Double)corn.clone();
334  
335      for (int i=1;i<4;i++) {
336        trans.transform(corners[i],corn);;
337        if (corn.x<min.x) min.x = corn.x;
338        if (corn.y<min.y) min.y = corn.y;
339        if (corn.x>max.x) max.x = corn.x;
340        if (corn.y>max.y) max.y = corn.y;
341      }
342      return(new Rectangle2D.Double(min.x,min.y,max.x-min.x,max.y-min.y));
343    }
344  
345    public java.awt.Shape getShape(AffineTransform trans) {
346      locate();
347  
348      return(trans.createTransformedShape(pos.createTransformedShape(box)));
349    }
350  
351    transient double csize = 0;
352  
353    public void checkSize(View view) {
354      if (size !=null && size!=Size.none) {
355        if (origin==null) locate();
356        csize = getScale(size,view);
357        if (csize!=0) anchorpoint.position.setScale(csize/view.scale,0,0);
358        located = false;
359      }
360    }
361  
362    public void paint(View view) {
363      if (!view.visible.isMember(this)) return;
364  
365      checkSize(view);
366      if (csize==0) return;
367  
368      locate();
369      view.push(pos);
370  //    if (lockrotation) // && ((position.angle + view.angle)!= rotation)) {
371  //      view.setRotation(anchorpoint.position.angle - view.angle,
372  //        	       origin.getX(),origin.getY());
373  
374  
375  //    if (lockscale) //&& position.scale * view.scale!= scale) {
376  //      view.setScale(anchorpoint.position.scale/view.scale,
377  //        	    origin.getX(),origin.getY());
378  
379      paintContents(view);
380      view.pop();
381    }
382  
383    public double getScale(Size size,View view) {
384  //    System.out.println(" size = "+size);
385  //    System.out.println(" view.mapscale = "+view.mapscale);
386  //    System.out.println(" width = "+box.getWidth());
387  //    System.out.println(" height = "+box.getHeight());
388  //    System.out.println("res = "+size.getSize(view.mapscale)/(sizewidth?box.getWidth():box.getHeight()));
389      return(size.getSize(view.mapscale)/(sizewidth?box.getWidth():box.getHeight()));
390    }
391  
392    public abstract void paintContents(View view);
393  
394    Point2D getPolar(Point2D p,View v) {
395      double r = anchorpoint.position.distance(p);
396  //    if (r<(MINR/v.scale)) return(new Point2D.Double(MINR/v.scale,0));
397      if (r==0) return(new Point2D.Double(MINR/v.scale,0));
398      else return(new Point2D.Double(r,-Math.atan2(p.getX()-anchorpoint.position.getTranslateX(),
399          					 p.getY()-anchorpoint.position.getTranslateY())));
400    }
401  
402    public boolean selectProbe(Point2D pos,View view){
403  //    System.out.println("this = "+this+" probe at "+anchorpoint.position+" where = "+pos);
404      brandnew = false;
405      if (anchorpoint.selectProbe(pos,view)) return(true);
406      double BOXSIZE = Size.handle.getSize(view.mapscale);
407      for (int i=0;i<4;i++)
408        if (pos.distance(corners[i].getX(),corners[i].getY())<BOXSIZE/view.scale) return(true);
409  //    System.out.println("this = "+this+" probe false");
410      return(false);
411    }
412  
413    transient boolean cornerdrag = false;
414    transient Point2D dragpos = null;
415    
416    public void startDrag(int command,Point2D where,View view){
417  //    System.out.println("this = "+this+" start drag at "+anchorpoint.position+" where = "+where);
418      cornerdrag = false;
419      int i=4;
420  
421      if (brandnew) {
422        checkSize(view);
423        showSelected(view);
424        where = corners[0];
425      }
426  
427      if ((brandnew || !anchorpoint.selectProbe(where,view)) && (editrotation || editscale || editaspect))
428        for (i=0;i<4;i++)
429          if (where.distance(corners[i].getX(),corners[i].getY())<Size.handle.getSize(view.mapscale)/view.scale ||
430              brandnew) {
431            cornerdrag = true;
432            if (editaspect)
433              dragpos = new Point2D.Double(where.getX()-anchorpoint.position.getTranslateX(),
434          				 where.getY()-anchorpoint.position.getTranslateY());
435            else {
436              dragpos = getPolar(where,view);
437            }
438            break;
439          }
440      if (i==4) dragpos = where;
441    }
442  
443    public void drag(int command,Point2D where,View view){
444  //    System.out.println("this = "+this+" drag at "+anchorpoint.position+" where = "+where);
445      showSelected(view);
446      located = false;
447      if (cornerdrag) {
448        Point2D newpos = null;
449        if (editaspect) {
450          double c,s;
451          newpos = new Point2D.Double(where.getX()-anchorpoint.position.getTranslateX(),
452          			    where.getY()-anchorpoint.position.getTranslateY());
453          if (!sizewidth&& size!=null && !size.equals(Size.none)) {
454            c = -Math.cos(/*view.angle*/-anchorpoint.position.angle);
455            s =  Math.sin(/*view.angle*/-anchorpoint.position.angle);
456            subaspect /= (c*newpos.getX()+s*newpos.getY())/(c*dragpos.getX()+s*dragpos.getY());
457            anchorpoint.position.setScale(anchorpoint.position.scale*(c*newpos.getX()+s*newpos.getY())/
458          				(c*dragpos.getX()+s*dragpos.getY()),0,0);//origin.getX(),origin.getY());
459          }
460          else {
461            s = Math.cos(/*view.angle*/-anchorpoint.position.angle);
462            c = Math.sin(/*view.angle*/-anchorpoint.position.angle);
463            double fact;
464            if (editscale) {
465              fact = (-s*newpos.getX()+c*newpos.getY())/
466                (-s*dragpos.getX()+c*dragpos.getY());
467              anchorpoint.position.setScale(anchorpoint.position.scale*fact
468          				  ,0,0);//origin.getX(),origin.getY());
469            } else fact = 1.0;
470            subaspect *= (c*newpos.getX()+s*newpos.getY())/(c*dragpos.getX()+s*dragpos.getY())/fact;
471          }
472          reshape();
473        }
474        else {
475          newpos = getPolar(where,view);
476            if (editscale)
477              anchorpoint.position.setScale(anchorpoint.position.scale*newpos.getX()/dragpos.getX(),
478          				  0,0);//origin.getX(),origin.getY());
479            if (editrotation)
480              anchorpoint.position.setRotation(anchorpoint.position.angle+newpos.getY()-dragpos.getY(),
481          				     0,0);//origin.getX(),origin.getY());
482        }
483        dragpos=newpos;
484      } else {
485        anchorpoint.position.preConcatenate(new AffineTransform(1,0,0,1,
486          						      where.getX()-dragpos.getX(),
487          						      where.getY()-dragpos.getY()));
488        dragpos=where;
489      }
490      showSelected(view);
491    }
492  
493    public void endDrag(int comand,Point2D pos,View view){
494      located=false;
495  //    System.out.println("this = "+this+" end-drag at "+pos);
496      anchorpoint.position.notifyChange();
497    }
498  
499    public void showSelected(View view) {
500      int BOXSIZE = (int)Size.handle.getSize(view.mapscale);
501      Point2D tpos = new Point2D.Double();
502      Point2D cpos = new Point2D.Double();
503      Graphics2D g;
504      int arrows=0;
505      double c=0,s=0;
506      g = view.draw;
507      locate();
508      anchorpoint.position.transformOrigin(view.trans,cpos);
509  //    view.trans.transform(anchorpoint.position,cpos);
510      g.drawRect((int)(cpos.getX()-BOXSIZE),(int)(cpos.getY()-BOXSIZE),2*BOXSIZE,2*BOXSIZE);
511  //    if (showcorners)
512      for (int i=0;i<4;i++) {
513        arrows = 0;
514        view.trans.transform(corners[i],tpos);
515        g.drawRect((int)(tpos.getX()-BOXSIZE),(int)(tpos.getY()-BOXSIZE),2*BOXSIZE,2*BOXSIZE);
516        if (editaspect) {
517          arrows = (editscale?2:1);
518          if (!sizewidth&& size!=null && !size.equals(Size.none)) {
519            c = Math.cos(-view.angle-anchorpoint.position.angle);
520            s = Math.sin(-view.angle-anchorpoint.position.angle);
521          }
522          else {
523            s = -Math.cos(view.angle-anchorpoint.position.angle);
524            c = Math.sin(view.angle-anchorpoint.position.angle);
525          }
526        }
527        else if (editrotation) {
528          arrows = (editscale?2:1);
529          c = tpos.getY()-cpos.getY();
530          s = -(tpos.getX()-cpos.getX());
531          double fact = Math.sqrt(c*c+s*s);
532          c/=fact;
533          s/=fact;
534        }
535        else if (editscale) {
536          arrows = 1;
537          s = tpos.getY()-cpos.getY();
538          c = tpos.getX()-cpos.getX();
539          double fact = Math.sqrt(c*c+s*s);
540          c/=fact;
541          s/=fact;
542        }
543        
544        if (arrows>0) {
545          g.drawLine((int)(tpos.getX()-BOXSIZE*c*2),(int)(tpos.getY()-BOXSIZE*s*2),
546          	   (int)(tpos.getX()+BOXSIZE*c*2),(int)(tpos.getY()+BOXSIZE*s*2));
547        }
548        if (arrows>1) {
549          g.drawLine((int)(tpos.getX()+BOXSIZE*s*2),(int)(tpos.getY()-BOXSIZE*c*2),
550          	   (int)(tpos.getX()-BOXSIZE*s*2),(int)(tpos.getY()+BOXSIZE*c*2));
551        }
552      }
553      
554    }
555  
556    public void reshape() {
557      double oldheight = box.getHeight();
558      box.height = subaspect*box.getWidth();
559      box.x = -box.getWidth()/2;
560      box.y = -box.getHeight()/2;
561      located = false;
562  //    getCenter();
563    }
564  
565    public void showAlignee(View view) {
566      anchorpoint.showAlignee(view);
567    }
568  
569    public Point2D showAligner(View view) {
570      return(anchorpoint.showAligner(view));
571    }
572  }
573