001package jmri.jmrix.loconet.soundloader; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.awt.Font; 005import java.io.IOException; 006import javax.swing.JButton; 007import javax.swing.JComboBox; 008import javax.swing.JFileChooser; 009import javax.swing.JFrame; 010import javax.swing.JScrollPane; 011import javax.swing.JTable; 012import javax.swing.JTextArea; 013import javax.swing.JTextField; 014import javax.swing.table.TableCellEditor; 015import jmri.jmrix.loconet.spjfile.SpjFile; 016import jmri.util.FileUtil; 017import jmri.util.davidflanagan.HardcopyWriter; 018import jmri.util.table.ButtonEditor; 019import jmri.util.table.ButtonRenderer; 020import org.slf4j.Logger; 021import org.slf4j.LoggerFactory; 022 023/** 024 * Table data model for display of Digitrax SPJ files. 025 * 026 * @author Bob Jacobsen Copyright (C) 2003, 2006 027 * @author Dennis Miller Copyright (C) 2006 028 */ 029public class EditorTableDataModel extends javax.swing.table.AbstractTableModel { 030 031 static public final int HEADERCOL = 0; 032 static public final int TYPECOL = 1; 033 static public final int MAPCOL = 2; 034 static public final int HANDLECOL = 3; 035 static public final int FILENAMECOL = 4; 036 static public final int LENGTHCOL = 5; 037 static public final int PLAYBUTTONCOL = 6; 038 static public final int REPLACEBUTTONCOL = 7; 039 040 static public final int NUMCOLUMN = 8; 041 042 SpjFile file; 043 044 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 045 justification = "cache resource at 1st start, threading OK") // NOI18N 046 public EditorTableDataModel(SpjFile file) { 047 super(); 048 this.file = file; 049 } 050 051 @Override 052 public int getRowCount() { 053 // The 0th header is not displayed 054 return file.numHeaders() - 1; 055 } 056 057 @Override 058 public int getColumnCount() { 059 return NUMCOLUMN; 060 } 061 062 @Override 063 public String getColumnName(int col) { 064 switch (col) { 065 case HEADERCOL: 066 return Bundle.getMessage("HeaderHEADERCOL"); 067 case TYPECOL: 068 return Bundle.getMessage("HeaderTYPECOL"); 069 case HANDLECOL: 070 return Bundle.getMessage("HeaderHANDLECOL"); 071 case MAPCOL: 072 return Bundle.getMessage("HeaderMAPCOL"); 073 case FILENAMECOL: 074 return Bundle.getMessage("HeaderFILENAMECOL"); 075 case LENGTHCOL: 076 return Bundle.getMessage("HeaderLENGTHCOL"); 077 case PLAYBUTTONCOL: 078 return ""; // no title 079 case REPLACEBUTTONCOL: 080 return ""; // no title 081 082 default: 083 return "unknown"; 084 } 085 } 086 087 @Override 088 public Class<?> getColumnClass(int col) { 089 switch (col) { 090 case HEADERCOL: 091 case HANDLECOL: 092 return Integer.class; 093 case LENGTHCOL: 094 return Float.class; 095 case MAPCOL: 096 case TYPECOL: 097 case FILENAMECOL: 098 return String.class; 099 case REPLACEBUTTONCOL: 100 case PLAYBUTTONCOL: 101 return JButton.class; 102 default: 103 return null; 104 } 105 } 106 107 @Override 108 public boolean isCellEditable(int row, int col) { 109 switch (col) { 110 case REPLACEBUTTONCOL: 111 case PLAYBUTTONCOL: 112 return true; 113 default: 114 return false; 115 } 116 } 117 118 @Override 119 public Object getValueAt(int row, int col) { 120 switch (col) { 121 case HEADERCOL: 122 return Integer.valueOf(row); 123 case HANDLECOL: 124 return Integer.valueOf(file.getHeader(row + 1).getHandle()); 125 case MAPCOL: 126 return file.getMapEntry(file.getHeader(row + 1).getHandle()); 127 case FILENAMECOL: 128 return "" + file.getHeader(row + 1).getName(); 129 case TYPECOL: 130 return file.getHeader(row + 1).typeAsString(); 131 case LENGTHCOL: 132 if (!file.getHeader(row + 1).isWAV()) { 133 return null; 134 } 135 float rate = (new jmri.jmrit.sound.WavBuffer(file.getHeader(row + 1).getByteArray())).getSampleRate(); 136 if (rate == 0.f) { 137 log.error("Rate should not be zero"); 138 return null; 139 } 140 float time = file.getHeader(row + 1).getDataLength() / rate; 141 return Float.valueOf(time); 142 case PLAYBUTTONCOL: 143 if (file.getHeader(row + 1).isWAV()) { 144 return Bundle.getMessage("ButtonPlay"); 145 } else if (file.getHeader(row + 1).isTxt()) { 146 return Bundle.getMessage("ButtonView"); 147 } else if (file.getHeader(row + 1).isMap()) { 148 return Bundle.getMessage("ButtonView"); 149 } else if (file.getHeader(row + 1).isSDF()) { 150 return Bundle.getMessage("ButtonView"); 151 } else { 152 return null; 153 } 154 case REPLACEBUTTONCOL: 155 if (file.getHeader(row + 1).isWAV()) { 156 return Bundle.getMessage("ButtonReplace"); 157 } 158 if (file.getHeader(row + 1).isSDF()) { 159 return Bundle.getMessage("ButtonEdit"); 160 } else { 161 return null; 162 } 163 default: 164 log.error("internal state inconsistent with table requst for {} {}", row, col); 165 return null; 166 } 167 } 168 169 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", 170 justification = "better to keep cases in column order rather than to combine") 171 public int getPreferredWidth(int col) { 172 JTextField b; 173 switch (col) { 174 case TYPECOL: 175 return new JTextField(8).getPreferredSize().width; 176 case MAPCOL: 177 return new JTextField(12).getPreferredSize().width; 178 case HEADERCOL: 179 case HANDLECOL: 180 return new JTextField(3).getPreferredSize().width; 181 case FILENAMECOL: 182 return new JTextField(12).getPreferredSize().width; 183 case LENGTHCOL: 184 return new JTextField(5).getPreferredSize().width; 185 case PLAYBUTTONCOL: 186 b = new JTextField((String) getValueAt(1, PLAYBUTTONCOL)); 187 return b.getPreferredSize().width + 30; 188 case REPLACEBUTTONCOL: 189 b = new JTextField((String) getValueAt(1, REPLACEBUTTONCOL)); 190 return b.getPreferredSize().width + 30; 191 default: 192 log.warn("Unexpected column in getPreferredWidth: {}", col); 193 return new JTextField(8).getPreferredSize().width; 194 } 195 } 196 197 @Override 198 public void setValueAt(Object value, int row, int col) { 199 if (col == PLAYBUTTONCOL) { 200 // button fired, handle 201 if (file.getHeader(row + 1).isWAV()) { 202 playButtonPressed(value, row, col); 203 return; 204 } else if (file.getHeader(row + 1).isTxt()) { 205 viewTxtButtonPressed(value, row, col); 206 return; 207 } else if (file.getHeader(row + 1).isMap()) { 208 viewTxtButtonPressed(value, row, col); 209 return; 210 } else if (file.getHeader(row + 1).isSDF()) { 211 viewSdfButtonPressed(value, row, col); 212 return; 213 } 214 } else if (col == REPLACEBUTTONCOL) { 215 // button fired, handle 216 if (file.getHeader(row + 1).isWAV()) { 217 replWavButtonPressed(value, row, col); 218 } else if (file.getHeader(row + 1).isSDF()) { 219 editSdfButtonPressed(value, row, col); 220 return; 221 } 222 } 223 } 224 225 // should probably be abstract and put in invoking GUI 226 static JFileChooser chooser; // shared across all uses 227 228 private synchronized static void setChooser( JFileChooser jfc ){ 229 chooser = jfc; 230 } 231 232 void replWavButtonPressed(Object value, int row, int col) { 233 if (chooser == null) { 234 setChooser( new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath())); 235 } 236 EditorTableDataModel.chooser.rescanCurrentDirectory(); 237 int retVal = EditorTableDataModel.chooser.showOpenDialog(null); 238 if (retVal != JFileChooser.APPROVE_OPTION) { 239 return; // give up if no file selected 240 } 241 // load file 242 jmri.jmrit.sound.WavBuffer buff; 243 try { 244 buff = new jmri.jmrit.sound.WavBuffer(chooser.getSelectedFile()); 245 } catch (Exception e) { 246 log.error("Exception loading file", e); 247 return; 248 } 249 // store to memory 250 file.getHeader(row + 1).setContent(buff.getByteArray(), buff.getDataStart(), buff.getDataSize()); 251 // update rest of header 252 file.getHeader(row + 1).setName(chooser.getSelectedFile().getName()); 253 254 // mark table changes in other rows 255 fireTableRowsUpdated(row, row); 256 } 257 258 // should probably be abstract and put in invoking GUI 259 void playButtonPressed(Object value, int row, int col) { 260 // new jmri.jmrit.sound.WavBuffer(file.getHeader(row+1).getByteArray()); 261 jmri.jmrit.sound.SoundUtil.playSoundBuffer(file.getHeader(row + 1).getByteArray()); 262 } 263 264 // should probably be abstract and put in invoking GUI 265 // Also used to display the .map block 266 void viewTxtButtonPressed(Object value, int row, int col) { 267 String content = new String(file.getHeader(row + 1).getByteArray()); 268 JFrame frame = new JFrame(); 269 JTextArea text = new JTextArea(content); 270 text.setEditable(false); 271 text.setFont(new Font("Monospaced", Font.PLAIN, text.getFont().getSize())); // NOI18N 272 frame.getContentPane().add(new JScrollPane(text)); 273 frame.pack(); 274 frame.setVisible(true); 275 } 276 277 // should probably be abstract and put in invoking GUI 278 void viewSdfButtonPressed(Object value, int row, int col) { 279 jmri.jmrix.loconet.sdf.SdfBuffer buff = new jmri.jmrix.loconet.sdf.SdfBuffer(file.getHeader(row + 1).getByteArray()); 280 String content = buff.toString(); 281 JFrame frame = new jmri.util.JmriJFrame(Bundle.getMessage("TitleSdfView")); 282 JTextArea text = new JTextArea(content); 283 text.setEditable(false); 284 text.setFont(new Font("Monospaced", Font.PLAIN, text.getFont().getSize())); // NOI18N 285 frame.getContentPane().add(new JScrollPane(text)); 286 frame.pack(); 287 frame.setVisible(true); 288 } 289 290 // should probably be abstract and put in invoking GUI 291 void editSdfButtonPressed(Object value, int row, int col) { 292 jmri.jmrix.loconet.sdfeditor.EditorFrame sdfEditor 293 = new jmri.jmrix.loconet.sdfeditor.EditorFrame(file.getHeader(row + 1).getSdfBuffer()); 294 sdfEditor.setVisible(true); 295 } 296 297 /** 298 * Configure a table to have our standard rows and columns. 299 * This is optional, in that other table formats can use this table model. 300 * But we put it here to help keep it consistent. 301 * @param table table to configured. 302 */ 303 public void configureTable(JTable table) { 304 // allow reordering of the columns 305 table.getTableHeader().setReorderingAllowed(true); 306 307 // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541) 308 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 309 310 // resize columns as requested 311 for (int i = 0; i < table.getColumnCount(); i++) { 312 int width = getPreferredWidth(i); 313 table.getColumnModel().getColumn(i).setPreferredWidth(width); 314 } 315 //table.sizeColumnsToFit(-1); 316 317 // have the value column hold a button 318 setColumnToHoldButton(table, PLAYBUTTONCOL, largestWidthButton(PLAYBUTTONCOL)); 319 setColumnToHoldButton(table, REPLACEBUTTONCOL, largestWidthButton(REPLACEBUTTONCOL)); 320 } 321 322 public JButton largestWidthButton(int col) { 323 JButton retval = new JButton("TTTT"); 324 if (col == PLAYBUTTONCOL) { 325 retval = checkLabelWidth(retval, "ButtonPlay"); 326 retval = checkLabelWidth(retval, "ButtonView"); 327 } else if (col == REPLACEBUTTONCOL) { 328 retval = checkLabelWidth(retval, "ButtonEdit"); 329 retval = checkLabelWidth(retval, "ButtonReplace"); 330 } 331 return retval; 332 } 333 334 private JButton checkLabelWidth(JButton now, String name) { 335 JButton b = new JButton(Bundle.getMessage(name)); 336 b.revalidate(); 337 if (b.getPreferredSize().width > now.getPreferredSize().width) { 338 return b; 339 } else { 340 return now; 341 } 342 } 343 344 /** 345 * Service method to set up a column so that it will hold a button for it's 346 * values. 347 * 348 * @param table The overall table, accessed for formatting 349 * @param column Which column to configure with this call 350 * @param sample Typical button, used for size 351 */ 352 void setColumnToHoldButton(JTable table, int column, JButton sample) { 353 //TableColumnModel tcm = table.getColumnModel(); 354 // install a button renderer & editor 355 ButtonRenderer buttonRenderer = new ButtonRenderer(); 356 table.setDefaultRenderer(JButton.class, buttonRenderer); 357 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 358 table.setDefaultEditor(JButton.class, buttonEditor); 359 // ensure the table rows, columns have enough room for buttons 360 table.setRowHeight(sample.getPreferredSize().height); 361 table.getColumnModel().getColumn(column) 362 .setPreferredWidth(sample.getPreferredSize().width + 30); 363 } 364 365 synchronized public void dispose() { 366 } 367 368 /** 369 * Self print - or print preview - the table. 370 * <p> 371 * Printed in equally sized 372 * columns across the page with headings and vertical lines between each 373 * column. Data is word wrapped within a column. Can handle data as strings, 374 * comboboxes or booleans. 375 * 376 * @param w the printer output to write to 377 */ 378 public void printTable(HardcopyWriter w) { 379 // determine the column size - evenly sized, with space between for lines 380 int columnSize = (w.getCharactersPerLine() - this.getColumnCount() - 1) / this.getColumnCount(); 381 382 // Draw horizontal dividing line 383 w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), 384 (columnSize + 1) * this.getColumnCount()); 385 386 // print the column header labels 387 String[] columnStrings = new String[this.getColumnCount()]; 388 // Put each column header in the array 389 for (int i = 0; i < this.getColumnCount(); i++) { 390 columnStrings[i] = this.getColumnName(i); 391 } 392 w.setFontStyle(Font.BOLD); 393 printColumns(w, columnStrings, columnSize); 394 w.setFontStyle(0); 395 w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), 396 (columnSize + 1) * this.getColumnCount()); 397 398 // now print each row of data 399 // create a base string the width of the column 400 StringBuilder spaces = new StringBuilder(""); 401 for (int i = 0; i < columnSize; i++) { 402 spaces.append(" "); 403 } 404 for (int i = 0; i < this.getRowCount(); i++) { 405 for (int j = 0; j < this.getColumnCount(); j++) { 406 //check for special, non string contents 407 if (this.getValueAt(i, j) == null) { 408 columnStrings[j] = spaces.toString(); 409 } else if (this.getValueAt(i, j) instanceof JComboBox) { 410 columnStrings[j] = (String) ((JComboBox<?>) this.getValueAt(i, j)).getSelectedItem(); 411 } else if (this.getValueAt(i, j) instanceof Boolean) { 412 columnStrings[j] = (this.getValueAt(i, j)).toString(); 413 } else { 414 columnStrings[j] = (String) this.getValueAt(i, j); 415 } 416 } 417 printColumns(w, columnStrings, columnSize); 418 w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), 419 (columnSize + 1) * this.getColumnCount()); 420 } 421 w.close(); 422 } 423 424 protected void printColumns(HardcopyWriter w, String columnStrings[], int columnSize) { 425 String columnString = ""; 426 StringBuilder lineString = new StringBuilder(""); 427 // create a base string the width of the column 428 StringBuilder spaces = new StringBuilder(""); 429 for (int i = 0; i < columnSize; i++) { 430 spaces.append(" "); 431 } 432 // loop through each column 433 boolean complete = false; 434 while (!complete) { 435 complete = true; 436 for (int i = 0; i < columnStrings.length; i++) { 437 // if the column string is too wide cut it at word boundary (valid delimiters are space, - and _) 438 // use the intial part of the text,pad it with spaces and place the remainder back in the array 439 // for further processing on next line 440 // if column string isn't too wide, pad it to column width with spaces if needed 441 if (columnStrings[i].length() > columnSize) { 442 boolean noWord = true; 443 for (int k = columnSize; k >= 1; k--) { 444 if (columnStrings[i].substring(k - 1, k).equals(" ") 445 || columnStrings[i].substring(k - 1, k).equals("-") 446 || columnStrings[i].substring(k - 1, k).equals("_")) { 447 columnString = columnStrings[i].substring(0, k) 448 + spaces.substring(columnStrings[i].substring(0, k).length()); 449 columnStrings[i] = columnStrings[i].substring(k); 450 noWord = false; 451 complete = false; 452 break; 453 } 454 } 455 if (noWord) { 456 columnString = columnStrings[i].substring(0, columnSize); 457 columnStrings[i] = columnStrings[i].substring(columnSize); 458 complete = false; 459 } 460 461 } else { 462 columnString = columnStrings[i] + spaces.substring(columnStrings[i].length()); 463 columnStrings[i] = ""; 464 } 465 lineString.append(columnString).append(" "); 466 } 467 try { 468 w.write(lineString.toString()); 469 //write vertical dividing lines 470 for (int i = 0; i < w.getCharactersPerLine(); i = i + columnSize + 1) { 471 w.write(w.getCurrentLineNumber(), i, w.getCurrentLineNumber() + 1, i); 472 } 473 w.write("\n"); // NOI18N 474 lineString = new StringBuilder(""); 475 } catch (IOException e) { 476 log.warn("error during printing:", e); 477 } 478 } 479 } 480 481 private final static Logger log = LoggerFactory.getLogger(EditorTableDataModel.class); 482 483}