ctakes-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From seanfi...@apache.org
Subject svn commit: r1660963 [13/19] - in /ctakes/sandbox/timelanes: META-INF/ edu/ edu/mayo/ edu/mayo/bmi/ edu/mayo/bmi/annotation/ edu/mayo/bmi/annotation/knowtator/ org/ org/chboston/ org/chboston/cnlp/ org/chboston/cnlp/anafora/ org/chboston/cnlp/anafora/a...
Date Thu, 19 Feb 2015 18:06:17 GMT
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TableTextHighlightPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TableTextHighlightPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TableTextHighlightPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TableTextHighlightPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,223 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import com.sun.java.swing.plaf.windows.WindowsScrollBarUI;
+import org.chboston.cnlp.nlp.annotation.textspan.DefaultDiscontiguousTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.DiscontiguousTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.TextSpan;
+import org.chboston.cnlp.timeline.gui.event.EventColor;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import javax.swing.table.TableModel;
+import javax.swing.text.*;
+import java.awt.*;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/2/13
+ */
+public class TableTextHighlightPanel extends JTextArea {
+
+   private final SelectionPainter _selectedPainter = new SelectionPainter( EventColor.SELECTED.getColor() );
+
+   private int _selectedRowY = -1;
+   private int _rowHeight = 0;
+
+   final private JTable _table;
+   private JScrollBar _verticalScrollBar;
+   final private Collection<Object> _highlightTags;
+
+
+   public TableTextHighlightPanel( final JTable table, final int textSpanColumn ) {
+      setEditable( false );
+      _table = table;
+      _highlightTags = new HashSet<>();
+      final ListSelectionModel selectionModel = _table.getSelectionModel();
+      selectionModel.addListSelectionListener( new PrivateListSelectionListener( textSpanColumn ) );
+      final TableModel model = _table.getModel();
+      model.addTableModelListener( new PrivateModelListener() );
+   }
+
+   protected JTable getTable() {
+      return _table;
+   }
+
+   public void setVerticalScrollBar( final JScrollBar scrollBar ) {
+      _verticalScrollBar = scrollBar;
+      final String lafName = UIManager.getSystemLookAndFeelClassName();
+      if ( lafName.equals( "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" ) ) {
+         _verticalScrollBar.setUI( new WindowsHighlightScrollBarUI() );
+      } else {
+         _verticalScrollBar.setUI( new HighlightScrollBarUI() );
+      }
+   }
+
+   private void addHighlight( final TextSpan textSpan ) {
+      if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+         for ( TextSpan textSpan1 : (DiscontiguousTextSpan)textSpan ) {
+            addHighlight( textSpan1 );
+         }
+         return;
+      }
+      try {
+         final Rectangle bounds = modelToView( textSpan.getStartIndex() );
+         _selectedRowY = bounds.y;
+         final Object tag = getHighlighter().addHighlight( textSpan.getStartIndex(),
+               textSpan.getEndIndex(),
+               _selectedPainter );
+         _highlightTags.add( tag );
+      } catch ( BadLocationException blE ) {
+         System.err.println( blE.getMessage() );
+      }
+   }
+
+   protected void selectTextSpan( final TextSpan textSpan ) {
+      final String text = getText();
+      if ( text == null || text.isEmpty() ) {
+         return;
+      }
+      _selectedRowY = -1;
+      final Highlighter highlighter = getHighlighter();
+      for ( Object tag : _highlightTags ) {
+         highlighter.removeHighlight( tag );
+      }
+      _highlightTags.clear();
+      if ( textSpan != null ) {
+         addHighlight( textSpan );
+         scrollToOffset( textSpan.getStartIndex() );
+      }
+   }
+
+   @Override
+   public void paint( final Graphics g ) {
+      super.paint( g );
+      // repaint the vertical scroll bar
+      _verticalScrollBar.repaint();
+   }
+
+
+   // Painter for underlined highlights
+   private class SelectionPainter extends LayeredHighlighter.LayerPainter {
+      private final Color __color;
+
+      private SelectionPainter( final Color color ) {
+         __color = color;
+      }
+
+      public void paint( final Graphics g, final int offs0, final int offs1, final Shape bounds,
+                         final JTextComponent comp ) {
+         // Do nothing: this method will never be called
+      }
+
+      public Shape paintLayer( final Graphics g, final int startIndex, final int stopIndex, final Shape bounds,
+                               final JTextComponent comp, final View view ) {
+         Rectangle alloc = null;
+         if ( startIndex == view.getStartOffset() && stopIndex == view.getEndOffset() ) {
+            if ( bounds instanceof Rectangle ) {
+               alloc = (Rectangle)bounds;
+            } else {
+               alloc = bounds.getBounds();
+            }
+         } else {
+            try {
+               final Shape shape = view.modelToView( startIndex,
+                     Position.Bias.Forward, stopIndex,
+                     Position.Bias.Backward, bounds );
+               alloc = (shape instanceof Rectangle) ? (Rectangle)shape
+                                                    : shape.getBounds();
+            } catch ( BadLocationException blE ) {
+               return null;
+            }
+         }
+         _rowHeight = alloc.height;
+         g.setColor( __color );
+         g.fillRect( alloc.x, alloc.y, alloc.width, alloc.height );
+         return alloc;
+      }
+   }
+
+   protected void paintScrollBarTrack( final Graphics g, final Rectangle trackBounds ) {
+      if ( _selectedRowY == -1 ) {
+         return;
+      }
+      g.setColor( EventColor.SELECTED.getColor() );
+      final double panelHeight = TableTextHighlightPanel.this.getHeight();
+      final double trackHeight = trackBounds.height;
+      final double xForm = trackHeight / panelHeight;
+      final int rowHeight = Math.max( 2, (int)(_rowHeight * xForm) );
+      final int trackY = trackBounds.y + (int)(_selectedRowY * xForm);
+      g.fillRect( trackBounds.x, trackY, trackBounds.width, rowHeight );
+   }
+
+
+   private class HighlightScrollBarUI extends BasicScrollBarUI {
+      protected void paintTrack( final Graphics g, final JComponent comp, final Rectangle trackBounds ) {
+         super.paintTrack( g, comp, trackBounds );
+         paintScrollBarTrack( g, trackBounds );
+      }
+   }
+
+   private class WindowsHighlightScrollBarUI extends WindowsScrollBarUI {
+      protected void paintTrack( final Graphics g, final JComponent comp, final Rectangle trackBounds ) {
+         super.paintTrack( g, comp, trackBounds );
+         paintScrollBarTrack( g, trackBounds );
+      }
+   }
+
+   private void scrollToOffset( final int offset ) {
+      final Rectangle visibleBounds = getVisibleRect();
+      try {
+         final Rectangle textBounds = modelToView( offset );
+         visibleBounds.y = Math.max( 0, textBounds.y - (visibleBounds.height - textBounds.height) / 2 );
+         scrollRectToVisible( visibleBounds );
+         repaint( textBounds.x, textBounds.y, textBounds.width, textBounds.height );
+      } catch ( BadLocationException blE ) {
+         System.err.println( blE.getMessage() );
+      }
+   }
+
+   private class PrivateListSelectionListener implements ListSelectionListener {
+      final private int __textSpanColumn;
+
+      private PrivateListSelectionListener( final int textSpanColumn ) {
+         __textSpanColumn = textSpanColumn;
+      }
+
+      public void valueChanged( final ListSelectionEvent event ) {
+         if ( event == null || event.getValueIsAdjusting() ) {
+            return;
+         }
+         final ListSelectionModel selectionModel = _table.getSelectionModel();
+         if ( selectionModel.isSelectionEmpty() ) {
+            return;
+         }
+         final int leadIndex = selectionModel.getLeadSelectionIndex();
+         if ( leadIndex < 0 || leadIndex >= _table.getRowCount() ) {
+            return;
+         }
+         final Object value = _table.getValueAt( leadIndex, __textSpanColumn );
+         if ( value == null || !(value instanceof TextSpan) ) {
+            return;
+         }
+         final TextSpan textSpan = (TextSpan)value;
+         selectTextSpan( textSpan );
+      }
+   }
+
+   private class PrivateModelListener implements TableModelListener {
+      public void tableChanged( final TableModelEvent event ) {
+         final int firstRow = event.getFirstRow();
+         if ( firstRow == -1 ) {
+            selectTextSpan( null );
+         }
+      }
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTable.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTable.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTable.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTable.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,303 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.gui.error.ErrorListener;
+import org.chboston.cnlp.gui.error.ErrorProducer;
+import org.chboston.cnlp.nlp.annotation.textspan.TextSpan;
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+
+import javax.swing.*;
+import javax.swing.border.LineBorder;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableRowSorter;
+import java.awt.*;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class TextClipsTable extends JTable implements QaClipComponent {
+
+   static private final int ID_WIDTH = 20;
+   static private final int ORDER_WIDTH = 50;
+   static private final int SUBORDER_WIDTH = 50;
+   static private final int EXACT_WIDTH = 20;
+   static private final int DOCTIMEREL_WIDTH = 20;
+   static private final int ADDITONAL_INFO_WIDTH = 100;
+   static private final int EVENT_TEXT_WIDTH = 100;
+
+   final private ErrorProducer _errorProducer = new ErrorProducer();
+
+
+   public TextClipsTable( final TextClipsTableModel tableModel ) {
+      super( tableModel );
+      putClientProperty( "terminateEditOnFocusLost", Boolean.TRUE );
+      //      getColumnModel().getColumn( 0 ).setPreferredWidth( ORDER_WIDTH );
+      //      getColumnModel().getColumn( 0 ).setMaxWidth( ORDER_WIDTH );
+      //      getColumnModel().getColumn( 0 ).setResizable( false );
+      //      getColumnModel().getColumn( 0 ).setWidth( ORDER_WIDTH );
+      //      getColumnModel().getColumn( 1 ).setPreferredWidth( EXACT_WIDTH );
+      //      getColumnModel().getColumn( 1 ).setMaxWidth( EXACT_WIDTH );
+      //      getColumnModel().getColumn( 1 ).setResizable( false );
+      //      getColumnModel().getColumn( 1 ).setWidth( EXACT_WIDTH );
+      //      getColumnModel().getColumn( 2 ).setPreferredWidth( DOCTIMEREL_WIDTH );
+      //      getColumnModel().getColumn( 2 ).setMaxWidth( DOCTIMEREL_WIDTH );
+      //      getColumnModel().getColumn( 2 ).setResizable( false );
+      //      getColumnModel().getColumn( 2 ).setWidth( DOCTIMEREL_WIDTH );
+      sizeColumn( 0, ID_WIDTH );
+      sizeColumn( 1, ORDER_WIDTH );
+      sizeColumn( 2, SUBORDER_WIDTH );
+      sizeColumn( 3, EXACT_WIDTH );
+      sizeColumn( 4, DOCTIMEREL_WIDTH );
+      sizeEventTextColumn( 5, EVENT_TEXT_WIDTH );
+      // only fix column sizes until the last column, otherwise the table does not dynamically resize with parent resize
+      for ( int i = 6; i < 10; i++ ) {
+         sizeColumn( i, ADDITONAL_INFO_WIDTH );
+      }
+      setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
+      setDefaultRenderer( Integer.class, new OrderCellRenderer() );
+      setDefaultRenderer( TextSpan.class, new AnswerTableCellRenderer() );
+      setDefaultEditor( Integer.class, new OrderCellEditor() );
+      setColumnSelectionAllowed( false );
+      setAutoscrolls( false );
+      getSelectionModel().setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
+      setAutoCreateRowSorter( true );
+      ((TableRowSorter)getRowSorter()).setSortable( 0, false );
+      ((TableRowSorter)getRowSorter()).setSortable( 2, false );
+      ((TableRowSorter)getRowSorter()).setSortable( 3, false );
+      ((TableRowSorter)getRowSorter()).setSortable( 4, false );
+      ((TableRowSorter)getRowSorter()).setSortable( 5, false );
+      for ( int i = 6; i < 11; i++ ) {
+         ((TableRowSorter)getRowSorter()).setSortable( i, false );
+      }
+      tableModel.addTableModelListener( new QaClipTableModelListener() );
+      addKeyListener( new TableKeyListener() );
+   }
+
+   private void sizeColumn( final int columnIndex, final int columnWidth ) {
+      getColumnModel().getColumn( columnIndex ).setPreferredWidth( columnWidth );
+      getColumnModel().getColumn( columnIndex ).setMaxWidth( columnWidth );
+      getColumnModel().getColumn( columnIndex ).setWidth( columnWidth );
+      getColumnModel().getColumn( columnIndex ).setResizable( false );
+   }
+
+   private void sizeEventTextColumn( final int columnIndex, final int columnWidth ) {
+      getColumnModel().getColumn( columnIndex ).setMinWidth( columnWidth );
+      getColumnModel().getColumn( columnIndex ).setPreferredWidth(
+            Math.max( columnWidth, getColumnModel().getColumn( columnIndex ).getWidth() ) );
+      getColumnModel().getColumn( columnIndex ).setWidth(
+            Math.max( columnWidth, getColumnModel().getColumn( columnIndex ).getWidth() ) );
+      getColumnModel().getColumn( columnIndex ).setResizable( true );
+   }
+
+   private TextClipsTableModel getTableModel() {
+      return (TextClipsTableModel)getModel();
+   }
+
+   public void setQaClip( final QaClip qaClip ) {
+      getTableModel().setTextClips( qaClip.getTextClips() );
+   }
+
+   public void adjustQaClip( final QaClip qaClip ) {
+      qaClip.setTextClips( getTableModel().getTextClips() );
+   }
+
+   public void setFullText( final String fullText ) {
+      getTableModel().setFullText( fullText );
+   }
+
+   private void fireErrorOccurred( final String error ) {
+      _errorProducer.fireErrorOccurred( error );
+   }
+
+   private void fireErrorCleared() {
+      _errorProducer.fireErrorCleared();
+   }
+
+   public void addErrorListener( final ErrorListener listener ) {
+      _errorProducer.addErrorListener( listener );
+   }
+
+
+   /**
+    * A pain in the arse, but apparently very necessary because of a series of Swing bugs
+    */
+   private class QaClipTableModelListener implements TableModelListener {
+      @Override
+      public void tableChanged( final TableModelEvent e ) {
+         if ( getAutoResizeMode() != JTable.AUTO_RESIZE_OFF ) {
+            setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
+         }
+         final int widthConstants = ORDER_WIDTH + EXACT_WIDTH + DOCTIMEREL_WIDTH;
+         int max = getParent().getSize().width - widthConstants;
+         for ( int i = 0; i < getRowCount(); i++ ) {
+            final Component comp
+                  = new AnswerTableCellRenderer().getTableCellRendererComponent( TextClipsTable.this,
+                  getValueAt( i, 5 ),
+                  false, false, i, 5 );
+            max = Math.max( max, comp.getPreferredSize().width );
+         }
+         final Dimension headerSize = new Dimension( max + widthConstants, getTableHeader().getPreferredSize().height );
+         getTableHeader().setPreferredSize( headerSize );
+         getTableHeader().setMaximumSize( headerSize );
+         sizeColumn( 0, ID_WIDTH );
+         sizeColumn( 1, ORDER_WIDTH );
+         sizeColumn( 2, SUBORDER_WIDTH );
+         sizeColumn( 3, EXACT_WIDTH );
+         sizeColumn( 4, DOCTIMEREL_WIDTH );
+         sizeEventTextColumn( 5, EVENT_TEXT_WIDTH );
+         for ( int i = 6; i < 10; i++ ) {
+            sizeColumn( i, ADDITONAL_INFO_WIDTH );
+         }
+         final Dimension tableSize = new Dimension( max + widthConstants, getRowHeight() * (getRowCount()) );
+         setPreferredSize( tableSize );
+         setMaximumSize( tableSize );
+      }
+   }
+
+
+   private class AnswerTableCellRenderer extends DefaultTableCellRenderer.UIResource {
+      public Component getTableCellRendererComponent( JTable table, Object value,
+                                                      boolean isSelected, boolean hasFocus, int row, int column ) {
+         super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
+         if ( !table.isCellEditable( row, column ) ) {
+            setBorder( null );
+         }
+         return this;
+      }
+
+      public void setValue( final Object value ) {
+         if ( value == null || !(value instanceof TextSpan) ) {
+            super.setValue( value );
+            return;
+         }
+         final TextSpan textSpan = (TextSpan)value;
+         final String text = getTableModel().getText( textSpan );
+         setText( text );
+      }
+   }
+
+   static private class OrderCellRenderer extends DefaultTableCellRenderer.UIResource {
+      private OrderCellRenderer() {
+         setHorizontalAlignment( JLabel.CENTER );
+         final Dimension size = new Dimension( ORDER_WIDTH, getPreferredSize().height );
+         setMaximumSize( size );
+         setPreferredSize( size );
+         setSize( size );
+      }
+
+      public Component getTableCellRendererComponent( JTable table, Object value,
+                                                      boolean isSelected, boolean hasFocus, int row, int column ) {
+         super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
+         if ( value != null && value instanceof Integer ) {
+            final int order = (Integer)value;
+            final Color orderColor = OrderColorFactory.getColor( order );
+            setBackground( orderColor );
+            //         final Color textColor = OrderColorFactory.getComplimentColor( orderColor );
+            final Color textColor = Color.WHITE;
+            setForeground( textColor );
+         }
+         return this;
+      }
+   }
+
+
+   private class OrderCellEditor extends DefaultCellEditor {
+      private Integer _value;
+
+      public OrderCellEditor() {
+         super( new JTextField() );
+         getComponent().setName( "Table.editor" );
+         ((JTextField)getComponent()).setHorizontalAlignment( JTextField.RIGHT );
+         final Dimension size = new Dimension( ORDER_WIDTH, getPreferredSize().height );
+         setMaximumSize( size );
+         setPreferredSize( size );
+         setSize( size );
+      }
+
+      public boolean stopCellEditing() {
+         String s = (String)super.getCellEditorValue();
+         try {
+            int value = -1;
+            if ( s.indexOf( '.' ) >= 0 ) {
+               final float f = Float.parseFloat( s );
+               if ( f > 0 ) {
+                  value = (int)Math.round( Math.ceil( f ) );
+                  getTableModel().adjustOrder( value );
+               }
+            } else {
+               value = Integer.valueOf( s );
+            }
+            if ( value > 0 ) {
+               _value = value;
+            } else {
+               ((JComponent)getComponent()).setBorder( new LineBorder( Color.red ) );
+               return false;
+            }
+         } catch ( NumberFormatException nfE ) {
+            ((JComponent)getComponent()).setBorder( new LineBorder( Color.red ) );
+            return false;
+         }
+         final boolean stopped = super.stopCellEditing();
+         getTableModel().collapseOrder();
+         return stopped;
+      }
+
+      public Component getTableCellEditorComponent( final JTable table, final Object value,
+                                                    final boolean isSelected,
+                                                    final int row, final int column ) {
+         _value = null;
+         ((JComponent)getComponent()).setBorder( new LineBorder( Color.black ) );
+         //         try {
+         //            Class type = table.getColumnClass(column);
+         //            // Since our obligation is to produce a value which is
+         //            // assignable for the required type it is OK to use the
+         //            // String constructor for columns which are declared
+         //            // to contain Objects. A String is an Object.
+         //            if (type == Object.class) {
+         //               type = String.class;
+         //            }
+         //            constructor = type.getConstructor(argTypes);
+         //         }
+         //         catch (Exception e) {
+         //            return null;
+         //         }
+         return super.getTableCellEditorComponent( table, value, isSelected, row, column );
+      }
+
+      public Object getCellEditorValue() {
+         return _value;
+      }
+   }
+
+   static private class TableKeyListener extends KeyAdapter {
+      public void keyTyped( final KeyEvent event ) {
+         final Object source = event.getSource();
+         if ( source == null || !(source instanceof TextClipsTable) ) {
+            return;
+         }
+         final int selectedRow = ((TextClipsTable)source).getSelectedRow();
+         final int rowCount = ((TextClipsTable)source).getRowCount();
+         if ( selectedRow < 0 || selectedRow >= rowCount ) {
+            return;
+         }
+         final int selectedColumn = ((TextClipsTable)source).getSelectedColumn();
+         if ( selectedColumn == 1 || selectedColumn == 2 || selectedColumn > 5 ) {
+            // editing the order or suborder, don't delete the row
+            return;
+         }
+         final int keyChar = event.getKeyChar();
+         if ( keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE ) {
+            ((TextClipsTable)source).getSelectionModel().clearSelection();
+            ((TextClipsTable)source).getTableModel().removeRow( selectedRow );
+            //            ((TextClipsTable)source).getSelectionModel().setSelectionInterval( selectedIndex, selectedIndex );
+         }
+      }
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTableModel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTableModel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTableModel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/TextClipsTableModel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,267 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.gui.error.ErrorListener;
+import org.chboston.cnlp.gui.error.ErrorProducer;
+import org.chboston.cnlp.nlp.annotation.textspan.TextSpan;
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import java.util.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class TextClipsTableModel extends AbstractTableModel {
+
+   static private final String[] COLUMN_NAMES = { "ID", "Order", "R", "X", "D", "Event / Time Text",
+                                                  "A", "B", "C", "D", "E" };
+   //   static private final String[] COLUMN_TIPS = { "Order if the Event / Time in the sequence involved in the answer",
+   //                                                 "Relation Text",
+   //                                                 "Is the exact answer to the question",
+   //                                                 "Use the DocTimeRel of this Event / Time to answer the question",
+   //                                                 "Event / Time Text" };
+   static private final Class[] COLUMN_CLASSES = { String.class, Integer.class, String.class, Boolean.class,
+                                                   Boolean.class, TextSpan.class,
+                                                   String.class, String.class, String.class, String.class,
+                                                   String.class };
+   static private final int ID_INDEX = 0;
+   static private final int ORDER_INDEX = 1;
+   static private final int R_INDEX = 2;
+   static private final int X_INDEX = 3;
+   static private final int D_INDEX = 4;
+   static private final int TEXT_INDEX = 5;
+
+   static private final int[] INFO_INDICES = { 6, 7, 8, 9, 10 };
+
+   final private ErrorProducer _errorProducer = new ErrorProducer();
+
+   final private List<QaClip.TextClip> _textClips = new ArrayList<>();
+
+   final private JTextField _answerField;
+   private String _fullText = "";
+
+   public TextClipsTableModel( final JTextField answerField ) {
+      _answerField = answerField;
+   }
+
+   public void setFullText( final String fullText ) {
+      _fullText = fullText;
+   }
+
+   public void setTextClips( final List<QaClip.TextClip> textClips ) {
+      final int oldSize = getRowCount();
+      _textClips.clear();
+      if ( oldSize > 0 ) {
+         fireTableRowsDeleted( -1, oldSize - 1 );
+      }
+      if ( textClips == null || textClips.isEmpty() ) {
+         return;
+      }
+      _textClips.addAll( textClips );
+      collapseOrder();
+      final int newSize = getRowCount();
+      fireTableRowsInserted( 0, newSize - 1 );
+   }
+
+   public List<QaClip.TextClip> getTextClips() {
+      Collections.sort( _textClips, QaClip.TextClipComparator.getInstance() );
+      return Collections.unmodifiableList( _textClips );
+   }
+
+   private QaClip.TextClip getTextClip( final int rowIndex ) {
+      if ( rowIndex < 0 || rowIndex >= getRowCount() ) {
+         return null;
+      }
+      return _textClips.get( rowIndex );
+   }
+
+   public int getRowCount() {
+      return _textClips.size();
+   }
+
+   public int getColumnCount() {
+      return COLUMN_NAMES.length;
+   }
+
+   public String getColumnName( final int columnIndex ) {
+      return COLUMN_NAMES[ columnIndex ];
+   }
+
+   public Class<?> getColumnClass( final int columnIndex ) {
+      return COLUMN_CLASSES[ columnIndex ];
+   }
+
+   public boolean isCellEditable( final int rowIndex, final int columnIndex ) {
+      return columnIndex != ID_INDEX && columnIndex != TEXT_INDEX
+             && rowIndex >= 0 && rowIndex < getRowCount();
+   }
+
+   public Object getValueAt( final int rowIndex, final int columnIndex ) {
+      final QaClip.TextClip textClip = getTextClip( rowIndex );
+      if ( textClip == null ) {
+         return null;
+      }
+      if ( columnIndex == ID_INDEX ) {
+         return "" + (rowIndex + 1);
+      } else if ( columnIndex == ORDER_INDEX ) {
+         return textClip.getOrder();
+      } else if ( columnIndex == R_INDEX ) {
+         return textClip.getSubOrder();
+      } else if ( columnIndex == X_INDEX ) {
+         return textClip.isExactAnswer();
+      } else if ( columnIndex == D_INDEX ) {
+         return textClip.useDocTimeRel();
+      } else if ( columnIndex == TEXT_INDEX ) {
+         return textClip.getTextSpan();
+      }
+      for ( int i : INFO_INDICES ) {
+         if ( columnIndex == i ) {
+            return textClip.getInfo()[ i - INFO_INDICES[ 0 ] ];
+         }
+      }
+      return null;
+   }
+
+   public void setValueAt( final Object aValue, final int rowIndex, final int columnIndex ) {
+      if ( !isCellEditable( rowIndex, columnIndex ) ) {
+         return;
+      }
+      final QaClip.TextClip textClip = getTextClip( rowIndex );
+      if ( textClip == null ) {
+         return;
+      }
+      if ( aValue instanceof Boolean ) {
+         if ( columnIndex == X_INDEX ) {
+            textClip.setIsExactAnswer( (Boolean)aValue );
+            if ( (Boolean)aValue ) {
+               final Object tsValue = getValueAt( rowIndex, TEXT_INDEX );
+               if ( tsValue != null && tsValue instanceof TextSpan ) {
+                  _answerField.setText( getText( (TextSpan)tsValue ) );
+               }
+            }
+         } else if ( columnIndex == D_INDEX ) {
+            textClip.setUseDocTimeRel( (Boolean)aValue );
+         } else {
+            fireErrorOccurred( "Don't want a boolean for column " + columnIndex );
+         }
+      } else if ( aValue instanceof String ) {
+         if ( columnIndex == ORDER_INDEX ) {
+            try {
+               final Integer order = Integer.parseInt( (String)aValue );
+               textClip.setOrder( order );
+               fireTableCellUpdated( rowIndex, columnIndex );
+            } catch ( NumberFormatException nfE ) {
+               fireErrorOccurred( "Cannot parse order : " + nfE.getMessage() );
+               setValueAt( getMaxTextSpanOrder(), rowIndex, columnIndex );
+            }
+         } else if ( columnIndex == R_INDEX ) {
+            textClip.setSubOrder( (String)aValue );
+         }
+         for ( int i : INFO_INDICES ) {
+            if ( columnIndex == i ) {
+               textClip.setInfo( i - INFO_INDICES[ 0 ], (String)aValue );
+            }
+         }
+      } else if ( aValue instanceof Integer ) {
+         textClip.setOrder( (Integer)aValue );
+         fireTableCellUpdated( rowIndex, columnIndex );
+      } else {
+         fireErrorOccurred( "Cannot parse : " + aValue );
+         setValueAt( getMaxTextSpanOrder(), rowIndex, columnIndex );
+      }
+   }
+
+   public String getText( final TextSpan textSpan ) {
+      return _fullText.substring( textSpan.getStartIndex(), textSpan.getEndIndex() );
+   }
+
+   public void removeRow( final int rowIndex ) {
+      if ( rowIndex < 0 || rowIndex >= getRowCount() ) {
+         return;
+      }
+      _textClips.remove( rowIndex );
+      collapseOrder();
+      try {
+         fireTableRowsDeleted( rowIndex, rowIndex );
+      } catch ( IndexOutOfBoundsException iobE ) {
+         // The default table row sorter may throw without good cause
+      }
+   }
+
+   private int getMaxTextSpanOrder() {
+      int max = 0;
+      for ( QaClip.TextClip textClip : _textClips ) {
+         max = Math.max( max, textClip.getOrder() );
+      }
+      return max + 1;
+   }
+
+   public void addTextSpan( final TextSpan textSpan ) {
+      for ( QaClip.TextClip textClip : _textClips ) {
+         if ( textClip.getTextSpan().equals( textSpan ) ) {
+            return;
+         }
+      }
+      final int size = getRowCount();
+      final QaClip.TextClip textClip = new QaClip.TextClip( textSpan, getMaxTextSpanOrder(), "" );
+      _textClips.add( textClip );
+      fireTableRowsInserted( size, size );
+   }
+
+   public void adjustOrder( final int insertion ) {
+      for ( QaClip.TextClip textClip : _textClips ) {
+         final int order = textClip.getOrder();
+         if ( order >= insertion ) {
+            textClip.setOrder( order + 1 );
+         }
+      }
+      sort();
+   }
+
+   private Set<Integer> getOrdersUsed() {
+      final Set<Integer> ordersUsed = new HashSet<>();
+      for ( QaClip.TextClip textClip : _textClips ) {
+         ordersUsed.add( textClip.getOrder() );
+      }
+      return ordersUsed;
+   }
+
+   public void collapseOrder() {
+      if ( getRowCount() <= 1 ) {
+         return;
+      }
+      final Set<Integer> ordersUsed = getOrdersUsed();
+      final List<Integer> sortedOrders = new ArrayList<>( ordersUsed );
+      Collections.sort( sortedOrders );
+      int nextOrder = 1;
+      for ( int sortedOrder : sortedOrders ) {
+         if ( sortedOrder != nextOrder ) {
+            for ( QaClip.TextClip textClip : _textClips ) {
+               final int order = textClip.getOrder();
+               if ( order == sortedOrder ) {
+                  textClip.setOrder( nextOrder );
+               }
+            }
+         }
+         nextOrder++;
+      }
+      sort();
+   }
+
+   private void sort() {
+      Collections.sort( _textClips, QaClip.TextClipComparator.getInstance() );
+      fireTableDataChanged();
+   }
+
+   private void fireErrorOccurred( final String error ) {
+      _errorProducer.fireErrorOccurred( error );
+   }
+
+   public void addErrorListener( final ErrorListener listener ) {
+      _errorProducer.addErrorListener( listener );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,183 @@
+//package org.chboston.cnlp.timeline.gui.query;
+//
+//import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+//import org.chboston.cnlp.nlp.annotation.entity.Entity;
+//import org.chboston.cnlp.nlp.annotation.relation.Relation;
+//import org.chboston.cnlp.timeline.gui.main.TimelineMain;
+//import org.chboston.cnlp.timeline.gui.search.TimelineSearchPanel;
+//import org.chboston.cnlp.timeline.gui.event.EventHtmlFactory;
+//import org.chboston.cnlp.timeline.gui.event.list.EventListElement;
+//import org.chboston.cnlp.timeline.gui.event.list.EventListModel;
+//import org.chboston.cnlp.timeline.timeline.Timeline;
+//import org.chboston.cnlp.timeline.timespan.plus.TimeSpanPlus;
+//
+//import javax.swing.*;
+//import java.awt.*;
+//import java.awt.event.ActionEvent;
+//import java.awt.event.ActionListener;
+//import java.awt.event.KeyEvent;
+//import java.awt.event.KeyListener;
+//import java.io.IOException;
+//import java.util.ArrayList;
+//import java.util.Collection;
+//import java.util.Collections;
+//
+///**
+// * Author: SPF
+// * Affiliation: CHIP-NLP
+// * Date: 8/14/13
+// */
+//public class QueryPanel extends JPanel {
+//
+//   private final TimelineSearchPanel _searchPanel;
+//   private final EventListModel _eventListModel;
+//   private final ListSelectionModel _eventListSelectionModel;
+//
+//   private final JTextField _queryBox;
+//   private Collection<String> _oldQueryTerms = Collections.emptySet();
+//   private Collection<String> _oldValidSearchTerms = Collections.emptySet();
+//
+//
+//   public QueryPanel( final TimelineSearchPanel searchPanel,
+//                      final EventListModel eventListModel,
+//                      final ListSelectionModel eventListSelectionModel ) {
+//      super( new BorderLayout( 5, 0 ) );
+//      _searchPanel = searchPanel;
+//      _eventListModel = eventListModel;
+//      _eventListSelectionModel = eventListSelectionModel;
+//      _queryBox = new JTextField();
+//      _queryBox.addKeyListener( new QueryKeyListener() );
+//      _queryBox.addActionListener( new QueryActionListener() );
+//      add( new JLabel( "Question:" ), BorderLayout.WEST );
+//      add( _queryBox, BorderLayout.CENTER );
+//      final JButton saveButton = new JButton( new SaveQuestionInfoAction() );
+//      add( saveButton, BorderLayout.EAST );
+//   }
+//
+//   private Timeline getModel() {
+//      return _searchPanel.getModel();
+//   }
+//
+//   public void clear() {
+//      _oldQueryTerms.clear();
+//      _oldValidSearchTerms.clear();
+//      _queryBox.setText( "" );
+//   }
+//
+//
+//   private final class QueryKeyListener implements KeyListener {
+//      public void keyTyped( final KeyEvent event ) {
+//         if ( Character.isLetterOrDigit( event.getKeyChar() ) ) {
+//            return;
+//         }
+//         final JTextField field = (JTextField) event.getSource();
+//         final Collection<String> queryTerms = QueryUtil.getQueryTerms( field.getText() );
+//         if ( queryTerms.equals( _oldQueryTerms ) ) {
+//            return;
+//         }
+//         _oldQueryTerms = queryTerms;
+//         final Collection<String> validSearchTerms = QueryUtil.getSearchableQueryTerms( getModel(), queryTerms );
+//         if ( validSearchTerms.equals( _oldValidSearchTerms ) ) {
+//            return;
+//         }
+//         _oldValidSearchTerms = validSearchTerms;
+//         _searchPanel.setSearchTerms( validSearchTerms );
+//         _searchPanel.runSearch();
+//      }
+//
+//      public void keyPressed( KeyEvent e ) {
+//      }
+//
+//      public void keyReleased( KeyEvent e ) {
+//      }
+//   }
+//
+//   private final class QueryActionListener implements ActionListener {
+//      public void actionPerformed( final ActionEvent event ) {
+//         _searchPanel.runSearch();
+//      }
+//   }
+//
+//   // todo store a map of timespans and discovered events for those timespans, then the search doesn't need to execute
+//   // a second time for saving ...
+//   private class SaveQuestionInfoAction extends AbstractAction {
+//      static private final String EOL = "<BR>";
+//      static private final String HR = "<HR>";
+//
+//      private SaveQuestionInfoAction() {
+//         super( "Save" );
+//      }
+//
+//      public void actionPerformed( final ActionEvent event ) {
+//         final String query = _queryBox.getText();
+//         if ( query.isEmpty() ) {
+//            return;
+//         }
+//         try {
+//            writeFileText( "<U>Question</U>: " + query );
+//            writeFileText( EOL );
+//            final String search = _searchPanel.getSearchText();
+//            writeFileText( "<U>Search</U>:   " + search );
+//            writeFileText( EOL );
+//            final String eventsHtml = createEventsHtml();
+//            writeFileText( eventsHtml );
+//            writeFileText( HR );
+//            TimelineMain.TIMELINE_QUERY_WRITER.flush();
+//         } catch ( IOException ioE ) {
+//            System.err.println( "Couldn't write Query " + ioE.getMessage() );
+//         }
+//      }
+//
+//      private String createEventsHtml( final java.util.List<Integer> indexList ) {
+//         if ( indexList == null || indexList.isEmpty() ) {
+//            return "";
+//         }
+//         final StringBuilder sb = new StringBuilder();
+//         TimeSpanPlus lastTimeSpan = null;
+//         for ( int index : indexList ) {
+//            final Object element = _eventListModel.getElementAt( index );
+//            if ( element == null ) {
+//               continue;
+//            }
+//            final EventListElement eventListElement = (EventListElement) element;
+//            final TimeSpanPlus timeSpan = eventListElement.getTimeSpan();
+//            final Entity entity = eventListElement.getEntity();
+//            final SemanticClassType semanticType = eventListElement.getSemanticType();
+//            final Collection<Relation> eventEvents = eventListElement.getEventEvents();
+//            final Collection<String> textSelections = eventListElement.getTextSelections();
+//            if ( !timeSpan.equals( lastTimeSpan ) ) {
+//               EventHtmlFactory.appendEventHtml( sb, timeSpan, entity, eventEvents, semanticType, textSelections );
+//               lastTimeSpan = timeSpan;
+//            } else {
+//               EventHtmlFactory.appendEventHtml( sb, entity, eventEvents, semanticType, textSelections );
+//            }
+//         }
+//         return sb.toString();
+//      }
+//
+//      private String createEventsHtml() {
+//         final java.util.List<Integer> indexList = new ArrayList<>();
+//         if ( _eventListSelectionModel.isSelectionEmpty() ) {
+//            final int size = _eventListModel.getSize();
+//            for ( int i = 0; i < size; i++ ) {
+//               indexList.add( i );
+//            }
+//         } else {
+//            final int min = _eventListSelectionModel.getMinSelectionIndex();
+//            final int max = _eventListSelectionModel.getMaxSelectionIndex();
+//            for ( int i = min; i <= max; i++ ) {
+//               if ( _eventListSelectionModel.isSelectedIndex( i ) ) {
+//                  indexList.add( i );
+//               }
+//            }
+//         }
+//         return createEventsHtml( indexList );
+//      }
+//
+//      private void writeFileText( final String text ) throws IOException {
+//         TimelineMain.TIMELINE_QUERY_WRITER.write( text );
+//         TimelineMain.TIMELINE_QUERY_WRITER.newLine();
+//      }
+//   }
+//
+//}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryUtil.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryUtil.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryUtil.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/query/QueryUtil.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,150 @@
+package org.chboston.cnlp.timeline.gui.query;
+
+import org.chboston.cnlp.timeline.gui.search.TimelineSearchUtil;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.TimeSpanPlus;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/14/13
+ */
+final public class QueryUtil {
+
+   private QueryUtil() {
+   }
+
+
+   static public Collection<String> getQueryTerms( final String queryText ) {
+      final String[] queryTerms = queryText.trim().toLowerCase().split( "\\s+" );
+      final Collection<String> queryTermSet = new HashSet<>();
+      final StringBuilder sb = new StringBuilder();
+      for ( String queryTerm : queryTerms ) {
+         if ( queryTerm.isEmpty() ) {
+            continue;
+         }
+         final boolean hasPunctuation = !Character.isLetterOrDigit( queryTerm.charAt( queryTerm.length() - 1 ) );
+         if ( hasPunctuation ) {
+            queryTerm = queryTerm.substring( 0, queryTerm.length() - 1 );
+         }
+         if ( !queryTerm.isEmpty() && !Character.isLetterOrDigit( queryTerm.charAt( 0 ) ) ) {
+            queryTerm = queryTerm.substring( 1 );
+         }
+         queryTerm = queryTerm.toLowerCase();
+         final boolean queryTermOk = queryTerm.length() > 3 && !queryTerm.equals( "with" );
+         if ( queryTermOk ) {
+            sb.append( queryTerm ).append( " " );
+         }
+         if ( sb.length() > 0 && (hasPunctuation || !queryTermOk) ) {
+            sb.setLength( sb.length() - 1 );
+            queryTermSet.add( sb.toString() );
+            sb.setLength( 0 );
+         }
+      }
+      if ( sb.length() > 0 ) {
+         sb.setLength( sb.length() - 1 );
+         queryTermSet.add( sb.toString() );
+      }
+      return queryTermSet;
+   }
+
+
+   /////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   //                                  Search term validation   (find terms that are in timeline events)
+   /////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+   static public Collection<String> getSearchableQueryTerms( final Timeline timeline,
+                                                             final Collection<String> queryTerms ) {
+      final Collection<String> searchableTerms = new HashSet<>();
+      for ( String queryTerm : queryTerms ) {
+         searchableTerms.addAll( getSearchableQueryTerms( timeline, queryTerm ) );
+      }
+      return searchableTerms;
+   }
+
+   static public Collection<String> getSearchableQueryTerms( final Timeline timeline, final String queryTerm ) {
+      final Collection<String> searchableTerms = new HashSet<>();
+      String nextPossibleTerm = queryTerm;
+      while ( !nextPossibleTerm.isEmpty() ) {
+         final Collection<String> firstSearchTerms = getFirstSearchableTerms( timeline, nextPossibleTerm );
+         searchableTerms.addAll( firstSearchTerms );
+         nextPossibleTerm = getNextPossibleTerm( nextPossibleTerm, firstSearchTerms );
+      }
+      return searchableTerms;
+   }
+
+   static private Collection<String> getFirstSearchableTerms( final Timeline timeline, final String queryTerm ) {
+      String testTerm = queryTerm;
+      int lastSpaceIndex = testTerm.length();
+      while ( lastSpaceIndex > 0 ) {
+         testTerm = testTerm.substring( 0, lastSpaceIndex );
+         final Collection<String> terms = getValidSearchTerms( timeline, testTerm );
+         if ( !terms.isEmpty() ) {
+            return terms;
+         }
+         lastSpaceIndex = testTerm.lastIndexOf( ' ' );
+      }
+      return Collections.emptySet();
+   }
+
+   static private String getNextPossibleTerm( final String queryTerm, final Iterable<String> previousSearchTerms ) {
+      int maxTermCount = 1;
+      for ( String previousSearchTerm : previousSearchTerms ) {
+         maxTermCount = Math.max( maxTermCount, previousSearchTerm.split( " " ).length );
+      }
+      int spaceIndex = 0;
+      for ( int i = 0; i < maxTermCount; i++ ) {
+         spaceIndex = queryTerm.indexOf( ' ', spaceIndex + 1 );
+      }
+      if ( spaceIndex > 0 ) {
+         return queryTerm.substring( spaceIndex + 1 );
+      }
+      return "";
+   }
+
+   static private Collection<String> getValidSearchTerms( final Timeline timeline, final String queryTerm ) {
+      final Collection<String> validSearchTerms = new HashSet<>();
+      if ( TimelineSearchUtil.isTimeTerm( timeline.getReferenceMillis(), queryTerm ) ) {
+         validSearchTerms.add( queryTerm );
+      }
+      final Collection<String> pluralTerms = getPluralTerms( queryTerm );
+      for ( String pluralTerm : pluralTerms ) {
+         for ( PointedTimeSpan timeSpan : timeline ) {
+            if ( TimelineSearchUtil.hasSearchTerm( timeline, timeSpan, pluralTerm ) ) {
+               validSearchTerms.add( pluralTerm );
+               break;
+            }
+         }
+         if ( TimelineSearchUtil.hasSearchTerm( timeline, TimeSpanPlus.UNKNOWN_TIMESPAN_PLUS, pluralTerm ) ) {
+            validSearchTerms.add( pluralTerm );
+         }
+      }
+      return validSearchTerms;
+   }
+
+   static private Collection<String> getPluralTerms( final String term ) {
+      final Collection<String> pluralTerms = new HashSet<>();
+      pluralTerms.add( term );
+      if ( term.endsWith( "y" ) ) {
+         // add plural "y"
+         pluralTerms.add( term.substring( 0, term.length() - 1 ) + "ies" );
+      } else if ( term.endsWith( "ies" ) ) {
+         // add singular "y"
+         pluralTerms.add( term.substring( 0, term.length() - 3 ) + "y" );
+      } else if ( term.endsWith( "s" ) ) {
+         // add simple singular
+         pluralTerms.add( term.substring( 0, term.length() - 1 ) );
+      } else {
+         // add simple plural
+         pluralTerms.add( term + "s" );
+      }
+      return pluralTerms;
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationCellRenderer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationCellRenderer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationCellRenderer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationCellRenderer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,64 @@
+package org.chboston.cnlp.timeline.gui.relation;
+
+import org.chboston.cnlp.nlp.annotation.attribute.Attribute;
+import org.chboston.cnlp.nlp.annotation.attribute.DefinedAttributeType;
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+import org.chboston.cnlp.timeline.gui.event.text.EventCellRenderer;
+import org.chboston.cnlp.timeline.gui.list.AbstractAnnotationCellRenderer;
+
+import java.awt.*;
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 10/1/2014
+ */
+public class RelationCellRenderer extends AbstractAnnotationCellRenderer<Relation> {
+
+   static final private Color RELATION_COLOR = new Color( 122, 39, 112 );
+
+   public RelationCellRenderer() {
+      setForeground( RELATION_COLOR );
+      setFont( getFont().deriveFont( Font.ITALIC + Font.BOLD ) );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void setAnnotation( final Relation tlink ) {
+      final String text = createTlinkTypeText( tlink );
+      setText( text );
+      final String firstEntityText = EventCellRenderer.getDescriptiveText( tlink.getFirstEntity() );
+      setToolTipText( firstEntityText + " " + text );
+   }
+
+   static private String createTlinkTypeText( final Relation tlink ) {
+//      final Attribute relationTypeAttribute = tlink.getAttribute( "Relation Type" );
+      final Attribute relationTypeAttribute = tlink.getAttribute( DefinedAttributeType.RELATION_TYPE.getName() );
+      if ( relationTypeAttribute == null ) {
+         // assume equality
+         return "occurs at the same time as";
+      }
+      // There is currently no default relation type in the thyme schema, but we must have one, so use overlap
+      final String relationType = relationTypeAttribute.getValue();
+      if ( relationType.equals( "BEFORE" ) ) {
+         return "occurs before";
+      } else if ( relationType.equals( "AFTER" ) ) {
+         return "occurs after";
+      } else if ( relationType.equals( "OVERLAP" ) ) {
+         return "overlaps";
+      } else if ( relationType.equals( "CONTAINS" ) ) {
+         return "contains";
+      } else if ( relationType.equals( "CONTAINED-BY" ) ) {
+         return "is within";
+      } else if ( relationType.equals( "BEGINS-ON" ) ) {
+         return "begins on";
+      } else if ( relationType.equals( "ENDS-ON" ) ) {
+         return "ends on";
+      }
+      return "overlaps";
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationColor.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationColor.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationColor.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/RelationColor.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,10 @@
+package org.chboston.cnlp.timeline.gui.relation;
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 9/24/2014
+ */
+public enum RelationColor {
+   CONTAINS
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListModel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListModel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListModel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListModel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,168 @@
+package org.chboston.cnlp.timeline.gui.relation.list;
+
+import org.chboston.cnlp.nlp.annotation.classtype.TemporalClassType;
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+import org.chboston.cnlp.timeline.gui.event.TlinkComparator;
+import org.chboston.cnlp.timeline.gui.event.list.EventListElement;
+import org.chboston.cnlp.timeline.gui.timespan.selection.DefaultTimeSpanSelectionModel;
+import org.chboston.cnlp.timeline.gui.timespan.selection.TimeSpanSelectionEvent;
+import org.chboston.cnlp.timeline.gui.timespan.selection.TimeSpanSelectionListener;
+import org.chboston.cnlp.timeline.gui.timespan.selection.TimeSpanSelectionModel;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 7/8/14
+ */
+public class RelationListModel extends AbstractListModel<Relation> {
+
+   final private ListModel<EventListElement> _eventListModel;
+   final private ListSelectionModel _eventListSelectionModel;
+   private TimeSpanSelectionModel _selectionModel;
+   private final TimeSpanSelectionListener _selectionListener;
+
+   private final List<Relation> _timexRelations;
+   private final List<Relation> _eventRelations;
+
+
+   public RelationListModel( final ListModel<EventListElement> eventListModel,
+                             final ListSelectionModel eventListSelectionModel ) {
+      _eventListModel = eventListModel;
+      eventListSelectionModel.addListSelectionListener( new PrivateListSelectionListener() );
+      _eventListSelectionModel = eventListSelectionModel;
+      _timexRelations = new ArrayList<>();
+      _eventRelations = new ArrayList<>();
+      _selectionListener = new MyTimeSpanSelectionListener();
+      setSelectionModel( new DefaultTimeSpanSelectionModel() );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int getSize() {
+      return _timexRelations.size() + _eventRelations.size();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Relation getElementAt( final int index ) {
+      if ( index < _timexRelations.size() ) {
+         return _timexRelations.get( index );
+      }
+      return _eventRelations.get( index - _timexRelations.size() );
+   }
+
+   /**
+    * Sets the TimeSpan selection model for this OldJTimeline to <code>newModel</code>
+    * and registers for listener notifications from the new selection model.
+    *
+    * @param newModel the new selection model
+    * @throws IllegalArgumentException if <code>newModel</code> is <code>null</code>
+    * @see #getSelectionModel
+    */
+   public void setSelectionModel( final TimeSpanSelectionModel newModel ) {
+      if ( newModel == null ) {
+         throw new IllegalArgumentException( "Cannot set a null SelectionModel" );
+      }
+      if ( newModel.equals( _selectionModel ) ) {
+         return;
+      }
+      if ( _selectionModel != null ) {
+         _selectionModel.removeSelectionListener( _selectionListener );
+      }
+      //      final TimeSpanSelectionModel oldModel = _selectionModel;
+      _selectionModel = newModel;
+      _selectionModel.addSelectionListener( _selectionListener );
+      // TODO - do we actually need this setSelectionModel method
+      //      firePropertyChange( SELECTION_MODEL_PROPERTY, oldModel, newModel );
+      //      repaint();
+      //      fireIntervalRemoved(  );
+   }
+
+   /**
+    * @return the object that provides TimeSpan selection state, <code>null</code>
+    * if selection is not allowed
+    * @see #setSelectionModel
+    */
+   public TimeSpanSelectionModel getSelectionModel() {
+      return _selectionModel;
+   }
+
+   private void selectEventListElement( final EventListElement event ) {
+      final int oldSize = getSize();
+      _timexRelations.clear();
+      _eventRelations.clear();
+      final Collection<Relation> relations = event.getEventEvents();
+      for ( Relation relation : relations ) {
+         if ( relation.getFirstEntity().isClassType( TemporalClassType.TIMEX )
+              || relation.getFirstEntity().isClassType( TemporalClassType.DOCTIME )
+              || relation.getSecondEntity().isClassType( TemporalClassType.TIMEX )
+              || relation.isClassType( TemporalClassType.DOCTIME ) ) {
+            _timexRelations.add( relation );
+         } else {
+            _eventRelations.add( relation );
+         }
+      }
+      Collections.sort( _timexRelations, TlinkComparator.INSTANCE );
+      Collections.sort( _eventRelations, TlinkComparator.INSTANCE );
+      if ( oldSize > 0 ) {
+         fireIntervalRemoved( this, 0, oldSize );
+      }
+      fireIntervalAdded( this, 0, getSize() );
+   }
+
+   private void selectTimeSpans( final TimeSpanSelectionEvent event ) {
+      final int oldSize = getSize();
+      _timexRelations.clear();
+      _eventRelations.clear();
+      if ( oldSize > 0 ) {
+         fireIntervalRemoved( this, 0, oldSize );
+      }
+   }
+
+
+   private class PrivateListSelectionListener implements ListSelectionListener {
+      public void valueChanged( final ListSelectionEvent event ) {
+         if ( event == null || event.getValueIsAdjusting() ) {
+            return;
+         }
+         if ( _eventListSelectionModel.isSelectionEmpty() ) {
+            return;
+         }
+         final int leadIndex = _eventListSelectionModel.getLeadSelectionIndex();
+         if ( leadIndex < 0 || leadIndex >= _eventListModel.getSize() ) {
+            return;
+         }
+         final EventListElement value = _eventListModel.getElementAt( leadIndex );
+         if ( value == null ) {
+            return;
+         }
+         selectEventListElement( value );
+      }
+   }
+
+   private class MyTimeSpanSelectionListener implements TimeSpanSelectionListener {
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      public void valueChanged( final TimeSpanSelectionEvent event ) {
+         if ( event == null || event.getSource() == null || event.getValueIsAdjusting() ) {
+            return;
+         }
+         selectTimeSpans( event );
+      }
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,222 @@
+package org.chboston.cnlp.timeline.gui.relation.list;
+
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+import org.chboston.cnlp.timeline.gui.event.EventHtmlFactory;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 7/8/14
+ */
+public class RelationListRenderer extends JEditorPane implements ListCellRenderer<Relation>, Scrollable {
+
+   private JViewport _viewport;
+   final private StyleSheet _styleSheet;
+
+   public RelationListRenderer() {
+      final HTMLEditorKit editorKit = new HTMLEditorKit();
+      setEditorKit( editorKit );
+      setDocument( new HTMLDocument() );
+      _styleSheet = editorKit.getStyleSheet();
+      setEditable( false );
+      setBorder( new EmptyBorder( 2, 10, 2, 10 ) );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Component getListCellRendererComponent( final JList<? extends Relation> list,
+                                                  final Relation element,
+                                                  final int index,
+                                                  final boolean isSelected, final boolean cellHasFocus ) {
+      if ( list != null ) {
+         if ( list.getParent() instanceof JViewport ) {
+            _viewport = (JViewport)list.getParent();
+         }
+         if ( isSelected ) {
+//            setBackground( list.getSelectionBackground().brighter().brighter() );
+            setBackground( Color.CYAN );
+         } else {
+            if ( cellHasFocus ) {
+               setBackground( Color.LIGHT_GRAY.brighter() );
+            } else {
+               setBackground( list.getBackground() );
+            }
+         }
+      } else {
+         _viewport = null;
+      }
+      if ( element == null ) {
+         setText( "" );
+         return this;
+      }
+      displayRelation( element );
+      return this;
+   }
+
+   private void displayRelation( Relation relation ) {
+      final String htmlText = EventHtmlFactory.createRelationHtml( relation );
+      setText( htmlText );
+   }
+
+   public void setFont( final Font font ) {
+      if ( _styleSheet != null ) {
+         final int size = font.getSize();
+         _styleSheet.setBaseFontSize( size );
+      }
+      super.setFont( font );
+   }
+
+   /**
+    * Normally one doesn't want to mess with getParent(), but in this case getParent() is used to size the JEditorPane.
+    * We could screw around with the flow, axis sizing, scrolling, etc. but in the end it would be a mess
+    * and get the same result as the cell renderer doesn't -really- have a parent anyhow.
+    *
+    * @return the JViewport that contains the JList
+    */
+   @Override
+   public JComponent getParent() {
+      return _viewport;
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void validate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    *
+    * @since 1.5
+    */
+   @Override
+   public void invalidate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    *
+    * @since 1.5
+    */
+   @Override
+   public void repaint() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void revalidate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void repaint( long tm, int x, int y, int width, int height ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void repaint( Rectangle r ) {
+   }
+
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, byte oldValue, byte newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, char oldValue, char newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, short oldValue, short newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, int oldValue, int newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, long oldValue, long newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, float oldValue, float newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, double oldValue, double newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, boolean oldValue, boolean newValue ) {
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer2.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer2.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer2.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/relation/list/RelationListRenderer2.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,194 @@
+package org.chboston.cnlp.timeline.gui.relation.list;
+
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+import org.chboston.cnlp.timeline.gui.event.text.EventCellRenderer;
+import org.chboston.cnlp.timeline.gui.list.AbstractAnnotationCellRenderer;
+import org.chboston.cnlp.timeline.gui.relation.RelationCellRenderer;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 7/8/14
+ */
+public class RelationListRenderer2 extends JPanel implements ListCellRenderer<Relation> {
+
+   final private AbstractAnnotationCellRenderer<Relation> _relationLabel;
+   final private AbstractAnnotationCellRenderer<Entity> _entityLabel;
+
+   public RelationListRenderer2() {
+      super( new FlowLayout( FlowLayout.LEADING, 4, 4 ) );
+      _relationLabel = new RelationCellRenderer();
+      _entityLabel = new EventCellRenderer();
+      setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
+      add( _relationLabel );
+      add( _entityLabel );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Component getListCellRendererComponent( final JList<? extends Relation> list,
+                                                  final Relation tlink,
+                                                  final int index,
+                                                  final boolean isSelected, final boolean cellHasFocus ) {
+      if ( isSelected ) {
+         setBackground( Color.CYAN );
+      } else {
+         if ( cellHasFocus ) {
+            setBackground( Color.LIGHT_GRAY.brighter() );
+         } else {
+            setBackground( list.getBackground() );
+         }
+      }
+      _relationLabel.setAnnotation( tlink );
+      _entityLabel.setAnnotation( tlink.getSecondEntity() );
+      return this;
+   }
+
+   @Override
+   public String getToolTipText( final MouseEvent event ) {
+      return _relationLabel.getToolTipText() + " " + _entityLabel.getToolTipText();
+   }
+
+
+//      /**
+//       * Overridden for performance reasons.
+//       * See the <a href="#override">Implementation Note</a>
+//       * for more information.
+//       */
+//   @Override
+//   public void validate() {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    *
+//    * @since 1.5
+//    */
+//   @Override
+//   public void invalidate() {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    *
+//    * @since 1.5
+//    */
+//   @Override
+//   public void repaint() {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void revalidate() {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void repaint( long tm, int x, int y, int width, int height ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void repaint( Rectangle r ) {
+//   }
+//
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, byte oldValue, byte newValue ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, char oldValue, char newValue ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, short oldValue, short newValue ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, int oldValue, int newValue ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, long oldValue, long newValue ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, float oldValue, float newValue ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, double oldValue, double newValue ) {
+//   }
+//
+//   /**
+//    * Overridden for performance reasons.
+//    * See the <a href="#override">Implementation Note</a>
+//    * for more information.
+//    */
+//   @Override
+//   public void firePropertyChange( String propertyName, boolean oldValue, boolean newValue ) {
+//   }
+//
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/search/TimelineSearchPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/search/TimelineSearchPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/search/TimelineSearchPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/search/TimelineSearchPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,359 @@
+package org.chboston.cnlp.timeline.gui.search;
+
+import org.chboston.cnlp.gui.GlobalHotkeyManager;
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.timeline.gui.event.list.EventListElement;
+import org.chboston.cnlp.timeline.gui.timeline.stack.TimelineStack;
+import org.chboston.cnlp.timeline.gui.timespan.selection.*;
+import org.chboston.cnlp.timeline.timeline.EventTextTimeline;
+import org.chboston.cnlp.timeline.timeline.SemanticTypeTimeline;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.util.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/8/13
+ */
+public class TimelineSearchPanel extends JPanel {
+
+   static public final String EVENT_SEARCH_TEXT = "EventSearchText";
+   static public final String SELECTION_MODEL_PROPERTY = "selectionModel";
+
+   final private Map<SemanticClassType, TimelineStack> _semanticStackMap;
+
+   final private Timeline _timeline;
+
+   private final TimeSpanSelectionListener _selectionListener;
+
+   private TimeSpanSelectionModel _selectionModel;
+
+   final private JTextField _searchBox;
+   final private ActionListener _searchActionListener;
+
+
+   private Map<String, Collection<PointedTimeSpan>> _validSearchTimeSpansMap;
+   // TODO refactor to use a map of timeSpans and events for the search
+   private Map<PointedTimeSpan, Map<SemanticClassType, Collection<Entity>>> _timeSpanSemanticEntityMap;
+
+
+   public TimelineSearchPanel( final Timeline timeline,
+                               final ListModel<EventListElement> eventListModel,
+                               final ListSelectionModel eventListSelectionModel,
+                               final TimelineStack signSymptomStack,
+                               final TimelineStack procedureStack,
+                               final TimelineStack diseaseDisStack,
+                               final TimelineStack medicationStack ) {
+      super( new GridLayout( 1, 2, 20, 0 ) );
+      _timeline = timeline;
+      _validSearchTimeSpansMap = Collections.emptyMap();
+      _timeSpanSemanticEntityMap = Collections.emptyMap();
+      _semanticStackMap = new EnumMap<>( SemanticClassType.class );
+      _semanticStackMap.put( SemanticClassType.SIGN_OR_SYMPTOM, signSymptomStack );
+      _semanticStackMap.put( SemanticClassType.PROCEDURE, procedureStack );
+      _semanticStackMap.put( SemanticClassType.DISEASE_DISORDER, diseaseDisStack );
+      _semanticStackMap.put( SemanticClassType.MEDICATION, medicationStack );
+      _selectionListener = new PrivateSelectionListener();
+
+//      final JScrollBar tempScrollBar = new JScrollBar( JScrollBar.VERTICAL );
+//      setBorder( new EmptyBorder( 2, tempScrollBar.getPreferredSize().width,
+//            10, tempScrollBar.getPreferredSize().width ) );
+      setBorder( new EmptyBorder( 5, 5, 5, 5 ) );
+
+      _searchBox = new JTextField();
+      _searchActionListener = new EventActionListener();
+      _searchBox.addActionListener( _searchActionListener );
+
+      final JPanel searchPanel = new JPanel( new BorderLayout( 5, 0 ) );
+      searchPanel.add( new JLabel( "Find Event:" ), BorderLayout.WEST );
+      searchPanel.add( _searchBox, BorderLayout.CENTER );
+      final JButton addSwimlaneButton = new JButton( new AddSwimlaneAction( _searchBox, _searchActionListener ) );
+      searchPanel.add( addSwimlaneButton, BorderLayout.EAST );
+
+      add( searchPanel );
+
+      setSelectionModel( new DefaultTimeSpanSelectionModel() );
+      // typing <CTRL>-F at any time will pop focus back on the search box.  Using the text string "requestFocus"
+      // is dangerous since swing can change it at any time. Java should have a non-string constant for this but doesn't
+      final GlobalHotkeyManager hotKeyManager = GlobalHotkeyManager.getInstance();
+      hotKeyManager.addHotKey( "FindEvent", KeyStroke.getKeyStroke(
+            KeyEvent.VK_F, KeyEvent.CTRL_MASK, false ), _searchBox.getActionMap().get( "requestFocus" ) );
+   }
+
+   public Timeline getModel() {
+      return _timeline;
+   }
+
+   public String getSearchText() {
+      return _searchBox.getText();
+   }
+
+   public void setSearchText( final String searchText ) {
+      _searchBox.setText( searchText );
+   }
+
+   public Collection<String> getSearchTerms() {
+      return TimelineSearchUtil.getSearchTerms( getSearchText() );
+   }
+
+   public void setSearchTerms( final Collection<String> searchTerms ) {
+      final StringBuilder sb = new StringBuilder();
+      if ( !searchTerms.isEmpty() ) {
+         for ( String term : searchTerms ) {
+            sb.append( term ).append( " | " );
+         }
+         sb.setLength( sb.length() - 3 );
+      }
+      setSearchText( sb.toString() );
+   }
+
+   public void runSearch() {
+      _searchActionListener.actionPerformed(
+            new ActionEvent( _searchBox, ActionEvent.ACTION_PERFORMED, "ACTION_PERFORMED" ) );
+   }
+
+   /**
+    * Sets the TimeSpan selection model for this OldJTimeline to <code>newModel</code>
+    * and registers for listener notifications from the new selection model.
+    *
+    * @param newModel the new selection model
+    * @throws IllegalArgumentException if <code>newModel</code> is <code>null</code>
+    * @see #getSelectionModel
+    */
+   public void setSelectionModel( final TimeSpanSelectionModel newModel ) {
+      if ( newModel == null ) {
+         throw new IllegalArgumentException( "Cannot set a null SelectionModel" );
+      }
+      if ( newModel.equals( _selectionModel ) ) {
+         return;
+      }
+      if ( _selectionModel != null ) {
+         _selectionModel.removeSelectionListener( _selectionListener );
+      }
+      final TimeSpanSelectionModel oldModel = _selectionModel;
+      _selectionModel = newModel;
+      _selectionModel.addSelectionListener( _selectionListener );
+      firePropertyChange( SELECTION_MODEL_PROPERTY, oldModel, newModel );
+      repaint();
+   }
+
+   /**
+    * @return the object that provides TimeSpan selection state, <code>null</code>
+    * if selection is not allowed
+    * @see #setSelectionModel
+    */
+   public TimeSpanSelectionModel getSelectionModel() {
+      return _selectionModel;
+   }
+
+
+   private void clear() {
+      _validSearchTimeSpansMap.clear();
+      _timeSpanSemanticEntityMap.clear();
+      _searchBox.setText( "" );
+      firePropertyChange( EVENT_SEARCH_TEXT, EVENT_SEARCH_TEXT, "" );
+   }
+
+   private boolean _isSelecting = false;
+
+   private Map<PointedTimeSpan, Collection<Entity>> getAggregatedTimeSpanEntities() {
+      final Collection<PointedTimeSpan> validSearchTimeSpans
+            = _validSearchTimeSpansMap.get( TimelineSearchUtil.ALL_FILTER_TIME_TERMS );
+      if ( validSearchTimeSpans == null || validSearchTimeSpans.isEmpty() ) {
+         return Collections.emptyMap();
+      }
+      if ( _timeSpanSemanticEntityMap == null || _timeSpanSemanticEntityMap.isEmpty() ) {
+         return Collections.emptyMap();
+      }
+      final Map<PointedTimeSpan, Collection<Entity>> timeSpanEntities = new HashMap<>();
+      for ( PointedTimeSpan timeSpan : validSearchTimeSpans ) {
+         final Map<SemanticClassType, Collection<Entity>> semanticEntities = _timeSpanSemanticEntityMap.get( timeSpan );
+         if ( semanticEntities == null || semanticEntities.isEmpty() ) {
+            continue;
+         }
+         final Collection<Entity> allEntities = new HashSet<>();
+         for ( Collection<Entity> entities : semanticEntities.values() ) {
+            allEntities.addAll( entities );
+         }
+         timeSpanEntities.put( timeSpan, allEntities );
+      }
+      return timeSpanEntities;
+   }
+
+   private void select() {
+      _isSelecting = true;
+      final Collection<PointedTimeSpan> validSearchTimeSpans
+            = _validSearchTimeSpansMap.get( TimelineSearchUtil.ALL_FILTER_TIME_TERMS );
+      if ( validSearchTimeSpans == null || validSearchTimeSpans.isEmpty() ) {
+         select( new HashMap<PointedTimeSpan, Collection<Entity>>( 0 ) );
+      } else {
+         final Map<PointedTimeSpan, Collection<Entity>> timeSpanEntities = getAggregatedTimeSpanEntities();
+         select( timeSpanEntities );
+      }
+      _isSelecting = false;
+   }
+
+
+   private void select( final Map<PointedTimeSpan, Collection<Entity>> timeSpanEntities ) {
+      if ( timeSpanEntities.isEmpty() ) {
+         _selectionModel.clearSelection( _selectionModel );
+         return;
+      }
+      _selectionModel.setValueIsAdjusting( _selectionModel, true );
+      _selectionModel.setSelectedTerms( _selectionModel,
+            TimelineSearchUtil.getSearchTerms( _searchBox.getText() ) );
+      _selectionModel.setSelectedEntities( _selectionModel, timeSpanEntities );
+      _selectionModel.setValueIsAdjusting( _selectionModel, false );
+   }
+
+
+   // TODO add removal of models/listeners from SelectionForwarder in TimelineMain
+   final private class EventActionListener implements ActionListener {
+
+      @Override
+      public void actionPerformed( final ActionEvent event ) {
+         _validSearchTimeSpansMap.clear();
+         _timeSpanSemanticEntityMap.clear();
+         final String searchText = _searchBox.getText();
+         if ( searchText == null || searchText.trim().isEmpty() ) {
+            firePropertyChange( EVENT_SEARCH_TEXT, EVENT_SEARCH_TEXT, "" );
+            select();
+            return;
+         }
+         final char command = TimelineSearchUtil.getSearchCommand( searchText );
+         final Collection<String> searchTerms = TimelineSearchUtil.getSearchTerms( searchText );
+         if ( command == '-' ) {
+            removeSelection( searchTerms );
+            return;
+         }
+         _validSearchTimeSpansMap = TimelineSearchUtil.getFilteredTimeSpansMap( _timeline, searchTerms );
+         final boolean requireSearchTerms = true;
+         _timeSpanSemanticEntityMap = TimelineSearchUtil.search( requireSearchTerms, _timeline,
+               _validSearchTimeSpansMap, searchTerms );
+         if ( command == '+' ) {
+            addSelection( searchTerms );
+            return;
+         }
+         firePropertyChange( EVENT_SEARCH_TEXT, EVENT_SEARCH_TEXT, searchText );
+         select();
+      }
+
+      private void removeSelection( final Collection<String> terms ) {
+         final String title = createTitle( terms );
+         for ( TimelineStack stack : _semanticStackMap.values() ) {
+            stack.removeTimeline( title, _timeline );
+         }
+         firePropertyChange( EVENT_SEARCH_TEXT, EVENT_SEARCH_TEXT, "" );
+         select();
+      }
+
+      private String createTitle( final Collection<String> searchTerms ) {
+         if ( searchTerms == null || searchTerms.isEmpty() ) {
+            return "Unknown";
+         }
+         final StringBuilder sb = new StringBuilder();
+         for ( String term : searchTerms ) {
+            sb.append( capitalize( term ) ).append( ", " );
+         }
+         sb.setLength( sb.length() - 2 );
+         return sb.toString();
+      }
+
+      private String capitalize( final String term ) {
+         if ( term == null || term.isEmpty() ) {
+            return "";
+         }
+         return term.substring( 0, 1 ).toUpperCase() + term.substring( 1 );
+      }
+
+//      private void addSemanticTimeline( final String title, final SemanticClassType semanticType ) {
+//         final TimelineStack stack = _semanticStackMap.get( semanticType );
+//         if ( stack == null ) {
+//            return;
+//         }
+//         final Timeline semanticTimeline = TimelineFactory.createSemanticTimeline( title, semanticType,
+//                                                                                   _timeSpanSemanticEntityMap,
+//                                                                                   _timeline );
+//         final Collection<TimeSpanPlus> timeSpans = semanticTimeline.getTimeSpans();
+//         if ( timeSpans == null || timeSpans.isEmpty() ) {
+//            return;
+//         }
+//         final TimeSpanSelectionModel selectionModel = new DefaultTimeSpanSelectionModel( semanticType );
+//         SelectionForwarder.getInstance().addSelectionModel( selectionModel );
+//         stack.addTimeline( title, semanticTimeline, _timeline, selectionModel );
+//      }
+
+      private void addSemanticTimeline( final String title, final SemanticClassType semanticType,
+                                        final Collection<String> searchTerms ) {
+         final TimelineStack stack = _semanticStackMap.get( semanticType );
+         if ( stack == null ) {
+            return;
+         }
+         final Timeline semanticTimeline
+               = new EventTextTimeline( title,
+               new SemanticTypeTimeline( title, _timeline, semanticType ), searchTerms );
+         final Collection<PointedTimeSpan> timeSpans = semanticTimeline.getTimeSpans();
+         if ( timeSpans == null || timeSpans.isEmpty() ) {
+            return;
+         }
+         final TimeSpanSelectionModel selectionModel = new DefaultTimeSpanSelectionModel( semanticType );
+         SelectionForwarder.getInstance().addSelectionModel( selectionModel );
+         stack.addTimeline( title, null, semanticTimeline, _timeline, selectionModel );
+      }
+
+      private void addSelection( final Collection<String> searchTerms ) {
+         final String title = createTitle( searchTerms );
+         for ( SemanticClassType semanticType : _semanticStackMap.keySet() ) {
+            addSemanticTimeline( title, semanticType, searchTerms );
+         }
+         firePropertyChange( EVENT_SEARCH_TEXT, EVENT_SEARCH_TEXT, searchTerms );
+         select();
+      }
+   }
+
+   private class PrivateSelectionListener implements TimeSpanSelectionListener {
+      public void valueChanged( final TimeSpanSelectionEvent event ) {
+         if ( _isSelecting || event == null || event.getValueIsAdjusting() ) {
+            return;
+         }
+         final Object source = event.getSource();
+         //         if ( source == null || source.equals( TimelineSearchPanel.this ) || source.equals( _searchBox ) ) {
+         if ( source == null || source.equals( getSelectionModel() ) ) {
+            return;
+         }
+         clear();
+      }
+   }
+
+
+   static private class AddSwimlaneAction extends AbstractAction {
+      final private JTextField __searchBox;
+      final private ActionListener __searchActionListener;
+
+      private AddSwimlaneAction( final JTextField searchBox, final ActionListener searchActionListener ) {
+         super( "Add Event" );
+         __searchBox = searchBox;
+         __searchActionListener = searchActionListener;
+      }
+
+      public void actionPerformed( final ActionEvent event ) {
+         final String text = __searchBox.getText();
+         if ( text.startsWith( "+" ) ) {
+            return;
+         }
+         __searchBox.setText( "+ " + text );
+         __searchActionListener.actionPerformed(
+               new ActionEvent( __searchBox, ActionEvent.ACTION_PERFORMED, "ACTION_PERFORMED" ) );
+      }
+   }
+
+}



Mime
View raw message