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