diff --git a/src/dashboard.form b/src/dashboard.form new file mode 100644 index 0000000..769eecd --- /dev/null +++ b/src/dashboard.form @@ -0,0 +1,35 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/dashboard.java b/src/dashboard.java new file mode 100644 index 0000000..99e986f --- /dev/null +++ b/src/dashboard.java @@ -0,0 +1,82 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +/** + * + * @author st8511x + */ +public class dashboard extends javax.swing.JFrame { + + /** + * Creates new form dashboard + */ + public dashboard() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // + private void initComponents() { + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 400, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 300, Short.MAX_VALUE) + ); + + pack(); + }// + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + /* Set the Nimbus look and feel */ + // + /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. + * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html + */ + try { + for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + javax.swing.UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (ClassNotFoundException ex) { + java.util.logging.Logger.getLogger(dashboard.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (InstantiationException ex) { + java.util.logging.Logger.getLogger(dashboard.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (IllegalAccessException ex) { + java.util.logging.Logger.getLogger(dashboard.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (javax.swing.UnsupportedLookAndFeelException ex) { + java.util.logging.Logger.getLogger(dashboard.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } + // + + /* Create and display the form */ + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new dashboard().setVisible(true); + } + }); + } + + // Variables declaration - do not modify + // End of variables declaration +} diff --git a/src/uk/ac/gre/comp1549/dashboard/DashboardDemoMain.java b/src/uk/ac/gre/comp1549/dashboard/DashboardDemoMain.java new file mode 100644 index 0000000..061d4f9 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/DashboardDemoMain.java @@ -0,0 +1,209 @@ +package uk.ac.gre.comp1549.dashboard; + +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JButton; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.WindowConstants; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import uk.ac.gre.comp1549.dashboard.controls.BarPanel; +import uk.ac.gre.comp1549.dashboard.controls.DialPanel; +import uk.ac.gre.comp1549.dashboard.controls.IndicatorFactory; +import uk.ac.gre.comp1549.dashboard.events.*; +import uk.ac.gre.comp1549.dashboard.scriptreader.DashboardEventGeneratorFromXML; +import uk.ac.gre.comp1549.dashboard.controls.IIndicator; +import uk.ac.gre.comp1549.dashboard.controls.Singleton; +import uk.ac.gre.comp1549.dashboard.controls.ArrivalBoard; + +/** + * DashboardDemoMain.java Contains the main method for the Dashboard demo + * application. It: a) provides the controller screen which allows user input + * which is passed to the display indicators, b) allows the user to run the XML + * script which changes indicator values, c) creates the dashboard JFrame and + * adds display indicators to it. + * + * @author COMP1549 + * @version 2.0 + */ +public class DashboardDemoMain extends JFrame { + + /** + * Name of the XML script file - change here if you want to use a different + * filename + */ + public static final String XML_SCRIPT = "dashboard_script.xml"; + + // fields that appear on the control panel + private JButton btnScript; + + // fields that appear on the dashboard itself + private DialPanel speedDial, fuelDial, compassDial, altitudeDial; + private BarPanel fuelBar; + + /** + * Constructor. Does maybe more work than is good for a constructor. + */ + public DashboardDemoMain() { + // Set up the frame for the controller + setTitle("Dashboard demonstration controller"); + setLayout(new FlowLayout()); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + JPanel panel = new JPanel(); + + btnScript = new JButton("Run XML Script"); + + // When the button is read the XML script will be run + btnScript.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + new Thread() { + public void run() { + runXMLScript(); + } + }.start(); + } + }); + panel.add(btnScript); + add(panel); + pack();//size determined by contained components + + setLocationRelativeTo(null); // display in centre of screen + // this.setVisible(true); + + // Set up the dashboard screen + JFrame dashboard = new JFrame("Demo dashboard"); + dashboard.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + dashboard.setLayout(new FlowLayout()); + +// IndicatorFactory indicatorFactory = new IndicatorFactory(); +// IIndicator fullDial = indicatorFactory.createIndicator("full"); + compassDial = new DialPanel("full");//full dial + compassDial.setLabel("COMPASS (deg.)"); + dashboard.add(compassDial); + + speedDial = new DialPanel("threequater");//three quaters dial + speedDial.setLabel("AIR SPEED (m/s)"); + dashboard.add(speedDial); + + + altitudeDial = new DialPanel("half");//half dial + altitudeDial.setLabel("ALTITUDE (km)"); + dashboard.add(altitudeDial); + + fuelDial = new DialPanel(("quater"));//quater dial + fuelDial.setLabel("FUEL (1000L)"); + dashboard.add(fuelDial); + + + DocumentListener petrolListener = new PetrolValueListener(); + fuelDial.txtUserInput.getDocument().addDocumentListener(petrolListener); + + // add the petrol Bar + fuelBar = new BarPanel(); + fuelBar.setLabel("FUEL"); + fuelBar.setValue(100); + dashboard.add(fuelBar); + ArrivalBoard ab = new ArrivalBoard(); + dashboard.add(ab); + +//dashboard.pack(); + dashboard.setSize(1000, 300); //w,h + + dashboard.setExtendedState(JFrame.MAXIMIZED_BOTH); + //dashboard.setUndecorated(true); + dashboard.add(panel); + // centre the dashboard frame above the control frame +// Point topLeft = this.getLocationOnScreen(); // top left of control frame (this) + int hControl = this.getHeight(); // height of control frame (this) + int wControl = this.getWidth(); // width of control frame (this) + int hDash = dashboard.getHeight(); // height of dashboard frame + int wDash = dashboard.getWidth(); // width of dashboard frame + // calculate where top left of the dashboard goes to centre it over the control frame + //Point p2 = new Point((int) topLeft.getX() - (wDash - wControl) / 2, (int) topLeft.getY() - (hDash + hControl)); + //dashboard.setLocation(p2); + + dashboard.setVisible(true); + } + + public void setFuel() { + try { + int value = Integer.parseInt(fuelDial.txtUserInput.getText().trim()); + fuelBar.setValue(value); + } catch (NumberFormatException e) { + } + // don't set the speed if the input can't be parsed + } + + /** + * Respond to user input in the Petrol textfield + */ + private class PetrolValueListener implements DocumentListener { + + @Override + public void insertUpdate(DocumentEvent event) { + setFuel(); + } + + @Override + public void removeUpdate(DocumentEvent event) { + setFuel(); + } + + @Override + public void changedUpdate(DocumentEvent event) { + } + } + + /** + * Run the XML script file which generates events for the dashboard + * indicators + */ + private void runXMLScript() { + try { + DashboardEventGeneratorFromXML dbegXML = new DashboardEventGeneratorFromXML(); + + // Register for speed events from the XML script file + DashBoardEventListener dbelSpeed = new DashBoardEventListener() { + @Override + public void processDashBoardEvent(Object originator, DashBoardEvent dbe) { + speedDial.setValue(Integer.parseInt(dbe.getValue())); + } + }; + dbegXML.registerDashBoardEventListener("speed", dbelSpeed); + + // Register for petrol events from the XML script file + DashBoardEventListener dbelPetrol = new DashBoardEventListener() { + @Override + public void processDashBoardEvent(Object originator, DashBoardEvent dbe) { + fuelDial.setValue(Integer.parseInt(dbe.getValue())); + fuelBar.setValue(Integer.parseInt(dbe.getValue())); + } + }; + dbegXML.registerDashBoardEventListener("petrol", dbelPetrol); + + // Process the script file - it willgenerate events as it runs + dbegXML.processScriptFile(XML_SCRIPT); + + } catch (Exception ex) { + Logger.getLogger(DashboardDemoMain.class.getName()).log(Level.SEVERE, null, ex); + } + } + + /** + * + * @param args - unused + */ + public static void main(String[] args) { + final DashboardDemoMain me = new DashboardDemoMain(); + } +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/ArrivalBoard.form b/src/uk/ac/gre/comp1549/dashboard/controls/ArrivalBoard.form new file mode 100644 index 0000000..72142fc --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/ArrivalBoard.form @@ -0,0 +1,304 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/ArrivalBoard.java b/src/uk/ac/gre/comp1549/dashboard/controls/ArrivalBoard.java new file mode 100644 index 0000000..2cd448d --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/ArrivalBoard.java @@ -0,0 +1,207 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +/** + * + * @author st8511x + */ +public class ArrivalBoard extends javax.swing.JPanel { + + /** + * Creates new form testBean + */ + public ArrivalBoard() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialise the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jLabel3 = new javax.swing.JLabel(); + jLabel4 = new javax.swing.JLabel(); + cmdStatus = new javax.swing.JComboBox(); + panEnterDate = new javax.swing.JPanel(); + cmdHrs = new javax.swing.JComboBox(); + cmdMins = new javax.swing.JComboBox(); + cmdAmPm = new javax.swing.JComboBox(); + txtDestination = new javax.swing.JTextField(); + btnShow = new javax.swing.JButton(); + board = new myjavabean.Bean(); + + jLabel1.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabel1.setText("Status"); + + jLabel2.setFont(new java.awt.Font("Tahoma", 1, 14)); // NOI18N + jLabel2.setText("Arrival:"); + + jLabel3.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabel3.setText("Destination"); + + jLabel4.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + jLabel4.setText("Time"); + + cmdStatus.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + cmdStatus.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "On Time", "Delayed ", "Cancelled" })); + + panEnterDate.setLayout(new java.awt.GridBagLayout()); + + cmdHrs.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + cmdHrs.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24" })); + cmdHrs.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmdHrsActionPerformed(evt); + } + }); + panEnterDate.add(cmdHrs, new java.awt.GridBagConstraints()); + + cmdMins.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + cmdMins.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59" })); + cmdMins.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmdMinsActionPerformed(evt); + } + }); + panEnterDate.add(cmdMins, new java.awt.GridBagConstraints()); + + cmdAmPm.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + cmdAmPm.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "am", "pm" })); + cmdAmPm.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmdAmPmActionPerformed(evt); + } + }); + panEnterDate.add(cmdAmPm, new java.awt.GridBagConstraints()); + + txtDestination.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtDestinationActionPerformed(evt); + } + }); + + btnShow.setText("show"); + btnShow.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnShowActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(12, 12, 12) + .addComponent(jLabel3)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(txtDestination, javax.swing.GroupLayout.PREFERRED_SIZE, 89, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panEnterDate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel4)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1) + .addComponent(cmdStatus, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel2) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(btnShow) + .addGap(26, 26, 26) + .addComponent(board, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel3) + .addComponent(jLabel4) + .addComponent(jLabel1)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(txtDestination, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(cmdStatus) + .addComponent(panEnterDate, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(13, 13, 13) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(btnShow) + .addComponent(board, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void txtDestinationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtDestinationActionPerformed + + // TODO add your handling code here: + }//GEN-LAST:event_txtDestinationActionPerformed + + private void cmdMinsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdMinsActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmdMinsActionPerformed + + private void cmdHrsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdHrsActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmdHrsActionPerformed + + private void btnShowActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnShowActionPerformed + String strDest = String.format(txtDestination.getText()); + String strHrs = "" + cmdHrs.getSelectedItem(); + String strMins = "" + cmdMins.getSelectedItem(); + String strAMPM = "" + cmdAmPm.getSelectedItem(); + String strStatus = "" + cmdStatus.getSelectedItem(); + + board.setDestination(strDest); + board.setHrs(strHrs); + board.setMins(strMins); + board.setAMPM(strAMPM); + board.setStatus(strStatus); + + // TODO add your handling code here: + }//GEN-LAST:event_btnShowActionPerformed + + private void cmdAmPmActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdAmPmActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmdAmPmActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private myjavabean.Bean board; + private javax.swing.JButton btnShow; + private javax.swing.JComboBox cmdAmPm; + private javax.swing.JComboBox cmdHrs; + private javax.swing.JComboBox cmdMins; + private javax.swing.JComboBox cmdStatus; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JPanel panEnterDate; + private javax.swing.JTextField txtDestination; + // End of variables declaration//GEN-END:variables +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/BarIndicator.java b/src/uk/ac/gre/comp1549/dashboard/controls/BarIndicator.java new file mode 100644 index 0000000..b6e2cf6 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/BarIndicator.java @@ -0,0 +1,100 @@ +package uk.ac.gre.comp1549.dashboard.controls; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GradientPaint; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; + +import javax.swing.JPanel; + +/** + * BarDrawPanel. Draw a horizontal bar indicator and show its current value + * + * @author COMP1549 + * @version 2.0 + */ +public class BarIndicator extends JPanel implements IIndicator {//because it shall be used in the facory pattern + + private int value; // current value - will be indicated on the bar + + private int barLength; // length/width of the bar + private int barHeight; // height of the bar + + private int padding; // padding around the bar + private float barMaxValue; // bar runs from 0 to barMaxValue + + /** + * Default constructor - sets default values + */ + public BarIndicator() { + this(200, 20, 8, 100, 0); + } + + /** + * + * @param length - length of the horizontal bar + * @param height - height of the bar + * @param padding - padding around the bar + * @param barMaxValue - bar runs from 0 to barMaxValue + * @param value - current value that will be indicated on the bar + */ + public BarIndicator(int length, int height, int padding, int barMaxValue, int value) { + // set size of the JPanel to be big enough to hold the bar plus padding + setPreferredSize(new Dimension(length + (2 * padding), height + (2 * padding))); + + this.barLength = length; + this.barHeight = height; + this.padding = padding; + this.barMaxValue = barMaxValue; + this.value = value; + } + + /** + * This method is called every time the Bar needs drawing for instance when + * the value has changed. It draws the bar itself and the indicator in the + * correct position to indicate the current value + * + * @param g - graphics object used to draw on the JPanel + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; // get a Graphics2D object to draw with + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // Draw the bar itself. The first 10% of the bar is red. The last 30% is yellow. Between 10% and 30% + // the colour graduates from red to yellow. Check the API documentation for GradientPaint to see + // how this works. + Rectangle2D barx = new Rectangle2D.Double(padding, padding, barLength, barHeight); + + GradientPaint redtoyellow = new GradientPaint(0 + (float) barx.getWidth() * 0.1F, 0, Color.RED, (float) barx.getWidth() * 0.3F, 0, Color.YELLOW); + g2.setPaint(redtoyellow); + + g2.fill(barx); + + // draw the value indicator to show the current value + g2.setStroke(new BasicStroke(barLength / 40, BasicStroke.CAP_SQUARE, 0)); + g2.setPaint(Color.GRAY); + Line2D valueIndicator = new Line2D.Double(padding + (barLength * value / barMaxValue), padding / 2F, padding + (barLength * value / barMaxValue), barHeight + (padding * 1.5F)); + + //valueIndicator. + g2.draw(valueIndicator); + } + + /** + * Set the value to be displayed on the bar + * + * @param value value + */ + public void setValue(int value) { + // don't let the value go over the maximum for the bar. But what about the minimum? + this.value = value > barMaxValue ? (int) barMaxValue : value; + repaint(); // causes paintComponent() to be called + } +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/BarPanel.java b/src/uk/ac/gre/comp1549/dashboard/controls/BarPanel.java new file mode 100644 index 0000000..7a25437 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/BarPanel.java @@ -0,0 +1,57 @@ +package uk.ac.gre.comp1549.dashboard.controls; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.BevelBorder; + +/** + * BarPanel. Container for BarDrawPanel to hold bar and label. If a label is not + * needed BarDrawPanel an be used on its own + * + * @author COMP1549 + * @version 2.0 + */ +public class BarPanel extends JPanel implements IIndicatorPanel{ + + private BarIndicator bar; // the bar itself + private JLabel lblTitle; // the label which always appears above the bar + + /** + * Default constructor + */ + public BarPanel() { + setLayout(new BorderLayout()); + // set the style of the border + setBorder(new BevelBorder(BevelBorder.LOWERED)); + + // position the label above the bar + lblTitle = new JLabel(); + lblTitle.setHorizontalAlignment(JLabel.CENTER); + add(lblTitle, BorderLayout.NORTH); + bar = new BarIndicator(); + + // bar. + add(bar, BorderLayout.CENTER); + + } + + /** + * Set the value for the bar + * + * @param value - value for the bar + */ + public void setValue(int value) { + bar.setValue(value); + } + + /** + * + * @param label - label to appear above the dial + */ + public void setLabel(String label) { + lblTitle.setText(label); + } + + +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/DialPanel.java b/src/uk/ac/gre/comp1549/dashboard/controls/DialPanel.java new file mode 100644 index 0000000..8f3575b --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/DialPanel.java @@ -0,0 +1,130 @@ +package uk.ac.gre.comp1549.dashboard.controls; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.GridBagLayout; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import uk.ac.gre.comp1549.dashboard.DashboardDemoMain; + +/** + * DialPanel. Container for DialDrawPanel to hold dial and label. If a label is + * not needed DialDrawPanel an be used on its own + * + * @author COMP1549 + * @version 2.0 + */ +public class DialPanel extends JPanel implements IIndicatorPanel { + + + + private JLabel lblTitle; // the label which always appears above the dial + public JTextField txtUserInput; + + IIndicator indicator; + String indicatorType; + IndicatorFactory indicatorFactory = new IndicatorFactory(); + + /** + * Default constructor + * + * @param degrees + */ + //STATIC POLYMORPHISM + public DialPanel(String userDesiredType) { + setLayout(new BorderLayout()); + indicatorType = userDesiredType; + labelDial(); + + //Indicator fullDial = indicatorFactory.createIndicator("full"); + indicator = getIndicatorFromFactory(indicatorType); + if (indicator != null) {//allows for null singleton handling with invalid parameter + add((Component) indicator, BorderLayout.CENTER); + displayInputFields(); + + DocumentListener inputListener = new UserInputListener(); + txtUserInput.getDocument().addDocumentListener(inputListener); + } + } + +//METHOD THAT BRIDGES DIAL PANEL TO FACOTORY CLASS + public IIndicator getIndicatorFromFactory(String indicator) { + IIndicator in = indicatorFactory.createIndicator(indicator); + + return in; + } + + //Listens for changes in user input fields for changes + private class UserInputListener implements DocumentListener { + + @Override + public void insertUpdate(DocumentEvent event) { + setDialValue(); + } + + @Override + public void removeUpdate(DocumentEvent event) { + setDialValue(); + } + + @Override + public void changedUpdate(DocumentEvent event) { + } + } + + //set Dial Value according to user input + public void setDialValue() { + try { + int value = Integer.parseInt(txtUserInput.getText().trim()); + indicator.setValue(value); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, " Invalid input type: " + e.getMessage()); + + } + // don't set the speed if the input can't be parsed + } + + private void displayInputFields() { + // this.add(new JLabel("Input value"), BorderLayout.SOUTH); + txtUserInput = new JTextField("0", 3); + //txtuserInput.SET + txtUserInput.setMaximumSize(txtUserInput.getPreferredSize()); + add(txtUserInput, BorderLayout.SOUTH); + } + + private void labelDial() { + setLayout(new BorderLayout()); + + // set the style of the border + setBorder(new BevelBorder(BevelBorder.LOWERED)); + + // position the label above the dial + lblTitle = new JLabel(); + lblTitle.setHorizontalAlignment(JLabel.CENTER); + add(lblTitle, BorderLayout.NORTH); + } + + /** + * Set the value for the dial + * + * @param value - value for the dial + */ + public void setValue(int value) {//USED FOR XML? + indicator.setValue(value); + } + + /** + * + * @param label - label to appear above the dial + */ + public void setLabel(String label) { + + lblTitle.setText(label); + } +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/Dial_FullCircle.java b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_FullCircle.java new file mode 100644 index 0000000..060d2c6 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_FullCircle.java @@ -0,0 +1,171 @@ +package uk.ac.gre.comp1549.dashboard.controls; + +import java.awt.BasicStroke; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.Arc2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import javafx.scene.paint.Color; +import javax.swing.JOptionPane; + +import javax.swing.JPanel; + +/** + * Dial_FullCircle. Draw a dial and indicate current value. + * + * @author COMP1549 + * @version 2.0 + */ +public class Dial_FullCircle extends JPanel implements IIndicator {//COMPASS + + protected int radius; // radius of dial + protected int padding; // padding outside the dial + protected float dialMaxValue; // dial runs from 0 to dialMaxValue + protected int value; // current value - where the hand will point + protected float dialMinValue = 0;//the starting value + protected double handLength; // length of indicator hand + protected double angle; + /** + * The extent of the dial. For a full circle this would be 360 + */ + protected float DIAL_EXTENT_DEGREES = 360; + + /** + * Where the dial starts being draw from. Due north is 90. + */ + //protected final float DIAL_START_OFFSET_DEGREES = -45; + protected float DIAL_START_OFFSET_DEGREES = 90; + + /** + * Default constructor - sets default values + */ + public Dial_FullCircle() { + this(100, 10, 360, 0);//radius, padding, max, pointValue + + } + + /** + * @param radius - radius of the dial + * @param padding - padding outside the dial + * @param dialMaxValue - dial runs from 0 to dialMaxValue + * @param value - current value - where the hand will point + */ + public Dial_FullCircle(int radius, int padding, int dialMaxValue, int value) { + // set size of the JPanel to be big enough to hold the dial plus padding + setPreferredSize(new Dimension(2 * (radius + padding), 2 * (radius + padding))); + this.radius = radius; + this.padding = padding; + this.dialMaxValue = dialMaxValue; + this.value = value; + handLength = 0.9 * radius; // hand length is fixed at 90% of radius + //this.setBackground(java.awt.Color.red); + } + + /** + * This method is called every time the Dial needs drawing for instance when + * the value has changed. It draws the dial itself and the hand in the + * correct position to indicate the current value + * + * @param g - graphics object used to draw on the JPanel + */ + @Override + public void paintComponent(Graphics g) { + //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; // get a Graphics2D object to draw with + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, 0)); + + // draw centre of the dial as a small circle of fixed size + Ellipse2D circle = new Ellipse2D.Double((radius + padding) - 5, (radius + padding) - 5, 10, 10); + g2.fill(circle); + + // draw the dial itself + Arc2D arc = new Arc2D.Double(padding, padding, 2 * radius, 2 * radius, DIAL_START_OFFSET_DEGREES, DIAL_EXTENT_DEGREES, Arc2D.Double.OPEN); +// Arc2D arc = new Arc2D.Double(padding, padding, 2 * radius, 2 * radius, dialMinValue, dialMaxValue, Arc2D.Double.OPEN); + + g2.draw(arc); + //g.fillArc(35, 45, 75, 95, 0, 90); + g.setColor(java.awt.Color.BLUE); + + // draw the little lines at the start and end of the dial + drawDialEnd(g2, Math.toRadians(DIAL_START_OFFSET_DEGREES)); + drawDialEnd(g2, Math.toRadians(DIAL_START_OFFSET_DEGREES + DIAL_EXTENT_DEGREES));//start mark + + float startMark = (DIAL_START_OFFSET_DEGREES + DIAL_EXTENT_DEGREES); + float endMark = DIAL_START_OFFSET_DEGREES; + + // double angle = ((value / dialMaxValue) * DIAL_EXTENT_DEGREES); + if (DIAL_EXTENT_DEGREES != 90) { + angle = Math.toRadians((180 - (DIAL_START_OFFSET_DEGREES)) - (value * (DIAL_EXTENT_DEGREES / dialMaxValue)));//****ORIGINAL**** + } else if (DIAL_EXTENT_DEGREES == 90) { + angle = Math.toRadians((180 - (90 - DIAL_START_OFFSET_DEGREES)) - (value * (DIAL_EXTENT_DEGREES / dialMaxValue)));//****ORIGINAL**** + + } + drawHand(g2, angle, handLength); + } + + protected void drawDialEnd(Graphics2D g2, double angle) { + // calculate endpoint of line furthest from centre of dial + Point2D outerEnd = new Point2D.Double((radius + padding) + radius * Math.cos(angle), + (radius + padding) - radius * Math.sin(angle)); + // calculate endpoint of line closest to centre of dial + Point2D innerEnd = new Point2D.Double((radius + padding) + ((radius + padding) * .8) * Math.cos(angle), + (radius + padding) - ((radius + padding) * .8) * Math.sin(angle)); + // draw the line + g2.draw(new Line2D.Double(outerEnd, innerEnd)); + //g2.draw(new Line2D.Double(outerEnd, innerEnd)); + // g2.setColor(java.awt.Color.red); + + } + + /** + * Draw the hand on the dial to indicate the current value + * + * @param g2 - graphics object used to draw on the JPanel + * @param angle - the angle on the dial at which the hand is to point + * @param handLength - length of the hand + */ + protected void drawHand(Graphics2D g2, double angle, double handLength) { + // calculate the outer end of the hand + Point2D end = new Point2D.Double((radius + padding) + handLength * Math.cos(angle), (radius + padding) - handLength * Math.sin(angle)); + + // Point2D end = outerEnd; +// calculate the centre + Point2D center = new Point2D.Double(radius + padding, radius + padding); + // Draw the line + //g2.draw(new Line2D.Double(center, end)); + g2.draw(new Line2D.Double(center, end)); + } + + /** + * Set the value to be displayed on the dial + * + * @param value value + */ + public void setValue(int value) { + // don't let the value go over the maximum for the dial. But what about the minimum? + // this.value = (int) (value > dialMaxValue ? dialMaxValue : value); + + try { + if (value <= dialMaxValue && value >= dialMinValue) { + this.value = value; + } else { + JOptionPane.showMessageDialog(this, + "Range needs to be between " + dialMinValue + " & " + dialMaxValue, + "Invalid range", + JOptionPane.ERROR_MESSAGE); + } + repaint(); // causes paintComponent() to be called + } catch (Exception e) { + JOptionPane.showMessageDialog(this,e.getMessage()); + + } + + } + +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/Dial_HalfCircle.java b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_HalfCircle.java new file mode 100644 index 0000000..9bd8327 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_HalfCircle.java @@ -0,0 +1,23 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +/** + * + * @author st8511x + */ +//DIAL TYPE 2. +public class Dial_HalfCircle extends Dial_FullCircle{//Child Class 1/3 of 270 deg. + + public Dial_HalfCircle() { + super(100, 10, 45, 0);//radius, padding, max, pointValue + DIAL_EXTENT_DEGREES = 180; + DIAL_START_OFFSET_DEGREES = 0; + dialMinValue = 0; + // double angle = Math.toRadians((180 - (DIAL_START_OFFSET_DEGREES)) - (value * (DIAL_EXTENT_DEGREES / dialMaxValue)));//****ORIGINAL**** + + } +}//CLASS diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/Dial_QuaterCircle.java b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_QuaterCircle.java new file mode 100644 index 0000000..90c9233 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_QuaterCircle.java @@ -0,0 +1,26 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +/** + * + * @author st8511x + */ +//DIAL TYPE 2. +public class Dial_QuaterCircle extends Dial_FullCircle {//Child Class 1/3 of 270 deg. + + public Dial_QuaterCircle() { + super(100, 10, 180, 0);//radius, padding, max, pointValue + DIAL_EXTENT_DEGREES = 90; + DIAL_START_OFFSET_DEGREES = 50; + dialMinValue = 0; + + // super.paintComponent(g); + repaint(); + //double angle = Math.toRadians(((90 - DIAL_START_OFFSET_DEGREES)) - (value * (DIAL_EXTENT_DEGREES / dialMaxValue)));//****ORIGINAL**** + } + +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/Dial_ThreeQuartersCircle.java b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_ThreeQuartersCircle.java new file mode 100644 index 0000000..bc1f2cc --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/Dial_ThreeQuartersCircle.java @@ -0,0 +1,31 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.Arc2D; +import java.awt.geom.Ellipse2D; + +/** + * + * @author st8511x + */ +//DIAL TYPE 2. +public class Dial_ThreeQuartersCircle extends Dial_FullCircle{//Child Class 1/3 of 270 deg. + + public Dial_ThreeQuartersCircle() { + super(100, 10, 500, 0);//radius, padding, max, pointValue + DIAL_EXTENT_DEGREES = 270; + DIAL_START_OFFSET_DEGREES = -45; + dialMinValue = 0; + // this.setBackground(Color.CYAN); + } + +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/IIndicator.java b/src/uk/ac/gre/comp1549/dashboard/controls/IIndicator.java new file mode 100644 index 0000000..9c6dcc0 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/IIndicator.java @@ -0,0 +1,22 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import javax.swing.JOptionPane; + +/** + * + * @author st8511x + */ +public interface IIndicator { + + void paintComponent(Graphics g); + + void setValue(int value); + //the remaining methods in the base class full circle dial made more sense to be protected +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/IIndicatorPanel.java b/src/uk/ac/gre/comp1549/dashboard/controls/IIndicatorPanel.java new file mode 100644 index 0000000..39ddaa8 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/IIndicatorPanel.java @@ -0,0 +1,20 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +import javax.swing.JLabel; + +/** + * + * @author st8511x + */ +//ALL PANELS MUST HAVE... +public interface IIndicatorPanel { + + void setLabel(String label); + + void setValue(int value); +} diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/IndicatorFactory.java b/src/uk/ac/gre/comp1549/dashboard/controls/IndicatorFactory.java new file mode 100644 index 0000000..223d716 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/IndicatorFactory.java @@ -0,0 +1,42 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +import com.sun.org.apache.xalan.internal.xsltc.dom.SingletonIterator; + +/** + * + * @author st8511x + */ +//IS IN CHARGE OF INSTANTIATING DESIRED OBJECTS BY USER +public class IndicatorFactory { + + //factory method + public IIndicator createIndicator(String Indicatortype) { + if (Indicatortype == null) { + return null; + + } else if (Indicatortype.equalsIgnoreCase("full")) { + + return Singleton.getDialTypeSingleton(Indicatortype);//Use of singleton of base class + + } else if (Indicatortype.equalsIgnoreCase("threequater")) { + return Singleton.getDialTypeSingleton(Indicatortype); + + } else if (Indicatortype.equalsIgnoreCase("half")) { + return Singleton.getDialTypeSingleton(Indicatortype); + + } else if (Indicatortype.equalsIgnoreCase("quater")) { + return Singleton.getDialTypeSingleton(Indicatortype); + + } + else{ + return null; + } + }//factory method + +//INCLUDE BARRRRRRR +}//factory diff --git a/src/uk/ac/gre/comp1549/dashboard/controls/Singleton.java b/src/uk/ac/gre/comp1549/dashboard/controls/Singleton.java new file mode 100644 index 0000000..448ae5b --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/controls/Singleton.java @@ -0,0 +1,53 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package uk.ac.gre.comp1549.dashboard.controls; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author st8511x + */ +//ENSURES THAT ONLY A SINGLE INSTANCE OF INDICATOR IS ACTED UPON +public class Singleton { + + //static full dial is only newed up once + private static Dial_FullCircle fullDialSingleton = null;//static instance for singleton + private static Dial_ThreeQuartersCircle threequaterSingleton = null;//static instance for singleton + private static Dial_HalfCircle halfSingleton = null;//static instance for singleton + private static final Dial_QuaterCircle quaterSingleton = null;//static instance for singleton + + private Singleton() { + + } + + //returns singleton of desired dial type by use + public static IIndicator getDialTypeSingleton(String dialType) { + IIndicator dial = null; + + switch (dialType) {//conditional operators f + case "full": + dial = (fullDialSingleton == null) ? new Dial_FullCircle() : fullDialSingleton; + break; + case "threequater": + dial = (threequaterSingleton == null) ? new Dial_ThreeQuartersCircle() : threequaterSingleton; + break; + case "half": + dial = (halfSingleton == null) ? new Dial_HalfCircle() : halfSingleton; + + break; + case "quater": + dial = (quaterSingleton == null) ? new Dial_QuaterCircle() : quaterSingleton; + + break; + default: + dial = null; + } + return dial; + } + +}//singleton class diff --git a/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEvent.java b/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEvent.java new file mode 100644 index 0000000..166353e --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEvent.java @@ -0,0 +1,34 @@ +package uk.ac.gre.comp1549.dashboard.events; + +/** + *Holds information about DashBoardEvents that can be + * passed to DashBoardEventListeners. + * @author COMP1549 + */ +public class DashBoardEvent { + + private String type; // type of event e.g "speed" + private String value; // value of the event e.g. "30" + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return "type:" + type + " value:" + value; + + } + +} diff --git a/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEventList.java b/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEventList.java new file mode 100644 index 0000000..b1dc416 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEventList.java @@ -0,0 +1,61 @@ +package uk.ac.gre.comp1549.dashboard.events; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Holds a Map the keys of which are types of DashBoardEvents (e.g type "speed) + * the values are the list of Listeners registered for that type of event. + * + * @author COMP1549 + */ +public class DashBoardEventList { + + // key is event type, value is list of listeners for that type + HashMap> eventListeners; + + /** + * default constructor + */ + public DashBoardEventList() { + eventListeners = new HashMap<>(); + } + + /** + * Add a DashBoardEventListener + * + * @param type - the type of event listened for (e.g "speed") + * @param listener - reference to the listener object + */ + public void addListener(String type, DashBoardEventListener listener) { + List dbl = eventListeners.get(type); + if (dbl == null) { // if no listeners for this type already registered + dbl = new ArrayList<>(); // create a new list + } + dbl.add(listener); // add the listener to the list + eventListeners.put(type, dbl); // update the map + } + + /** + * Remove a DashBoardEventListener + * + * @param type - the type of event listened for (e.g "speed") + * @param listener - reference to the listener object + */ + public void removeListener(String type, DashBoardEventListener listener) { + List dbl = eventListeners.get(type); + if (dbl != null) { // if there are any listeners for the specified event type + while (dbl.remove(listener)); // remove listener looping in case more than one + } + } + + /** + * Return a list of DashBoardEventListeners for a specified type of event + * @param type - the type of event for which listeners are required (e.g. "speed") + * @return - the list of listeners of the specified type + */ + public List getListeners(String type) { + return eventListeners.get(type); + } +} diff --git a/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEventListener.java b/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEventListener.java new file mode 100644 index 0000000..02fbd27 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/events/DashBoardEventListener.java @@ -0,0 +1,10 @@ +package uk.ac.gre.comp1549.dashboard.events; + +/** + * Interface implemented by classes that want to be notified of DashBoardEvents + * @author COMP1549 + */ +public interface DashBoardEventListener { + public void processDashBoardEvent(Object originator, DashBoardEvent dbe); + +} diff --git a/src/uk/ac/gre/comp1549/dashboard/scriptreader/DashboardEventGeneratorFromXML.java b/src/uk/ac/gre/comp1549/dashboard/scriptreader/DashboardEventGeneratorFromXML.java new file mode 100644 index 0000000..dadacd6 --- /dev/null +++ b/src/uk/ac/gre/comp1549/dashboard/scriptreader/DashboardEventGeneratorFromXML.java @@ -0,0 +1,279 @@ +package uk.ac.gre.comp1549.dashboard.scriptreader; + +import javax.xml.parsers.*; +import org.xml.sax.*; +import org.xml.sax.helpers.*; + +import java.util.*; +import java.io.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import uk.ac.gre.comp1549.dashboard.events.*; + +/** + * DashboardEventGeneratorFromXML reads and parses an XML file which holds a + * script of dashboard events (i.e. events that may cause a change in the value + * of a dashboard indicator). It generates DashBoardEvents which listeners can + * register to receive. + * + * @author COMP1549 + * @version 3.0 + */ +public class DashboardEventGeneratorFromXML extends DefaultHandler { + + // constants used to control processing of the XML file + /** + * XML tag used to indicate a dashboard event + */ + public final static String EVENT_TAG = "dashboard_event"; + + /** + * XML tag used to indicate the type of event + */ + public final static String TYPE_TAG = "type"; + + /** + * XML tag used to indicate the value of event + */ + public final static String VALUE_TAG = "value"; + + /** + * XML tag used to indicate the a time delay in the script + */ + public final static String DELAY_TAG = "delay"; + + /** + * How many milliseconds each unit in a delay element is to cause the script + * to pause for. To speed up the script processing, decrease the number. To + * slow the script processing, increase the number. + */ + public final static int delayUnits = 100; // 1000 = 1 second + + // current event being processed + private DashBoardEvent currentEvent = null; + private String currentTag = ""; // current tag being processed + + // list of listeners registered to receive dashboard events + private final DashBoardEventList dashBoardListeners; + + // the xml parser object + private final XMLReader xmlReader; + + /** + * + * @throws Exception - problem creating the parser + */ + public DashboardEventGeneratorFromXML() throws Exception { + dashBoardListeners = new DashBoardEventList(); + + // The following code configures and creates an object known as a SAXParser which is capable of + // reading and interpreting an XML file. + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(true); + SAXParser saxParser = spf.newSAXParser(); + xmlReader = saxParser.getXMLReader(); + } + + /** + * + * @param filename - filename of the XML file to be processed + * @throws IOException - problem reading the file + * @throws SAXException - problem parsing the file + */ + public void processScriptFile(String filename) throws IOException, SAXException { + // register the current object to receive callbacks when elements are encountered in the XML file + xmlReader.setContentHandler(this); + // Start the parsing process. As the file is processed methods in the startElement(), endElement() and + // characters() methods in the current object will be called to handle the content of the XML file. + xmlReader.parse(convertToFileURL(filename)); + } + + /** + * startElement() is called by the parser whenever a start tag (tag = + * element) is encountered in the XML file. Store the tag's name and if it + is an EVENT_tag create a new DashBoardEvent object that will be + populated with data by the character() method + * + * @param namespaceURI + * @param localName - the name of the tag e.g. "dashboard_event" + * @param atts + * @throws SAXException - problem parsing the file + */ + @Override + public void startElement(String namespaceURI, + String localName, + String qName, + Attributes atts) + throws SAXException { + + currentTag = localName; + + if (currentTag.equals(EVENT_TAG)) { + currentEvent = new DashBoardEvent(); + } + } + + /** + * characters() is called by the parser when character content is to be + * processed. Note that we need to check what type of tag is currently being + * processed in order to know what to do with the characters. For instance + * if we are processing a delay tag we want to pause processing for the + * specified amount of time. + * + * @param ch - array holding the characters to be processed + * @param start - start position of current characters within the array + * @param length - number of characters to process + * @throws SAXException + */ + @Override + public void characters(char ch[], int start, int length) + throws SAXException { + // get the characters into a String and lose any unwanted whitespace + String val = new String(ch, start, length).trim(); + + if (val.length() < 1) { // is no characters to process (was all whitespace) just return + return; + } + + // process the characters based on what type of tag we are currently dealing with. + switch (currentTag) { + case TYPE_TAG: + currentEvent.setType(val); + break; + case VALUE_TAG: + currentEvent.setValue(val); + break; + case DELAY_TAG: + pause(Integer.parseInt(val)); + break; + } + } + + /** + * endElement() is called by the parser when the end tag of an element is + * encountered. At that point know that we have finished processing that + * tag. The only situation we are interested in is when the end + * "dashboard_event" tag is found. At that [point we know that we have all + * the data for the event and can fire the event by creating the event + * object and passing it to all the listeners + * + * @param uri + * @param localName - the name of the tag e.g. "dashboard_event" + * @param qName + * @throws SAXException + */ + @Override + public void endElement(String uri, String localName, String qName) + throws SAXException { + + if (localName.equals(EVENT_TAG)) { + // get all listeners + List listeners = dashBoardListeners.getListeners(currentEvent.getType()); + if (listeners != null) { + // loop through the listeners passing the event object to them - this is "firing" the event + for (DashBoardEventListener dbel : listeners) { + dbel.processDashBoardEvent(this, currentEvent); + } + } + currentEvent = null; + } + currentTag = ""; + } + + /** + * + * @param delay - the length of the delay + */ + private void pause(int delay) { + try { + Thread.sleep(delay * delayUnits); + } catch (InterruptedException ex) { + Logger.getLogger(DashboardEventGeneratorFromXML.class.getName()).log(Level.SEVERE, null, ex); + } + } + + /** + * registerDashBoardEventListener() is called by objects that want to be + * notified when a dashboard event occurs, + * + * @param type - type of the event listener is interested in (e.g. "speed") + * @param dbel - reference to the listener object + */ + public void registerDashBoardEventListener(String type, DashBoardEventListener dbel) { + dashBoardListeners.addListener(type, dbel); + } + + /** + * removeDashBoardEventListener() is called by objects that not longer want + * to be notified when a dashboard event occurs, + * + * @param type - type of the event listener wishes to deregister for (e.g. + * "speed") + * @param dbel - reference to the listener object + */ + public void removeDashBoardEventListener(String type, DashBoardEventListener dbel) { + dashBoardListeners.removeListener(type, dbel); + } + + /** + * convertToFileURL() is a utility method just used if DashboardDemoMain is + * run in standalone mode to test the class + * + * @param filename - name of the xml file + * @return filename in URL format e.g "file:///c:/files/file.xml" + */ + private static String convertToFileURL(String filename) { + String path = new File(filename).getAbsolutePath(); + if (File.separatorChar != '/') { + path = path.replace(File.separatorChar, '/'); + } + + if (!path.startsWith("/")) { + path = "/" + path; + } + return "file:" + path; + } + + /** + * Output a help message to the user + */ + private static void usage() { + System.err.println("Usage: DashboardEventGeneratorFromXML "); + System.err.println(" -usage or -help = this message"); + System.exit(1); + } + + /** + * main() method is only used if the class is run in standalone mode for testing purposes. + * @param args - last argument (args[length-1]) is the name of the xml file to process + * @throws Exception + */ + public static void main(String[] args) throws Exception { + String filename = null; + + // get the filename if persent + for (int i = 0; i < args.length; i++) { + filename = args[i]; + if (i != args.length - 1) { + usage(); + } + } + + if (filename == null) { + usage(); + } + + // Create an instance of DashboardEventGeneratorFromXML and test it + DashboardEventGeneratorFromXML me = new DashboardEventGeneratorFromXML(); + DashBoardEventListener dbel = new DashBoardEventListener() { + @Override + public void processDashBoardEvent(Object originator, DashBoardEvent dbe) { + System.out.println("***** " + dbe); + } + }; + me.registerDashBoardEventListener("speed", dbel); + me.processScriptFile(filename); + me.removeDashBoardEventListener("speed", dbel); + me.processScriptFile(filename); + } +}