- This is basic calcuator desktop application developed in java 17.
- It has the basic functions like
addition,subtraction,multiplication,divisionandsquare root. - and The UI design is inspired from Iphone Calculator.
for documentation i used AI to generate the md code but the content is mine.
A desktop calculator built with Java Swing/AWT, supporting both mouse clicks and full keyboard input.
| Aspect | Detail |
|---|---|
| UI Toolkit | javax.swing.* (components) + java.awt.* (layout, events) |
| Entry point | Calculator class, instantiated from App.java |
| Input methods | Mouse clicks (ActionListener) and keyboard (KeyListener) |
| Operations | +, -, *, /, %, +/-, √, decimal, backspace, clear |
| Display style | Live expression view — e.g. typing 10 + 10 shows exactly that before = collapses it to 20 |
classDiagram
class Calculator {
-JFrame frame
-JLabel displayLabel
-JPanel displayPanel
-JPanel buttonsPanel
-String A
-String currentInput
-String operator
-boolean waitingForSecondOperand
-boolean resultDisplayed
+Calculator()
-clearAll() void
-refreshDisplay() void
-calculateAndShowResult() void
-removeZeroDecimal(double) String
-handleInput(String) void
-handleBackspace() void
+keyPressed(KeyEvent) void
+keyReleased(KeyEvent) void
+keyTyped(KeyEvent) void
}
Calculator ..|> KeyListener : implements
Why implements KeyListener?
This lets Calculator itself act as its own keyboard event handler. The class must then provide bodies for all three interface methods (keyPressed, keyReleased, keyTyped) — only keyPressed does real work here, the other two are required but empty.
This shows the actual Swing container tree — what's nested inside what, and which layout manager governs each level.
flowchart TD
Frame["JFrame 'frame'<br/>BorderLayout<br/>360x540"]
Frame -->|BorderLayout.NORTH| DisplayPanel["JPanel 'displayPanel'<br/>BorderLayout"]
Frame -->|BorderLayout.CENTER default| ButtonsPanel["JPanel 'buttonsPanel'<br/>GridLayout 5x4<br/>black background"]
DisplayPanel --> DisplayLabel["JLabel 'displayLabel'<br/>right-aligned, white text<br/>Font 80pt<br/>shows current expression"]
ButtonsPanel --> B1["JButton AC"]
ButtonsPanel --> B2["JButton +/-"]
ButtonsPanel --> B3["JButton %"]
ButtonsPanel --> B4["JButton /"]
ButtonsPanel --> B5["JButton 7"]
ButtonsPanel --> B6["JButton 8"]
ButtonsPanel --> B7["... 12 more buttons"]
ButtonsPanel --> B8["JButton ="]
style Frame fill:#1c1c1c,color:#fff
style DisplayPanel fill:#333,color:#fff
style ButtonsPanel fill:#333,color:#fff
style DisplayLabel fill:#000,color:#fff
How the layout actually renders:
flowchart TD
subgraph JFrame["JFrame (BorderLayout)"]
direction TB
subgraph North["NORTH region"]
direction TB
subgraph DP["JPanel: displayPanel"]
DL["JLabel: displayLabel<br/>'10 + 10'"]
end
end
subgraph Center["CENTER region"]
direction TB
subgraph BP["JPanel: buttonsPanel (GridLayout 5x4)"]
direction TB
Row1["AC | +/- | % | /"]
Row2["7 | 8 | 9 | x"]
Row3["4 | 5 | 6 | -"]
Row4["1 | 2 | 3 | +"]
Row5["0 | . | √ | ="]
Row1 ~~~ Row2 ~~~ Row3 ~~~ Row4 ~~~ Row5
end
end
North --> Center
end
Why BorderLayout on the frame, but GridLayout on the buttons panel?
frame.setLayout(new BorderLayout())divides the window into up to 5 regions (NORTH,SOUTH,EAST,WEST,CENTER). OnlyNORTH(the display) and the defaultCENTER(the buttons panel) are used here —displayPanelis pinned to the top and stays a fixed height, whilebuttonsPanelfills all remaining space.buttonsPanel.setLayout(new GridLayout(5, 4))then arranges its own children (the 20 buttons) into a strict 5-row, 4-column grid, all cells equal size — which is why the buttons form a clean uniform keypad.
This is a common and correct Swing pattern: nest a simple layout inside a region of a more flexible outer layout, rather than trying to make one layout manager do everything.
| Variable | Meaning |
|---|---|
A |
The first operand, captured the moment an operator is pressed |
currentInput |
Whatever number is currently being typed (first operand before an operator, or second operand after) |
operator |
The pending operation (+, -, *, /), or null if none chosen yet |
waitingForSecondOperand |
true right after an operator is pressed, before the user types any digit for the second number |
resultDisplayed |
true right after = — lets the next digit typed start a fresh calculation instead of appending to the old result |
flowchart TD
A[Constructor starts] --> B[Configure frame: size, icon, layout]
B --> C[Build displayLabel + displayPanel]
C --> D[Build buttonsPanel with GridLayout 5x4]
D --> E[Loop through buttonValues array]
E --> F[Create JButton, set color by category]
F --> G[Add ActionListener → calls handleInput]
G --> H{More buttons?}
H -- yes --> E
H -- no --> I[frame.setVisible true]
I --> J[frame.requestFocusInWindow]
J --> K[frame.addKeyListener this]
K --> L[Calculator ready]
Order matters here — setVisible(true) must come after every button is added, otherwise the window can render partially (a bug this project hit earlier). Similarly, addKeyListener(this) must be called exactly once, not inside the button loop, or every keypress fires multiple times.
flowchart TD
Start[For each buttonValue] --> Check1{In topSymbols?<br/>AC, +/-, %}
Check1 -- yes --> Light[Light gray background<br/>black text]
Check1 -- no --> Check2{In rightSymbols?<br/>+ - * / =}
Check2 -- yes --> Orange[Orange background<br/>white text]
Check2 -- no --> Dark[Dark gray background<br/>white text]
This single method receives a String value (either from a button's text or from a keyboard mapping) and routes it based on what kind of input it is.
flowchart TD
Start["handleInput(value)"] --> Q1{value is +,-,*,/,=?}
Q1 -- yes --> Q2{value == '='?}
Q2 -- yes --> Q3{operator and A set?}
Q3 -- yes --> Calc[calculateAndShowResult]
Q3 -- no --> Ignore1[do nothing]
Q2 -- no --> Q4{operator == null?}
Q4 -- yes --> SetFirst[A = currentInput<br/>operator = value<br/>waitingForSecondOperand = true]
Q4 -- no --> Q5{waitingForSecondOperand?}
Q5 -- yes --> SwapOp[operator = value<br/>just changes the pending operator]
Q5 -- no --> ChainCalc[calculateAndShowResult<br/>then start new operation<br/>with previous result as A]
Q1 -- no --> Q6{value is AC, +/-, or %?}
Q6 -- yes --> TopOps[Clear / negate / divide by 100]
Q6 -- no --> Q7{value == '√'?}
Q7 -- yes --> Sqrt[Square root of currentInput]
Q7 -- no --> Digits[Digit or decimal point<br/>appended to currentInput]
SetFirst --> Refresh[refreshDisplay]
SwapOp --> Refresh
ChainCalc --> Refresh
TopOps --> Refresh
Sqrt --> Refresh
Digits --> Refresh
Calc --> End[Done]
Refresh --> End
Typing 10, pressing +, typing 10, pressing =:
sequenceDiagram
participant User
participant handleInput
participant State as A / operator / currentInput
participant Display as displayLabel
User->>handleInput: "1"
handleInput->>State: currentInput = "1"
handleInput->>Display: refreshDisplay → "1"
User->>handleInput: "0"
handleInput->>State: currentInput = "10"
handleInput->>Display: refreshDisplay → "10"
User->>handleInput: "+"
handleInput->>State: A = "10", operator = "+"<br/>waitingForSecondOperand = true
handleInput->>Display: refreshDisplay → "10 +"
User->>handleInput: "1"
handleInput->>State: currentInput = "1"<br/>waitingForSecondOperand = false
handleInput->>Display: refreshDisplay → "10 + 1"
User->>handleInput: "0"
handleInput->>State: currentInput = "10"
handleInput->>Display: refreshDisplay → "10 + 10"
User->>handleInput: "="
handleInput->>State: calculateAndShowResult → A = "20"<br/>operator = null, resultDisplayed = true
handleInput->>Display: displayLabel.setText → "20"
This is handled by refreshDisplay(), which checks three states:
flowchart TD
R[refreshDisplay called] --> C1{operator == null?}
C1 -- yes --> Show1[Show currentInput only<br/>e.g. '10']
C1 -- no --> C2{waitingForSecondOperand?}
C2 -- yes --> Show2[Show 'A operator'<br/>e.g. '10 +']
C2 -- no --> Show3[Show 'A operator currentInput'<br/>e.g. '10 + 10']
flowchart TD
Key[Key pressed] --> Shift{Shift+5?}
Shift -- yes --> Percent[handleInput '%'] --> Stop[return]
Shift -- no --> Switch{Switch on getKeyCode}
Switch -- Backspace --> BS[handleBackspace]
Switch -- Enter --> Eq[handleInput '=']
Switch -- Escape --> AC[handleInput 'AC']
Switch -- Numpad +/-/*// --> Ops[handleInput operator]
Switch -- F1 --> Sqrt[handleInput '√']
Switch -- default --> Digit{isDigit or '.'?}
Digit -- yes --> DigitCall[handleInput char]
Digit -- no --> Nothing[ignored]
Why addKeyListener(this) is required: implementing the KeyListener interface only gives the class the methods — it does nothing until you register an actual object as a listener on a component. frame.addKeyListener(this) tells Swing "call these methods on this exact Calculator instance whenever the frame receives key events."
flowchart LR
Mouse[Mouse click on JButton] --> AL[ActionListener]
Keyboard[Key press] --> KL[keyPressed switch]
AL --> HI[handleInput value]
KL --> HI
HI --> RD[refreshDisplay / calculateAndShowResult]
RD --> UI[displayLabel updates]
Both input paths converge on the same handleInput() method — this is intentional and important: it guarantees mouse clicks and keyboard presses always produce identical behavior, since there's only one place the actual calculator logic lives.
- Dividing by zero (
10 / 0) producesInfinityon screen rather than a friendly error — not currently guarded. - Top-row
+/-/*//keys (not numpad) fall through to thedefaultcase and rely onCharacter.isDigit()filtering them out — they are not currently wired to trigger operators, only numpadVK_ADD/VK_SUBTRACT/VK_MULTIPLY/VK_DIVIDEare. √of a negative number setscurrentInputto"Error"but doesn't lock further input — typing digits afterward will append to"Error"rather than resetting cleanly.