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