001package jmri.jmrit.throttle;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Component;
006import java.awt.Container;
007import java.awt.Dimension;
008import java.awt.Graphics;
009import java.awt.Point;
010import java.awt.Rectangle;
011import java.awt.event.ComponentEvent;
012import java.awt.event.ComponentListener;
013import java.awt.event.ContainerEvent;
014import java.awt.event.ContainerListener;
015import java.beans.PropertyVetoException;
016import java.io.File;
017import java.io.IOException;
018import java.net.URI;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022
023import javax.swing.*;
024import javax.swing.event.InternalFrameAdapter;
025import javax.swing.event.InternalFrameEvent;
026
027import jmri.DccThrottle;
028import jmri.InstanceManager;
029import jmri.LocoAddress;
030import jmri.configurexml.LoadXmlConfigAction;
031import jmri.configurexml.StoreXmlConfigAction;
032import jmri.jmrit.XmlFile;
033import jmri.jmrit.jython.Jynstrument;
034import jmri.jmrit.jython.JynstrumentFactory;
035import jmri.jmrit.roster.RosterEntry;
036import jmri.util.FileUtil;
037import jmri.util.iharder.dnd.URIDrop;
038
039import org.jdom2.Document;
040import org.jdom2.Element;
041import org.jdom2.JDOMException;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045/**
046 * Should be named ThrottlePanel but was already existing with that name and
047 * don't want to break dependencies (particularly in Jython code)
048 *
049 * @author Glen Oberhauser
050 * @author Andrew Berridge Copyright 2010
051 */
052public class ThrottleFrame extends JDesktopPane implements ComponentListener, AddressListener {
053
054    private final Integer BACKPANEL_LAYER = Integer.MIN_VALUE;
055    private final Integer PANEL_LAYER_FRAME = 1;
056    private final Integer PANEL_LAYER_PANEL = 2;
057
058    private static final int ADDRESS_PANEL_INDEX = 0;
059    private static final int CONTROL_PANEL_INDEX = 1;
060    private static final int FUNCTION_PANEL_INDEX = 2;
061    private static final int SPEED_DISPLAY_INDEX = 3;
062    private static final int NUM_FRAMES = 4;
063
064    private JInternalFrame[] frameList;
065    private int activeFrame;
066
067    private final ThrottleWindow throttleWindow;
068
069    private ControlPanel controlPanel;
070    private FunctionPanel functionPanel;
071    private AddressPanel addressPanel;
072    private BackgroundPanel backgroundPanel;
073    private FrameListener frameListener;
074    private SpeedPanel speedPanel;
075
076    private String title;
077    private String lastUsedSaveFile = null;
078
079    private boolean isEditMode = true;
080    private boolean willSwitch = false;
081
082    private static final String DEFAULT_THROTTLE_FILENAME = "JMRI_ThrottlePreference.xml";
083
084    public static String getDefaultThrottleFolder() {
085        return FileUtil.getUserFilesPath() + "throttle" + File.separator;
086    }
087
088    public static String getDefaultThrottleFilename() {
089        return getDefaultThrottleFolder() + DEFAULT_THROTTLE_FILENAME;
090    }
091
092    public ThrottleFrame(ThrottleWindow tw) {
093        super();        
094        throttleWindow = tw;
095        if (jmri.InstanceManager.getNullableDefault(ThrottlesPreferences.class) == null) {
096            log.debug("Creating new ThrottlesPreference Instance");
097            jmri.InstanceManager.store(new ThrottlesPreferences(), ThrottlesPreferences.class);
098        }        
099        initGUI();
100        applyPreferences();
101        InstanceManager.getDefault(ThrottleFrameManager.class).getThrottlesListPanel().getTableModel().addThrottleFrame(this);
102    }
103
104    public ThrottleWindow getThrottleWindow() {
105        return throttleWindow;
106    }
107
108    public ControlPanel getControlPanel() {
109        return controlPanel;
110    }
111
112    public FunctionPanel getFunctionPanel() {
113        return functionPanel;
114    }
115
116    public AddressPanel getAddressPanel() {
117        return addressPanel;
118    }
119
120    public RosterEntry getRosterEntry() {
121        return addressPanel.getRosterEntry();
122    }
123
124    public void toFront() {
125        if (throttleWindow == null) {
126            return;
127        }
128        throttleWindow.toFront(title);
129    }
130
131    public SpeedPanel getSpeedPanel() {
132        return speedPanel;
133    }
134
135    /**
136     * Sets the location of a throttle frame on the screen according to x and y
137     * coordinates
138     *
139     * @see java.awt.Component#setLocation(int, int)
140     */
141    @Override
142    public void setLocation(int x, int y) {
143        if (throttleWindow == null) {
144            return;
145        }
146        throttleWindow.setLocation(new Point(x, y));
147    }
148
149    public void setTitle(String txt) {
150        title = txt;
151    }
152
153    public String getTitle() {
154        return title;
155    }
156
157    private void saveThrottle(String sfile) {
158        // Save throttle: title / window position
159        // as strongly linked to extended throttles and roster presence, do not save function buttons and background window as they're stored in the roster entry
160        XmlFile xf = new XmlFile() {
161        };   // odd syntax is due to XmlFile being abstract
162        xf.makeBackupFile(sfile);
163        File file = new File(sfile);
164        try {
165            //The file does not exist, create it before writing
166            File parentDir = file.getParentFile();
167            if (!parentDir.exists()) {
168                if (!parentDir.mkdir()) { // make directory and check result
169                    log.error("could not make parent directory");
170                }
171            }
172            if (!file.createNewFile()) { // create file, check success
173                log.error("createNewFile failed");
174            }
175        } catch (IOException exp) {
176            log.error("Exception while writing the throttle file, may not be complete: {}", exp.getMessage());
177        }
178
179        try {
180            Element root = new Element("throttle-config");
181            Document doc = XmlFile.newDocument(root, XmlFile.getDefaultDtdLocation() + "throttle-config.dtd");
182            // add XSLT processing instruction
183            // <?xml-stylesheet type="text/xsl" href="XSLT/throttle.xsl"?>
184            /*   java.util.Map<String,String> m = new java.util.HashMap<String, String>();
185             m.put("type", "text/xsl");
186             m.put("href", jmri.jmrit.XmlFile.xsltLocation + "throttle.xsl");
187             ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
188             doc.addContent(0,p);*/
189            Element throttleElement = getXml();
190            // don't save the loco address or consist address
191            //   throttleElement.getChild("AddressPanel").removeChild("locoaddress");
192            //   throttleElement.getChild("AddressPanel").removeChild("locoaddress");
193            if ((this.getRosterEntry() != null) &&
194                    (getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml").compareTo(sfile) == 0) // don't save function buttons labels, they're in roster entry
195            {
196                throttleElement.getChild("FunctionPanel").removeChildren("FunctionButton");
197            }
198
199            root.setContent(throttleElement);
200            xf.writeXML(file, doc);
201            setLastUsedSaveFile(sfile);
202        } catch (IOException ex) {
203            log.warn("Exception while storing throttle xml: {}", ex.getMessage());
204        }
205    }
206
207    public void loadThrottle(String sfile) {
208        if (sfile == null) {
209            JFileChooser fileChooser = jmri.jmrit.XmlFile.userFileChooser(Bundle.getMessage("PromptXmlFileTypes"), "xml");
210            fileChooser.setCurrentDirectory(new File(getDefaultThrottleFolder()));
211            fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
212            java.io.File file = LoadXmlConfigAction.getFile(fileChooser);
213            if (file == null) {
214                return;
215            }
216            sfile = file.getAbsolutePath();
217        }
218
219        boolean switchAfter = false;
220        if (!isEditMode) {
221            setEditMode(true);
222            switchAfter = true;
223        }
224
225        try {
226            XmlFile xf = new XmlFile() {
227            };   // odd syntax is due to XmlFile being abstract
228            File f = new File(sfile);
229            Element root = xf.rootFromFile(f);
230            Element conf = root.getChild("ThrottleFrame");
231            // File looks ok
232            setLastUsedSaveFile(sfile);
233            // close all existing Jynstruments
234            Component[] cmps = getComponents();
235            for (Component cmp : cmps) {
236                try {
237                    if (cmp instanceof JInternalFrame) {
238                        JInternalFrame jyf = (JInternalFrame) cmp;
239                        Component[] cmps2 = jyf.getContentPane().getComponents();
240                        for (Component cmp2 : cmps2) {
241                            if (cmp2 instanceof Jynstrument) {
242                                ((Jynstrument) cmp2).exit();
243                                jyf.dispose();
244                            }
245                        }
246                    }
247                } catch (Exception ex) {
248                    log.debug("Got exception (no panic) {}", ex.getMessage());
249                }
250            }
251            // and finally load all preferences
252            setXml(conf);
253        } catch (IOException | JDOMException ex) {
254            log.debug("Loading throttle exception: {}", ex.getMessage());
255        }
256//     checkPosition();
257        if (switchAfter) {
258            setEditMode(false);
259        }
260    }
261
262    /**
263     * Place and initialize the GUI elements.
264     * <ul>
265     * <li> ControlPanel
266     * <li> FunctionPanel
267     * <li> AddressPanel
268     * <li> SpeedPanel
269     * <li> JMenu
270     * </ul>
271     */
272    private void initGUI() {
273        frameListener = new FrameListener();
274
275        controlPanel = new ControlPanel();
276        controlPanel.setResizable(true);
277        controlPanel.setClosable(true);
278        controlPanel.setIconifiable(true);
279        controlPanel.setTitle(Bundle.getMessage("ThrottleMenuViewControlPanel"));
280        controlPanel.pack();
281        controlPanel.setVisible(true);
282        controlPanel.setEnabled(false);
283        controlPanel.addInternalFrameListener(frameListener);
284
285        functionPanel = new FunctionPanel();
286        functionPanel.setResizable(true);
287        functionPanel.setClosable(true);
288        functionPanel.setIconifiable(true);
289        functionPanel.setTitle(Bundle.getMessage("ThrottleMenuViewFunctionPanel"));
290
291        // assumes button width of 54, height of 30 (set in class FunctionButton) with
292        // horiz and vert gaps of 5 each (set in FunctionPanel class)
293        // with 3 buttons across and 6 rows high
294        int width = 3 * (FunctionButton.getButtonWidth()) + 2 * 3 * 5 + 10;   // = 192
295        int height = 8 * (FunctionButton.getButtonHeight()) + 2 * 6 * 5 + 20; // = 240 (but there seems to be another 10 needed for some LAFs)
296
297        functionPanel.setSize(width, height);
298        functionPanel.setLocation(controlPanel.getWidth(), 0);
299        functionPanel.setVisible(true);
300        functionPanel.setEnabled(false);
301        functionPanel.addInternalFrameListener(frameListener);
302
303        speedPanel = new SpeedPanel();
304        speedPanel.setResizable(true);
305        speedPanel.setVisible(false);
306        speedPanel.setClosable(true);
307        speedPanel.setIconifiable(true);
308        speedPanel.setTitle(Bundle.getMessage("ThrottleMenuViewSpeedPanel"));
309        speedPanel.addInternalFrameListener(frameListener);
310        speedPanel.pack();
311
312        addressPanel = new AddressPanel();
313        addressPanel.setResizable(true);
314        addressPanel.setClosable(true);
315        addressPanel.setIconifiable(true);
316        addressPanel.setTitle(Bundle.getMessage("ThrottleMenuViewAddressPanel"));
317        addressPanel.pack();
318        if (addressPanel.getWidth()<functionPanel.getWidth()) {
319            addressPanel.setSize(functionPanel.getWidth(),addressPanel.getHeight());
320        }
321        addressPanel.setLocation(controlPanel.getWidth(), functionPanel.getHeight());
322        addressPanel.setVisible(true);
323        addressPanel.addInternalFrameListener(frameListener);
324        functionPanel.setAddressPanel(addressPanel); // so the function panel can get access to the roster
325        controlPanel.setAddressPanel(addressPanel);
326        speedPanel.setAddressPanel(addressPanel);
327
328        if (controlPanel.getHeight() < functionPanel.getHeight() + addressPanel.getHeight()) {
329            controlPanel.setSize(controlPanel.getWidth(), functionPanel.getHeight() + addressPanel.getHeight());
330        }
331        if (controlPanel.getHeight() > functionPanel.getHeight() + addressPanel.getHeight()) {
332            addressPanel.setSize(addressPanel.getWidth(), controlPanel.getHeight() - functionPanel.getHeight());
333        }
334        if (functionPanel.getWidth() < addressPanel.getWidth()) {
335            functionPanel.setSize(addressPanel.getWidth(), functionPanel.getHeight());
336        }
337
338        speedPanel.setSize(addressPanel.getWidth() + controlPanel.getWidth(), addressPanel.getHeight() / 2);
339        speedPanel.setLocation(0, controlPanel.getHeight());
340
341        addressPanel.addAddressListener(controlPanel);
342        addressPanel.addAddressListener(functionPanel);
343        addressPanel.addAddressListener(speedPanel);
344        addressPanel.addAddressListener(this);
345        addressPanel.addAddressListener(InstanceManager.getDefault(ThrottleFrameManager.class).getThrottlesListPanel().getTableModel());
346
347        add(controlPanel, PANEL_LAYER_FRAME);
348        add(functionPanel, PANEL_LAYER_FRAME);
349        add(addressPanel, PANEL_LAYER_FRAME);
350        add(speedPanel, PANEL_LAYER_FRAME);
351        
352        backgroundPanel = new BackgroundPanel();
353        backgroundPanel.setAddressPanel(addressPanel); // reusing same way to do it than existing thing in functionPanel
354        addComponentListener(backgroundPanel); // backgroudPanel warned when desktop resized
355        addressPanel.addAddressListener(backgroundPanel);
356        addressPanel.setBackgroundPanel(backgroundPanel); // so that it's changeable when browsing through rosters
357        add(backgroundPanel, BACKPANEL_LAYER);
358            
359        addComponentListener(this); // to force sub windows repositionning
360           
361        frameList = new JInternalFrame[NUM_FRAMES];
362        frameList[ADDRESS_PANEL_INDEX] = addressPanel;
363        frameList[CONTROL_PANEL_INDEX] = controlPanel;
364        frameList[FUNCTION_PANEL_INDEX] = functionPanel;
365        frameList[SPEED_DISPLAY_INDEX] = speedPanel;
366        activeFrame = ADDRESS_PANEL_INDEX;
367
368        setPreferredSize(new Dimension(Math.max(controlPanel.getWidth() + functionPanel.getWidth(), controlPanel.getWidth() + addressPanel.getWidth()),
369                Math.max(addressPanel.getHeight() + functionPanel.getHeight(), controlPanel.getHeight())));
370
371        // #JYNSTRUMENT# Bellow prepare drag'n drop receptacle:
372        new URIDrop(this, uris -> {
373                if (isEditMode) {
374                    for (URI uri : uris ) {
375                        ynstrument(new File(uri).getPath());
376                    }
377                }
378            });
379           
380        try {
381            addressPanel.setSelected(true);
382        } catch (PropertyVetoException ex) {
383            log.error("Error selecting InternalFrame: {}", ex.getMessage());
384        }
385    }
386
387    // #JYNSTRUMENT# here instantiate the Jynstrument, put it in a component, initialize the context and start it
388    public JInternalFrame ynstrument(String path) {
389        if (path == null) {
390            return null;
391        }
392        Jynstrument it = JynstrumentFactory.createInstrument(path, this); // everything is there
393        if (it == null) {
394            log.error("Error while creating Jynstrument {}", path);
395            return null;
396        }
397        setTransparentBackground(it);
398        JInternalFrame newiFrame = new JInternalFrame(it.getClassName());
399        newiFrame.add(it);
400        newiFrame.addInternalFrameListener(frameListener);
401        newiFrame.setDoubleBuffered(true);
402        newiFrame.setResizable(true);
403        newiFrame.setClosable(true);
404        newiFrame.setIconifiable(true);
405        newiFrame.getContentPane().addContainerListener(new ContainerListener() {
406            @Override
407            public void componentAdded(ContainerEvent e) {
408            }
409
410            @Override
411            public void componentRemoved(ContainerEvent e) {
412                Container c = e.getContainer();
413                while ((!(c instanceof JInternalFrame)) && (!(c instanceof TranslucentJPanel))) {
414                    c = c.getParent();
415                }
416                c.setVisible(false);
417                remove(c);
418                repaint();
419            }
420        });
421        newiFrame.pack();
422        add(newiFrame, PANEL_LAYER_FRAME);
423        newiFrame.setVisible(true);
424        return newiFrame;
425    }
426
427    // make sure components are inside this frame bounds
428    private void checkPosition(Component comp) {
429        if ((this.getWidth() < 1) || (this.getHeight() < 1)) {
430            return;
431        }
432
433        Rectangle pos = comp.getBounds();
434
435        if (pos.width > this.getWidth()) { // Component largest than container
436            pos.width = this.getWidth() - 2;
437            pos.x = 1;
438        }
439        if (pos.x + pos.width > this.getWidth()) // Component to large
440        {
441            pos.x = this.getWidth() - pos.width - 1;
442        }
443        if (pos.x < 0) // Component to far on the left
444        {
445            pos.x = 1;
446        }
447
448        if (pos.height > this.getHeight()) { // Component higher than container
449            pos.height = this.getHeight() - 2;
450            pos.y = 1;
451        }
452        if (pos.y + pos.height > this.getHeight()) // Component to low
453        {
454            pos.y = this.getHeight() - pos.height - 1;
455        }
456        if (pos.y < 0) // Component to high
457        {
458            pos.y = 1;
459        }
460
461        comp.setBounds(pos);
462    }
463
464    public void makeAllComponentsInBounds() {
465        Component[] cmps = getComponents();
466        for (Component cmp : cmps) {
467            checkPosition(cmp);
468        }
469    }
470
471    private HashMap<Container, JInternalFrame> contentPanes;
472
473    public void applyPreferences() {      
474        ThrottlesPreferences preferences = InstanceManager.getDefault(ThrottlesPreferences.class);
475        
476        backgroundPanel.setVisible(  (preferences.isUsingExThrottle()) && (preferences.isUsingRosterImage()));
477        
478        controlPanel.applyPreferences();
479        functionPanel.applyPreferences();
480        addressPanel.applyPreferences();
481        backgroundPanel.applyPreferences();        
482    }
483
484    private static class TranslucentJPanel extends JPanel {
485
486        private final Color TRANS_COL = new Color(100, 100, 100, 100);
487
488        public TranslucentJPanel() {
489            super();
490            setOpaque(false);
491        }
492
493        @Override
494        public void paintComponent(Graphics g) {
495            g.setColor(TRANS_COL);
496            g.fillRoundRect(0, 0, getSize().width, getSize().height, 10, 10);
497            super.paintComponent(g);
498        }
499    }
500
501    private void playRendering() {
502        Component[] cmps = getComponentsInLayer(PANEL_LAYER_FRAME);
503        contentPanes = new HashMap<>();
504        for (Component cmp : cmps) {
505            if ((cmp instanceof JInternalFrame) && (cmp.isVisible())) {
506                translude((JInternalFrame)cmp);
507            }
508        }
509    }
510    
511    private void translude(JInternalFrame jif) {
512        Dimension cpSize = jif.getContentPane().getSize();
513        Point cpLoc = jif.getContentPane().getLocationOnScreen();
514        TranslucentJPanel pane = new TranslucentJPanel();
515        pane.setLayout(new BorderLayout());
516        contentPanes.put(pane, jif);
517        pane.add(jif.getContentPane(), BorderLayout.CENTER);
518        setTransparent(pane, true);
519        jif.setContentPane(new JPanel());
520        jif.setVisible(false);
521        Point loc = new Point(cpLoc.x - this.getLocationOnScreen().x, cpLoc.y - this.getLocationOnScreen().y);
522        add(pane, PANEL_LAYER_PANEL);
523        pane.setLocation(loc);
524        pane.setSize(cpSize);
525    }
526
527    private void editRendering() {
528        Component[] cmps = getComponentsInLayer(PANEL_LAYER_PANEL);
529        for (Component cmp : cmps) {
530            if (cmp instanceof JPanel) {
531                JPanel pane = (JPanel) cmp;
532                JInternalFrame jif = contentPanes.get(pane);
533                jif.setContentPane((Container) pane.getComponent(0));
534                setTransparent(jif, false);
535                jif.setVisible(true);
536                remove(pane);
537            }
538        }
539    }
540
541    public void setEditMode(boolean mode) {
542        if (mode == isEditMode)
543            return;
544        if (isVisible()) {
545            if (!mode) {
546                playRendering();
547            } else {
548                editRendering();
549            }
550            isEditMode = mode;
551            willSwitch = false;
552        } else {
553            willSwitch = true;
554        }
555        throttleWindow.updateGUI();
556    }
557
558    public boolean getEditMode() {
559        return isEditMode;
560    }
561
562    /**
563     * @deprecated since 4.19.5; use {@link #setEditMode(boolean)} instead
564     */
565    @Deprecated
566    public void switchMode() {
567        setEditMode(!isEditMode);
568    }
569
570    /**
571     * Handle my own destruction.
572     * <ol>
573     * <li> dispose of sub windows.
574     * <li> notify my manager of my demise.
575     * </ol>
576     */
577    public void dispose() {
578        log.debug("Disposing {}", getTitle());
579        addressPanel.removeAddressListener(this);
580        InstanceManager.getDefault(ThrottleFrameManager.class).getThrottlesListPanel().getTableModel().removeThrottleFrame(this, addressPanel.getCurrentAddress());
581        // check for any special disposing in InternalFrames
582        controlPanel.destroy();
583        functionPanel.destroy();
584        speedPanel.destroy();
585        // dispose of this last because it will release and destroy throttle.
586        addressPanel.destroy();
587    }
588
589    public void saveRosterChanges() {
590        RosterEntry rosterEntry = addressPanel.getRosterEntry();
591        if (rosterEntry == null) {
592            JOptionPane.showMessageDialog(this, Bundle.getMessage("ThrottleFrameNoRosterItemMessageDialog"), Bundle.getMessage("ThrottleFrameNoRosterItemTitleDialog"), JOptionPane.ERROR_MESSAGE);
593            return;
594        }
595        if (JOptionPane.showConfirmDialog(this, Bundle.getMessage("ThrottleFrameRosterChangeMesageDialog"), Bundle.getMessage("ThrottleFrameRosterChangeTitleDialog"), JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
596            return;
597        }
598        functionPanel.saveFunctionButtonsToRoster(rosterEntry);
599        controlPanel.saveToRoster(rosterEntry);
600    }
601
602    /**
603     * An extension of InternalFrameAdapter for listening to the closing of of
604     * this frame's internal frames.
605     *
606     * @author glen
607     */
608    class FrameListener extends InternalFrameAdapter {
609
610        /**
611         * Listen for the closing of an internal frame and set the "View" menu
612         * appropriately. Then hide the closing frame
613         *
614         * @param e The InternalFrameEvent leading to this action
615         */
616        @Override
617        public void internalFrameClosing(InternalFrameEvent e) {
618            if (e.getSource() == controlPanel) {
619                throttleWindow.getViewControlPanel().setSelected(false);
620                controlPanel.setVisible(false);
621            } else if (e.getSource() == addressPanel) {
622                throttleWindow.getViewAddressPanel().setSelected(false);
623                addressPanel.setVisible(false);
624            } else if (e.getSource() == functionPanel) {
625                throttleWindow.getViewFunctionPanel().setSelected(false);
626                functionPanel.setVisible(false);
627            } else if (e.getSource() == speedPanel) {
628                throttleWindow.getViewSpeedPanel().setSelected(false);
629                speedPanel.setVisible(false);
630            } else {
631                try { // #JYNSTRUMENT#, Very important, clean the Jynstrument
632                    if ((e.getSource() instanceof JInternalFrame)) {
633                        Component[] cmps = ((JInternalFrame) e.getSource()).getContentPane().getComponents();
634                        int i = 0;
635                        while ((i < cmps.length) && (!(cmps[i] instanceof Jynstrument))) {
636                            i++;
637                        }
638                        if ((i < cmps.length) && (cmps[i] instanceof Jynstrument)) {
639                            ((Jynstrument) cmps[i]).exit();
640                        }
641                    }
642                } catch (Exception exc) {
643                    log.debug("Got exception, can ignore: ", exc);
644                }
645            }
646        }
647
648        /**
649         * Listen for the activation of an internal frame record this property
650         * for correct processing of the frame cycling key.
651         *
652         * @param e The InternalFrameEvent leading to this action
653         */
654        @Override
655        public void internalFrameActivated(InternalFrameEvent e) {
656            if (e.getSource() == controlPanel) {
657                activeFrame = CONTROL_PANEL_INDEX;
658            } else if (e.getSource() == addressPanel) {
659                activeFrame = ADDRESS_PANEL_INDEX;
660            } else if (e.getSource() == functionPanel) {
661                activeFrame = FUNCTION_PANEL_INDEX;
662            } else if (e.getSource() == functionPanel) {
663                activeFrame = SPEED_DISPLAY_INDEX;
664            }
665        }
666    }
667
668    /**
669     * Collect the prefs of this object into XML Element
670     * <ul>
671     * <li> Window prefs
672     * <li> ControlPanel
673     * <li> FunctionPanel
674     * <li> AddressPanel
675     * <li> SpeedPanel
676     * </ul>
677     *
678     *
679     * @return the XML of this object.
680     */
681    public Element getXml() {
682        boolean switchAfter = false;
683        if (!isEditMode) {
684            setEditMode(true);
685            switchAfter = true;
686        }
687
688        Element me = new Element("ThrottleFrame");
689
690        if (((javax.swing.plaf.basic.BasicInternalFrameUI) getControlPanel().getUI()).getNorthPane() != null) {
691            Dimension bDim = ((javax.swing.plaf.basic.BasicInternalFrameUI) getControlPanel().getUI()).getNorthPane().getPreferredSize();
692            me.setAttribute("border", Integer.toString(bDim.height));
693        }
694
695        ArrayList<Element> children = new ArrayList<>(1);
696
697//        children.add(WindowPreferences.getPreferences(this));  // not required as it is in ThrottleWindow
698        children.add(controlPanel.getXml());
699        children.add(functionPanel.getXml());
700        children.add(addressPanel.getXml());
701        children.add(speedPanel.getXml());
702        // Save Jynstruments
703        Component[] cmps = getComponents();
704        for (Component cmp : cmps) {
705            try {
706                if (cmp instanceof JInternalFrame) {
707                    Component[] cmps2 = ((JInternalFrame) cmp).getContentPane().getComponents();
708                    int j = 0;
709                    while ((j < cmps2.length) && (!(cmps2[j] instanceof Jynstrument))) {
710                        j++;
711                    }
712                    if ((j < cmps2.length) && (cmps2[j] instanceof Jynstrument)) {
713                        Jynstrument jyn = (Jynstrument) cmps2[j];
714                        Element elt = new Element("Jynstrument");
715                        elt.setAttribute("JynstrumentFolder", FileUtil.getPortableFilename(jyn.getFolder()));
716                        ArrayList<Element> jychildren = new ArrayList<>(1);
717                        jychildren.add(WindowPreferences.getPreferences((JInternalFrame) cmp));
718                        Element je = jyn.getXml();
719                        if (je != null) {
720                            jychildren.add(je);
721                        }
722                        elt.setContent(jychildren);
723                        children.add(elt);
724                    }
725                }
726            } catch (Exception ex) {
727                log.debug("Got exception (no panic) {}", ex.getMessage());
728            }
729        }
730        me.setContent(children);
731        if (switchAfter) {
732            setEditMode(false);
733        }
734        return me;
735    }
736
737    public Element getXmlFile() {
738        if (getLastUsedSaveFile() == null) { // || (getRosterEntry()==null))
739            return null;
740        }
741        Element me = new Element("ThrottleFrame");
742        me.setAttribute("ThrottleXMLFile", FileUtil.getPortableFilename(getLastUsedSaveFile()));
743        return me;
744    }
745
746    /**
747     * Set the preferences based on the XML Element.
748     * <ul>
749     * <li> Window prefs
750     * <li> Frame title
751     * <li> ControlPanel
752     * <li> FunctionPanel
753     * <li> AddressPanel
754     * <li> SpeedPanel
755     * </ul>
756     *
757     * @param e The Element for this object.
758     */
759    public void setXml(Element e) {
760        if (e == null) {
761            return;
762        }
763
764        String sfile = e.getAttributeValue("ThrottleXMLFile");
765        if (sfile != null) {
766            loadThrottle(FileUtil.getExternalFilename(sfile));
767            return;
768        }
769
770        boolean switchAfter = false;
771        if (!isEditMode) {
772            setEditMode(true);
773            switchAfter = true;
774        }
775
776        int bSize = 23;
777        // Get InternalFrame border size
778        if (e.getAttribute("border") != null) {
779            bSize = Integer.parseInt((e.getAttribute("border").getValue()));
780        }
781        if (e.getChild("window") != null) { // Old format
782            throttleWindow.setXml(e);
783        }
784        Element controlPanelElement = e.getChild("ControlPanel");
785        controlPanel.setXml(controlPanelElement);
786        if (((javax.swing.plaf.basic.BasicInternalFrameUI) controlPanel.getUI()).getNorthPane() != null) {
787            ((javax.swing.plaf.basic.BasicInternalFrameUI) controlPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize));
788        }
789        Element functionPanelElement = e.getChild("FunctionPanel");
790        functionPanel.setXml(functionPanelElement);
791        if (((javax.swing.plaf.basic.BasicInternalFrameUI) functionPanel.getUI()).getNorthPane() != null) {
792            ((javax.swing.plaf.basic.BasicInternalFrameUI) functionPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize));
793        }
794        Element addressPanelElement = e.getChild("AddressPanel");
795        addressPanel.setXml(addressPanelElement);
796        if (((javax.swing.plaf.basic.BasicInternalFrameUI) addressPanel.getUI()).getNorthPane() != null) {
797            ((javax.swing.plaf.basic.BasicInternalFrameUI) addressPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize));
798        }
799        Element speedPanelElement = e.getChild("SpeedPanel");
800        if (speedPanelElement != null) { // older throttle configs may not have this element
801            speedPanel.setXml(speedPanelElement);
802            if (((javax.swing.plaf.basic.BasicInternalFrameUI) speedPanel.getUI()).getNorthPane() != null) {
803                ((javax.swing.plaf.basic.BasicInternalFrameUI) speedPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize));
804            }
805        }
806
807        List<Element> jinsts = e.getChildren("Jynstrument");
808        if ((jinsts != null) && (jinsts.size() > 0)) {
809            for (Element jinst : jinsts) {
810                JInternalFrame jif = ynstrument(FileUtil.getExternalFilename(jinst.getAttributeValue("JynstrumentFolder")));
811                Element window = jinst.getChild("window");
812                if (jif != null) {
813                    if (window != null) {
814                        WindowPreferences.setPreferences(jif, window);
815                    }
816                    Component[] cmps2 = jif.getContentPane().getComponents();
817                    int j = 0;
818                    while ((j < cmps2.length) && (!(cmps2[j] instanceof Jynstrument))) {
819                        j++;
820                    }
821                    if ((j < cmps2.length) && (cmps2[j] instanceof Jynstrument)) {
822                        ((Jynstrument) cmps2[j]).setXml(jinst);
823                    }
824
825                    jif.repaint();
826                }
827            }
828        }
829        setFrameTitle();
830        if (switchAfter) {
831            setEditMode(false);
832        }
833    }
834
835    /**
836     * setFrameTitle - set the frame title based on type, text and address
837     */
838    public void setFrameTitle() {
839        String addr = Bundle.getMessage("ThrottleTitle");
840        if (addressPanel.getThrottle() != null) {
841            addr = addressPanel.getCurrentAddress().toString();
842        }
843        if (throttleWindow.getTitleTextType().compareTo("address") == 0) {
844            throttleWindow.setTitle(addr);
845        } else if (throttleWindow.getTitleTextType().compareTo("text") == 0) {
846            throttleWindow.setTitle(throttleWindow.getTitleText());
847        } else if (throttleWindow.getTitleTextType().compareTo("addressText") == 0) {
848            throttleWindow.setTitle(addr + " " + throttleWindow.getTitleText());
849        } else if (throttleWindow.getTitleTextType().compareTo("textAddress") == 0) {
850            throttleWindow.setTitle(throttleWindow.getTitleText() + " " + addr);
851        } else if (throttleWindow.getTitleTextType().compareTo("rosterID") == 0) {
852            if ((addressPanel.getRosterEntry() != null) && (addressPanel.getRosterEntry().getId() != null)
853                    && (addressPanel.getRosterEntry().getId().length() > 0)) {
854                throttleWindow.setTitle(addressPanel.getRosterEntry().getId());
855            } else {
856                throttleWindow.setTitle(addr);
857            }
858        }
859    }
860
861    @Override
862    public void componentHidden(ComponentEvent e) {
863    }
864
865    @Override
866    public void componentMoved(ComponentEvent e) {
867    }
868
869    @Override
870    public void componentResized(ComponentEvent e) {
871//  checkPosition ();
872    }
873
874    @Override
875    public void componentShown(ComponentEvent e) {
876        throttleWindow.setCurrentThrottleFrame(this);
877        if (willSwitch) {
878            setEditMode(this.throttleWindow.getEditMode());
879            repaint();
880        }
881        throttleWindow.updateGUI();
882        // Make sure the throttle frame as the focus, to receive keyboard inputs        
883        Component src = (Component) e.getSource();
884        src.requestFocusInWindow();
885    }
886
887    public void saveThrottle() {
888        if (getRosterEntry() != null) {
889            saveThrottle(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml");
890        } else if (getLastUsedSaveFile() != null) {
891            saveThrottle(getLastUsedSaveFile());
892        }
893    }
894
895    public void saveThrottleAs() {
896        JFileChooser fileChooser = jmri.jmrit.XmlFile.userFileChooser(Bundle.getMessage("PromptXmlFileTypes"), "xml");
897        fileChooser.setCurrentDirectory(new File(getDefaultThrottleFolder()));
898        fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
899        java.io.File file = StoreXmlConfigAction.getFileName(fileChooser);
900        if (file == null) {
901            return;
902        }
903        saveThrottle(file.getAbsolutePath());
904    }
905   
906    public void activateNextJInternalFrame() {
907        try {
908            activeFrame = (activeFrame + 1) % NUM_FRAMES;
909            frameList[activeFrame].setSelected(true);
910        } catch (PropertyVetoException ex) {
911            log.warn("Exception selecting internal frame:{}", ex.getMessage());
912        }
913    }
914    
915    public void activatePreviousJInternalFrame() {
916        try {
917            activeFrame--;
918            if (activeFrame < 0) {
919                activeFrame = NUM_FRAMES - 1;
920            }
921            frameList[activeFrame].setSelected(true);
922        } catch (PropertyVetoException ex) {
923            log.warn("Exception selecting internal frame:{}", ex.getMessage());
924        }
925    }
926
927    @Override
928    public void notifyAddressChosen(LocoAddress l) {
929    }
930
931    @Override
932    public void notifyAddressReleased(LocoAddress la) {
933        setLastUsedSaveFile(null);
934        setFrameTitle();
935        throttleWindow.updateGUI();
936    }
937
938    @Override
939    public void notifyAddressThrottleFound(DccThrottle throttle) {
940        if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle())
941                && (InstanceManager.getDefault(ThrottlesPreferences.class).isAutoLoading())
942                && (addressPanel != null) && (addressPanel.getRosterEntry() != null)
943                && ((getLastUsedSaveFile() == null) || (getLastUsedSaveFile().compareTo(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml") != 0))) {
944            loadThrottle(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml");
945        }
946        setFrameTitle();
947        throttleWindow.updateGUI();
948    }
949
950    @Override
951    public void notifyConsistAddressChosen(int newAddress, boolean isLong) {
952    }
953
954    @Override
955    public void notifyConsistAddressReleased(int address, boolean isLong) {
956    }
957
958    @Override
959    public void notifyConsistAddressThrottleFound(DccThrottle throttle) {
960    }
961
962    public String getLastUsedSaveFile() {
963        return lastUsedSaveFile;
964    }
965
966    public void setLastUsedSaveFile(String lusf) {
967        lastUsedSaveFile = lusf;
968        throttleWindow.updateGUI();
969    }
970
971    // some utilities to turn a component background transparent
972    public static void setTransparentBackground(JComponent jcomp) {
973        if (jcomp instanceof JPanel) //OS X: Jpanel components are enough
974        {
975            jcomp.setBackground(new Color(0, 0, 0, 0));
976        }
977        setTransparentBackground(jcomp.getComponents());
978    }
979
980    public static void setTransparentBackground(Component[] comps) {
981        for (Component comp : comps) {
982            try {
983                if (comp instanceof JComponent) {
984                    setTransparentBackground((JComponent) comp);
985                }
986            } catch (Exception e) {
987                // Do nothing, just go on
988            }
989        }
990    }
991
992// some utilities to turn a component background transparent
993    public static void setTransparent(JComponent jcomp) {
994        setTransparent(jcomp, true);
995    }
996
997    public static void setTransparent(JComponent jcomp, boolean transparency) {
998        if (jcomp instanceof JPanel) { //OS X: Jpanel components are enough
999            jcomp.setOpaque(!transparency);
1000        }
1001        setTransparent(jcomp.getComponents(), transparency);
1002    }
1003
1004    private static void setTransparent(Component[] comps, boolean transparency) {
1005        for (Component comp : comps) {
1006            try {
1007                if (comp instanceof JComponent) {
1008                    setTransparent((JComponent) comp, transparency);
1009                }
1010            } catch (Exception e) {
1011                // Do nothing, just go on
1012            }
1013        }
1014    }
1015
1016    private final static Logger log = LoggerFactory.getLogger(ThrottleFrame.class);
1017}