import java.awt.*; import java.awt.event.*; import javax.swing.*; /* * The main class for the SCorTrap program. It implements the main frame. * * Part of sample solution for Assignment 4, CISC 323, Winter 2006. * Please note that the use of fonts, colours and borders was not required. * author: M. Lamb */ public class SCorTrap extends JFrame implements ActionListener { // A large font for the header label private Font largeFont = new Font("Serif", Font.BOLD, 24); // Medium-size font for the sub-headers private Font mediumFont = new Font("Serif", Font.BOLD, 18); // The database, holding information about researchers and operators private SCorTrapDatabase database; // Buttons for the right-hand column private JButton changePassButton; private JButton logoutButton; private JButton resReportButton; private JButton opReportButton; private JButton addOpButton; private JButton delOpButton; private JButton changeRecallButton; private JButton exitButton; // Buttons for the left-hand column private JButton onShipButton; private JButton onPlanetButton; private JButton recoverButton; // Text fields for entering data private JTextField nameField; private JTextField timeField, pressureField, temperatureField, oxygenField, waterField, ozoneField, massField; // Buttons for exposure data private JButton submitButton, clearButton; // Dialogs that pop up when needed private LoginDialog loginPopup; private NewOperatorDialog newOpPopup; // Helper method: wraps a JPanel around a component, using the // default flow layout. This keeps the component from stretching // when the frame is stretched; instead, the panel will stretch and // add empty space around the component. private JPanel wrapFlow(JComponent component) { JPanel thePanel = new JPanel(); thePanel.add(component); return thePanel; } // end wrapFlow // Helper method: wraps a JPanel around a component, using a // border layout and putting the component in the NORTH area. // This means that the component will not stretch but will stay // at the top of the area in which it is put. private JPanel wrapNorth(JComponent component) { JPanel thePanel = new JPanel(new BorderLayout()); thePanel.add(component, BorderLayout.NORTH); return thePanel; } // end wrapNorth /* * Constructor: Sets up the frame. Creates all components, lays * them out on the frame, and registers action listeners. */ public SCorTrap() { // create the database and initialize it from the backup file database = new SCorTrapDatabase(); database.readBackup(this); // The overall layout is a border layout. The north component is a // large label identifying the program Container contents = getContentPane(); JLabel bigLabel = new JLabel("SCorTrap"); bigLabel.setBorder(BorderFactory.createLineBorder(Color.blue, 3)); bigLabel.setFont(largeFont); bigLabel.setHorizontalAlignment(JLabel.CENTER); contents.add(bigLabel, BorderLayout.NORTH); // Center component: everything but the top label. I use a panel inside // a panel here just to improve the look if the window is stretched -- not // a requirement. JPanel centerPanel = new JPanel(new BorderLayout()); contents.add(wrapNorth(centerPanel), BorderLayout.CENTER); // The east component is a vertical list of one label and several // buttons. JPanel eastPanel = new JPanel(new GridLayout(0,1)); eastPanel.setBorder(BorderFactory.createLineBorder(Color.blue, 3)); centerPanel.add(eastPanel, BorderLayout.EAST); JLabel otherLabel = new JLabel("Other Functions:"); otherLabel.setFont(mediumFont); eastPanel.add(wrapFlow(otherLabel)); changePassButton = new JButton("change my password"); eastPanel.add(wrapFlow(changePassButton)); logoutButton = new JButton("log me out"); eastPanel.add(wrapFlow(logoutButton)); resReportButton = new JButton("create researcher report"); eastPanel.add(wrapFlow(resReportButton)); opReportButton = new JButton("create operator report"); eastPanel.add(wrapFlow(opReportButton)); addOpButton = new JButton("add a new operator"); eastPanel.add(wrapFlow(addOpButton)); delOpButton = new JButton("delete operator"); eastPanel.add(wrapFlow(delOpButton)); changeRecallButton = new JButton("change recall number"); eastPanel.add(wrapFlow(changeRecallButton)); exitButton = new JButton("exit program"); eastPanel.add(wrapFlow(exitButton)); // The "researcher panel" contains three areas: // the line for the researcher's name (on top) // an area for entering exposure data (on the right), // and a column of buttons for modifying other researcher information (on the left) JPanel researcherPanel = new JPanel(new BorderLayout()); researcherPanel.setBorder(BorderFactory.createLineBorder(Color.blue, 3)); centerPanel.add(researcherPanel, BorderLayout.CENTER); // The "name panel" contains a place to enter a researcher's name JPanel namePanel = new JPanel(new FlowLayout()); researcherPanel.add(namePanel, BorderLayout.NORTH); namePanel.add(new JLabel("researcher name: ")); nameField = new JTextField(20); namePanel.add(nameField); // The "other panel" contains three buttons for modifying researcher // information in ways other than entering exposure data JPanel otherPanel = new JPanel(new GridLayout(0,1)); researcherPanel.add(wrapNorth(otherPanel), BorderLayout.WEST); JLabel otherResLabel = new JLabel("Other Researcher Information:"); otherResLabel.setHorizontalAlignment(JLabel.CENTER); otherResLabel.setFont(mediumFont); otherPanel.add(otherResLabel); onShipButton = new JButton("researcher is on the ship"); otherPanel.add(wrapFlow(onShipButton)); onPlanetButton = new JButton("researcher is on the planet"); otherPanel.add(wrapFlow(onPlanetButton)); recoverButton = new JButton("researcher has recovered"); otherPanel.add(wrapFlow(recoverButton)); // The "exposure panel" contains fields and buttons for entering // exposure data JPanel exposurePanel = new JPanel(new GridLayout(0,1)); researcherPanel.add(wrapNorth(exposurePanel), BorderLayout.CENTER); JLabel enterExposureLabel = new JLabel(" Enter Exposure Data:"); enterExposureLabel.setHorizontalAlignment(JLabel.CENTER); enterExposureLabel.setFont(mediumFont); exposurePanel.add(enterExposureLabel); JPanel timeRow = new JPanel(); timeRow.add(new JLabel("time (PODs): ")); timeField = new JTextField(10); timeRow.add(timeField); exposurePanel.add(timeRow); JPanel pressureRow = new JPanel(); pressureRow.add(new JLabel("pressure (PFs): ")); pressureField = new JTextField(10); pressureRow.add(pressureField); exposurePanel.add(pressureRow); JPanel temperatureRow = new JPanel(); temperatureRow.add(new JLabel("temperature (TFs): ")); temperatureField = new JTextField(10); temperatureRow.add(temperatureField); exposurePanel.add(temperatureRow); JPanel oxygenRow = new JPanel(); oxygenRow.add(new JLabel("O2 content (%): ")); oxygenField = new JTextField(10); oxygenRow.add(oxygenField); exposurePanel.add(oxygenRow); JPanel waterRow = new JPanel(); waterRow.add(new JLabel("H2O content (%): ")); waterField = new JTextField(10); waterRow.add(waterField); exposurePanel.add(waterRow); JPanel ozoneRow = new JPanel(); ozoneRow.add(new JLabel("O3 content (%): ")); ozoneField = new JTextField(10); ozoneRow.add(ozoneField); exposurePanel.add(ozoneRow); JPanel massRow = new JPanel(); massRow.add(new JLabel("body mass (MFs): ")); massField = new JTextField(10); massRow.add(massField); exposurePanel.add(massRow); JPanel buttonRow = new JPanel(); submitButton = new JButton("submit"); buttonRow.add(submitButton); clearButton = new JButton("clear"); buttonRow.add(clearButton); exposurePanel.add(buttonRow); // add action listeners for every button registerButtons(); // create the popup dialogs for when they are needed loginPopup = new LoginDialog(this, database); newOpPopup = new NewOperatorDialog(this, database); // Disable the Windows close button (the "X" in the top right corner) // so that we can make sure we write a backup database file when // the program stops. Disabling this button was not required. setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); pack(); setVisible(true); // Pop up a login dialog so that this frame can't be used until // an operator logs in loginPopup.setVisible(true); } // end constructor /* * Registers this frame as an action listener for each button */ private void registerButtons() { changePassButton.addActionListener(this); logoutButton.addActionListener(this); resReportButton.addActionListener(this); opReportButton.addActionListener(this); addOpButton.addActionListener(this); delOpButton.addActionListener(this); changeRecallButton.addActionListener(this); exitButton.addActionListener(this); onShipButton.addActionListener(this); onPlanetButton.addActionListener(this); recoverButton.addActionListener(this); submitButton.addActionListener(this); clearButton.addActionListener(this); } // end registerButtons /* * Changes status of administrator-only buttons. * Parameter: isAdmin, true if the current operator is an administrator */ public void setAdminStatus(boolean isAdmin) { if (isAdmin) { addOpButton.setEnabled(true); delOpButton.setEnabled(true); changeRecallButton.setEnabled(true); } else { addOpButton.setEnabled(false); delOpButton.setEnabled(false); changeRecallButton.setEnabled(false); } // end if } // end setAdminStatus /* * Looks up a researcher name in the database. If the researcher exists, * returns a Researcher object describing it. If not, pops up an error * message and returns null. */ public Researcher resLookup(String name) { Researcher result = database.lookupResearcher(name); if (result == null) JOptionPane.showMessageDialog(this, "error: researcher " + name + " is not in database"); return result; } // end resLookup /* * This method is called whenever the user clicks a button * in this frame. For any button requiring more than * one line to handle, it simply dispatches to the appropriate * "handle" method. * The parameter describes the button-click event. */ public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source == exitButton) endProgram(); else if (source == onShipButton) handleOnShip(); else if (source == onPlanetButton) handleOnPlanet(); else if (source == recoverButton) handleRecovered(); else if (source == submitButton) handleSubmit(); else if (source == clearButton) handleClear(); else if (source == changePassButton) handleChangePass(); else if (source == resReportButton) database.researcherReport(this); else if (source == opReportButton) database.operatorReport(this); else if (source == addOpButton) newOpPopup.setVisible(true); else if (source == delOpButton) handleDelOperator(); else if (source == logoutButton) handleLogout(); else if (source == changeRecallButton) handleChangeRecall(); else JOptionPane.showMessageDialog(this, "Internal Error: unknown button"); } // end actionPerformed // Called when it's time to end the program. Public so the login dialog // can call it. Uses a confirm dialog to make sure the user really wants // to quit. If it does, writes the backup file before ending the program. public void endProgram() { int answer = JOptionPane.showConfirmDialog(this, "are you sure you want to exit SCorTrap?"); if (answer == JOptionPane.YES_OPTION) { database.writeBackup(this); System.exit(0); } // end if } // end endProgram // Called when the "researcher is on ship" button is clicked private void handleOnShip() { String name = nameField.getText(); Researcher res = resLookup(name); if (res == null) return; // lookup method already did an error message if (!res.onPlanet) { JOptionPane.showMessageDialog(this, "error: " + name + " is already on the ship"); } else { res.onPlanet = false; database.updateResearcher(res); JOptionPane.showMessageDialog(this, name + " is now on the ship"); } } // end handleOnShip // Called when the "researcher has recovered" button is clicked private void handleRecovered() { String name = nameField.getText(); Researcher res = resLookup(name); if (res == null) return; // lookup method already did an error message if (res.onPlanet) { JOptionPane.showMessageDialog(this, "Error: researcher " + name + " is on the planet; can't be " + "recovering on ship!"); } else { res.numTreatments++; res.exposure = 0; database.updateResearcher(res); JOptionPane.showMessageDialog(this, "researcher " + name + " now has accumulated exposure of 0 BTUs"); } } // end handleRecovered // Called when the "researcher is on planet" button is clicked private void handleOnPlanet() { String name = nameField.getText(); Researcher res = resLookup(name); if (res == null) return; // lookup method already did an error message if (res.onPlanet) { JOptionPane.showMessageDialog(this, "error: " + name + " is already on the planet"); } else { res.onPlanet = true; database.updateResearcher(res); JOptionPane.showMessageDialog(this, name + " is now on the planet"); } } // end handleOnPlanet // Called when the "clear" button is clicked private void handleClear() { nameField.setText(""); timeField.setText(""); pressureField.setText(""); temperatureField.setText(""); oxygenField.setText(""); waterField.setText(""); ozoneField.setText(""); massField.setText(""); } // end handleClear // Called when the "change my password" button is clicked private void handleChangePass() { String opName = loginPopup.getOpName(); Operator op = database.lookupOperator(opName); // Since this operator logged in successfully, the result of the lookup // shouldn't be null, but check anyway. It's possible the current operator // deleted itself and then tried to change its password.... if (op == null) JOptionPane.showMessageDialog(this, "internal error: operator " + opName + " not found in database"); else { String newPassword = JOptionPane.showInputDialog(this, "new password for operator " + opName + ":"); if (newPassword != null) { op.password = newPassword; database.updateOperator(op); } // end if } // end if } // end handleChangePass // Called when the "delete operator" button is clicked private void handleDelOperator() { String opName = JOptionPane.showInputDialog(this, "name of operator to delete: "); if (opName == null) // null means user cancelled return; Operator op = database.lookupOperator(opName); if (op == null) { JOptionPane.showMessageDialog(this, "no operator with that name"); return; } // end if database.deleteOperator(opName); } // end handleDelOperator // Called when the "log me out" button is clicked private void handleLogout() { int answer = JOptionPane.showConfirmDialog(this, "are you sure you want to log out?"); if (answer == JOptionPane.YES_OPTION) loginPopup.setVisible(true); } // end handleLogout /* * Helper method: Gets a integer input from a text field. * If the input is not an integer, or if it's outside the * legal bounds, pops up an error message and returns * Integer.MIN_VALUE as a flag. (This value is the lowest * possible negative integer in Java, and it will never come * up as a legal input in this program.) * Parameters: * field: the text field containing the input * name: the name of the input, for use in error messages * lowBound: the lowest legal value * upBound: the greatest legal value * Return value: the input as an integer, or Integer.MIN_VALUE if * there was an error. */ private int getIntInput( JTextField field, String name, int lowBound, int upBound) { String input = field.getText(); try { int inputValue = Integer.parseInt(input); if (inputValue < lowBound || inputValue > upBound) { JOptionPane.showMessageDialog(this, "error: " + name + " is not in the legal range of [" + lowBound + ".." + upBound + "]"); return Integer.MIN_VALUE; } else return inputValue; } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, "error: " + name + " is not an integer"); return Integer.MIN_VALUE; } } // end getIntInput /* * Helper method: Gets a floating point input from a text field. * This is just like getIntInput, but gets a double. * If the input is not a double, or if it's outside the * legal bounds, pops up an error message and returns * Double.MIN_VALUE as a flag. (This value is the lowest * possible negative double in Java, and it will never come * up as a legal input in this program.) * Parameters: * field: the text field containing the input * name: the name of the input, for use in error messages * lowBound: the lowest legal value * upBound: the greatest legal value * Return value: the input as an integer, or Double.MIN_VALUE if * there was an error. */ private double getDoubleInput( JTextField field, String name, double lowBound, double upBound) { String input = field.getText(); try { double inputValue = Double.parseDouble(input); if (inputValue < lowBound || inputValue > upBound) { JOptionPane.showMessageDialog(this, "error: " + name + " is not in the legal range of [" + lowBound + ".." + upBound + "]"); return Double.MIN_VALUE; } else return inputValue; } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, "error: " + name + " is not a number"); return Double.MIN_VALUE; } } // end getDoubleInput // Called when the "submit" button is clicked private void handleSubmit() { // Get the 7 inputs. If any is an error, the input method will // pop up an error message and this method can just stop. int time = getIntInput(timeField, "time", 0, Integer.MAX_VALUE); if (time == Integer.MIN_VALUE) return; double pressure = getDoubleInput(pressureField, "pressure", 80,500); if (pressure == Double.MIN_VALUE) return; double temperature = getDoubleInput(temperatureField, "temperature", -72, 100); if (temperature == Double.MIN_VALUE) return; double oxygen = getDoubleInput(oxygenField, "O2 content", 0, 100); if (oxygen == Double.MIN_VALUE) return; double water = getDoubleInput(waterField, "H2O content", 0, 100); if (water == Double.MIN_VALUE) return; double ozone = getDoubleInput(ozoneField, "O3 content", 0, 100); if (ozone == Double.MIN_VALUE) return; int mass = getIntInput(massField, "body mass", 260, 150000); if (mass == Integer.MIN_VALUE) return; // If we get here, we've got 7 legal inputs. Now look up the researcher // in the database and make sure it exists and is on planet. String name = nameField.getText(); Researcher researcher = database.lookupResearcher(name); if (researcher == null) { JOptionPane.showMessageDialog(this, "error: researcher " + name + " is not in database"); return; } // end if if (!researcher.onPlanet) { JOptionPane.showMessageDialog(this, "error: researcher " + name + " is on the ship; can't be reporting " + "exposure data!"); return; } // end if // If we get here, everything's legal and we've got an object representing // the researcher who is reporting exposure data. Finally, compute the // exposure from this orbit and add it to the researcher's total. double thisOrbit = CalcEngine.calculate(time, pressure, temperature, oxygen, water, ozone, mass); // exposure in this orbit double oldExp = researcher.exposure; // previous total double newExp = oldExp + thisOrbit; // updated total // Put the new total back into the database researcher.exposure = newExp; database.updateResearcher(researcher); // The estimated exposure during the next orbit is the exposure during this // orbit. If that puts the researcher up to the limit, the reseacher must // be recalled. Either way, we'll be popping up a message and most of the // information will be the same. Accumulate the common parts of the message // in popupMessage. double estimate = newExp + thisOrbit; // estimate after next orbit String popupMessage = "Exposure Data For Researcher " + name + "\n\n"; popupMessage += "total exposure to date: " + newExp + " BTUs\n"; popupMessage += "estimated exposure after next orbit: " + estimate + " BTUs\n"; double recallNum = database.getRecall(); if (estimate >= recallNum) { popupMessage += "RESEARCHER SHOULD BE RECALLED!"; } else { popupMessage += "researcher may remain on planet"; } JOptionPane.showMessageDialog(this, popupMessage); } // end handleSubmit // Called with the "change recall number" button is clicked private void handleChangeRecall() { String input = JOptionPane.showInputDialog(this, "new recall number: "); if (input == null) // user cancelled return; int newRecall; try { newRecall = Integer.parseInt(input); database.setRecall(newRecall); JOptionPane.showMessageDialog(this, "recall number changed"); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, "error: recall number is not an integer"); } } /* * Main method: just creates an instance of this frame class. */ public static void main(String args[]) { new SCorTrap(); } // end main } // end class SCorTrap;