import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * This is the class that does most of the work in the MosaicDraw program. * It creates the two pieces of the program -- the panel and the menu bar -- and * makes them available in its getMosaicPanel() and getMenuBar() methods. * It attaches a mouse listener to the panel to respond to user mouse actions. * It attaches a listener to all the menu items to respond to menu commands from * the user. It containes other instance methods and instance variables to * implement the drawing and all the menu commands. (Note that this class does * not itself represent a GUI component.) This class depends on MosaicPanel.java. */ public class MosaicDrawController { private final static int DRAW_TOOL = 0; // possible values for currentTool private final static int ERASE_TOOL = 1; private final static int DRAW_3x3_TOOL = 2; private final static int ERASE_3x3_TOOL = 3; private int currentTool; // The current tool; this is changed when the // user makes a selection from the Tools menu. private int currentRed, currentGreen, currentBlue; // The current color. // These change when the user selects from the Color menu // They are uses whenever a square is painted. (NOTE: // I am using three integers to represent the color, rather // than a variable of type Color, to make it easier to add // in the random color variation.) private MosaicPanel mosaic; // The panel where the drawing takes place. private boolean useRandomness; // If true, then a small random variation is // added to the current color whenever a // square is painted. The value is controlled // by the "Use Randomness" option in the // Control menu. private boolean useSymmetry; // If true, then whenever a square is painted // or erased, the three symmetrical squares // obtained by reflecting the square vertically // and horizontally are also painted or erased. // This is controlled by the "Use Symmetry" // option in the control menu. /** * Create a controller that uses a MosaicPanel with 40 rows and 40 columns, * and in which the preferred size of each square is 12 pixels. */ public MosaicDrawController() { this(40,40,12); } /** * Create a controller that uses a MosaicPanel with a specified number of * rows and columns and in which the preferred size of each square is 12 pixels. */ public MosaicDrawController(int rows, int columns) { this(rows,columns,12); } /** * Create a controller that uses a MosaicPanel with a specified number of * rows and columns and in which the preferred size of each square is also * given as a parameter. */ public MosaicDrawController(int rows, int columns, int squareSize) { mosaic = new MosaicPanel(rows, columns, squareSize, squareSize); useRandomness = true; useSymmetry = false; currentRed = 150; currentGreen = 150; currentBlue = 225; MouseHandler listener = new MouseHandler(); mosaic.addMouseListener(listener); mosaic.addMouseMotionListener(listener); } /** * Returns the MosaicPanel that is used by this controller, so that it * can, for example, be used as the content pane of a JFrame or JApplet. */ public MosaicPanel getMosaicPanel() { return mosaic; } /** * Creates and returns a menu bar that contains options that affect the * drawing that is done on the MosaicPanel. */ public JMenuBar getMenuBar() { JMenuBar menuBar = new JMenuBar(); MenuHandler listener = new MenuHandler(); JMenu controlMenu = new JMenu("Control"); addMenuItem(controlMenu,"Fill",listener); addMenuItem(controlMenu,"Clear",listener); controlMenu.addSeparator(); addToggleMenuItem(controlMenu,"Use Randomness",listener,true); addToggleMenuItem(controlMenu,"Use Symmetry",listener,false); addToggleMenuItem(controlMenu,"Show Grouting",listener,true); menuBar.add(controlMenu); JMenu colorMenu = new JMenu("Color"); addMenuItem(colorMenu,"Red",listener); addMenuItem(colorMenu,"Green",listener); addMenuItem(colorMenu,"Blue",listener); addMenuItem(colorMenu,"Cyan",listener); addMenuItem(colorMenu,"Magenta",listener); addMenuItem(colorMenu,"Yellow",listener); addMenuItem(colorMenu,"Gray",listener); colorMenu.addSeparator(); addMenuItem(colorMenu,"Custom Color...",listener); menuBar.add(colorMenu); JMenu toolMenu = new JMenu("Tools"); addMenuItem(toolMenu,"Draw",listener); addMenuItem(toolMenu,"Erase",listener); addMenuItem(toolMenu,"Draw 3x3",listener); addMenuItem(toolMenu,"Erase 3x3",listener); menuBar.add(toolMenu); return menuBar; } /** * Utility method to create a menu item, add a listener to it, and add it to a menu. */ private void addMenuItem(JMenu menu, String command, ActionListener listener) { JMenuItem menuItem = new JMenuItem(command); menuItem.addActionListener(listener); menu.add(menuItem); } /** * Utility method to create a checkbox menu item, add a listener to it, * add it to a menu, and say whether it is initially selected or not. */ private void addToggleMenuItem(JMenu menu, String command, ActionListener listener, boolean selected) { JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(command); menuItem.setSelected(selected); menuItem.addActionListener(listener); menu.add(menuItem); } /** * Erases the square in a specified row and column. If symmetry is turned * on, the three symmetrical squares are also erased. */ private void eraseSquare(int row, int col) { mosaic.setColor(row, col, null); if (useSymmetry) { mosaic.setColor(mosaic.getRowCount() - 1 - row, col, null); mosaic.setColor(row, mosaic.getColumnCount() - 1 - col, null); mosaic.setColor(mosaic.getRowCount() - 1 - row, mosaic.getColumnCount() - 1 - col, null); } } /** * Applys the current drawing color to the square in a given row and column. * If randomness is turned on, a random amount is added to the red, green, and * blue components of the drawing color. If symmetry is turned on, then the * three symmetrical squares are also painted. */ private void paintSquare(int row, int col) { int r = currentRed; int g = currentGreen; int b = currentBlue; if (useRandomness) { if (r < 60) r = (int)(60*Math.random()); else if (r > 255-60) r = 255 - (int)(60*Math.random()); else r = r + (int)(60*Math.random() - 30); if (g < 60) g = (int)(60*Math.random()); else if (g > 255-60) g = 255 - (int)(60*Math.random()); else g = g + (int)(60*Math.random() - 30); if (b < 60) b = (int)(60*Math.random()); else if (b > 255-60) b = 255 - (int)(60*Math.random()); else b = b + (int)(60*Math.random() - 30); } mosaic.setColor(row, col, r, g, b); if (useSymmetry) { mosaic.setColor(mosaic.getRowCount() - 1 - row, col, r, g, b); mosaic.setColor(row, mosaic.getColumnCount() - 1 - col, r, g, b); mosaic.setColor(mosaic.getRowCount() - 1 - row, mosaic.getColumnCount() - 1 - col, r, g, b); } } /** * This method is called when the user clicks the mouse or drags it over the * square in the specified row and column. It takes the appropriate action, * depending on which drawing tool is currently selected. */ private void applyCurrentTool(int row, int col) { int minrow, mincol, maxrow, maxcol; switch (currentTool) { case DRAW_TOOL: paintSquare(row,col); break; case ERASE_TOOL: eraseSquare(row,col); break; case DRAW_3x3_TOOL: minrow = Math.max(0, row-1); maxrow = Math.min(mosaic.getRowCount()-1, row+1); mincol = Math.max(0, col-1); maxcol = Math.min(mosaic.getColumnCount()-1, col+1); for (int i = minrow; i <= maxrow; i++) for (int j = mincol; j <= maxcol; j++) paintSquare(i,j); break; case ERASE_3x3_TOOL: minrow = Math.max(0, row-1); maxrow = Math.min(mosaic.getRowCount()-1, row+1); mincol = Math.max(0, col-1); maxcol = Math.min(mosaic.getColumnCount()-1, col+1); for (int i = minrow; i <= maxrow; i++) for (int j = mincol; j <= maxcol; j++) eraseSquare(i,j); break; } } /** * An object of type MouseHandler is installed as a mouse listener and mouse * motion listener on the MosaicPanel. It responds to a mousePressed or * mouseDragged event on the panel by calling the applyCurrentTool() method * for the square that contained the mouse. */ private class MouseHandler implements MouseListener, MouseMotionListener { public void mousePressed(MouseEvent evt) { int row = mosaic.yCoordToRowNumber(evt.getY()); int col = mosaic.xCoordToColumnNumber(evt.getX()); if (row >= 0 && row < mosaic.getRowCount() && col >= 0 && col < mosaic.getColumnCount()) applyCurrentTool(row,col); } public void mouseDragged(MouseEvent evt) { int row = mosaic.yCoordToRowNumber(evt.getY()); int col = mosaic.xCoordToColumnNumber(evt.getX()); if (row >= 0 && row < mosaic.getRowCount() && col >= 0 && col < mosaic.getColumnCount()) applyCurrentTool(row,col); } public void mouseReleased(MouseEvent evt) { } public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } public void mouseMoved(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } } // end class MouseHandler /** * An object of type MenuHandler is used as the action listener for all * the menu items in the menu. It can tell which item was selected by * the user by looking at the action command associtated with the ActionEvent. * The action command will be the text of the menu item that was selected. * (This requires, of course, that all the menu items have different names.) * This is a fairly simple way to handle menu items, but not the best or * most flexable. */ private class MenuHandler implements ActionListener { public void actionPerformed(ActionEvent evt) { String command = evt.getActionCommand(); if (command.equals("Fill")) { // color every square mosaic.setAutopaint(false); for (int row = 0; row < mosaic.getRowCount(); row++) for (int col = 0; col < mosaic.getColumnCount(); col++) paintSquare(row,col); mosaic.setAutopaint(true); } else if (command.equals("Clear")) { // clear by filling mosaic with null mosaic.fill(null); } else if (command.equals("Use Randomness")) { // Set the value of useRandomness depending on the menu item's state. JCheckBoxMenuItem toggle = (JCheckBoxMenuItem)evt.getSource(); useRandomness = toggle.isSelected(); } else if (command.equals("Use Symmetry")) { // Set the value of useSymmetry depending on the menu item's state. JCheckBoxMenuItem toggle = (JCheckBoxMenuItem)evt.getSource(); useSymmetry = toggle.isSelected(); } else if (command.equals("Show Grouting")) { // Turn grouting on or off, depending on the menu item's state. JCheckBoxMenuItem toggle = (JCheckBoxMenuItem)evt.getSource(); if (toggle.isSelected()) mosaic.setGroutingColor(Color.GRAY); else mosaic.setGroutingColor(null); // Turns grouting off. } else if (command.equals("Red")) { currentRed = 255; // Set current drawing color. currentGreen = 0; currentBlue = 0; } else if (command.equals("Green")) { currentRed = 0; currentGreen = 255; currentBlue = 0; } else if (command.equals("Blue")) { currentRed = 0; currentGreen = 0; currentBlue = 255; } else if (command.equals("Cyan")) { currentRed = 0; currentGreen = 255; currentBlue = 255; } else if (command.equals("Magenta")) { currentRed = 255; currentGreen = 0; currentBlue = 255; } else if (command.equals("Yellow")) { currentRed = 255; currentGreen = 255; currentBlue = 0; } else if (command.equals("Gray")) { currentRed = 180; currentGreen = 180; currentBlue = 180; } else if (command.equals("Custom Color...")) { // Let the user select the current drawing color using a // standard color chooser dialog. The color chooser // is initially set to the current drawing color. Color c = new Color(currentRed, currentGreen, currentBlue); c = JColorChooser.showDialog(mosaic, "Select Drawing Color", c); // If c comes back null, it means that the user canceled // the dialog, so the current drawing color should not change. if (c != null) { currentRed = c.getRed(); currentGreen = c.getGreen(); currentBlue = c.getBlue(); } } else if (command.equals("Draw")) currentTool = DRAW_TOOL; else if (command.equals("Erase")) currentTool = ERASE_TOOL; else if (command.equals("Draw 3x3")) currentTool = DRAW_3x3_TOOL; else if (command.equals("Erase 3x3")) currentTool = ERASE_3x3_TOOL; } } // end class MenuHandler } // end class MosaicDrawController