// DrawTool.java // // Author: Rahul Simha // Modified: James Marshall // Jan-March 2011 // // A simple widget for drawing, animation and text input. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.*; import java.text.*; import java.io.*; class DrawObject { double x,y; // For Points, one end of lines, // and top-left of ovals/rectangles. double x2,y2; // For the other end of lines. double width,height; // For ovals/rectangles. int diameter = 6; // For points. Color color; // Desired color. } public class DrawTool extends JPanel { // What we draw: points, lines, ovals and rectangles. static java.util.List points, lines, ovals, rectangles; // Animated versions: these will clear between successive frames. static java.util.List animPoints, animLines, animOvals, animRectangles; // One can change the color. These are the defaults. static Color pointColor = Color.red; static Color lineColor = Color.black; static Color ovalColor = Color.blue; static Color rectangleColor = Color.green; static Color backgroundColor = Color.white; static double minX=0, maxX=10, minY=0, maxY=10; // Bounds. static int numIntervals = 10; // # tick marks. static int pointDiameter = 6; // Size of dot. // To prettify text: static DecimalFormat df = new DecimalFormat(); // GUI stuff. static JPanel drawArea = new DrawTool (); static Dimension D; // Size of drawing area. static int inset=60; // Inset of axes and bounding box. // GUI elements. static JFrame frame; static JLabel statusLabel = new JLabel (" "); static JLabel outputLabel = new JLabel (" "); static JTextField inputField = new JTextField (40); // Very light blue: static Color inputPanelColor = new Color (240, 240, 255); // Has input occurred? That is, has "Enter" been clicked? static boolean hasEntered = false; // Animation stuff. The current object (in each category). static boolean animationMode = false; // Static initializer. static { points = Collections.synchronizedList (new ArrayList()); lines = Collections.synchronizedList (new ArrayList()); ovals = Collections.synchronizedList (new ArrayList()); rectangles = Collections.synchronizedList (new ArrayList()); animPoints = Collections.synchronizedList (new ArrayList()); animLines = Collections.synchronizedList (new ArrayList()); animOvals = Collections.synchronizedList (new ArrayList()); animRectangles = Collections.synchronizedList (new ArrayList()); df.setMaximumFractionDigits (4); } //////////////////////////////////////////////////////////////// // // The public methods. public static void setXYRange (double minXvalue, double maxXvalue, double minYvalue, double maxYvalue) { minX = minXvalue; maxX = maxXvalue; minY = minYvalue; maxY = maxYvalue; } public static void display () { // Store reference to frame for use in dialogs. frame = new JFrame (); buildGUI (); frame.setVisible (true); } public static void drawPoint (double x, double y) { DrawObject p = new DrawObject (); p.color = pointColor; p.x = x; p.y = y; p.diameter = pointDiameter; if (animationMode) { synchronized(animPoints) { animPoints.add (p); } } else { synchronized(points) { points.add (p); } } drawArea.repaint (); } public static void drawLine (double x1, double y1, double x2, double y2) { DrawObject L = new DrawObject (); L.color = lineColor; L.x = x1; L.y = y1; L.x2 = x2; L.y2 = y2; if (animationMode) { synchronized(animLines) { animLines.add (L); } } else { synchronized(lines) { lines.add (L); } } drawArea.repaint (); } public static void drawOval (double x1, double y1, double width, double height) { DrawObject R = new DrawObject (); R.color = ovalColor; R.x = x1; R.y = y1; R.width = width; R.height = height; if (animationMode) { synchronized(animOvals) { animOvals.add (R); } } else { synchronized(ovals) { ovals.add (R); } } drawArea.repaint (); } public static void drawRectangle (double x1, double y1, double width, double height) { DrawObject R = new DrawObject (); R.color = rectangleColor; R.x = x1; R.y = y1; R.width = width; R.height = height; if (animationMode) { synchronized(animRectangles) { animRectangles.add (R); } } else { synchronized(rectangles) { rectangles.add (R); } } drawArea.repaint (); } public static void setPointColor (String colorString) { pointColor = toColor (colorString, pointColor); } public static void setLineColor (String colorString) { lineColor = toColor (colorString, lineColor); } public static void setOvalColor (String colorString) { ovalColor = toColor (colorString, ovalColor); } public static void setRectangleColor (String colorString) { rectangleColor = toColor (colorString, rectangleColor); } public static void setBackgroundColor (String colorString) { backgroundColor = toColor (colorString, backgroundColor); } public static void setPointSize (int diameter) { if (diameter > 0) { pointDiameter = diameter; } } public static void writePrompt (String s) { if (s == null) { return; } outputLabel.setForeground (Color.black); outputLabel.setText (" " + s); hasEntered = false; } public static void writeTopString (String s) { if (s == null) { return; } statusLabel.setForeground (Color.black); statusLabel.setText (" " + s); } public static void writeTopValue (double v) { writeTopString ("" + df.format(v)); } public static String getInputString () { return waitForInputString(); } public static int getInputInteger () { try { String inputString = waitForInputString (); int i = Integer.parseInt (inputString.trim()); return i; } catch (Exception e) { JOptionPane.showMessageDialog (frame, null, "Improper integer", JOptionPane.ERROR_MESSAGE); return 0; } } public static double getInputDouble () { try { String inputString = waitForInputString (); double d = Double.parseDouble (inputString.trim()); return d; } catch (Exception e) { JOptionPane.showMessageDialog (frame, null, "Improper double", JOptionPane.ERROR_MESSAGE); return 0; } } public static void startAnimationMode () { animationMode = true; } public static void endAnimationMode () { animationMode = false; } public static void animationPause (int pauseTime) { if ((pauseTime < 1) || (pauseTime > 1000)) { pauseTime = 100; } try { Thread.sleep (pauseTime); synchronized(animPoints) { animPoints.clear (); } synchronized(animLines) { animLines.clear (); } synchronized(animOvals) { animOvals.clear (); } synchronized(animRectangles) { animRectangles.clear (); } } catch (InterruptedException e) { } } //////////////////////////////////////////////////////////////// // // GUI and drawing static Color toColor (String colorString, Color defaultColor) { // Convert standard colors. if (colorString.equalsIgnoreCase ("black")) { return Color.black; } else if (colorString.equalsIgnoreCase ("blue")) { return Color.blue; } else if (colorString.equalsIgnoreCase ("cyan")) { return Color.cyan; } else if (colorString.equalsIgnoreCase ("dark gray")) { return Color.darkGray; } else if (colorString.equalsIgnoreCase ("gray")) { return Color.gray; } else if (colorString.equalsIgnoreCase ("green")) { return Color.green; } else if (colorString.equalsIgnoreCase ("light gray")) { return Color.lightGray; } else if (colorString.equalsIgnoreCase ("magenta")) { return Color.magenta; } else if (colorString.equalsIgnoreCase ("orange")) { return Color.orange; } else if (colorString.equalsIgnoreCase ("pink")) { return Color.pink; } else if (colorString.equalsIgnoreCase ("red")) { return Color.red; } else if (colorString.equalsIgnoreCase ("white")) { return Color.white; } else if (colorString.equalsIgnoreCase ("yellow")) { return Color.yellow; } else { return defaultColor; } } static String waitForInputString () { try { while (! hasEntered) { Thread.sleep (100); } return inputField.getText(); } catch (Exception e) { return ""; } } static void buildGUI () { // Need this size to balance axes. frame.setSize (650, 770); frame.setTitle ("DrawTool"); frame.addWindowListener ( new WindowAdapter () { public void windowClosing (WindowEvent e) { System.exit(0); } } ); Container cPane = frame.getContentPane (); // Status label on top. Unused for now. statusLabel.setOpaque (true); statusLabel.setBackground (Color.white); cPane.add (statusLabel, BorderLayout.NORTH); // Build the input/output elements at the bottom. JPanel panel = new JPanel (); panel.setBorder (BorderFactory.createLineBorder (Color.black)); panel.setBackground (inputPanelColor); panel.setLayout (new GridLayout (2,1)); panel.add (outputLabel); JPanel bottomPanel = new JPanel (); bottomPanel.setBackground (inputPanelColor); bottomPanel.add (inputField); bottomPanel.add (new JLabel (" ")); JButton enterButton = new JButton ("Enter"); enterButton.addActionListener ( new ActionListener () { public void actionPerformed (ActionEvent a) { hasEntered = true; } } ); bottomPanel.add (enterButton); panel.add (bottomPanel); cPane.add (panel, BorderLayout.SOUTH); // Drawing in the center. drawArea = new DrawTool (); cPane.add (drawArea, BorderLayout.CENTER); } public void paintComponent (Graphics g) { super.paintComponent (g); Graphics2D g2d = (Graphics2D)g; RenderingHints rh = g2d.getRenderingHints(); rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHints(rh); // Background. D = this.getSize (); g.setColor (backgroundColor); g.fillRect (0,0, D.width, D.height); Graphics2D g2 = (Graphics2D) g; // Axes, bounding box. g.setColor (Color.gray); g.drawLine (inset,D.height-inset, D.width-inset, D.height-inset); g.drawLine (inset,inset, inset,D.height-inset); g.drawLine (D.width-inset,inset, D.width-inset,D.height-inset); g.drawLine (inset,inset, D.width-inset, inset); double xDelta = (maxX-minX) / numIntervals; // X-ticks and labels. for (int i=1; i<=numIntervals; i++) { double xTickd = i*xDelta; int xTick = (int) ( xTickd/(maxX-minX) * (D.width-2*inset)); g.drawLine (inset+xTick,D.height-inset-5, inset+xTick,D.height-inset+5); double x = minX + i*xDelta; g.drawString (df.format(x), xTick+inset-5, D.height-inset+20); } // Y-ticks double yDelta = (maxY-minY) / numIntervals; for (int i=0; i points, java.util.List lines, java.util.List ovals, java.util.List rectangles) { try { // Lines synchronized(lines) { for (DrawObject L: lines) { g.setColor (L.color); drawLine (g, L); } } // Rectangles synchronized(rectangles) { for (DrawObject R: rectangles) { g.setColor (R.color); drawOvalOrRectangle (g, R, true); } } // Ovals synchronized(ovals) { for (DrawObject R: ovals) { g.setColor (R.color); drawOvalOrRectangle (g, R, false); } } // Points. synchronized(points) { for (DrawObject p: points) { g.setColor (p.color); drawPoint (g, p); } } } catch (ConcurrentModificationException e) { e.printStackTrace(); } } void drawPoint (Graphics g, DrawObject p) { if (p == null) { return; } int x = (int) ( (p.x-minX)/(maxX-minX) * (D.width-2*inset) ); int y = (int) ((p.y-minY) / (maxY-minY) * (D.height-2.0*inset) ); if (p.diameter > 1) { int r = p.diameter/2; g.fillOval (inset+x-r, D.height-y-inset-r, 2*r, 2*r); } else { g.fillRect (inset+x, D.height-y-inset, 1, 1); } } void drawLine (Graphics g, DrawObject L) { if (L == null) { return; } int x1 = (int) ( (L.x-minX)/(maxX-minX) * (D.width-2*inset) ); int y1 = (int) ((L.y-minY) / (maxY-minY) * (D.height-2.0*inset) ); int x2 = (int) ( (L.x2-minX)/(maxX-minX) * (D.width-2*inset) ); int y2 = (int) ((L.y2-minY) / (maxY-minY) * (D.height-2.0*inset) ); g.drawLine (inset+x1, D.height-y1-inset, inset+x2, D.height-y2-inset); } void drawOvalOrRectangle (Graphics g, DrawObject R, boolean isRect) { if (R == null) { return; } int x1 = (int) ( (R.x-minX)/(maxX-minX) * (D.width-2*inset) ); int y1 = (int) ((R.y-minY) / (maxY-minY) * (D.height-2.0*inset) ); double x = R.x + R.width; double y = R.y - R.height; int x2 = (int) ( (x-minX)/(maxX-minX) * (D.width-2*inset) ); int y2 = (int) ((y-minY) / (maxY-minY) * (D.height-2.0*inset) ); if (isRect) { g.drawRect (inset+x1, D.height-y1-inset, x2-x1, y1-y2); } else { g.drawOval (inset+x1, D.height-y1-inset, x2-x1, y1-y2); } } }