1// BEGIN LICENSE BLOCK
2// Version: CMPL 1.1
3//
4// The contents of this file are subject to the Cisco-style Mozilla Public
5// License Version 1.1 (the "License"); you may not use this file except
6// in compliance with the License.  You may obtain a copy of the License
7// at www.eclipse-clp.org/license.
8//
9// Software distributed under the License is distributed on an "AS IS"
10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
11// the License for the specific language governing rights and limitations
12// under the License.
13//
14// The Original Code is  The ECLiPSe Constraint Logic Programming System.
15// The Initial Developer of the Original Code is  Cisco Systems, Inc.
16// Portions created by the Initial Developer are
17// Copyright (C) 2006 Cisco Systems, Inc.  All Rights Reserved.
18//
19// Contributor(s):
20//
21// END LICENSE BLOCK
22
23package com.parctechnologies.eclipse.visualisation.viewers;
24
25import com.parctechnologies.eclipse.*;
26import com.parctechnologies.eclipse.visualisation.*;
27
28import java.awt.Rectangle;
29import java.awt.Shape;
30import java.awt.geom.*;
31import java.awt.Color;
32import java.awt.Component;
33import java.awt.Graphics;
34import java.awt.Graphics2D;
35import java.util.*;
36import java.awt.event.ActionEvent;
37import javax.swing.*;
38import javax.swing.table.*;
39import att.grappa.*;
40
41
42/**
43 * Displays a graphical representation of the numeric range, as
44 * reported by get_bounds/3
45 **/
46public class BoundsViewletType extends AbstractViewletType {
47    private TableCellRenderer tableCellRenderer;
48    private CustomRenderer customRenderer;
49
50    public BoundsViewletType(String changeable) {
51        super(changeable);
52    }
53
54
55    /* ViewletFactory methods */
56    public boolean canBuildFrom(ElementType elementType)
57    {
58      //return(elementType instanceof NumericBounds);
59      return true;
60    }
61
62    public ViewletData build()
63    {
64	return new Data();
65    }
66
67    public String getDescription()
68    {
69	return("Bounds viewlet");
70    }
71
72    /* ViewletType methods */
73    public synchronized TableCellRenderer getTableCellRenderer() {
74	if (tableCellRenderer == null) {
75	    tableCellRenderer = new CellRenderer();
76	}
77	return tableCellRenderer;
78    }
79
80    public Class getCustomRendererClass() {
81      return Renderer.class;
82    }
83
84    public void customizeElement(ViewletDataStore store,
85                                 java.util.List index,
86                                 Element element) {
87        Data data = (Data)(store.getViewletDataAt(index));
88        if (element instanceof Node) {
89            if (DebuggingSupport.logMessages) {
90              DebuggingSupport.logMessage(this,"Bounds customizeElement for element "+element + " step 1");
91            }
92            // set the custom renderer
93            element.setAttribute("shape",new Integer(Grappa.CUSTOM_SHAPE));
94            element.setAttribute(Grappa.CUSTOM_ATTR,getCustomRendererClass().getName());
95            if (DebuggingSupport.logMessages) {
96              DebuggingSupport.logMessage(this,"Bounds customizeElement for element "+element + " step 2");
97            }
98            // set the node label
99            element.setAttribute("label","");
100            if (DebuggingSupport.logMessages) {
101              DebuggingSupport.logMessage(this,"Bounds customizeElement for element "+element + " step 3");
102            }
103            // set background color
104            if (data.getHoldsOnUpdates()) {
105                element.setAttribute("color", Color.blue.darker());
106            } else {
107                element.setAttribute("color", Color.blue);
108            }
109            if (DebuggingSupport.logMessages) {
110              DebuggingSupport.logMessage(this,"Bounds customizeElement for element "+element + " step 4");
111            }
112            // set filled
113            element.setAttribute("style", "filled");
114            if (DebuggingSupport.logMessages) {
115              DebuggingSupport.logMessage(this,"Bounds customizeElement for element "+element + " step 5");
116            }
117            // force shape update
118            element.object = data;
119            element.getGrappaNexus().updateShape();
120            if (DebuggingSupport.logMessages) {
121              DebuggingSupport.logMessage(this,"Bounds customizeElement for element "+element + " step 6");
122            }
123        } else {
124            // instance of edge
125        }
126    }
127
128
129    public BatchGoal collectPreBuildGoal(Viewer viewer,
130                                         ViewletDataStore store,
131                                         ViewletRange range)
132    {
133	BatchGoal result = new BatchGoal();
134	Iterator indexListIterator = range.iterator();
135	java.util.List currentIndex;
136	Collection viewlets;
137
138	while(indexListIterator.hasNext())
139	    {
140		currentIndex = (java.util.List) indexListIterator.next();
141                CompoundTerm goal =
142                    new CompoundTermImpl("get_var_bounds", new CompoundTermImpl("element",currentIndex), null, null);
143
144//  		CompoundTerm goal =
145//  		    new CompoundTermImpl(":", new Atom("vc_support"),
146
147//  					 new CompoundTermImpl("viewable_element_to_string",
148//  							      new CompoundTermImpl("element",currentIndex), null));
149		result.add(composeElementGoal(currentIndex, store.getViewable().getNameAtom(), goal));
150	    }
151	return(result);
152    }
153
154    protected void processResults(Viewer viewer,
155                                  ViewletDataStore store,
156                                  ViewletRange range,
157                                  List results,
158                                  boolean isBuildResults) {
159	Iterator indexListIterator = range.iterator();
160	Iterator resultsIterator = results.iterator();
161	CompoundTermImpl elementResult;
162	List currentIndex;
163        if (DebuggingSupport.logMessages) {
164            DebuggingSupport.logMessage(this,
165                                        "startBuild called with range=" +
166                                        range + " results=" + results);
167        }
168
169	while(indexListIterator.hasNext()) {
170	    currentIndex = (List) indexListIterator.next();
171	    if (DebuggingSupport.logMessages) {
172		DebuggingSupport.logMessage(this, "currentIndex="+currentIndex);
173	    }
174	    Data viewletData =
175		(Data)(store.getViewletDataAt(currentIndex));
176	    if (DebuggingSupport.logMessages) {
177		DebuggingSupport.logMessage(this, "viewletData="+viewletData);
178	    }
179	    elementResult = (CompoundTermImpl) resultsIterator.next();
180	    if (DebuggingSupport.logMessages) {
181		DebuggingSupport.logMessage(this, "elementResult="+elementResult);
182	    }
183	    // store the goal result text into the viewletDataStore
184	    Double lowerBound = (Double)decomposeElementGoal(elementResult).arg(2);
185	    Double upperBound = (Double)decomposeElementGoal(elementResult).arg(3);
186	    if (viewletData == null) {
187		viewletData = (Data)build();
188	    }
189            if (isBuildResults) {
190                viewletData.setAbsoluteBounds(lowerBound.doubleValue(), upperBound.doubleValue());
191                viewletData.setInitialBounds(lowerBound.doubleValue(), upperBound.doubleValue());
192            }
193	    viewletData.setCurrentBounds(lowerBound.doubleValue(), upperBound.doubleValue());
194	    store.setViewletDataAt(currentIndex, viewletData);
195	}
196    }
197
198    public void startBuild(Viewer viewer,
199                           ViewletDataStore store,
200                           ViewletRange range,
201                           List results) {
202        processResults(viewer, store, range, results, true);
203    }
204
205    public BatchGoal collectPreUpdateGoal(Viewer viewer,
206                                          ViewletDataStore store,
207					  ViewletRange range,
208					  UpdateEvent updateEvent)
209    {
210	BatchGoal result = new BatchGoal();
211	Iterator indexListIterator = range.iterator();
212	java.util.List currentIndex;
213	Collection viewlets;
214
215	while(indexListIterator.hasNext())
216	    {
217		currentIndex = (java.util.List) indexListIterator.next();
218                CompoundTerm goal =
219                    new CompoundTermImpl("get_var_bounds", new CompoundTermImpl("element",currentIndex), null, null);
220
221//  		CompoundTerm goal =
222//  		    new CompoundTermImpl(":", new Atom("vc_support"),
223//  					 new CompoundTermImpl("viewable_element_to_string",
224//  							      new CompoundTermImpl("element",currentIndex), null));
225		result.add(composeElementGoal(currentIndex, store.getViewable().getNameAtom(), goal));
226	    }
227	return(result);
228    }
229
230    protected void setUpdating(Viewer viewer,
231                               ViewletDataStore store,
232			       ViewletRange range,
233			       int fadeCount) {
234	Iterator indexListIterator = range.iterator();
235	List currentIndex;
236	while(indexListIterator.hasNext()) {
237	    currentIndex = (List) indexListIterator.next();
238	    if (DebuggingSupport.logMessages) {
239		DebuggingSupport.logMessage(this, "currentIndex="+currentIndex);
240	    }
241	    Data viewletData =
242		(Data)(store.getViewletDataAt(currentIndex));
243	    if (DebuggingSupport.logMessages) {
244		DebuggingSupport.logMessage(this, "viewletData="+viewletData);
245	    }
246	    // Set the updating flag and store the new data
247	    if (viewletData == null) {
248		viewletData = (Data)build();
249	    }
250	    //viewletData.setFadeCount(fadeCount);
251	    store.setViewletDataAt(currentIndex, viewletData);
252	}
253    }
254
255    public void startUpdate(Viewer viewer,
256                            ViewletDataStore store,
257			    ViewletRange range,
258			    List results,
259			    UpdateEvent updateEvent)
260    {
261        processResults(viewer, store, range, results, false);
262        // do the same as for building a viewlet
263        //startBuild(viewer, store, range, results);
264        // set the fade counter
265        //setUpdating(viewer, store, range,((updateEvent instanceof ForwardUpdateEvent)?MAX_FADE:-MAX_FADE));
266
267//            ViewletRange all = store.getEntireViewletRange() ;
268//            ViewletRange faded = new ViewletRangeCollection();
269//            for(Iterator it = all.iterator(); it.hasNext(); ) {
270//                List index = (List)it.next();
271//                Data data = (Data)(store.getViewletDataAt(index));
272//                if (data.fade()) {
273//                    faded.add(index);
274//                }
275//            }
276          // Indicate that these cells were updated
277          store.fireViewletRangeUpdated(range);
278          //        store.fireViewletRangeUpdated(range);
279    }
280
281    /**
282     * For the given index, return the smallest pertinent value
283     **/
284    public double getMin(ViewletDataStore store, List index) {
285        return ((Data)(store.getViewletDataAt(index))).absoluteMin;
286    }
287
288    /**
289     * For the given index, return the largest pertinent value
290     **/
291    public double getMax(ViewletDataStore store, List index) {
292        return ((Data)(store.getViewletDataAt(index))).absoluteMax;
293    }
294
295    /*
296     * Data is a viewlet which can monitor elements of any type. It is
297     * responsible for:
298     * <ul>
299     * <li> Maintaining a record of the text representation of the term.
300     * </ul>
301     */
302    public static class Data extends ViewletDataImpl
303    {
304        double absoluteMin, absoluteMax;
305        double initialMin, initialMax;
306        double min, max;
307        boolean vertical;
308
309	public Data()
310	{
311	    super();
312	}
313
314	public String toString() {
315          return initialMin+".."+min+".."+max+".."+initialMax;
316	}
317
318        public void setAbsoluteBounds(double min, double max) {
319            absoluteMin = min;
320            absoluteMax = max;
321        }
322
323        public void setInitialBounds(double min, double max) {
324            initialMin = min;
325            initialMax = max;
326        }
327
328        public void setCurrentBounds(double min, double max) {
329            this.min = min;
330            this.max = max;
331        }
332    }
333
334
335    /**
336     * Return a collection of actions which can be applied to viewlets
337     * in this table
338     */
339    public Collection getActions(ViewletDataStore store,
340                                 ViewletRange range) {
341        Collection ll = super.getActions(store, range);
342        if ((range != null) & (!range.isEmpty())) {
343            // Add new actions here
344            ll.add(new AlignBoundsAction(store, range));
345            ll.add(new ToggleHorizontalVerticalAction(store, range));
346        }
347        if ((range != null) & (range.size()==1)) {
348            java.util.List index = (java.util.List)(range.iterator().next());
349            // Add new actions here which apply only to single viewlets
350            ll.add(new DisplayBoundsInDetailAction(store, index));
351        }
352        return ll;
353    }
354
355    protected void allignBounds(ViewletDataStore store,
356                                ViewletRange range) {
357            double newMin = Double.POSITIVE_INFINITY;
358            double newMax = Double.NEGATIVE_INFINITY;
359
360            for(Iterator iterator = range.iterator(); iterator.hasNext(); ) {
361                List index = (List) iterator.next();
362                Data viewlet = (Data)(store.getViewletDataAt(index));
363                if(viewlet.initialMin < newMin) {
364                    newMin = viewlet.initialMin;
365                }
366                if(viewlet.initialMax > newMax) {
367                    newMax = viewlet.initialMax;
368                }
369            }
370
371            for(Iterator iterator = range.iterator(); iterator.hasNext(); ) {
372                List index = (List) iterator.next();
373                Data viewlet = (Data)(store.getViewletDataAt(index));
374                viewlet.setAbsoluteBounds(newMin, newMax);
375                store.setViewletDataAt(index, viewlet);
376            }
377            store.fireViewletRangeUpdated(range);
378    }
379
380    /**
381     * Recordable command to perform the bounds allignment
382     */
383    public static class AlignBoundsCommand extends ViewletTypeRangeCommand {
384        public AlignBoundsCommand(ViewletType type,
385                                  ViewletDataStore store,
386                                  ViewletRange range) {
387            super(type, store, range);
388        }
389
390        public void postRecordIssue() {
391            if (DebuggingSupport.logMessages) {
392                DebuggingSupport.logMessage(this, "AlignBoundCommand postRecordIssue invoked with type="+getViewletType()+" store="+getViewletDataStore()+" range="+getViewletRange());
393            }
394            ((BoundsViewletType)getViewletType()).
395                allignBounds(getViewletDataStore(), getViewletRange());
396        }
397    }
398
399    /**
400     * Action class to set a range of viewables to have common
401     * AbsoluteMin and AbsolutMax
402     **/
403    protected class AlignBoundsAction extends ViewletAction {
404        ViewletRange range;
405        ViewletDataStore store;
406
407        AlignBoundsAction(ViewletDataStore store, ViewletRange range) {
408            super("Align bounds");
409            putValue(Action.NAME, "Align bounds");
410            putValue(Action.LONG_DESCRIPTION,
411                     "Re-align a selection of bounds viewlets so that their bounds are displayed on the same axes");
412            putValue(Action.SHORT_DESCRIPTION,
413               "Re-align so that bounds viewlets use the same axes");
414            //putValue(Action.SMALL_ICON, new FadeIcon(20, 20));
415            this.store = store;
416            this.range = range;
417        }
418
419      public void actionPerformed(ActionEvent e) {
420          new AlignBoundsCommand(BoundsViewletType.this, store, range).issue();
421      }
422    }
423
424
425    protected void toggleHorizontalVertical(ViewletDataStore store,
426                                          ViewletRange range) {
427        boolean allHorizontal = true;
428
429        for(Iterator iterator = range.iterator(); iterator.hasNext(); ) {
430            List index = (List) iterator.next();
431            Data data = (Data)(store.getViewletDataAt(index));
432            if(data.vertical) {
433                allHorizontal = false;
434            }
435        }
436        // if all the viewlets are horizontal then set them all
437        // vertical, otherwise set them all horizontal
438        for(Iterator iterator = range.iterator(); iterator.hasNext(); ) {
439            List index = (List) iterator.next();
440            Data data = (Data)(store.getViewletDataAt(index));
441            data.vertical = allHorizontal;
442            store.setViewletDataAt(index, data);
443        }
444        store.fireViewletRangeUpdated(range);
445    }
446
447    /**
448     * Recordable command to perform the bounds allignment
449     */
450    public static class ToggleHorizontalVerticalCommand extends ViewletTypeRangeCommand {
451        public ToggleHorizontalVerticalCommand(ViewletType type,
452                                               ViewletDataStore store,
453                                               ViewletRange range) {
454            super(type, store, range);
455        }
456
457        public void postRecordIssue() {
458            if (DebuggingSupport.logMessages) {
459                DebuggingSupport.logMessage(this, "AlignBoundCommand postRecordIssue invoked with type="+getViewletType()+" store="+getViewletDataStore()+" range="+getViewletRange());
460            }
461            ((BoundsViewletType)getViewletType()).
462                toggleHorizontalVertical(getViewletDataStore(), getViewletRange());
463        }
464    }
465
466
467    /**
468     * Action class to set a range of viewables to have common
469     * AbsoluteMin and AbsolutMax
470     **/
471    protected class ToggleHorizontalVerticalAction extends ViewletAction {
472        ViewletRange range;
473        ViewletDataStore store;
474
475        ToggleHorizontalVerticalAction(ViewletDataStore store,
476                                       ViewletRange range) {
477            super("Toggle horizontal/vertical range bar");
478            putValue(Action.NAME, "Toggle horizontal/vertical range bar");
479            putValue(Action.LONG_DESCRIPTION,
480                     "Change whether the range bar is displayed horizontally or vertically");
481            putValue(Action.SHORT_DESCRIPTION,
482                     "Change whether the range bar is horizontal or vertical");
483            this.store = store;
484            this.range = range;
485        }
486
487        public void actionPerformed(ActionEvent e) {
488            new ToggleHorizontalVerticalCommand(BoundsViewletType.this,
489                                                store,
490                                                range).issue();
491        }
492    }
493
494    /**
495     * Action class to display the bounds in detail in a popup window
496     **/
497    protected class DisplayBoundsInDetailAction extends ViewletAction {
498        List index;
499        ViewletDataStore store;
500
501        DisplayBoundsInDetailAction(ViewletDataStore store,
502                                    java.util.List index) {
503            super("Display bounds");
504            putValue(Action.NAME, "Display bounds");
505            putValue(Action.LONG_DESCRIPTION,
506                     "Popup window displaying bounds in detail");
507            putValue(Action.SHORT_DESCRIPTION,
508               "Popup window displaying bounds in detail");
509            //putValue(Action.SMALL_ICON, new FadeIcon(20, 20));
510            this.store = store;
511            this.index = index;
512        }
513
514      public void actionPerformed(ActionEvent e) {
515        Data data = (Data)(store.getViewletDataAt(index));
516        JOptionPane.
517            showConfirmDialog(null,
518                              "Initial upper bound: "+data.initialMax+"\n\n"+
519                              "Current upper bound: "+data.max+"\n\n"+
520                              "Current lower bound: "+data.min+"\n\n"+
521                              "Initial lower bound: "+data.initialMin,
522                              "Bounds data in detail",
523                              JOptionPane.DEFAULT_OPTION);
524      }
525    }
526
527    /**
528     * The default text cell render
529     */
530    private class CellRenderer extends DefaultTableCellRenderer {
531
532	public CellRenderer() {
533	    super();
534	    setHorizontalAlignment(SwingConstants.CENTER);
535	}
536
537	public Component getTableCellRendererComponent(JTable table,
538						       Object value,
539						       boolean isSelected,
540						       boolean hasFocus,
541						       int row,
542						       int column) {
543
544          JLabel result ;
545          Rectangle bounds ;
546            if (table == null) {
547              result = new JLabel();
548              bounds = result.getBounds();
549            } else {
550  	    result =
551  		(JLabel)(super.getTableCellRendererComponent(table,
552  							     "",
553  							     isSelected,
554  							     hasFocus,
555  							     row,
556  							     column));
557            bounds = table.getCellRect(row, column, false);
558            }
559	    Data data = (Data)value;
560
561
562            Graphics2D g2d = (Graphics2D)result.getGraphics();
563            Element element = new Graph("bar");
564            element.object = null;
565            Renderer renderer = new Renderer(element,
566                                             0,0,
567                                             1,1);
568            // set background color
569            if (data.getHoldsOnUpdates()) {
570                if (isSelected) {
571                    result.setBackground(Color.white.darker().darker().darker());
572                } else {
573                    result.setBackground(Color.white.darker());
574                }
575            } else {
576                if (isSelected) {
577                    result.setBackground(Color.white.darker().darker());
578                } else {
579                    result.setBackground(Color.white);
580                }
581            }
582            result.setIcon(new RendererIcon(renderer,data,bounds));
583            //result.setBackground(getColor(data, isSelected));
584//              if (data.getFading()) {
585//                  result.setIcon(fadeIcon);
586//              } else {
587//                  result.setIcon(null);
588//              }
589	    return result;
590	}
591
592	public void setValue(Object value) {
593	    super.setValue(value);
594	    setToolTipText(value.toString());
595	}
596
597    }
598
599    public static class RendererIcon implements Icon {
600        Renderer renderer;
601        Data data;
602        Rectangle bounds;
603
604        public RendererIcon(Renderer renderer,
605                            Data data,
606                            Rectangle parentBounds) {
607            this.renderer = renderer;
608            this.data = data;
609            int w = (parentBounds.width * 9) / 10;
610            int h = (parentBounds.height * 9) / 10;
611            int x = (parentBounds.width * 1) / 20;
612            int y = (parentBounds.height * 1) / 20;
613            this.bounds = new Rectangle(x,y,w,h);
614        }
615
616        public int getIconHeight() {
617            // nominal height
618            return bounds.height;
619        }
620        public int getIconWidth() {
621            // nominal width
622            return bounds.width;
623        }
624        public void paintIcon(Component c,
625                              Graphics g,
626                              int x,
627                              int y) {
628            //Data data = renderer.getViewletData();
629          if (DebuggingSupport.logMessages) {
630            DebuggingSupport.logMessage(this, "Clip = "+g.getClip().getBounds2D()+" bounds="+bounds+" (x,y)=("+x+","+y+")");
631          }
632            //renderer.configure(new Rectangle2D.Double(x,y,c.getWidth(),c.getHeight()),
633          //renderer.configure(g.getClip().getBounds2D(),
634            renderer.configure(bounds,
635                               data.absoluteMin,
636                               data.absoluteMax,
637                               data.initialMin,
638                               data.initialMax,
639                               data.min,
640                               data.max,
641                               data.vertical);
642            g.setColor(Color.black);
643            renderer.draw((Graphics2D)g);
644            g.setColor(Color.blue);
645            renderer.fill((Graphics2D)g);
646        }
647    }
648
649
650    public static class Renderer extends CustRenderer {
651        public Renderer(Element element,
652                        double x, double y, double w, double h) {
653            super(element, x, y, w, h);
654            if (DebuggingSupport.logMessages) {
655              DebuggingSupport.logMessage(this,"Bounds Renderer constructed for element "+element);
656            }
657            // somehow work out a way of getting the actual
658            // ViewletData for this element (must work when the
659            // viewlettype is contained in a multiViewlet type)
660            //Data data = (Data)element.object;
661            Data data = (Data)getViewletData();
662            if (data != null) {
663              configure(new Rectangle2D.Double(x,y,w,h),
664                        data.absoluteMin,
665                        data.absoluteMax,
666                        data.initialMin,
667                        data.initialMax,
668                        data.min,
669                        data.max,
670                        data.vertical);
671            }
672        }
673
674        public void configure(Rectangle2D bounds,
675                              double absoluteMin, double absoluteMax,
676                              double initialMin, double initialMax,
677                              double min, double max, boolean vertical) {
678            // draw a "box and stick" diagram within the specified bounds
679            //
680            // G  +--      +-------+         --+
681            //    |        |       |           |
682            // H      |    |       |     |
683            // I1     |----|       |-----|
684            // I      |                  |
685            // I2     |----|       |-----|
686            // J      |    |       |     |
687            //    |        |       |           |
688            // K  +--      +-------+         --+
689            //    A   B    C       D     E     F
690            //
691          //Rectangle2D bounds = getBounds2D();
692          float A,B,C,D,E,F,G,H,I,I1,I2,J,K;
693          if (vertical) {
694            A = (float)bounds.getMinY();
695            F = (float)bounds.getMaxY();
696          } else {
697            A = (float)bounds.getMinX();
698            F = (float)bounds.getMaxX();
699          }
700          // remove potential for divide by zero
701          if (absoluteMax == absoluteMin) {
702            absoluteMax++;
703          }
704          // calculate scale for drawing
705          float scaleX = (F-A) / (float)(absoluteMax - absoluteMin);
706          B = A + (float)initialMin * scaleX;
707          C = A + (float)min * scaleX;
708          D = A + (float)max * scaleX;
709          E = A + (float)initialMax * scaleX;
710
711          if (vertical) {
712            G = (float)bounds.getMinX();
713            K = (float)bounds.getMaxX();
714          } else {
715            G = (float)bounds.getMinY();
716            K = (float)bounds.getMaxY();
717          }
718          float sixteenth = (K-G)/16;
719          I = G+8*sixteenth;
720
721          H  = I - 4*sixteenth;
722          I1 = I - 1*sixteenth;
723          I2 = I + 1*sixteenth;
724          J  = I + 4*sixteenth;
725
726          // draw outline
727          float vertices[][];
728
729          if (vertical) {
730            float vert[][] = {{H,B},{I1,B},{I1,C},{G,C},{G,D},{I1,D},{I1,E},
731                              {H,E},{J,E},{I2,E},{I2,D},{K,D},{K,C},{I2,C},
732                              {I2,B},{J,B}};
733            vertices = vert;
734          } else {
735            float horiz[][] = {{B,H},{B,I1},{C,I1},{C,G},{D,G},{D,I1},{E,I1},
736                               {E,H},{E,J},{E,I2},{D,I2},{D,K},{C,K},{C,I2},
737                               {B,I2},{B,J}};
738            vertices = horiz;
739          }
740          path.moveTo(vertices[0][0], vertices[0][1]);
741          for(int i = 1; i < vertices.length; i++) {
742            if (DebuggingSupport.logMessages) {
743              DebuggingSupport.logMessage(this,"Bounds point "+i+
744                                          " x="+vertices[i][0]+
745                                            " y="+vertices[i][1]);
746            }
747            path.lineTo(vertices[i][0], vertices[i][1]);
748          }
749          path.closePath();
750        }
751    }
752
753}
754
755