source: tmcsimulator-scriptbuilder/trunk/src/scriptbuilder/gui/panels/IncidentTimelinePanel.java @ 145

Revision 145, 22.0 KB checked in by sdanthin, 6 years ago (diff)

Move from Git to Svn (LARGE COMMIT)

Line 
1package scriptbuilder.gui.panels;
2
3import event.editor.frame.Editor;
4import event.editor.frame.Properties;
5import java.awt.BorderLayout;
6import java.awt.Dimension;
7import java.awt.Graphics;
8import java.awt.Graphics2D;
9import java.awt.event.ActionEvent;
10import java.awt.event.ActionListener;
11import java.awt.event.MouseEvent;
12import java.io.File;
13import java.util.HashMap;
14import java.util.Map;
15import javax.swing.JFrame;
16import javax.swing.JMenuItem;
17import javax.swing.JOptionPane;
18import javax.swing.JPanel;
19import javax.swing.JPopupMenu;
20import javax.swing.event.MouseInputAdapter;
21import scriptbuilder.gui.IncidentEditorFrame;
22import scriptbuilder.gui.ScriptBuilderFrame;
23import scriptbuilder.gui.ScriptBuilderGuiConstants;
24import scriptbuilder.gui.drawers.CursorDrawer;
25import scriptbuilder.gui.drawers.EventIconDrawer;
26import scriptbuilder.gui.drawers.IncidentTimelineDrawer;
27import scriptbuilder.structures.ScriptEvent;
28import scriptbuilder.structures.ScriptEvent.ScriptEventType;
29import scriptbuilder.structures.ScriptIncident;
30import scriptbuilder.structures.SimulationScript;
31import scriptbuilder.structures.TimeSlice;
32import scriptbuilder.structures.events.I_ScriptEvent;
33
34/**
35 * Represents a single incident timeline in the GUI. Listens for mouse actions.
36 *
37 * @author Greg Eddington <geddingt@calpoly.edu>
38 * @author Bryan McGuffin
39 * @version 2017/06/30
40 */
41public class IncidentTimelinePanel extends JPanel
42{
43
44    /**
45     * The incident this panel represents.
46     */
47    ScriptIncident incident;
48    /**
49     * If true, this panel is in its minimized state.
50     */
51    //boolean collapsed;
52    /**
53     * If false, this panel won't be drawn.
54     */
55    boolean visible;
56    /**
57     * If true, this panel has focus.
58     */
59    boolean focused;
60
61    /**
62     * If true, right-clicking on this panel will produce the popup menu.
63     */
64    private boolean hasPopupAccess;
65
66    int cursorTime, lastSlice, x, y;
67
68    /**
69     * Filler time at the end of the screen for a particular incident
70     */
71    public int requestedEditorFillerTime;
72
73    /**
74     * Filler time at the end of the screen for the whole script
75     */
76    public static int requestedScriptBuilderFillerTime = 0;
77
78    /**
79     * Constant for amount of filler to add, in seconds. Set to 15 minutes
80     */
81    public static final int FILLER_INTERVAL_SECONDS = 900;
82
83    /**
84     * The map representing the properties of this incident's events. Keys:
85     * event types. Values: Properties objects for those events.
86     */
87    public static Map<ScriptEventType, Properties> eventTypeToPropertyMap;
88
89    /**
90     * Listener for the mouse. Receives notifications when the mouse enters,
91     * exits, moves through, or clicks inside the panel.
92     */
93    public class IncidentTimelineMouseListener extends MouseInputAdapter
94    {
95
96        /**
97         * Action to take when the mouse enters the panel. Here, the incident
98         * corresponding to this panel gains focus.
99         *
100         * @param e the mouse event
101         */
102        @Override
103        public void mouseEntered(MouseEvent e)
104        {
105            incident.setIncidentActive();
106            focused = true;
107        }
108
109        /**
110         * Action to take when the mouse leaves the panel. Here, the incident
111         * loses focus and the panel gets repainted.
112         *
113         * @param e the mouse event
114         */
115        @Override
116        public void mouseExited(MouseEvent e)
117        {
118            focused = false;
119            repaint();
120        }
121
122        /*
123         *   Popup menu for incident actions
124         */
125        private JPopupMenu createPopup()
126        {
127            JPopupMenu menu = new JPopupMenu();
128            JMenuItem eventsMenuItem = new JMenuItem("Events");
129            JMenuItem propsMenuItem = new JMenuItem("Properties");
130            eventsMenuItem.setActionCommand("Edit Events");
131            propsMenuItem.setActionCommand("Modify Incident Properties");
132
133            PopupMenuItemListener menuItemListener = new PopupMenuItemListener();
134
135            eventsMenuItem.addActionListener(menuItemListener);
136            propsMenuItem.addActionListener(menuItemListener);
137
138            menu.add(eventsMenuItem);
139            menu.add(propsMenuItem);
140            return menu;
141        }
142
143        class PopupMenuItemListener implements ActionListener
144        {
145
146            public void actionPerformed(ActionEvent e)
147            {
148                JFrame topFrame = (JFrame) getTopLevelAncestor();
149                if (topFrame instanceof ScriptBuilderFrame)
150                {
151                    SimulationScript script = ((ScriptBuilderFrame) topFrame).getScript();
152                    if (e.getActionCommand().equals("Edit Events"))
153                    {
154                        IncidentEditorFrame editor = new IncidentEditorFrame(incident, (ScriptBuilderFrame) topFrame);
155                        script.addObserver(editor);
156                        editor.setVisible(true);
157                        ((ScriptBuilderFrame) topFrame).update(script, script);
158                    }
159                    if (e.getActionCommand().equals("Modify Incident Properties"))
160                    {
161                        ((ScriptBuilderFrame) topFrame).incidentDetailsScreen(incident);
162                        ((ScriptBuilderFrame) topFrame).update(script, script);
163                    }
164                }
165                topFrame.repaint();
166            }
167        }
168        /**
169         * Note: Popup menus are triggered differently on different systems.
170         * Therefore, isPopupTrigger should be checked in both mousePressed and
171         * mouseReleased for proper cross-platform functionality.
172         * @param e event that triggered this method
173         */
174        @Override
175        public void mousePressed(MouseEvent e)
176        {
177            int currentMouseX = e.getX();
178            int currentMouseY = e.getY();
179
180            // Does user want a popup menu?
181            if (e.isPopupTrigger() && hasPopupAccess)
182            {
183                JPopupMenu popup = createPopup();
184                popup.show(e.getComponent(), currentMouseX, currentMouseY);
185            }
186        }
187        @Override
188        public void mouseReleased(MouseEvent e)
189        {
190            int currentMouseX = e.getX();
191            int currentMouseY = e.getY();
192
193            // Does user want a popup menu?
194            if (e.isPopupTrigger() && hasPopupAccess)
195            {
196                JPopupMenu popup = createPopup();
197                popup.show(e.getComponent(), currentMouseX, currentMouseY);
198            }
199        }
200
201        /**
202         * Determine if the mouse click happened within a valid timeSlice on
203         * this incident; if so, activate the Editor window for that timeSlice.
204         *todo: fix bug where the event editor window appears even when a event type is not selected.
205         * @param clickEvent the mouse event
206         */
207        @Override
208        public void mouseClicked(MouseEvent clickEvent)
209        {
210            Editor editor = null;
211            ScriptBuilderFrame scriptBuilderFrameCurrent = null;
212            IncidentEditorFrame incidentEditFrameCurrent = null;
213            if (getTopLevelAncestor() instanceof ScriptBuilderFrame)
214            {
215                scriptBuilderFrameCurrent = (ScriptBuilderFrame) getTopLevelAncestor();
216
217            }
218            else if (getTopLevelAncestor() instanceof IncidentEditorFrame)
219            {
220                incidentEditFrameCurrent = (IncidentEditorFrame) getTopLevelAncestor();
221                editor = new Editor(incidentEditFrameCurrent);
222            }
223
224            x = cursorTime = clickEvent.getX();
225            y = clickEvent.getY();
226            //System.out.println(cursorTime);
227            //logic that follows is used to "snap" the cursor location to the lines displayed on the timeline panel
228            if (clickEvent.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
229                    > ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK / 2)
230            {
231                cursorTime += ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
232                        - clickEvent.getX()
233                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
234            }
235            else
236            {
237                cursorTime -= clickEvent.getX()
238                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
239            }
240
241            int newSlice = (cursorTime / ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK);
242           
243            newSlice *= ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION;
244            /**
245             * Check if click is out of bounds *
246             */
247            if (newSlice < 0 || incident == null)
248            {
249                return;
250            }
251
252           
253
254            /**
255             * Add a new icon if left mouse button was clicked
256             */
257            if (clickEvent.getButton() == MouseEvent.BUTTON1)
258            {
259               
260                if (getTopLevelAncestor() instanceof IncidentEditorFrame)
261                {
262                   
263                    if (incidentEditFrameCurrent.currentEventType != null)
264                    {
265                        if ((incident.slices.size() == 0 && newSlice != 0)||(incident.slices.get(0).events.isEmpty()))
266                        {
267                           
268                            JOptionPane.showMessageDialog(incidentEditFrameCurrent, "This is the first event in the incident.\n"
269                                    + "Therefore, it will be automatically positioned at time 00:00:00,\n"
270                                    + "relative to the start of the incident.", "Event will be moved", JOptionPane.INFORMATION_MESSAGE);
271                            newSlice = 0;
272                        }
273                        //add the time of addition to be passed through to the event itself somewhere in here? Stored in newSlice
274                        I_ScriptEvent newScriptEvent = ScriptEvent.factoryByType(incidentEditFrameCurrent.currentEventType);
275//                        if (editor != null)
276//                        {
277//                            //this is not necessary if we just load the slices after we create a new one in the window, creates some extra dummy slice for no reason.
278//                            //this adds a new event to the existing editor window
279//                            editor.addEvent(eventTypeToPropertyMap.get(incidentEditFrameCurrent.currentEventType), newScriptEvent);
280//                        }
281                        if (incident.slices.get(newSlice) == null || incident.slices.get(newSlice).events.isEmpty())
282                        {
283                            //if there is no slice at the newSlice time, then create a new event with a new slice at that time
284                           
285                            incident.addNewEvent(newScriptEvent, newSlice);
286                            //find out where the new slice is added and then make it
287                            //possible to
288                            //editor.setSlice(incident.getSlices().get(newSlice));
289                        }
290                        else
291                        {
292                            //if there is already a slice there, just add the event to the
293                            incident.slices.get(newSlice).addEvent(newScriptEvent);
294                        }
295                        incidentEditFrameCurrent.update(null, incidentEditFrameCurrent.getIncident());
296                    }
297                }
298            }
299
300            if (editor != null)
301            {
302                editor.setSlice(incident.slices.get(newSlice));
303            }
304            if (incident.slices.get(newSlice) != null
305                    && getTopLevelAncestor() instanceof IncidentEditorFrame)
306            {
307                editor.setVisible(true);
308            }
309        }
310
311        /**
312         * Determine if the mouse is now hovering over a valid timeslice; if so,
313         * alter tooltip text and info window text to reflect the new timeslice.
314         *
315         * @param e the mouse event
316         */
317        @Override
318        public void mouseMoved(MouseEvent e)
319        {
320            x = cursorTime = e.getX();
321            y = e.getY();
322
323            if (e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
324                    > ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK / 2)
325            {
326                cursorTime += ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
327                        - e.getX()
328                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
329            }
330            else
331            {
332                cursorTime -= e.getX()
333                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
334            }
335
336            if (incident != null)
337            {
338                int newSlice = (cursorTime / ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK);
339                newSlice *= ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION;
340                if (newSlice >= 0 && incident.slices.get(newSlice) != null)
341                {
342                    incident.setSliceActive(newSlice);
343                    lastSlice = newSlice;
344                    String newToolTip;
345
346                    newToolTip = incident.slices.get(newSlice).getToolTipText(y);
347
348                    setToolTipText((newToolTip == null || newToolTip.equals(""))
349                            ? null : newToolTip);
350                }
351            }
352
353            repaint();
354        }
355    }
356
357    /**
358     * Constructor. Generates a HashMap of all possible event types.
359     */
360    public IncidentTimelinePanel()
361    {
362        super();
363
364        hasPopupAccess = true;
365
366        requestedEditorFillerTime = 0;
367//        FACILITATOR_EVAL_EVENT, RADIO_EVAL_EVENT
368        eventTypeToPropertyMap = new HashMap();
369        eventTypeToPropertyMap.put(ScriptEventType.AUDIO_EVENT, Properties.Audio);
370        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVENT, Properties.CADLog);
371        eventTypeToPropertyMap.put(ScriptEventType.CCTV_EVENT, Properties.CCTV);
372        eventTypeToPropertyMap.put(ScriptEventType.CHP_RADIO_EVENT, Properties.CHPRadio);
373        eventTypeToPropertyMap.put(ScriptEventType.PARAMICS_EVENT, Properties.Paramics);
374        eventTypeToPropertyMap.put(ScriptEventType.TOW_EVENT, Properties.Tow);
375        eventTypeToPropertyMap.put(ScriptEventType.UNIT_EVENT, Properties.Unit);
376        eventTypeToPropertyMap.put(ScriptEventType.WITNESS_EVENT, Properties.Witness);
377        eventTypeToPropertyMap.put(ScriptEventType.MAINTENANCE_RADIO_EVENT, Properties.MaintenanceRadio);
378        eventTypeToPropertyMap.put(ScriptEventType.TMT_RADIO_EVENT, Properties.TMTRadio);
379        eventTypeToPropertyMap.put(ScriptEventType.TELEPHONE_EVENT, Properties.Telephone);
380        eventTypeToPropertyMap.put(ScriptEventType.ATMS_EVAL_EVENT, Properties.ATMS);
381        eventTypeToPropertyMap.put(ScriptEventType.ACTIVITY_LOG_EVAL_EVENT, Properties.ActivityLog);
382        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVAL_EVENT, Properties.CAD);
383        eventTypeToPropertyMap.put(ScriptEventType.CMS_EVAL_EVENT, Properties.CMS);
384        eventTypeToPropertyMap.put(ScriptEventType.FACILITATOR_EVAL_EVENT, Properties.Facilitator);
385        eventTypeToPropertyMap.put(ScriptEventType.RADIO_EVAL_EVENT, Properties.Radio);
386
387        // Add the mouse listener
388        IncidentTimelineMouseListener mouseListener
389                = new IncidentTimelineMouseListener();
390        addMouseMotionListener(mouseListener);
391        addMouseListener(mouseListener);
392    }
393
394    /**
395     * Constructor. Generates a HashMap of all possible event types.
396     *
397     * @param usesPopup determines whether or not right-clicking on this panel
398     * will display a popup menu.
399     */
400    public IncidentTimelinePanel(boolean usesPopup)
401    {
402        super();
403
404        hasPopupAccess = usesPopup;
405
406        requestedEditorFillerTime = 0;
407//        FACILITATOR_EVAL_EVENT, RADIO_EVAL_EVENT
408        eventTypeToPropertyMap = new HashMap();
409        eventTypeToPropertyMap.put(ScriptEventType.AUDIO_EVENT, Properties.Audio);
410        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVENT, Properties.CADLog);
411        eventTypeToPropertyMap.put(ScriptEventType.CCTV_EVENT, Properties.CCTV);
412        eventTypeToPropertyMap.put(ScriptEventType.CHP_RADIO_EVENT, Properties.CHPRadio);
413        eventTypeToPropertyMap.put(ScriptEventType.PARAMICS_EVENT, Properties.Paramics);
414        eventTypeToPropertyMap.put(ScriptEventType.TOW_EVENT, Properties.Tow);
415        eventTypeToPropertyMap.put(ScriptEventType.UNIT_EVENT, Properties.Unit);
416        eventTypeToPropertyMap.put(ScriptEventType.WITNESS_EVENT, Properties.Witness);
417        eventTypeToPropertyMap.put(ScriptEventType.MAINTENANCE_RADIO_EVENT, Properties.MaintenanceRadio);
418        eventTypeToPropertyMap.put(ScriptEventType.TMT_RADIO_EVENT, Properties.TMTRadio);
419        eventTypeToPropertyMap.put(ScriptEventType.TELEPHONE_EVENT, Properties.Telephone);
420        eventTypeToPropertyMap.put(ScriptEventType.ATMS_EVAL_EVENT, Properties.ATMS);
421        eventTypeToPropertyMap.put(ScriptEventType.ACTIVITY_LOG_EVAL_EVENT, Properties.ActivityLog);
422        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVAL_EVENT, Properties.CAD);
423        eventTypeToPropertyMap.put(ScriptEventType.CMS_EVAL_EVENT, Properties.CMS);
424        eventTypeToPropertyMap.put(ScriptEventType.FACILITATOR_EVAL_EVENT, Properties.Facilitator);
425        eventTypeToPropertyMap.put(ScriptEventType.RADIO_EVAL_EVENT, Properties.Radio);
426
427        // Add the mouse listener
428        IncidentTimelineMouseListener mouseListener
429                = new IncidentTimelineMouseListener();
430        addMouseMotionListener(mouseListener);
431        addMouseListener(mouseListener);
432    }
433
434    /**
435     * Update the panel if it's changed collapsed status. Redraw it with the
436     * correct dimensions for its status.
437     *
438     * @param incident the incident this panel represents
439     */
440    public void timelinePanelUpdate(ScriptIncident incident)
441    {
442        this.incident = incident;
443        this.visible = (incident != null);
444
445        Dimension newSize;
446        if (visible)
447        {
448
449            if (getTopLevelAncestor() instanceof IncidentEditorFrame)
450            {
451
452                newSize = new Dimension(((incident.length + incident.offset + requestedEditorFillerTime)
453                        / ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION
454                        * ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK)
455                        + ScriptBuilderGuiConstants.EVENT_ICON_WIDTH,
456                        ScriptBuilderGuiConstants.TIMELINE_COLLAPSED_HEIGHT
457                        + ScriptBuilderGuiConstants.SCRIPT_EVENT_ICON_STEP * 2);
458            }
459            else
460            {
461                newSize = new Dimension(((incident.length + incident.offset + requestedScriptBuilderFillerTime)
462                        / ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION
463                        * ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK)
464                        + ScriptBuilderGuiConstants.EVENT_ICON_WIDTH,
465                        ScriptBuilderGuiConstants.TIMELINE_COLLAPSED_HEIGHT
466                        + ScriptBuilderGuiConstants.SCRIPT_EVENT_ICON_STEP * 2);
467            }
468
469        }
470        else
471        {
472            newSize = new Dimension(0, 0);
473        }
474        this.setSize(newSize);
475        this.setPreferredSize(newSize);
476
477        invalidate();
478    }
479
480    /**
481     * Redraw this panel and all the icons inside it. If user is holding an
482     * event, draw that icon under the mouse.
483     *
484     * @param g the graphics component
485     */
486    @Override
487    public void paint(Graphics g)
488    {
489        super.paint(g);
490
491        if (!visible)
492        {
493            return;
494        }
495
496        Graphics2D g2d = (Graphics2D) g;
497//        jdalbey removed this decision and replaced it with ELSE clause
498//                so it will work with any alternate ancestor.
499//        if (getTopLevelAncestor() instanceof ScriptBuilderFrame)
500//        {
501//            IncidentTimelineDrawer.DrawScriptBuilderTimeline(g2d, incident);
502//        }
503        if (getTopLevelAncestor() instanceof IncidentEditorFrame)
504        {
505            IncidentTimelineDrawer.DrawIncidentTimeline(g2d, incident, false);
506        }
507        else
508        {
509            IncidentTimelineDrawer.DrawScriptBuilderTimeline(g2d, incident);
510        }
511
512        if (focused)
513        {
514            //System.out.println("Cursor Time: " + cursorTime);
515            CursorDrawer.DrawCursor(g2d, cursorTime, false);
516            if (this.getTopLevelAncestor() instanceof ScriptBuilderFrame)
517            {
518                if (((ScriptBuilderFrame) this.getTopLevelAncestor()).currentEventType != null)
519                {
520                    EventIconDrawer.DrawEventIcon(g2d,
521                            ((ScriptBuilderFrame) this.getTopLevelAncestor()).currentEventType,
522                            x + 5, y + 10);
523                }
524            }
525            if (this.getTopLevelAncestor() instanceof IncidentEditorFrame)
526            {
527                if (((IncidentEditorFrame) this.getTopLevelAncestor()).currentEventType != null)
528                {
529                    EventIconDrawer.DrawEventIcon(g2d,
530                            ((IncidentEditorFrame) this.getTopLevelAncestor()).currentEventType,
531                            x + 5, y + 10);
532                }
533            }
534        }
535    }
536
537    /**
538     * Local main for viewing this panel only.
539     *
540     * @author jdalbey
541     * @param args not used
542     */
543    public static void main(String[] args)
544    {
545        JFrame frame = new JFrame("ScriptBuilderTimelinePanel Demo");
546        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
547
548        IncidentTimelinePanel pnl = new IncidentTimelinePanel();
549
550        // Create a script
551        File inFile = new File("test/scriptbuilder/structures/test_input_file.xml");
552        SimulationScript script = new SimulationScript();
553        script.loadScriptFromFile(inFile);
554        // retrieve a single incident from the script
555        ScriptIncident inci = script.incidents.get(2);
556        // update this panel with an incident
557        pnl.timelinePanelUpdate(inci);
558
559        frame.getContentPane().add(pnl, BorderLayout.CENTER);
560        frame.pack();
561
562        frame.setVisible(true);
563
564    }
565}
Note: See TracBrowser for help on using the repository browser.