/* Stuart Reges and Marty Stepp 2006/2/12 The DrawingPanel class provides a simple interface for drawing persistent images using a Graphics object. An internal BufferedImage object is used to keep track of what has been drawn. A client of the class simply constructs a DrawingPanel of a particular size and then draws on it with the Graphics object, setting the background color if they so choose. To ensure that the image is always displayed, a timer calls repaint at regular intervals. */ import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; import javax.swing.filechooser.FileFilter; // A drawing surface for painting 2D graphics. public class DrawingPanel extends FileFilter implements ActionListener, MouseMotionListener, WindowListener { // class constants private static final String TITLE = "CSE 142 Drawing Panel"; private static final int DELAY = 250; // delay between repaints in millis public static final String DUMP_IMAGE_PROPERTY_NAME = "drawingpanel.autosavefile"; public static final String DUMP_DELAY_PROPERTY_NAME = "drawingpanel.autosavetime"; public static final String EXIT_PROPERTY_NAME = "drawingpanel.exit"; // shared variables private static String TARGET_IMAGE_FILE_NAME = null; private static long DUMP_IMAGE_DELAY = DELAY; private static boolean PRETTY = false; // true to anti-alias private static boolean DUMP_IMAGE = false; // true to write DrawingPanel to file private static boolean EXIT = true; // exit program on window close // last drawing panel that has been created private static DrawingPanel lastInstance; // returns the last drawing panel that has been created (used by test cases) public static DrawingPanel getLastInstance() { return lastInstance; } // whether the screen will be anti-aliased public static void setAntiAlias(boolean b) { PRETTY = b; } // delay until saving the image public static long getDelay() { return DUMP_IMAGE_DELAY; } // whether the program should exit when the window is closed public static void setExit(boolean exit) { EXIT = exit; } // the image file to save into public static void setTargetImageFileName(String fileName) { TARGET_IMAGE_FILE_NAME = fileName; DUMP_IMAGE = true; } private int width, height; // dimensions of window frame private JFrame frame; // overall window frame private JPanel panel; // overall drawing surface private BufferedImage image; // remembers drawing commands private Graphics2D g2; // graphics context for painting private JLabel statusBar; // status bar showing mouse position private JFileChooser chooser; // file chooser to save files private long createTime; // time when the window was created // class initializer static { // checks for setting of target image file TARGET_IMAGE_FILE_NAME = System.getProperty(DUMP_IMAGE_PROPERTY_NAME); DUMP_IMAGE = (TARGET_IMAGE_FILE_NAME != null); EXIT = (System.getProperty(EXIT_PROPERTY_NAME) == null); String dumpDelay = System.getProperty(DUMP_DELAY_PROPERTY_NAME); if (dumpDelay != null) { DUMP_IMAGE = true; DUMP_IMAGE_DELAY = Long.parseLong(dumpDelay); } } // construct a drawing panel of given width and height enclosed in a window public DrawingPanel(int width, int height) { lastInstance = this; // checks for setting of target image file TARGET_IMAGE_FILE_NAME = System.getProperty(DUMP_IMAGE_PROPERTY_NAME); DUMP_IMAGE = (TARGET_IMAGE_FILE_NAME != null); this.width = width; this.height = height; this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); this.statusBar = new JLabel(" "); this.statusBar.setBorder(BorderFactory.createLineBorder(Color.BLACK)); this.panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); this.panel.setBackground(Color.WHITE); this.panel.setPreferredSize(new Dimension(width, height)); this.panel.add(new JLabel(new ImageIcon(image))); // listen to mouse movement this.panel.addMouseMotionListener(this); this.g2 = (Graphics2D)image.getGraphics(); this.g2.setColor(Color.BLACK); if (PRETTY) { this.g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // this.g2.setStroke(new BasicStroke(1.1f)); } // main window frame this.frame = new JFrame(TITLE); this.frame.setResizable(false); this.frame.addWindowListener(this); this.frame.getContentPane().add(panel); this.frame.getContentPane().add(statusBar, "South"); // menu bar JMenuItem saveAs = new JMenuItem("Save As...", 'A'); saveAs.addActionListener(this); saveAs.setAccelerator(KeyStroke.getKeyStroke("ctrl S")); JMenu file = new JMenu("File"); file.setMnemonic('F'); file.add(saveAs); JMenuBar bar = new JMenuBar(); bar.add(file); this.frame.setJMenuBar(bar); this.frame.pack(); this.frame.setVisible(true); if (DUMP_IMAGE) { createTime = System.currentTimeMillis(); // this.frame.toBack(); } else { this.toFront(); } // repaint timer so that the screen will update Timer timer = new Timer(DELAY, this); timer.start(); } // used for an internal timer that keeps repainting public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof javax.swing.Timer) { // redraw the screen at regular intervals to catch all paint operations this.panel.repaint(); if (DUMP_IMAGE && System.currentTimeMillis() > createTime + DUMP_IMAGE_DELAY) { // shut down window if requested to do so this.exit(); } } else { // use file chooser dialog to save image if (this.chooser == null) { this.chooser = new JFileChooser(System.getProperty("user.dir")); this.chooser.setMultiSelectionEnabled(false); this.chooser.setFileFilter(this); } int choice = this.chooser.showSaveDialog(this.frame); if (choice == JFileChooser.APPROVE_OPTION) { File selectedFile = this.chooser.getSelectedFile(); try { // confirm overwrite of file if (selectedFile.exists()) { if (JOptionPane.showConfirmDialog(frame, "File exists. Overwrite?", "Overwrite?", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) { return; } } // save the file this.save(selectedFile.toString()); } catch (IOException ex) { JOptionPane.showMessageDialog(this.frame, "Unable to save image:\n" + ex); } } } } // closes the frame and exits the program public void exit() { this.frame.setVisible(false); this.frame.dispose(); if (DUMP_IMAGE) { try { DrawingPanel.this.save(TARGET_IMAGE_FILE_NAME); } catch (IOException e) { System.err.println("Unable to save to " + TARGET_IMAGE_FILE_NAME + ":\n" + e); } } if (EXIT) { System.exit(0); } } // obtain the Graphics object to draw on the panel public Graphics2D getGraphics() { return this.g2; } // obtain the JFrame object for the onscreen window public JFrame getJFrame() { return this.frame; } // listens to mouse dragging public void mouseDragged(MouseEvent e) {} // listens to mouse movement public void mouseMoved(MouseEvent e) { DrawingPanel.this.statusBar.setText("(" + e.getX() + ", " + e.getY() + ")"); } // take the current contents of the panel and write them to a file public void save(String filename) throws IOException { String extension = filename.substring(filename.lastIndexOf(".") + 1); // create second image so we get the background color BufferedImage image2 = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB); Graphics g = image2.getGraphics(); g.setColor(panel.getBackground()); g.fillRect(0, 0, this.width, this.height); g.drawImage(this.image, 0, 0, panel); // write file ImageIO.write(image2, extension, new java.io.File(filename)); } // set the background color of the drawing panel public void setBackground(Color c) { this.panel.setBackground(c); } // show or hide the drawing panel on the screen public void setVisible(boolean visible) { this.frame.setVisible(visible); } // makes the program pause for the given amount of time, // allowing for animation public void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) {} } // makes drawing panel become the rearmost window on the screen public void toBack() { this.frame.toBack(); } // makes drawing panel become the frontmost window on the screen public void toFront() { this.frame.toFront(); } // waits for the drawing panel to be closed public void waitForClose() { while (this.frame.isVisible()) { try { Thread.sleep(500); } catch (InterruptedException e) {} } } // listens for window to close public void windowClosing(WindowEvent e) { this.exit(); } // methods of WindowListener interface public void windowActivated(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowOpened(WindowEvent e) {} // methods of FileFilter interface public boolean accept(File file) { return file.isDirectory() || file.getName().toLowerCase().endsWith(".png"); } public String getDescription() { return "PNG images (*.png)"; } }