001package jmri.jmrix.loconet.swing; 002 003import javax.swing.Action; 004import javax.swing.JMenu; 005import javax.swing.JSeparator; 006 007import java.util.ArrayList; 008 009import jmri.jmrix.loconet.locomon.LocoMonPane; 010import jmri.jmrix.loconet.slotmon.SlotMonPane; 011import jmri.jmrix.loconet.clockmon.ClockMonPane; 012import jmri.jmrix.loconet.locostats.swing.LocoStatsPanel; 013import jmri.jmrix.loconet.bdl16.BDL16Panel; 014import jmri.jmrix.loconet.pm4.PM4Panel; 015import jmri.jmrix.loconet.se8.SE8Panel; 016import jmri.jmrix.loconet.ds64.Ds64TabbedPanel; 017import jmri.jmrix.loconet.cmdstnconfig.CmdStnConfigPane; 018import jmri.jmrix.loconet.locoid.LocoIdPanel; 019import jmri.jmrix.loconet.duplexgroup.swing.DuplexGroupTabbedPanel; 020import jmri.jmrix.loconet.swing.throttlemsg.MessagePanel; 021import jmri.jmrix.loconet.locogen.LocoGenPanel; 022import jmri.jmrix.loconet.swing.lncvprog.LncvProgPane; 023import jmri.jmrix.loconet.pr3.swing.Pr3SelectPane; 024import jmri.jmrix.loconet.soundloader.LoaderPane; 025import jmri.jmrix.loconet.soundloader.EditorPane; 026import jmri.jmrix.loconet.loconetovertcp.LnTcpServerAction; 027import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 028import jmri.jmrix.loconet.LnCommandStationType; 029import jmri.jmrix.loconet.swing.menuitemspi.MenuItemsService; 030 031import jmri.util.swing.WindowInterface; 032import jmri.util.swing.sdi.JmriJFrameInterface; 033 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037 038/** 039 * Create a "Systems" menu containing the Jmri LocoNet-specific tools. 040 * 041 * @author Bob Jacobsen Copyright 2003, 2010 042 * @author B. Milhaupt Copyright 2021, 2022 043 */ 044public class LocoNetMenu extends JMenu { 045 private boolean lastWasSeparator; 046 047 048 049 /** 050 * Create a LocoNet menu. 051 * <br> 052 * Adds menu items for JMRI code's LocoNet menu items, as defined in 053 * the initialization of <code>panelItems</code> here, and appends those 054 * menu items from SPI extensions which implement 055 * {@link jmri.jmrix.loconet.swing.menuitemspi.spi.MenuItemsInterface} 056 * to report additional menu items for inclusion on the LocoNet menu. 057 * <br> 058 * This method pre-loads the TrafficController to certain actions. 059 * <br> 060 * Actions will open new windows. 061 *<br> 062 * @param memo {@link jmri.jmrix.loconet.LocoNetSystemConnectionMemo} to 063 * be used by this object 064 * @see jmri.jmrix.loconet.swing.menuitemspi.spi.MenuItemsInterface 065 * @see jmri.jmrix.loconet.swing.menuitemspi.MenuItemsService 066 */ 067 public LocoNetMenu(LocoNetSystemConnectionMemo memo) { 068 super(); 069 ArrayList<LocoNetMenuItem> panelItems; 070 panelItems = new ArrayList<>(); 071 072 // Define the common allExtensionItems in the LocoNet menu. Note that 073 // LnMessageServer and LnTcpServer are special-cased because they have no 074 // GUI interface and are handled slightly differently by processItems(). 075 076 panelItems.add(new LocoNetMenuItem("MenuItemLocoNetMonitor", LocoMonPane.class, false, true)); // NOI18N 077 panelItems.add(new LocoNetMenuItem("MenuItemSlotMonitor", SlotMonPane.class, false, true)); // NOI18N 078 panelItems.add(new LocoNetMenuItem("MenuItemClockMon", ClockMonPane.class, true, true)); // NOI18N 079 panelItems.add(new LocoNetMenuItem("MenuItemLocoStats", LocoStatsPanel.class, false, true)); // NOI18N 080 panelItems.add(null); 081 panelItems.add(new LocoNetMenuItem("MenuItemBDL16Programmer", BDL16Panel.class, true, true)); // NOI18N 082 panelItems.add(new LocoNetMenuItem("MenuItemPM4Programmer", PM4Panel.class, true, true)); // NOI18N 083 panelItems.add(new LocoNetMenuItem("MenuItemSE8cProgrammer", SE8Panel.class, true, true)); // NOI18N 084 panelItems.add(new LocoNetMenuItem("MenuItemDS64Programmer", Ds64TabbedPanel.class, true, true)); // NOI18N 085 panelItems.add(new LocoNetMenuItem("MenuItemCmdStnConfig", CmdStnConfigPane.class,true, true)); // NOI18N 086 panelItems.add(new LocoNetMenuItem("MenuItemSetID", LocoIdPanel.class, true, true)); // NOI18N 087 panelItems.add(new LocoNetMenuItem("MenuItemDuplex", DuplexGroupTabbedPanel.class, true, true)); // NOI18N 088 panelItems.add(new LocoNetMenuItem("MenuItemLncvProg", LncvProgPane.class, true, true)); // NOI18N 089 panelItems.add(null); 090 panelItems.add(new LocoNetMenuItem("MenuItemThrottleMessages", MessagePanel.class, true, true)); // NOI18N 091 panelItems.add(new LocoNetMenuItem("MenuItemSendPacket", LocoGenPanel.class, false, true)); // NOI18N 092 panelItems.add(new LocoNetMenuItem("MenuItemPr3ModeSelect", Pr3SelectPane.class, false, true)); // NOI18N 093 panelItems.add(null); 094 panelItems.add(new LocoNetMenuItem("MenuItemDownload", jmri.jmrix.loconet.downloader.LoaderPane.class, false, true)); // NOI18N 095 panelItems.add(new LocoNetMenuItem("MenuItemSoundload", LoaderPane.class, false, true)); // NOI18N 096 panelItems.add(new LocoNetMenuItem("MenuItemSoundEditor", EditorPane.class, false, true)); // NOI18N 097 panelItems.add(null); 098 panelItems.add(new LocoNetMenuItem("MenuItemLocoNetOverTCPServer", LnTcpServerAction.class, false, false)); 099 100 LnCommandStationType cmdStation = null; 101 if (memo != null) { 102 setText(memo.getUserName()); 103 cmdStation = memo.getSlotManager().getCommandStationType(); 104 } else { 105 setText(Bundle.getMessage("MenuLocoNet")); 106 } 107 108 WindowInterface wi = new JmriJFrameInterface(); 109 110 boolean isLocoNetInterface; 111 isLocoNetInterface = (cmdStation == null) || 112 (!cmdStation.equals(LnCommandStationType.COMMAND_STATION_PR2_ALONE) && 113 !cmdStation.equals(LnCommandStationType.COMMAND_STATION_PR3_ALONE) && 114 !cmdStation.equals(LnCommandStationType.COMMAND_STATION_PR4_ALONE) && 115 !cmdStation.equals(LnCommandStationType.COMMAND_STATION_USB_DCS240_ALONE) && 116 !cmdStation.equals(LnCommandStationType.COMMAND_STATION_USB_DCS52_ALONE)); 117 118 JMenu hostMenu = this; 119 lastWasSeparator = true; // to prevent an initial separator in menu 120 processItems(hostMenu, panelItems, isLocoNetInterface, wi, memo); 121 122 lastWasSeparator = false; 123 add(hostMenu); 124 panelItems.clear(); 125 // Deal with menu item tasks from SPI extensions 126 ArrayList<JMenu> extensionMenus = getExtensionMenuItems(isLocoNetInterface, 127 wi, memo); 128 log.trace("number of extension items is {}.", panelItems.size()); 129 while ((!extensionMenus.isEmpty()) && (extensionMenus.get(extensionMenus.size()-1) == null)) { 130 // remove any dangling separators at end of list of menu allExtensionItems 131 extensionMenus.remove(panelItems.size()-1); 132 } 133 if (!extensionMenus.isEmpty()) { 134 add(new JSeparator()); // ensure placement of a horizontal bar above 135 // extension menu 136 log.debug("number of items {}", panelItems.size()); 137 while (!extensionMenus.isEmpty()) { 138 JMenu menu = extensionMenus.get(0); 139 add(menu); 140 log.trace("Added extension menu {}", menu.getName()); 141 extensionMenus.remove(0); 142 } 143 } 144 } 145 146 /** 147 * Create an Action suitable for inclusion as a menu item on a LocoNet menu. 148 * 149 * @param item a LocoetMenuItem object which defines the menu item's 150 * characteristics, and which will be the basis for the returned Action 151 * object. 152 * @param isLocoNetInterface is true if the LocoNet connection has a physical 153 * interface to LocoNet, else false. 154 * @param wi the WindowInterface associated with the JMRI instance and LocoNetMenu. 155 * @param memo the LocoNetSystemConnectionMemo associated with the LocoNet 156 * connection. 157 * @return an Action which may be added to a local JMenu for inclusion in a 158 * LocoNet connection's menu; the action's object may make use of the LocoNet 159 * memo and associate its GUI objects with the JMRI WindowInterface. If the 160 * item requires a physical LocoNet interface but the connection does not have 161 * such an interface, then null is returned. 162 */ 163 public Action processExternalItem(LocoNetMenuItem item, boolean isLocoNetInterface, 164 WindowInterface wi, LocoNetSystemConnectionMemo memo) { 165 if (item == null) { 166 return null; 167 } 168 if (item.hasGui() ) { 169 if (isLocoNetInterface || (!item.isInterfaceOnly())) { 170 log.trace("created GUI menu item {}.", item.getName()); 171 return createGuiAction(item, wi, memo); 172 } else { 173 log.trace("not displaying item {} ({}) account requires " 174 + "interface which is not present in current " 175 + "configuration.", 176 item.getName(), item.getClassToLoad().getCanonicalName() 177 ); 178 return null; 179 } 180 } else { 181 log.trace("created non-GUI menu item {}.", item.getName()); 182 return createNonGuiAction(item); 183 } 184 } 185 186 /** 187 * Create an Action object from a LocoNetMenuItem, linked to the appropriate 188 * WindowInterface, for use as a menu item on a LocoNet menu. 189 * 190 * Depending on whether the item needs a gui and/or a physical LocoNet 191 * interface, this method returns null or an Action which is suitable for 192 * use as a menu item on a LocoNet menu. 193 *<br> 194 * If the item's name is found as a key the Bundle associated with this object, 195 * then the I18N'd string will be used as the Action's text. 196 * 197 * @param item LocoNetMenuItem which defines the menu item's requirements. 198 * @param wi WindowInterface to which the item's GUI object will be linked. 199 * @param memo LocoNetSystemConnectionMemo with which the item will be linked. 200 * @return null if the item's requirements are not met by the current 201 * connection, or an Action which may be used as a JMenuItem. 202 */ 203 public Action createGuiAction(LocoNetMenuItem item, WindowInterface wi, 204 LocoNetSystemConnectionMemo memo) { 205 String translatedMenuItemName; 206 try { 207 translatedMenuItemName = Bundle.getMessage(item.getName()); 208 } catch (java.util.MissingResourceException e) { 209 // skip internationalization if name is not present as a "key" 210 translatedMenuItemName = item.getName(); 211 } 212 213 return new LnNamedPaneAction(translatedMenuItemName, wi, 214 item.getClassToLoad().getCanonicalName(), memo); 215 } 216 217 /** 218 * Create an Action object from a LocoNetMenuItem, for use as a menu item on 219 * a LocoNet menu, without linkage to the WindowInterface associated with the 220 * LocoNet menu. 221 * 222 * This method returns an Action which is suitable for use as a menu item on 223 * a LocoNet menu. 224 *<br> 225 * If the item's name is found as a key the Bundle associated with this object, 226 * then the I18N'd string will be used as the Action's text. 227 * 228 * @param item LocoNetMenuItem which defines the menu item's requirements. 229 * @return an Action which may be used as a JMenuItem. 230 */ 231 public Action createNonGuiAction(LocoNetMenuItem item) { 232 Action menuItem = null; 233 try { 234 menuItem = (Action) item.getClassToLoad() 235 .getDeclaredConstructor().newInstance(); 236 menuItem.putValue("NAME", item.getName()); // set the menu item name // NOI18N 237 } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | java.lang.reflect.InvocationTargetException ex ) { 238 log.warn("could not load menu item {} ({})", 239 item.getName(), item.getClassToLoad().getCanonicalName(), ex); 240 } 241 return menuItem; 242 } 243 244 /** 245 * Get an ArrayList of JMenu objects as provided via the SPI "extension" 246 * mechanisms. 247 * @param isConnectionWithHardwareInterface informs whether the connection 248 * has actual hardware 249 * @param wi allows the extension menu items to be associated with the 250 * JAVA WindowInterface which relates to the connection's menu 251 * @param memo the LocoNetSystemConnectionMemo associated with the menu to 252 * which the extension's MenuItem(s) are to be attached. 253 * @return an ArrayList of JMenu objects, as populated from the menu items 254 * reported by any available SPI extensions. May be an empty ArrayList 255 * if none of the SPI extensions provide menu items for this menu. 256 * <br> 257 * @see jmri.jmrix.loconet.swing.menuitemspi.spi.MenuItemsInterface 258 * @see jmri.jmrix.loconet.swing.menuitemspi.MenuItemsService 259 */ 260 public final java.util.ArrayList<JMenu> getExtensionMenuItems( 261 boolean isConnectionWithHardwareInterface, WindowInterface wi, 262 LocoNetSystemConnectionMemo memo) { 263 ArrayList<JMenu> locoNetMenuItems = new ArrayList<>(); 264 log.trace("searching for extensions for the canonical name {}", 265 this.getClass().getCanonicalName()); 266 MenuItemsService lnMenuItemServiceInstance; 267 lnMenuItemServiceInstance = MenuItemsService.getInstance(); 268 locoNetMenuItems.addAll( 269 lnMenuItemServiceInstance.getMenuExtensionsItems( 270 isConnectionWithHardwareInterface, wi, memo)); 271 log.trace("LocoNetItems size is {}", locoNetMenuItems.size()); 272 return locoNetMenuItems; 273 } 274 275 private void processItems(JMenu menu, ArrayList<LocoNetMenuItem> items, boolean isLocoNetInterface, 276 WindowInterface wi, LocoNetSystemConnectionMemo memo) { 277 items.forEach(item -> { 278 processAnItem(menu, item, isLocoNetInterface, wi, memo); 279 }); 280 } 281 282 private void processAnItem(JMenu menu, LocoNetMenuItem item, boolean isLocoNetInterface, 283 WindowInterface wi, LocoNetSystemConnectionMemo memo) { 284 if (item == null) { 285 if (!lastWasSeparator) { 286 menu.add(new JSeparator()); 287 log.trace("Added new JSeparator"); 288 289 lastWasSeparator = true; 290 } 291 } else if (item.hasGui() ) { 292 if (isLocoNetInterface || (!item.isInterfaceOnly())) { 293 addGuiItem(menu, item, wi, memo); 294 log.trace("added GUI item {}", item.getName()); 295 } else { 296 log.trace("not displaying item {} ({}) account requires " 297 + "interface which is not present in current " 298 + "configuration.", 299 item.getName(), item.getClassToLoad().getCanonicalName()); 300 } 301 } else { 302 addNonGuiItem(menu, item); 303 log.trace("added non-GUI item {}", item.getName()); 304 } 305 } 306 307 private void addNonGuiItem(JMenu menu, LocoNetMenuItem item) { 308 if (item != null) { 309 Action menuItem = createNonGuiAction(item); 310 menu.add(menuItem); 311 log.debug("Adding (non-gui) item {} ({})", 312 item.getName(), item.getClassToLoad()); 313 lastWasSeparator = false; 314 } else { 315 menu.add(new JSeparator()); 316 lastWasSeparator = true; 317 log.trace("adding a JSeparator account null item"); 318 } 319 } 320 private void addGuiItem(JMenu menu, LocoNetMenuItem item, WindowInterface wi, 321 LocoNetSystemConnectionMemo memo) { 322 Action a = createGuiAction(item, wi, memo); 323 menu.add(a); 324 lastWasSeparator = false; 325 log.debug("Added new GUI-based item for {} ({}).", 326 item.getName(), item.getClassToLoad().getCanonicalName()); 327 } 328 329 private static final Logger log = LoggerFactory.getLogger(LocoNetMenu.class); 330 331}