/** @file
* Copyright (C) 2003–5 John D Lamb (J.D.Lamb@btinternet.com)
* Copyright (C) 2007, 2008 John D Lamb (J.D.Lamb@btinternet.com)
* Copyright (C) 2011 John D Lamb (J.D.Lamb@btinternet.com)
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jscicalc;
import jscicalc.pobject.PObject;
import jscicalc.pobject.Numeral;
import jscicalc.pobject.Mode;
import jscicalc.pobject.Mean;
import jscicalc.pobject.StDev;
import jscicalc.pobject.PopStDev;
import jscicalc.button.CalculatorButton;
import jscicalc.complex.Complex;
/**
* This class is effectively the <em>controller</em> class for the calculator.
* It creates either a frame or an applet with the calculator in it, stores
* the results of calculations, a history of calculations done and the state of
* the calculator. Parser <em>model</em> class, in the sense that it stores and
* evaluates the current expression. The <em>view</em> of the current expression
* is handled by DisplayPanel and the classes it contains.
*
* If you want to use the calculator as a convenient dialog for calculating values
* for some JTextComponent, then attach the component with setTextComponent() and
* detach it if necessary with setTextComponent( null ).
*
* @author J. D. Lamb
* @version $Revision: 1.18 $
*/
public class CalculatorApplet extends javax.swing.JApplet
implements java.awt.event.KeyListener, ReadOnlyCalculatorApplet {
/**
* Constructor. This is the constructor we use for the applet.
* @param args Acceps -g argument for graphical calculator
*/
public CalculatorApplet( String args[]) {
applet = this;
// Possibly set to graphical
setGraphical( false );
for( String arg : args ){
if( arg.equals( new String( "-g" ) ) ){
setGraphical( true );
}
}
addMouseListener( new java.awt.event.MouseAdapter() {
public void mouseClicked( java.awt.event.MouseEvent mouseEvent ){
boolean showAbout = mouseEvent.getButton()
!= java.awt.event.MouseEvent.BUTTON1;
if( !showAbout ){
return;
}
javax.swing.JOptionPane
.showMessageDialog( applet,
"<html>Copyright © 2004–5, 2007–5"
+ ", John D Lamb"
+ " <J.D.Lamb@btinternet.com><br>"
+ "This is free software; see the"
+ " source for copying "
+ "conditions. There is NO "
+ "warranty;<br>not even for"
+ " MERCHANTABILITY or FITNESS"
+ " FOR A PARTICULAR PURPOSE.</html>",
"About",
javax.swing.JOptionPane
.INFORMATION_MESSAGE );
}
} );
}
/**
* Used to construct the applet. Calls setup to do most of the work.
* @see #setup()
*/
public void init(){
// set up GUI
applet = this;
frame( null );
try {
javax.swing.SwingUtilities
.invokeAndWait( new Runnable() {
public void run(){ setup(); }
} );
} catch( Exception e ){
System.err.println( "InterruptedException" );
}
}
/**
* Use this to construct a calculator. At the moment the args parameter is
* completely ignored, though there are some useful possibilities. It would be
* nice if we could specify the size (minSize) by passing some argument.
* The MODE button currently allows this and a default is hardcoded.
* @param args Acceps -g argument for graphical calculator
*/
public static void main( final String args[] ){
javax.swing.SwingUtilities
.invokeLater( new Runnable() { public void run() { showFrame( args ); } } );
}
/**
* Shows the frame. This is used by main to make sure the calculator JFrame
* is created on the event-dispatching thread if we don’t run it as an applet.
* I haven’t checked that this all works as it should.
* @param args Acceps -g argument for graphical calculator
*/
private static void showFrame( final String args[] ){
javax.swing.JFrame frame = createFrame( args );
frame.setDefaultCloseOperation( javax.swing.JFrame.EXIT_ON_CLOSE );
frame.setVisible( true );
}
/**
* Actually create a JFrame so that forby using this as an applet, we can
* use it as a standalone application. Like init(), this uses setup() to do
* most of the real work. Additionally it prints out a boilerplate copyright
* message.
* @param args Acceps -g argument for graphical calculator
* @return A JFrame with the calculator in it
* @see #init()
* @see #main( String[] )
* @see #showFrame()
*/
public static javax.swing.JFrame createFrame( final String args[] ){
CalculatorApplet main = new CalculatorApplet( args );
System.out.println( "Copyright (C) 2004–5, 2007–2008, John D Lamb"
+ " <J.D.Lamb@btinternet.com>" );
System.out.println( "This is free software; see the source for copying "
+ "conditions. There is NO" );
System.out.println( "warranty; not even for MERCHANTABILITY"
+ " or FITNESS FOR A PARTICULAR PURPOSE." );
javax.swing.JFrame frame
= new javax.swing.JFrame( "Scientific Calculator" );
main.frame( frame );
main.setup();
frame.setContentPane( main );
frame.setResizable( false );
frame.setUndecorated( false );
frame.pack();
return frame;
}
/**
* This sets up the calculator for either the applet of the standalone
* application. I’m not sure why the function is public because it probably
* should be private.
*/
public void setup() {
/* intitially no attached component and no graph */
setTextComponent( null );
graph = null;
/* set initial value */
setValue( new Complex() );
/* set initial sizes */
setSizes();
/* set initial state */
parser = new Parser();
shift = false;
angleType = AngleType.DEGREES;
mode = 0;
stat = false;
notation = new Notation(); // set default of standard, rectangular, non-complex
memory = new Complex();
statMemory = new java.util.Vector<Complex>();
statMemoryNeg = new java.util.Vector<Complex>();
//listen for key presses
addKeyListener( this );
setFocusable( true );
getContentPane().removeAll();
/* set up initial objects */
displayPanel = new DisplayPanel( this );
displayPanel
.setBorder( new javax.swing.border
.BevelBorder( javax.swing.border.BevelBorder.LOWERED ) );
setPanels();
/* create a DataTransfer so we can copy to system clipboard */
/* this code does not actually work -- I don't know why */
dataTransfer = new DataTransfer( this );
calculatorPanel.getInputMap()
.put( javax.swing.KeyStroke
.getKeyStroke( java.awt.event.KeyEvent.VK_COPY, 0), "copy" );
calculatorPanel.getInputMap()
.put( javax.swing.KeyStroke
.getKeyStroke( java.awt.event.KeyEvent.VK_C,
java.awt.event.ActionEvent.CTRL_MASK |
java.awt.event.ActionEvent.SHIFT_MASK, false ),
"copy" );
calculatorPanel.getActionMap().put( "copy", dataTransfer );
dataTransfer.setEnabled( true );
//set up history
setBase( Base.DECIMAL );
history = new java.util.Vector<HistoryItem>();
tempParserList
= new HistoryItem( null, getAngleType(), getBase(), getNotation() );
historyPosition = -1;
shiftDown = false;
setOn( true );
setValue( new Complex( 0 ) );
updateDisplay( true, true );
requestFocusInWindow();
}
/**
* The calculator buttons are all set on objects of class CalculatorPanel. I
* tried first changing the buttons according to context, but redoing 40 buttons
* or so every time you press shift slows the calculator down visibly. So this
* is the alternative. The panels also take some time to generate and so they
* are created on separate threads. This allows us to see and start using the
* calculator while some of the panels are still being constructed.
* @see CalculatorPanel
*/
private void setPanels() {
displayPanel.setUp();
calculatorPanels
= new java.util.HashMap<SpecialButtonType,CalculatorPanel>();
for( SpecialButtonType sbt : SpecialButtonType.values() ){
CalculatorPanel calculatorPanel
= CalculatorPanel
.createPanel( this, sbt, panelColour );
calculatorPanels.put( sbt, calculatorPanel );
}
setCalculatorPanel( SpecialButtonType.NONE );
}
/**
* Choose a CalculatorPanel. You would do this in response to a button
* press. The most obvious one is the <em>shift</em> button, but we also
* change panels for <em>Stat</em> mode and when changing base.
* @param sbt An enumerated type representing the panel we want
*/
private void setCalculatorPanel( SpecialButtonType sbt ){
if( calculatorPanel != null )
getContentPane().remove( calculatorPanel );
calculatorPanel = calculatorPanels.get( sbt );
getContentPane().add( calculatorPanel );
calculatorPanel.setDisplayPanel();
repaint();
}
/**
* As you might expect this clears the calculator history. The only time we use
* this is when we switch the calculator off. Otherwise we keep a record of the
* last few expressions together with information about the calculator state
* (base, degrees or radians, etc.).
*/
public void clearHistory(){
history = new java.util.Vector<HistoryItem>();
historyPosition = -1;
}
/**
* Moves up the history list (if possible). CalculatorApplet maintains
* a list of previously evaluated expressions and this function allows
* you to move backward through them. It’s called by UpButton and is
* particularly useful if you want to reedit some expression.
* @see #downHistory()
* @see UpButton
*/
public void upHistory(){
if( displayPanel.displayLabelHasCaret() ){
setCaretToEntry();
//return;
}
if( historyPosition >= history.size() - 1 ) return;
// save old buffer
if( historyPosition == -1 ){
// if error shown clear it.
if( getValue() instanceof jscicalc.Error )
setValue( new Complex() );
// copy to temporary buffer
tempParserList.list = parser.getList();
tempParserList.angleType = getAngleType();
tempParserList.base = getBase();
tempParserList.notation = getNotation();
} else {
// copy to history buffer
history.elementAt( historyPosition ).list = parser.getList();
history.elementAt( historyPosition ).angleType = getAngleType();
history.elementAt( historyPosition ).base = getBase();
history.elementAt( historyPosition ).notation = getNotation();
}
// move up history list
++historyPosition;
// copy history buffer to parser
parser.setList( history.elementAt( historyPosition ).list );
setAngleType( history.elementAt( historyPosition ).angleType );
setBase( history.elementAt( historyPosition ).base );
setNotation( history.elementAt( historyPosition ).notation );
// reset display panel
displayPanel.setExpression( parser );
}
/**
* Moves down the history list (if possible).
* @see #upHistory()
* @see DownButton
* @return <em>true</em> if any action is performed, <em>false</em> otherwise.
*/
public boolean downHistory(){
if( historyPosition < 0 ){
if( displayPanel.displayLabelScrollable() ){
setCaretToDisplay();
return true;
}
return false;
}
// copy to history buffer
history.elementAt( historyPosition ).list = parser.getList();
history.elementAt( historyPosition ).angleType = getAngleType();
history.elementAt( historyPosition ).base = getBase();
history.elementAt( historyPosition ).notation = getNotation();
// move down history list
--historyPosition;
if( historyPosition == -1 ) {
parser.setList( tempParserList.list );
setAngleType( tempParserList.angleType );
setBase( tempParserList.base );
setNotation( tempParserList.notation );
} else {
parser.setList( history.elementAt( historyPosition ).list );
setAngleType( history.elementAt( historyPosition ).angleType );
setBase( history.elementAt( historyPosition ).base );
setNotation( history.elementAt( historyPosition ).notation );
}
// reset display panel
displayPanel.setExpression( parser );
return true;
}
/**
* This is called by EqualsButton and its subclasses and stores an expression
* in the history list before evaluating it. This allows the possibility of
* going back and editing a mistyped expression.
* @see EqualsButton
*/
public void pushHistory(){
java.util.LinkedList<PObject> list = parser.getList();
if( list.size() == 0 ) return;
history.add( 0,
new HistoryItem( list, getAngleType(), getBase(), getNotation() ) );
while( history.size() > HISTORY_SIZE )
history.removeElementAt( HISTORY_SIZE );
// Once we have added an item, we want to return to end of history list
historyPosition = -1;
}
/**
* Moves right through the current expression. We need this so that expressions
* are editable.
* @see RightButton
*/
public void right(){
displayPanel.right();
}
/**
* Moves left through the current expression. We need this so that expressions
* are editable.
* @see LeftButton
*/
public void left(){
displayPanel.left();
}
/**
* This was an experimental feature that I couldn’t get to work. probably it
* could be removed.
* @return An Action
*/
public javax.swing.Action backward(){
return displayPanel.backward();
}
/**
* When we evaluate an expression, we need to create a new one. This function
* delegates the job to the DisplayPanel.
* @see DisplayPanel
*/
public void newExpression(){
displayPanel.newExpression();
}
public DisplayPanel displayPanel(){
return displayPanel;
}
/**
* We use this when we want to clear the expresion in the LCD window. As
* usual we delegate this to the <em>view</em> class. The expression doesn’t
* get cleared until we start typing a new one, which is why we separate this
* from newexpression().
* @see DisplayPanel
* @see #newExpression()
*/
public void clear(){
displayPanel.clear( getParser() );
displayPanel.setExpression( parser );
}
/**
* Delete at the current caret position. Delegated to DisplayPanel.
* @see DisplayPanel
*/
public void delete(){
displayPanel.delete( getParser() );
}
/**
* Copy from displayPanel to system clipboard.
*/
public void copy(){
dataTransfer.copy();
}
/**
* Insert at the current caret position. Delegated to DisplayPanel.
* @param p The PObject representing whatever we’re trying to insert
* @see DisplayPanel
*/
public void insert( PObject p ){
displayPanel.insert( p, getParser() );
}
/**
* Used to tell the <em>view</em> that we want the display changed, for example,
* in response to pressing the <em>shift</em> button.
* We sometimes want to update the display but not the expression in the
* EntryLabel object.
*
* Additionally this function will send a copy of the calculated value as a
* string to any JTextComponent you happen to have attached to the calculator.
*
* @param entry Set to <em>true</em> or <em>false</em> according as we want the
* entry label updated or not
* @param extra Designed so that we can specify whether the ExtraPanel object
* should be updated, but not currently used
* @see DisplayPanel
* @see EntryLabel
*/
public void updateDisplay( boolean entry, boolean extra ){
if( displayPanel != null )
displayPanel.update( entry, extra );
//if( textComponent != null && getValue() instanceof Complex )
//textComponent.setText( ((Complex)(getValue())).toString() );
if( textComponent != null )
textComponent.setText( getValue().toString() );
// FIXME
}
public int displayHeight( int minSize ){
return dHeight * minSize;
}
public int buttonHeight( int minSize ){
return bHeight * minSize;
}
public int buttonWidth( int minSize ){
return bWidth * minSize;
}
public int strutSize( int minSize ){
return sSize * minSize;
}
public int displayHeight(){
return displayHeight( minSize() );
}
public int buttonHeight(){
return buttonHeight( minSize() );
}
public int buttonWidth(){
return buttonWidth( minSize() );
}
public int strutSize(){
return strutSize( minSize() );
}
/**
* An important little function. This effectively defines the size of the
* calculator. Values in the range 3-6 seem to work well. The
* CalculatorApplet object stores this value and uses it to calculate everything
* else. That way it stays in control of sizes but also allows you to scale
* the calculator.
* @param mSize A small integer determining the smallest unit used in drawing
* the calculator
*/
public void setMinSize( int mSize ){
this.mSize = mSize;
if( frame() == null )
return;
setPanels();
frame().pack();
if( graph != null )
graph.updateMenu(); // pass on to graph
}
public int minSize(){
return mSize;
}
public float buttonTextSize(){
return mSize * bTextSize;
}
public float entryTextSize(){
return mSize * eTextSize;
}
public float displayTextSize(){
return mSize * dTextSize;
}
public float extraTextSize(){
return mSize * sTextSize;
}
/**
* Set the <em>shift</em> button to pressed or unpressed. Obviously, it will be
* called by ShiftButton, but it’s also called when a shifted key is pressed and
* on some other occasions.
* @param value <em>true</em> or <em>false</em> according as the shift button is
* pressed or not.
* @see ShiftButton
*/
public void setShift( boolean value ){
shift = value;
if( shift )
if( stat )
setCalculatorPanel( SpecialButtonType.SHIFT_STAT );
else
switch( getBase() ){
case DECIMAL:
setCalculatorPanel( SpecialButtonType.SHIFT );
break;
default:
setCalculatorPanel( SpecialButtonType.SHIFT_HEX );
}
else
if( stat )
setCalculatorPanel( SpecialButtonType.STAT );
else
switch( getBase() ){
case DECIMAL:
setCalculatorPanel( SpecialButtonType.NONE );
break;
default:
setCalculatorPanel( SpecialButtonType.HEX );
}
}
/**
* Called when we set the calculator on or off.
* @param value <em>true</em> for on, <em>false</em> for off.
* @see OnButton
* @see OffButton
*/
public void setOn( boolean value ){
if( displayPanel != null ){
displayPanel.setOn( value );
displayPanel.repaint();
} else
System.out.println( "no display panel" );
}
public boolean getOn(){
if( displayPanel != null )
return displayPanel.getOn();
else
return false;
}
public boolean getShift(){
return shift;
}
public Parser getParser(){
return parser;
}
public OObject getValue(){
return value;
}
/**
* when an EqualsButton calculates a value it has to transfer it from the Parser
* object to this and setValue() is the function we use. The object should be
* a Complex, but we can’t represent errors as Complex numbers and it's
* convenient just to extract what we can as an object and use instanceof to
* check if needed.
*
* You could modify this function to put a value directly into some other
* Component. One of the design objectives of the calculator is that you should
* can use it as part of some applet as a pop up calculator. A better approach
* might be to use EqualsButton to send the value where you want or to tell the
* Component to set its value using getValue().
*
* Have a look at CalcButtonApplet if you want to see how you can make the
* calculator pop up on demand.
*
* @param o The value (should be gotten from the Parser Object)
* @see Parser
* @see EqualsButton
* @see getValue()
* @see CalcButtonApplet
*/
public void setValue( OObject o ){
value = o;
if( value != null && displayPanel != null )
displayPanel.setValue();
}
/**
* The calculator can store one value and this is how we do it. Notice that
* it allows memory addition and subtraction when used
* in conjunction with getMemory().
* @param o The value to put into memory
* @see getMemory()
*/
public void setMemory( OObject o ){
memory = o;
}
/**
* @return value from memory.
*/
public OObject getMemory(){
return memory;
}
/**
* We can work with degrees or radians and this is how we set them. Does anyone
* use grads? If so you can ay change AngleType etc, to handle. these.
* @param angleType AngleType.DEGREES or AngleType.RADIANS
* @see AngleType
*/
public void setAngleType( AngleType angleType ){
this.angleType = angleType;
}
/**
* @return Angletype: degrees or radians.
*/
public AngleType getAngleType(){
return angleType;
}
/**
* @return <em>true</em> or <em>false</em> according as we are in statistics
* mode or not.
*/
public boolean getStat(){
return stat;
}
/**
* Set <em>stat</em> mode on or off.
* @param stat A value: <em>true</em> for <em>stat</em> mode.
* @see StatPanel
* @see #getStat()
*/
public void setStat( boolean stat ){
this.stat = stat;
if( stat == true )
setBase( Base.DECIMAL );
setShift( getShift() );
}
public int getMode(){
return mode;
}
/**
* Sets the mode. Allows us to change from degrees to radians, change calculator
* size, set STAT mode on or off.
* @param i The new mode: it’s forced into the right range even if you give an
* illegal value.
*/
public void setMode( int i ){
mode = i % ( (frame() != null && sizes.size() > 1) ? 4 : 3 );
}
/**
* Sets the mode. Allows us to change from degrees to radians, change calculator
* size, set STAT mode on or off.
* CalculatorButton objects generate PObjects rather than integers; so we pass
* these and act accordingly. This is not a particularly good piece of code, but
* there’s not much that can go seriously wrong with it; so I'm not planning
* to fix it.
* @param p A PObject, generated by a Calculator button during change of mode.
*/
public void setMode( PObject p ){
//System.out.print( "Mode: " );
//System.out.println( getMode() );
if( p instanceof Numeral ){
Numeral numeral = (Numeral)p;
//System.out.print( "numeral: " );
//System.out.println( numeral.name() );
if( mode == 3 ){
int size = 0;
switch( numeral.get() ){
case '1': size = 1; break;
case '2': size = 2; break;
case '3': size = 3; break;
case '4': size = 4; break;
case '5': size = 5; break;
case '6': size = 6; break;
case '7': size = 7; break;
case '8': size = 8; break;
case '9': size = 9; break;
default: size = 0;
}
if( size < sizes.size() ){
size = sizes.elementAt( size );
if( minSize() != size ) setMinSize( size );
setMode( 0 );
updateDisplay( true, true );
}
} else if( numeral.name().equals( "1" ) ){
//System.out.println( "***** 1 *****" );
if( mode == 2 )
setAngleType( AngleType.DEGREES );
else if( mode == 1 )
setStat( false );
setMode( 0 );
updateDisplay( true, true );
} else if( numeral.name().equals( "2" ) ){
//System.out.println( "***** 2 *****" );
if( mode == 2 )
setAngleType( AngleType.RADIANS );
else if( mode == 1 )
setStat( true );
setMode( 0 );
updateDisplay( true, true );
} else if( numeral.name().equals( "3" ) ){
setMode( 0 );
updateDisplay( true, true );
}
} else if( p instanceof Mode ){
setMode( getMode() + 1 );
}
}
/**
* Clear statistical memory.
* @see SclButton
*/
public void clearStatMemory(){
statMemory.clear();
statMemoryNeg.clear();
}
/**
* Add a Complex to statistical memory. Statistical memory holds a list of these
* numbers so that mean, standard deviation and the like can be calcualted at
* will.
* The function also returns the number of objects in statistical memory as
* an Object. An object is used because it conveniently fits what we typically
* want to do with the return result: display it in the calculator where the
* expression was evaluated.
* @param d The Complex to put into memory
* @return The number of objects in statistical memory.
*/
public Complex statAdd( Complex d ){
statMemory.add( d );
return new Complex( statSize() );
}
/**
* Removes a Complex from statistical memory.
* @param d The Complex to put into memory
* @return The number of objects in statistcal memory.
* @see #statAdd( Complex )
*/
public Complex statSub( Complex d ){
statMemoryNeg.add( d );
return new Complex( statSize() );
}
/**
* Used internally in calculations of mean and standard deviation so that
* we can get these right even when we’ve removed a number.
* @return The number to use in statistical calculations
*/
private double statSize(){
return statMemory.size() - statMemoryNeg.size();
}
public Mean statMean(){
Mean mean = new Mean();
Complex d = new Complex();
for( Complex o : statMemory ){
d = d.add( o );
}
for( Complex o : statMemoryNeg ){
d = d.subtract( o );
}
if( statSize() > 0 ){
mean.setValue( d.divide( new Complex( statSize(), 0 ) ) );
} else {
mean.setError( true );
}
return mean;
}
public Complex statSumSquares(){
Mean m = statMean();
if( m.error() || !(m.value() instanceof Complex) )
throw new RuntimeException( "Stat Error" );
Complex e = (Complex)(m.value());
Complex d = new Complex();
for( Complex o : statMemory ){
Complex c = o.subtract( e );
d = d.add( c.square() );
}
for( Complex o : statMemoryNeg ){
Complex c = o.subtract( e );
d = d.subtract( c.square() );
}
return d;
}
public StDev statSampleStDev(){
StDev stDev = new StDev();
try {
Complex d = statSumSquares();
//if( d < 0 || statSize() < 2 ){
if( statSize() < 2 ){
stDev.setError( true );
return stDev;
} else {
stDev.setValue( d.divide( new Complex( statSize() - 1, 0 ) ).sqrt() );
return stDev;
}
} catch( Exception e ){
stDev.setError( true );
return stDev;
}
}
public PopStDev statPopulationStDev(){
PopStDev stDev = new PopStDev();
try {
Complex d = statSumSquares();
//if( d < 0 || statSize() < 1 ){
if( statSize() < 1 ){
stDev.setError( true );
return stDev;
} else {
stDev.setValue( d.divide( new Complex( statSize(), 0 ) ).sqrt() );
return stDev;
}
} catch( Exception e ){
stDev.setError( true );
return stDev;
}
}
/**
* Used to set value of jframe. I can’t remember why we need this, possibly
* for CalcButtonApplet.
* @param jframe A JFrame
*/
public void frame( javax.swing.JFrame jframe ){
this.jframe = jframe;
}
public javax.swing.JFrame frame(){
return jframe;
}
/**
* Some keys don’t generate keyTyped Events. This function handles
* the arrow keys.
* @param keyEvent The event of key pressed
*/
public void keyPressed( java.awt.event.KeyEvent keyEvent ){
if( keyEvent.getKeyCode() == java.awt.event.KeyEvent.VK_LEFT )
calculatorPanel.buttons().elementAt( 40 ).actionPerformed( null );
else if( keyEvent.getKeyCode() == java.awt.event.KeyEvent.VK_RIGHT )
calculatorPanel.buttons().elementAt( 41 ).actionPerformed( null );
else if( keyEvent.getKeyCode() == java.awt.event.KeyEvent.VK_UP )
calculatorPanel.buttons().elementAt( 43 ).actionPerformed( null );
else if( keyEvent.getKeyCode() == java.awt.event.KeyEvent.VK_DOWN )
calculatorPanel.buttons().elementAt( 44 ).actionPerformed( null );
else if( keyEvent.getKeyCode() == java.awt.event.KeyEvent.VK_ALT_GRAPH ){
if( !shiftDown ){
calculatorPanel.buttons().elementAt( 10 ).actionPerformed( null );
shiftDown = true;
}
}
else if( keyEvent.getKeyCode() == java.awt.event.KeyEvent.VK_COPY ){
if( !shiftDown ){
copy();
} else {
// also does copy but changes switch state too
calculatorPanel.buttons().elementAt( 15 ).actionPerformed( null );
}
}
}
/**
* Some keys don’t generate keyTyped Events. This function handles
* the release of the AltGraph key associated with the <em>shift</em> button. As
* far as I can tell Java 2 doesn’t actually generate an event with this
* key; so this part of the function is probably redundant. I’ve also
* implemented
* a crude hack here to recognise when CTRL-C is pressed and generate a
* copy-to-clipboard event.
* @param keyEvent The event of key pressed
*/
public void keyReleased( java.awt.event.KeyEvent keyEvent ){
if( keyEvent.getKeyCode() == java.awt.event.KeyEvent.VK_ALT_GRAPH ){
shiftDown = false;
} else {
char c = keyEvent.getKeyChar();
//System.out.println( c );
//System.out.println( keyEvent.getKeyCode() );
//System.out.println( keyEvent.paramString() );
if( (keyEvent.getModifiersEx()
& java.awt.event.InputEvent.CTRL_DOWN_MASK) != 0
&& ( keyEvent.getKeyCode() == 67 ) ){ // CTRL-C
dataTransfer.copy();
}
}
}
/**
* Handle typed keys. This is how we associate keys to buttons. There are too
* many buttons to ask each to handle its own (and too much risk of confusion);
* so we handle them together from a list of buttons generated by
* CalculatorPanel.
*
* Generally this function just finds which key you typed and uses a map
* to activate the appropriate button.
*
* @param keyEvent The event of key typed
* @see CalculatorPanel#keyMap()
*/
public void keyTyped( java.awt.event.KeyEvent keyEvent ){
if( (keyEvent.getModifiersEx()
& java.awt.event.InputEvent.CTRL_DOWN_MASK) == 0 ){
char c = keyEvent.getKeyChar();
if( c == java.awt.event.KeyEvent.VK_ENTER )
c = '=';
CalculatorButton b = calculatorPanel.keyMap().get( c );
if( b != null )
b.actionPerformed( null );
else if( c == java.awt.event.KeyEvent.VK_BACK_SPACE && !shift )
calculatorPanel.buttons().elementAt( 30 ).actionPerformed( null );
}
}
/**
* javax.swing.Spring doesn’t have a scale function. Although we only use this
* in CalculatorPanel and its derived classes, we define a scale function here.
* Why? Originally we used this class to generate Springs. It could probably be
* moved to CalculatorPanel.
* @param spring A spring
* @param s how much we want to scale the spring: a positive integer -
* negatives and zero are ignored
* @see CalculatorPanel
*/
private static javax.swing.Spring scale( javax.swing.Spring spring, int s ){
if( s < 2 )
return spring;
javax.swing.Spring result = javax.swing.Spring.sum( spring, spring );
for( int i = 2; i < s; ++i ){
result = javax.swing.Spring.sum( result, spring );
}
return result;
}
public Base getBase(){
return parser.base();
}
/**
* This is where we set the base. Used by
* EqualsButton and its derived classed.
* @param b The base
* @see EqualsButton
* @see HexButton
* @see BinButton
* @see DecButton
* @see OctButton
*/
public void setBase( Base b ){
if( getBase() == b ) return;
//System.out.println( b );
parser.base( b );
//displayPanel.setBase( b );
switch( b ){
case DECIMAL:
setCalculatorPanel( SpecialButtonType.NONE );
break;
default:
setCalculatorPanel( SpecialButtonType.HEX );
break;
}
}
/**
* This function is useful if you want to attach a JTextComponent such as
* JTextField to the calculator so that when you calculate a value, it gets
* transferred to the JTextComponent.
* @param textComponent The component you want to attach
*/
public void setTextComponent( javax.swing.text.JTextComponent textComponent ){
this.textComponent = textComponent;
}
/**
* Get the JTextComponent associated with this oject. This feature is only useful
* if you want to attach the calculator to a component, for example to
* calculate some number for you.
* @return The component attached
*/
public final javax.swing.text.JTextComponent getTextComponent(){
return textComponent;
}
/**
* Get notation (scientific, polar, complex)
* @return the notation.
*/
public Notation getNotation(){
return notation;
}
/**
* Set notation (scientific, polar, complex)
* @param notation The notation to set
*/
public void setNotation( Notation notation ){
this.notation = notation;
}
/**
* Get the frame Insets
* @return frame Insets
*/
public final java.awt.Insets getFrameInsets(){
if( jframe == null ) return new java.awt.Insets( 29, 4, 4, 4 ); // just guess
return jframe.getInsets();
}
/**
* Estimate width of panel at given size.
* @param mSize The size at which to estimate wdith
* @return An estimate (usually correct) of current frame width at given size
*/
private int getFrameWidth( int mSize ){
int x = getFrameInsets().left + getFrameInsets().right;
return 4 * strutSize( mSize ) + 5 * mSize + 8 * buttonWidth( mSize ) + x;
}
/**
* Estimate height of panel at given size.
* @param mSize The size at which to estimate width
* @return An estimate (usually correct) of current frame height at given size
*/
private int getFrameHeight( int mSize ){
int y = getFrameInsets().top + getFrameInsets().bottom;
return 3 * strutSize( mSize ) + 4 * mSize + 5 * buttonHeight( mSize )
+ displayHeight( mSize ) + y;
}
/**
* Get height for Graph.
* @return A height
*/
public int graphHeight(){
if( jframe == null )
return getFrameHeight( mSize );
else
return jframe.getHeight();
}
/**
* Set the allowable range of sizes for the calulator. These are the values
* that mSize may take.
*/
private void setSizes(){
mSize = 4; // A default value;
// Do nothing if no displayPanel
if( jframe == null ) return;
// get the screen width and height
int width = java.awt.Toolkit.getDefaultToolkit().getScreenSize().width;
int height = java.awt.Toolkit.getDefaultToolkit().getScreenSize().height;
int max = minimumSize;
for( int i = 0; i < 11; ++i ){ // 11 stops infinite loop, redundant, detectable
if( getFrameWidth( minimumSize + i ) > width
|| getFrameHeight( minimumSize + i ) > height ){
max = i;
break;
} else if( i == 10 )
max = i;
}
sizes = new java.util.Vector<Integer>( max );
for( int i = minimumSize; i < minimumSize + max; ++i )
sizes.add( i );
if( max < 4 )
mSize = minimumSize;
}
/**
* Get the value of sizes.size().
* @return the value of sizes.size()
*/
public int getSizesSize(){
return sizes.size();
}
/**
* Get the value of minimumSize.
* @return the value of minimumSize
*/
public int getMinSize(){
return minimumSize;
}
/**
* Set whether calculator is graphical.
* @param g \em true or \em false according as you want a graphical calculator
*/
private void setGraphical( boolean g ){
graphical = g;
}
/**
* Get whether calculator is graphical or not
* @return \em true or \em false according as calculator graphical or not
*/
public boolean getGraphical(){
return graphical;
}
/**
* Set Caret to entry
*/
private void setCaretToEntry(){
displayPanel.setCaretToEntry();
}
/**
* Set Caret to display
*/
private void setCaretToDisplay(){
displayPanel.setCaretToDisplay();
}
/**
* Display a graph;
*/
public void displayGraph(){
if( graph == null )
graph = new jscicalc.graph.Graph( this );
else
graph.setVisible( true );
graph.setLocus( getValue() );
}
/**
* A graph. Initially null.
*/
private jscicalc.graph.Graph graph;
/**
* The value of the last expression evaluated. Usually a Double unless an error
* occurred.
*/
private OObject value;
/**
* DEGREES or RADIANS
*/
private AngleType angleType;
/**
* The MODE
*/
private int mode;
/**
* Statistics mode or not
*/
private boolean stat;
/**
* The calculator’s memory.
*/
private OObject memory;
/**
* Statistics memory needs to hold multiple values.
*/
private java.util.Vector<Complex> statMemory;
/**
* Statistics memory needs to know if you’ve removed values.
*/
private java.util.Vector<Complex> statMemoryNeg;
/**
* The frame height - I think this is redundant because the frame calculates
* its own height using pack().
*/
private int frameHeight;
/**
* If we create a frame, we create an applet, otherwise, this.
*/
private CalculatorApplet applet;
/**
* The <em>view</em> object
*/
private DisplayPanel displayPanel;
/**
* The object that contains all the buttons: the input object.
*/
private CalculatorPanel calculatorPanel;
/**
* More than one input panel so keep track of all of them with an map and
* enumerated type.
*/
private java.util.HashMap<SpecialButtonType,CalculatorPanel> calculatorPanels;
/**
* The parameter that determines how much everything is scaled.
*/
private int mSize;
/**
* The <em>model</em> object. It parses expressions and is actually the real
* power behind the calculator. Nearly everything else is just interface.
*/
private Parser parser;
/**
* Is shift button pressed?
*/
private boolean shift;
/**
* Button width.
*/
private static final int bWidth = 23;
/**
* Button width.
*/
private static final int bHeight = 10;
/**
* Strut size.
*/
private static final int sSize = 3;
/**
* Height of DisplayPanel.
*/
private static final int dHeight = 30;
/**
* Button text size.
*/
private static final float bTextSize = 4;
/**
* EntryPanel text size.
*/
//private static final float eTextSize = (float)6.8;
private static final float eTextSize = (float)6.4;
/**
* DisplayLabel text size.
*/
//private static final float dTextSize = (float)9.5;
private static final float dTextSize = (float)9.2;
/**
* Small text size.
*/
private static final float sTextSize = (float)2.5;
/**
* The history.
*/
private java.util.Vector<HistoryItem> history;
/**
* How many events should we store? Change this to suit yourself.
*/
private static final int HISTORY_SIZE = 24;
/**
* If we roll up the history with a half completed expression, we can change
* our minds and go back to it.
*/
HistoryItem tempParserList;
/**
* where are we in the history list.
*/
int historyPosition;
/**
* This is where you change the DisplayPanel colour. I haven’t checked that
* it actually works, but in principle you could have (say) a red or blue
* calculator if you want.
*/
private static final java.awt.Color panelColour = java.awt.Color.LIGHT_GRAY;
/**
* Used by CalcButtonApplet and DisplayPanel
*/
protected javax.swing.JFrame jframe;
/**
* Used to tell if a modifier key is being held down for shift—I
* don’t think
* it actually does anything at the moment.
*/
private boolean shiftDown;
/**
* Notation
*/
private Notation notation;
/**
* Can be used to attach a text component to the object.
*/
private javax.swing.text.JTextComponent textComponent;
/**
* Used to Paste from DisplayPanel to system clipboard.
*/
private DataTransfer dataTransfer;
/**
* The minimum value of msize
*/
private final int minimumSize = 3;
/**
* The range of sizes allowed
*/
private java.util.Vector<Integer> sizes;
/**
* Used to indicate whether the calculator should be graphical.
*/
private boolean graphical;
private static final long serialVersionUID = 1L;
}