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