package arcade.view.forms; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileNotFoundException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import java.util.Scanner; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; import util.BackgroundPanel; import arcade.controller.Controller; import arcade.exceptions.UndefinedFormException; import arcade.view.TextKeywords; /** * This represents a form that a user can fill out. * * @author Ellango */ @SuppressWarnings("serial") public abstract class Form extends JFrame { private static final String FORMS_DIRECTORY = System.getProperty("user.dir") + "/src/arcade/resources/forms/"; public static final String BACKGROUND_FILENAME = "arcade/resources/images/LoginBackGround.jpg"; private static final int TEXT_FIELD_HEIGHT = 25; private static final int TEXT_FIELD_SIZE = 10; private static final int LABEL_WIDTH = 80; private static final int MESSAGE_WIDTH = 140; private Controller myController; private ResourceBundle myResources; private JLabel myWarningMessage = new JLabel(); /** * Constructs the form view with a Controller and ResourceBundle. It adds a * background and adds all the components from makeComponents(). * * @param controller * @param resources */ public Form (Controller controller, ResourceBundle resources) { myController = controller; myResources = resources; BackgroundPanel background = new BackgroundPanel(BACKGROUND_FILENAME); getContentPane().add(background); JPanel mainContents = new JPanel(); mainContents.setLayout(new BoxLayout(mainContents, BoxLayout.Y_AXIS)); for (JComponent component : makeComponents()) { addTransparentComponent(mainContents, component); } background.add(mainContents); setTitle(myResources.getString(TextKeywords.TITLE)); setResizable(false); setVisible(true); } /** * Display an error message in the view. * * For example, if user makes failed log in attempt or tries to register an account * that has already been named. * * @param message */ public void sendMessage (String message) { myWarningMessage.setText("<html><body style='width:" + MESSAGE_WIDTH + " px'>" + "<center><font color = red>" + message); } /** * Create all the components to be displayed in the form. * * This is done by reading from the file in the arcade/resources/forms * directory that has the same name as this class. That file contains * a list of which components should be added in a specified order. * * @return */ private List<JComponent> makeComponents () { List<JComponent> components = new ArrayList<JComponent>(); Class<?> thisClass = getClass(); try { Scanner scanner = new Scanner(new File(FORMS_DIRECTORY + thisClass.getSimpleName())); while (scanner.hasNextLine()) { String methodName = scanner.nextLine(); Method createComponent = getMethod(thisClass, methodName); createComponent.setAccessible(true); JComponent component = (JComponent) createComponent.invoke(this, new Object[0]); components.add(component); } scanner.close(); return components; } catch (FileNotFoundException e) { throw new UndefinedFormException(); } catch (NoSuchMethodException e) { throw new UndefinedFormException(); } catch (SecurityException e) { throw new UndefinedFormException(); } catch (IllegalAccessException e) { throw new UndefinedFormException(); } catch (IllegalArgumentException e) { throw new UndefinedFormException(); } catch (InvocationTargetException e) { throw new UndefinedFormException(); } } /** * A helper to find the method named methodName for the provided clazz. * Looks through clazz and every superclass of clazz until the method is found. * * @param clazz * @param methodName * @return * @throws NoSuchMethodException */ private Method getMethod(Class<?> clazz, String methodName) throws NoSuchMethodException { try { return clazz.getDeclaredMethod(methodName); } catch (NoSuchMethodException e) { // base case if (clazz.equals(Object.class)) { throw e; } return getMethod(clazz.getSuperclass(), methodName); } } /** * Makes the provided component transparent, and then adds it to the provided * container. * * @param container * @param component */ private void addTransparentComponent (Container container, JComponent component) { component.setOpaque(false); container.add(component); } /** * Create an instruction for the user. The instruction is chosen from the * ResourceBundle with instructionKeyword * * The result is wrapped in a panel so box layout behaves. * @return */ protected JComponent createInstruction(String instructionKeyword) { JPanel panel = new JPanel(); panel.add(new JLabel(getResources().getString(instructionKeyword))); return panel; } /** * Create a panel with a description and a corresponding text field. * * @param descriptionKeyword is the keyword in the resource bundle. * @param inputField * @return */ protected JComponent createTextPanel (String descriptionKeyword, JTextField inputField) { JPanel panel = new JPanel(); String description = myResources.getString(descriptionKeyword); JLabel label = new JLabel("<html><b>" + description); label.setPreferredSize(new Dimension(LABEL_WIDTH, label.getPreferredSize().height)); panel.add(label); inputField.setColumns(TEXT_FIELD_SIZE); inputField.setPreferredSize(new Dimension(getPreferredSize().width, TEXT_FIELD_HEIGHT)); panel.add(inputField); return panel; } /** * Create a panel with a description of an instruction, and a button to * select an image. * * @param descriptionKeyword is the ResourceBundle keyword for the description * @param buttonKeyword is the ResourceBundle keyword for the button label * @param action is the FileChooserAction with the method defined for what * to do on approval */ protected JComponent createImageSelector (String descriptionKeyword, String buttonKeyword, final FileChooserAction action) { FileFilter filter = new FileNameExtensionFilter(getResources().getString(TextKeywords.IMAGE), "jpg", "gif", "png"); return createFileSelector(descriptionKeyword, buttonKeyword, action, filter); } /** * Create a panel with a description of an instruction, and a button to * select a file. * * @param descriptionKeyword is the ResourceBundle keyword for the description * @param buttonKeyword is the ResourceBundle keyword for the button label * @param action is the FileChooserAction with the method defined for what * to do on approval * @param filter is the Filter on the file chooser. */ protected JComponent createFileSelector (String descriptionKeyword, String buttonKeyword, final FileChooserAction action, final FileFilter filter) { JPanel panel = new JPanel(); JLabel description = new JLabel(getResources().getString(descriptionKeyword)); panel.add(description); JButton button = new JButton(getResources().getString(buttonKeyword)); button.addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent arg0) { JFileChooser chooser = new JFileChooser(); chooser.setFileFilter(filter); int returnVal = chooser.showOpenDialog(null); if (returnVal == JFileChooser.APPROVE_OPTION) { action.approve(chooser); } } }); panel.add(button); return panel; } /** * Create a label where an error message can be displayed. * * @return */ protected JComponent createMessageArea () { JPanel panel = new JPanel(); panel.add(myWarningMessage); return panel; } /** * Create a button to submit results. The text on the button is from the * ResourceBundle for buttonKeyword. On hitting the button, the performed * action is taken. * * Button is wrapped in a panel so BoxLayout works correctly. * */ protected JComponent createButton(String buttonKeyword, ActionListener action) { JPanel panel = new JPanel(); JButton button = new JButton(getResources().getString(buttonKeyword)); button.addActionListener(action); panel.add(button); return panel; } /** * Access to the controller for subclasses. * * @return */ protected Controller getController () { return myController; } /** * Access to the resources for subclasses. * * @return */ protected ResourceBundle getResources () { return myResources; } }