001package jmri.jmrit.logixng.tools.swing;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.io.File;
006import java.io.IOException;
007import java.util.function.Predicate;
008
009import javax.swing.*;
010
011import jmri.*;
012import jmri.jmrit.entryexit.EntryExitPairs;
013import jmri.jmrit.logix.OBlockManager;
014import jmri.jmrit.logix.WarrantManager;
015import jmri.jmrit.logixng.util.WhereUsed;
016import jmri.swing.NamedBeanComboBox;
017import jmri.util.FileUtil;
018import jmri.util.swing.JComboBoxUtil;
019import jmri.util.swing.JmriJOptionPane;
020
021/**
022 * Create a where used report based on the selected bean.  The selection combo box is
023 * based on the selected type.
024 * <P>
025 * This class is a copy of jmri.jmrit.whereused.WhereUsedFrame, but it only shows
026 * where a bean is used by LogixNG. On the other hand, it shows where in the LogixNG
027 * tree the bean is used.
028 *
029 * @author Dave Sand         Copyright (C) 2020
030 * @author Daniel Bergqvist  Copyright (C) 2023
031 */
032public class WhereUsedFrame extends jmri.util.JmriJFrame {
033    ItemType _itemType = ItemType.NONE;
034    JComboBox<ItemType> _itemTypeBox;
035
036    NamedBean _itemBean;
037    NamedBeanComboBox<?> _itemNameBox = new NamedBeanComboBox<>(
038                        InstanceManager.getDefault(SensorManager.class));
039
040    JPanel _topPanel;
041    JPanel _bottomPanel;
042    JPanel _scrolltext = new JPanel();
043    JTextArea _textArea = new JTextArea();
044    JButton _createButton;
045    JLabel itemNameLabel;
046
047    public WhereUsedFrame() {
048        super(true, true);
049        setTitle(Bundle.getMessage("WhereUsed_Title"));  // NOI18N
050        createFrame();
051        addHelpMenu("package.jmri.jmrit.logixng.LogixNGWhereUsed", true);  // NOI18N
052    }
053
054    /**
055     * Create the window frame.  The top part contains the item type, the item name
056     * combo box, and a Create button.  The middle contains the scrollable "where used" text area and the
057     * bottom part has a button for saving the content to a file.
058     */
059    void createFrame() {
060        Container contentPane = getContentPane();
061        contentPane.setLayout(new BorderLayout());
062
063        // Build the top panel
064        buildTopPanel();
065        contentPane.add(_topPanel, BorderLayout.NORTH);
066
067        // Build an empty where used listing
068        JScrollPane scrollPane;
069        buildWhereUsedListing(ItemType.NONE, null);
070        scrollPane = new JScrollPane(_scrolltext);
071        contentPane.add(scrollPane);
072
073        // Build the bottom panel
074        buildBottomPanel();
075        contentPane.add(_bottomPanel, BorderLayout.SOUTH);
076
077        pack();
078    }
079
080    void buildTopPanel() {
081        _topPanel = new JPanel();
082        JLabel itemTypeLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("WhereUsed_LabelItemType")));  // NOI18N
083        _topPanel.add(itemTypeLabel);
084        _itemTypeBox = new JComboBox<>();
085        itemTypeLabel.setLabelFor(_itemTypeBox);
086        for (ItemType itemType : ItemType.values()) {
087            _itemTypeBox.addItem(itemType);
088        }
089        JComboBoxUtil.setupComboBoxMaxRows(_itemTypeBox);
090        _topPanel.add(_itemTypeBox);
091
092        itemNameLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("WhereUsed_LabelItemName")));  // NOI18N
093        _topPanel.add(itemNameLabel);
094        itemNameLabel.setLabelFor(_itemNameBox);
095        _topPanel.add(_itemNameBox);
096        _itemTypeBox.addActionListener((e) -> {
097            _itemType = _itemTypeBox.getItemAt(_itemTypeBox.getSelectedIndex());
098            setItemNameBox(_itemType);
099        });
100
101        _createButton = new JButton(Bundle.getMessage("ButtonCreate"));  // NOI18N
102        _createButton.addActionListener((e) -> buildWhereUsedListing(_itemType, _itemBean));
103
104        _topPanel.add(_createButton);
105        _itemNameBox.setEnabled(false);
106        _createButton.setEnabled(false);
107    }
108
109    void buildBottomPanel() {
110        _bottomPanel = new JPanel();
111        _bottomPanel.setLayout(new BorderLayout());
112
113        JButton saveButton = new JButton(Bundle.getMessage("WhereUsed_SaveButton"));   // NOI18N
114        saveButton.setToolTipText(Bundle.getMessage("WhereUsed_SaveButtonHint"));      // NOI18N
115        _bottomPanel.add(saveButton, BorderLayout.EAST);
116        saveButton.addActionListener((ActionEvent e) -> saveWhereUsedPressed());
117    }
118
119    /**
120     * Create a new NamedBeanComboBox based on the item type and refresh the panel.
121     * A selection listener saves the selection and enables the Create button.
122     * @param itemType The enum for the selected item type.
123     */
124    void setItemNameBox(ItemType itemType) {
125        _createButton.setEnabled(false);
126        buildWhereUsedListing(ItemType.NONE, null);
127        NamedBeanComboBox<?> newNameBox = createNameBox(itemType);
128        if (newNameBox == null) {
129            _itemNameBox.setSelectedIndex(-1);
130            _itemNameBox.setEnabled(false);
131            return;
132        }
133        _itemNameBox = newNameBox;
134        itemNameLabel.setLabelFor(newNameBox);
135        _itemNameBox.setSelectedIndex(-1);
136        _topPanel.remove(3);
137        _topPanel.add(_itemNameBox, 3);
138
139        _itemNameBox.setEnabled(true);
140        _itemNameBox.addItemListener((e) -> {
141            if (e.getStateChange() == ItemEvent.SELECTED) {
142                _itemBean = (NamedBean) e.getItem();
143                _createButton.setEnabled(true);
144            }
145        });
146        pack();
147        repaint();
148    }
149
150    /**
151     * Build the where used content and update the JScrollPane.
152     * <p>
153     * The selected object is passed to the appropriate detail class which returns a populated textarea.
154     * The textarea is formatted and inserted into a scrollable panel.
155     * @param type Indicated type of item being examined
156     * @param bean The bean being examined
157     */
158    void buildWhereUsedListing(ItemType type, NamedBean bean) {
159        if (type != ItemType.NONE && bean != null) {
160            String str = WhereUsed.whereUsed(bean);
161            if (str.isEmpty()) {
162                str = Bundle.getMessage("WhereUsed_NotInUse");
163            }
164            _textArea.setText(str);
165        } else {
166            _textArea.setText("");
167        }
168        _textArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
169        _textArea.setTabSize(4);
170        _textArea.setEditable(false);
171        _textArea.setCaretPosition(0);
172        if (_scrolltext.getComponentCount() > 0) {
173            _scrolltext.remove(0);
174        }
175        _scrolltext.add(_textArea);
176        pack();
177        repaint();
178    }
179
180    JFileChooser userFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
181
182    /**
183     * Save the where used textarea content to a text file.
184     */
185    void saveWhereUsedPressed() {
186        userFileChooser.setApproveButtonText(Bundle.getMessage("SaveDialogApprove"));  // NOI18N
187        userFileChooser.setDialogTitle(Bundle.getMessage("SaveDialogTitle"));  // NOI18N
188        userFileChooser.rescanCurrentDirectory();
189
190        String itemName = _itemNameBox.getSelectedItemDisplayName();
191        String fileName = Bundle.getMessage("SaveFileName", (itemName == null) ? "Unknown" : itemName);  // NOI18N
192        userFileChooser.setSelectedFile(new File(fileName));
193        int retVal = userFileChooser.showSaveDialog(null);
194        if (retVal != JFileChooser.APPROVE_OPTION) {
195            log.debug("Save where used content stopped, no file selected");  // NOI18N
196            return;  // give up if no file selected or cancel pressed
197        }
198        File file = userFileChooser.getSelectedFile();
199        log.debug("Save where used content to '{}'", file);  // NOI18N
200
201        if (file.exists()) {
202            Object[] options = {Bundle.getMessage("SaveDuplicateReplace"),  // NOI18N
203                    Bundle.getMessage("SaveDuplicateAppend"),  // NOI18N
204                    Bundle.getMessage("ButtonCancel")};               // NOI18N
205            int selectedOption = JmriJOptionPane.showOptionDialog(null,
206                    Bundle.getMessage("SaveDuplicatePrompt", file.getName(),
207                            Bundle.getMessage("SaveDuplicateAppend"),
208                            Bundle.getMessage("SaveDuplicateReplace")), // NOI18N
209                    Bundle.getMessage("SaveDuplicateTitle"),   // NOI18N
210                    JmriJOptionPane.DEFAULT_OPTION,
211                    JmriJOptionPane.WARNING_MESSAGE,
212                    null, options, options[0]);
213            if (selectedOption == 2 || selectedOption == -1) {
214                log.debug("Save where used content stopped, file replace/append cancelled");  // NOI18N
215                return;  // Cancel selected or dialog box closed
216            }
217            if (selectedOption == 0) {
218                FileUtil.delete(file);  // Replace selected
219            }
220        }
221
222        // Create the file content
223        try {
224            FileUtil.appendTextToFile(file, _textArea.getText());
225        } catch (IOException e) {
226            log.error("Unable to write where used content to '{}', exception", file, e);  // NOI18N
227        }
228    }
229
230    /**
231     * Create a combo name box for name selection.
232     *
233     * @param itemType The selected bean type
234     * @return a combo box based on the item type or null if no match
235     */
236    NamedBeanComboBox<?> createNameBox(ItemType itemType) {
237        NamedBeanComboBox<?> nameBox;
238        switch (itemType) {
239            case TURNOUT:
240                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class));
241                break;
242            case SENSOR:
243                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SensorManager.class));
244                break;
245            case LIGHT:
246                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(LightManager.class));
247                break;
248            case SIGNALHEAD:
249                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalHeadManager.class));
250                break;
251            case SIGNALMAST:
252                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class));
253                break;
254            case REPORTER:
255                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(ReporterManager.class));
256                break;
257            case MEMORY:
258                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(MemoryManager.class));
259                break;
260            case ROUTE:
261                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(RouteManager.class));
262                break;
263            case OBLOCK:
264                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(OBlockManager.class));
265                break;
266            case BLOCK:
267                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(BlockManager.class));
268                break;
269            case SECTION:
270                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SectionManager.class));
271                break;
272            case WARRANT:
273                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(WarrantManager.class));
274                break;
275            case ENTRYEXIT:
276                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(EntryExitPairs.class));
277                break;
278            case AUDIO:
279                Predicate<Audio> filter = (bean) -> { return bean.getSubType() != Audio.BUFFER; };
280                nameBox = new NamedBeanComboBox<>(InstanceManager.getDefault(AudioManager.class),
281                        null, jmri.NamedBean.DisplayOptions.DISPLAYNAME, filter);
282                break;
283            default:
284                return null;             // Skip any other items.
285        }
286        nameBox.setEditable(false);
287        nameBox.setValidatingInput(false);
288        JComboBoxUtil.setupComboBoxMaxRows(nameBox);
289        return nameBox;
290    }
291
292    /**
293     * The item types.  A bundle key for each type is stored with the type to
294     * create a language dependent toString result.
295     */
296    enum ItemType {
297        NONE("ItemTypeNone"),
298        TURNOUT("BeanNameTurnout"),
299        SENSOR("BeanNameSensor"),
300        LIGHT("BeanNameLight"),
301        SIGNALHEAD("BeanNameSignalHead"),
302        SIGNALMAST("BeanNameSignalMast"),
303        REPORTER("BeanNameReporter"),
304        MEMORY("BeanNameMemory"),
305        ROUTE("BeanNameRoute"),
306        OBLOCK("BeanNameOBlock"),
307        BLOCK("BeanNameBlock"),
308        SECTION("BeanNameSection"),
309        WARRANT("BeanNameWarrant"),
310        ENTRYEXIT("BeanNameEntryExit"),
311        AUDIO("BeanNameAudio");
312
313        private final String _bundleKey;
314
315        ItemType(String bundleKey) {
316            _bundleKey = bundleKey;
317        }
318
319        @Override
320        public String toString() {
321            return Bundle.getMessage(_bundleKey);
322        }
323    }
324
325    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WhereUsedFrame.class);
326
327}