001package jmri.jmrit.throttle;
002
003import java.util.ArrayList;
004import java.util.Iterator;
005
006import javax.annotation.CheckForNull;
007import javax.swing.JFrame;
008
009import org.jdom2.Element;
010
011import jmri.DccLocoAddress;
012import jmri.InstanceManagerAutoDefault;
013import jmri.jmrit.roster.RosterEntry;
014import jmri.jmrit.throttle.interfaces.ThrottleControllerUI;
015import jmri.jmrit.throttle.interfaces.ThrottleControllersUIContainer;
016import jmri.jmrit.throttle.list.ThrottlesListPanel;
017import jmri.jmrit.throttle.preferences.ThrottlesPreferencesWindow;
018import jmri.util.JmriJFrame;
019
020/**
021 * Interface for allocating and deallocating throttles frames. Not to be
022 * confused with ThrottleManager.
023 * 
024 * <hr>
025 * This file is part of JMRI.
026 * <p>
027 * JMRI is free software; you can redistribute it and/or modify it under the
028 * terms of version 2 of the GNU General Public License as published by the Free
029 * Software Foundation. See the "COPYING" file for a copy of this license.
030 * <p>
031 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
032 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
033 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
034 *
035 * @author Glen Oberhauser
036 * @author Lionel Jeanson
037 * 
038 */
039public class ThrottleFrameManager implements InstanceManagerAutoDefault {
040
041    private int activeFrame;
042    private int frameCounterID = 0; // to generate unique names for each card    
043
044    private ArrayList<ThrottleControllersUIContainer> throttleUIContainers; // synchronized access
045
046    private ThrottlesPreferencesWindow throttlePreferencesFrame;
047    private JmriJFrame throttlesListFrame;
048    private ThrottlesListPanel throttlesListPanel;
049
050    /**
051     * Constructor for the ThrottleFrameManager object.
052     */
053    public ThrottleFrameManager() {
054        throttleUIContainers = new ArrayList<>(0);
055    }
056
057    /**
058     * Ask this manager to create a new Throttle Window
059     *
060     * @return The newly created ThrottleWindow
061     */
062    public ThrottleWindow createThrottleWindow() {
063        return createThrottleWindow((jmri.jmrix.ConnectionConfig) null);
064    }
065
066    /**
067     * Ask this manager to create a new Throttle Window
068     *
069     * @param connectionConfig the connection config
070     * @return The newly created ThrottleWindow
071     */
072    public ThrottleWindow createThrottleWindow(jmri.jmrix.ConnectionConfig connectionConfig) {
073        ThrottleWindow tw = new ThrottleWindow(connectionConfig);
074        tw.pack();
075        synchronized (this) {
076            throttleUIContainers.add(tw);
077            activeFrame = throttleUIContainers.indexOf(tw);
078        }
079        getThrottlesListPanel().getTableModel().fireTableStructureChanged();
080        return tw;
081    }
082
083    /**
084     * Ask this manager to create a new Throttle Window
085     *
086     * @param e the xml element for the throttle window
087     * @return The newly created ThrottleWindow
088     */
089    public ThrottleWindow createThrottleWindow(Element e) {
090        ThrottleWindow tw = ThrottleWindow.createThrottleWindow(e);
091        tw.pack();
092        synchronized (this) {
093            throttleUIContainers.add(tw);
094            activeFrame = throttleUIContainers.indexOf(tw);
095        }
096        getThrottlesListPanel().getTableModel().fireTableStructureChanged();
097        return tw;
098    }
099
100    /**
101     * Ask this manager to create a new Throttle Frame
102     * A ThrottleWindow will be created, but the inner Panel (ThrottleFrame)
103     * will be returned.
104     * 
105     * This method is backward compatible with the first implementation of JMRI throttle (200x).
106     *
107     * @return The newly created ThrottleFrame
108     */
109    public ThrottleControllerUI createThrottleFrame() {
110        return createThrottleFrame(null);
111    }    
112
113    /**
114     * Ask this manager to create a new Throttle Frame
115     * A ThrottleWindow will be created, but the inner Panel (ThrottleFrame)
116     * will be returned.
117     * 
118     * This method is backward compatible with the first implementation of JMRI throttle (200x).
119     *
120     * @param connectionConfig the connection config
121     * @return The newly created ThrottleFrame
122     */
123    public ThrottleControllerUI createThrottleFrame(jmri.jmrix.ConnectionConfig connectionConfig) {
124        return createThrottleWindow(connectionConfig).getCurentThrottleController();
125    }
126
127    /**
128     * Ask this manager to create a new Simple Throttle Frame
129     * A SimpleThrottleWindow will be created, but the inner Panel (SimpleThrottleFrame)
130     * will be returned.
131     * 
132     * @param re the RosterEntry that this throttle should control
133     *
134     * @return The newly created SimpleThrottleFrame
135     */
136    public ThrottleControllerUI createSimpleThrottleFrame(RosterEntry re) {
137        return createSimpleThrottleFrame(null,re.getDccLocoAddress());
138    }
139
140    /**
141     * Ask this manager to create a new Simple Throttle Frame
142     * A SimpleThrottleWindow will be created, but the inner Panel (SimpleThrottleFrame)
143     * will be returned.
144     * 
145     * @param la the loco address that this throttle should control
146     *
147     * @return The newly created SimpleThrottleFrame
148     */
149    public ThrottleControllerUI createSimpleThrottleFrame(DccLocoAddress la) {
150        return createSimpleThrottleFrame(null, la);
151    } 
152
153    /**
154     * Ask this manager to create a new Simple Throttle Frame
155     * A SimpleThrottleWindow will be created, but the inner Panel (SimpleThrottleFrame)
156     * will be returned.
157     * 
158     * @param connectionConfig the connection config
159     * @param la the loco address that this throttle should control
160     *
161     * @return The newly created SimpleThrottleFrame
162     */    
163    public ThrottleControllerUI createSimpleThrottleFrame(jmri.jmrix.ConnectionConfig connectionConfig, DccLocoAddress la) {
164        SimpleThrottleWindow stw = new SimpleThrottleWindow(connectionConfig, la);
165        stw.pack();
166        stw.setVisible(true);
167        synchronized (this) {
168            throttleUIContainers.add(stw);
169            activeFrame = throttleUIContainers.indexOf(stw);
170        }
171        getThrottlesListPanel().getTableModel().fireTableStructureChanged();
172        return stw.getThrottleControllerAt(0);
173    }
174
175    /**
176     * Request that this manager destroy a ThrottleControllersUIContainer. 
177     * Is called by the ThrottleWindow, or SimpleThrottleWindow, when it is disposed
178     *
179     * @param throtCont The to-be-destroyed Throttle Container
180     */
181    public void requestThrottleWindowDestruction(ThrottleControllersUIContainer throtCont) {
182        if (throtCont != null) {
183            destroyThrottleWindow(throtCont);
184            synchronized (this) {
185                throttleUIContainers.remove(throtCont);
186                if (!throttleUIContainers.isEmpty()) {
187                    requestFocusForNextThrottleWindow();
188                }
189            }
190        }
191        getThrottlesListPanel().getTableModel().fireTableStructureChanged();        
192    }
193
194    /**
195     * Request that this manager destroy all throttle containers.
196     */    
197    public synchronized void requestAllThrottleWindowsDestroyed() {
198        for (ThrottleControllersUIContainer frame : throttleUIContainers) {
199            destroyThrottleWindow(frame);
200        }
201        throttleUIContainers = new ArrayList<>(0);
202        getThrottlesListPanel().getTableModel().fireTableStructureChanged();        
203    }
204
205    /**
206     * Request this manager for a unique identifier (used by ThrottleWindow to identify themselves).
207     * 
208     * @return a unique identifier
209     * 
210     */         
211    public int generateUniqueFrameID() {
212         return frameCounterID++;
213    }
214
215    /**
216     * Perform the destruction of a Throttle UI containers 
217     *
218     * @param throtCont The ThrottleFrame to be destroyed.
219     */
220    private void destroyThrottleWindow(ThrottleControllersUIContainer throtCont) {
221        throttleUIContainers.remove(throtCont);        
222        getThrottlesListPanel().getTableModel().fireTableStructureChanged();        
223    }
224    
225    /**
226     * Gets an iterator over all the Throttle UI containers
227     * 
228     * @return an iterator over all the Throttle UI containers
229     */    
230    public Iterator<ThrottleControllersUIContainer> iterator() {
231        return throttleUIContainers.iterator();
232    }
233       
234    /**
235     * Return the number of active thottle UI containers
236     *
237     * @return the number of active thottle UI containers
238     */
239    public synchronized int getNbThrottleControllersContainers() {
240        return throttleUIContainers.size();
241    }
242    
243    /**
244     * Return the thottle controller container at nth position in the list
245     *
246     * @param n position of the throttle controller container
247     * @return a thottle controller container
248     */ 
249    public synchronized ThrottleControllersUIContainer getThrottleControllersContainerAt(int n) {
250        if (! (n < throttleUIContainers.size())) {
251            return null;
252        }
253        return throttleUIContainers.get(n);
254    }
255
256    public synchronized void requestFocusForNextThrottleWindow() {
257        activeFrame = (activeFrame + 1) % throttleUIContainers.size();
258        JmriJFrame tw = (JmriJFrame) throttleUIContainers.get(activeFrame);
259        tw.requestFocus();
260        tw.toFront();
261    }
262
263    public synchronized void requestFocusForPreviousThrottleWindow() {
264        activeFrame--;
265        if (activeFrame < 0) {
266            activeFrame = throttleUIContainers.size() - 1;
267        }
268        JmriJFrame tw =(JmriJFrame) throttleUIContainers.get(activeFrame);
269        tw.requestFocus();
270        tw.toFront();
271    }
272
273    public synchronized JmriJFrame getCurentThrottleController() {
274        if (throttleUIContainers == null) {
275            return null;
276        }
277        if (throttleUIContainers.isEmpty()) {
278            return null;
279        }
280        return  (JmriJFrame) throttleUIContainers.get(activeFrame);
281    }
282
283    public ThrottlesListPanel getThrottlesListPanel() {
284        if (throttlesListPanel == null) {
285            throttlesListPanel = new ThrottlesListPanel();
286        }
287        return throttlesListPanel;
288    }
289
290    /*
291     * Show JMRI native throttle list window
292     *
293     */
294    public void showThrottlesList() {
295        if (throttlesListFrame == null) {            
296            throttlesListFrame = new JmriJFrame(Bundle.getMessage("ThrottleListFrameTile"));        
297            throttlesListFrame.setContentPane(getThrottlesListPanel());
298            throttlesListFrame.pack();            
299        }
300        throttlesListFrame.setVisible(true);
301    }
302
303    /*
304     * Show throttle preferences window
305     *
306     */
307    public void showThrottlesPreferences() {
308        if (throttlePreferencesFrame == null) {
309            throttlePreferencesFrame = new ThrottlesPreferencesWindow(Bundle.getMessage("ThrottlePreferencesFrameTitle"));
310            throttlePreferencesFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
311            throttlePreferencesFrame.pack();
312        } else {
313            throttlePreferencesFrame.resetComponents();
314            throttlePreferencesFrame.revalidate();
315        }
316        throttlePreferencesFrame.setVisible(true);
317        throttlePreferencesFrame.requestFocus();
318    }
319
320    /**
321     * Force emergency stop of all managed throttles windows
322     *
323     */   
324    public void emergencyStopAll() {
325        throttleUIContainers.forEach(tw -> {
326            tw.emergencyStopAll();
327        });
328    }
329    
330    /**
331     * Return the number of throttle controllers for a LocoAddress,
332     * usefull to know if a layout throttle object should actually be released
333     *
334     * @param la locoaddrress we're looking for
335     * @return the number of throttle controllers for that LocoAddress
336     */   
337    public int getNumberOfEntriesFor(@CheckForNull DccLocoAddress la) {
338        if (la == null) { 
339            return 0; 
340        }
341        int ret = 0;
342        for (ThrottleControllersUIContainer tw : throttleUIContainers) {        
343            ret += tw.getNumberOfEntriesFor(la);
344        }
345        return ret;
346    }
347
348    // private static final Logger log = LoggerFactory.getLogger(ThrottleFrameManager.class);
349}