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("MenuItemRouteTable"), "jmri.jmrit.beantable.RouteTableAction"));
080        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLRouteTable"), "jmri.jmrit.beantable.LRouteTableAction"));
081        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixTable"), "jmri.jmrit.beantable.LogixTableAction"));
082
083        JMenu logixNG_Menu = new JMenu(Bundle.getMessage("MenuLogixNG"));
084        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGTable"), "jmri.jmrit.beantable.LogixNGTableAction"));
085        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGModuleTable"), "jmri.jmrit.beantable.LogixNGModuleTableAction"));
086        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGTableTable"), "jmri.jmrit.beantable.LogixNGTableTableAction"));
087        logixNG_Menu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemLogixNGGlobalVariableTableAction"), "jmri.jmrit.beantable.LogixNGGlobalVariableTableAction"));
088        tableMenu.add(logixNG_Menu);
089
090        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemBlockTable"), "jmri.jmrit.beantable.BlockTableAction"));
091        if (InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed()) { // turn on or off in prefs
092            tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemOBlockTable"), "jmri.jmrit.beantable.OBlockTableAction"));
093        } else {
094            tableMenu.add(new jmri.jmrit.beantable.OBlockTableAction(Bundle.getMessage("MenuItemOBlockTable")));
095        }
096        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemSectionTable"), "jmri.jmrit.beantable.SectionTableAction"));
097        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemTransitTable"), "jmri.jmrit.beantable.TransitTableAction"));
098        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemAudioTable"), "jmri.jmrit.beantable.AudioTableAction"));
099        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemIdTagTable"), "jmri.jmrit.beantable.IdTagTableTabAction"));
100        tableMenu.add(new jmri.jmrit.beantable.ListedTableAction(Bundle.getMessage("MenuItemRailComTable"), "jmri.jmrit.beantable.RailComTableAction"));
101        add(tableMenu);
102
103        JMenu throttleMenu = new JMenu(Bundle.getMessage("MenuThrottles"));
104        ThrottleCreationAction.addNewThrottleItemsToThrottleMenu(throttleMenu);
105
106        throttleMenu.add(new jmri.jmrit.throttle.ThrottlesListAction(Bundle.getMessage("MenuItemThrottlesList")));
107        throttleMenu.addSeparator();
108        throttleMenu.add(new jmri.jmrit.throttle.StoreXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveThrottleLayout")));
109        throttleMenu.add(new jmri.jmrit.throttle.LoadXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadThrottleLayout")));
110        throttleMenu.addSeparator();
111        throttleMenu.add(new jmri.jmrit.throttle.StoreDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemSaveAsDefaultThrottleLayout")));
112        throttleMenu.add(new jmri.jmrit.throttle.LoadDefaultXmlThrottlesLayoutAction(Bundle.getMessage("MenuItemLoadDefaultThrottleLayout")));
113        //throttleMenu.addSeparator();
114        //throttleMenu.add(new jmri.jmrit.throttle.ThrottlesPreferencesAction(Bundle.getMessage("MenuItemThrottlesPreferences"))); // now in tabbed preferences
115        throttleMenu.add(new JSeparator());
116        throttleMenu.add(new jmri.jmrit.withrottle.WiThrottleCreationAction(Bundle.getMessage("MenuItemStartWiThrottle")));
117        add(throttleMenu);
118
119        // disable the throttle menu if there is no throttle Manager
120        if (jmri.InstanceManager.getNullableDefault(jmri.ThrottleManager.class) == null) {
121            throttleMenu.setEnabled(false);
122        }
123
124        AbstractAction consistAction = new jmri.jmrit.consisttool.ConsistToolAction(Bundle.getMessage("MenuItemConsistTool"));
125
126        add(consistAction);
127
128        // disable the consist tool if there is no consist Manager
129        jmri.ConsistManager consistManager = jmri.InstanceManager.getNullableDefault(jmri.ConsistManager.class);
130        if (consistManager == null) {
131            consistAction.setEnabled(false);
132        } else if (consistManager.canBeDisabled()) {
133            consistManager.registerEnableListener((value) -> {
134                consistAction.setEnabled(value);
135            });
136            consistAction.setEnabled(consistManager.isEnabled());
137        }
138
139        JMenu clockMenu = new JMenu(Bundle.getMessage("MenuClocks"));
140        clockMenu.add(new jmri.jmrit.simpleclock.SimpleClockAction(Bundle.getMessage("MenuItemSetupClock")));
141        clockMenu.add(new jmri.jmrit.nixieclock.NixieClockAction(Bundle.getMessage("MenuItemNixieClock")));
142        clockMenu.add(new jmri.jmrit.lcdclock.LcdClockAction(Bundle.getMessage("MenuItemLcdClock")));
143        clockMenu.add(new jmri.jmrit.analogclock.AnalogClockAction(Bundle.getMessage("MenuItemAnalogClock")));
144        clockMenu.add(new jmri.jmrit.pragotronclock.PragotronClockAction(Bundle.getMessage("MenuItemPragotronClock")));
145        add(clockMenu);
146
147        add(new JSeparator());
148        // single-pane tools
149        add(new jmri.jmrit.powerpanel.PowerPanelAction(Bundle.getMessage("MenuItemPowerControl")));
150        add(new jmri.jmrit.simpleturnoutctrl.SimpleTurnoutCtrlAction(Bundle.getMessage("MenuItemTurnoutControl")));
151        add(new jmri.jmrit.simplelightctrl.SimpleLightCtrlAction(Bundle.getMessage("MenuItemLightControl")));
152        add(new jmri.jmrit.speedometer.SpeedometerAction(Bundle.getMessage("MenuItemSpeedometer")));
153        add(new jmri.jmrit.swing.meter.MeterAction(Bundle.getMessage("MenuItemMeter")));
154        add(new jmri.jmrit.sensorgroup.SensorGroupAction(Bundle.getMessage("MenuItemSensorGroup")));
155        add(new jmri.jmrit.blockboss.BlockBossAction(Bundle.getMessage("MenuItemSimpleSignal")));
156        add(new jmri.jmrit.sendpacket.SendPacketAction(Bundle.getMessage("MenuItemSendDCCPacket")));
157
158        add(new JSeparator());
159        // more complex multi-window tools
160        add(new jmri.jmrit.operations.OperationsMenu());
161        add(new jmri.jmrit.dispatcher.DispatcherAction(Bundle.getMessage("MenuItemDispatcher")));
162        add(new jmri.jmrit.timetable.swing.TimeTableAction(Bundle.getMessage("MenuItemTimeTable")));
163        add(new jmri.jmrit.whereused.WhereUsedAction(Bundle.getMessage("MenuItemWhereUsed")));
164        // CTC menu item with submenus
165        JMenu ctcMenu = new JMenu(Bundle.getMessage("MenuCTC"));
166        ctcMenu.add(new jmri.jmrit.ctc.editor.CtcEditorAction(Bundle.getMessage("MenuItemCTCEditor")));
167        ctcMenu.add(new jmri.jmrit.ctc.CtcRunAction(Bundle.getMessage("MenuItemCTCMain")));
168        add(ctcMenu);
169        // US&S CTC subsystem tools
170        add(new jmri.jmrit.ussctc.ToolsMenu());
171        // add cab signals
172        add(new jmri.jmrit.cabsignals.CabSignalAction());
173
174        add(new JSeparator());
175        JMenu serverMenu = new JMenu(Bundle.getMessage("MenuServers"));
176        serverMenu.add(new jmri.web.server.WebServerAction());
177        serverMenu.add(new jmri.jmrit.withrottle.WiThrottleCreationAction());
178        serverMenu.add(new Z21serverCreationAction());
179        serverMenu.add(new JSeparator());
180        serverMenu.add(new jmri.jmris.simpleserver.SimpleServerAction());
181        serverMenu.add(new jmri.jmris.srcp.JmriSRCPServerAction());
182        add(serverMenu);
183
184        add(new JSeparator());
185        JMenu vsdMenu = new JMenu(Bundle.getMessage("MenuItemVSDecoder"));
186        vsdMenu.add(new jmri.jmrit.vsdecoder.VSDecoderCreationAction(Bundle.getMessage("MenuItemVSDecoderManager")));
187        vsdMenu.add(new jmri.jmrit.vsdecoder.swing.ManageLocationsAction(Bundle.getMessage("MenuItemVSDecoderLocationManager")));
188        vsdMenu.add(new jmri.jmrit.vsdecoder.swing.VSDPreferencesAction(Bundle.getMessage("MenuItemVSDecoderPreferences")));
189        add(vsdMenu);
190
191        add(new JSeparator());
192        // LogixNG menu
193        add(new jmri.jmrit.logixng.tools.swing.LogixNGMenu());
194
195        // Enable or disable the service mode programmer menu items for the types of programmer available.
196        updateProgrammerStatus(null);
197        ConnectionStatus.instance().addPropertyChangeListener((PropertyChangeEvent e) -> {
198            if ((e.getPropertyName().equals("change")) || (e.getPropertyName().equals("add"))) {
199                log.debug("Received property {} with value {} ", e.getPropertyName(), e.getNewValue());
200                updateProgrammerStatus(e);
201            }
202        });
203        InstanceManager.addPropertyChangeListener(InstanceManager.getListPropertyName(AddressedProgrammerManager.class),
204                evt -> {
205                    AddressedProgrammerManager m = (AddressedProgrammerManager) evt.getNewValue();
206                    if (m != null) {
207                        m.addPropertyChangeListener(this::updateProgrammerStatus);
208                    }
209                    updateProgrammerStatus(evt);
210                });
211        InstanceManager.getList(AddressedProgrammerManager.class).forEach(m -> m.addPropertyChangeListener(this::updateProgrammerStatus));
212        InstanceManager.addPropertyChangeListener(InstanceManager.getListPropertyName(GlobalProgrammerManager.class),
213                evt -> {
214                    GlobalProgrammerManager m = (GlobalProgrammerManager) evt.getNewValue();
215                    if (m != null) {
216                        m.addPropertyChangeListener(this::updateProgrammerStatus);
217                    }
218                    updateProgrammerStatus(evt);
219                });
220        InstanceManager.getList(GlobalProgrammerManager.class).forEach(m -> m.addPropertyChangeListener(this::updateProgrammerStatus));
221
222        // add items given by ToolsMenuItem service provider
223        var newItemList = new ArrayList<ToolsMenuAction>();
224        java.util.ServiceLoader.load(jmri.jmrit.swing.ToolsMenuAction.class).forEach((toolsMenuAction) -> {
225            newItemList.add(toolsMenuAction);
226        });
227        if (!newItemList.isEmpty()) {
228            add(new JSeparator());
229            newItemList.forEach((item) -> {
230                log.info("Adding Plug In \'{}\' to Tools Menu", item);
231                add(item);
232            });
233        }
234
235    }
236
237    /**
238     * Enable or disable the service mode programmer menu items for the types of programmer
239     * available.
240     *
241     * Adapted from similar named function in @link jmri.jmrit.roster.swing.RosterFrame.java
242     *
243     * @param evt the triggering event; if not null and if a removal of a
244     *            ProgrammerManager, care will be taken not to trigger the
245     *            automatic creation of a new ProgrammerManager
246     */
247    protected void updateProgrammerStatus(@CheckForNull PropertyChangeEvent evt) {
248        log.debug("Updating Programmer Status for property {}", (evt != null) ? evt.getPropertyName() : "null");
249        ConnectionConfig oldServMode = serModeProCon;
250        ConnectionConfig oldOpsMode = opsModeProCon;
251        GlobalProgrammerManager gpm = null;
252        AddressedProgrammerManager apm = null;
253
254        // Find the connection that goes with the global programmer
255        // test that IM has a default GPM, or that event is not the removal of a GPM
256        if (InstanceManager.containsDefault(GlobalProgrammerManager.class)
257                || (evt != null
258                && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(GlobalProgrammerManager.class))
259                && evt.getNewValue() == null)) {
260            gpm = InstanceManager.getNullableDefault(GlobalProgrammerManager.class);
261            log.trace("found global programming manager {}", gpm);
262        }
263        if (gpm != null) {
264            String serviceModeProgrammerName = gpm.getUserName();
265            log.debug("GlobalProgrammerManager found of class {} name {} ", gpm.getClass(), serviceModeProgrammerName);
266            InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> {
267                for (ConnectionConfig connection : ccm) {
268                    log.debug("Checking connection name {}", connection.getConnectionName());
269                    if (connection.getConnectionName() != null && connection.getConnectionName().equals(serviceModeProgrammerName)) {
270                        log.debug("Connection found for GlobalProgrammermanager");
271                        serModeProCon = connection;
272                    }
273                }
274            });
275        }
276
277        // Find the connection that goes with the addressed programmer
278        // test that IM has a default APM, or that event is not the removal of an APM
279        if (InstanceManager.containsDefault(AddressedProgrammerManager.class)
280                || (evt != null
281                && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(AddressedProgrammerManager.class))
282                && evt.getNewValue() == null)) {
283            apm = InstanceManager.getNullableDefault(AddressedProgrammerManager.class);
284            log.trace("found addressed programming manager {}", gpm);
285        }
286        if (apm != null) {
287            String opsModeProgrammerName = apm.getUserName();
288            log.debug("AddressedProgrammerManager found of class {} name {} ", apm.getClass(), opsModeProgrammerName);
289            InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> {
290                for (ConnectionConfig connection : ccm) {
291                    log.debug("Checking connection name {}", connection.getConnectionName());
292                    if (connection.getConnectionName() != null && connection.getConnectionName().equals(opsModeProgrammerName)) {
293                        log.debug("Connection found for AddressedProgrammermanager");
294                        opsModeProCon = connection;
295                    }
296                }
297            });
298        }
299
300        log.trace("start global check with {}, {}, {}", serModeProCon, gpm, (gpm != null ? gpm.isGlobalProgrammerAvailable() : "<none>"));
301        if (gpm != null && gpm.isGlobalProgrammerAvailable()) {
302            log.debug("service mode available");
303            if (oldServMode == null) {
304                serviceAction.setEnabled(true);
305                firePropertyChange("setprogservice", "setEnabled", true);
306            }
307        } else {
308            // No service programmer available, disable menu
309            log.debug("no service programmer");
310            if (oldServMode != null) {
311                serviceAction.setEnabled(false);
312                firePropertyChange("setprogservice", "setEnabled", false);
313            }
314            serModeProCon = null;
315        }
316
317        if (apm != null && apm.isAddressedModePossible()) {
318            log.debug("ops mode available");
319            if (oldOpsMode == null) {
320                opsAction.setEnabled(true);
321                firePropertyChange("setprogops", "setEnabled", true);
322            }
323        } else {
324            // No ops mode programmer available, disable interface sections not available
325            log.debug("no ops mode programmer");
326            if (oldOpsMode != null) {
327                opsAction.setEnabled(false);
328                firePropertyChange("setprogops", "setEnabled", false);
329            }
330            opsModeProCon = null;
331        }
332    }
333
334    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(jmri.jmrit.ToolsMenu.class);
335
336}