001package jmri.jmrit.throttle.preferences; 002 003import java.awt.Dimension; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import java.io.File; 007import java.io.IOException; 008import java.util.ArrayList; 009 010import jmri.jmrit.XmlFile; 011import jmri.util.FileUtil; 012 013import org.jdom2.Document; 014import org.jdom2.Element; 015import org.jdom2.JDOMException; 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * A class to store JMRI throttles preferences. 021 * <p> 022 * A singleton instance is provided by a call to 023 * <code>jmri.InstanceManager.getDefault(ThrottlesPreferences.class);</code> or 024 * <code>jmri.InstanceManager.getNullableDefault(ThrottlesPreferences.class)</code>; 025 * @author Lionel Jeanson - 2009-2021 026 * 027 */ 028public class ThrottlesPreferences implements jmri.InstanceManagerAutoDefault { 029 030 public static final String prefPopertyName = "ThrottlePreferences"; 031 032 private boolean _useExThrottle = true; 033 private boolean _useToolBar = true; 034 private boolean _useFunctionIcon = true; 035 private boolean _useLargeSpeedSlider = true; 036 private boolean _resizeWinImg = false; 037 private boolean _useRosterImage = true; 038 private boolean _enableRosterSearch = true; 039 private boolean _enableAutoLoad = true; 040 private boolean _hideUndefinedFunButton = false; 041 private boolean _hideSpeedStepSelector = false; 042 private boolean _ignoreThrottlePosition = true; 043 private boolean _saveThrottleOnLayoutSave = true; 044 private boolean _isAddressSelectorShowingAllRosterGroup = false; 045 private boolean _isSilentSteal = false; 046 private boolean _isSilentShare = false; 047 private String _defaultThrottleFilePath = "resources/throttle/NMRA throttle.xml"; 048 049 // we store default throttle layout XML elements 050 private Element adressPanelDefXML; 051 private Element controlPanelDefXML; 052 private Element functionPanelDefXML; 053 private Element throttleFrameDefXML; 054 055 private ThrottlesPreferencesWindowKeyboardControls _tpwkc = new ThrottlesPreferencesWindowKeyboardControls(); 056 protected boolean dirty = false; 057 058 private Dimension _winDim = new Dimension(800, 600); 059 private String prefFile; 060 private ArrayList<PropertyChangeListener> listeners; 061 062 public ThrottlesPreferences() { 063 String dirname = FileUtil.getUserFilesPath() + "throttle" + File.separator; 064 FileUtil.createDirectory(dirname); 065 prefFile = dirname + "ThrottlesPreferences.xml"; 066 ThrottlesPrefsXml prefs = new ThrottlesPrefsXml(); 067 File file = new File(prefFile); 068 Element root; 069 try { 070 root = prefs.rootFromFile(file); 071 } catch (java.io.FileNotFoundException e2) { 072 log.info("Did not find throttle preferences file. This is normal if you haven't save the preferences before"); 073 root = null; 074 } catch (Exception e) { 075 log.error("Exception while loading throttles preferences", e); 076 root = null; 077 } 078 if (root != null) { 079 load(root.getChild("throttlesPreferences")); 080 } else { // at least preload default throttle layout 081 preloadDefaultThrottleFile(); 082 } 083 } 084 085 public void load(org.jdom2.Element e) { 086 if (e == null) { 087 return; 088 } 089 org.jdom2.Attribute a; 090 org.jdom2.Attribute b; 091 if ((a = e.getAttribute("isUsingExThrottle")) != null) { 092 setUseExThrottle(a.getValue().compareTo("true") == 0); 093 } 094 if ((a = e.getAttribute("isUsingToolBar")) != null) { 095 setUsingToolBar(a.getValue().compareTo("true") == 0); 096 } 097 if ((a = e.getAttribute("isResizingWindow")) != null) { 098 setResizeWindow(a.getValue().compareTo("true") == 0); 099 } 100 if ((a = e.getAttribute("isUsingFunctionIcon")) != null) { 101 setUsingFunctionIcon(a.getValue().compareTo("true") == 0); 102 } 103 if (((a = e.getAttribute("windowDimensionWidth")) != null) && ((b = e.getAttribute("windowDimensionHeight")) != null)) { 104 setWindowDimension(new Dimension(Integer.parseInt(a.getValue()), Integer.parseInt(b.getValue()))); 105 } 106 if ((a = e.getAttribute("isSavingThrottleOnLayoutSave")) != null) { 107 setSaveThrottleOnLayoutSave(a.getValue().compareTo("true") == 0); 108 } 109 if ((a = e.getAttribute("isUsingRosterImage")) != null) { 110 setUseRosterImage(a.getValue().compareTo("true") == 0); 111 } 112 if ((a = e.getAttribute("isEnablingRosterSearch")) != null) { 113 setEnableRosterSearch(a.getValue().compareTo("true") == 0); 114 } 115 if ((a = e.getAttribute("isAutoLoading")) != null) { 116 setAutoLoad(a.getValue().compareTo("true") == 0); 117 } 118 if ((a = e.getAttribute("isHidingUndefinedFunctionButtons")) != null) { 119 setHideUndefinedFuncButt(a.getValue().compareTo("true") == 0); 120 } 121 if ((a = e.getAttribute("isIgnoringThrottlePosition")) != null) { 122 setIgnoreThrottlePosition(a.getValue().compareTo("true") == 0); 123 } 124 if ((a = e.getAttribute("isSilentSteal")) != null) { 125 setSilentSteal(a.getValue().compareTo("true") == 0); 126 } 127 if ((a = e.getAttribute("isSilentShare")) != null) { 128 setSilentShare(a.getValue().compareTo("true") == 0); 129 } 130 if ((a = e.getAttribute("isUsingLargeSpeedSlider")) != null) { 131 setUseLargeSpeedSlider(a.getValue().compareTo("true") == 0); 132 } 133 if ((a = e.getAttribute("isHidingSpeedStepSelector")) != null) { 134 setHideSpeedStepSelector(a.getValue().compareTo("true") == 0); 135 } 136 if ((a = e.getAttribute("isAddressSelectorShowingAllRosterGroup")) != null) { 137 setAddressSelectorShowingAllRosterGroup(a.getValue().compareTo("true") == 0); 138 } 139 if (e.getChild("throttlesControls") != null) { 140 this._tpwkc.load(e.getChild("throttlesControls")); 141 } 142 if ((a = e.getAttribute("defaultThrottleFilePath")) != null) { 143 setDefaultThrottleFilePath(a.getValue()); 144 } 145 146 this.dirty = false; 147 } 148 149 /** 150 * @return true if preferences need to be saved 151 */ 152 public boolean isDirty() { 153 return dirty; 154 } 155 156 /** 157 * An extension of the abstract XmlFile. No changes made to that class. 158 * 159 */ 160 static class ThrottlesPrefsXml extends XmlFile { 161 } 162 163 public Element store() { 164 org.jdom2.Element e = new org.jdom2.Element("throttlesPreferences"); 165 e.setAttribute("isUsingExThrottle", "" + isUsingExThrottle()); 166 e.setAttribute("isUsingToolBar", "" + isUsingToolBar()); 167 e.setAttribute("isUsingFunctionIcon", "" + isUsingFunctionIcon()); 168 e.setAttribute("isResizingWindow", "" + isResizingWindow()); 169 e.setAttribute("windowDimensionWidth", "" + (int) getWindowDimension().getWidth()); 170 e.setAttribute("windowDimensionHeight", "" + (int) getWindowDimension().getHeight()); 171 e.setAttribute("isSavingThrottleOnLayoutSave", "" + isSavingThrottleOnLayoutSave()); 172 e.setAttribute("isUsingRosterImage", "" + isUsingRosterImage()); 173 e.setAttribute("isEnablingRosterSearch", "" + isEnablingRosterSearch()); 174 e.setAttribute("isAutoLoading", "" + isAutoLoading()); 175 e.setAttribute("isHidingUndefinedFunctionButtons", "" + isHidingUndefinedFuncButt()); 176 e.setAttribute("isIgnoringThrottlePosition", "" + isIgnoringThrottlePosition()); 177 e.setAttribute("isSilentSteal", "" + isSilentSteal()); 178 e.setAttribute("isSilentShare", "" + isSilentShare()); 179 e.setAttribute("isHidingSpeedStepSelector", "" + isHidingSpeedStepSelector()); 180 e.setAttribute("isUsingLargeSpeedSlider", "" + isUsingLargeSpeedSlider()); 181 e.setAttribute("defaultThrottleFilePath", "" + getDefaultThrottleFilePath()); 182 e.setAttribute("isAddressSelectorShowingAllRosterGroup", "" + isAddressSelectorShowingAllRosterGroup()); 183 java.util.ArrayList<Element> children = new java.util.ArrayList<>(1); 184 children.add(this._tpwkc.store()); 185 e.setContent(children); 186 return e; 187 } 188 189 public void set(ThrottlesPreferences tp) { 190 setWindowDimension(tp.getWindowDimension()); 191 setUseExThrottle(tp.isUsingExThrottle()); 192 setUsingToolBar(tp.isUsingToolBar()); 193 setUsingFunctionIcon(tp.isUsingFunctionIcon()); 194 setResizeWindow(tp.isResizingWindow()); 195 setSaveThrottleOnLayoutSave(tp.isSavingThrottleOnLayoutSave()); 196 setUseRosterImage(tp.isUsingRosterImage()); 197 setEnableRosterSearch(tp.isEnablingRosterSearch()); 198 setAutoLoad(tp.isAutoLoading()); 199 setHideUndefinedFuncButt(tp.isHidingUndefinedFuncButt()); 200 setIgnoreThrottlePosition(tp.isIgnoringThrottlePosition()); 201 setSilentSteal(tp.isSilentSteal()); 202 setSilentShare(tp.isSilentShare()); 203 setUseLargeSpeedSlider(tp.isUsingLargeSpeedSlider()); 204 setThrottlesKeyboardControls(tp.getThrottlesKeyboardControls()); 205 setDefaultThrottleFilePath(tp.getDefaultThrottleFilePath()); 206 setHideSpeedStepSelector(tp.isHidingSpeedStepSelector()); 207 setAddressSelectorShowingAllRosterGroup(tp.isAddressSelectorShowingAllRosterGroup()); 208 209 if (listeners != null) { 210 for (int i = 0; i < listeners.size(); i++) { 211 PropertyChangeListener l = listeners.get(i); 212 PropertyChangeEvent e = new PropertyChangeEvent(this, prefPopertyName, null, this); 213 l.propertyChange(e); 214 } 215 } 216 } 217 218 public void save() { 219 if (prefFile == null) { 220 return; 221 } 222 XmlFile xf = new XmlFile() { 223 }; // odd syntax is due to XmlFile being abstract 224 xf.makeBackupFile(prefFile); 225 File file = new File(prefFile); 226 try { 227 //The file does not exist, create it before writing 228 File parentDir = file.getParentFile(); 229 if (!parentDir.exists()) { 230 if (!parentDir.mkdir()) // make directory, check result 231 { 232 log.error("failed to make parent directory"); 233 } 234 } 235 if (!file.createNewFile()) // create file, check result 236 { 237 log.error("createNewFile failed"); 238 } 239 } catch (Exception exp) { 240 log.error("Exception while writing the new throttles preferences file, may not be complete", exp); 241 } 242 243 try { 244 Element root = new Element("throttles-preferences"); 245 Document doc = XmlFile.newDocument(root, XmlFile.getDefaultDtdLocation() + "throttles-preferences.dtd"); 246 // add XSLT processing instruction 247 // <?xml-stylesheet type="text/xsl" href="XSLT/throttle.xsl"?> 248/*TODO (write XSLT processing instruction) java.util.Map<String,String> m = new java.util.HashMap<String,String>(); 249 m.put("type", "text/xsl"); 250 m.put("href", jmri.jmrit.XmlFile.xsltLocation+"throttles-preferences.xsl"); 251 ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m); 252 doc.addContent(0,p);*/ 253 root.setContent(store()); 254 xf.writeXML(file, doc); 255 } catch (java.io.IOException ex) { 256 log.warn("Exception in storing throttles preferences xml", ex); 257 } 258 this.dirty = false; 259 } 260 261 public Dimension getWindowDimension() { 262 return _winDim; 263 } 264 265 public void setWindowDimension(Dimension d) { 266 _winDim = d; 267 this.dirty = true; 268 } 269 270 public boolean isUsingExThrottle() { 271 return _useExThrottle; 272 } 273 274 public void setUseExThrottle(boolean exThrottle) { 275 _useExThrottle = exThrottle; 276 this.dirty = true; 277 } 278 279 public boolean isUsingToolBar() { 280 return _useToolBar; 281 } 282 283 public void setUsingToolBar(boolean win4all) { 284 _useToolBar = win4all; 285 this.dirty = true; 286 } 287 288 /** 289 * Check if function icons are in use. 290 * 291 * @return user preference to use function icons. 292 */ 293 public boolean isUsingFunctionIcon() { 294 return _useFunctionIcon; 295 } 296 297 public void setUsingFunctionIcon(boolean useFunctionIcon) { 298 _useFunctionIcon = useFunctionIcon; 299 this.dirty = true; 300 } 301 302 public boolean isResizingWindow() { 303 return _resizeWinImg; 304 } 305 306 public void setResizeWindow(boolean winImg) { 307 _resizeWinImg = winImg; 308 this.dirty = true; 309 } 310 311 public boolean isUsingRosterImage() { 312 return _useRosterImage; 313 } 314 315 public void setUseRosterImage(boolean rosterImage) { 316 _useRosterImage = rosterImage; 317 this.dirty = true; 318 } 319 320 public boolean isEnablingRosterSearch() { 321 return _enableRosterSearch; 322 } 323 324 public void setEnableRosterSearch(boolean b) { 325 _enableRosterSearch = b; 326 this.dirty = true; 327 } 328 329 public void setAutoLoad(boolean b) { 330 _enableAutoLoad = b; 331 this.dirty = true; 332 } 333 334 public boolean isAutoLoading() { 335 return _enableAutoLoad; 336 } 337 338 public void setHideUndefinedFuncButt(boolean b) { 339 _hideUndefinedFunButton = b; 340 this.dirty = true; 341 } 342 343 public boolean isHidingUndefinedFuncButt() { 344 return _hideUndefinedFunButton; 345 } 346 347 public void setIgnoreThrottlePosition(boolean b) { 348 _ignoreThrottlePosition = b; 349 this.dirty = true; 350 } 351 352 public boolean isIgnoringThrottlePosition() { 353 return _ignoreThrottlePosition; 354 } 355 356 public void setSaveThrottleOnLayoutSave(boolean b) { 357 _saveThrottleOnLayoutSave = b; 358 this.dirty = true; 359 } 360 361 public boolean isSavingThrottleOnLayoutSave() { 362 return _saveThrottleOnLayoutSave; 363 } 364 365 public boolean isSilentSteal() { 366 return _isSilentSteal; 367 } 368 369 public boolean isSilentShare() { 370 return _isSilentShare; 371 } 372 373 public void setSilentSteal(boolean b) { 374 _isSilentSteal = b; 375 this.dirty = true; 376 } 377 378 public void setSilentShare(boolean b) { 379 _isSilentShare = b; 380 this.dirty = true; 381 } 382 383 384 public void setUseLargeSpeedSlider(boolean b) { 385 _useLargeSpeedSlider = b; 386 this.dirty = true; 387 } 388 389 public boolean isUsingLargeSpeedSlider() { 390 return _useLargeSpeedSlider; 391 } 392 393 public void setDefaultThrottleFilePath(String p) { 394 _defaultThrottleFilePath = p; 395 this.dirty = true; 396 preloadDefaultThrottleFile(); 397 } 398 399 public String getDefaultThrottleFilePath() { 400 return _defaultThrottleFilePath; 401 } 402 403 public boolean isHidingSpeedStepSelector() { 404 return _hideSpeedStepSelector; 405 } 406 407 public void setHideSpeedStepSelector(boolean b) { 408 _hideSpeedStepSelector = b; 409 this.dirty = true; 410 } 411 412 public boolean isAddressSelectorShowingAllRosterGroup() { 413 return _isAddressSelectorShowingAllRosterGroup; 414 } 415 416 public void setAddressSelectorShowingAllRosterGroup(boolean b) { 417 _isAddressSelectorShowingAllRosterGroup = b; 418 this.dirty = true; 419 } 420 421 /** 422 * @return the throttles keyboard controls preferences 423 */ 424 public ThrottlesPreferencesWindowKeyboardControls getThrottlesKeyboardControls() { 425 return _tpwkc; 426 } 427 428 /** 429 * Set the throttles keyboard controls preferences 430 * @param tpwkc the new keyboard preferences 431 */ 432 public void setThrottlesKeyboardControls(ThrottlesPreferencesWindowKeyboardControls tpwkc) { 433 _tpwkc = tpwkc; 434 } 435 436 /** 437 * Get the default throttle address panel XML layout 438 * 439 * @return the default address panel XML layout, null if none 440 */ 441 public Element getAdressPanelDefaultXML() { 442 return adressPanelDefXML; 443 } 444 445 /** 446 * Get the default throttle control panel XML layout 447 * 448 * @return the default control panel XML layout, null if none 449 */ 450 public Element getControlPanelDefaultXML() { 451 return controlPanelDefXML; 452 } 453 454 /** 455 * Get the default throttle function panel XML layout 456 * 457 * @return the default function panel XML layout, null if none 458 */ 459 public Element getFunctionPanelDefaultXML() { 460 return functionPanelDefXML; 461 } 462 463 public Element getThrottleFrameDefaultXML() { 464 return throttleFrameDefXML; 465 } 466 467 /** 468 * Preload the default throttle layout 469 * 470 */ 471 private void preloadDefaultThrottleFile() { 472 adressPanelDefXML = null; 473 controlPanelDefXML = null; 474 functionPanelDefXML = null; 475 throttleFrameDefXML = null; 476 477 if (getDefaultThrottleFilePath() == null) { 478 return; 479 } 480 481 XmlFile xf = new XmlFile() {}; // odd syntax is due to XmlFile being abstract 482 xf.setValidate(XmlFile.Validate.CheckDtdThenSchema); 483 File f = new File(getDefaultThrottleFilePath()); 484 Element root; 485 try { 486 root = xf.rootFromFile(f); 487 } catch ( JDOMException | IOException e) { 488 log.debug("Exception while preloading default throttle file", e); 489 return; 490 } 491 throttleFrameDefXML = root.getChild("ThrottleFrame"); 492 if (throttleFrameDefXML == null) { 493 return; 494 } 495 adressPanelDefXML = throttleFrameDefXML.getChild("AddressPanel"); 496 controlPanelDefXML = throttleFrameDefXML.getChild("ControlPanel"); 497 functionPanelDefXML = throttleFrameDefXML.getChild("FunctionPanel"); 498 } 499 500 /** 501 * Add a PropertyChangeListener. 502 * AddressListeners are notified when the throtlle preferences are updated 503 * @param l listener to add. 504 * 505 */ 506 public void addPropertyChangeListener(PropertyChangeListener l) { 507 if (listeners == null) { 508 listeners = new ArrayList<>(2); 509 } 510 if (!listeners.contains(l)) { 511 listeners.add(l); 512 } 513 } 514 515 /** 516 * Remove a PropertyChangeListener. 517 * @param l listener to remove. 518 */ 519 public void removePropertyChangeListener(PropertyChangeListener l) { 520 if (listeners == null) { 521 return; 522 } 523 listeners.remove(l); 524 } 525 526 private static final Logger log = LoggerFactory.getLogger(ThrottlesPreferences.class); 527}