ctakes-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From seanfi...@apache.org
Subject svn commit: r1660963 [3/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/an...
Date Thu, 19 Feb 2015 18:06:17 GMT
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeCheckBox.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeCheckBox.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeCheckBox.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeCheckBox.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,50 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 11/21/12
+ */
+public class MultiSizeCheckBox extends JCheckBox {
+
+   final private Set<String> _labelTexts;
+
+   public MultiSizeCheckBox( final String... text ) {
+      super( text[ 0 ] );
+      _labelTexts = new HashSet<>( Arrays.asList( text ) );
+   }
+
+   /**
+    * @param length pixel length into which the label text should fit
+    * @return the largest text label that fits the size of this label
+    */
+   public String getTextForLength( final int length ) {
+      final FontMetrics fm = getFontMetrics( getFont() );
+      int maxTextLength = 0;
+      String maxText = "";
+      for ( String text : _labelTexts ) {
+         final int textLength = SwingUtilities.computeStringWidth( fm, text );
+         if ( textLength > maxTextLength && textLength <= length ) {
+            maxTextLength = textLength;
+            maxText = text;
+         }
+      }
+      return maxText;
+   }
+
+   /**
+    * {@inheritDoc}
+    *
+    * @return the longest label text
+    */
+   public String getToolTipText() {
+      return getTextForLength( Integer.MAX_VALUE );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeLabel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeLabel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeLabel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/MultiSizeLabel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,50 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 11/19/12
+ */
+public class MultiSizeLabel extends JLabel {
+
+   final private Set<String> _labelTexts;
+
+   public MultiSizeLabel( final String... text ) {
+      super( text[ 0 ] );
+      _labelTexts = new HashSet<>( Arrays.asList( text ) );
+   }
+
+   /**
+    * @param length pixel length into which the label text should fit
+    * @return the largest text label that fits the size of this label
+    */
+   public String getTextForLength( final int length ) {
+      final FontMetrics fm = getFontMetrics( getFont() );
+      int maxTextLength = 0;
+      String maxText = "";
+      for ( String text : _labelTexts ) {
+         final int textLength = SwingUtilities.computeStringWidth( fm, text );
+         if ( textLength > maxTextLength && textLength <= length ) {
+            maxTextLength = textLength;
+            maxText = text;
+         }
+      }
+      return maxText;
+   }
+
+   /**
+    * {@inheritDoc}
+    *
+    * @return the longest label text
+    */
+   public String getToolTipText() {
+      return getTextForLength( Integer.MAX_VALUE );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PartialEtchedBorder.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PartialEtchedBorder.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PartialEtchedBorder.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PartialEtchedBorder.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,107 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.border.EtchedBorder;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class PartialEtchedBorder extends EtchedBorder {
+
+   static private final int INSET = 2;
+
+   final private boolean _top;
+   final private boolean _bottom;
+   final private boolean _left;
+   final private boolean _right;
+
+
+   public PartialEtchedBorder( final boolean top, final boolean bottom, final boolean left, final boolean right ) {
+      _top = top;
+      _bottom = bottom;
+      _left = left;
+      _right = right;
+   }
+
+   /**
+    * Paint only desired borders
+    * {@inheritDoc}
+    */
+   @Override
+   public void paintBorder( final Component comp, final Graphics g,
+                            final int x, final int y, final int w, final int h ) {
+      g.translate( x, y );
+
+      final int width = (_left || _right) ? w - INSET : w;
+      final int height = (_top || _bottom) ? h - INSET : h;
+
+      g.setColor( etchType == LOWERED ? getShadowColor( comp ) : getHighlightColor( comp ) );
+      if ( _left ) {
+         g.drawLine( 0, 0, 0, height );
+      }
+      if ( _right ) {
+         g.drawLine( width, 0, width, height );
+      }
+      if ( _top ) {
+         g.drawLine( 0, 0, width, 0 );
+      }
+      if ( _bottom ) {
+         g.drawLine( 0, height, width, height );
+      }
+      g.setColor( etchType == LOWERED ? getHighlightColor( comp ) : getShadowColor( comp ) );
+      if ( _left ) {
+         g.drawLine( 1, 1, 1, height - 1 );
+      }
+      if ( _right ) {
+         g.drawLine( width - 1, 0, width - 1, height - 1 );
+      }
+      if ( _top ) {
+         g.drawLine( 1, 1, width - 1, 1 );
+      }
+      if ( _bottom ) {
+         g.drawLine( 0, height - 1, width - 1, height - 1 );
+      }
+
+      g.translate( -x, -y );
+   }
+
+
+   private int getTop() {
+      return _top ? INSET : 0;
+   }
+
+   private int getBottom() {
+      return _bottom ? INSET : 0;
+   }
+
+   private int getLeft() {
+      return _left ? INSET : 0;
+   }
+
+   private int getRight() {
+      return _right ? INSET : 0;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Insets getBorderInsets( final Component comp ) {
+      return new Insets( getTop(), getLeft(), getBottom(), getRight() );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Insets getBorderInsets( final Component comp, final Insets insets ) {
+      insets.left = getLeft();
+      insets.right = getRight();
+      insets.top = getTop();
+      insets.bottom = getBottom();
+      return insets;
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PositioningSplitPane.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PositioningSplitPane.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PositioningSplitPane.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/PositioningSplitPane.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,94 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * JSplitPane does not start with the given position - a bug in Java.
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 5/30/12
+ */
+public class PositioningSplitPane extends JSplitPane {
+
+   private boolean _isLocationSet;
+   private int _pixelLocation = -1;
+   private double _proportionalLocation = -1d;
+
+   /**
+    * {@inheritDoc}
+    */
+   public PositioningSplitPane() {
+      super();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public PositioningSplitPane( final int orientation ) {
+      super( orientation );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public PositioningSplitPane( final int orientation, final boolean isContinuousLayout ) {
+      super( orientation, isContinuousLayout );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public PositioningSplitPane( final int orientation,
+                                final Component leftComponent,
+                                final Component rightComponent ) {
+      super( orientation, leftComponent, rightComponent );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public PositioningSplitPane( final int orientation,
+                                final boolean isContinuousLayout,
+                                final Component leftComponent,
+                                final Component rightComponent ) {
+      super( orientation, isContinuousLayout, leftComponent, rightComponent );
+   }
+
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void setDividerLocation( final double proportionalLocation ) {
+      super.setDividerLocation( proportionalLocation );
+      _proportionalLocation = proportionalLocation;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void setDividerLocation( final int pixelLocation ) {
+      super.setDividerLocation( pixelLocation );
+      _pixelLocation = pixelLocation;
+   }
+
+   /**
+    * If this is the first paint, set the divider location first.  Fixes a bug (imo) in Java.
+    * {@inheritDoc}
+    */
+   @Override
+   public void paint( final Graphics g ) {
+      if ( !_isLocationSet || getDividerLocation() < 0 ) {
+         if ( _pixelLocation > 0 ) {
+            super.setDividerLocation( _pixelLocation );
+         } else if ( _proportionalLocation > 0 ) {
+            super.setDividerLocation( _proportionalLocation );
+         }
+         _isLocationSet = true;
+      }
+      super.paint( g );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/RedirectTextArea.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/RedirectTextArea.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/RedirectTextArea.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/RedirectTextArea.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,139 @@
+package org.chboston.cnlp.gui;
+
+import net.jcip.annotations.NotThreadSafe;
+
+import javax.swing.*;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * A Text Component that will output text that would normally go to the console for standard output or standard error
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 5/30/12
+ */
+public class RedirectTextArea extends JScrollPane {
+
+   private final boolean _isStandardOutRedirect;
+   private final Document _textAreaDoc = new PlainDocument();
+   private PrintStream _oldStandardStream;
+
+   /**
+    * new text gui that will display standard output
+    */
+   public RedirectTextArea() {
+      this( true );
+   }
+
+   /**
+    * @param isStandardOutRedirect true if this gui is to display standard output, false for standard error
+    */
+   public RedirectTextArea( final boolean isStandardOutRedirect ) {
+      _isStandardOutRedirect = isStandardOutRedirect;
+      final JTextArea textArea = new JTextArea( _textAreaDoc );
+      textArea.setEditable( false );
+      super.setViewportView( textArea );
+   }
+
+   /**
+    * Starts redirecting standard output (or error) from the console (or other) to this gui
+    */
+   public void startRedirect() {
+      final PrintStream newStandardStream = new PrintStream( new UiOutputStream() );
+      clearText();
+      if ( _isStandardOutRedirect ) {
+         _oldStandardStream = System.out;
+         System.setOut( newStandardStream );
+      } else {
+         _oldStandardStream = System.err;
+         System.setErr( newStandardStream );
+      }
+   }
+
+   /**
+    * Stops redirecting the standard output (or error) and returns it to its former consumer
+    */
+   public void endRedirect() {
+      if ( _isStandardOutRedirect ) {
+         System.setOut( _oldStandardStream );
+      } else {
+         System.setErr( _oldStandardStream );
+      }
+   }
+
+   /**
+    * @return all the text in this gui
+    */
+   public String getText() {
+      try {
+         return _textAreaDoc.getText( 0, _textAreaDoc.getLength() );
+      } catch ( BadLocationException blE ) {
+         return "";
+      }
+   }
+
+   /**
+    * clear the text in this gui
+    */
+   public void clearText() {
+      SwingUtilities.invokeLater( new Runnable() {
+         @Override
+         public void run() {
+            try {
+               _textAreaDoc.remove( 0, _textAreaDoc.getLength() );
+            } catch ( BadLocationException blE ) {
+               //
+            }
+         }
+      } );
+   }
+
+   /**
+    * @param text to append to the text displayed in this gui
+    */
+   public void appendText( final String text ) {
+      SwingUtilities.invokeLater( new Runnable() {
+         @Override
+         public void run() {
+            try {
+               _textAreaDoc.insertString( _textAreaDoc.getLength(), text, null );
+            } catch ( BadLocationException blE ) {
+               //
+            }
+         }
+      } );
+   }
+
+   /**
+    * stream that will accept characters until a newline, then append the line to this gui
+    */
+   @NotThreadSafe
+   private class UiOutputStream extends OutputStream {
+      private final StringBuilder __sb = new StringBuilder();
+
+      public void write( final int b ) throws IOException {
+         __sb.append( (char)b );
+         if ( (char)b == '\n' ) {
+            appendText( __sb.toString() );
+            __sb.setLength( 0 );
+         }
+      }
+
+      public void flush() throws IOException {
+         appendText( __sb.toString() );
+         __sb.setLength( 0 );
+         super.flush();
+      }
+
+      public void close() throws IOException {
+         appendText( __sb.toString() );
+         __sb.setLength( 0 );
+         super.close();
+      }
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothTipList.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothTipList.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothTipList.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothTipList.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,28 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.*;
+
+/**
+ * JList that displays ToolTips with SmoothToolTip
+ *
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 10/3/2014
+ */
+final public class SmoothTipList<E> extends JList<E> {
+
+
+   public SmoothTipList( final ListModel<E> dataModel ) {
+      super( dataModel );
+   }
+
+
+   @Override
+   public JToolTip createToolTip() {
+      final SmoothToolTip tip = new SmoothToolTip();
+      tip.setComponent( this );
+      return tip;
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTip.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTip.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTip.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTip.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,80 @@
+package org.chboston.cnlp.gui;
+
+
+import javax.swing.*;
+import javax.swing.plaf.ToolTipUI;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/14/12
+ */
+public class SmoothToolTip extends JToolTip {
+
+   private static final String UI_CLASS_ID = "SmoothToolTipUI";
+
+   /**
+    * This is set to true for the life of the <code>setUI</code> call.
+    */
+   private boolean _settingUI;
+
+   public SmoothToolTip() {
+      setBorder( null );
+      setOpaque( false );
+   }
+
+
+   /**
+    * @return the <code>TimelineUI</code> object that renders this component
+    */
+   @Override
+   final public ToolTipUI getUI() {
+      return (ToolTipUI)ui;
+   }
+
+   final public void setUI( final ToolTipUI ui ) {
+      if ( _settingUI ) {
+         return;
+      }
+      if ( this.ui != ui ) {
+         _settingUI = true;
+         try {
+            super.setUI( ui );
+         } finally {
+            _settingUI = false;
+         }
+      }
+   }
+
+   /**
+    * Notification from the <code>UIManager</code> that the L&F has changed.
+    * Replaces the current UI object with the latest version from the
+    * <code>UIManager</code>.
+    *
+    * @see JComponent#updateUI
+    */
+   @Override
+   final public void updateUI() {
+      setUI( new SmoothToolTipUI() );
+   }
+
+
+   /**
+    * Returns the name of the L&F class that renders this component.
+    *
+    * @return the string "TimelineUI"
+    * @see JComponent#getUIClassID
+    * @see UIDefaults#getUI
+    */
+   @Override
+   public String getUIClassID() {
+      return UI_CLASS_ID;
+   }
+
+   @Override
+   public void paintBorder( final Graphics g ) {
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTipUI.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTipUI.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTipUI.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/SmoothToolTipUI.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,58 @@
+package org.chboston.cnlp.gui;
+
+import sun.swing.SwingUtilities2;
+
+import javax.swing.*;
+import javax.swing.plaf.basic.BasicToolTipUI;
+import java.awt.*;
+import java.awt.geom.RoundRectangle2D;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/14/12
+ */
+public class SmoothToolTipUI extends BasicToolTipUI {
+
+   @Override
+   public void update( Graphics g, JComponent c ) {
+      paint( g, c );
+   }
+
+   @Override
+   public void paint( final Graphics g, final JComponent comp ) {
+      final Font font = comp.getFont();
+      final FontMetrics metrics = SwingUtilities2.getFontMetrics( comp, g, font );
+      final Dimension size = comp.getSize();
+
+      final Color background = Color.YELLOW;//c.getBackground();
+      Color transground = new Color( background.getRed(), background.getGreen(), background.getBlue(), 127 );
+      g.setColor( transground );
+      g.fillRoundRect( 0, 0, size.width, size.height, 6, 3 );
+
+      final Color foreground = comp.getForeground();
+      transground = new Color( foreground.getRed(), foreground.getGreen(), foreground.getBlue(), 195 );
+      g.setColor( transground );
+
+      // fix for bug 4153892
+      String tipText = ((JToolTip)comp).getTipText();
+      if ( tipText == null ) {
+         tipText = "";
+      }
+
+      final Insets insets = comp.getInsets();
+      final RoundRectangle2D.Float paintTextR = new RoundRectangle2D.Float(
+            insets.left + 3,
+            insets.top,
+            size.width - (insets.left + insets.right) - 6,
+            size.height - (insets.top + insets.bottom), 10, 10 );
+      g.setFont( font );
+      SwingUtilities2.drawString( comp, g, tipText, (int)paintTextR.getX(),
+            (int)paintTextR.getY() + metrics.getAscent() );
+      transground = new Color( foreground.getRed(), foreground.getGreen(), foreground.getBlue(), 63 );
+      g.setColor( transground );
+      g.drawRoundRect( 0, 0, size.width - 1, size.height - 1, 10, 10 );
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/StandardRedirectUI.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/StandardRedirectUI.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/StandardRedirectUI.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/StandardRedirectUI.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,167 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 6/21/12
+ */
+public class StandardRedirectUI {
+
+   private final JFrame _frame;
+   private final InputConstructor _inputConstructor;
+   private final RunnableUtil _runnableUtil;
+   private RedirectTextArea _outputArea;
+   private RedirectTextArea _errorArea;
+
+   public StandardRedirectUI( final InputConstructor inputConstructor, final RunnableUtil runnableUtil ) {
+      _inputConstructor = inputConstructor != null ? inputConstructor : NULL_INPUT_CONSTRUCTOR;
+      _runnableUtil = runnableUtil;
+      _frame = new JFrame( runnableUtil.getClass().getName() );
+      _frame.setSize( 800, 600 );
+      _frame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
+      _frame.getContentPane().add( createMainPanel() );
+      _frame.setVisible( true );
+   }
+
+   private JComponent createGoButton() {
+      return new JButton( new AbstractAction( "GO!" ) {
+         final UtilRunner __utilRunner = new UtilRunner();
+
+         @Override
+         public void actionPerformed( final ActionEvent e ) {
+            new Thread( __utilRunner ).start();
+         }
+      } );
+   }
+
+   private JComponent createOutputsView() {
+      _outputArea = new RedirectTextArea( true );
+      _errorArea = new RedirectTextArea( false );
+      final JSplitPane splitPane = new PositioningSplitPane( JSplitPane.VERTICAL_SPLIT, _outputArea, _errorArea );
+      splitPane.setDividerLocation( 0.9 );
+      return splitPane;
+   }
+
+   private JComponent createMainPanel() {
+      final JPanel mainPanel = new JPanel( new BorderLayout( 10, 10 ) );
+      mainPanel.setBorder( new EmptyBorder( 5, 5, 5, 5 ) );
+      final JComponent topInputComponent = _inputConstructor.getTopInputComponent();
+      if ( topInputComponent != null ) {
+         final JPanel northPanel = new JPanel( new BorderLayout( 5, 5 ) );
+         northPanel.add( topInputComponent, BorderLayout.CENTER );
+         northPanel.add( createGoButton(), BorderLayout.SOUTH );
+         mainPanel.add( northPanel, BorderLayout.NORTH );
+      } else {
+         mainPanel.add( createGoButton(), BorderLayout.NORTH );
+      }
+      final JComponent leftInputComponent = _inputConstructor.getLeftInputComponent();
+      if ( leftInputComponent != null ) {
+         final JPanel westPanel = new JPanel( new BorderLayout( 5, 5 ) );
+         westPanel.add( topInputComponent, BorderLayout.WEST );
+         westPanel.add( createOutputsView(), BorderLayout.CENTER );
+         mainPanel.add( westPanel, BorderLayout.CENTER );
+      } else {
+         mainPanel.add( createOutputsView(), BorderLayout.CENTER );
+      }
+      return mainPanel;
+   }
+
+   ////////////////////////////////////////////////////////////////////////////////////////
+   //
+   //                               Inner Classes
+   //
+   ////////////////////////////////////////////////////////////////////////////////////////
+
+
+   private class UtilRunner implements Runnable {
+      private volatile boolean __isRunning = false;
+
+      public void run() {
+         if ( __isRunning || !_inputConstructor.canRun() ) {
+            return;
+         }
+         __isRunning = true;
+         // Just in case the RunnableUtil takes a while to run
+         _frame.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
+         // reassign standard output and error to the ui
+         _outputArea.startRedirect();
+         _errorArea.startRedirect();
+         // run the main utility
+         _runnableUtil.run( _inputConstructor.getRunnableUtilArgs() );
+         // set standard output and error back, switch back to default cursor
+         _outputArea.endRedirect();
+         _errorArea.endRedirect();
+         _frame.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
+         __isRunning = false;
+      }
+   }
+
+
+   ////////////////////////////////////////////////////////////////////////////////////////
+   //
+   //                               static interfaces to use
+   //
+   ////////////////////////////////////////////////////////////////////////////////////////
+
+
+   /**
+    * Function class
+    */
+   static public interface InputConstructor {
+      JComponent getTopInputComponent();
+
+      JComponent getLeftInputComponent();
+
+      boolean canRun();
+
+      String[] getRunnableUtilArgs();
+   }
+
+   /**
+    * Function class.  Can easily swap main() with an instance and .run()
+    */
+   static public interface RunnableUtil {
+      /**
+       * This can be used to quickly replace the standard "main(args[])" for GUI startup
+       *
+       * @param args should be project path and document path
+       */
+      void run( final String... args );
+   }
+
+
+   ////////////////////////////////////////////////////////////////////////////////////////
+   //
+   //                               Null Input Constructor
+   //
+   ////////////////////////////////////////////////////////////////////////////////////////
+
+
+   static public final InputConstructor NULL_INPUT_CONSTRUCTOR = new InputConstructor() {
+      @Override
+      public JComponent getTopInputComponent() {
+         return null;
+      }
+
+      @Override
+      public JComponent getLeftInputComponent() {
+         return null;
+      }
+
+      @Override
+      public boolean canRun() {
+         return true;
+      }
+
+      @Override
+      public String[] getRunnableUtilArgs() {
+         return new String[ 0 ];
+      }
+   };
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalCheckBoxUI.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalCheckBoxUI.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalCheckBoxUI.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalCheckBoxUI.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,150 @@
+package org.chboston.cnlp.gui;
+
+
+import javax.swing.*;
+import javax.swing.plaf.basic.BasicCheckBoxUI;
+import javax.swing.plaf.basic.BasicHTML;
+import javax.swing.text.View;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 11/21/12
+ */
+public class VerticalCheckBoxUI extends BasicCheckBoxUI {
+
+   final private boolean _clockwise;
+
+   /**
+    * @param clockwise true if the label should be rotated clockwise, false if counterclockwise
+    */
+   public VerticalCheckBoxUI( final boolean clockwise ) {
+      super();
+      _clockwise = clockwise;
+   }
+
+   /**
+    * @param component the label currently of interest to this UI
+    * @return the normal preferred size of the label, rotated 90 degrees
+    */
+   @Override
+   public Dimension getPreferredSize( final JComponent component ) {
+      final Dimension size = super.getPreferredSize( component );
+      return new Dimension( size.height, size.width );
+   }
+
+   /**
+    * @param g         graphics with which to paint the label
+    * @param component the label to be painted
+    */
+   @Override
+   public void paint( final Graphics g, final JComponent component ) {
+      final JCheckBox checkBox = (JCheckBox)component;
+      String text = checkBox.getText();
+      Icon icon = (checkBox.isEnabled()) ? checkBox.getIcon() : checkBox.getDisabledIcon();
+      if ( (icon == null) && (text == null) ) {
+         return;
+      }
+      // insets will be handled by the label with respect to alignments.
+      // The rotation will cause a label with horizontalAlignment = center to have text centered vertically ...
+      final Insets insets = component.getInsets();// _paintViewInsets );
+      final Rectangle viewBounds = new Rectangle();
+      viewBounds.x = insets.left;
+      viewBounds.y = insets.top;
+      // Use inverted height and width
+      viewBounds.height = component.getWidth() - (insets.left + insets.right);
+      viewBounds.width = component.getHeight() - (insets.top + insets.bottom);
+
+      final Rectangle iconBounds = new Rectangle();
+      final Rectangle textBounds = new Rectangle();
+      iconBounds.x = iconBounds.y = iconBounds.width = iconBounds.height = 0;
+      textBounds.x = textBounds.y = textBounds.width = textBounds.height = 0;
+
+      // Get the button
+      if ( icon != null ) {
+         ButtonModel model = checkBox.getModel();
+
+         if ( !model.isEnabled() ) {
+            if ( model.isSelected() ) {
+               icon = checkBox.getDisabledSelectedIcon();
+            } else {
+               icon = checkBox.getDisabledIcon();
+            }
+         } else if ( model.isPressed() && model.isArmed() ) {
+            icon = checkBox.getPressedIcon();
+            if ( icon == null ) {
+               // Use selected icon
+               icon = checkBox.getSelectedIcon();
+            }
+         } else if ( model.isSelected() ) {
+            if ( checkBox.isRolloverEnabled() && model.isRollover() ) {
+               icon = checkBox.getRolloverSelectedIcon();
+               if ( icon == null ) {
+                  icon = checkBox.getSelectedIcon();
+               }
+            } else {
+               icon = checkBox.getSelectedIcon();
+            }
+         } else if ( checkBox.isRolloverEnabled() && model.isRollover() ) {
+            icon = checkBox.getRolloverIcon();
+         }
+         if ( icon == null ) {
+            icon = checkBox.getIcon();
+         }
+      } else {
+         icon = getDefaultIcon();
+      }
+
+      if ( component instanceof MultiSizeCheckBox ) {
+         final int textWidth = icon == null ? viewBounds.width
+                                            : viewBounds.width - icon.getIconWidth() - checkBox.getIconTextGap();
+         text = ((MultiSizeCheckBox)component).getTextForLength( textWidth );
+      }
+      final FontMetrics fm = g.getFontMetrics();
+      final String clippedText = SwingUtilities.layoutCompoundLabel( checkBox, fm, text, icon,
+            checkBox.getVerticalAlignment(),
+            checkBox.getHorizontalAlignment(),
+            checkBox.getVerticalTextPosition(),
+            checkBox.getHorizontalTextPosition(),
+            viewBounds,
+            iconBounds,
+            textBounds,
+            0 );
+      // Use g2d to rotate
+      final Graphics2D g2d = (Graphics2D)g;
+      final AffineTransform oldTransform = g2d.getTransform();
+      if ( _clockwise ) {
+         g2d.rotate( Math.PI / 2 );
+         g2d.translate( 0, -component.getWidth() );
+      } else {
+         g2d.rotate( -Math.PI / 2 );
+         g2d.translate( -component.getHeight(), 0 );
+      }
+      // fill background
+      if ( checkBox.isOpaque() ) {
+         g.setColor( checkBox.getBackground() );
+         g.fillRect( 0, 0, viewBounds.width, viewBounds.height );
+      }
+      // Paint the button
+      if ( icon != null ) {
+         icon.paintIcon( checkBox, g, iconBounds.x, iconBounds.y );
+      }
+      // Draw the Text
+      textBounds.x += checkBox.getIconTextGap();
+      View view = (View)checkBox.getClientProperty( BasicHTML.propertyKey );
+      if ( view != null ) {
+         view.paint( g, textBounds );
+      } else {
+         paintText( g, checkBox, textBounds, text );
+      }
+      if ( checkBox.hasFocus() && checkBox.isFocusPainted() && textBounds.width > 0 && textBounds.height > 0 ) {
+         paintFocus( g, textBounds, viewBounds.getSize() );
+      }
+      g2d.setTransform( oldTransform );
+      g2d.dispose();
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalLabelUI.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalLabelUI.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalLabelUI.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalLabelUI.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,105 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.*;
+import javax.swing.plaf.basic.BasicLabelUI;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+
+/**
+ * Author: Unknown, multiple copies freely available on the web
+ */
+public class VerticalLabelUI extends BasicLabelUI {
+
+   static {
+      labelUI = new VerticalLabelUI( false );
+   }
+
+   private static Rectangle _paintIconBounds = new Rectangle();
+   private static Rectangle _paintTextBounds = new Rectangle();
+   private static Rectangle _paintViewBounds = new Rectangle();
+   private static Insets _paintViewInsets = new Insets( 0, 0, 0, 0 );
+
+   final private boolean _clockwise;
+
+   /**
+    * @param clockwise true if the label should be rotated clockwise, false if counterclockwise
+    */
+   public VerticalLabelUI( final boolean clockwise ) {
+      super();
+      _clockwise = clockwise;
+   }
+
+   /**
+    * @param component the label currently of interest to this UI
+    * @return the normal preferred size of the label, rotated 90 degrees
+    */
+   @Override
+   public Dimension getPreferredSize( final JComponent component ) {
+      final Dimension size = super.getPreferredSize( component );
+      return new Dimension( size.height, size.width );
+   }
+
+   /**
+    * @param g         graphics with which to paint the label
+    * @param component the label to be painted
+    */
+   @Override
+   public void paint( final Graphics g, final JComponent component ) {
+      final JLabel label = (JLabel)component;
+      String text = label.getText();
+      final Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
+      if ( (icon == null) && (text == null) ) {
+         return;
+      }
+      final FontMetrics fm = g.getFontMetrics();
+      // insets will be handled by the label with respect to alignments.
+      // The rotation will cause a label with horizontalAlignment = center to have text centered vertically ...
+      _paintViewInsets = component.getInsets( _paintViewInsets );
+      _paintViewBounds.x = _paintViewInsets.left;
+      _paintViewBounds.y = _paintViewInsets.top;
+      // Use inverted height and width
+      _paintViewBounds.height = component.getWidth() - (_paintViewInsets.left + _paintViewInsets.right);
+      _paintViewBounds.width = component.getHeight() - (_paintViewInsets.top + _paintViewInsets.bottom);
+
+      _paintIconBounds.x = _paintIconBounds.y = _paintIconBounds.width = _paintIconBounds.height = 0;
+      _paintTextBounds.x = _paintTextBounds.y = _paintTextBounds.width = _paintTextBounds.height = 0;
+
+      if ( component instanceof MultiSizeLabel ) {
+         text = ((MultiSizeLabel)component).getTextForLength( _paintViewBounds.width );
+      }
+
+      final String clippedText = layoutCL( label,
+            fm,
+            text,
+            icon,
+            _paintViewBounds,
+            _paintIconBounds,
+            _paintTextBounds );
+
+      final Graphics2D g2 = (Graphics2D)g;
+      final AffineTransform oldTransform = g2.getTransform();
+      if ( _clockwise ) {
+         g2.rotate( Math.PI / 2 );
+         g2.translate( 0, -component.getWidth() );
+      } else {
+         g2.rotate( -Math.PI / 2 );
+         g2.translate( -component.getHeight(), 0 );
+      }
+      if ( icon != null ) {
+         icon.paintIcon( component, g, _paintIconBounds.x, _paintIconBounds.y );
+      }
+      if ( text != null ) {
+         final int textX = _paintTextBounds.x;
+         final int textY = _paintTextBounds.y + fm.getAscent();
+
+         if ( label.isEnabled() ) {
+            paintEnabledText( label, g, clippedText, textX, textY );
+         } else {
+            paintDisabledText( label, g, clippedText, textX, textY );
+         }
+      }
+      g2.setTransform( oldTransform );
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicLayout.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicLayout.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicLayout.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicLayout.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,117 @@
+package org.chboston.cnlp.gui;
+
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 11/19/12
+ */
+public class VerticalMimicLayout implements LayoutManager {
+
+   final Container _containerToMimic;
+
+   final private Map<Component, Component> _componentMap;
+
+
+   public VerticalMimicLayout( final Container containerToMimic ) {
+      _containerToMimic = containerToMimic;
+      _componentMap = new HashMap<>();
+   }
+
+   public void addComponentFor( final Component componentToMimic, final Component component ) {
+      _componentMap.put( componentToMimic, component );
+   }
+
+
+   public void removeComponentFor( final Component componentToMimic ) {
+      _componentMap.remove( componentToMimic );
+   }
+
+   public Component getMimicker( final Component mimickedComp ) {
+      return _componentMap.get( mimickedComp );
+   }
+
+   private int getPreferredChildWidth( final Container container ) {
+      int width = 0;
+      for ( Component component : container.getComponents() ) {
+         width = Math.max( width, component.getPreferredSize().width );
+      }
+      return width;
+   }
+
+   private int getMaxChildWidth( final Container container ) {
+      int width = 0;
+      for ( Component component : container.getComponents() ) {
+         width = Math.max( width, component.getMaximumSize().width );
+      }
+      return width;
+   }
+
+   private int getMinChildWidth( final Container container ) {
+      int width = 0;
+      for ( Component component : container.getComponents() ) {
+         width = Math.min( width, component.getMinimumSize().width );
+      }
+      return width;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void addLayoutComponent( final String name, final Component comp ) {
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void removeLayoutComponent( final Component comp ) {
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Dimension preferredLayoutSize( final Container parent ) {
+      final int prefChildWidth = getPreferredChildWidth( parent );
+      final Dimension mimicSize = _containerToMimic.getPreferredSize();
+      return new Dimension( prefChildWidth, mimicSize.height );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Dimension minimumLayoutSize( final Container parent ) {
+      final int minChildWidth = getMinChildWidth( parent );
+      final Dimension mimicSize = _containerToMimic.getMinimumSize();
+      return new Dimension( minChildWidth, mimicSize.height );
+   }
+
+   /**
+    * Lays out the specified container.
+    *
+    * @param parent the container to be laid out
+    */
+   public void layoutContainer( final Container parent ) {
+      _containerToMimic.validate();
+      final int width = parent.getWidth() - parent.getInsets().left - parent.getInsets().right;
+      final Component[] mimicComps = _containerToMimic.getComponents();
+      for ( Component comp : mimicComps ) {
+         final Component mimicker = _componentMap.get( comp );
+         if ( mimicker != null ) {
+            final Rectangle bounds = mimicker.getBounds();
+            bounds.y = comp.getY();
+            bounds.width = width;
+            bounds.height = comp.getHeight();
+            mimicker.setBounds( bounds );
+         }
+      }
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/VerticalMimicPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,118 @@
+package org.chboston.cnlp.gui;
+
+import org.apache.log4j.Logger;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/24/12
+ */
+public class VerticalMimicPanel extends JPanel {
+
+
+   static private final Logger LOGGER = Logger.getLogger( "VerticalMimicPanel" );
+
+   //   static private void addListenerToParents( final Component child, final ComponentListener listener ) {
+   //      final Component parent = child.getParent();
+   //      if ( parent == null ) {
+   //         return;
+   //      }
+   //      if ( parent instanceof JViewport || parent instanceof JScrollPane
+   //            || ((JComponent) parent).getLayout() instanceof BoxLayout ) {
+   //         parent.addComponentListener( listener );
+   //      }
+   //      addListenerToParents( parent, listener );
+   //   }
+
+
+   final VerticalMimicLayout _layout;
+
+   public VerticalMimicPanel( final Container containerToMimic ) {
+      _layout = new VerticalMimicLayout( containerToMimic );
+      setLayout( _layout );
+      final ComponentListener mimicComponentListener = new MimicComponentAdapter();
+      containerToMimic.addComponentListener( mimicComponentListener );
+      //      addListenerToParents( containerToMimic, mimicComponentListener );
+   }
+
+
+   public void addComponentFor( final Component componentToMimic, final Component component ) {
+      _layout.addComponentFor( componentToMimic, component );
+      super.add( component );
+   }
+
+   public void removeComponentFor( final Component componentToMimic, final Component component ) {
+      _layout.removeComponentFor( componentToMimic );
+      super.remove( component );
+   }
+
+   public void removeComponentFor( final Component componentToMimic ) {
+      final Component mimicker = _layout.getMimicker( componentToMimic );
+      if ( mimicker != null ) {
+         removeComponentFor( componentToMimic, mimicker );
+      }
+   }
+
+   /**
+    * Not Implemented
+    * {@inheritDoc}
+    */
+   @Override
+   public void add( final Component component, final Object constraints ) {
+      // don't throw, don't do anything
+      LOGGER.warn( "Do not use VerticalMimicPanel.add method, use .addComponentFor method" );
+   }
+
+   /**
+    * Not Implemented
+    * {@inheritDoc}
+    */
+   @Override
+   public void remove( final Component component ) {
+      // don't throw, don't do anything
+      LOGGER.warn( "Do not use VerticalMimicPanel.remove method, use .removeComponentFor method" );
+   }
+
+   /**
+    * Keeps the mimic panel in line with the mimicked panel when it changes
+    */
+   private class MimicComponentAdapter implements ComponentListener {
+      /**
+       * Invoked when the component's size changes.
+       */
+      @Override
+      public void componentResized( ComponentEvent e ) {
+         revalidate();
+      }
+
+      /**
+       * Invoked when the component's position changes.
+       */
+      @Override
+      public void componentMoved( ComponentEvent e ) {
+         revalidate();
+      }
+
+      /**
+       * Invoked when the component has been made visible.
+       */
+      @Override
+      public void componentShown( ComponentEvent e ) {
+         revalidate();
+      }
+
+      /**
+       * Invoked when the component has been made invisible.
+       */
+      @Override
+      public void componentHidden( ComponentEvent e ) {
+      }
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/ZoomSlider.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/ZoomSlider.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/ZoomSlider.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/ZoomSlider.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,336 @@
+package org.chboston.cnlp.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/10/12
+ */
+// TODO extract ZoomModel
+public class ZoomSlider extends JSlider {
+
+   static private final int ZOOM_CACHE_SIZE = 10;
+
+   static private class ZoomProperties {
+      final private double __value1;
+      final private double __value2;
+      final private boolean __isCenterZoom;
+
+      private ZoomProperties( final double value1, final double value2, final boolean isCenterZoom ) {
+         __value1 = value1;
+         __value2 = value2;
+         __isCenterZoom = isCenterZoom;
+      }
+   }
+
+   static private final Dictionary<Integer, Component> LABEL_TABLE;
+
+   static {
+      LABEL_TABLE = new Hashtable<>();
+      LABEL_TABLE.put( 10, new JLabel( "1x" ) );
+      LABEL_TABLE.put( 20, new JLabel( "2x" ) );
+      LABEL_TABLE.put( 30, new JLabel( "3x" ) );
+      LABEL_TABLE.put( 40, new JLabel( "4x" ) );
+      LABEL_TABLE.put( 50, new JLabel( "5x" ) );
+      LABEL_TABLE.put( 60, new JLabel( "6x" ) );
+      LABEL_TABLE.put( 70, new JLabel( "7x" ) );
+      LABEL_TABLE.put( 80, new JLabel( "8x" ) );
+      LABEL_TABLE.put( 90, new JLabel( "9x" ) );
+      LABEL_TABLE.put( 100, new JLabel( "10x" ) );
+      LABEL_TABLE.put( 110, new JLabel( "Inf" ) );
+   }
+
+   final private JComponent _viewComponent;
+   private boolean _isZooming;
+   private Deque<ZoomProperties> _zoomLevels = new ArrayDeque<>( ZOOM_CACHE_SIZE );
+
+
+   public ZoomSlider( final JComponent viewComponent ) {
+      super( JSlider.HORIZONTAL, 10, 110, 10 );
+      _viewComponent = viewComponent;
+      setMajorTickSpacing( 10 );
+      //      setPaintTicks( true );
+      setPaintLabels( true );
+      setSnapToTicks( true );
+      setFocusable( false );
+
+      setLabelTable( LABEL_TABLE );
+   }
+
+
+   final protected boolean isZooming() {
+      return _isZooming;
+   }
+
+   private void cacheZoomLevel( final double value1, final double value2, final boolean isCenterZoom ) {
+      if ( _zoomLevels.size() == ZOOM_CACHE_SIZE ) {
+         _zoomLevels.removeLast();
+      }
+      _zoomLevels.addFirst( new ZoomProperties( value1, value2, isCenterZoom ) );
+   }
+
+   final public boolean canUndoZoom() {
+      return _zoomLevels.size() > 1;
+   }
+
+   final public void undoZoomLevel() {
+      if ( !canUndoZoom() ) {
+         return;
+      }
+      // the top zoom level is actually the current zoom level, which we don't want.
+      _zoomLevels.removeFirst();
+      // the second zoom level is the previous zoom level, which we do want
+      final ZoomProperties zoomProperties = _zoomLevels.removeFirst();
+      if ( zoomProperties.__isCenterZoom ) {
+         zoomTo( zoomProperties.__value1, zoomProperties.__value2 );
+      } else {
+         zoomInTo( zoomProperties.__value1, zoomProperties.__value2 );
+      }
+   }
+
+   protected int getSetValueValue( final int value ) {
+      if ( value > 100 && !_isZooming ) {
+         return 100;
+      }
+      return value;
+   }
+
+   protected double getStateChangeZoom( final int value ) {
+      if ( value == 0 ) {
+         return 0;
+      }
+      double zoom;
+      if ( value < 0 ) {
+         zoom = 1d / Math.abs( (double)value - 1 );
+      } else {
+         zoom = (double)value;
+      }
+      return zoom / 10d;
+   }
+
+   protected double getZoomInCenterX( final double zoom, final double oldZoom,
+                                      final int visibleRectX, final double halfWidth ) {
+//      final double centerX = zoom / oldZoom * (visibleRect.x + halfWidth);
+      return zoom / oldZoom * (visibleRectX + halfWidth);
+   }
+
+   protected int getSliderValue( final double zoom ) {
+      final int value = (int)(zoom * 10);
+      if ( value > 100 ) {
+         return 110;
+      }
+      return value;
+   }
+
+
+   /**
+    * Step 1 and Step 4.5, step 4.5 after adjustment and value is 10-110 for display only
+    *
+    * @param value value for the slider display
+    */
+   final public void setValue( final int value ) {
+      super.setValue( getSetValueValue( value ) );
+   }
+
+   /**
+    * Step 2
+    */
+   final public void fireStateChanged() {
+      if ( _isZooming ) {
+         return;
+      }
+      if ( !getValueIsAdjusting() ) {
+         final int value = getValue();
+         if ( value != 0 ) {
+            final double zoom = getStateChangeZoom( value );
+            zoomIn( zoom );
+         }
+      }
+      super.fireStateChanged();
+   }
+
+
+   static private JViewport getViewport( final Container container ) {
+      if ( container == null ) {
+         return null;
+      }
+      if ( container instanceof JViewport ) {
+         return (JViewport)container;
+      }
+      return getViewport( container.getParent() );
+   }
+
+
+   protected Dimension getViewportSize() {
+      final JViewport viewport = getViewport( _viewComponent );
+      if ( viewport != null ) {
+         return viewport.getSize();
+      }
+      return _viewComponent.getPreferredSize();
+   }
+
+   /**
+    * @param desiredWidth the width of the view desired by the zoom
+    * @return the maximum view width allowable by a zoom, the lesser of the desired width and Integer.MAX-1
+    */
+   protected double getBestWidth( final double desiredWidth ) {
+      return Math.min( Integer.MAX_VALUE - 1, desiredWidth );
+   }
+
+
+   /**
+    * Step 3
+    *
+    * @param zoom the actual gui zoom level
+    */
+   public void zoomIn( final double zoom ) {
+      if ( zoom < 1d ) {
+         return;
+      }
+      final Rectangle visibleRect = _viewComponent.getVisibleRect();
+      final double halfWidth = (double)visibleRect.width / 2d;
+      final double oldZoom = (double)_viewComponent.getSize().width / (double)getViewportSize().width;
+//      final double centerX = zoom / oldZoom * (visibleRect.x + halfWidth);
+      final double centerX = getZoomInCenterX( zoom, oldZoom, visibleRect.x, halfWidth );
+      zoomTo( zoom, centerX );
+   }
+
+
+   /**
+    * Step 4, will call setValue with -fake- zoom level for display
+    *
+    * @param zoom        the actual gui zoom level, 1 -> n
+    * @param zoomCenterX -
+    * @return true if zoom was executed
+    */
+   private boolean zoomTo( final double zoom, final double zoomCenterX ) {
+      if ( zoom < 1d ) {
+         return false;
+      }
+      // set the slider value
+      _isZooming = true;
+      final int sliderValue = getSliderValue( zoom );
+      setValue( sliderValue );
+      _isZooming = false;
+
+//      new ZoomSmoothlyAction( zoom, zoomCenterX ).start();
+
+      // Calculate new zoom size
+      final Dimension viewportSize = getViewportSize();
+      final double viewportWidth = viewportSize.getWidth();
+      // because of Component sizing maximums, we have to limit the zoom
+      final double newViewWidth = getBestWidth( zoom * viewportWidth );
+      final Dimension newViewSize = new Dimension( (int)newViewWidth, _viewComponent.getHeight() );
+      // Do it later
+      SwingUtilities.invokeLater( new Runnable() {
+         @Override
+         public void run() {
+            _viewComponent.setPreferredSize( newViewSize );
+            _viewComponent.revalidate();
+            // Scroll to 0,0 first, otherwise there are some odd Java hierarchy issues
+            _viewComponent.scrollRectToVisible( new Rectangle() );
+            // Calculate new visible rectangle
+            final Dimension viewportSize2 = getViewportSize();
+            final double viewportWidth2 = viewportSize2.getWidth();
+            final double x2 = zoomCenterX - viewportWidth2 / 2d;
+            final Rectangle visibleRect2 = _viewComponent.getVisibleRect();
+            visibleRect2.x = (int)x2;
+            _viewComponent.scrollRectToVisible( visibleRect2 );
+         }
+      } );
+      cacheZoomLevel( zoom, zoomCenterX, true );
+      return true;
+   }
+
+   /**
+    * Called from outside when a scroll section is dragged or a specific timespan is selected
+    *
+    * @param start -
+    * @param end   -
+    * @return true if the main panel was zoomed and repainted
+    */
+   public boolean zoomInTo( final double start, final double end ) {
+      final double minStart = Math.max( 0d, start );
+      final double oldWidth = _viewComponent.getSize().getWidth();
+      final double maxEnd = Math.min( oldWidth, end );
+      final double newWidth = maxEnd - minStart;
+      if ( newWidth <= 0 ) {
+         return false;
+      }
+//      final double oldZoom = oldWidth / (double) getViewportSize().width;
+      final double newZoom = oldWidth / newWidth;
+//      final double centerX = newZoom / oldZoom * (start + end) / 2d;
+      final double centerX = getViewportSize().getWidth() / newWidth * (start + end) / 2d;
+      return zoomTo( newZoom, centerX );
+   }
+
+
+   private class ZoomSmoothlyAction implements ActionListener {
+      final Timer __timer;
+      private int __iterationCount;
+      final private double[] __zooms;
+      final private double[] __centerXs;
+      final private Image __image;
+
+      private ZoomSmoothlyAction( final double zoom, final double zoomCenterX ) {
+         __image = _viewComponent.createImage( _viewComponent.getWidth(), _viewComponent.getHeight() );
+         _viewComponent.prepareImage( __image, _viewComponent );
+         // Was thinking about using an image stamp of the view to paint
+         final Rectangle visibleRect = _viewComponent.getVisibleRect();
+         final double halfWidth = (double)visibleRect.width / 2d;
+         __zooms = new double[ 10 ];
+         __centerXs = new double[ 10 ];
+         __zooms[ 0 ] = _viewComponent.getSize().getWidth() / getViewportSize().getWidth();
+         __centerXs[ 0 ] = visibleRect.x + halfWidth;
+         for ( int i = 1; i < 9; i++ ) {
+            __zooms[ i ] = __zooms[ i - 1 ] + (zoom - __zooms[ i - 1 ]) / 5;
+            __centerXs[ i ] = __centerXs[ i - 1 ] + (zoomCenterX - __centerXs[ i - 1 ]) / 5;
+         }
+         __zooms[ 9 ] = zoom;
+         __centerXs[ 9 ] = zoomCenterX;
+         __timer = new Timer( 100, ZoomSmoothlyAction.this );
+         __timer.setRepeats( true );
+      }
+
+      private void start() {
+         __iterationCount = 1;
+         __timer.start();
+      }
+
+      public void actionPerformed( final ActionEvent event ) {
+         // Calculate new zoom size
+         final Dimension viewportSize = getViewportSize();
+         final double viewportWidth = viewportSize.getWidth();
+         // because of Component sizing maximums, we have to limit the zoom
+         final double newViewWidth = getBestWidth( __zooms[ __iterationCount ] * viewportWidth );
+         final Dimension newViewSize = new Dimension( (int)newViewWidth, _viewComponent.getHeight() );
+         final Dimension viewportSize2 = getViewportSize();
+         final double viewportWidth2 = viewportSize2.getWidth();
+         final double x2 = __centerXs[ __iterationCount ] - viewportWidth2 / 2d;
+         final Rectangle visibleRect2 = _viewComponent.getVisibleRect();
+         visibleRect2.x = (int)x2;
+         _viewComponent.scrollRectToVisible( visibleRect2 );
+         _viewComponent.setPreferredSize( newViewSize );
+         _viewComponent.revalidate();
+         __iterationCount++;
+         if ( __iterationCount >= __zooms.length ) {
+            __timer.stop();
+            // Scroll to 0,0 first, otherwise there are some odd Java hierarchy issues
+//            _viewComponent.scrollRectToVisible( new Rectangle() );
+            // Calculate new visible rectangle
+//            final double x2 = __centerXs[__iterationCount] - viewportWidth2 / 2d;
+            _viewComponent.scrollRectToVisible( visibleRect2 );
+         }
+      }
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorEvent.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorEvent.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorEvent.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorEvent.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,31 @@
+package org.chboston.cnlp.gui.error;
+
+import java.util.EventObject;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class ErrorEvent extends EventObject {
+
+   private final String _error;
+
+   public ErrorEvent( final Object source, final String error ) {
+      super( source );
+      _error = error;
+   }
+
+   public String getError() {
+      return _error;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String toString() {
+      return super.toString() + " " + getError();
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorLabel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorLabel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorLabel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorLabel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,40 @@
+package org.chboston.cnlp.gui.error;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+final public class ErrorLabel extends JLabel implements ErrorListener {
+
+   public ErrorLabel() {
+      setHorizontalAlignment( JLabel.CENTER );
+      setForeground( Color.RED );
+      setBorder( new CompoundBorder( new EmptyBorder( 5, 5, 5, 5 ), new EtchedBorder() ) );
+      setText( " " );
+   }
+
+   public void setError( final String text ) {
+      if ( text == null || text.isEmpty() ) {
+         // Set to a space so that the error label does not collapse
+         setText( " " );
+         return;
+      }
+      setText( text );
+   }
+
+   public void errorOccurred( final ErrorEvent event ) {
+      if ( event != null ) {
+         setError( event.getError() );
+      } else {
+         setError( "" );
+      }
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorListener.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorListener.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorListener.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorListener.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,18 @@
+package org.chboston.cnlp.gui.error;
+
+import java.util.EventListener;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public interface ErrorListener extends EventListener {
+
+   /**
+    * Invoked when an error occurs.
+    */
+   public void errorOccurred( final ErrorEvent event );
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorProducer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorProducer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorProducer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/gui/error/ErrorProducer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,42 @@
+package org.chboston.cnlp.gui.error;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 10/15/13
+ */
+public class ErrorProducer {
+
+   private Collection<ErrorListener> _errorListeners;
+
+   public void fireErrorOccurred( final String error ) {
+      if ( _errorListeners == null ) {
+         return;
+      }
+      final ErrorEvent event = new ErrorEvent( this, error );
+      for ( ErrorListener listener : _errorListeners ) {
+         listener.errorOccurred( event );
+      }
+   }
+
+   public void fireErrorCleared() {
+      if ( _errorListeners == null ) {
+         return;
+      }
+      final ErrorEvent event = new ErrorEvent( this, "" );
+      for ( ErrorListener listener : _errorListeners ) {
+         listener.errorOccurred( event );
+      }
+   }
+
+   public void addErrorListener( final ErrorListener listener ) {
+      if ( _errorListeners == null ) {
+         _errorListeners = new HashSet<>( 1 );
+      }
+      _errorListeners.add( listener );
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/DefaultTlinkTypeStore.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/DefaultTlinkTypeStore.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/DefaultTlinkTypeStore.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/DefaultTlinkTypeStore.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,200 @@
+package org.chboston.cnlp.iaa.evaluator.temporal;
+
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import static org.chboston.cnlp.iaa.evaluator.temporal.TlinkType.*;
+import static org.chboston.cnlp.iaa.evaluator.temporal.TlinkType.AFTER;
+import static org.chboston.cnlp.iaa.evaluator.temporal.TlinkType.BEFORE;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 4/17/13
+ */
+public class DefaultTlinkTypeStore implements TlinkTypeStore {
+
+   private Set<TlinkType> _tlinkTypes;
+
+   final private boolean _refineAdditions;
+
+   public DefaultTlinkTypeStore( final boolean refineAdditions ) {
+      _refineAdditions = refineAdditions;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean add( final TlinkType tlinkType ) {
+      if ( _tlinkTypes == null ) {
+         _tlinkTypes = EnumSet.noneOf( TlinkType.class );
+      }
+      if ( _refineAdditions ) {
+         return refine( tlinkType );
+      }
+      return _tlinkTypes.add( tlinkType );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int size() {
+      return _tlinkTypes == null ? 0 : _tlinkTypes.size();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isEmpty() {
+      return _tlinkTypes == null || _tlinkTypes.isEmpty();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean contains( final TlinkType tlinkType ) {
+      return _tlinkTypes != null && _tlinkTypes.contains( tlinkType );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public TlinkTypeStore createReciprocalStore() {
+      final TlinkTypeStore reciprocals = new DefaultTlinkTypeStore( _refineAdditions );
+      for ( TlinkType tlinkType : this ) {
+         reciprocals.add( tlinkType.getReciprocal() );
+      }
+      return reciprocals;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public TlinkTypeStore createAtoCstore( final TlinkTypeStore tlinkTypesBtoC ) {
+      if ( tlinkTypesBtoC == null || isEmpty() || tlinkTypesBtoC.isEmpty() ) {
+         return null;
+      }
+      final TlinkTypeStore tlinkTypeStoreAtoC = new DefaultTlinkTypeStore( _refineAdditions );
+      for ( TlinkType tlinkTypeAtoB : this ) {
+         for ( TlinkType tlinkTypeBtoC : tlinkTypesBtoC ) {
+            final TlinkType tertiary = tlinkTypeAtoB.getTimeRelationTypeAtoC( tlinkTypeBtoC );
+            if ( tertiary != null ) {
+               tlinkTypeStoreAtoC.add( tertiary );
+            }
+         }
+      }
+      return tlinkTypeStoreAtoC;
+   }
+
+
+   /**
+    * {@inheritDoc}
+    *
+    * @return "NONE," if empty, else a series of & separated time relation type names
+    */
+   @Override
+   public String toString() {
+      if ( isEmpty() ) {
+         return "NONE,";
+      }
+      final StringBuilder sb = new StringBuilder();
+      for ( TlinkType type : _tlinkTypes ) {
+         sb.append( type.name() ).append( " & " );
+      }
+      sb.setLength( sb.length() - 3 );
+      sb.append( "," );
+      return sb.toString();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Iterator<TlinkType> iterator() {
+      if ( isEmpty() ) {
+         return EMPTY_ITERATOR;
+      }
+      return _tlinkTypes.iterator();
+   }
+
+
+   static private final Iterator<TlinkType> EMPTY_ITERATOR = new Iterator<TlinkType>() {
+      @Override
+      public boolean hasNext() {
+         return false;
+      }
+
+      @Override
+      public TlinkType next() {
+         return null;
+      }
+
+      @Override
+      public void remove() {
+      }
+   };
+
+
+
+   private boolean containsOpposite( final TlinkType tlinkType ) {
+      switch ( tlinkType ) {
+         case BEFORE : return _tlinkTypes.contains( AFTER ) || _tlinkTypes.contains( BEGINS_ON );
+         case AFTER : return _tlinkTypes.contains( BEFORE ) || _tlinkTypes.contains( ENDS_ON );
+         case CONTAINS : return _tlinkTypes.contains( CONTAINED_BY );
+         case CONTAINED_BY: return _tlinkTypes.contains( CONTAINS );
+         case BEGINS_ON: return _tlinkTypes.contains( ENDS_ON ) || _tlinkTypes.contains( BEFORE );
+         case ENDS_ON: return _tlinkTypes.contains( BEGINS_ON ) || _tlinkTypes.contains( AFTER );
+      }
+      return false;
+   }
+
+   private boolean containsRefined() {
+      return _tlinkTypes.contains( CONTAINS ) || _tlinkTypes.contains( CONTAINED_BY )
+                || _tlinkTypes.contains( ENDS_ON ) || _tlinkTypes.contains( BEGINS_ON );
+   }
+
+
+   static private boolean isRefined( final TlinkType tlinkType ) {
+      return tlinkType == CONTAINS || tlinkType == CONTAINED_BY || tlinkType == ENDS_ON || tlinkType == BEGINS_ON;
+   }
+
+
+   private boolean refine( final TlinkType tlinkType ) {
+      if ( containsOpposite( tlinkType ) ) {
+         return false;
+      }
+      if ( isRefined( tlinkType ) ) {
+         _tlinkTypes.remove( OVERLAP );
+         _tlinkTypes.remove( BEFORE );
+         _tlinkTypes.remove( AFTER );
+//      } else if ( containsRefined() ) {
+//         return false;
+      }
+      return _tlinkTypes.add( tlinkType );
+   }
+
+   //   /**
+   //    * Agreement between two Time Relation sets.
+   //    * 0 indicates no agreement, 1 indicates perfect agreement, anything between indicates partial agreement.
+   //    * @param otherTimeRelationTypes time relation type set to contrast with this one
+   //    * @return fraction of agreement: # common time relations(*2) divided by # total time relations
+   //    */
+   //   public double contrast( final TlinkTypeSet otherTimeRelationTypes ) {
+   //      if ( otherTimeRelationTypes == null || isEmpty() || otherTimeRelationTypes.isEmpty() ) {
+   //         return 0d;
+   //      }
+   //      final int total = size() + otherTimeRelationTypes.size();
+   //      final Set<TlinkType> commonSet = new HashSet<TlinkType>( _tlinkTypes );
+   //      commonSet.retainAll( otherTimeRelationTypes._tlinkTypes );
+   //      return 2d * (double) commonSet.size() / (double) total;
+   //   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/TLinkTypeArray3.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/TLinkTypeArray3.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/TLinkTypeArray3.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/iaa/evaluator/temporal/TLinkTypeArray3.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,401 @@
+package org.chboston.cnlp.iaa.evaluator.temporal;
+
+//import org.apache.log4j.Logger;
+import org.chboston.cnlp.nlp.annotation.attribute.Attribute;
+import org.chboston.cnlp.nlp.annotation.attribute.DefaultAttribute;
+import org.chboston.cnlp.nlp.annotation.attribute.DefinedAttributeType;
+import org.chboston.cnlp.nlp.annotation.classtype.TemporalClassType;
+import org.chboston.cnlp.nlp.annotation.coreference.EntityIdStore;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.nlp.annotation.relation.DefaultRelation;
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+
+import java.util.*;
+import java.util.logging.Logger;
+
+import static org.chboston.cnlp.iaa.evaluator.temporal.TlinkType.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 6/18/13
+ */
+public class TLinkTypeArray3 {
+
+
+   static private final Logger LOGGER = Logger.getLogger( "TLinkTypeArray3" );
+
+   static private final int REFINEMENT_THRESHOLD = 500;
+
+   //  Array of relation types for each event/time to other event/time
+   private final TlinkTypeStore[][] _tlinkTypeStoreArray;
+
+   private final EntityIdStore _entityIdStore;
+   private final List<Integer> _uniqueEntityIdList;
+
+   private final Collection<Relation> _originalTlinks;
+
+   private final boolean _refineAdditions;
+
+
+   // Refine Additions for things like Timeline Vis.  Do NOT refine for IAA Closure
+   public TLinkTypeArray3( final List<Relation> tlinkList, final EntityIdStore entityIdStore ) {
+      // With large tlink lists we have an uncanny ability to create closure cobwebs from conflicting annotations
+      // TODO - remove refinement garbage after first checkin
+      _refineAdditions = tlinkList.size() > REFINEMENT_THRESHOLD;
+      _originalTlinks = new ArrayList<>( tlinkList );
+      final Set<Integer> tlinkEntityIds = new HashSet<>();
+      for ( Relation tlink : tlinkList ) {
+         // Add all entities, even for relations that have no valid relation type
+         tlinkEntityIds.add( entityIdStore.getUniqueEntityId( tlink.getFirstEntity() ) );
+         tlinkEntityIds.add( entityIdStore.getUniqueEntityId( tlink.getSecondEntity() ) );
+      }
+      _uniqueEntityIdList = new ArrayList<>( tlinkEntityIds );
+      Collections.sort( _uniqueEntityIdList ); // just so that -reported- closure counts are consistent
+      final int entityCount = _uniqueEntityIdList.size();
+      _tlinkTypeStoreArray = new TlinkTypeStore[ entityCount ][ entityCount ];
+      _entityIdStore = entityIdStore;
+      populateTlinkTypesArray( tlinkList );
+   }
+
+
+   public List<Relation> getUniqueClosedTlinks() {
+      final int length = getEntityIdCount();
+      if ( length == 0 ) {
+         return Collections.emptyList();
+      }
+      final Collection<Relation> uniqueClosedTlinks = new HashSet<>();
+      for ( int row = 0; row < length; row++ ) {
+         for ( int column = 0; column < length; column++ ) {
+            if ( row == column ) {
+               continue;
+            }
+            final TlinkTypeStore tlinkTypeStore = _tlinkTypeStoreArray[ row ][ column ];
+            if ( tlinkTypeStore == null || tlinkTypeStore.isEmpty() ) {
+               continue;
+            }
+            for ( TlinkType tlinkType : tlinkTypeStore ) {
+               final Collection<Relation> tlinks = createUniqueTlinks( row, column, tlinkType );
+               if ( tlinks != null && !tlinks.isEmpty() ) {
+                  uniqueClosedTlinks.addAll( tlinks );
+               }
+            }
+         }
+      }
+      return new ArrayList<>( uniqueClosedTlinks );
+   }
+
+
+   private Collection<Relation> createUniqueTlinks( final int row, final int column, final TlinkType tlinkType ) {
+      final Collection<Entity> entitiesA = getArrayUniqueEntities( row );
+      final Collection<Entity> entitiesB = getArrayUniqueEntities( column );
+      if ( entitiesA == null || entitiesA.isEmpty() || entitiesB == null || entitiesB.isEmpty() ) {
+         return null;
+      }
+      final Attribute tlinkAttribute = tlinkType.getAsAttribute();
+      final Attribute closureAnnotator = new DefaultAttribute( DefinedAttributeType.CREATOR, "Closure" );
+      final Collection<Relation> tlinks = new HashSet<>();
+      for ( Entity entityA : entitiesA ) {
+         final int idA = _entityIdStore.getUniqueEntityId( entityA );
+         for ( Entity entityB : entitiesB ) {
+            Attribute annotatorAttribute = closureAnnotator;
+            final int idB = _entityIdStore.getUniqueEntityId( entityB );
+            for ( Relation tlink : _originalTlinks ) {
+               final int tlinkFirstEntityId = _entityIdStore.getUniqueEntityId( tlink.getFirstEntity() );
+               final int tlinkSecondEntityId = _entityIdStore.getUniqueEntityId( tlink.getSecondEntity() );
+               if ( tlinkFirstEntityId == idA && tlinkSecondEntityId == idB ) {
+                  Attribute relationType = tlink.getAttribute( DefinedAttributeType.RELATION_TYPE );
+                  if ( relationType == null ) {
+                     relationType = tlink.getAttribute( DefinedAttributeType.TYPE );
+                  }
+                  if ( relationType != null && relationType.equals( tlinkAttribute ) ) {
+                     final Attribute specificAnnotator = tlink.getAttribute( DefinedAttributeType.CREATOR );
+                     if ( specificAnnotator != null ) {
+                        annotatorAttribute = specificAnnotator;
+                     }
+                     break;
+                  }
+               }
+            }
+            tlinks.add( new DefaultRelation( entityA, entityB, TemporalClassType.TLINK,
+                  tlinkAttribute, annotatorAttribute ) );
+         }
+      }
+      return tlinks;
+   }
+
+
+   private int getUniqueEntityArrayIndex( final Entity entity, final EntityIdStore entityIdCollection ) {
+      final int uniqueEntityId = _entityIdStore.getUniqueEntityId( entity );
+      return _uniqueEntityIdList.indexOf( uniqueEntityId );
+   }
+
+   private int getUniqueEntityId( final int index ) {
+      return _uniqueEntityIdList.get( index );
+   }
+
+   private Collection<Entity> getArrayUniqueEntities( final int index ) {
+      final int uniqueEntityId = getUniqueEntityId( index );
+      return _entityIdStore.getUniqueEntities( uniqueEntityId );
+   }
+
+
+   //   --------------------  Population of array  --------------------   //
+
+   private void populateTlinkTypesArray( final List<Relation> tlinkList ) {
+      // initialize array with existing known time relation types
+      int explicitCount = 0;
+      for ( Relation tlink : tlinkList ) {
+         if ( addTlinkType( tlink ) ) {
+            explicitCount++;
+         }
+      }
+      // add to array inferred time relation types
+      final int entityIdCount = getEntityIdCount();
+      // column
+      int inferredInIteration = -1; // seed
+      int iterations = 0;
+      int totalInferred = 0;
+      // The iterations < 3 check shouldn't be necessary, but some human annotations create
+      // graphs with all vertex types between all nodes.
+      // In other words, all entities become BF, AF, CN, CB, OV ... all other entities.
+      boolean badSet = false;
+      while ( !badSet && inferredInIteration != 0 ) {
+         inferredInIteration = 0;
+         for ( int index = 0; index < entityIdCount; index++ ) {
+            inferredInIteration += inferForIndex( index );
+            // stop for bad tlinks
+            // This check shouldn't be necessary, but some human annotations create
+            // graphs with all vertex types between all nodes.
+            // In other words, all entities become BF, AF, CN, CB, OV ... all other entities.
+            if ( inferredInIteration > 4*explicitCount ) {
+               LOGGER.severe( "BAD TLINK SET, got " + inferredInIteration + " inferrences from "
+                              + explicitCount + " tlinks at index " + index + " on iteration " + iterations );
+               badSet = true;
+               break;
+            }
+         }
+         totalInferred += inferredInIteration;
+         if ( !badSet && totalInferred > 6*explicitCount ) {
+            LOGGER.severe( "BAD TLINK SET, got " + totalInferred + " inferrences from "
+                           + explicitCount + " on iteration " + iterations );
+            badSet = true;
+         }
+         iterations++;
+      }
+      // What follows is just info
+      int tlinkCount = 0;
+      for ( int i = 0; i < entityIdCount; i++ ) {
+         for ( int j = 0; j < entityIdCount; j++ ) {
+            if ( _tlinkTypeStoreArray[ i ][ j ] != null ) {
+               tlinkCount += _tlinkTypeStoreArray[ i ][ j ].size();
+            }
+         }
+      }
+//      LOGGER.debug( "===================================================================================" );
+      LOGGER.info( "   Marked Entities: " + entityIdCount
+                   + ", Marked TLinks: " + tlinkList.size()
+                   + ", Unique TLinks: " + explicitCount
+                   + ", Inferred TLinks: " + totalInferred
+                   + ", Total TLinks: " + (tlinkCount / 2)
+                   + ", Iterations: " + iterations );
+//      LOGGER.debug( "===================================================================================" );
+   }
+
+
+   private int inferForIndex( final int indexA ) {
+      final int entityIdCount = getEntityIdCount();
+      int inferredCount = 0;
+      for ( int indexB = 0; indexB < entityIdCount; indexB++ ) {
+         if ( indexA == indexB ) {
+            continue;
+         }
+         final TlinkTypeStore tlinkTypesAtoB = getTlinkTypes( indexA, indexB );
+         if ( tlinkTypesAtoB == null ) {
+            // No time relation types for cell, can't infer something from nothing, move on
+            continue;
+         }
+         for ( int indexC = 0; indexC < entityIdCount; indexC++ ) {
+            if ( indexC == indexA || indexC == indexB ) {
+               continue;
+            }
+            final TlinkTypeStore tlinkTypesBtoC = getTlinkTypes( indexB, indexC );
+            if ( tlinkTypesBtoC == null ) {
+               // No time relation types for cell, can't infer something from nothing, move on
+               continue;
+            }
+            inferredCount += inferTlinkTypesForIndexAtoBtoC( indexA, indexB, indexC,
+                  tlinkTypesAtoB, tlinkTypesBtoC );
+            if ( !hasTlinkType( indexA, indexB, CONTAINS ) ) {
+               // search for contains between two nodes marked contains
+               inferredCount += inferContainsForEntityAtoBwithCandD( indexA, indexB, indexC );
+            }
+         }
+      }
+      return inferredCount;
+   }
+
+   private int inferTlinkTypesForIndexAtoBtoC( final int indexA, final int indexB, final int indexC,
+                                               final TlinkTypeStore tlinkTypesAtoB,
+                                               final TlinkTypeStore tlinkTypesBtoC ) {
+      if ( tlinkTypesAtoB == null ) {
+         return 0;
+      }
+      int addedCount = 0;
+      final TlinkTypeStore tlinkTypesAtoC = tlinkTypesAtoB.createAtoCstore( tlinkTypesBtoC );
+      addedCount += addTlinkTypes( indexA, indexC, tlinkTypesAtoC );
+      return addedCount;
+   }
+
+//   // search overlap between two nodes marked overlap   or  contains between two nodes marked contains
+//   private int inferTlinkTypesForEntityAtoBwithCandD( final int indexA, final int indexB, final int indexC,
+//                                                      final TlinkType tlinkType ) {
+//      final int entityCount = getEntityIdCount();
+//      int addedCount = 0;
+//      // if A ov C && A ov D, C < B || C eo B, B < D || D bo B, then A cn B   ( A ov C < B < D ov A )
+//      // if A cn C && A cn D, C < B || C eo B, B < D || D bo B, then A cn B   ( A cn C < B < D cb A )
+//      if ( hasTlinkType( indexA, indexC, tlinkType ) ) {
+//         // A ov C   or   A cn C
+//         for ( int indexD = 0; indexD < entityCount; indexD++ ) {
+//            if ( indexD == indexA || indexD == indexB || indexD == indexC ) {
+//               continue;
+//            }
+//            if ( hasTlinkType( indexA, indexD, tlinkType ) ) {
+//               // A ov D   or   A cn D
+//               // TODO This needs to be changed so two before/after make a contained and begins/ends-on makes overlap
+//               if ( (hasTlinkType( indexB, indexC, AFTER )
+//                     || hasTlinkType( indexB, indexC, BEGINS_ON ))
+//                     && (hasTlinkType( indexB, indexD, BEFORE )
+//                     || hasTlinkType( indexB, indexD, ENDS_ON )) ) {
+//                  // A ov C < B < D ov A, therefore A ov B   or   A cn C < B < D cn A, therefore A cn B
+//                  if ( addTlinkType( indexA, indexB, tlinkType ) ) {
+//                     addedCount++;
+//                  }
+//               }
+//            }
+//         }
+//      }
+//      return addedCount;
+//   }
+
+   // search overlap between two nodes marked overlap   or  contains between two nodes marked contains
+   private int inferContainsForEntityAtoBwithCandD( final int indexA, final int indexB, final int indexC ) {
+      final int entityCount = getEntityIdCount();
+      int addedCount = 0;
+      // if A ov C && A ov D, C < B || C eo B, B < D || D bo B, then A cn B   ( A ov C < B < D ov A )
+      // if A cn C && A cn D, C < B || C eo B, B < D || D bo B, then A cn B   ( A cn C < B < D cb A )
+      if ( hasTlinkType( indexA, indexC, OVERLAP ) || hasTlinkType( indexA, indexC, CONTAINS ) ) {
+         // A ov C   or   A cn C
+         for ( int indexD = 0; indexD < entityCount; indexD++ ) {
+            if ( indexD == indexA || indexD == indexB || indexD == indexC ) {
+               continue;
+            }
+            if ( hasTlinkType( indexA, indexC, OVERLAP ) || hasTlinkType( indexA, indexC, CONTAINS ) ) {
+               // A ov D   or   A cn D
+               if ( (hasTlinkType( indexB, indexC, AFTER ) || hasTlinkType( indexB, indexC, BEGINS_ON ))
+                    && (hasTlinkType( indexB, indexD, BEFORE ) || hasTlinkType( indexB, indexD, ENDS_ON )) ) {
+                  // A ov C < B < D ov A, therefore A cn B   or   A cn C < B < D cn A, therefore A cn B
+                  if ( addTlinkType( indexA, indexB, CONTAINS ) ) {
+                     addedCount++;
+                  }
+               }
+            }
+         }
+      }
+      return addedCount;
+   }
+
+   private boolean isIndexOk( final int entityIndex ) {
+      return entityIndex >= 0 && entityIndex < getEntityIdCount();
+   }
+
+   private int getEntityIdCount() {
+      return _uniqueEntityIdList.size();
+   }
+
+   private boolean hasTlinkType( final int indexA, final int indexB,
+                                 final TlinkType tlinkType ) {
+      if ( indexA == indexB ) {
+         // entities are the same, therefore no relationship
+         return false;
+      }
+      final TlinkTypeStore tlinkTypes = getTlinkTypes( indexA, indexB );
+      return tlinkTypes != null && tlinkTypes.contains( tlinkType );
+   }
+
+   private TlinkTypeStore getTlinkTypes( final int indexA, final int indexB ) {
+      if ( indexA == indexB || !isIndexOk( indexA ) || !isIndexOk( indexB ) ) {
+         return null;
+      }
+      return _tlinkTypeStoreArray[ indexA ][ indexB ];
+   }
+
+   private int addTlinkTypes( final int indexA, final int indexC,
+                              final TlinkTypeStore tlinkTypeStore ) {
+      if ( tlinkTypeStore == null || tlinkTypeStore.isEmpty()
+            || !isIndexOk( indexA ) || !isIndexOk( indexC ) ) {
+         return 0;
+      }
+      if ( _tlinkTypeStoreArray[ indexA ][ indexC ] == null ) {
+         _tlinkTypeStoreArray[ indexA ][ indexC ] = new DefaultTlinkTypeStore( _refineAdditions );
+      }
+      if ( _tlinkTypeStoreArray[ indexC ][ indexA ] == null ) {
+         _tlinkTypeStoreArray[ indexC ][ indexA ] = new DefaultTlinkTypeStore( _refineAdditions );
+      }
+      int addedCount = 0;
+      for ( TlinkType tlinkType : tlinkTypeStore ) {
+         boolean added = _tlinkTypeStoreArray[ indexA ][ indexC ].add( tlinkType );
+         if ( added ) {
+            addedCount++;
+         }
+         added = _tlinkTypeStoreArray[ indexC ][ indexA ].add( tlinkType.getReciprocal() );
+         if ( added ) {
+            addedCount++;
+         }
+      }
+      return addedCount;
+   }
+
+   ///////////       Add TlinkType       /////////////
+
+   private boolean addTlinkType( final Relation tlink ) {
+      // Checked and ok 7/10/13 spf
+      final TlinkType tlinkType = TlinkType.getTlinkType( tlink );
+      if ( tlinkType == null ) {
+         return false;
+      }
+      final boolean added1 = addTlinkType( tlink.getFirstEntity(), tlink.getSecondEntity(),
+            tlinkType );
+      // Add reciprocal
+      final boolean added2 = addTlinkType( tlink.getSecondEntity(), tlink.getFirstEntity(),
+            tlinkType.getReciprocal() );
+      return added1 || added2;
+   }
+
+   private boolean addTlinkType( final Entity entityA, final Entity entityB,
+                                 final TlinkType tlinkType ) {
+      // Checked and ok 7/12/13 spf
+      final int entityIdA = _entityIdStore.getUniqueEntityId( entityA );
+      final int entityIdB = _entityIdStore.getUniqueEntityId( entityB );
+      return addTlinkType( entityIdA, entityIdB, tlinkType );
+   }
+
+   private boolean addTlinkType( final int entityIdA, final int entityIdB,
+                                 final TlinkType tlinkType ) {
+      // Checked and ok 7/12/13 spf
+      final int indexA = _uniqueEntityIdList.indexOf( entityIdA );
+      final int indexB = _uniqueEntityIdList.indexOf( entityIdB );
+      if ( tlinkType == null || !isIndexOk( indexA ) || !isIndexOk( indexB ) ) {
+         return false;
+      }
+      if ( _tlinkTypeStoreArray[ indexA ][ indexB ] == null ) {
+         _tlinkTypeStoreArray[ indexA ][ indexB ] = new DefaultTlinkTypeStore( _refineAdditions );
+      }
+      if ( tlinkType != OVERLAP && _tlinkTypeStoreArray[ indexA ][ indexB ].contains( tlinkType.getReciprocal() ) ) {
+//         LOGGER.error( "TLINK " + tlinkType + " conflicts with Same-Entities TLINK " + tlinkType.getReciprocal() );
+      }
+      return _tlinkTypeStoreArray[ indexA ][ indexB ].add( tlinkType );
+   }
+
+
+}



Mime
View raw message