package scriptbuilder.gui.panels; import event.editor.Editor; import event.editor.Properties; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.io.File; import java.util.HashMap; import java.util.Map; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.event.MouseInputAdapter; import scriptbuilder.gui.IncidentEditorFrame; import scriptbuilder.gui.ScriptBuilderFrame; import scriptbuilder.gui.ScriptBuilderGuiConstants; import scriptbuilder.gui.drawers.CursorDrawer; import scriptbuilder.gui.drawers.EventIconDrawer; import scriptbuilder.gui.drawers.IncidentTimelineDrawer; import scriptbuilder.structures.ScriptEvent; import scriptbuilder.structures.ScriptEvent.ScriptEventType; import scriptbuilder.structures.ScriptIncident; import scriptbuilder.structures.SimulationScript; import scriptbuilder.structures.TimeSlice; import scriptbuilder.structures.events.I_ScriptEvent; /** * Represents a single incident timeline in the GUI. Listens for mouse actions. * * @author Greg Eddington * @author Bryan McGuffin * @version 2017/06/30 */ public class IncidentTimelinePanel extends JPanel { /** * The incident this panel represents. */ ScriptIncident incident; /** * If true, this panel is in its minimized state. */ //boolean collapsed; /** * If false, this panel won't be drawn. */ boolean visible; /** * If true, this panel has focus. */ boolean focused; int cursorTime, lastSlice, x, y; /** * The map representing the properties of this incident's events. Keys: * event types. Values: Properties objects for those events. */ Map eventTypeToPropertyMap; /** * Listener for the mouse. Receives notifications when the mouse enters, * exits, moves through, or clicks inside the panel. */ public class IncidentTimelineMouseListener extends MouseInputAdapter { /** * Action to take when the mouse enters the panel. Here, the incident * corresponding to this panel gains focus. * * @param e the mouse event */ @Override public void mouseEntered(MouseEvent e) { incident.setIncidentActive(); focused = true; } /** * Action to take when the mouse leaves the panel. Here, the incident * loses focus and the panel gets repainted. * * @param e the mouse event */ @Override public void mouseExited(MouseEvent e) { focused = false; repaint(); } /* * Popup menu for incident actions */ private JPopupMenu createPopup() { JPopupMenu menu = new JPopupMenu(); JMenuItem eventsMenuItem = new JMenuItem("Events"); JMenuItem propsMenuItem = new JMenuItem("Properties"); JMenuItem deleteMenuItem = new JMenuItem("Delete"); eventsMenuItem.setActionCommand("Edit Events"); propsMenuItem.setActionCommand("Modify Incident Properties"); deleteMenuItem.setActionCommand("Delete Incident"); PopupMenuItemListener menuItemListener = new PopupMenuItemListener(); eventsMenuItem.addActionListener(menuItemListener); propsMenuItem.addActionListener(menuItemListener); deleteMenuItem.addActionListener(menuItemListener); menu.add(eventsMenuItem); menu.add(propsMenuItem); menu.add(deleteMenuItem); return menu; } class PopupMenuItemListener implements ActionListener { public void actionPerformed(ActionEvent e) { JFrame topFrame = (JFrame) getTopLevelAncestor(); if (topFrame instanceof ScriptBuilderFrame) { SimulationScript script = ((ScriptBuilderFrame) topFrame).getScript(); if (e.getActionCommand().equals("Edit Events")) { IncidentEditorFrame editor = new IncidentEditorFrame(incident); script.addObserver(editor); editor.setVisible(true); ((ScriptBuilderFrame) topFrame).update(script, script); } if (e.getActionCommand().equals("Modify Incident Properties")) { ((ScriptBuilderFrame) topFrame).incidentDetailsScreen(incident); ((ScriptBuilderFrame) topFrame).update(script, script); } if (e.getActionCommand().equals("Delete Incident")) { int confirm = JOptionPane.showConfirmDialog(((ScriptBuilderFrame) topFrame), "Are you sure you want to delete " + incident.toString() + "?"); if (confirm == JOptionPane.YES_OPTION) { script.incidents.remove(incident); script.incidents.add(null); ((ScriptBuilderFrame) topFrame).update(script, script); } } } topFrame.repaint(); } } @Override public void mousePressed(MouseEvent e) { int currentMouseX = e.getX(); int currentMouseY = e.getY(); // Does user want a popup menu? if (e.isPopupTrigger()) { JPopupMenu popup = createPopup(); popup.show(e.getComponent(), currentMouseX, currentMouseY); } } /** * Determine if the mouse click happened within a valid timeSlice on * this incident; if so, activate the Editor window for that timeSlice. * * @param e the mouse event */ @Override public void mouseClicked(MouseEvent e) { Editor ed = null; ScriptBuilderFrame f = null; IncidentEditorFrame g = null; if (getTopLevelAncestor() instanceof ScriptBuilderFrame) { f = (ScriptBuilderFrame) getTopLevelAncestor(); } else if (getTopLevelAncestor() instanceof IncidentEditorFrame) { g = (IncidentEditorFrame) getTopLevelAncestor(); ed = new Editor(g); } x = cursorTime = e.getX(); y = e.getY(); if (e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK > ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK / 2) { cursorTime += ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK - e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK; } else { cursorTime -= e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK; } int newSlice = (cursorTime / ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK); newSlice *= ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION; /** * Check if click is out of bounds * */ if (newSlice < 0 || incident == null) { return; } if (incident.slices.get(newSlice) != null) { for (I_ScriptEvent se : incident.slices.get(newSlice).events) { if (ed != null) { ed.addProperty(eventTypeToPropertyMap.get(se.getScriptEventType()), se); } } } /** * Add a new icon if left mouse button was clicked * */ if (e.getButton() == MouseEvent.BUTTON1) { if (getTopLevelAncestor() instanceof IncidentEditorFrame) { if (g.currentEventType != null) { I_ScriptEvent s = ScriptEvent.factoryByType(g.currentEventType); if (ed != null) { ed.addProperty(eventTypeToPropertyMap.get(g.currentEventType), s); } if (incident.slices.get(newSlice) == null) { incident.addNewEvent(s, newSlice); } else { incident.slices.get(newSlice).addEvent(s); } g.update(null, g.getIncident()); } } } if (incident.slices.get(newSlice) != null && getTopLevelAncestor() instanceof IncidentEditorFrame) { ed.setVisible(true); } } /** * Determine if the mouse is now hovering over a valid timeslice; if so, * alter tooltip text and info window text to reflect the new timeslice. * * @param e the mouse event */ @Override public void mouseMoved(MouseEvent e) { x = cursorTime = e.getX(); y = e.getY(); if (e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK > ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK / 2) { cursorTime += ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK - e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK; } else { cursorTime -= e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK; } if (incident != null) { int newSlice = (cursorTime / ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK); newSlice *= ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION; if (newSlice >= 0 && incident.slices.get(newSlice) != null) { incident.setSliceActive(newSlice); lastSlice = newSlice; String newToolTip; newToolTip = incident.slices.get(newSlice).getToolTipText(y); setToolTipText((newToolTip == null || newToolTip.equals("")) ? null : newToolTip); } } repaint(); } } /** * Constructor. Generates a HashMap of all possible event types. */ public IncidentTimelinePanel() { super(); // FACILITATOR_EVAL_EVENT, RADIO_EVAL_EVENT eventTypeToPropertyMap = new HashMap(); eventTypeToPropertyMap.put(ScriptEventType.AUDIO_EVENT, Properties.Audio); eventTypeToPropertyMap.put(ScriptEventType.CAD_EVENT, Properties.CADLog); eventTypeToPropertyMap.put(ScriptEventType.CCTV_EVENT, Properties.CCTV); eventTypeToPropertyMap.put(ScriptEventType.CHP_RADIO_EVENT, Properties.CHPRadio); eventTypeToPropertyMap.put(ScriptEventType.PARAMICS_EVENT, Properties.Paramics); eventTypeToPropertyMap.put(ScriptEventType.TOW_EVENT, Properties.Tow); eventTypeToPropertyMap.put(ScriptEventType.UNIT_EVENT, Properties.Unit); eventTypeToPropertyMap.put(ScriptEventType.WITNESS_EVENT, Properties.Witness); eventTypeToPropertyMap.put(ScriptEventType.MAINTENANCE_RADIO_EVENT, Properties.MaintenanceRadio); eventTypeToPropertyMap.put(ScriptEventType.TMT_RADIO_EVENT, Properties.TMTRadio); eventTypeToPropertyMap.put(ScriptEventType.TELEPHONE_EVENT, Properties.Telephone); eventTypeToPropertyMap.put(ScriptEventType.ATMS_EVAL_EVENT, Properties.ATMS); eventTypeToPropertyMap.put(ScriptEventType.ACTIVITY_LOG_EVAL_EVENT, Properties.ActivityLog); eventTypeToPropertyMap.put(ScriptEventType.CAD_EVAL_EVENT, Properties.CAD); eventTypeToPropertyMap.put(ScriptEventType.CMS_EVAL_EVENT, Properties.CMS); eventTypeToPropertyMap.put(ScriptEventType.FACILITATOR_EVAL_EVENT, Properties.Facilitator); eventTypeToPropertyMap.put(ScriptEventType.RADIO_EVAL_EVENT, Properties.Radio); // Add the mouse listener IncidentTimelineMouseListener mouseListener = new IncidentTimelineMouseListener(); addMouseMotionListener(mouseListener); addMouseListener(mouseListener); } /** * Update the panel if it's changed collapsed status. Redraw it with the * correct dimensions for its status. * * @param incident the incident this panel represents */ public void timelinePanelUpdate(ScriptIncident incident) { this.incident = incident; this.visible = incident != null; Dimension newSize; if (visible) { newSize = new Dimension(((incident.length + incident.offset) / ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION * ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK) + ScriptBuilderGuiConstants.EVENT_ICON_WIDTH, ScriptBuilderGuiConstants.TIMELINE_COLLAPSED_HEIGHT + ScriptBuilderGuiConstants.SCRIPT_EVENT_ICON_STEP * 2); } else { newSize = new Dimension(0, 0); } this.setSize(newSize); this.setPreferredSize(newSize); invalidate(); } /** * Redraw this panel and all the icons inside it. If user is holding an * event, draw that icon under the mouse. * * @param g the graphics component */ @Override public void paint(Graphics g) { super.paint(g); if (!visible) { return; } Graphics2D g2d = (Graphics2D) g; // jdalbey removed this decision and replaced it with ELSE clause // so it will work with any alternate ancestor. // if (getTopLevelAncestor() instanceof ScriptBuilderFrame) // { // IncidentTimelineDrawer.DrawScriptBuilderTimeline(g2d, incident); // } if (getTopLevelAncestor() instanceof IncidentEditorFrame) { IncidentTimelineDrawer.DrawIncidentTimeline(g2d, incident, false); } else { IncidentTimelineDrawer.DrawScriptBuilderTimeline(g2d, incident); } if (focused) { CursorDrawer.DrawCursor(g2d, cursorTime, false); if (this.getTopLevelAncestor() instanceof ScriptBuilderFrame) { if (((ScriptBuilderFrame) this.getTopLevelAncestor()).currentEventType != null) { EventIconDrawer.DrawEventIcon(g2d, ((ScriptBuilderFrame) this.getTopLevelAncestor()).currentEventType, x + 5, y + 10); } } if (this.getTopLevelAncestor() instanceof IncidentEditorFrame) { if (((IncidentEditorFrame) this.getTopLevelAncestor()).currentEventType != null) { EventIconDrawer.DrawEventIcon(g2d, ((IncidentEditorFrame) this.getTopLevelAncestor()).currentEventType, x + 5, y + 10); } } } } /** * Local main for viewing this panel only. * * @author jdalbey * @param args not used */ public static void main(String[] args) { JFrame frame = new JFrame("ScriptBuilderTimelinePanel Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); IncidentTimelinePanel pnl = new IncidentTimelinePanel(); // Create a script File inFile = new File("test/scriptbuilder/structures/test_input_file.xml"); SimulationScript script = new SimulationScript(); script.loadScriptFromFile(inFile); // retrieve a single incident from the script ScriptIncident inci = script.incidents.get(2); // update this panel with an incident pnl.timelinePanelUpdate(inci); frame.getContentPane().add(pnl, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } }