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 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("ErrorComLibLoad")); 259 return; 260 } 261 262 if (adapter.getSystemConnectionMemo() != null) { 263 systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix()); 264 connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName()); 265 NUMOPTIONS = NUMOPTIONS + 2; 266 } 267 268 refreshPortBox(); 269 270 NUMOPTIONS = NUMOPTIONS + options.size(); 271 272 portBoxLabel = new JLabel(Bundle.getMessage("UsbPortLocationLabel")); 273 274 showAdvanced.setFont(showAdvanced.getFont().deriveFont(9f)); 275 showAdvanced.setForeground(Color.blue); 276 showAdvanced.addItemListener((ItemEvent e) -> showAdvancedItems()); 277 showAdvancedItems(); 278 init = false; // need to reload action listeners 279 checkInitDone(); 280 } 281 282 @Override 283 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", 284 justification = "Type is checked before casting") 285 protected void showAdvancedItems() { 286 log.debug("* showAdvancedItems()"); 287 _details.removeAll(); 288 cL.anchor = GridBagConstraints.WEST; 289 cL.insets = new Insets(2, 5, 0, 5); 290 cR.insets = new Insets(2, 0, 0, 5); 291 cR.anchor = GridBagConstraints.WEST; 292 cR.gridx = 1; 293 cL.gridx = 0; 294 int i = 0; 295 296 boolean incAdvancedOptions = isPortAdvanced(); 297 298 if (!incAdvancedOptions) { 299 for (Map.Entry<String, Option> entry : options.entrySet()) { 300 if (entry.getValue().isAdvanced()) { 301 incAdvancedOptions = true; 302 break; 303 } 304 } 305 } 306 307 _details.setLayout(gbLayout); 308 309 i = addStandardDetails(incAdvancedOptions, i); 310 311 showAdvanced.setVisible(incAdvancedOptions); 312 313 if (incAdvancedOptions && showAdvanced.isSelected()) { 314 if (isPortAdvanced()) { 315 cR.gridy = i; 316 cL.gridy = i; 317 gbLayout.setConstraints(portBoxLabel, cL); 318 gbLayout.setConstraints(portBox, cR); 319 320 //panel.add(row1Label); 321 _details.add(portBoxLabel); 322 _details.add(portBox); 323 i++; 324 } 325 326 for (Map.Entry<String, Option> entry : options.entrySet()) { 327 if (entry.getValue().isAdvanced()) { 328 cR.gridy = i; 329 cL.gridy = i; 330 gbLayout.setConstraints(entry.getValue().getLabel(), cL); 331 gbLayout.setConstraints(entry.getValue().getComponent(), cR); 332 _details.add(entry.getValue().getLabel()); 333 _details.add(entry.getValue().getComponent()); 334 i++; 335 } 336 } 337 } 338 cL.gridwidth = 2; 339 for (JComponent item : additionalItems) { 340 cL.gridy = i; 341 gbLayout.setConstraints(item, cL); 342 _details.add(item); 343 i++; 344 } 345 cL.gridwidth = 1; 346 347 if ((_details.getParent() != null) && (_details.getParent() instanceof JViewport)) { 348 JViewport vp = (JViewport) _details.getParent(); 349 vp.revalidate(); 350 vp.repaint(); 351 } 352 } 353 354 protected int addStandardDetails(boolean incAdvanced, int i) { 355 log.debug("* addStandardDetails()"); 356 if (!isPortAdvanced()) { 357 cR.gridy = i; 358 cL.gridy = i; 359 gbLayout.setConstraints(portBoxLabel, cL); 360 gbLayout.setConstraints(portBox, cR); 361 _details.add(portBoxLabel); 362 _details.add(portBox); 363 i++; 364 } 365 366 return addStandardDetails(adapter, incAdvanced, i); 367 } 368 369 public boolean isPortAdvanced() { 370 log.debug("* isPortAdvanced()"); 371 return false; 372 } 373 374 @Override 375 public String getManufacturer() { 376 log.debug("* getManufacturer()"); 377 return adapter.getManufacturer(); 378 } 379 380 @Override 381 public void setManufacturer(String manufacturer) { 382 setInstance(); 383 log.debug("* setManufacturer('{}')", manufacturer); 384 adapter.setManufacturer(manufacturer); 385 } 386 387 @Override 388 public boolean getDisabled() { 389 log.debug("* getDisabled()"); 390 if (adapter == null) { 391 return true; 392 } 393 return adapter.getDisabled(); 394 } 395 396 @Override 397 public void setDisabled(boolean disabled) { 398 log.debug("* setDisabled({})", disabled ? "True" : "False"); 399 if (adapter != null) { 400 adapter.setDisabled(disabled); 401 } 402 } 403 404 @Override 405 public String getConnectionName() { 406 log.debug("* getConnectionName()"); 407 if ((adapter != null) && (adapter.getSystemConnectionMemo() != null)) { 408 return adapter.getSystemConnectionMemo().getUserName(); 409 } else { 410 return name(); 411 } 412 } 413 414 @Override 415 public void dispose() { 416 log.debug("* dispose()"); 417 if (adapter != null) { 418 adapter.dispose(); 419 adapter = null; 420 } 421 //removeFromActionList(); 422 super.dispose(); 423 424 } 425 426 class ComboBoxRenderer extends JLabel 427 implements ListCellRenderer<String> { 428 429 public ComboBoxRenderer() { 430 setHorizontalAlignment(LEFT); 431 setVerticalAlignment(CENTER); 432 } 433 434 /* 435 * This method finds the image and text corresponding 436 * to the selected value and returns the label, set up 437 * to display the text and image. 438 */ 439 @Override 440 public Component getListCellRendererComponent( 441 JList<? extends String> list, 442 String name, 443 int index, 444 boolean isSelected, 445 boolean cellHasFocus) { 446 447 setOpaque(index > -1); 448 setForeground(Color.black); 449 list.setSelectionForeground(Color.black); 450 if (isSelected && index > -1) { 451 setBackground(list.getSelectionBackground()); 452 } else { 453 setBackground(list.getBackground()); 454 } 455 if (invalidPort != null) { 456 if ((name == null) || name.isEmpty() || name.equals(invalidPort)) { 457 list.setSelectionForeground(Color.red); 458 setForeground(Color.red); 459 } 460 } 461 462 setText(name); 463 464 return this; 465 } 466 } 467 468 /** 469 * Handle friendly port names. Note that this changes the selection in 470 * portCombo, so that should be tracked after this returns. 471 * 472 * @param portName The currently-selected port name 473 * @param portCombo The combo box that's displaying the available ports 474 * @param portList The list of valid (unfriendly) port names 475 */ 476 protected synchronized static void updateUsbPortNames(String portName, JComboBox<String> portCombo, List<String> portList) { 477 for (int i = 0; i < portList.size(); i++) { 478 String commPort = portList.get(i); 479 portCombo.addItem(commPort); 480 if (commPort.equals(portName)) { 481 portCombo.setSelectedIndex(i); 482 } 483 } 484 } 485 486 @Nonnull 487 protected List<String> getPortNames() { 488 log.error("getPortNames() called in abstract class; should be overridden."); 489 return new ArrayList<>(); 490 } 491 492 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractUsbConnectionConfig.class); 493 494}