001package jmri.jmrit.beantable;
002
003import java.awt.Container;
004import java.awt.FlowLayout;
005import java.awt.GridBagConstraints;
006import java.awt.GridBagLayout;
007import java.awt.event.ActionEvent;
008import java.awt.event.ItemEvent;
009import java.beans.PropertyVetoException;
010import javax.swing.*;
011import javax.swing.filechooser.FileNameExtensionFilter;
012
013import jmri.InstanceManager;
014import jmri.Manager;
015import jmri.jmrit.logixng.Table;
016import jmri.util.FileUtil;
017import jmri.util.JmriJFrame;
018
019import jmri.jmrit.logixng.NamedTable;
020import jmri.jmrit.logixng.NamedTableManager;
021import jmri.jmrit.logixng.tools.swing.AbstractLogixNGEditor;
022import jmri.jmrit.logixng.tools.swing.TableEditor;
023
024/**
025 * Swing action to create and register a LogixNG Table.
026 * <p>
027 Also contains the panes to create, edit, and delete a LogixNG.
028 <p>
029 * Most of the text used in this GUI is in BeanTableBundle.properties, accessed
030 * via Bundle.getMessage().
031 *
032 * @author Dave Duchamp Copyright (C) 2007 (LogixTableAction)
033 * @author Pete Cressman Copyright (C) 2009, 2010, 2011 (LogixTableAction)
034 * @author Matthew Harris copyright (c) 2009 (LogixTableAction)
035 * @author Dave Sand copyright (c) 2017 (LogixTableAction)
036 * @author Daniel Bergqvist copyright (c) 2019
037 * @author Dave Sand copyright (c) 2021
038 * @author J. Scott Walton (c) 2022 (Csv types)
039 */
040public class LogixNGTableTableAction extends AbstractLogixNGTableAction<NamedTable> {
041
042    JRadioButton _typeExternalTable = new JRadioButton(Bundle.getMessage("LogixNG_typeExternalTable"));
043    JRadioButton _typeInternalTable = new JRadioButton(Bundle.getMessage("LogixNG_typeInternalTable"));
044    ButtonGroup _buttonGroup = new ButtonGroup();
045    JTextField _csvFileName = new JTextField(50);
046
047    ButtonGroup _csvGroup = new ButtonGroup();
048    JRadioButton _csvTabbed = new JRadioButton(Table.CsvType.TABBED.toString());
049    JRadioButton _csvComma = new JRadioButton(Table.CsvType.COMMA.toString());
050
051    JLabel _csvLabel = new JLabel(Bundle.getMessage("LogixNG_CsvType") + ":");
052    /**
053     * Create a LogixNGTableAction instance.
054     *
055     * @param s the Action title, not the title of the resulting frame. Perhaps
056     *          this should be changed?
057     */
058    public LogixNGTableTableAction(String s) {
059        super(s);
060    }
061
062    /**
063     * Create a LogixNGTableAction instance with default title.
064     */
065    public LogixNGTableTableAction() {
066        this(Bundle.getMessage("TitleLogixNGTableTable"));
067    }
068
069    @Override
070    protected void setTitle() {
071        f.setTitle(Bundle.getMessage("TitleLogixNGTableTable"));
072    }
073
074    @Override
075    public String getClassDescription() {
076        return Bundle.getMessage("TitleLogixNGTableTable");        // NOI18N
077    }
078
079    @Override
080    protected AbstractLogixNGEditor<NamedTable> getEditor(BeanTableDataModel<NamedTable> m, String sName) {
081        return new TableEditor(m, sName);
082    }
083
084    @Override
085    protected Manager<NamedTable> getManager() {
086        return InstanceManager.getDefault(NamedTableManager.class);
087    }
088
089    @Override
090    protected void enableAll(boolean enable) {
091        // Not used by the tables table
092    }
093
094    @Override
095    protected void setEnabled(NamedTable bean, boolean enable) {
096        // Not used by the tables table
097    }
098
099    @Override
100    protected boolean isEnabled(NamedTable bean) {
101        return true;
102    }
103
104    @Override
105    protected NamedTable createBean(String userName) {
106        String systemName = InstanceManager.getDefault(NamedTableManager.class).getAutoSystemName();
107        return createBean(systemName, userName);
108    }
109
110    @Override
111    protected NamedTable createBean(String systemName, String userName) {
112        if (_typeExternalTable.isSelected()) {
113            String fileName = _csvFileName.getText();
114            if (fileName == null || fileName.isEmpty()) {
115                jmri.util.swing.JmriJOptionPane.showMessageDialog(addLogixNGFrame,
116                        Bundle.getMessage("LogixNGError2"), Bundle.getMessage("ErrorTitle"), // NOI18N
117                        jmri.util.swing.JmriJOptionPane.ERROR_MESSAGE);
118                return null;
119            }
120            if (_csvTabbed.isSelected()) {
121                return InstanceManager.getDefault(NamedTableManager.class)
122                        .newCSVTable(systemName, userName, fileName, Table.CsvType.TABBED);
123            } else if (_csvComma.isSelected()) {
124                return InstanceManager.getDefault(NamedTableManager.class).newCSVTable(systemName, userName, fileName, Table.CsvType.COMMA);
125            }
126        } else if (_typeInternalTable.isSelected()) {
127            // Open table editor
128        } else {
129            log.error("No table type selected");
130            throw new RuntimeException("No table type selected");
131        }
132
133//        InstanceManager.getDefault(NamedTableManager.class).loadTableFromCSV(file, systemName, userName);
134        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
135    }
136
137    @Override
138    protected void deleteBean(NamedTable bean) {
139        try {
140            InstanceManager.getDefault(NamedTableManager.class).deleteBean(bean, "DoDelete");
141        } catch (PropertyVetoException e) {
142            //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto
143            log.error("{} : Could not Delete.", e.getMessage());
144        }
145    }
146
147    @Override
148    protected boolean browseMonoSpace() {
149        return true;
150    }
151
152    @Override
153    protected String getBeanText(NamedTable bean) {
154        int maxColumnWidth = 0;
155        int columnWidth[] = new int[bean.numColumns()+1];
156        String[][] cells = new String[bean.numRows()+1][];
157        for (int row=0; row <= bean.numRows(); row++) {
158            cells[row] = new String[bean.numColumns()+1];
159            for (int col=0; col <= bean.numColumns(); col++) {
160                Object value = bean.getCell(row, col);
161                cells[row][col] = value != null ? value.toString() : "<null>";
162                columnWidth[col] = Math.max(columnWidth[col], cells[row][col].length());
163                maxColumnWidth = Math.max(maxColumnWidth, columnWidth[col]);
164            }
165        }
166        String columnLine = "-".repeat(maxColumnWidth+2);
167        String columnPadding = " ".repeat(maxColumnWidth);
168        StringBuilder sb = new StringBuilder();
169        sb.append("+");
170        for (int col=0; col <= bean.numColumns(); col++) {
171            sb.append(columnLine.substring(0,columnWidth[col]+2));
172            sb.append("+");
173            if (col == bean.numColumns()) sb.append(String.format("%n"));
174        }
175        for (int row=0; row <= bean.numRows(); row++) {
176            sb.append("|");
177            for (int col=0; col <= bean.numColumns(); col++) {
178                sb.append(" ");
179                sb.append((cells[row][col]+columnPadding).substring(0,columnWidth[col]));
180                sb.append(" |");
181                if (col == bean.numColumns()) sb.append(String.format("%n"));
182            }
183            sb.append("+");
184            for (int col=0; col <= bean.numColumns(); col++) {
185                sb.append(columnLine.substring(0,columnWidth[col]+2));
186                sb.append("+");
187                if (col == bean.numColumns()) sb.append(String.format("%n"));
188            }
189        }
190        return sb.toString();
191    }
192
193    @Override
194    protected String getBrowserTitle() {
195        return Bundle.getMessage("LogixNG_Table_Browse_Title");
196    }
197
198    @Override
199    protected String getAddTitleKey() {
200        return "TitleLogixNGTableTable";
201    }
202
203    @Override
204    protected String getCreateButtonHintKey() {
205        return "LogixNGTableCreateButtonHint";
206    }
207
208    @Override
209    protected String helpTarget() {
210        return "package.jmri.jmrit.beantable.LogixNGTableTable";  // NOI18N
211    }
212
213    private JButton createFileChooser() {
214        JButton selectFileButton = new JButton("..."); // "File" replaced by ...
215        selectFileButton.setMaximumSize(selectFileButton.getPreferredSize());
216        selectFileButton.setToolTipText(Bundle.getMessage("LogixNG_FileButtonHint"));  // NOI18N
217        selectFileButton.addActionListener((ActionEvent e) -> {
218            JFileChooser csvFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
219            csvFileChooser.setFileFilter(new FileNameExtensionFilter("CSV files", "csv", "txt", "tsv")); // NOI18N
220            csvFileChooser.rescanCurrentDirectory();
221            int retVal = csvFileChooser.showOpenDialog(null);
222            // handle selection or cancel
223            if (retVal == JFileChooser.APPROVE_OPTION) {
224                // set selected file location
225                try {
226                    _csvFileName.setText(FileUtil.getPortableFilename(csvFileChooser.getSelectedFile().getCanonicalPath()));
227                } catch (java.io.IOException ex) {
228                    log.error("exception setting file location", ex);  // NOI18N
229                    _csvFileName.setText("");
230                }
231            }
232        });
233        return selectFileButton;
234    }
235
236    /**
237     * Create or copy bean frame.
238     *
239     * @param titleId   property key to fetch as title of the frame (using Bundle)
240     * @param startMessageId part 1 of property key to fetch as user instruction on
241     *                  pane, either 1 or 2 is added to form the whole key
242     * @return the button JPanel
243     */
244    @Override
245    protected JPanel makeAddFrame(String titleId, String startMessageId) {
246        addLogixNGFrame = new JmriJFrame(Bundle.getMessage(titleId));
247        addLogixNGFrame.addHelpMenu(
248                "package.jmri.jmrit.beantable.LogixNGTableTable", true);     // NOI18N
249        addLogixNGFrame.setLocation(50, 30);
250        Container contentPane = addLogixNGFrame.getContentPane();
251        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
252
253        JPanel p;
254        p = new JPanel();
255        p.setLayout(new FlowLayout());
256        p.setLayout(new GridBagLayout());
257        GridBagConstraints c = new GridBagConstraints();
258        c.gridwidth = 1;
259        c.gridheight = 1;
260
261        c.gridx = 0;
262        c.gridy = 0;
263        c.anchor = GridBagConstraints.EAST;
264
265        p.add(_sysNameLabel, c);
266        _sysNameLabel.setLabelFor(_systemName);
267        c.gridy = 1;
268        p.add(_userNameLabel, c);
269        _userNameLabel.setLabelFor(_addUserName);
270        c.gridy = 2;
271        _csvLabel.setLabelFor(null);
272        p.add(_csvLabel, c);
273        JPanel csvPanel = new JPanel();
274        csvPanel.setLayout(new FlowLayout());
275        _csvGroup.add(_csvTabbed);
276        _csvGroup.add(_csvComma);
277        _csvTabbed.setSelected(true);
278        csvPanel.add(_csvTabbed);
279        csvPanel.add(_csvComma);
280        c.gridx = 1;
281        p.add(csvPanel,c);
282        c.gridx = 0;
283        c.gridy = 3;
284        p.add(new JLabel(Bundle.getMessage("LogixNG_CsvFileName")), c);
285
286        c.gridx = 1;
287        c.gridy = 0;
288        c.anchor = GridBagConstraints.WEST;
289        c.weightx = 1.0;
290        c.fill = GridBagConstraints.HORIZONTAL;  // text field will expand
291        p.add(_systemName, c);
292        c.gridy = 1;
293        p.add(_addUserName, c);
294
295        c.gridy = 3;
296        createFileChooser();
297        p.add(createFileChooser(), c);
298
299        c.gridx = 2;        // make room for file selector
300        c.gridwidth = GridBagConstraints.REMAINDER;
301        p.add(_csvFileName, c);
302
303        c.gridwidth = 1;
304        c.gridx = 2;
305        c.gridy = 1;
306        c.anchor = GridBagConstraints.WEST;
307        c.weightx = 1.0;
308        c.fill = GridBagConstraints.HORIZONTAL;  // text field will expand
309        c.gridy = 0;
310        p.add(_autoSystemName, c);
311
312
313        _buttonGroup.add(_typeExternalTable);
314        _buttonGroup.add(_typeInternalTable);
315        _typeExternalTable.setSelected(true);
316        _typeInternalTable.setEnabled(false);
317
318        _addUserName.setToolTipText(Bundle.getMessage("LogixNGUserNameHint"));    // NOI18N
319        _systemName.setToolTipText(Bundle.getMessage("LogixNGSystemNameHint"));   // NOI18N
320        contentPane.add(p);
321
322        JPanel panel98 = new JPanel();
323        panel98.setLayout(new FlowLayout());
324        JPanel panel99 = new JPanel();
325        panel99.setLayout(new BoxLayout(panel99, BoxLayout.Y_AXIS));
326        panel99.add(_typeExternalTable, c);
327        panel99.add(_typeInternalTable, c);
328        panel98.add(panel99);
329        contentPane.add(panel98);
330
331        // set up message
332        JPanel panel3 = new JPanel();
333        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
334        JPanel panel31 = new JPanel();
335        panel31.setLayout(new FlowLayout());
336        JLabel message1 = new JLabel(Bundle.getMessage(startMessageId + "LogixNGTableMessage1"));  // NOI18N
337        panel31.add(message1);
338        JPanel panel32 = new JPanel();
339        JLabel message2 = new JLabel(Bundle.getMessage(startMessageId + "LogixNGTableMessage2"));  // NOI18N
340        panel32.add(message2);
341        panel3.add(panel31);
342        panel3.add(panel32);
343        contentPane.add(panel3);
344
345        // set up create and cancel buttons
346        JPanel panel5 = new JPanel();
347        panel5.setLayout(new FlowLayout());
348        // Cancel
349        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));    // NOI18N
350        panel5.add(cancel);
351        cancel.addActionListener(this::cancelAddPressed);
352        cancel.setToolTipText(Bundle.getMessage("CancelLogixNGButtonHint"));      // NOI18N
353
354        addLogixNGFrame.addWindowListener(new java.awt.event.WindowAdapter() {
355            @Override
356            public void windowClosing(java.awt.event.WindowEvent e) {
357                cancelAddPressed(null);
358            }
359        });
360        contentPane.add(panel5);
361
362        _autoSystemName.addItemListener((ItemEvent e) -> {
363            autoSystemName();
364        });
365        return panel5;
366    }
367
368    @Override
369    protected void getListenerRefsIncludingChildren(NamedTable table, java.util.List<String> list) {
370        // Do nothing
371    }
372
373    @Override
374    protected boolean hasChildren(NamedTable table) {
375        // Tables doesn't have children
376        return false;
377    }
378
379    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNGTableTableAction.class);
380
381}