001package jmri.managers; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.Component; 006import java.awt.Dimension; 007import java.awt.Point; 008import java.awt.Toolkit; 009import java.io.File; 010import java.io.FileNotFoundException; 011import java.lang.reflect.Constructor; 012import java.lang.reflect.InvocationTargetException; 013import java.lang.reflect.Method; 014import java.util.ArrayList; 015import java.util.HashMap; 016import java.util.HashSet; 017import java.util.Map.Entry; 018import java.util.Set; 019import java.util.concurrent.ConcurrentHashMap; 020import javax.annotation.Nonnull; 021import javax.annotation.CheckForNull; 022import javax.swing.BoxLayout; 023import javax.swing.JCheckBox; 024import javax.swing.JLabel; 025import javax.swing.JPanel; 026import jmri.ConfigureManager; 027import jmri.InstanceInitializer; 028import jmri.InstanceManager; 029import jmri.InstanceManagerAutoInitialize; 030import jmri.JmriException; 031import jmri.UserPreferencesManager; 032import jmri.beans.Bean; 033import jmri.implementation.AbstractInstanceInitializer; 034import jmri.profile.Profile; 035import jmri.profile.ProfileManager; 036import jmri.profile.ProfileUtils; 037import jmri.swing.JmriJTablePersistenceManager; 038import jmri.util.FileUtil; 039import jmri.util.JmriJFrame; 040import jmri.util.jdom.JDOMUtil; 041import jmri.util.node.NodeIdentity; 042import jmri.util.swing.JmriJOptionPane; 043import org.jdom2.DataConversionException; 044import org.jdom2.Element; 045import org.jdom2.JDOMException; 046import org.openide.util.lookup.ServiceProvider; 047 048/** 049 * Implementation of {@link UserPreferencesManager} that saves user interface 050 * preferences that should be automatically remembered as they are set. 051 * <p>This class is intended to be a transitional class from a single user 052 * interface preferences manager to multiple, domain-specific (windows, tables, 053 * dialogs, etc) user interface preferences managers. Domain-specific managers 054 * can more efficiently, both in the API and at runtime, handle each user 055 * interface preference need than a single monolithic manager.</p> 056 * 057 * <p>The following items are available. Each item has its own section in the 058 * <b>user-interface.xml</b> file.</p> 059 * 060 * <dl> 061 * <dt><b>Class Preferences</b></dt> 062 * <dd>This contains reminders and selections from dialogs displayed to users. These are normally 063 * related to the JMRI NamedBeans represented by the various PanelPro tables. The 064 * responses are shown in <b>Preferences -> Messages</b>. This provides the ability to 065 * revert previous choices. See {@link jmri.jmrit.beantable.usermessagepreferences.UserMessagePreferencesPane} 066 * 067 * <p>The dialogs are invoked by the various <b>show<Info|Warning|Error>Message</b> dialogs. 068 * 069 * There are two types of messages created by the dialogs.</p> 070 * <dl> 071 * <dt><b>multipleChoice</b></dt> 072 * <dd>The multiple choice message has a keyword and the selected option. It only exists when the 073 * selected option index is greater than zero.</dd> 074 * 075 * <dt><b>reminderPrompts</b></dt> 076 * <dd>The reminder prompt message has a keyword, such as <i>remindSaveRoute</i>. It only exists when 077 * the reminder is active.</dd> 078 * </dl> 079 * 080 * <p>When the <i>Skip message in future?</i> or <i>Remember this setting for next time?</i> is selected, 081 * an entry will be added. The {@link #setClassDescription(String)} method will use Java reflection 082 * to request additional information from the class that was used to the show dialog. This requires some 083 * specific changes to the originating class.</p> 084 * 085 * <dl> 086 * <dt><b>Class Constructor</b></dt> 087 * <dd>A constructor without parameters is required. This is used to get the class so that 088 * the following public methods can be invoked.</dd> 089 * 090 * <dt><b>getClassDescription()</b></dt> 091 * <dd>This returns a string that will be used by <b>Preferences -> Messages</b>.</dd> 092 * 093 * <dt><b>setMessagePreferenceDetails()</b></dt> 094 * <dd>This does not return anything directly. It makes call backs using two methods. 095 * <dl> 096 * <dt>{@link #setMessageItemDetails(String, String, String, HashMap, int)}</dt> 097 * <dd>Descriptive information, the items for a combo box and the current selection are sent. 098 * This information is used to create the <b>multipleChoice</b> item.</dd> 099 * 100 * <dt>{@link #setPreferenceItemDetails(String, String, String)}</dt> 101 * <dd>Descriptive information is sent to create the <b>reminderPrompt</b> item.</dd> 102 * </dl> 103 * </dd> 104 * </dl> 105 * <p>The messages are normally created by the various NamedBean classes. LogixNG uses a 106 * separate class instead of changing each affected class. This provides a concise example 107 * of the required changes at 108 * <a href="https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/logixng/LogixNG_UserPreferences.java">LogixNG_UserPreferences</a></p> 109 * </dd> 110 * 111 * <dt><b>Checkbox State</b></dt> 112 * <dd>Contains the last checkbox state.<br>Methods: 113 * <ul> 114 * <li>{@link #getCheckboxPreferenceState(String, boolean)}</li> 115 * <li>{@link #setCheckboxPreferenceState(String, boolean)}</li> 116 * </ul> 117 * </dd> 118 * 119 * <dt><b>Combobox Selection</b></dt> 120 * <dd>Contains the last combo box selection.<br>Methods: 121 * <ul> 122 * <li>{@link #getComboBoxLastSelection(String)}</li> 123 * <li>{@link #setComboBoxLastSelection(String, String)}</li> 124 * </ul> 125 * </dd> 126 * 127 * <dt><b>Settings</b></dt> 128 * <dd>The existence of an entry indicates a true state.<br>Methods: 129 * <ul> 130 * <li>{@link #getSimplePreferenceState(String)}</li> 131 * <li>{@link #setSimplePreferenceState(String, boolean)}</li> 132 * </ul> 133 * </dd> 134 * 135 * <dt><b>Window Details</b></dt> 136 * <dd>The main data is the window location and size. This is handled by 137 * {@link jmri.util.JmriJFrame}. The window details can also include 138 * window specific properties.<br>Methods: 139 * <ul> 140 * <li>{@link #getProperty(String, String)}</li> 141 * <li>{@link #setProperty(String, String, Object)}</li> 142 * </ul> 143 * </dd> 144 * </dl> 145 * 146 * 147 * 148 * @author Randall Wood (C) 2016 149 */ 150public class JmriUserPreferencesManager extends Bean implements UserPreferencesManager, InstanceManagerAutoInitialize { 151 152 public static final String SAVE_ALLOWED = "saveAllowed"; 153 154 private static final String CLASSPREFS_NAMESPACE = "http://jmri.org/xml/schema/auxiliary-configuration/class-preferences-4-3-5.xsd"; // NOI18N 155 private static final String CLASSPREFS_ELEMENT = "classPreferences"; // NOI18N 156 private static final String COMBOBOX_NAMESPACE = "http://jmri.org/xml/schema/auxiliary-configuration/combobox-4-3-5.xsd"; // NOI18N 157 private static final String COMBOBOX_ELEMENT = "comboBoxLastValue"; // NOI18N 158 private static final String CHECKBOX_NAMESPACE = "http://jmri.org/xml/schema/auxiliary-configuration/checkbox-4-21-3.xsd"; // NOI18N 159 private static final String CHECKBOX_ELEMENT = "checkBoxLastValue"; // NOI18N 160 private static final String SETTINGS_NAMESPACE = "http://jmri.org/xml/schema/auxiliary-configuration/settings-4-3-5.xsd"; // NOI18N 161 private static final String SETTINGS_ELEMENT = "settings"; // NOI18N 162 private static final String WINDOWS_NAMESPACE = "http://jmri.org/xml/schema/auxiliary-configuration/window-details-4-3-5.xsd"; // NOI18N 163 private static final String WINDOWS_ELEMENT = "windowDetails"; // NOI18N 164 165 private static final String REMINDER = "reminder"; 166 private static final String JMRI_UTIL_JMRI_JFRAME = "jmri.util.JmriJFrame"; 167 private static final String CLASS = "class"; 168 private static final String VALUE = "value"; 169 private static final String WIDTH = "width"; 170 private static final String HEIGHT = "height"; 171 private static final String PROPERTIES = "properties"; 172 173 private boolean dirty = false; 174 private boolean loading = false; 175 private boolean allowSave; 176 private final ArrayList<String> simplePreferenceList = new ArrayList<>(); 177 //sessionList is used for messages to be suppressed for the current JMRI session only 178 private final ArrayList<String> sessionPreferenceList = new ArrayList<>(); 179 protected final HashMap<String, String> comboBoxLastSelection = new HashMap<>(); 180 protected final HashMap<String, Boolean> checkBoxLastSelection = new HashMap<>(); 181 private final HashMap<String, WindowLocations> windowDetails = new HashMap<>(); 182 private final HashMap<String, ClassPreferences> classPreferenceList = new HashMap<>(); 183 private File file; 184 185 public JmriUserPreferencesManager() { 186 // prevent attempts to write during construction 187 this.allowSave = false; 188 189 //I18N in ManagersBundle.properties (this is a checkbox on prefs tab Messages|Misc items) 190 this.setPreferenceItemDetails(getClassName(), REMINDER, Bundle.getMessage("HideReminderLocationMessage")); // NOI18N 191 //I18N in ManagersBundle.properties (this is the title of prefs tab Messages|Misc items) 192 this.classPreferenceList.get(getClassName()).setDescription(Bundle.getMessage("UserPreferences")); // NOI18N 193 194 // allow attempts to write 195 this.allowSave = true; 196 this.dirty = false; 197 } 198 199 @Override 200 public synchronized void setSaveAllowed(boolean saveAllowed) { 201 boolean old = this.allowSave; 202 this.allowSave = saveAllowed; 203 if (saveAllowed && this.dirty) { 204 this.savePreferences(); 205 } 206 this.firePropertyChange(SAVE_ALLOWED, old, this.allowSave); 207 } 208 209 @Override 210 public synchronized boolean isSaveAllowed() { 211 return this.allowSave; 212 } 213 214 @Override 215 public Dimension getScreen() { 216 return Toolkit.getDefaultToolkit().getScreenSize(); 217 } 218 219 /** 220 * This is used to remember the last selected state of a checkBox and thus 221 * allow that checkBox to be set to a true state when it is next 222 * initialized. This can also be used anywhere else that a simple yes/no, 223 * true/false type preference needs to be stored. 224 * <p> 225 * It should not be used for remembering if a user wants to suppress a 226 * message as there is no means in the GUI for the user to reset the flag. 227 * setPreferenceState() should be used in this instance The name is 228 * free-form, but to avoid ambiguity it should start with the package name 229 * (package.Class) for the primary using class. 230 * 231 * @param name A unique name to identify the state being stored 232 * @param state simple boolean. 233 */ 234 @Override 235 public void setSimplePreferenceState(String name, boolean state) { 236 if (state) { 237 if (!simplePreferenceList.contains(name)) { 238 simplePreferenceList.add(name); 239 } 240 } else { 241 simplePreferenceList.remove(name); 242 } 243 this.saveSimplePreferenceState(); 244 } 245 246 @Override 247 public boolean getSimplePreferenceState(String name) { 248 return simplePreferenceList.contains(name); 249 } 250 251 @Nonnull 252 @Override 253 public ArrayList<String> getSimplePreferenceStateList() { 254 return new ArrayList<>(simplePreferenceList); 255 } 256 257 /** 258 * Displays remember dialogue on save. 259 * {@inheritDoc} 260 */ 261 @Override 262 public void setPreferenceState(String strClass, String item, boolean state) { 263 // convert old manager preferences to new manager preferences 264 if (strClass.equals("jmri.managers.DefaultUserMessagePreferences")) { 265 this.setPreferenceState("jmri.managers.JmriUserPreferencesManager", item, state); 266 return; 267 } 268 if (!classPreferenceList.containsKey(strClass)) { 269 classPreferenceList.put(strClass, new ClassPreferences()); 270 setClassDescription(strClass); 271 } 272 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 273 boolean found = false; 274 for (int i = 0; i < a.size(); i++) { 275 if (a.get(i).getItem().equals(item)) { 276 a.get(i).setState(state); 277 found = true; 278 } 279 } 280 if (!found) { 281 a.add(new PreferenceList(item, state)); 282 } 283 displayRememberMsg(); 284 this.savePreferencesState(); 285 } 286 287 @Override 288 public boolean getPreferenceState(String strClass, String item) { 289 if (classPreferenceList.containsKey(strClass)) { 290 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 291 for (int i = 0; i < a.size(); i++) { 292 if (a.get(i).getItem().equals(item)) { 293 return a.get(i).getState(); 294 } 295 } 296 } 297 return false; 298 } 299 300 @Override 301 public final void setPreferenceItemDetails(String strClass, String item, String description) { 302 if (!classPreferenceList.containsKey(strClass)) { 303 classPreferenceList.put(strClass, new ClassPreferences()); 304 } 305 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 306 for (int i = 0; i < a.size(); i++) { 307 if (a.get(i).getItem().equals(item)) { 308 a.get(i).setDescription(description); 309 return; 310 } 311 } 312 a.add(new PreferenceList(item, description)); 313 } 314 315 @Nonnull 316 @Override 317 public ArrayList<String> getPreferenceList(String strClass) { 318 if (classPreferenceList.containsKey(strClass)) { 319 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 320 ArrayList<String> list = new ArrayList<>(); 321 for (int i = 0; i < a.size(); i++) { 322 list.add(a.get(i).getItem()); 323 } 324 return list; 325 } 326 //Just return a blank array list will save call code checking for null 327 return new ArrayList<>(); 328 } 329 330 @Override 331 @CheckForNull 332 public String getPreferenceItemName(String strClass, int n) { 333 if (classPreferenceList.containsKey(strClass)) { 334 return classPreferenceList.get(strClass).getPreferenceName(n); 335 } 336 return null; 337 } 338 339 @Override 340 @CheckForNull 341 public String getPreferenceItemDescription(String strClass, String item) { 342 if (classPreferenceList.containsKey(strClass)) { 343 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 344 for (int i = 0; i < a.size(); i++) { 345 if (a.get(i).getItem().equals(item)) { 346 return a.get(i).getDescription(); 347 } 348 } 349 } 350 return null; 351 352 } 353 354 /** 355 * Used to surpress messages for a particular session, the information is 356 * not stored, can not be changed via the GUI. 357 * <p> 358 * This can be used to help prevent over loading the user with repetitive 359 * error messages such as turnout not found while loading a panel file due 360 * to a connection failing. The name is free-form, but to avoid ambiguity it 361 * should start with the package name (package.Class) for the primary using 362 * class. 363 * 364 * @param name A unique identifier for preference. 365 */ 366 @Override 367 public void setSessionPreferenceState(String name, boolean state) { 368 if (state) { 369 if (!sessionPreferenceList.contains(name)) { 370 sessionPreferenceList.add(name); 371 } 372 } else { 373 sessionPreferenceList.remove(name); 374 } 375 } 376 377 /** 378 * {@inheritDoc} 379 */ 380 @Override 381 public boolean getSessionPreferenceState(String name) { 382 return sessionPreferenceList.contains(name); 383 } 384 385 /** 386 * {@inheritDoc} 387 */ 388 @Override 389 public void showInfoMessage(String title, String message, String strClass, String item) { 390 showInfoMessage(title, message, strClass, item, false, true); 391 } 392 393 /** 394 * {@inheritDoc} 395 */ 396 @Override 397 public void showInfoMessage(@CheckForNull Component parentComponent, String title, String message, String strClass, String item) { 398 showInfoMessage(parentComponent, title, message, strClass, item, false, true); 399 } 400 401 /** 402 * {@inheritDoc} 403 */ 404 @Override 405 public void showErrorMessage(String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 406 this.showMessage(null, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.ERROR_MESSAGE); 407 } 408 409 /** 410 * {@inheritDoc} 411 */ 412 @Override 413 public void showErrorMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 414 this.showMessage(parentComponent, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.ERROR_MESSAGE); 415 } 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override 421 public void showInfoMessage(String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 422 this.showMessage(null, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.INFORMATION_MESSAGE); 423 } 424 425 /** 426 * {@inheritDoc} 427 */ 428 @Override 429 public void showInfoMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 430 this.showMessage(parentComponent, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.INFORMATION_MESSAGE); 431 } 432 433 /** 434 * {@inheritDoc} 435 */ 436 @Override 437 public void showWarningMessage(String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 438 this.showMessage(null, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.WARNING_MESSAGE); 439 } 440 441 /** 442 * {@inheritDoc} 443 */ 444 @Override 445 public void showWarningMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 446 this.showMessage(parentComponent, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.WARNING_MESSAGE); 447 } 448 449 protected void showMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, 450 final String item, final boolean sessionOnly, final boolean alwaysRemember, int type) { 451 final String preference = strClass + "." + item; 452 453 if (this.getSessionPreferenceState(preference)) { 454 return; 455 } 456 if (!this.getPreferenceState(strClass, item)) { 457 JPanel container = new JPanel(); 458 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 459 container.add(new JLabel(message)); 460 //I18N in ManagersBundle.properties 461 final JCheckBox rememberSession = new JCheckBox(Bundle.getMessage("SkipMessageSession")); // NOI18N 462 if (sessionOnly) { 463 rememberSession.setFont(rememberSession.getFont().deriveFont(10f)); 464 container.add(rememberSession); 465 } 466 //I18N in ManagersBundle.properties 467 final JCheckBox remember = new JCheckBox(Bundle.getMessage("SkipMessageFuture")); // NOI18N 468 if (alwaysRemember) { 469 remember.setFont(remember.getFont().deriveFont(10f)); 470 container.add(remember); 471 } 472 JmriJOptionPane.showMessageDialog(parentComponent, // center over parent component if present 473 container, 474 title, 475 type); 476 if (remember.isSelected()) { 477 this.setPreferenceState(strClass, item, true); 478 } 479 if (rememberSession.isSelected()) { 480 this.setSessionPreferenceState(preference, true); 481 } 482 483 } 484 } 485 486 @Override 487 @CheckForNull 488 public String getComboBoxLastSelection(String comboBoxName) { 489 return this.comboBoxLastSelection.get(comboBoxName); 490 } 491 492 @Override 493 public void setComboBoxLastSelection(String comboBoxName, String lastValue) { 494 comboBoxLastSelection.put(comboBoxName, lastValue); 495 setChangeMade(false); 496 this.saveComboBoxLastSelections(); 497 } 498 499 @Override 500 public boolean getCheckboxPreferenceState(String name, boolean defaultState) { 501 return this.checkBoxLastSelection.getOrDefault(name, defaultState); 502 } 503 504 @Override 505 public void setCheckboxPreferenceState(String name, boolean state) { 506 checkBoxLastSelection.put(name, state); 507 setChangeMade(false); 508 this.saveCheckBoxLastSelections(); 509 } 510 511 public synchronized boolean getChangeMade() { 512 return dirty; 513 } 514 515 public synchronized void setChangeMade(boolean fireUpdate) { 516 dirty = true; 517 if (fireUpdate) { 518 this.firePropertyChange(UserPreferencesManager.PREFERENCES_UPDATED, null, null); 519 } 520 } 521 522 //The reset is used after the preferences have been loaded for the first time 523 @Override 524 public synchronized void resetChangeMade() { 525 dirty = false; 526 } 527 528 /** 529 * Check if this object is loading preferences from storage. 530 * 531 * @return true if loading preferences; false otherwise 532 */ 533 protected boolean isLoading() { 534 return loading; 535 } 536 537 @Override 538 public void setLoading() { 539 loading = true; 540 } 541 542 @Override 543 public void finishLoading() { 544 loading = false; 545 resetChangeMade(); 546 } 547 548 public void displayRememberMsg() { 549 if (loading) { 550 return; 551 } 552 showInfoMessage(Bundle.getMessage("Reminder"), Bundle.getMessage("ReminderLine"), getClassName(), REMINDER); // NOI18N 553 } 554 555 @Override 556 public Point getWindowLocation(String strClass) { 557 if (windowDetails.containsKey(strClass)) { 558 return windowDetails.get(strClass).getLocation(); 559 } 560 return null; 561 } 562 563 @Override 564 public Dimension getWindowSize(String strClass) { 565 if (windowDetails.containsKey(strClass)) { 566 return windowDetails.get(strClass).getSize(); 567 } 568 return null; 569 } 570 571 @Override 572 public boolean getSaveWindowSize(String strClass) { 573 if (windowDetails.containsKey(strClass)) { 574 return windowDetails.get(strClass).getSaveSize(); 575 } 576 return false; 577 } 578 579 @Override 580 public boolean getSaveWindowLocation(String strClass) { 581 if (windowDetails.containsKey(strClass)) { 582 return windowDetails.get(strClass).getSaveLocation(); 583 } 584 return false; 585 } 586 587 @Override 588 public void setSaveWindowSize(String strClass, boolean b) { 589 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 590 return; 591 } 592 if (!windowDetails.containsKey(strClass)) { 593 windowDetails.put(strClass, new WindowLocations()); 594 } 595 windowDetails.get(strClass).setSaveSize(b); 596 this.saveWindowDetails(); 597 } 598 599 @Override 600 public void setSaveWindowLocation(String strClass, boolean b) { 601 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 602 return; 603 } 604 if (!windowDetails.containsKey(strClass)) { 605 windowDetails.put(strClass, new WindowLocations()); 606 } 607 windowDetails.get(strClass).setSaveLocation(b); 608 this.saveWindowDetails(); 609 } 610 611 @Override 612 public void setWindowLocation(String strClass, Point location) { 613 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 614 return; 615 } 616 if (!windowDetails.containsKey(strClass)) { 617 windowDetails.put(strClass, new WindowLocations()); 618 } 619 windowDetails.get(strClass).setLocation(location); 620 this.saveWindowDetails(); 621 } 622 623 @Override 624 public void setWindowSize(String strClass, Dimension dim) { 625 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 626 return; 627 } 628 if (!windowDetails.containsKey(strClass)) { 629 windowDetails.put(strClass, new WindowLocations()); 630 } 631 windowDetails.get(strClass).setSize(dim); 632 this.saveWindowDetails(); 633 } 634 635 @Override 636 public ArrayList<String> getWindowList() { 637 return new ArrayList<>(windowDetails.keySet()); 638 } 639 640 @Override 641 public void setProperty(String strClass, String key, Object value) { 642 if (strClass.equals(JmriJFrame.class.getName())) { 643 return; 644 } 645 if (!windowDetails.containsKey(strClass)) { 646 windowDetails.put(strClass, new WindowLocations()); 647 } 648 windowDetails.get(strClass).setProperty(key, value); 649 this.saveWindowDetails(); 650 } 651 652 @Override 653 public Object getProperty(String strClass, String key) { 654 if (windowDetails.containsKey(strClass)) { 655 return windowDetails.get(strClass).getProperty(key); 656 } 657 return null; 658 } 659 660 @Override 661 public Set<String> getPropertyKeys(String strClass) { 662 if (windowDetails.containsKey(strClass)) { 663 return windowDetails.get(strClass).getPropertyKeys(); 664 } 665 return null; 666 } 667 668 @Override 669 public boolean hasProperties(String strClass) { 670 return windowDetails.containsKey(strClass); 671 } 672 673 @Nonnull 674 @Override 675 public String getClassDescription(String strClass) { 676 if (classPreferenceList.containsKey(strClass)) { 677 return classPreferenceList.get(strClass).getDescription(); 678 } 679 return ""; 680 } 681 682 @Nonnull 683 @Override 684 public ArrayList<String> getPreferencesClasses() { 685 return new ArrayList<>(this.classPreferenceList.keySet()); 686 } 687 688 /** 689 * Given that we know the class as a string, we will try and attempt to 690 * gather details about the preferences that has been added, so that we can 691 * make better sense of the details in the preferences window. 692 * <p> 693 * This looks for specific methods within the class called 694 * "getClassDescription" and "setMessagePreferencesDetails". If found it 695 * will invoke the methods, this will then trigger the class to send details 696 * about its preferences back to this code. 697 */ 698 @Override 699 public void setClassDescription(String strClass) { 700 try { 701 Class<?> cl = Class.forName(strClass); 702 Object t; 703 try { 704 t = cl.getDeclaredConstructor().newInstance(); 705 } catch (IllegalArgumentException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException | java.lang.reflect.InvocationTargetException ex) { 706 log.error("setClassDescription({}) failed in newInstance", strClass, ex); 707 return; 708 } 709 boolean classDesFound; 710 boolean classSetFound; 711 String desc = null; 712 Method method; 713 //look through declared methods first, then all methods 714 try { 715 method = cl.getDeclaredMethod("getClassDescription"); 716 desc = (String) method.invoke(t); 717 classDesFound = true; 718 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 719 log.debug("Unable to call declared method \"getClassDescription\" with exception", ex); 720 classDesFound = false; 721 } 722 if (!classDesFound) { 723 try { 724 method = cl.getMethod("getClassDescription"); 725 desc = (String) method.invoke(t); 726 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 727 log.debug("Unable to call undeclared method \"getClassDescription\" with exception", ex); 728 classDesFound = false; 729 } 730 } 731 if (classDesFound) { 732 if (!classPreferenceList.containsKey(strClass)) { 733 classPreferenceList.put(strClass, new ClassPreferences(desc)); 734 } else { 735 classPreferenceList.get(strClass).setDescription(desc); 736 } 737 this.savePreferencesState(); 738 } 739 740 try { 741 method = cl.getDeclaredMethod("setMessagePreferencesDetails"); 742 method.invoke(t); 743 classSetFound = true; 744 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 745 // TableAction.setMessagePreferencesDetails() method is routinely not present in multiple classes 746 log.debug("Unable to call declared method \"setMessagePreferencesDetails\" with exception", ex); 747 classSetFound = false; 748 } 749 if (!classSetFound) { 750 try { 751 method = cl.getMethod("setMessagePreferencesDetails"); 752 method.invoke(t); 753 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 754 log.debug("Unable to call undeclared method \"setMessagePreferencesDetails\" with exception", ex); 755 } 756 } 757 758 } catch (ClassNotFoundException ex) { 759 log.warn("class name \"{}\" cannot be found, perhaps an expected plugin is missing?", strClass); 760 } catch (IllegalAccessException ex) { 761 log.error("unable to access class \"{}\"", strClass, ex); 762 } catch (InstantiationException ex) { 763 log.error("unable to get a class name \"{}\"", strClass, ex); 764 } 765 } 766 767 /** 768 * Add descriptive details about a specific message box, so that if it needs 769 * to be reset in the preferences, then it is easily identifiable. displayed 770 * to the user in the preferences GUI. 771 * 772 * @param strClass String value of the calling class/group 773 * @param item String value of the specific item this is used for. 774 * @param description A meaningful description that can be used in a label 775 * to describe the item 776 * @param options A map of the integer value of the option against a 777 * meaningful description. 778 * @param defaultOption The default option for the given item. 779 */ 780 @Override 781 public void setMessageItemDetails(String strClass, String item, String description, HashMap<Integer, String> options, int defaultOption) { 782 if (!classPreferenceList.containsKey(strClass)) { 783 classPreferenceList.put(strClass, new ClassPreferences()); 784 } 785 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 786 for (int i = 0; i < a.size(); i++) { 787 if (a.get(i).getItem().equals(item)) { 788 a.get(i).setMessageItems(description, options, defaultOption); 789 return; 790 } 791 } 792 a.add(new MultipleChoice(description, item, options, defaultOption)); 793 } 794 795 @Override 796 public HashMap<Integer, String> getChoiceOptions(String strClass, String item) { 797 if (classPreferenceList.containsKey(strClass)) { 798 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 799 for (int i = 0; i < a.size(); i++) { 800 if (a.get(i).getItem().equals(item)) { 801 return a.get(i).getOptions(); 802 } 803 } 804 } 805 return new HashMap<>(); 806 } 807 808 @Override 809 public int getMultipleChoiceSize(String strClass) { 810 if (classPreferenceList.containsKey(strClass)) { 811 return classPreferenceList.get(strClass).getMultipleChoiceListSize(); 812 } 813 return 0; 814 } 815 816 @Override 817 public ArrayList<String> getMultipleChoiceList(String strClass) { 818 if (classPreferenceList.containsKey(strClass)) { 819 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 820 ArrayList<String> list = new ArrayList<>(); 821 for (int i = 0; i < a.size(); i++) { 822 list.add(a.get(i).getItem()); 823 } 824 return list; 825 } 826 return new ArrayList<>(); 827 } 828 829 @Override 830 public String getChoiceName(String strClass, int n) { 831 if (classPreferenceList.containsKey(strClass)) { 832 return classPreferenceList.get(strClass).getChoiceName(n); 833 } 834 return null; 835 } 836 837 @Override 838 public String getChoiceDescription(String strClass, String item) { 839 if (classPreferenceList.containsKey(strClass)) { 840 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 841 for (int i = 0; i < a.size(); i++) { 842 if (a.get(i).getItem().equals(item)) { 843 return a.get(i).getOptionDescription(); 844 } 845 } 846 } 847 return null; 848 } 849 850 @Override 851 public int getMultipleChoiceOption(String strClass, String item) { 852 if (classPreferenceList.containsKey(strClass)) { 853 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 854 for (int i = 0; i < a.size(); i++) { 855 if (a.get(i).getItem().equals(item)) { 856 return a.get(i).getValue(); 857 } 858 } 859 } 860 return 0; 861 } 862 863 @Override 864 public int getMultipleChoiceDefaultOption(String strClass, String choice) { 865 if (classPreferenceList.containsKey(strClass)) { 866 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 867 for (int i = 0; i < a.size(); i++) { 868 if (a.get(i).getItem().equals(choice)) { 869 return a.get(i).getDefaultValue(); 870 } 871 } 872 } 873 return 0; 874 } 875 876 @Override 877 public void setMultipleChoiceOption(String strClass, String choice, String value) { 878 if (!classPreferenceList.containsKey(strClass)) { 879 classPreferenceList.put(strClass, new ClassPreferences()); 880 } 881 classPreferenceList.get(strClass).getMultipleChoiceList().stream() 882 .filter(mc -> (mc.getItem().equals(choice))).forEachOrdered(mc -> mc.setValue(value)); 883 this.savePreferencesState(); 884 } 885 886 @Override 887 public void setMultipleChoiceOption(String strClass, String choice, int value) { 888 889 // LogixNG bug fix: 890 // The class 'strClass' must have a default constructor. Otherwise, 891 // an error is logged to the log. Early versions of LogixNG used 892 // AbstractLogixNGTableAction and ??? as strClass, which didn't work. 893 // Now, LogixNG uses the class jmri.jmrit.logixng.LogixNG_UserPreferences 894 // for this purpose. 895 if ("jmri.jmrit.beantable.AbstractLogixNGTableAction".equals(strClass)) return; 896 if ("jmri.jmrit.logixng.tools.swing.TreeEditor".equals(strClass)) return; 897 898 if (!classPreferenceList.containsKey(strClass)) { 899 classPreferenceList.put(strClass, new ClassPreferences()); 900 } 901 boolean set = false; 902 for (MultipleChoice mc : classPreferenceList.get(strClass).getMultipleChoiceList()) { 903 if (mc.getItem().equals(choice)) { 904 mc.setValue(value); 905 set = true; 906 } 907 } 908 if (!set) { 909 classPreferenceList.get(strClass).getMultipleChoiceList().add(new MultipleChoice(choice, value)); 910 setClassDescription(strClass); 911 } 912 displayRememberMsg(); 913 this.savePreferencesState(); 914 } 915 916 public String getClassDescription() { 917 return "Preference Manager"; 918 } 919 920 protected final String getClassName() { 921 return this.getClass().getName(); 922 } 923 924 protected final ClassPreferences getClassPreferences(String strClass) { 925 return this.classPreferenceList.get(strClass); 926 } 927 928 @Override 929 public int getPreferencesSize(String strClass) { 930 if (classPreferenceList.containsKey(strClass)) { 931 return classPreferenceList.get(strClass).getPreferencesSize(); 932 } 933 return 0; 934 } 935 936 public final void readUserPreferences() { 937 log.trace("starting readUserPreferences"); 938 this.allowSave = false; 939 this.loading = true; 940 File perNodeConfig = null; 941 try { 942 perNodeConfig = FileUtil.getFile(FileUtil.PROFILE + Profile.PROFILE + "/" + NodeIdentity.storageIdentity() + "/" + Profile.UI_CONFIG); // NOI18N 943 if (!perNodeConfig.canRead()) { 944 perNodeConfig = null; 945 log.trace(" sharedConfig can't be read"); 946 } 947 } catch (FileNotFoundException ex) { 948 // ignore - this only means that sharedConfig does not exist. 949 log.trace(" FileNotFoundException: sharedConfig does not exist"); 950 } 951 if (perNodeConfig != null) { 952 file = perNodeConfig; 953 log.debug(" start perNodeConfig file: {}", file.getPath()); 954 this.readComboBoxLastSelections(); 955 this.readCheckBoxLastSelections(); 956 this.readPreferencesState(); 957 this.readSimplePreferenceState(); 958 this.readWindowDetails(); 959 } else { 960 try { 961 file = FileUtil.getFile(FileUtil.PROFILE + Profile.UI_CONFIG_FILENAME); 962 if (file.exists()) { 963 log.debug("start load user pref file: {}", file.getPath()); 964 try { 965 boolean result = InstanceManager.getDefault(ConfigureManager.class).load(file, true); 966 if (!result) { 967 log.error("Failed to load file:{}", file); 968 } 969 this.allowSave = true; 970 this.savePreferences(); // write new preferences format immediately 971 } catch (JmriException e) { 972 log.error("Unhandled problem loading configuration: {}", e.getMessage()); 973 } catch (NullPointerException e) { 974 log.error("NPE when trying to load user pref {}", file); 975 } 976 } else { 977 // if we got here, there is no saved user preferences 978 log.info("No saved user preferences file"); 979 } 980 } catch (FileNotFoundException ex) { 981 // ignore - this only means that UserPrefsProfileConfig.xml does not exist. 982 log.debug("UserPrefsProfileConfig.xml does not exist"); 983 } 984 } 985 this.loading = false; 986 this.allowSave = true; 987 log.trace(" ending readUserPreferences"); 988 } 989 990 private void readComboBoxLastSelections() { 991 Element element = this.readElement(COMBOBOX_ELEMENT, COMBOBOX_NAMESPACE); 992 if (element != null) { 993 element.getChildren("comboBox").stream().forEach(combo -> 994 comboBoxLastSelection.put(combo.getAttributeValue("name"), combo.getAttributeValue("lastSelected"))); 995 } 996 } 997 998 private void saveComboBoxLastSelections() { 999 this.setChangeMade(false); 1000 if (this.allowSave && !comboBoxLastSelection.isEmpty()) { 1001 Element element = new Element(COMBOBOX_ELEMENT, COMBOBOX_NAMESPACE); 1002 // Do not store blank last entered/selected values 1003 comboBoxLastSelection.entrySet().stream(). 1004 filter(cbls -> (cbls.getValue() != null && !cbls.getValue().isEmpty())).map(cbls -> { 1005 Element combo = new Element("comboBox"); 1006 combo.setAttribute("name", cbls.getKey()); 1007 combo.setAttribute("lastSelected", cbls.getValue()); 1008 return combo; 1009 }).forEach(element::addContent); 1010 this.saveElement(element); 1011 this.resetChangeMade(); 1012 } 1013 } 1014 1015 private void readCheckBoxLastSelections() { 1016 Element element = this.readElement(CHECKBOX_ELEMENT, CHECKBOX_NAMESPACE); 1017 if (element != null) { 1018 element.getChildren("checkBox").stream().forEach(checkbox -> 1019 checkBoxLastSelection.put(checkbox.getAttributeValue("name"), "yes".equals(checkbox.getAttributeValue("lastChecked")))); 1020 } 1021 } 1022 1023 private void saveCheckBoxLastSelections() { 1024 this.setChangeMade(false); 1025 if (this.allowSave && !checkBoxLastSelection.isEmpty()) { 1026 Element element = new Element(CHECKBOX_ELEMENT, CHECKBOX_NAMESPACE); 1027 // Do not store blank last entered/selected values 1028 checkBoxLastSelection.entrySet().stream(). 1029 filter(cbls -> (cbls.getValue() != null)).map(cbls -> { 1030 Element checkbox = new Element("checkBox"); 1031 checkbox.setAttribute("name", cbls.getKey()); 1032 checkbox.setAttribute("lastChecked", cbls.getValue() ? "yes" : "no"); 1033 return checkbox; 1034 }).forEach(element::addContent); 1035 this.saveElement(element); 1036 this.resetChangeMade(); 1037 } 1038 } 1039 1040 private void readPreferencesState() { 1041 Element element = this.readElement(CLASSPREFS_ELEMENT, CLASSPREFS_NAMESPACE); 1042 if (element != null) { 1043 element.getChildren("preferences").stream().forEach(preferences -> { 1044 String clazz = preferences.getAttributeValue(CLASS); 1045 log.debug("Reading class preferences for \"{}\"", clazz); 1046 preferences.getChildren("multipleChoice").stream().forEach(mc -> 1047 mc.getChildren("option").stream().forEach(option -> { 1048 int value = 0; 1049 try { 1050 value = option.getAttribute(VALUE).getIntValue(); 1051 } catch (DataConversionException ex) { 1052 log.error("failed to convert positional attribute"); 1053 } 1054 this.setMultipleChoiceOption(clazz, option.getAttributeValue("item"), value); 1055 })); 1056 preferences.getChildren("reminderPrompts").stream().forEach(rp -> 1057 rp.getChildren(REMINDER).stream().forEach(reminder -> { 1058 log.debug("Setting preferences state \"true\" for \"{}\", \"{}\"", clazz, reminder.getText()); 1059 this.setPreferenceState(clazz, reminder.getText(), true); 1060 })); 1061 }); 1062 } 1063 } 1064 1065 private void savePreferencesState() { 1066 this.setChangeMade(true); 1067 if (this.allowSave) { 1068 Element element = new Element(CLASSPREFS_ELEMENT, CLASSPREFS_NAMESPACE); 1069 this.classPreferenceList.keySet().stream().forEach(name -> { 1070 ClassPreferences cp = this.classPreferenceList.get(name); 1071 if (!cp.multipleChoiceList.isEmpty() || !cp.preferenceList.isEmpty()) { 1072 Element clazz = new Element("preferences"); 1073 clazz.setAttribute(CLASS, name); 1074 if (!cp.multipleChoiceList.isEmpty()) { 1075 Element choices = new Element("multipleChoice"); 1076 // only save non-default values 1077 cp.multipleChoiceList.stream().filter(mc -> (mc.getDefaultValue() != mc.getValue())).forEach(mc -> 1078 choices.addContent(new Element("option") 1079 .setAttribute("item", mc.getItem()) 1080 .setAttribute(VALUE, Integer.toString(mc.getValue())))); 1081 if (!choices.getChildren().isEmpty()) { 1082 clazz.addContent(choices); 1083 } 1084 } 1085 if (!cp.preferenceList.isEmpty()) { 1086 Element reminders = new Element("reminderPrompts"); 1087 cp.preferenceList.stream().filter(pl -> (pl.getState())).forEach(pl -> 1088 reminders.addContent(new Element(REMINDER).addContent(pl.getItem()))); 1089 if (!reminders.getChildren().isEmpty()) { 1090 clazz.addContent(reminders); 1091 } 1092 } 1093 element.addContent(clazz); 1094 } 1095 }); 1096 if (!element.getChildren().isEmpty()) { 1097 this.saveElement(element); 1098 } 1099 } 1100 } 1101 1102 private void readSimplePreferenceState() { 1103 Element element = this.readElement(SETTINGS_ELEMENT, SETTINGS_NAMESPACE); 1104 if (element != null) { 1105 element.getChildren("setting").stream().forEach(setting -> 1106 this.simplePreferenceList.add(setting.getText())); 1107 } 1108 } 1109 1110 private void saveSimplePreferenceState() { 1111 this.setChangeMade(false); 1112 if (this.allowSave) { 1113 Element element = new Element(SETTINGS_ELEMENT, SETTINGS_NAMESPACE); 1114 getSimplePreferenceStateList().stream().forEach(setting -> 1115 element.addContent(new Element("setting").addContent(setting))); 1116 this.saveElement(element); 1117 this.resetChangeMade(); 1118 } 1119 } 1120 1121 private void readWindowDetails() { 1122 // TODO: COMPLETE! 1123 Element element = this.readElement(WINDOWS_ELEMENT, WINDOWS_NAMESPACE); 1124 if (element != null) { 1125 element.getChildren("window").stream().forEach(window -> { 1126 String reference = window.getAttributeValue(CLASS); 1127 log.debug("Reading window details for {}", reference); 1128 try { 1129 if (window.getAttribute("locX") != null && window.getAttribute("locY") != null) { 1130 double x = window.getAttribute("locX").getDoubleValue(); 1131 double y = window.getAttribute("locY").getDoubleValue(); 1132 this.setWindowLocation(reference, new java.awt.Point((int) x, (int) y)); 1133 } 1134 if (window.getAttribute(WIDTH) != null && window.getAttribute(HEIGHT) != null) { 1135 double width = window.getAttribute(WIDTH).getDoubleValue(); 1136 double height = window.getAttribute(HEIGHT).getDoubleValue(); 1137 this.setWindowSize(reference, new java.awt.Dimension((int) width, (int) height)); 1138 } 1139 } catch (DataConversionException ex) { 1140 log.error("Unable to read dimensions of window \"{}\"", reference); 1141 } 1142 if (window.getChild(PROPERTIES) != null) { 1143 window.getChild(PROPERTIES).getChildren().stream().forEach(property -> { 1144 String key = property.getChild("key").getText(); 1145 try { 1146 Class<?> cl = Class.forName(property.getChild(VALUE).getAttributeValue(CLASS)); 1147 Constructor<?> ctor = cl.getConstructor(new Class<?>[]{String.class}); 1148 Object value = ctor.newInstance(new Object[]{property.getChild(VALUE).getText()}); 1149 log.debug("Setting property {} for {} to {}", key, reference, value); 1150 this.setProperty(reference, key, value); 1151 } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { 1152 log.error("Unable to retrieve property \"{}\" for window \"{}\"", key, reference); 1153 } catch (NullPointerException ex) { 1154 // null properties do not get set 1155 log.debug("Property \"{}\" for window \"{}\" is null", key, reference); 1156 } 1157 }); 1158 } 1159 }); 1160 } 1161 } 1162 1163 @SuppressFBWarnings(value = "DMI_ENTRY_SETS_MAY_REUSE_ENTRY_OBJECTS", 1164 justification = "needs to copy the items of the hashmap windowDetails") 1165 private void saveWindowDetails() { 1166 this.setChangeMade(false); 1167 if (this.allowSave) { 1168 if (!windowDetails.isEmpty()) { 1169 Element element = new Element(WINDOWS_ELEMENT, WINDOWS_NAMESPACE); 1170 // Copy the entries before iterate over them since 1171 // ConcurrentModificationException may happen otherwise 1172 Set<Entry<String, WindowLocations>> entries = new HashSet<>(windowDetails.entrySet()); 1173 for (Entry<String, WindowLocations> entry : entries) { 1174 Element window = new Element("window"); 1175 window.setAttribute(CLASS, entry.getKey()); 1176 if (entry.getValue().getSaveLocation()) { 1177 try { 1178 window.setAttribute("locX", Double.toString(entry.getValue().getLocation().getX())); 1179 window.setAttribute("locY", Double.toString(entry.getValue().getLocation().getY())); 1180 } catch (NullPointerException ex) { 1181 // Expected if the location has not been set or the window is open 1182 } 1183 } 1184 if (entry.getValue().getSaveSize()) { 1185 try { 1186 double height = entry.getValue().getSize().getHeight(); 1187 double width = entry.getValue().getSize().getWidth(); 1188 // Do not save the width or height if set to zero 1189 if (!(height == 0.0 && width == 0.0)) { 1190 window.setAttribute(WIDTH, Double.toString(width)); 1191 window.setAttribute(HEIGHT, Double.toString(height)); 1192 } 1193 } catch (NullPointerException ex) { 1194 // Expected if the size has not been set or the window is open 1195 } 1196 } 1197 if (!entry.getValue().parameters.isEmpty()) { 1198 Element properties = new Element(PROPERTIES); 1199 entry.getValue().parameters.entrySet().stream().map(property -> { 1200 Element propertyElement = new Element("property"); 1201 propertyElement.addContent(new Element("key").setText(property.getKey())); 1202 Object value = property.getValue(); 1203 if (value != null) { 1204 propertyElement.addContent(new Element(VALUE) 1205 .setAttribute(CLASS, value.getClass().getName()) 1206 .setText(value.toString())); 1207 } 1208 return propertyElement; 1209 }).forEach(properties::addContent); 1210 window.addContent(properties); 1211 } 1212 element.addContent(window); 1213 } 1214 this.saveElement(element); 1215 this.resetChangeMade(); 1216 } 1217 } 1218 } 1219 1220 /** 1221 * 1222 * @return an Element or null if the requested element does not exist 1223 */ 1224 @CheckForNull 1225 private Element readElement(@Nonnull String elementName, @Nonnull String namespace) { 1226 org.w3c.dom.Element element = ProfileUtils.getUserInterfaceConfiguration(ProfileManager.getDefault().getActiveProfile()).getConfigurationFragment(elementName, namespace, false); 1227 if (element != null) { 1228 return JDOMUtil.toJDOMElement(element); 1229 } 1230 return null; 1231 } 1232 1233 protected void saveElement(@Nonnull Element element) { 1234 log.trace("Saving {} element.", element.getName()); 1235 try { 1236 ProfileUtils.getUserInterfaceConfiguration(ProfileManager.getDefault().getActiveProfile()).putConfigurationFragment(JDOMUtil.toW3CElement(element), false); 1237 } catch (JDOMException ex) { 1238 log.error("Unable to save user preferences", ex); 1239 } 1240 } 1241 1242 private void savePreferences() { 1243 this.saveComboBoxLastSelections(); 1244 this.saveCheckBoxLastSelections(); 1245 this.savePreferencesState(); 1246 this.saveSimplePreferenceState(); 1247 this.saveWindowDetails(); 1248 this.resetChangeMade(); 1249 InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent(manager -> 1250 manager.savePreferences(ProfileManager.getDefault().getActiveProfile())); 1251 } 1252 1253 @Override 1254 public void initialize() { 1255 this.readUserPreferences(); 1256 } 1257 1258 /** 1259 * Holds details about the specific class. 1260 */ 1261 protected static final class ClassPreferences { 1262 1263 String classDescription; 1264 1265 ArrayList<MultipleChoice> multipleChoiceList = new ArrayList<>(); 1266 ArrayList<PreferenceList> preferenceList = new ArrayList<>(); 1267 1268 ClassPreferences() { 1269 } 1270 1271 ClassPreferences(String classDescription) { 1272 this.classDescription = classDescription; 1273 } 1274 1275 String getDescription() { 1276 return classDescription; 1277 } 1278 1279 void setDescription(String description) { 1280 classDescription = description; 1281 } 1282 1283 ArrayList<PreferenceList> getPreferenceList() { 1284 return preferenceList; 1285 } 1286 1287 int getPreferenceListSize() { 1288 return preferenceList.size(); 1289 } 1290 1291 ArrayList<MultipleChoice> getMultipleChoiceList() { 1292 return multipleChoiceList; 1293 } 1294 1295 int getPreferencesSize() { 1296 return multipleChoiceList.size() + preferenceList.size(); 1297 } 1298 1299 public String getPreferenceName(int n) { 1300 try { 1301 return preferenceList.get(n).getItem(); 1302 } catch (IndexOutOfBoundsException ioob) { 1303 return null; 1304 } 1305 } 1306 1307 int getMultipleChoiceListSize() { 1308 return multipleChoiceList.size(); 1309 } 1310 1311 public String getChoiceName(int n) { 1312 try { 1313 return multipleChoiceList.get(n).getItem(); 1314 } catch (IndexOutOfBoundsException ioob) { 1315 return null; 1316 } 1317 } 1318 } 1319 1320 protected static final class MultipleChoice { 1321 1322 HashMap<Integer, String> options; 1323 String optionDescription; 1324 String item; 1325 int value = -1; 1326 int defaultOption = -1; 1327 1328 MultipleChoice(String description, String item, HashMap<Integer, String> options, int defaultOption) { 1329 this.item = item; 1330 setMessageItems(description, options, defaultOption); 1331 } 1332 1333 MultipleChoice(String item, int value) { 1334 this.item = item; 1335 this.value = value; 1336 1337 } 1338 1339 void setValue(int value) { 1340 this.value = value; 1341 } 1342 1343 void setValue(String value) { 1344 options.keySet().stream().filter(o -> (options.get(o).equals(value))).forEachOrdered(o -> this.value = o); 1345 } 1346 1347 void setMessageItems(String description, HashMap<Integer, String> options, int defaultOption) { 1348 optionDescription = description; 1349 this.options = options; 1350 this.defaultOption = defaultOption; 1351 if (value == -1) { 1352 value = defaultOption; 1353 } 1354 } 1355 1356 int getValue() { 1357 return value; 1358 } 1359 1360 int getDefaultValue() { 1361 return defaultOption; 1362 } 1363 1364 String getItem() { 1365 return item; 1366 } 1367 1368 String getOptionDescription() { 1369 return optionDescription; 1370 } 1371 1372 HashMap<Integer, String> getOptions() { 1373 return options; 1374 } 1375 1376 } 1377 1378 protected static final class PreferenceList { 1379 1380 // need to fill this with bits to get a meaning full description. 1381 boolean set = false; 1382 String item = ""; 1383 String description = ""; 1384 1385 PreferenceList(String item) { 1386 this.item = item; 1387 } 1388 1389 PreferenceList(String item, boolean state) { 1390 this.item = item; 1391 set = state; 1392 } 1393 1394 PreferenceList(String item, String description) { 1395 this.description = description; 1396 this.item = item; 1397 } 1398 1399 void setDescription(String desc) { 1400 description = desc; 1401 } 1402 1403 String getDescription() { 1404 return description; 1405 } 1406 1407 boolean getState() { 1408 return set; 1409 } 1410 1411 void setState(boolean state) { 1412 this.set = state; 1413 } 1414 1415 String getItem() { 1416 return item; 1417 } 1418 1419 } 1420 1421 protected static final class WindowLocations { 1422 1423 private Point xyLocation = new Point(0, 0); 1424 private Dimension size = new Dimension(0, 0); 1425 private boolean saveSize = false; 1426 private boolean saveLocation = false; 1427 1428 WindowLocations() { 1429 } 1430 1431 Point getLocation() { 1432 return xyLocation; 1433 } 1434 1435 Dimension getSize() { 1436 return size; 1437 } 1438 1439 void setSaveSize(boolean b) { 1440 saveSize = b; 1441 } 1442 1443 void setSaveLocation(boolean b) { 1444 saveLocation = b; 1445 } 1446 1447 boolean getSaveSize() { 1448 return saveSize; 1449 } 1450 1451 boolean getSaveLocation() { 1452 return saveLocation; 1453 } 1454 1455 void setLocation(Point xyLocation) { 1456 this.xyLocation = xyLocation; 1457 saveLocation = true; 1458 } 1459 1460 void setSize(Dimension size) { 1461 this.size = size; 1462 saveSize = true; 1463 } 1464 1465 void setProperty(@Nonnull String key, @CheckForNull Object value) { 1466 if (value == null) { 1467 parameters.remove(key); 1468 } else { 1469 parameters.put(key, value); 1470 } 1471 } 1472 1473 @CheckForNull 1474 Object getProperty(String key) { 1475 return parameters.get(key); 1476 } 1477 1478 Set<String> getPropertyKeys() { 1479 return parameters.keySet(); 1480 } 1481 1482 final ConcurrentHashMap<String, Object> parameters = new ConcurrentHashMap<>(); 1483 1484 } 1485 1486 @ServiceProvider(service = InstanceInitializer.class) 1487 public static class Initializer extends AbstractInstanceInitializer { 1488 1489 @Override 1490 public <T> Object getDefault(Class<T> type) { 1491 if (type.equals(UserPreferencesManager.class)) { 1492 return new JmriUserPreferencesManager(); 1493 } 1494 return super.getDefault(type); 1495 } 1496 1497 @Override 1498 public Set<Class<?>> getInitalizes() { 1499 Set<Class<?>> set = super.getInitalizes(); 1500 set.add(UserPreferencesManager.class); 1501 return set; 1502 } 1503 } 1504 1505 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JmriUserPreferencesManager.class); 1506 1507}