001package jmri.util.gui; 002 003import java.awt.Font; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Enumeration; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Locale; 010import java.util.Set; 011import java.util.prefs.BackingStoreException; 012import java.util.prefs.Preferences; 013import javax.annotation.Nonnull; 014import javax.swing.ToolTipManager; 015import javax.swing.UIManager; 016import javax.swing.UIManager.LookAndFeelInfo; 017import javax.swing.UnsupportedLookAndFeelException; 018import jmri.InstanceManagerAutoDefault; 019import jmri.beans.Bean; 020import jmri.profile.Profile; 021import jmri.profile.ProfileUtils; 022import jmri.spi.PreferencesManager; 023import jmri.util.prefs.InitializationException; 024import org.openide.util.lookup.ServiceProvider; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028/** 029 * Manage GUI Look and Feel (LAF) preferences. 030 * 031 * @author Randall Wood (C) 2015, 2020 032 */ 033@ServiceProvider(service = PreferencesManager.class) 034public class GuiLafPreferencesManager extends Bean implements PreferencesManager, InstanceManagerAutoDefault { 035 036 public static final String FONT_NAME = "fontName"; 037 public static final String FONT_SIZE = "fontSize"; 038 public static final String LOCALE = "locale"; 039 public static final String LOOK_AND_FEEL = "lookAndFeel"; 040 public static final String NONSTANDARD_MOUSE_EVENT = "nonstandardMouseEvent"; 041 // Display state in bean tables as icon. 042 public static final String GRAPHIC_TABLE_STATE = "graphicTableState"; 043 // Classic OBlock editor or tabbed tables 044 public static final String OBLOCK_EDIT_TABBED = "oblockEditTabbed"; 045 public static final String VERTICAL_TOOLBAR = "verticalToolBar"; 046 public static final String SHOW_TOOL_TIP_TIME = "showToolTipDismissDelay"; 047 public static final String EDITOR_USE_OLD_LOC_SIZE = "editorUseOldLocSize"; 048 public static final String JFILECHOOSER_FORMAT = "jfilechooserformat"; 049 public static final String MAX_COMBO_ROWS = "maxComboRows"; 050 /** 051 * Smallest font size a user can set the font size to other than zero 052 * ({@value}). A font size of 0 indicates that the system default font size 053 * will be used. 054 * 055 * @see apps.GuiLafConfigPane#MIN_DISPLAYED_FONT_SIZE 056 */ 057 public static final int MIN_FONT_SIZE = 9; 058 /** 059 * Largest font size a user can set the font size to ({@value}). 060 * 061 * @see apps.GuiLafConfigPane#MAX_DISPLAYED_FONT_SIZE 062 */ 063 public static final int MAX_FONT_SIZE = 36; 064 public static final String PROP_DIRTY = "dirty"; 065 public static final String PROP_RESTARTREQUIRED = "restartRequired"; 066 public static final String DEFAULT_FONT = "List.font"; 067 068 // preferences with default values 069 private Locale locale = Locale.getDefault(); 070 private Font currentFont = null; 071 private Font defaultFont = null; 072 private int fontSize = 0; 073 private int defaultFontSize = 11; 074 private boolean nonStandardMouseEvent = false; 075 private boolean graphicTableState = false; 076 private boolean oblockEditTabbed = false; 077 private boolean editorUseOldLocSize = false; 078 private int jFileChooserFormat = 0; 079 private String lookAndFeel = UIManager.getLookAndFeel().getClass().getName(); 080 private int toolTipDismissDelay = ToolTipManager.sharedInstance().getDismissDelay(); 081 private int maxComboRows = 0; 082 private boolean dirty = false; 083 private boolean restartRequired = false; 084 085 /* 086 * Unlike most PreferencesProviders, the GUI Look & Feel preferences should 087 * be per-application instead of per-profile. 088 */ 089 private boolean initialized = false; 090 private final List<InitializationException> exceptions = new ArrayList<>(); 091 private static final Logger log = LoggerFactory.getLogger(GuiLafPreferencesManager.class); 092 093 @Override 094 public void initialize(Profile profile) throws InitializationException { 095 if (!this.initialized) { 096 Preferences preferences = ProfileUtils.getPreferences(profile, this.getClass(), true); 097 this.setLocale(Locale.forLanguageTag(preferences.get(LOCALE, this.getLocale().toLanguageTag()))); 098 099 var lookAndFeelClassname = preferences.get(LOOK_AND_FEEL, this.getLookAndFeel()); 100 this.setLookAndFeel(lookAndFeelClassname); 101 102 this.setDefaultFontSize(); // before we change anything 103 this.setFontSize(preferences.getInt(FONT_SIZE, this.getDefaultFontSize())); 104 if (this.getFontSize() == 0) { 105 this.setFontSize(this.getDefaultFontSize()); 106 } 107 108 this.setFontByName(preferences.get(FONT_NAME, this.getDefaultFont().getFontName())); 109 if (this.getFont() == null) { 110 this.setFont(this.getDefaultFont()); 111 } 112 113 this.setNonStandardMouseEvent( 114 preferences.getBoolean(NONSTANDARD_MOUSE_EVENT, this.isNonStandardMouseEvent())); 115 this.setGraphicTableState(preferences.getBoolean(GRAPHIC_TABLE_STATE, this.isGraphicTableState())); 116 this.setOblockEditTabbed(preferences.getBoolean(OBLOCK_EDIT_TABBED, this.isOblockEditTabbed())); 117 this.setEditorUseOldLocSize(preferences.getBoolean(EDITOR_USE_OLD_LOC_SIZE, this.isEditorUseOldLocSize())); 118 this.setJFileChooserFormat(preferences.getInt(JFILECHOOSER_FORMAT, this.getJFileChooserFormat())); 119 this.setMaxComboRows(preferences.getInt(MAX_COMBO_ROWS, this.getMaxComboRows())); 120 this.setToolTipDismissDelay(preferences.getInt(SHOW_TOOL_TIP_TIME, this.getToolTipDismissDelay())); 121 122 log.debug("About to setDefault Locale"); 123 Locale.setDefault(this.getLocale()); 124 javax.swing.JComponent.setDefaultLocale(this.getLocale()); 125 126 this.applyLookAndFeel(); 127 this.applyFontSize(); 128 this.initialized = true; 129 } 130 } 131 132 @Override 133 public boolean isInitialized(Profile profile) { 134 return this.initialized && this.exceptions.isEmpty(); 135 } 136 137 @Override 138 @Nonnull 139 public Collection<Class<? extends PreferencesManager>> getRequires() { 140 return new HashSet<>(); 141 } 142 143 @Override 144 @Nonnull 145 public Iterable<Class<?>> getProvides() { 146 Set<Class<?>> provides = new HashSet<>(); 147 provides.add(this.getClass()); 148 return provides; 149 } 150 151 @Override 152 public void savePreferences(Profile profile) { 153 Preferences preferences = ProfileUtils.getPreferences(profile, this.getClass(), true); 154 preferences.put(LOCALE, this.getLocale().toLanguageTag()); 155 preferences.put(LOOK_AND_FEEL, this.getLookAndFeel()); 156 157 if (currentFont == null) { 158 currentFont = this.getDefaultFont(); 159 } 160 161 String currentFontName = currentFont.getFontName(); 162 if (currentFontName != null) { 163 String prefFontName = preferences.get(FONT_NAME, currentFontName); 164 if ((prefFontName == null) || (!prefFontName.equals(currentFontName))) { 165 preferences.put(FONT_NAME, currentFontName); 166 } 167 } 168 169 int temp = this.getFontSize(); 170 if (temp == this.getDefaultFontSize()) { 171 temp = 0; 172 } 173 if (temp != preferences.getInt(FONT_SIZE, -1)) { 174 preferences.putInt(FONT_SIZE, temp); 175 } 176 preferences.putBoolean(NONSTANDARD_MOUSE_EVENT, this.isNonStandardMouseEvent()); 177 preferences.putBoolean(GRAPHIC_TABLE_STATE, this.isGraphicTableState()); 178 preferences.putBoolean(OBLOCK_EDIT_TABBED, this.isOblockEditTabbed()); 179 preferences.putBoolean(EDITOR_USE_OLD_LOC_SIZE, this.isEditorUseOldLocSize()); 180 preferences.putInt(JFILECHOOSER_FORMAT, this.jFileChooserFormat); 181 preferences.putInt(MAX_COMBO_ROWS, this.getMaxComboRows()); 182 preferences.putInt(SHOW_TOOL_TIP_TIME, this.getToolTipDismissDelay()); 183 try { 184 preferences.sync(); 185 } catch (BackingStoreException ex) { 186 log.error("Unable to save preferences.", ex); 187 } 188 this.setDirty(false); 189 } 190 191 /** 192 * @return the locale 193 */ 194 public Locale getLocale() { 195 return locale; 196 } 197 198 /** 199 * @param locale the locale to set 200 */ 201 public void setLocale(Locale locale) { 202 Locale oldLocale = this.locale; 203 this.locale = locale; 204 firePropertyChange(LOCALE, oldLocale, locale); 205 } 206 207 /** 208 * @return the currently selected font 209 */ 210 public Font getFont() { 211 return currentFont; 212 } 213 214 /** 215 * Sets a new font 216 * 217 * @param newFont the new font to set 218 */ 219 public void setFont(Font newFont) { 220 Font oldFont = this.currentFont; 221 this.currentFont = newFont; 222 firePropertyChange(FONT_NAME, oldFont, this.currentFont); 223 } 224 225 /** 226 * Sets a new font by name 227 * 228 * @param newFontName the name of the new font to set 229 */ 230 public void setFontByName(String newFontName) { 231 Font oldFont = getFont(); 232 if (oldFont == null) { 233 oldFont = this.getDefaultFont(); 234 } 235 setFont(new Font(newFontName, oldFont.getStyle(), fontSize)); 236 } 237 238 /** 239 * @return the current Look and Feel default font 240 */ 241 public Font getDefaultFont() { 242 if (defaultFont == null) { 243 setDefaultFont(); 244 } 245 return defaultFont; 246 } 247 248 /** 249 * Called to load the current Look and Feel default font, based on 250 * looking up the {@value #DEFAULT_FONT}. 251 */ 252 public void setDefaultFont() { 253 java.util.Enumeration<Object> keys = UIManager.getDefaults().keys(); 254 while (keys.hasMoreElements()) { 255 Object key = keys.nextElement(); 256 Object value = UIManager.get(key); 257 258 if (value instanceof javax.swing.plaf.FontUIResource && key.toString().equals(DEFAULT_FONT)) { 259 Font f = UIManager.getFont(key); 260 log.debug("Key:{} Font: {}", key, f.getName()); 261 defaultFont = f; 262 return; 263 } 264 } 265 // couldn't find the default return a reasonable font 266 defaultFont = UIManager.getFont(DEFAULT_FONT); 267 if (defaultFont == null) { 268 // or maybe not quite as reasonable 269 defaultFont = UIManager.getFont("TextArea.font"); 270 } 271 } 272 273 /** 274 * @return the currently selected font size 275 */ 276 public int getFontSize() { 277 if (fontSize == 0) { 278 return defaultFontSize; 279 } 280 return fontSize; 281 } 282 283 /** 284 * Set the new font size. If newFontSize is non-zero and less than 285 * {@value #MIN_FONT_SIZE}, the font size is set to {@value #MIN_FONT_SIZE} 286 * or if greater than {@value #MAX_FONT_SIZE}, the font size is set to 287 * {@value #MAX_FONT_SIZE}. 288 * 289 * @param newFontSize the new font size to set 290 */ 291 public void setFontSize(int newFontSize) { 292 int oldFontSize = this.fontSize; 293 if (newFontSize != 0 && newFontSize < MIN_FONT_SIZE) { 294 this.fontSize = MIN_FONT_SIZE; 295 } else if (newFontSize > MAX_FONT_SIZE) { 296 this.fontSize = MAX_FONT_SIZE; 297 } else { 298 this.fontSize = newFontSize; 299 } 300 firePropertyChange(FONT_SIZE, oldFontSize, this.fontSize); 301 } 302 303 /** 304 * Get the default font size for the current Look and Feel. 305 * 306 * @return the default font size 307 */ 308 public int getDefaultFontSize() { 309 return defaultFontSize; 310 } 311 312 /** 313 * Get the default font size for the current Look and Feel, based 314 * on looking up the {@value #DEFAULT_FONT} size. 315 */ 316 public void setDefaultFontSize() { 317 java.util.Enumeration<Object> keys = UIManager.getDefaults().keys(); 318 while (keys.hasMoreElements()) { 319 Object key = keys.nextElement(); 320 Object value = UIManager.get(key); 321 322 if (value instanceof javax.swing.plaf.FontUIResource && key.toString().equals(DEFAULT_FONT)) { 323 Font f = UIManager.getFont(key); 324 log.debug("Key:{} Font: {} size: {}", key, f.getName(), f.getSize()); 325 defaultFontSize = f.getSize(); 326 return; 327 } 328 } 329 defaultFontSize = 11; // couldn't find the default return a reasonable 330 // font size 331 } 332 333 /** 334 * Logs LAF fonts at the TRACE level. 335 */ 336 private void logAllFonts() { 337 // avoid any activity if logging at this level is disabled to avoid 338 // the unnecessary overhead of getting the fonts 339 if (log.isTraceEnabled()) { 340 log.trace("******** LAF={}", UIManager.getLookAndFeel().getClass().getName()); 341 java.util.Enumeration<Object> keys = UIManager.getDefaults().keys(); 342 while (keys.hasMoreElements()) { 343 Object key = keys.nextElement(); 344 Object value = UIManager.get(key); 345 if (value != null && 346 (value instanceof javax.swing.plaf.FontUIResource || 347 value instanceof java.awt.Font || 348 key.toString().endsWith(".font"))) { 349 Font f = UIManager.getFont(key); 350 log.trace("Class={}; Key: {} Font: {} size: {}", value.getClass().getName(), key, f.getName(), 351 f.getSize()); 352 } 353 } 354 } 355 } 356 357 /** 358 * Sets the time a tooltip is displayed before it goes away. 359 * <p> 360 * Note that this preference takes effect immediately. 361 * 362 * @param time the delay in seconds. 363 */ 364 public void setToolTipDismissDelay(int time) { 365 int old = this.toolTipDismissDelay; 366 this.toolTipDismissDelay = time; 367 ToolTipManager.sharedInstance().setDismissDelay(time); 368 firePropertyChange(SHOW_TOOL_TIP_TIME, old, time); 369 } 370 371 /** 372 * Get the time a tooltip is displayed before being dismissed. 373 * 374 * @return the delay in seconds 375 */ 376 public int getToolTipDismissDelay() { 377 return this.toolTipDismissDelay; 378 } 379 380 /** 381 * @return the nonStandardMouseEvent 382 */ 383 public boolean isNonStandardMouseEvent() { 384 return nonStandardMouseEvent; 385 } 386 387 /** 388 * @param nonStandardMouseEvent the nonStandardMouseEvent to set 389 */ 390 public void setNonStandardMouseEvent(boolean nonStandardMouseEvent) { 391 boolean oldNonStandardMouseEvent = this.nonStandardMouseEvent; 392 this.nonStandardMouseEvent = nonStandardMouseEvent; 393 firePropertyChange(NONSTANDARD_MOUSE_EVENT, oldNonStandardMouseEvent, nonStandardMouseEvent); 394 } 395 396 /** 397 * @return the graphicTableState 398 */ 399 public boolean isGraphicTableState() { 400 return graphicTableState; 401 } 402 403 /** 404 * @param graphicTableState the graphicTableState to set 405 */ 406 public void setGraphicTableState(boolean graphicTableState) { 407 boolean oldGraphicTableState = this.graphicTableState; 408 this.graphicTableState = graphicTableState; 409 firePropertyChange(GRAPHIC_TABLE_STATE, oldGraphicTableState, graphicTableState); 410 } 411 412 /** 413 * @return the graphicTableState 414 */ 415 public boolean isOblockEditTabbed() { 416 return oblockEditTabbed; 417 } 418 419 /** 420 * @param tabbed the Editor interface to set (fasle = desktop) 421 */ 422 public void setOblockEditTabbed(boolean tabbed) { 423 boolean oldOblockTabbed = this.oblockEditTabbed; 424 this.oblockEditTabbed = tabbed; 425 firePropertyChange(OBLOCK_EDIT_TABBED, oldOblockTabbed, tabbed); 426 } 427 428 /** 429 * @return the number of combo box rows to be displayed. 430 */ 431 public int getMaxComboRows() { 432 return maxComboRows; 433 } 434 435 /** 436 * Set a new value for the number of combo box rows to be displayed. 437 * @param maxRows The new value, zero for no limit 438 */ 439 public void setMaxComboRows(int maxRows) { 440 maxComboRows = maxRows; 441 } 442 443 /** 444 * @return the editorUseOldLocSize value 445 */ 446 public boolean isEditorUseOldLocSize() { 447 return editorUseOldLocSize; 448 } 449 450 /** 451 * @param editorUseOldLocSize the editorUseOldLocSize value to set 452 */ 453 public void setEditorUseOldLocSize(boolean editorUseOldLocSize) { 454 boolean oldEditorUseOldLocSize = this.editorUseOldLocSize; 455 this.editorUseOldLocSize = editorUseOldLocSize; 456 firePropertyChange(EDITOR_USE_OLD_LOC_SIZE, oldEditorUseOldLocSize, editorUseOldLocSize); 457 } 458 459 /** 460 * JFileChooser Type 461 * @return 0 default, 1 List 2 Detail 462 */ 463 public int getJFileChooserFormat() { 464 return jFileChooserFormat; 465 } 466 467 /** 468 * @param jFileChooserFormat the JFileChooser 0 default, 1 list, 2 detail 469 */ 470 public void setJFileChooserFormat( int jFileChooserFormat) { 471 int oldjFileChooserFormat = this.jFileChooserFormat; 472 this.jFileChooserFormat = jFileChooserFormat; 473 firePropertyChange(JFILECHOOSER_FORMAT, oldjFileChooserFormat, jFileChooserFormat); 474 } 475 476 /** 477 * Get the name of the class implementing the preferred look and feel. Note 478 * this may not be the in-use look and feel if the preferred look and feel 479 * is not available on the current platform; and will be overwritten if 480 * preferences are saved on a platform where the preferred look and feel is 481 * not available. 482 * 483 * @return the look and feel class name 484 */ 485 public String getLookAndFeel() { 486 return lookAndFeel; 487 } 488 489 /** 490 * Set the name of the class implementing the preferred look and feel. Note 491 * this change only takes effect after the application is restarted, because 492 * Java has some issues setting the look and feel correctly on already open 493 * windows. 494 * 495 * @param lookAndFeel the look and feel class name 496 */ 497 public void setLookAndFeel(String lookAndFeel) { 498 String oldLookAndFeel = this.lookAndFeel; 499 this.lookAndFeel = lookAndFeel; 500 firePropertyChange(LOOK_AND_FEEL, oldLookAndFeel, lookAndFeel); 501 // the actual change to the LAF will happen when that event reaches `applyLookAndFeel` below 502 } 503 504 /** 505 * Apply the existing look and feel. 506 */ 507 public void applyLookAndFeel() { 508 String lafClassName = lookAndFeel; 509 for (LookAndFeelInfo LAF : UIManager.getInstalledLookAndFeels()) { 510 // accept either name or classname of look and feel 511 if (LAF.getClassName().equals(this.lookAndFeel) || LAF.getName().equals(this.lookAndFeel)) { 512 lafClassName = LAF.getClassName(); 513 break; // use first match, not last match (unlikely to be 514 // different, but you never know) 515 } 516 } 517 log.debug("Look and feel selection \"{}\" ({})", this.lookAndFeel, lafClassName); 518 if (lafClassName != null) { 519 if (!lafClassName.equals(UIManager.getLookAndFeel().getClass().getName())) { 520 log.debug("Apply look and feel \"{}\" ({})", this.lookAndFeel, lafClassName); 521 final String localLafClassName = lafClassName; // final for thread invoke 522 jmri.util.ThreadingUtil.runOnGUI(() -> { 523 try { 524 if (localLafClassName.startsWith("com.github.weisj.darklaf") ) { 525 // DarkLAF special case - will have to use reflection if we have more than one in GuiLafConfigPane 526 com.github.weisj.darklaf.LafManager.install(new com.github.weisj.darklaf.theme.HighContrastDarkTheme()); 527 } else { 528 // Swing-handled class name 529 UIManager.setLookAndFeel(localLafClassName); 530 } 531 } catch (ClassNotFoundException ex) { 532 log.error("Could not find look and feel \"{}\".", this.lookAndFeel); 533 } catch ( 534 IllegalAccessException | 535 InstantiationException ex) { 536 log.error("Could not load look and feel \"{}\".", this.lookAndFeel); 537 } catch (UnsupportedLookAndFeelException ex) { 538 log.error("Look and feel \"{}\" is not supported on this platform.", this.lookAndFeel); 539 } 540 }); 541 } else { 542 log.debug("Not updating look and feel {} matching existing look and feel", lafClassName); 543 } 544 } 545 } 546 547 /** 548 * Applies a new calculated font size to all found fonts. 549 * <p> 550 * Calls {@link #getCalcFontSize(int) getCalcFontSize} to calculate new size 551 * for each. 552 */ 553 private void applyFontSize() { 554 if (log.isTraceEnabled()) { 555 logAllFonts(); 556 } 557 if (this.getFontSize() != this.getDefaultFontSize()) { 558 Enumeration<Object> keys = UIManager.getDefaults().keys(); 559 while (keys.hasMoreElements()) { 560 Object key = keys.nextElement(); 561 Object value = UIManager.get(key); 562 if (value != null && 563 (value instanceof javax.swing.plaf.FontUIResource || 564 value instanceof java.awt.Font || 565 key.toString().endsWith(".font"))) { 566 UIManager.put(key, UIManager.getFont(key).deriveFont(((Font) value).getStyle(), 567 getCalcFontSize(((Font) value).getSize()))); 568 } 569 } 570 if (log.isTraceEnabled()) { 571 logAllFonts(); 572 } 573 } 574 } 575 576 /** 577 * Stand-alone service routine to set the default Locale. 578 * <p> 579 * Intended to be invoked early, as soon as a profile is available, to 580 * ensure the correct language is set as startup proceeds. Must be followed 581 * eventually by a complete {@link #setLocale}. 582 * 583 * @param profile The profile to get the locale from 584 */ 585 @SuppressWarnings("deprecation") // The constructor Locale(String) is deprecated since version 19 586 // The replacement Locale.of(String) isn't available before version 19 587 public static void setLocaleMinimally(Profile profile) { 588 // en is default if a locale preference has not been set 589 String name = ProfileUtils.getPreferences(profile, GuiLafPreferencesManager.class, true).get("locale", "en"); 590 log.debug("setLocaleMinimally found language {}, setting", name); 591 Locale.setDefault(new Locale(name)); 592 javax.swing.JComponent.setDefaultLocale(new Locale(name)); 593 } 594 595 /** 596 * @return a new calculated font size based on difference between default 597 * size and selected size 598 * @param oldSize the old font size 599 */ 600 private int getCalcFontSize(int oldSize) { 601 return oldSize + (this.getFontSize() - this.getDefaultFontSize()); 602 } 603 604 /** 605 * Check if preferences need to be saved. 606 * 607 * @return true if preferences need to be saved 608 */ 609 public boolean isDirty() { 610 return dirty; 611 } 612 613 /** 614 * Set dirty state. 615 * 616 * @param dirty true if preferences need to be saved 617 */ 618 private void setDirty(boolean dirty) { 619 if (this.initialized) { 620 boolean oldDirty = this.dirty; 621 this.dirty = dirty; 622 super.firePropertyChange(PROP_DIRTY, oldDirty, dirty); 623 } 624 } 625 626 /** 627 * Check if application needs to restart to apply preferences. 628 * 629 * @return true if preferences are only applied on application start 630 */ 631 public boolean isRestartRequired() { 632 return restartRequired; 633 } 634 635 /** 636 * Set restart required state. Sets the state to true if 637 * {@link #isInitialized(jmri.profile.Profile)} is true. 638 */ 639 private void setRestartRequired() { 640 if (initialized && !restartRequired) { 641 restartRequired = true; 642 super.firePropertyChange(PROP_RESTARTREQUIRED, false, restartRequired); 643 } 644 } 645 646 /** 647 * {@inheritDoc} 648 */ 649 @Override 650 public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { 651 if (oldValue != newValue) { 652 setDirty(true); 653 setRestartRequired(); 654 super.firePropertyChange(propertyName, oldValue, newValue); 655 } 656 } 657 658 /** 659 * {@inheritDoc} 660 */ 661 @Override 662 public void firePropertyChange(String propertyName, int oldValue, int newValue) { 663 if (oldValue != newValue) { 664 setDirty(true); 665 setRestartRequired(); 666 super.firePropertyChange(propertyName, oldValue, newValue); 667 } 668 } 669 670 /** 671 * {@inheritDoc} 672 */ 673 @Override 674 public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 675 if (oldValue == null || newValue == null || oldValue != newValue) { 676 setDirty(true); 677 setRestartRequired(); 678 super.firePropertyChange(propertyName, oldValue, newValue); 679 } 680 } 681 682 @Override 683 public boolean isInitializedWithExceptions(Profile profile) { 684 return this.initialized && !this.exceptions.isEmpty(); 685 } 686 687 @Override 688 @Nonnull 689 public List<Exception> getInitializationExceptions(Profile profile) { 690 return new ArrayList<>(this.exceptions); 691 } 692 693}