001package jmri.jmrit.throttle.implementation; 002 003import java.io.File; 004import java.io.FileNotFoundException; 005import java.io.IOException; 006import java.util.ArrayList; 007import javax.swing.*; 008import jmri.DccLocoAddress; 009import jmri.DccThrottle; 010import jmri.InstanceManager; 011import jmri.LocoAddress; 012import jmri.ThrottleManager; 013import jmri.configurexml.StoreXmlConfigAction; 014import jmri.jmrit.XmlFile; 015import jmri.jmrit.roster.RosterEntry; 016import jmri.jmrit.throttle.ThrottleFrameManager; 017import jmri.jmrit.throttle.interfaces.AddressListener; 018import jmri.jmrit.throttle.interfaces.ThrottleControllerUI; 019import jmri.jmrit.throttle.panels.AddressPanel; 020import jmri.jmrit.throttle.panels.BackgroundPanel; 021import jmri.jmrit.throttle.panels.ConsistFunctionPanel; 022import jmri.jmrit.throttle.panels.ControlPanel; 023import jmri.jmrit.throttle.panels.FunctionPanel; 024import jmri.jmrit.throttle.panels.SpeedPanel; 025import jmri.jmrit.throttle.panels.LocoIconPanel; 026import jmri.jmrit.throttle.preferences.ThrottlesPreferences; 027import jmri.util.FileUtil; 028import jmri.util.swing.JmriJOptionPane; 029 030import org.jdom2.Document; 031import org.jdom2.Element; 032import org.jdom2.JDOMException; 033 034/** 035 * 036 * Inner class of a throttle UI holding most of the logic. 037 * Used by classes actually implementing a throttle view (ThrottleFrame, SimpleThrottlePanel, ConsistFunctionPanel) 038 * 039 * <hr> 040 * This file is part of JMRI. 041 * <p> 042 * JMRI is free software; you can redistribute it and/or modify it under the 043 * terms of version 2 of the GNU General Public License as published by the Free 044 * Software Foundation. See the "COPYING" file for a copy of this license. 045 * <p> 046 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 047 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 048 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 049 * 050 * @author Glen Oberhauser 051 * @author Andrew Berridge Copyright 2010 052 * @author Lionel Jeanson 2026 053 * 054 */ 055 056public class ThrottleUICore implements AddressListener { 057 058 private DccThrottle throttle; 059 private final ThrottleManager throttleManager; 060 private final ThrottleFrameManager throttleFrameManager = InstanceManager.getDefault(ThrottleFrameManager.class); 061 private final ThrottleControllerUI myThrottleController; 062 private final boolean withPopupMenu; // should panel show the contextual menu that enable customization 063 064 private ControlPanel controlPanel; 065 private FunctionPanel functionPanel; 066 private AddressPanel addressPanel; 067 private BackgroundPanel backgroundPanel; 068 private SpeedPanel speedPanel; 069 private LocoIconPanel locoIconPanel; 070 private ConsistFunctionPanel consistFunctionsPanel; 071 072 private String lastUsedSaveFile = null; 073 074 private static final String DEFAULT_THROTTLE_FILENAME = "JMRI_ThrottlePreference.xml"; 075 076 public static String getDefaultThrottleFolder() { 077 return FileUtil.getUserFilesPath() + "throttle" + File.separator; 078 } 079 080 public static String getDefaultThrottleFilename() { 081 return getDefaultThrottleFolder() + DEFAULT_THROTTLE_FILENAME; 082 } 083 084 public ThrottleUICore(ThrottleManager tm, ThrottleControllerUI tc, boolean withPopupMenu) { 085 super(); 086 throttleManager = tm; 087 myThrottleController = tc; 088 this.withPopupMenu = withPopupMenu; 089 initGUI(); 090 } 091 092 public ThrottleUICore(ThrottleManager tm, ThrottleControllerUI tc) { 093 this(tm,tc,true); 094 } 095 096 public AddressPanel getAddressPanel() { 097 return addressPanel; 098 } 099 100 public DccThrottle getThrottle() { 101 return getAddressPanel().getThrottle(); 102 } 103 104 public DccThrottle getFunctionThrottle() { 105 if (getAddressPanel().getConsistAddress() == null) { 106 return getThrottle(); 107 } 108 return getConsistFunctionsPanel().getFunctionThrottle(); 109 } 110 111 public RosterEntry getRosterEntry() { 112 return addressPanel.getRosterEntry(); 113 } 114 115 public RosterEntry getFunctionRosterEntry() { 116 if (getAddressPanel().getConsistAddress() == null) { 117 return getRosterEntry(); 118 } 119 return getConsistFunctionsPanel().getFunctionRosterEntry(); 120 } 121 122 public ControlPanel getControlPanel() { 123 if (controlPanel == null) { // init only when requested 124 controlPanel = new ControlPanel(throttleManager, withPopupMenu); 125 controlPanel.setAddressPanel(addressPanel); 126 } 127 return controlPanel; 128 } 129 130 public FunctionPanel getFunctionPanel() { 131 if (functionPanel == null) { // init only when requested 132 functionPanel = new FunctionPanel(withPopupMenu); 133 functionPanel.setAddressPanel(addressPanel); 134 } 135 return functionPanel; 136 } 137 138 public SpeedPanel getSpeedPanel() { 139 if (speedPanel == null) { // init only when requested 140 speedPanel = new SpeedPanel(); 141 speedPanel.setAddressPanel(addressPanel); 142 } 143 return speedPanel; 144 } 145 146 public ConsistFunctionPanel getConsistFunctionsPanel() { 147 if (consistFunctionsPanel == null) { // init only when requested 148 consistFunctionsPanel = new ConsistFunctionPanel(throttleManager); 149 consistFunctionsPanel.setAddressPanel(addressPanel); 150 } 151 return consistFunctionsPanel; 152 } 153 154 public BackgroundPanel getBackgroundPanel() { 155 if (backgroundPanel == null) { // init only when requested 156 backgroundPanel = new BackgroundPanel(); 157 backgroundPanel.setAddressPanel(addressPanel); 158 } 159 return backgroundPanel; 160 } 161 162 public LocoIconPanel getLocoIconPanel() { 163 if (locoIconPanel == null) { // init only when requested 164 locoIconPanel = new LocoIconPanel(); 165 locoIconPanel.setAddressPanel(addressPanel); 166 } 167 return locoIconPanel; 168 } 169 170 public boolean hasActiveFunction() { 171 if (getAddressPanel().getThrottle() == null) { 172 return false; 173 } 174 for (boolean b : getAddressPanel().getThrottle().getFunctions() ) { 175 if (b) { 176 return true; 177 } 178 } 179 return false; 180 } 181 182 private void initGUI() { 183 // create panels that are actually required for a throttle 184 // some (speed Panel, backgroundPanel) will be created on demand only (see getters) 185 addressPanel = new AddressPanel(throttleManager); 186 addressPanel.setEnabled(true); 187 addressPanel.addAddressListener(this); 188 } 189 190 public void setRosterEntry(RosterEntry re) { 191 getAddressPanel().setRosterEntry(re); 192 } 193 194 public void setAddress(DccLocoAddress la) { 195 getAddressPanel().setCurrentAddress(la); 196 } 197 198 public DccLocoAddress getAddress() { 199 return getAddressPanel().getCurrentAddress(); 200 } 201 202 public void setConsistAddress(DccLocoAddress la) { 203 getAddressPanel().setConsistAddress(la); 204 } 205 206 public void eStop() { 207 DccThrottle throt = getAddressPanel().getThrottle(); 208 if (throt != null) { 209 throt.setSpeedSetting(-1); 210 } 211 } 212 213 /** 214 * Handle my own destruction. 215 * <ol> 216 * <li> dispose of sub windows. 217 * <li> notify my manager of my demise. 218 * </ol> 219 */ 220 public void dispose() { 221 log.debug("Disposing"); 222 addressPanel.removeAddressListener(this); 223 // should the throttle list table stop listening to that throttle? 224 if (throttle!=null && throttleFrameManager.getNumberOfEntriesFor((DccLocoAddress) throttle.getLocoAddress()) == 0 ) { // 0 because this throtte frame window has been removed from the list already, so this is last chance to remove listener 225 throttleManager.removeListener(throttle.getLocoAddress(), throttleFrameManager.getThrottlesListPanel().getTableModel()); 226 throttleFrameManager.getThrottlesListPanel().getTableModel().fireTableDataChanged(); 227 } 228 // check for any special disposing in InternalFrames 229 if (controlPanel!=null) { 230 controlPanel.dispose(); 231 } 232 if (functionPanel!=null) { 233 functionPanel.dispose(); 234 } 235 if (speedPanel!=null) { 236 speedPanel.dispose(); 237 } 238 if (backgroundPanel!=null) { 239 backgroundPanel.dispose(); 240 } 241 if (consistFunctionsPanel!=null) { 242 consistFunctionsPanel.dispose(); 243 } 244 // dispose of this last because it will release and destroy the throttle. 245 addressPanel.dispose(); 246 } 247 248 public void saveRosterChanges() { 249 RosterEntry rosterEntry = addressPanel.getRosterEntry(); 250 if (rosterEntry == null) { 251 JmriJOptionPane.showMessageDialog(this.getAddressPanel(), Bundle.getMessage("ThrottleFrameNoRosterItemMessageDialog"), 252 Bundle.getMessage("ThrottleFrameNoRosterItemTitleDialog"), JmriJOptionPane.ERROR_MESSAGE); 253 return; 254 } 255 if ((!InstanceManager.getDefault(ThrottlesPreferences.class).isSavingThrottleOnLayoutSave()) && (JmriJOptionPane.showConfirmDialog(this.getAddressPanel(), Bundle.getMessage("ThrottleFrameRosterChangeMesageDialog"), 256 Bundle.getMessage("ThrottleFrameRosterChangeTitleDialog"), JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION)) { 257 return; 258 } 259 if (functionPanel!=null) { 260 functionPanel.saveFunctionButtonsToRoster(rosterEntry); 261 } 262 if (controlPanel!=null) { 263 controlPanel.saveToRoster(rosterEntry); 264 } 265 } 266 267 /** 268 * Collect the prefs of this object into the given XML Element array 269 * 270 * @param children the array to fill with the XML Elements for this object. The caller will add these to the parent element as needed. 271 * 272 */ 273 public void getXml(ArrayList<Element> children) { 274 if (controlPanel != null) { 275 children.add(controlPanel.getXml()); 276 } 277 if (functionPanel != null) { 278 children.add(functionPanel.getXml()); 279 } 280 children.add(addressPanel.getXml()); 281 if (speedPanel != null) { 282 children.add(speedPanel.getXml()); 283 } 284 if (locoIconPanel != null) { 285 children.add(locoIconPanel.getXml()); 286 } 287 if (consistFunctionsPanel != null) { 288 children.add(consistFunctionsPanel.getXml()); 289 } 290 } 291 292 /** 293 * Set the preferences based on the XML Element. 294 * <ul> 295 * <li> Window prefs 296 * <li> Frame title 297 * <li> ControlPanel 298 * <li> FunctionPanel 299 * <li> AddressPanel 300 * <li> SpeedPanel 301 * </ul> 302 * 303 * @param e The Element for this object. 304 */ 305 public void setXml(Element e) { 306 if (e == null) { 307 return; 308 } 309 Element child = e.getChild("AddressPanel"); 310 addressPanel.setXml(child); 311 child = e.getChild("ControlPanel"); 312 if (child != null) { 313 getControlPanel().setXml(child); 314 } 315 child = e.getChild("FunctionPanel"); 316 if (child != null) { 317 getFunctionPanel().setXml(child); 318 } 319 child = e.getChild("SpeedPanel"); 320 if (child != null) { 321 getSpeedPanel().setXml(child); 322 } 323 child = e.getChild("LocoIconPanel"); 324 if (child != null) { 325 getLocoIconPanel().setXml(child); 326 } 327 child = e.getChild("ConsistFunctionsPanel"); 328 if (child != null && 329 // SimpleThrottlePanel being used to implement ConsistFunctionsPanel, avoid loading recursively 330 // when using CS consist, xml for consist will be head unit one, that will be reloaded again and agin here 331 (myThrottleController != null) && (myThrottleController.getThrottleControllersContainer() != null)) { 332 getConsistFunctionsPanel().setXml(child); 333 } 334 } 335 336 public void saveThrottleAs(Element throttleElement) { 337 JFileChooser fileChooser = jmri.jmrit.XmlFile.userFileChooser(Bundle.getMessage("PromptXmlFileTypes"), "xml"); 338 fileChooser.setCurrentDirectory(new File(getDefaultThrottleFolder())); 339 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); 340 java.io.File file = StoreXmlConfigAction.getFileName(fileChooser); 341 if (file == null) { 342 return; 343 } 344 saveThrottle(throttleElement, file.getAbsolutePath()); 345 } 346 347 public void saveThrottle(Element throttleElement) { 348 if (getRosterEntry() != null) { 349 saveThrottle(throttleElement, ThrottleUICore.getDefaultThrottleFolder() + getRosterEntry().getId().trim() + ".xml"); 350 } else if (getLastUsedSaveFile() != null) { 351 saveThrottle(throttleElement, getLastUsedSaveFile()); 352 } 353 } 354 355 private void saveThrottle(Element throttleElement, String sfile) { 356 // Save throttle: title / window position 357 // as strongly linked to extended throttles and roster presence, do not save function buttons and background window as they're stored in the roster entry 358 XmlFile xf = new XmlFile() { 359 }; // odd syntax is due to XmlFile being abstract 360 xf.makeBackupFile(sfile); 361 File file = new File(sfile); 362 try { 363 //The file does not exist, create it before writing 364 File parentDir = file.getParentFile(); 365 if (!parentDir.exists()) { 366 if (!parentDir.mkdir()) { // make directory and check result 367 log.error("could not make parent directory"); 368 } 369 } 370 if (!file.createNewFile()) { // create file, check success 371 log.error("createNewFile failed"); 372 } 373 } catch (IOException exp) { 374 log.error("Exception while writing the throttle file, may not be complete: {}", exp.getMessage()); 375 } 376 377 try { 378 Element root = new Element("throttle-config"); 379 root.setAttribute("noNamespaceSchemaLocation", // NOI18N 380 "http://jmri.org/xml/schema/throttle-config.xsd", // NOI18N 381 org.jdom2.Namespace.getNamespace("xsi", 382 "http://www.w3.org/2001/XMLSchema-instance")); // NOI18N 383 Document doc = new Document(root); 384 385 // add XSLT processing instruction 386 // <?xml-stylesheet type="text/xsl" href="XSLT/throttle.xsl"?> 387 java.util.Map<String,String> m = new java.util.HashMap<String, String>(); 388 m.put("type", "text/xsl"); 389 m.put("href", jmri.jmrit.XmlFile.xsltLocation + "throttle-config.xsl"); 390 org.jdom2.ProcessingInstruction p = new org.jdom2.ProcessingInstruction("xml-stylesheet", m); 391 doc.addContent(0,p); 392 393 // don't save the loco address or consist address 394 // throttleElement.getChild("AddressPanel").removeChild("locoaddress"); 395 // throttleElement.getChild("AddressPanel").removeChild("locoaddress"); 396 if ((getRosterEntry() != null) && 397 (ThrottleUICore.getDefaultThrottleFolder() + getRosterEntry().getId().trim() + ".xml").compareTo(sfile) == 0) // don't save function buttons labels, they're in roster entry 398 { 399 throttleElement.getChild("FunctionPanel").removeChildren("FunctionButton"); 400 saveRosterChanges(); 401 } 402 403 root.setContent(throttleElement); 404 xf.writeXML(file, doc); 405 setLastUsedSaveFile(sfile); 406 } catch (IOException ex) { 407 log.warn("Exception while storing throttle xml: {}", ex.getMessage()); 408 } 409 } 410 411 public Element loadThrottle(String sfile) throws IOException, NullPointerException, JDOMException, FileNotFoundException { 412 log.debug("Loading throttle file : {}", sfile); 413 if (sfile == null) { 414 return null; 415 } 416 417 XmlFile xf = new XmlFile() {}; // odd syntax is due to XmlFile being abstract 418 xf.setValidate(XmlFile.Validate.CheckDtdThenSchema); 419 File f = new File(sfile); 420 Element root = xf.rootFromFile(f); 421 Element conf = root.getChild("ThrottleFrame"); 422 // File looks ok 423 setLastUsedSaveFile(sfile); 424 // and finally load all preferences 425 setXml(conf); 426 // and return it to be used by caller if needed 427 return conf; 428 } 429 430 private boolean isLoadingDefault = false; 431 432 public void loadDefaultThrottle() { 433 if (isLoadingDefault) { // avoid looping on this method 434 return; 435 } 436 isLoadingDefault = true; 437 String dtf = InstanceManager.getDefault(ThrottlesPreferences.class).getDefaultThrottleFilePath(); 438 if (dtf == null || dtf.isEmpty()) { 439 return; 440 } 441 log.debug("Loading default throttle file : {}", dtf); 442 if (myThrottleController!=null) { 443 myThrottleController.loadThrottleFile(dtf); 444 } 445 setLastUsedSaveFile(null); 446 isLoadingDefault = false; 447 } 448 449 @Override 450 public void notifyAddressChosen(LocoAddress l) { 451 } 452 453 @Override 454 public void notifyRosterEntrySelected(RosterEntry re) { 455 } 456 457 @Override 458 public void notifyAddressReleased(LocoAddress la) { 459 if (throttle == null) { 460 log.debug("notifyAddressReleased() throttle already null, called for loc {}",la); 461 return; 462 } 463 if (throttleFrameManager.getNumberOfEntriesFor((DccLocoAddress) throttle.getLocoAddress()) == 1 ) { 464 throttleManager.removeListener(throttle.getLocoAddress(), throttleFrameManager.getThrottlesListPanel().getTableModel()); 465 } throttle = null; 466 setLastUsedSaveFile(null); 467 if (myThrottleController!=null) { 468 myThrottleController.updateFrameTitle(); 469 myThrottleController.updateGUI(); 470 } 471 throttleFrameManager.getThrottlesListPanel().getTableModel().fireTableStructureChanged(); 472 } 473 474 @Override 475 public void notifyAddressThrottleFound(DccThrottle t) { 476 if (throttle != null) { 477 log.debug("notifyAddressThrottleFound() throttle non null, called for loc {}",t.getLocoAddress()); 478 return; 479 } 480 throttle = t; 481 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) 482 && (InstanceManager.getDefault(ThrottlesPreferences.class).isAutoLoading()) && (addressPanel != null)) { 483 if ((addressPanel.getRosterEntry() != null) 484 && ((getLastUsedSaveFile() == null) || (getLastUsedSaveFile().compareTo(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml") != 0))) { 485 if (myThrottleController!=null) { 486 myThrottleController.loadThrottleFile(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 487 } 488 setLastUsedSaveFile(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 489 } else if ((addressPanel.getRosterEntry() == null) 490 && ((getLastUsedSaveFile() == null) || (getLastUsedSaveFile().compareTo(getDefaultThrottleFolder() + addressPanel.getCurrentAddress()+ ".xml") != 0))) { 491 if (myThrottleController!=null) { 492 myThrottleController.loadThrottleFile(getDefaultThrottleFolder() + throttle.getLocoAddress().getNumber() + ".xml"); 493 } 494 setLastUsedSaveFile(getDefaultThrottleFolder() + throttle.getLocoAddress().getNumber() + ".xml"); 495 } 496 } else { 497 if ((addressPanel != null) && (addressPanel.getRosterEntry() == null)) { // no known roster entry 498 loadDefaultThrottle(); 499 } 500 } 501 if (myThrottleController!=null) { 502 myThrottleController.updateFrameTitle(); 503 myThrottleController.updateGUI(); 504 } 505 throttleFrameManager.getThrottlesListPanel().getTableModel().fireTableDataChanged(); 506 } 507 508 @Override 509 public void notifyConsistAddressChosen(LocoAddress l) { 510 notifyAddressChosen(l); 511 } 512 513 @Override 514 public void notifyConsistAddressReleased(LocoAddress la) { 515 notifyAddressReleased(la); 516 } 517 518 @Override 519 public void notifyConsistAddressThrottleFound(DccThrottle throttle) { 520 notifyAddressThrottleFound(throttle); 521 } 522 523 public String getLastUsedSaveFile() { 524 return lastUsedSaveFile; 525 } 526 527 public void setLastUsedSaveFile(String lusf) { 528 lastUsedSaveFile = lusf; 529 if (myThrottleController!=null) { 530 myThrottleController.updateGUI(); 531 } 532 } 533 534 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ThrottleUICore.class); 535}