001package jmri.jmrix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.*;
006import java.awt.event.FocusEvent;
007import java.awt.event.FocusListener;
008import java.util.ArrayList;
009import java.util.Map;
010import java.util.TreeMap;
011import javax.annotation.Nonnull;
012import javax.swing.*;
013
014import jmri.InstanceManager;
015import jmri.jmrix.configurexml.AbstractConnectionConfigXml;
016import jmri.util.swing.JmriJOptionPane;
017import jmri.util.swing.ValidatedTextField;
018
019/**
020 * Abstract base class for common implementation of the ConnectionConfig.
021 *
022 * @author Bob Jacobsen Copyright (C) 2001, 2003
023 */
024abstract public class AbstractConnectionConfig implements ConnectionConfig {
025
026    /**
027     * Ctor for a functional object with no preexisting adapter. Expect that the
028     * subclass setInstance() will fill the adapter member.
029     * {@link AbstractConnectionConfigXml}loadCommon
030     */
031    public AbstractConnectionConfig() {
032        try {
033            systemPrefixField = new ValidatedTextField(4,
034                    true,
035                    "[A-Za-z]\\d*",
036                    Bundle.getMessage("TipPrefixFormat"));
037            // see the "Prefix Needs Migration" dialog in jmri.jmrix.configurexml.AbstractConnectionConfigXml#loadCommon
038        } catch (java.util.regex.PatternSyntaxException e) {
039            log.error("Prefix unexpected parse exception during setup", e);
040        }
041    }
042
043    /**
044     * Complete connection adapter initialization, adding desired options to the
045     * Connection Configuration pane. Required action: set init to true.
046     * Optional actions:
047     * <ul>
048     *     <li>fill in connectionNameField</li>
049     *     <li>add ActionListeners to config fields eg. systemPrefixField to update adapter after change by the user</li>
050     * </ul>
051     */
052    abstract protected void checkInitDone();
053
054    abstract public void updateAdapter();
055
056    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "Field used by implementing classes")
057    protected int NUMOPTIONS = 2;
058
059    // Load localized field names
060    protected JCheckBox showAdvanced = new JCheckBox(Bundle.getMessage("AdditionalConnectionSettings"));
061    protected JLabel systemPrefixLabel = new JLabel(Bundle.getMessage("ConnectionPrefix"));
062    protected JLabel connectionNameLabel = new JLabel(Bundle.getMessage("ConnectionName"));
063    protected ValidatedTextField systemPrefixField;
064    protected JTextField connectionNameField = new JTextField(15);
065
066    protected JPanel _details = null;
067
068    protected final Map<String, Option> options = new TreeMap<>();
069
070    /**
071     * Determine if configuration needs to be written to disk.
072     * <p>
073     * This default implementation always returns true to maintain the existing
074     * behavior.
075     *
076     * @return true if configuration need to be saved, false otherwise
077     */
078    @Override
079    public boolean isDirty() {
080        return (this.getAdapter() == null || this.getAdapter().isDirty());
081    }
082
083    /**
084     * Determine if application needs to be restarted for configuration changes
085     * to be applied.
086     * <p>
087     * The default implementation always returns true to maintain the existing
088     * behavior.
089     *
090     * @return true if application needs to restart, false otherwise
091     */
092    @Override
093    public boolean isRestartRequired() {
094        return (this.getAdapter() == null || this.getAdapter().isRestartRequired());
095    }
096
097    protected static class Option {
098
099        String optionDisplayName;
100        JComponent optionSelection;
101        Boolean advanced;
102        JLabel label = null;
103
104        public Option(String name, JComponent optionSelection, Boolean advanced) {
105            this.optionDisplayName = name;
106            this.optionSelection = optionSelection;
107            this.advanced = advanced;
108        }
109
110        protected String getDisplayName() {
111            return optionDisplayName;
112        }
113
114        public JLabel getLabel() {
115            if (label == null) {
116                label = new JLabel(getDisplayName(), JLabel.LEFT);
117            }
118            return label;
119        }
120
121        public JComponent getComponent() {
122            return optionSelection;
123        }
124
125        protected Boolean isAdvanced() {
126            return advanced;
127        }
128
129        protected void setAdvanced(Boolean boo) {
130            advanced = boo;
131        }
132
133        @SuppressWarnings("unchecked")
134        public String getItem() {
135            if (optionSelection instanceof JComboBox) {
136                return (String) ((JComboBox<String>) optionSelection).getSelectedItem();
137            } else if (optionSelection instanceof JTextField) {
138                return ((JTextField) optionSelection).getText();
139            }
140            return null;
141        }
142    }
143
144    /**
145     * Load the adapter with an appropriate object
146     * <i>unless</i> it's already been set.
147     */
148    abstract protected void setInstance();
149
150    @Override
151    abstract public String getInfo();
152
153    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "Field used by implementing classes")
154    protected ArrayList<JComponent> additionalItems = new ArrayList<>(0);
155
156    /**
157     * Load the Swing widgets needed to configure this connection into a
158     * specified JPanel. Used during the configuration process to fill out the
159     * preferences window with content specific to this Connection type. The
160     * JPanel contents need to handle their own gets/sets to the underlying
161     * Connection content.
162     *
163     * @param details the specific Swing object to be configured and filled
164     */
165    @Override
166    abstract public void loadDetails(final JPanel details);
167
168    protected GridBagLayout gbLayout = new GridBagLayout();
169    protected GridBagConstraints cL = new GridBagConstraints();
170    protected GridBagConstraints cR = new GridBagConstraints();
171
172    abstract protected void showAdvancedItems();
173
174    protected int addStandardDetails(PortAdapter adapter, boolean incAdvanced, int i) {
175        for (Map.Entry<String, Option> entry : options.entrySet()) {
176            if (!entry.getValue().isAdvanced()) {
177                cR.gridy = i;
178                cL.gridy = i;
179                gbLayout.setConstraints(entry.getValue().getLabel(), cL);
180                gbLayout.setConstraints(entry.getValue().getComponent(), cR);
181                _details.add(entry.getValue().getLabel());
182                _details.add(entry.getValue().getComponent());
183                i++;
184            }
185        }
186
187        if (adapter.getSystemConnectionMemo() != null) {
188            // Labels are field initializers created before the L&F is fully set up on some
189            // platforms (e.g. GTK); updateUI() resets the font/paint delegate to the current L&F.
190            systemPrefixLabel.updateUI();
191            connectionNameLabel.updateUI();
192            cR.gridy = i;
193            cL.gridy = i;
194            gbLayout.setConstraints(systemPrefixLabel, cL);
195            gbLayout.setConstraints(systemPrefixField, cR);
196            systemPrefixLabel.setLabelFor(systemPrefixField);
197            _details.add(systemPrefixLabel);
198            _details.add(systemPrefixField);
199            systemPrefixField.setToolTipText(Bundle.getMessage("TipPrefixFormat"));
200            i++;
201            cR.gridy = i;
202            cL.gridy = i;
203            gbLayout.setConstraints(connectionNameLabel, cL);
204            gbLayout.setConstraints(connectionNameField, cR);
205            connectionNameLabel.setLabelFor(connectionNameField);
206            _details.add(connectionNameLabel);
207            _details.add(connectionNameField);
208            i++;
209        }
210        if (incAdvanced) {
211            cL.gridwidth = 2;
212            cL.gridy = i;
213            cR.gridy = i;
214            gbLayout.setConstraints(showAdvanced, cL);
215            _details.add(showAdvanced);
216            cL.gridwidth = 1;
217            i++;
218        }
219        return i;
220    }
221
222    @Override
223    abstract public String getManufacturer();
224
225    @Override
226    abstract public void setManufacturer(String manufacturer);
227
228    @Override
229    abstract public String getConnectionName();
230
231    @Override
232    abstract public boolean getDisabled();
233
234    @Override
235    abstract public void setDisabled(boolean disable);
236
237    /**
238     * {@inheritDoc}
239     */
240    @Override
241    public void register() {
242        this.setInstance();
243        InstanceManager.getDefault(jmri.ConfigureManager.class).registerPref(this);
244        ConnectionConfigManager ccm = InstanceManager.getNullableDefault(ConnectionConfigManager.class);
245        if (ccm != null) {
246            ccm.add(this);
247        }
248    }
249
250    @Override
251    public void dispose() {
252        ConnectionConfigManager ccm = InstanceManager.getNullableDefault(ConnectionConfigManager.class);
253        if (ccm != null) {
254            ccm.remove(this);
255        }
256    }
257
258    protected void addNameEntryCheckers(@Nonnull PortAdapter adapter) {
259        if (adapter.getSystemConnectionMemo() != null) {
260            systemPrefixField.addActionListener(e -> checkPrefixEntry(adapter));
261            systemPrefixField.addFocusListener(new FocusListener() {
262                @Override
263                public void focusLost(FocusEvent e) {
264                    checkPrefixEntry(adapter);
265                }
266
267                @Override
268                public void focusGained(FocusEvent e) {
269                }
270            });
271            connectionNameField.addActionListener(e -> checkNameEntry(adapter));
272            connectionNameField.addFocusListener(new FocusListener() {
273                @Override
274                public void focusLost(FocusEvent e) {
275                    checkNameEntry(adapter);
276                }
277
278                @Override
279                public void focusGained(FocusEvent e) {
280                }
281            });
282        }
283    }
284
285    private void checkPrefixEntry(@Nonnull PortAdapter adapter) {
286        if (!systemPrefixField.isValid()) { // invalid prefix format entry, actually can't lose focus until valid
287            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
288        }
289        if (!adapter.getSystemConnectionMemo().setSystemPrefix(systemPrefixField.getText())) { // in use
290            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ConnectionPrefixDialog", systemPrefixField.getText()));
291            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
292        }
293    }
294
295    private void checkNameEntry(@Nonnull PortAdapter adapter) {
296        if (!adapter.getSystemConnectionMemo().setUserName(connectionNameField.getText())) {
297            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ConnectionNameDialog", connectionNameField.getText()));
298            connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName());
299        }
300    }
301
302    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractConnectionConfig.class);
303
304}