/* * Class was formerly known as "OuijaViewer" * * Created on February 5, 2003, 5:04 PM */ package randchars; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Point; import java.awt.geom.Point2D; import javax.swing.JPanel; //import MDUtils.OracleUtils; import MDUtils.FontUtils; import MDUtils.IPublicInformation; /** A panel which displays a list of characters. * A controller or model should activate this viewer by calling repaint(); * * @author dickey */ public class RandCharViewer extends JPanel implements IPublicInformation { private static final int panelWidth = 400; private static final int panelHeight = 200; /** Position in the panel where we want the first character to display. */ private double xStart = 5, yStart = 0; //y is adjusted dynamically. /** Desired size of the first character drawn. It IS important that his be a float, and not an int or double. */ float startingFontSize = 60.0f; /** Size of a character area on the screen; the area may include more * than just the single character (for example). */ private int charWidth, charHeight; /** The model of the list of characters. */ private RandCharModel charsModel; /** A code prefix that is the same for each object. */ private final static String IDPrefix = "RandChars Panel-"; /** A suffix that is unique for each object.*/ private static char objtag = 'a'; /** A value that is unique for each object. */ private String objectID; private Font niceFont; /** Creates a new instance of the viewer */ public RandCharViewer(RandCharModel charsModel) { assert charsModel != null; this.charsModel = charsModel; this.setPreferredSize(new java.awt.Dimension(panelWidth, panelHeight)); this.objtag = (char) (1 + (int) objtag); this.objectID = IDPrefix + objtag; //bunch of test cases. harmless. Only the last one matters. /* this.niceFont = FontUtils.setUnicodeFont("Serif"); this.niceFont = FontUtils.setUnicodeFont( new String[] {"Arial Unicode MS", "Serif"}); this.niceFont = FontUtils.setUnicodeFont(); String junkString = null; this.niceFont = FontUtils.setUnicodeFont(junkString); String[] junksA = null; this.niceFont = FontUtils.setUnicodeFont(junksA); this.niceFont = FontUtils.setUnicodeFont(new String[] {}); this.niceFont = FontUtils.setUnicodeFont( new String[] {"null", null, null, "no such font", "SansSerif"}); */ this.niceFont = FontUtils.setUnicodeFont( //use one of these fonts if available; otherwise, pick a default. new String[] {"Arial Unicode MS", "Code2000", "Batang"}); assert niceFont != null; this.niceFont = this.niceFont.deriveFont(startingFontSize); } /** Paint the screen with information from the model. */ public void paintComponent(java.awt.Graphics origG) { java.awt.Graphics2D g = (java.awt.Graphics2D) origG; this.setBackground(this.getColor()); super.paintComponent(g); assert this.niceFont != null; g.setFont(this.niceFont); FontMetrics fm = g.getFontMetrics(); double newyStart = fm.getHeight() + 5; // provide a small top margin if (newyStart >= this.yStart) { //line height has increased -- honor it immediately this.yStart = newyStart; } else { //line height has decreased -- smooth the decrease this.yStart = (this.yStart + newyStart) / 2.0; } g.setColor(new Color(180, 40, (int) (50 * Math.random()))); //random dark red //Here is where we get the characters from the model: java.util.List cList = this.charsModel.getCharList(); java.util.Iterator iter = cList.iterator(); Point2D position = new Point2D.Double(xStart, yStart); while (iter.hasNext()) { if (!iter.hasNext()) { g.setColor(Color.black); //make this one (the last one) black } char currentChar = ((Character) iter.next()).charValue(); String charToDraw = " " + currentChar; //leave room for a space //See if the current string will fit on the current line. Point2D oldposition = new Point2D.Double( position.getX(), position.getY()); //don't use just = updatePosition(position, charToDraw, g.getFontMetrics()); //careful -- "position" is itself updated by the previous call. //At this point, position is where we would be IF we wrote the string. //following assertion might be too string -- //might be some characters with 0 width. //However, in the current solution there is at least the space char. assert !oldposition.equals(position); if (pageWrapHappens(oldposition, position)) { //this char won't fit on the page //careful to use float for the font size. //an int is interpreted as a style!!! float oldFSize = niceFont.getSize2D(); float listSize = cList.size(); //following estimates needed reduction in font size, based //on assumption that only one more char needs to be drawn. float newFSize = oldFSize * (listSize - 1)/listSize; newFSize = (oldFSize + newFSize)/2.0f; //smoother this.niceFont = niceFont.deriveFont(newFSize); if (this.niceFont.getSize2D() >= oldFSize) { //shouldn't happen assert this.niceFont.getSize2D() < oldFSize; } this.repaint(); // start all over with the smaller font break; } else { //No page wrap. if (lineWrapHappens(oldposition, position)) { //better use the new position g.drawString(charToDraw, Math.round(position.getX()), Math.round(position.getY())); updatePosition(position, charToDraw, fm); } else { g.drawString(charToDraw, Math.round(oldposition.getX()), Math.round(oldposition.getY())); } String printInfo = " " + currentChar + " (u" + Integer.toHexString((int) currentChar) + ") "; System.out.println(printInfo); //for debug only } } //end paint } //***************** INVARIANTS OF UPDATE POSITION *********** // Described with comments and/or executable assert statements. // (Note: the basic code looks slightly different from the start code, // because double precision is now used for the position, instead // of int precision. That was not a necessary part of the solution. /** Update the drawing position for next time. * Precondition: xStart and yStart are valid x, y coordinates within * the current panel. * @param position The position (lower left corner) of the last String * drawn; this must be a valid location in the current component. Will * be updated (see below). * @param lastString The last string drawn (at position). * @param fm The FontMetrics object of the current drawing position. * Postconditions: the position argument is updated, to represent the next * position on the current component where are character can be drawn. * @return a reference to the updated position * It takes into account only the width of the previous character, and * not the next character. The position may wrap to a new line or back * to the beginning of the drawing area (as given by startX and startY) * if the current line or area is exhausted. No instance variables are * modified. */ private Point2D updatePosition(Point2D position, String lastString, FontMetrics fm) { assert position != null; //check precondition double x = position.getX(); assert 0.0 <= x && x <= this.panelWidth; //test precondition double y = position.getY(); assert 0.0 <= y && y <= this.panelHeight; //test precondition assert lastString != null; //test precondition assert fm != null; //test precondition int latestWidth = fm.stringWidth(lastString); assert latestWidth >= 0; //test postcondition of stringWidth position.setLocation(x + latestWidth, y); assert (x + latestWidth) == position.getX(); //test postcondition of setLocation assert y == position.getY(); //ditto if (x + latestWidth > panelWidth) { //start a new line position.setLocation(this.xStart, y + fm.getHeight()); //Yes, postconditions of setLocation could be checked! if (position.getY() >= panelHeight - 2) { //at bottom of panel already -- no room. Wrap to top. position.setLocation(position.getX(), this.yStart); //Could check postconditions here... } } //Before returning, check the invariants on position (same as preconditions) //The method in the starter code did not have a return value, //so it wouldn't have any of the following. assert position != null; double x2 = position.getX(); assert 0.0 <= x2 && x2 <= this.panelWidth; //test postcondition double y2 = position.getY(); assert 0.0 <= y2 && y2 <= this.panelHeight; //test postcondition return position; } public static boolean lineWrapHappens(Point2D oldPosition, Point2D newPosition) { return oldPosition.getX() > newPosition.getX(); } public static boolean pageWrapHappens(Point2D oldPosition, Point2D newPosition) { return oldPosition.getY() > newPosition.getY(); } /** Tells a small amount of author information. * @return a "short string" identifying the author(s) * of the class. * **** IF YOU DO NOT WISH TO BE PUBLICLY IDENTIFIED, return a pseudonym or * code name. */ public String getAuthor() { return "Gess Hoo"; } /** Return a preferred color for the object. Colors might be used by * clients to set a background color, a border color, etc. * @return A Color, or null if there is no preferred color. */ public Color getColor() { return new Color(0xff, 0xaa, 20 + (int)(30 * Math.random())); //random brown } /** Gives a URL to an image representing this class. * @return a string which is a full URL to a .jpg or .gif file. The * image is expected to in some way stand for or depict the class. It * is acceptable to return null. */ public String getImageURLString() { //return "C:\\Documents and Settings\\dickey\\My Documents\\My Pictures\\2003_01_19 & 20\\144-4444_IMG.JPG"; //return "http://www.prairieghosts.com/ouija1.jpg"; return "http://www.unicode.org/img/globe_iuc23.gif"; } /** Return a "long string" describing the object or how to use it. */ public String getInstructions() { return "Tap on the Board (above) to see the next character"; } /** Return a "long string" describing this object. The description might * be the same for all objects of the type. */ public String getObjectDescription() { String retString = "The board is like a telegraph receiver." + " It hears messages coming through the ether, " + "and reveals them to you one character at a time."; //" For Unicode character charts, see http://www.unicode.org/charts/"; String fontName = ""; if (this.niceFont != null) { fontName = this.niceFont.getFontName(); } else { fontName = " a default."; } retString += " The font in use is " + fontName + "."; return retString; } /** Return a short string identifying this object. The object name should * be unique for each object instantiated. */ public String getObjectName() { return this.objectID; } public String toString() { return this.getObjectName(); } //end class RandCharPanel }