8
\$\begingroup\$

I am a beginner in Java and programming at all. I decide to try to write a calculator, as a my first "complete" program. It works for me, however I am curious is it worth anything in reality. I would be very grateful for any comments and advices. I am especially concerned about:

  1. usage of access modifiers - am I doing it correctly?
  2. code structure - is it make sense in this case, to base it on inner classes?
  3. readability of code
  4. length of code - is code not too long as for a calculator?

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.math.*;
import java.util.*;
import java.math.BigDecimal;
import java.io.*;

public class Kalkulator {
    JFrame frame;
    JPanel mainPanel;
    JPanel displayPanel;
    JPanel sidePanel;
    static JTextArea mainDisplay;
    // main display, holds only recently introduced value
    static JTextArea secDisplay;
    // secondary display, holds whole operation date, until use of "="
    static JTextArea sideTextArea;
    static JTextField sideTextField;

    private static String numValue;
    private static ArrayList<BigDecimal> memory; // working memory of calculator
    private static ArrayList<String> operation;
    private static BigDecimal result;
    private BigDecimal memoryStore; // function of memory, holds one value
    private static Integer prec;
    private static boolean root;
    ImageIcon img;

    JCheckBoxMenuItem wrap;
    JCheckBoxMenuItem history;

    public static void main(String[] args) {
        Kalkulator calc = new Kalkulator();
        calc.go();
    }

