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 @Override 258 public void setPreferenceState(String strClass, String item, boolean state) { 259 // convert old manager preferences to new manager preferences 260 if (strClass.equals("jmri.managers.DefaultUserMessagePreferences")) { 261 this.setPreferenceState("jmri.managers.JmriUserPreferencesManager", item, state); 262 return; 263 } 264 if (!classPreferenceList.containsKey(strClass)) { 265 classPreferenceList.put(strClass, new ClassPreferences()); 266 setClassDescription(strClass); 267 } 268 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 269 boolean found = false; 270 for (int i = 0; i < a.size(); i++) { 271 if (a.get(i).getItem().equals(item)) { 272 a.get(i).setState(state); 273 found = true; 274 } 275 } 276 if (!found) { 277 a.add(new PreferenceList(item, state)); 278 } 279 displayRememberMsg(); 280 this.savePreferencesState(); 281 } 282 283 @Override 284 public boolean getPreferenceState(String strClass, String item) { 285 if (classPreferenceList.containsKey(strClass)) { 286 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 287 for (int i = 0; i < a.size(); i++) { 288 if (a.get(i).getItem().equals(item)) { 289 return a.get(i).getState(); 290 } 291 } 292 } 293 return false; 294 } 295 296 @Override 297 public final void setPreferenceItemDetails(String strClass, String item, String description) { 298 if (!classPreferenceList.containsKey(strClass)) { 299 classPreferenceList.put(strClass, new ClassPreferences()); 300 } 301 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 302 for (int i = 0; i < a.size(); i++) { 303 if (a.get(i).getItem().equals(item)) { 304 a.get(i).setDescription(description); 305 return; 306 } 307 } 308 a.add(new PreferenceList(item, description)); 309 } 310 311 @Nonnull 312 @Override 313 public ArrayList<String> getPreferenceList(String strClass) { 314 if (classPreferenceList.containsKey(strClass)) { 315 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 316 ArrayList<String> list = new ArrayList<>(); 317 for (int i = 0; i < a.size(); i++) { 318 list.add(a.get(i).getItem()); 319 } 320 return list; 321 } 322 //Just return a blank array list will save call code checking for null 323 return new ArrayList<>(); 324 } 325 326 @Override 327 @CheckForNull 328 public String getPreferenceItemName(String strClass, int n) { 329 if (classPreferenceList.containsKey(strClass)) { 330 return classPreferenceList.get(strClass).getPreferenceName(n); 331 } 332 return null; 333 } 334 335 @Override 336 @CheckForNull 337 public String getPreferenceItemDescription(String strClass, String item) { 338 if (classPreferenceList.containsKey(strClass)) { 339 ArrayList<PreferenceList> a = classPreferenceList.get(strClass).getPreferenceList(); 340 for (int i = 0; i < a.size(); i++) { 341 if (a.get(i).getItem().equals(item)) { 342 return a.get(i).getDescription(); 343 } 344 } 345 } 346 return null; 347 348 } 349 350 /** 351 * Used to surpress messages for a particular session, the information is 352 * not stored, can not be changed via the GUI. 353 * <p> 354 * This can be used to help prevent over loading the user with repetitive 355 * error messages such as turnout not found while loading a panel file due 356 * to a connection failing. The name is free-form, but to avoid ambiguity it 357 * should start with the package name (package.Class) for the primary using 358 * class. 359 * 360 * @param name A unique identifier for preference. 361 */ 362 @Override 363 public void setSessionPreferenceState(String name, boolean state) { 364 if (state) { 365 if (!sessionPreferenceList.contains(name)) { 366 sessionPreferenceList.add(name); 367 } 368 } else { 369 sessionPreferenceList.remove(name); 370 } 371 } 372 373 /** 374 * {@inheritDoc} 375 */ 376 @Override 377 public boolean getSessionPreferenceState(String name) { 378 return sessionPreferenceList.contains(name); 379 } 380 381 /** 382 * {@inheritDoc} 383 */ 384 @Override 385 public void showInfoMessage(String title, String message, String strClass, String item) { 386 showInfoMessage(title, message, strClass, item, false, true); 387 } 388 389 /** 390 * {@inheritDoc} 391 */ 392 @Override 393 public void showInfoMessage(@CheckForNull Component parentComponent, String title, String message, String strClass, String item) { 394 showInfoMessage(parentComponent, title, message, strClass, item, false, true); 395 } 396 397 /** 398 * {@inheritDoc} 399 */ 400 @Override 401 public void showErrorMessage(String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 402 this.showMessage(null, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.ERROR_MESSAGE); 403 } 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override 409 public void showErrorMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 410 this.showMessage(parentComponent, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.ERROR_MESSAGE); 411 } 412 413 /** 414 * {@inheritDoc} 415 */ 416 @Override 417 public void showInfoMessage(String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 418 this.showMessage(null, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.INFORMATION_MESSAGE); 419 } 420 421 /** 422 * {@inheritDoc} 423 */ 424 @Override 425 public void showInfoMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 426 this.showMessage(parentComponent, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.INFORMATION_MESSAGE); 427 } 428 429 /** 430 * {@inheritDoc} 431 */ 432 @Override 433 public void showWarningMessage(String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 434 this.showMessage(null, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.WARNING_MESSAGE); 435 } 436 437 /** 438 * {@inheritDoc} 439 */ 440 @Override 441 public void showWarningMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, final String item, final boolean sessionOnly, final boolean alwaysRemember) { 442 this.showMessage(parentComponent, title, message, strClass, item, sessionOnly, alwaysRemember, JmriJOptionPane.WARNING_MESSAGE); 443 } 444 445 protected void showMessage(@CheckForNull Component parentComponent, String title, String message, final String strClass, 446 final String item, final boolean sessionOnly, final boolean alwaysRemember, int type) { 447 final String preference = strClass + "." + item; 448 449 if (this.getSessionPreferenceState(preference)) { 450 return; 451 } 452 if (!this.getPreferenceState(strClass, item)) { 453 JPanel container = new JPanel(); 454 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 455 container.add(new JLabel(message)); 456 //I18N in ManagersBundle.properties 457 final JCheckBox rememberSession = new JCheckBox(Bundle.getMessage("SkipMessageSession")); // NOI18N 458 if (sessionOnly) { 459 rememberSession.setFont(rememberSession.getFont().deriveFont(10f)); 460 container.add(rememberSession); 461 } 462 //I18N in ManagersBundle.properties 463 final JCheckBox remember = new JCheckBox(Bundle.getMessage("SkipMessageFuture")); // NOI18N 464 if (alwaysRemember) { 465 remember.setFont(remember.getFont().deriveFont(10f)); 466 container.add(remember); 467 } 468 JmriJOptionPane.showMessageDialog(parentComponent, // center over parent component if present 469 container, 470 title, 471 type); 472 if (remember.isSelected()) { 473 this.setPreferenceState(strClass, item, true); 474 } 475 if (rememberSession.isSelected()) { 476 this.setSessionPreferenceState(preference, true); 477 } 478 479 } 480 } 481 482 @Override 483 @CheckForNull 484 public String getComboBoxLastSelection(String comboBoxName) { 485 return this.comboBoxLastSelection.get(comboBoxName); 486 } 487 488 @Override 489 public void setComboBoxLastSelection(String comboBoxName, String lastValue) { 490 comboBoxLastSelection.put(comboBoxName, lastValue); 491 setChangeMade(false); 492 this.saveComboBoxLastSelections(); 493 } 494 495 @Override 496 public boolean getCheckboxPreferenceState(String name, boolean defaultState) { 497 return this.checkBoxLastSelection.getOrDefault(name, defaultState); 498 } 499 500 @Override 501 public void setCheckboxPreferenceState(String name, boolean state) { 502 checkBoxLastSelection.put(name, state); 503 setChangeMade(false); 504 this.saveCheckBoxLastSelections(); 505 } 506 507 public synchronized boolean getChangeMade() { 508 return dirty; 509 } 510 511 public synchronized void setChangeMade(boolean fireUpdate) { 512 dirty = true; 513 if (fireUpdate) { 514 this.firePropertyChange(UserPreferencesManager.PREFERENCES_UPDATED, null, null); 515 } 516 } 517 518 //The reset is used after the preferences have been loaded for the first time 519 @Override 520 public synchronized void resetChangeMade() { 521 dirty = false; 522 } 523 524 /** 525 * Check if this object is loading preferences from storage. 526 * 527 * @return true if loading preferences; false otherwise 528 */ 529 protected boolean isLoading() { 530 return loading; 531 } 532 533 @Override 534 public void setLoading() { 535 loading = true; 536 } 537 538 @Override 539 public void finishLoading() { 540 loading = false; 541 resetChangeMade(); 542 } 543 544 public void displayRememberMsg() { 545 if (loading) { 546 return; 547 } 548 showInfoMessage(Bundle.getMessage("Reminder"), Bundle.getMessage("ReminderLine"), getClassName(), REMINDER); // NOI18N 549 } 550 551 @Override 552 public Point getWindowLocation(String strClass) { 553 if (windowDetails.containsKey(strClass)) { 554 return windowDetails.get(strClass).getLocation(); 555 } 556 return null; 557 } 558 559 @Override 560 public Dimension getWindowSize(String strClass) { 561 if (windowDetails.containsKey(strClass)) { 562 return windowDetails.get(strClass).getSize(); 563 } 564 return null; 565 } 566 567 @Override 568 public boolean getSaveWindowSize(String strClass) { 569 if (windowDetails.containsKey(strClass)) { 570 return windowDetails.get(strClass).getSaveSize(); 571 } 572 return false; 573 } 574 575 @Override 576 public boolean getSaveWindowLocation(String strClass) { 577 if (windowDetails.containsKey(strClass)) { 578 return windowDetails.get(strClass).getSaveLocation(); 579 } 580 return false; 581 } 582 583 @Override 584 public void setSaveWindowSize(String strClass, boolean b) { 585 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 586 return; 587 } 588 if (!windowDetails.containsKey(strClass)) { 589 windowDetails.put(strClass, new WindowLocations()); 590 } 591 windowDetails.get(strClass).setSaveSize(b); 592 this.saveWindowDetails(); 593 } 594 595 @Override 596 public void setSaveWindowLocation(String strClass, boolean b) { 597 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 598 return; 599 } 600 if (!windowDetails.containsKey(strClass)) { 601 windowDetails.put(strClass, new WindowLocations()); 602 } 603 windowDetails.get(strClass).setSaveLocation(b); 604 this.saveWindowDetails(); 605 } 606 607 @Override 608 public void setWindowLocation(String strClass, Point location) { 609 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 610 return; 611 } 612 if (!windowDetails.containsKey(strClass)) { 613 windowDetails.put(strClass, new WindowLocations()); 614 } 615 windowDetails.get(strClass).setLocation(location); 616 this.saveWindowDetails(); 617 } 618 619 @Override 620 public void setWindowSize(String strClass, Dimension dim) { 621 if ((strClass == null) || (strClass.equals(JMRI_UTIL_JMRI_JFRAME))) { 622 return; 623 } 624 if (!windowDetails.containsKey(strClass)) { 625 windowDetails.put(strClass, new WindowLocations()); 626 } 627 windowDetails.get(strClass).setSize(dim); 628 this.saveWindowDetails(); 629 } 630 631 @Override 632 public ArrayList<String> getWindowList() { 633 return new ArrayList<>(windowDetails.keySet()); 634 } 635 636 @Override 637 public void setProperty(String strClass, String key, Object value) { 638 if (strClass.equals(JmriJFrame.class.getName())) { 639 return; 640 } 641 if (!windowDetails.containsKey(strClass)) { 642 windowDetails.put(strClass, new WindowLocations()); 643 } 644 windowDetails.get(strClass).setProperty(key, value); 645 this.saveWindowDetails(); 646 } 647 648 @Override 649 public Object getProperty(String strClass, String key) { 650 if (windowDetails.containsKey(strClass)) { 651 return windowDetails.get(strClass).getProperty(key); 652 } 653 return null; 654 } 655 656 @Override 657 public Set<String> getPropertyKeys(String strClass) { 658 if (windowDetails.containsKey(strClass)) { 659 return windowDetails.get(strClass).getPropertyKeys(); 660 } 661 return null; 662 } 663 664 @Override 665 public boolean hasProperties(String strClass) { 666 return windowDetails.containsKey(strClass); 667 } 668 669 @Nonnull 670 @Override 671 public String getClassDescription(String strClass) { 672 if (classPreferenceList.containsKey(strClass)) { 673 return classPreferenceList.get(strClass).getDescription(); 674 } 675 return ""; 676 } 677 678 @Nonnull 679 @Override 680 public ArrayList<String> getPreferencesClasses() { 681 return new ArrayList<>(this.classPreferenceList.keySet()); 682 } 683 684 /** 685 * Given that we know the class as a string, we will try and attempt to 686 * gather details about the preferences that has been added, so that we can 687 * make better sense of the details in the preferences window. 688 * <p> 689 * This looks for specific methods within the class called 690 * "getClassDescription" and "setMessagePreferencesDetails". If found it 691 * will invoke the methods, this will then trigger the class to send details 692 * about its preferences back to this code. 693 */ 694 @Override 695 public void setClassDescription(String strClass) { 696 try { 697 Class<?> cl = Class.forName(strClass); 698 Object t; 699 try { 700 t = cl.getDeclaredConstructor().newInstance(); 701 } catch (IllegalArgumentException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException | java.lang.reflect.InvocationTargetException ex) { 702 log.error("setClassDescription({}) failed in newInstance", strClass, ex); 703 return; 704 } 705 boolean classDesFound; 706 boolean classSetFound; 707 String desc = null; 708 Method method; 709 //look through declared methods first, then all methods 710 try { 711 method = cl.getDeclaredMethod("getClassDescription"); 712 desc = (String) method.invoke(t); 713 classDesFound = true; 714 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 715 log.debug("Unable to call declared method \"getClassDescription\" with exception", ex); 716 classDesFound = false; 717 } 718 if (!classDesFound) { 719 try { 720 method = cl.getMethod("getClassDescription"); 721 desc = (String) method.invoke(t); 722 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 723 log.debug("Unable to call undeclared method \"getClassDescription\" with exception", ex); 724 classDesFound = false; 725 } 726 } 727 if (classDesFound) { 728 if (!classPreferenceList.containsKey(strClass)) { 729 classPreferenceList.put(strClass, new ClassPreferences(desc)); 730 } else { 731 classPreferenceList.get(strClass).setDescription(desc); 732 } 733 this.savePreferencesState(); 734 } 735 736 try { 737 method = cl.getDeclaredMethod("setMessagePreferencesDetails"); 738 method.invoke(t); 739 classSetFound = true; 740 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 741 // TableAction.setMessagePreferencesDetails() method is routinely not present in multiple classes 742 log.debug("Unable to call declared method \"setMessagePreferencesDetails\" with exception", ex); 743 classSetFound = false; 744 } 745 if (!classSetFound) { 746 try { 747 method = cl.getMethod("setMessagePreferencesDetails"); 748 method.invoke(t); 749 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError | NoSuchMethodException ex) { 750 log.debug("Unable to call undeclared method \"setMessagePreferencesDetails\" with exception", ex); 751 } 752 } 753 754 } catch (ClassNotFoundException ex) { 755 log.warn("class name \"{}\" cannot be found, perhaps an expected plugin is missing?", strClass); 756 } catch (IllegalAccessException ex) { 757 log.error("unable to access class \"{}\"", strClass, ex); 758 } catch (InstantiationException ex) { 759 log.error("unable to get a class name \"{}\"", strClass, ex); 760 } 761 } 762 763 /** 764 * Add descriptive details about a specific message box, so that if it needs 765 * to be reset in the preferences, then it is easily identifiable. displayed 766 * to the user in the preferences GUI. 767 * 768 * @param strClass String value of the calling class/group 769 * @param item String value of the specific item this is used for. 770 * @param description A meaningful description that can be used in a label 771 * to describe the item 772 * @param options A map of the integer value of the option against a 773 * meaningful description. 774 * @param defaultOption The default option for the given item. 775 */ 776 @Override 777 public void setMessageItemDetails(String strClass, String item, String description, HashMap<Integer, String> options, int defaultOption) { 778 if (!classPreferenceList.containsKey(strClass)) { 779 classPreferenceList.put(strClass, new ClassPreferences()); 780 } 781 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 782 for (int i = 0; i < a.size(); i++) { 783 if (a.get(i).getItem().equals(item)) { 784 a.get(i).setMessageItems(description, options, defaultOption); 785 return; 786 } 787 } 788 a.add(new MultipleChoice(description, item, options, defaultOption)); 789 } 790 791 @Override 792 public HashMap<Integer, String> getChoiceOptions(String strClass, String item) { 793 if (classPreferenceList.containsKey(strClass)) { 794 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 795 for (int i = 0; i < a.size(); i++) { 796 if (a.get(i).getItem().equals(item)) { 797 return a.get(i).getOptions(); 798 } 799 } 800 } 801 return new HashMap<>(); 802 } 803 804 @Override 805 public int getMultipleChoiceSize(String strClass) { 806 if (classPreferenceList.containsKey(strClass)) { 807 return classPreferenceList.get(strClass).getMultipleChoiceListSize(); 808 } 809 return 0; 810 } 811 812 @Override 813 public ArrayList<String> getMultipleChoiceList(String strClass) { 814 if (classPreferenceList.containsKey(strClass)) { 815 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 816 ArrayList<String> list = new ArrayList<>(); 817 for (int i = 0; i < a.size(); i++) { 818 list.add(a.get(i).getItem()); 819 } 820 return list; 821 } 822 return new ArrayList<>(); 823 } 824 825 @Override 826 public String getChoiceName(String strClass, int n) { 827 if (classPreferenceList.containsKey(strClass)) { 828 return classPreferenceList.get(strClass).getChoiceName(n); 829 } 830 return null; 831 } 832 833 @Override 834 public String getChoiceDescription(String strClass, String item) { 835 if (classPreferenceList.containsKey(strClass)) { 836 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 837 for (int i = 0; i < a.size(); i++) { 838 if (a.get(i).getItem().equals(item)) { 839 return a.get(i).getOptionDescription(); 840 } 841 } 842 } 843 return null; 844 } 845 846 @Override 847 public int getMultipleChoiceOption(String strClass, String item) { 848 if (classPreferenceList.containsKey(strClass)) { 849 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 850 for (int i = 0; i < a.size(); i++) { 851 if (a.get(i).getItem().equals(item)) { 852 return a.get(i).getValue(); 853 } 854 } 855 } 856 return 0; 857 } 858 859 @Override 860 public int getMultipleChoiceDefaultOption(String strClass, String choice) { 861 if (classPreferenceList.containsKey(strClass)) { 862 ArrayList<MultipleChoice> a = classPreferenceList.get(strClass).getMultipleChoiceList(); 863 for (int i = 0; i < a.size(); i++) { 864 if (a.get(i).getItem().equals(choice)) { 865 return a.get(i).getDefaultValue(); 866 } 867 } 868 } 869 return 0; 870 } 871 872 @Override 873 public void setMultipleChoiceOption(String strClass, String choice, String value) { 874 if (!classPreferenceList.containsKey(strClass)) { 875 classPreferenceList.put(strClass, new ClassPreferences()); 876 } 877 classPreferenceList.get(strClass).getMultipleChoiceList().stream() 878 .filter(mc -> (mc.getItem().equals(choice))).forEachOrdered(mc -> mc.setValue(value)); 879 this.savePreferencesState(); 880 } 881 882 @Override 883 public void setMultipleChoiceOption(String strClass, String choice, int value) { 884 885 // LogixNG bug fix: 886 // The class 'strClass' must have a default constructor. Otherwise, 887 // an error is logged to the log. Early versions of LogixNG used 888 // AbstractLogixNGTableAction and ??? as strClass, which didn't work. 889 // Now, LogixNG uses the class jmri.jmrit.logixng.LogixNG_UserPreferences 890 // for this purpose. 891 if ("jmri.jmrit.beantable.AbstractLogixNGTableAction".equals(strClass)) return; 892 if ("jmri.jmrit.logixng.tools.swing.TreeEditor".equals(strClass)) return; 893 894 if (!classPreferenceList.containsKey(strClass)) { 895 classPreferenceList.put(strClass, new ClassPreferences()); 896 } 897 boolean set = false; 898 for (MultipleChoice mc : classPreferenceList.get(strClass).getMultipleChoiceList()) { 899 if (mc.getItem().equals(choice)) { 900 mc.setValue(value); 901 set = true; 902 } 903 } 904 if (!set) { 905 classPreferenceList.get(strClass).getMultipleChoiceList().add(new MultipleChoice(choice, value)); 906 setClassDescription(strClass); 907 } 908 displayRememberMsg(); 909 this.savePreferencesState(); 910 } 911 912 public String getClassDescription() { 913 return "Preference Manager"; 914 } 915 916 protected final String getClassName() { 917 return this.getClass().getName(); 918 } 919 920 protected final ClassPreferences getClassPreferences(String strClass) { 921 return this.classPreferenceList.get(strClass); 922 } 923 924 @Override 925 public int getPreferencesSize(String strClass) { 926 if (classPreferenceList.containsKey(strClass)) { 927 return classPreferenceList.get(strClass).getPreferencesSize(); 928 } 929 return 0; 930 } 931 932 public final void readUserPreferences() { 933 log.trace("starting readUserPreferences"); 934 this.allowSave = false; 935 this.loading = true; 936 File perNodeConfig = null; 937 try { 938 perNodeConfig = FileUtil.getFile(FileUtil.PROFILE + Profile.PROFILE + "/" + NodeIdentity.storageIdentity() + "/" + Profile.UI_CONFIG); // NOI18N 939 if (!perNodeConfig.canRead()) { 940 perNodeConfig = null; 941 log.trace(" sharedConfig can't be read"); 942 } 943 } catch (FileNotFoundException ex) { 944 // ignore - this only means that sharedConfig does not exist. 945 log.trace(" FileNotFoundException: sharedConfig does not exist"); 946 } 947 if (perNodeConfig != null) { 948 file = perNodeConfig; 949 log.debug(" start perNodeConfig file: {}", file.getPath()); 950 this.readComboBoxLastSelections(); 951 this.readCheckBoxLastSelections(); 952 this.readPreferencesState(); 953 this.readSimplePreferenceState(); 954 this.readWindowDetails(); 955 } else { 956 try { 957 file = FileUtil.getFile(FileUtil.PROFILE + Profile.UI_CONFIG_FILENAME); 958 if (file.exists()) { 959 log.debug("start load user pref file: {}", file.getPath()); 960 try { 961 InstanceManager.getDefault(ConfigureManager.class).load(file, true); 962 this.allowSave = true; 963 this.savePreferences(); // write new preferences format immediately 964 } catch (JmriException e) { 965 log.error("Unhandled problem loading configuration: {}", e.getMessage()); 966 } catch (NullPointerException e) { 967 log.error("NPE when trying to load user pref {}", file); 968 } 969 } else { 970 // if we got here, there is no saved user preferences 971 log.info("No saved user preferences file"); 972 } 973 } catch (FileNotFoundException ex) { 974 // ignore - this only means that UserPrefsProfileConfig.xml does not exist. 975 log.debug("UserPrefsProfileConfig.xml does not exist"); 976 } 977 } 978 this.loading = false; 979 this.allowSave = true; 980 log.trace(" ending readUserPreferences"); 981 } 982 983 private void readComboBoxLastSelections() { 984 Element element = this.readElement(COMBOBOX_ELEMENT, COMBOBOX_NAMESPACE); 985 if (element != null) { 986 element.getChildren("comboBox").stream().forEach(combo -> 987 comboBoxLastSelection.put(combo.getAttributeValue("name"), combo.getAttributeValue("lastSelected"))); 988 } 989 } 990 991 private void saveComboBoxLastSelections() { 992 this.setChangeMade(false); 993 if (this.allowSave && !comboBoxLastSelection.isEmpty()) { 994 Element element = new Element(COMBOBOX_ELEMENT, COMBOBOX_NAMESPACE); 995 // Do not store blank last entered/selected values 996 comboBoxLastSelection.entrySet().stream(). 997 filter(cbls -> (cbls.getValue() != null && !cbls.getValue().isEmpty())).map(cbls -> { 998 Element combo = new Element("comboBox"); 999 combo.setAttribute("name", cbls.getKey()); 1000 combo.setAttribute("lastSelected", cbls.getValue()); 1001 return combo; 1002 }).forEach(element::addContent); 1003 this.saveElement(element); 1004 this.resetChangeMade(); 1005 } 1006 } 1007 1008 private void readCheckBoxLastSelections() { 1009 Element element = this.readElement(CHECKBOX_ELEMENT, CHECKBOX_NAMESPACE); 1010 if (element != null) { 1011 element.getChildren("checkBox").stream().forEach(checkbox -> 1012 checkBoxLastSelection.put(checkbox.getAttributeValue("name"), "yes".equals(checkbox.getAttributeValue("lastChecked")))); 1013 } 1014 } 1015 1016 private void saveCheckBoxLastSelections() { 1017 this.setChangeMade(false); 1018 if (this.allowSave && !checkBoxLastSelection.isEmpty()) { 1019 Element element = new Element(CHECKBOX_ELEMENT, CHECKBOX_NAMESPACE); 1020 // Do not store blank last entered/selected values 1021 checkBoxLastSelection.entrySet().stream(). 1022 filter(cbls -> (cbls.getValue() != null)).map(cbls -> { 1023 Element checkbox = new Element("checkBox"); 1024 checkbox.setAttribute("name", cbls.getKey()); 1025 checkbox.setAttribute("lastChecked", cbls.getValue() ? "yes" : "no"); 1026 return checkbox; 1027 }).forEach(element::addContent); 1028 this.saveElement(element); 1029 this.resetChangeMade(); 1030 } 1031 } 1032 1033 private void readPreferencesState() { 1034 Element element = this.readElement(CLASSPREFS_ELEMENT, CLASSPREFS_NAMESPACE); 1035 if (element != null) { 1036 element.getChildren("preferences").stream().forEach(preferences -> { 1037 String clazz = preferences.getAttributeValue(CLASS); 1038 log.debug("Reading class preferences for \"{}\"", clazz); 1039 preferences.getChildren("multipleChoice").stream().forEach(mc -> 1040 mc.getChildren("option").stream().forEach(option -> { 1041 int value = 0; 1042 try { 1043 value = option.getAttribute(VALUE).getIntValue(); 1044 } catch (DataConversionException ex) { 1045 log.error("failed to convert positional attribute"); 1046 } 1047 this.setMultipleChoiceOption(clazz, option.getAttributeValue("item"), value); 1048 })); 1049 preferences.getChildren("reminderPrompts").stream().forEach(rp -> 1050 rp.getChildren(REMINDER).stream().forEach(reminder -> { 1051 log.debug("Setting preferences state \"true\" for \"{}\", \"{}\"", clazz, reminder.getText()); 1052 this.setPreferenceState(clazz, reminder.getText(), true); 1053 })); 1054 }); 1055 } 1056 } 1057 1058 private void savePreferencesState() { 1059 this.setChangeMade(true); 1060 if (this.allowSave) { 1061 Element element = new Element(CLASSPREFS_ELEMENT, CLASSPREFS_NAMESPACE); 1062 this.classPreferenceList.keySet().stream().forEach(name -> { 1063 ClassPreferences cp = this.classPreferenceList.get(name); 1064 if (!cp.multipleChoiceList.isEmpty() || !cp.preferenceList.isEmpty()) { 1065 Element clazz = new Element("preferences"); 1066 clazz.setAttribute(CLASS, name); 1067 if (!cp.multipleChoiceList.isEmpty()) { 1068 Element choices = new Element("multipleChoice"); 1069 // only save non-default values 1070 cp.multipleChoiceList.stream().filter(mc -> (mc.getDefaultValue() != mc.getValue())).forEach(mc -> 1071 choices.addContent(new Element("option") 1072 .setAttribute("item", mc.getItem()) 1073 .setAttribute(VALUE, Integer.toString(mc.getValue())))); 1074 if (!choices.getChildren().isEmpty()) { 1075 clazz.addContent(choices); 1076 } 1077 } 1078 if (!cp.preferenceList.isEmpty()) { 1079 Element reminders = new Element("reminderPrompts"); 1080 cp.preferenceList.stream().filter(pl -> (pl.getState())).forEach(pl -> 1081 reminders.addContent(new Element(REMINDER).addContent(pl.getItem()))); 1082 if (!reminders.getChildren().isEmpty()) { 1083 clazz.addContent(reminders); 1084 } 1085 } 1086 element.addContent(clazz); 1087 } 1088 }); 1089 if (!element.getChildren().isEmpty()) { 1090 this.saveElement(element); 1091 } 1092 } 1093 } 1094 1095 private void readSimplePreferenceState() { 1096 Element element = this.readElement(SETTINGS_ELEMENT, SETTINGS_NAMESPACE); 1097 if (element != null) { 1098 element.getChildren("setting").stream().forEach(setting -> 1099 this.simplePreferenceList.add(setting.getText())); 1100 } 1101 } 1102 1103 private void saveSimplePreferenceState() { 1104 this.setChangeMade(false); 1105 if (this.allowSave) { 1106 Element element = new Element(SETTINGS_ELEMENT, SETTINGS_NAMESPACE); 1107 getSimplePreferenceStateList().stream().forEach(setting -> 1108 element.addContent(new Element("setting").addContent(setting))); 1109 this.saveElement(element); 1110 this.resetChangeMade(); 1111 } 1112 } 1113 1114 private void readWindowDetails() { 1115 // TODO: COMPLETE! 1116 Element element = this.readElement(WINDOWS_ELEMENT, WINDOWS_NAMESPACE); 1117 if (element != null) { 1118 element.getChildren("window").stream().forEach(window -> { 1119 String reference = window.getAttributeValue(CLASS); 1120 log.debug("Reading window details for {}", reference); 1121 try { 1122 if (window.getAttribute("locX") != null && window.getAttribute("locY") != null) { 1123 double x = window.getAttribute("locX").getDoubleValue(); 1124 double y = window.getAttribute("locY").getDoubleValue(); 1125 this.setWindowLocation(reference, new java.awt.Point((int) x, (int) y)); 1126 } 1127 if (window.getAttribute(WIDTH) != null && window.getAttribute(HEIGHT) != null) { 1128 double width = window.getAttribute(WIDTH).getDoubleValue(); 1129 double height = window.getAttribute(HEIGHT).getDoubleValue(); 1130 this.setWindowSize(reference, new java.awt.Dimension((int) width, (int) height)); 1131 } 1132 } catch (DataConversionException ex) { 1133 log.error("Unable to read dimensions of window \"{}\"", reference); 1134 } 1135 if (window.getChild(PROPERTIES) != null) { 1136 window.getChild(PROPERTIES).getChildren().stream().forEach(property -> { 1137 String key = property.getChild("key").getText(); 1138 try { 1139 Class<?> cl = Class.forName(property.getChild(VALUE).getAttributeValue(CLASS)); 1140 Constructor<?> ctor = cl.getConstructor(new Class<?>[]{String.class}); 1141 Object value = ctor.newInstance(new Object[]{property.getChild(VALUE).getText()}); 1142 log.debug("Setting property {} for {} to {}", key, reference, value); 1143 this.setProperty(reference, key, value); 1144 } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { 1145 log.error("Unable to retrieve property \"{}\" for window \"{}\"", key, reference); 1146 } catch (NullPointerException ex) { 1147 // null properties do not get set 1148 log.debug("Property \"{}\" for window \"{}\" is null", key, reference); 1149 } 1150 }); 1151 } 1152 }); 1153 } 1154 } 1155 1156 @SuppressFBWarnings(value = "DMI_ENTRY_SETS_MAY_REUSE_ENTRY_OBJECTS", 1157 justification = "needs to copy the items of the hashmap windowDetails") 1158 private void saveWindowDetails() { 1159 this.setChangeMade(false); 1160 if (this.allowSave) { 1161 if (!windowDetails.isEmpty()) { 1162 Element element = new Element(WINDOWS_ELEMENT, WINDOWS_NAMESPACE); 1163 // Copy the entries before iterate over them since 1164 // ConcurrentModificationException may happen otherwise 1165 Set<Entry<String, WindowLocations>> entries = new HashSet<>(windowDetails.entrySet()); 1166 for (Entry<String, WindowLocations> entry : entries) { 1167 Element window = new Element("window"); 1168 window.setAttribute(CLASS, entry.getKey()); 1169 if (entry.getValue().getSaveLocation()) { 1170 try { 1171 window.setAttribute("locX", Double.toString(entry.getValue().getLocation().getX())); 1172 window.setAttribute("locY", Double.toString(entry.getValue().getLocation().getY())); 1173 } catch (NullPointerException ex) { 1174 // Expected if the location has not been set or the window is open 1175 } 1176 } 1177 if (entry.getValue().getSaveSize()) { 1178 try { 1179 double height = entry.getValue().getSize().getHeight(); 1180 double width = entry.getValue().getSize().getWidth(); 1181 // Do not save the width or height if set to zero 1182 if (!(height == 0.0 && width == 0.0)) { 1183 window.setAttribute(WIDTH, Double.toString(width)); 1184 window.setAttribute(HEIGHT, Double.toString(height)); 1185 } 1186 } catch (NullPointerException ex) { 1187 // Expected if the size has not been set or the window is open 1188 } 1189 } 1190 if (!entry.getValue().parameters.isEmpty()) { 1191 Element properties = new Element(PROPERTIES); 1192 entry.getValue().parameters.entrySet().stream().map(property -> { 1193 Element propertyElement = new Element("property"); 1194 propertyElement.addContent(new Element("key").setText(property.getKey())); 1195 Object value = property.getValue(); 1196 if (value != null) { 1197 propertyElement.addContent(new Element(VALUE) 1198 .setAttribute(CLASS, value.getClass().getName()) 1199 .setText(value.toString())); 1200 } 1201 return propertyElement; 1202 }).forEach(properties::addContent); 1203 window.addContent(properties); 1204 } 1205 element.addContent(window); 1206 } 1207 this.saveElement(element); 1208 this.resetChangeMade(); 1209 } 1210 } 1211 } 1212 1213 /** 1214 * 1215 * @return an Element or null if the requested element does not exist 1216 */ 1217 @CheckForNull 1218 private Element readElement(@Nonnull String elementName, @Nonnull String namespace) { 1219 org.w3c.dom.Element element = ProfileUtils.getUserInterfaceConfiguration(ProfileManager.getDefault().getActiveProfile()).getConfigurationFragment(elementName, namespace, false); 1220 if (element != null) { 1221 return JDOMUtil.toJDOMElement(element); 1222 } 1223 return null; 1224 } 1225 1226 protected void saveElement(@Nonnull Element element) { 1227 log.trace("Saving {} element.", element.getName()); 1228 try { 1229 ProfileUtils.getUserInterfaceConfiguration(ProfileManager.getDefault().getActiveProfile()).putConfigurationFragment(JDOMUtil.toW3CElement(element), false); 1230 } catch (JDOMException ex) { 1231 log.error("Unable to save user preferences", ex); 1232 } 1233 } 1234 1235 private void savePreferences() { 1236 this.saveComboBoxLastSelections(); 1237 this.saveCheckBoxLastSelections(); 1238 this.savePreferencesState(); 1239 this.saveSimplePreferenceState(); 1240 this.saveWindowDetails(); 1241 this.resetChangeMade(); 1242 InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent(manager -> 1243 manager.savePreferences(ProfileManager.getDefault().getActiveProfile())); 1244 } 1245 1246 @Override 1247 public void initialize() { 1248 this.readUserPreferences(); 1249 } 1250 1251 /** 1252 * Holds details about the specific class. 1253 */ 1254 protected static final class ClassPreferences { 1255 1256 String classDescription; 1257 1258 ArrayList<MultipleChoice> multipleChoiceList = new ArrayList<>(); 1259 ArrayList<PreferenceList> preferenceList = new ArrayList<>(); 1260 1261 ClassPreferences() { 1262 } 1263 1264 ClassPreferences(String classDescription) { 1265 this.classDescription = classDescription; 1266 } 1267 1268 String getDescription() { 1269 return classDescription; 1270 } 1271 1272 void setDescription(String description) { 1273 classDescription = description; 1274 } 1275 1276 ArrayList<PreferenceList> getPreferenceList() { 1277 return preferenceList; 1278 } 1279 1280 int getPreferenceListSize() { 1281 return preferenceList.size(); 1282 } 1283 1284 ArrayList<MultipleChoice> getMultipleChoiceList() { 1285 return multipleChoiceList; 1286 } 1287 1288 int getPreferencesSize() { 1289 return multipleChoiceList.size() + preferenceList.size(); 1290 } 1291 1292 public String getPreferenceName(int n) { 1293 try { 1294 return preferenceList.get(n).getItem(); 1295 } catch (IndexOutOfBoundsException ioob) { 1296 return null; 1297 } 1298 } 1299 1300 int getMultipleChoiceListSize() { 1301 return multipleChoiceList.size(); 1302 } 1303 1304 public String getChoiceName(int n) { 1305 try { 1306 return multipleChoiceList.get(n).getItem(); 1307 } catch (IndexOutOfBoundsException ioob) { 1308 return null; 1309 } 1310 } 1311 } 1312 1313 protected static final class MultipleChoice { 1314 1315 HashMap<Integer, String> options; 1316 String optionDescription; 1317 String item; 1318 int value = -1; 1319 int defaultOption = -1; 1320 1321 MultipleChoice(String description, String item, HashMap<Integer, String> options, int defaultOption) { 1322 this.item = item; 1323 setMessageItems(description, options, defaultOption); 1324 } 1325 1326 MultipleChoice(String item, int value) { 1327 this.item = item; 1328 this.value = value; 1329 1330 } 1331 1332 void setValue(int value) { 1333 this.value = value; 1334 } 1335 1336 void setValue(String value) { 1337 options.keySet().stream().filter(o -> (options.get(o).equals(value))).forEachOrdered(o -> this.value = o); 1338 } 1339 1340 void setMessageItems(String description, HashMap<Integer, String> options, int defaultOption) { 1341 optionDescription = description; 1342 this.options = options; 1343 this.defaultOption = defaultOption; 1344 if (value == -1) { 1345 value = defaultOption; 1346 } 1347 } 1348 1349 int getValue() { 1350 return value; 1351 } 1352 1353 int getDefaultValue() { 1354 return defaultOption; 1355 } 1356 1357 String getItem() { 1358 return item; 1359 } 1360 1361 String getOptionDescription() { 1362 return optionDescription; 1363 } 1364 1365 HashMap<Integer, String> getOptions() { 1366 return options; 1367 } 1368 1369 } 1370 1371 protected static final class PreferenceList { 1372 1373 // need to fill this with bits to get a meaning full description. 1374 boolean set = false; 1375 String item = ""; 1376 String description = ""; 1377 1378 PreferenceList(String item) { 1379 this.item = item; 1380 } 1381 1382 PreferenceList(String item, boolean state) { 1383 this.item = item; 1384 set = state; 1385 } 1386 1387 PreferenceList(String item, String description) { 1388 this.description = description; 1389 this.item = item; 1390 } 1391 1392 void setDescription(String desc) { 1393 description = desc; 1394 } 1395 1396 String getDescription() { 1397 return description; 1398 } 1399 1400 boolean getState() { 1401 return set; 1402 } 1403 1404 void setState(boolean state) { 1405 this.set = state; 1406 } 1407 1408 String getItem() { 1409 return item; 1410 } 1411 1412 } 1413 1414 protected static final class WindowLocations { 1415 1416 private Point xyLocation = new Point(0, 0); 1417 private Dimension size = new Dimension(0, 0); 1418 private boolean saveSize = false; 1419 private boolean saveLocation = false; 1420 1421 WindowLocations() { 1422 } 1423 1424 Point getLocation() { 1425 return xyLocation; 1426 } 1427 1428 Dimension getSize() { 1429 return size; 1430 } 1431 1432 void setSaveSize(boolean b) { 1433 saveSize = b; 1434 } 1435 1436 void setSaveLocation(boolean b) { 1437 saveLocation = b; 1438 } 1439 1440 boolean getSaveSize() { 1441 return saveSize; 1442 } 1443 1444 boolean getSaveLocation() { 1445 return saveLocation; 1446 } 1447 1448 void setLocation(Point xyLocation) { 1449 this.xyLocation = xyLocation; 1450 saveLocation = true; 1451 } 1452 1453 void setSize(Dimension size) { 1454 this.size = size; 1455 saveSize = true; 1456 } 1457 1458 void setProperty(@Nonnull String key, @CheckForNull Object value) { 1459 if (value == null) { 1460 parameters.remove(key); 1461 } else { 1462 parameters.put(key, value); 1463 } 1464 } 1465 1466 @CheckForNull 1467 Object getProperty(String key) { 1468 return parameters.get(key); 1469 } 1470 1471 Set<String> getPropertyKeys() { 1472 return parameters.keySet(); 1473 } 1474 1475 final ConcurrentHashMap<String, Object> parameters = new ConcurrentHashMap<>(); 1476 1477 } 1478 1479 @ServiceProvider(service = InstanceInitializer.class) 1480 public static class Initializer extends AbstractInstanceInitializer { 1481 1482 @Override 1483 public <T> Object getDefault(Class<T> type) { 1484 if (type.equals(UserPreferencesManager.class)) { 1485 return new JmriUserPreferencesManager(); 1486 } 1487 return super.getDefault(type); 1488 } 1489 1490 @Override 1491 public Set<Class<?>> getInitalizes() { 1492 Set<Class<?>> set = super.getInitalizes(); 1493 set.add(UserPreferencesManager.class); 1494 return set; 1495 } 1496 } 1497 1498 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JmriUserPreferencesManager.class); 1499 1500}