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