info/gridworld/gui/GridPanel

From JavaWIDE

Jump to: navigation, search

001 /* 
002  * AP(r) Computer Science GridWorld Case Study:
003  * Copyright(c) 2002-2006 College Entrance Examination Board 
004  * (http://www.collegeboard.com).
005  *
006  * This code is free software; you can redistribute it and/or modify
007  * it under the terms of the GNU General Public License as published by
008  * the Free Software Foundation.
009  *
010  * This code is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013  * GNU General Public License for more details.
014  
015  @author Julie Zelenski
016  @author Cay Horstmann
017  */
018 
019 package info.gridworld.gui;
020 
021 import info.gridworld.grid.Grid;
022 import info.gridworld.grid.Location;
023 
024 import java.awt.Color;
025 import java.awt.Component;
026 import java.awt.Dimension;
027 import java.awt.Font;
028 import java.awt.Graphics;
029 import java.awt.Graphics2D;
030 import java.awt.Insets;
031 import java.awt.Point;
032 import java.awt.Rectangle;
033 import java.awt.RenderingHints;
034 import java.awt.event.ActionEvent;
035 import java.awt.event.ActionListener;
036 import java.awt.event.MouseEvent;
037 import java.awt.font.FontRenderContext;
038 import java.awt.font.LineMetrics;
039 import java.awt.geom.Rectangle2D;
040 import java.text.MessageFormat;
041 import java.util.ArrayList;
042 import java.util.ResourceBundle;
043 
044 import javax.swing.JPanel;
045 import javax.swing.JToolTip;
046 import javax.swing.JViewport;
047 import javax.swing.Scrollable;
048 import javax.swing.SwingConstants;
049 import javax.swing.SwingUtilities;
050 import javax.swing.Timer;
051 import javax.swing.ToolTipManager;
052 
053 /**
054  * A <code>GridPanel</code> is a panel containing a graphical display of the
055  * grid occupants. <br />
056  * This code is not tested on the AP CS A and AB exams. It contains GUI
057  * implementation details that are not intended to be understood by AP CS
058  * students.
059  */
060 
061 public class GridPanel extends JPanel implements Scrollable,
062         PseudoInfiniteViewport.Pannable
063 {
064     private static final int MIN_CELL_SIZE = 12;
065     private static final int DEFAULT_CELL_SIZE = 48;
066     private static final int DEFAULT_CELL_COUNT = 10;
067     private static final int TIP_DELAY = 1000;
068 
069     private Grid<?> grid;
070     private int numRows, numCols, originRow, originCol;
071     private int cellSize; // the size of each cell, EXCLUDING the gridlines
072     private boolean toolTipsEnabled;
073     private Color backgroundColor = Color.WHITE;
074     private ResourceBundle resources;
075     private DisplayMap displayMap;
076     private Location currentLocation;
077     private Timer tipTimer;
078     private JToolTip tip;
079     private JPanel glassPane;
080     
081     /**
082      * Construct a new GridPanel object with no grid. The view will be
083      * empty.
084      */
085     public GridPanel(DisplayMap map, ResourceBundle res)
086     {
087         displayMap = map;
088         resources = res;
089         setToolTipsEnabled(true);
090     }
091 
092     /**
093      Paint this component.
094      @param g the Graphics object to use to render this component
095      */
096     public void paintComponent(Graphics g)
097     {
098         Graphics2D g2 = (Graphics2Dg;
099 
100         super.paintComponent(g2);
101         if (grid == null)
102             return;
103 
104         Insets insets = getInsets();
105         g2.setColor(backgroundColor)
106         g2.fillRect(insets.left, insets.top, numCols * (cellSize + 1+ 1, numRows
107                 (cellSize + 1+ 1);
108 
109         drawWatermark(g2);
110         drawGridlines(g2);
111         drawOccupants(g2);
112         drawCurrentLocation(g2);
113     }
114 
115     /**
116      * Draw one occupant object. First verify that the object is actually
117      * visible before any drawing, set up the clip appropriately and use the
118      * DisplayMap to determine which object to call upon to render this
119      * particular Locatable. Note that we save and restore the graphics
120      * transform to restore back to normalcy no matter what the renderer did to
121      * to the coordinate system.
122      @param g2 the Graphics2D object to use to render
123      @param xleft the leftmost pixel of the rectangle
124      @param ytop the topmost pixel of the rectangle
125      @param obj the Locatable object to draw
126      */
127     private void drawOccupant(Graphics2D g2, int xleft, int ytop, Object obj)
128     {
129         Rectangle cellToDraw = new Rectangle(xleft, ytop, cellSize, cellSize);
130 
131         // Only draw if the object is visible within the current clipping
132         // region.
133         if (cellToDraw.intersects(g2.getClip().getBounds()))
134         {
135             Graphics2D g2copy = (Graphics2Dg2.create();
136             g2copy.clip(cellToDraw);
137             // Get the drawing object to display this occupant.
138             Display displayObj = displayMap.findDisplayFor(obj.getClass());
139             displayObj.draw(obj, this, g2copy, cellToDraw);
140             g2copy.dispose();
141         }
142     }
143 
144     /**
145      * Draw the gridlines for the grid. We only draw the portion of the
146      * lines that intersect the current clipping bounds.
147      @param g2 the Graphics2 object to use to render
148      */
149     private void drawGridlines(Graphics2D g2)
150     {
151         Rectangle curClip = g2.getClip().getBounds();
152         int top = getInsets().top, left = getInsets().left;
153 
154         int miny = Math.max(0(curClip.y - top(cellSize + 1)) (cellSize + 1+ top;
155         int minx = Math.max(0(curClip.x - left(cellSize + 1)) (cellSize + 1+ left;
156         int maxy = Math.min(numRows, 
157                 (curClip.y + curClip.height - top + cellSize(cellSize + 1)) 
158                 (cellSize + 1+ top;
159         int maxx = Math.min(numCols, 
160                 (curClip.x + curClip.width - left + cellSize(cellSize + 1))
161                 (cellSize + 1+ left;
162 
163         g2.setColor(Color.GRAY);
164         for (int = miny; y <= maxy; y += cellSize + 1)
165             for (int = minx; x <= maxx; x += cellSize + 1)
166             {
167                 Location loc = locationForPoint(
168                         new Point(+ cellSize / 2, y + cellSize / 2));
169                 if (loc != null && !grid.isValid(loc))
170                     g2.fillRect(+ 1, y + 1, cellSize, cellSize);
171             }
172 
173         g2.setColor(Color.BLACK);
174         for (int = miny; y <= maxy; y += cellSize + 1)
175             // draw horizontal lines
176             g2.drawLine(minx, y, maxx, y);
177 
178         for (int = minx; x <= maxx; x += cellSize + 1)
179             // draw vertical lines
180             g2.drawLine(x, miny, x, maxy);
181     }
182 
183     /**
184      * Draws the occupants of the grid.
185      @param g2 the graphics context
186      */
187     private void drawOccupants(Graphics2D g2)
188     {
189         ArrayList<Location> occupantLocs = grid.getOccupiedLocations();
190         for (int index = 0; index < occupantLocs.size(); index++)
191         {
192             Location loc = (LocationoccupantLocs.get(index);
193             
194             int xleft = colToXCoord(loc.getCol());
195             int ytop = rowToYCoord(loc.getRow());
196             drawOccupant(g2, xleft, ytop, grid.get(loc));
197         }
198     }
199 
200     /**
201      * Draws a square that marks the current location.
202      @param g2 the graphics context
203      */
204     private void drawCurrentLocation(Graphics2D g2)
205     {
206         try
207         {
208             if ("hide".equals(System.getProperty("info.gridworld.gui.selection")))
209                 return;
210         } 
211         catch (SecurityException ex)
212         {
213             // oh well...
214         }
215         if (currentLocation != null)
216         {
217             Point p = pointForLocation(currentLocation);
218             g2.drawRect(p.x - cellSize / 2, p.y - cellSize / 2,
219                     cellSize + 3, cellSize + 3);
220         }
221     }
222 
223     /**
224      * Draws a watermark that shows the version number if it is < 1.0
225      @param g2 the graphics context
226      */
227     private void drawWatermark(Graphics2D g2)
228     {
229         String versionId = resources.getString("version.id");
230         if ("1.00".compareTo(versionId== 0return// TODO: Better mechanism
231 
232         try
233         {
234             if ("hide".equals(System.getProperty("info.gridworld.gui.watermark")))
235                 return;
236         }
237         catch (SecurityException ex)
238         {
239             // oh well...
240         }
241 
242         g2 = (Graphics2Dg2.create();
243         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
244                 RenderingHints.VALUE_ANTIALIAS_ON);
245         Rectangle rect = getBounds();
246         g2.setPaint(new Color(0xE30xD30xD3));
247         final int WATERMARK_FONT_SIZE = 100;
248         g2.setFont(new Font("SansSerif"Font.BOLD, WATERMARK_FONT_SIZE));
249         FontRenderContext frc = g2.getFontRenderContext();
250         Rectangle2D bounds = g2.getFont().getStringBounds(versionId, frc);
251         float centerX = rect.x + rect.width / 2;
252         float centerY = rect.y + rect.height / 2;
253         float leftX = centerX - (floatbounds.getWidth() 2;
254         LineMetrics lm = g2.getFont().getLineMetrics(versionId, frc);
255         float baselineY = centerY - lm.getHeight() + lm.getAscent();
256         g2.drawString(versionId, leftX, baselineY);
257     }
258 
259     /**
260      * Enables/disables showing of tooltip giving information about the
261      * occupant beneath the mouse.
262      @param flag true/false to enable/disable tool tips
263      */
264     public void setToolTipsEnabled(boolean flag)
265     {
266         try
267         {
268             if ("hide".equals(System.getProperty("info.gridworld.gui.tooltips")))
269                 flag = false;
270         }
271         catch (SecurityException ex)
272         {
273             // oh well...
274         }
275         if (flag)
276             ToolTipManager.sharedInstance().registerComponent(this);
277         else
278             ToolTipManager.sharedInstance().unregisterComponent(this);
279         toolTipsEnabled = flag;
280     }
281 
282     /**
283      * Sets the grid being displayed. Reset the cellSize to be the
284      * largest that fits the entire grid in the current visible area (use
285      default if grid is too large).
286      @param gr the grid to display
287      */
288     public void setGrid(Grid<?> gr)
289     {
290         currentLocation = new Location(00);
291         JViewport vp = getEnclosingViewport()// before changing, reset
292         // scroll/pan position
293         if (vp != null)
294             vp.setViewPosition(new Point(00));
295 
296         grid = gr;
297         originRow = originCol = 0;
298 
299         if (grid.getNumRows() == -&& grid.getNumCols() == -1)
300         {
301             numRows = numCols = 2000
302             // This determines the "virtual" size of the pan world
303         }
304         else
305         {
306             numRows = grid.getNumRows();
307             numCols = grid.getNumCols();
308         }
309         recalculateCellSize(MIN_CELL_SIZE);        
310     }
311 
312     // private helpers to calculate extra width/height needs for borders/insets.
313     private int extraWidth()
314     {
315         return getInsets().left + getInsets().right;
316     }
317 
318     private int extraHeight()
319     {
320         return getInsets().top + getInsets().left;
321     }
322 
323     /**
324      * Returns the desired size of the display, for use by layout manager.
325      @return preferred size
326      */
327     public Dimension getPreferredSize()
328     {
329         return new Dimension(numCols * (cellSize + 1+ + extraWidth()
330                 numRows * (cellSize + 1+ + extraHeight());
331     }
332 
333     /**
334      * Returns the minimum size of the display, for use by layout manager.
335      @return minimum size
336      */
337     public Dimension getMinimumSize()
338     {
339         return new Dimension(numCols * (MIN_CELL_SIZE + 1+ + extraWidth()
340                 numRows * (MIN_CELL_SIZE + 1+ + extraHeight());
341     }
342 
343     /**
344      * Zooms in the display by doubling the current cell size.
345      */
346     public void zoomIn()
347     {
348         cellSize *= 2;
349         revalidate();
350     }
351 
352     /**
353      * Zooms out the display by halving the current cell size.
354      */
355     public void zoomOut()
356     {
357         cellSize = Math.max(cellSize / 2, MIN_CELL_SIZE);
358         revalidate();
359     }
360 
361     /**
362      * Pans the display back to the origin, so that 0, 0 is at the the upper
363      * left of the visible viewport.
364      */
365     public void recenter(Location loc)
366     {
367         originRow = loc.getRow();
368         originCol = loc.getCol();
369         repaint();
370         JViewport vp = getEnclosingViewport();
371         if (vp != null)
372         {
373             if (!isPannableUnbounded()
374                     || !(vp instanceof PseudoInfiniteViewport))
375                 vp.setViewPosition(pointForLocation(loc));
376             else
377                 showPanTip();
378         }
379     }
380 
381     /**
382      * Given a Point determine which grid location (if any) is under the
383      * mouse. This method is used by the GUI when creating Fish by clicking on
384      * cells in the display.
385      @param p the Point in question (in display's coordinate system)
386      @return the Location beneath the event (which may not be a
387      * valid location in the grid)
388      */
389     public Location locationForPoint(Point p)
390     {
391         return new Location(yCoordToRow(p.y), xCoordToCol(p.x));
392     }
393 
394     public Point pointForLocation(Location loc)
395     {
396         return new Point(colToXCoord(loc.getCol()) + cellSize / 2,
397                 rowToYCoord(loc.getRow()) + cellSize / 2);
398     }
399 
400     // private helpers to convert between (x,y) and (row,col)
401     private int xCoordToCol(int xCoord)
402     {
403         return (xCoord - - getInsets().left(cellSize + 1+ originCol;
404     }
405 
406     private int yCoordToRow(int yCoord)
407     {
408         return (yCoord - - getInsets().top(cellSize + 1+ originRow;
409     }
410 
411     private int colToXCoord(int col)
412     {
413         return (col - originCol(cellSize + 1+ + getInsets().left;
414     }
415 
416     private int rowToYCoord(int row)
417     {
418         return (row - originRow(cellSize + 1+ + getInsets().top;
419     }
420 
421     /**
422      * Given a MouseEvent, determine what text to place in the floating tool tip
423      * when the the mouse hovers over this location. If the mouse is over a
424      * valid grid cell. we provide some information about the cell and
425      * its contents. This method is automatically called on mouse-moved events
426      * since we register for tool tips.
427      @param evt the MouseEvent in question
428      @return the tool tip string for this location
429      */
430     public String getToolTipText(MouseEvent evt)
431     {
432         Location loc = locationForPoint(evt.getPoint());
433         return getToolTipText(loc);
434     }
435 
436     private String getToolTipText(Location loc)
437     {
438         if (!toolTipsEnabled || loc == null || !grid.isValid(loc))
439             return null;
440         Object f = grid.get(loc);
441         if (!= null)
442             return MessageFormat.format(resources
443                     .getString("cell.tooltip.nonempty")new Object[]
444                 { loc, f });
445         else
446             return MessageFormat.format(resources
447                     .getString("cell.tooltip.empty")new Object[]
448                 { loc, f });
449     }
450 
451     /**
452      * Sets the current location.
453      @param loc the new location
454      */
455     public void setCurrentLocation(Location loc)
456     {
457         currentLocation = loc;
458     }
459 
460     /**
461      * Gets the current location.
462      @return the currently selected location (marked with a bold square)
463      */
464     public Location getCurrentLocation()
465     {
466         return currentLocation;
467     }
468 
469     /**
470      * Moves the current location by a given amount.
471      @param dr the number of rows by which to move the location
472      @param dc the number of columns by which to move the location
473      */
474     public void moveLocation(int dr, int dc)
475     {
476         Location newLocation = new Location(currentLocation.getRow() + dr,
477                 currentLocation.getCol() + dc);
478         if (!grid.isValid(newLocation))
479             return;
480 
481         currentLocation = newLocation;
482 
483         JViewport viewPort = getEnclosingViewport();
484         if (isPannableUnbounded())
485         {
486             if (originRow > currentLocation.getRow())
487                 originRow = currentLocation.getRow();
488             if (originCol > currentLocation.getCol())
489                 originCol = currentLocation.getCol();
490             Dimension dim = viewPort.getSize();
491             int rows = dim.height / (cellSize + 1);
492             int cols = dim.width / (cellSize + 1);
493             if (originRow + rows - < currentLocation.getRow())
494                 originRow = currentLocation.getRow() - rows + 1;
495             if (originCol + rows - < currentLocation.getCol())
496                 originCol = currentLocation.getCol() - cols + 1;
497         }
498         else if (viewPort != null)
499         {
500             int dx = 0;
501             int dy = 0;
502             Point p = pointForLocation(currentLocation);
503             Rectangle locRect = new Rectangle(p.x - cellSize / 2, p.y
504                     - cellSize / 2, cellSize + 1, cellSize + 1);
505 
506             Rectangle viewRect = viewPort.getViewRect();
507             if (!viewRect.contains(locRect))
508             {
509                 while (locRect.x < viewRect.x + dx)
510                     dx -= cellSize + 1;
511                 while (locRect.y < viewRect.y + dy)
512                     dy -= cellSize + 1;
513                 while (locRect.getMaxX() > viewRect.getMaxX() + dx)
514                     dx += cellSize + 1;
515                 while (locRect.getMaxY() > viewRect.getMaxY() + dy)
516                     dy += cellSize + 1;
517 
518                 Point pt = viewPort.getViewPosition();
519                 pt.x += dx;
520                 pt.y += dy;
521                 viewPort.setViewPosition(pt);
522             }
523         }
524         repaint();
525         showTip(getToolTipText(currentLocation),
526                 pointForLocation(currentLocation));
527     }
528 
529     /**
530      * Show a tool tip.
531      @param tipText the tool tip text
532      @param pt the pixel position over which to show the tip
533      */
534     public void showTip(String tipText, Point pt)
535     {
536         if (getRootPane() == null)
537             return;
538         // draw in glass pane to appear on top of other components
539         if (glassPane == null)
540         {
541             getRootPane().setGlassPane(glassPane = new JPanel());
542             glassPane.setOpaque(false);
543             glassPane.setLayout(null)// will control layout manually
544             glassPane.add(tip = new JToolTip());
545             tipTimer = new Timer(TIP_DELAY, new ActionListener()
546             {
547                 public void actionPerformed(ActionEvent evt)
548                 {
549                     glassPane.setVisible(false);
550                 }
551             });
552             tipTimer.setRepeats(false);
553         }
554         if (tipText == null)
555             return;
556 
557         // set tip text to identify current origin of pannable view
558         tip.setTipText(tipText);
559 
560         // position tip to appear at upper left corner of viewport
561         tip.setLocation(SwingUtilities.convertPoint(this, pt, glassPane));
562         tip.setSize(tip.getPreferredSize());
563 
564         // show glass pane (it contains tip)
565         glassPane.setVisible(true);
566         glassPane.repaint();
567 
568         // this timer will hide the glass pane after a short delay
569         tipTimer.restart();
570     }
571 
572     /**
573      * Calculate the cell size to use given the current viewable region and the
574      * the number of rows and columns in the grid. We use the largest
575      * cellSize that will fit in the viewable region, bounded to be at least the
576      * parameter minSize.
577      */
578     private void recalculateCellSize(int minSize)
579     {
580         if (numRows == || numCols == 0)
581         {
582             cellSize = 0;
583         }
584         else
585         {
586             JViewport vp = getEnclosingViewport();
587             Dimension viewableSize = (vp != null? vp.getSize() : getSize();
588             int desiredCellSize = Math.min(
589                     (viewableSize.height - extraHeight()) / numRows,
590                     (viewableSize.width - extraWidth()) / numCols1;
591             // now we want to approximate this with 
592             // DEFAULT_CELL_SIZE * Math.pow(2, k)
593             cellSize = DEFAULT_CELL_SIZE;
594             if (cellSize <= desiredCellSize)                
595                 while (* cellSize <= desiredCellSize)
596                     cellSize *= 2;
597             else
598                 while (cellSize / >= Math.max(desiredCellSize, MIN_CELL_SIZE))
599                     cellSize /= 2;
600         }
601         revalidate();
602     }
603 
604     // helper to get our parent viewport, if we are in one.
605     private JViewport getEnclosingViewport()
606     {
607         Component parent = getParent();
608         return (parent instanceof JViewport(JViewportparent : null;
609     }
610 
611     // GridPanel implements the Scrollable interface to get nicer behavior in a
612     // JScrollPane. The 5 methods below are the methods in that interface
613 
614     public int getScrollableUnitIncrement(Rectangle visibleRect,
615             int orientation, int direction)
616     {
617         return cellSize + 1;
618     }
619 
620     public int getScrollableBlockIncrement(Rectangle visibleRect,
621             int orientation, int direction)
622     {
623         if (orientation == SwingConstants.VERTICAL)
624             return (int) (visibleRect.height * .9);
625         else
626             return (int) (visibleRect.width * .9);
627     }
628 
629     public boolean getScrollableTracksViewportWidth()
630     {
631         return false;
632     }
633 
634     public boolean getScrollableTracksViewportHeight()
635     {
636         return false;
637     }
638 
639     public Dimension getPreferredScrollableViewportSize()
640     {
641         return new Dimension(DEFAULT_CELL_COUNT * (DEFAULT_CELL_SIZE + 1+ + extraWidth()
642                 DEFAULT_CELL_COUNT * (DEFAULT_CELL_SIZE + 1+ + extraHeight());
643     }
644 
645     // GridPanel implements the PseudoInfiniteViewport.Pannable interface to
646     // play nicely with the pan behavior for unbounded view.
647     // The 3 methods below are the methods in that interface.
648 
649     public void panBy(int hDelta, int vDelta)
650     {
651         originCol += hDelta / (cellSize + 1);
652         originRow += vDelta / (cellSize + 1);
653         repaint();
654     }
655 
656     public boolean isPannableUnbounded()
657     {
658         return grid != null && (grid.getNumRows() == -|| grid.getNumCols() == -1);
659     }
660 
661     /**
662      * Shows a tool tip over the upper left corner of the viewport with the
663      * contents of the pannable view's pannable tip text (typically a string
664      * identifiying the corner point). Tip is removed after a short delay.
665      */
666     public void showPanTip()
667     {
668         String tipText = null;
669         Point upperLeft = new Point(00);
670         JViewport vp = getEnclosingViewport();
671         if (!isPannableUnbounded() && vp != null)
672             upperLeft = vp.getViewPosition();
673         Location loc = locationForPoint(upperLeft);
674         if (loc != null)
675             tipText = getToolTipText(loc);
676 
677         showTip(tipText, getLocation());
678     }
679 }
680 
681 //Uploaded on Sat Feb 19 13:40:40 EST 2011


Download/View info/gridworld/gui/GridPanel.java





Views
Personal tools
Add to 
del.icio.usAdd to 
diggAdd to 
FacebookAdd to 
favoritesAdd to 
GoogleAdd to 
MySpaceAdd to 
PrintAdd to 
SlashdotAdd to 
StumbleUponAdd to 
Twitter