001package jmri.jmrix; 002 003import java.awt.Color; 004import java.awt.Component; 005import java.awt.GridBagConstraints; 006import java.awt.Insets; 007import java.awt.event.ActionEvent; 008import java.awt.event.FocusEvent; 009import java.awt.event.FocusListener; 010import java.awt.event.ItemEvent; 011import java.util.Collections; 012import java.util.Map; 013import java.util.ResourceBundle; 014import java.util.Vector; 015 016import javax.swing.JButton; 017import javax.swing.JComboBox; 018import javax.swing.JComponent; 019import javax.swing.JLabel; 020import javax.swing.JList; 021import javax.swing.JPanel; 022import javax.swing.JSpinner; 023import javax.swing.JTextField; 024import javax.swing.ListCellRenderer; 025import javax.swing.SpinnerNumberModel; 026 027import jmri.util.PortNameMapper; 028import jmri.util.PortNameMapper.SerialPortFriendlyName; 029import jmri.util.swing.JComboBoxUtil; 030 031/** 032 * Abstract base class for common implementation of the SerialConnectionConfig. 033 * 034 * @author Bob Jacobsen Copyright (C) 2001, 2003 035 */ 036abstract public class AbstractSerialConnectionConfig extends AbstractConnectionConfig { 037 038 /** 039 * Ctor for an object being created during load process. 040 * 041 * @param p port being configured 042 */ 043 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "Thought to be safe as default connection config") 044 public AbstractSerialConnectionConfig(jmri.jmrix.PortAdapter p) { 045 this((jmri.jmrix.SerialPortAdapter) p); 046 } 047 048 public AbstractSerialConnectionConfig(jmri.jmrix.SerialPortAdapter p) { 049 adapter = p; 050 } 051 052 /** 053 * Ctor for a functional object with no preexisting adapter. Expect that the 054 * subclass setInstance() will fill the adapter member. 055 */ 056 public AbstractSerialConnectionConfig() { 057 adapter = null; 058 059 } 060 061 @Override 062 public jmri.jmrix.SerialPortAdapter getAdapter() { 063 return adapter; 064 } 065 066 protected boolean init = false; 067 068 /** 069 * {@inheritDoc} 070 */ 071 @Override 072 protected void checkInitDone() { 073 log.debug("init called for {}", name()); 074 if (init) { 075 return; 076 } 077 078 baudBox.addActionListener(e -> { 079 adapter.configureBaudRate((String) baudBox.getSelectedItem()); 080 p.setComboBoxLastSelection(adapter.getClass().getName() + ".baud", (String) baudBox.getSelectedItem()); // NOI18N 081 }); 082 083 addNameEntryCheckers(adapter); 084 085 portBox.addFocusListener(new FocusListener() { 086 @Override 087 public void focusGained(FocusEvent e) { 088 refreshPortBox(); 089 } 090 091 @Override 092 public void focusLost(FocusEvent e) { 093 } 094 }); 095 096 // set/change delay interval between (actually before) output (Turnout) commands 097 outputIntervalSpinner.addChangeListener(e -> adapter.getSystemConnectionMemo().setOutputInterval((Integer) outputIntervalSpinner.getValue())); 098 099 for (Map.Entry<String, Option> entry : options.entrySet()) { 100 final String item = entry.getKey(); 101 if (entry.getValue().getComponent() instanceof JComboBox) { 102 ((JComboBox<?>) entry.getValue().getComponent()).addActionListener((ActionEvent e) -> { 103 adapter.setOptionState(item, options.get(item).getItem()); 104 }); 105 JComboBoxUtil.setupComboBoxMaxRows((JComboBox<?>) entry.getValue().getComponent()); 106 } 107 } 108 109 init = true; 110 } 111 112 @Override 113 public void updateAdapter() { 114 log.debug("updateAdapter() to {}", systemPrefixField.getText()); 115 adapter.setPort(PortNameMapper.getPortFromName((String) portBox.getSelectedItem())); 116 adapter.configureBaudRateFromIndex(baudBox.getSelectedIndex()); // manage by index, not item value 117 for (Map.Entry<String, Option> entry : options.entrySet()) { 118 adapter.setOptionState(entry.getKey(), entry.getValue().getItem()); 119 } 120 121 if (adapter.getSystemConnectionMemo() != null && !adapter.getSystemConnectionMemo().setSystemPrefix(systemPrefixField.getText())) { 122 systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix()); 123 connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName()); 124 } 125 } 126 127 jmri.UserPreferencesManager p = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class); 128 protected JComboBox<String> portBox = new JComboBox<>(); 129 protected JLabel portBoxLabel; 130 protected JComboBox<String> baudBox = new JComboBox<>(); 131 protected JLabel baudBoxLabel; 132 protected String[] baudList; 133 134 private final SpinnerNumberModel intervalSpinner = new SpinnerNumberModel(250, 0, 10000, 1); // 10 sec max seems long enough 135 // the following items are protected so they can be hidden when not applicable from a specific ConnectionConfig (ie. Simulator) implementation 136 protected JSpinner outputIntervalSpinner = new JSpinner(intervalSpinner); 137 protected JLabel outputIntervalLabel; 138 protected JButton outputIntervalReset = new JButton(Bundle.getMessage("ButtonReset")); 139 140 protected jmri.jmrix.SerialPortAdapter adapter; 141 142 /** 143 * {@inheritDoc} 144 */ 145 @Override 146 abstract protected void setInstance(); 147 148 @Override 149 public String getInfo() { 150 String t = (String) portBox.getSelectedItem(); 151 if (t != null) { 152 return PortNameMapper.getPortFromName(t); 153 //return t; 154 } else if ((adapter != null) && (adapter.getCurrentPortName() != null)) { 155 return adapter.getCurrentPortName(); 156 } 157 158 return JmrixConfigPane.NONE; 159 } 160 161// @SuppressWarnings("UseOfObsoleteCollectionType") 162 Vector<String> v; 163// @SuppressWarnings("UseOfObsoleteCollectionType") 164 Vector<String> originalList; 165 String invalidPort = null; 166 167// @SuppressWarnings("UseOfObsoleteCollectionType") 168 public void refreshPortBox() { 169 if (!init) { 170 v = getPortNames(); 171 portBox.setRenderer(new ComboBoxRenderer()); 172 // Add this line to ensure that the combo box header isn't made too narrow 173 portBox.setPrototypeDisplayValue("A fairly long port name of 40 characters"); //NO18N 174 } else { 175 Vector<String> v2 = getPortNames(); 176 if (v2.equals(originalList)) { 177 log.debug("List of valid Ports has not changed, therefore we will not refresh the port list"); 178 // but we will insist on setting the current value into the port 179 adapter.setPort(PortNameMapper.getPortFromName((String) portBox.getSelectedItem())); 180 return; 181 } 182 log.debug("List of valid Ports has been changed, therefore we will refresh the port list"); 183 v = new Vector<>(); 184 v.setSize(v2.size()); 185 Collections.copy(v, v2); 186 } 187 188 if (v == null) { 189 log.error("port name Vector v is null!"); 190 return; 191 } 192 193 /* as we make amendments to the list of port in vector v, we keep a copy of it before 194 modification, this copy is then used to validate against any changes in the port lists. 195 */ 196 originalList = new Vector<>(); 197 originalList.setSize(v.size()); 198 Collections.copy(originalList, v); 199 if (portBox.getActionListeners().length > 0) { 200 portBox.removeActionListener(portBox.getActionListeners()[0]); 201 } 202 portBox.removeAllItems(); 203 log.debug("getting fresh list of available Serial Ports"); 204 205 if (v.isEmpty()) { 206 v.add(0, Bundle.getMessage("noPortsFound")); 207 } 208 String portName = adapter.getCurrentPortName(); 209 if (portName != null && !portName.equals(Bundle.getMessage("noneSelected")) && !portName.equals(Bundle.getMessage("noPortsFound"))) { 210 if (!v.contains(portName)) { 211 v.add(0, portName); 212 invalidPort = portName; 213 portBox.setForeground(Color.red); 214 } else if (invalidPort != null && invalidPort.equals(portName)) { 215 invalidPort = null; 216 } 217 } else { 218 if (!v.contains(portName)) { 219 v.add(0, Bundle.getMessage("noneSelected")); 220 } else if (p.getComboBoxLastSelection(adapter.getClass().getName() + ".port") == null) { 221 v.add(0, Bundle.getMessage("noneSelected")); 222 } 223 } 224 updateSerialPortNames(portName, portBox, v); 225 JComboBoxUtil.setupComboBoxMaxRows(portBox); 226 227 // If there's no name selected, select one that seems most likely 228 boolean didSetName = false; 229 if (portName == null || portName.equals(Bundle.getMessage("noneSelected")) || portName.equals(Bundle.getMessage("noPortsFound"))) { 230 for (int i = 0; i < portBox.getItemCount(); i++) { 231 for (String friendlyName : getPortFriendlyNames()) { 232 if ((portBox.getItemAt(i)).contains(friendlyName)) { 233 portBox.setSelectedIndex(i); 234 adapter.setPort(PortNameMapper.getPortFromName(portBox.getItemAt(i))); 235 didSetName = true; 236 break; 237 } 238 } 239 } 240 // if didn't set name, don't leave it hanging 241 if (!didSetName) { 242 portBox.setSelectedIndex(0); 243 } 244 } 245 // finally, insist on synchronization of selected port name with underlying port 246 adapter.setPort(PortNameMapper.getPortFromName((String) portBox.getSelectedItem())); 247 248 // add a listener for later changes 249 portBox.addActionListener((ActionEvent e) -> { 250 String port = PortNameMapper.getPortFromName((String) portBox.getSelectedItem()); 251 adapter.setPort(port); 252 }); 253 } 254 255 /** 256 * {@inheritDoc} 257 */ 258 @Override 259// @SuppressWarnings("UseOfObsoleteCollectionType") 260 public void loadDetails(final JPanel details) { 261 _details = details; 262 setInstance(); 263 if (!init) { 264 //Build up list of options 265 String[] optionsAvailable = adapter.getOptions(); 266 options.clear(); 267 for (String i : optionsAvailable) { 268 JComboBox<String> opt = new JComboBox<>(adapter.getOptionChoices(i)); 269 opt.setSelectedItem(adapter.getOptionState(i)); 270 // check that it worked 271 if (!adapter.getOptionState(i).equals(opt.getSelectedItem())) { 272 // no, set 1st option choice 273 opt.setSelectedIndex(0); 274 // log before setting new value to show old value 275 log.warn("Loading found invalid value for option {}, found \"{}\", setting to \"{}\"", i, adapter.getOptionState(i), opt.getSelectedItem()); 276 adapter.setOptionState(i, (String) opt.getSelectedItem()); 277 } 278 options.put(i, new Option(adapter.getOptionDisplayName(i), opt, adapter.isOptionAdvanced(i))); 279 } 280 } 281 282 try { 283 v = getPortNames(); 284 if (log.isDebugEnabled()) { 285 log.debug("loadDetails called in class {}", this.getClass().getName()); 286 log.debug("adapter class: {}", adapter.getClass().getName()); 287 log.debug("loadDetails called for {}", name()); 288 if (v != null) { 289 log.debug("Found {} ports", v.size()); 290 } else { 291 log.debug("Zero-length port vector"); 292 } 293 } 294 } catch (java.lang.UnsatisfiedLinkError e1) { 295 log.error("UnsatisfiedLinkError - the serial library has not been installed properly"); 296 log.error("java.library.path={}", System.getProperty("java.library.path", "<unknown>")); 297 jmri.util.swing.JmriJOptionPane.showMessageDialog(null, "Failed to load comm library.\nYou have to fix that before setting preferences."); 298 return; 299 } 300 301 if (adapter.getSystemConnectionMemo() != null) { 302 systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix()); 303 connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName()); 304 NUMOPTIONS = NUMOPTIONS + 2; 305 } 306 307 refreshPortBox(); 308 309 baudList = adapter.validBaudRates(); // when not supported should not return null, but an empty String[] {} 310 // need to remove ActionListener before addItem() or action event will occur 311 if (baudBox.getActionListeners().length > 0) { 312 baudBox.removeActionListener(baudBox.getActionListeners()[0]); 313 } 314 // rebuild baudBox combo list 315 baudBox.removeAllItems(); 316 if (log.isDebugEnabled()) { 317 log.debug("after remove, {} items, first is {}", baudBox.getItemCount(), 318 baudBox.getItemAt(0)); 319 } 320 321 // empty array means: baud not supported by adapter (but extends serialConnConfig) 322 if (baudList.length == 0) { 323 log.debug("empty array received from adapter"); 324 } 325 for (String baudList1 : baudList) { 326 baudBox.addItem(baudList1); 327 } 328 if (log.isDebugEnabled()) { 329 log.debug("after reload, {} items, first is {}", baudBox.getItemCount(), 330 baudBox.getItemAt(0)); 331 } 332 333 if (baudList.length > 1) { 334 baudBox.setToolTipText(Bundle.getMessage("TipBaudRateMatch")); 335 baudBox.setEnabled(true); 336 } else { 337 baudBox.setToolTipText(Bundle.getMessage("TipBaudRateFixed")); 338 baudBox.setEnabled(false); 339 } 340 341 NUMOPTIONS = NUMOPTIONS + options.size(); 342 343 portBoxLabel = new JLabel(Bundle.getMessage("SerialPortLabel")); 344 baudBoxLabel = new JLabel(Bundle.getMessage("BaudRateLabel")); 345 if (baudBox.getItemCount() > 0) { // skip when adapter returned an empty array (= spotbug's preference) 346 baudBox.setSelectedIndex(adapter.getCurrentBaudIndex()); 347 } 348 // connection (memo) specific output command delay option, calls jmri.jmrix.SystemConnectionMemo#setOutputInterval(int) 349 outputIntervalLabel = new JLabel(Bundle.getMessage("OutputIntervalLabel")); 350 outputIntervalSpinner.setToolTipText(Bundle.getMessage("OutputIntervalTooltip", 351 adapter.getSystemConnectionMemo().getDefaultOutputInterval(),adapter.getManufacturer())); 352 JTextField field = ((JSpinner.DefaultEditor) outputIntervalSpinner.getEditor()).getTextField(); 353 field.setColumns(6); 354 outputIntervalSpinner.setMaximumSize(outputIntervalSpinner.getPreferredSize()); // set spinner JTextField width 355 outputIntervalSpinner.setValue(adapter.getSystemConnectionMemo().getOutputInterval()); 356 outputIntervalSpinner.setEnabled(true); 357 outputIntervalReset.addActionListener((ActionEvent event) -> { 358 outputIntervalSpinner.setValue(adapter.getSystemConnectionMemo().getDefaultOutputInterval()); 359 adapter.getSystemConnectionMemo().setOutputInterval(adapter.getSystemConnectionMemo().getDefaultOutputInterval()); 360 }); 361 362 showAdvanced.setFont(showAdvanced.getFont().deriveFont(9f)); 363 showAdvanced.setForeground(Color.blue); 364 showAdvanced.addItemListener((ItemEvent e) -> { 365 showAdvancedItems(); 366 }); 367 showAdvancedItems(); 368 init = false; // need to reload action listeners 369 checkInitDone(); 370 } 371 372 @Override 373 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", 374 justification = "Type is checked before casting") 375 protected void showAdvancedItems() { 376 _details.removeAll(); 377 cL.anchor = GridBagConstraints.WEST; 378 cL.insets = new Insets(2, 5, 0, 5); 379 cR.insets = new Insets(2, 0, 0, 5); 380 cR.anchor = GridBagConstraints.WEST; 381 cR.gridx = 1; 382 cL.gridx = 0; 383 int i = 0; 384 int stdrows = 0; 385 boolean incAdvancedOptions = true; 386 if (!isBaudAdvanced()) { 387 stdrows++; 388 } 389 if (!isPortAdvanced()) { 390 stdrows++; 391 } 392 for (Map.Entry<String, Option> entry : options.entrySet()) { 393 if (!entry.getValue().isAdvanced()) { 394 stdrows++; 395 } 396 } 397 398 if (adapter.getSystemConnectionMemo() != null) { 399 stdrows = stdrows + 2; 400 } 401 if (stdrows == NUMOPTIONS) { 402 incAdvancedOptions = false; 403 } 404 _details.setLayout(gbLayout); 405 i = addStandardDetails(incAdvancedOptions, i); 406 if (showAdvanced.isSelected()) { 407 408 if (isPortAdvanced()) { 409 cR.gridy = i; 410 cL.gridy = i; 411 gbLayout.setConstraints(portBoxLabel, cL); 412 gbLayout.setConstraints(portBox, cR); 413 414 _details.add(portBoxLabel); 415 _details.add(portBox); 416 i++; 417 } 418 419 if (isBaudAdvanced()) { 420 cR.gridy = i; 421 cL.gridy = i; 422 gbLayout.setConstraints(baudBoxLabel, cL); 423 gbLayout.setConstraints(baudBox, cR); 424 _details.add(baudBoxLabel); 425 _details.add(baudBox); 426 i++; 427 } 428 429 for (Map.Entry<String, Option> entry : options.entrySet()) { 430 if (entry.getValue().isAdvanced()) { 431 cR.gridy = i; 432 cL.gridy = i; 433 gbLayout.setConstraints(entry.getValue().getLabel(), cL); 434 gbLayout.setConstraints(entry.getValue().getComponent(), cR); 435 _details.add(entry.getValue().getLabel()); 436 _details.add(entry.getValue().getComponent()); 437 i++; 438 } 439 } 440 441 // interval config field 442 cR.gridy = i; 443 cL.gridy = i; 444 gbLayout.setConstraints(outputIntervalLabel, cL); 445 _details.add(outputIntervalLabel); 446 JPanel intervalPanel = new JPanel(); 447 gbLayout.setConstraints(intervalPanel, cR); 448 intervalPanel.add(outputIntervalSpinner); 449 intervalPanel.add(outputIntervalReset); 450 _details.add(intervalPanel); 451 i++; 452 453 } 454 cL.gridwidth = 2; 455 for (JComponent item : additionalItems) { 456 cL.gridy = i; 457 gbLayout.setConstraints(item, cL); 458 _details.add(item); 459 i++; 460 } 461 cL.gridwidth = 1; 462 463 if (_details.getParent() != null && _details.getParent() instanceof javax.swing.JViewport) { 464 javax.swing.JViewport vp = (javax.swing.JViewport) _details.getParent(); 465 vp.revalidate(); 466 vp.repaint(); 467 } 468 } 469 470 protected int addStandardDetails(boolean incAdvanced, int i) { 471 if (!isPortAdvanced()) { 472 cR.gridy = i; 473 cL.gridy = i; 474 gbLayout.setConstraints(portBoxLabel, cL); 475 gbLayout.setConstraints(portBox, cR); 476 _details.add(portBoxLabel); 477 _details.add(portBox); 478 i++; 479 } 480 481 if (!isBaudAdvanced()) { 482 cR.gridy = i; 483 cL.gridy = i; 484 gbLayout.setConstraints(baudBoxLabel, cL); 485 gbLayout.setConstraints(baudBox, cR); 486 _details.add(baudBoxLabel); 487 _details.add(baudBox); 488 i++; 489 } 490 491 return addStandardDetails(adapter, incAdvanced, i); 492 } 493 494 public boolean isPortAdvanced() { 495 return false; 496 } 497 498 public boolean isBaudAdvanced() { 499 return true; 500 } 501 502 @Override 503 public String getManufacturer() { 504 return adapter.getManufacturer(); 505 } 506 507 @Override 508 public void setManufacturer(String manufacturer) { 509 setInstance(); 510 adapter.setManufacturer(manufacturer); 511 } 512 513 @Override 514 public boolean getDisabled() { 515 if (adapter == null) { 516 return true; 517 } 518 return adapter.getDisabled(); 519 } 520 521 @Override 522 public void setDisabled(boolean disabled) { 523 if (adapter != null) { 524 adapter.setDisabled(disabled); 525 } 526 } 527 528 @Override 529 public String getConnectionName() { 530 if ((adapter != null) && (adapter.getSystemConnectionMemo() != null)) { 531 return adapter.getSystemConnectionMemo().getUserName(); 532 } else { 533 return name(); 534 } 535 } 536 537 @Override 538 public void dispose() { 539 super.dispose(); 540 if (adapter != null) { 541 adapter.dispose(); 542 adapter = null; 543 } 544 } 545 546 class ComboBoxRenderer extends JLabel 547 implements ListCellRenderer<String> { 548 549 public ComboBoxRenderer() { 550 setHorizontalAlignment(LEFT); 551 setVerticalAlignment(CENTER); 552 } 553 554 /* 555 * This method finds the image and text corresponding 556 * to the selected value and returns the label, set up 557 * to display the text and image. 558 */ 559 @Override 560 public Component getListCellRendererComponent( 561 JList<? extends String> list, 562 String name, 563 int index, 564 boolean isSelected, 565 boolean cellHasFocus) { 566 567 setOpaque(index > -1); 568 setForeground(Color.black); 569 list.setSelectionForeground(Color.black); 570 if (isSelected && index > -1) { 571 setBackground(list.getSelectionBackground()); 572 } else { 573 setBackground(list.getBackground()); 574 } 575 if (invalidPort != null) { 576 String port = PortNameMapper.getPortFromName(name); 577 if (port.equals(invalidPort)) { 578 list.setSelectionForeground(Color.red); 579 setForeground(Color.red); 580 } 581 } 582 583 setText(name); 584 585 return this; 586 } 587 } 588 589 /** 590 * Handle friendly port names. Note that this 591 * changes the selection in portCombo, so 592 * that should be tracked after this returns. 593 * 594 * @param portName The currently-selected port name 595 * @param portCombo The combo box that's displaying the available ports 596 * @param portList The list of valid (unfriendly) port names 597 */ 598// @SuppressWarnings("UseOfObsoleteCollectionType") 599 protected synchronized static void updateSerialPortNames(String portName, JComboBox<String> portCombo, Vector<String> portList) { 600 for (Map.Entry<String, SerialPortFriendlyName> en : PortNameMapper.getPortNameMap().entrySet()) { 601 en.getValue().setValidPort(false); 602 } 603 for (int i = 0; i < portList.size(); i++) { 604 String commPort = portList.elementAt(i); 605 SerialPortFriendlyName port = PortNameMapper.getPortNameMap().get(commPort); 606 if (port == null) { 607 port = new SerialPortFriendlyName(commPort, null); 608 PortNameMapper.getPortNameMap().put(commPort, port); 609 } 610 port.setValidPort(true); 611 portCombo.addItem(port.getDisplayName()); 612 if (commPort.equals(portName)) { 613 portCombo.setSelectedIndex(i); 614 } 615 } 616 } 617 618 /** 619 * Provide a vector of valid port names, each a String. 620 * This may be implemented differently in subclasses 621 * that e.g. do loopback or use a custom port-access library. 622 * @return Valid port names in the form used to select them later. 623 */ 624// @SuppressWarnings("UseOfObsoleteCollectionType") // historical interface 625 protected Vector<String> getPortNames() { 626 return AbstractSerialPortController.getActualPortNames(); 627 } 628 629 /** 630 * This provides a method to return potentially meaningful names that are 631 * used in OS to help identify ports against Hardware. 632 * 633 * @return array of friendly port names 634 */ 635 protected String[] getPortFriendlyNames() { 636 return new String[]{}; 637 } 638 639 /** 640 * This is purely here for systems that do not implement the 641 * SystemConnectionMemo and can be removed once they have been migrated. 642 * 643 * @return Resource bundle for action model 644 */ 645 protected ResourceBundle getActionModelResourceBundle() { 646 return null; 647 } 648 649 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSerialConnectionConfig.class); 650 651}