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