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 } 528 529 private static class TranslucentJPanel extends JPanel { 530 531 private final Color TRANS_COL = new Color(100, 100, 100, 100); 532 533 public TranslucentJPanel() { 534 super(); 535 setOpaque(false); 536 } 537 538 @Override 539 public void paintComponent(Graphics g) { 540 g.setColor(TRANS_COL); 541 g.fillRoundRect(0, 0, getSize().width, getSize().height, 10, 10); 542 super.paintComponent(g); 543 } 544 } 545 546 private void playRendering() { 547 Component[] cmps = getComponentsInLayer(PANEL_LAYER_FRAME); 548 contentPanes = new HashMap<>(); 549 for (Component cmp : cmps) { 550 if ((cmp instanceof JInternalFrame) && (cmp.isVisible())) { 551 translude((JInternalFrame)cmp); 552 } 553 } 554 } 555 556 private void translude(JInternalFrame jif) { 557 Dimension cpSize = jif.getContentPane().getSize(); 558 Point cpLoc = jif.getContentPane().getLocationOnScreen(); 559 TranslucentJPanel pane = new TranslucentJPanel(); 560 pane.setLayout(new BorderLayout()); 561 contentPanes.put(pane, jif); 562 pane.add(jif.getContentPane(), BorderLayout.CENTER); 563 setTransparent(pane, true); 564 jif.setContentPane(new JPanel()); 565 jif.setVisible(false); 566 Point loc = new Point(cpLoc.x - this.getLocationOnScreen().x, cpLoc.y - this.getLocationOnScreen().y); 567 add(pane, PANEL_LAYER_PANEL); 568 pane.setLocation(loc); 569 pane.setSize(cpSize); 570 } 571 572 private void editRendering() { 573 Component[] cmps = getComponentsInLayer(PANEL_LAYER_PANEL); 574 for (Component cmp : cmps) { 575 if (cmp instanceof JPanel) { 576 JPanel pane = (JPanel) cmp; 577 JInternalFrame jif = contentPanes.get(pane); 578 jif.setContentPane((Container) pane.getComponent(0)); 579 setTransparent(jif, false); 580 jif.setVisible(true); 581 remove(pane); 582 } 583 } 584 } 585 586 public void setEditMode(boolean mode) { 587 if (mode == isEditMode) 588 return; 589 if (isVisible()) { 590 if (!mode) { 591 playRendering(); 592 } else { 593 editRendering(); 594 } 595 isEditMode = mode; 596 willSwitch = false; 597 } else { 598 willSwitch = true; 599 } 600 throttleWindow.updateGUI(); 601 } 602 603 public boolean getEditMode() { 604 return isEditMode; 605 } 606 607 /** 608 * Handle my own destruction. 609 * <ol> 610 * <li> dispose of sub windows. 611 * <li> notify my manager of my demise. 612 * </ol> 613 */ 614 public void dispose() { 615 log.debug("Disposing {}", getTitle()); 616 URIDrop.remove(backgroundPanel); 617 addressPanel.removeAddressListener(this); 618 // should the throttle list table stop listening to that throttle? 619 if (throttle!=null && allThrottlesTableModel.getNumberOfEntriesFor((DccLocoAddress) throttle.getLocoAddress()) == 1 ) { 620 throttleManager.removeListener(throttle.getLocoAddress(), allThrottlesTableModel); 621 allThrottlesTableModel.fireTableDataChanged(); 622 } 623 624 // remove from the throttle list table 625 InstanceManager.getDefault(ThrottleFrameManager.class).getThrottlesListPanel().getTableModel().removeThrottleFrame(this, addressPanel.getCurrentAddress()); 626 // check for any special disposing in InternalFrames 627 controlPanel.destroy(); 628 functionPanel.destroy(); 629 speedPanel.destroy(); 630 backgroundPanel.destroy(); 631 // dispose of this last because it will release and destroy the throttle. 632 addressPanel.destroy(); 633 } 634 635 public void saveRosterChanges() { 636 RosterEntry rosterEntry = addressPanel.getRosterEntry(); 637 if (rosterEntry == null) { 638 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ThrottleFrameNoRosterItemMessageDialog"), 639 Bundle.getMessage("ThrottleFrameNoRosterItemTitleDialog"), JmriJOptionPane.ERROR_MESSAGE); 640 return; 641 } 642 if (JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("ThrottleFrameRosterChangeMesageDialog"), 643 Bundle.getMessage("ThrottleFrameRosterChangeTitleDialog"), JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) { 644 return; 645 } 646 functionPanel.saveFunctionButtonsToRoster(rosterEntry); 647 controlPanel.saveToRoster(rosterEntry); 648 } 649 650 /** 651 * An extension of InternalFrameAdapter for listening to the closing of of 652 * this frame's internal frames. 653 * 654 * @author glen 655 */ 656 class FrameListener extends InternalFrameAdapter { 657 658 /** 659 * Listen for the closing of an internal frame and set the "View" menu 660 * appropriately. Then hide the closing frame 661 * 662 * @param e The InternalFrameEvent leading to this action 663 */ 664 @Override 665 public void internalFrameClosing(InternalFrameEvent e) { 666 if (e.getSource() == controlPanel) { 667 throttleWindow.getViewControlPanel().setSelected(false); 668 controlPanel.setVisible(false); 669 } else if (e.getSource() == addressPanel) { 670 throttleWindow.getViewAddressPanel().setSelected(false); 671 addressPanel.setVisible(false); 672 } else if (e.getSource() == functionPanel) { 673 throttleWindow.getViewFunctionPanel().setSelected(false); 674 functionPanel.setVisible(false); 675 } else if (e.getSource() == speedPanel) { 676 throttleWindow.getViewSpeedPanel().setSelected(false); 677 speedPanel.setVisible(false); 678 } else { 679 try { // #JYNSTRUMENT#, Very important, clean the Jynstrument 680 if ((e.getSource() instanceof JInternalFrame)) { 681 Component[] cmps = ((JInternalFrame) e.getSource()).getContentPane().getComponents(); 682 int i = 0; 683 while ((i < cmps.length) && (!(cmps[i] instanceof Jynstrument))) { 684 i++; 685 } 686 if ((i < cmps.length) && (cmps[i] instanceof Jynstrument)) { 687 ((Jynstrument) cmps[i]).exit(); 688 } 689 } 690 } catch (Exception exc) { 691 log.debug("Got exception, can ignore: ", exc); 692 } 693 } 694 } 695 696 /** 697 * Listen for the activation of an internal frame record this property 698 * for correct processing of the frame cycling key. 699 * 700 * @param e The InternalFrameEvent leading to this action 701 */ 702 @Override 703 public void internalFrameActivated(InternalFrameEvent e) { 704 if (e.getSource() == controlPanel) { 705 activeFrame = CONTROL_PANEL_INDEX; 706 } else if (e.getSource() == addressPanel) { 707 activeFrame = ADDRESS_PANEL_INDEX; 708 } else if (e.getSource() == functionPanel) { 709 activeFrame = FUNCTION_PANEL_INDEX; 710 } else if (e.getSource() == functionPanel) { 711 activeFrame = SPEED_DISPLAY_INDEX; 712 } 713 } 714 } 715 716 /** 717 * Collect the prefs of this object into XML Element 718 * <ul> 719 * <li> Window prefs 720 * <li> ControlPanel 721 * <li> FunctionPanel 722 * <li> AddressPanel 723 * <li> SpeedPanel 724 * </ul> 725 * 726 * 727 * @return the XML of this object. 728 */ 729 public Element getXml() { 730 boolean switchAfter = false; 731 if (!isEditMode) { 732 setEditMode(true); 733 switchAfter = true; 734 } 735 736 Element me = new Element("ThrottleFrame"); 737 738 if (((javax.swing.plaf.basic.BasicInternalFrameUI) getControlPanel().getUI()).getNorthPane() != null) { 739 Dimension bDim = ((javax.swing.plaf.basic.BasicInternalFrameUI) getControlPanel().getUI()).getNorthPane().getPreferredSize(); 740 me.setAttribute("border", Integer.toString(bDim.height)); 741 } 742 743 ArrayList<Element> children = new ArrayList<>(1); 744 745// children.add(WindowPreferences.getPreferences(this)); // not required as it is in ThrottleWindow 746 children.add(controlPanel.getXml()); 747 children.add(functionPanel.getXml()); 748 children.add(addressPanel.getXml()); 749 children.add(speedPanel.getXml()); 750 // Save Jynstruments 751 Component[] cmps = getComponents(); 752 for (Component cmp : cmps) { 753 try { 754 if (cmp instanceof JInternalFrame) { 755 Component[] cmps2 = ((JInternalFrame) cmp).getContentPane().getComponents(); 756 int j = 0; 757 while ((j < cmps2.length) && (!(cmps2[j] instanceof Jynstrument))) { 758 j++; 759 } 760 if ((j < cmps2.length) && (cmps2[j] instanceof Jynstrument)) { 761 Jynstrument jyn = (Jynstrument) cmps2[j]; 762 Element elt = new Element("Jynstrument"); 763 elt.setAttribute("JynstrumentFolder", FileUtil.getPortableFilename(jyn.getFolder())); 764 ArrayList<Element> jychildren = new ArrayList<>(1); 765 jychildren.add(WindowPreferences.getPreferences((JInternalFrame) cmp)); 766 Element je = jyn.getXml(); 767 if (je != null) { 768 jychildren.add(je); 769 } 770 elt.setContent(jychildren); 771 children.add(elt); 772 } 773 } 774 } catch (Exception ex) { 775 log.debug("Got exception (no panic) {}", ex.getMessage()); 776 } 777 } 778 me.setContent(children); 779 if (switchAfter) { 780 setEditMode(false); 781 } 782 return me; 783 } 784 785 public Element getXmlFile() { 786 if (getLastUsedSaveFile() == null) { // || (getRosterEntry()==null)) 787 return null; 788 } 789 Element me = new Element("ThrottleFrame"); 790 me.setAttribute("ThrottleXMLFile", FileUtil.getPortableFilename(getLastUsedSaveFile())); 791 return me; 792 } 793 794 /** 795 * Set the preferences based on the XML Element. 796 * <ul> 797 * <li> Window prefs 798 * <li> Frame title 799 * <li> ControlPanel 800 * <li> FunctionPanel 801 * <li> AddressPanel 802 * <li> SpeedPanel 803 * </ul> 804 * 805 * @param e The Element for this object. 806 */ 807 public void setXml(Element e) { 808 if (e == null) { 809 return; 810 } 811 812 String sfile = e.getAttributeValue("ThrottleXMLFile"); 813 if (sfile != null) { 814 loadThrottle(FileUtil.getExternalFilename(sfile)); 815 return; 816 } 817 818 boolean switchAfter = false; 819 if (!isEditMode) { 820 setEditMode(true); 821 switchAfter = true; 822 } 823 824 int bSize = 23; 825 // Get InternalFrame border size 826 if (e.getAttribute("border") != null) { 827 bSize = Integer.parseInt((e.getAttribute("border").getValue())); 828 } 829 Element controlPanelElement = e.getChild("ControlPanel"); 830 controlPanel.setXml(controlPanelElement); 831 if (((javax.swing.plaf.basic.BasicInternalFrameUI) controlPanel.getUI()).getNorthPane() != null) { 832 ((javax.swing.plaf.basic.BasicInternalFrameUI) controlPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 833 } 834 Element functionPanelElement = e.getChild("FunctionPanel"); 835 functionPanel.setXml(functionPanelElement); 836 if (((javax.swing.plaf.basic.BasicInternalFrameUI) functionPanel.getUI()).getNorthPane() != null) { 837 ((javax.swing.plaf.basic.BasicInternalFrameUI) functionPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 838 } 839 Element addressPanelElement = e.getChild("AddressPanel"); 840 addressPanel.setXml(addressPanelElement); 841 if (((javax.swing.plaf.basic.BasicInternalFrameUI) addressPanel.getUI()).getNorthPane() != null) { 842 ((javax.swing.plaf.basic.BasicInternalFrameUI) addressPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 843 } 844 Element speedPanelElement = e.getChild("SpeedPanel"); 845 if (speedPanelElement != null) { // older throttle configs may not have this element 846 speedPanel.setXml(speedPanelElement); 847 if (((javax.swing.plaf.basic.BasicInternalFrameUI) speedPanel.getUI()).getNorthPane() != null) { 848 ((javax.swing.plaf.basic.BasicInternalFrameUI) speedPanel.getUI()).getNorthPane().setPreferredSize(new Dimension(0, bSize)); 849 } 850 } 851 852 List<Element> jinsts = e.getChildren("Jynstrument"); 853 if ((jinsts != null) && (jinsts.size() > 0)) { 854 for (Element jinst : jinsts) { 855 JInternalFrame jif = ynstrument(FileUtil.getExternalFilename(jinst.getAttributeValue("JynstrumentFolder"))); 856 Element window = jinst.getChild("window"); 857 if (jif != null) { 858 if (window != null) { 859 WindowPreferences.setPreferences(jif, window); 860 } 861 Component[] cmps2 = jif.getContentPane().getComponents(); 862 int j = 0; 863 while ((j < cmps2.length) && (!(cmps2[j] instanceof Jynstrument))) { 864 j++; 865 } 866 if ((j < cmps2.length) && (cmps2[j] instanceof Jynstrument)) { 867 ((Jynstrument) cmps2[j]).setXml(jinst); 868 } 869 870 jif.repaint(); 871 } 872 } 873 } 874 setFrameTitle(); 875 if (switchAfter) { 876 setEditMode(false); 877 } 878 } 879 880 /** 881 * setFrameTitle - set the frame title based on type, text and address 882 */ 883 public void setFrameTitle() { 884 String winTitle = Bundle.getMessage("ThrottleTitle"); 885 if (throttleWindow.getTitleTextType().compareTo("text") == 0) { 886 winTitle = throttleWindow.getTitleText(); 887 } else if ( throttle != null) { 888 String addr = addressPanel.getCurrentAddress().toString(); 889 if (throttleWindow.getTitleTextType().compareTo("address") == 0) { 890 winTitle = addr; 891 } else if (throttleWindow.getTitleTextType().compareTo("addressText") == 0) { 892 winTitle = addr + " " + throttleWindow.getTitleText(); 893 } else if (throttleWindow.getTitleTextType().compareTo("textAddress") == 0) { 894 winTitle = 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 winTitle = addressPanel.getRosterEntry().getId(); 899 } else { 900 winTitle = addr; // better than nothing in that particular case 901 } 902 } 903 } 904 throttleWindow.setTitle(winTitle); 905 } 906 907 @Override 908 public void componentHidden(ComponentEvent e) { 909 } 910 911 @Override 912 public void componentMoved(ComponentEvent e) { 913 } 914 915 @Override 916 public void componentResized(ComponentEvent e) { 917// checkPosition (); 918 } 919 920 @Override 921 public void componentShown(ComponentEvent e) { 922 throttleWindow.setCurrentThrottleFrame(this); 923 if (willSwitch) { 924 setEditMode(this.throttleWindow.isEditMode()); 925 repaint(); 926 } 927 throttleWindow.updateGUI(); 928 // bring addresspanel to front if no allocated throttle 929 if (addressPanel.getThrottle() == null && throttleWindow.isEditMode()) { 930 if (!addressPanel.isVisible()) { 931 addressPanel.setVisible(true); 932 } 933 if (addressPanel.isIcon()) { 934 try { 935 addressPanel.setIcon(false); 936 } catch (PropertyVetoException ex) { 937 log.debug("JInternalFrame uniconify, vetoed"); 938 } 939 } 940 addressPanel.requestFocus(); 941 addressPanel.toFront(); 942 try { 943 addressPanel.setSelected(true); 944 } catch (java.beans.PropertyVetoException ex) { 945 log.debug("JInternalFrame selection, vetoed"); 946 } 947 } 948 } 949 950 public void saveThrottle() { 951 if (getRosterEntry() != null) { 952 saveThrottle(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 953 } else if (getLastUsedSaveFile() != null) { 954 saveThrottle(getLastUsedSaveFile()); 955 } 956 } 957 958 public void saveThrottleAs() { 959 JFileChooser fileChooser = jmri.jmrit.XmlFile.userFileChooser(Bundle.getMessage("PromptXmlFileTypes"), "xml"); 960 fileChooser.setCurrentDirectory(new File(getDefaultThrottleFolder())); 961 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); 962 java.io.File file = StoreXmlConfigAction.getFileName(fileChooser); 963 if (file == null) { 964 return; 965 } 966 saveThrottle(file.getAbsolutePath()); 967 } 968 969 public void activateNextJInternalFrame() { 970 try { 971 int initialFrame = activeFrame; // avoid infinite loop 972 do { 973 activeFrame = (activeFrame + 1) % NUM_FRAMES; 974 frameList[activeFrame].setSelected(true); 975 } while ((frameList[activeFrame].isClosed() || frameList[activeFrame].isIcon() || (!frameList[activeFrame].isVisible())) && (initialFrame != activeFrame)); 976 } catch (PropertyVetoException ex) { 977 log.warn("Exception selecting internal frame:{}", ex.getMessage()); 978 } 979 } 980 981 public void activatePreviousJInternalFrame() { 982 try { 983 int initialFrame = activeFrame; // avoid infinite loop 984 do { 985 activeFrame--; 986 if (activeFrame < 0) { 987 activeFrame = NUM_FRAMES - 1; 988 } 989 frameList[activeFrame].setSelected(true); 990 } while ((frameList[activeFrame].isClosed() || frameList[activeFrame].isIcon() || (!frameList[activeFrame].isVisible())) && (initialFrame != activeFrame)); 991 } catch (PropertyVetoException ex) { 992 log.warn("Exception selecting internal frame:{}", ex.getMessage()); 993 } 994 } 995 996 @Override 997 public void notifyAddressChosen(LocoAddress l) { 998 } 999 1000 @Override 1001 public void notifyAddressReleased(LocoAddress la) { 1002 if (throttle == null) { 1003 log.debug("notifyAddressReleased() throttle already null, called for loc {}",la); 1004 return; 1005 } 1006 if (allThrottlesTableModel.getNumberOfEntriesFor((DccLocoAddress) throttle.getLocoAddress()) == 1 ) { 1007 throttleManager.removeListener(throttle.getLocoAddress(), allThrottlesTableModel); 1008 } 1009 throttle = null; 1010 setLastUsedSaveFile(null); 1011 setFrameTitle(); 1012 throttleWindow.updateGUI(); 1013 allThrottlesTableModel.fireTableDataChanged(); 1014 } 1015 1016 @Override 1017 public void notifyAddressThrottleFound(DccThrottle t) { 1018 if (throttle != null) { 1019 log.debug("notifyAddressThrottleFound() throttle non null, called for loc {}",t.getLocoAddress()); 1020 return; 1021 } 1022 throttle = t; 1023 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) 1024 && (InstanceManager.getDefault(ThrottlesPreferences.class).isAutoLoading()) && (addressPanel != null)) { 1025 if ((addressPanel.getRosterEntry() != null) 1026 && ((getLastUsedSaveFile() == null) || (getLastUsedSaveFile().compareTo(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml") != 0))) { 1027 loadThrottle(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 1028 setLastUsedSaveFile(getDefaultThrottleFolder() + addressPanel.getRosterEntry().getId().trim() + ".xml"); 1029 } else if ((addressPanel.getRosterEntry() == null) 1030 && ((getLastUsedSaveFile() == null) || (getLastUsedSaveFile().compareTo(getDefaultThrottleFolder() + addressPanel.getCurrentAddress()+ ".xml") != 0))) { 1031 loadThrottle(getDefaultThrottleFolder() + throttle.getLocoAddress().getNumber() + ".xml"); 1032 setLastUsedSaveFile(getDefaultThrottleFolder() + throttle.getLocoAddress().getNumber() + ".xml"); 1033 } 1034 } else { 1035 if ((addressPanel != null) && (addressPanel.getRosterEntry() == null)) { // no known roster entry 1036 loadDefaultThrottle(); 1037 } 1038 } 1039 setFrameTitle(); 1040 throttleWindow.updateGUI(); 1041 throttleManager.attachListener(throttle.getLocoAddress(), allThrottlesTableModel); 1042 allThrottlesTableModel.fireTableDataChanged(); 1043 } 1044 1045 1046 @Override 1047 public void notifyConsistAddressChosen(LocoAddress l) { 1048 notifyAddressChosen(l); 1049 } 1050 1051 1052 @Override 1053 public void notifyConsistAddressReleased(LocoAddress la) { 1054 notifyAddressReleased(la); 1055 } 1056 1057 @Override 1058 public void notifyConsistAddressThrottleFound(DccThrottle throttle) { 1059 notifyAddressThrottleFound(throttle); 1060 } 1061 1062 public String getLastUsedSaveFile() { 1063 return lastUsedSaveFile; 1064 } 1065 1066 public void setLastUsedSaveFile(String lusf) { 1067 lastUsedSaveFile = lusf; 1068 throttleWindow.updateGUI(); 1069 } 1070 1071 // some utilities to turn a component background transparent 1072 public static void setTransparentBackground(JComponent jcomp) { 1073 if (jcomp instanceof JPanel) //OS X: Jpanel components are enough 1074 { 1075 jcomp.setBackground(new Color(0, 0, 0, 0)); 1076 } 1077 setTransparentBackground(jcomp.getComponents()); 1078 } 1079 1080 public static void setTransparentBackground(Component[] comps) { 1081 for (Component comp : comps) { 1082 try { 1083 if (comp instanceof JComponent) { 1084 setTransparentBackground((JComponent) comp); 1085 } 1086 } catch (Exception e) { 1087 // Do nothing, just go on 1088 } 1089 } 1090 } 1091 1092// some utilities to turn a component background transparent 1093 public static void setTransparent(JComponent jcomp) { 1094 setTransparent(jcomp, true); 1095 } 1096 1097 public static void setTransparent(JComponent jcomp, boolean transparency) { 1098 if (jcomp instanceof JPanel) { //OS X: Jpanel components are enough 1099 jcomp.setOpaque(!transparency); 1100 } 1101 setTransparent(jcomp.getComponents(), transparency); 1102 } 1103 1104 private static void setTransparent(Component[] comps, boolean transparency) { 1105 for (Component comp : comps) { 1106 try { 1107 if (comp instanceof JComponent) { 1108 setTransparent((JComponent) comp, transparency); 1109 } 1110 } catch (Exception e) { 1111 // Do nothing, just go on 1112 } 1113 } 1114 } 1115 1116 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ThrottleFrame.class); 1117}