001package jmri.jmrix; 002 003import java.awt.BorderLayout; 004import java.awt.event.ActionEvent; 005import java.lang.reflect.InvocationTargetException; 006import javax.swing.BorderFactory; 007import javax.swing.BoxLayout; 008import javax.swing.JComboBox; 009import javax.swing.JComponent; 010import javax.swing.JPanel; 011import javax.swing.JScrollPane; 012import jmri.ConfigureManager; 013import jmri.InstanceManager; 014import jmri.swing.JTitledSeparator; 015import jmri.swing.PreferencesPanel; 016import jmri.util.swing.JComboBoxUtil; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020/** 021 * Provide GUI to configure communications links. 022 * <p> 023 * This is really just a catalog of connections to classes within the systems. 024 * Reflection is used to reduce coupling at load time. 025 * <p> 026 * Objects of this class are based on an underlying ConnectionConfig 027 * implementation, which in turn is obtained from the InstanceManager. Those 028 * must be created at load time by the ConfigXml process, or in some Application 029 * class. 030 * <p> 031 * The classes referenced are the specific subclasses of 032 * {@link jmri.jmrix.ConnectionConfig} which provides the methods providing data 033 * to the configuration GUI, and responding to its changes. 034 * 035 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2004, 2010 036 */ 037public class JmrixConfigPane extends JPanel implements PreferencesPanel { 038 039 public static final String NONE_SELECTED = Bundle.getMessage("noneSelected"); 040 public static final String NO_PORTS_FOUND = Bundle.getMessage("noPortsFound"); 041 public static final String NONE = Bundle.getMessage("none"); 042 // initialize logging 043 private final static Logger log = LoggerFactory.getLogger(JmrixConfigPane.class); 044 045 /* 046 * Create panel is separated off from the instance and synchronized, so that only 047 * one connection can be configured at once. This prevents multiple threads from 048 * trying to create the same panel at the same time. 049 */ 050 /** 051 * Create a new connection configuration panel. 052 * 053 * @param index the index of the desired connection configuration from 054 * {@link jmri.jmrix.ConnectionConfigManager#getConnections(int)} 055 * @return the panel for the requested connection or for a new connection if 056 * index did not match an existing connection configuration 057 */ 058 public static synchronized JmrixConfigPane createPanel(int index) { 059 ConnectionConfig c = null; 060 try { 061 c = InstanceManager.getDefault(ConnectionConfigManager.class).getConnections(index); 062 log.debug("connection {} is {}", index, c); 063 } catch (IndexOutOfBoundsException ex) { 064 log.debug("connection {} is null, creating new one", index); 065 } 066 return createPanel(c); 067 } 068 069 /** 070 * Create a new configuration panel for the given connection. 071 * 072 * @param c the connection; if null, the panel is ready for a new connection 073 * @return the new panel 074 */ 075 public static synchronized JmrixConfigPane createPanel(ConnectionConfig c) { 076 JmrixConfigPane pane = new JmrixConfigPane(c); 077 if (c == null) { 078 pane.isDirty = true; 079 } 080 return pane; 081 } 082 083 /** 084 * Get access to a new pane for creating new connections. 085 * 086 * @return a new configuration panel 087 */ 088 public static JmrixConfigPane createNewPanel() { 089 return createPanel(null); 090 } 091 092 /** 093 * Disposes of the underlying connection for a configuration pane. 094 * 095 * @param confPane the pane to dispose of 096 */ 097 public static void dispose(JmrixConfigPane confPane) { 098 if (confPane == null) { 099 log.debug("no instance found therefore can not dispose of it!"); 100 return; 101 } 102 103 if (confPane.ccCurrent != null) { 104 try { 105 confPane.ccCurrent.dispose(); 106 } catch (RuntimeException ex) { 107 log.error("Error Occurred while disposing connection {}", ex.toString()); 108 } 109 } 110 ConfigureManager cmOD = InstanceManager.getNullableDefault(jmri.ConfigureManager.class); 111 if (cmOD != null) { 112 cmOD.deregister(confPane); 113 cmOD.deregister(confPane.ccCurrent); 114 } 115 InstanceManager.getDefault(ConnectionConfigManager.class).remove(confPane.ccCurrent); 116 } 117 118 private boolean isDirty = false; 119 120 JComboBox<String> modeBox = new JComboBox<>(); 121 JComboBox<String> manuBox = new JComboBox<>(); 122 123 JPanel details = new JPanel(); 124 String[] classConnectionNameList; 125 ConnectionConfig[] classConnectionList; 126 String[] manufactureNameList; 127 128 jmri.UserPreferencesManager p = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class); 129 130 ConnectionConfig ccCurrent = null; 131 132 protected JmrixConfigPane() { 133 } 134 135 /** 136 * Use "instance" to get one of these. That allows it to reconnect to 137 * existing information in an existing ConnectionConfig object. It's 138 * permitted to call this with a null argument, e.g. for when first 139 * configuring the system. 140 * @param original Existing ConnectionConfig object to (re)connect with 141 */ 142 protected JmrixConfigPane(ConnectionConfig original) { 143 144 ConnectionConfigManager manager = InstanceManager.getDefault(ConnectionConfigManager.class); 145 ccCurrent = original; 146 147 setLayout(new BorderLayout()); 148 this.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8)); 149 150 manuBox.addItem(NONE_SELECTED); 151 152 manufactureNameList = manager.getConnectionManufacturers(); 153 for (String manuName : manufactureNameList) { 154 if (original != null && original.getManufacturer() != null 155 && original.getManufacturer().equals(manuName)) { 156 manuBox.addItem(manuName); 157 manuBox.setSelectedItem(manuName); 158 } else { 159 manuBox.addItem(manuName); 160 } 161 } 162 JComboBoxUtil.setupComboBoxMaxRows(manuBox); 163 164 manuBox.addActionListener((ActionEvent evt) -> { 165 updateComboConnection(); 166 }); 167 168 // get the list of ConnectionConfig items into a selection box 169 String selectedItem = (String) manuBox.getSelectedItem(); 170 if (selectedItem != null) { 171 classConnectionNameList = manager.getConnectionTypes(selectedItem); 172 } 173 classConnectionList = new jmri.jmrix.ConnectionConfig[classConnectionNameList.length + 1]; 174 modeBox.addItem(NONE_SELECTED); 175 if (manuBox.getSelectedIndex() != 0) { 176 modeBox.setEnabled(true); 177 } else { 178 modeBox.setSelectedIndex(0); 179 modeBox.setEnabled(false); 180 } 181 int n = 1; 182 if (manuBox.getSelectedIndex() != 0) { 183 for (String className : classConnectionNameList) { 184 try { 185 ConnectionConfig config; 186 if (original != null && original.getClass().getName().equals(className)) { 187 config = original; 188 log.debug("matched existing config object"); 189 modeBox.addItem(config.name()); 190 modeBox.setSelectedItem(config.name()); 191 if (classConnectionNameList.length == 1) { 192 modeBox.setSelectedIndex(1); 193 } 194 } else { 195 Class<?> cl = Class.forName(className); 196 config = (ConnectionConfig) cl.getDeclaredConstructor().newInstance(); 197 if( !(config instanceof StreamConnectionConfig)) { 198 // only include if the connection is not a 199 // StreamConnection. Those connections require 200 // additional context. 201 modeBox.addItem(config.name()); 202 } else { 203 continue; 204 } 205 } 206 classConnectionList[n++] = config; 207 } catch (NullPointerException e) { 208 log.error("Attempt to load {} failed.", className, e); 209 } catch (InvocationTargetException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { 210 log.error("Attempt to load {} failed", className, e); 211 } 212 } 213 if ((modeBox.getSelectedIndex() == 0) && (p.getComboBoxLastSelection((String) manuBox.getSelectedItem()) != null)) { 214 modeBox.setSelectedItem(p.getComboBoxLastSelection((String) manuBox.getSelectedItem())); 215 } 216 } 217 JComboBoxUtil.setupComboBoxMaxRows(modeBox); 218 219 modeBox.addActionListener((ActionEvent a) -> { 220 if ((String) modeBox.getSelectedItem() != null) { 221 if (!((String) modeBox.getSelectedItem()).equals(NONE_SELECTED)) { 222 p.setComboBoxLastSelection((String) manuBox.getSelectedItem(), (String) modeBox.getSelectedItem()); 223 } 224 } 225 selection(); 226 }); 227 JPanel manufacturerPanel = new JPanel(); 228 manufacturerPanel.add(manuBox); 229 JPanel connectionPanel = new JPanel(); 230 connectionPanel.add(modeBox); 231 JPanel initialPanel = new JPanel(); 232 initialPanel.setLayout(new BoxLayout(initialPanel, BoxLayout.Y_AXIS)); 233 initialPanel.add(new JTitledSeparator(Bundle.getMessage("SystemManufacturer"))); // NOI18N 234 initialPanel.add(manufacturerPanel); 235 initialPanel.add(new JTitledSeparator(Bundle.getMessage("SystemConnection"))); // NOI18N 236 initialPanel.add(connectionPanel); 237 add(initialPanel, BorderLayout.NORTH); 238 initialPanel.add(new JTitledSeparator(Bundle.getMessage("Settings"))); // NOI18N 239 JScrollPane scroll = new JScrollPane(details); 240 scroll.setBorder(BorderFactory.createEmptyBorder()); 241 add(scroll, BorderLayout.CENTER); 242 243 selection(); // first time through, pretend we've selected a value 244 // to load the rest of the GUI 245 } 246 247 public void updateComboConnection() { 248 modeBox.removeAllItems(); 249 modeBox.addItem(NONE_SELECTED); 250 String selectedItem = (String) manuBox.getSelectedItem(); 251 if (selectedItem != null) { 252 classConnectionNameList = InstanceManager.getDefault(ConnectionConfigManager.class).getConnectionTypes(selectedItem); 253 } 254 classConnectionList = new jmri.jmrix.ConnectionConfig[classConnectionNameList.length + 1]; 255 256 if (manuBox.getSelectedIndex() != 0) { 257 int n = 1; 258 modeBox.setEnabled(true); 259 for (String classConnectionNameList1 : classConnectionNameList) { 260 try { 261 jmri.jmrix.ConnectionConfig config; 262 Class<?> cl = Class.forName(classConnectionNameList1); 263 config = (jmri.jmrix.ConnectionConfig) cl.getDeclaredConstructor().newInstance(); 264 if( !(config instanceof StreamConnectionConfig)) { 265 // only include if the connection is not a 266 // StreamConnection. Those connections require 267 // additional context. 268 modeBox.addItem(config.name()); 269 } else { 270 continue; 271 } 272 classConnectionList[n++] = config; 273 if (classConnectionNameList.length == 1) { 274 modeBox.setSelectedIndex(1); 275 } 276 } catch (InvocationTargetException | NullPointerException | ClassNotFoundException 277 | InstantiationException | IllegalAccessException | NoSuchMethodException e) { 278 log.warn("Attempt to load {} failed", classConnectionNameList1, e); 279 } 280 } 281 if (p.getComboBoxLastSelection((String) manuBox.getSelectedItem()) != null) { 282 modeBox.setSelectedItem(p.getComboBoxLastSelection((String) manuBox.getSelectedItem())); 283 } 284 } else { 285 modeBox.setSelectedIndex(0); 286 modeBox.setEnabled(false); 287 if (ccCurrent != null) { 288 ccCurrent.dispose(); 289 } 290 } 291 } 292 293 void selection() { 294 ConnectionConfig old = this.ccCurrent; 295 int current = modeBox.getSelectedIndex(); 296 details.removeAll(); 297 // first choice is -no- protocol chosen 298 log.debug("new selection is {} {}", current, modeBox.getSelectedItem()); 299 if ((current != 0) && (current != -1)) { 300 if ((ccCurrent != null) && (ccCurrent != classConnectionList[current])) { 301 ccCurrent.dispose(); 302 } 303 ccCurrent = classConnectionList[current]; 304 ccCurrent.setManufacturer((String) manuBox.getSelectedItem()); 305 ccCurrent.loadDetails(details); 306 } else { 307 if (ccCurrent != null) { 308 ccCurrent.dispose(); 309 } 310 } 311 if (old != this.ccCurrent) { 312 assert this.ccCurrent != null; 313 this.ccCurrent.register(); 314 } 315 validate(); 316 317 repaint(); 318 } 319 320 public String getConnectionName() { 321 int current = modeBox.getSelectedIndex(); 322 if (current == 0) { 323 return null; 324 } 325 return classConnectionList[current].getConnectionName(); 326 } 327 328 public String getCurrentManufacturerName() { 329 int current = modeBox.getSelectedIndex(); 330 if (current == 0) { 331 return NONE; 332 } 333 return classConnectionList[current].getManufacturer(); 334 } 335 336 public String getCurrentProtocolName() { 337 int current = modeBox.getSelectedIndex(); 338 if (current == 0) { 339 return NONE; 340 } 341 return classConnectionList[current].name(); 342 } 343 344 public String getCurrentProtocolInfo() { 345 int current = modeBox.getSelectedIndex(); 346 if (current == 0) { 347 return NONE; 348 } 349 return classConnectionList[current].getInfo(); 350 } 351 352 public ConnectionConfig getCurrentObject() { 353 int current = modeBox.getSelectedIndex(); 354 if (current != 0) { 355 return classConnectionList[current]; 356 } 357 return null; 358 } 359 360 public boolean getDisabled() { 361 int current = modeBox.getSelectedIndex(); 362 if (current == 0) { 363 return false; 364 } 365 return classConnectionList[current].getDisabled(); 366 } 367 368 public void setDisabled(boolean disabled) { 369 int current = modeBox.getSelectedIndex(); 370 if (current == 0) { 371 return; 372 } 373 classConnectionList[current].setDisabled(disabled); 374 } 375 376 @Override 377 public String getPreferencesItem() { 378 return "CONNECTIONS"; // NOI18N 379 } 380 381 @Override 382 public String getPreferencesItemText() { 383 return Bundle.getMessage("MenuConnections"); // NOI18N 384 } 385 386 @Override 387 public String getTabbedPreferencesTitle() { 388 String title = this.getConnectionName(); 389 if (title == null 390 && this.getCurrentProtocolName() != null 391 && !this.getCurrentProtocolName().equals(JmrixConfigPane.NONE)) { 392 title = this.getCurrentProtocolName(); 393 } 394 if (title != null && !this.getDisabled()) { 395 title = "(" + title + ")"; 396 } 397 return title; 398 } 399 400 @Override 401 public String getLabelKey() { 402 return null; 403 } 404 405 @Override 406 public JComponent getPreferencesComponent() { 407 return this; 408 } 409 410 @Override 411 public boolean isPersistant() { 412 return true; 413 } 414 415 @Override 416 public String getPreferencesTooltip() { 417 return this.getTabbedPreferencesTitle(); 418 } 419 420 @Override 421 public void savePreferences() { 422 // do nothing - the persistent manager will take care of this 423 } 424 425 @Override 426 public boolean isDirty() { 427 // avoid potentially expensive extra test for isDirty 428 if (log.isDebugEnabled()) { 429 log.debug("Connection \"{}\" is {}.", 430 this.getConnectionName(), 431 (this.isDirty || ((this.ccCurrent == null) || this.ccCurrent.isDirty()) ? "dirty" : "clean")); 432 } 433 return this.isDirty || ((this.ccCurrent == null) || this.ccCurrent.isDirty()); 434 } 435 436 @Override 437 public boolean isRestartRequired() { 438 return (this.ccCurrent != null) ? this.ccCurrent.isRestartRequired() : this.isDirty(); 439 } 440 441 @Override 442 public boolean isPreferencesValid() { 443 return true; // no validity checking performed 444 } 445 446}