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