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