Jump to content

[Java] GUI Calculator


WarFox
 Share

Recommended Posts

Here is a bit of an incomplete program I started. Well, the code I post works, but I had planned to extend this out. This calculator has a GUI and takes into account order of operations. The only issues that I've had with it, is that output can be a little wonky when answers are negative (such as 1- 9 * 9).

 

At some point when I have time to work on it again, my original plan was to build in the functionality to input an equation and allow the user to specify a range of values that X can hold, it would compute it and output all of the results. And of course to add in more operations such as trig functions, etc. Essentially my end goal at some point is a calculator that could take the place of my graphing calculator.

 

Main.java

Spoiler

public class Main {

    public static void main (String[] args) {
        Calculator calculator = new Calculator();
    }
}

 

Calculator.java

Spoiler


import java.lang.*;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.Box;
import java.awt.Dimension;
import javax.swing.JOptionPane;


public class Calculator implements ActionListener {

    private JTextField display;
    private String input = "";
    private JFrame cal;

    public Calculator () {

        cal = new JFrame("A Calculator");

        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());

        JPanel topPanel = new JPanel();
        topPanel.setLayout(new BorderLayout());
        mainPanel.add(topPanel, BorderLayout.NORTH);

        JPanel sysPanel = new JPanel();
        // default flow layout
        topPanel.add(sysPanel, BorderLayout.SOUTH);

        topPanel.add(Box.createRigidArea(new Dimension(300, 25)), BorderLayout.NORTH);

        display = new JTextField(20);
        topPanel.add(display, BorderLayout.CENTER);

        addButton(sysPanel, "About");
        addButton(sysPanel, "Exit");
        addButton(sysPanel, "Clear");

        JPanel buttonPanel = new JPanel();
        // flow layout

        JPanel numberGrid = new JPanel();
        numberGrid.setLayout(new GridLayout(4, 3));
        addButton(numberGrid, "7");
        addButton(numberGrid, "8");
        addButton(numberGrid, "9");
        addButton(numberGrid, "4");
        addButton(numberGrid, "5");
        addButton(numberGrid, "6");
        addButton(numberGrid, "1");
        addButton(numberGrid, "2");
        addButton(numberGrid, "3");
        addButton(numberGrid, "0");
        
        addButton(numberGrid, ".");
        addButton(numberGrid, "-");
        buttonPanel.add(numberGrid);

        JPanel operationPanel = new JPanel();
        operationPanel.setLayout(new GridLayout(5, 1));
        addButton(operationPanel, "/");
        addButton(operationPanel, "*");
        addButton(operationPanel, "-");
        addButton(operationPanel, "+");
        addButton(operationPanel, "=");
        buttonPanel.add(operationPanel);

        mainPanel.add(buttonPanel);
        cal.add(mainPanel);

        cal.setSize(300, 300);
        // cal.setDefaultLookAndFeelDecorated(true);
        cal.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        cal.setVisible(true);

    }

    private void addButton (JPanel pPanel, String pString) {
        JButton button = new JButton(pString);
        button.addActionListener(this);
        pPanel.add(button);
    }

    @Override
    public void actionPerformed (ActionEvent e) {
        if (e.getActionCommand().equals("Exit")) {
            System.exit(-1);
        } else if (e.getActionCommand().equals("About")) {
            JOptionPane popUp = new JOptionPane();
            String message = "JACalculator \n" +
                    "https://github.com/martintc/JACalculator";
            popUp.showMessageDialog(cal, message);
        } else if (e.getActionCommand().equals("Clear")) {
            input = "";
            display.setText(input);
        } else if (e.getActionCommand().equals("=")) {
            ParseCalculation calculate = new ParseCalculation(input);
            if (calculate.run()) {
                Double answer = calculate.getAnswer();
                display.setText(answer.toString());
            } else {
                display.setText("Error");
            }
        } else {
            String buttonInput = e.getActionCommand();
            if (buttonInput.equals("+") || buttonInput.equals("-") || buttonInput.equals("/") || buttonInput.equals("*")) {
                input = input + " " + buttonInput + " ";
            } else {
                input = input + buttonInput;
            }
            display.setText(input);
        }


    }

}

 

ParseCalculation.java

Spoiler

import java.lang.Double;

public class ParseCalculation {

    private String input;
    private double answer;

    public ParseCalculation (String pInput) {
        input = pInput;

    }

    /*
     * Runs the main part of the program to take the input, check for basic errors of input
     * and facilitate the calculation.
     */
    public boolean run () {
        String[] calculation = inputToArray(input);

        if (checkInputSyntax(calculation)) {
            answer = parseString(calculation); // change this value after completing class
            return true;
        } else {
            return false;
        }

    }

    /*
     * takes the input given by user and plits it up into an array using white space
     * as the division point.
     */
    private String[] inputToArray (String pInput) {
        return pInput.split(" ");
    }

    /*
     *
     * parseString
     * Goes through an array of Strings. and performs calculations
     *
     */
    private double parseString (String[] pInput) {
        double total = parseStringToInt(pInput[0]);
        for (int i = 1; i < pInput.length; i++) {
            if (pInput[i].equals("+")) {
                total = total + parseStringToInt(pInput[i+1]);
            } else if (pInput[i].equals("-")) {
                total = total - parseStringToInt(pInput[i+1]);
            } else if (pInput[i].equals("*")) {
                total = total * parseStringToInt(pInput[i+1]);
            } else if (pInput[i].equals("/")) {
                total = total / parseStringToInt(pInput[i+1]);
            } else {
                // do nothing
            }
        }
        return total;
    }

