001package jmri.jmrit;
002
003import java.beans.PropertyChangeEvent;
004import java.util.ArrayList;
005
006import javax.swing.AbstractAction;
007import javax.swing.JMenu;
008import javax.swing.JSeparator;
009import javax.annotation.CheckForNull;
010
011import jmri.InstanceManager;
012import jmri.jmrit.throttle.ThrottleCreationAction;
013import jmri.jmrit.z21server.Z21serverCreationAction;
014import jmri.util.gui.GuiLafPreferencesManager;
015import jmri.AddressedProgrammerManager;
016import jmri.GlobalProgrammerManager;
017import jmri.jmrit.swing.ToolsMenuAction;
018import jmri.jmrix.ConnectionStatus;
019import jmri.jmrix.ConnectionConfig;
020import jmri.jmrix.ConnectionConfigManager;
021
022/**
023 * Create a "Tools" menu containing the Jmri system-independent tools
024 * <p>
025 * As a best practice, we are migrating the action names (constructor arguments)
026 * out of this class and into the contructors themselves.
027 *
028 * @author Bob Jacobsen Copyright 2003, 2008
029 * @author Matthew Harris copyright (c) 2009
030 */
031public class ToolsMenu extends JMenu {
032
033    ConnectionConfig serModeProCon = null;
034    ConnectionConfig opsModeProCon = null;
035
036    AbstractAction serviceAction = new jmri.jmrit.symbolicprog.tabbedframe.PaneProgAction(Bundle.getMessage("MenuItemDecoderProServiceProgrammer"));
037    AbstractAction opsAction = new jmri.jmrit.symbolicprog.tabbedframe.PaneOpsProgAction(Bundle.getMessage("MenuItemDecoderProOpsModeProgrammer"));
038
039    public ToolsMenu(String name) {
040        this();
041        setText(name);
042    }
043
044    public ToolsMenu() {
045
046        super();
047
048        setText(Bundle.getMessage("MenuTools"));
049
050        JMenu programmerMenu = new JMenu(Bundle.getMessage("MenuProgrammers"));
051        programmerMenu.add(new jmri.jmrit.simpleprog.SimpleProgAction());
052        programmerMenu.add(serviceAction);
053        programmerMenu.add(opsAction);
054        programmerMenu.add(new jmri.jmrit.dualdecoder.DualDecoderToolAction());
055        add(programmerMenu);
056
057        // disable programmer menu if there's no programmer manager
058        if (InstanceManager.getNullableDefault(jmri.AddressedProgrammerManager.class) == null
059                && InstanceManager.getNullableDefault(jmri.GlobalProgrammerManager.class) == null) {
060            programmerMenu.setEnabled(false);
061        }
062
063        JMenu tableMenu = new JMenu(Bundle.getMessage("MenuTables"));
064
065        ///tableMenu.add(tableMenu);    /// <=== WHY?
066        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemTurnoutTable"), "jmri.jmrit.beantable.TurnoutTableTabAction"));
067        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemSensorTable"), "jmri.jmrit.beantable.SensorTableTabAction"));
068        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLightTable"), "jmri.jmrit.beantable.LightTableTabAction"));
069
070        JMenu signalMenu = new JMenu(Bundle.getMessage("MenuSignals"));
071        signalMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemSignalTable"), "jmri.jmrit.beantable.SignalHeadTableAction"));
072        signalMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemSignalMastTable"), "jmri.jmrit.beantable.SignalMastTableAction"));
073        signalMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemSignalGroupTable"), "jmri.jmrit.beantable.SignalGroupTableAction"));
074        signalMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemSignalMastLogicTable"), "jmri.jmrit.beantable.SignalMastLogicTableAction"));
075        tableMenu.add(signalMenu);
076
077        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemReporterTable"), "jmri.jmrit.beantable.ReporterTableTabAction"));
078        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemMemoryTable"), "jmri.jmrit.beantable.MemoryTableAction"));
079        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemStringIOTable"), "jmri.jmrit.beantable.StringIOTableAction"));
080        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemRouteTable"), "jmri.jmrit.beantable.RouteTableAction"));
081        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLRouteTable"), "jmri.jmrit.beantable.LRouteTableAction"));
082        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixTable"), "jmri.jmrit.beantable.LogixTableAction"));
083
084        JMenu logixNG_Menu = new JMenu(Bundle.getMessage("MenuLogixNG"));
085        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGTable"), "jmri.jmrit.beantable.LogixNGTableAction"));
086        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGModuleTable"), "jmri.jmrit.beantable.LogixNGModuleTableAction"));
087        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGTableTable"), "jmri.jmrit.beantable.LogixNGTableTableAction"));
088        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGGlobalVariableTableAction"), "jmri.jmrit.beantable.LogixNGGlobalVariableTableAction"));
089        tableMenu.add(logixNG_Menu);
090
091        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemBlockTable"), "jmri.jmrit.beantable.BlockTableAction"));
092        if (InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed()) { // turn on or off in prefs
093            tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemOBlockTable"), "jmri.jmrit.beantable.OBlockTableAction"));
094        } else {
095            tableMenu.add(new jmri.jmrit.beantable.OBlockTableAction(Bundle.getMessage("MenuItemOBlockTable")));
096        }
097        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemSectionTable"), "jmri.jmrit.beantable.SectionTableAction"));
098        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemTransitTable"), "jmri.jmrit.beantable.TransitTableAction"));
099        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemAudioTable"), "jmri.jmrit.beantable.AudioTableAction"));
100        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemIdTagTable"), "jmri.jmrit.beantable.IdTagTableTabAction"));
101        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemRailComTable"), "jmri.jmrit.beantable.RailComTableAction"));
102        add(tableMenu);
103
104        JMenu throttleMenu = new JMenu(Bundle.getMessage("MenuThrottles"));
105        ThrottleCreationAction.addNewThrottleItemsToThrottleMenu(throttleMenu);
106
107        throttleMenu.add(new jmri.jmrit.throttle.ThrottlesListAction(Bundle.getMessage("MenuItemThrottlesList")));
108        throttleMenu.addSeparator();
109        throttleMenu.add(new jmri.jmrit.throttle.StoreXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveThrottleLayout")));
110        throttleMenu.add(new jmri.jmrit.throttle.LoadXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadThrottleLayout")));
111        throttleMenu.addSeparator();
112        throttleMenu.add(new jmri.jmrit.throttle.StoreDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveAsDefaultThrottleLayout")));
113        throttleMenu.add(new jmri.jmrit.throttle.LoadDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadDefaultThrottleLayout")));
114        //throttleMenu.addSeparator();
115        //throttleMenu.add(new jmri.jmrit.throttle.ThrottlesPreferencesAction(Bundle.getMessage("MenuItemThrottlesPreferences"))); // now in tabbed preferences
116        throttleMenu.add(new JSeparator());
117        throttleMenu.add(new jmri.jmrit.withrottle.WiThrottleCreationAction(Bundle.getMessage("MenuItemStartWiThrottle")));
118        add(throttleMenu);
119
120        // disable the throttle menu if there is no throttle Manager
121        if (jmri.InstanceManager.getNullableDefault(jmri.ThrottleManager.class) == null) {
122            throttleMenu.setEnabled(false);
123        }
124
125        AbstractAction consistAction = new jmri.jmrit.consisttool.ConsistToolAction(Bundle.getMessage("MenuItemConsistTool"));
126
127        add(consistAction);
128
129        // disable the consist tool if there is no consist Manager
130        jmri.ConsistManager consistManager = jmri.InstanceManager.getNullableDefault(jmri.ConsistManager.class);
131        if (consistManager == null) {
132            consistAction.setEnabled(false);
133        } else if (consistManager.canBeDisabled()) {
134            consistManager.registerEnableListener((value) -> {
135                consistAction.setEnabled(value);
136            });
137            consistAction.setEnabled(consistManager.isEnabled());
138        }
139
140        JMenu clockMenu = new JMenu(Bundle.getMessage("MenuClocks"));
141        clockMenu.add(new jmri.jmrit.simpleclock.SimpleClockAction(Bundle.getMessage("MenuItemSetupClock")));
142        clockMenu.add(new jmri.jmrit.nixieclock.NixieClockAction(Bundle.getMessage("MenuItemNixieClock")));
143        clockMenu.add(new jmri.jmrit.lcdclock.LcdClockAction(Bundle.getMessage("MenuItemLcdClock")));
144        clockMenu.add(new jmri.jmrit.analogclock.AnalogClockAction(Bundle.getMessage("MenuItemAnalogClock")));
145        clockMenu.add(new jmri.jmrit.pragotronclock.PragotronClockAction(Bundle.getMessage("MenuItemPragotronClock")));
146        add(clockMenu);
147
148        add(new JSeparator());
149        // single-pane tools
150        add(new jmri.jmrit.powerpanel.PowerPanelAction(Bundle.getMessage("MenuItemPowerControl")));
151        add(new jmri.jmrit.simpleturnoutctrl.SimpleTurnoutCtrlAction(Bundle.getMessage("MenuItemTurnoutControl")));
152        add(new jmri.jmrit.simplelightctrl.SimpleLightCtrlAction(Bundle.getMessage("MenuItemLightControl")));
153        add(new jmri.jmrit.speedometer.SpeedometerAction(Bundle.getMessage("MenuItemSpeedometer")));
154        add(new jmri.jmrit.swing.meter.MeterAction(Bundle.getMessage("MenuItemMeter")));
155        add(new jmri.jmrit.sensorgroup.SensorGroupAction(Bundle.getMessage("MenuItemSensorGroup")));
156        add(new jmri.jmrit.blockboss.BlockBossAction(Bundle.getMessage("MenuItemSimpleSignal")));
157        add(new jmri.jmrit.sendpacket.SendPacketAction(Bundle.getMessage("MenuItemSendDCCPacket")));
158
159        add(new JSeparator());
160        // more complex multi-window tools
161        add(new jmri.jmrit.operations.OperationsMenu());
162        add(new jmri.jmrit.dispatcher.DispatcherAction(Bundle.getMessage("MenuItemDispatcher")));
163        add(new jmri.jmrit.timetable.swing.TimeTableAction(Bundle.getMessage("MenuItemTimeTable")));
164        add(new jmri.jmrit.whereused.WhereUsedAction(Bundle.getMessage("MenuItemWhereUsed")));
165        // CTC menu item with submenus
166        JMenu ctcMenu = new JMenu(Bundle.getMessage("MenuCTC"));
167        ctcMenu.add(new jmri.jmrit.ctc.editor.CtcEditorAction(Bundle.getMessage("MenuItemCTCEditor")));
168        ctcMenu.add(new jmri.jmrit.ctc.CtcRunAction(Bundle.getMessage("MenuItemCTCMain")));
169        add(ctcMenu);
170        // US&S CTC subsystem tools
171        add(new jmri.jmrit.ussctc.ToolsMenu());
172        // add cab signals
173        add(new jmri.jmrit.cabsignals.CabSignalAction());
174
175        add(new JSeparator());
176        JMenu serverMenu = new JMenu(Bundle.getMessage("MenuServers"));
177        serverMenu.add(new jmri.web.server.WebServerAction());
178        serverMenu.add(new jmri.jmrit.withrottle.WiThrottleCreationAction());
179        serverMenu.add(new Z21serverCreationAction());
180        serverMenu.add(new JSeparator());
181        serverMenu.add(new jmri.jmris.simpleserver.SimpleServerAction());
182        serverMenu.add(new jmri.jmris.srcp.JmriSRCPServerAction());
183        add(serverMenu);
184
185        add(new JSeparator());
186        JMenu vsdMenu = new JMenu(Bundle.getMessage("MenuItemVSDecoder"));
187        vsdMenu.add(new jmri.jmrit.vsdecoder.VSDecoderCreationAction(Bundle.getMessage("MenuItemVSDecoderManager")));
188        vsdMenu.add(new jmri.jmrit.vsdecoder.swing.ManageLocationsAction(Bundle.getMessage("MenuItemVSDecoderLocationManager")));
189        vsdMenu.add(new jmri.jmrit.vsdecoder.swing.VSDPreferencesAction(Bundle.getMessage("MenuItemVSDecoderPreferences")));
190        add(vsdMenu);
191
192        add(new JSeparator());
193        // LogixNG menu
194        add(new jmri.jmrit.logixng.tools.swing.LogixNGMenu());
195
196        // Enable or disable the service mode programmer menu items for the types of programmer available.
197        updateProgrammerStatus(null);
198        ConnectionStatus.instance().addPropertyChangeListener((PropertyChangeEvent e) -> {
199            if ((e.getPropertyName().equals("change")) || (e.getPropertyName().equals("add"))) {
200                log.debug("Received property {} with value {} ", e.getPropertyName(), e.getNewValue());
201                updateProgrammerStatus(e);
202            }
203        });
204        InstanceManager.addPropertyChangeListener(InstanceManager.getListPropertyName(AddressedProgrammerManager.class),
205                evt -> {
206                    AddressedProgrammerManager m = (AddressedProgrammerManager) evt.getNewValue();
207                    if (m != null) {
208                        m.addPropertyChangeListener(this::updateProgrammerStatus);
209                    }
210                    updateProgrammerStatus(evt);
211                });
212        InstanceManager.getList(AddressedProgrammerManager.class).forEach(m -> m.addPropertyChangeListener(this::updateProgrammerStatus));
213        InstanceManager.addPropertyChangeListener(InstanceManager.getListPropertyName(GlobalProgrammerManager.class),
214                evt -> {
215                    GlobalProgrammerManager m = (GlobalProgrammerManager) evt.getNewValue();
216                    if (m != null) {
217                        m.addPropertyChangeListener(this::updateProgrammerStatus);
218                    }
219                    updateProgrammerStatus(evt);
220                });
221        InstanceManager.getList(GlobalProgrammerManager.class).forEach(m -> m.addPropertyChangeListener(this::updateProgrammerStatus));
222
223        // add items given by ToolsMenuItem service provider
224        var newItemList = new ArrayList<ToolsMenuAction>();
225        java.util.ServiceLoader.load(jmri.jmrit.swing.ToolsMenuAction.class).forEach((toolsMenuAction) -> {
226            newItemList.add(toolsMenuAction);
227        });
228        if (!newItemList.isEmpty()) {
229            add(new JSeparator());
230            newItemList.forEach((item) -> {
231                log.info("Adding Plug In \'{}\' to Tools Menu", item);
232                add(item);
233            });
234        }
235
236    }
237
238    /**
239     * Enable or disable the service mode programmer menu items for the types of programmer
240     * available.
241     *
242     * Adapted from similar named function in @link jmri.jmrit.roster.swing.RosterFrame.java
243     *
244     * @param evt the triggering event; if not null and if a removal of a
245     *            ProgrammerManager, care will be taken not to trigger the
246     *            automatic creation of a new ProgrammerManager
247     */
248    protected void updateProgrammerStatus(@CheckForNull PropertyChangeEvent evt) {
249        log.debug("Updating Programmer Status for property {}", (evt != null) ? evt.getPropertyName() : "null");
250        ConnectionConfig oldServMode = serModeProCon;
251        ConnectionConfig oldOpsMode = opsModeProCon;
252        GlobalProgrammerManager gpm = null;
253        AddressedProgrammerManager apm = null;
254
255        // Find the connection that goes with the global programmer
256        // test that IM has a default GPM, or that event is not the removal of a GPM
257        if (InstanceManager.containsDefault(GlobalProgrammerManager.class)
258                || (evt != null
259                && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(GlobalProgrammerManager.class))
260                && evt.getNewValue() == null)) {
261            gpm = InstanceManager.getNullableDefault(GlobalProgrammerManager.class);
262            log.trace("found global programming manager {}", gpm);
263        }
264        if (gpm != null) {
265            String serviceModeProgrammerName = gpm.getUserName();
266            log.debug("GlobalProgrammerManager found of class {} name {} ", gpm.getClass(), serviceModeProgrammerName);
267            InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> {
268                for (ConnectionConfig connection : ccm) {
269                    log.debug("Checking connection name {}", connection.getConnectionName());
270                    if (connection.getConnectionName() != null && connection.getConnectionName().equals(serviceModeProgrammerName)) {
271                        log.debug("Connection found for GlobalProgrammermanager");
272                        serModeProCon = connection;
273                    }
274                }
275            });
276        }
277
278        // Find the connection that goes with the addressed programmer
279        // test that IM has a default APM, or that event is not the removal of an APM
280        if (InstanceManager.containsDefault(AddressedProgrammerManager.class)
281                || (evt != null
282                && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(AddressedProgrammerManager.class))
283                && evt.getNewValue() == null)) {
284            apm = InstanceManager.getNullableDefault(AddressedProgrammerManager.class);
285            log.trace("found addressed programming manager {}", gpm);
286        }
287        if (apm != null) {
288            String opsModeProgrammerName = apm.getUserName();
289            log.debug("AddressedProgrammerManager found of class {} name {} ", apm.getClass(), opsModeProgrammerName);
290            InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> {
291                for (ConnectionConfig connection : ccm) {
292                    log.debug("Checking connection name {}", connection.getConnectionName());
293                    if (connection.getConnectionName() != null && connection.getConnectionName().equals(opsModeProgrammerName)) {
294                        log.debug("Connection found for AddressedProgrammermanager");
295                        opsModeProCon = connection;
296                    }
297                }
298            });
299        }
300
301        log.trace("start global check with {}, {}, {}", serModeProCon, gpm, (gpm != null ? gpm.isGlobalProgrammerAvailable() : "<none>"));
302        if (gpm != null && gpm.isGlobalProgrammerAvailable()) {
303            log.debug("service mode available");
304            if (oldServMode == null) {
305                serviceAction.setEnabled(true);
306                firePropertyChange("setprogservice", "setEnabled", true);
307            }
308        } else {
309            // No service programmer available, disable menu
310            log.debug("no service programmer");
311            if (oldServMode != null) {
312                serviceAction.setEnabled(false);
313                firePropertyChange("setprogservice", "setEnabled", false);
314            }
315            serModeProCon = null;
316        }
317
318        if (apm != null && apm.isAddressedModePossible()) {
319            log.debug("ops mode available");
320            if (oldOpsMode == null) {
321                opsAction.setEnabled(true);
322                firePropertyChange("setprogops", "setEnabled", true);
323            }
324        } else {
325            // No ops mode programmer available, disable interface sections not available
326            log.debug("no ops mode programmer");
327            if (oldOpsMode != null) {
328                opsAction.setEnabled(false);
329                firePropertyChange("setprogops", "setEnabled", false);
330            }
331            opsModeProCon = null;
332        }
333    }
334
335    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(jmri.jmrit.ToolsMenu.class);
336
337}