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            cR.gridy = i;
189            cL.gridy = i;
190            gbLayout.setConstraints(systemPrefixLabel, cL);
191            gbLayout.setConstraints(systemPrefixField, cR);
192            systemPrefixLabel.setLabelFor(systemPrefixField);
193            _details.add(systemPrefixLabel);
194            _details.add(systemPrefixField);
195            systemPrefixField.setToolTipText(Bundle.getMessage("TipPrefixFormat"));
196            i++;
197            cR.gridy = i;
198            cL.gridy = i;
199            gbLayout.setConstraints(connectionNameLabel, cL);
200            gbLayout.setConstraints(connectionNameField, cR);
201            connectionNameLabel.setLabelFor(connectionNameField);
202            _details.add(connectionNameLabel);
203            _details.add(connectionNameField);
204            i++;
205        }
206        if (incAdvanced) {
207            cL.gridwidth = 2;
208            cL.gridy = i;
209            cR.gridy = i;
210            gbLayout.setConstraints(showAdvanced, cL);
211            _details.add(showAdvanced);
212            cL.gridwidth = 1;
213            i++;
214        }
215        return i;
216    }
217
218    @Override
219    abstract public String getManufacturer();
220
221    @Override
222    abstract public void setManufacturer(String manufacturer);
223
224    @Override
225    abstract public String getConnectionName();
226
227    @Override
228    abstract public boolean getDisabled();
229
230    @Override
231    abstract public void setDisabled(boolean disable);
232
233    /**
234     * {@inheritDoc}
235     */
236    @Override
237    public void register() {
238        this.setInstance();
239        InstanceManager.getDefault(jmri.ConfigureManager.class).registerPref(this);
240        ConnectionConfigManager ccm = InstanceManager.getNullableDefault(ConnectionConfigManager.class);
241        if (ccm != null) {
242            ccm.add(this);
243        }
244    }
245
246    @Override
247    public void dispose() {
248        ConnectionConfigManager ccm = InstanceManager.getNullableDefault(ConnectionConfigManager.class);
249        if (ccm != null) {
250            ccm.remove(this);
251        }
252    }
253
254    protected void addNameEntryCheckers(@Nonnull PortAdapter adapter) {
255        if (adapter.getSystemConnectionMemo() != null) {
256            systemPrefixField.addActionListener(e -> checkPrefixEntry(adapter));
257            systemPrefixField.addFocusListener(new FocusListener() {
258                @Override
259                public void focusLost(FocusEvent e) {
260                    checkPrefixEntry(adapter);
261                }
262
263                @Override
264                public void focusGained(FocusEvent e) {
265                }
266            });
267            connectionNameField.addActionListener(e -> checkNameEntry(adapter));
268            connectionNameField.addFocusListener(new FocusListener() {
269                @Override
270                public void focusLost(FocusEvent e) {
271                    checkNameEntry(adapter);
272                }
273
274                @Override
275                public void focusGained(FocusEvent e) {
276                }
277            });
278        }
279    }
280
281    private void checkPrefixEntry(@Nonnull PortAdapter adapter) {
282        if (!systemPrefixField.isValid()) { // invalid prefix format entry, actually can't lose focus until valid
283            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
284        }
285        if (!adapter.getSystemConnectionMemo().setSystemPrefix(systemPrefixField.getText())) { // in use
286            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ConnectionPrefixDialog", systemPrefixField.getText()));
287            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
288        }
289    }
290
291    private void checkNameEntry(@Nonnull PortAdapter adapter) {
292        if (!adapter.getSystemConnectionMemo().setUserName(connectionNameField.getText())) {
293            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ConnectionNameDialog", connectionNameField.getText()));
294            connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName());
295        }
296    }
297
298    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractConnectionConfig.class);
299
300}