001package jmri.jmrit.throttle; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.Component; 006import java.awt.Container; 007import java.awt.Dimension; 008import java.awt.Graphics; 009import java.awt.Point; 010import java.awt.Rectangle; 011import java.awt.event.ComponentEvent; 012import java.awt.event.ComponentListener; 013import java.awt.event.ContainerEvent; 014import java.awt.event.ContainerListener; 015import java.beans.PropertyVetoException; 016import java.io.File; 017import java.io.FileNotFoundException; 018import java.io.IOException; 019import java.net.URI; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023 024import javax.swing.*; 025import javax.swing.event.InternalFrameAdapter; 026import javax.swing.event.InternalFrameEvent; 027 028import jmri.DccLocoAddress; 029import jmri.DccThrottle; 030import jmri.InstanceManager; 031import jmri.LocoAddress; 032import jmri.ThrottleManager; 033import jmri.configurexml.LoadXmlConfigAction; 034import jmri.configurexml.StoreXmlConfigAction; 035import jmri.jmrit.XmlFile; 036import jmri.jmrit.jython.Jynstrument; 037import jmri.jmrit.jython.JynstrumentFactory; 038import jmri.jmrit.roster.RosterEntry; 039import jmri.util.FileUtil; 040import jmri.util.iharder.dnd.URIDrop; 041import jmri.util.swing.JmriJOptionPane; 042import jmri.util.swing.TransparencyUtils; 043 044import org.jdom2.Document; 045import org.jdom2.Element; 046import org.jdom2.JDOMException; 047 048/** 049 * Should be named ThrottlePanel but was already existing with that name and 050 * don't want to break dependencies (particularly in Jython code) 051 * 052 * @author Glen Oberhauser 053 * @author Andrew Berridge Copyright 2010 054 */ 055public class ThrottleFrame extends JDesktopPane implements ComponentListener, AddressListener, ThrottleControllerUI { 056 057 private DccThrottle throttle; 058 private final ThrottleManager throttleManager; 059 private final ThrottleFrameManager throttleFrameManager = InstanceManager.getDefault(ThrottleFrameManager.class); 060 061 private final Integer BACKPANEL_LAYER = Integer.MIN_VALUE; 062 private final Integer PANEL_LAYER_FRAME = 1; 063 private final Integer PANEL_LAYER_PANEL = 2; 064 065 private static final int ADDRESS_PANEL_INDEX = 0; 066 private static final int CONTROL_PANEL_INDEX = 1; 067 private static final int FUNCTION_PANEL_INDEX = 2; 068 private static final int SPEED_DISPLAY_INDEX = 3; 069 private static final int NUM_FRAMES = 4; 070 071 private JInternalFrame[] frameList; 072 private int activeFrame; 073 074 private ThrottleWindow throttleWindow; 075 076 private ControlPanel controlPanel; 077 private FunctionPanel functionPanel; 078 private AddressPanel addressPanel; 079 private BackgroundPanel backgroundPanel; 080 private FrameListener frameListener; 081 private SpeedPanel speedPanel; 082 083 private String title; 084 private String lastUsedSaveFile = null; 085 086 private boolean isEditMode = true; 087 private boolean willSwitch = false; 088 private boolean isLoadingDefault = false; 089 090 private static final String DEFAULT_THROTTLE_FILENAME = "JMRI_ThrottlePreference.xml"; 091 092 public static String getDefaultThrottleFolder() { 093 return FileUtil.getUserFilesPath() + "throttle" + File.separator; 094 } 095 096 public static String getDefaultThrottleFilename() { 097 return getDefaultThrottleFolder() + DEFAULT_THROTTLE_FILENAME; 098 } 099 100 public ThrottleFrame(ThrottleWindow tw) { 101 this(tw, InstanceManager.getDefault(ThrottleManager.class)); 102 } 103 104 public ThrottleFrame(ThrottleWindow tw, ThrottleManager tm) { 105 super(); 106 throttleWindow = tw; 107 throttleManager = tm; 108 initGUI(); 109 applyPreferences(); 110 throttleFrameManager.getThrottlesListPanel().getTableModel().fireTableStructureChanged(); 111 } 112 113 @Override 114 public ThrottleWindow getThrottleControllersContainer() { 115 return throttleWindow; 116 } 117 118 @Override 119 public void setThrottleControllersContainer(ThrottleControllersUIContainer tw) { 120 throttleWindow = (ThrottleWindow) tw; 121 } 122 123 public ControlPanel getControlPanel() { 124 return controlPanel; 125 } 126 127 public FunctionPanel getFunctionPanel() { 128 return functionPanel; 129 } 130 131 public AddressPanel getAddressPanel() { 132 return addressPanel; 133 } 134 135 public RosterEntry getRosterEntry() { 136 return addressPanel.getRosterEntry(); 137 } 138 139 public void toFront() { 140 if (throttleWindow == null) { 141 return; 142 } 143 throttleWindow.toFront(title); 144 } 145 146 public SpeedPanel getSpeedPanel() { 147 return speedPanel; 148 } 149 150 /** 151 * Sets the location of a throttle frame on the screen according to x and y 152 * coordinates 153 * 154 * @see java.awt.Component#setLocation(int, int) 155 */ 156 @Override 157 public void setLocation(int x, int y) { 158 if (throttleWindow == null) { 159 return; 160 } 161 throttleWindow.setLocation(new Point(x, y)); 162 } 163 164 public void setTitle(String txt) { 165 title = txt; 166 } 167 168 public String getTitle() { 169 return title; 170 } 171 172 private void saveThrottle(String sfile) { 173 // Save throttle: title / window position 174 // 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 175 XmlFile xf = new XmlFile() { 176 }; // odd syntax is due to XmlFile being abstract 177 xf.makeBackupFile(sfile); 178 File file = new File(sfile); 179 try { 180 //The file does not exist, create it before writing 181 File parentDir = file.getParentFile(); 182 if (!parentDir.exists()) { 183 if (!parentDir.mkdir()) { // make directory and check result 184 log.error("could not make parent directory"); 185 } 186 } 187 if (!file.createNewFile()) { // create file, check success 188 log.error("createNewFile failed"); 189 } 190 } catch (IOException exp) { 191 log.error("Exception while writing the throttle file, may not be complete: {}", exp.getMessage()); 192 } 193 194 try { 195 Element root = new Element("throttle-config"); 196 root.setAttribute("noNamespaceSchemaLocation", // NOI18N 197 "http://jmri.org/xml/schema/throttle-config.xsd", // NOI18N 198 org.jdom2.Namespace.getNamespace("xsi", 199 "http://www.w3.org/2001/XMLSchema-instance")); // NOI18N 200 Document doc = new Document(root); 201 202 // add XSLT processing instruction 203 // <?xml-stylesheet type="text/xsl" href="XSLT/throttle.xsl"?> 204 java.util.Map<String,String> m = new java.util.HashMap<String, String>(); 205 m.put("type", "text/xsl"); 206 m.put("href", jmri.jmrit.XmlFile.xsltLocation + "throttle-config.xsl"); 207 org.jdom2.ProcessingInstruction p = new org.jdom2.ProcessingInstruction("xml-stylesheet", m); 208 doc.addContent(0,p); 209 210 Element throttleElement = getXml(); 211 // don't save the loco address or consist address 212 // throttleElement.getChild("AddressPanel").removeChild("locoaddress"); 213 // throttleElement.getChild("AddressPanel").removeChild("locoaddress"); 214 if ((this.getRosterEntry() != null) && 215 (getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml").compareTo(sfile) == 0) // don't save function buttons labels, they're in roster entry 216 { 217 throttleElement.getChild("FunctionPanel").removeChildren("FunctionButton"); 218 saveRosterChanges(); 219 } 220 221 root.setContent(throttleElement); 222 xf.writeXML(file, doc); 223 setLastUsedSaveFile(sfile); 224 } catch (IOException ex) { 225 log.warn("Exception while storing throttle xml: {}", ex.getMessage()); 226 } 227 } 228 229 private void loadDefaultThrottle() { 230 if (isLoadingDefault) { // avoid looping on this method 231 return; 232 } 233 isLoadingDefault = true; 234 String dtf = InstanceManager.getDefault(ThrottlesPreferences.class).getDefaultThrottleFilePath(); 235 if (dtf == null || dtf.isEmpty()) { 236 return; 237 } 238 log.debug("Loading default throttle file : {}", dtf); 239 loadThrottle(dtf); 240 setLastUsedSaveFile(null); 241 isLoadingDefault = false; 242 } 243 244 public void loadThrottle() { 245 JFileChooser fileChooser = jmri.jmrit.XmlFile.userFileChooser(Bundle.getMessage("PromptXmlFileTypes"), "xml"); 246 fileChooser.setCurrentDirectory(new File(getDefaultThrottleFolder())); 247 fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); 248 java.io.File file = LoadXmlConfigAction.getFile(fileChooser, this); 249 if (file == null) { 250 return ; 251 } 252 loadThrottle(file.getAbsolutePath()); 253 } 254 255 public void loadThrottle(String sfile) { 256 if (sfile == null) { 257 loadThrottle(); 258 return; 259 } 260 log.debug("Loading throttle file : {}", sfile); 261 boolean switchAfter = false; 262 if (!isEditMode) { 263 setEditMode(true); 264 switchAfter = true; 265 } 266 267 try { 268 XmlFile xf = new XmlFile() { 269 }; // odd syntax is due to XmlFile being abstract 270 xf.setValidate(XmlFile.Validate.CheckDtdThenSchema); 271 File f = new File(sfile); 272 Element root = xf.rootFromFile(f); 273 Element conf = root.getChild("ThrottleFrame"); 274 // File looks ok 275 setLastUsedSaveFile(sfile); 276 // close all existing Jynstruments 277 Component[] cmps = getComponents(); 278 for (Component cmp : cmps) { 279 try { 280 if (cmp instanceof JInternalFrame) { 281 JInternalFrame jyf = (JInternalFrame) cmp; 282 Component[] cmps2 = jyf.getContentPane().getComponents(); 283 for (Component cmp2 : cmps2) { 284 if (cmp2 instanceof Jynstrument) { 285 ((Jynstrument) cmp2).exit(); 286 jyf.dispose(); 287 } 288 } 289 } 290 } catch (Exception ex) { 291 log.debug("Got exception (no panic) {}", ex.getMessage()); 292 } 293 } 294 // and finally load all preferences 295 setXml(conf); 296 } catch (FileNotFoundException ex) { 297 // Don't show error dialog if file is not found 298 log.debug("Loading throttle exception: {}", ex.getMessage()); 299 log.debug("Tried loading throttle file \"{}\" , reverting to default, if any", sfile); 300 loadDefaultThrottle(); // revert to loading default one 301 } catch (NullPointerException | IOException | JDOMException ex) { 302 log.debug("Loading throttle exception: {}", ex.getMessage()); 303 log.debug("Tried loading throttle file \"{}\" , reverting to default, if any", sfile); 304 jmri.configurexml.ConfigXmlManager.creationErrorEncountered( 305 null, "parsing file " + sfile, 306 "Parse error", null, null, ex); 307 loadDefaultThrottle(); // revert to loading default one 308 } 309// checkPosition(); 310 if (switchAfter) { 311 setEditMode(false); 312 } 313 } 314 315 /** 316 * Place and initialize the GUI elements. 317 * <ul> 318 * <li> ControlPanel 319 * <li> FunctionPanel 320 * <li> AddressPanel 321 * <li> SpeedPanel 322 * <li> JMenu 323 * </ul> 324 */ 325 private void initGUI() { 326 frameListener = new FrameListener(); 327 328 controlPanel = new ControlPanel(throttleManager); 329 controlPanel.setResizable(true); 330 controlPanel.setClosable(true); 331 controlPanel.setIconifiable(true); 332 controlPanel.setTitle(Bundle.getMessage("ThrottleMenuViewControlPanel")); 333 controlPanel.pack(); 334 controlPanel.setVisible(true); 335 controlPanel.setEnabled(false); 336 controlPanel.addInternalFrameListener(frameListener); 337 338 functionPanel = new FunctionPanel(); 339 functionPanel.setResizable(true); 340 functionPanel.setClosable(true); 341 functionPanel.setIconifiable(true); 342 functionPanel.setTitle(Bundle.getMessage("ThrottleMenuViewFunctionPanel")); 343 344 // assumes button width of 54, height of 30 (set in class FunctionButton) with 345 // horiz and vert gaps of 5 each (set in FunctionPanel class) 346 // with 3 buttons across and 6 rows high 347 int width = 3 * (FunctionButton.getButtonWidth()) + 2 * 3 * 5 + 10; // = 192 348 int height = 8 * (FunctionButton.getButtonHeight()) + 2 * 6 * 5 + 20; // = 240 (but there seems to be another 10 needed for some LAFs) 349 350 functionPanel.setSize(width, height); 351 functionPanel.setLocation(controlPanel.getWidth(), 0); 352 functionPanel.setVisible(true); 353 functionPanel.setEnabled(false); 354 functionPanel.addInternalFrameListener(frameListener); 355 356 speedPanel = new SpeedPanel(); 357 speedPanel.setResizable(true); 358 speedPanel.setVisible(false); 359 speedPanel.setClosable(true); 360 speedPanel.setIconifiable(true); 361 speedPanel.setTitle(Bundle.getMessage("ThrottleMenuViewSpeedPanel")); 362 speedPanel.addInternalFrameListener(frameListener); 363 speedPanel.pack(); 364 365 addressPanel = new AddressPanel(throttleManager); 366 addressPanel.setResizable(true); 367 addressPanel.setClosable(true); 368 addressPanel.setIconifiable(true); 369 addressPanel.setTitle(Bundle.getMessage("ThrottleMenuViewAddressPanel")); 370 addressPanel.pack(); 371 if (addressPanel.getWidth()<functionPanel.getWidth()) { 372 addressPanel.setSize(functionPanel.getWidth(),addressPanel.getHeight()); 373 } 374 addressPanel.setLocation(controlPanel.getWidth(), functionPanel.getHeight()); 375 addressPanel.setVisible(true); 376 addressPanel.addInternalFrameListener(frameListener); 377 functionPanel.setAddressPanel(addressPanel); // so the function panel can get access to the roster 378 controlPanel.setAddressPanel(addressPanel); 379 speedPanel.setAddressPanel(addressPanel); 380 381 if (controlPanel.getHeight() < functionPanel.getHeight() + addressPanel.getHeight()) { 382 controlPanel.setSize(controlPanel.getWidth(), functionPanel.getHeight() + addressPanel.getHeight()); 383 } 384 if (controlPanel.getHeight() > functionPanel.getHeight() + addressPanel.getHeight()) { 385 addressPanel.setSize(addressPanel.getWidth(), controlPanel.getHeight() - functionPanel.getHeight()); 386 } 387 if (functionPanel.getWidth() < addressPanel.getWidth()) { 388 functionPanel.setSize(addressPanel.getWidth(), functionPanel.getHeight()); 389 } 390 391 speedPanel.setSize(addressPanel.getWidth() + controlPanel.getWidth(), addressPanel.getHeight() / 2); 392 speedPanel.setLocation(0, controlPanel.getHeight()); 393 394 addressPanel.addAddressListener(controlPanel); 395 addressPanel.addAddressListener(functionPanel); 396 addressPanel.addAddressListener(speedPanel); 397 addressPanel.addAddressListener(this); 398 399 add(controlPanel, PANEL_LAYER_FRAME); 400 add(functionPanel, PANEL_LAYER_FRAME); 401 add(addressPanel, PANEL_LAYER_FRAME); 402 add(speedPanel, PANEL_LAYER_FRAME); 403 404 backgroundPanel = new BackgroundPanel(); 405 backgroundPanel.setAddressPanel(addressPanel); // reusing same way to do it than existing thing in functionPanel 406 addComponentListener(backgroundPanel); // backgroudPanel warned when desktop resized 407 addressPanel.addAddressListener(backgroundPanel); 408 addressPanel.setBackgroundPanel(backgroundPanel); // so that it's changeable when browsing through rosters 409 add(backgroundPanel, BACKPANEL_LAYER); 410 411 addComponentListener(this); // to force sub windows repositionning 412 413 frameList = new JInternalFrame[NUM_FRAMES]; 414 frameList[ADDRESS_PANEL_INDEX] = addressPanel; 415 frameList[CONTROL_PANEL_INDEX] = controlPanel; 416 frameList[FUNCTION_PANEL_INDEX] = functionPanel; 417 frameList[SPEED_DISPLAY_INDEX] = speedPanel; 418 activeFrame = ADDRESS_PANEL_INDEX; 419 420 setPreferredSize(new Dimension(Math.max(controlPanel.getWidth() + functionPanel.getWidth(), controlPanel.getWidth() + addressPanel.getWidth()), 421 Math.max(addressPanel.getHeight() + functionPanel.getHeight(), controlPanel.getHeight()))); 422 423 // #JYNSTRUMENT# Bellow prepare drag'n drop receptacle: 424 new URIDrop(backgroundPanel, uris -> { 425 if (isEditMode) { 426 for (URI uri : uris ) { 427 ynstrument(new File(uri).getPath()); 428 } 429 } 430 }); 431 432 try { 433 addressPanel.setSelected(true); 434 } catch (PropertyVetoException ex) { 435 log.error("Error selecting InternalFrame: {}", ex.getMessage()); 436 } 437 } 438 439 // #JYNSTRUMENT# here instantiate the Jynstrument, put it in a component, initialize the context and start it 440 public JInternalFrame ynstrument(String path) { 441 if (path == null) { 442 return null; 443 } 444 Jynstrument it = JynstrumentFactory.createInstrument(path, this); // everything is there 445 if (it == null) { 446 log.error("Error while creating Jynstrument {}", path); 447 return null; 448 } 449 setTransparentBackground(it); 450 JInternalFrame newiFrame = new JInternalFrame(it.getClassName()); 451 newiFrame.add(it); 452 newiFrame.addInternalFrameListener(frameListener); 453 newiFrame.setDoubleBuffered(true); 454 newiFrame.setResizable(true); 455 newiFrame.setClosable(true); 456 newiFrame.setIconifiable(true); 457 newiFrame.getContentPane().addContainerListener(new ContainerListener() { 458 @Override 459 public void componentAdded(ContainerEvent e) { 460 } 461 462 @Override 463 public void componentRemoved(ContainerEvent e) { 464 Container c = e.getContainer(); 465 while ((!(c instanceof JInternalFrame)) && (!(c instanceof TranslucentJPanel))) { 466 c = c.getParent(); 467 } 468 c.setVisible(false); 469 remove(c); 470 repaint(); 471 } 472 }); 473 newiFrame.pack(); 474 add(newiFrame, PANEL_LAYER_FRAME); 475 newiFrame.setVisible(true); 476 return newiFrame; 477 } 478 479 // make sure components are inside this frame bounds 480 private void checkPosition(Component comp) { 481 if ((this.getWidth() < 1) || (this.getHeight() < 1)) { 482 return; 483 } 484 485 Rectangle pos = comp.getBounds(); 486 487 if (pos.width > this.getWidth()) { // Component largest than container 488 pos.width = this.getWidth() - 2; 489 pos.x = 1; 490 } 491 if (pos.x + pos.width > this.getWidth()) // Component to large 492 { 493 pos.x = this.getWidth() - pos.width - 1; 494 } 495 if (pos.x < 0) // Component to far on the left 496 { 497 pos.x = 1; 498 } 499 500 if (pos.height > this.getHeight()) { // Component higher than container 501 pos.height = this.getHeight() - 2; 502 pos.y = 1; 503 } 504 if (pos.y + pos.height > this.getHeight()) // Component to low 505 { 506 pos.y = this.getHeight() - pos.height - 1; 507 } 508 if (pos.y < 0) // Component to high 509 { 510 pos.y = 1; 511 } 512 513 comp.setBounds(pos); 514 } 515 516 public void makeAllComponentsInBounds() { 517 Component[] cmps = getComponents(); 518 for (Component cmp : cmps) { 519 checkPosition(cmp); 520 } 521 } 522 523 private HashMap<Container, JInternalFrame> contentPanes; 524 525 public void applyPreferences() { 526 ThrottlesPreferences preferences = InstanceManager.getDefault(ThrottlesPreferences.class); 527 528 backgroundPanel.setVisible( (preferences.isUsingExThrottle()) && (preferences.isUsingRosterImage())); 529 530 controlPanel.applyPreferences(); 531 functionPanel.applyPreferences(); 532 addressPanel.applyPreferences(); 533 backgroundPanel.applyPreferences(); 534 loadDefaultThrottle(); 535 } 536 537 @Override 538 public void setRosterEntry(RosterEntry re) { 539 getAddressPanel().setRosterEntry(re); 540 } 541 542 @Override 543 public void setAddress(int number, boolean isLong) { 544 getAddressPanel().setAddress(number, isLong); 545 } 546 547 @Override 548 public void setAddress(DccLocoAddress la) { 549 getAddressPanel().setCurrentAddress(la); 550 } 551 552 @Override 553 public void eStop() { 554 DccThrottle throt = getAddressPanel().getThrottle(); 555 if (throt != null) { 556 throt.setSpeedSetting(-1); 557 } 558 } 559 560 private static class TranslucentJPanel extends JPanel { 561 562 private final Color TRANS_COL = new Color(100, 100, 100, 100); 563 564 public TranslucentJPanel() { 565 super(); 566 setOpaque(false); 567 } 568 569 @Override 570 public void paintComponent(Graphics g) { 571 g.setColor(TRANS_COL); 572 g.fillRoundRect(0, 0, getSize().width, getSize().height, 10, 10); 573 super.paintComponent(g); 574 } 575 } 576 577 private void playRendering() { 578 Component[] cmps = getComponentsInLayer(PANEL_LAYER_FRAME); 579 contentPanes = new HashMap<>(); 580 for (Component cmp : cmps) { 581 if ((cmp instanceof JInternalFrame) && (cmp.isVisible())) { 582 translude((JInternalFrame)cmp); 583 } 584 } 585 } 586 587 private void translude(JInternalFrame jif) { 588 Dimension cpSize = jif.getContentPane().getSize(); 589 Point cpLoc = jif.getContentPane().getLocationOnScreen(); 590 TranslucentJPanel pane = new TranslucentJPanel(); 591 pane.setLayout(new BorderLayout()); 592 contentPanes.put(pane, jif); 593 pane.add(jif.getContentPane(), BorderLayout.CENTER); 594 TransparencyUtils.setTransparent(pane, true); 595 jif.setContentPane(new JPanel()); 596 jif.setVisible(false); 597 Point loc = new Point(cpLoc.x - this.getLocationOnScreen().x, cpLoc.y - this.getLocationOnScreen().y); 598 add(pane, PANEL_LAYER_PANEL); 599 pane.setLocation(loc); 600 pane.setSize(cpSize); 601 } 602 603 private void editRendering() { 604 Component[] cmps = getComponentsInLayer(PANEL_LAYER_PANEL); 605 for (Component cmp : cmps) { 606 if (cmp instanceof JPanel) { 607 JPanel pane = (JPanel) cmp; 608 JInternalFrame jif = contentPanes.get(pane); 609 jif.setContentPane((Container) pane.getComponent(0)); 610 TransparencyUtils.setTransparent(jif, false); 611 jif.setVisible(true); 612 remove(pane); 613 } 614 } 615 } 616 617 public void setEditMode(boolean mode) { 618 if (mode == isEditMode) 619 return; 620 if (isVisible()) { 621 if (!mode) { 622 playRendering(); 623 } else { 624 editRendering(); 625 } 626 isEditMode = mode; 627 willSwitch = false; 628 } else { 629 willSwitch = true; 630 } 631 throttleWindow.updateGUI(); 632 } 633 634 public boolean getEditMode() { 635 return isEditMode; 636 } 637 638 /** 639 * Handle my own destruction. 640 * <ol> 641 * <li> dispose of sub windows. 642 * <li> notify my manager of my demise. 643 * </ol> 644 */ 645 public void dispose() { 646 log.debug("Disposing {}", getTitle()); 647 URIDrop.remove(backgroundPanel); 648 addressPanel.removeAddressListener(this); 649 // should the throttle list table stop listening to that throttle? 650 if (throttle!=null && throttleFrameManager.getNumberOfEntriesFor((DccLocoAddress) throttle.getLocoAddress()) == 1 ) { 651 throttleManager.removeListener(throttle.getLocoAddress(), throttleFrameManager.getThrottlesListPanel().getTableModel()); 652 throttleFrameManager.getThrottlesListPanel().getTableModel().fireTableDataChanged(); 653 } 654 655 // check for any special disposing in InternalFrames 656 controlPanel.destroy(); 657 functionPanel.destroy(); 658 speedPanel.destroy(); 659 backgroundPanel.destroy(); 660 // dispose of this last because it will release and destroy the throttle. 661 addressPanel.destroy(); 662 } 663 664 public void saveRosterChanges() { 665 RosterEntry rosterEntry = addressPanel.getRosterEntry(); 666 if (rosterEntry == null) { 667 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ThrottleFrameNoRosterItemMessageDialog"), 668 Bundle.getMessage("ThrottleFrameNoRosterItemTitleDialog"), JmriJOptionPane.ERROR_MESSAGE); 669 return; 670 } 671 if ((!InstanceManager.getDefault(ThrottlesPreferences.class).isSavingThrottleOnLayoutSave()) && (JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("ThrottleFrameRosterChangeMesageDialog"), 672 Bundle.getMessage("ThrottleFrameRosterChangeTitleDialog"), JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION)) { 673 return; 674 } 675 functionPanel.saveFunctionButtonsToRoster(rosterEntry); 676 controlPanel.saveToRoster(rosterEntry); 677 } 678 679 /** 680 * An extension of InternalFrameAdapter for listening to the closing of of 681 * this frame's internal frames. 682 * 683 * @author glen 684 */ 685 class FrameListener extends InternalFrameAdapter { 686 687 /** 688 * Listen for the closing of an internal frame and set the "View" menu 689 * appropriately. Then hide the closing frame 690 * 691 * @param e The InternalFrameEvent leading to this action 692 */ 693 @Override 694 public void internalFrameClosing(InternalFrameEvent e) { 695 if (e.getSource() == controlPanel) { 696 throttleWindow.getViewControlPanel().setSelected(false); 697 controlPanel.setVisible(false); 698 } else if (e.getSource() == addressPanel) { 699 throttleWindow.getViewAddressPanel().setSelected(false); 700 addressPanel.setVisible(false); 701 } else if (e.getSource() == functionPanel) { 702 throttleWindow.getViewFunctionPanel().setSelected(false); 703 functionPanel.setVisible(false); 704 } else if (e.getSource() == speedPanel) { 705 throttleWindow.getViewSpeedPanel().setSelected(false); 706 speedPanel.setVisible(false); 707 } else { 708 try { // #JYNSTRUMENT#, Very important, clean the Jynstrument 709 if ((e.getSource() instanceof JInternalFrame)) { 710 Component[] cmps = ((JInternalFrame) e.getSource()).getContentPane().getComponents(); 711 int i = 0; 712 while ((i < cmps.length) && (!(cmps[i] instanceof Jynstrument))) { 713 i++; 714 } 715 if ((i < cmps.length) && (cmps[i] instanceof Jynstrument)) { 716 ((Jynstrument) cmps[i]).exit(); 717 } 718 } 719 } catch (Exception exc) { 720 log.debug("Got exception, can ignore: ", exc); 721 } 722 } 723 } 724 725 /** 726 * Listen for the activation of an internal frame record this property 727 * for correct processing of the frame cycling key. 728 * 729 * @param e The InternalFrameEvent leading to this action 730 */ 731 @Override 732 public void internalFrameActivated(InternalFrameEvent e) { 733 if (e.getSource() == controlPanel) { 734 activeFrame = CONTROL_PANEL_INDEX; 735 } else if (e.getSource() == addressPanel) { 736 activeFrame = ADDRESS_PANEL_INDEX; 737 } else if (e.getSource() == functionPanel) { 738 activeFrame = FUNCTION_PANEL_INDEX; 739 } else if (e.getSource() == functionPanel) { 740 activeFrame = SPEED_DISPLAY_INDEX; 741 } 742 } 743 } 744 745 /** 746 * Collect the prefs of this object into XML Element 747 * <ul> 748 * <li> Window prefs 749 * <li> ControlPanel 750 * <li> FunctionPanel 751 * <li> AddressPanel 752 * <li> SpeedPanel 753 * </ul> 754 * 755 * 756 * @return the XML of this object. 757 */ 758 public Element getXml() { 759 boolean switchAfter = false; 760 if (!isEditMode) { 761 setEditMode(true); 762 switchAfter = true; 763 } 764 765 Element me = new Element("ThrottleFrame"); 766 767 if (((javax.swing.plaf.basic.BasicInternalFrameUI) getControlPanel().getUI()).getNorthPane() != null) { 768 Dimension bDim = ((javax.swing.plaf.basic.BasicInternalFrameUI) getControlPanel().getUI()).getNorthPane().getPreferredSize(); 769 me.setAttribute("border", Integer.toString(bDim.height)); 770 } 771 772 ArrayList<Element> children = new ArrayList<>(1); 773 774// children.add(WindowPreferences.getPreferences(this)); // not required as it is in ThrottleWindow 775 children.add(controlPanel.getXml()); 776 children.add(functionPanel.getXml()); 777 children.add(addressPanel.getXml()); 778 children.add(speedPanel.getXml()); 779 // Save Jynstruments 780 Component[] cmps = getComponents(); 781 for (Component cmp : cmps) { 782 try { 783 if (cmp instanceof JInternalFrame) { 784 Component[] cmps2 = ((JInternalFrame) cmp).getContentPane().getComponents(); 785 int j = 0; 786 while ((j < cmps2.length) && (!(cmps2[j] instanceof Jynstrument))) { 787 j++; 788 } 789 if ((j < cmps2.length) && (cmps2[j] instanceof Jynstrument)) { 790 Jynstrument jyn = (Jynstrument) cmps2[j]; 791 Element elt = new Element("Jynstrument"); 792 elt.setAttribute("JynstrumentFolder", FileUtil.getPortableFilename(jyn.getFolder())); 793 ArrayList<Element> jychildren = new ArrayList<>(1); 794 jychildren.add(WindowPreferences.getPreferences((JInternalFrame) cmp)); 795 Element je = jyn.getXml(); 796 if (je != null) { 797 jychildren.add(je); 798 } 799 elt.setContent(jychildren); 800 children.add(elt); 801 } 802 } 803 } catch (Exception ex) { 804 log.debug("Got exception (no panic) {}", ex.getMessage()); 805 } 806 } 807 me.setContent(children); 808 if (switchAfter) { 809 setEditMode(false); 810 } 811 return me; 812 } 813 814 public Element getXmlFile() { 815 if (getLastUsedSaveFile() == null) { // || (getRosterEntry()==null)) 816 return null; 817 } 818 Element me = new Element("ThrottleFrame"); 819 me.setAttribute("ThrottleXMLFile", FileUtil.getPortableFilename(getLastUsedSaveFile())); 820 return me; 821 } 822 823 /** 824 * Set the preferences based on the XML Element. 825 * <ul> 826 * <li> Window prefs 827 * <li> Frame title 828 * <li> ControlPanel 829 * <li> FunctionPanel 830 * <li> AddressPanel 831 * <li> SpeedPanel 832 * </ul> 833 * 834 * @param e The Element for this object. 835 */ 836 public void setXml(Element e) { 837 if (e == null) { 838 return; 839 } 840 841 String sfile = e.getAttributeValue("ThrottleXMLFile"); 842 if (sfile != null) { 843 loadThrottle(FileUtil.getExternalFilename(sfile)); 844 return; 845 } 846 847 boolean switchAfter = false; 848 if (!isEditMode) { 849 setEditMode(true); 850 switchAfter = true; 851 } 852 853 int bSize = 23; 854 // Get InternalFrame border size 855 if (e.getAttribute("border") != null) { 856 bSize = Integer.parseInt((e.getAttribute("border").getValue())); 857 } 858 Element controlPanelElement = e.getChild("ControlPanel"); 859 controlPanel.setXml(controlPanelElement); 860 if (((javax.swing.plaf.basic.BasicInternalFrameUI) controlPanel.getUI()).getNorthPane() != null) { 861 ((javax.swing.plaf.basic.BasicInternalFrameUI) controlPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 862 } 863 Element functionPanelElement = e.getChild("FunctionPanel"); 864 functionPanel.setXml(functionPanelElement); 865 if (((javax.swing.plaf.basic.BasicInternalFrameUI) functionPanel.getUI()).getNorthPane() != null) { 866 ((javax.swing.plaf.basic.BasicInternalFrameUI) functionPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 867 } 868 Element addressPanelElement = e.getChild("AddressPanel"); 869 addressPanel.setXml(addressPanelElement); 870 if (((javax.swing.plaf.basic.BasicInternalFrameUI) addressPanel.getUI()).getNorthPane() != null) { 871 ((javax.swing.plaf.basic.BasicInternalFrameUI) addressPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 872 } 873 Element speedPanelElement = e.getChild("SpeedPanel"); 874 if (speedPanelElement != null) { // older throttle configs may not have this element 875 speedPanel.setXml(speedPanelElement); 876 if (((javax.swing.plaf.basic.BasicInternalFrameUI) speedPanel.getUI()).getNorthPane() != null) { 877 ((javax.swing.plaf.basic.BasicInternalFrameUI) speedPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 878 } 879 } 880 881 List<Element> jinsts = e.getChildren("Jynstrument"); 882 if ((jinsts != null) && (jinsts.size() > 0)) { 883 for (Element jinst : jinsts) { 884 JInternalFrame jif = ynstrument(FileUtil.getExternalFilename(jinst.getAttributeValue("JynstrumentFolder"))); 885 Element window = jinst.getChild("window"); 886 if (jif != null) { 887 if (window != null) { 888 WindowPreferences.setPreferences(jif, window); 889 } 890 Component[] cmps2 = jif.getContentPane().getComponents(); 891 int j = 0; 892 while ((j < cmps2.length) && (!(cmps2[j] instanceof Jynstrument))) { 893 j++; 894 } 895 if ((j < cmps2.length) && (cmps2[j] instanceof Jynstrument)) { 896 ((Jynstrument) cmps2[j]).setXml(jinst); 897 } 898 899 jif.repaint(); 900 } 901 } 902 } 903 setFrameTitle(); 904 if (switchAfter) { 905 setEditMode(false); 906 } 907 } 908 909 /** 910 * setFrameTitle - set the frame title based on type, text and address 911 */ 912 public void setFrameTitle() { 913 String winTitle = Bundle.getMessage("ThrottleTitle"); 914 if (throttleWindow.getTitleTextType().compareTo("text") == 0) { 915 winTitle = throttleWindow.getTitleText(); 916 } else if ( throttle != null) { 917 String addr = addressPanel.getCurrentAddress().toString(); 918 if (throttleWindow.getTitleTextType().compareTo("address") == 0) { 919 winTitle = addr; 920 } else if (throttleWindow.getTitleTextType().compareTo("addressText") == 0) { 921 winTitle = addr + " " + throttleWindow.getTitleText(); 922 } else if (throttleWindow.getTitleTextType().compareTo("textAddress") == 0) { 923 winTitle = throttleWindow.getTitleText() + " " + addr; 924 } else if (throttleWindow.getTitleTextType().compareTo("rosterID") == 0) { 925 if ( (addressPanel.getRosterEntry() != null) && (addressPanel.getRosterEntry().getId() != null) 926 && (addressPanel.getRosterEntry().getId().length() > 0)) { 927 winTitle = addressPanel.getRosterEntry().getId(); 928 } else { 929 winTitle = addr; // better than nothing in that particular case 930 } 931 } 932 } 933 throttleWindow.setTitle(winTitle); 934 } 935 936 @Override 937 public void componentHidden(ComponentEvent e) { 938 } 939 940 @Override 941 public void componentMoved(ComponentEvent e) { 942 } 943 944 @Override 945 public void componentResized(ComponentEvent e) { 946// checkPosition (); 947 } 948 949 @Override 950 public void componentShown(ComponentEvent e) { 951 throttleWindow.setCurrentThrottleFrame(this); 952 if (willSwitch) { 953 setEditMode(this.throttleWindow.isEditMode()); 954 repaint(); 955 } 956 throttleWindow.updateGUI(); 957 // bring addresspanel to front if no allocated throttle 958 if (addressPanel.getThrottle() == null && throttleWindow.isEditMode()) { 959 if (!addressPanel.isVisible()) { 960 addressPanel.setVisible(true); 961 } 962 if (addressPanel.isIcon()) { 963 try { 964 addressPanel.setIcon(false); 965 } catch (PropertyVetoException ex) { 966 log.debug("JInternalFrame uniconify, vetoed"); 967 } 968 } 969 addressPanel.requestFocus(); 970 addressPanel.toFront(); 971 try { 972 addressPanel.setSelected(true); 973 } catch (java.beans.PropertyVetoException ex) { 974 log.debug("JInternalFrame selection, vetoed"); 975 } 976 } 977 } 978 979 public void saveThrottle() { 980 if (getRosterEntry() != null) { 981 saveThrottle(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 982 } else if (getLastUsedSaveFile() != null) { 983 saveThrottle(getLastUsedSaveFile()); 984 } 985 } 986 987 public void saveThrottleAs() { 988 JFileChooser fileChooser = jmri.jmrit.XmlFile.userFileChooser(Bundle.getMessage("PromptXmlFileTypes"), "xml"); 989 fileChooser.setCurrentDirectory(new File(getDefaultThrottleFolder())); 990 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); 991 java.io.File file = StoreXmlConfigAction.getFileName(fileChooser); 992 if (file == null) { 993 return; 994 } 995 saveThrottle(file.getAbsolutePath()); 996 } 997 998 public void activateNextJInternalFrame() { 999 try { 1000 int initialFrame = activeFrame; // avoid infinite loop 1001 do { 1002 activeFrame = (activeFrame + 1) % NUM_FRAMES; 1003 frameList[activeFrame].setSelected(true); 1004 } while ((frameList[activeFrame].isClosed() || frameList[activeFrame].isIcon() || (!frameList[activeFrame].isVisible())) && (initialFrame != activeFrame)); 1005 } catch (PropertyVetoException ex) { 1006 log.warn("Exception selecting internal frame:{}", ex.getMessage()); 1007 } 1008 } 1009 1010 public void activatePreviousJInternalFrame() { 1011 try { 1012 int initialFrame = activeFrame; // avoid infinite loop 1013 do { 1014 activeFrame--; 1015 if (activeFrame < 0) { 1016 activeFrame = NUM_FRAMES - 1; 1017 } 1018 frameList[activeFrame].setSelected(true); 1019 } while ((frameList[activeFrame].isClosed() || frameList[activeFrame].isIcon() || (!frameList[activeFrame].isVisible())) && (initialFrame != activeFrame)); 1020 } catch (PropertyVetoException ex) { 1021 log.warn("Exception selecting internal frame:{}", ex.getMessage()); 1022 } 1023 } 1024 1025 @Override 1026 public void notifyAddressChosen(LocoAddress l) { 1027 } 1028 1029 @Override 1030 public void notifyAddressReleased(LocoAddress la) { 1031 if (throttle == null) { 1032 log.debug("notifyAddressReleased() throttle already null, called for loc {}",la); 1033 return; 1034 } 1035 if (throttleFrameManager.getNumberOfEntriesFor((DccLocoAddress) throttle.getLocoAddress()) == 1 ) { 1036 throttleManager.removeListener(throttle.getLocoAddress(), throttleFrameManager.getThrottlesListPanel().getTableModel()); 1037 } 1038 throttle = null; 1039 setLastUsedSaveFile(null); 1040 setFrameTitle(); 1041 throttleWindow.updateGUI(); 1042 throttleFrameManager.getThrottlesListPanel().getTableModel().fireTableStructureChanged(); 1043 } 1044 1045 @Override 1046 public void notifyAddressThrottleFound(DccThrottle t) { 1047 if (throttle != null) { 1048 log.debug("notifyAddressThrottleFound() throttle non null, called for loc {}",t.getLocoAddress()); 1049 return; 1050 } 1051 throttle = t; 1052 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) 1053 && (InstanceManager.getDefault(ThrottlesPreferences.class).isAutoLoading()) && (addressPanel != null)) { 1054 if ((addressPanel.getRosterEntry() != null) 1055 && ((getLastUsedSaveFile() == null) || (getLastUsedSaveFile().compareTo(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml") != 0))) { 1056 loadThrottle(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 1057 setLastUsedSaveFile(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 1058 } else if ((addressPanel.getRosterEntry() == null) 1059 && ((getLastUsedSaveFile() == null) || (getLastUsedSaveFile().compareTo(getDefaultThrottleFolder() + addressPanel.getCurrentAddress()+ ".xml") != 0))) { 1060 loadThrottle(getDefaultThrottleFolder() + throttle.getLocoAddress().getNumber() + ".xml"); 1061 setLastUsedSaveFile(getDefaultThrottleFolder() + throttle.getLocoAddress().getNumber() + ".xml"); 1062 } 1063 } else { 1064 if ((addressPanel != null) && (addressPanel.getRosterEntry() == null)) { // no known roster entry 1065 loadDefaultThrottle(); 1066 } 1067 } 1068 setFrameTitle(); 1069 throttleWindow.updateGUI(); 1070 throttleManager.attachListener(throttle.getLocoAddress(), throttleFrameManager.getThrottlesListPanel().getTableModel()); 1071 throttleFrameManager.getThrottlesListPanel().getTableModel().fireTableDataChanged(); 1072 } 1073 1074 1075 @Override 1076 public void notifyConsistAddressChosen(LocoAddress l) { 1077 notifyAddressChosen(l); 1078 } 1079 1080 1081 @Override 1082 public void notifyConsistAddressReleased(LocoAddress la) { 1083 notifyAddressReleased(la); 1084 } 1085 1086 @Override 1087 public void notifyConsistAddressThrottleFound(DccThrottle throttle) { 1088 notifyAddressThrottleFound(throttle); 1089 } 1090 1091 public String getLastUsedSaveFile() { 1092 return lastUsedSaveFile; 1093 } 1094 1095 public void setLastUsedSaveFile(String lusf) { 1096 lastUsedSaveFile = lusf; 1097 throttleWindow.updateGUI(); 1098 } 1099 1100 // some utilities to turn a component background transparent 1101 public static void setTransparentBackground(JComponent jcomp) { 1102 if (jcomp instanceof JPanel) //OS X: Jpanel components are enough 1103 { 1104 jcomp.setBackground(new Color(0, 0, 0, 0)); 1105 } 1106 setTransparentBackground(jcomp.getComponents()); 1107 } 1108 1109 public static void setTransparentBackground(Component[] comps) { 1110 for (Component comp : comps) { 1111 try { 1112 if (comp instanceof JComponent) { 1113 setTransparentBackground((JComponent) comp); 1114 } 1115 } catch (Exception e) { 1116 // Do nothing, just go on 1117 } 1118 } 1119 } 1120 1121 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ThrottleFrame.class); 1122}