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