001package jmri.jmrix;
002
003import java.awt.Color;
004import java.awt.GridBagConstraints;
005import java.awt.Insets;
006import java.awt.event.ActionEvent;
007import java.awt.event.KeyEvent;
008import java.awt.event.KeyListener;
009import javax.swing.JButton;
010import java.util.Map;
011import javax.swing.JCheckBox;
012import javax.swing.JComboBox;
013import javax.swing.JComponent;
014import javax.swing.JLabel;
015import javax.swing.JPanel;
016import javax.swing.JPasswordField;
017import javax.swing.JSpinner;
018import javax.swing.JTextField;
019import javax.swing.SpinnerNumberModel;
020import jmri.InstanceManager;
021import jmri.UserPreferencesManager;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025/**
026 * Abstract base class for common implementation of the NetworkConnectionConfig.
027 *
028 * @author Bob Jacobsen Copyright (C) 2001, 2003
029 */
030abstract public class AbstractNetworkConnectionConfig extends AbstractConnectionConfig {
031
032    /**
033     * Create a connection configuration with a preexisting adapter. This is
034     * used principally when loading a configuration that defines this
035     * connection.
036     *
037     * @param p the adapter to create a connection configuration for
038     */
039    public AbstractNetworkConnectionConfig(NetworkPortAdapter p) {
040        adapter = p;
041    }
042
043    /**
044     * Ctor for a functional object with no preexisting adapter. Expect that the
045     * subclass setInstance() will fill the adapter member.
046     */
047    public AbstractNetworkConnectionConfig() {
048    }
049
050    protected boolean init = false;
051
052    /**
053     * {@inheritDoc}
054     */
055    @Override
056    protected void checkInitDone() {
057        log.debug("init called for {}", name());
058        if (init) {
059            return;
060        }
061        hostNameField.addActionListener(e -> {
062            adapter.setHostName(hostNameField.getText());
063            p.setComboBoxLastSelection(adapter.getClass().getName() + ".hostname", hostNameField.getText());
064        });
065        hostNameField.addKeyListener(new KeyListener() {
066            @Override
067            public void keyPressed(KeyEvent keyEvent) {
068            }
069
070            @Override
071            public void keyReleased(KeyEvent keyEvent) {
072                adapter.setHostName(hostNameField.getText());
073                p.setComboBoxLastSelection(adapter.getClass().getName() + ".hostname", hostNameField.getText());
074            }
075
076            @Override
077            public void keyTyped(KeyEvent keyEvent) {
078            }
079        });
080        portField.addActionListener(e -> {
081            try {
082                adapter.setPort(Integer.parseInt(portField.getText()));
083            } catch (NumberFormatException ex) {
084                log.warn("Could not parse port attribute");
085            }
086        });
087
088        portField.addKeyListener(new KeyListener() {
089            @Override
090            public void keyPressed(KeyEvent keyEvent) {
091            }
092
093            @Override
094            public void keyReleased(KeyEvent keyEvent) {
095                try {
096                    adapter.setPort(Integer.parseInt(portField.getText()));
097                } catch (java.lang.NumberFormatException ex) {
098                    log.warn("Could not parse port attribute");
099                }
100            }
101
102            @Override
103            public void keyTyped(KeyEvent keyEvent) {
104            }
105        });
106
107        adNameField.addActionListener(e -> adapter.setAdvertisementName(adNameField.getText()));
108
109        adNameField.addKeyListener(new KeyListener() {
110            @Override
111            public void keyPressed(KeyEvent keyEvent) {
112            }
113
114            @Override
115            public void keyReleased(KeyEvent keyEvent) {
116                adapter.setAdvertisementName(adNameField.getText());
117            }
118
119            @Override
120            public void keyTyped(KeyEvent keyEvent) {
121            }
122        });
123
124        serviceTypeField.addActionListener(e -> adapter.setServiceType(serviceTypeField.getText()));
125
126        serviceTypeField.addKeyListener(new KeyListener() {
127            @Override
128            public void keyPressed(KeyEvent keyEvent) {
129            }
130
131            @Override
132            public void keyReleased(KeyEvent keyEvent) {
133                adapter.setServiceType(serviceTypeField.getText());
134            }
135
136            @Override
137            public void keyTyped(KeyEvent keyEvent) {
138            }
139        });
140
141        options.entrySet().forEach(entry -> {
142            final String item = entry.getKey();
143            if (entry.getValue().getComponent() instanceof JComboBox) {
144                ((JComboBox<?>) entry.getValue().getComponent()).addActionListener((ActionEvent e) -> {
145                    log.debug("option combo box changed to {}", options.get(item).getItem());
146                    adapter.setOptionState(item, options.get(item).getItem());
147                });
148            } else if (entry.getValue().getComponent() instanceof JTextField) {
149                // listen for enter
150                ((JTextField) entry.getValue().getComponent()).addActionListener((ActionEvent e) -> {
151                    log.debug("option text field changed to {}", options.get(item).getItem());
152                    adapter.setOptionState(item, options.get(item).getItem());
153                });
154                // listen for key press so you don't have to hit enter
155                (entry.getValue().getComponent()).addKeyListener(new KeyListener() {
156                    @Override
157                    public void keyPressed(KeyEvent keyEvent) {
158                    }
159
160                    @Override
161                    public void keyReleased(KeyEvent keyEvent) {
162                        adapter.setOptionState(item, options.get(item).getItem());
163                    }
164
165                    @Override
166                    public void keyTyped(KeyEvent keyEvent) {
167                    }
168                });
169            }
170        });
171
172        addNameEntryCheckers(adapter);
173
174        // set/change delay interval between (actually before) output (Turnout) commands
175        outputIntervalSpinner.addChangeListener(e -> adapter.getSystemConnectionMemo().setOutputInterval((Integer) outputIntervalSpinner.getValue()));
176
177        init = true;
178    }
179
180    @Override
181    public void updateAdapter() {
182        if (adapter.getMdnsConfigure()) {
183            // set the hostname if it is not blank
184            if (!(hostNameField.getText().isEmpty())) {
185                adapter.setHostName(hostNameField.getText());
186            }
187            // set the advertisement name if it is not blank
188            if (!(adNameField.getText().isEmpty())) {
189                adapter.setAdvertisementName(adNameField.getText());
190            }
191            // set the Service Type if it is not blank.
192            if (!(serviceTypeField.getText().isEmpty())) {
193                adapter.setServiceType(serviceTypeField.getText());
194            }
195            // and get the host IP and port number
196            // via mdns
197            adapter.autoConfigure();
198        } else {
199            adapter.setHostName(hostNameField.getText());
200            adapter.setPort(Integer.parseInt(portField.getText()));
201        }
202        options.entrySet().forEach(entry -> {
203            adapter.setOptionState(entry.getKey(), entry.getValue().getItem());
204        });
205        if (adapter.getSystemConnectionMemo() != null && !adapter.getSystemConnectionMemo().setSystemPrefix(systemPrefixField.getText())) {
206            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
207            connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName());
208        }
209    }
210
211    UserPreferencesManager p = InstanceManager.getDefault(UserPreferencesManager.class);
212    protected JTextField hostNameField = new JTextField(15);
213    protected JLabel hostNameFieldLabel;
214    protected JTextField portField = new JTextField(10);
215    protected JLabel portFieldLabel;
216
217    protected JCheckBox showAutoConfig = new JCheckBox(Bundle.getMessage("AutoConfigLabel"));
218    protected JTextField adNameField = new JTextField(15);
219    protected JLabel adNameFieldLabel;
220    protected JTextField serviceTypeField = new JTextField(15);
221    protected JLabel serviceTypeFieldLabel;
222
223    protected SpinnerNumberModel intervalSpinner = new SpinnerNumberModel(250, 0, 10000, 1); // 10 sec max seems long enough
224    protected JSpinner outputIntervalSpinner = new JSpinner(intervalSpinner);
225    protected JLabel outputIntervalLabel;
226    protected JButton outputIntervalReset = new JButton(Bundle.getMessage("ButtonReset"));
227
228    protected NetworkPortAdapter adapter = null;
229
230    @Override
231    public NetworkPortAdapter getAdapter() {
232        return adapter;
233    }
234
235    /**
236     * {@inheritDoc}
237     */
238    @Override
239    abstract protected void setInstance();
240
241    @Override
242    public String getInfo() {
243        return adapter.getCurrentPortName();
244    }
245
246    protected void checkOptionValueValidity(String i, JComboBox<String> opt) {
247        if (!adapter.getOptionState(i).equals(opt.getSelectedItem())) {
248            // no, set 1st option choice
249            opt.setSelectedIndex(0);
250            // log before setting new value to show old value
251            log.warn("Loading found invalid value for option {}, found \"{}\", setting to \"{}\"", i, adapter.getOptionState(i), opt.getSelectedItem());
252            adapter.setOptionState(i, (String) opt.getSelectedItem());
253        }
254    }
255
256    /**
257     * {@inheritDoc}
258     */
259    @Override
260    public void loadDetails(final JPanel details) {
261        _details = details;
262        setInstance();
263        if (!init) {
264            //Build up list of options
265            //Hashtable<String, AbstractPortController.Option> adapterOptions = ((AbstractPortController)adapter).getOptionList();
266            String[] optionsAvailable = adapter.getOptions();
267            options.clear();
268            for (String i : optionsAvailable) {
269                if (adapter.isOptionTypeText(i) ) {
270                    JTextField opt = new JTextField(15);
271                    opt.setText(adapter.getOptionState(i));
272                    options.put(i, new Option(adapter.getOptionDisplayName(i), opt, adapter.isOptionAdvanced(i)));
273                } else if (adapter.isOptionTypePassword(i) ) {
274                    JTextField opt = new JPasswordField(15);
275                    opt.setText(adapter.getOptionState(i));
276                    options.put(i, new Option(adapter.getOptionDisplayName(i), opt, adapter.isOptionAdvanced(i)));
277                } else {
278                    JComboBox<String> opt = new JComboBox<>(adapter.getOptionChoices(i));
279                    opt.setSelectedItem(adapter.getOptionState(i));
280                
281                    // check that it worked
282                    checkOptionValueValidity(i, opt);
283                
284                    options.put(i, new Option(adapter.getOptionDisplayName(i), opt, adapter.isOptionAdvanced(i)));
285                }
286            }
287        }
288
289        if (hostNameField.getActionListeners().length > 0) {
290            hostNameField.removeActionListener(hostNameField.getActionListeners()[0]);
291        }
292
293        if (adapter.getSystemConnectionMemo() != null) {
294            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
295            connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName());
296            NUMOPTIONS = NUMOPTIONS + 2;
297        }
298        NUMOPTIONS = NUMOPTIONS + options.size();
299
300        hostNameField.setText(adapter.getHostName());
301        hostNameFieldLabel = new JLabel(Bundle.getMessage("HostFieldLabel"));
302        hostNameField.setToolTipText(Bundle.getMessage("HostFieldToolTip"));
303        if (adapter.getHostName() == null || adapter.getHostName().isEmpty()) {
304            hostNameField.setText(p.getComboBoxLastSelection(adapter.getClass().getName() + ".hostname"));
305            adapter.setHostName(hostNameField.getText());
306        }
307
308        portField.setToolTipText(Bundle.getMessage("PortFieldToolTip"));
309        portField.setEnabled(true);
310        portField.setText("" + adapter.getPort());
311        portFieldLabel = new JLabel(Bundle.getMessage("PortFieldLabel"));
312
313        adNameField.setToolTipText(Bundle.getMessage("AdNameFieldToolTip"));
314        adNameField.setEnabled(false);
315        adNameField.setText("" + adapter.getAdvertisementName());
316        adNameFieldLabel = new JLabel(Bundle.getMessage("AdNameFieldLabel"));
317        adNameFieldLabel.setEnabled(false);
318
319        serviceTypeField.setToolTipText(Bundle.getMessage("ServiceTypeFieldToolTip"));
320        serviceTypeField.setEnabled(false);
321        serviceTypeField.setText("" + adapter.getServiceType());
322        serviceTypeFieldLabel = new JLabel(Bundle.getMessage("ServiceTypeFieldLabel"));
323        serviceTypeFieldLabel.setEnabled(false);
324
325        // connection (memo) specific output command delay option, calls jmri.jmrix.SystemConnectionMemo#setOutputInterval(int)
326        outputIntervalLabel = new JLabel(Bundle.getMessage("OutputIntervalLabel"));
327        outputIntervalSpinner.setToolTipText(Bundle.getMessage("OutputIntervalTooltip",
328                adapter.getSystemConnectionMemo().getDefaultOutputInterval(),adapter.getManufacturer()));
329        JTextField field = ((JSpinner.DefaultEditor) outputIntervalSpinner.getEditor()).getTextField();
330        field.setColumns(6);
331        outputIntervalSpinner.setMaximumSize(outputIntervalSpinner.getPreferredSize()); // set spinner JTextField width
332        outputIntervalSpinner.setValue(adapter.getSystemConnectionMemo().getOutputInterval());
333        outputIntervalSpinner.setEnabled(true);
334        outputIntervalReset.addActionListener((ActionEvent event) -> {
335            outputIntervalSpinner.setValue(adapter.getSystemConnectionMemo().getDefaultOutputInterval());
336            adapter.getSystemConnectionMemo().setOutputInterval(adapter.getSystemConnectionMemo().getDefaultOutputInterval());
337        });
338
339        showAutoConfig.setFont(showAutoConfig.getFont().deriveFont(9f));
340        showAutoConfig.setForeground(Color.blue);
341        showAutoConfig.addItemListener(e -> setAutoNetworkConfig());
342        showAutoConfig.setSelected(adapter.getMdnsConfigure());
343        setAutoNetworkConfig();
344
345        showAdvanced.setFont(showAdvanced.getFont().deriveFont(9f));
346        showAdvanced.setForeground(Color.blue);
347        showAdvanced.addItemListener(e -> showAdvancedItems());
348        showAdvancedItems();
349
350        init = false;  // need to reload action listeners
351        checkInitDone();
352    }
353
354    @Override
355    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE",
356        justification = "type was checked before casting")
357    protected void showAdvancedItems() {
358        _details.removeAll();
359        cL.anchor = GridBagConstraints.WEST;
360        cL.insets = new Insets(2, 5, 0, 5);
361        cR.insets = new Insets(2, 0, 0, 5);
362        cR.anchor = GridBagConstraints.WEST;
363        cR.gridx = 1;
364        cL.gridx = 0;
365        int i = 0;
366        int stdrows = 0;
367        boolean incAdvancedOptions = true;
368        if (!isPortAdvanced()) {
369            stdrows++;
370        }
371        if (!isHostNameAdvanced()) {
372            stdrows++;
373        }
374        for (Map.Entry<String, Option> entry : options.entrySet()) {
375            if (!entry.getValue().isAdvanced()) {
376                stdrows++;
377            }
378        }
379        if (adapter.getSystemConnectionMemo() != null) {
380            stdrows = stdrows + 2;
381        }
382        if (stdrows == NUMOPTIONS) {
383            incAdvancedOptions = false;
384        }
385        _details.setLayout(gbLayout);
386        i = addStandardDetails(incAdvancedOptions, i);
387        if (showAdvanced.isSelected()) {
388            if (isHostNameAdvanced()) {
389                cR.gridy = i;
390                cL.gridy = i;
391                gbLayout.setConstraints(hostNameFieldLabel, cL);
392                gbLayout.setConstraints(hostNameField, cR);
393                _details.add(hostNameFieldLabel);
394                _details.add(hostNameField);
395                i++;
396            }
397
398            if (isPortAdvanced()) {
399                cR.gridy = i;
400                cL.gridy = i;
401                gbLayout.setConstraints(portFieldLabel, cL);
402                gbLayout.setConstraints(portField, cR);
403                _details.add(portFieldLabel);
404                _details.add(portField);
405                i++;
406            }
407
408            if (showAutoConfig.isSelected()) {
409                cR.gridy = i;
410                cL.gridy = i;
411                gbLayout.setConstraints(adNameFieldLabel, cL);
412                gbLayout.setConstraints(adNameField, cR);
413                _details.add(adNameFieldLabel);
414                _details.add(adNameField);
415                i++;
416                cR.gridy = i;
417                cL.gridy = i;
418                gbLayout.setConstraints(serviceTypeFieldLabel, cL);
419                gbLayout.setConstraints(serviceTypeField, cR);
420                _details.add(serviceTypeFieldLabel);
421                _details.add(serviceTypeField);
422                i++;
423            }
424
425            for (Map.Entry<String, Option> entry : options.entrySet()) {
426                if (entry.getValue().isAdvanced()) {
427                    cR.gridy = i;
428                    cL.gridy = i;
429                    gbLayout.setConstraints(entry.getValue().getLabel(), cL);
430                    gbLayout.setConstraints(entry.getValue().getComponent(), cR);
431                    _details.add(entry.getValue().getLabel());
432                    _details.add(entry.getValue().getComponent());
433                    i++;
434                }
435            }
436            // interval config field
437            cR.gridy = i;
438            cL.gridy = i;
439            gbLayout.setConstraints(outputIntervalLabel, cL);
440            _details.add(outputIntervalLabel);
441            JPanel intervalPanel = new JPanel();
442            gbLayout.setConstraints(intervalPanel, cR);
443            intervalPanel.add(outputIntervalSpinner);
444            intervalPanel.add(outputIntervalReset);
445            _details.add(intervalPanel);
446            i++;
447        }
448        cL.gridwidth = 2;
449        for (JComponent item : additionalItems) {
450            cL.gridy = i;
451            gbLayout.setConstraints(item, cL);
452            _details.add(item);
453            i++;
454        }
455        cL.gridwidth = 1;
456        if (_details.getParent() != null && _details.getParent() instanceof javax.swing.JViewport) {
457            javax.swing.JViewport vp = (javax.swing.JViewport) _details.getParent();
458            vp.revalidate();
459            vp.repaint();
460        }
461    }
462
463    protected int addStandardDetails(boolean incAdvanced, int i) {
464
465        if (isAutoConfigPossible()) {
466            cR.gridy = i;
467            cL.gridy = i;
468            gbLayout.setConstraints(showAutoConfig, cR);
469            _details.add(showAutoConfig);
470            _details.add(showAutoConfig);
471            i++;
472        }
473
474        if (!isHostNameAdvanced()) {
475            cR.gridy = i;
476            cL.gridy = i;
477            gbLayout.setConstraints(hostNameFieldLabel, cL);
478            gbLayout.setConstraints(hostNameField, cR);
479            _details.add(hostNameFieldLabel);
480            _details.add(hostNameField);
481            i++;
482        }
483
484        if (!isPortAdvanced()) {
485            cR.gridy = i;
486            cL.gridy = i;
487            gbLayout.setConstraints(portFieldLabel, cL);
488            gbLayout.setConstraints(portField, cR);
489            _details.add(portFieldLabel);
490            _details.add(portField);
491            i++;
492        }
493        return addStandardDetails(adapter, incAdvanced, i);
494    }
495
496    public boolean isHostNameAdvanced() {
497        return false;
498    }
499
500    /**
501     * Determine whether to display port in Advanced options.
502     * <p>
503     * Default in Abstract Net Conn Config. Abstract True.
504     * @return true to display port in advanced options.
505     */
506    public boolean isPortAdvanced() {
507        return true;
508    }
509
510    public boolean isAutoConfigPossible() {
511        return false;
512    }
513
514    public void setAutoNetworkConfig() {
515        if (showAutoConfig.isSelected()) {
516            portField.setEnabled(false);
517            portFieldLabel.setEnabled(false);
518            adapter.setMdnsConfigure(true);
519        } else {
520            portField.setEnabled(true);
521            portFieldLabel.setEnabled(true);
522            adapter.setMdnsConfigure(false);
523        }
524    }
525
526    @Override
527    public String getManufacturer() {
528        return adapter.getManufacturer();
529    }
530
531    @Override
532    public void setManufacturer(String manufacturer) {
533        setInstance();
534        adapter.setManufacturer(manufacturer);
535    }
536
537    @Override
538    public boolean getDisabled() {
539        if (adapter == null) {
540            return true;
541        }
542        return adapter.getDisabled();
543    }
544
545    @Override
546    public void setDisabled(boolean disabled) {
547        if (adapter != null) {
548            adapter.setDisabled(disabled);
549        }
550    }
551
552    @Override
553    public String getConnectionName() {
554        if (adapter.getSystemConnectionMemo() != null) {
555            return adapter.getSystemConnectionMemo().getUserName();
556        } else {
557            return name();
558        }
559    }
560
561    @Override
562    public void dispose() {
563        super.dispose();
564        if (adapter != null) {
565            adapter.dispose();
566            adapter = null;
567        }
568    }
569
570    private final static Logger log = LoggerFactory.getLogger(AbstractNetworkConnectionConfig.class);
571
572}