001: /*
002: * @(#)Calculator.java 7/11/2006
003: *
004: * Copyright 2002 - 2006 JIDE Software Inc. All rights reserved.
005: */
006:
007: package com.jidesoft.swing;
008:
009: import com.jidesoft.utils.PortingUtils;
010:
011: import javax.swing.*;
012: import java.awt.*;
013: import java.awt.event.ActionEvent;
014: import java.awt.event.ActionListener;
015: import java.awt.event.KeyEvent;
016: import java.text.NumberFormat;
017:
018: /**
019: * <tt>Calculator</tt> is a component that can do simple arithmetic calculation. Since it
020: * extends JPanel, you can use it at any place in your application.
021: * <p/>
022: * To make it more flexible, the <tt>Calculator</tt> has no text field to display
023: * the result. You can create your own JTextField or JLabel to display the result.
024: * Here is a simple example to create a text field and associate it with Calculator.
025: * <pre><code>
026: * final JTextField textField = new JTextField();
027: * textField.setColumns(20);
028: * textField.setHorizontalAlignment(JTextField.TRAILING);
029: * Calculator calculator = new Calculator();
030: * calculator.registerKeyboardActions(textField, JComponent.WHEN_FOCUSED);
031: * calculator.addPropertyChangeListener(Calculator.PROPERTY_DISPLAY_TEXT, new PropertyChangeListener() {
032: * public void propertyChange(PropertyChangeEvent evt) {
033: * textField.setText("" + evt.getNewValue());
034: * }
035: * });
036: * calculator.clear();
037: * </code></pre>
038: * With the code above, user can type in directly into text field and do the calculation.
039: * If you just want to display the result and don't mind if the text field accepts keyboard input,
040: * you don't need to call registerKeyboardActions method.
041: * <p/>
042: * All numeric and operator keys work as expected. Here are a few special keys that worth mentioning
043: * <ul>
044: * <li> 'C', 'c' or ESC to clear current result
045: * <li> '!' to make current displayed number from positive to negative (or from negative to positive)
046: * <li> ENTER is equalivent to '='..
047: * </ul>
048: * <p/>
049: * Another interesting way to use Calculator is to use it without using GUI.
050: * <pre><code>
051: * Calculator calculator = new Calculator();
052: * calculator.input('1');
053: * calculator.input('0');
054: * calculator.input('*');
055: * calculator.input('2');
056: * calculator.input('4');
057: * calculator.input('=');
058: * System.out.println("10 * 24 = " + calculator.getDisplayText());
059: * </code></pre>
060: * The print out will be "10 * 24 = 240".
061: * <p/>
062: * There are seveal methods you can use to get internal state of the Calculator.
063: * <ul>
064: * <li> {@link #getDisplayText()}: to get the result that should be displayed. Please note, this method return a string.
065: * <li> {@link #getResult()}: to get the last calculated result. This method returns a double value.
066: * <li> {@link #getOperator()}: to get the current operator
067: * <li> {@link #isOverflow()}: to check if there is an overflow. Usually if you try to divide by zero, you will get an overflow.
068: * </ul>
069: */
070: public class Calculator extends JPanel implements ActionListener {
071:
072: private double _result;
073: private StringBuffer _op1;
074: private StringBuffer _op2;
075: private int _operator = OPERATOR_NONE;
076: private String _displayText;
077: private boolean _overflow = false;
078: private boolean _negationOp1 = true;
079: private boolean _backspaceOp1 = false;
080: private boolean _backspaceOp2 = false;
081: private boolean _clearOperatorPending = false;
082:
083: public final static int OPERATOR_NONE = -1;
084: public final static int OPERATOR_ADD = 0;
085: public final static int OPERATOR_MINUS = 1;
086: public final static int OPERATOR_MULTIPLY = 2;
087: public final static int OPERATOR_DIVIDE = 3;
088:
089: private AbstractButton _addButton;
090: private AbstractButton _minusButton;
091: private AbstractButton _multiplyButton;
092: private AbstractButton _divideButton;
093: private AbstractButton _pointButton;
094: private AbstractButton _equalButton;
095: private AbstractButton _backspaceButton;
096: private AbstractButton _clearButton;
097: private AbstractButton _negativeButton;
098: private AbstractButton[] _numberButtons;
099:
100: private NumberFormat _displayFormat;
101:
102: public static final char CHAR_CLEAR = 'c';
103: public static final char CHAR_POINT = '.';
104: public static final char CHAR_ADD = '+';
105: public static final char CHAR_MINUS = '-';
106: public static final char CHAR_MULTIPLY = '*';
107: public static final char CHAR_DIVIDE = '/';
108: public static final char CHAR_EQUAL = '=';
109: public static final char CHAR_NEGATIVE = '!';
110: public static final char CHAR_BACKSPACE = '<';
111: public static final char CHAR_0 = '0';
112: public static final char CHAR_1 = '1';
113: public static final char CHAR_2 = '2';
114: public static final char CHAR_3 = '3';
115: public static final char CHAR_4 = '4';
116: public static final char CHAR_5 = '5';
117: public static final char CHAR_6 = '6';
118: public static final char CHAR_7 = '7';
119: public static final char CHAR_8 = '8';
120: public static final char CHAR_9 = '9';
121:
122: public final static String PROPERTY_DISPLAY_TEXT = "displayText";
123: public final static String PROPERTY_OPERATOR = "operator";
124:
125: private int _buttonWidth = 24;
126: private int _buttonHeight = 24;
127: private int _buttonGap = 2;
128:
129: /**
130: * Creates a <code>Calculator</code>.
131: */
132: public Calculator() {
133: _op1 = new StringBuffer();
134: _op2 = new StringBuffer();
135: initComponents();
136: _displayFormat = NumberFormat.getNumberInstance();
137: configureNumberFormat();
138: registerKeyboardActions(this ,
139: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
140: }
141:
142: /**
143: * Configures the number format for displaying purpose.
144: */
145: protected void configureNumberFormat() {
146: _displayFormat.setMaximumFractionDigits(20);
147: _displayFormat.setMinimumFractionDigits(0);
148: _displayFormat.setGroupingUsed(false);
149: }
150:
151: /**
152: * Checks if the key event a valid key event that can be accepted by the Calculator.
153: *
154: * @param keyEvent the key event.
155: * @return true if it is a valid key event for the Calculator.
156: */
157: public static boolean isValidKeyEvent(KeyEvent keyEvent) {
158: char c = keyEvent.getKeyChar();
159: return (keyEvent.getModifiers() & ~KeyEvent.SHIFT_MASK) != 0 // if it has any modify, ignore it
160: || Character.isDigit(c)
161: || isOperator(keyEvent)
162: || isEnter(keyEvent)
163: || c == KeyEvent.VK_PERIOD
164: || c == CHAR_CLEAR
165: || Character.toLowerCase(c) == CHAR_CLEAR
166: || c == KeyEvent.VK_ESCAPE
167: || c == KeyEvent.VK_BACK_SPACE;
168: }
169:
170: /**
171: * Checks if the key event a key event for operators. In the other words, if it is {@link #CHAR_ADD},
172: * {@link #CHAR_MINUS}, {@link #CHAR_MULTIPLY} or {@link #CHAR_DIVIDE}, this method will return true.
173: *
174: * @param keyEvent the key event.
175: * @return true if it is a valid key event is an operator.
176: */
177: public static boolean isOperator(KeyEvent keyEvent) {
178: char c = keyEvent.getKeyChar();
179: return c == CHAR_ADD || c == CHAR_MINUS || c == CHAR_MULTIPLY
180: || c == CHAR_DIVIDE;
181: }
182:
183: /**
184: * Checks if the key event a key event for enter. In the other words, if it is {@link KeyEvent#VK_ENTER}, this method will return true.
185: *
186: * @param keyEvent the key event.
187: * @return true if it is a valid key event is an enter key.
188: */
189: public static boolean isEnter(KeyEvent keyEvent) {
190: char c = keyEvent.getKeyChar();
191: return c == KeyEvent.VK_ENTER;
192: }
193:
194: /**
195: * Registers necessary keyboard actions onto the component. Usually the component
196: * is a <code>JTextField</code>.
197: *
198: * @param component the component where the key input will be taken and passed to the <code>Calculator</code>.
199: * @param condition the condition as defined in
200: * {@link JComponent#registerKeyboardAction(java.awt.event.ActionListener,javax.swing.KeyStroke,int)}.
201: */
202: public void registerKeyboardActions(JComponent component,
203: int condition) {
204: boolean isCellEditor = isCellEditor();
205: component.registerKeyboardAction(this , "" + CHAR_ADD, KeyStroke
206: .getKeyStroke(CHAR_ADD), condition);
207: component.registerKeyboardAction(this , "" + CHAR_MINUS,
208: KeyStroke.getKeyStroke(CHAR_MINUS), condition);
209: component.registerKeyboardAction(this , "" + CHAR_MULTIPLY,
210: KeyStroke.getKeyStroke(CHAR_MULTIPLY), condition);
211: component.registerKeyboardAction(this , "" + CHAR_DIVIDE,
212: KeyStroke.getKeyStroke(CHAR_DIVIDE), condition);
213: component.registerKeyboardAction(this , "" + CHAR_EQUAL,
214: KeyStroke.getKeyStroke(CHAR_EQUAL), condition);
215: if (!isCellEditor)
216: component.registerKeyboardAction(this , "" + CHAR_EQUAL,
217: KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
218: condition);
219: component.registerKeyboardAction(this , "" + CHAR_0, KeyStroke
220: .getKeyStroke(CHAR_0), condition);
221: component.registerKeyboardAction(this , "" + CHAR_1, KeyStroke
222: .getKeyStroke(CHAR_1), condition);
223: component.registerKeyboardAction(this , "" + CHAR_2, KeyStroke
224: .getKeyStroke(CHAR_2), condition);
225: component.registerKeyboardAction(this , "" + CHAR_3, KeyStroke
226: .getKeyStroke(CHAR_3), condition);
227: component.registerKeyboardAction(this , "" + CHAR_4, KeyStroke
228: .getKeyStroke(CHAR_4), condition);
229: component.registerKeyboardAction(this , "" + CHAR_5, KeyStroke
230: .getKeyStroke(CHAR_5), condition);
231: component.registerKeyboardAction(this , "" + CHAR_6, KeyStroke
232: .getKeyStroke(CHAR_6), condition);
233: component.registerKeyboardAction(this , "" + CHAR_7, KeyStroke
234: .getKeyStroke(CHAR_7), condition);
235: component.registerKeyboardAction(this , "" + CHAR_8, KeyStroke
236: .getKeyStroke(CHAR_8), condition);
237: component.registerKeyboardAction(this , "" + CHAR_9, KeyStroke
238: .getKeyStroke(CHAR_9), condition);
239: component.registerKeyboardAction(this , "" + CHAR_POINT,
240: KeyStroke.getKeyStroke(CHAR_POINT), condition);
241: component.registerKeyboardAction(this , "" + CHAR_BACKSPACE,
242: KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0),
243: condition);
244: if (!isCellEditor)
245: component.registerKeyboardAction(this , "" + CHAR_CLEAR,
246: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
247: condition);
248: if (!isCellEditor)
249: component.registerKeyboardAction(this , "" + CHAR_CLEAR,
250: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
251: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
252: component.registerKeyboardAction(this , "" + CHAR_CLEAR,
253: KeyStroke.getKeyStroke(Character
254: .toUpperCase(CHAR_CLEAR)), condition);
255: component.registerKeyboardAction(this , "" + CHAR_CLEAR,
256: KeyStroke.getKeyStroke(Character
257: .toLowerCase(CHAR_CLEAR)), condition);
258: }
259:
260: /**
261: * Unregisters the keyboard actions you registered using {@link #registerKeyboardActions(javax.swing.JComponent,int)}.
262: *
263: * @param component the component.
264: */
265: public void unregisterKeyboardActions(JComponent component) {
266: boolean isCellEditor = isCellEditor();
267: component.unregisterKeyboardAction(KeyStroke
268: .getKeyStroke(CHAR_ADD));
269: component.unregisterKeyboardAction(KeyStroke
270: .getKeyStroke(CHAR_MINUS));
271: component.unregisterKeyboardAction(KeyStroke
272: .getKeyStroke(CHAR_MULTIPLY));
273: component.unregisterKeyboardAction(KeyStroke
274: .getKeyStroke(CHAR_DIVIDE));
275: component.unregisterKeyboardAction(KeyStroke
276: .getKeyStroke(CHAR_EQUAL));
277: if (!isCellEditor)
278: component.unregisterKeyboardAction(KeyStroke.getKeyStroke(
279: KeyEvent.VK_ENTER, 0));
280: component.unregisterKeyboardAction(KeyStroke
281: .getKeyStroke(CHAR_0));
282: component.unregisterKeyboardAction(KeyStroke
283: .getKeyStroke(CHAR_1));
284: component.unregisterKeyboardAction(KeyStroke
285: .getKeyStroke(CHAR_2));
286: component.unregisterKeyboardAction(KeyStroke
287: .getKeyStroke(CHAR_3));
288: component.unregisterKeyboardAction(KeyStroke
289: .getKeyStroke(CHAR_4));
290: component.unregisterKeyboardAction(KeyStroke
291: .getKeyStroke(CHAR_5));
292: component.unregisterKeyboardAction(KeyStroke
293: .getKeyStroke(CHAR_6));
294: component.unregisterKeyboardAction(KeyStroke
295: .getKeyStroke(CHAR_7));
296: component.unregisterKeyboardAction(KeyStroke
297: .getKeyStroke(CHAR_8));
298: component.unregisterKeyboardAction(KeyStroke
299: .getKeyStroke(CHAR_9));
300: component.unregisterKeyboardAction(KeyStroke
301: .getKeyStroke(CHAR_POINT));
302: component.unregisterKeyboardAction(KeyStroke.getKeyStroke(
303: KeyEvent.VK_BACK_SPACE, 0));
304: if (!isCellEditor)
305: component.unregisterKeyboardAction(KeyStroke.getKeyStroke(
306: KeyEvent.VK_ESCAPE, 0));
307: component.unregisterKeyboardAction(KeyStroke
308: .getKeyStroke(Character.toUpperCase(CHAR_CLEAR)));
309: component.unregisterKeyboardAction(KeyStroke
310: .getKeyStroke(Character.toLowerCase(CHAR_CLEAR)));
311: }
312:
313: protected void initComponents() {
314: setLayout(new CalculatorLayoutManager());
315: add(_addButton = createButton("+"));
316: add(_minusButton = createButton("-"));
317: add(_multiplyButton = createButton("*"));
318: add(_divideButton = createButton("/"));
319: _numberButtons = new AbstractButton[10];
320: for (int i = 0; i <= 9; i++) {
321: add(_numberButtons[i] = createButton("" + i));
322: }
323: add(_pointButton = createButton("."));
324: add(_equalButton = createButton("="));
325: add(_backspaceButton = createButton(null, new BackspaceIcon()));
326: add(_negativeButton = createButton(null,
327: new ToggleNegativeIcon()));
328: add(_clearButton = createButton("C"));
329: }
330:
331: class BackspaceIcon implements Icon {
332: public BackspaceIcon() {
333: }
334:
335: public void paintIcon(Component c, Graphics g, int x, int y) {
336: Object save = JideSwingUtilities.setupShapeAntialiasing(g);
337: Color old = g.getColor();
338: g.setColor(c.getForeground());
339: g.drawLine(x, y + 3, x + 3, y);
340: g.drawLine(x, y + 3, x + 3, y + 6);
341: g.drawLine(x + 3, y + 3, x + 7, y + 3);
342: g.setColor(old);
343: JideSwingUtilities.restoreShapeAntialiasing(g, save);
344: }
345:
346: public int getIconWidth() {
347: return 7;
348: }
349:
350: public int getIconHeight() {
351: return 7;
352: }
353: }
354:
355: class ToggleNegativeIcon implements Icon {
356: public ToggleNegativeIcon() {
357: }
358:
359: public void paintIcon(Component c, Graphics g, int x, int y) {
360: Color old = g.getColor();
361: Object save = JideSwingUtilities.setupShapeAntialiasing(g);
362: g.setColor(c.getForeground());
363: g.drawLine(x, y + 2, x + 6, y + 2);
364: g.drawLine(x, y + 7, x + 6, y + 7);
365: g.drawLine(x + 3, y, x + 3, y + 5);
366: g.setColor(old);
367: JideSwingUtilities.restoreShapeAntialiasing(g, save);
368: }
369:
370: public int getIconWidth() {
371: return 7;
372: }
373:
374: public int getIconHeight() {
375: return 7;
376: }
377: }
378:
379: /**
380: * Creates the button that is used in the Calculator. By default, it will create a JideButton. Here is the code. You can override it
381: * to create your own button. This method is used to create all buttons except the backspace and the +/- button. So if you want
382: * to override it, it's better to override {@link #createButton(String,javax.swing.Icon)} method.
383: *
384: * @param text the text on the button.
385: * @return the button.
386: */
387: protected AbstractButton createButton(String text) {
388: return createButton(text, null);
389: }
390:
391: /**
392: * Creates the button that is used in the Calculator. By default, it will create a JideButton. Here is the code. You can override it
393: * to create your own button.
394: * <pre><code>
395: * AbstractButton button = new JideButton(text, icon);
396: * button.setOpaque(true);
397: * button.setContentAreaFilled(true);
398: * button.setRequestFocusEnabled(false);
399: * button.setFocusable(false);
400: * button.addActionListener(this);
401: * return button;
402: * </code></pre>
403: *
404: * @param text the text on the button.
405: * @param icon the icon on the button.
406: * @return the button.
407: */
408: protected AbstractButton createButton(String text, Icon icon) {
409: AbstractButton button = new JideButton(text, icon);
410: button.setOpaque(true);
411: button.setContentAreaFilled(true);
412: button.setRequestFocusEnabled(false);
413: button.setFocusable(false);
414: button.addActionListener(this );
415: return button;
416: }
417:
418: /**
419: * Checks if the calculator is in overflow state.
420: *
421: * @return true if overflow.
422: */
423: public boolean isOverflow() {
424: return _overflow;
425: }
426:
427: /**
428: * Sets the overflow flag.
429: *
430: * @param overflow the overflow flag.
431: */
432: public void setOverflow(boolean overflow) {
433: _overflow = overflow;
434: }
435:
436: /**
437: * Inputs a char to the calculator. Please note, not all chars are acceptable. Valid chars are defined in {@link Calculator} class
438: * as CHAR_XXX constants.
439: *
440: * @param c the char inputed char.
441: */
442: public void input(char c) {
443: if (CHAR_CLEAR == Character.toLowerCase(c)
444: || CHAR_CLEAR == Character.toUpperCase(c)) {
445: clear();
446: return;
447: }
448:
449: if (_overflow) {
450: beep();
451: return;
452: }
453:
454: if (Character.isDigit(c) || CHAR_POINT == c) {
455: if (_clearOperatorPending) {
456: setOperator(OPERATOR_NONE);
457: _op1.setLength(0);
458: _clearOperatorPending = false;
459: }
460: if (getOperator() == -1) {
461: if (CHAR_POINT != c
462: || _op1.indexOf("" + CHAR_POINT) == -1) {
463: _op1.append(c);
464: _backspaceOp1 = true;
465: _backspaceOp2 = false;
466: setDisplayText(_op1.toString());
467: } else {
468: beep();
469: }
470: } else {
471: if (CHAR_POINT != c
472: || _op2.indexOf("" + CHAR_POINT) == -1) {
473: _op2.append(c);
474: _backspaceOp2 = true;
475: _backspaceOp1 = false;
476: setDisplayText(_op2.toString());
477: } else {
478: beep();
479: }
480: }
481: } else {
482: switch (c) {
483: case CHAR_ADD:
484: _op2.setLength(0);
485: calculateResult(false);
486: setOperator(OPERATOR_ADD);
487: _negationOp1 = false;
488: _clearOperatorPending = false;
489: break;
490: case CHAR_MINUS:
491: _op2.setLength(0);
492: calculateResult(false);
493: setOperator(OPERATOR_MINUS);
494: _negationOp1 = false;
495: _clearOperatorPending = false;
496: break;
497: case CHAR_MULTIPLY:
498: _op2.setLength(0);
499: calculateResult(false);
500: setOperator(OPERATOR_MULTIPLY);
501: _negationOp1 = false;
502: _clearOperatorPending = false;
503: break;
504: case CHAR_DIVIDE:
505: _op2.setLength(0);
506: calculateResult(false);
507: setOperator(OPERATOR_DIVIDE);
508: _negationOp1 = false;
509: _clearOperatorPending = false;
510: break;
511: case CHAR_EQUAL:
512: calculateResult(true);
513: _clearOperatorPending = true;
514: break;
515: case CHAR_NEGATIVE:
516: if (_negationOp1) {
517: negativePressed(_op1);
518: setDisplayText(_op1.toString());
519: } else {
520: negativePressed(_op2);
521: setDisplayText(_op2.toString());
522: }
523: break;
524: case CHAR_BACKSPACE:
525: if (_backspaceOp1) {
526: backspacePressed(_op1);
527: setDisplayText(_op1.toString());
528: } else if (_backspaceOp2) {
529: backspacePressed(_op2);
530: setDisplayText(_op2.toString());
531: } else {
532: beep();
533: }
534: break;
535: }
536: }
537: }
538:
539: protected void beep() {
540: PortingUtils.notifyUser();
541: }
542:
543: private void negativePressed(StringBuffer buf) {
544: if (buf.length() == 0) {
545: return;
546: }
547: if (buf.charAt(0) == CHAR_MINUS) {
548: buf.deleteCharAt(0);
549: } else {
550: buf.insert(0, CHAR_MINUS);
551: }
552: }
553:
554: private void backspacePressed(StringBuffer buf) {
555: if (buf.length() == 0) {
556: return;
557: }
558: buf.deleteCharAt(buf.length() - 1);
559: }
560:
561: private void calculateResult(boolean equalPressed) {
562: if (getOperator() == -1) {
563: return;
564: }
565:
566: if (_op1.length() == 0) {
567: beep();
568: return;
569: }
570:
571: if (equalPressed) {
572: if (_op2.length() == 0) {
573: _op2.append(_op1);
574: }
575: } else if (_op2.length() == 0) {
576: return;
577: }
578: double op1 = Double.parseDouble(_op1.toString());
579: double op2 = Double.parseDouble(_op2.toString());
580: try {
581: switch (getOperator()) {
582: case OPERATOR_ADD:
583: _result = op1 + op2;
584: break;
585: case OPERATOR_MINUS:
586: _result = op1 - op2;
587: break;
588: case OPERATOR_MULTIPLY:
589: _result = op1 * op2;
590: break;
591: case OPERATOR_DIVIDE:
592: if (op2 == 0) {
593: _result = Double.NaN;
594: _overflow = true;
595: } else {
596: _result = op1 / op2;
597: }
598: break;
599: }
600: } catch (Exception e) {
601: _overflow = true;
602: }
603:
604: if (_overflow) {
605: setDisplayText("E");
606: } else {
607: _op1.setLength(0);
608: if (_displayFormat != null) {
609: String displayText = _displayFormat.format(_result);
610: setDisplayText(displayText);
611: } else {
612: setDisplayText("" + _result);
613: }
614: _op1.append(getDisplayText());
615: _negationOp1 = true;
616: _backspaceOp1 = true;
617: _backspaceOp2 = false;
618: }
619: }
620:
621: private void clearOps() {
622: setOperator(OPERATOR_NONE);
623: _op1.setLength(0);
624: _op2.setLength(0);
625: }
626:
627: /**
628: * Clears the internal state and reset the calculator.
629: */
630: public void clear() {
631: clearOps();
632: _overflow = false;
633: _clearOperatorPending = false;
634: setDisplayText("0");
635: }
636:
637: /**
638: * Gets the last calculated result.
639: *
640: * @return the last calculated result.
641: */
642: public double getResult() {
643: return _result;
644: }
645:
646: /**
647: * Gets the display text.
648: *
649: * @return the display text.
650: */
651: public String getDisplayText() {
652: return _displayText;
653: }
654:
655: /**
656: * Sets the display text and fire property change event on property named {@link #PROPERTY_DISPLAY_TEXT}.
657: *
658: * @param displayText the displayed text.
659: */
660: public void setDisplayText(String displayText) {
661: String old = _displayText;
662: _displayText = displayText;
663: firePropertyChange(PROPERTY_DISPLAY_TEXT, old, _displayText);
664: }
665:
666: /**
667: * Gets the current operator.
668: *
669: * @return the current operator.
670: */
671: public int getOperator() {
672: return _operator;
673: }
674:
675: /**
676: * Sets the operator and fire property change event on property named {@link #PROPERTY_OPERATOR}.
677: *
678: * @param operator the operator.
679: */
680: public void setOperator(int operator) {
681: int old = _operator;
682: if (old != operator) {
683: _operator = operator;
684: firePropertyChange(PROPERTY_OPERATOR, new Integer(old),
685: new Integer(operator));
686: }
687: }
688:
689: private class CalculatorLayoutManager implements LayoutManager {
690: public CalculatorLayoutManager() {
691: }
692:
693: public void addLayoutComponent(String name, Component comp) {
694:
695: }
696:
697: public void removeLayoutComponent(Component comp) {
698: }
699:
700: public Dimension preferredLayoutSize(Container parent) {
701: return minimumLayoutSize(parent);
702: }
703:
704: public Dimension minimumLayoutSize(Container parent) {
705: return new Dimension(getButtonWidth() * 4 + getButtonGap()
706: * 3, getButtonHeight() * 5 + getButtonGap() * 4);
707: }
708:
709: public void layoutContainer(Container parent) {
710: int x = 0;
711: int y = 0;
712:
713: int w = getButtonWidth();
714: int h = getButtonHeight();
715: int gap = getButtonGap();
716:
717: _numberButtons[7].setBounds(x, y, w, h);
718: x += w + gap;
719: _numberButtons[8].setBounds(x, y, w, h);
720: x += w + gap;
721: _numberButtons[9].setBounds(x, y, w, h);
722: x += w + gap;
723: _divideButton.setBounds(x, y, w, h);
724:
725: x = 0;
726: y += h + gap;
727:
728: _numberButtons[4].setBounds(x, y, w, h);
729: x += w + gap;
730: _numberButtons[5].setBounds(x, y, w, h);
731: x += w + gap;
732: _numberButtons[6].setBounds(x, y, w, h);
733: x += w + gap;
734: _multiplyButton.setBounds(x, y, w, h);
735:
736: x = 0;
737: y += h + gap;
738:
739: _numberButtons[1].setBounds(x, y, w, h);
740: x += w + gap;
741: _numberButtons[2].setBounds(x, y, w, h);
742: x += w + gap;
743: _numberButtons[3].setBounds(x, y, w, h);
744: x += w + gap;
745: _minusButton.setBounds(x, y, w, h);
746:
747: x = 0;
748: y += h + gap;
749:
750: _numberButtons[0].setBounds(x, y, w, h);
751: x += w + gap;
752: _pointButton.setBounds(x, y, w, h);
753: x += w + gap;
754: _negativeButton.setBounds(x, y, w, h);
755: x += w + gap;
756: _addButton.setBounds(x, y, w, h);
757:
758: x = 0;
759: y += h + gap;
760:
761: _clearButton.setBounds(x, y, w, h);
762: x += w + gap;
763: _backspaceButton.setBounds(x, y, w, h);
764: x += w + gap;
765: _equalButton.setBounds(x, y, w * 2 + gap, h);
766: }
767: }
768:
769: public void actionPerformed(ActionEvent e) {
770: Object source = e.getSource();
771: if (_addButton == source) {
772: input(CHAR_ADD);
773: } else if (_minusButton == source) {
774: input(CHAR_MINUS);
775: } else if (_multiplyButton == source) {
776: input(CHAR_MULTIPLY);
777: } else if (_divideButton == source) {
778: input(CHAR_DIVIDE);
779: } else if (_equalButton == source) {
780: input(CHAR_EQUAL);
781: } else if (_pointButton == source) {
782: input(CHAR_POINT);
783: } else if (_negativeButton == source) {
784: input(CHAR_NEGATIVE);
785: } else if (_backspaceButton == source) {
786: input(CHAR_BACKSPACE);
787: } else if (_clearButton == source) {
788: input(CHAR_CLEAR);
789: } else {
790: boolean found = false;
791: for (int i = 0; i <= 9; i++) {
792: if (_numberButtons[i] == source) {
793: input(("" + i).charAt(0));
794: found = true;
795: break;
796: }
797: }
798: if (!found) {
799: if (e.getActionCommand() != null
800: && e.getActionCommand().length() > 0) {
801: fakePressButton(e.getActionCommand().charAt(0));
802: } else {
803: fakePressButton(CHAR_EQUAL);
804: }
805: }
806: }
807: }
808:
809: private final static int DELAY = 100;
810:
811: private void fakePressButton(char c) {
812: switch (c) {
813: case CHAR_CLEAR:
814: _clearButton.doClick(DELAY);
815: break;
816: case CHAR_BACKSPACE:
817: _backspaceButton.doClick(DELAY);
818: break;
819: case CHAR_EQUAL:
820: _equalButton.doClick(DELAY);
821: break;
822: case CHAR_POINT:
823: _pointButton.doClick(DELAY);
824: break;
825: case CHAR_NEGATIVE:
826: _negativeButton.doClick(DELAY);
827: break;
828: case CHAR_ADD:
829: _addButton.doClick(DELAY);
830: break;
831: case CHAR_MINUS:
832: _minusButton.doClick(DELAY);
833: break;
834: case CHAR_MULTIPLY:
835: _multiplyButton.doClick(DELAY);
836: break;
837: case CHAR_DIVIDE:
838: _divideButton.doClick(DELAY);
839: break;
840: case CHAR_0:
841: _numberButtons[0].doClick(DELAY);
842: break;
843: case CHAR_1:
844: _numberButtons[1].doClick(DELAY);
845: break;
846: case CHAR_2:
847: _numberButtons[2].doClick(DELAY);
848: break;
849: case CHAR_3:
850: _numberButtons[3].doClick(DELAY);
851: break;
852: case CHAR_4:
853: _numberButtons[4].doClick(DELAY);
854: break;
855: case CHAR_5:
856: _numberButtons[5].doClick(DELAY);
857: break;
858: case CHAR_6:
859: _numberButtons[6].doClick(DELAY);
860: break;
861: case CHAR_7:
862: _numberButtons[7].doClick(DELAY);
863: break;
864: case CHAR_8:
865: _numberButtons[8].doClick(DELAY);
866: break;
867: case CHAR_9:
868: _numberButtons[9].doClick(DELAY);
869: break;
870: }
871: }
872:
873: /**
874: * Gets the display format for the number.
875: *
876: * @return the display format for the number.
877: */
878: public NumberFormat getDisplayFormat() {
879: return _displayFormat;
880: }
881:
882: /**
883: * Sets the display format for the number.
884: *
885: * @param displayFormat the display format.
886: */
887: public void setDisplayFormat(NumberFormat displayFormat) {
888: _displayFormat = displayFormat;
889: }
890:
891: /**
892: * Calculates the pending calculation. If the Calculator has both operations
893: * and a valid operator, this method will do the calculation and set the display text and result.
894: */
895: public void commit() {
896: if (!_clearOperatorPending) {
897: input(CHAR_EQUAL);
898: }
899: }
900:
901: /**
902: * Gets the button width.
903: *
904: * @return the button width.
905: */
906: public int getButtonWidth() {
907: return _buttonWidth;
908: }
909:
910: /**
911: * Sets the button width.
912: *
913: * @param buttonWidth the new button width.
914: */
915: public void setButtonWidth(int buttonWidth) {
916: _buttonWidth = buttonWidth;
917: }
918:
919: /**
920: * Gets the button height.
921: *
922: * @return the button height.
923: */
924: public int getButtonHeight() {
925: return _buttonHeight;
926: }
927:
928: /**
929: * Sets the button height.
930: *
931: * @param buttonHeight the new button height.
932: */
933: public void setButtonHeight(int buttonHeight) {
934: _buttonHeight = buttonHeight;
935: }
936:
937: /**
938: * Gets the gap betwen buttons. Default is 2.
939: *
940: * @return the gap betwen buttons.
941: */
942: public int getButtonGap() {
943: return _buttonGap;
944: }
945:
946: public void setButtonGap(int buttonGap) {
947: _buttonGap = buttonGap;
948: }
949:
950: /**
951: * If this method return true, ENTER and ESCAPE key will be registered. Otherwise they will not be.
952: * The reason we do so because the two keys are conflicted with keys in JTable.
953: *
954: * @return true or false.
955: */
956: protected boolean isCellEditor() {
957: return false;
958: }
959:
960: public void setInitialValue(String value) {
961: _op1.setLength(0);
962: _op1.append(value);
963: _backspaceOp1 = true;
964: _backspaceOp2 = false;
965: setDisplayText(_op1.toString());
966: }
967:
968: public static void main(String[] args) {
969: Calculator calculator = new Calculator();
970: calculator.input('1');
971: calculator.input('0');
972: calculator.input('*');
973: calculator.input('2');
974: calculator.input('4');
975: calculator.input('=');
976: System.out.println("10 * 24 = " + calculator.getDisplayText());
977: }
978:
979: }
|