package org.das2.qds.util; import java.awt.Color; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.text.ParseException; import java.util.Comparator; import java.util.UnknownFormatConversionException; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.JPopupMenu; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.border.MatteBorder; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.JTableHeader; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.EnumerationUnits; import org.das2.datum.TimeUtil; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import org.das2.datum.format.DatumFormatter; import org.das2.datum.format.FormatStringFormatter; import org.das2.datum.format.TimeDatumFormatter; import org.das2.util.LoggerManager; import org.das2.qds.DataSetOps; import org.das2.qds.DataSetUtil; import org.das2.qds.QDataSet; import org.das2.qds.SemanticOps; import org.das2.qds.examples.Schemes; import org.das2.qds.ops.Ops; import test.BundleBinsDemo; /** * TableModel that shows a QDataSet in a JTable. * @author jbf */ public class QDataSetTableModel extends AbstractTableModel { private static final Logger logger= LoggerManager.getLogger("qdataset.dsutil"); QDataSet ds; QDataSet wds; // weights for ds; QDataSet bundle1; QDataSet dep0; // rank 1 or rank 2 bins dataset. QDataSet dep1; // rank 1 or rank 2 bins dataset. int dep0Offset; int colCount; Units[] units; String[] labels; DatumFormatter[] df; /** * creates a QDataSetTableModel * @param ds dataset to adapt to a model/ */ public QDataSetTableModel( QDataSet ds ) { if ( ds.rank()==1 && SemanticOps.getUnits(ds) instanceof EnumerationUnits ) { ds= Ops.createEvents(ds); } this.ds = ds; this.wds= DataSetUtil.weightsDataSet(ds); this.dep0 = (QDataSet) ds.property(QDataSet.DEPEND_0); dep0Offset = dep0 == null ? 0 : 1; this.bundle1 = (QDataSet) ds.property(QDataSet.BUNDLE_1); this.dep1 = (QDataSet) ds.property(QDataSet.DEPEND_1); if ( dep1!=null && dep1.rank()>1 && !SemanticOps.isBins(dep1) ) { System.err.println("dep1 is sliced at 0"); } colCount = dep0Offset; if ( ds.rank()==1 ) { colCount+= 1; } else { colCount += ds.length(0); } units = new Units[colCount]; labels = new String[colCount]; df= new DatumFormatter[colCount]; int i = 0; if (dep0 != null) { units[i] = SemanticOps.getUnits(dep0); Datum dt= Ops.datum( DataSetUtil.guessCadence( dep0,null ) ); if ( dt!=null && UnitsUtil.isTimeLocation(units[i]) && dt.lt( Units.milliseconds.createDatum(1) ) ) { df[i]= TimeDatumFormatter.formatterForScale( TimeUtil.NANO, null ); } else { df[i]= units[i].getDatumFormatterFactory().defaultFormatter(); } labels[i] = (String) dep0.property(QDataSet.LABEL); i++; } if (bundle1 != null) { for (int j = 0; j < bundle1.length(); j++) { units[i] = (Units) bundle1.property(QDataSet.UNITS, j); if ( units[i]==null ) units[i]= Units.dimensionless; String format= (String)bundle1.property(QDataSet.FORMAT, j); if ( format==null ) { df[i]= units[i].getDatumFormatterFactory().defaultFormatter(); } else { df[i]= getDataFormatter( format, units[i] ); } labels[i] = (String) bundle1.property(QDataSet.LABEL, j); if ( labels[i]==null ) labels[i]= (String) bundle1.property(QDataSet.NAME, j); i++; } } else if (dep1 != null) { Units dep1Units = SemanticOps.getUnits(dep1); if (dep1Units == null) { dep1Units = Units.dimensionless; } int dep1len= ( dep1.rank()==1 || SemanticOps.isBins(dep1) ) ? dep1.length() : dep1.length(0); for (int k = 0; k < dep1len; k++) { units[i] = SemanticOps.getUnits(ds); df[i]= units[i].getDatumFormatterFactory().defaultFormatter(); if ( dep1.rank()==1 ) { labels[i] = dep1Units.createDatum(dep1.value(k)).toString(); } else { if ( SemanticOps.isBins(dep1) ) { DatumRange dr= DataSetUtil.asDatumRange( this.dep0.slice(k) ); labels[i] = dr.toString(); } else { labels[i] = dep1Units.createDatum(dep1.value(0,k)).toString() + "*"; } } i++; } } if ( this.ds.rank()==1 ) { labels[i]= (String) this.ds.property(QDataSet.LABEL); if ( labels[i]==null ) labels[i]= "data"; units[i]= SemanticOps.getUnits(ds); df[i]= units[i].getDatumFormatterFactory().defaultFormatter(); if ( !identifiesUnits( labels[i], units[i]) ) { labels[i]+= " (" +units[i] +")"; } i++; } for (i = 0; i < units.length; i++) { if (units[i] == null) { units[i] = Units.dimensionless; df[i]= units[i].getDatumFormatterFactory().defaultFormatter(); } if ( labels[i]==null ) { labels[i]= "col "+i; } } String format= (String) ds.property(QDataSet.FORMAT); if ( format!=null ) { DatumFormatter thedf= new FormatStringFormatter(format,false); for ( i=0; i<df.length; i++ ) { df[i]= thedf; } } } JTableHeader header; TableColumn column; JTextField text; JPopupMenu renamePopup; private void editColumnAt( Point p) { int columnIndex = header.columnAtPoint(p); if (columnIndex != -1) { column = header.getColumnModel().getColumn(columnIndex); Rectangle columnRectangle = header.getHeaderRect(columnIndex); text.setText(column.getHeaderValue().toString()); renamePopup.setPreferredSize( new Dimension(columnRectangle.width, columnRectangle.height - 1)); renamePopup.show(header, columnRectangle.x, 0); text.requestFocusInWindow(); text.selectAll(); } } private void renameColumn() { column.setHeaderValue(text.getText()); renamePopup.setVisible(false); header.repaint(); } public MouseListener getTableHeaderMouseListener( JTable jTable1 ) { header= jTable1.getTableHeader(); MouseListener result= new MouseAdapter() { @Override public void mouseClicked(MouseEvent event) { if (event.getClickCount() == 1) { editColumnAt(event.getPoint()); } } }; text = new JTextField(); text.setBorder(null); text.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { renameColumn(); } }); renamePopup = new JPopupMenu(); renamePopup.setBorder(new MatteBorder(0, 1, 1, 1, Color.DARK_GRAY)); renamePopup.add(text); return result; } /** * copied from AsciiTableDataSourceFormat. See String.format. * @param df the string, such as "%f9.2" * @param u the units, which will provide the formatter when the string doesn't work. * @return a DatumFormatter for the column. * @see String#format(java.lang.String, java.lang.Object...) */ private DatumFormatter getDataFormatter( String df, Units u ) { try { if ( df.trim().isEmpty() ) { return u.getDatumFormatterFactory().defaultFormatter(); } if ( !df.contains("%") ) df= "%"+df; //TODO: would be nice if we could verify formatter. I had %f5.2 instead of %5.2f and it wasn't telling me. return new FormatStringFormatter( df, false ); } catch ( UnknownFormatConversionException ex ) { logger.log(Level.FINER,null,ex); return u.getDatumFormatterFactory().defaultFormatter(); } catch ( RuntimeException ex ) { logger.log(Level.FINER,null,ex); return u.getDatumFormatterFactory().defaultFormatter(); } } private boolean identifiesUnits( String s, Units u ) { return ( s.contains( String.valueOf(u) ) ); } @Override public int getRowCount() { return ds.length(); } @Override public int getColumnCount() { return colCount; } /** * return the units of the column. * @param columnIndex * @return */ public Units getColumnUnits( int columnIndex ) { return this.units[columnIndex]; } /** * return the sorter to use with any column. * @param tm * @return */ public static TableRowSorter getRowSorter( final TableModel tm ) { return new TableRowSorter(tm) { @Override public Comparator getComparator(int col) { if ( tm instanceof QDataSetTableModel ) { // it is... QDataSetTableModel qtm= (QDataSetTableModel)tm; final Units u= qtm.getColumnUnits(col); if ( u instanceof EnumerationUnits ) { return super.getComparator(col); } else { return new Comparator() { @Override public int compare(Object o1, Object o2) { //wow, all the data has been converted to Strings, wonder why... Datum d1=null; Datum d2=null; if ( o1 instanceof String ) { try { d1= u.parse((String)o1); } catch (ParseException ex) { logger.fine("parse exception"); } } if ( o2 instanceof String ) { try { d2= u.parse((String)o2); } catch (ParseException ex) { logger.fine("parse exception"); } } if ( d1!=null && d2!=null ) { try { return d1.compareTo(d2); } catch ( IllegalArgumentException ex ) { // this too should not happen. return o1.toString().compareTo(o2.toString()); } } else { // this too should not happen. return o1.toString().compareTo(o2.toString()); } } }; } } return super.getComparator(col); } }; } @Override public Object getValueAt(int rowIndex, int columnIndex) { if (columnIndex < dep0Offset) { if ( this.dep0.rank()==2 ) { DatumRange dr= DataSetUtil.asDatumRange( this.dep0.slice(rowIndex) ); return dr.toString(); } else { Datum d= units[columnIndex].createDatum(this.dep0.value(rowIndex)); try { return df[columnIndex].format( d,units[columnIndex] ); } catch ( IllegalArgumentException ex ) { return d.toString(); // for example times when format is %5.2f } } } else { if ( this.ds.rank()==1 ) { if ( wds.value(rowIndex)==0 ) { return "fill ("+this.ds.value(rowIndex)+")"; } else { Datum d= units[columnIndex].createDatum(this.ds.value(rowIndex)); return df[columnIndex].format(d,units[columnIndex]); } } else if (this.ds.rank()==2 ) { if ( wds.value(rowIndex, columnIndex - dep0Offset)==0 ) { return "fill ("+this.ds.value(rowIndex, columnIndex - dep0Offset)+")"; } else { double d= this.ds.value(rowIndex, columnIndex - dep0Offset); if ( d>-1e31 ) { Datum datum= units[columnIndex].createDatum(d); return df[columnIndex].format(datum,units[columnIndex]); } else { return "fill ("+d+")"; } } } else { return "?????"; } } } /** * this currently isn't used because there's a bug. * @return the table model. */ public TableColumnModel getTableColumnModel() { DefaultTableColumnModel result = new DefaultTableColumnModel(); QDataSet bds= (QDataSet) ds.property(QDataSet.BUNDLE_1); if ( bds!=null ) bds= DataSetOps.flattenBundleDescriptor(bds); for (int i = 0; i < colCount; i++) { TableColumn c = new TableColumn(i); Units u; if (i < dep0Offset) { c.setHeaderValue(dep0.property(QDataSet.LABEL)); u= (Units) dep0.property(QDataSet.UNITS); } else { c.setHeaderValue(labels[i]); if ( bds==null ) { u= (Units) ds.property(QDataSet.UNITS); } else { u= (Units) bds.property(QDataSet.UNITS,i-dep0Offset); } } c.setPreferredWidth( ( u!=null && UnitsUtil.isTimeLocation(u) ) ? 150 : 80 ); c.setMinWidth( ( u!=null && UnitsUtil.isTimeLocation(u) ) ? 130 : 80 ); if ( u instanceof EnumerationUnits && ds.length()>0 ) { String s= ds.slice(0).slice(i-dep0Offset).toString(); if ( s.length()>14 ) { c.setPreferredWidth( Math.min( s.length() * 7, 600 ) ); //c.setMinWidth( s.length()*4 ); } } result.addColumn( c ); } return result; } @Override public String getColumnName( int i ) { if (i < dep0Offset) { return (String)dep0.property(QDataSet.LABEL); } else { return labels[i]; } } public static void main(String[] args) { QDataSet ds = BundleBinsDemo.demo1(); QDataSetTableModel m = new QDataSetTableModel(ds); JTable t = new JTable(); t.setModel(m); t.setColumnModel(m.getTableColumnModel()); JFrame frame = new JFrame(); frame.getContentPane().add(t); frame.pack(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }