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 jmri.util.swing.JmriJFileChooser(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}