You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ctakes.apache.org by se...@apache.org on 2015/02/19 19:06:17 UTC

svn commit: r1660963 [12/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...

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QaToAnafora.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QaToAnafora.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QaToAnafora.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QaToAnafora.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,184 @@
+package org.chboston.cnlp.timeline.gui.qaclipper;
+
+import org.chboston.cnlp.nlp.annotation.textspan.DefaultTextSpan;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 5/2/14
+ */
+public class QaToAnafora {
+
+
+   private String _comment = "";
+
+
+   public static void main( final String[] args ) {
+      if ( args.length != 1 ) {
+         System.err.println( "What the heck?  Give me the path to a text file!" );
+         System.exit( 0 );
+      }
+      new QaToAnafora( args[ 0 ] );
+   }
+
+
+   private QaToAnafora( final String textFilePath ) {
+      final List<QaClip> qaClips = loadQaClips( textFilePath );
+      final QaClipAnaforaWriter writer = new QaClipAnaforaWriter();
+      writer.saveQaClips( textFilePath, qaClips );
+   }
+
+
+   public List<QaClip> loadQaClips( final String textFilePath ) {
+      _comment = "";
+      final List<QaClip> qaClips = new ArrayList<>();
+      final File file = new File( textFilePath + QaClipSaver.FILE_EXTENSION );
+      if ( !file.exists() ) {
+         return qaClips;
+      }
+      try {
+         final BufferedReader reader = new BufferedReader( new FileReader( file ) );
+         _comment = loadHeader( reader );
+         QaClip qaClip = loadQaClip( reader );
+         while ( qaClip != null ) {
+            qaClips.add( qaClip );
+            qaClip = loadQaClip( reader );
+         }
+         reader.close();
+      } catch ( IOException ioE ) {
+         System.err.println( "Couldn't load QaClips: " + ioE.getMessage() );
+      }
+      return qaClips;
+   }
+
+   private String loadHeader( final BufferedReader reader ) throws IOException {
+      final StringBuilder stringBuilder = new StringBuilder();
+      boolean readingComment = false;
+      String line = reader.readLine();
+      while ( line != null && !line.equals( QaClipSaver.HEADER_END ) ) {
+         line = reader.readLine();
+         if ( readingComment && !line.equals( QaClipSaver.HEADER_END ) ) {
+            stringBuilder.append( line ).append( "\n" );
+         } else if ( line.startsWith( "Comment:  " ) ) {
+            stringBuilder.append( line.substring( 10 ) ).append( "\n" );
+            readingComment = true;
+         }
+      }
+      return stringBuilder.toString();
+   }
+
+   private QaClip loadQaClip( final BufferedReader reader ) throws IOException {
+      final QaClip qaClip = new QaClip();
+      String questionLine = reader.readLine();
+      if ( questionLine != null && questionLine.isEmpty() ) {
+         questionLine = reader.readLine();
+      }
+      if ( questionLine == null ) {
+         return null;
+      }
+      if ( !questionLine.startsWith( QaClipSaver.QUESTION_LABEL ) ) {
+         System.err.println( "Malformed Question: " + questionLine );
+         return null;
+      }
+      qaClip.setQuestion( questionLine.substring( QaClipSaver.QUESTION_LABEL.length() ) );
+      final String answerLine = reader.readLine();
+      if ( !answerLine.startsWith( QaClipSaver.ANSWER_LABEL ) ) {
+         System.err.println( "Malformed Answer: " + answerLine );
+         return null;
+      }
+      final String answer = answerLine.substring( QaClipSaver.ANSWER_LABEL.length() );
+      qaClip.setAnswer( answerLine.substring( QaClipSaver.ANSWER_LABEL.length() ) );
+      if ( answer.equals( QaClipSaver.NO_ANSWER ) ) {
+         // skip the next question separator
+         reader.readLine();
+         return qaClip;
+      }
+      final String confidenceLine = reader.readLine();
+      if ( !confidenceLine.startsWith( QaClipSaver.CONFIDENCE_LABEL ) ) {
+         System.err.println( "Malformed Confidence: " + confidenceLine );
+         return null;
+      }
+      qaClip.setConfidence( confidenceLine.substring( QaClipSaver.CONFIDENCE_LABEL.length() ) );
+      final String difficultyLine = reader.readLine();
+      if ( !difficultyLine.startsWith( QaClipSaver.DIFFICULTY_LABEL ) ) {
+         System.err.println( "Malformed Difficulty: " + difficultyLine );
+         return null;
+      }
+      qaClip.setDifficulty( difficultyLine.substring( QaClipSaver.DIFFICULTY_LABEL.length() ) );
+      final String docTimeRelLine = reader.readLine();
+      if ( !docTimeRelLine.startsWith( QaClipSaver.DOCTIMEREL_LABEL ) ) {
+         System.err.println( "Malformed DocTimeRel: " + docTimeRelLine );
+         return null;
+      }
+      qaClip.setQaDocTimeRel( docTimeRelLine.substring( QaClipSaver.DOCTIMEREL_LABEL.length() ) );
+      String textClipLine = reader.readLine();
+      while ( textClipLine != null && !textClipLine.equals( QaClipSaver.QA_CLIP_END ) ) {
+         if ( textClipLine.startsWith( QaClipSaver.TEXT_CLIP_LABEL ) ) {
+            final QaClip.TextClip textClip = loadTextClip( textClipLine
+                  .substring( QaClipSaver.TEXT_CLIP_LABEL.length() ) );
+            qaClip.addTextClip( textClip );
+         }
+         textClipLine = reader.readLine();
+      }
+      return qaClip;
+   }
+
+   private QaClip.TextClip loadTextClip( final String textClipLine ) {
+      final String[] splits = textClipLine.split( "\\s+" );
+      if ( splits.length < 4 ) {
+         System.err.println( "Malformed Text Span: " + textClipLine );
+         return null;
+      }
+      final String[] startStop = splits[ 1 ].split( "," );
+      if ( startStop.length != 2 ) {
+         System.err.println( "Malformed Text Span: " + textClipLine );
+         return null;
+      }
+      String orderString = splits[ 0 ];
+      String subOrder = "";
+      final int orderDotIndex = splits[ 0 ].indexOf( '.' );
+      if ( orderDotIndex > 0 ) {
+         orderString = splits[ 0 ].substring( 0, orderDotIndex );
+         if ( splits[ 0 ].length() > orderDotIndex + 1 ) {
+            subOrder = splits[ 0 ].substring( orderDotIndex + 1 );
+         }
+      }
+      Integer order;
+      Integer start;
+      Integer stop;
+      try {
+         order = Integer.parseInt( orderString );
+         start = Integer.parseInt( startStop[ 0 ] );
+         stop = Integer.parseInt( startStop[ 1 ] );
+      } catch ( NumberFormatException nfE ) {
+         System.err.println( "Malformed Text Span: " + textClipLine );
+         return null;
+      }
+      final QaClip.TextClip textClip = new QaClip.TextClip( new DefaultTextSpan( start, stop ), order, subOrder );
+      textClip.setIsExactAnswer( splits[ 2 ].equals( QaClipSaver.EXACT_ANSWER_CODE ) );
+      textClip.setUseDocTimeRel( splits[ 3 ].equals( QaClipSaver.USE_DOCTIMEREL_CODE ) );
+      if ( splits.length > 4 ) {
+         int lastBarIndex = -1;
+         int nextBarIndex = -1;
+         for ( int i = 0; i < 5; i++ ) {
+            nextBarIndex = splits[ 4 ].indexOf( '|', lastBarIndex + 1 );
+            if ( nextBarIndex < 0 ) {
+               break;
+            }
+            if ( nextBarIndex != lastBarIndex + 1 ) {
+               textClip.setInfo( i, splits[ 4 ].substring( lastBarIndex + 1, nextBarIndex ) );
+            }
+            lastBarIndex = nextBarIndex;
+         }
+      }
+      return textClip;
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QuestionClipper.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QuestionClipper.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QuestionClipper.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/QuestionClipper.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,217 @@
+package org.chboston.cnlp.timeline.gui.qaclipper;
+
+import org.chboston.cnlp.gui.FileSelectionPanel;
+import org.chboston.cnlp.gui.FontResizeSlider;
+import org.chboston.cnlp.gui.GlobalHotkeyManager;
+import org.chboston.cnlp.gui.error.ErrorLabel;
+import org.chboston.cnlp.timeline.gui.qaclipper.gui.ClipperMainPanel;
+import org.chboston.cnlp.timeline.gui.qaclipper.gui.QaClipListList;
+import org.chboston.cnlp.timeline.gui.qaclipper.gui.QaClipListModel;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/11/13
+ */
+public class QuestionClipper {
+
+   static private final String TEXT_DOC_PATH
+         = "C:\\Spiffy\\Data\\THYME2\\Gold_Batch_08\\ID008_clinic_024.txt";
+
+   final private QaClipLoader _qaClipLoader = new QaClipLoader();
+   final private QaClipSaver _qaClipSaver = new QaClipSaver();
+   final private QaClipListModel _qaClipListModel = new QaClipListModel();
+   final private ListSelectionModel _qaClipListSelectionModel;
+
+   final JTextArea _commentArea = new JTextArea();
+
+   public QuestionClipper() {
+      final JFrame frame = new JFrame( "Question Answer Clipper (QuACer)" );
+      frame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
+      // Use 1024 x 768 as the minimum required resolution (XGA)
+      // iPhone 3 : 480 x 320 (3:2, HVGA)
+      // iPhone 4 : 960 x 640  (3:2, unique to Apple)
+      // iPhone 5 : 1136 x 640 (under 16:9, unique to Apple)
+      // iPad 3&4 : 2048 x 1536 (4:3, QXGA)
+      // iPad Mini: 1024 x 768 (4:3, XGA)
+      frame.setSize( 1024, 768 );
+      frame.setMinimumSize( new Dimension( 1024, 768 ) );
+      final JMenuBar menuBar = new JMenuBar();
+      final JMenu fileMenu = new JMenu( "File" );
+      fileMenu.add( new JMenuItem( new AbstractAction( "Exit" ) {
+         public void actionPerformed( final ActionEvent event ) {
+            save();
+            System.exit( 0 );
+         }
+      } ) );
+      menuBar.add( fileMenu );
+      final JMenu viewMenu = new JMenu( "View" );
+      final JMenu fontSizeMenu = FontResizeSlider.createFontResizeMenu( frame );
+      viewMenu.add( fontSizeMenu );
+      menuBar.add( viewMenu );
+      frame.setJMenuBar( menuBar );
+      System.setProperty( "apple.laf.useScreenMenuBar", "true" );
+
+      final FileSelectionPanel fileSelectionPanel = new FileSelectionPanel( "Text Document:",
+            JFileChooser.FILES_ONLY, null );
+      fileSelectionPanel.addActionListener( new FileSelectionListener() );
+      final QaClipListList qaClipListList = new QaClipListList( _qaClipListModel );
+      _qaClipListSelectionModel = qaClipListList.getSelectionModel();
+
+      final ErrorLabel errorLabel = new ErrorLabel();
+
+      final ClipperMainPanel clipperMainPanel = new ClipperMainPanel( qaClipListList );
+      clipperMainPanel.addErrorListener( errorLabel );
+
+      _qaClipLoader.addErrorListener( errorLabel );
+      _qaClipSaver.addErrorListener( errorLabel );
+
+      final JPanel qaClipListPanel = new JPanel( new BorderLayout() );
+      qaClipListPanel.add( fileSelectionPanel, BorderLayout.NORTH );
+      final JScrollPane listScrollPane = new JScrollPane( qaClipListList );
+      listScrollPane.setBorder( new CompoundBorder( new EmptyBorder( 0, 5, 5, 5 ), listScrollPane.getBorder() ) );
+      qaClipListPanel.add( listScrollPane, BorderLayout.CENTER );
+
+      final JPanel mainPanel = new JPanel( new BorderLayout() );
+      mainPanel.add( qaClipListPanel, BorderLayout.NORTH );
+      mainPanel.add( clipperMainPanel, BorderLayout.CENTER );
+      final JPanel commentErrorPanel = new JPanel( new BorderLayout() );
+      final JScrollPane commentScrollPane = new JScrollPane( _commentArea );
+      commentScrollPane.setMinimumSize( new Dimension( 0, 50 ) );
+      commentScrollPane.setPreferredSize( new Dimension( 0, 50 ) );
+      commentScrollPane.setBorder( new CompoundBorder( new EmptyBorder( 5, 5, 0, 5 ), commentScrollPane.getBorder() ) );
+      commentErrorPanel.add( commentScrollPane, BorderLayout.CENTER );
+      commentErrorPanel.add( errorLabel, BorderLayout.SOUTH );
+      mainPanel.add( commentErrorPanel, BorderLayout.SOUTH );
+//      mainPanel.add( errorLabel, BorderLayout.SOUTH );
+
+      frame.addWindowListener( new WindowAdapter() {
+         @Override
+         public void windowClosing( final WindowEvent e ) {
+            save();
+            super.windowClosing( e );
+         }
+      } );
+      frame.getContentPane().add( mainPanel );
+
+      final GlobalHotkeyManager hotKeyManager = GlobalHotkeyManager.getInstance();
+      hotKeyManager.addHotKey( "PreviousQuestion", KeyStroke.getKeyStroke(
+            KeyEvent.VK_P, KeyEvent.CTRL_MASK, false ), new PreviousQuestionAction() );
+      hotKeyManager.addHotKey( "NextQuestion", KeyStroke.getKeyStroke(
+            KeyEvent.VK_N, KeyEvent.CTRL_MASK, false ), new NextQuestionAction() );
+
+      frame.setVisible( true );
+
+//      load( TEXT_DOC_PATH );
+      qaClipListList.setSelectedIndex( _qaClipListModel.getSize() - 1 );
+   }
+
+   private void save() {
+      if ( _qaClipListModel.getSize() <= 1 ) {
+         return;
+      }
+      // fire a selection event to update the currently selected QaClipList
+      if ( _qaClipListSelectionModel != null ) {
+         final int newQaClipIndex = _qaClipListModel.getSize() - 1;
+         _qaClipListSelectionModel.setSelectionInterval( newQaClipIndex, newQaClipIndex );
+      }
+      final String title = _qaClipListModel.getTitle();
+      final List<QaClip> qaClips = _qaClipListModel.getQaClips();
+      final String fullText = _qaClipListModel.getFullText();
+      _qaClipSaver.saveQaClips( title, _commentArea.getText(), qaClips, fullText );
+   }
+
+   private void load( final String textFilePath ) {
+      save();
+      _qaClipListSelectionModel.clearSelection();
+      _qaClipListModel.setQaClips( new ArrayList<QaClip>( 0 ) );
+      _qaClipListModel.setTitle( textFilePath );
+      final String text = loadDocumentText( textFilePath );
+      _qaClipListModel.setFullText( text );
+      final List<QaClip> qaClips = _qaClipLoader.loadQaClips( textFilePath );
+      _qaClipListModel.setQaClips( qaClips );
+      _commentArea.setText( _qaClipLoader.getComment() );
+      final int newQaClipIndex = _qaClipListModel.getSize() - 1;
+      _qaClipListSelectionModel.setSelectionInterval( newQaClipIndex, newQaClipIndex );
+   }
+
+   static private String loadDocumentText( final String filePath ) {
+      final File textFile = new File( filePath );
+      if ( !textFile.canRead() ) {
+         return "Cannot read file " + filePath;
+      }
+      final StringBuilder sb = new StringBuilder();
+      try {
+         final BufferedReader reader = new BufferedReader( new FileReader( textFile ) );
+         final char[] buffer = new char[ 8192 ];
+         int length = reader.read( buffer, 0, buffer.length );
+         while ( length >= 0 ) {
+            sb.append( buffer, 0, length );
+            length = reader.read( buffer, 0, buffer.length );
+         }
+         reader.close();
+      } catch ( IOException ioE ) {
+         System.err.println( ioE.getMessage() );
+         return "Problem reading " + filePath + "\n" + ioE.getMessage();
+      }
+      return sb.toString();
+   }
+
+
+   private class FileSelectionListener implements ActionListener {
+      public void actionPerformed( final ActionEvent event ) {
+         if ( !event.getActionCommand().equals( FileSelectionPanel.FILE_SELECTED ) ) {
+            return;
+         }
+         final Object source = event.getSource();
+         if ( source == null || !(source instanceof FileSelectionPanel) ) {
+            return;
+         }
+         final String textFilePath = ((FileSelectionPanel)source).getFilePath();
+         load( textFilePath );
+      }
+   }
+
+
+   private class PreviousQuestionAction extends AbstractAction {
+      public void actionPerformed( final ActionEvent event ) {
+         if ( _qaClipListSelectionModel == null ) {
+            return;
+         }
+         int index = _qaClipListSelectionModel.getLeadSelectionIndex();
+         if ( index > 0 ) {
+            _qaClipListSelectionModel.setSelectionInterval( index - 1, index - 1 );
+         }
+      }
+   }
+
+   private class NextQuestionAction extends AbstractAction {
+      public void actionPerformed( final ActionEvent event ) {
+         if ( _qaClipListSelectionModel == null ) {
+            return;
+         }
+         int index = _qaClipListSelectionModel.getLeadSelectionIndex();
+         if ( index < _qaClipListModel.getSize() - 2 ) {
+            _qaClipListSelectionModel.setSelectionInterval( index + 1, index + 1 );
+         }
+      }
+   }
+
+
+   public static void main( String[] args ) {
+      new QuestionClipper();
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/SimpleTimelineWriter.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/SimpleTimelineWriter.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/SimpleTimelineWriter.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/SimpleTimelineWriter.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,71 @@
+package org.chboston.cnlp.timeline.gui.qaclipper;
+
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.DefaultTimeSpan;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 6/9/14
+ */
+final public class SimpleTimelineWriter {
+
+   static private final Logger LOGGER = Logger.getLogger( "SimpleTimelineWriter" );
+
+   private SimpleTimelineWriter() {
+   }
+
+   static public void writeTimeline( final String filePath, final Timeline timeline ) {
+      try ( BufferedWriter writer = new BufferedWriter( new FileWriter( filePath + ".timeline.txt" ) ) ) {
+         TimeSpan oldTimeSpan = null;
+         boolean printedTimeSpan = false;
+         for ( PointedTimeSpan timeSpan : timeline ) {
+            final TimeSpan simpleTimeSpan = new DefaultTimeSpan( timeSpan );
+            if ( !simpleTimeSpan.equals( oldTimeSpan ) ) {
+               printedTimeSpan = false;
+               oldTimeSpan = simpleTimeSpan;
+            }
+            boolean printedTimeRelation = false;
+            final Collection<Entity> entities = timeline.getEntities( timeSpan );
+            for ( Entity entity : entities ) {
+               if ( !entity.isClassType( SemanticClassType.SIGN_OR_SYMPTOM )
+                    && !entity.isClassType( SemanticClassType.PROCEDURE )
+                    && !entity.isClassType( SemanticClassType.DISEASE_DISORDER )
+                    && !entity.isClassType( SemanticClassType.MEDICATION ) ) {
+                  continue;
+               }
+               String text = entity.getSpannedTextRepresentation();
+               if ( text.indexOf( '[' ) >= 0 && !text.contains( "..." ) ) {
+                  text = text.replace( "[", "" );
+                  text = text.replace( "]", "" );
+               }
+               if ( !printedTimeSpan ) {
+                  writer.write( timeSpan.getSpanText() );
+                  writer.newLine();
+                  printedTimeSpan = true;
+               }
+               if ( !printedTimeRelation ) {
+                  writer.write( "\t" + timeSpan.getRelationText() );
+                  writer.newLine();
+                  printedTimeRelation = true;
+               }
+               writer.write( "\t\t" + text );
+               writer.newLine();
+            }
+         }
+      } catch ( IOException ioE ) {
+         LOGGER.severe( ioE.getMessage() );
+      }
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter1.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter1.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter1.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter1.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,295 @@
+package org.chboston.cnlp.timeline.gui.qaclipper;
+
+import org.chboston.cnlp.nlp.annotation.annotation.Annotation;
+import org.chboston.cnlp.nlp.annotation.attribute.Attribute;
+import org.chboston.cnlp.nlp.annotation.attribute.DefinedAttributeType;
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+import org.chboston.cnlp.nlp.annotation.classtype.TemporalClassType;
+import org.chboston.cnlp.nlp.annotation.coreference.CoreferenceChain;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+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.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.DefaultTimeSpan;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.jdom.Comment;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.logging.Logger;
+
+import static org.chboston.cnlp.anafora.annotation.parser.AnaforaTag.*;
+
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 6/17/14
+ */
+final public class TimelineAnaforaWriter1 {
+
+   static private final Logger LOGGER = Logger.getLogger( "TimelineAnaforaWriter1" );
+   static private final DateFormat LONG_DATE_FORMAT = new SimpleDateFormat( "EEEE, MMMM d, yyyy" );
+
+   static private final String PARENT_TIMELINE = "Timeline";
+   static private final String TIME_SPAN_RELATION = "TimeSpanRelation";
+   static private final String TIME_SPAN = "TimeSpan";
+   static private final String TIME_SPAN_START = "StartTime";
+   static private final String TIME_SPAN_STOP = "StopTime";
+   static private final String TIME_SPAN_FUZZY = "Fuzzy";
+
+   private TimelineAnaforaWriter1() {
+   }
+
+
+   static public void writeTimeline( final String textFilePath, final Timeline timeline ) {
+      try ( Writer writer = new BufferedWriter( new FileWriter( textFilePath + ".timeline1.xml" ) ) ) {
+         final Element root = new Element( ROOT_ELEMENT.getName() );
+         final Document doc = new Document( root );
+         doc.setRootElement( root );
+         final Element annotations = new Element( ANNOTATIONS_GROUP.getName() );
+
+         TimeSpan oldTimeSpan = null;
+         for ( PointedTimeSpan timeSpan : timeline ) {
+            final TimeSpan simpleTimeSpan = new DefaultTimeSpan( timeSpan );
+            addTimeSpan( annotations, timeline, timeSpan, !simpleTimeSpan.equals( oldTimeSpan ) );
+            oldTimeSpan = simpleTimeSpan;
+         }
+
+         root.addContent( annotations );
+         final XMLOutputter outputter = new XMLOutputter();
+         outputter.setFormat( Format.getPrettyFormat() );
+         outputter.output( doc, writer );
+      } catch ( IOException ioE ) {
+         LOGGER.severe( ioE.getMessage() );
+      }
+   }
+
+
+   static private void addTimeSpan( final Element annotations, final Timeline timeline,
+                                    final PointedTimeSpan timeSpan, final boolean addTimeSpanElement )
+         throws IOException {
+      final String startText = LONG_DATE_FORMAT.format( new Date( timeSpan.getStartMillis() ) );
+      final String stopText = LONG_DATE_FORMAT.format( new Date( timeSpan.getStopMillis() ) );
+
+      if ( addTimeSpanElement ) {
+         annotations
+               .addContent( new Comment( "All Events Related to the Time Span " + startText + " .. " + stopText ) );
+         addSimpleTimeSpan( annotations, timeSpan );
+      }
+
+      boolean wroteLinkComment = false;
+      final Collection<Entity> entities = timeline.getEntities( timeSpan );
+      for ( Entity entity : entities ) {
+         if ( !(entity instanceof CoreferenceChain) ) {
+            continue;
+         }
+         if ( !entity.isClassType( SemanticClassType.SIGN_OR_SYMPTOM )
+              && !entity.isClassType( SemanticClassType.PROCEDURE )
+              && !entity.isClassType( SemanticClassType.DISEASE_DISORDER )
+              && !entity.isClassType( SemanticClassType.MEDICATION ) ) {
+            continue;
+         }
+         if ( !wroteLinkComment ) {
+            annotations.addContent( new Comment( "All Events " + timeSpan.getRelationText()
+                                                 + " the Time Span " + startText + " .. " + stopText ) );
+            wroteLinkComment = true;
+         }
+         annotations.addContent( new Comment( "Entities for Coreference Event "
+                                              + entity.getSpannedTextRepresentation() ) );
+         addChain( annotations, (CoreferenceChain)entity );
+         addTimeSpanLink( annotations, timeSpan, entity );
+      }
+   }
+
+
+   static private void addChain( final Element annotations, final CoreferenceChain chain ) throws IOException {
+      for ( Entity link : chain ) {
+         final Element element = createEntityElement( link );
+         if ( element != null ) {
+            annotations.addContent( element );
+         }
+      }
+      final Element element = createCorefElement( chain );
+      if ( element != null ) {
+         annotations.addContent( element );
+      }
+   }
+
+   static private Element createEntityElement( final Entity entity ) throws IOException {
+      final Element element = new Element( ENTITY_ELEMENT.getName() );
+      // ID
+      element.addContent(
+            new Element( ANNOTATION_ID.getName() ).setText( getId( entity ) ) );
+      // Text Span
+      element.addContent(
+            new Element( ANNOTATION_TEXT_SPAN.getName() ).setText( createTextSpanText( entity.getTextSpan() ) ) );
+      // Class Type
+      element.addContent(
+            new Element( ANNOTATION_TYPE.getName() ).setText( entity.getClassType().getName() ) );
+      // Parent Class
+      if ( entity.isClassType( TemporalClassType.EVENT ) || entity.isClassType( TemporalClassType.TIMEX )
+           || entity.isClassType( TemporalClassType.DOCTIME ) ) {
+         element.addContent(
+               new Element( ANNOTATION_PARENT_TYPE.getName() ).setText( PARENT_TEMPORAL.getName() ) );
+      } else {
+         element.addContent(
+               new Element( ANNOTATION_PARENT_TYPE.getName() ).setText( PARENT_UMLS.getName() ) );
+      }
+      // Attributes
+      final Element properties = new Element( ANNOTATION_PROPERTIES.getName() );
+      final Collection<String> attributeNames = entity.getAttributeNames();
+      for ( String name : attributeNames ) {
+         properties.addContent( createAttributeElement( entity.getAttribute( name ) ) );
+      }
+      element.addContent( properties );
+      return element;
+   }
+
+   static private Element createCorefElement( final CoreferenceChain chain ) throws IOException {
+      final Element element = new Element( RELATION_ELEMENT.getName() );
+      // ID
+      element.addContent(
+            new Element( ANNOTATION_ID.getName() ).setText( getId( chain ) ) );
+      // Class Type
+      element.addContent(
+            new Element( ANNOTATION_TYPE.getName() ).setText( COREF_IDENTICAL.getName() ) );
+      // Parent Class
+      element.addContent(
+            new Element( ANNOTATION_PARENT_TYPE.getName() ).setText( PARENT_COREFERENCE.getName() ) );
+      // Attributes
+      final Element properties = new Element( ANNOTATION_PROPERTIES.getName() );
+      boolean isFirst = true;
+      for ( Entity entity : chain ) {
+         if ( isFirst ) {
+            properties.addContent(
+                  new Element( FIRST_COREF_INSTANCE.getName() ).setText( getId( entity ) ) );
+            isFirst = false;
+         } else {
+            properties.addContent(
+                  new Element( OTHER_COREF_INSTANCE.getName() ).setText( getId( entity ) ) );
+         }
+      }
+//      final Collection<String> attributeNames = chain.getAttributeNames();
+//      for ( String name : attributeNames ) {
+//         properties.addContent( createAttributeElement( chain.getAttribute( name ) ) );
+//      }
+      element.addContent( properties );
+      return element;
+   }
+
+
+   static private void addSimpleTimeSpan( final Element annotations, final TimeSpan timeSpan ) {
+      final Element element = createTimeSpanElement( timeSpan );
+      if ( element != null ) {
+         annotations.addContent( element );
+      }
+   }
+
+   static private Element createTimeSpanElement( final TimeSpan timeSpan ) {
+      final Element element = new Element( TIME_SPAN );
+      // ID
+      element.addContent(
+            new Element( ANNOTATION_ID.getName() ).setText( getId( timeSpan ) ) );
+      // Class Type - same as element type
+      element.addContent(
+            new Element( ANNOTATION_TYPE.getName() ).setText( TIME_SPAN ) );
+      // Parent Class
+      element.addContent(
+            new Element( ANNOTATION_PARENT_TYPE.getName() ).setText( PARENT_TIMELINE ) );
+      // Attributes
+      final Element properties = new Element( ANNOTATION_PROPERTIES.getName() );
+      final String startText = LONG_DATE_FORMAT.format( new Date( timeSpan.getStartMillis() ) );
+      final String stopText = LONG_DATE_FORMAT.format( new Date( timeSpan.getStopMillis() ) );
+      properties.addContent(
+            new Element( TIME_SPAN_START ).setText( startText ) );
+      properties.addContent(
+            new Element( TIME_SPAN_STOP ).setText( stopText ) );
+      if ( timeSpan.isFuzzyDate() ) {
+         properties.addContent(
+               new Element( TIME_SPAN_FUZZY ).setText( Boolean.toString( true ) ) );
+      }
+      element.addContent( properties );
+      return element;
+   }
+
+
+   static private void addTimeSpanLink( final Element annotations, final PointedTimeSpan timeSpan,
+                                        final Entity entity ) throws IOException {
+      final Element element = createTimeSpanLinkElement( timeSpan, entity );
+      if ( element != null ) {
+         annotations.addContent( element );
+      }
+   }
+
+   static private Element createTimeSpanLinkElement( final PointedTimeSpan timeSpan,
+                                                     final Entity entity ) throws IOException {
+      final Element element = new Element( TIME_SPAN_RELATION );
+      // ID
+      element.addContent(
+            new Element( ANNOTATION_ID.getName() ).setText( getId( timeSpan, entity ) ) );
+      // Class Type
+      element.addContent(
+            new Element( ANNOTATION_TYPE.getName() ).setText( timeSpan.getRelationText() ) );
+      // Parent Class
+      element.addContent(
+            new Element( ANNOTATION_PARENT_TYPE.getName() ).setText( PARENT_TIMELINE ) );
+      // Attributes
+      final Element properties = new Element( ANNOTATION_PROPERTIES.getName() );
+      properties.addContent(
+            new Element( TIME_SPAN ).setText( getId( timeSpan ) ) );
+      properties.addContent(
+            new Element( ENTITY_ELEMENT.getName() ).setText( getId( entity ) ) );
+      element.addContent( properties );
+      return element;
+   }
+
+
+   static private String createTextSpanText( final TextSpan textSpan ) {
+      if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+         final StringBuilder sb = new StringBuilder();
+         for ( TextSpan subSpan : (DiscontiguousTextSpan)textSpan ) {
+            sb.append( createTextSpanText( subSpan ) ).append( ';' );
+         }
+         sb.setLength( sb.length() - 1 );
+         return sb.toString();
+      }
+      return textSpan.getStartIndex() + "," + textSpan.getEndIndex();
+   }
+
+   static private String getId( final TimeSpan timeSpan ) {
+      return "TimeSpan" + new DefaultTimeSpan( timeSpan ).hashCode();
+   }
+
+   static private String getId( final Annotation annotation ) {
+      final Attribute idAttribute = annotation.getAttribute( DefinedAttributeType.UNIQUE_ID );
+      if ( idAttribute != null ) {
+         return idAttribute.getValue();
+      }
+      return annotation.getClassType().getName() + annotation.hashCode();
+   }
+
+   static private String getId( final TimeSpan timeSpan, final Annotation annotation ) {
+      return getId( timeSpan ) + "_" + getId( annotation );
+   }
+
+
+   static private Element createAttributeElement( final Attribute attribute ) {
+      final String name = attribute.getName().replace( "(", " " ).replace( ")", " " ).replaceAll( "\\s+", "_" );
+      return new Element( name ).setText( attribute.getValue() );
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter5.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter5.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter5.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/TimelineAnaforaWriter5.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,256 @@
+package org.chboston.cnlp.timeline.gui.qaclipper;
+
+import org.chboston.cnlp.nlp.annotation.attribute.AttributeUtil;
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+import org.chboston.cnlp.nlp.annotation.classtype.TemporalClassType;
+import org.chboston.cnlp.nlp.annotation.coreference.CoreferenceChain;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+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.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.jdom.Comment;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.logging.Logger;
+
+import static org.chboston.cnlp.anafora.annotation.parser.AnaforaTag.*;
+
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 6/17/14
+ */
+final public class TimelineAnaforaWriter5 {
+
+   static private final Logger LOGGER = Logger.getLogger( "TimelineAnaforaWriter5" );
+   //   static private final DateFormat SHORT_DATE_FORMAT = new SimpleDateFormat( "M/d/yyyy" );
+//   static private final DateFormat SHORT_TIME_FORMAT = new SimpleDateFormat( "h:mm a" );
+   static private final DateFormat LONG_DATE_FORMAT = new SimpleDateFormat( "EEEE, MMMM d, yyyy" );
+
+   static private final DateFormat ISO_8601_DATE = new SimpleDateFormat( "yyyy-MM-dd" );
+   static private final DateFormat ISO_8601_TIME = new SimpleDateFormat( "HH:mm" );
+
+   static private final String ENTITY_TEXT = "text";
+   static private final String NEGATED = "negated";
+   static private final String UNCERTAIN = "uncertain";
+
+   //   static private final String START_DATE = "startDate";
+   static private final String START_TIME = "startTime";
+   static private final String START_DIR = "startDir";
+   //   static private final String STOP_DATE = "stopDate";
+   static private final String STOP_TIME = "stopTime";
+   static private final String STOP_DIR = "stopDir";
+
+   private TimelineAnaforaWriter5() {
+   }
+
+
+   static public void writeTimeline( final String textFilePath, final Timeline timeline ) {
+      try ( Writer writer = new BufferedWriter( new FileWriter( textFilePath + ".timeline5.xml" ) ) ) {
+         final Element root = new Element( ROOT_ELEMENT.getName() );
+         final Document doc = new Document( root );
+         doc.setRootElement( root );
+         final Element annotations = new Element( ANNOTATIONS_GROUP.getName() );
+
+         for ( PointedTimeSpan timeSpan : timeline ) {
+            addTimeSpan( annotations, timeline, timeSpan );
+         }
+
+         root.addContent( annotations );
+         final XMLOutputter outputter = new XMLOutputter();
+         outputter.setFormat( Format.getPrettyFormat() );
+         outputter.output( doc, writer );
+      } catch ( IOException ioE ) {
+         LOGGER.severe( ioE.getMessage() );
+      }
+   }
+
+
+   static private void addTimeSpan( final Element annotations, final Timeline timeline,
+                                    final PointedTimeSpan timeSpan ) throws IOException {
+      boolean wroteLinkComment = false;
+      final Collection<Entity> entities = timeline.getEntities( timeSpan );
+      for ( Entity entity : entities ) {
+         if ( !entity.isClassType( TemporalClassType.EVENT ) || !isSemanticEntity( entity ) ) {
+            continue;
+         }
+         if ( !wroteLinkComment ) {
+            writeTimeSpanComment( annotations, timeSpan );
+            wroteLinkComment = true;
+         }
+         addChain( annotations, timeSpan, (CoreferenceChain)entity );
+      }
+      for ( Entity entity : entities ) {
+         if ( !entity.isClassType( TemporalClassType.EVENT ) || isSemanticEntity( entity ) ) {
+            continue;
+         }
+         if ( !wroteLinkComment ) {
+            writeTimeSpanComment( annotations, timeSpan );
+            wroteLinkComment = true;
+         }
+         addEvent( annotations, timeSpan, entity );
+      }
+   }
+
+   static private boolean isSemanticEntity( final Entity entity ) {
+      return entity.isClassType( SemanticClassType.SIGN_OR_SYMPTOM )
+             || entity.isClassType( SemanticClassType.PROCEDURE )
+             || entity.isClassType( SemanticClassType.DISEASE_DISORDER )
+             || entity.isClassType( SemanticClassType.MEDICATION );
+   }
+
+   static private void writeTimeSpanComment( final Element annotations, final PointedTimeSpan timeSpan ) {
+      final String startText = LONG_DATE_FORMAT.format( new Date( timeSpan.getStartMillis() ) );
+      final String stopText = LONG_DATE_FORMAT.format( new Date( timeSpan.getStopMillis() ) );
+      annotations.addContent( new Comment( timeSpan.getRelationText() + " " + startText + " .. " + stopText ) );
+   }
+
+
+   static private void addChain( final Element annotations, final PointedTimeSpan timeSpan,
+                                 final CoreferenceChain chain ) throws IOException {
+      final Element element = createCorefElement( timeSpan, chain );
+      if ( element != null ) {
+         annotations.addContent( element );
+      }
+   }
+
+   static private Element createCorefElement( final PointedTimeSpan timeSpan, final CoreferenceChain chain )
+         throws IOException {
+      final Element element = new Element( ENTITY_ELEMENT.getName() );
+      String text = chain.getSpannedTextRepresentation();
+      if ( text.startsWith( "[" ) && text.endsWith( "]" ) && !text.contains( "] ... [" ) ) {
+         text = text.substring( 1, text.length() - 1 );
+      }
+      element.addContent(
+            new Element( ENTITY_TEXT ).setText( text ) );
+      element.addContent(
+            new Element( ANNOTATION_TEXT_SPAN.getName() ).setText( createTextSpanText( chain.getTextSpan() ) ) );
+      // negation, uncertainty
+      if ( AttributeUtil.hasNegativePolarity( chain ) ) {
+         element.addContent(
+               new Element( NEGATED ).setText( "true" ) );
+      }
+      if ( AttributeUtil.hasUncertainty( chain ) ) {
+         element.addContent(
+               new Element( UNCERTAIN ).setText( "true" ) );
+      }
+      // Class Type
+      final Collection<String> classTypes = new HashSet<>();
+      for ( Entity entity : chain ) {
+         if ( entity.getClassType() instanceof SemanticClassType ) {
+            classTypes.add( entity.getClassType().getName() );
+         }
+      }
+      final List<String> classTypeList = new ArrayList<>( classTypes );
+      if ( !classTypeList.isEmpty() ) {
+         Collections.sort( classTypeList );
+         element.addContent(
+               new Element( ANNOTATION_TYPE.getName() ).setText( classTypeList.get( 0 ) ) );
+      } else {
+         element.addContent(
+               new Element( ANNOTATION_TYPE.getName() ).setText( COREF_IDENTICAL.getName() ) );
+      }
+      // Attributes
+      final String startDate = ISO_8601_DATE.format( new Date( timeSpan.getStartMillis() ) );
+      final String startTime = ISO_8601_TIME.format( new Date( timeSpan.getStartMillis() ) );
+      final String stopDate = ISO_8601_DATE.format( new Date( timeSpan.getStopMillis() ) );
+      final String stopTime = ISO_8601_TIME.format( new Date( timeSpan.getStopMillis() ) );
+//      element.addContent(
+//            new Element( START_DATE ).setText( startDate ) );
+      element.addContent(
+            new Element( START_DIR ).setText( timeSpan.getStartTime().getPointer().getName() ) );
+      element.addContent(
+            new Element( START_TIME ).setText( startDate + "T" + startTime ) );
+//      element.addContent(
+//            new Element( STOP_DATE ).setText( stopDate ) );
+      element.addContent(
+            new Element( STOP_DIR ).setText( timeSpan.getStopTime().getPointer().getName() ) );
+      element.addContent(
+            new Element( STOP_TIME ).setText( stopDate + "T" + stopTime ) );
+      if ( classTypeList.size() > 1 ) {
+         for ( String type : classTypeList ) {
+            element.addContent(
+                  new Element( "SemanticType" ).setText( type ) );
+         }
+      }
+      return element;
+   }
+
+
+   static private void addEvent( final Element annotations, final PointedTimeSpan timeSpan,
+                                 final Entity entity ) throws IOException {
+      final Element element = createEventElement( timeSpan, entity );
+      if ( element != null ) {
+         annotations.addContent( element );
+      }
+   }
+
+   static private Element createEventElement( final PointedTimeSpan timeSpan, final Entity entity ) throws IOException {
+      final Element element = new Element( ENTITY_ELEMENT.getName() );
+      String text = entity.getSpannedTextRepresentation();
+      if ( text.startsWith( "[" ) && text.endsWith( "]" ) && !text.contains( "] ... [" ) ) {
+         text = text.substring( 1, text.length() - 1 );
+      }
+      element.addContent(
+            new Element( ENTITY_TEXT ).setText( text ) );
+      element.addContent(
+            new Element( ANNOTATION_TEXT_SPAN.getName() ).setText( createTextSpanText( entity.getTextSpan() ) ) );
+      // negation, uncertainty
+      if ( AttributeUtil.hasNegativePolarity( entity ) ) {
+         element.addContent(
+               new Element( NEGATED ).setText( "true" ) );
+      }
+      if ( AttributeUtil.hasUncertainty( entity ) ) {
+         element.addContent(
+               new Element( UNCERTAIN ).setText( "true" ) );
+      }
+      // Class Type
+      element.addContent(
+            new Element( ANNOTATION_TYPE.getName() ).setText( "Event" ) );
+      // Attributes
+      final String startDate = ISO_8601_DATE.format( new Date( timeSpan.getStartMillis() ) );
+      final String startTime = ISO_8601_TIME.format( new Date( timeSpan.getStartMillis() ) );
+      final String stopDate = ISO_8601_DATE.format( new Date( timeSpan.getStopMillis() ) );
+      final String stopTime = ISO_8601_TIME.format( new Date( timeSpan.getStopMillis() ) );
+      //      element.addContent(
+      //            new Element( START_DATE ).setText( startDate ) );
+      element.addContent(
+            new Element( START_DIR ).setText( timeSpan.getStartTime().getPointer().getName() ) );
+      element.addContent(
+            new Element( START_TIME ).setText( startDate + "T" + startTime ) );
+      //      element.addContent(
+      //            new Element( STOP_DATE ).setText( stopDate ) );
+      element.addContent(
+            new Element( STOP_DIR ).setText( timeSpan.getStopTime().getPointer().getName() ) );
+      element.addContent(
+            new Element( STOP_TIME ).setText( stopDate + "T" + stopTime ) );
+      return element;
+   }
+
+
+   static private String createTextSpanText( final TextSpan textSpan ) {
+      if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+         final StringBuilder sb = new StringBuilder();
+         for ( TextSpan subSpan : (DiscontiguousTextSpan)textSpan ) {
+            sb.append( createTextSpanText( subSpan ) ).append( ';' );
+         }
+         sb.setLength( sb.length() - 1 );
+         return sb.toString();
+      }
+      return textSpan.getStartIndex() + "," + textSpan.getEndIndex();
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/AbstractQaRadioPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/AbstractQaRadioPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/AbstractQaRadioPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/AbstractQaRadioPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,66 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+abstract public class AbstractQaRadioPanel extends JPanel implements QaClipComponent {
+
+   final private JRadioButton _lowButton;
+   final private JRadioButton _medButton;
+   final private JRadioButton _highButton;
+
+   protected AbstractQaRadioPanel( final String panelTitle,
+                                   final String lowTitle, final String mediumTitle, final String highTitle ) {
+      super( new GridLayout( 4, 1, 5, 5 ) );
+      setBorder( new LineBorder( Color.LIGHT_GRAY, 1 ) );
+
+      final JLabel titleLabel = new JLabel( panelTitle );
+      titleLabel.setBorder( new EmptyBorder( 0, 5, 0, 0 ) );
+      titleLabel.setFont( titleLabel.getFont().deriveFont( Font.BOLD ) );
+
+      _lowButton = new JRadioButton( lowTitle );
+      _medButton = new JRadioButton( mediumTitle );
+      _highButton = new JRadioButton( highTitle );
+
+      final ButtonGroup buttonGroup = new ButtonGroup();
+      buttonGroup.add( _lowButton );
+      buttonGroup.add( _medButton );
+      buttonGroup.add( _highButton );
+
+      add( titleLabel );
+      add( _lowButton );
+      add( _medButton );
+      add( _highButton );
+   }
+
+   public void reset() {
+      _medButton.setSelected( true );
+   }
+
+   protected String getSelection() {
+      if ( _lowButton.isSelected() ) {
+         return _lowButton.getText();
+      } else if ( _highButton.isSelected() ) {
+         return _highButton.getText();
+      }
+      return _medButton.getText();
+   }
+
+   protected void setSelection( final String selection ) {
+      if ( _lowButton.getText().equals( selection ) ) {
+         _lowButton.setSelected( true );
+      } else if ( _highButton.getText().equals( selection ) ) {
+         _highButton.setSelected( true );
+      } else {
+         _medButton.setSelected( true );
+      }
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/ClipperMainPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/ClipperMainPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/ClipperMainPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/ClipperMainPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,327 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.gui.PartialEtchedBorder;
+import org.chboston.cnlp.gui.PositioningSplitPane;
+import org.chboston.cnlp.gui.error.ErrorListener;
+import org.chboston.cnlp.gui.error.ErrorProducer;
+import org.chboston.cnlp.nlp.annotation.textspan.DefaultTextSpan;
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.text.Caret;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.Collection;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class ClipperMainPanel extends JPanel implements QaClipComponent {
+
+   final private JTextField _queryField;
+   final private JTextField _answerField;
+
+   final private TableTextHighlightPanel _textArea;
+   final private TextClipsTable _textClipsTable;
+   final private TextClipsTableModel _answerTableModel;
+
+   final private QaConfidencePanel _confidencePanel;
+   final private QaDifficultyPanel _difficultyPanel;
+   final private QaDocTimeRelPanel _docTimeRelPanel;
+
+
+   final private QaClipListModel _qaClipListModel;
+   final private ListSelectionModel _qaClipListSelectionModel;
+
+   final private ErrorProducer _errorProducer = new ErrorProducer();
+
+
+   public ClipperMainPanel( final QaClipListList qaClipListList ) {
+      super( new BorderLayout( 0, 5 ) );
+      setBorder( new CompoundBorder( new PartialEtchedBorder( true, true, false, false ),
+            new EmptyBorder( 5, 10, 5, 10 ) ) );
+
+      _qaClipListModel = qaClipListList.getListModel();
+      _qaClipListModel.addListDataListener( new QaListDataListener() );
+      _qaClipListSelectionModel = qaClipListList.getSelectionModel();
+      qaClipListList.addListSelectionListener( new QaListSelectionListener() );
+
+      _queryField = new JTextField();
+      final JComponent queryPanel = createQaPanel( "Question:", _queryField );
+      _answerField = new JTextField();
+      final JComponent answerPanel = createQaPanel( "Answer:", _answerField );
+
+      final JPanel qaPanel = new JPanel( new GridLayout( 1, 2, 20, 5 ) );
+      qaPanel.add( queryPanel );
+      qaPanel.add( answerPanel );
+
+      _answerTableModel = new TextClipsTableModel( _answerField );
+      _textClipsTable = new TextClipsTable( _answerTableModel );
+
+      _textArea = createTextArea( _textClipsTable );
+      final JScrollPane textScrollPane = new JScrollPane( _textArea );
+      _textArea.setVerticalScrollBar( textScrollPane.getVerticalScrollBar() );
+
+      _confidencePanel = new QaConfidencePanel();
+      _difficultyPanel = new QaDifficultyPanel();
+      final JPanel radioPanel = new JPanel( new GridLayout( 1, 2, 5, 0 ) );
+      radioPanel.add( _confidencePanel );
+      radioPanel.add( _difficultyPanel );
+
+      _docTimeRelPanel = new QaDocTimeRelPanel();
+      final JPanel bigRadioPanel = new JPanel( new BorderLayout() );
+      bigRadioPanel.add( _docTimeRelPanel, BorderLayout.NORTH );
+      bigRadioPanel.add( radioPanel, BorderLayout.SOUTH );
+
+      final PositioningSplitPane listSplitPane = new PositioningSplitPane( JSplitPane.VERTICAL_SPLIT,
+            new JScrollPane( _textClipsTable ),
+//                                                                           radioPanel );
+            bigRadioPanel );
+//      listSplitPane.setDividerLocation( 0.75 );
+      listSplitPane.setDividerLocation( 0.5 );
+
+      final PositioningSplitPane textListSplitPane = new PositioningSplitPane( JSplitPane.HORIZONTAL_SPLIT,
+            textScrollPane,
+            listSplitPane );
+      textListSplitPane.setDividerLocation( 0.75 );
+
+      final JButton addQaButton = new JButton( new AddQaAction() );
+      final JButton resetAllButton = new JButton( new NewAllAction() );
+      final JButton resetAnswerButton = new JButton( new NewAnswerAction() );
+      final Box buttonPanel = Box.createHorizontalBox();
+      buttonPanel.add( Box.createHorizontalGlue() );
+      buttonPanel.add( addQaButton );
+      buttonPanel.add( Box.createHorizontalStrut( 5 ) );
+      buttonPanel.add( resetAllButton );
+      buttonPanel.add( Box.createHorizontalStrut( 5 ) );
+      buttonPanel.add( resetAnswerButton );
+
+      add( qaPanel, BorderLayout.NORTH );
+      add( textListSplitPane, BorderLayout.CENTER );
+      add( buttonPanel, BorderLayout.SOUTH );
+   }
+
+   public void setQaClip( final QaClip qaClip ) {
+      _queryField.setText( qaClip.getQuestion() );
+      _answerField.setText( qaClip.getAnswer() );
+      _textClipsTable.setQaClip( qaClip );
+      _confidencePanel.setQaClip( qaClip );
+      _difficultyPanel.setQaClip( qaClip );
+      _docTimeRelPanel.setQaClip( qaClip );
+      fireErrorCleared();
+   }
+
+   public void adjustQaClip( final QaClip qaClip ) {
+      qaClip.setQuestion( _queryField.getText() );
+      qaClip.setAnswer( _answerField.getText() );
+      _textClipsTable.adjustQaClip( qaClip );
+      _confidencePanel.adjustQaClip( qaClip );
+      _difficultyPanel.adjustQaClip( qaClip );
+      _docTimeRelPanel.adjustQaClip( qaClip );
+   }
+
+   static private JComponent createQaPanel( final String title, final JTextField textField ) {
+      final Box panel = Box.createHorizontalBox();
+      panel.add( new JLabel( title ) );
+      panel.add( Box.createHorizontalStrut( 5 ) );
+      panel.add( textField );
+      return panel;
+   }
+
+   private TableTextHighlightPanel createTextArea( final JTable table ) {
+      final OrderHighlightPanel textArea = new OrderHighlightPanel( table, 5 );
+      textArea.setBorder( new EmptyBorder( 0, 5, 0, 5 ) );
+      textArea.setEditable( false );
+      textArea.setLineWrap( true );
+      textArea.setWrapStyleWord( true );
+      textArea.addKeyListener( new QaKeyListener() );
+      return textArea;
+   }
+
+   private void addQandA() {
+      final QaClip qaClip = new QaClip();
+      adjustQaClip( qaClip );
+      if ( !isQaClipComplete( qaClip ) ) {
+         return;
+      }
+      _qaClipListModel.addQaClip( qaClip );
+      final int lastQaClipIndex = _qaClipListModel.getSize() - 2;
+      _qaClipListSelectionModel.setSelectionInterval( lastQaClipIndex, lastQaClipIndex );
+   }
+
+   private void newAnswer() {
+//      final QaClip qaClip = new QaClip();
+//      qaClip.setQuestion( _queryField.getText() );
+//      setQaClip( qaClip );
+      final String question = _queryField.getText();
+      newQandA();
+      _queryField.setText( question );
+   }
+
+   public void newQandA() {
+//      final QaClip qaClip = new QaClip();
+//      setQaClip( qaClip );
+      final int newQaClipIndex = _qaClipListModel.getSize() - 1;
+      _qaClipListSelectionModel.setSelectionInterval( newQaClipIndex, newQaClipIndex );
+   }
+
+
+   private boolean isQaClipComplete( final QaClip qaClip ) {
+      final String question = qaClip.getQuestion();
+      if ( question.trim().isEmpty() ) {
+         fireErrorOccurred( "Please Type a question" );
+         return false;
+      }
+      final Collection<QaClip.TextClip> textClips = qaClip.getTextClips();
+      final String answer = qaClip.getAnswer();
+      if ( answer.trim().isEmpty() && !textClips.isEmpty() ) {
+         fireErrorOccurred( "Please Type an answer" );
+         return false;
+      }
+      if ( !answer.trim().isEmpty() && textClips.isEmpty() ) {
+         fireErrorOccurred( "Please Select some Text" );
+         return false;
+      }
+      fireErrorCleared();
+      return true;
+   }
+
+   private void fireErrorOccurred( final String error ) {
+      _errorProducer.fireErrorOccurred( error );
+   }
+
+   private void fireErrorCleared() {
+      _errorProducer.fireErrorCleared();
+   }
+
+   public void addErrorListener( final ErrorListener listener ) {
+      _errorProducer.addErrorListener( listener );
+      _answerTableModel.addErrorListener( listener );
+   }
+
+
+   private class QaKeyListener extends KeyAdapter {
+      public void keyTyped( final KeyEvent event ) {
+         final char c = event.getKeyChar();
+         if ( c != 'a' ) {
+            return;
+         }
+         final String text = _textArea.getSelectedText();
+         if ( text == null || text.trim().isEmpty() ) {
+            return;
+         }
+         final Caret caret = _textArea.getCaret();
+         if ( caret == null ) {
+            return;
+         }
+         final int start = Math.min( caret.getDot(), caret.getMark() );
+         final int stop = Math.max( caret.getDot(), caret.getMark() );
+         if ( start == stop ) {
+            return;
+         }
+         _answerTableModel.addTextSpan( new DefaultTextSpan( start, stop ) );
+      }
+   }
+
+   private class AddQaAction extends AbstractAction {
+      private AddQaAction() {
+         super( "Add Q & A" );
+      }
+
+      public void actionPerformed( final ActionEvent event ) {
+         addQandA();
+      }
+   }
+
+   private class NewAnswerAction extends AbstractAction {
+      private NewAnswerAction() {
+         super( "New Answer Only" );
+      }
+
+      public void actionPerformed( final ActionEvent event ) {
+         newAnswer();
+      }
+   }
+
+   private class NewAllAction extends AbstractAction {
+      private NewAllAction() {
+         super( "New Q & A" );
+      }
+
+      public void actionPerformed( final ActionEvent event ) {
+         newQandA();
+      }
+   }
+
+   private class QaListDataListener implements ListDataListener {
+      public void intervalAdded( final ListDataEvent event ) {
+      }
+
+      public void intervalRemoved( final ListDataEvent event ) {
+         final Object source = event.getSource();
+         if ( source == null || !(source instanceof QaClipListModel) ) {
+            return;
+         }
+         if ( ((QaClipListModel)source).getSize() <= 1 ) {
+            newQandA();
+         }
+      }
+
+      public void contentsChanged( final ListDataEvent event ) {
+         final Object source = event.getSource();
+         if ( source == null || !(source instanceof QaClipListModel) ) {
+            return;
+         }
+         final String fullText = ((QaClipListModel)source).getFullText();
+         _textArea.setText( fullText );
+         _textClipsTable.setFullText( fullText );
+      }
+   }
+
+   private class QaListSelectionListener implements ListSelectionListener {
+      public void valueChanged( final ListSelectionEvent event ) {
+         if ( event == null || event.getValueIsAdjusting() ) {
+            return;
+         }
+         final Object source = event.getSource();
+         if ( !(source instanceof QaClipListList) ) {
+            return;
+         }
+         final ListSelectionModel selectionModel = ((QaClipListList)source).getSelectionModel();
+         if ( selectionModel.isSelectionEmpty() ) {
+            return;
+         }
+         final int firstIndex = event.getFirstIndex();
+         final int lastIndex = event.getLastIndex();
+         final int leadIndex = selectionModel.getLeadSelectionIndex();
+         final int oldIndex = (leadIndex != firstIndex) ? firstIndex : lastIndex;
+         if ( firstIndex != lastIndex && oldIndex < ((QaClipListList)source).getModel().getSize() - 1 ) {
+            // Was a movement up or down in the list (not from newQaClip), save the state of the old QaClip
+            final QaClip qaClip = new QaClip();
+            adjustQaClip( qaClip );
+            if ( isQaClipComplete( qaClip ) ) {
+               ((QaClipListList)source).getListModel().setQaClipAt( oldIndex, qaClip );
+            }
+         }
+         if ( leadIndex < 0 || leadIndex >= ((QaClipListList)source).getModel().getSize() ) {
+            return;
+         }
+         final Object value = ((QaClipListList)source).getModel().getElementAt( leadIndex );
+         if ( value == null || !(value instanceof QaClip) ) {
+            return;
+         }
+         setQaClip( (QaClip)value );
+      }
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderColorFactory.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderColorFactory.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderColorFactory.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderColorFactory.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,66 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/13/13
+ */
+final public class OrderColorFactory {
+   static final private int STARTING_RED = 255 + 64;
+   static final private int STARTING_GREEN = 255 + 64;
+   static final private int STARTING_BLUE = 255 + 64;
+
+   static final private Map<Integer, Color> COLOR_MAP = new HashMap<>();
+
+   private OrderColorFactory() {
+   }
+
+   static private int getColorValue( final int startingColor, final int offset ) {
+      return Math.min( 255, Math.max( 0, startingColor - offset * 64 ) );
+   }
+
+   static public Color getColor( final int order ) {
+      if ( COLOR_MAP.containsKey( order ) ) {
+         return COLOR_MAP.get( order );
+      }
+      int red = 0;
+      int green = 0;
+      int blue = 0;
+      if ( order < 12 ) {
+         green = getColorValue( STARTING_GREEN, order );
+         blue = (green == 0) ? getColorValue( STARTING_BLUE, order - 4 ) : 0;
+         red = (green == 0 && blue == 0) ? getColorValue( STARTING_RED, order - 8 ) : 0;
+      } else if ( order < 16 ) {
+         green = getColorValue( STARTING_GREEN, order - 12 );
+         blue = getColorValue( STARTING_BLUE, order - 12 );
+         red = 0;
+      } else if ( order < 20 ) {
+         green = getColorValue( STARTING_GREEN, order - 16 );
+         blue = 0;
+         red = getColorValue( STARTING_RED, order - 16 );
+      } else if ( order < 24 ) {
+         green = 0;
+         blue = getColorValue( STARTING_BLUE, order - 20 );
+         red = getColorValue( STARTING_RED, order - 20 );
+      } else {
+         red = (int)(Math.random() * 255);
+         green = (int)(Math.random() * 255);
+         blue = (int)(Math.random() * 255);
+      }
+      final Color color = new Color( red, green, blue );
+      COLOR_MAP.put( order, color );
+      return color;
+   }
+
+
+   static public Color getComplimentColor( final Color color ) {
+      if ( color.getRed() < 128 && color.getGreen() < 96 && color.getBlue() < 128 ) {
+         return Color.WHITE;
+      }
+      return Color.BLACK;
+   }
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderHighlightPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderHighlightPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderHighlightPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/OrderHighlightPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,173 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.nlp.annotation.textspan.DefaultDiscontiguousTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.DefaultTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.DiscontiguousTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.TextSpan;
+
+import javax.swing.*;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.TableModel;
+import javax.swing.text.*;
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/13/13
+ */
+public class OrderHighlightPanel extends TableTextHighlightPanel {
+
+   private final OrderedSpanPainter _textSpanPainter = new OrderedSpanPainter();
+
+   final private Map<TextSpan, Integer> _textSpanYs;
+   final private Map<TextSpan, Integer> _textSpanOrders;
+   //   final private Collection<Object> _highlightTags;
+
+   public OrderHighlightPanel( final JTable table, final int textSpanColumn ) {
+      super( table, textSpanColumn );
+      _textSpanYs = new HashMap<>();
+      _textSpanOrders = new HashMap<>();
+      //      _highlightTags = new HashSet<Object>();
+      table.getModel().addTableModelListener( new PrivateModelListener() );
+   }
+
+   private void highlightTextSpan( final TextSpan textSpan ) {
+      if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+         for ( TextSpan textSpan1 : (DiscontiguousTextSpan)textSpan ) {
+            highlightTextSpan( textSpan1 );
+         }
+         return;
+      }
+      try {
+         final Rectangle bounds = modelToView( textSpan.getStartIndex() );
+         _textSpanYs.put( textSpan, bounds.y );
+         final Object tag = getHighlighter().addHighlight( textSpan.getStartIndex(), textSpan.getEndIndex(),
+               _textSpanPainter );
+         //         _highlightTags.add( tag );
+      } catch ( BadLocationException blE ) {
+         System.err.println( blE.getMessage() );
+      }
+   }
+
+   private void clear() {
+      _textSpanYs.clear();
+      _textSpanOrders.clear();
+      //      _highlightTags.clear();
+      final Highlighter highlighter = getHighlighter();
+      highlighter.removeAllHighlights();
+   }
+
+   private class PrivateModelListener implements TableModelListener {
+      public void tableChanged( final TableModelEvent event ) {
+         int firstRow = event.getFirstRow();
+         if ( firstRow == -1 ) {
+            clear();
+            return;
+         }
+         final TableModel tableModel = getTable().getModel();
+         // some table events ( e.g. AbstractTableModel.fireTableChanged() ) use Integer.MAX_VALUE
+         int lastRow = Math.max( 0, Math.min( tableModel.getRowCount() - 1, event.getLastRow() ) );
+         if ( event.getType() == TableModelEvent.DELETE ) {
+            clear();
+            firstRow = 0;
+            lastRow = tableModel.getRowCount() - 1;
+         }
+         for ( int i = firstRow; i <= lastRow; i++ ) {
+            final Object value1 = tableModel.getValueAt( i, 1 );
+            if ( value1 == null ) {
+               continue;
+            }
+            final int order = (Integer)value1;
+            final Object value2 = tableModel.getValueAt( i, 5 );
+            final TextSpan textSpan = (TextSpan)value2;
+            _textSpanOrders.put( textSpan, order );
+            highlightTextSpan( textSpan );
+         }
+      }
+   }
+
+   protected void paintScrollBarTrack( final Graphics g, final Rectangle trackBounds ) {
+      if ( _textSpanYs.isEmpty() ) {
+         super.paintScrollBarTrack( g, trackBounds );
+         return;
+      }
+      final double panelHeight = OrderHighlightPanel.this.getHeight();
+      final double trackHeight = trackBounds.height;
+      final double xForm = trackHeight / panelHeight;
+      final int rowHeight = Math.max( 2, (int)(getRowHeight() * xForm) );
+      for ( Map.Entry<TextSpan, Integer> textSpanY : _textSpanYs.entrySet() ) {
+         final int order = _textSpanOrders.get( textSpanY.getKey() );
+         final Color orderColor = OrderColorFactory.getColor( order );
+         g.setColor( orderColor );
+         final int trackY = trackBounds.y + (int)(textSpanY.getValue() * xForm);
+         g.fillRect( trackBounds.x, trackY, trackBounds.width, rowHeight );
+      }
+      super.paintScrollBarTrack( g, trackBounds );
+   }
+
+   private class OrderedSpanPainter extends LayeredHighlighter.LayerPainter {
+      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 ) {
+         final TextSpan key = new DefaultTextSpan( startIndex, stopIndex );
+         TextSpan textSpanOrderKey = null;
+         if ( _textSpanOrders.containsKey( key ) ) {
+            textSpanOrderKey = key;
+         } else {
+            // multi-line text will not match any textspan in the map, go through all textSpans and check for contains
+            for ( TextSpan orderSpan : _textSpanOrders.keySet() ) {
+               if ( orderSpan.getIntersectionSpan( key ).equals( key ) ) {
+                  textSpanOrderKey = orderSpan;
+                  break;
+               }
+            }
+         }
+         if ( textSpanOrderKey == null ) {
+            return null;
+         }
+         final Integer order = _textSpanOrders.get( textSpanOrderKey );
+         if ( order == null ) {
+            return null;
+         }
+         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;
+            }
+         }
+         final Color orderColor = OrderColorFactory.getColor( order );
+         g.setColor( orderColor );
+         final FontMetrics fm = comp.getFontMetrics( comp.getFont() );
+         final int lineHeight = fm.getHeight();
+         final int lineCount = alloc.height / lineHeight;
+         for ( int i = 0; i < lineCount; i++ ) {
+            final int baseline = alloc.y + lineHeight + i * lineHeight - fm.getDescent() + 1;
+            g.drawLine( alloc.x, baseline, alloc.x + alloc.width, baseline );
+            g.drawLine( alloc.x, baseline + 1, alloc.x + alloc.width, baseline + 1 );
+         }
+         return alloc;
+      }
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipComponent.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipComponent.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipComponent.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipComponent.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,16 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public interface QaClipComponent {
+
+   void setQaClip( final QaClip qaClip );
+
+   void adjustQaClip( final QaClip qaClip );
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListList.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListList.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListList.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListList.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,100 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/16/13
+ */
+public class QaClipListList extends JList {
+
+   public QaClipListList( final QaClipListModel model ) {
+      super( model );
+      setVisibleRowCount( 3 );
+      setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
+      setCellRenderer( new QaClipListRenderer() );
+      addKeyListener( new ListKeyListener() );
+      //      addListSelectionListener( new SelectionScroller() );
+   }
+
+   public QaClipListModel getListModel() {
+      return (QaClipListModel)getModel();
+   }
+
+   static private class QaClipListRenderer extends DefaultListCellRenderer {
+      static private final Border BORDER = new LineBorder( Color.LIGHT_GRAY, 1 );
+
+      public Component getListCellRendererComponent( final JList list, final Object value, final int index,
+                                                     final boolean isSelected, final boolean cellHasFocus ) {
+         if ( value == null || !(value instanceof QaClip) ) {
+            return super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
+         }
+         if ( value.equals( QaClipListModel.NEW_QA_CLIP ) ) {
+            return super.getListCellRendererComponent( list,
+                  "New Question and Answer",
+                  index,
+                  isSelected,
+                  cellHasFocus );
+         }
+         final QaClip qaClip = (QaClip)value;
+         final String textValue = qaClip.getQuestion() + " : " + qaClip.getAnswer();
+         return super.getListCellRendererComponent( list, textValue, index, isSelected, cellHasFocus );
+      }
+
+      public Border getBorder() {
+         return BORDER;
+      }
+   }
+
+   static private class ListKeyListener extends KeyAdapter {
+      public void keyTyped( final KeyEvent event ) {
+         final Object source = event.getSource();
+         if ( source == null || !(source instanceof QaClipListList) ) {
+            return;
+         }
+         final int listSize = ((QaClipListList)source).getModel().getSize();
+         final int selectedIndex = ((QaClipListList)source).getSelectedIndex();
+         if ( selectedIndex < 0 || selectedIndex >= listSize - 1 ) {
+            return;
+         }
+         final int keyChar = event.getKeyChar();
+         if ( keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE ) {
+//            ((QaClipListList) source).clearSelection();
+            ((QaClipListList)source).setSelectedIndex( ((QaClipListList)source).getListModel().getSize() - 1 );
+            ((QaClipListList)source).getListModel().removeElementAt( selectedIndex );
+//            ((QaClipListList) source).setSelectedIndex( ((QaClipListList) source).getListModel().getSize()-1 );
+         }
+      }
+   }
+
+   // Not working
+   //   static private class SelectionScroller implements ListSelectionListener  {
+   //      public void valueChanged( final ListSelectionEvent event ) {
+   //         if ( event == null || event.getValueIsAdjusting() ) {
+   //            return;
+   //         }
+   //         final Object source = event.getSource();
+   //         if ( !(source instanceof QaClipListList) ) {
+   //            return;
+   //         }
+   //         final ListSelectionModel selectionModel = ((QaClipListList)source).getSelectionModel();
+   //         if ( selectionModel.isSelectionEmpty() ) {
+   //            return;
+   //         }
+   //         final int leadIndex = selectionModel.getLeadSelectionIndex();
+   //         if ( leadIndex < 0 || leadIndex >= ((QaClipListList)source).getModel().getSize() ) {
+   //            return;
+   //         }
+   //         ((QaClipListList)source).scrollRectToVisible( new Rectangle( 0, ((QaClipListList)source).getHeight()-10, 10, 10 ) );
+   //      }
+   //   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListModel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListModel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListModel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaClipListModel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,93 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class QaClipListModel extends AbstractListModel {
+
+   static public final QaClip NEW_QA_CLIP = new QaClip();
+
+   private String _title;
+   private String _fullText;
+   private List<QaClip> _qaClips = new ArrayList<>();
+
+   public void setTitle( final String title ) {
+      _title = title;
+   }
+
+   public String getTitle() {
+      return _title;
+   }
+
+   public void setFullText( final String fullText ) {
+      _fullText = fullText;
+      fireContentsChanged( this, -1, -1 );
+   }
+
+   public String getFullText() {
+      return _fullText;
+   }
+
+
+   public void setQaClips( final List<QaClip> qaClips ) {
+      final int oldSize = getSize();
+      _qaClips.clear();
+      fireIntervalRemoved( this, 0, oldSize );
+      _qaClips.addAll( qaClips );
+      fireIntervalAdded( this, 0, getSize() );
+   }
+
+   public List<QaClip> getQaClips() {
+      return Collections.unmodifiableList( _qaClips );
+   }
+
+   public void addQaClip( final QaClip qaClip ) {
+      _qaClips.add( qaClip );
+      final int index = getSize() - 1;
+      fireIntervalAdded( this, index, index );
+   }
+
+   public void removeElementAt( final int index ) {
+      if ( index < 0 || index >= getSize() - 1 ) {
+         return;
+      }
+      _qaClips.remove( index );
+      fireIntervalRemoved( this, index, index );
+   }
+
+   public void setQaClipAt( final int index, final QaClip qaClip ) {
+      if ( qaClip != null ) {
+         _qaClips.set( index, qaClip );
+         fireContentsChanged( this, index, index );
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int getSize() {
+      return _qaClips.size() + 1;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getElementAt( final int index ) {
+      if ( index == getSize() - 1 ) {
+         return NEW_QA_CLIP;
+      }
+      return _qaClips.get( index );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaConfidencePanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaConfidencePanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaConfidencePanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaConfidencePanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,28 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClipSaver;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class QaConfidencePanel extends AbstractQaRadioPanel {
+
+   public QaConfidencePanel() {
+      super( QaClipSaver.CONFIDENCE_LABEL.trim(),
+            QaClip.Confidence.Uncertain.name(), QaClip.Confidence.Medium.name(), QaClip.Confidence.Certain.name() );
+   }
+
+   public void setQaClip( final QaClip qaClip ) {
+      final QaClip.Confidence confidence = qaClip.getConfidence();
+      setSelection( confidence.name() );
+   }
+
+   public void adjustQaClip( final QaClip qaClip ) {
+      final String selection = getSelection();
+      qaClip.setConfidence( selection );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDifficultyPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDifficultyPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDifficultyPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDifficultyPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,28 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClipSaver;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class QaDifficultyPanel extends AbstractQaRadioPanel {
+
+   public QaDifficultyPanel() {
+      super( QaClipSaver.DIFFICULTY_LABEL.trim(),
+            QaClip.Difficulty.Easy.name(), QaClip.Difficulty.Medium.name(), QaClip.Difficulty.Difficult.name() );
+   }
+
+   public void setQaClip( final QaClip qaClip ) {
+      final QaClip.Difficulty difficulty = qaClip.getDifficulty();
+      setSelection( difficulty.name() );
+   }
+
+   public void adjustQaClip( final QaClip qaClip ) {
+      final String selection = getSelection();
+      qaClip.setDifficulty( selection );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDocTimeRelPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDocTimeRelPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDocTimeRelPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/qaclipper/gui/QaDocTimeRelPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,85 @@
+package org.chboston.cnlp.timeline.gui.qaclipper.gui;
+
+import org.chboston.cnlp.timeline.gui.qaclipper.QaClip;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/16/13
+ */
+public class QaDocTimeRelPanel extends JPanel implements QaClipComponent {
+
+   final private JRadioButton _beforeButton;
+   final private JRadioButton _overlapButton;
+   final private JRadioButton _afterButton;
+   final private JRadioButton _naButton;
+
+   protected QaDocTimeRelPanel() {
+      super( new GridLayout( 5, 1, 5, 5 ) );
+      setBorder( new LineBorder( Color.LIGHT_GRAY, 1 ) );
+
+      final JLabel titleLabel = new JLabel( "DocTimeRel to Use:" );
+      titleLabel.setBorder( new EmptyBorder( 0, 5, 0, 0 ) );
+      titleLabel.setFont( titleLabel.getFont().deriveFont( Font.BOLD ) );
+
+      _beforeButton = new JRadioButton( QaClip.QaDocTimeRel.Before.name() );
+      _overlapButton = new JRadioButton( QaClip.QaDocTimeRel.Overlap.name() );
+      _afterButton = new JRadioButton( QaClip.QaDocTimeRel.After.name() );
+      _naButton = new JRadioButton( QaClip.QaDocTimeRel.NA.name() );
+
+      final ButtonGroup buttonGroup = new ButtonGroup();
+      buttonGroup.add( _beforeButton );
+      buttonGroup.add( _overlapButton );
+      buttonGroup.add( _afterButton );
+      buttonGroup.add( _naButton );
+
+      add( titleLabel );
+      add( _beforeButton );
+      add( _overlapButton );
+      add( _afterButton );
+      add( _naButton );
+   }
+
+   public void setQaClip( final QaClip qaClip ) {
+      final QaClip.QaDocTimeRel docTimeRel = qaClip.getQaDocTimeRel();
+      setSelection( docTimeRel.name() );
+   }
+
+   public void adjustQaClip( final QaClip qaClip ) {
+      final String selection = getSelection();
+      qaClip.setQaDocTimeRel( selection );
+   }
+
+   public void reset() {
+      _naButton.setSelected( true );
+   }
+
+   protected String getSelection() {
+      if ( _beforeButton.isSelected() ) {
+         return _beforeButton.getText();
+      } else if ( _overlapButton.isSelected() ) {
+         return _overlapButton.getText();
+      } else if ( _afterButton.isSelected() ) {
+         return _afterButton.getText();
+      }
+      return _naButton.getText();
+   }
+
+   protected void setSelection( final String selection ) {
+      if ( _beforeButton.getText().equals( selection ) ) {
+         _beforeButton.setSelected( true );
+      } else if ( _overlapButton.getText().equals( selection ) ) {
+         _overlapButton.setSelected( true );
+      } else if ( _afterButton.getText().equals( selection ) ) {
+         _afterButton.setSelected( true );
+      } else {
+         _naButton.setSelected( true );
+      }
+   }
+
+}