    /*
     *
     * parseStringToInt
     * Takes a string, parses it's value into an object of Integer.
     * Then it unboxes the value into a primitive type of int.
     *
     */
    private double parseStringToInt(String pIn) {
        Double in = Double.parseDouble(pIn);
        double unwrappedIn = in;
        return unwrappedIn;
    }

    /*
     * Takes the input in array format and checks if two operands are placed next to eachother.
     * If two operands are placed next to eachother, it will return false causing the JTextField
     * in the main window to change the text "Error."
     */
    private boolean checkInputSyntax (String[] pIn) {
        boolean isTrue = true;
        for (int i = 0; i < pIn.length-1; i++) {
            if ((pIn[i].equals("+") || pIn[i].equals("-") || pIn[i].equals("*") || pIn[i].equals("/")) && (pIn[i].equals("+") || pIn[i].equals("-") || pIn[i].equals("*"))) {
                isTrue = true;
            } else {
                isTrue = false;
            }

        }
        return isTrue;
    }

    /*
     * returns the answer integer variable
     */
    public double getAnswer() {
        return answer;
    }

}

 

 

Edited by WarFox
Formatting errors
  • I Like This! 2
Link to comment
Share on other sites

This is very cool! I've actually never used JPanel or BorderLayout to make a GUI before and it looks like it's really convenient. I've just been doing them the same way that I learned like 7 years ago haha.

I'm curious why you chose to make certain variables "Doubles" instead of "doubles". I've just never done that, and it looks like it was a little cumbersome in a few spots. For example, in ParseCalculation.java:

private double parseStringToInt(String pIn) {
	Double in = Double.parseDouble(pIn);
	double unwrappedIn = in;
	return unwrappedIn;
}

Could have been:

private double parseStringToInt(String pIn) {
	return Double.parseDouble(pIn);
}

(it also returns a double when the comments above it say it's supposed to return an int)
Your way is not wrong, and my way might even be a bit less readable, but like I said, I've just never done it that way so I'm curious about it.

I'm also really confused about this part of ParseCalculation.java:

private boolean checkInputSyntax (String[] pIn) {
	boolean isTrue = true;
	for (int i = 0; i < pIn.length-1; i++) {
		if ((pIn[i].equals("+") || pIn[i].equals("-") || pIn[i].equals("*") || pIn[i].equals("/")) && (pIn[i].equals("+") || pIn[i].equals("-") || pIn[i].equals("*"))) {
			isTrue = true;
		} else {
			isTrue = false;
		}
	}
	return isTrue;
}

Now I haven't actually tried to run the program so I could be wrong, but from looking at this, it looks like it shouldn't work quite right. The if statement checks if pIn is equal to +,-,*, or / and if it's equal to +,-, or *.  That would return true for +,-, or * but false for /. Additionally I don't see how this compares two positions in a row. It seems that your "isTrue" value will be equal to whatever it is assigned on the last iteration of the for loop. 
(You might have intended for the part after && to be pIn[i-1] but then isTrue should be assigned false not true, and that would only check if there were two operations in a row at the very end of the string. If you changed it to be pIn[i-1] after the &&, added pIn[i-1].equals("/") in that part , started i at 1 instead of 0, switched the assignments of isTrue to be the opposite, and added a break statement in the if section, then it would work [and the else section would be redundant] ).

For example, if the input was "2 + 4" then the last thing it looks at will be the "+" and return true which is good.
But if your input was "2 + + 4" then from what I'm seeing it should still return true because the last thing it checks is "+" which returns true.
Finally, if your input was "4 / 2" then I think it would return false because it fulfills the first half of the if condition (before &&) but not the second.

An easier way to prevent syntax errors from the user might be to check for them in the actionPerformed part of the GUI instead. I can think of 2 ways you could do it easily. First, if someone presses an operation, you could convert input to an array by splitting for " " and seeing if the last thing that was entered was an operation or a number, and don't allow the user to do two operations in a row. That would be pretty inefficient though, so the better way than that would probably be to have a boolean variable called lastWasOperation or something, and change it each time a button is pressed (to 0 if they click a number and 1 if they click an operation). Then if they press "+" and lastWasOperation == 1 then you simply don't add on another +. This would be especially nice because if someone keys in a long statement but accidentally messed up the syntax, it would be frustrating to get an error and need to enter it again. If instead they were prevented from making the mistake in the first place, they wouldn't have that problem.

 

I also don't see how it accounts for order of operations. The method "parseString" seems to just evaluate left to right. The input "1 + 2 * 2" looks like it would be evaluated as 6 instead of 5 because it would start by adding 1+2 then doing 3*2. It evaluates things as if they're in nested parenthesis. So "1 + 2 * 2" is evaluated as "(((1)+2)*3) which is a problem for something like "1+1+1+1*2" because it'll be evaluated as 4*2 = 8 instead of 3+2 = 5

Edited by Freak
Half of my post was randomly italicized after I clicked submit :/
Link to comment
Share on other sites

Ill to a more indepth reply later. I used Doubles because it would be easier to error handle in the event the user just typed in a character.

 

Yea, so by using the wrapper class of Double rather than the primitive double, one can error handle, or prevent the user from breaking the program. With using the primitive, if a user typed into the bar (this program does not prevent the user from typing whatever into the bar), it would error out. But by using the wrapper class of Double, I don't know if I fully implemented it here (I wrote this after a class months ago then got busy with math), you can take advantage of methods like parseDouble() which from there you can handle if someone tried to input 'a' a lot easier. I'm not sure if that makes sense.

 

Also it reads the input calculation as an array of strings, it was just easier to use wrapper classes to go from strings to Double then to primitives.

 

The checksyntax method was never completed on this version.

Edited by WarFox
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...