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