    public void go() {

        numValue = ""; // String with currently introduced values
        memory = new ArrayList<BigDecimal>();
        // holds String with values for operations
        operation = new ArrayList<String>(); // holds symbols of operations
        prec = 15; // decimal point accuracy,
        memoryStore = new BigDecimal(0); // BigDecimal keeps one value in memory
        root = false; // controls blocking of buttons

        /** GUI **/
        img = new ImageIcon(getClass().getResource("icon.png"));
        frame = new JFrame("Calculator");
        frame.setIconImage(img.getImage());
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainPanel = new JPanel();
        frame.getContentPane().add(BorderLayout.CENTER, mainPanel);

        mainPanel.setLayout(new GridLayout(6, 4));
        mainPanel.setBackground(Color.GRAY);

        ArrayList<JButton> jButtons = new ArrayList<JButton>();
        // simplify a configuration of buttons

        /* menu */
        JMenuBar menuBar = new JMenuBar();
        JMenu options = new JMenu("Options");
        JMenu help = new JMenu("Help");
        JMenuItem viewHelp = new JMenuItem("Help");
        viewHelp.addActionListener(new helpActList());
        JMenuItem info = new JMenuItem("Information");
        info.addActionListener(new infoActList());
        wrap = new JCheckBoxMenuItem("Wrap lines");
        wrap.addItemListener(new wrapList());
        history = new JCheckBoxMenuItem("Results history");
        history.addActionListener(new histList());
        JMenuItem precison = new JMenuItem("Accuracy");
        precison.addActionListener(new precList());

        menuBar.add(options);
        menuBar.add(help);
        options.add(wrap);
        options.add(history);
        options.add(precison);
        help.add(viewHelp);
        help.add(info);
        frame.setJMenuBar(menuBar);

        /* Fonts */
        Font mainDisplayFont = new Font("Ariala", Font.BOLD, 20);
        Font secDisplayFont = new Font("Ariala", Font.PLAIN, 10);
        Font buttonsFont = new Font("Ariala Black", Font.PLAIN, 20);
        /* wyświetlacze */
        mainDisplay = new JTextArea(2, 10);
        mainDisplay.setFont(mainDisplayFont);
        mainDisplay.setText("");
        mainDisplay.setEditable(false);
        JScrollPane scrollPane = new JScrollPane(mainDisplay);

        scrollPane
                .setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollPane
                .setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        secDisplay = new JTextArea(1, 20);
        secDisplay.setFont(secDisplayFont);
        secDisplay.setText(numValue);
        secDisplay.setEditable(false);

        displayPanel = new JPanel();
        displayPanel.setLayout(new BorderLayout());
        frame.getContentPane().add(BorderLayout.NORTH, displayPanel);
        displayPanel.add(BorderLayout.NORTH, secDisplay);
        displayPanel.add(BorderLayout.CENTER, scrollPane);
        displayPanel.setBorder(BorderFactory.createBevelBorder(1, Color.GRAY,
                Color.LIGHT_GRAY));

        /* buttons */
        JButton C = new JButton("C");
        C.addActionListener(new CActList());
        jButtons.add(C);

        JButton MC = new JButton("MC");
        MC.addActionListener(new MCActList());
        jButtons.add(MC);

        JButton MS = new JButton("MS");
        MS.addActionListener(new MSActList());
        jButtons.add(MS);

        JButton MR = new JButton("MR");
        MR.addActionListener(new MRActList());
        jButtons.add(MR);

        JButton percent = new JButton("%");
        percent.addActionListener(new percentActList());
        jButtons.add(percent);

        JButton rootSquere = new JButton("\u221A");
        rootSquere.addActionListener(new rootActList());
        jButtons.add(rootSquere);

        JButton nPower = new JButton("x" + "\u207F");
        nPower.addActionListener(new nActList());
        jButtons.add(nPower);

        JButton plusminus = new JButton("\u00B1");
        plusminus.addActionListener(new pmActList());
        jButtons.add(plusminus);

        JButton B7 = new JButton("7");
        B7.addActionListener(new B7ActList());
        jButtons.add(B7);

        JButton B8 = new JButton("8");
        B8.addActionListener(new B8ActList());
        jButtons.add(B8);

        JButton B9 = new JButton("9");
        B9.addActionListener(new B9ActList());
        jButtons.add(B9);

        JButton Bplus = new JButton("+");
        Bplus.addActionListener(new BplusActList());
        jButtons.add(Bplus);

        JButton B4 = new JButton("4");
        B4.addActionListener(new B4ActList());
        jButtons.add(B4);

        JButton B5 = new JButton("5");
        B5.addActionListener(new B5ActList());
        jButtons.add(B5);

        JButton B6 = new JButton("6");
        B6.addActionListener(new B6ActList());
        jButtons.add(B6);

        JButton Bmulti = new JButton("\u00D7");
        Bmulti.addActionListener(new BmultiActList());
        jButtons.add(Bmulti);

        JButton B1 = new JButton("1");
        B1.addActionListener(new B1ActList());
        jButtons.add(B1);

        JButton B2 = new JButton("2");
        B2.addActionListener(new B2ActList());
        jButtons.add(B2);

        JButton B3 = new JButton("3");
        B3.addActionListener(new B3ActList());
        jButtons.add(B3);

        JButton Bdiv = new JButton(":");
        Bdiv.addActionListener(new BdivActList());
        jButtons.add(Bdiv);

        JButton B0 = new JButton("0");
        B0.addActionListener(new B0ActList());
        jButtons.add(B0);

        JButton Bpoint = new JButton(".");
        Bpoint.addActionListener(new BpointActList());
        jButtons.add(Bpoint);

        JButton Bequal = new JButton("=");
        Bequal.addActionListener(new BequalActList());
        jButtons.add(Bequal);

        JButton Bminus = new JButton("-");
        Bminus.addActionListener(new BminusActList());
        jButtons.add(Bminus);

        for (JButton button : jButtons) {
            button.setFont(buttonsFont);
            button.setBorder(BorderFactory.createBevelBorder(1, Color.GRAY,
                    Color.LIGHT_GRAY));
            mainPanel.add(button);
        }

        /* Results history - holds results of former operations */

        sidePanel = new JPanel();
        JPanel sideTextPanel = new JPanel();
        sideTextPanel.setLayout(new BorderLayout());
        sidePanel.setBackground(Color.WHITE);
        sideTextField = new JTextField("M: ", 7);
        sideTextArea = new JTextArea(11, 7);
        sideTextField.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        sideTextField.setEditable(false);
        sideTextArea.setEditable(false);
        sideTextArea.setLineWrap(true);
        JScrollPane sideScroll = new JScrollPane(sideTextArea);
        sideScroll.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
        sideTextPanel.add(BorderLayout.NORTH, sideTextField);
        sideTextPanel.add(BorderLayout.CENTER, sideScroll);
        sideScroll
                .setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        sideScroll
                .setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
        sidePanel.add(sideTextPanel);
        frame.getContentPane().add(BorderLayout.EAST, sidePanel);
        sidePanel.setVisible(false);

        frame.setSize(250, 325);
        frame.setResizable(false);
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    static void numButton(String s) {
        // reaction for use of button: adding number to displays and memory
        numValue += s;
        mainDisplay.setText(numValue);
        secDisplay.append(s);
        root = false; // unblocking of buttons block
    }

    static void numOperation(String n, String s) {
        // controls operation on numbers, without math
        root = false; // n - number/symbol of operation (0=,1+,2-,3*,4/,5^,6%);
                        // s - string, displayed symbol
        if (operation.isEmpty()) {
            memory.add(new BigDecimal(numValue));
            secDisplay.append(s);
            numValue = "";
            operation.add(0, n);
            /* if user want to continue counting on result of former operations */
        } else if (operation.get(0).equals("0")) {
            memory.add(new BigDecimal(mainDisplay.getText()));
            secDisplay.setText(mainDisplay.getText());
            numValue = "";
            operation.add(0, n);
            secDisplay.append(s);
            //if user do more than one operation, without usage of "=" -
            //displays mid-result after every operation

        } else if ((!operation.isEmpty()) && (!operation.get(0).equals("0"))) {
            try {
                memory.add(new BigDecimal(numValue));
                numValue = "";
                result = opEqual(memory, operation);
                mainDisplay.setText(result.toString());
                memory.removeAll(memory);
                memory.add(new BigDecimal(mainDisplay.getText()));
                operation.add(0, n);
                secDisplay.setText("(" + secDisplay.getText() + ")" + s);
            } catch (NumberFormatException ex) {
                ex.printStackTrace();
                /* if user decide to change current operation - change symbol */
                if (!operation.get(0).equals(n)) {
                    operation.add(0, n);
                    String error = secDisplay.getText();
                    secDisplay.setText(error.substring(0, error.length() - 1)
                            + s);
                }
            }
        }
    }

    static void block() {
        // if buttons are blocked(there is final value on display) use of next
        // button cause a display reset
        if (root) {
            secDisplay.setText("");
            mainDisplay.setText("");
            numValue = "";
        }
    }

    static BigDecimal opEqual(ArrayList<BigDecimal> list, ArrayList<String> s) {
        // operations on numbers
        BigDecimal temp = new BigDecimal(0);
        int op = Integer.parseInt(s.get(0));
        switch (op) {
        case 1:
            temp = list.get(0).add(list.get(1));
            break;
        case 2:
            temp = list.get(0).subtract(list.get(1));
            break;
        case 3:
            temp = list.get(0).multiply(list.get(1));
            break;
        case 4:
            temp = list.get(0)
                    .divide(list.get(1), prec, RoundingMode.HALF_DOWN)
                    .stripTrailingZeros();
            break;
        case 5:
            temp = list.get(0).pow(list.get(1).intValue());
            break;
        case 6:
            temp = list.get(0).divide(new BigDecimal("100"))
                    .multiply(list.get(1));
            break;
        }
        return temp;
    }

    /* buttons listeners, every in separated inner class */
    class B9ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("9");
        }
    }

    class B8ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("8");
        }
    }

    class B7ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("7");
        }
    }

    class B6ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("6");
        }
    }

    class B5ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("5");
        }
    }

    class B4ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("4");
        }
    }

    class B3ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("3");
        }
    }

    class B2ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("2");
        }
    }

    class B1ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("1");
        }
    }

    class B0ActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            Kalkulator.numButton("0");
        }
    }

    class BpointActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            block();
            boolean point = false;
            for (int i = numValue.length() - 1; i >= 0; i--) {
                if (numValue.substring(i, i + 1).equals(".")) {
                    point = true;
                }
            }
            if (numValue.equals("")) {
                Kalkulator.numButton("0.");
            } else if (point) {

            } else {
                Kalkulator.numButton(".");
            }
        }
    }

    class BequalActList implements ActionListener { // useage of "="
        public void actionPerformed(ActionEvent event) {
            root = false;
            if (numValue.equals("0") && operation.get(0).equals("4")) {
                // attempt to divide by"O"
                secDisplay.setText("Error:can not divide by \"0\"");
                operation.removeAll(operation);
                memory.removeAll(memory);
                numValue = "";
                root = true;
            } else if (operation.isEmpty() || operation.get(0).equals("0")) {
                // unnecessary use of "=" button
                sideTextArea.append("\u00BB" + mainDisplay.getText() + "\n");
                // send display content to history

            } else {
                try { // standard "=" operation
                    memory.add(new BigDecimal(numValue));
                    numValue = "";
                    result = opEqual(memory, operation);
                    mainDisplay.setText(result
                            .setScale(prec, RoundingMode.HALF_DOWN)
                            .stripTrailingZeros().toPlainString());

                    operation.add(0, "0");
                    memory.removeAll(memory);
                    secDisplay.setText("");
                    sideTextArea
                            .append("\u00BB" + mainDisplay.getText() + "\n");

                    // if user will try to get result, after unnecessary usage
                    // of operation button(+,-,etc.

                } catch (Exception ex) {
                    sideTextArea
                            .append("\u00BB" + mainDisplay.getText() + "\n");
                    if (ex instanceof NumberFormatException) {
                        memory.removeAll(memory);
                        operation.removeAll(operation);
                        numValue = mainDisplay.getText();
                        secDisplay.setText(numValue);
                    } else if (ex instanceof IndexOutOfBoundsException) {
                        memory.removeAll(memory);
                        operation.removeAll(operation);
                        numValue = mainDisplay.getText();
                    }
                }
            }
        }
    }

    class BplusActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            Kalkulator.numOperation("1", "+");
        }
    }

    class BminusActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            Kalkulator.numOperation("2", "-");
        }
    }

    class BmultiActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            Kalkulator.numOperation("3", "\u00D7");
        }
    }

    class BdivActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            Kalkulator.numOperation("4", ":");
        }
    }

    class nActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            Kalkulator.numOperation("5", "^");
        }
    }

    class percentActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            Kalkulator.numOperation("6", "%");
        }
    }

    /**
     * Root Listener is different form other operations, it does not use a
     * opEqual method, it counts by itself. The result of root operation is not
     * counted as normal result of operation, to send it to history, it is
     * necessary to additional use of "=".
     */
    class rootActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            root = false;
            if ((!operation.isEmpty()) && operation.get(0).equals("0")) {
                // if user want to use root on result of former operations
                secDisplay.setText(mainDisplay.getText());
            }
            String sqrt = secDisplay.getText();
            try {
                BigDecimal squareRoot = new BigDecimal(Math.sqrt(Double
                        .parseDouble(mainDisplay.getText()))).setScale(prec,
                        RoundingMode.HALF_DOWN);

                for (int i = sqrt.length() - 1; i >= 0; i--) {
                    char c = sqrt.charAt(i);
                    // root on already rooted number
                    if ((!Character.isDigit(c) && sqrt.substring(i, i + 1)
                            .equals("\u221A"))) {
                        // to avoid root symbol doubling
                        sqrt = sqrt.substring(0, i + 1) + mainDisplay.getText();
                        break;
                    } else if (!(Character.isDigit(c) || c == '.' || sqrt
                            .substring(i, i + 1).equals("\u221A"))) {
                        sqrt = sqrt.substring(0, i + 1) + "\u221A"
                                + sqrt.substring(i + 1);
                        break;
                    } else if (i == 0 && Character.isDigit(c)) {
                        sqrt = "\u221A" + sqrt;
                        break;
                    }
                }
                secDisplay.setText(sqrt);
                /*
                 * changing a value on secondary display after use root one
                 * before rooted number
                 */
                if ((!operation.isEmpty()) && operation.get(0).equals("6")) {
                    secDisplay.setText("\u221A" + mainDisplay.getText());
                }
                mainDisplay.setText(squareRoot
                        .setScale(prec, RoundingMode.HALF_UP)
                        .stripTrailingZeros().toString());
                numValue = mainDisplay.getText();
                root = true;
            } catch (NumberFormatException ex) { // attempt to root negative
                                                    // number
                secDisplay.setText("Error: incorrect data");
                operation.removeAll(operation);
                memory.removeAll(memory);
                numValue = "";
                root = true;
            }
        }
    }

    class pmActList implements ActionListener {
        // change value sign, form + to -, and from - to +
        public void actionPerformed(ActionEvent event) {
            root = false;
            if (numValue.charAt(0) == '-') {
                numValue = numValue.substring(1);
            } else {
                numValue = "-" + numValue;
            }
            mainDisplay.setText(numValue);

            String s = secDisplay.getText(); // adding sign to secondary display
            if (numValue.substring(0, 1).equals("-")) {
                secDisplay.setText(s.substring(0,
                        s.length() - (numValue.length() - 1))
                        + "(" + numValue + ")");
            } else {
                secDisplay.setText(s.substring(0,
                        s.length() - (numValue.length() + 3))
                        + numValue);
            }
            root = true;
        }
    }

    class CActList implements ActionListener { // calculator reset
        public void actionPerformed(ActionEvent event) {
            memory.removeAll(memory);
            operation.removeAll(operation);
            numValue = "";
            secDisplay.setText("");
            mainDisplay.setText(numValue);
            root = false;
        }
    }

    class MCActList implements ActionListener {
        // reset of memory and results history
        public void actionPerformed(ActionEvent event) {
            memoryStore = new BigDecimal(0);
            sideTextField.setText("M: ");
            sideTextArea.setText("");
        }
    }

    class MSActList implements ActionListener { // sending value to memory
        public void actionPerformed(ActionEvent event) {
            memoryStore = new BigDecimal(mainDisplay.getText());
            sideTextField.setText("M: " + mainDisplay.getText());

        }
    }

    class MRActList implements ActionListener { // recall value from memory
        public void actionPerformed(ActionEvent event) {
            String s = secDisplay.getText();
            String m = memoryStore.toString();
            numValue = m;
            mainDisplay.setText(numValue);

            if (s.length() == 0
                    || ((s.length() > 0 && (!s.substring(
                            s.length() - m.length()).equals(m))))) {
                secDisplay.append(numValue);
            }
        }
    }

    // options from menu
    class wrapList implements ItemListener {
        // wrap a lines in main display if value is to big
        public void itemStateChanged(ItemEvent event) {
            if (wrap.isSelected()) {
                mainDisplay.setLineWrap(true);
            } else {
                mainDisplay.setLineWrap(false);
            }
        }
    }

    class precList implements ActionListener { // decimal point accuracy
        public void actionPerformed(ActionEvent event) {
            try {
                prec = Integer.parseInt(JOptionPane.showInputDialog(frame,
                        "Current decimal point accuracy\n" + prec
                                + "\nChange a decimal point accuracy:"));
            } catch (NumberFormatException ex) {
                ex.printStackTrace();
            }
        }
    }

    class infoActList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            JOptionPane
                    .showMessageDialog(
                            frame,
                            "Calculator\n Cracov 27.02.2015\n \nSimple calculator for basic usage.",
                            "Abount the program",
                            JOptionPane.INFORMATION_MESSAGE);
        }
    }

    class helpActList implements ActionListener { // displays help
        public void actionPerformed(ActionEvent event) {
            JPanel helpPanel = new JPanel();
            JTextArea helpArea = new JTextArea(10, 20);
            Font helpFont = new Font("New Times Roman", Font.PLAIN, 10);
            helpArea.setFont(helpFont);
            helpArea.setLineWrap(true);
            helpArea.setWrapStyleWord(true);
            helpArea.setBorder(BorderFactory.createBevelBorder(4,
                    Color.LIGHT_GRAY, Color.GRAY));
            helpArea.setSize(400, 400);
            helpArea.setEditable(false);
            InputStream is = getClass().getResourceAsStream("help.txt");
            InputStreamReader isr = new InputStreamReader(is);

            try {
                Reader reader = new InputStreamReader(is, "UTF-8");
                while (true) {
                    char c = (char) reader.read();
                    if (c == '`') {
                        break;
                    }
                    helpArea.append(Character.toString(c));
                }
                reader.close();
                isr.close();
                is.close();

            } catch (Exception ex) {
                secDisplay.setText("Error: file not found");
            }

            root = true;
            helpPanel.add(helpArea);
            JOptionPane.showMessageDialog(frame, helpArea, "Help",
                    JOptionPane.INFORMATION_MESSAGE);
        }
    }

    class histList implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            // adding side panel with result history
            if (history.isSelected()) {
                sidePanel.setVisible(true);
                frame.setSize(340, 325);
            } else {
                sidePanel.setVisible(false);
                frame.setSize(250, 325);
            }
        }
    }
}
\$\endgroup\$

