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