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