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