001package jmri.jmrix;
002
003import java.awt.BorderLayout;
004import java.awt.event.ActionEvent;
005import java.lang.reflect.InvocationTargetException;
006import javax.swing.BorderFactory;
007import javax.swing.BoxLayout;
008import javax.swing.JComboBox;
009import javax.swing.JComponent;
010import javax.swing.JPanel;
011import javax.swing.JScrollPane;
012import jmri.ConfigureManager;
013import jmri.InstanceManager;
014import jmri.swing.JTitledSeparator;
015import jmri.swing.PreferencesPanel;
016import jmri.util.swing.JComboBoxUtil;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * Provide GUI to configure communications links.
022 * <p>
023 * This is really just a catalog of connections to classes within the systems.
024 * Reflection is used to reduce coupling at load time.
025 * <p>
026 * Objects of this class are based on an underlying ConnectionConfig
027 * implementation, which in turn is obtained from the InstanceManager. Those
028 * must be created at load time by the ConfigXml process, or in some Application
029 * class.
030 * <p>
031 * The classes referenced are the specific subclasses of
032 * {@link jmri.jmrix.ConnectionConfig} which provides the methods providing data
033 * to the configuration GUI, and responding to its changes.
034 *
035 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2004, 2010
036 */
037public class JmrixConfigPane extends JPanel implements PreferencesPanel {
038
039    public static final String NONE_SELECTED = Bundle.getMessage("noneSelected");
040    public static final String NO_PORTS_FOUND = Bundle.getMessage("noPortsFound");
041    public static final String NONE = Bundle.getMessage("none");
042    // initialize logging
043    private final static Logger log = LoggerFactory.getLogger(JmrixConfigPane.class);
044
045    /*
046     * Create panel is separated off from the instance and synchronized, so that only
047     * one connection can be configured at once. This prevents multiple threads from
048     * trying to create the same panel at the same time.
049     */
050    /**
051     * Create a new connection configuration panel.
052     *
053     * @param index the index of the desired connection configuration from
054     *              {@link jmri.jmrix.ConnectionConfigManager#getConnections(int)}
055     * @return the panel for the requested connection or for a new connection if
056     *         index did not match an existing connection configuration
057     */
058    public static synchronized JmrixConfigPane createPanel(int index) {
059        ConnectionConfig c = null;
060        try {
061            c = InstanceManager.getDefault(ConnectionConfigManager.class).getConnections(index);
062            log.debug("connection {} is {}", index, c);
063        } catch (IndexOutOfBoundsException ex) {
064            log.debug("connection {} is null, creating new one", index);
065        }
066        return createPanel(c);
067    }
068
069    /**
070     * Create a new configuration panel for the given connection.
071     *
072     * @param c the connection; if null, the panel is ready for a new connection
073     * @return the new panel
074     */
075    public static synchronized JmrixConfigPane createPanel(ConnectionConfig c) {
076        JmrixConfigPane pane = new JmrixConfigPane(c);
077        if (c == null) {
078            pane.isDirty = true;
079        }
080        return pane;
081    }
082
083    /**
084     * Get access to a new pane for creating new connections.
085     *
086     * @return a new configuration panel
087     */
088    public static JmrixConfigPane createNewPanel() {
089        return createPanel(null);
090    }
091
092    /**
093     * Disposes of the underlying connection for a configuration pane.
094     *
095     * @param confPane the pane to dispose of
096     */
097    public static void dispose(JmrixConfigPane confPane) {
098        if (confPane == null) {
099            log.debug("no instance found therefore can not dispose of it!");
100            return;
101        }
102
103        if (confPane.ccCurrent != null) {
104            try {
105                confPane.ccCurrent.dispose();
106            } catch (RuntimeException ex) {
107                log.error("Error Occurred while disposing connection {}", ex.toString());
108            }
109        }
110        ConfigureManager cmOD = InstanceManager.getNullableDefault(jmri.ConfigureManager.class);
111        if (cmOD != null) {
112            cmOD.deregister(confPane);
113            cmOD.deregister(confPane.ccCurrent);
114        }
115        InstanceManager.getDefault(ConnectionConfigManager.class).remove(confPane.ccCurrent);
116    }
117
118    private boolean isDirty = false;
119
120    JComboBox<String> modeBox = new JComboBox<>();
121    public JComboBox<String> manuBox = new JComboBox<>();
122
123    JPanel details = new JPanel();
124    String[] classConnectionNameList;
125    ConnectionConfig[] classConnectionList;
126    String[] manufactureNameList;
127
128    jmri.UserPreferencesManager p = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class);
129
130    ConnectionConfig ccCurrent = null;
131
132    protected JmrixConfigPane() {
133    }
134
135    /**
136     * Use "instance" to get one of these. That allows it to reconnect to
137     * existing information in an existing ConnectionConfig object. It's
138     * permitted to call this with a null argument, e.g. for when first
139     * configuring the system.
140     * @param original Existing ConnectionConfig object to (re)connect with
141     */
142    protected JmrixConfigPane(ConnectionConfig original) {
143
144        ConnectionConfigManager manager = InstanceManager.getDefault(ConnectionConfigManager.class);
145        ccCurrent = original;
146
147        setLayout(new BorderLayout());
148        this.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
149
150        manuBox.addItem(NONE_SELECTED);
151
152        manufactureNameList = manager.getConnectionManufacturers();
153        for (String manuName : manufactureNameList) {
154            if (original != null && original.getManufacturer() != null
155                    && original.getManufacturer().equals(manuName)) {
156                manuBox.addItem(manuName);
157                manuBox.setSelectedItem(manuName);
158            } else {
159                manuBox.addItem(manuName);
160            }
161        }
162        JComboBoxUtil.setupComboBoxMaxRows(manuBox);
163
164        manuBox.addActionListener((ActionEvent evt) -> {
165            updateComboConnection();
166        });
167
168        // get the list of ConnectionConfig items into a selection box
169        String selectedItem = (String) manuBox.getSelectedItem();
170        if (selectedItem != null) {
171            classConnectionNameList = manager.getConnectionTypes(selectedItem);
172        }
173        classConnectionList = new jmri.jmrix.ConnectionConfig[classConnectionNameList.length + 1];
174        modeBox.addItem(NONE_SELECTED);
175        if (manuBox.getSelectedIndex() != 0) {
176            modeBox.setEnabled(true);
177        } else {
178            modeBox.setSelectedIndex(0);
179            modeBox.setEnabled(false);
180        }
181        int n = 1;
182        if (manuBox.getSelectedIndex() != 0) {
183            for (String className : classConnectionNameList) {
184                try {
185                    ConnectionConfig config;
186                    if (original != null && original.getClass().getName().equals(className)) {
187                        config = original;
188                        log.debug("matched existing config object");
189                        modeBox.addItem(config.name());
190                        modeBox.setSelectedItem(config.name());
191                        if (classConnectionNameList.length == 1) {
192                            modeBox.setSelectedIndex(1);
193                        }
194                    } else {
195                        Class<?> cl = Class.forName(className);
196                        config = (ConnectionConfig) cl.getDeclaredConstructor().newInstance();
197                        if( !(config instanceof StreamConnectionConfig)) {
198                           // only include if the connection is not a
199                           // StreamConnection.  Those connections require
200                           // additional context.
201                           modeBox.addItem(config.name());
202                        } else {
203                               continue;
204                        }
205                    }
206                    classConnectionList[n++] = config;
207                } catch (NullPointerException e) {
208                    log.error("Attempt to load {} failed.", className, e);
209                } catch (InvocationTargetException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
210                    log.error("Attempt to load {} failed", className, e);
211                }
212            }
213            if ((modeBox.getSelectedIndex() == 0) && (p.getComboBoxLastSelection((String) manuBox.getSelectedItem()) != null)) {
214                modeBox.setSelectedItem(p.getComboBoxLastSelection((String) manuBox.getSelectedItem()));
215            }
216        }
217        JComboBoxUtil.setupComboBoxMaxRows(modeBox);
218
219        modeBox.addActionListener((ActionEvent a) -> {
220            if ((String) modeBox.getSelectedItem() != null) {
221                if (!((String) modeBox.getSelectedItem()).equals(NONE_SELECTED)) {
222                    p.setComboBoxLastSelection((String) manuBox.getSelectedItem(), (String) modeBox.getSelectedItem());
223                }
224            }
225            selection();
226        });
227        JPanel manufacturerPanel = new JPanel();
228        manufacturerPanel.add(manuBox);
229        JPanel connectionPanel = new JPanel();
230        connectionPanel.add(modeBox);
231        JPanel initialPanel = new JPanel();
232        initialPanel.setLayout(new BoxLayout(initialPanel, BoxLayout.Y_AXIS));
233        initialPanel.add(new JTitledSeparator(Bundle.getMessage("SystemManufacturer"))); // NOI18N
234        initialPanel.add(manufacturerPanel);
235        initialPanel.add(new JTitledSeparator(Bundle.getMessage("SystemConnection"))); // NOI18N
236        initialPanel.add(connectionPanel);
237        add(initialPanel, BorderLayout.NORTH);
238        initialPanel.add(new JTitledSeparator(Bundle.getMessage("Settings"))); // NOI18N
239        JScrollPane scroll = new JScrollPane(details);
240        scroll.setBorder(BorderFactory.createEmptyBorder());
241        add(scroll, BorderLayout.CENTER);
242
243        selection();  // first time through, pretend we've selected a value
244        // to load the rest of the GUI
245    }
246
247    public void updateComboConnection() {
248        modeBox.removeAllItems();
249        modeBox.addItem(NONE_SELECTED);
250        String selectedItem = (String) manuBox.getSelectedItem();
251        if (selectedItem != null) {
252            classConnectionNameList = InstanceManager.getDefault(ConnectionConfigManager.class).getConnectionTypes(selectedItem);
253        }
254        classConnectionList = new jmri.jmrix.ConnectionConfig[classConnectionNameList.length + 1];
255
256        if (manuBox.getSelectedIndex() != 0) {
257            int n = 1;
258            modeBox.setEnabled(true);
259            for (String classConnectionNameList1 : classConnectionNameList) {
260                try {
261                    jmri.jmrix.ConnectionConfig config;
262                    Class<?> cl = Class.forName(classConnectionNameList1);
263                    config = (jmri.jmrix.ConnectionConfig) cl.getDeclaredConstructor().newInstance();
264                    if( !(config instanceof StreamConnectionConfig)) {
265                        // only include if the connection is not a
266                        // StreamConnection.  Those connections require
267                        // additional context.
268                        modeBox.addItem(config.name());
269                    } else {
270                        continue;
271                    }
272                    classConnectionList[n++] = config;
273                    if (classConnectionNameList.length == 1) {
274                        modeBox.setSelectedIndex(1);
275                    }
276                } catch (InvocationTargetException | NullPointerException | ClassNotFoundException
277                                | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
278                    log.warn("Attempt to load {} failed", classConnectionNameList1, e);
279                }
280            }
281            if (p.getComboBoxLastSelection((String) manuBox.getSelectedItem()) != null) {
282                modeBox.setSelectedItem(p.getComboBoxLastSelection((String) manuBox.getSelectedItem()));
283            }
284        } else {
285            modeBox.setSelectedIndex(0);
286            modeBox.setEnabled(false);
287            if (ccCurrent != null) {
288                ccCurrent.dispose();
289            }
290        }
291    }
292
293    void selection() {
294        ConnectionConfig old = this.ccCurrent;
295        int current = modeBox.getSelectedIndex();
296        details.removeAll();
297        // first choice is -no- protocol chosen
298        log.debug("new selection is {} {}", current, modeBox.getSelectedItem());
299        if ((current != 0) && (current != -1)) {
300            if ((ccCurrent != null) && (ccCurrent != classConnectionList[current])) {
301                ccCurrent.dispose();
302            }
303            ccCurrent = classConnectionList[current];
304            ccCurrent.setManufacturer((String) manuBox.getSelectedItem());
305            ccCurrent.loadDetails(details);
306        } else {
307            if (ccCurrent != null) {
308                ccCurrent.dispose();
309            }
310        }
311        if (old != this.ccCurrent) {
312            assert this.ccCurrent != null;
313            this.ccCurrent.register();
314        }
315        validate();
316
317        repaint();
318    }
319
320    public String getConnectionName() {
321        int current = modeBox.getSelectedIndex();
322        if (current == 0) {
323            return null;
324        }
325        return classConnectionList[current].getConnectionName();
326    }
327
328    public String getCurrentManufacturerName() {
329        int current = modeBox.getSelectedIndex();
330        if (current == 0) {
331            return NONE;
332        }
333        return classConnectionList[current].getManufacturer();
334    }
335
336    public String getCurrentProtocolName() {
337        int current = modeBox.getSelectedIndex();
338        if (current == 0) {
339            return NONE;
340        }
341        return classConnectionList[current].name();
342    }
343
344    public String getCurrentProtocolInfo() {
345        int current = modeBox.getSelectedIndex();
346        if (current == 0) {
347            return NONE;
348        }
349        return classConnectionList[current].getInfo();
350    }
351
352    public ConnectionConfig getCurrentObject() {
353        int current = modeBox.getSelectedIndex();
354        if (current != 0) {
355            return classConnectionList[current];
356        }
357        return null;
358    }
359
360    public boolean getDisabled() {
361        int current = modeBox.getSelectedIndex();
362        if (current == 0) {
363            return false;
364        }
365        return classConnectionList[current].getDisabled();
366    }
367
368    public void setDisabled(boolean disabled) {
369        int current = modeBox.getSelectedIndex();
370        if (current == 0) {
371            return;
372        }
373        classConnectionList[current].setDisabled(disabled);
374    }
375
376    @Override
377    public String getPreferencesItem() {
378        return "CONNECTIONS"; // NOI18N
379    }
380
381    @Override
382    public String getPreferencesItemText() {
383        return Bundle.getMessage("MenuConnections"); // NOI18N
384    }
385
386    @Override
387    public String getTabbedPreferencesTitle() {
388        String title = this.getConnectionName();
389        if (title == null
390                && this.getCurrentProtocolName() != null
391                && !this.getCurrentProtocolName().equals(JmrixConfigPane.NONE)) {
392            title = this.getCurrentProtocolName();
393        }
394        if (title != null && !this.getDisabled()) {
395            title = "(" + title + ")";
396        }
397        return title;
398    }
399
400    @Override
401    public String getLabelKey() {
402        return null;
403    }
404
405    @Override
406    public JComponent getPreferencesComponent() {
407        return this;
408    }
409
410    @Override
411    public boolean isPersistant() {
412        return true;
413    }
414
415    @Override
416    public String getPreferencesTooltip() {
417        return this.getTabbedPreferencesTitle();
418    }
419
420    @Override
421    public void savePreferences() {
422        // do nothing - the persistent manager will take care of this
423    }
424
425    @Override
426    public boolean isDirty() {
427        // avoid potentially expensive extra test for isDirty
428        if (log.isDebugEnabled()) {
429            log.debug("Connection \"{}\" is {}.",
430                    this.getConnectionName(),
431                    (this.isDirty || ((this.ccCurrent == null) || this.ccCurrent.isDirty()) ? "dirty" : "clean"));
432        }
433        return this.isDirty || ((this.ccCurrent == null) || this.ccCurrent.isDirty());
434    }
435
436    @Override
437    public boolean isRestartRequired() {
438        return (this.ccCurrent != null) ? this.ccCurrent.isRestartRequired() : this.isDirty();
439    }
440
441    @Override
442    public boolean isPreferencesValid() {
443        return true; // no validity checking performed
444    }
445
446}