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 static final 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            ConnectionConfig.Config config = null;
301            if ((ccCurrent != null) && (ccCurrent != classConnectionList[current])) {
302                config = ccCurrent.getConfig();
303                ccCurrent.dispose();
304            }
305            ccCurrent = classConnectionList[current];
306            ccCurrent.setManufacturer((String) manuBox.getSelectedItem());
307            ccCurrent.loadDetails(details);
308            if (config != null) {
309                ccCurrent.setConfig(config);
310            }
311        } else {
312            if (ccCurrent != null) {
313                ccCurrent.dispose();
314            }
315        }
316        if (old != this.ccCurrent) {
317            assert this.ccCurrent != null;
318            this.ccCurrent.register();
319        }
320        validate();
321
322        repaint();
323    }
324
325    public String getConnectionName() {
326        int current = modeBox.getSelectedIndex();
327        if (current == 0) {
328            return null;
329        }
330        return classConnectionList[current].getConnectionName();
331    }
332
333    public String getCurrentManufacturerName() {
334        int current = modeBox.getSelectedIndex();
335        if (current == 0) {
336            return NONE;
337        }
338        return classConnectionList[current].getManufacturer();
339    }
340
341    public String getCurrentProtocolName() {
342        int current = modeBox.getSelectedIndex();
343        if (current == 0) {
344            return NONE;
345        }
346        return classConnectionList[current].name();
347    }
348
349    public String getCurrentProtocolInfo() {
350        int current = modeBox.getSelectedIndex();
351        if (current == 0) {
352            return NONE;
353        }
354        return classConnectionList[current].getInfo();
355    }
356
357    public ConnectionConfig getCurrentObject() {
358        int current = modeBox.getSelectedIndex();
359        if (current != 0) {
360            return classConnectionList[current];
361        }
362        return null;
363    }
364
365    public boolean getDisabled() {
366        int current = modeBox.getSelectedIndex();
367        if (current == 0) {
368            return false;
369        }
370        return classConnectionList[current].getDisabled();
371    }
372
373    public void setDisabled(boolean disabled) {
374        int current = modeBox.getSelectedIndex();
375        if (current == 0) {
376            return;
377        }
378        classConnectionList[current].setDisabled(disabled);
379    }
380
381    @Override
382    public String getPreferencesItem() {
383        return "CONNECTIONS"; // NOI18N
384    }
385
386    @Override
387    public String getPreferencesItemText() {
388        return Bundle.getMessage("MenuConnections"); // NOI18N
389    }
390
391    @Override
392    public String getTabbedPreferencesTitle() {
393        String title = this.getConnectionName();
394        if (title == null
395                && this.getCurrentProtocolName() != null
396                && !this.getCurrentProtocolName().equals(JmrixConfigPane.NONE)) {
397            title = this.getCurrentProtocolName();
398        }
399        if (title != null && !this.getDisabled()) {
400            title = "(" + title + ")";
401        }
402        return title;
403    }
404
405    @Override
406    public String getLabelKey() {
407        return null;
408    }
409
410    @Override
411    public JComponent getPreferencesComponent() {
412        return this;
413    }
414
415    @Override
416    public boolean isPersistant() {
417        return true;
418    }
419
420    @Override
421    public String getPreferencesTooltip() {
422        return this.getTabbedPreferencesTitle();
423    }
424
425    @Override
426    public void savePreferences() {
427        // do nothing - the persistent manager will take care of this
428    }
429
430    @Override
431    public boolean isDirty() {
432        // avoid potentially expensive extra test for isDirty
433        if (log.isDebugEnabled()) {
434            log.debug("Connection \"{}\" is {}.",
435                    this.getConnectionName(),
436                    (this.isDirty || ((this.ccCurrent == null) || this.ccCurrent.isDirty()) ? "dirty" : "clean"));
437        }
438        return this.isDirty || ((this.ccCurrent == null) || this.ccCurrent.isDirty());
439    }
440
441    @Override
442    public boolean isRestartRequired() {
443        return (this.ccCurrent != null) ? this.ccCurrent.isRestartRequired() : this.isDirty();
444    }
445
446    @Override
447    public boolean isPreferencesValid() {
448        return true; // no validity checking performed
449    }
450
451}