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.ArrayList; 012import java.util.List; 013import java.util.Map; 014 015import javax.annotation.Nonnull; 016import javax.swing.JComboBox; 017import javax.swing.JComponent; 018import javax.swing.JLabel; 019import javax.swing.JList; 020import javax.swing.JPanel; 021import javax.swing.JViewport; 022import javax.swing.ListCellRenderer; 023 024import jmri.InstanceManager; 025import jmri.UserPreferencesManager; 026import jmri.util.swing.JmriJOptionPane; 027 028/** 029 * Abstract base class for common implementation of the ConnectionConfig 030 * 031 * @author Bob Jacobsen Copyright (C) 2001, 2003 032 * @author George Warner Copyright (c) 2017-2018 033 */ 034abstract public class AbstractUsbConnectionConfig extends AbstractConnectionConfig { 035 036 /** 037 * Create a connection configuration with a preexisting adapter. This is 038 * used principally when loading a configuration that defines this 039 * connection. 040 * 041 * @param p the adapter to create a connection configuration for 042 */ 043 public AbstractUsbConnectionConfig(UsbPortAdapter p) { 044 adapter = p; 045 //addToActionList(); 046 log.debug("* AbstractUSBConnectionConfig({})", p); 047 } 048 049 /** 050 * Ctor for a functional object with no preexisting adapter. Expect that the 051 * subclass setInstance() will fill the adapter member. 052 */ 053 public AbstractUsbConnectionConfig() { 054 this(null); 055 log.debug("* AbstractUSBConnectionConfig()"); 056 } 057 058 protected UsbPortAdapter adapter; 059 060 @Override 061 public UsbPortAdapter getAdapter() { 062 log.debug("* 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 addNameEntryCheckers(adapter); 076 portBox.addFocusListener(new FocusListener() { 077 @Override 078 public void focusGained(FocusEvent e) { 079 refreshPortBox(); 080 } 081 082 @Override 083 public void focusLost(FocusEvent e) { 084 } 085 086 }); 087 088 for (Map.Entry<String, Option> entry : options.entrySet()) { 089 final String item = entry.getKey(); 090 if (entry.getValue().getComponent() instanceof JComboBox) { 091 ((JComboBox<?>) entry.getValue().getComponent()).addActionListener((ActionEvent e) -> { 092 adapter.setOptionState(item, options.get(item).getItem()); 093 }); 094 } 095 } 096 init = true; 097 } 098 } 099 100 @Override 101 public void updateAdapter() { 102 log.debug("* updateAdapter()"); 103 } 104 105 protected UserPreferencesManager p = InstanceManager.getDefault(UserPreferencesManager.class); 106 protected JComboBox<String> portBox = new JComboBox<>(); 107 protected JLabel portBoxLabel; 108 109 @Override 110 public String getInfo() { 111 log.debug("* getInfo()"); 112 String t = (String) portBox.getSelectedItem(); 113 if (t != null) { 114 return t; 115 } else if ((adapter != null) && (adapter.getCurrentPortName() != null)) { 116 return adapter.getCurrentPortName(); 117 } 118 119 return JmrixConfigPane.NONE; 120 } 121 122 List<String> newList = null; 123 List<String> originalList = null; 124 String invalidPort = null; 125 126 public void refreshPortBox() { 127 log.debug("* refreshPortBox()"); 128 if (!init) { 129 newList = getPortNames(); 130 portBox.setRenderer(new ComboBoxRenderer()); 131 // Add this line to ensure that the combo box header isn't made too narrow 132 portBox.setPrototypeDisplayValue("A fairly long port name of 40 characters"); //NO18N 133 } else { 134 List<String> v2 = getPortNames(); 135 if (v2.equals(originalList)) { 136 log.debug("List of valid Ports has not changed, therefore we will not refresh the port list"); 137 // but we will insist on setting the current value into the port 138 adapter.setPort((String) portBox.getSelectedItem()); 139 return; 140 } 141 log.debug("List of valid Ports has been changed, therefore we will refresh the port list"); 142 newList = new ArrayList<>(v2); 143 } 144 145 if (newList == null) { 146 log.error("port name List v is null!"); 147 return; 148 } 149 150 /* As we make amendments to the list of ports in newList, we keep a copy of it before 151 modification. This copy is then used to validate against any changes in the port lists. 152 */ 153 originalList = new ArrayList<>(newList); 154 if (portBox.getActionListeners().length > 0) { 155 portBox.removeActionListener(portBox.getActionListeners()[0]); 156 } 157 portBox.removeAllItems(); 158 log.debug("getting fresh list of available Serial Ports"); 159 160 if (newList.isEmpty()) { 161 newList.add(0, Bundle.getMessage("noPortsFound")); 162 } 163 String portName = adapter.getCurrentPortName(); 164 if (portName != null && !portName.equals(Bundle.getMessage("noneSelected")) && !portName.equals(Bundle.getMessage("noPortsFound"))) { 165 if (!newList.contains(portName)) { 166 newList.add(0, portName); 167 invalidPort = portName; 168 portBox.setForeground(Color.red); 169 } else if (invalidPort != null && invalidPort.equals(portName)) { 170 invalidPort = null; 171 } 172 } else { 173 if (!newList.contains(portName)) { 174 newList.add(0, Bundle.getMessage("noneSelected")); 175 } else if (p.getComboBoxLastSelection(adapter.getClass().getName() + ".port") == null) { 176 newList.add(0, Bundle.getMessage("noneSelected")); 177 } 178 } 179 180 updateUsbPortNames(portName, portBox, newList); 181 182 // If no name is selected, select one that seems most likely 183 boolean didSetName = false; 184 if ((portName == null) 185 || portName.equals(Bundle.getMessage("noneSelected")) 186 || portName.equals(Bundle.getMessage("noPortsFound"))) { 187// for (int i = 0; i < portBox.getItemCount(); i++) { 188// for (String friendlyName : getPortFriendlyNames()) { 189// if ((portBox.getItemAt(i)).contains(friendlyName)) { 190// portBox.setSelectedIndex(i); 191// adapter.setPort(portBox.getItemAt(i)); 192// didSetName = true; 193// break; 194// } 195// } 196// } 197 // if didn't set name, don't leave it hanging 198 if (!didSetName) { 199 portBox.setSelectedIndex(0); 200 } 201 } 202 // finally, insist on synchronization of selected port name with underlying port 203 204 adapter.setPort((String) portBox.getSelectedItem()); 205 206 // add a listener for later changes 207 portBox.addActionListener( 208 (ActionEvent e) -> { 209 String port = (String) portBox.getSelectedItem(); 210 adapter.setPort(port); 211 } 212 ); 213 } 214 215 /** 216 * {@inheritDoc} 217 */ 218 @Override 219 public void loadDetails(final JPanel details) { 220 log.debug("* loadDetails()"); 221 _details = details; 222 setInstance(); 223 if (!init) { 224 //Build up list of options 225 String[] optionsAvailable = adapter.getOptions(); 226 options.clear(); 227 for (String i : optionsAvailable) { 228 JComboBox<String> opt = new JComboBox<>(adapter.getOptionChoices(i)); 229 opt.setSelectedItem(adapter.getOptionState(i)); 230 // check that it worked 231 if (!adapter.getOptionState(i).equals(opt.getSelectedItem())) { 232 // no, set 1st option choice 233 opt.setSelectedIndex(0); 234 // log before setting new value to show old value 235 log.warn("Loading found invalid value for option {}, found \"{}\", setting to \"{}\"", i, adapter.getOptionState(i), opt.getSelectedItem()); 236 adapter.setOptionState(i, (String) opt.getSelectedItem()); 237 } 238 options.put(i, new Option(adapter.getOptionDisplayName(i), opt, adapter.isOptionAdvanced(i))); 239 } 240 } 241 242 try { 243 newList = getPortNames(); 244 if (log.isDebugEnabled()) { 245 log.debug("loadDetails called in class {}", this.getClass().getName()); 246 log.debug("adapter class: {}", adapter.getClass().getName()); 247 log.debug("loadDetails called for {}", name()); 248 if (newList != null) { 249 log.debug("Found {} ports", newList.size()); 250 } else { 251 log.debug("Zero-length port List"); 252 } 253 } 254 } 255 catch (UnsatisfiedLinkError e1) { 256 log.error("UnsatisfiedLinkError - the serial library has not been installed properly"); 257 log.error("java.library.path={}", System.getProperty("java.library.path", "<unknown>")); 258 log.error("Exception: ", e1); 259 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ErrorComLibLoad")); 260 return; 261 } 262 263 if (adapter.getSystemConnectionMemo() != null) { 264 systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix()); 265 connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName()); 266 NUMOPTIONS = NUMOPTIONS + 2; 267 } 268 269 refreshPortBox(); 270 271 NUMOPTIONS = NUMOPTIONS + options.size(); 272 273 portBoxLabel = new JLabel(Bundle.getMessage("UsbPortLocationLabel")); 274 275 showAdvanced.setFont(showAdvanced.getFont().deriveFont(9f)); 276 showAdvanced.setForeground(Color.blue); 277 showAdvanced.addItemListener((ItemEvent e) -> showAdvancedItems()); 278 showAdvancedItems(); 279 init = false; // need to reload action listeners 280 checkInitDone(); 281 } 282 283 @Override 284 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", 285 justification = "Type is checked before casting") 286 protected void showAdvancedItems() { 287 log.debug("* showAdvancedItems()"); 288 _details.removeAll(); 289 cL.anchor = GridBagConstraints.WEST; 290 cL.insets = new Insets(2, 5, 0, 5); 291 cR.insets = new Insets(2, 0, 0, 5); 292 cR.anchor = GridBagConstraints.WEST; 293 cR.gridx = 1; 294 cL.gridx = 0; 295 int i = 0; 296 297 boolean incAdvancedOptions = isPortAdvanced(); 298 299 if (!incAdvancedOptions) { 300 for (Map.Entry<String, Option> entry : options.entrySet()) { 301 if (entry.getValue().isAdvanced()) { 302 incAdvancedOptions = true; 303 break; 304 } 305 } 306 } 307 308 _details.setLayout(gbLayout); 309 310 i = addStandardDetails(incAdvancedOptions, i); 311 312 showAdvanced.setVisible(incAdvancedOptions); 313 314 if (incAdvancedOptions && showAdvanced.isSelected()) { 315 if (isPortAdvanced()) { 316 cR.gridy = i; 317 cL.gridy = i; 318 gbLayout.setConstraints(portBoxLabel, cL); 319 gbLayout.setConstraints(portBox, cR); 320 321 //panel.add(row1Label); 322 _details.add(portBoxLabel); 323 _details.add(portBox); 324 i++; 325 } 326 327 for (Map.Entry<String, Option> entry : options.entrySet()) { 328 if (entry.getValue().isAdvanced()) { 329 cR.gridy = i; 330 cL.gridy = i; 331 gbLayout.setConstraints(entry.getValue().getLabel(), cL); 332 gbLayout.setConstraints(entry.getValue().getComponent(), cR); 333 _details.add(entry.getValue().getLabel()); 334 _details.add(entry.getValue().getComponent()); 335 i++; 336 } 337 } 338 } 339 cL.gridwidth = 2; 340 for (JComponent item : additionalItems) { 341 cL.gridy = i; 342 gbLayout.setConstraints(item, cL); 343 _details.add(item); 344 i++; 345 } 346 cL.gridwidth = 1; 347 348 if ((_details.getParent() != null) && (_details.getParent() instanceof JViewport)) { 349 JViewport vp = (JViewport) _details.getParent(); 350 vp.revalidate(); 351 vp.repaint(); 352 } 353 } 354 355 protected int addStandardDetails(boolean incAdvanced, int i) { 356 log.debug("* addStandardDetails()"); 357 if (!isPortAdvanced()) { 358 cR.gridy = i; 359 cL.gridy = i; 360 gbLayout.setConstraints(portBoxLabel, cL); 361 gbLayout.setConstraints(portBox, cR); 362 _details.add(portBoxLabel); 363 _details.add(portBox); 364 i++; 365 } 366 367 return addStandardDetails(adapter, incAdvanced, i); 368 } 369 370 public boolean isPortAdvanced() { 371 log.debug("* isPortAdvanced()"); 372 return false; 373 } 374 375 @Override 376 public String getManufacturer() { 377 log.debug("* getManufacturer()"); 378 return adapter.getManufacturer(); 379 } 380 381 @Override 382 public void setManufacturer(String manufacturer) { 383 setInstance(); 384 log.debug("* setManufacturer('{}')", manufacturer); 385 adapter.setManufacturer(manufacturer); 386 } 387 388 @Override 389 public boolean getDisabled() { 390 log.debug("* getDisabled()"); 391 if (adapter == null) { 392 return true; 393 } 394 return adapter.getDisabled(); 395 } 396 397 @Override 398 public void setDisabled(boolean disabled) { 399 log.debug("* setDisabled({})", disabled ? "True" : "False"); 400 if (adapter != null) { 401 adapter.setDisabled(disabled); 402 } 403 } 404 405 @Override 406 public String getConnectionName() { 407 log.debug("* getConnectionName()"); 408 if ((adapter != null) && (adapter.getSystemConnectionMemo() != null)) { 409 return adapter.getSystemConnectionMemo().getUserName(); 410 } else { 411 return name(); 412 } 413 } 414 415 @Override 416 public void dispose() { 417 log.debug("* dispose()"); 418 if (adapter != null) { 419 adapter.dispose(); 420 adapter = null; 421 } 422 //removeFromActionList(); 423 super.dispose(); 424 425 } 426 427 class ComboBoxRenderer extends JLabel 428 implements ListCellRenderer<String> { 429 430 public ComboBoxRenderer() { 431 setHorizontalAlignment(LEFT); 432 setVerticalAlignment(CENTER); 433 } 434 435 /* 436 * This method finds the image and text corresponding 437 * to the selected value and returns the label, set up 438 * to display the text and image. 439 */ 440 @Override 441 public Component getListCellRendererComponent( 442 JList<? extends String> list, 443 String name, 444 int index, 445 boolean isSelected, 446 boolean cellHasFocus) { 447 448 setOpaque(index > -1); 449 setForeground(Color.black); 450 list.setSelectionForeground(Color.black); 451 if (isSelected && index > -1) { 452 setBackground(list.getSelectionBackground()); 453 } else { 454 setBackground(list.getBackground()); 455 } 456 if (invalidPort != null) { 457 if ((name == null) || name.isEmpty() || name.equals(invalidPort)) { 458 list.setSelectionForeground(Color.red); 459 setForeground(Color.red); 460 } 461 } 462 463 setText(name); 464 465 return this; 466 } 467 } 468 469 /** 470 * Handle friendly port names. Note that this changes the selection in 471 * portCombo, so that should be tracked after this returns. 472 * 473 * @param portName The currently-selected port name 474 * @param portCombo The combo box that's displaying the available ports 475 * @param portList The list of valid (unfriendly) port names 476 */ 477 protected synchronized static void updateUsbPortNames(String portName, JComboBox<String> portCombo, List<String> portList) { 478 for (int i = 0; i < portList.size(); i++) { 479 String commPort = portList.get(i); 480 portCombo.addItem(commPort); 481 if (commPort.equals(portName)) { 482 portCombo.setSelectedIndex(i); 483 } 484 } 485 } 486 487 @Nonnull 488 protected List<String> getPortNames() { 489 log.error("getPortNames() called in abstract class; should be overridden."); 490 return new ArrayList<>(); 491 } 492 493 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractUsbConnectionConfig.class); 494 495}