001package jmri.jmrit.logix; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.BorderLayout; 006import java.awt.event.ActionEvent; 007import java.awt.event.ActionListener; 008import java.awt.Point; 009import java.util.ArrayList; 010import java.util.List; 011 012import javax.annotation.Nonnull; 013import javax.swing.AbstractAction; 014import javax.swing.Box; 015import javax.swing.BoxLayout; 016import javax.swing.JButton; 017import javax.swing.JDialog; 018import javax.swing.JMenu; 019import javax.swing.JMenuItem; 020import javax.swing.JPanel; 021import javax.swing.JScrollPane; 022import javax.swing.JTextArea; 023 024import jmri.InstanceManager; 025import jmri.InvokeOnGuiThread; 026import jmri.Path; 027import jmri.util.ThreadingUtil; 028 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032/** 033 * A WarrantAction contains the operating permissions and directives needed for 034 * a train to proceed from an Origin to a Destination. WarrantTableAction 035 * provides the menu for panels to List, Edit and Create Warrants. It launches 036 * the appropriate frame for each action. 037 * <br> 038 * <hr> 039 * This file is part of JMRI. 040 * <p> 041 * JMRI is free software; you can redistribute it and/or modify it under the 042 * terms of version 2 of the GNU General Public License as published by the Free 043 * Software Foundation. See the "COPYING" file for a copy of this license. 044 * <p> 045 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 046 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 047 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 048 * 049 * @author Pete Cressman Copyright (C) 2009, 2010 050 **/ 051public class WarrantTableAction extends AbstractAction { 052 053 private JMenu _warrantMenu; 054 055 private boolean _hasErrors = false; 056 private JDialog _errorDialog; 057 private WarrantFrame _openFrame; 058 private Point _warFrameLoc = new Point(20,20); 059 private NXFrame _nxFrame; 060 private Point _nxFrameLoc = new Point(40,40); 061 private boolean _logging = false; 062 private Runnable _shutDownTask = null; 063 064 private WarrantTableAction(String menuOption) { 065 super(Bundle.getMessage(menuOption)); 066 } 067 068 public static WarrantTableAction getDefault() { 069 return InstanceManager.getOptionalDefault(WarrantTableAction.class).orElseGet(() -> { 070 WarrantTableAction wta = new WarrantTableAction("ShowWarrants"); // NOI18N 071 wta.errorCheck(); 072 return InstanceManager.setDefault(WarrantTableAction.class, wta); 073 }); 074 } 075 076 @Override 077 @InvokeOnGuiThread 078 public void actionPerformed(ActionEvent e) { 079 WarrantTableFrame.getDefault().setVisible(true); 080 } 081 082 /** 083 * @param edit true if portal errors should be shown in window created from 084 * menu item 085 * @return a menu containing warrant actions 086 */ 087 public JMenu makeWarrantMenu(boolean edit) { 088 if ( InstanceManager.getDefault(OBlockManager.class).getNamedBeanSet().size() > 1) { 089 synchronized (this) { 090 _warrantMenu = new JMenu(Bundle.getMessage("MenuWarrant")); 091 updateWarrantMenu(); 092 return _warrantMenu; 093 } 094 } 095 return null; 096 } 097 098 @InvokeOnGuiThread 099 protected synchronized void updateWarrantMenu() { 100 _warrantMenu.removeAll(); 101 _warrantMenu.add(getDefault()); 102 JMenu editWarrantMenu = new JMenu(Bundle.getMessage("EditWarrantMenu")); 103 _warrantMenu.add(editWarrantMenu); 104 ActionListener editWarrantAction = (ActionEvent e) -> openWarrantFrame(e.getActionCommand()); 105 WarrantManager manager = InstanceManager.getDefault(WarrantManager.class); 106 if (manager.getObjectCount() == 0) { // when there are no Warrants, enter the word "None" to the submenu 107 JMenuItem noWarrants = new JMenuItem(Bundle.getMessage("None")); 108 editWarrantMenu.add(noWarrants); 109 // disable it 110 noWarrants.setEnabled(false); 111 } else { // when there are warrants, add them to the submenu 112 for (Warrant warrant : manager.getNamedBeanSet()) { 113 JMenuItem mi = new JMenuItem(warrant.getDisplayName()); 114 mi.setActionCommand(warrant.getDisplayName()); 115 mi.addActionListener(editWarrantAction); 116 editWarrantMenu.add(mi); 117 } 118 } 119 _warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateWarrant")) { 120 @Override 121 public void actionPerformed(ActionEvent e) { 122 makeWarrantFrame(null, null); 123 } 124 }); 125 _warrantMenu.add(InstanceManager.getDefault(TrackerTableAction.class)); 126 _warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateNXWarrant")) { 127 @Override 128 public void actionPerformed(ActionEvent e) { 129 makeNXFrame(); 130 } 131 }); 132 _warrantMenu.add(makeLogMenu()); 133 134 log.debug("updateMenu to {} warrants.", manager.getObjectCount()); 135 } 136 137 protected JMenuItem makeLogMenu() { 138 JMenuItem mi; 139 if (!_logging) { 140 mi = new JMenuItem(Bundle.getMessage("startLog")); 141 mi.addActionListener((ActionEvent e) -> { 142 if (!OpSessionLog.makeLogFile(WarrantTableFrame.getDefault())) { 143 return; 144 } 145 _logging = true; 146 _shutDownTask = () -> { 147 OpSessionLog.close(); 148 _logging = false; 149 }; 150 InstanceManager.getDefault(jmri.ShutDownManager.class).register(_shutDownTask); 151 updateWarrantMenu(); 152 }); 153 } else { 154 mi = new JMenuItem(Bundle.getMessage("flushLog")); 155 mi.addActionListener((ActionEvent e) -> OpSessionLog.flush()); 156 _warrantMenu.add(mi); 157 mi = new JMenuItem(Bundle.getMessage("stopLog")); 158 mi.addActionListener((ActionEvent e) -> { 159 OpSessionLog.close(); 160 InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(_shutDownTask); 161 _shutDownTask = null; 162 _logging = false; 163 updateWarrantMenu(); 164 }); 165 } 166 return mi; 167 } 168 169 synchronized protected void writetoLog(String text) { 170 if (_logging) { 171 OpSessionLog.writeLn(text); 172 } 173 } 174 175 @InvokeOnGuiThread 176 protected void closeNXFrame() { 177 if (_nxFrame != null) { 178 _nxFrame.clearTempWarrant(); 179 _nxFrameLoc = _nxFrame.getLocation(); 180 _nxFrame.dispose(); 181 _nxFrame = null; 182 } 183 } 184 185 @InvokeOnGuiThread 186 protected void makeNXFrame() { 187 closeWarrantFrame(); 188 if (_nxFrame == null) { 189 _nxFrame = new NXFrame(); 190 } 191 _nxFrame.setState(java.awt.Frame.NORMAL); 192 _nxFrame.setVisible(true); 193 _nxFrameLoc.setLocation(_nxFrameLoc); 194 _nxFrame.toFront(); 195 } 196 197 @InvokeOnGuiThread 198 protected boolean closeWarrantFrame() { 199 if (_openFrame != null) { 200 if (!_openFrame.askClose()) { 201 return false; 202 } 203 _warFrameLoc = _openFrame.getLocation(); 204 _openFrame.close(); 205 _openFrame = null; 206 } 207 return true; 208 } 209 210 protected void makeWarrantFrame(Warrant startW, Warrant endW) { 211 if (!closeWarrantFrame()) { 212 return; 213 } 214 closeNXFrame(); 215 _openFrame = new WarrantFrame(startW, endW); 216 _openFrame.setState(java.awt.Frame.NORMAL); 217 _openFrame.toFront(); 218 } 219 220 protected void editWarrantFrame(Warrant w) { 221 if (!closeWarrantFrame()) { 222 return; 223 } 224 closeNXFrame(); 225 _openFrame = new WarrantFrame(w); 226 _openFrame.setState(java.awt.Frame.NORMAL); 227 _openFrame.toFront(); 228 _openFrame.setLocation(_warFrameLoc); 229 } 230 231 private void openWarrantFrame(String key) { 232 Warrant w = InstanceManager.getDefault(WarrantManager.class).getWarrant(key); 233 if (w != null) { 234 editWarrantFrame(w); 235 } 236 } 237 238 synchronized public void mouseClickedOnBlock(OBlock block) { 239 if (block == null) { 240 return; 241 } 242 243 if (_openFrame != null) { 244 _openFrame.mouseClickedOnBlock(block); 245 return; 246 } 247 248 if (_nxFrame != null && _nxFrame.isVisible() && _nxFrame.isRouteSeaching()) { 249 _nxFrame.mouseClickedOnBlock(block); 250 return; 251 } 252 253 InstanceManager.getDefault(TrackerTableAction.class).mouseClickedOnBlock(block); 254 } 255 256 protected WarrantFrame getOpenFrame() { 257 return _openFrame; 258 } 259 260 /* ****************** Error checking ************************/ 261 public boolean errorCheck() { 262 _hasErrors = false; 263 javax.swing.JTextArea textArea = new javax.swing.JTextArea(10, 50); 264 textArea.setEditable(false); 265 textArea.setTabSize(4); 266 textArea.append(Bundle.getMessage("ErrWarnAreaMsg")); 267 textArea.append("\n\n"); 268 OBlockManager manager = InstanceManager.getDefault(OBlockManager.class); 269 for (OBlock block : manager.getNamedBeanSet()) { 270 textArea.append(checkPathPortals(block)); 271 } 272 return ThreadingUtil.runOnGUIwithReturn( () -> showPathPortalErrors(textArea)); 273 } 274 275 /** 276 * Validation of paths within a block. Gathers messages in a text area that 277 * can be displayed after all are written. 278 * 279 * @param b the block to validate 280 * @return error/warning message, if any 281 */ 282 @Nonnull 283 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path") 284 public String checkPathPortals(OBlock b) { 285 if (log.isDebugEnabled()) { 286 log.debug("checkPathPortals for {}", b.getDisplayName()); 287 } 288 StringBuilder sb = new StringBuilder(); 289 List<Path> pathList = b.getPaths(); 290 if (pathList.isEmpty()) { 291 if (b.getPortals().isEmpty()) { 292 sb.append(Bundle.getMessage("NoPortals")); 293 sb.append(" "); 294 } 295 sb.append(Bundle.getMessage("NoPaths", b.getDisplayName())); 296 sb.append("\n"); 297 _hasErrors = true; 298 return sb.toString(); 299 } 300 List<Portal> portalList = b.getPortals(); 301 // make list of names of all portals. Then remove those we check, leaving the orphans 302 ArrayList<String> portalNameList = new ArrayList<>(); 303 for (Portal portal : portalList) { 304 if (portal.getFromPaths().isEmpty()) { 305 sb.append(Bundle.getMessage("BlockPortalNoPath", portal.getName(), portal.getFromBlockName())); 306 sb.append("\n"); 307 _hasErrors = true; 308 return sb.toString(); 309 } 310 if (portal.getToPaths().isEmpty()) { 311 sb.append(Bundle.getMessage("BlockPortalNoPath", portal.getName(), portal.getToBlockName())); 312 sb.append("\n"); 313 _hasErrors = true; 314 return sb.toString(); 315 } 316 portalNameList.add(portal.getName()); 317 } 318 for (Path value : pathList) { 319 OPath path = (OPath) value; 320 OBlock block = (OBlock) path.getBlock(); 321 if (block == null || !block.equals(b)) { 322 sb.append(Bundle.getMessage("PathWithBadBlock", path.getName(), b.getDisplayName())); 323 sb.append("\n"); 324 _hasErrors = true; 325 return sb.toString(); 326 } 327 String msg = null; 328 boolean hasPortal = false; 329 Portal fromPortal = path.getFromPortal(); 330 if (fromPortal != null) { 331 if (!fromPortal.isValid()) { 332 msg = fromPortal.getName(); 333 } 334 hasPortal = true; 335 portalNameList.remove(fromPortal.getName()); 336 } 337 Portal toPortal = path.getToPortal(); 338 if (toPortal != null) { 339 if (!toPortal.isValid()) { 340 msg = toPortal.getName(); 341 } 342 hasPortal = true; 343 portalNameList.remove(toPortal.getName()); 344 if (fromPortal != null && fromPortal.equals(toPortal)) { 345 sb.append(Bundle.getMessage("PathWithDuplicatePortal", path.getName(), b.getDisplayName())); 346 sb.append("\n"); 347 } 348 } 349 if (msg != null) { 350 sb.append(Bundle.getMessage("PortalNeedsBlock", msg)); 351 sb.append("\n"); 352 _hasErrors = true; 353 } else if (!hasPortal) { 354 sb.append(Bundle.getMessage("PathNeedsPortal", path.getName(), b.getDisplayName())); 355 sb.append("\n"); 356 _hasErrors = true; 357 } 358 // check that the path's portals have the path in their lists 359 boolean validPath; 360 if (toPortal != null) { 361 if (fromPortal != null) { 362 validPath = toPortal.isValidPath(path) && fromPortal.isValidPath(path); 363 } else { 364 validPath = toPortal.isValidPath(path); 365 } 366 } else { 367 if (fromPortal != null) { 368 validPath = fromPortal.isValidPath(path); 369 } else { 370 validPath = false; 371 } 372 } 373 if (!validPath) { 374 sb.append(Bundle.getMessage("PathNotConnectedToPortal", path.getName(), b.getDisplayName())); 375 sb.append("\n"); 376 _hasErrors = true; 377 } 378 } 379 for (String s : portalNameList) { 380 sb.append(Bundle.getMessage("BlockPortalNoPath", s, b.getDisplayName())); 381 sb.append("\n"); 382 _hasErrors = true; 383 } 384 // check whether any turnouts are shared between two blocks; 385 return sb.toString(); 386 } 387 388 public boolean showPathPortalErrors(JTextArea textArea) { 389 if (_errorDialog != null) { 390 _errorDialog.dispose(); 391 } 392 if (!_hasErrors) { 393 return false; 394 } 395 JScrollPane scrollPane = new JScrollPane(textArea); 396 _errorDialog = new JDialog(); 397 _errorDialog.setTitle(Bundle.getMessage("ErrorDialogTitle")); 398 JButton ok = new JButton(Bundle.getMessage("ButtonOK")); 399 class myListener extends java.awt.event.WindowAdapter implements ActionListener { 400 401 @Override 402 public void actionPerformed(ActionEvent e) { 403 _errorDialog.dispose(); 404 } 405 406 @Override 407 public void windowClosing(java.awt.event.WindowEvent e) { 408 _errorDialog.dispose(); 409 } 410 } 411 ok.addActionListener(new myListener()); 412 ok.setMaximumSize(ok.getPreferredSize()); 413 414 java.awt.Container contentPane = _errorDialog.getContentPane(); 415 contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); 416 contentPane.add(scrollPane, BorderLayout.CENTER); 417 contentPane.add(Box.createVerticalStrut(5)); 418 contentPane.add(Box.createVerticalGlue()); 419 JPanel panel = new JPanel(); 420 panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); 421 panel.add(ok); 422 contentPane.add(panel, BorderLayout.SOUTH); 423 _errorDialog.addWindowListener(new myListener()); 424 _errorDialog.pack(); 425 _errorDialog.setVisible(true); 426 return true; 427 } 428 429 private final static Logger log = LoggerFactory.getLogger(WarrantTableAction.class); 430 431}