001package jmri.jmrit.logix; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.awt.BorderLayout; 005import java.awt.event.ActionEvent; 006import java.awt.event.ActionListener; 007import java.awt.Point; 008import java.util.ArrayList; 009import java.util.List; 010 011import javax.annotation.Nonnull; 012import javax.swing.AbstractAction; 013import javax.swing.Box; 014import javax.swing.BoxLayout; 015import javax.swing.JButton; 016import javax.swing.JDialog; 017import javax.swing.JMenu; 018import javax.swing.JMenuItem; 019import javax.swing.JPanel; 020import javax.swing.JScrollPane; 021import javax.swing.JTextArea; 022import jmri.InstanceManager; 023import jmri.InvokeOnGuiThread; 024import jmri.Path; 025 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * A WarrantAction contains the operating permissions and directives needed for 031 * a train to proceed from an Origin to a Destination. WarrantTableAction 032 * provides the menu for panels to List, Edit and Create Warrants. It launches 033 * the appropriate frame for each action. 034 * <br> 035 * <hr> 036 * This file is part of JMRI. 037 * <p> 038 * JMRI is free software; you can redistribute it and/or modify it under the 039 * terms of version 2 of the GNU General Public License as published by the Free 040 * Software Foundation. See the "COPYING" file for a copy of this license. 041 * <p> 042 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 043 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 044 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 045 * 046 * @author Pete Cressman Copyright (C) 2009, 2010 047 **/ 048public class WarrantTableAction extends AbstractAction { 049 050 static int STRUT_SIZE = 10; 051 private JMenu _warrantMenu; 052 053 private boolean _hasErrors = false; 054 private JDialog _errorDialog; 055 private WarrantFrame _openFrame; 056 private Point _warFrameLoc = new Point(20,20); 057 private NXFrame _nxFrame; 058 private Point _nxFrameLoc = new Point(40,40); 059 private boolean _logging = false; 060 private Runnable _shutDownTask = null; 061 062 private WarrantTableAction(String menuOption) { 063 super(Bundle.getMessage(menuOption)); 064 } 065 066 public static WarrantTableAction getDefault() { 067 return InstanceManager.getOptionalDefault(WarrantTableAction.class).orElseGet(() -> { 068 WarrantTableAction wta = new WarrantTableAction("ShowWarrants"); // NOI18N 069 wta.errorCheck(); 070 return InstanceManager.setDefault(WarrantTableAction.class, wta); 071 }); 072 } 073 074 @Override 075 @InvokeOnGuiThread 076 public void actionPerformed(ActionEvent e) { 077 WarrantTableFrame.getDefault().setVisible(true); 078 } 079 080 /** 081 * @param edit true if portal errors should be shown in window created from 082 * menu item 083 * @return a menu containing warrant actions 084 */ 085 public JMenu makeWarrantMenu(boolean edit) { 086 if (jmri.InstanceManager.getDefault(OBlockManager.class).getNamedBeanSet().size() > 1) { 087 synchronized (this) { 088 _warrantMenu = new JMenu(Bundle.getMessage("MenuWarrant")); 089 updateWarrantMenu(); 090 return _warrantMenu; 091 } 092 } 093 return null; 094 } 095 096 @InvokeOnGuiThread 097 synchronized protected void updateWarrantMenu() { 098 _warrantMenu.removeAll(); 099 _warrantMenu.add(getDefault()); 100 JMenu editWarrantMenu = new JMenu(Bundle.getMessage("EditWarrantMenu")); 101 _warrantMenu.add(editWarrantMenu); 102 ActionListener editWarrantAction = (ActionEvent e) -> openWarrantFrame(e.getActionCommand()); 103 WarrantManager manager = InstanceManager.getDefault(WarrantManager.class); 104 if (manager.getObjectCount() == 0) { // when there are no Warrants, enter the word "None" to the submenu 105 JMenuItem _noWarrants = new JMenuItem(Bundle.getMessage("None")); 106 editWarrantMenu.add(_noWarrants); 107 // disable it 108 _noWarrants.setEnabled(false); 109 } else { // when there are warrants, add them to the submenu 110 for (Warrant warrant : manager.getNamedBeanSet()) { 111 // Warrant warrent = (Warrant) object; 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 jmri.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 jmri.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}