001    package jigcell.compare.views;
002    
003    import java.awt.Color;
004    import java.beans.PropertyChangeEvent;
005    import java.util.Arrays;
006    import java.util.Iterator;
007    import javax.swing.JFileChooser;
008    import javax.swing.JPopupMenu;
009    import javax.swing.ListSelectionModel;
010    import jigcell.compare.IConfigEditor;
011    import jigcell.compare.IDataElement;
012    import jigcell.compare.IDataGenerator;
013    import jigcell.compare.IEditableDataGenerator;
014    import jigcell.compare.ITab;
015    import jigcell.compare.ITypeChecker;
016    import jigcell.compare.IWriteableDataSource;
017    import jigcell.compare.data.DataGeneratorList;
018    import jigcell.compare.data.EditableDataGenerator;
019    import jigcell.compare.data.FlippedDataElement;
020    import jigcell.compare.data.IDataGeneratorList;
021    import jigcell.compare.data.SparseTreeDataElement;
022    import jigcell.compare.data.type.TimeSeriesTypeChecker;
023    import jigcell.compare.data.type.TypeChecker;
024    import jigcell.compare.impl.Compare;
025    import jigcell.compare.impl.SuffixFileFilter;
026    import jigcell.compare.plotter.IPlotter;
027    import jigcell.compare.plotter.PlotException;
028    import jigcell.compare.ui.BasicTable;
029    import jigcell.compare.ui.ConfigEditor;
030    import jigcell.compare.ui.EditorDialog;
031    import jigcell.compare.ui.ICellEditorTab;
032    import jigcell.compare.ui.IClipboardTab;
033    import jigcell.compare.ui.IDataEditorTab;
034    import jigcell.compare.ui.IRowEditorTab;
035    import jigcell.compare.ui.InterfaceBuilder;
036    import jigcell.compare.ui.SeriesEditorDialog;
037    
038    /**
039     * An editable display for presenting a two-dimensional set of data organized by generators.
040     *
041     * <p>
042     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
043     * </p>
044     *
045     * @author Nicholas Allen
046     */
047    
048    public class EditableSeriesView extends SeriesView implements ICellEditorTab, IClipboardTab, IDataEditorTab, IRowEditorTab {
049    
050       /**
051        * Property key for the plotter class
052        */
053    
054       public final static String CONFIG_PLOTTER = "EditableSeriesView.plotter";
055    
056       /**
057        * Name of popup with plotter options
058        */
059    
060       protected final static String POPUP_PLOTTER = "plotter";
061    
062       /**
063        * File filter for experimental data files
064        */
065    
066       protected final static SuffixFileFilter FILTER_EXPERIMENT = new SuffixFileFilter (".dat", "Data Series (*.dat)");
067    
068       /**
069        * Data model of the generators
070        */
071    
072       protected EditableSeriesModel model;
073    
074       /**
075        * Plotter
076        */
077    
078       protected IPlotter plotter;
079    
080       /**
081        * Table model for an EditableDataSeriesView.
082        */
083    
084       protected class EditableSeriesModel extends SeriesModel {
085    
086          /**
087           * Error message when plotting a cell with no data
088           */
089    
090          protected final static String MESSAGE_PLOTNODATAERROR = "No data to plot.";
091    
092          /**
093           * Creates a new table model.
094           */
095    
096          public EditableSeriesModel () {
097             super ();
098          }
099    
100          /**
101           * Starts editing a cell in the table.
102           *
103           * @param row Row
104           * @param column Column
105           */
106    
107          public void editCellAt (int row, int column) {
108             if (!isCellEditable (row, column))
109                return;
110             IEditableDataGenerator generator = (IEditableDataGenerator) generators.get (row);
111             if (column != findColumn (COLUMN_VALUE) || generator.getElement ().isScalar ())
112                new EditorDialog (compare, generator.getName (), row, column, model).setVisible (true);
113             else
114                new SeriesEditorDialog (compare, generator, row, column, model).setVisible (true);
115          }
116    
117          /**
118           * Whether a value in the table is editable.
119           *
120           * @param row Row
121           * @param column Column
122           */
123    
124          public boolean isCellEditable (int row, int column) {
125             return row >= 0 && row < getRowCount () && generators.get (row) instanceof IEditableDataGenerator && column >= 0 &&
126                column < getColumnCount ();
127          }
128    
129          /**
130           * Sets a value in the table.
131           *
132           * @param value Value
133           * @param row Row
134           * @param column Column
135           */
136    
137          public void setValueAt (Object value, int row, int column) {
138             if (!isCellEditable (row, column) || !(value instanceof String))
139                return;
140             if (column == findColumn (COLUMN_NAME)) {
141                try {
142                   ((IEditableDataGenerator) generators.get (row)).setName ((String) value);
143                   fireTableRowsUpdated (row, row);
144                   compare.firePropertyChange (IDataGenerator.PROPERTY_GENERATOR_EDIT);
145                } catch (IllegalArgumentException e) {
146                   compare.shellHandleException (Compare.MESSAGE_ERROR, Compare.getString ("EditableSeriesView.nameSetError"), e);
147                }
148                return;
149             }
150             if (column == findColumn (COLUMN_VALUE)) {
151                try {
152                   ((IEditableDataGenerator) generators.get (row)).setElement (SparseTreeDataElement.createElement ((String) value));
153                   fireTableRowsUpdated (row, row);
154                   compare.firePropertyChange (IDataGenerator.PROPERTY_GENERATOR_EDIT);
155                } catch (IllegalArgumentException e) {
156                   compare.shellHandleException (Compare.MESSAGE_ERROR, Compare.getString ("EditableSeriesView.valueSetError"), e);
157                }
158                return;
159             }
160             if (column == findColumn (COLUMN_COMMENT)) {
161                ((IEditableDataGenerator) generators.get (row)).setComment ((String) value);
162                fireTableRowsUpdated (row, row);
163                compare.firePropertyChange (IDataGenerator.PROPERTY_GENERATOR_EDIT);
164             }
165          }
166    
167          /**
168           * Builds a time series type for the generator.
169           *
170           * @param generator Generator
171           */
172    
173          protected TimeSeriesTypeChecker computeTypeForPlot (IDataGenerator generator) {
174             ITypeChecker rawType = TypeChecker.getTypeCheckerForGenerator (generator);
175             if (rawType == null) {
176                String typeName = generator.getAttribute (ITypeChecker.ATTRIBUTE_TYPE);
177                if (typeName != null) {
178                   TimeSeriesTypeChecker type = new TimeSeriesTypeChecker (typeName);
179                   if (typeName.equals (type.getName ()))
180                      generator.setOption (ITypeChecker.OPTION_TYPECHECKER, type);
181                   return type;
182                }
183             }
184             if (rawType instanceof TimeSeriesTypeChecker)
185                return (TimeSeriesTypeChecker) rawType;
186             return (TimeSeriesTypeChecker) TimeSeriesTypeChecker.getInstance ();
187          }
188    
189          /**
190           * Plots the data contained in a table cell.
191           *
192           * @param row Row
193           * @param column Column
194           */
195    
196          protected void plotCellAt (int row, int column) throws PlotException {
197             IDataGenerator experiment = (IDataGenerator) generators.get (row);
198             IDataElement experimentElement = FlippedDataElement.flip (experiment.getElement ());
199             long experimentCount = experimentElement.getLength ();
200             if (experimentCount == 0)
201                throw new PlotException (MESSAGE_PLOTNODATAERROR);
202             TimeSeriesTypeChecker type = computeTypeForPlot (experiment);
203             plotter.setData (experimentElement);
204             for (long experimentIndex = 1; experimentIndex <= experimentCount; experimentIndex++)
205                plotter.setSeriesName (experimentIndex, type.getName (experimentIndex));
206             plotter.setSeriesCombine (IPlotter.Combine.ACROSS);
207             plotter.setTitle (experiment.getName ());
208             plotter.plot ();
209          }
210       }
211    
212       /**
213        * Creates a new table view with the ability to edit data.
214        *
215        * @param compare Comparator backend to interface with
216        * @param configMarker Marker for retrieving configuration information from Comparator backend
217        */
218    
219       public EditableSeriesView (Compare compare, String configMarker) {
220          super (compare, configMarker);
221       }
222    
223       /**
224        * {@inheritDoc}
225        */
226    
227       public void addRow () {
228          addRows (1);
229       }
230    
231       /**
232        * {@inheritDoc}
233        */
234    
235       public void addRows (int count) {
236          for (int i = 0; i < count; i++)
237             generators.add (new EditableDataGenerator ());
238          model.fireTableDataChanged ();
239          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
240       }
241    
242       /**
243        * {@inheritDoc}
244        */
245    
246       public void clear () {
247          generators.clear ();
248          generators.addAll (createGenerators ());
249          model.fireTableDataChanged ();
250          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
251       }
252    
253       /**
254        * {@inheritDoc}
255        */
256    
257       public void clearAndNew () {
258          setCurrentSource (null);
259          clear ();
260       }
261    
262       /**
263        * {@inheritDoc}
264        */
265    
266       public void clipboardCut () {
267          IDataGeneratorList list = (IDataGeneratorList) generators.clone ();
268          if (!clipboardWrite (list, Compare.getString ("EditableSeriesView.cutError")))
269             return;
270          generators.clear ();
271          generators.addAll (createGenerators ());
272          model.fireTableDataChanged ();
273          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
274       }
275    
276       /**
277        * {@inheritDoc}
278        */
279    
280       public void clipboardCut (int rows []) {
281          IDataGeneratorList list = new DataGeneratorList ();
282          Arrays.sort (rows);
283          int l = rows.length;
284          for (int i = 0; i < l; i++)
285             list.add (generators.get (rows [i]));
286          if (!clipboardWrite (list, Compare.getString ("EditableSeriesView.cutError")))
287             return;
288          for (int i = l - 1; i >= 0; i--)
289             generators.remove (rows [i]);
290          if (generators.isEmpty ())
291             generators.addAll (createGenerators ());
292          model.fireTableDataChanged ();
293          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
294       }
295    
296       /**
297        * {@inheritDoc}
298        */
299    
300       public void clipboardPaste () {
301          try {
302             generators.addAll ((IDataGeneratorList) clipboard.read ());
303             model.fireTableDataChanged ();
304             compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
305          } catch (Exception e) {
306             compare.shellHandleException (Compare.MESSAGE_ERROR, Compare.getString ("EditableSeriesView.pasteError"), e);
307          }
308       }
309    
310       /**
311        * {@inheritDoc}
312        */
313    
314       public IConfigEditor createConfigEditor () {
315          IConfigEditor editor = new ConfigEditor (compare, this, configMarkers, getConfigForView ());
316          editor.addOption (CONFIG_TABNAME, "Name", String.class);
317          editor.addOption (CONFIG_RECENTSOURCECOUNT, "File History Size", Integer.class);
318          editor.addOption (CONFIG_PLOTTER, "Plotter", Class.class);
319          editor.addOption (CONFIG_TEXTCUTOFF, "Text Display Cutoff", Integer.class);
320          editor.addOption (BasicTable.BasicRenderer.CONFIG_BACKGROUNDEDITABLE, "Editable Cell Background Color", Color.class);
321          editor.addOption (BasicTable.BasicRenderer.CONFIG_BACKGROUNDUNEDITABLE, "Uneditable Cell Background Color", Color.class);
322          editor.addOption (BasicTable.BasicEditor.CONFIG_FOREGROUNDMODIFIED, "Modified Cell Foreground Color", Color.class);
323          return editor;
324       }
325    
326       /**
327        * {@inheritDoc}
328        */
329    
330       public void deleteRow (int row) {
331          generators.remove (row);
332          if (generators.isEmpty ())
333             generators.addAll (createGenerators ());
334          model.fireTableDataChanged ();
335          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
336       }
337    
338       /**
339        * {@inheritDoc}
340        */
341    
342       public void deleteRows (int rows []) {
343          int i = rows.length - 1;
344          if (i == -1)
345             return;
346          for (Arrays.sort (rows); i >= 0; i--)
347             generators.remove (rows [i]);
348          if (generators.isEmpty ())
349             generators.addAll (createGenerators ());
350          model.fireTableDataChanged ();
351          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
352       }
353    
354       /**
355        * {@inheritDoc}
356        */
357    
358       public void editCell (int row, int column) {
359          model.editCellAt (row, column);
360       }
361    
362       /**
363        * {@inheritDoc}
364        */
365    
366       public void fillDown (int row, int column) {
367          model.fillRangeAt (row, column, row + 1, generators.size () - 1);
368       }
369    
370       /**
371        * {@inheritDoc}
372        */
373    
374       public void fillSelected (int row, int column, int rows []) {
375          model.fillSelected (row, column, rows);
376       }
377    
378       /**
379        * {@inheritDoc}
380        */
381    
382       public void fillUp (int row, int column) {
383          model.fillRangeAt (row, column, 0, row - 1);
384       }
385    
386       /**
387        * {@inheritDoc}
388        */
389    
390       public void insertRow (int row) {
391          generators.add (row, new EditableDataGenerator ());
392          model.fireTableDataChanged ();
393          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
394       }
395    
396       /**
397        * {@inheritDoc}
398        */
399    
400       public void insertRows (int row, int count) {
401          for (int i = 0; i < count; i++)
402             generators.add (row, new EditableDataGenerator ());
403          model.fireTableDataChanged ();
404          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
405       }
406    
407       /**
408        * {@inheritDoc}
409        */
410    
411       public void moveRowsDown (int rows []) {
412          int i = rows.length - 1;
413          if (i == -1)
414             return;
415          Arrays.sort (rows);
416          for (int length = generators.size (); rows [i] == --length; )
417             if (i-- == 0)
418                return;
419          for (; i >= 0; i--) {
420             int pos = rows [i]++;
421             IDataGenerator generator = (IDataGenerator) generators.get (pos + 1);
422             generators.set (pos + 1, generators.get (pos));
423             generators.set (pos, generator);
424          }
425          model.fireTableDataChanged ();
426          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
427          setSelectedRows (rows);
428       }
429    
430       /**
431        * {@inheritDoc}
432        */
433    
434       public void moveRowsUp (int rows []) {
435          int l = rows.length;
436          if (l == 0)
437             return;
438          Arrays.sort (rows);
439          int i = 0;
440          while (rows [i] == i)
441             if (++i == l)
442                return;
443          for (; i < l; i++) {
444             int pos = rows [i]--;
445             IDataGenerator generator = (IDataGenerator) generators.get (pos - 1);
446             generators.set (pos - 1, generators.get (pos));
447             generators.set (pos, generator);
448          }
449          model.fireTableDataChanged ();
450          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
451          setSelectedRows (rows);
452       }
453    
454       /**
455        * Plots the data contained in a table cell.
456        *
457        * @param row Row
458        * @param column Column
459        */
460    
461       public void plotCell (int row, int column) {
462          try {
463             model.plotCellAt (row, column);
464          } catch (PlotException e) {
465             compare.shellHandleException (Compare.MESSAGE_ERROR, e.getMessage (), e);
466          } catch (RuntimeException e) {
467             compare.shellHandleException (Compare.MESSAGE_ERROR, Compare.getString ("EditableSeriesView.plotError"), e);
468          }
469       }
470    
471       public void propertyChange (PropertyChangeEvent e) {
472          if (RESOURCE_LASTTABLESELECTION.equals (e.getPropertyName ())) {
473             ITab last = (ITab) e.getNewValue ();
474             if (last == this || selectionGroup == null)
475                return;
476             ListSelectionModel selectionModel = table.getSelectionModel ();
477             selectionModel.removeListSelectionListener (this);
478             int rows [] = (int []) compare.getResourceMap (RESOURCE_TABLESELECTION).get (last);
479             table.clearSelection ();
480             for (int i = 0, l = rows.length; i < l; i++) {
481                int row = rows [i];
482                table.addRowSelectionInterval (row, row);
483             }
484             selectionModel.addListSelectionListener (this);
485             return;
486          }
487          super.propertyChange (e);
488       }
489    
490       /**
491        * {@inheritDoc}
492        */
493    
494       public void readConfiguration (String state) {
495          super.readConfiguration (state);
496          if (state != STATE_INITIALIZE)
497             return;
498          try {
499             plotter = (IPlotter) Class.forName (compare.getConfig ().findValue (configMarkers, CONFIG_PLOTTER)).newInstance ();
500          } catch (Exception e) {
501             Compare.assertion ("Unable to create plotter.", e);
502          }
503       }
504    
505       /**
506        * {@inheritDoc}
507        */
508    
509       public void setCurrentSource (IWriteableDataSource source) {
510          super.setCurrentSource (source);
511          menuManager.setCommandEnabled ("save", source != null);
512       }
513    
514       /**
515        * {@inheritDoc}
516        */
517    
518       public void viewCell (int row, int column) {
519          model.viewCellAt (row, column);
520       }
521    
522       /**
523        * A file chooser specialized for the view.
524        */
525    
526       protected JFileChooser createFileChooser () {
527          return InterfaceBuilder.createFileChooser (FILTER_EXPERIMENT);
528       }
529    
530       /**
531        * A list of generators for the view.
532        */
533    
534       protected IDataGeneratorList createGenerators () {
535          IDataGeneratorList generators = super.createGenerators ();
536          generators.add (new EditableDataGenerator ());
537          return generators;
538       }
539    
540       /**
541        * Creates a context menu for the view.
542        */
543    
544       protected JPopupMenu createPopup () {
545          return menuManager.createPopup (plotter == null ? POPUP_DEFAULT : POPUP_PLOTTER);
546       }
547    
548       /**
549        * {@inheritDoc}
550        */
551    
552       protected void createUI () {
553          createUI (new EditableSeriesModel ());
554       }
555    
556       /**
557        * @see jigcell.compare.views.BasicTableView#createUI(jigcell.compare.ui.BasicTable.BasicTableModel)
558        */
559    
560       protected void createUI (EditableSeriesModel model) {
561          super.createUI (model);
562          this.model = model;
563       }
564    
565       /**
566        * {@inheritDoc}
567        */
568    
569       protected void initialize () {
570          super.initialize ();
571       }
572    
573       /**
574        * Prepares the popup for display.
575        *
576        * @param row Model row of the popup point
577        * @param column Model column of the popup point
578        */
579    
580       protected void preparePopup (int row, int column) {
581          boolean enabled = model.isCellEditable (row, column);
582          menuManager.setCommandEnabled ("edit", enabled);
583          menuManager.setCommandEnabled ("filldown", enabled);
584          menuManager.setCommandEnabled ("fillselected", enabled);
585          menuManager.setCommandEnabled ("fillup", enabled);
586       }
587    
588       /**
589        * {@inheritDoc}
590        */
591    
592       protected void readInternal (Object readResult) throws Exception {
593          Iterator iterator = DataGeneratorList.createList (readResult).iterator ();
594          generators.clear ();
595          while (iterator.hasNext ())
596             try {
597                generators.add ((IDataGenerator) iterator.next ());
598             } catch (Exception e) {
599                compare.shellHandleException (Compare.MESSAGE_WARNING, Compare.getString ("EditableSeriesView.corruptLoadWarning"), e);
600             }
601       }
602    
603       /**
604        * {@inheritDoc}
605        */
606    
607       protected void readNotify () {
608          model.fireTableDataChanged ();
609          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
610       }
611    
612       /**
613        * {@inheritDoc}
614        */
615    
616       protected void write (IWriteableDataSource source) throws Exception {
617          source.write (generators);
618       }
619    }