package ptft; /* * JCellularAutomataPaint.java * * Written by Will Braynen * Group for Logic and Formal Semantics, SUNY Stony Brook (www.ptft.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Created on May 13, 2004, 9:01 PM */ import java.awt.*; import java.awt.event.*; import java.net.*; /** * Adds the functionality of a simple paint program so that the user can * paint cells a different strategy or ethnicity color. * * @author Will Braynen */ public final class JPaintCellularAutomata extends JPtftCellularAutomata implements MouseListener, MouseMotionListener { // fields protected int m_activeStrategy; // to paint with protected int m_activeEthnicity; // to paint with protected JPtftCell m_lastDrawnCell; protected boolean m_isPaintModeEnabled = true; protected boolean m_isPencil = true; protected boolean m_isMouseButtonPressed = false; protected Cursor m_pencilCursor; protected Cursor m_bucketCursor; protected boolean[][] m_isFlooded; private int m_n = 0; /** Creates new form JPtftCellularAutomata */ public JPaintCellularAutomata() { //Harry: this is the only place that the cell width needs to be set //it will propogate from here to the rest of the classes this(8); //initComponents(); // DO NOT CALL THIS -- ALREADY CALLED IN PARENT CLASS } /** Creates new form JCellularAutomataPaint */ public JPaintCellularAutomata(int cellWidth) { super(cellWidth); //initComponents(); // DO NOT CALL THIS -- already called in parent constructor // init fields m_activeStrategy = 0; m_activeEthnicity = 0; m_isStrategyDisplayed = true; // listen for mouse events if (m_isPaintModeEnabled) { addMouseListener (this); addMouseMotionListener (this); } m_pencilCursor = ImageToolkit.createCursor( this, "/ptft/images/pencil.gif", new Point(07,24), "pencil" ); m_bucketCursor = ImageToolkit.createCursor( this, "/ptft/images/bucket.gif", new Point(22,22), "bucket" ); m_isFlooded = new boolean[m_rows][m_columns]; } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ private void initComponents() {//GEN-BEGIN:initComponents setLayout(new java.awt.BorderLayout()); }//GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables /** enables paint mode */ public void setPaintMode( boolean isEnabled, boolean isPencil ) { // do nothing if this is the way things are already if (m_isPaintModeEnabled == isEnabled && m_isPencil == isPencil) return; m_isPaintModeEnabled = isEnabled; m_isPencil = isPencil; if (isEnabled) { // listen for mouse events addMouseListener (this); addMouseMotionListener (this); // change the cursors if (m_isPencil) setCursor( m_pencilCursor ); else setCursor( m_bucketCursor ); } else // disable paint mode { // ignore mouse events removeMouseListener (this); removeMouseMotionListener (this); // change the cursors setCursor( new Cursor(Cursor.DEFAULT_CURSOR) ); } } public boolean isPaintModeEnabled() { return m_isPaintModeEnabled; } public boolean isPencil() { return m_isPencil; } /** User-painting will be done with this strategy color */ public void setActiveStrategy( int strategy ) { m_activeStrategy = strategy; } /** User-painting will be done with this ethnicity color */ public void setActiveEthnicity( int ethnicity ) { m_activeEthnicity = ethnicity; } /** Draws a line recursively connecting two cells * * @param c1 starting point of the line * @param c2 ending point of the line */ protected void drawLine( JPtftCell c1, JPtftCell c2 ) { if ((null == c1) || (null == c2)) return; // If we've been dragging the mouse already, then fill in // the cells between this one and the last one. (If the // mouse movement was fast, we might've missed some cells) // base case if ((Math.abs (c1.m_row - c2.m_row) < 2) && (Math.abs (c1.m_column - c2.m_column) < 2)) { // p1 and p2 are either the same or next to each other drawCell( c1 ); drawCell( c2 ); return; } // recursive call: split the line in two int midpointRow = (c1.m_row + c2.m_row) / 2; int midpointColumn = (c1.m_column + c2.m_column) / 2; JPtftCell midpointCell = (JPtftCell) m_grid[ midpointRow ][ midpointColumn ]; drawLine ( c1, midpointCell ); drawLine ( c2, midpointCell ); } /** * Depending on whether we're viewing strategies or ethnicities, changes * the cell's strategy or ethnicity to m_activeStrategy * or m_activeEthnicity. * * @see #setActiveStrategy * @see #setActiveEthnicity * @see #toggleViews */ protected void drawCell( JPtftCell cell ) { if (null == cell) return; if (m_isStrategyDisplayed) { cell.setStrategy (m_activeStrategy); } else // painting ethnicities { cell.setEthnicity (m_activeEthnicity); } m_lastDrawnCell = cell; repaint(); } /** * Returns the cell located at the pixel coordinate p. * This also helps us avoid missing mouse clicks that are in-between cells. */ protected JPtftCell getCell( Point p ) { int row = (p.y - m_cellPadding) / (m_cellWidth + m_cellPadding); int col = (p.x - m_cellPadding) / (m_cellWidth + m_cellPadding); if ((row >= m_rows) || (col >= m_columns) || (row < 0) || (col < 0)) { return null; } return (JPtftCell) m_grid[row][col]; } public void fillWithBucket( JPtftCell cell, int strategy, int ethnicity ) { //if (m_isFloodFilling) return; //m_isFloodFilling = true; for (int x = 0; x < m_columns; x++) for (int y = 0; y < m_rows; y++) m_isFlooded [x][y] = false; int fillColor = m_isStrategyDisplayed ? strategy : ethnicity; int oldColor = m_isStrategyDisplayed ? cell.getStrategy() : cell.getEthnicity(); m_n = 0; floodFill( cell, fillColor, oldColor, "original" ); //m_isFloodFilling = false; repaint(); } // end fillWithBucket protected void setCell( JPtftCell cell, int color ) { if (m_isStrategyDisplayed) cell.setStrategy( color ); else cell.setEthnicity( color ); } // end setCell protected boolean sameColor( JPtftCell cell, int color ) { return ( m_isStrategyDisplayed ? cell.getStrategy() == color : cell.getEthnicity() == color ); } // end sameColor /** * Fills regions of color "old" with the new color "fill" starting at * the given coordinates * * @param cell starting cell for the flood fill * @param oldColor color to paint with */ protected void floodFill( JPtftCell cell, int fillColor, int oldColor, String text ) { int row = cell.getRow(); int column = cell.getColumn(); //if (m_isFlooded[row][column]) return; if (row < 0 || row >= m_rows) return; if (column < 0 || column >= m_columns) return; System.out.println ("floodFill " + m_n++ + ", direction: " + text); if ( sameColor( cell, oldColor )) { } m_n--; // int row = startCell.getRow(); // int column = startCell.getColumn(); // if (m_isFlooded[row][column]) return; // if ( ! sameColor( startCell, oldColor )) return; // Wraps around the grid while filling. // (Otherwise, m_isFlooded array would not be necessary and // we could instead just return when x or y fall lower than 0 // or exceed the width and height of the grid). // int row = startCell.getRow(); // int column = startCell.getColumn(); // if (m_isFlooded[row][column]) return; // if ( ! sameColor( startCell, oldColor )) return; // // // scan to the left, starting to the left of startCell // JPtftCell cell = (JPtftCell)getNeighbor( startCell, Direction.W ); // row = cell.getRow(); // column = cell.getColumn(); // while ( ! m_isFlooded[row][column] && sameColor( cell, oldColor )) // { // setCell( cell, fillColor ); // m_isFlooded[row][column] = true; // // // next cell to the left // cell = (JPtftCell)getNeighbor( cell, Direction.W ); // column = cell.getColumn(); // // // fill children (neighbors above and below) // floodFill( (JPtftCell)getNeighbor( cell, Direction.N ), fillColor, oldColor, "N" ); // floodFill( (JPtftCell)getNeighbor( cell, Direction.S ), fillColor, oldColor, "S" ); // } // // // scan to the right, starting with startCell // cell = startCell; // row = cell.getRow(); // column = cell.getColumn(); // while ( ! m_isFlooded[row][column] && sameColor( cell, oldColor )) // { // setCell( cell, fillColor ); // m_isFlooded[row][column] = true; // // // next cell to the right // cell = (JPtftCell)getNeighbor( cell, Direction.E ); // column = cell.getColumn(); // } } // end floodFill public void mouseDragged(MouseEvent e) { if (m_isPencil) { JPtftCell cell = getCell( e.getPoint() ); drawLine (cell, m_lastDrawnCell); m_lastDrawnCell = cell; } } public void mouseMoved(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) { m_isMouseButtonPressed = true; JPtftCell cell = getCell( e.getPoint() ); if (m_isPencil) { drawCell( cell ); } else { fillWithBucket( cell, m_activeStrategy, m_activeEthnicity ); } } public void mouseReleased(MouseEvent e) { // disable drawLine call in mouseDragged m_lastDrawnCell = null; m_isMouseButtonPressed = false; } }