1 
  2 import java.awt.*;
  3 import java.awt.font.LineMetrics;
  4 import java.util.List;
  5 import java.util.ArrayList;
  6 import javax.swing.JFrame;
  7 import javax.swing.JPanel;
  8 import javax.swing.SwingUtilities;
  9 import javax.swing.JTabbedPane;
 10 
 11 ////////////////////////
 12 // Copyright (c) 2014 Richard Creamer - All Rights Reserved
 13 ////////////////////////
 14 
 15 public class Main {
 16 
 17     public static void main( String [] args ) {
 18         SwingUtilities.invokeLater( new Runnable() {
 19             @Override
 20             public void run() {
 21                 JTabbedPane tp = new JTabbedPane( JTabbedPane.TOP );
 22                 JPanel pCbCent = new MockupMailPanel( false );
 23                 JPanel pCbCell = new MockupMailPanel( true );
 24                 tp.add( "Centroid In Checkbox", pCbCent );
 25                 tp.add( "Centroid In Checkbox Cell", pCbCell );
 26                 JFrame jf = new JFrame();
 27                 jf.setSize( 450, 470 );
 28                 jf.setResizable( false );
 29                 jf.setTitle( "Most Likely Widget Touch Point Tester" );
 30                 jf.setLayout( new BorderLayout() );
 31                 jf.add( tp, BorderLayout.CENTER );
 32                 jf.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 33                 jf.setVisible( true );
 34             }
 35         } );
 36     }
 37 
 38 }
 39 
 40 class MockupMailPanel extends JPanel {
 41 
 42     private static final int CONTROL_HORIZ_MARGIN = 0;
 43     private static final int LINE_SEP_HEIGHT = 2;
 44     private static final int PANEL_CELL_HEIGHT = 95;
 45     private static final int VERT_MARGIN = 20; // Gap between top of window and beginning of mockup email panel
 46 
 47     private static final Color BKG_CLR = new Color( 236, 236, 236 );
 48     private static final Color LINE_DARK_CLR = new Color( 166, 166, 166 );
 49     private static final Color LINE_LITE_CLR = new Color( 255, 255, 255 );
 50     private static final Color CHECKBOX_LINE_CLR = new Color( 109, 109, 109 );
 51     private static final Color CHECKBOX_FILL_CLR = new Color( 201, 201, 201 );
 52     private static final Color TEXT_CLR = new Color( 0, 0, 0 );
 53     private static final int CHECKBOX_SIZE = 22;
 54     private static final int CELL_MARGIN = 15;
 55     private static final int MAIL_TEXT_INSET = 2 * CELL_MARGIN + CHECKBOX_SIZE;
 56     private static final String [] MAIL_TIMES = { "7:37 AM", "7:16 AM", "6:42 AM", "6:05 AM" };
 57     private List<Rectangle> checkboxRects = new ArrayList<>();
 58     private List<Rectangle> checkboxCellRects = new ArrayList<>();
 59     private List<Rectangle> mailCellRects = new ArrayList<>();
 60     private List<Rectangle> mailTextAreaRects = new ArrayList<>();
 61     private List<Point> checkboxCentroids = new ArrayList<>();
 62     private List<Point> emailCentroids = new ArrayList<>();
 63     private Font largeFont = new Font( "Arial", Font.BOLD, 16 );
 64     private Font normFont = new Font( "Arial", Font.PLAIN, 16 );
 65     private int largeFontHt = 0;
 66     private int normFontHt = 0;
 67     private int largeFontAscent = 0;
 68     private int normFontAscent = 0;
 69     private boolean fontParamsComputed = false;
 70     private Color [] checkboxCentroidColors = { Color.RED, new Color( 0x4a, 0xb2, 0x3a ), Color.BLUE, new Color( 0x4a, 0xb2, 0xff ) };
 71     private Color [] emailTextAreaCentroidColors = { Color.CYAN, Color.MAGENTA, new Color( 0x9a, 0x6e, 0x04 ), new Color( 0xff, 0x77, 0x04 ) };
 72     private boolean useCellCentroidsForCheckboxes = false;
 73     private int numEmails = MAIL_TIMES.length;
 74 
 75     public MockupMailPanel( boolean useCellCentroidsForCheckboxes ) {
 76         this.useCellCentroidsForCheckboxes = useCellCentroidsForCheckboxes;
 77         setSize( getWidth(), 4 * ( PANEL_CELL_HEIGHT + LINE_SEP_HEIGHT ) + 2 * VERT_MARGIN );
 78     }
 79 
 80     @Override
 81     public void paintComponent( Graphics g ) {
 82         Graphics2D g2d = ( Graphics2D ) g;
 83         g2d.setRenderingHint( java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON );
 84 
 85         g2d.setColor( BKG_CLR );
 86         g2d.fillRect( 0, 0, getWidth(), getHeight() );
 87         int x = 0;
 88         int y = VERT_MARGIN;
 89         for ( int i = 0; i < numEmails; ++i ) {
 90             drawMailEntry( i, g2d, x, y );
 91             y += PANEL_CELL_HEIGHT + 2; // 2 pixels for the separator lines
 92         }
 93         drawCentroids( g2d );
 94         drawMostLikelyWidgetOverlay( g2d );
 95     }
 96 
 97     private void drawMostLikelyWidgetOverlay( Graphics2D g2d ) {
 98 
 99         int touchPointRadius = 50;  // Threshold at which to not route touch events to widgets farther than this distance
100         // Make single list of special purpose colored rectangle objects to simplify(? ;-) this algorithm eval code
101         List<ColoredRect> cr = new ArrayList<>();
102         List<Rectangle> cbRects = ( useCellCentroidsForCheckboxes ) ? checkboxCellRects : checkboxRects;
103         for ( int i = 0; i < numEmails; ++i ) {
104             cr.add( new ColoredRect( cbRects.get( i ), checkboxCentroidColors[ i ] ) );
105             cr.add( new ColoredRect( mailCellRects.get( i ), emailTextAreaCentroidColors[ i ] ) );
106         }
107 
108         // Evaluate every pixel in this JPanel-derived class containing mocked up email summary list
109         int w = getWidth();
110         int h = getHeight();
111         for ( int i = 0; i < h; ++i ) {
112             int y = i;
113             for ( int j = 0; j < w; ++j ) {
114                 int x = j;
115                 ColoredRect mostLikelyWidget = getMostLikelyWidget( x, y, cr, touchPointRadius );
116                 if ( mostLikelyWidget != null )
117                     drawAlphaPixel( g2d, x, y, mostLikelyWidget.c );
118             }
119         }
120     }
121 
122     private ColoredRect getMostLikelyWidget( int x, int y, List<ColoredRect> widgets, int thresholdDist ) {
123         int minDist = Integer.MAX_VALUE;
124         ColoredRect closestWidget = null;
125         ColoredRect pointInWidget = null;
126         for ( ColoredRect cr : widgets ) {
127                 Rectangle r = cr.r;
128                 Point pt = new Point( x, y );
129                 int xc = r.x + r.width/2;
130                 int yc = r.y + r.height/2;
131                 int dist = ( int ) Math.round( Math.sqrt( ( x - xc ) * ( x - xc ) + ( y - yc ) * ( y - yc ) ) );
132                 if ( dist < minDist && dist < thresholdDist ) {
133                         minDist = dist;
134                         closestWidget= cr;
135                 }
136                 if ( r.contains( x, y ) )  {
137                         pointInWidget = cr;
138                 }
139         }
140         return ( closestWidget != null ) ? closestWidget : pointInWidget;
141     }
142 
143     private void drawAlphaPixel( Graphics2D g2d, int x, int y, Color c ) {
144         int overlayAlpha = 50;
145         g2d.setColor( new Color( c.getRed(), c.getGreen(), c.getBlue(), overlayAlpha ) );
146         g2d.fillRect( x, y, 1, 1 );
147     }
148 
149     private ColoredRect getNearestRect( int x, int y, List<ColoredRect> rectList, int touchPointRadius ) {
150         int minDist = Integer.MAX_VALUE;
151         ColoredRect retCr = null;
152         for ( ColoredRect cr : rectList  ) {
153             Rectangle r = cr.r;
154             int xc = ( r.x + r.width )/2;
155             int yc = ( r.y + r.height)/2;
156             int dist = ( int ) Math.round( Math.sqrt( ( x - xc ) * ( x - xc ) + ( y - yc ) * ( y - yc ) ) );
157             if ( dist < minDist && dist < touchPointRadius ) {
158                 minDist = dist;
159                 retCr = cr;
160             }
161         }
162         return retCr;
163     }
164 
165     private void drawCentroids( Graphics2D g2d ) {
166         // Draw checkbox centroid crosshairs
167         for ( int i = 0; i < numEmails; ++i ) {
168             Rectangle r = ( useCellCentroidsForCheckboxes ) ? checkboxCellRects.get( i ) : checkboxRects.get( i );
169             drawCrosshairInRect( g2d, r, checkboxCentroidColors[ i ] );
170         }
171         // Draw email text area cell centroid crosshairs
172         for ( int i = 0; i < numEmails; ++i ) {
173             Rectangle r = mailCellRects.get( i );
174             drawCrosshairInRect( g2d, r, emailTextAreaCentroidColors[ i ] );
175         }
176     }
177 
178     private void drawCrosshairInRect( Graphics2D g2d, Rectangle r, Color c ) {
179             int crosshairRadius = 7;
180             int strokeSize = 3;
181             int xc = r.x + r.width/2;
182             int yc = r.y + r.height/2;
183             g2d.setColor( c );
184             g2d.setStroke( new BasicStroke( strokeSize ) );
185             g2d.drawLine( xc - crosshairRadius,  yc, xc + crosshairRadius, yc );
186             g2d.drawLine( xc, yc - crosshairRadius, xc, yc + crosshairRadius );
187     }
188 
189     private void drawMailEntry( int emailNum, Graphics2D g2d, int x, int y ) {
190 
191         // Draw separator lines
192         g2d.setColor( LINE_DARK_CLR );
193         g2d.drawLine( x, y, getWidth(), y );
194         ++y;
195         g2d.setColor( LINE_LITE_CLR );
196         g2d.drawLine( x, y, getWidth(), y );
197         ++y;
198 
199         // Draw checkbox and save rectangle for later
200         Rectangle r = new Rectangle( x + CELL_MARGIN, y + CELL_MARGIN, CHECKBOX_SIZE, CHECKBOX_SIZE );
201         if ( checkboxRects.size() < numEmails )
202             checkboxRects.add( r );
203         drawCheckBox( g2d, r );
204 
205         // Compute and save CELL rectangle in which checkbox is contained
206         r = new Rectangle( x, y, MAIL_TEXT_INSET, PANEL_CELL_HEIGHT );
207         if ( checkboxCellRects.size() < numEmails )
208             checkboxCellRects.add( r );
209 
210         // Draw email text summary cell
211 
212         // First compute cell rectangle containing text
213         r = new Rectangle( x + MAIL_TEXT_INSET, y, getWidth() - 2 * CELL_MARGIN, PANEL_CELL_HEIGHT );
214         if ( mailCellRects.size() < numEmails )
215             mailCellRects.add( r );
216 
217         // Now compute inset rectangle into which text will be drawn
218         r = new Rectangle( x + MAIL_TEXT_INSET, y + CELL_MARGIN, getWidth() - MAIL_TEXT_INSET - CELL_MARGIN, PANEL_CELL_HEIGHT  - 2 * CELL_MARGIN);
219         if ( mailTextAreaRects.size() < numEmails )
220             mailTextAreaRects.add( r );
221 
222         String [] textLines = new String [] { "Email Sender #" + ( emailNum + 1 ),
223                                                                  "Email subject blah blah yada yada blah blah..." + MAIL_TIMES[ emailNum ],
224                                                                  "First line of email body blah blah blah yada yada blah",
225                                                                  "blah" };
226 
227         int textX = x + MAIL_TEXT_INSET;
228         int textY = y + CELL_MARGIN;
229         g2d.setColor( Color.BLACK );
230         for ( int i = 0; i < textLines.length; ++i ) {
231             g2d.setFont( ( i == 0 ) ? largeFont : normFont );
232             FontMetrics fm = getFontMetrics( g2d.getFont() );
233             LineMetrics lm = fm.getLineMetrics( textLines[ i ], g2d );
234             g2d.drawString( textLines[ i ], textX, textY + lm.getAscent() - 3 ); // The 3 is a fudge factor for this quickly hacked code
235             textY += lm.getHeight();
236         }
237 
238     }
239 
240     private void drawCheckBox( Graphics2D g2d, Rectangle r ) {
241         g2d.setColor( CHECKBOX_FILL_CLR );
242         g2d.fillRect( r.x, r.y, r.width, r.height );
243         g2d.setColor( CHECKBOX_LINE_CLR );
244         g2d.drawLine( r.x, r.y, r.x + CHECKBOX_SIZE - 1, r.y );
245         g2d.drawLine( r.x, r.y, r.x, r.y + CHECKBOX_SIZE - 1 );
246     }
247 
248 }
249 
250 class ColoredRect {
251     public final Rectangle r;
252     public final Color c;
253     public ColoredRect( Rectangle r, Color c ) {
254         this.r = r;
255         this.c = c;
256     }
257 }
258 
259 
260 
261 
262 
263 
264 
265 
266