001package jmri.jmrit.throttle;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.beans.PropertyChangeListener;
006import java.beans.PropertyVetoException;
007import java.io.File;
008import java.net.URI;
009import java.util.*;
010import java.util.List;
011
012import javax.annotation.CheckForNull;
013import javax.swing.*;
014
015import jmri.*;
016import jmri.jmrit.catalog.NamedIcon;
017import jmri.jmrit.jython.Jynstrument;
018import jmri.jmrit.jython.JynstrumentFactory;
019import jmri.util.FileUtil;
020import jmri.util.JmriJFrame;
021import jmri.util.iharder.dnd.URIDrop;
022import jmri.util.swing.TransparencyUtils;
023
024import org.jdom2.Element;
025import org.jdom2.Attribute;
026
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030// Should be named ThrottleFrame, but ThrottleFrame already exit, hence ThrottleWindow
031public class ThrottleWindow extends JmriJFrame implements ThrottleControllersUIContainer {
032
033    private final jmri.jmrix.ConnectionConfig connectionConfig;
034    private final ThrottleManager throttleManager;
035    private final ThrottleFrameManager throttleFrameManager = InstanceManager.getDefault(ThrottleFrameManager.class);
036
037    private JPanel throttlesPanel;
038    private ThrottleFrame currentThrottleFrame;
039    private CardLayout throttlesLayout;
040
041    private JCheckBoxMenuItem viewControlPanel;
042    private JCheckBoxMenuItem viewFunctionPanel;
043    private JCheckBoxMenuItem viewAddressPanel;
044    private JCheckBoxMenuItem viewSpeedPanel;
045    private JMenuItem viewAllButtons;
046    private JMenuItem fileMenuSave;
047    private JMenuItem editMenuExportRoster;
048
049    private JButton jbPrevious = null;
050    private JButton jbNext = null;
051    private JButton jbPreviousRunning = null;
052    private JButton jbNextRunning = null;
053    private JButton jbThrottleList = null;
054    private JButton jbNew = null;
055    private JButton jbClose = null;
056    private JButton jbMode = null;
057    private JToolBar throttleToolBar;
058
059    private String titleText = "";
060    private String titleTextType = "rosterID";
061    private boolean isEditMode = true;
062
063    private final PowerManager powerMgr;
064    private SmallPowerManagerButton smallPowerMgmtButton;
065
066    private final ThrottleWindowActionsFactory myActionFactory;
067
068    private ArrayList<ThrottleFrame> throttleFrames = new ArrayList<>(5);
069
070    java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this);
071
072    /**
073     * Default constructor
074     */
075    public ThrottleWindow() {
076        this((jmri.jmrix.ConnectionConfig) null);
077    }
078
079    /**
080     * Constructor
081     * @param connectionConfig the connection config
082     */
083    public ThrottleWindow(jmri.jmrix.ConnectionConfig connectionConfig) {
084        super(Bundle.getMessage("ThrottleTitle"));
085        this.connectionConfig = connectionConfig;
086        if (connectionConfig != null) {
087            this.throttleManager = connectionConfig.getAdapter().getSystemConnectionMemo().get(jmri.ThrottleManager.class);
088        } else {
089            this.throttleManager = InstanceManager.getDefault(jmri.ThrottleManager.class);
090        }
091
092        myActionFactory = new ThrottleWindowActionsFactory(this);
093        powerMgr = InstanceManager.getNullableDefault(PowerManager.class);
094        if (powerMgr == null) {
095            log.info("No power manager instance found, panel not active");
096        }
097        pcs.addPropertyChangeListener(throttleFrameManager.getThrottlesListPanel().getTableModel());        
098        initGUI();
099        applyPreferences();
100    }
101
102    /**
103     * Create a ThrottleWindow
104     * @param e the xml element for the throttle window
105     * @return the throttle window
106     */
107    public static ThrottleWindow createThrottleWindow(Element e) {
108        jmri.jmrix.ConnectionConfig connectionConfig = null;
109
110        Attribute systemPrefixAttr = e.getAttribute("systemPrefix");
111        if (systemPrefixAttr != null) {
112            String systemPrefix = systemPrefixAttr.getValue();
113            // Set connectionConfig to null in case the systemPrefix
114            // points to a connection that doesn't exist anymore.
115
116            for (jmri.jmrix.ConnectionConfig c : InstanceManager.getDefault(jmri.jmrix.ConnectionConfigManager.class)) {
117                if (c.getAdapter().getSystemPrefix().equals(systemPrefix)) {
118                    connectionConfig = c;
119                }
120            }
121        }
122
123        ThrottleWindow tw = new ThrottleWindow(connectionConfig);
124        tw.setXml(e);
125        return tw;
126    }
127
128    private void initGUI() {
129        setTitle(Bundle.getMessage("ThrottleTitle"));
130        setLayout(new BorderLayout());
131        throttlesLayout = new CardLayout();
132        throttlesPanel = new JPanel(throttlesLayout);
133        throttlesPanel.setDoubleBuffered(true);
134
135        initializeToolbar();
136        initializeMenu();
137
138        String txt = "ThrottleFrame-" + throttleFrameManager.generateUniqueFrameID();
139        setCurrentThrottleFrame(new ThrottleFrame(this, throttleManager));
140        getCurrentThrottleFrame().setTitle(txt);
141        throttlesPanel.add(txt, getCurrentThrottleFrame());
142        throttleFrames.add(getCurrentThrottleFrame());
143        add(throttlesPanel, BorderLayout.CENTER);
144
145        installInputsListenerOnAllComponents(this);
146        // to get something to put focus on
147        getRootPane().setFocusable(true);
148
149        ActionMap am = myActionFactory.buildActionMap();
150        for (Object k : am.allKeys()) {
151            getRootPane().getActionMap().put(k, am.get(k));
152        }
153        
154        addMouseWheelListener( new ThrottleWindowInputsListener(this) );
155
156        this.addWindowListener(new WindowAdapter() {
157            @Override
158            public void windowClosing(WindowEvent e) {
159                ThrottleWindow me = (ThrottleWindow) e.getSource();
160                throttleFrameManager.requestThrottleWindowDestruction(me);
161                if (throttleToolBar != null) {
162                    Component[] cmps = throttleToolBar.getComponents();
163                    if (cmps != null) {
164                        for (Component cmp : cmps) {
165                            if (cmp instanceof Jynstrument) {
166                                ((Jynstrument) cmp).exit();
167                            }
168                        }
169                    }
170                }
171            }
172
173            @Override
174            public void windowOpened(WindowEvent e) {
175                try { // on initial open, force selection of address panel
176                    getCurrentThrottleFrame().getAddressPanel().setSelected(true);
177                } catch (PropertyVetoException ex) {
178                    log.warn("Unable to force selection of address panel", ex);
179                }
180            }
181        });
182        updateGUI();
183    }
184
185    public void updateGUI() {
186        if (getCurrentThrottleFrame() == null) {
187            return;
188        }
189        // title bar
190        getCurrentThrottleFrame().setFrameTitle();
191        // menu items
192        viewAddressPanel.setEnabled(isEditMode);
193        viewControlPanel.setEnabled(isEditMode);
194        viewFunctionPanel.setEnabled(isEditMode);
195        viewSpeedPanel.setEnabled(isEditMode);
196        if (isEditMode) {
197            viewAddressPanel.setSelected(getCurrentThrottleFrame().getAddressPanel().isVisible());
198            viewControlPanel.setSelected(getCurrentThrottleFrame().getControlPanel().isVisible());
199            viewFunctionPanel.setSelected(getCurrentThrottleFrame().getFunctionPanel().isVisible());
200            viewSpeedPanel.setSelected(getCurrentThrottleFrame().getSpeedPanel().isVisible());
201        }
202        fileMenuSave.setEnabled(getCurrentThrottleFrame().getLastUsedSaveFile() != null || getCurrentThrottleFrame().getRosterEntry() != null);
203        editMenuExportRoster.setEnabled(getCurrentThrottleFrame().getRosterEntry() != null);
204        // toolbar items
205        if (jbPrevious != null) // means toolbar enabled
206        {
207            if (throttleFrames.size() > 1) {
208                jbPrevious.setEnabled(true);
209                jbNext.setEnabled(true);
210                jbClose.setEnabled(true);
211                jbPreviousRunning.setEnabled(true);
212                jbNextRunning.setEnabled(true);
213            } else {
214                jbPrevious.setEnabled(false);
215                jbNext.setEnabled(false);
216                jbClose.setEnabled(false);
217                jbPreviousRunning.setEnabled(false);
218                jbNextRunning.setEnabled(false);
219            }
220        }
221        getRootPane().requestFocusInWindow();
222    }
223
224    private void initializeToolbar() {
225        throttleToolBar = new JToolBar("Throttles toolbar");
226
227        jbNew = new JButton();
228        //    nouveau.setText(Bundle.getMessage("ThrottleToolBarNew"));
229        jbNew.setIcon(new NamedIcon("resources/icons/throttles/add.png", "resources/icons/throttles/add.png"));
230        jbNew.setToolTipText(Bundle.getMessage("ThrottleToolBarNewToolTip"));
231        jbNew.setVerticalTextPosition(JButton.BOTTOM);
232        jbNew.setHorizontalTextPosition(JButton.CENTER);
233        jbNew.addActionListener(e -> newThrottleController());
234        throttleToolBar.add(jbNew);
235
236        jbClose = new JButton();
237//     close.setText(Bundle.getMessage("ThrottleToolBarClose"));
238        jbClose.setIcon(new NamedIcon("resources/icons/throttles/remove.png", "resources/icons/throttles/remove.png"));
239        jbClose.setToolTipText(Bundle.getMessage("ThrottleToolBarCloseToolTip"));
240        jbClose.setVerticalTextPosition(JButton.BOTTOM);
241        jbClose.setHorizontalTextPosition(JButton.CENTER);
242        jbClose.addActionListener(e -> removeAndDisposeCurentThrottleFrame());
243        throttleToolBar.add(jbClose);
244
245        throttleToolBar.addSeparator();
246
247        jbPreviousRunning = new JButton();
248        jbPreviousRunning.setIcon(new NamedIcon("resources/icons/throttles/previous-jump.png", "resources/icons/throttles/previous-jump.png"));
249        jbPreviousRunning.setVerticalTextPosition(JButton.BOTTOM);
250        jbPreviousRunning.setHorizontalTextPosition(JButton.CENTER);
251        jbPreviousRunning.setToolTipText(Bundle.getMessage("ThrottleToolBarPrevRunToolTip"));
252        jbPreviousRunning.addActionListener(e -> previousRunningThrottleFrame());
253        throttleToolBar.add(jbPreviousRunning);
254
255        jbPrevious = new JButton();
256        jbPrevious.setIcon(new NamedIcon("resources/icons/throttles/previous.png", "resources/icons/throttles/previous.png"));
257        jbPrevious.setVerticalTextPosition(JButton.BOTTOM);
258        jbPrevious.setHorizontalTextPosition(JButton.CENTER);
259        jbPrevious.setToolTipText(Bundle.getMessage("ThrottleToolBarPrevToolTip"));
260        jbPrevious.addActionListener(e -> previousThrottleFrame());
261        throttleToolBar.add(jbPrevious);
262
263        jbNext = new JButton();
264        //    next.setText(Bundle.getMessage("ThrottleToolBarNext"));
265        jbNext.setIcon(new NamedIcon("resources/icons/throttles/next.png", "resources/icons/throttles/next.png"));
266        jbNext.setToolTipText(Bundle.getMessage("ThrottleToolBarNextToolTip"));
267        jbNext.setVerticalTextPosition(JButton.BOTTOM);
268        jbNext.setHorizontalTextPosition(JButton.CENTER);
269        jbNext.addActionListener(e -> nextThrottleFrame());
270        throttleToolBar.add(jbNext);
271
272        jbNextRunning = new JButton();
273        jbNextRunning.setIcon(new NamedIcon("resources/icons/throttles/next-jump.png", "resources/icons/throttles/next-jump.png"));
274        jbNextRunning.setToolTipText(Bundle.getMessage("ThrottleToolBarNextRunToolTip"));
275        jbNextRunning.setVerticalTextPosition(JButton.BOTTOM);
276        jbNextRunning.setHorizontalTextPosition(JButton.CENTER);
277        jbNextRunning.addActionListener(e -> nextRunningThrottleFrame());
278        throttleToolBar.add(jbNextRunning);
279
280        throttleToolBar.addSeparator();
281
282        throttleToolBar.add(new StopAllButton());
283
284        if (powerMgr != null) {
285            throttleToolBar.add(new LargePowerManagerButton(false));
286        }
287
288        throttleToolBar.addSeparator();
289
290        jbMode = new JButton();
291        jbMode.setIcon(new NamedIcon("resources/icons/throttles/edit-view.png", "resources/icons/throttles/edit-view.png"));
292        jbMode.setToolTipText(Bundle.getMessage("ThrottleToolBarEditToolTip"));
293        jbMode.setVerticalTextPosition(JButton.BOTTOM);
294        jbMode.setHorizontalTextPosition(JButton.CENTER);
295        jbMode.addActionListener(e -> setEditMode( !isEditMode ));
296        throttleToolBar.add(jbMode);
297
298        throttleToolBar.addSeparator();
299
300        jbThrottleList = new JButton();
301        jbThrottleList.setIcon(new NamedIcon("resources/icons/throttles/list.png", "resources/icons/throttles/list.png"));
302        jbThrottleList.setToolTipText(Bundle.getMessage("ThrottleToolBarOpenThrottleListToolTip"));
303        jbThrottleList.setVerticalTextPosition(JButton.BOTTOM);
304        jbThrottleList.setHorizontalTextPosition(JButton.CENTER);
305        jbThrottleList.addActionListener(new ThrottlesListAction());
306        throttleToolBar.add(jbThrottleList);
307
308        // Receptacle for Jynstruments
309        new URIDrop(throttleToolBar, uris -> {
310                for (URI uri : uris ) {
311                    ynstrument(new File(uri).getPath());
312                }
313            });
314
315        add(throttleToolBar, BorderLayout.PAGE_START);
316    }
317    
318    /**
319     * Return the number of active thottle frames.
320     *
321     * @return the number of active thottle frames.
322     */
323    @Override
324    public  int getNbThrottlesControllers() {
325        return throttleFrames.size() ;
326    }
327        
328    /**
329     * Return the nth thottle frame of that throttle window
330     *
331     * @param n index of thr throtle frame
332     * @return the nth thottle frame of that throttle window
333     */
334    @Override
335    public ThrottleControllerUI getThrottleControllerAt(int n) {
336        if (! (n < throttleFrames.size())) {
337            return null;
338        }
339        return throttleFrames.get(n);
340    }
341    
342    /**
343     * Get the number of usages of a particular Loco Address.
344     * @param la the Loco Address, can be null.
345     * @return 0 if no usages, else number of AddressPanel usages.
346     */
347    @Override
348    public int getNumberOfEntriesFor(@CheckForNull DccLocoAddress la) {
349        if (la == null) { 
350            return 0; 
351        }
352        int ret = 0;
353        for (ThrottleFrame tf: throttleFrames) {
354            AddressPanel ap = tf.getAddressPanel();
355            if ( ap.getThrottle() != null && 
356                ( la.equals( ap.getCurrentAddress()) || la.equals(ap.getConsistAddress()) ) ) {
357                ret++; // in use, increment count.
358            }
359        }
360        return ret;
361    }
362       
363    @Override
364    public void emergencyStopAll() {
365        if (!throttleFrames.isEmpty()) {
366            for (ThrottleFrame tf: throttleFrames) {
367                tf.eStop();
368            }
369        }
370    }
371
372    /** {@inheritDoc} */
373    @Override
374    public void setTitle(String title) {
375        if (connectionConfig != null) {
376            super.setTitle(Bundle.getMessage("ThrottleTitleWithConnection", title, connectionConfig.getConnectionName()));
377        } else {
378            super.setTitle(title);
379        }
380    }
381
382    public void setEditMode(boolean mode) {
383        if (mode == isEditMode)
384            return;
385        isEditMode = mode;
386        if (!throttleFrames.isEmpty()) {
387            for (ThrottleFrame tf: throttleFrames) {
388                tf.setEditMode(isEditMode);
389            }
390        }
391        updateGUI();
392    }
393
394    public boolean isEditMode() {
395        return isEditMode;
396    }
397
398    public Jynstrument ynstrument(String path) {
399        Jynstrument it = JynstrumentFactory.createInstrument(path, this);
400        if (it == null) {
401            log.error("Error while creating Jynstrument {}", path);
402            return null;
403        }
404        TransparencyUtils.setTransparent(it, true);
405        it.setVisible(true);
406        throttleToolBar.add(it);
407        throttleToolBar.repaint();
408        return it;
409    }
410
411    /**
412     * Set up View, Edit and Power Menus
413     */
414    private void initializeMenu() {
415        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
416
417        JMenuItem fileMenuLoad = new JMenuItem(Bundle.getMessage("ThrottleFileMenuLoadThrottle"));
418        fileMenuLoad.addActionListener(new AbstractAction() {
419
420            @Override
421            public void actionPerformed(ActionEvent e) {
422                getCurrentThrottleFrame().loadThrottle();
423            }
424        });
425        fileMenuSave = new JMenuItem(Bundle.getMessage("ThrottleFileMenuSaveThrottle"));
426        fileMenuSave.addActionListener(new AbstractAction() {
427
428            @Override
429            public void actionPerformed(ActionEvent e) {
430                getCurrentThrottleFrame().saveThrottle();
431            }
432        });
433        JMenuItem fileMenuSaveAs = new JMenuItem(Bundle.getMessage("ThrottleFileMenuSaveAsThrottle"));
434        fileMenuSaveAs.addActionListener(new AbstractAction() {
435
436            @Override
437            public void actionPerformed(ActionEvent e) {
438                getCurrentThrottleFrame().saveThrottleAs();
439            }
440        });
441
442        jmri.jmrit.throttle.ThrottleCreationAction.addNewThrottleItemsToThrottleMenu(fileMenu);
443        fileMenu.add(fileMenuLoad);
444        fileMenu.add(fileMenuSave);
445        fileMenu.add(fileMenuSaveAs);
446        fileMenu.addSeparator();
447
448        fileMenu.add(new jmri.jmrit.throttle.LoadXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadThrottleLayout")));
449        fileMenu.add(new jmri.jmrit.throttle.StoreXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveThrottleLayout")));
450        fileMenu.addSeparator();
451        fileMenu.add(new jmri.jmrit.throttle.LoadDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadDefaultThrottleLayout")));
452        fileMenu.add(new jmri.jmrit.throttle.StoreDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveAsDefaultThrottleLayout")));
453        fileMenu.addSeparator();
454        fileMenu.add(new jmri.jmrit.withrottle.WiThrottleCreationAction(Bundle.getMessage("MenuItemStartWiThrottle")));
455
456        JMenu viewMenu = new JMenu(Bundle.getMessage("ThrottleMenuView"));
457        viewAddressPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewAddressPanel"));
458        viewAddressPanel.setSelected(true);
459        viewAddressPanel.addItemListener(e -> getCurrentThrottleFrame().getAddressPanel().setVisible(e.getStateChange() == ItemEvent.SELECTED));
460
461        viewControlPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewControlPanel"));
462        viewControlPanel.setSelected(true);
463        viewControlPanel.addItemListener(e -> getCurrentThrottleFrame().getControlPanel().setVisible(e.getStateChange() == ItemEvent.SELECTED));
464        viewFunctionPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewFunctionPanel"));
465        viewFunctionPanel.setSelected(true);
466        viewFunctionPanel.addItemListener(e -> getCurrentThrottleFrame().getFunctionPanel().setVisible(e.getStateChange() == ItemEvent.SELECTED));
467        viewSpeedPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewSpeedPanel"));
468        viewSpeedPanel.setSelected(false);
469        viewSpeedPanel.addItemListener(e -> getCurrentThrottleFrame().getSpeedPanel().setVisible(e.getStateChange() == ItemEvent.SELECTED));
470
471        viewAllButtons = new JMenuItem(Bundle.getMessage("ThrottleMenuViewAllFunctionButtons"));
472        viewAllButtons.addActionListener(new AbstractAction() {
473
474            @Override
475            public void actionPerformed(ActionEvent ev) {
476                getCurrentThrottleFrame().getFunctionPanel().resetFnButtons();
477                getCurrentThrottleFrame().getFunctionPanel().setEnabled();
478            }
479        });
480
481        JMenuItem makeAllComponentsInBounds = new JMenuItem(Bundle.getMessage("ThrottleMenuViewMakeAllComponentsInBounds"));
482        makeAllComponentsInBounds.addActionListener(new AbstractAction() {
483
484            @Override
485            public void actionPerformed(ActionEvent ev) {
486                getCurrentThrottleFrame().makeAllComponentsInBounds();
487            }
488        });
489
490        JMenuItem switchViewMode = new JMenuItem(Bundle.getMessage("ThrottleMenuViewSwitchMode"));
491        switchViewMode.addActionListener(new AbstractAction() {
492
493            @Override
494            public void actionPerformed(ActionEvent ev) {
495                setEditMode(!isEditMode);
496            }
497        });
498        JMenuItem viewThrottlesList = new JMenuItem(Bundle.getMessage("ThrottleMenuViewViewThrottleList"));
499        viewThrottlesList.addActionListener(new ThrottlesListAction());
500
501        viewMenu.add(viewAddressPanel);
502        viewMenu.add(viewControlPanel);
503        viewMenu.add(viewFunctionPanel);
504        viewMenu.add(viewSpeedPanel);
505        viewMenu.addSeparator();
506        viewMenu.add(viewAllButtons);
507        viewMenu.add(makeAllComponentsInBounds);
508        viewMenu.addSeparator();
509        viewMenu.add(switchViewMode);
510        viewMenu.add(viewThrottlesList);
511
512        JMenu editMenu = new JMenu(Bundle.getMessage("MenuEdit"));
513        JMenuItem preferencesItem = new JMenuItem(Bundle.getMessage("ThrottleMenuEditFrameProperties"));
514        editMenu.add(preferencesItem);
515        preferencesItem.addActionListener(e -> editPreferences());
516        editMenuExportRoster = new JMenuItem(Bundle.getMessage("ThrottleMenuEditSaveCustoms"));
517        editMenu.add(editMenuExportRoster);
518        editMenuExportRoster.addActionListener(e -> getCurrentThrottleFrame().saveRosterChanges());
519        editMenu.addSeparator();
520        editMenu.add(new jmri.jmrit.throttle.ThrottlesPreferencesAction(Bundle.getMessage("MenuItemThrottlesPreferences"))); // now in tabbed preferences
521
522        this.setJMenuBar(new JMenuBar());
523        this.getJMenuBar().add(fileMenu);
524        this.getJMenuBar().add(editMenu);
525        this.getJMenuBar().add(viewMenu);
526
527        if (powerMgr != null) {
528            JMenu powerMenu = new JMenu(Bundle.getMessage("ThrottleMenuPower"));
529            JMenuItem powerOn = new JMenuItem(Bundle.getMessage("ThrottleMenuPowerOn"));
530            powerMenu.add(powerOn);
531            powerOn.addActionListener(e -> {
532                try {
533                    powerMgr.setPower(PowerManager.ON);
534                } catch (JmriException e1) {
535                    log.error("Error when setting power: ", e1);
536                }
537            });
538
539            JMenuItem powerOff = new JMenuItem(Bundle.getMessage("ThrottleMenuPowerOff"));
540            powerMenu.add(powerOff);
541            powerOff.addActionListener(e -> {
542                try {
543                    powerMgr.setPower(PowerManager.OFF);
544                } catch (JmriException e1) {
545                    log.error("Error when setting power: ", e1);
546                }
547            });
548
549            this.getJMenuBar().add(powerMenu);
550
551            smallPowerMgmtButton = new SmallPowerManagerButton();
552            this.getJMenuBar().add(smallPowerMgmtButton);
553        }
554
555        // add help selection
556        addHelpMenu("package.jmri.jmrit.throttle.ThrottleFrame", true);
557    }
558
559    private void editPreferences() {
560        ThrottleFramePropertyEditor editor = new ThrottleFramePropertyEditor(this);
561        editor.setVisible(true);
562    }
563
564    /**
565     * Handle my own destruction.
566     * <ol>
567     * <li> dispose of sub windows.
568     * <li> notify my manager of my demise.
569     * </ol>
570     *
571     */
572    @Override
573    public void dispose() {
574        URIDrop.remove(throttleToolBar);
575        if ((throttleFrames != null) && (!throttleFrames.isEmpty())) {
576            for (ThrottleFrame tf: throttleFrames) {
577                tf.dispose();
578            }
579            throttleFrames.clear();
580        }
581        throttleFrames = null;
582        currentThrottleFrame  = null;
583        for (PropertyChangeListener pcl : pcs.getPropertyChangeListeners()) {
584            pcs.removePropertyChangeListener(pcl);
585        }
586        for (MouseWheelListener mwl : getMouseWheelListeners()) {
587            removeMouseWheelListener(mwl);
588        }
589        getRootPane().getActionMap().clear();
590        throttlesPanel.removeAll();        
591        removeAll();
592        super.dispose();
593    }
594
595    public JCheckBoxMenuItem getViewControlPanel() {
596        return viewControlPanel;
597    }
598
599    public JCheckBoxMenuItem getViewFunctionPanel() {
600        return viewFunctionPanel;
601    }
602
603    public JCheckBoxMenuItem getViewAddressPanel() {
604        return viewAddressPanel;
605    }
606
607    public JCheckBoxMenuItem getViewSpeedPanel() {
608        return viewSpeedPanel;
609    }
610    
611    private void updateCurentThrottleFrame() {
612        currentThrottleFrame = null;
613        for (Component comp : throttlesPanel.getComponents()) {
614            if (comp instanceof ThrottleFrame && comp.isVisible()) {
615                currentThrottleFrame = (ThrottleFrame) comp;
616            }
617        }
618    }
619
620    public ThrottleFrame getCurrentThrottleFrame() {
621        return currentThrottleFrame;
622    }
623
624    public void setCurrentThrottleFrame(ThrottleFrame tf) {
625        if (getCurrentThrottleFrame() != null) {
626            log.debug("setCurrentThrottleFrame from {} to {}", getCurrentThrottleFrame().getAddressPanel().getCurrentAddress(), tf.getAddressPanel().getCurrentAddress());
627        }
628        pcs.firePropertyChange("ThrottleFrameChanged", getCurrentThrottleFrame(), tf);
629        currentThrottleFrame = tf;
630    }
631
632    @Override
633    public void removeThrottleController(ThrottleControllerUI tf) {       
634        if (getCurrentThrottleFrame() == tf) {
635            log.debug("Closing last created");
636        }
637        throttlesPanel.remove((ThrottleFrame)tf);
638        throttleFrames.remove(tf);
639        throttlesLayout.invalidateLayout(throttlesPanel);
640        updateGUI();
641        updateCurentThrottleFrame();
642        pcs.firePropertyChange("ThrottleFrameRemoved", tf, getCurrentThrottleFrame());
643    }
644
645    public void nextThrottleFrame() {
646        ThrottleFrame otf = getCurrentThrottleFrame();
647        throttlesLayout.next(throttlesPanel);
648        updateCurentThrottleFrame();
649        updateGUI();
650        pcs.firePropertyChange("ThrottleFrameChanged", otf, getCurrentThrottleFrame());
651    }
652
653    public void previousThrottleFrame() {
654        ThrottleFrame otf = getCurrentThrottleFrame();
655        throttlesLayout.previous(throttlesPanel);
656        updateCurentThrottleFrame();
657        updateGUI();
658        pcs.firePropertyChange("ThrottleFrameChanged", otf, getCurrentThrottleFrame());
659    }
660
661    public void nextRunningThrottleFrame() {
662        if (!throttleFrames.isEmpty()) {
663            ThrottleFrame cf = this.getCurrentThrottleFrame();
664            ThrottleFrame nf = null;
665            boolean passed = false;
666            for (ThrottleFrame tf : throttleFrames) {
667                if (tf != cf) {
668                    if ((tf.getAddressPanel() != null) && (tf.getAddressPanel().getThrottle() != null) && (tf.getAddressPanel().getThrottle().getSpeedSetting() > 0)) {
669                        if (passed) { // if we passed the curent one, and found something then return it
670                            nf = tf;
671                            break;
672                        } else if (nf == null) {
673                            nf = tf;
674                        }
675                    }
676                } else {
677                    passed = true;
678                }
679            }
680            if (nf != null) {
681                nf.toFront();
682                updateCurentThrottleFrame();
683                pcs.firePropertyChange("ThrottleFrameChanged", cf, nf);
684            }
685        }
686    }
687
688    public void previousRunningThrottleFrame() {
689        if (!throttleFrames.isEmpty()) {
690            ThrottleFrame cf = this.getCurrentThrottleFrame();
691            ThrottleFrame nf = null;            
692            for (ThrottleFrame tf : throttleFrames) {
693                if ((tf != cf) && (tf.getAddressPanel() != null) && (tf.getAddressPanel().getThrottle() != null) && (tf.getAddressPanel().getThrottle().getSpeedSetting() > 0)) {
694                    nf = tf;
695                }
696                if ((tf == cf) && (nf != null)) { // return the last one found before the curent one
697                    break;
698                }
699            }
700            if (nf != null) {
701                nf.toFront();
702                updateCurentThrottleFrame();
703                pcs.firePropertyChange("ThrottleFrameChanged", cf, nf);
704            }
705        }
706    }
707
708    private void removeAndDisposeCurentThrottleFrame() {
709        ThrottleFrame tf = getCurrentThrottleFrame();
710        removeThrottleController(getCurrentThrottleFrame());
711        tf.dispose();
712    }
713
714    @Override
715    public void addThrottleControllerAt(ThrottleControllerUI tp, int idx) {
716        String txt = "ThrottleFrame-" + throttleFrameManager.generateUniqueFrameID();
717        ((ThrottleFrame)tp).setTitle(txt);
718        if (idx>throttleFrames.size()) {
719            idx = throttleFrames.size();
720        }
721        throttleFrames.add(idx,(ThrottleFrame)tp);
722        throttlesPanel.add((ThrottleFrame)tp,txt,idx);
723        ((ThrottleFrame)tp).setEditMode(isEditMode); // sync with window     
724        updateGUI();
725        pcs.firePropertyChange("ThrottleFrameAdded", null, this);
726    }
727
728    @Override
729    public ThrottleControllerUI newThrottleController() {
730        ThrottleFrame otf = getCurrentThrottleFrame();
731        ThrottleFrame tf = new ThrottleFrame(this, throttleManager);
732        throttleFrames.add(tf);
733        String txt = "ThrottleFrame-" + throttleFrameManager.generateUniqueFrameID();
734        tf.setTitle(txt);        
735        throttlesPanel.add(tf, txt);  
736        tf.setEditMode(isEditMode); // sync with window                
737        installInputsListenerOnAllComponents(tf);
738        throttlesLayout.show(throttlesPanel, txt);
739        setCurrentThrottleFrame(tf);
740        updateGUI();
741        pcs.firePropertyChange("ThrottleFrameNew", otf, getCurrentThrottleFrame());
742        return getCurrentThrottleFrame();
743    }
744
745    public void toFront(String throttleFrameTitle) {
746        ThrottleFrame otf = getCurrentThrottleFrame();
747        throttlesLayout.show(throttlesPanel, throttleFrameTitle);
748        updateCurentThrottleFrame();
749        setVisible(true);
750        requestFocus();
751        toFront();
752        pcs.firePropertyChange("ThrottleFrameChanged", otf, getCurrentThrottleFrame());
753    }
754
755    public String getTitleTextType() {
756        return titleTextType;
757    }
758
759    public String getTitleText() {
760        return titleText;
761    }
762
763    public void setTitleText(String titleText) {
764        this.titleText = titleText;
765    }
766
767    public void setTitleTextType(String titleTextType) {
768        this.titleTextType = titleTextType;
769    }
770
771    public Element getXml() {
772        Element me = new Element("ThrottleWindow");
773        if (connectionConfig != null) {
774            me.setAttribute("systemPrefix", connectionConfig.getAdapter().getSystemPrefix());
775        }
776        me.setAttribute("title", titleText);
777        me.setAttribute("titleType", titleTextType);
778        me.setAttribute("isEditMode",  String.valueOf(isEditMode));
779
780        java.util.ArrayList<Element> children = new java.util.ArrayList<>(1);
781        children.add(WindowPreferences.getPreferences(this));
782        if (!throttleFrames.isEmpty()) {
783            ThrottleFrame cf = this.getCurrentThrottleFrame();
784            for (ThrottleFrame tf : throttleFrames) {
785                if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) && (InstanceManager.getDefault(ThrottlesPreferences.class).isSavingThrottleOnLayoutSave())) {
786                    tf.toFront();
787                    tf.saveThrottle();
788                }
789                Element tfe = tf.getXmlFile();
790                if (tfe == null) {
791                    tfe = tf.getXml();
792                }
793                children.add(tfe);
794            }
795            if (cf != null) {
796                cf.toFront();
797            }
798        }
799
800        // Save Jynstruments
801        if (throttleToolBar != null) {
802            Component[] cmps = throttleToolBar.getComponents();
803            if (cmps != null) {
804                for (Component cmp : cmps) {
805                    try {
806                        if (cmp instanceof Jynstrument) {
807                            Jynstrument jyn = (Jynstrument) cmp;
808                            Element elt = new Element("Jynstrument");
809                            elt.setAttribute("JynstrumentFolder", FileUtil.getPortableFilename(jyn.getFolder()));
810                            Element je = jyn.getXml();
811                            if (je != null) {
812                                java.util.ArrayList<Element> jychildren = new java.util.ArrayList<>(1);
813                                jychildren.add(je);
814                                elt.setContent(jychildren);
815                            }
816                            children.add(elt);
817                        }
818
819                    } catch (Exception ex) {
820                        log.debug("Got exception (no panic): ", ex);
821                    }
822                }
823            }
824        }
825        me.setContent(children);
826        return me;
827    }
828
829    private void setXml(Element e) {
830        if (e.getAttribute("title") != null) {
831            setTitle(e.getAttribute("title").getValue());
832        }
833        if (e.getAttribute("title") != null) {
834            setTitleText(e.getAttribute("title").getValue());
835        }
836        if (e.getAttribute("titleType") != null) {
837            setTitleTextType(e.getAttribute("titleType").getValue());
838        }
839        if (e.getAttribute("isEditMode") != null) {
840            isEditMode = Boolean.parseBoolean(e.getAttribute("isEditMode").getValue());
841        }
842
843        Element window = e.getChild("window");
844        if (window != null) {
845            WindowPreferences.setPreferences(this, window);
846        }
847
848        List<Element> tfes = e.getChildren("ThrottleFrame");
849        if ((tfes != null) && (!tfes.isEmpty())) {
850            for (int i = 0; i < tfes.size(); i++) {
851                ThrottleFrame tf;
852                if (i == 0) {
853                    tf = getCurrentThrottleFrame();
854                } else {
855                    tf = (ThrottleFrame) newThrottleController();
856                }
857                tf.setXml(tfes.get(i));
858                tf.setEditMode(isEditMode);
859            }
860        }
861
862        List<Element> jinsts = e.getChildren("Jynstrument");
863        if ((jinsts != null) && (!jinsts.isEmpty())) {
864            jinsts.forEach((jinst) -> {
865                Jynstrument jyn = ynstrument(FileUtil.getExternalFilename(jinst.getAttributeValue("JynstrumentFolder")));
866                if (jyn != null) {
867                    jyn.setXml(jinst);
868                }
869            });
870        }
871
872        updateGUI();
873    }
874
875    @Override
876    public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
877        pcs.addPropertyChangeListener(l);
878    }
879
880    @Override
881    public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
882        pcs.removePropertyChangeListener(l);
883    }
884
885    private void installInputsListenerOnAllComponents(Container c) {
886        c.setFocusTraversalKeysEnabled(false); // make tab and shift tab available
887        if (! ( c instanceof JTextField)) {
888            c.setFocusable(false);
889        }
890        for (Component component : c.getComponents()) {
891            if (component instanceof Container) {
892                installInputsListenerOnAllComponents( (Container) component);
893            } else {
894                if (! ( component instanceof JTextField)) {
895                    component.setFocusable(false);
896                }
897            }
898        }
899    }
900
901    public void applyPreferences() {
902        ThrottlesPreferences preferences = InstanceManager.getDefault(ThrottlesPreferences.class);
903
904        ComponentInputMap im = new ComponentInputMap(getRootPane());
905        for (Object k : this.getRootPane().getActionMap().allKeys()) {
906            KeyStroke[] kss = preferences.getThrottlesKeyboardControls().getKeyStrokes((String)k);
907            if (kss !=null) {
908                for (KeyStroke keystroke : kss) {
909                    if (keystroke != null) {
910                        im.put(keystroke, k);
911                    }
912                }
913            }
914        }
915        getRootPane().setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW,im);
916
917        throttleToolBar.setVisible ( preferences.isUsingExThrottle() && preferences.isUsingToolBar() );
918
919        if (smallPowerMgmtButton != null) {
920            smallPowerMgmtButton.setVisible( (!preferences.isUsingExThrottle()) || (!preferences.isUsingToolBar()) );
921        }
922
923        for (ThrottleFrame tf : throttleFrames) {
924            tf.applyPreferences();
925        }
926    }
927
928    private final static Logger log = LoggerFactory.getLogger(ThrottleWindow.class);
929}