6
\$\begingroup\$

I've made an IDE in Java, using swing. It uses a couple of resources (prefs.json and help.txt) and some imported .jar files for reading the JSON. I know the code's quite long, but the answer doesn't have to be a very detailed one.

package io.j4cobgarby.github;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;

import org.json.JSONException;
import org.json.JSONObject;

public class Main {

    private JFrame frame;
    private JTextField textField;

    public static String currentFile = null;
    private static java.util.Scanner scanner;
    private JTextField cmd2Run;

    JSONObject prefs = null;

    String osType = "Unknown";

    static JTextArea area = new JTextArea();

    static JLabel filenameLabel = new JLabel("");
    private JTextField status;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    Main window = new Main();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     * 
     * @throws IOException
     * @throws JSONException
     */
    public Main() throws IOException, JSONException {
        initialize();

        String prefsText = convertStreamToString(Main.class.getResourceAsStream("prefs.json"));

        osType = (System.getProperty("os.name").startsWith("Windows") ? "win" : "unix");
        System.out.println(osType);

        try {
            prefs = new JSONObject(prefsText);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        String pyCommandWin = (String) prefs.get("pyCommandWin");
        String pyCommandUnix = (String) prefs.get("pyCommandUnix");
        String lastFile = (String) prefs.get("lastFile");
        int tabSize = prefs.getInt("tabSize");
        area.setTabSize(tabSize);

        area.addCaretListener(new CaretListener() {
            public void caretUpdate(CaretEvent e) {
                JTextArea editArea = (JTextArea) e.getSource();

                int linenum = 1;
                int columnnum = 1;

                try {
                    int caretpos = editArea.getCaretPosition();
                    linenum = editArea.getLineOfOffset(caretpos);

                    columnnum = caretpos - editArea.getLineStartOffset(linenum);

                    linenum += 1;
                } catch (Exception ex) {
                }

                updateStatus(linenum, columnnum);
            }
        });

        frame.getContentPane().add(area, BorderLayout.CENTER);

        status = new JTextField();
        frame.getContentPane().add(status, BorderLayout.SOUTH);

        updateStatus(1, 1);

        System.out.println(System.getProperty("os.name") + " is " + osType);

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException e2) {
            e2.printStackTrace();
        } catch (InstantiationException e2) {
            e2.printStackTrace();
        } catch (IllegalAccessException e2) {
            e2.printStackTrace();
        } catch (UnsupportedLookAndFeelException e2) {
            e2.printStackTrace();
        }
        frame.setTitle("Python IDE");
        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        frame.getContentPane().add(toolBar, BorderLayout.NORTH);

        Component horizontalStrut_2 = Box.createHorizontalStrut(20);
        toolBar.add(horizontalStrut_2);

        area.setFont(new Font("Courier New", Font.PLAIN, 12));
        // area.setText(convertStreamToString(Main.class.getResourceAsStream("help.txt")));
        area.setText((lastFile.isEmpty()) ? convertStreamToString(Main.class.getResourceAsStream("help.txt"))
                : readFile(lastFile, Charset.defaultCharset()));
        frame.getContentPane().add(area, BorderLayout.CENTER);

        filenameLabel.setFont(new Font("Tahoma", Font.PLAIN, 11));
        filenameLabel.setToolTipText("File name");
        toolBar.add(filenameLabel);
        filenameLabel.setText("Untitled");

        Component horizontalStrut_6 = Box.createHorizontalStrut(20);
        toolBar.add(horizontalStrut_6);

        JButton btnRun = new JButton("Run in python");
        btnRun.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                // cmd2Run.setText(pyCommandWin + " " + currentFile);
                runPython((osType == "win") ? pyCommandWin : pyCommandUnix);
            }
        });
        toolBar.add(btnRun);

        Component horizontalStrut_1 = Box.createHorizontalStrut(20);
        toolBar.add(horizontalStrut_1);

        Component horizontalStrut_7 = Box.createHorizontalStrut(2);
        toolBar.add(horizontalStrut_7);

        JButton btnRunThisCommand = new JButton("Run this command");
        btnRunThisCommand
                .setToolTipText("Use this for running a script in a different language, or for command line args.");
        btnRunThisCommand.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // format command so that text within braces is checked

                Runtime rt = Runtime.getRuntime();
                try {
                    rt.exec("cmd.exe /c start cmd.exe /k \"" + cmd2Run.getText() + "\"");
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        });
        toolBar.add(btnRunThisCommand);

        cmd2Run = new JTextField();
        cmd2Run.setToolTipText("Use this for running a script in a different language, or for command line args.");
        cmd2Run.setHorizontalAlignment(SwingConstants.LEFT);
        cmd2Run.setFont(new Font("Courier New", Font.PLAIN, 11));
        toolBar.add(cmd2Run);
        cmd2Run.setColumns(5);

        Component horizontalStrut = Box.createHorizontalStrut(300);
        toolBar.add(horizontalStrut);

        textField = new JTextField();
        textField.setHorizontalAlignment(SwingConstants.RIGHT);
        textField.setText(new File(System.getProperty("user.home")).toString());
        toolBar.add(textField);
        textField.setColumns(10);

        JButton btnLoadFile = new JButton("Load file");
        btnLoadFile.setHorizontalAlignment(SwingConstants.RIGHT);
        btnLoadFile.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                loadFile(textField.getText());
            }

        });
        toolBar.add(btnLoadFile);

        JToolBar toolBar_1 = new JToolBar();
        toolBar_1.setFloatable(false);
        frame.getContentPane().add(toolBar_1, BorderLayout.SOUTH);

        status = new JTextField();
        status.setEditable(false);
        toolBar_1.add(status);
        status.setColumns(10);

        JButton btnSettings = new JButton("Settings");
        btnSettings.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                File file = new File("src/io/j4cobgarby/github/prefs.json");
                loadFile(file.getAbsolutePath());
            }
        });
        toolBar_1.add(btnSettings);

        JScrollPane scrollPane = new JScrollPane(area);
        frame.getContentPane().add(scrollPane, BorderLayout.CENTER);

        JMenuBar menuBar = new JMenuBar();
        frame.setJMenuBar(menuBar);

        JMenu mnFile = new JMenu("File");
        menuBar.add(mnFile);

        JMenuItem mntmSaveCurrentFile = new JMenuItem(new AbstractAction("Save file") {
            public void actionPerformed(ActionEvent e) {
                saveFile(currentFile);
            }
        });
        mntmSaveCurrentFile.setText("Save current file");
        mnFile.add(mntmSaveCurrentFile);

        JMenuItem mntmSaveUnderDifferent = new JMenuItem(new AbstractAction("Save file as..") {
            public void actionPerformed(ActionEvent e) {
                JTextField saveFileName = new JTextField(System.getProperty("user.home"));
                JPanel panel = new JPanel(new GridLayout(0, 1));
                panel.add(new JLabel("What would you like to save this file as?"));
                panel.add(saveFileName);
                int result = JOptionPane.showConfirmDialog(null, panel, "Save as..", JOptionPane.OK_CANCEL_OPTION,
                        JOptionPane.PLAIN_MESSAGE);
                if (result == JOptionPane.OK_OPTION) {
                    // On OK
                    saveFile(saveFileName.getText());
                } else {
                    System.out.println("Cancelled");
                }
            }
        });
        mntmSaveUnderDifferent.setText("Save as..");
        mnFile.add(mntmSaveUnderDifferent);

        JMenuItem mntmCreateNewFile = new JMenuItem(new AbstractAction("Create new file") {
            public void actionPerformed(ActionEvent e) {
                JTextField newFilePath = new JTextField(System.getProperty("user.home"));
                JPanel panel = new JPanel(new GridLayout(0, 1));
                panel.add(new JLabel("Path for new file:"));
                panel.add(newFilePath);
                int result = JOptionPane.showConfirmDialog(null, panel, "New file", JOptionPane.OK_CANCEL_OPTION,
                        JOptionPane.PLAIN_MESSAGE);
                if (result == JOptionPane.OK_OPTION) {
                    // On OK
                    newFile(newFilePath.getText());
                } else {
                    System.out.println("Cancelled");
                }
            }
        });
        mnFile.add(mntmCreateNewFile);
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 938, 618);
        frame.addWindowListener(new java.awt.event.WindowAdapter() {
            @Override
            public void windowClosing(java.awt.event.WindowEvent windowEvent) {
                if (JOptionPane.showConfirmDialog(frame, "Are you sure you wish to exit?", "Quit",
                        JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
                    try {
                        System.out.println("Exiting");
                        System.out.println(prefs.toString(4));
                    } catch (JSONException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.exit(0);
                }
            }
        });
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    }

    /*
     * _    _      _                    __             _   _                   
     *| |  | |    | |                  / _|           | | (_)                _ 
     *| |__| | ___| |_ __   ___ _ __  | |_ _   _ _ __ | |_ _  ___  _ __  ___(_)
     *|  __  |/ _ \ | '_ \ / _ \ '__| |  _| | | | '_ \| __| |/ _ \| '_ \/ __|  
     *| |  | |  __/ | |_) |  __/ |    | | | |_| | | | | |_| | (_) | | | \__ \_ 
     *|_|  |_|\___|_| .__/ \___|_|    |_|  \__,_|_| |_|\__|_|\___/|_| |_|___(_)
     *              | |                                                        
     *              |_|                                                        
     */

    /*
     * Create a new file from a String path
     */
    private static void newFile(String path) {
        File file = new File(path);
        try {
            file.createNewFile();
            loadFile(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*
     * Save a file to a String path
     */
    private static void saveFile(String saveTo) {
        FileWriter fwrite = null;
        try {
            fwrite = new FileWriter(new File(saveTo), false);
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        try {
            fwrite.write(area.getText());
            fwrite.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        System.out.println("Saved " + currentFile);
    }

    /*
     * Used with the caret position display; the actual output
     */
    private void updateStatus(int linenumber, int columnnumber) {
        status.setText(linenumber + ":" + columnnumber);
    }

    /*
     * Loads a file to JTextArea area from a String path
     */
    static void loadFile(String filepath) {
        try {
            area.setText(readFile(filepath, Charset.defaultCharset()));
            filenameLabel.setText(new File(filepath).getName());
            currentFile = filepath;
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }

    /*
     * Reads a file from a String path with Charset encoding and returns it as a
     * String
     */
    static String readFile(String path, Charset encoding) throws IOException {
        byte[] encoded = Files.readAllBytes(Paths.get(path));
        return new String(encoded, encoding);
    }

    /*
     * Returns a string of an InputStream as a string
     */
    static String convertStreamToString(java.io.InputStream is) {
        scanner = new java.util.Scanner(is);
        java.util.Scanner s = scanner.useDelimiter("\\A");
        return s.hasNext() ? s.next() : "";
    }

    /*
     * Runs the current file as Python in cmd.
     */
    public void runPython(String cmdPrefix) {
        Runtime rt = Runtime.getRuntime();
        String pyCmd = cmdPrefix + " " + currentFile;
        System.out.println(osType);
        try {
            rt.exec("cmd.exe /c start cmd.exe /k \"" + pyCmd + "\"");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*
     * Shows a popup
     */
    @SuppressWarnings("unused")
    private static void addPopup(final Component component, final JPopupMenu popup) {
        component.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    showMenu(e);
                }
            }

            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    showMenu(e);
                }
            }

            private void showMenu(MouseEvent e) {
                popup.show(e.getComponent(), e.getX(), e.getY());
            }
        });
    }
}

If (understandably) no one wants the read all of this, I'm mainly just looking at the naming conventions and also the way I've used swing, as this is my first Swing project.

\$\endgroup\$
  • \$\begingroup\$ Awesome project! Welcome to CodeReview. I hope you get some equally awesome answers. \$\endgroup\$ – Legato Sep 10 '16 at 20:00
4
\$\begingroup\$

Don't put everything in Main

                    Main window = new Main();

This is a different approach. Usually Main exists only to hold the main method. There's no point in instantiating it. That's why it's often separate, so that you can put main separately from the application classes. It often has helper methods, but they usually do things related to input and output to make main simpler. The application itself would be in other classes.

I think that if I were writing this, I'd say something like

                    IdeGui window = new IdeGui();

Then all that constructor code could be in something called IdeGui or GUI that could be reused. And all the business logic methods could be in another class, IdeApplication, IdeFileManager, or similar.

Access level

    private static java.util.Scanner scanner;

This is only used in one method and you remake it every time the method is called. Why is it an object field? It could just be a local variable.

    public static String currentFile = null;

Why is this public? Everything is in one class. You could make everything private just as well. My rule of thumb is to make everything private until I have a reason to give it a more accessible level.

I have the same issue with the package-protected (default access level) members.

Don't save extra variables

                runPython((osType == "win") ? pyCommandWin : pyCommandUnix);

This could be

                runPython(pyCommand);

with

        String pyCommandWin = (String) prefs.get("pyCommandWin");
        String pyCommandUnix = (String) prefs.get("pyCommandUnix");

changed to

        String pyCommand = (String)prefs.get((osType == "win") ? "pyCommandWin" : "pyCommandUnix");

Then you don't have this extra variable that you never use floating around. Also, if you add a third command format, this way you only make the change in one place. You can just change the way that pyCommand is set. Running it stays exactly the same.

Parallel logic is risky. It's easy to forget to make a change in one place or the other.

try-with-resources

        FileWriter fwrite = null;
        try {
            fwrite = new FileWriter(new File(saveTo), false);
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        try {
            fwrite.write(area.getText());
            fwrite.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }

If there is an exception in the first block, you could end up trying to write to and close a null in the second block. Or an exception on writing might keep the close from being reached.

        try (FileWriter fwrite = new FileWriter(new File(saveTo), false)) {
            fwrite.write(area.getText());
        } catch (IOException e1) {
            e1.printStackTrace();
        }

Now we only write if the file can be opened and we let the try-with-resources handle closing. So the close always happens.

\$\endgroup\$
1
\$\begingroup\$

Please do not make huge ASCII art comments like this:

/*
 * _    _      _                    __             _   _                   
 *| |  | |    | |                  / _|           | | (_)                _ 
 *| |__| | ___| |_ __   ___ _ __  | |_ _   _ _ __ | |_ _  ___  _ __  ___(_)
 *|  __  |/ _ \ | '_ \ / _ \ '__| |  _| | | | '_ \| __| |/ _ \| '_ \/ __|  
 *| |  | |  __/ | |_) |  __/ |    | | | |_| | | | | |_| | (_) | | | \__ \_ 
 *|_|  |_|\___|_| .__/ \___|_|    |_|  \__,_|_| |_|\__|_|\___/|_| |_|___(_)
 *              | |                                                        
 *              |_|                                                        
 */

If absolutely absolutely necessary, make it like this:

/*
 * Helper functions
 */

But quite often even that is unnecessary, I would suggest you move all your helper functions to another class, maybe even called HelperFunctions. That way your Main class would be tidier.


public static String currentFile = null;
private static java.util.Scanner scanner;

Avoid the use of static fields such as the above. Using static fields goes against object-oriented principles. static fields will limit you to only having one instance of it and in the long run will limit your capabilities to write large flexible applications. Pass around the objects that your methods need, or use non-static fields, instead of having them lookup a static field.

\$\endgroup\$
  • \$\begingroup\$ Why should I avoid the use of static fields? Here it says that they're fine. \$\endgroup\$ – Jacob Garby Sep 12 '16 at 15:26
  • \$\begingroup\$ @JacobGarby That is about static methods. I am talking about the static fields. See the edit I just did, I hope that clears it up a bit. I also added a reason for why you should avoid it. If that reason is not enough just let me know and I can provide you with more. I have lots of bad experience with using static fields and have learned from my mistakes. \$\endgroup\$ – Simon Forsberg Sep 12 '16 at 16:20
  • 1
    \$\begingroup\$ @JacobGarby A public static variable is essentially a global variable, using the "class" as a namespace. Global state should be avoided. \$\endgroup\$ – 200_success Sep 12 '16 at 17:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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