package fractal;
import java.awt.*;
import java.awt.event.*;
import fractal.utils.*;
class ControlPanel extends Panel
{
private DrawingCanvas drawingCanvas;
private Fractal
fractal;
private Insets
insets;
private final int
NUM_ROWS = 13;
private boolean
parameterChangeFlag; // See hasNewParameters()
private GridLayout
controlsGrid;
private GridLayout[] controlGrids;
private Panel
controlsPanel;
private Panel[]
controlPanels;
private Button
deleteButton;
private Button
drawButton;
private Button
helpButton;
private Button
nextButton;
private Button
prevButton;
private Button
stopButton;
private Checkbox
drawJuliaCheckbox;
private Choice
colorChoice;
private Label
colorLabel;
private Label
iMaxLabel;
private Label
iMinLabel;
private Label
iterationsLabel;
private Label
juliaCILabel;
private Label
juliaCRLabel;
private Label
rMaxLabel;
private Label
rMinLabel;
private Label
status;
private Label
status2;
private TextField
iMaxText;
private TextField
iMinText;
private TextField
iterationsText;
private TextField
juliaCIText;
private TextField
juliaCRText;
private TextField
rMaxText;
private TextField
rMinText;
protected ControlPanel( Fractal fractal, DrawingCanvas drawingCanvas
)
{
this.fractal = fractal;
this.drawingCanvas = drawingCanvas;
parameterChangeFlag = false;
insets = null;
}
protected void addColor( String colorSchemeName )
{
colorChoice.addItem( colorSchemeName );
}
public boolean getColor( StringBuffer colorSchemeName )
{
try
{
String scheme = colorChoice.getSelectedItem();
if( scheme == null )
{
return false;
}
colorSchemeName.setLength( 0 );
colorSchemeName.append( scheme );
}
catch( Throwable t )
{
return false;
}
return true;
}
protected boolean getComplexRect( ComplexRectangle newRect
)
{
double rMin;
double rMax;
double iMin;
double iMax;
// Get the user's new draw parameters from the
text controls.
// Convert them from Strings to numbers, if
possible.
try
{
rMin = Double.valueOf( rMinText.getText().trim()
).doubleValue();
rMax = Double.valueOf( rMaxText.getText().trim()
).doubleValue();
iMin = Double.valueOf( iMinText.getText().trim()
).doubleValue();
iMax = Double.valueOf( iMaxText.getText().trim()
).doubleValue();
if( rMin >= rMax )
{
setStatus( " Warning:
The Real Max is less than the Min." );
return false;
}
if( iMin >= iMax )
{
setStatus( " Warning:
Imaginary Max is less than the Min." );
return false;
}
}
catch( Throwable t )
{
setStatus( " Warning: Could not
convert all input to numbers." );
return false;
}
newRect.set( rMin, rMax, iMin, iMax );
return true;
}
public Insets getInsets()
{
// Called by the AWT. There is no setInsets().
(Swing fixes this.)
// Overriding getInsets() is the only way to
set the inset values.
if( insets == null )
{
insets = super.getInsets(); // Define
a 5 pixel border.
insets = new Insets( insets.top
+ 5, insets.left + 5,
insets.bottom + 5, insets.right + 5 );
}
return insets;
}
protected boolean getIterations( IntWrapper iterations )
{
// Get the maximum number of iterations, if
possible.
int mi = 0;
try
{
mi = Integer.valueOf( iterationsText.getText().trim()
).intValue();
if( mi < 1 )
{
setStatus( " Warning:
there must be at least 1 iteration." );
return false;
}
}
catch( Throwable t )
{
setStatus( " Warning: Cannot convert
iterations to a number." );
return false;
}
iterations.setValue( mi );
return true;
}
protected boolean getJuliaPoint( ComplexPoint c )
{
try
{
double juliaCR = Double.valueOf(
juliaCRText.getText().trim() ).doubleValue();
double juliaCI = Double.valueOf(
juliaCIText.getText().trim() ).doubleValue();
c.set( juliaCR, juliaCI );
}
catch( Throwable t )
{
setStatus( " Warning: Cannot get
Julia point." );
return false;
}
return true;
}
protected boolean hasNewParameters()
{
// Has the user made any changes to any of the
drawing parameters?
// Testing equality of doubles doesn't always
work due to roundoff error,
// so the code instead checks if anything has
been modified at all.
// Consistent use of BigDecimal rounding should
allow equality tests
// to work, but for now, this does the trick.
return parameterChangeFlag;
}
protected void initializeEventListeners()
{
// Example of the "Observer" Design Pattern:
// ControlPanel's inner classes "observe" the
AWT widgets.
// Detect the Draw ("Make New Fractal") button
being pressed.
DrawHandler drawHandler = new DrawHandler();
drawButton.addActionListener( drawHandler );
// Detect the "Stop" button being pressed.
stopButton.addActionListener( new StopHandler()
);
// Detect the "Previous" button being pressed.
prevButton.addActionListener( new PreviousHandler()
);
// Detect the "Next" button being pressed.
nextButton.addActionListener( new NextHandler()
);
// Detect the "Delete" button being pressed.
deleteButton.addActionListener( new DeleteHandler()
);
// Detect the "Help" button being pressed.
helpButton.addActionListener( new HelpHandler()
);
// Listen for the return key, which will always
Draw.
ReturnKeyHandler returnKeyHandler = new ReturnKeyHandler();
// The return key is a KeyEvent for Buttons
drawButton.addKeyListener( returnKeyHandler
);
prevButton.addKeyListener( returnKeyHandler
);
helpButton.addKeyListener( returnKeyHandler
);
nextButton.addKeyListener( returnKeyHandler
);
deleteButton.addKeyListener( returnKeyHandler
);
// The return key is an ActionEvent for TextFields
// Reuse drawHandler; we want to draw.
iterationsText.addActionListener( drawHandler
);
rMinText.addActionListener( drawHandler );
rMaxText.addActionListener( drawHandler );
iMinText.addActionListener( drawHandler );
iMaxText.addActionListener( drawHandler );
juliaCRText.addActionListener( drawHandler );
juliaCIText.addActionListener( drawHandler );
// Listen for FocusEvents on the TextFields
// in order to (de)select text for easier UI
editing.
FocusHandler focusHandler = new FocusHandler();
iterationsText.addFocusListener( focusHandler
);
rMinText.addFocusListener( focusHandler );
rMaxText.addFocusListener( focusHandler );
iMinText.addFocusListener( focusHandler );
iMaxText.addFocusListener( focusHandler );
juliaCRText.addFocusListener( focusHandler );
juliaCIText.addFocusListener( focusHandler );
// The Julia checkbox:
drawJuliaCheckbox.addItemListener( new JuliaHandler()
);
drawJuliaCheckbox.addKeyListener( returnKeyHandler
);
// Color:
colorChoice.addKeyListener( returnKeyHandler
);
// See if the user changes any parameters by
typing.
TextChangeHandler changeHandler = new TextChangeHandler();
iterationsText.addKeyListener( changeHandler
);
rMinText.addKeyListener( changeHandler );
rMaxText.addKeyListener( changeHandler );
iMinText.addKeyListener( changeHandler );
iMaxText.addKeyListener( changeHandler );
juliaCRText.addKeyListener( changeHandler );
juliaCIText.addKeyListener( changeHandler );
}
protected void initializeGraphics()
{
// BorderLayout is the default layout strategy,
but set it anyway:
BorderLayout mainBorderLayout = new BorderLayout();
mainBorderLayout.setHgap( 10 );
mainBorderLayout.setVgap( 10 );
setLayout( mainBorderLayout );
// Add the top-level Components to "this", which
is-a-kind-of Container.
add( drawingCanvas, BorderLayout.CENTER );
status = new Label( "" );
status.setFont( Fractal.FONT );
add( status, BorderLayout.SOUTH );
controlsPanel = new Panel();
add( controlsPanel, BorderLayout.WEST );
// The controlsPanel has a grid of grids...
controlsGrid = new GridLayout( NUM_ROWS, 1 );
controlsGrid.setVgap( 10 );
controlsGrid.setHgap( 10 );
controlsPanel.setLayout( controlsGrid );
controlPanels = new Panel[ NUM_ROWS ];
controlGrids = new GridLayout[ NUM_ROWS ];
// Initialize the Panel objects in the controlPanels
array,
// and associate them with their respective
GridLayout objects.
int rowNum = 0;
for( rowNum = 0; rowNum < NUM_ROWS; rowNum++
)
{
controlGrids[ rowNum ] = new GridLayout(
1, 2 );
controlPanels[ rowNum ] = new Panel(
);
controlPanels[ rowNum ].setLayout(
controlGrids[ rowNum ] );
controlGrids[ rowNum ].setHgap( 10
);
controlGrids[ rowNum ].setVgap(
10 );
controlsPanel.add( controlPanels[
rowNum ] );
}
// Draw Button
rowNum = 0;
drawButton = new Button( "Make New Fractal"
);
drawButton.setEnabled( false );
drawButton.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( drawButton );
// "Color:" || Choice
rowNum++;
colorLabel = new Label( " Colors:" );
colorLabel.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( colorLabel );
colorChoice = new Choice();
colorChoice.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( colorChoice );
// "Iterations:" || TextField
rowNum++;
iterationsLabel = new Label( " Iterations:"
);
iterationsLabel.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( iterationsLabel
);
iterationsText = new TextField( "" );
iterationsText.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( iterationsText
);
// Stop Button || Status Label
rowNum++;
stopButton = new Button( "Stop" );
stopButton.setEnabled( false );
stopButton.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( stopButton );
status2 = new Label( "" );
status2.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( status2 );
// Delete Button || Help Button
rowNum++;
deleteButton = new Button( "Delete" );
deleteButton.setEnabled( false );
deleteButton.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( deleteButton );
helpButton = new Button( "Help" );
helpButton.setEnabled( true );
helpButton.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( helpButton );
// Previous Button || Next Button
rowNum++;
prevButton = new Button( "Previous" );
prevButton.setEnabled( false );
prevButton.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( prevButton );
nextButton = new Button( "Next" );
nextButton.setEnabled( false );
nextButton.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( nextButton );
// "Min Real:" || TextField
rowNum++;
rMinLabel = new Label( " Min Real:" );
rMinLabel.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( rMinLabel );
rMinText = new TextField( "" );
rMinText.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( rMinText );
// "Max Real:" || TextField
rowNum++;
rMaxLabel = new Label( " Max Real:" );
rMaxLabel.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( rMaxLabel );
rMaxText = new TextField( "" );
rMaxText.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( rMaxText );
// "Min Imaginary:" || TextField
rowNum++;
iMinLabel = new Label( " Min Imaginary:" );
iMinLabel.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( iMinLabel );
iMinText = new TextField( "" );
iMinText.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( iMinText );
// "Max Imaginary: " || TextField
// Note: pad the string with blanks to make
the length = 19 so that the
// (sometimes invisible) juliaCILabel is not
the longest string.
// This is required to prevent an annoying layout
management nit.
rowNum++;
iMaxLabel = new Label( " Max Imaginary:
" );
iMaxLabel.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( iMaxLabel );
iMaxText = new TextField( "" );
iMaxText.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( iMaxText );
// "Draw Julia Set:" Checkbox
rowNum++;
drawJuliaCheckbox = new Checkbox( "Draw Julia
Set: ", false );
drawJuliaCheckbox.setFont( Fractal.FONT );
controlPanels[ rowNum ].add( drawJuliaCheckbox
);
// Julia Set Constants:
// Don't make these visible until needed.
juliaCRLabel = new Label( " Julia C Real:" );
juliaCRLabel.setFont( Fractal.FONT );
juliaCILabel = new Label( " Julia C Imaginary:"
); // String length = 19
juliaCILabel.setFont( Fractal.FONT );
juliaCRText = new TextField( "0.0" );
juliaCRText.setFont( Fractal.FONT );
juliaCIText = new TextField( "0.0" );
juliaCIText.setFont( Fractal.FONT );
}
protected boolean isJulia()
{
return drawJuliaCheckbox.getState();
}
protected void setParameterChangeFlag( boolean b )
{
parameterChangeFlag = b;
}
protected void setState( boolean drawingNow, boolean outOfMemory,
boolean hasNext, boolean hasPrevious )
{
if( ! drawingNow && ! outOfMemory )
{
drawButton.setEnabled( true );
}
else
{
drawButton.setEnabled( false );
}
if( hasPrevious )
{
prevButton.setEnabled( true );
}
else
{
prevButton.setEnabled( false );
}
if( hasNext )
{
nextButton.setEnabled( true );
}
else
{
nextButton.setEnabled( false );
}
if( drawingNow )
{
stopButton.setEnabled( true );
stopButton.requestFocus();
}
else
{
stopButton.setEnabled( false );
iterationsText.requestFocus();
}
if( ! hasPrevious && ! hasNext )
{
deleteButton.setEnabled( false );
}
else
{
deleteButton.setEnabled( true );
if( outOfMemory )
{
deleteButton.requestFocus();
}
}
}
protected void setStatus( String s )
{
// Always show something in the status label.
if( s.length() == 0 )
{
s = fractal.getWelcome();
}
status.setText( s );
}
protected void setStatus2( String s )
{
status2.setText( s );
}
private void showJuliaControls( boolean isJulia )
{
// The Julia Point Labels & TextFields reside
in the last two rows.
// Sometimes these controls are invisible...
So we need to be able to
// dynamically add/remove the controls from
the layout, and then force the
// layout to be recalculated and redisplayed...
try
{
controlPanels[ NUM_ROWS - 2 ].setVisible(
false );
controlPanels[ NUM_ROWS - 2 ].removeAll();
controlPanels[ NUM_ROWS - 1 ].setVisible(
false );
controlPanels[ NUM_ROWS - 1 ].removeAll();
if( isJulia )
{
int rowNum = NUM_ROWS
- 2;
controlPanels[ rowNum
].add( juliaCRLabel );
controlPanels[ rowNum
].add( juliaCRText );
controlPanels[ rowNum
].setVisible( true );
rowNum = NUM_ROWS -1;
controlPanels[ rowNum
].add( juliaCILabel );
controlPanels[ rowNum
].add( juliaCIText );
controlPanels[ rowNum
].setVisible( true );
}
validate();
fractal.recalculateLayout(); //
Recalculate the layout and repaint.
}
catch( Throwable t )
{
System.out.println( "Fractal ERROR
!!! (show julia controls) ... " + t );
}
}
protected void updateColor( String colorSchemeName )
{
colorChoice.select( colorSchemeName );
}
protected void updateComplexRect( ComplexRectangle rect )
{
double rMin = rect.getRMin();
double rMax = rect.getRMax();
double iMin = rect.getIMin();
double iMax = rect.getIMax();
// Adjust the zoom factor to agree with these
numbers.
fractal.adjustZoomFactor( rect );
rMinText.setText( "" + Fractal.doubleAsString(
rMin ));
rMaxText.setText( "" + Fractal.doubleAsString(
rMax ));
iMinText.setText( "" + Fractal.doubleAsString(
iMin ));
iMaxText.setText( "" + Fractal.doubleAsString(
iMax ));
}
protected void updateDrawing( Drawing d )
{
updateColor( d.getColor() );
updateComplexRect( d.getComplexRect() );
updateIterations( d.getMaxIterations() );
updateJulia( d );
}
protected void updateIterations( int mi )
{
iterationsText.setText( "" + mi );
}
private void updateJulia( Drawing d )
{
boolean isJulia = ( d instanceof JuliaDrawing
);
drawJuliaCheckbox.setState( isJulia );
drawJuliaCheckbox.setEnabled( ! isJulia );
if( isJulia )
{
JuliaDrawing jd = (JuliaDrawing)
d;
updateJuliaPoint( jd.getJuliaPoint()
);
}
showJuliaControls( isJulia );
}
protected void updateJuliaPoint( ComplexPoint cp )
{
juliaCRText.setText( "" + Fractal.doubleAsString(
cp.getReal() ) );
juliaCIText.setText( "" + Fractal.doubleAsString(
cp.getImaginary() ) );
}
/////////////////////////////////////////////////////////////////////
// INNER CLASSES of class ControlPanel: /////////////////////////////
/////////////////////////////////////////////////////////////////////
class DeleteHandler implements ActionListener
{
public void actionPerformed( ActionEvent e )
{
// The user just clicked the Delete
Button...
fractal.doDelete();
}
}
class DrawHandler implements ActionListener
{
// For the "Draw!" Action Events:
// This is used for the ActionEvents which occur
when
// the the user clicks the Draw Button, and
when
// the return key is pressed while some other
widget has the focus.
public void actionPerformed( ActionEvent e )
{
fractal.doDraw();
}
}
class FocusHandler implements FocusListener
{
// For TextField Focus Listener:
// This doesn't work on some browsers...
public void focusLost( FocusEvent fe )
{
TextField tf = (TextField) fe.getComponent();
tf.select( 0, 0 ); // Unselect
all.
}
public void focusGained( FocusEvent fe )
{
TextField tf = (TextField) fe.getComponent();
tf.selectAll();
}
}
class HelpHandler implements ActionListener
{
public void actionPerformed( ActionEvent e )
{
// The user just clicked the Help
Button...
fractal.doHelp();
}
}
class JuliaHandler implements ItemListener
{
public void itemStateChanged( ItemEvent e )
{
// The user just (un)checked the
Draw Julia checkbox...
synchronized( fractal )
{
showJuliaControls( isJulia()
);
fractal.juliaClicked(
isJulia() );
}
}
}
class NextHandler implements ActionListener
{
public void actionPerformed( ActionEvent e )
{
// The user just clicked the Next
Button...
fractal.doNext();
}
}
class PreviousHandler implements ActionListener
{
public void actionPerformed( ActionEvent e )
{
// The user just clicked the Previous
Button...
fractal.doPrevious();
}
}
class ReturnKeyHandler extends KeyAdapter
{
// For the "Draw!" Key Events:
// This is used to listen for the return key
being pressed
// when a Button has the focus. (The return
key produces an
// ActionEvent, not a KeyEvent when a TextField
has the focus.)
public void keyTyped( KeyEvent e )
{
char theKey = e.getKeyChar();
if( theKey == '\r' ) // return /
enter
{
fractal.doDraw();
}
}
}
class StopHandler implements ActionListener
{
public void actionPerformed( ActionEvent e )
{
// The user just clicked the Stop
Button...
fractal.doStop();
}
}
class TextChangeHandler extends KeyAdapter
{
// For knowing if any control parameters have
been updated by the user.
// If not, then maybe we can quickly remap colors
instead of
// recalculating a new fractal from scratch...
public void keyTyped( KeyEvent e )
{
char theKey = e.getKeyChar();
if( theKey != '\t' ) // ignore
tabs
{
parameterChangeFlag
= true;
}
}
}
}