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