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