001package jmri.jmrit.throttle; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.beans.PropertyChangeEvent; 006import java.beans.PropertyChangeListener; 007import java.io.File; 008import java.net.URI; 009import java.util.*; 010import java.util.List; 011 012import javax.annotation.CheckForNull; 013import javax.swing.*; 014 015import jmri.*; 016import jmri.jmrit.catalog.NamedIcon; 017import jmri.jmrit.jython.Jynstrument; 018import jmri.jmrit.jython.JynstrumentFactory; 019import jmri.jmrit.throttle.actions.ThrottleWindowActionsFactory; 020import jmri.jmrit.throttle.actions.ThrottleWindowInputsListener; 021import jmri.jmrit.throttle.preferences.ThrottlesPreferences; 022import jmri.jmrit.throttle.buttons.LargePowerManagerButton; 023import jmri.jmrit.throttle.buttons.SmallPowerManagerButton; 024import jmri.jmrit.throttle.buttons.StopAllButton; 025import jmri.jmrit.throttle.implementation.ThrottleFrame; 026import jmri.jmrit.throttle.implementation.ThrottleFramePropertyEditor; 027import jmri.jmrit.throttle.implementation.WindowPreferences; 028import jmri.jmrit.throttle.interfaces.ThrottleControllerUI; 029import jmri.jmrit.throttle.interfaces.ThrottleControllersUIContainer; 030import jmri.util.FileUtil; 031import jmri.util.JmriJFrame; 032import jmri.util.iharder.dnd.URIDrop; 033import jmri.util.swing.TransparencyUtils; 034 035import org.jdom2.Element; 036import org.jdom2.Attribute; 037 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * The JMRI throttle window. 043 * 044 * <hr> 045 * This file is part of JMRI. 046 * <p> 047 * JMRI is free software; you can redistribute it and/or modify it under the 048 * terms of version 2 of the GNU General Public License as published by the Free 049 * Software Foundation. See the "COPYING" file for a copy of this license. 050 * <p> 051 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 052 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 053 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 054 * 055 * @author Lionel Jeanson 2007-2026 056 * 057 */ 058 059public class ThrottleWindow extends JmriJFrame implements ThrottleControllersUIContainer, PropertyChangeListener { 060 061 private final jmri.jmrix.ConnectionConfig connectionConfig; 062 private final ThrottleManager throttleManager; 063 private final ThrottleFrameManager throttleFrameManager = InstanceManager.getDefault(ThrottleFrameManager.class); 064 065 private JPanel throttlesPanel; 066 private ThrottleFrame currentThrottleFrame; 067 private CardLayout throttlesLayout; 068 069 private JCheckBoxMenuItem viewControlPanel; 070 private JCheckBoxMenuItem viewFunctionPanel; 071 private JCheckBoxMenuItem viewAddressPanel; 072 private JCheckBoxMenuItem viewSpeedPanel; 073 private JCheckBoxMenuItem viewLocoIconPanel; 074 private JCheckBoxMenuItem viewConsistFunctionsPanel; 075 private JMenuItem viewAllButtons; 076 private JMenuItem fileMenuSave; 077 private JMenuItem editMenuExportRoster; 078 079 private JButton jbPrevious = null; 080 private JButton jbNext = null; 081 private JButton jbPreviousRunning = null; 082 private JButton jbNextRunning = null; 083 private JButton jbThrottleList = null; 084 private JButton jbNew = null; 085 private JButton jbClose = null; 086 private JButton jbMode = null; 087 private JToolBar throttleToolBar; 088 089 private String titleText = ""; 090 private String titleTextType = "rosterID"; 091 private boolean isEditMode = true; 092 093 private final PowerManager powerMgr; 094 private SmallPowerManagerButton smallPowerMgmtButton; 095 096 private final ThrottleWindowActionsFactory myActionFactory; 097 098 private ArrayList<ThrottleFrame> throttleFrames = new ArrayList<>(5); 099 100 java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this); 101 102 /** 103 * Default constructor 104 */ 105 public ThrottleWindow() { 106 this((jmri.jmrix.ConnectionConfig) null); 107 } 108 109 /** 110 * Constructor 111 * @param connectionConfig the connection config 112 */ 113 public ThrottleWindow(jmri.jmrix.ConnectionConfig connectionConfig) { 114 super(Bundle.getMessage("ThrottleTitle")); 115 this.connectionConfig = connectionConfig; 116 if (connectionConfig != null) { 117 this.throttleManager = connectionConfig.getAdapter().getSystemConnectionMemo().get(jmri.ThrottleManager.class); 118 } else { 119 this.throttleManager = InstanceManager.getDefault(jmri.ThrottleManager.class); 120 } 121 myActionFactory = new ThrottleWindowActionsFactory(this); 122 powerMgr = InstanceManager.getNullableDefault(PowerManager.class); 123 if (powerMgr == null) { 124 log.info("No power manager instance found, panel not active"); 125 } 126 pcs.addPropertyChangeListener(throttleFrameManager.getThrottlesListPanel().getTableModel()); 127 initGUI(); 128 InstanceManager.getDefault(ThrottlesPreferences.class).addPropertyChangeListener(this); 129 applyPreferences(); 130 } 131 132 /** 133 * Create a ThrottleWindow 134 * @param e the xml element for the throttle window 135 * @return the throttle window 136 */ 137 public static ThrottleWindow createThrottleWindow(Element e) { 138 jmri.jmrix.ConnectionConfig connectionConfig = null; 139 140 Attribute systemPrefixAttr = e.getAttribute("systemPrefix"); 141 if (systemPrefixAttr != null) { 142 String systemPrefix = systemPrefixAttr.getValue(); 143 // Set connectionConfig to null in case the systemPrefix 144 // points to a connection that doesn't exist anymore. 145 146 for (jmri.jmrix.ConnectionConfig c : InstanceManager.getDefault(jmri.jmrix.ConnectionConfigManager.class)) { 147 if (c.getAdapter().getSystemPrefix().equals(systemPrefix)) { 148 connectionConfig = c; 149 } 150 } 151 } 152 153 ThrottleWindow tw = new ThrottleWindow(connectionConfig); 154 tw.setXml(e); 155 return tw; 156 } 157 158 private void initGUI() { 159 setTitle(Bundle.getMessage("ThrottleTitle")); 160 setLayout(new BorderLayout()); 161 throttlesLayout = new CardLayout(); 162 throttlesPanel = new JPanel(throttlesLayout); 163 throttlesPanel.setDoubleBuffered(true); 164 165 initializeToolbar(); 166 initializeMenu(); 167 168 String txt = "ThrottleJDesktopPane-" + throttleFrameManager.generateUniqueFrameID(); 169 setCurrentThrottleFrame(new ThrottleFrame(this, throttleManager)); 170 getCurentThrottleController().setTitle(txt); 171 throttlesPanel.add(txt, getCurentThrottleController()); 172 throttleFrames.add(getCurentThrottleController()); 173 add(throttlesPanel, BorderLayout.CENTER); 174 175 installInputsListenerOnAllComponents(this); 176 // to get something to put focus on 177 getRootPane().setFocusable(true); 178 179 ActionMap am = myActionFactory.buildActionMap(); 180 for (Object k : am.allKeys()) { 181 getRootPane().getActionMap().put(k, am.get(k)); 182 } 183 184 addMouseWheelListener( new ThrottleWindowInputsListener(this) ); 185 186 this.addWindowListener(new WindowAdapter() { 187 @Override 188 public void windowOpened(WindowEvent e) { 189 // on initial open, force selection of address panel 190 getCurentThrottleController().forceAddressPanelSelected(); 191 } 192 }); 193 updateGUI(); 194 } 195 196 public void updateGUI() { 197 if (getCurentThrottleController() == null) { 198 return; 199 } 200 // title bar 201 getCurentThrottleController().updateFrameTitle(); 202 // menu items 203 viewAddressPanel.setEnabled(isEditMode); 204 viewControlPanel.setEnabled(isEditMode); 205 viewFunctionPanel.setEnabled(isEditMode); 206 viewSpeedPanel.setEnabled(isEditMode); 207 viewLocoIconPanel.setEnabled(isEditMode); 208 viewConsistFunctionsPanel.setEnabled(isEditMode); 209 if (isEditMode) { 210 viewAddressPanel.setSelected(getCurentThrottleController().isAddressPanelVisible()); 211 viewControlPanel.setSelected(getCurentThrottleController().isControlPanelVisible()); 212 viewFunctionPanel.setSelected(getCurentThrottleController().isFunctionPanelVisible()); 213 viewSpeedPanel.setSelected(getCurentThrottleController().isSpeedPanelVisible()); 214 viewLocoIconPanel.setSelected(getCurentThrottleController().isLocoIconPanelVisible()); 215 viewConsistFunctionsPanel.setSelected(getCurentThrottleController().isConsistFunctionsPanelVisible()); 216 } 217 fileMenuSave.setEnabled(getCurentThrottleController().isKnownLastUsedSaveFile() || getCurentThrottleController().isKnownRosterEntry()); 218 editMenuExportRoster.setEnabled(getCurentThrottleController().isKnownRosterEntry()); 219 // toolbar items 220 if (jbPrevious != null) // means toolbar enabled 221 { 222 if (throttleFrames.size() > 1) { 223 jbPrevious.setEnabled(true); 224 jbNext.setEnabled(true); 225 jbClose.setEnabled(true); 226 jbPreviousRunning.setEnabled(true); 227 jbNextRunning.setEnabled(true); 228 } else { 229 jbPrevious.setEnabled(false); 230 jbNext.setEnabled(false); 231 jbClose.setEnabled(false); 232 jbPreviousRunning.setEnabled(false); 233 jbNextRunning.setEnabled(false); 234 } 235 } 236 getRootPane().requestFocusInWindow(); 237 } 238 239 private void initializeToolbar() { 240 throttleToolBar = new JToolBar("Throttles toolbar"); 241 242 jbNew = new JButton(); 243 jbNew.setIcon(new NamedIcon("resources/icons/throttles/add.png", "resources/icons/throttles/add.png")); 244 jbNew.setToolTipText(Bundle.getMessage("ThrottleToolBarNewToolTip")); 245 jbNew.setVerticalTextPosition(JButton.BOTTOM); 246 jbNew.setHorizontalTextPosition(JButton.CENTER); 247 jbNew.addActionListener(e -> newThrottleController()); 248 throttleToolBar.add(jbNew); 249 250 jbClose = new JButton(); 251 jbClose.setIcon(new NamedIcon("resources/icons/throttles/remove.png", "resources/icons/throttles/remove.png")); 252 jbClose.setToolTipText(Bundle.getMessage("ThrottleToolBarCloseToolTip")); 253 jbClose.setVerticalTextPosition(JButton.BOTTOM); 254 jbClose.setHorizontalTextPosition(JButton.CENTER); 255 jbClose.addActionListener(e -> removeAndDisposeCurentThrottleFrame()); 256 throttleToolBar.add(jbClose); 257 258 throttleToolBar.addSeparator(); 259 260 jbPreviousRunning = new JButton(); 261 jbPreviousRunning.setIcon(new NamedIcon("resources/icons/throttles/previous-jump.png", "resources/icons/throttles/previous-jump.png")); 262 jbPreviousRunning.setVerticalTextPosition(JButton.BOTTOM); 263 jbPreviousRunning.setHorizontalTextPosition(JButton.CENTER); 264 jbPreviousRunning.setToolTipText(Bundle.getMessage("ThrottleToolBarPrevRunToolTip")); 265 jbPreviousRunning.addActionListener(e -> previousRunningThrottleFrame()); 266 throttleToolBar.add(jbPreviousRunning); 267 268 jbPrevious = new JButton(); 269 jbPrevious.setIcon(new NamedIcon("resources/icons/throttles/previous.png", "resources/icons/throttles/previous.png")); 270 jbPrevious.setVerticalTextPosition(JButton.BOTTOM); 271 jbPrevious.setHorizontalTextPosition(JButton.CENTER); 272 jbPrevious.setToolTipText(Bundle.getMessage("ThrottleToolBarPrevToolTip")); 273 jbPrevious.addActionListener(e -> previousThrottleFrame()); 274 throttleToolBar.add(jbPrevious); 275 276 jbNext = new JButton(); 277 jbNext.setIcon(new NamedIcon("resources/icons/throttles/next.png", "resources/icons/throttles/next.png")); 278 jbNext.setToolTipText(Bundle.getMessage("ThrottleToolBarNextToolTip")); 279 jbNext.setVerticalTextPosition(JButton.BOTTOM); 280 jbNext.setHorizontalTextPosition(JButton.CENTER); 281 jbNext.addActionListener(e -> nextThrottleFrame()); 282 throttleToolBar.add(jbNext); 283 284 jbNextRunning = new JButton(); 285 jbNextRunning.setIcon(new NamedIcon("resources/icons/throttles/next-jump.png", "resources/icons/throttles/next-jump.png")); 286 jbNextRunning.setToolTipText(Bundle.getMessage("ThrottleToolBarNextRunToolTip")); 287 jbNextRunning.setVerticalTextPosition(JButton.BOTTOM); 288 jbNextRunning.setHorizontalTextPosition(JButton.CENTER); 289 jbNextRunning.addActionListener(e -> nextRunningThrottleFrame()); 290 throttleToolBar.add(jbNextRunning); 291 292 throttleToolBar.addSeparator(); 293 294 throttleToolBar.add(new StopAllButton()); 295 296 if (powerMgr != null) { 297 throttleToolBar.add(new LargePowerManagerButton(false)); 298 } 299 300 throttleToolBar.addSeparator(); 301 302 jbMode = new JButton(); 303 jbMode.setIcon(new NamedIcon("resources/icons/throttles/edit-view.png", "resources/icons/throttles/edit-view.png")); 304 jbMode.setToolTipText(Bundle.getMessage("ThrottleToolBarEditToolTip")); 305 jbMode.setVerticalTextPosition(JButton.BOTTOM); 306 jbMode.setHorizontalTextPosition(JButton.CENTER); 307 jbMode.addActionListener(e -> setEditMode( !isEditMode )); 308 throttleToolBar.add(jbMode); 309 310 throttleToolBar.addSeparator(); 311 312 jbThrottleList = new JButton(); 313 jbThrottleList.setIcon(new NamedIcon("resources/icons/throttles/list.png", "resources/icons/throttles/list.png")); 314 jbThrottleList.setToolTipText(Bundle.getMessage("ThrottleToolBarOpenThrottleListToolTip")); 315 jbThrottleList.setVerticalTextPosition(JButton.BOTTOM); 316 jbThrottleList.setHorizontalTextPosition(JButton.CENTER); 317 jbThrottleList.addActionListener(new ThrottlesListAction()); 318 throttleToolBar.add(jbThrottleList); 319 320 // Receptacle for Jynstruments 321 new URIDrop(throttleToolBar, uris -> { 322 for (URI uri : uris ) { 323 ynstrument(new File(uri).getPath()); 324 } 325 }); 326 327 add(throttleToolBar, BorderLayout.PAGE_START); 328 } 329 330 /** 331 * Return the number of active thottle frames. 332 * 333 * @return the number of active thottle frames. 334 */ 335 @Override 336 public int getNbThrottlesControllers() { 337 return throttleFrames.size() ; 338 } 339 340 /** 341 * Return the nth thottle frame of that throttle window 342 * 343 * @param n index of thr throtle frame 344 * @return the nth thottle frame of that throttle window 345 */ 346 @Override 347 public ThrottleControllerUI getThrottleControllerAt(int n) { 348 if (! (n < throttleFrames.size())) { 349 return null; 350 } 351 return throttleFrames.get(n); 352 } 353 354 /** 355 * Get the number of usages of a particular Loco Address. 356 * @param la the Loco Address, can be null. 357 * @return 0 if no usages, else number of AddressPanel usages. 358 */ 359 @Override 360 public int getNumberOfEntriesFor(@CheckForNull DccLocoAddress la) { 361 if (la == null) { 362 return 0; 363 } 364 int ret = 0; 365 for (ThrottleFrame tf: throttleFrames) { 366 if (tf.isUsingAddress(la)) { 367 ret++; // in use, increment count. 368 } 369 } 370 return ret; 371 } 372 373 @Override 374 public void emergencyStopAll() { 375 if (!throttleFrames.isEmpty()) { 376 for (ThrottleFrame tf: throttleFrames) { 377 tf.eStop(); 378 } 379 } 380 } 381 382 /** {@inheritDoc} */ 383 @Override 384 public void setTitle(String title) { 385 if (connectionConfig != null) { 386 super.setTitle(Bundle.getMessage("ThrottleTitleWithConnection", title, connectionConfig.getConnectionName())); 387 } else { 388 super.setTitle(title); 389 } 390 } 391 392 public void setEditMode(boolean mode) { 393 if (mode == isEditMode) 394 return; 395 isEditMode = mode; 396 if (!throttleFrames.isEmpty()) { 397 for (ThrottleFrame tf: throttleFrames) { 398 tf.setEditMode(isEditMode); 399 } 400 } 401 updateGUI(); 402 } 403 404 public boolean isEditMode() { 405 return isEditMode; 406 } 407 408 public Jynstrument ynstrument(String path) { 409 Jynstrument it = JynstrumentFactory.createInstrument(path, this); 410 if (it == null) { 411 log.error("Error while creating Jynstrument {}", path); 412 return null; 413 } 414 TransparencyUtils.setOpacityRec(it, true); 415 it.setVisible(true); 416 throttleToolBar.add(it); 417 throttleToolBar.repaint(); 418 return it; 419 } 420 421 /** 422 * Set up View, Edit and Power Menus 423 */ 424 private void initializeMenu() { 425 JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile")); 426 427 JMenuItem fileMenuLoad = new JMenuItem(Bundle.getMessage("ThrottleFileMenuLoadThrottle")); 428 fileMenuLoad.addActionListener(new AbstractAction() { 429 430 @Override 431 public void actionPerformed(ActionEvent e) { 432 getCurentThrottleController().loadThrottleFile(null); 433 } 434 }); 435 fileMenuSave = new JMenuItem(Bundle.getMessage("ThrottleFileMenuSaveThrottle")); 436 fileMenuSave.addActionListener(new AbstractAction() { 437 438 @Override 439 public void actionPerformed(ActionEvent e) { 440 getCurentThrottleController().saveThrottle(); 441 } 442 }); 443 JMenuItem fileMenuSaveAs = new JMenuItem(Bundle.getMessage("ThrottleFileMenuSaveAsThrottle")); 444 fileMenuSaveAs.addActionListener(new AbstractAction() { 445 446 @Override 447 public void actionPerformed(ActionEvent e) { 448 getCurentThrottleController().saveThrottleAs(); 449 } 450 }); 451 452 jmri.jmrit.throttle.ThrottleCreationAction.addNewThrottleItemsToThrottleMenu(fileMenu); 453 fileMenu.add(fileMenuLoad); 454 fileMenu.add(fileMenuSave); 455 fileMenu.add(fileMenuSaveAs); 456 fileMenu.addSeparator(); 457 458 fileMenu.add(new jmri.jmrit.throttle.LoadXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadThrottleLayout"))); 459 fileMenu.add(new jmri.jmrit.throttle.StoreXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveThrottleLayout"))); 460 fileMenu.addSeparator(); 461 fileMenu.add(new jmri.jmrit.throttle.LoadDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadDefaultThrottleLayout"))); 462 fileMenu.add(new jmri.jmrit.throttle.StoreDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveAsDefaultThrottleLayout"))); 463 fileMenu.addSeparator(); 464 fileMenu.add(new jmri.jmrit.withrottle.WiThrottleCreationAction(Bundle.getMessage("MenuItemStartWiThrottle"))); 465 466 JMenu viewMenu = new JMenu(Bundle.getMessage("ThrottleMenuView")); 467 viewAddressPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewAddressPanel")); 468 viewAddressPanel.setSelected(true); 469 viewAddressPanel.addItemListener(e -> getCurentThrottleController().setAddressPanelVisible(e.getStateChange() == ItemEvent.SELECTED)); 470 471 viewControlPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewControlPanel")); 472 viewControlPanel.setSelected(true); 473 viewControlPanel.addItemListener(e -> getCurentThrottleController().setControlPanelVisible(e.getStateChange() == ItemEvent.SELECTED)); 474 viewFunctionPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewFunctionPanel")); 475 viewFunctionPanel.setSelected(true); 476 viewFunctionPanel.addItemListener(e -> getCurentThrottleController().setFunctionPanelVisible(e.getStateChange() == ItemEvent.SELECTED)); 477 viewSpeedPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewSpeedPanel")); 478 viewSpeedPanel.setSelected(false); 479 viewSpeedPanel.addItemListener(e -> getCurentThrottleController().setSpeedPanelVisible(e.getStateChange() == ItemEvent.SELECTED)); 480 viewLocoIconPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewLocoIconPanel")); 481 viewLocoIconPanel.setSelected(false); 482 viewLocoIconPanel.addItemListener(e -> getCurentThrottleController().setLocoIconPanelVisible(e.getStateChange() == ItemEvent.SELECTED)); 483 viewConsistFunctionsPanel = new JCheckBoxMenuItem(Bundle.getMessage("ThrottleMenuViewConsistFunctionsPanel")); 484 viewConsistFunctionsPanel.setSelected(false); 485 viewConsistFunctionsPanel.addItemListener(e -> getCurentThrottleController().setConsistFunctionsPanelVisible(e.getStateChange() == ItemEvent.SELECTED)); 486 487 viewAllButtons = new JMenuItem(Bundle.getMessage("ThrottleMenuViewAllFunctionButtons")); 488 viewAllButtons.addActionListener(new AbstractAction() { 489 490 @Override 491 public void actionPerformed(ActionEvent ev) { 492 getCurentThrottleController().resetFunctionPanelButton(); 493 } 494 }); 495 496 JMenuItem makeAllComponentsInBounds = new JMenuItem(Bundle.getMessage("ThrottleMenuViewMakeAllComponentsInBounds")); 497 makeAllComponentsInBounds.addActionListener(new AbstractAction() { 498 499 @Override 500 public void actionPerformed(ActionEvent ev) { 501 getCurentThrottleController().makeAllComponentsInBounds(); 502 } 503 }); 504 505 JMenuItem switchViewMode = new JMenuItem(Bundle.getMessage("ThrottleMenuViewSwitchMode")); 506 switchViewMode.addActionListener(new AbstractAction() { 507 508 @Override 509 public void actionPerformed(ActionEvent ev) { 510 setEditMode(!isEditMode); 511 } 512 }); 513 JMenuItem viewThrottlesList = new JMenuItem(Bundle.getMessage("ThrottleMenuViewViewThrottleList")); 514 viewThrottlesList.addActionListener(new ThrottlesListAction()); 515 516 viewMenu.add(viewAddressPanel); 517 viewMenu.add(viewControlPanel); 518 viewMenu.add(viewFunctionPanel); 519 viewMenu.add(viewSpeedPanel); 520 viewMenu.add(viewLocoIconPanel); 521 viewMenu.add(viewConsistFunctionsPanel); 522 viewMenu.addSeparator(); 523 viewMenu.add(viewAllButtons); 524 viewMenu.add(makeAllComponentsInBounds); 525 viewMenu.addSeparator(); 526 viewMenu.add(switchViewMode); 527 viewMenu.add(viewThrottlesList); 528 529 JMenu editMenu = new JMenu(Bundle.getMessage("MenuEdit")); 530 JMenuItem preferencesItem = new JMenuItem(Bundle.getMessage("ThrottleMenuEditFrameProperties")); 531 editMenu.add(preferencesItem); 532 preferencesItem.addActionListener(e -> editPreferences()); 533 editMenuExportRoster = new JMenuItem(Bundle.getMessage("ThrottleMenuEditSaveCustoms")); 534 editMenu.add(editMenuExportRoster); 535 editMenuExportRoster.addActionListener(e -> getCurentThrottleController().saveRosterChanges()); 536 editMenu.addSeparator(); 537 editMenu.add(new jmri.jmrit.throttle.ThrottlesPreferencesAction(Bundle.getMessage("MenuItemThrottlesPreferences"))); // now in tabbed preferences 538 539 this.setJMenuBar(new JMenuBar()); 540 this.getJMenuBar().add(fileMenu); 541 this.getJMenuBar().add(editMenu); 542 this.getJMenuBar().add(viewMenu); 543 544 if (powerMgr != null) { 545 JMenu powerMenu = new JMenu(Bundle.getMessage("ThrottleMenuPower")); 546 JMenuItem powerOn = new JMenuItem(Bundle.getMessage("ThrottleMenuPowerOn")); 547 powerMenu.add(powerOn); 548 powerOn.addActionListener(e -> { 549 try { 550 powerMgr.setPower(PowerManager.ON); 551 } catch (JmriException e1) { 552 log.error("Error when setting power: ", e1); 553 } 554 }); 555 556 JMenuItem powerOff = new JMenuItem(Bundle.getMessage("ThrottleMenuPowerOff")); 557 powerMenu.add(powerOff); 558 powerOff.addActionListener(e -> { 559 try { 560 powerMgr.setPower(PowerManager.OFF); 561 } catch (JmriException e1) { 562 log.error("Error when setting power: ", e1); 563 } 564 }); 565 566 this.getJMenuBar().add(powerMenu); 567 568 smallPowerMgmtButton = new SmallPowerManagerButton(); 569 this.getJMenuBar().add(smallPowerMgmtButton); 570 } 571 572 // add help selection 573 addHelpMenu("package.jmri.jmrit.throttle.ThrottleFrame", true); 574 } 575 576 private void editPreferences() { 577 ThrottleFramePropertyEditor editor = new ThrottleFramePropertyEditor(this); 578 editor.setVisible(true); 579 } 580 581 /** 582 * Handle my own destruction. 583 * <ol> 584 * <li> dispose of sub windows. 585 * <li> notify my manager of my demise. 586 * </ol> 587 * 588 */ 589 @Override 590 public void dispose() { 591 InstanceManager.getDefault(ThrottlesPreferences.class).removePropertyChangeListener(this); 592 if (throttleToolBar != null) { 593 URIDrop.remove(throttleToolBar); 594 Component[] cmps = throttleToolBar.getComponents(); 595 if (cmps != null) { 596 for (Component cmp : cmps) { 597 if (cmp instanceof Jynstrument) { 598 ((Jynstrument) cmp).exit(); 599 } 600 } 601 } 602 } 603 if ((throttleFrames != null) && (!throttleFrames.isEmpty())) { 604 for (ThrottleFrame tf: throttleFrames) { 605 tf.dispose(); 606 } 607 throttleFrames.clear(); 608 } 609 throttleFrames = null; 610 currentThrottleFrame = null; 611 for (PropertyChangeListener pcl : pcs.getPropertyChangeListeners()) { 612 pcs.removePropertyChangeListener(pcl); 613 } 614 for (MouseWheelListener mwl : getMouseWheelListeners()) { 615 removeMouseWheelListener(mwl); 616 } 617 getRootPane().getActionMap().clear(); 618 throttlesPanel.removeAll(); 619 removeAll(); 620 throttleFrameManager.requestThrottleWindowDestruction(this); 621 super.dispose(); 622 } 623 624 public JCheckBoxMenuItem getViewControlPanel() { 625 return viewControlPanel; 626 } 627 628 public JCheckBoxMenuItem getViewFunctionPanel() { 629 return viewFunctionPanel; 630 } 631 632 public JCheckBoxMenuItem getViewAddressPanel() { 633 return viewAddressPanel; 634 } 635 636 public JCheckBoxMenuItem getViewSpeedPanel() { 637 return viewSpeedPanel; 638 } 639 640 public JCheckBoxMenuItem getViewLocoIconPanel() { 641 return viewLocoIconPanel; 642 } 643 644 public JCheckBoxMenuItem getViewConsistFunctionsPanel() { 645 return viewConsistFunctionsPanel; 646 } 647 648 private void updateCurentThrottleFrame() { 649 currentThrottleFrame = null; 650 for (Component comp : throttlesPanel.getComponents()) { 651 if (comp instanceof ThrottleFrame && comp.isVisible()) { 652 currentThrottleFrame = (ThrottleFrame) comp; 653 } 654 } 655 } 656 657 @Override 658 public ThrottleFrame getCurentThrottleController() { 659 return currentThrottleFrame; 660 } 661 662 public void setCurrentThrottleFrame(ThrottleFrame tf) { 663 pcs.firePropertyChange("ThrottleFrameChanged", getCurentThrottleController(), tf); 664 currentThrottleFrame = tf; 665 } 666 667 @Override 668 public void removeThrottleController(ThrottleControllerUI tf) { 669 if (!(tf instanceof ThrottleFrame)) { 670 throw new IllegalArgumentException("Only ThrottleFrame can be removed from ThrottleWindow"); 671 } 672 if (getCurentThrottleController() == tf) { 673 log.debug("Closing currently active throttle frame"); 674 throttlesLayout.previous(throttlesPanel); 675 } 676 throttlesPanel.remove((ThrottleFrame)tf); 677 throttleFrames.remove(tf); 678 throttlesLayout.invalidateLayout(throttlesPanel); 679 updateGUI(); 680 updateCurentThrottleFrame(); 681 pcs.firePropertyChange("ThrottleFrameRemoved", tf, getCurentThrottleController()); 682 } 683 684 /** 685 * Set next throttle frame as current frame. If the current frame is the only one, then do nothing. 686 * 687 */ 688 public void nextThrottleFrame() { 689 ThrottleFrame otf = getCurentThrottleController(); 690 throttlesLayout.next(throttlesPanel); 691 updateCurentThrottleFrame(); 692 updateGUI(); 693 pcs.firePropertyChange("ThrottleFrameChanged", otf, getCurentThrottleController()); 694 } 695 696 /** 697 * Set previous throttle frame as current frame. If the current frame is the only one, then do nothing. 698 * 699 */ 700 public void previousThrottleFrame() { 701 ThrottleFrame otf = getCurentThrottleController(); 702 throttlesLayout.previous(throttlesPanel); 703 updateCurentThrottleFrame(); 704 updateGUI(); 705 pcs.firePropertyChange("ThrottleFrameChanged", otf, getCurentThrottleController()); 706 } 707 708 709 /** 710 * Set next running (with non null speed) throttle frame as current frame. If the current frame is the only one running, then do nothing. 711 * 712 */ 713 public void nextRunningThrottleFrame() { 714 if (!throttleFrames.isEmpty()) { 715 ThrottleFrame cf = this.getCurentThrottleController(); 716 ThrottleFrame nf = null; 717 boolean passed = false; 718 for (ThrottleFrame tf : throttleFrames) { 719 if (tf != cf) { 720 if (tf.isRunning()) { 721 if (passed) { // if we passed the curent one, and found something then return it 722 nf = tf; 723 break; 724 } else if (nf == null) { 725 nf = tf; 726 } 727 } 728 } else { 729 passed = true; 730 } 731 } 732 if (nf != null) { 733 nf.toFront(); 734 updateCurentThrottleFrame(); 735 pcs.firePropertyChange("ThrottleFrameChanged", cf, nf); 736 } 737 } 738 } 739 740 /** 741 * Set previous running (with non null speed) throttle frame as current frame. If the current frame is the only one running, then do nothing. 742 * 743 */ 744 public void previousRunningThrottleFrame() { 745 if (!throttleFrames.isEmpty()) { 746 ThrottleFrame cf = this.getCurentThrottleController(); 747 ThrottleFrame nf = null; 748 for (ThrottleFrame tf : throttleFrames) { 749 if (tf.isRunning()) { 750 nf = tf; 751 } 752 if ((tf == cf) && (nf != null)) { // return the last one found before the curent one 753 break; 754 } 755 } 756 if (nf != null) { 757 nf.toFront(); 758 updateCurentThrottleFrame(); 759 pcs.firePropertyChange("ThrottleFrameChanged", cf, nf); 760 } 761 } 762 } 763 764 /** 765 * Set next throttle frame with active function (at least one function ON) or non null speed as current frame. If the current frame is the only one with active function, then do nothing. 766 * 767 */ 768 public void nextThrottleFrameWithActiveFunction() { 769 if (!throttleFrames.isEmpty()) { 770 ThrottleFrame cf = this.getCurentThrottleController(); 771 ThrottleFrame nf = null; 772 boolean passed = false; 773 for (ThrottleFrame tf : throttleFrames) { 774 if (tf != cf) { 775 if (tf.isActive()) { 776 if (passed) { // if we passed the curent one, and found something then return it 777 nf = tf; 778 break; 779 } else if (nf == null) { 780 nf = tf; 781 } 782 } 783 } else { 784 passed = true; 785 } 786 } 787 if (nf != null) { 788 nf.toFront(); 789 updateCurentThrottleFrame(); 790 pcs.firePropertyChange("ThrottleFrameChanged", cf, nf); 791 } 792 } 793 } 794 795 /** 796 * Set previous throttle frame with active function (at least one function ON) or non null speed as current frame. If the current frame is the only one with active function, then do nothing. 797 * 798 */ 799 public void previousThrottleFrameWithActiveFunction() { 800 if (!throttleFrames.isEmpty()) { 801 ThrottleFrame cf = this.getCurentThrottleController(); 802 ThrottleFrame nf = null; 803 for (ThrottleFrame tf : throttleFrames) { 804 if ((tf != cf) && tf.isActive()) { 805 nf = tf; 806 } 807 if ((tf == cf) && (nf != null)) { // return the last one found before the curent one 808 break; 809 } 810 } 811 if (nf != null) { 812 nf.toFront(); 813 updateCurentThrottleFrame(); 814 pcs.firePropertyChange("ThrottleFrameChanged", cf, nf); 815 } 816 } 817 } 818 819 private void removeAndDisposeCurentThrottleFrame() { 820 ThrottleFrame tf = getCurentThrottleController(); 821 removeThrottleController(getCurentThrottleController()); 822 tf.dispose(); 823 } 824 825 @Override 826 public void addThrottleControllerAt(ThrottleControllerUI tp, int idx) { 827 if (!(tp instanceof ThrottleFrame)) { 828 throw new IllegalArgumentException("Only ThrottleFrame supported in ThrottleWindow"); 829 } 830 831 String txt = "ThrottleJDesktopPane-" + throttleFrameManager.generateUniqueFrameID(); 832 ((ThrottleFrame)tp).setTitle(txt); 833 if (idx>throttleFrames.size()) { 834 idx = throttleFrames.size(); 835 } 836 throttleFrames.add(idx,(ThrottleFrame)tp); 837 throttlesPanel.add((ThrottleFrame)tp,txt,idx); 838 ((ThrottleFrame)tp).setEditMode(isEditMode); // sync with window 839 updateGUI(); 840 pcs.firePropertyChange("ThrottleFrameAdded", null, this); 841 } 842 843 @Override 844 public ThrottleControllerUI newThrottleController() { 845 ThrottleFrame otf = getCurentThrottleController(); 846 ThrottleFrame tf = new ThrottleFrame(this, throttleManager); 847 throttleFrames.add(tf); 848 String txt = "ThrottleJDesktopPane-" + throttleFrameManager.generateUniqueFrameID(); 849 tf.setTitle(txt); 850 throttlesPanel.add(tf, txt); 851 tf.setEditMode(isEditMode); // sync with window 852 installInputsListenerOnAllComponents(tf); 853 throttlesLayout.show(throttlesPanel, txt); 854 setCurrentThrottleFrame(tf); 855 updateGUI(); 856 pcs.firePropertyChange("ThrottleFrameNew", otf, getCurentThrottleController()); 857 return getCurentThrottleController(); 858 } 859 860 public void toFront(String throttleFrameTitle) { 861 ThrottleFrame otf = getCurentThrottleController(); 862 throttlesLayout.show(throttlesPanel, throttleFrameTitle); 863 updateCurentThrottleFrame(); 864 setVisible(true); 865 requestFocus(); 866 toFront(); 867 pcs.firePropertyChange("ThrottleFrameChanged", otf, getCurentThrottleController()); 868 } 869 870 public String getTitleTextType() { 871 return titleTextType; 872 } 873 874 public String getTitleText() { 875 return titleText; 876 } 877 878 public void setTitleText(String titleText) { 879 this.titleText = titleText; 880 } 881 882 public void setTitleTextType(String titleTextType) { 883 this.titleTextType = titleTextType; 884 } 885 886 public Element getXml() { 887 Element me = new Element("ThrottleWindow"); 888 if (connectionConfig != null) { 889 me.setAttribute("systemPrefix", connectionConfig.getAdapter().getSystemPrefix()); 890 } 891 me.setAttribute("title", titleText); 892 me.setAttribute("titleType", titleTextType); 893 me.setAttribute("isEditMode", String.valueOf(isEditMode)); 894 895 java.util.ArrayList<Element> children = new java.util.ArrayList<>(1); 896 children.add(WindowPreferences.getPreferences(this)); 897 if (!throttleFrames.isEmpty()) { 898 ThrottleFrame cf = this.getCurentThrottleController(); 899 for (ThrottleFrame tf : throttleFrames) { 900 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) && (InstanceManager.getDefault(ThrottlesPreferences.class).isSavingThrottleOnLayoutSave())) { 901 tf.toFront(); 902 tf.saveThrottle(); 903 } 904 Element tfe = tf.getXmlFile(); 905 if (tfe == null) { 906 tfe = tf.getXml(); 907 } 908 children.add(tfe); 909 } 910 if (cf != null) { 911 cf.toFront(); 912 } 913 } 914 915 // Save Jynstruments 916 if (throttleToolBar != null) { 917 Component[] cmps = throttleToolBar.getComponents(); 918 if (cmps != null) { 919 for (Component cmp : cmps) { 920 try { 921 if (cmp instanceof Jynstrument) { 922 Jynstrument jyn = (Jynstrument) cmp; 923 Element elt = new Element("Jynstrument"); 924 elt.setAttribute("JynstrumentFolder", FileUtil.getPortableFilename(jyn.getFolder())); 925 Element je = jyn.getXml(); 926 if (je != null) { 927 java.util.ArrayList<Element> jychildren = new java.util.ArrayList<>(1); 928 jychildren.add(je); 929 elt.setContent(jychildren); 930 } 931 children.add(elt); 932 } 933 934 } catch (Exception ex) { 935 log.debug("Got exception (no panic): ", ex); 936 } 937 } 938 } 939 } 940 me.setContent(children); 941 return me; 942 } 943 944 private void setXml(Element e) { 945 if (e.getAttribute("title") != null) { 946 setTitle(e.getAttribute("title").getValue()); 947 } 948 if (e.getAttribute("title") != null) { 949 setTitleText(e.getAttribute("title").getValue()); 950 } 951 if (e.getAttribute("titleType") != null) { 952 setTitleTextType(e.getAttribute("titleType").getValue()); 953 } 954 if (e.getAttribute("isEditMode") != null) { 955 isEditMode = Boolean.parseBoolean(e.getAttribute("isEditMode").getValue()); 956 } 957 958 Element window = e.getChild("window"); 959 if (window != null) { 960 WindowPreferences.setPreferences(this, window); 961 } 962 963 List<Element> tfes = e.getChildren("ThrottleFrame"); 964 if ((tfes != null) && (!tfes.isEmpty())) { 965 for (int i = 0; i < tfes.size(); i++) { 966 ThrottleFrame tf; 967 if (i == 0) { 968 tf = getCurentThrottleController(); 969 } else { 970 tf = (ThrottleFrame) newThrottleController(); 971 } 972 tf.setXml(tfes.get(i)); 973 tf.setEditMode(isEditMode); 974 } 975 } 976 977 List<Element> jinsts = e.getChildren("Jynstrument"); 978 if ((jinsts != null) && (!jinsts.isEmpty())) { 979 jinsts.forEach((jinst) -> { 980 Jynstrument jyn = ynstrument(FileUtil.getExternalFilename(jinst.getAttributeValue("JynstrumentFolder"))); 981 if (jyn != null) { 982 jyn.setXml(jinst); 983 } 984 }); 985 } 986 987 updateGUI(); 988 } 989 990 @Override 991 public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) { 992 pcs.addPropertyChangeListener(l); 993 } 994 995 @Override 996 public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) { 997 pcs.removePropertyChangeListener(l); 998 } 999 1000 private void installInputsListenerOnAllComponents(Container c) { 1001 c.setFocusTraversalKeysEnabled(false); // make tab and shift tab available 1002 if (! ( c instanceof JTextField)) { 1003 c.setFocusable(false); 1004 } 1005 for (Component component : c.getComponents()) { 1006 if (component instanceof Container) { 1007 installInputsListenerOnAllComponents( (Container) component); 1008 } else { 1009 if (! ( component instanceof JTextField)) { 1010 component.setFocusable(false); 1011 } 1012 } 1013 } 1014 } 1015 1016 private void applyPreferences() { 1017 ThrottlesPreferences preferences = InstanceManager.getDefault(ThrottlesPreferences.class); 1018 // inputs 1019 ComponentInputMap im = new ComponentInputMap(getRootPane()); 1020 for (Object k : this.getRootPane().getActionMap().allKeys()) { 1021 KeyStroke[] kss = preferences.getThrottlesKeyboardControls().getKeyStrokes((String)k); 1022 if (kss !=null) { 1023 for (KeyStroke keystroke : kss) { 1024 if (keystroke != null) { 1025 im.put(keystroke, k); 1026 } 1027 } 1028 } 1029 } 1030 getRootPane().setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW,im); 1031 // UI elements 1032 throttleToolBar.setVisible ( preferences.isUsingExThrottle() && preferences.isUsingToolBar() ); 1033 if (smallPowerMgmtButton != null) { 1034 smallPowerMgmtButton.setVisible( (!preferences.isUsingExThrottle()) || (!preferences.isUsingToolBar()) ); 1035 } 1036 if (! preferences.isUsingExThrottle()) { 1037 setEditMode(true); 1038 } 1039 } 1040 1041 @Override 1042 public void propertyChange(PropertyChangeEvent evt) { 1043 if (ThrottlesPreferences.prefPopertyName.compareTo(evt.getPropertyName()) == 0) { 1044 applyPreferences(); 1045 } 1046 } 1047 1048 private static final Logger log = LoggerFactory.getLogger(ThrottleWindow.class); 1049}