001package jmri.jmrit.vsdecoder.swing; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.awt.event.KeyEvent; 006import java.beans.PropertyChangeEvent; 007import java.beans.PropertyChangeListener; 008import java.text.MessageFormat; 009import java.util.ArrayList; 010import java.util.HashMap; 011import java.util.Iterator; 012import java.util.Map; 013 014import javax.swing.BorderFactory; 015import javax.swing.BoxLayout; 016import javax.swing.JButton; 017import javax.swing.JDialog; 018import javax.swing.JPanel; 019import javax.swing.JTabbedPane; 020import javax.swing.SwingUtilities; 021import javax.swing.border.TitledBorder; 022 023import jmri.InstanceManager; 024import jmri.jmrit.DccLocoAddressSelector; 025import jmri.jmrit.roster.Roster; 026import jmri.jmrit.roster.RosterEntry; 027import jmri.jmrit.roster.swing.RosterEntrySelectorPanel; 028import jmri.jmrit.vsdecoder.LoadVSDFileAction; 029import jmri.jmrit.vsdecoder.VSDConfig; 030import jmri.jmrit.vsdecoder.VSDManagerEvent; 031import jmri.jmrit.vsdecoder.VSDManagerListener; 032import jmri.jmrit.vsdecoder.VSDecoderManager; 033import jmri.util.swing.JmriJOptionPane; 034import jmri.jmrit.throttle.ThrottleControllerUI; 035 036/** 037 * Configuration dialog for setting up a new VSDecoder 038 * 039 * <hr> 040 * This file is part of JMRI. 041 * <p> 042 * JMRI is free software; you can redistribute it and/or modify it under 043 * the terms of version 2 of the GNU General Public License as published 044 * by the Free Software Foundation. See the "COPYING" file for a copy 045 * of this license. 046 * <p> 047 * JMRI is distributed in the hope that it will be useful, but WITHOUT 048 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 049 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 050 * for more details. 051 * 052 * @author Mark Underwood Copyright (C) 2011 053 */ 054public class VSDConfigDialog extends JDialog { 055 056 private static final String CONFIG_PROPERTY = "Config"; 057 058 // Map of Mnemonic KeyEvent values to GUI Components 059 private static final Map<String, Integer> Mnemonics = new HashMap<>(); 060 061 static { 062 Mnemonics.put("RosterTab", KeyEvent.VK_R); 063 Mnemonics.put("ManualTab", KeyEvent.VK_M); 064 Mnemonics.put("AddressSet", KeyEvent.VK_T); 065 Mnemonics.put("ProfileLoad", KeyEvent.VK_L); 066 Mnemonics.put("RosterSave", KeyEvent.VK_S); 067 Mnemonics.put("CloseButton", KeyEvent.VK_O); 068 Mnemonics.put("CancelButton", KeyEvent.VK_C); 069 } 070 071 // GUI Elements 072 private javax.swing.JLabel addressLabel; 073 private javax.swing.JButton addressSetButton; 074 private DccLocoAddressSelector addressSelector; 075 private RosterEntrySelectorPanel rosterSelector; 076 private javax.swing.JLabel rosterLabel; 077 private javax.swing.JButton rosterSaveButton; 078 private javax.swing.JComboBox<Object> profileComboBox; 079 private javax.swing.JButton profileLoadButton; 080 private javax.swing.JPanel rosterPanel; 081 private javax.swing.JPanel profilePanel; 082 private javax.swing.JPanel addressPanel; 083 private javax.swing.JTabbedPane locoSelectPanel; 084 private javax.swing.JButton closeButton; 085 086 private NullProfileBoxItem loadProfilePrompt; // dummy profileComboBox entry 087 private VSDConfig config; // local reference to the config being constructed by this dialog 088 private RosterEntry rosterEntry; // local reference to the selected RosterEntry 089 090 private RosterEntry rosterEntrySelected; 091 private boolean is_auto_loading; 092 private boolean is_viewing; 093 094 /** 095 * Constructor 096 * 097 * @param parent Ancestor panel 098 * @param title title for the dialog 099 * @param c Config object to be set by the dialog 100 * @param ial Is Auto Loading 101 * @param viewing Viewing mode flag 102 */ 103 public VSDConfigDialog(JPanel parent, String title, VSDConfig c, boolean ial, boolean viewing) { 104 super(SwingUtilities.getWindowAncestor(parent), title); 105 config = c; 106 is_auto_loading = ial; 107 is_viewing = viewing; 108 VSDecoderManager.instance().addEventListener(new VSDManagerListener() { 109 @Override 110 public void eventAction(VSDManagerEvent evt) { 111 vsdecoderManagerEventAction(evt); 112 } 113 }); 114 initComponents(); 115 setLocationRelativeTo(parent); 116 } 117 118 /** 119 * Init the GUI components 120 */ 121 protected void initComponents() { 122 this.setLayout(new BoxLayout(this.getContentPane(), BoxLayout.PAGE_AXIS)); 123 124 // Tabbed pane for loco select (Roster or Manual) 125 locoSelectPanel = new JTabbedPane(); 126 TitledBorder title = BorderFactory.createTitledBorder(BorderFactory.createLoweredBevelBorder(), 127 Bundle.getMessage("LocoTabbedPaneTitle")); 128 title.setTitlePosition(TitledBorder.DEFAULT_POSITION); 129 locoSelectPanel.setBorder(title); 130 131 // Roster Tab and Address Tab 132 rosterPanel = new JPanel(); 133 rosterPanel.setLayout(new BoxLayout(rosterPanel, BoxLayout.LINE_AXIS)); 134 addressPanel = new JPanel(); 135 addressPanel.setLayout(new BoxLayout(addressPanel, BoxLayout.LINE_AXIS)); 136 locoSelectPanel.addTab(Bundle.getMessage("RosterLabel"), rosterPanel); // tab name 137 locoSelectPanel.addTab(Bundle.getMessage("LocoTabbedPaneManualTab"), addressPanel); 138 //NOTE: There appears to be a bug in Swing that doesn't let Mnemonics work on a JTabbedPane when a sibling component 139 // has the focus. Oh well. 140 try { 141 locoSelectPanel.setToolTipTextAt(locoSelectPanel.indexOfTab(Bundle.getMessage("RosterLabel")), Bundle.getMessage("LTPRosterTabToolTip")); 142 locoSelectPanel.setMnemonicAt(locoSelectPanel.indexOfTab(Bundle.getMessage("RosterLabel")), Mnemonics.get("RosterTab")); 143 locoSelectPanel.setToolTipTextAt(locoSelectPanel.indexOfTab(Bundle.getMessage("LocoTabbedPaneManualTab")), Bundle.getMessage("LTPManualTabToolTip")); 144 locoSelectPanel.setMnemonicAt(locoSelectPanel.indexOfTab(Bundle.getMessage("LocoTabbedPaneManualTab")), Mnemonics.get("ManualTab")); 145 } catch (IndexOutOfBoundsException iobe) { 146 log.debug("Index out of bounds setting up tabbed Pane", iobe); 147 // Ignore out-of-bounds exception. We just won't have mnemonics or tool tips this go round 148 } 149 // Roster Tab components 150 rosterSelector = new RosterEntrySelectorPanel(); 151 rosterSelector.setNonSelectedItem(Bundle.getMessage("EmptyRosterBox")); 152 rosterSelector.setToolTipText(Bundle.getMessage("LTPRosterSelectorToolTip")); 153 //rosterComboBox.setToolTipText("tool tip for roster box"); 154 rosterSelector.addPropertyChangeListener("selectedRosterEntries", new PropertyChangeListener() { 155 @Override 156 public void propertyChange(PropertyChangeEvent pce) { 157 rosterItemSelectAction(null); 158 } 159 }); 160 rosterPanel.add(rosterSelector); 161 rosterLabel = new javax.swing.JLabel(); 162 rosterLabel.setText(Bundle.getMessage("RosterLabel")); 163 164 // Address Tab Components 165 addressLabel = new javax.swing.JLabel(); 166 addressSelector = new DccLocoAddressSelector(); 167 addressSelector.setToolTipText(Bundle.getMessage("LTPAddressSelectorToolTip", Bundle.getMessage("ButtonSet"))); 168 addressSetButton = new javax.swing.JButton(); 169 addressSetButton.setText(Bundle.getMessage("ButtonSet")); 170 addressSetButton.setEnabled(true); 171 addressSetButton.setToolTipText(Bundle.getMessage("AddressSetButtonToolTip")); 172 addressSetButton.setMnemonic(Mnemonics.get("AddressSet")); 173 addressSetButton.addActionListener(new java.awt.event.ActionListener() { 174 @Override 175 public void actionPerformed(java.awt.event.ActionEvent evt) { 176 addressSetButtonActionPerformed(evt); 177 } 178 }); 179 180 addressPanel.add(addressSelector.getCombinedJPanel()); 181 addressPanel.add(addressSetButton); 182 addressPanel.add(addressLabel); 183 184 // Profile select Pane 185 profilePanel = new JPanel(); 186 profilePanel.setLayout(new BoxLayout(profilePanel, BoxLayout.PAGE_AXIS)); 187 profileComboBox = new javax.swing.JComboBox<>(); 188 profileComboBox.setToolTipText(Bundle.getMessage("ProfileComboBoxToolTip")); 189 profileLoadButton = new JButton(Bundle.getMessage("VSDecoderFileMenuLoadVSDFile")); 190 profileLoadButton.setToolTipText(Bundle.getMessage("ProfileLoadButtonToolTip")); 191 profileLoadButton.setMnemonic(Mnemonics.get("ProfileLoad")); 192 profileLoadButton.setEnabled(true); 193 TitledBorder title2 = BorderFactory.createTitledBorder(BorderFactory.createLoweredBevelBorder(), 194 Bundle.getMessage("ProfileSelectorPaneTitle")); 195 title.setTitlePosition(TitledBorder.DEFAULT_POSITION); 196 profilePanel.setBorder(title2); 197 198 profileComboBox.setModel(new javax.swing.DefaultComboBoxModel<>()); 199 // Add any already-loaded profile names 200 ArrayList<String> sl = VSDecoderManager.instance().getVSDProfileNames(); 201 if (sl.isEmpty()) { 202 profileComboBox.setEnabled(false); 203 } else { 204 profileComboBox.setEnabled(true); 205 } 206 updateProfileList(sl); 207 profileComboBox.addItem((loadProfilePrompt = new NullProfileBoxItem())); 208 profileComboBox.setSelectedItem(loadProfilePrompt); 209 profileComboBox.addActionListener(new java.awt.event.ActionListener() { 210 @Override 211 public void actionPerformed(java.awt.event.ActionEvent evt) { 212 profileComboBoxActionPerformed(evt); 213 } 214 }); 215 profilePanel.add(profileComboBox); 216 profilePanel.add(profileLoadButton); 217 profileLoadButton.addActionListener(new java.awt.event.ActionListener() { 218 @Override 219 public void actionPerformed(java.awt.event.ActionEvent evt) { 220 profileLoadButtonActionPerformed(evt); 221 } 222 }); 223 224 rosterSaveButton = new javax.swing.JButton(); 225 rosterSaveButton.setText(Bundle.getMessage("ConfigSaveButtonLabel")); 226 rosterSaveButton.addActionListener(new ActionListener() { 227 @Override 228 public void actionPerformed(ActionEvent e) { 229 rosterSaveButtonAction(e); 230 } 231 }); 232 rosterSaveButton.setEnabled(false); // temporarily disable this until we update the RosterEntry 233 rosterSaveButton.setToolTipText(Bundle.getMessage("RosterSaveButtonToolTip")); 234 rosterSaveButton.setMnemonic(Mnemonics.get("RosterSave")); 235 236 JPanel cbPanel = new JPanel(); 237 closeButton = new JButton(Bundle.getMessage("ButtonOK")); 238 closeButton.setEnabled(false); 239 closeButton.setToolTipText(Bundle.getMessage("CD_CloseButtonToolTip")); 240 closeButton.setMnemonic(Mnemonics.get("CloseButton")); 241 closeButton.addActionListener(new java.awt.event.ActionListener() { 242 @Override 243 public void actionPerformed(java.awt.event.ActionEvent e) { 244 closeButtonActionPerformed(e); 245 } 246 }); 247 248 JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel")); 249 cancelButton.setToolTipText(Bundle.getMessage("CD_CancelButtonToolTip")); 250 cancelButton.setMnemonic(Mnemonics.get("CancelButton")); 251 cancelButton.addActionListener(new java.awt.event.ActionListener() { 252 @Override 253 public void actionPerformed(java.awt.event.ActionEvent evt) { 254 cancelButtonActionPerformed(evt); 255 } 256 }); 257 cbPanel.add(cancelButton); 258 cbPanel.add(rosterSaveButton); 259 cbPanel.add(closeButton); 260 261 this.add(locoSelectPanel); 262 this.add(profilePanel); 263 //this.add(rosterSaveButton); 264 this.add(cbPanel); 265 this.pack(); 266 this.setVisible(true); 267 } 268 269 private void cancelButtonActionPerformed(java.awt.event.ActionEvent ae) { 270 dispose(); 271 } 272 273 /** 274 * Handle the "Close" (or "OK") button action 275 */ 276 private void closeButtonActionPerformed(java.awt.event.ActionEvent ae) { 277 if (profileComboBox.getSelectedItem() == null) { 278 log.debug("Profile item selected: {}", profileComboBox.getSelectedItem()); 279 JmriJOptionPane.showMessageDialog(null, "Please select a valid Profile"); 280 rosterSaveButton.setEnabled(false); 281 closeButton.setEnabled(false); 282 } else { 283 config.setProfileName(profileComboBox.getSelectedItem().toString()); 284 log.debug("Profile item selected: {}", config.getProfileName()); 285 286 config.setLocoAddress(addressSelector.getAddress()); 287 if (getSelectedRosterItem() != null) { 288 config.setRosterEntry(getSelectedRosterItem()); 289 // decoder volume 290 String dv = config.getRosterEntry().getAttribute("VSDecoder_Volume"); 291 if (dv !=null && !dv.isEmpty()) { 292 config.setVolume(Float.parseFloat(dv)); 293 } 294 log.debug("Decoder volume in config: {}", config.getVolume()); 295 } else { 296 config.setRosterEntry(null); 297 } 298 firePropertyChange(CONFIG_PROPERTY, config, null); // open the new VSDControl 299 dispose(); 300 } 301 } 302 303 // class NullComboBoxItem 304 // 305 // little object to insert into profileComboBox when it's empty 306 static class NullProfileBoxItem { 307 @Override 308 public String toString() { 309 return Bundle.getMessage("NoLocoSelectedText"); 310 } 311 } 312 313 private void enableProfileStuff(Boolean t) { 314 closeButton.setEnabled(t); 315 profileComboBox.setEnabled(t); 316 profileLoadButton.setEnabled(t); 317 rosterSaveButton.setEnabled(t); 318 } 319 320 /** 321 * rosterItemSelectAction() 322 * 323 * ActionEventListener function for rosterSelector 324 * Chooses a RosterEntry from the list and loads its relevant info. 325 * If all VSD Infos are provided, close the Config Dialog. 326 */ 327 private void rosterItemSelectAction(ActionEvent e) { 328 if (getSelectedRosterItem() != null) { 329 log.debug("Roster Entry selected... {}", getSelectedRosterItem().getId()); 330 setRosterEntry(getSelectedRosterItem()); 331 enableProfileStuff(true); 332 333 log.debug("profile ComboBox selected item: {}", profileComboBox.getSelectedItem()); 334 // undo the close button enable if there's no profile selected (this would 335 // be when selecting a RosterEntry that doesn't have predefined VSD info) 336 if ((profileComboBox.getSelectedIndex() == -1) 337 || (profileComboBox.getSelectedItem() instanceof NullProfileBoxItem)) { 338 rosterSaveButton.setEnabled(false); 339 closeButton.setEnabled(false); 340 log.warn("No Profile found"); 341 } else { 342 closeButton.doClick(); // All done 343 } 344 } 345 } 346 347 // Roster Entry via Auto-Load from VSDManagerFrame 348 void setRosterItem(RosterEntry s) { 349 rosterEntrySelected = s; 350 log.debug("Auto-Load selected roster id: {}, profile: {}", rosterEntrySelected.getId(), 351 rosterEntrySelected.getAttribute("VSDecoder_Profile")); 352 rosterItemSelectAction(null); // trigger the next step for Auto-Load (works, but does not seem to be implemented correctly) 353 } 354 355 private RosterEntry getRosterItem() { 356 return rosterEntrySelected; 357 } 358 359 private RosterEntry getSelectedRosterItem() { 360 // Used by Auto-Load and non Auto-Load 361 if ((is_auto_loading || is_viewing) && getRosterItem() != null) { 362 rosterEntrySelected = getRosterItem(); 363 } else { 364 if (rosterSelector.getSelectedRosterEntries().length != 0) { 365 rosterEntrySelected = rosterSelector.getSelectedRosterEntries()[0]; 366 } else { 367 rosterEntrySelected = null; 368 } 369 } 370 return rosterEntrySelected; 371 } 372 373 /** 374 * rosterSaveButtonAction() 375 * 376 * ActionEventListener method for rosterSaveButton Writes VSDecoder info to 377 * the RosterEntry. 378 */ 379 private void rosterSaveButtonAction(ActionEvent e) { 380 log.debug("rosterSaveButton pressed"); 381 if (rosterSelector.getSelectedRosterEntries().length != 0) { 382 RosterEntry r = rosterSelector.getSelectedRosterEntries()[0]; 383 String profile = profileComboBox.getSelectedItem().toString(); 384 String path = VSDecoderManager.instance().getProfilePath(profile); 385 if (path == null) { 386 log.warn("Path not selected. Ignore Save button press."); 387 return; 388 } else { 389 int value = JmriJOptionPane.showConfirmDialog(null, 390 MessageFormat.format(Bundle.getMessage("UpdateRoster"), 391 new Object[]{r.titleString()}), 392 Bundle.getMessage("SaveRoster?"), JmriJOptionPane.YES_NO_OPTION); 393 if (value == JmriJOptionPane.YES_OPTION) { 394 r.putAttribute("VSDecoder_Path", path); 395 r.putAttribute("VSDecoder_Profile", profile); 396 if (r.getAttribute("VSDecoder_LaunchThrottle") == null) { 397 r.putAttribute("VSDecoder_LaunchThrottle", "no"); 398 } 399 if (r.getAttribute("VSDecoder_Volume") == null) { 400 // convert Float to String without decimal places 401 r.putAttribute("VSDecoder_Volume", String.valueOf(config.DEFAULT_VOLUME)); 402 } 403 r.updateFile(); // write and update timestamp 404 log.info("Roster Media updated for {}", r.getDisplayName()); 405 closeButton.doClick(); // All done 406 } else { 407 log.info("Roster Media not saved"); 408 } 409 } 410 } 411 } 412 413 // Probably the last setting step of the manually "Add Decoder" process 414 // (but the user also can load a VSD file and then set the address). 415 // Enable the OK button (closeButton) and the Roster Save button. 416 // note: a selected roster entry sets an Address too 417 private void profileComboBoxActionPerformed(java.awt.event.ActionEvent evt) { 418 // if there's also an Address entered, then enable the OK button. 419 if (addressSelector.getAddress() != null 420 && !(profileComboBox.getSelectedItem() instanceof NullProfileBoxItem)) { 421 closeButton.setEnabled(true); 422 // Roster Entry is required to enable the Roster Save button 423 if (rosterSelector.getSelectedRosterEntries().length != 0) { 424 rosterSaveButton.setEnabled(true); 425 } 426 } 427 } 428 429 private void profileLoadButtonActionPerformed(java.awt.event.ActionEvent evt) { 430 LoadVSDFileAction vfa = new LoadVSDFileAction(); 431 vfa.actionPerformed(evt); 432 // Note: This will trigger a PROFILE_LIST_CHANGE event from VSDecoderManager 433 } 434 435 /** 436 * handle the address "Set" button 437 */ 438 private void addressSetButtonActionPerformed(java.awt.event.ActionEvent evt) { 439 // address should be an integer, not a string 440 if (addressSelector.getAddress() == null) { 441 log.warn("Address is not valid"); 442 } 443 // if a profile is already selected enable the OK button (closeButton) 444 if ((profileComboBox.getSelectedIndex() != -1) 445 && (!(profileComboBox.getSelectedItem() instanceof NullProfileBoxItem))) { 446 closeButton.setEnabled(true); 447 } 448 } 449 450 /** 451 * handle profile list changes from the VSDecoderManager 452 */ 453 @SuppressWarnings("unchecked") 454 private void vsdecoderManagerEventAction(VSDManagerEvent evt) { 455 if (evt.getType() == VSDManagerEvent.EventType.PROFILE_LIST_CHANGE) { 456 log.debug("Received Profile List Change Event"); 457 updateProfileList((ArrayList<String>) evt.getData()); 458 } 459 } 460 461 /** 462 * Update the profile combo box 463 */ 464 private void updateProfileList(ArrayList<String> s) { 465 // There's got to be a more efficient way to do this. 466 // Most of this is about merging the new array list with 467 // the entries already in the ComboBox. 468 if (s == null) { 469 return; 470 } 471 472 // This is a bit tedious... 473 // Pull all of the existing names from the Profile ComboBox 474 ArrayList<String> ce_list = new ArrayList<>(); 475 for (int i = 0; i < profileComboBox.getItemCount(); i++) { 476 if (!(profileComboBox.getItemAt(i) instanceof NullProfileBoxItem)) { 477 ce_list.add(profileComboBox.getItemAt(i).toString()); 478 } 479 } 480 481 // Cycle through the list provided as "s" and add only 482 // those profiles that aren't already there. 483 Iterator<String> itr = s.iterator(); 484 while (itr.hasNext()) { 485 String st = itr.next(); 486 if (!ce_list.contains(st)) { 487 log.debug("added item {}", st); 488 profileComboBox.addItem(st); 489 } 490 } 491 492 // If the combo box isn't empty, enable it and enable it 493 if (profileComboBox.getItemCount() > 0) { 494 profileComboBox.setEnabled(true); 495 // select a profile if roster items are available 496 if (getSelectedRosterItem() != null) { 497 RosterEntry r = getSelectedRosterItem(); 498 String profile = r.getAttribute("VSDecoder_Profile"); 499 log.debug("Trying to set the ProfileComboBox to this Profile: {}", profile); 500 if (profile != null) { 501 profileComboBox.setSelectedItem(profile); 502 } 503 } 504 } 505 } 506 507 /** 508 * setRosterEntry() 509 * 510 * Respond to the user choosing an entry from the rosterSelector 511 * Launch a JMRI throttle (optional) 512 */ 513 private void setRosterEntry(RosterEntry entry) { 514 // Update the roster entry local var. 515 rosterEntry = entry; 516 517 // Get VSD info from Roster 518 String vsd_path = rosterEntry.getAttribute("VSDecoder_Path"); 519 String vsd_launch_throttle = rosterEntry.getAttribute("VSDecoder_LaunchThrottle"); 520 521 log.debug("Roster entry path: {}, LaunchThrottle: {}", vsd_path, vsd_launch_throttle); 522 523 // If the roster entry has VSD info stored, load it. 524 if (vsd_path == null || vsd_path.isEmpty()) { 525 log.warn("No VSD Path found for Roster Entry \"{}\". Use the \"Save to Roster\" button to add the VSD info.", 526 rosterEntry.getId()); 527 } else { 528 // Load the indicated VSDecoder Profile and update the Profile combo box 529 // This will trigger a PROFILE_LIST_CHANGE event from the VSDecoderManager. 530 boolean is_loaded = LoadVSDFileAction.loadVSDFile(vsd_path); 531 532 if (is_loaded && 533 vsd_launch_throttle != null && 534 vsd_launch_throttle.equals("yes") && 535 InstanceManager.throttleManagerInstance().getThrottleUsageCount(rosterEntry) == 0) { 536 // Launch a JMRI Throttle (if setup by the Roster media attribut and a throttle not already exists). 537 ThrottleControllerUI tf = InstanceManager.getDefault(jmri.jmrit.throttle.ThrottleFrameManager.class).createThrottleController(); 538 tf.toFront(); 539 tf.setRosterEntry(Roster.getDefault().entryFromTitle(rosterEntry.getId())); 540 } 541 } 542 543 // Set the Address box from the Roster entry. 544 // Do this after the VSDecoder create, so it will see the change. 545 addressSelector.setAddress(entry.getDccLocoAddress()); 546 addressSelector.setEnabled(true); 547 addressSetButton.setEnabled(true); 548 } 549 550 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(VSDConfigDialog.class); 551 552}