001package jmri.jmrit.etcs.dmi.swing;
002
003import java.awt.*;
004import java.beans.PropertyChangeListener;
005
006import javax.annotation.CheckForNull;
007import javax.annotation.Nonnull;
008import javax.swing.*;
009import javax.swing.border.Border;
010
011import jmri.jmrit.etcs.*;
012import jmri.util.ThreadingUtil;
013
014import org.apiguardian.api.API;
015
016/**
017 * JPanel containing the ERTMS ETCS DMI.
018 * @author Steve Young Copyright (C) 2024
019 * @since 5.7.4
020 */
021@API(status=API.Status.EXPERIMENTAL)
022public class DmiPanel extends JPanel {
023
024    public static final Color WHITE = Color.WHITE;
025    public static final Color BLACK = Color.BLACK;
026    public static final Color GREY = new Color(195,195,195);
027    public static final Color DARK_GREY = new Color (85,85,85);
028    public static final Color MEDIUM_GREY = new Color(150,150,150);
029    public static final Color DARK_BLUE = new Color(3,17,34);
030    public static final Color ORANGE = new Color(234,145,0);
031    public static final Color RED = new Color(191,0,2);
032    public static final Color YELLOW = new Color(223,223,0);
033    protected static final Color BACKGROUND_COLOUR = DARK_BLUE;
034
035    protected static final Border BORDER_ACK =  BorderFactory.createLineBorder(DmiPanel.YELLOW , 2);
036    protected static final Border BORDER_NORMAL =  BorderFactory.createLineBorder(DmiPanel.BACKGROUND_COLOUR , 2);
037
038    protected static final String FONT_NAME = "Helvetica";
039
040    public static final String PROP_CHANGE_CABMESSAGE_ACK = "MessageAcknowledged";
041    public static final String PROP_CHANGE_LEVEL_NTC_TRANSITION_ACK = "LevelNTCAcknowledged";
042    public static final String PROP_CHANGE_LEVEL_0_TRANSITION_ACK = "Level0Acknowledged";
043    public static final String PROP_CHANGE_LEVEL_1_TRANSITION_ACK = "Level1Acknowledged";
044    public static final String PROP_CHANGE_LEVEL_2_TRANSITION_ACK = "Level2Acknowledged";
045    public static final String PROP_CHANGE_LEVEL_3_TRANSITION_ACK = "Level3Acknowledged";
046
047    public static final int MODE_NONE = 0;
048    public static final int MODE_SHUNTING = 1;
049    public static final int MODE_TRIP = 4;
050    public static final int MODE_POST_TRIP = 6;
051    public static final int MODE_ON_SIGHT = 7;
052    public static final int MODE_STAFF_RESPONSIBLE = 9;
053    public static final int MODE_FULL_SUPERVISION = 11;
054    public static final int MODE_NON_LEADING = 12;
055    public static final int MODE_STANDBY = 13;
056    public static final int MODE_REVERSING = 14;
057    public static final int MODE_UNFITTED = 16;
058    public static final int MODE_SYSTEM_FAILURE = 18;
059    public static final int MODE_NATIONAL_SYSTEM = 20;
060    public static final int MODE_LIMITED_SUPERVISION = 21;
061    public static final int MODE_AUTOMATIC_DRIVING = 23;
062    public static final int MODE_SUPERVISED_MANOEUVRE = 24;
063
064    public static final String PROP_CHANGE_MODE_SHUNTING_ACK = "ModeShuntingAcknowledged";
065    public static final String PROP_CHANGE_MODE_TRIP_ACK = "ModeTripAcknowledged";
066    public static final String PROP_CHANGE_MODE_ON_SIGHT_ACK = "ModeOnSightAcknowledged";
067    public static final String PROP_CHANGE_MODE_STAFF_RESPONSIBLE_ACK = "ModeStaffResponsibleAcknowledged";
068    public static final String PROP_CHANGE_MODE_REVERSING_ACK = "ModeReversingAcknowledged";
069    public static final String PROP_CHANGE_MODE_UNFITTED_ACK = "ModeUnfittedAcknowledged";
070    public static final String PROP_CHANGE_MODE_NATIONAL_SYSTEM_ACK = "ModeNationalSystemAcknowledged";
071    public static final String PROP_CHANGE_MODE_LIMITED_SUPERVISION_ACK = "ModeLimitedSupervisionAcknowledged";
072    public static final String PROP_CHANGE_TRACK_AHEAD_FREE_TRUE = "DriverAdvisesTrackAheadFree";
073
074    public static final String PROP_CHANGE_ATO_DRIVER_REQUEST_START = "AtoDriverStart";
075    public static final String PROP_CHANGE_ATO_DRIVER_REQUEST_STOP = "AtoDriverStop";
076    public static final String PROP_CHANGE_SKIP_STOPPING_POINT_INACTIVE_DRIVER = "SkipStoppingPointInactive";
077    public static final String PROP_CHANGE_SKIP_STOPPING_POINT_REQUEST_DRIVER = "SkipStoppingPointRequestByDriver";
078
079    public static final String PROP_CHANGE_TUNNEL_STOP_AREA_ACK = "TunnelStopAreaAcknowledged";
080    public static final String PROP_CHANGE_SOUND_HORN_ACK = "SoundHornAcknowledged";
081    public static final String PROP_CHANGE_LOWER_PANT_ACK = "LowerPantographAcknowledged";
082    public static final String PROP_CHANGE_RAISE_PANT_ACK = "RaisePantographAcknowledged";
083    public static final String PROP_CHANGE_AIRCON_OPEN_ACK = "AirConOpenAcknowledged";
084    public static final String PROP_CHANGE_AIRCON_CLOSE_ACK = "AirConCloseAcknowledged";
085    public static final String PROP_CHANGE_NEUTRAL_START_ACK = "NeutralSectionStartAcknowledged";
086    public static final String PROP_CHANGE_NEUTRAL_END_ACK = "NeutralSectionEndAcknowledged";
087    public static final String PROP_CHANGE_NONSTOP_ACK = "NonStoppingAreaAcknowledged";
088    public static final String PROP_CHANGE_INHIBIT_MAG_BRAKE_ACK = "InhibitMagShoeBrakeAcknowledged";
089    public static final String PROP_CHANGE_INHIBIT_EDDY_BRAKE_ACK = "InhibitEddyCurrentBrakeAcknowledged";
090    public static final String PROP_CHANGE_INHIBIT_REGEN_BRAKE_ACK = "InhibitRegenerativeBrakeAcknowledged";
091    public static final String PROP_CHANGE_TRACTION_0_ACK = "NoTractionAcknowledge";
092    public static final String PROP_CHANGE_TRACTION_25KV_ACK = "TractionSystemAC25kVAcknowledge";
093    public static final String PROP_CHANGE_TRACTION_15KV_ACK = "TractionSystemAC15kVAcknowledge";
094    public static final String PROP_CHANGE_TRACTION_3KV_ACK = "TractionSystemDC3kVAcknowledge";
095    public static final String PROP_CHANGE_TRACTION_1_5KV_ACK = "TractionSystemDC1.5kVAcknowledge";
096    public static final String PROP_CHANGE_TRACTION_750V_ACK = "TractionSystemDC600750VAcknowledge";
097
098    protected static final String PROPERTY_CENTRE_TEXT = DmiPanel.class.getName()+"centreText";
099
100    private final DmiPanelA panelA;
101    private final DmiPanelB panelB;
102    private final DmiPanelC panelC;
103    private final DmiPanelD panelD;
104    private final DmiPanelE panelE;
105    private final DmiPanelG panelG;
106    private final DmiFlashTimer flashTimer;
107    private int mode = 0; // unset
108
109    /**
110     * Create a new DmiPanel.
111     */
112    public DmiPanel(){
113        super();
114        setPreferredSize(new Dimension(640,480));
115        setLayout(null); // Set the layout manager to null
116
117        flashTimer = new DmiFlashTimer(this);
118
119        panelA = getPanelA();
120        panelB = getPanelB();
121        panelC = getPanelC();
122        panelD = getPanelD();
123        panelE = getPanelE();
124        panelG = getPanelG();
125
126        add(panelA);
127        add(panelB);
128        add(panelC);
129        add(panelD);
130        add(panelE);
131        add(getPanelF());
132        add(panelG);
133        add(getPanelY());
134        add(getPanelZ());
135
136    }
137
138    // distance countdown bar
139    private DmiPanelA getPanelA() {
140        return new DmiPanelA(this);
141    }
142
143    // speedometer
144    private DmiPanelB getPanelB() {
145        return new DmiPanelB(this);
146    }
147
148    // larger icons under speedometer
149    private DmiPanelC getPanelC() {
150        return new DmiPanelC(this);
151    }
152
153    // planning area
154    private DmiPanelD getPanelD() {
155        return new DmiPanelD(this);
156    }
157
158    // messages
159    private DmiPanelE getPanelE() {
160        return new DmiPanelE(this);
161    }
162
163    // right hand side buttons
164    private DmiPanelF getPanelF() {
165        return new DmiPanelF(this);
166    }
167
168    // ATO and clock
169    private DmiPanelG getPanelG() {
170        return new DmiPanelG(this);
171    }
172
173    // top panel bar spacer
174    private JPanel getPanelY() {
175        JPanel p = new JPanel();
176        p.setBackground(BACKGROUND_COLOUR);
177        p.setBounds(0, 0, 640, 15);
178        return p;
179    }
180
181    // bottom panel bar spacer
182    private JPanel getPanelZ() {
183        JPanel p = new JPanel();
184        p.setBackground(BACKGROUND_COLOUR);
185        p.setLayout(null);
186        p.setBounds(0, 465, 640, 15);
187        JLabel jmriLabel = new JLabel("JMRI " + jmri.Version.getCanonicalVersion());
188        jmriLabel.setFont(new Font(DmiPanel.FONT_NAME, Font.PLAIN, 9));
189        jmriLabel.setForeground(BLACK);
190        jmriLabel.setLayout(null);
191        jmriLabel.setBounds(590, 2, 110 , 15);
192        p.add(jmriLabel);
193        return p;
194    }
195
196    /**
197     * Set the Maximum Speed on the Speed Dial.
198     * @param speed 140, 180, 250 or 400
199     */
200    public void setMaxDialSpeed( int speed ) {
201        ThreadingUtil.runOnGUI( () -> panelB.setMaxDialSpeed(speed) );
202    }
203
204    /**
205     * Set the speed value to be displayed by the dial and in centre of dial.
206     * @param speed no unit specified.
207     */
208    public void setActualSpeed( int speed ) {
209        ThreadingUtil.runOnGUI( () -> panelB.setActualSpeed(speed) );
210    }
211
212    /**
213     * Set the Centre Speedometer Circle and Dial Colour.
214     * Default is DmiPanel.GREY
215     * @param colour the colour to use.
216     */
217    public void setCentreCircleAndDialColor ( Color colour ) {
218        ThreadingUtil.runOnGUI( () -> panelB.setCentreCircleAndDialColor(colour) );
219    }
220
221    /**
222     * Set a list of Circular Speed Guide sections to display.
223     * @param list the list to display.
224     */
225    public void setCsgSections(java.util.List<DmiCircularSpeedGuideSection> list){
226        ThreadingUtil.runOnGUI( () -> panelB.setCsgSections(list) );
227    }
228
229    /**
230     * Set a speed unit to be displayed in the dial.
231     * @param newVal the speed unit, for display purpose only.
232     */
233    public void setDisplaySpeedUnit( String newVal ) {
234        ThreadingUtil.runOnGUI( () -> panelB.setDisplaySpeedUnit(newVal) );
235    }
236
237    /**
238     * Set the ATO Target Advice Speed.
239     * @param newVal Target speed.
240     * Negative values hide the advice.
241     */
242    public void setTargetAdviceSpeed(int newVal) {
243        ThreadingUtil.runOnGUI(() -> panelB.setTargetAdviceSpeed(newVal) );
244    }
245
246    /**
247     * Set distance to the next Advice Change.
248     * @param distance to next advice.
249     * Negative values hide the advice.
250     */
251    public void setNextAdviceChange(int distance) {
252        ThreadingUtil.runOnGUI( () -> panelD.setNextAdviceChange(distance) );
253    }
254
255    /**
256     * Set the release speed.
257     * A negative value hides the speed.
258     * @param speed to display.
259     */
260    public void setReleaseSpeed(int speed) {
261        ThreadingUtil.runOnGUI(() -> panelB.setReleaseSpeed(speed) );
262    }
263
264    /**
265     * Set the text colour of the Release Speed.
266     * @param newColour the colour to use.
267     */
268    public void setReleaseSpeedColour(Color newColour) {
269        ThreadingUtil.runOnGUI(() -> panelB.setReleaseSpeedColour( newColour ));
270    }
271
272    /**
273     * Set Level Transition Announcement Notification.
274     * Note that some valid options for ERTMS3.6 are invalid for ERTMS4 ,
275     * e.g. 2, false.
276     * @param newLevel
277     * -2 : No notification displayed.
278     * -1 : NTC
279     * 0 : Level 0
280     * 1 : Level 1 Intermittent
281     * 2 : Level 2
282     * 3 : Level 3
283     * @param ackRequired true if acknowledgement required by driver, else false.
284     */
285    public void setLevelTransition(int newLevel, boolean ackRequired) {
286        ThreadingUtil.runOnGUI( () -> panelC.setLevelTransition(newLevel, ackRequired) );
287    }
288
289    /**
290     * Display Level Symbol.
291     * @param level
292     * -2 : No notification displayed.
293     * -1 : NTC
294     * 0 : Level 0
295     * 1 : Level 1 Intermittent
296     * 2 : Level 2
297     * 3 : Level 3 ( ERTMS &lt; 4 )
298     */
299    public void setLevel(int level){
300        ThreadingUtil.runOnGUI( () -> panelC.setLevel(level) );
301    }
302
303    /**
304     * Set Mode.
305     * 0 - No Mode Displayed
306     * 1 - Shunting
307     * 4 - Trip
308     * 6 - Post Trip
309     * 7 - On Sight
310     * 9 - Staff Responsible
311     * 11 - Full Supervision Mode
312     * 12 - Non-leading
313     * 13 - Standby
314     * 14 - Reversing
315     * 16 - Unfitted
316     * 18 - System Failure
317     * 21 - Limited Supervision
318     * 23 - Automatic Driving ( From ERTMS4 )
319     * 24 - Supervised Manoeuvre ( From ERTMS4 )
320     * @param newMode the mode to display.
321     */
322    public void setMode(int newMode){
323        mode = newMode;
324        ThreadingUtil.runOnGUI(() -> {
325            panelB.setMode(newMode);
326            panelD.repaint(); // Panel D also has conditionals depending on mode.
327        });
328    }
329
330    /**
331     * Set the display to acknowledge the transition to a new Mode.
332     * @param newMode the new Mode to request acknowledgement for.
333     */
334    public void setModeAcknowledge(int newMode){
335        ThreadingUtil.runOnGUI(() -> panelC.setModeAcknowledge(newMode) );
336    }
337
338    /**
339     * Get the displayed operating mode.
340     * @return the current mode.
341     */
342    protected int getMode(){
343        return mode;
344    }
345
346    /**
347     * Add a TrackCondition Announcement to under the Dial.
348     * @param tc the Announcement to add.
349     */
350    public void addAnnouncement( TrackCondition tc ) {
351        ThreadingUtil.runOnGUI( () -> panelB.addAnnouncement(tc) );
352    }
353
354    /**
355     * Remove an Announcement from under the Dial.
356     * @param tc the Announcement to remove.
357     */
358    public void removeAnnouncement ( TrackCondition tc ) {
359        ThreadingUtil.runOnGUI( () -> panelB.removeAnnouncement(tc) );
360    }
361
362    /**
363     * Set a Limited Supervision Speed.
364     * A negative value hides the icon.
365     * @param spd the Limited Supervision Speed.
366     */
367    public void setLimitedSupervisionSpeed(int spd) {
368        ThreadingUtil.runOnGUI( () -> panelA.setLimitedSupervisionSpeed(spd) );
369    }
370
371    /**
372     * Set the distance to target bar.
373     * A negative value hides the field.
374     * Values displayed to nearest 10m.
375     * @param distance the distance to set.
376     */
377    public void setDistanceToTarget(int distance){
378        ThreadingUtil.runOnGUI( () -> panelA.setDistanceToTarget(distance) );
379    }
380
381    /**
382     * Set the adhesion Factor symbol displayed.
383     * @param newVal true to display, else false.
384     */
385    public void setAdhesionFactorOn(boolean newVal){
386        ThreadingUtil.runOnGUI( () -> panelA.setAdhesionFactorOn(newVal) );
387    }
388
389    /**
390     * Set if Intervention Symbol is displayed.
391     * @param newVal true to display, false to hide.
392     */
393    public void setIntervetionSymbol(boolean newVal){
394        ThreadingUtil.runOnGUI( () -> panelC.setIntervetionSymbol(newVal) );
395    }
396
397    /**
398     * Set the Reversing Permitted symbol visible.
399     * @param newVal true to display, false to hide.
400     */
401    public void setReversingPermittedSymbol(boolean newVal){
402        ThreadingUtil.runOnGUI( () -> panelC.setReversingPermittedSymbol(newVal) );
403    }
404
405    /**
406     * Set the Indication marker.
407     * Negative values not displayed.
408     * @param distance the distance at which to display the marker.
409     * @param whichSpeedChange the order of the speed change in the Movement Authority.
410     */
411    public void setIndicationMarker(int distance, int whichSpeedChange ) {
412        ThreadingUtil.runOnGUI( () -> panelD.setIndicationMarkerLine(distance, whichSpeedChange) );
413    }
414
415    /**
416     * Set Automatic Train Operation Mode.
417     * @param mode the new ATO Mode.
418     * 0: No ATO 
419     * 1: ATO selected
420     * 2: ATO Ready for Engagement
421     * 3: ATO Engaged
422     * 4: ATO Disengaging
423     * 5: ATO failure
424     */
425    protected void setAtoMode(int mode){
426        ThreadingUtil.runOnGUI( () -> panelG.setAtoMode(mode) );
427    }
428
429    /**
430     * Set the Coasting Symbol visible.
431     * Only valid to display if in ATO mode
432     * @param visible true to display, else false.
433     */
434    public void setCoasting(boolean visible){
435        ThreadingUtil.runOnGUI( () -> panelB.setCoasting(visible) );
436    }
437
438    /**
439     * Set Stopping accuracy symbol visible.
440     * Only valid in ATO Mode.
441     * @param acc -2: Hidden, -1: Undershot 0: Accurate 1: Overshot
442     */
443    public void setStoppingAccuracy(int acc){
444        ThreadingUtil.runOnGUI( () -> panelG.setStoppingAccuracy(acc) );
445    }
446
447    /**
448     * Set stopping point text.
449     * Only valid in ATO mode.
450     * @param station the next station.
451     * @param eta ETA of next station.
452     */
453    public void setStoppingPointLabel(String station, String eta){
454        ThreadingUtil.runOnGUI( () -> panelG.setStoppingPointLabel(station, eta) );
455    }
456
457    /**
458     * Set remaining Station Dwell time.
459     * @param mins minutes remaining.
460     * @param secs seconds remaining.
461     */
462    public void setDwellTime(int mins, int secs){
463        ThreadingUtil.runOnGUI( () -> panelG.setDwellTime(mins, secs) );
464    }
465
466    /**
467     * Set Door Icon.
468     * @param mode the icon code to display.
469     * 0: Unset
470     * 10: Request driver to open both sides doors
471     * 11: Request driver to open left doors
472     * 12: Request driver to open right doors
473     * 13: Doors are open
474     * 14: Request driver to close doors
475     * 15: Doors are being closed by ATO
476     * 16: Doors are closed
477     */
478    public void setDoorIcon(int mode){
479        ThreadingUtil.runOnGUI( () -> panelG.setDoorIcon(mode) );
480    }
481
482    /**
483     * Set Skip Stopping Point Icon.
484     * @param mode the icon code to display.
485     * 0: Unset
486     * 17: Skip Stopping Point Inactive
487     * 18: Skip Stopping Point requested by ATO-TS
488     * 19: Skip Stopping Point requested by driver
489     */
490    public void setSkipStoppingPoint(int mode){
491        ThreadingUtil.runOnGUI( () -> panelG.setSkipStoppingPoint(mode) );
492    }
493
494    /**
495     * Set the Direction Symbol and visibility.
496     * @param newDirection -1: Reverse, 0 Hidden, 1 Forwards.
497     */
498    public void setSupervisedDirection(int newDirection) {
499        ThreadingUtil.runOnGUI( () -> panelB.setSupervisedDirection(newDirection) );
500    }
501
502    /**
503     * No value displayed if distance &lt; 1
504     * @param distance in m to stopping area.
505     * 
506     */
507    public void setTunnelStoppingDistance(int distance) {
508        ThreadingUtil.runOnGUI( () -> panelC.setTunnelStoppingDistance(distance) );
509    }
510
511    /**
512     * Set Tunnel Stopping Icon Visible.
513     * @param visible true if visible, false hidden.
514     * @param ack true if Acknowledgement Required.
515     */
516    public void setTunnelStoppingIconVisible(boolean visible, boolean ack){
517        ThreadingUtil.runOnGUI( () -> panelC.setTunnelStoppingIconVisible(visible, ack) );
518    }
519
520    /**
521     * Set the Safe Radio Connection Symbol.
522     * @param newVal -1 default, not displayed.
523     *                0 Connection Lost
524     *                1 Connection OK
525     */
526    public void setSafeRadioConnection(int newVal) {
527        ThreadingUtil.runOnGUI( () -> panelE.setSafeRadioConnection(newVal) );
528    }
529
530    /**
531     * Set the Track Ahead Free? Question visible.
532     * @param newVal true to display, false to hide.
533     */
534    public void setTrackAheadFreeQuestionVisible(boolean newVal) {
535        ThreadingUtil.runOnGUI( () -> panelD.setTrackAheadFreeQuestionVisible(newVal) );
536    }
537
538    /**
539     * Set the Scale on the Planning Area.
540     * 0 : 0 - 1000
541     * 1 : 0 - 2000
542     * 2 : 0 - 4000
543     * 3 : 0 - 8000
544     * 4 : 0 - 16000
545     * 5 : 0 - 32000
546     * @param scale the scale to use.
547     */
548    public void setScale(int scale){
549        ThreadingUtil.runOnGUI( () -> panelD.setScale(scale) );
550    }
551
552    /**
553     * Reset the Movement Authorities to the supplied List.
554     * Existing MAs will be discarded.
555     * @param a List of MAs.
556     */
557    public void resetMovementAuthorities(@Nonnull java.util.List<MovementAuthority> a) {
558        ThreadingUtil.runOnGUI( () -> panelD.resetMovementAuthorities(a) );
559    }
560
561    /**
562     * Get a List of current Movement Authorities.
563     * @return List of MAs.
564     */
565    public java.util.List<MovementAuthority> getMovementAuthorities() {
566        return panelD.getMovementAuthorities();
567    }
568
569    /**
570     * Extend the Movement Authority.
571     * @param dma the Movement Authority to add to existing List.
572     */
573    public void extendMovementAuthorities(@Nonnull MovementAuthority dma){
574        ThreadingUtil.runOnGUI( () -> panelD.extendMovementAuthorities(dma) );
575    }
576
577    /**
578     * Get the next Planning Track Announcement with the Movement Authority.
579     * @param mustBeStation true if only station data is required.
580     * @return the next Announcement, may be null if none within the Movement Authority.
581     */
582    @CheckForNull
583    public TrackCondition getNextAnnouncement(boolean mustBeStation){
584        java.util.List<MovementAuthority> mas = panelD.getMovementAuthorities();
585        for ( MovementAuthority ma : mas ) {
586            java.util.List<TrackSection> tsList = ma.getTrackSections();
587            for ( TrackSection ts : tsList ){
588                java.util.List<TrackCondition> anList = ts.getAnnouncements();
589                for ( TrackCondition tc : anList ){
590                     if ( !mustBeStation || tc instanceof StationTrackCondition ) {
591                        return tc;
592                    }
593                }
594            }
595        }
596        return null;
597    } 
598
599    /**
600     * Advance the train.
601     * Updates planning panel.
602     * Updates distance to target.
603     * @param distance to advance.
604     */
605    public void advance(int distance){
606        ThreadingUtil.runOnGUI( () -> {
607            panelD.advance(distance);
608            panelA.advance(distance);
609        });
610    }
611
612    /**
613     * Send a CabMessage to the Driver.
614     * @param msg the CabMessage to send.
615     */
616    public void messageDriver(CabMessage msg) {
617        ThreadingUtil.runOnGUI( () -> panelE.addMessage(msg) );
618    }
619
620    /**
621     * Remove a previously sent CabMessage from the display.
622     * @param messageId the messageId of the CabMessage to remove.
623     */
624    public void removeMessage(String messageId) {
625        ThreadingUtil.runOnGUI( () -> panelE.removeMessage(messageId) );
626    }
627
628    /**
629     * Play one of the DMI UI Sounds.
630     * <p>
631     * 1 - S1_toofast.wav - 2 secs, plays once.
632     * <p>
633     * 2 - S2_warning.wav - 3 secs, loops until stopped.
634     * <p>
635     * 3 - S_info.wav - 1 sec, plays once.
636     * <p>
637     * 4 - click.wav - 1 sec, plays once.
638     * @param sound which Sound, 
639     */
640    public void playDmiSound(int sound) throws IllegalArgumentException {
641        ResourceUtil.playDmiSound(sound);
642    }
643
644    /**
645     * Stop playing a DMI Sound.
646     * @param sound the sound to Stop, normally 2 which plays in a loop.
647     */
648    public void stopDmiSound(int sound) {
649        ResourceUtil.stopDmiSound(sound);
650    }
651
652    /**
653     * Add a listener to synchronise panel flashing.
654     * @param pcl the listener to add.
655     * @param fast true if fast flashing, false for slow.
656     */
657    protected void addFlashListener( PropertyChangeListener pcl, boolean fast ) {
658        flashTimer.addFlashListener(pcl, fast);
659    }
660
661    /**
662     * Remove a listener from panel Flash timer notifications.
663     * @param pcl the listener to remove.
664     * @param fast true if fast listener, false if slow.
665     */
666    protected void removeFlashListener ( PropertyChangeListener pcl, boolean fast ) {
667        flashTimer.removeFlashListener(pcl, fast);
668    }
669
670    /**
671     * Fire a Property Change from this panel.
672     * Old value fired as empty String.
673     * @param property the property name.
674     * @param newVal the new value.
675     */
676    protected void firePropertyChange(String property, String newVal) {
677        this.firePropertyChange(property, "", newVal);
678    }
679
680    @Override
681    public void setVisible(boolean newVal){
682        ThreadingUtil.runOnGUI( () -> super.setVisible(newVal) );
683    }
684
685    /**
686     * Dispose of any Listeners, e.g. Fast Clock.
687     */
688    public void dispose(){
689        panelG.dispose();
690        panelC.dispose();
691        flashTimer.dispose();
692    }
693
694    // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DmiPanel.class);
695
696}