I really don't understand why these calculators came unpopular as they are really oriented for programmer's mind.
My old HP16C is about to dye, I am ready to pay few hundred $ for a new one but HP does not make them anymore.
So I wrote my own RPN calculator last week. Here is it absolutly free for you.
It uses byte, word, integer, long or BigInteger. You can compute 12345678889986862 at power 23456789.
This is not really a tutorial but too much code to be post in the Code Snippet section.
There is a lot of comments in the code and I will be glad to answer any question about it.
Many classes have good example of Swing GUI features and how to use them.
For the JFrame a used my VariableGridPanel class already posted in the Code Snippet section
VariableGridPanel.java
import javax.swing.*;
import java.util.*;
import java.awt.*;
/**
* A class that implements a JPanel which is split in a grid like in a GridLayout
* but a lot easier to use than a GridBagLayout
*
* Differences with GridLayout:
* - you can only add JComponent to it
* - you are not force to add a component to all cells
* - for each component you add you specify on whow many grid it should spread
* horizontaly and verticaly (default 1, 1) this allow a component to span over more than one cell
* - you can add and remove component whenever you want
* - a cell can be shared by two components (bigger than 1X1) they will just overlap
* - when a gap is specified it also apply to the beginning and edge of the JPanel
*
* I wrote it quick and dirty because I quickly need a calculator (usually ideal from GridLayout)
* but I need the "0" and "Enter" key to spread over 2 cells
*/
public class VariableGridPanel extends JPanel {
private static final long serialVersionUID = 1L;
// an ArrayList to hold all the JComponent added
private ArrayList<CompPosSize> al = new ArrayList<CompPosSize>();
// number of rows, columns and the gap in pixels between them
private int nbRow, nbCol, xGap, yGap;
/**
* Constructor that receives the number of row and column as parameter
*/
public VariableGridPanel(int row, int col) {
// wich calls the constructor where gaps are secified
this(row, col, 0, 0);
}
/**
* Constructor that receives the number of row, the number of columns and the gap
*/
public VariableGridPanel(int row, int col, int hGap, int vGap) {
// we are using a null panel
super(null);
nbRow = row;
nbCol = col;
xGap = hGap;
yGap = vGap;
}
// to add a JComponent with its coordinates
public void addComp(JComponent comp, int row, int col) {
// defaulted to 1 grid width and 1 grid height
addComp(comp, row, col, 1, 1);
}
// to add a JComponent that spread over more than one cell
public void addComp(JComponent comp, int row, int col, int w, int h) {
// register the JComponent and its x,y,w,h in the ArrayList
al.add(new CompPosSize(comp, row, col, w, h));
add(comp);
}
// to remove a component
public void remove(JComponent comp) {
int size = al.size();
// scan the arraylist
for(int i = 0; i < size; i++) {
CompPosSize cps = al.get(i);
// if found
if(cps.comp == comp) {
super.remove(comp);
al.remove(i); // remove it
return; // done
}
}
}
/**
* Overload the paintComponent method
*/
public void paint(Graphics g) {
// before calling my super.paintComponent() method
// determine the size of each registered component
Dimension size = getSize();
int cellWidth = size.width / nbCol;
int cellHeight = size.height / nbRow;
for(CompPosSize cps : al) {
// the x position is x * cellWidth
int xPos = cellWidth * cps.x + xGap;
// the width is the cellWidth times my width in cell - gap
int width = cellWidth * cps.w - xGap;
// same thing on the yAxis
int yPos = cellHeight * cps.y + yGap;
int height = cellHeight * cps.h - yGap;
// set the bounds of our component
cps.comp.setBounds(xPos, yPos, width, height);
}
// call my super to paint the components now they have been positionned
super.paint(g);
}
/*
* To Unit test the whole thing
*/
public static void main(String[] args) {
// create a frame
JFrame f = new JFrame("Test variable grid");
// and a VariableGridPanel
VariableGridPanel vgp = new VariableGridPanel(10, 10, 2, 1);
// add different buttons for our test
vgp.addComp(new JButton("BTN0"), 0, 0, 2, 1);
vgp.addComp(new JButton("BTN1"), 2, 2);
vgp.addComp(new JButton("BTN3"), 3, 1);
vgp.addComp(new JButton("BTN4"), 3, 2);
vgp.addComp(new JButton("BTN2"), 2, 5, 2, 3);
// and one that will share a cell with the previous one
vgp.addComp(new JButton("BTN5"), 4, 6, 2, 2);
vgp.addComp(new JButton("BTN6"), 9, 9);
// and a JLabel that I will make disappear
JLabel label = new JLabel("I will eventually disappear :)/>");
vgp.addComp(label, 6, 2, 4, 1);
f.add(vgp);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// show the JFrame
f.setSize(500, 500);
f.setVisible(true);
// wait 3 seconds
try {
Thread.sleep(3000);
}
catch(Exception e) {}
// remove the JLabel
vgp.remove(label);
vgp.repaint();
}
/**
* A private class that holds a JComponent, its coordinates and size
*/
private class CompPosSize {
JComponent comp;
int x, y, w, h;
/**
* Constructor
*/
CompPosSize(JComponent comp, int row, int col, int width, int height) {
this.comp = comp;
y = row;
x = col;
w = width;
h = height;
}
}
}
The rightmost part of the calculator shows an Ascii table in a JScrollPane. You can scroll that pane to see ascii value.
The ascii value of the number in the led display is showed in YELLOW. A JCheckedBox in the main panel enable/disable the
feature of having the scroll pane to scroll to the displayed value. This is a good tutorial on how to have a JTable to
display a particular row.
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.*;
import java.awt.*;
/*
* To display the ascii characters set
*/
public class AsciiPanel extends JPanel {
private static final long serialVersionUID = 1L;
// the ascii representation of the first 32 control characters
private static final String[] control = {"nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", "bsp", "tab",
"lf", "vt", "np", "cr", "so", "si", "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", "can",
"em", "eof", "esc", "fs", "gs", "rs", "us"};
// the header of the JTable
private static String[] header = {"Dec", "Hex", "Char"};
private JTable table;
private JViewport viewport;
// last called (init to last one surely not showed at startup)
private int lastCode = 255;
// table model which implements CellRenderer
private Model model;
// not to create a TableModelEvent at each time the character changes
// we will pre-create the 256 of them
private TableModelEvent[] tme = new TableModelEvent[256];
// the checkBox that says if we should scroll the AsciiPanel to show the character
private JCheckBox checkBox;
/*
* Constructor
*/
AsciiPanel(JCheckBox checkBox) {
super(new BorderLayout());
this.checkBox = checkBox;
// header at the top in the North Region
JLabel l = new JLabel("Ascii");
l.setHorizontalAlignment(SwingConstants.CENTER);
l.setOpaque(true);
l.setBackground(Color.YELLOW);
l.setForeground(Color.BLUE);
add(l, BorderLayout.NORTH);
// creation of the table
model = new Model();
table = new MyTable();
JScrollPane scrollPane = new JScrollPane(table);
viewport = scrollPane.getViewport();
add(scrollPane, BorderLayout.CENTER);
setBorder(BorderFactory.createLineBorder(Color.BLACK));
// creates a model event for every row
for(int i = 0; i < 255; ++i)
tme[i] = new TableModelEvent(model, i);
}
// Assumes table is contained in a JScrollPane. Scrolls the
// cell (rowIndex, vColIndex) so that it is visible within the viewport.
private void scrollToVisible(int rowIndex, int vColIndex) {
// This rectangle is relative to the table where the
// northwest corner of cell (0,0) is always (0,0).
Rectangle rect = table.getCellRect(rowIndex, vColIndex, true);
// The location of the viewport relative to the table
Point pt = viewport.getViewPosition();
// Translate the cell location so that it is relative
// to the view, assuming the northwest corner of the
// view is (0,0)
rect.setLocation(rect.x -pt.x, rect.y -pt.y);
// Scroll the area into view
viewport.scrollRectToVisible(rect);
}
/*
* The method invoked by the DisplayPanel saying which Ascii code to show
*/
void showAscii(int code) {
// if no change
if(lastCode == code) {
return;
}
// save last code to fired TableModelEvent if required
table.tableChanged(tme[lastCode]);
table.tableChanged(tme[code]);
lastCode = code;
// if checkbox is disabled exit
if(!checkBox.isSelected())
return;
// else display the last code
scrollToCode();
}
/*
* Scroll to JTable to show the code
*/
void scrollToCode() {
// position the scrolling region (about 30 normally showed so make it as we
// wanted to show row @to do center the stuff
scrollToVisible(lastCode, 0);
}
/*
* Extends JTable just to be able to set the cell renderer
*/
class MyTable extends JTable {
private static final long serialVersionUID = 1L;
// to save the model which is also the renderer
MyTable() {
super(model);
}
/** tell to call the Model which is also a renderer for all rendering (we return JLabel) */
public TableCellRenderer getCellRenderer(int row, int col) {
return model;
}
}
/*
* The model for the JTable
*/
class Model extends AbstractTableModel implements TableCellRenderer {
private static final long serialVersionUID = 1L;
private JLabel label;
// contructor
Model() {
// prepare the JLabel tha I will return
label = new JLabel();
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setOpaque(true);
label.setForeground(Color.BLUE);
}
// decimal, hexa and the ascii value
public int getColumnCount() {
return 3;
}
// the 256 asii code
public int getRowCount() {
return 256;
}
public String getColumnName(int col) {
return header[col];
}
@Override
// just return an object we have a cell renderer anyhow
public Object getValueAt(int row, int col) {
return "";
}
@Override
public Component getTableCellRendererComponent(JTable arg0,
Object arg1, boolean arg2, boolean arg3, int row, int col) {
if(row == lastCode)
label.setBackground(Color.YELLOW);
else
label.setBackground(Color.WHITE);
// column 0 return the decimal value
if(col == 0) {
label.setText(String.format("%d", row));
} else if(col == 1) { // column 1 the hex value
label.setText(String.format("x%X", row));
} else { // the ascii representation
if(row < control.length) {
label.setText("<" + control[row] + ">");
}
else {
String str = Character.toString((char) row);
label.setText(str);
}
}
return label;
}
}
}
The BinaryPanel shows the last 64 bits of the number displayed in the display.
It is a turorial about to change the Font size of a JComponent
import javax.swing.*;
import java.awt.*;
/*
* This panel display the last 64 bits of the number displayed on the led of the RPN calculator in
* the following format
* 0000 0000 0000 0000 0000 0000 0000 0000
* 56 48 40 32
* 0000 0000 0000 0000 0000 0000 0000 0000
* 24 16 8 0
*
* Both the bits and the bits numbers are only showed based on the word size
*/
public class BinaryPanel extends VariableGridPanel {
private static final long serialVersionUID = 1L;
private static int[] bitNumberStr = {56, 48, 40, 32, 24, 16, 8, 0};
// the JLabel for the bitNumber
private JLabel[] bitNumber = new JLabel[8];
// the jLabel for the 64 bits
private JLabel[] label = new JLabel[64];
/*
* Constructor
*/
BinaryPanel() {
// 4 rows with 39 columns 8 times 4 bits plus a gap between them
super(4, 39);
setBackground(new Color(220, 220, 220));
// smaller Font
Font font = new JLabel().getFont();
float size = font.getSize2D() * 0.9f;
Font fontBit = font.deriveFont(size);
size = font.getSize2D() * 0.8f;
font = font.deriveFont(size);
// the bit number
int col = 0; // starting column
int row = 1; // starting row
for(int i = 0; i < bitNumber.length; i++) {
// right justified using 8 bits + the space between the 2 quatuor 1111 1111
bitNumber[i] = new JLabel(String.format("[%02d]", bitNumberStr[i]));
bitNumber[i].setHorizontalAlignment(SwingConstants.RIGHT);
bitNumber[i].setForeground(Color.DARK_GRAY);
bitNumber[i].setFont(font);
// add to panel 9 cases width
addComp(bitNumber[i], row, col, 9, 1);
col += 10;
// begin second line
if(i == 3) {
row = 3;
col = 0; // and restart a col 0
}
}
// the bit labels put in reverse order
col = 0;
row = 0;
for(int i = 63; i >= 0; i--) {
label[i] = new JLabel("" + i);
label[i].setHorizontalAlignment(SwingConstants.CENTER);
label[i].setForeground(Color.BLUE);
label[i].setFont(fontBit);
addComp(label[i], row, col);
++col;
if(i % 4 == 0)
++col;
// change line a bit 32
if(i == 32) {
row = 2;
col = 0; // and restart from the beginning
}
}
setBorder(BorderFactory.createLineBorder(Color.BLUE, 1));
// init first time with 0 for int
refresh(StackElement.SIZE_INT, "0");
}
// update from the main display
protected void refresh(int wordSize, String bin) {
// make the bit number visible
for(int i = 0; i < bitNumber.length - 1; ++i)
bitNumber[i].setVisible(true);
// now the bits
// check if big integer
int ilen = bin.length();
if(ilen > 64) {
// but we will keep the leading 0
bin = bin.substring(ilen - 64);
ilen = 64;
}
char[] bit = bin.toCharArray();
// put to " " all bits in greater word size
for(int i = ilen; i < 64; ++i)
label[i].setText("");
// put the set bits
int index = ilen;
for(int i = 0; i < ilen; ++i) {
label[--index].setText("" + bit[i]);
}
// make visible the one that should be seen
int max = 56;
for(int i = 0; i < bitNumber.length; i++) {
if(ilen > max)
break;
bitNumber[i].setVisible(false);
max -= 8;
}
}
}
The ButtonCallBack interface was written because at design time I though that different class would be called back
depending of what button was pressed. It is actually not used as all call back when a button is pressed is done to the
MainDisplay class. Anyhow it is an example of how an Interface is implemented.
/**
* This interface is used for the call back of a JButton
* The RpnButton constructor receives as parameter a ButtonCallBack
* They implement and actionPerformed and when this one is fired
* they call buttonclick() with a code that was provided when the button was built
*
*/
public interface ButtonCallBack {
// button has been clicked and returned that code
void buttonclick(int code);
}
CalculatorGUI is the main class. It is the one that creates the JFrame for the whole application. It is the one that
contains the main() method which is invoked to start the application.
When a button is clicked it performs what it has to do and then transfer back the focus to the JFrame.
That is not very elegant but it allows the JFrame to react to keyboard input as it always have focus.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* The RpnCalculator GUI
*/
public class CalculatorGUI extends JFrame implements KeyListener, ActionListener, MouseListener {
private static final long serialVersionUID = 1L;
private static final String copyRight = "Copyright © 2010 Les Services Informatiques PBL";
private static final Color darkGreen = new Color(0, 110, 0);
// the JLabel where to enter data
private MainDisplay mainDisplay;
// we have to save it for the calls back when the word size of the base change
private StackPanel stackPanel;
// the buttons for keyboard access
private RpnButton rpnButton;
// The AsciiPanel for call back when scrolling is enabled
private AsciiPanel asciiPanel;
// The checkbox if we scroll the ascii table and if we display the help
private JCheckBox asciiCheckBox, helpCheckBox;
// The label for context help
private JLabel helpLabel;
/**
* Constructor
*/
CalculatorGUI() {
super("RPN Calculator");
// First the panels that are independant from the others
BinaryPanel binaryPanel = new BinaryPanel();
MemoryPanel memoryPanel = new MemoryPanel(this);
// the JCheckBox for ascii scrolling
asciiCheckBox = new JCheckBox("Scroll Ascii Table");
asciiCheckBox.addActionListener(this);
asciiCheckBox.addMouseListener(this);
// the checkBox for help display
helpCheckBox = new JCheckBox("Display buttons help", true);
helpCheckBox.addActionListener(this);
helpCheckBox.addMouseListener(this);
// the panel offering the RadioButtons and the checkedBoxes
JPanel wordBasePanel = new WordSizeAndBasePanel(this, asciiCheckBox, helpCheckBox);
// create the stack
RpnStack stack = new RpnStack(this);
// the stackPanel was built when the RpnStatck was built
stackPanel = stack.getPanel();
// the ascii panel
asciiPanel = new AsciiPanel(asciiCheckBox);
// the main display that display the output led abd performs the opeartion
mainDisplay = new MainDisplay(stack, binaryPanel, memoryPanel, asciiPanel);
// generate the buttons
rpnButton = new RpnButton(mainDisplay, this);
// the Panel that display the Calculator buttons
// needs to be called once the rpnButtons has been created
CenterPanel centerPanel = new CenterPanel();
// the led display of the calculator
JLabel label = mainDisplay.getLabel();
label.setOpaque(true);
label.setBackground(Color.WHITE);
label.setHorizontalAlignment(SwingConstants.RIGHT);
label.addMouseListener(this);
// put in a ScrollPane if ever it becomes really big
JScrollPane displayPane = new JScrollPane(label);
displayPane.setBorder(BorderFactory.createLineBorder(Color.BLUE));
// the JLabel to display Help
helpLabel = new JLabel(copyRight);
helpLabel.setOpaque(true);
helpLabel.setBackground(Color.WHITE);
helpLabel.addMouseListener(this);
// position all our internal panels
//---------------------------------
// The panel we will put in the center containing all the others
VariableGridPanel p = new VariableGridPanel(19, 5, 3, 3);
// the main display at the top left
p.addComp(displayPane, 0, 0, 3, 2);
// put the memoryPanel in
p.addComp(memoryPanel, 2, 0, 1, 3);
// the panel that offer the RadioButtons for base and word size and the checkboxes on the left just under
memory
p.addComp(wordBasePanel, 5, 0, 1, 13);
// the binary panel with the binary representation under the display
p.addComp(binaryPanel, 2, 1, 2, 3);
// the Panel that display the Calculator buttons just under it
p.addComp(centerPanel, 5, 1, 2, 13);
// the stack panel at the left
p.addComp(stackPanel, 0, 3, 1, 18);
// and the Ascii panel at its right
p.addComp(asciiPanel, 0, 4, 1, 18);
// the help
p.addComp(helpLabel, 18, 0, 5, 1);
// all that in the Center Region
add(p, BorderLayout.CENTER);
// for keyboard access
this.setFocusable(true);
addKeyListener(this);
}
// to display the help message
protected void setHelpText(String str) {
// if help display is enabled
if(helpCheckBox.isSelected()) {
if(str == null) {
helpLabel.setForeground(Color.BLACK);
helpLabel.setText(copyRight);
}
else {
helpLabel.setForeground(darkGreen);
helpLabel.setText(str);
}
}
}
// change in the base inform everybody who should be notified about it
protected void setBase(int baseIdx) {
// enable/disable the buttons depending of the base
RpnButton.setBase(baseIdx);
// change the display in the stack
stackPanel.setBase(baseIdx);
mainDisplay.setBase(baseIdx);
}
// change in the word size inform everybody who should be notified about it
protected void setWordSize(int wordSize) {
mainDisplay.setWordSize(wordSize);
stackPanel.setWordSize(wordSize);
}
// called by the StackPanel to set the register X to a certain value
void setReg0(StackElement se) {
// will be null if coming from MemoryPanel
if(se == null)
mainDisplay.setReg0(null);
else // clone it before sending it in
mainDisplay.setReg0(new StackElement(se));
}
/*
* A listener on the checkbox just to reset
* the focus on the JFrame
*/
public void actionPerformed(ActionEvent e) {
Object o = e.getSource();
// keep the focus on the frame for the KeyListener
if(o == asciiCheckBox) {
asciiPanel.scrollToCode();
}
else { // the help enabled/disable
// if disabled put the copyright
if(!helpCheckBox.isSelected()) {
helpLabel.setForeground(Color.BLACK);
helpLabel.setText(copyRight);
}
}
this.requestFocus();
}
/* A key is entered on the keyboard. Thansfer to our RpnButton */
public void keyPressed(KeyEvent e) {
char c = e.getKeyChar();
int code = e.getKeyCode();
rpnButton.keyTyped(c, code);
}
/* For the Help of the CheckBox */
public void mouseEntered(MouseEvent e) {
Object o = e.getSource();
if(o == asciiCheckBox)
setHelpText("Check this button if you want the Ascii table to scroll automatically to the lowest
byte value in the X register.");
else if(o == helpCheckBox)
setHelpText("Check this button to have a help text displayed at the bottom of the screen when your
mouse is over a button.");
else if(o == helpLabel)
setHelpText("To disable multiple messages displayed in green here check off the \"Display buttons
help\" CheckedBox at the left of the panel.");
else // so it has to be the master display
setHelpText("The master register second operand of all operations. Named X in Help messages
displayed");
}
// clear the help message
public void mouseExited(MouseEvent e) {
setHelpText(null);
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent arg0) {}
public void mouseClicked(MouseEvent arg0) {}
public void mousePressed(MouseEvent arg0) {}
public void mouseReleased(MouseEvent arg0) {}
/**
* To start the whole applicatio
*/
public static void main(String[] args) {
// prepare the GUI to run in another thread
Runnable detach = new Runnable() {
public void run() {
CalculatorGUI rpncGui= new CalculatorGUI();
// make it visible and display it
rpncGui.setBounds(100, 100, 750, 500);
rpncGui.setVisible(true);
rpncGui.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
};
// start the thread
SwingUtilities.invokeLater(detach);
}
}
The CenterPanel class is the class that display the calculator buttons. It has an actionListener that calls back the
MainDisplay class.
public class CenterPanel extends VariableGridPanel {
private static final long serialVersionUID = 1L;
/**
* Constructor
*/
CenterPanel() {
super(6, 7, 2, 2);
addComp(RpnButton.getButton(RpnButton.BTN_FLIP), 0, 0);
addComp(RpnButton.getButton(RpnButton.BTN_POW), 0, 1);
addComp(RpnButton.getButton(RpnButton.BTN_A), 0, 2);
addComp(RpnButton.getButton(RpnButton.BTN_CLEAR), 1, 0);
addComp(RpnButton.getButton(RpnButton.BTN_ABS), 1, 1);
addComp(RpnButton.getButton(RpnButton.BTN_B)/>, 1, 2);
addComp(RpnButton.getButton(RpnButton.BTN_BSP), 1, 3);
addComp(RpnButton.getButton(RpnButton.BTN_POP), 1, 4);
addComp(RpnButton.getButton(RpnButton.BTN_C), 2, 2);
addComp(RpnButton.getButton(RpnButton.BTN_D), 3, 2);
addComp(RpnButton.getButton(RpnButton.BTN_E), 4, 2);
addComp(RpnButton.getButton(RpnButton.BTN_F), 5, 2);
addComp(RpnButton.getButton(RpnButton.BTN_SEVEN), 2, 3);
addComp(RpnButton.getButton(RpnButton.BTN_HEIGHT), 2, 4);
addComp(RpnButton.getButton(RpnButton.BTN_NINE), 2, 5);
addComp(RpnButton.getButton(RpnButton.BTN_MULT), 2, 6);
addComp(RpnButton.getButton(RpnButton.BTN_FOUR), 3, 3);
addComp(RpnButton.getButton(RpnButton.BTN_FIVE), 3, 4);
addComp(RpnButton.getButton(RpnButton.BTN_SIX), 3, 5);
addComp(RpnButton.getButton(RpnButton.BTN_MINUS), 3, 6);
addComp(RpnButton.getButton(RpnButton.BTN_ONE), 4, 3);
addComp(RpnButton.getButton(RpnButton.BTN_TWO), 4, 4);
addComp(RpnButton.getButton(RpnButton.BTN_THREE), 4, 5);
addComp(RpnButton.getButton(RpnButton.BTN_PLUS), 4, 6);
addComp(RpnButton.getButton(RpnButton.BTN_ZERO), 5, 3, 2, 1);
addComp(RpnButton.getButton(RpnButton.BTN_ENTER), 5, 5, 2, 1);
addComp(RpnButton.getButton(RpnButton.BTN_ROT_L), 2, 0);
addComp(RpnButton.getButton(RpnButton.BTN_ROT_R), 2, 1);
addComp(RpnButton.getButton(RpnButton.BTN_SHIFT_L), 3, 0);
addComp(RpnButton.getButton(RpnButton.BTN_SHIFT_R), 3, 1);
addComp(RpnButton.getButton(RpnButton.BTN_OR), 4, 0);
addComp(RpnButton.getButton(RpnButton.BTN_XOR), 4, 1);
addComp(RpnButton.getButton(RpnButton.BTN_AND), 5, 0);
addComp(RpnButton.getButton(RpnButton.BTN_NOT), 5, 1);
addComp(RpnButton.getButton(RpnButton.BTN_MEM_SET), 0, 4);
addComp(RpnButton.getButton(RpnButton.BTN_MEM_RECALL), 0, 5);
addComp(RpnButton.getButton(RpnButton.BTN_RMD), 0, 6);
addComp(RpnButton.getButton(RpnButton.BTN_CHANGE_SIGN), 1, 5);
addComp(RpnButton.getButton(RpnButton.BTN_DIV), 1, 6);
}
}
The MainDisplay class is the one that performs all mathematical/logical operations. It reacts to button push because the
CenterPanel defined it as ActionListener to all buttons.
import javax.swing.*;
import java.awt.*;
import java.math.BigInteger;
/**
* The main display where user enters value
*
*/
public class MainDisplay {
// the basic digit stuff
private static String displayCode = "0123456789ABCDEF";
// the baseIdx to use default to Decimal at construction time
private int baseIdx = StackElement.DECIMAL_IDX;
// the translation in base
private static final int[] base = {16, 10, 8, 2};
// the max size in hex and bin and octal
private static final int[] hexMaxSize = {0, 16, 8, 4, 2};
private static final int[] binMaxSize = {0, 64, 32, 16, 8};
// for the max and min in decimal
private static final BigInteger bigMax = new BigInteger("" + Long.MAX_VALUE);
private static final BigInteger bigMin = new BigInteger("" + Long.MIN_VALUE);
// the panel that display MemoryContent
private MemoryPanel memoryPanel;
// the word size
private int wordSize = StackElement.SIZE_INT;
// the StackElement that I display
private StackElement display = new StackElement("0", baseIdx, StackElement.SIZE_INT);
// end the memory
private StackElement memory = new StackElement("0", baseIdx, StackElement.SIZE_INT);
// the JLabel to display user's number in the base
private JLabel label;
// the actual number to be display the JLabel
private String numberStr;
// if the lastOparation was "enter". In that case do not push new display
private boolean lastOperationWasEnter;
// if last entered was a digit
private boolean lastOperationWasDigitInput = true;
// the stack
private RpnStack stack;
// the second display
private SecondDisplay secondDisplayPanel;
// the AsciiPanel
private AsciiPanel asciiPanel;
/**
* Constructor
*/
protected MainDisplay(RpnStack stack, SecondDisplay secondDisplayPanel, MemoryPanel memoryPanel, AsciiPanel asciiPanel) {
this.stack = stack;
this.secondDisplayPanel = secondDisplayPanel;
this.memoryPanel = memoryPanel;
this.asciiPanel = asciiPanel;
// the label that display the number
numberStr = "0";
// the label at the top that display based on the base
label = new JLabel(numberStr + " ");
// increase the size of the digit
Font font = label.getFont();
label.setFont(font.deriveFont(font.getSize2D() * 1.4f));
// init the first ascii showed
asciiPanel.showAscii(0);
}
// call back when a button is clicked
public void buttonclick(int code) {
// if it is a digit button we call the method that appends
if(code <= RpnButton.BTN_F) {
userTyped(code);
return;
}
// so it is not a digit
lastOperationWasDigitInput = false; // will be for all but backspace and +/- that will switch it later
// big switch for the other
switch(code) {
// enter on: we simply have to push on the stack a copy of the display
case RpnButton.BTN_ENTER:
StackElement se = new StackElement(display);
stack.push(se);
lastOperationWasEnter = true;
updateDisplay();
break;
// a pop
case RpnButton.BTN_POP:
display = stack.pop();
numberStr = display.toString(wordSize, baseIdx);
updateDisplay();
break;
// backspace as been entered
case RpnButton.BTN_BSP:
// exception a backspace and +/- are like digit input
lastOperationWasDigitInput = true;
processBSP();
break;
// a change sign
case RpnButton.BTN_CHANGE_SIGN:
// exception a backspace and +/- are like digit input
lastOperationWasDigitInput = true;
processChangeSign();
break;
// + - * /
case RpnButton.BTN_DIV:
case RpnButton.BTN_PLUS:
case RpnButton.BTN_MINUS:
case RpnButton.BTN_MULT:
case RpnButton.BTN_POW:
case RpnButton.BTN_RMD:
// logical
case RpnButton.BTN_OR:
case RpnButton.BTN_XOR:
case RpnButton.BTN_AND:
case RpnButton.BTN_AND_NOT:
case RpnButton.BTN_FLIP:
case RpnButton.BTN_CLEAR:
// shift
case RpnButton.BTN_SHIFT_L:
case RpnButton.BTN_SHIFT_R:
processOperationCode(code);
break;
// no statck access
case RpnButton.BTN_NOT:
case RpnButton.BTN_ABS:
performNoPopOperation(code);
break;
case RpnButton.BTN_ROT_L:
case RpnButton.BTN_ROT_R:
performRotateOperation(code);
break;
// memory operation
case RpnButton.BTN_MEM_SET:
case RpnButton.BTN_MEM_RECALL:
performMemOperation(code);
break;
}
}
// user has type a digit to append to the display
private void userTyped(int code) {
// if the last operation was not a dgitEnterered save
// the value on the stack but for enter
if(!lastOperationWasDigitInput) {
if(!lastOperationWasEnter) {
StackElement last = new StackElement(numberStr, baseIdx, wordSize);
stack.push(last);
}
numberStr = "";
}
lastOperationWasDigitInput = true;
lastOperationWasEnter = false;
// length of the display number in BigInteger
int ilen = numberStr.length();
// get ride of the leading "0" if there is one
if(ilen == 1 && numberStr.charAt(0) == '0') {
numberStr = "";
// reset the fact that the last operation was an "enter"
}
// reset the String value in BigInteger
numberStr += displayCode.charAt(code);
updateDisplay();
}
/*
* Update label display based on base and word size
*/
private void updateDisplay() {
// reset our value based on that String
display.setValue(numberStr, baseIdx, wordSize);
// get the value for that type based on base
label.setText(display.toString(wordSize, baseIdx) + " ");
asciiPanel.showAscii(display.getInt() & 0xFF);
// display the second displays (using long as max)
int tmpSize = wordSize;
if(tmpSize == StackElement.SIZE_BIG)
tmpSize = StackElement.SIZE_LONG;
// get longValue for decimal panel
long longValue = display.getLong();
secondDisplayPanel.refresh(wordSize,
display.toString(tmpSize, StackElement.BINARY_IDX),
display.toString(tmpSize, StackElement.HEXADECIMAL_IDX),
longValue);
// reset by default the digit buttons enabled or not
RpnButton.setBase(baseIdx);
// enable/disable the / and Reminder buttons if divisor == 0
boolean divEnabled = true;
if(numberStr.length() == 1 && numberStr.charAt(0) == '0')
divEnabled = false;
RpnButton.getButton(RpnButton.BTN_DIV).setEnabled(divEnabled);
RpnButton.getButton(RpnButton.BTN_RMD).setEnabled(divEnabled);
RpnButton.getButton(RpnButton.BTN_BSP).setEnabled(divEnabled);
// if last operation was a push does not disable some buttons
if(lastOperationWasEnter)
return;
// there are no buttons to enable disable for BigInteger
// all math operations are permitted and we can append digit to it
if(wordSize == StackElement.SIZE_BIG)
return;
int ilen = numberStr.length();
// in hexa and binary easy the number of digits entered let us konw if we can continue
if(baseIdx == StackElement.HEXADECIMAL_IDX) {
if(ilen < hexMaxSize[wordSize])
return;
disableBut(0);
return;
}
if(baseIdx == StackElement.BINARY_IDX) {
if(ilen < binMaxSize[wordSize])
return;
disableBut(0);
return;
}
// in octal, we have to see if the binary representation of this octal number has room for 3 bits
if(baseIdx == StackElement.OCTAL_IDX) {
// get binary representation
String str = display.toString(wordSize, StackElement.BINARY_IDX);
if(str.length() > binMaxSize[wordSize] - 3)
disableBut(0);
return;
}
// we will use the long value to perform our tests
// as Java promotes byte and short operation so int anyway
long val = display.getLong();
// determine which button should be disabled as they would overload the word size
if(val == 0) // 0 nothing to do
return;
// Get the min and max value for that word size
long max = StackElement.getMaxValue(wordSize);
long min = StackElement.getMinValue(wordSize);
// for byte, short, int and long you can always change a positive number
// to its negative value. This not the case for MIN_BYTE, MIN_SHORT,....
// for example byte go from -128 to 127 and short from -32768 to 32767.
// If the value is the minimum value for the type we have to disable the +/- button
if(val == min)
RpnButton.getButton(RpnButton.BTN_CHANGE_SIGN).setEnabled(false);
// if one of any end disable all
if(val == min || val == max) {
disableBut(0);
return;
}
// for byte, short, int I can test in a long
// for long I will have to use a BigInteger
// test first in within 10 times the size
if(val > min / 10 && val < max / 10) {
return;
}
// number that we can add to the (actual value * 10)
long free;
// we will have to use BigInteger
if(wordSize == StackElement.SIZE_LONG) {
// create BigInteger 10 times as big as our number
BigInteger bigVal = new BigInteger(numberStr + "0");
BigInteger bigFree;
// positive
if(val > 0) {
if(bigVal.compareTo(bigMax) > 0) {
disableBut(0);
return;
}
bigFree = bigMax.subtract(bigVal);
}
else { // negative
if(bigVal.compareTo(bigMin) < 0) {
disableBut(0);
return;
}
bigFree = bigMax.subtract(bigVal);
bigFree = bigFree.negate();
}
free = bigFree.longValue();
}
else {
// for smaller than long I can perform the calculation in long
// see if we can multiply by 10 to add another digit
long times10 = val * 10;
if(times10 < min || times10 > max) {
disableBut(0); // disable all buttons
return;
}
// see if all digits are available
// positive value
if(val > 0) {
free = max - times10; // how many can I add after I multiply by 10
}
else {
free = min - times10;
free = -free; // make it positive for single testing
}
}
// so disable everything higer
if(free < 9)
disableBut((int) free + 1);
}
/*
* Disable buttons from N to F
*/
private void disableBut(int from) {
for(int i = from; i < base[baseIdx]; i++)
RpnButton.setOutOfRange(i);
}
/*
* Change in the base
*/
void setBase(int baseIdx) {
this.baseIdx = baseIdx;
numberStr = display.toString(wordSize, baseIdx);
updateDisplay();
String str = memory.toString(wordSize, baseIdx);
memoryPanel.setText(str);
}
/*
* Change in the word size
*/
void setWordSize(int wordSize) {
this.wordSize = wordSize;
numberStr = display.toString(wordSize, baseIdx);
updateDisplay();
String str = memory.toString(wordSize, baseIdx);
memoryPanel.setText(str);
}
/*
* Call from the JFrame which was called by the StackPanel or the MemoryPanel so set the Reg0
* to a certain stack value
*/
protected void setReg0(StackElement se) {
// if se == null it is for the memory register
if(se == null) {
performMemOperation(RpnButton.BTN_MEM_RECALL);
return;
}
// OK coming from StackPanel
display = se;
numberStr = display.toString(wordSize, baseIdx);
updateDisplay();
}
/*
* For the CenterPanel to access my label and pane
*/
JLabel getLabel() {
return label;
}
// handles a BackSpace
private void processBSP() {
// user decided to keep that number
lastOperationWasEnter = false;
// if len == 1 then just put a 0
int ilen = numberStr.length();
if(ilen == 1) {
numberStr = "0";
updateDisplay();
return;
}
// if len == 2 but first one is '-'
if(ilen == 2 && numberStr.charAt(0) == '-') {
numberStr = "0";
updateDisplay();
return;
}
// remove last digit
numberStr = numberStr.substring(0, ilen-1);
updateDisplay();
}
// process the +/-
private void processChangeSign() {
// we are in decimal because +/- is disabled in other modes
if(numberStr.length() == 1 && numberStr.charAt(0) == '0')
return;
// reset the fact that the last operation was an "enter"
lastOperationWasEnter = false;
// check if first digit is a -
if(numberStr.charAt(0) == '-')
numberStr = numberStr.substring(1);
else
numberStr = "-" + numberStr;
// redo calculations
updateDisplay();
}
// operations without pop
private void performNoPopOperation(int code) {
BigInteger val = display.getBigInteger();
BigInteger result = null;
switch(code) {
case RpnButton.BTN_NOT:
result = val.not();
break;
case RpnButton.BTN_ABS:
result = val.abs();
break;
}
numberStr = result.toString(base[baseIdx]);
// update our value
updateDisplay();
}
// memory clear/set/recall
private void performMemOperation(int code) {
switch(code) {
case RpnButton.BTN_MEM_SET:
memory = new StackElement(display);
break;
case RpnButton.BTN_MEM_RECALL:
display = new StackElement(memory);
lastOperationWasEnter = false;
numberStr = display.toString(wordSize, baseIdx);
updateDisplay();
return; // <--- return no need to update memory display
}
// for clear and set update mem display
String str = memory.toString(wordSize, baseIdx);
memoryPanel.setText(str);
}
// process operations in big integer
private void processOperationCode(int code) {
BigInteger opr = stack.pop().getBigInteger();
BigInteger val = display.getBigInteger();
BigInteger result = null;
// as for a push/enter next character entered will clear display
// no need to disable out of range digit
switch(code) {
case RpnButton.BTN_PLUS:
result = opr.add(val);
break;
case RpnButton.BTN_MINUS:
result = opr.subtract(val);
break;
case RpnButton.BTN_MULT:
result = opr.multiply(val);
break;
case RpnButton.BTN_DIV:
result = opr.divide(val);
break;
case RpnButton.BTN_OR:
result = opr.or(val);
break;
case RpnButton.BTN_XOR:
result = opr.xor(val);
break;
case RpnButton.BTN_AND:
result = opr.and(val);
break;
case RpnButton.BTN_AND_NOT:
result = val.not();
result = result.and(opr);
break;
case RpnButton.BTN_POW:
result = opr.pow(display.getInt());
break;
case RpnButton.BTN_SHIFT_L:
result = opr.shiftLeft(display.getInt());
break;
case RpnButton.BTN_SHIFT_R:
result = opr.shiftRight(display.getInt());
break;
case RpnButton.BTN_RMD:
result = opr.remainder(val);
break;
case RpnButton.BTN_CLEAR:
result = opr.clearBit(display.getInt());
break;
case RpnButton.BTN_FLIP:
result = opr.flipBit(display.getInt());
break;
}
numberStr = result.toString(base[baseIdx]);
// update our value
updateDisplay();
}
// rotate can only performed on long
private void performRotateOperation(int code) {
// the number that will be rotate
StackElement num = stack.pop();
// local variable for
int i, shift;
String str;
// as for a push/enter next character entered will clear display
// no need to disable out of range digit
switch(wordSize) {
case StackElement.SIZE_LONG:
long l;
if(code == RpnButton.BTN_ROT_L)
l = Long.rotateLeft(num.getLong(), display.getInt());
else
l = Long.rotateRight(num.getLong(), display.getInt());
str = String.format("%d", l);
display.setValue(str, StackElement.DECIMAL_IDX, StackElement.SIZE_LONG);
break;
case StackElement.SIZE_INT:
if(code == RpnButton.BTN_ROT_L)
i = Integer.rotateLeft(num.getInt(), display.getInt());
else
i = Integer.rotateRight(num.getInt(), display.getInt());
str = String.format("%d", i);
display.setValue(str, StackElement.DECIMAL_IDX, StackElement.SIZE_INT);
break;
case StackElement.SIZE_SHORT:
i = num.getShort();
// remove signe extension
i &= 0xFFFF;
shift = i << 16;
// copy into higher bit
i |= shift;
if(code == RpnButton.BTN_ROT_L)
i = Integer.rotateLeft(i, display.getInt());
else
i = Integer.rotateRight(i, display.getInt());
i &= 0xFFFF;
str = String.format("%d", i);
display.setValue(str, StackElement.DECIMAL_IDX, StackElement.SIZE_SHORT);
break;
case StackElement.SIZE_BYTE:
i = num.getByte();
// remove sign extension
i &= 0xFF; // 000x
shift = i << 8; // 00x0
shift |= i; // 00xx
shift <<= 8; // 0xx0
shift |= i; // 0xxx
shift <<= 8; // xxx0
shift |= i; // xxxx
i = shift;
if(code == RpnButton.BTN_ROT_L)
i = Integer.rotateLeft(i, display.getInt());
else
i = Integer.rotateRight(i, display.getInt());
i &= 0xFF;
str = String.format("%d", i);
display.setValue(str, StackElement.DECIMAL_IDX, StackElement.SIZE_BYTE);
break;
}
// have it back to actual representation
numberStr = display.toString(wordSize, baseIdx);
updateDisplay();
}
}
The MemoryPanel displays the memory used to save a value. The calculator has a memory register to which any value can be
saved and later back recall.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/*
* Display the memory word
*/
public class MemoryPanel extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
private JLabel displayLabel;
private CalculatorGUI father;
MemoryPanel(CalculatorGUI father) {
super(new BorderLayout());
this.father = father;
setBorder(BorderFactory.createLineBorder(Color.BLACK));
// the header
JLabel label = new JLabel("Memory");
label.setHorizontalAlignment(SwingConstants.CENTER);
add(label, BorderLayout.NORTH);
// to display word stored in memory
displayLabel = new JLabel("0 ");
displayLabel.setOpaque(true);
displayLabel.setBackground(Color.WHITE);
displayLabel.setForeground(Color.BLUE);
displayLabel.setHorizontalAlignment(SwingConstants.RIGHT);
displayLabel.addMouseListener(this);
add(new JScrollPane(displayLabel), BorderLayout.CENTER);
// add a border by putting 2 empty label east and west
add(new JLabel(" "), BorderLayout.EAST);
add(new JLabel(" "), BorderLayout.WEST);
add(new JLabel(" "), BorderLayout.SOUTH);
}
/*
* To update memory display
*/
void setText(String str) {
displayLabel.setText(str + " ");
}
// call main frame to update main display with memory register
public void mouseClicked(MouseEvent arg0) {
father.setReg0(null);
}
public void mouseEntered(MouseEvent arg0) {
father.setHelpText("Click on this Memory register to copy it into the X register.");
}
public void mouseExited(MouseEvent arg0) {
father.setHelpText(null);
}
public void mousePressed(MouseEvent arg0) {}
public void mouseReleased(MouseEvent arg0) {}
}
The RpnButton class contains the definition of all buttons including the way they should be displayed in the GUI and the
help text associated to them. There is a nice tutorial on how to use a Swing.Timer and a MouseListener. When the mouse
enters a JButton the color of the button changes. When the mouse leaves, this color fades out due to successive Timer
calls.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
/*
* A class that extends JButton to expands there capabilities
* - degrading of color when mouse leaves it
* - disabled when related to a base they are irrelevant
* - all the buttons are made static an registered in a static array so they can be accessed by multiple classes
*/
public class RpnButton {
// list of all the codes put the number first we will use their code value
public static final int BTN_ZERO = 0,
BTN_ONE = 1,
BTN_TWO = 2,
BTN_THREE = 3,
BTN_FOUR = 4,
BTN_FIVE = 5,
BTN_SIX = 6,
BTN_SEVEN = 7,
BTN_HEIGHT = 8,
BTN_NINE = 9,
BTN_A = 10,
BTN_B = 11,
BTN_C = 12,
BTN_D = 13,
BTN_E = 14,
BTN_F = 15,
BTN_ROT_L = 16,
BTN_ROT_R = 17,
BTN_SHIFT_L = 18,
BTN_SHIFT_R = 19,
BTN_OR = 20,
BTN_XOR = 21,
BTN_NOT = 22,
BTN_AND = 23,
BTN_CLEAR = 24,
BTN_MEM_SET = 25,
BTN_MEM_RECALL = 26,
BTN_CHANGE_SIGN = 27,
BTN_DIV = 28,
BTN_MULT = 29,
BTN_PLUS = 30,
BTN_MINUS = 31,
BTN_ENTER = 32,
BTN_BSP = 33,
BTN_POP = 34,
BTN_POW = 35,
BTN_RMD = 36,
BTN_ABS = 37,
BTN_FLIP = 38;
// and their associated Strings. One for the button one for the Help
private static String[][] label = {
{"0", "Binary/Octal/Decimal/Hexadecimal 0."},
{"1", "Binary/Octal/Decimal/Hexadecimal 1."},
{"2", "Octal/Decimal/Hexadecimal 2 not available in Binary mode."},
{"3", "Octal/Decimal/Hexadecimal 3 not available in Binary mode."},
{"4", "Octal/Decimal/Hexadecimal 4 not available in Binary mode."},
{"5", "Octal/Decimal/Hexadecimal 5 not available in Binary mode."},
{"6", "Octal/Decimal/Hexadecimal 6 not available in Binary mode."},
{"7", "Octal/Decimal/Hexadecimal 7 not available in Binary mode."},
{"8", "Decimal/Hexadecimal 8 not available in Binary or Octal mode."},
{"9", "Decimal/Hexadecimal 9 not available in Binary or Octal mode."},
{"A", "Hexadecimal 0xA not enabled in other mode than Hexadecimal mode."},
{"B", "Hexadecimal 0xB not enabled in other mode than Hexadecimal mode."},
{"C", "Hexadecimal 0xC not enabled in other mode than Hexadecimal mode."},
{"D", "Hexadecimal 0xD not enabled in other mode than Hexadecimal mode."},
{"E", "Hexadecimal 0xE not enabled in other mode than Hexadecimal mode."},
{"F", "Hexadecimal 0xF not enabled in other mode than Hexadecimal mode."},
{"RoL", "Rotate Left the Y register by the number of bits specified in the X register. Stack is popped.
(Register X Long word size used)."},
{"RoR", "Rotate Right the Y register by the number of bits specified in the X register. Stack is popped.
(Register X Long word size used)."},
{"LsH", "Shift Left the Y register by the number of bits specified in the X register. (Register X Integer
word size used)."},
{"RsH", "Shift Right the Y register by the number of bits specified in the X register. (Register X Integer
word size used)."},
{"Or", "ORs the X and Y registers. Stack is popped."},
{"Xor", "XORs the X and Y registers. Stack is popped."},
{"Not", "Performs a logival NOT of the X register. The stack is NOT popped."},
{"And", "ANDs the X and Y registers. Stack is popped."},
{"Clr", "Clears in Y register the bit specified in X register. Stack is popped. (Register X Integer word
size used)."},
{"MS", "Copy the X register into Memory register. (No stack operation)."},
{"MR", "Copy the Memory register into X register. (No stack operation)."},
{"±", "Changes the sign of the X register. Stack is NOT popped. (Only available in Decimal mode)."},
{"/", "Divides Y register by the X register. Stack is popped. (Disabled when X register equals 0)."},
{"*", "Multiplies Y register by the X register. Stack is popped."},
{"+", "Adds Y register to the X register. Stack is popped."},
{"-", "Subtracts the X register from the Y register. Stack is popped."},
{"Enter", "Pushes the X register into the stack. Next digit entered would clear the X register. Backspace
and ChangeSign would keep it."},
{"BSP", "Removes the rightmost digit from the X register. (Not available when X register equals 0)."},
{"Pop", "Stack is popped which copies the Y register into the X register."},
{"POW", "Raise the Y register to the power of X register. Stack is popped. (Register X Integer word size
used)."},
{"RMD", "Performs the modulo of register Y by register X. Stack is popped. (Disabled when X register equals
0)."},
{"ABS", "Set the X register to absolute value. Stack is NOT popped. (Only available in Decimal mode)."},
{"Flip", "Flips in Y register the bit specified in X register. Stack is popped. (Register X Integer word
size used)."},
};
// the buttons kept in static variable
private static Btn[] button = new Btn[label.length];
// an arraylist to contains all my buttons so when the base changes I can check if they
// should be enabled or not
private static ArrayList<Btn> al = new ArrayList<Btn>(16);
// a larger Font for the smaller one
private Font smallFont, largerFont;
// the colors for my fadeout
private Color[] color;
// the color for the unary operator (do not change the stack)
private static final Color unaryOprColor = new Color(0, 100, 0); // dark greeen
// the operators using that color
private static final int unaryOpr[] = {BTN_CHANGE_SIGN, BTN_NOT, BTN_BSP, BTN_ABS, BTN_MEM_SET, BTN_MEM_RECALL};
// the color for the operations that use the Long version of the X register
private static final Color longColor = new Color(255, 0, 255);
// the buttons that use X register long value
private static final int longX[] = {BTN_ROT_L, BTN_ROT_R};
// the color for the operations that use the Integer version of the X register
private static final Color intColor = new Color(125, 0, 255);
// the buttons that use X register long value
private static final int intX[] = {BTN_SHIFT_L, BTN_SHIFT_R, BTN_CLEAR, BTN_FLIP, BTN_POW};
// the margin
private Insets inset = new Insets(1, 1, 1, 1);
// the interface for which I will call the buttonclick method when I am clicked
private ButtonCallBack callBack;
// the JFrame for focus
private CalculatorGUI master;
RpnButton(ButtonCallBack callBack, CalculatorGUI master) {
this.callBack = callBack;
this.master = master;
// build of yellow fading out
// and the base and the button
color = new Color[6];
// start by full white
int b = 255;
// loop inversed removing 5 times 30 to b so 255 to 155
color[5] = new Color(255, 255, B)/>;
for(int i = 4; i >= 0; --i) {
b -= 30;
color[i] = new Color(255, 255, B)/>;
}
// build the larger font and the font for "enter:
JButton but = new JButton("");
Font regularFont = but.getFont();
float size = regularFont.getSize2D();
smallFont = regularFont.deriveFont(size * 0.8f);
largerFont = regularFont.deriveFont(size * 1.4f);
// now generate the button
for(int i = 0; i < button.length; i++)
button[i] = new Btn(i);
}
/**
* To get a button
*/
static JButton getButton(int code) {
return button[code];
}
// the base was changed
protected static void setBase(int idx) {
// enable/disable button accordingly
for(Btn b : al) {
// change sign is an exception
if(b.code == BTN_CHANGE_SIGN || b.code == BTN_ABS)
b.setEnabled(idx == StackElement.DECIMAL_IDX);
else // the other based on the base
b.setEnabled(b.baseIdx >= idx);
}
}
// disable button outOfRange
protected static void setOutOfRange(int n) {
button[n].setOutOfRange();
}
// a key was typed on the keyboard
protected void keyTyped(char c, int keyCode) {
int idx;
if(c >= '0' && c <= '9')
idx = c - '0';
else if(c >= 'A' && c <= 'F')
idx = c - 'A' + 10;
else if(c >= 'a' && c <= 'f')
idx = c - 'a' + 10;
else if(c == '+')
idx = BTN_PLUS;
else if(c == '-')
idx = BTN_MINUS;
else if(c == '*')
idx = BTN_MULT;
else if(c == '/')
idx = BTN_DIV;
else if(keyCode == KeyEvent.VK_ENTER)
idx = BTN_ENTER;
else if(keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE)
idx = BTN_BSP;
else if(keyCode == KeyEvent.VK_DOWN)
idx = BTN_POP;
else if(keyCode == KeyEvent.VK_UP)
idx = BTN_ENTER;
else
return;
// test if enabled
if(button[idx].isEnabled()) {
// fake a mouse entering over that but button
button[idx].mouseEntered(null);
// if it is not an out of value
if(!button[idx].outOfRange) {
// do like a regular call back
callBack.buttonclick(idx);
}
// fake a mouse eciting event to start the shading
button[idx].mouseExited(null);
}
}
class Btn extends JButton implements ActionListener, MouseListener{
// base index over which I am disable
private static final long serialVersionUID = 1L;
// code that I will send to the callBack routine
private int code;
// if the base of the GUI is higher than this value, I will be disabled
private int baseIdx;
// the Timer to fadeout my yellow color
private Timer timer;
// color index formy fadeout
private int colorIdx;
// if I am disabled because out of range for the word size
// will be still enable and react to button click but won't perform call back
private boolean outOfRange;
/**
* Constructor for buttons that are always displayed
*/
private Btn(int code) {
super(label[code][0]);
// save code
this.code = code;
// if just a char make it larger
if(label[code][0].length() == 1)
setFont(largerFont);
else if(code == BTN_ENTER)
setFont(largerFont);
setMargin(inset);
setHorizontalAlignment(SwingConstants.CENTER);
setBackground(Color.WHITE);
setForeground(Color.BLUE);
// check for unary operator green
for(int i = 0; i < unaryOpr.length; i++) {
if(code == unaryOpr[i]) {
setForeground(unaryOprColor);
break;
}
}
// long only
for(int i = 0; i < longX.length; i++) {
if(code == longX[i]) {
setForeground(longColor);
break;
}
}
// int only
for(int i = 0; i < intX.length; i++) {
if(code == intX[i]) {
setForeground(intColor);
break;
}
}
// my timer when I fade out my color
timer = new Timer(100, this);
// action listener when I click the button
addActionListener(this);
// to trap the mouse in and out and change my color accordingly
addMouseListener(this);
// the / button is disabled at start time (cannot divide by 0)
if(code == BTN_DIV)
setEnabled(false);
// excption we register the change sign and ABS button only available in decimal
if(code == BTN_CHANGE_SIGN || code == BTN_ABS) {
al.add(this);
return;
}
// if not a digit button (but 0 and 1) always enabled but we have to put them
// because they can be disabled by the MainDisplay to avoid word overlap
if(code < BTN_TWO || code > BTN_F)
baseIdx = StackElement.BINARY_IDX;
// determine base
else if(code < BTN_HEIGHT)
baseIdx = StackElement.OCTAL_IDX;
else if(code > BTN_NINE)
baseIdx = StackElement.HEXADECIMAL_IDX;
else // only 8 and 9 left
baseIdx = StackElement.DECIMAL_IDX;
al.add(this);
// enable only decimal at start up
if(baseIdx == StackElement.HEXADECIMAL_IDX)
setEnabled(false);
}
@Override
public void actionPerformed(ActionEvent e) {
Object o = e.getSource();
// button was click execute my callback
if(o == this) {
if(outOfRange) {
master.requestFocus();
return;
}
callBack.buttonclick(code);
master.requestFocus();
return;
}
// ok it is the timer
++colorIdx;
if(colorIdx >= color.length) {
timer.stop();
return;
}
setBackground(color[colorIdx]);
}
/*
* Declare the button to large for word size
*/
private void setOutOfRange() {
outOfRange = true;
setFont(smallFont);
}
/*
* oveload regular steEnabled to stop color shading if in progress
*/
@Override
public void setEnabled(boolean state) {
// remove out of range if I am called is to enable or disable the button
if(outOfRange) {
outOfRange = false;
setFont(largerFont);
}
// if enabling
if(!state) {
// if I am fading out
if(timer.isRunning())
timer.stop();
setBackground(Color.WHITE);
}
super.setEnabled(state);
}
@Override
public void mouseEntered(MouseEvent arg0) {
// check if this button has tooltip (enabled or not)
if(label[code].length == 2)
master.setHelpText(label[code][1]);
// do nothing is the button is disabled
if(!isEnabled())
return;
// stop the timer if it was on
timer.stop();
// set the color to yellow
setBackground(color[0]);
}
@Override
public void mouseExited(MouseEvent arg0) {
master.setHelpText(null);
// do nothing is the button is disabled
if(!isEnabled())
return;
// reset the color for fading out
colorIdx = 0;
timer.start();
}
public void mouseClicked(MouseEvent arg0) {}
public void mousePressed(MouseEvent arg0) {}
public void mouseReleased(MouseEvent arg0) {}
}
}
An RPN calculator cannot exist without a Stack. This class contains an ArrayList of StackElement (what an original name
The stack contains a minimum of SIZE elements. If a Pop oreation would make the stack < SIZE a zero elemnt is added to it
import java.util.*;
/*
* Holds the 5 RPN stack in 5 ArrayList (one for each word size) and keep them synchronized
*/
public class RpnStack {
// number of element to keep int the stack
private static final int SIZE = 10;
// an element at 0 use you fill it at init time and whatever all the SIZE slots are not used
// (the base parameter is really not important here)
private static final StackElement ZERO = new StackElement("0", StackElement.DECIMAL_IDX, StackElement.SIZE_INT);
// the arrayList that contain our Stack
private ArrayList<StackElement> al;
// the panel that display my data
private StackPanel panel;
// constructor
protected RpnStack(CalculatorGUI father) {
// build the ArrayList with it's size
al = new ArrayList<StackElement>(SIZE);
// fill it with elements initialize to 0
for(int i = 0; i < SIZE; ++i)
al.add(ZERO);
// build the panel to display my data
panel = new StackPanel(this, father);
}
// to retreive the first element of the stack
protected final StackElement pop() {
StackElement se = al.remove(0);
// if less size less than SIZE we pop too much so add a new one at 0
if(al.size() < SIZE)
al.add(ZERO);
// on refresh
panel.refresh();
return se;
}
// to push a StackElement on the stack all the other ones are moved one up
protected void push(StackElement se) {
al.add(0, se);
panel.refresh();
}
// returns an element
protected StackElement get(int i) {
return al.get(i);
}
// returns the size of the stack
protected int getSize() {
return SIZE;
}
// for the Gui to display my panel
StackPanel getPanel() {
return panel;
}
}
The StackElement class defines the elements contained in the RpnStack. The class contains the BigInteger, long, int, word,
byte version of any number used in the calculator. It has a toString() method to return the displayable version of its
value base on the word size and base used.
import java.math.BigInteger;
/**
* Contains the 5 elements that are kept in the stack
*/
public class StackElement {
static public final int HEXADECIMAL_IDX = 0, DECIMAL_IDX = 1, OCTAL_IDX = 2, BINARY_IDX = 3;
static public final int SIZE_BIG = 0, SIZE_LONG = 1, SIZE_INT = 2, SIZE_SHORT = 3, SIZE_BYTE = 4;
private static final int baseList[] = {16, 10, 8, 2};
// format because iInteger.toString, val, base) returns a minus sign - for Hex and Octal
private static final String[] format = {"%X", "%d", "%o"};
// the smallest and largest in long does not apply to BigInteger
private static final long min[] = {0, Long.MIN_VALUE, Integer.MIN_VALUE, Short.MIN_VALUE, Byte.MIN_VALUE};
private static final long max[] = {0, Long.MAX_VALUE, Integer.MAX_VALUE, Short.MAX_VALUE, Byte.MAX_VALUE};
// for octal conversion
static private final char[] ascii = {'0', '1', '2', '3', '4', '5', '6', '7'};
// the element in the 5 base
private BigInteger bigValue;
private long longValue;
private int intValue;
private short shortValue;
private byte byteValue;
// constructor receives a String (most often from the JLabel) and the actual base and wordSize
protected StackElement(String label, int base, int wordSize) {
setValue(label, base, wordSize);
}
// to clone a StackElement
protected StackElement (StackElement se) {
bigValue = new BigInteger(se.bigValue.toString());
longValue = se.longValue;
intValue = se.intValue;
shortValue = se.shortValue;
byteValue = se.byteValue;
}
// when changing a value or called by the constructor
protected void setValue(String label, int base, int wordSize) {
// System.out.println("SetValue Str: >" + label + "< base: " + base + ">" + baseList[base]);
// parse it anyhow as a BigInteger to avoid java expecting - sign for Hex/oct/bin
bigValue = new BigInteger(label, baseList[base]);
// convert from bigger to smaller
longValue = bigValue.longValue();
intValue = (int) longValue;
shortValue = (short) intValue;
byteValue = (byte) shortValue;
// then promote from smaller to bigger
switch(wordSize) {
case SIZE_BYTE:
longValue = intValue = shortValue = byteValue;
break;
case SIZE_SHORT:
longValue = intValue = shortValue;
break;
case SIZE_INT:
longValue = intValue;
break;
}
// System.out.println("> " + label + "< L " + longValue + " I " + intValue + " S " + shortValue + " B " +
byteValue);
}
// returns the String representation based on size and base
protected String toString(int sizeIdx, int baseIdx) {
// for Short and Byte the hex string of intValue
String str;
// and it's len
int ilen;
// index of first not 0 when in binary
int index;
// depending of wordSize
switch(sizeIdx) {
case SIZE_BIG:
str = bigValue.toString(baseList[baseIdx]);
if(baseIdx == HEXADECIMAL_IDX)
return str.toUpperCase();
return str;
case SIZE_LONG:
if(baseIdx == BINARY_IDX)
return Long.toBinaryString(longValue);
else
return String.format(format[baseIdx], longValue);
case SIZE_INT:
if(baseIdx == BINARY_IDX)
return Integer.toBinaryString(intValue);
else
return String.format(format[baseIdx], intValue);
case SIZE_SHORT:
switch(baseIdx) {
case HEXADECIMAL_IDX:
str = String.format("%X", intValue);
ilen = str.length();
if(ilen > 4)
str = str.substring(ilen - 4);
return str;
case DECIMAL_IDX:
return String.format("%d", shortValue);
case OCTAL_IDX:
return shortToOctalString();
case BINARY_IDX:
str = Integer.toBinaryString(intValue & 0xFFFF);
// get rid of leadind 0
index = str.indexOf('1');
if(index == -1)
return "0";
else
return str.substring(index);
}
case SIZE_BYTE:
switch(baseIdx) {
case HEXADECIMAL_IDX:
str = String.format("%X", intValue);
ilen = str.length();
if(ilen > 2)
str = str.substring(ilen - 2);
return str;
case DECIMAL_IDX:
return String.format("%d", byteValue);
case OCTAL_IDX:
return byteToOctalString();
case BINARY_IDX:
str = Integer.toBinaryString(intValue & 0xFF);
// get rid of leadind 0
index = str.indexOf('1');
if(index == -1)
return "0";
else
return str.substring(index);
}
} // end switch(sizeIdx)
throw new IllegalStateException("Shouldn't pass here");
}
// return min and max value for a size
static long getMinValue(int sizeIdx) {
return min[sizeIdx];
}
// return min and max value for a size
static long getMaxValue(int sizeIdx) {
return max[sizeIdx];
}
/**
* the getters
*/
protected final BigInteger getBigInteger() {
return bigValue;
}
protected final long getLong() {
return longValue;
}
protected final int getInt() {
return intValue;
}
protected final short getShort() {
return shortValue;
}
protected final byte getByte() {
return byteValue;
}
/*
* Octal conversion more complicated than the others because not on byte boundary
* we cannot i & 0xFF or i & 0xFFFF as we did for binaty or hexa
*/
private String byteToOctalString() {
// to stor the octal digit as we found them
char[] digit = new char[3];
// index to digit[] as we go backward
int digitIdx = digit.length;
// in int (would be promoted anyway)
int value = intValue;
// to store twice the 3 first bits and the 2 last one
int j;
// the 2 first 3 bits
for(int i = 0; i < 2; i++) {
j = value & 0x7;
digit[--digitIdx] = ascii[j];
value >>= 3;
}
// the last 2 bits
j = value & 0x3;
digit[0] = ascii[j];
return new String(digit);
}
private String shortToOctalString() {
// to stor the octal digit as we found them
char[] digit = new char[6];
// index to digit[] as we go backward
int digitIdx = digit.length;
// in int (would be promoted anyway)
int value = intValue;
// to store 5 times the 3 first bits and the 2 last one
int j;
// the 2 first 3 bits
for(int i = 0; i < 5; i++) {
j = value & 0x7;
digit[--digitIdx] = ascii[j];
value >>= 3;
}
// the last single bit
j = value & 0x1;
digit[0] = ascii[j];
return new String(digit);
}
}
The StackPanel class displays the stack on the EAST region of the JFrame.
It has a MouseListener to display Help and to react to Mouse click. When a stackelement is clicked it's value is copied
into the X register. Regular RpnCalculators do not have this feature
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* To display the stack
*
*/
public class StackPanel extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
// color of the label
private static final Color lightColor = new Color(255, 255, 160);
// to know which stack to display
private int baseIdx = StackElement.DECIMAL_IDX;
// the word size
private int sizeIdx = StackElement.SIZE_INT;
// who to call back for the values
private RpnStack rpnStack;
// the JLabel displaying the stack
private JLabel[] label;
// the master frame for help callback
private CalculatorGUI father;
/**
* Constructor
*/
protected StackPanel(RpnStack rpnStack, CalculatorGUI father) {
super(new BorderLayout());
this.father = father;
setBorder(BorderFactory.createLineBorder(Color.BLACK));
// save my stack
this.rpnStack = rpnStack;
int size = rpnStack.getSize();
// a Header in the NorthRegion
JLabel head = new JLabel("Stack");
head.setHorizontalAlignment(SwingConstants.CENTER);
head.setOpaque(true);
head.setBackground(Color.YELLOW);
add(head, BorderLayout.NORTH);
// on the EastRegion the stack depth
JPanel layoutPanel = new JPanel(new GridLayout(size, 1));
// the labels of the depth
for(int i = size - 1; i >= 0; --i) {
JLabel label = new JLabel();
// we call the first stack element Y
if(i == 0) {
label.setText(" Y ");
}
else {
label.setText(" " + i + " ");
}
label.setBorder(BorderFactory.createLineBorder(Color.BLUE));
label.setOpaque(true);
label.setBackground(lightColor);
label.setHorizontalAlignment(SwingConstants.CENTER);
layoutPanel.add(label);
}
// added to the East Region
add(layoutPanel, BorderLayout.WEST);
// The center column
layoutPanel = new JPanel(new GridLayout(size, 1));
// the labels to display stack element
label = new JLabel[size];
for(int i = size-1; i >= 0; i--) {
label[i] = new JLabel();
label[i].setOpaque(true);
label[i].setBackground(Color.WHITE);
label[i].setHorizontalAlignment(SwingConstants.RIGHT);
// for the help text or the click
label[i].addMouseListener(this);
JScrollPane sp = new JScrollPane(label[i]);
sp.setBorder(BorderFactory.createLineBorder(Color.BLACK));
layoutPanel.add(sp);
}
// add it center
add(layoutPanel, BorderLayout.CENTER);
// load the stack for the first time
refresh();
}
/**
* The base change
*/
protected void setBase(int idx) {
baseIdx = idx;
// base changed the display element surely
refresh();
}
/**
* The word size change
*/
protected void setWordSize(int idx) {
sizeIdx = idx;
// base changed the display element surely
refresh();
}
/*
* Refresh required, the stack changed
*/
protected void refresh() {
// we update our labels
for(int i = 0; i < label.length; ++i) {
// the stack element to display
StackElement se = rpnStack.get(i);
// according to word size and base
label[i].setText(se.toString(sizeIdx, baseIdx) + " ");
}
}
// righ now just one JLabel has a mouse listener
public void mouseEntered(MouseEvent e) {
if(e.getSource() == label[0])
father.setHelpText("The bottom of the stack which is the first operand of all operations performed.
Named register Y in the help messages.");
else
father.setHelpText("Click on this stack register to copy it into the X register");
}
// simply erase the help text
public void mouseExited(MouseEvent arg0) {
father.setHelpText(null);
}
// mouse click we have to copy into register X but for label[0]
public void mouseClicked(MouseEvent e) {
Object o = e.getSource();
for(int i = 0; i < label.length; i++) {
if(o == label[i]) {
// the stack element to display
father.setReg0(rpnStack.get(i));
return;
}
}
}
// unused mouse event
public void mousePressed(MouseEvent arg0) {}
public void mouseReleased(MouseEvent arg0) {}
}
The WordSizeAndBasePanel class is a JPanel containing the JRadioButton to select the word size and the base (Hexadecimal,
Decimal, Octal, Binary) to use. It also contains the JCheckedBox if the help should be displayed and if the AsciiPanel
should scroll to display the ascii value of the rightmost byte in the X register.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Display the panel that permit to select the base and the size of the word
*
*/
public class WordSizeAndBasePanel extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
static private final String[] btnTextBase = {"Hexadecimal", "Decimal", "Octal", "Binary"};
static private final String[] btnTextSize = {"BigInteger", "Long", "Integer", "Short", "Byte"};
private JRadioButton[] radioBase = new JRadioButton[btnTextBase.length];
private JRadioButton[] radioSize = new JRadioButton[btnTextSize.length];
// the father that I will call when the base changes
private CalculatorGUI rpn;
/**
* Constructor
*/
WordSizeAndBasePanel(CalculatorGUI rpn, JCheckBox scroll, JCheckBox help) {
// nb rows: 3 header + 2 JCheckBox + all JRadioButton
super(new GridLayout(3 + 2 + btnTextBase.length + btnTextSize.length, 1, 3, 3));
this.rpn = rpn;
setBorder(BorderFactory.createLineBorder(Color.BLACK));
// the base header
JLabel label = new JLabel("--- Number Base ---");
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setForeground(Color.BLUE);
add(label);
// the radioButton for the base
ButtonGroup bg = new ButtonGroup();
for(int i = 0; i < radioBase.length; i++) {
radioBase[i] = new JRadioButton(btnTextBase[i]);
radioBase[i].addActionListener(this);
bg.add(radioBase[i]);
add(radioBase[i]);
}
// set the decimal button to on
radioBase[StackElement.DECIMAL_IDX].setSelected(true);
// the wordsize
label = new JLabel("--- Word Size ---");
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setForeground(Color.BLUE);
add(label);
// the radioButton for the word size
bg = new ButtonGroup();
for(int i = 0; i < radioSize.length; i++) {
radioSize[i] = new JRadioButton(btnTextSize[i]);
radioSize[i].addActionListener(this);
bg.add(radioSize[i]);
add(radioSize[i]);
}
// set the int button to on
radioSize[StackElement.SIZE_INT].setSelected(true);
// the options
label = new JLabel("--- Options ---");
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setForeground(Color.BLUE);
add(label);
// the 2 checkbox
add(scroll);
add(help);
}
@Override
public void actionPerformed(ActionEvent e) {
// find wich button has been clicked
Object but = e.getSource();
for(int i = 0; i < radioBase.length; i++) {
if(but == radioBase[i]) {
rpn.setBase(i); // inform everybody the base changed
// give back focus to JFrame
rpn.requestFocus();
return;
}
}
for(int i = 0; i < radioSize.length; i++) {
if(but == radioSize[i]) {
rpn.setWordSize(i); // inform everybody the base changed
// give back focus to JFrame
rpn.requestFocus();
return;
}
}
}
}
This post has been edited by pbl: 20 December 2010 - 10:05 PM





MultiQuote









|