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