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 }