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}