2 Answers 2

6
\$\begingroup\$
  1. usage of access modifiers - am I doing it correctly?

It's a good rule of thumb to first make everything private as much as possible, and relax some of those restrictions later as needed and well justified.

All those static fields... Look bad. Static fields are good for immutable constants. They should not be used to keep track of state. I suggest to make all the static fields non static.

  1. code structure - is it make sense in this case, to base it on inner classes?

It's OK to use inner classes, but you should not copy-paste classes but instead generalize them with parametrized constructors when possible. For example you could replace all the B0ActList to B9ActList classes with a single more general class that takes the button label as constructor parameter. Some of the other inner classes can be generalized too.

  1. readability of code

The code is well formatted, well indented, nicely readable.

  1. Length of code - is code not too long as for a calculator?

Yes, I think it's a bit long for what it is. There are many repeated elements and duplicated logic. For example the way you add the buttons and their action listeners is overly repetitive. Keep in mind that duplicated elements are not only boring to read, they quickly become a maintenance nightmare. When you want to change something later, you have to remember to make the same change everywhere, which is prone to errors. I suggest to refactor, and little by little eliminate the repeated elements.

\$\endgroup\$
5
\$\begingroup\$

length of code - is code not too long as for a calculator?

I don't think overall length would be a real concern, especially if all the code is essential for robustness. However, significant refactoring may be needed to split up the outer class for better design.

When considering nesting a class, ask yourself: does this class depend entirely on the outer class? If an inner class is, for instance, a helper and can stand alone, then it should be in a separate file. If the outer class needs it, then it'll just have one or more instances of this class. This will not only facilitate better design, but it'll also make your code much easier to read and maintain.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.