You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by sc...@apache.org on 2015/05/18 21:36:11 UTC

svn commit: r1680077 - /uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima/tools/viewer/CasAnnotationViewer.java

Author: schor
Date: Mon May 18 19:36:11 2015
New Revision: 1680077

URL: http://svn.apache.org/r1680077
Log:
[UIMA-3374] apply patch 2015-5-15 after running some simple tests of the UI

Modified:
    uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima/tools/viewer/CasAnnotationViewer.java

Modified: uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima/tools/viewer/CasAnnotationViewer.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima/tools/viewer/CasAnnotationViewer.java?rev=1680077&r1=1680076&r2=1680077&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima/tools/viewer/CasAnnotationViewer.java (original)
+++ uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima/tools/viewer/CasAnnotationViewer.java Mon May 18 19:36:11 2015
@@ -27,10 +27,14 @@ import java.awt.Dimension;
 import java.awt.GridLayout;
 import java.awt.Insets;
 import java.awt.Rectangle;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.util.ArrayList;
@@ -56,6 +60,7 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JScrollPane;
 import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
 import javax.swing.JTextPane;
 import javax.swing.JTree;
 import javax.swing.Scrollable;
@@ -133,6 +138,7 @@ public class CasAnnotationViewer extends
   // Mode constants
   private static final short MODE_ANNOTATIONS = 0;
   private static final short MODE_ENTITIES = 1;
+  private static final short MODE_FEATURES = 2;
 
   private static String[] DEFAULT_HIDDEN_FEATURES = { "sofa" };
   // colors to use for highlighting annotations
@@ -178,20 +184,20 @@ public class CasAnnotationViewer extends
   private Type stringType;
   private Type fsArrayType;
   private boolean useConsistentColors = true;
-  private List<String> highFrequencyTypes;
-  private String[] boldFaceKeyWords;
-  private int[] boldFaceSpans;
-  private Set<String> hiddenFeatureNames;
-  private Set<String> hiddenTypeNames;
+  private List<String> highFrequencyTypes = new ArrayList<String>();
+  private String[] boldFaceKeyWords = new String[0];
+  private int[] boldFaceSpans = new int[0];
+  private Set<String> hiddenFeatureNames = new HashSet<String>();
+  private Set<String> hiddenTypeNames = new HashSet<String>();
   private Set<String> displayedTypeNames;
   private Set<String> initiallySelectedTypeNames;
   private boolean hideUnselectedCheckBoxes;
-  private List<String> userTypes;
-  private Set<String> typesNotChecked;
-  private Map<String, Color> typeColorMap;
+  private List<String> userTypes = new ArrayList<String>();
+  private Set<String> typesNotChecked = new HashSet<String>();
+  private Map<String, Color> typeColorMap = new HashMap<String, Color>();
+  private Map<String, Color> featureColorMap = new HashMap<String, Color>();
   private EntityResolver entityResolver = new DefaultEntityResolver();
 
-  private boolean entityViewEnabled = false; 
   private short viewMode = MODE_ANNOTATIONS;
   // GUI components
   private Map<Type, JCheckBox> typeToCheckBoxMap = new HashMap<Type, JCheckBox>();
@@ -200,21 +206,32 @@ public class CasAnnotationViewer extends
   private JSplitPane verticalSplitPane;
   private JTextPane textPane;
   private JScrollPane textScrollPane;
-  private JScrollPane legendScrollPane;
-  private JPanel annotationCheckboxPanel;
-  private JPanel entityCheckboxPanel;
-  private JButton selectAllButton;
-  private JButton deselectAllButton;
+  private Map<Type, JRadioButton> typeRadioButtonMap = new HashMap<Type, JRadioButton>();
+  private Map<String, JRadioButton> featureRadioButtonMap = new HashMap<String, JRadioButton>();
+  private Map<String, JCheckBox> featureValueCheckBoxMap = new HashMap<String, JCheckBox>();
+  private Map<String, Color> featureValueColorMap = new HashMap<String, Color>();
   private JButton showHideUnselectedButton;
   private JTree selectedAnnotationTree;
   private DefaultTreeModel selectedAnnotationTreeModel;
-  private JPanel viewModePanel;
   private JRadioButton annotationModeButton;
   private JRadioButton entityModeButton;
+  private JRadioButton featureModeButton;
   private JPanel sofaSelectionPanel;
   @SuppressWarnings("rawtypes")
   private JComboBox sofaSelectionComboBox;
 
+  private JTabbedPane tabbedChoicePane;
+  private JScrollPane typeCheckBoxScrollPane;
+  private VerticallyScrollablePanel typeCheckBoxVerticalScrollPanel;
+  private JScrollPane entityCheckBoxScrollPane;
+  private VerticallyScrollablePanel entityCheckBoxVerticalScrollPanel;
+  private JScrollPane typeRadioButtonScrollPane;
+  private VerticallyScrollablePanel typeRadioButtonVerticalScrollPanel;
+  private JScrollPane featureRadioButtonScrollPane;
+  private VerticallyScrollablePanel featureRadioButtonVerticalScrollPanel;
+  private JScrollPane featureValueCheckBoxScrollPane;
+  private VerticallyScrollablePanel featureValueCheckBoxVerticalScrollPanel;
+
   /**
    * Creates a CAS Annotation Viewer.
    */
@@ -231,7 +248,7 @@ public class CasAnnotationViewer extends
     this.add(this.createControlPanel(), BorderLayout.SOUTH);
 
     // initialize hidden feature names map
-    this.hiddenFeatureNames = new HashSet<String>(Arrays.asList(DEFAULT_HIDDEN_FEATURES));
+    this.hiddenFeatureNames.addAll(Arrays.asList(DEFAULT_HIDDEN_FEATURES));
   }
 
   private JPanel createControlPanel() {
@@ -244,10 +261,8 @@ public class CasAnnotationViewer extends
 
   private JPanel createSelectButtonPanel() {
     JPanel selectButtonPanel = new JPanel();
-    this.createSelectAllButton();
-    selectButtonPanel.add(this.selectAllButton);
-    this.createDeselectAllButton();
-    selectButtonPanel.add(this.deselectAllButton);
+    selectButtonPanel.add(this.createSelectAllButton());
+    selectButtonPanel.add(this.createDeselectAllButton());
     this.createShowHideUnselectedButton();
     selectButtonPanel.add(this.showHideUnselectedButton);
     return selectButtonPanel;
@@ -258,22 +273,42 @@ public class CasAnnotationViewer extends
     viewPanel.setLayout(new BorderLayout());
     this.createSofaSelectionPanel();
     viewPanel.add(this.sofaSelectionPanel, BorderLayout.NORTH);
-    this.createViewModePanel();
-    viewPanel.add(this.viewModePanel, BorderLayout.CENTER);
+    viewPanel.add(this.createViewModePanel(), BorderLayout.CENTER);
     return viewPanel;
   }
 
-  private void createViewModePanel() {
-    this.viewModePanel = new JPanel();
-    this.viewModePanel.add(new JLabel("Mode: "));
+  private JPanel createViewModePanel() {
+    JPanel viewModePanel = new JPanel();
+    viewModePanel.add(new JLabel("Mode: "));
     this.createAnnotationModeButton();
-    this.viewModePanel.add(this.annotationModeButton);
+    viewModePanel.add(this.annotationModeButton);
     this.createEntityModeButton();
-    this.viewModePanel.add(this.entityModeButton);
+    viewModePanel.add(this.entityModeButton);
+    this.createFeatureModeButton();
+    viewModePanel.add(this.featureModeButton);
     ButtonGroup group = new ButtonGroup();
     group.add(this.annotationModeButton);
     group.add(this.entityModeButton);
-    this.viewModePanel.setVisible(false);
+    group.add(this.featureModeButton);
+    return viewModePanel;
+  }
+
+  private void createFeatureModeButton() {
+    this.featureModeButton = new JRadioButton("Features", (this.viewMode == MODE_FEATURES));
+    this.featureModeButton.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        JRadioButton radioButton = (JRadioButton) e.getSource();
+        if (!radioButton.isSelected() || viewMode == MODE_FEATURES) {
+            // If the radio button is unselected, there is no need to refresh the UI.
+            // If the view mode is already features view mode, there is no need to refresh the UI.
+          return;
+        }
+        viewMode = MODE_FEATURES;
+        resetTabbedPane();
+        display();
+      }
+    });
   }
 
   private void createEntityModeButton() {
@@ -288,6 +323,7 @@ public class CasAnnotationViewer extends
           return;
         }
         viewMode = MODE_ENTITIES;
+        resetTabbedPane();
         display();
       }
     });
@@ -305,6 +341,7 @@ public class CasAnnotationViewer extends
           return;
         }
         viewMode = MODE_ANNOTATIONS;
+        resetTabbedPane();
         display();
       }
     });
@@ -330,8 +367,7 @@ public class CasAnnotationViewer extends
         String sofaId = (String) e.getItem();
         CAS newCas = "DEFAULT".equalsIgnoreCase(sofaId) ? cas.getView(CAS.NAME_DEFAULT_SOFA) : cas.getView(sofaId);
         if (newCas != cas) {
-          cas = newCas;
-          display();
+          setCAS(newCas);
         }
       }
     });
@@ -343,39 +379,235 @@ public class CasAnnotationViewer extends
       @Override
       public void actionPerformed(ActionEvent e) {
         hideUnselectedCheckBoxes = !hideUnselectedCheckBoxes;
-        display();
+        switch (viewMode) {
+        case MODE_ANNOTATIONS:
+          if (typeToCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox typeCheckBox : typeToCheckBoxMap.values()) {
+              if (!typeCheckBox.isSelected()) {
+                // If any check boxes for types are unchecked, the UI needs to be refreshed.
+                needRefresh = true;
+                break;
+              }
+            }
+            if (needRefresh) {
+              if (typeCheckBoxVerticalScrollPanel != null) {
+                typeCheckBoxVerticalScrollPanel.removeAll();
+              }
+              display();
+            }
+          }
+          break;
+        case MODE_ENTITIES:
+          if (entityToCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox entityCheckBox : entityToCheckBoxMap.values()) {
+              if (!entityCheckBox.isSelected()) {
+                // If any check boxes for entities are unchecked, the UI needs to be refreshed.
+                needRefresh = true;
+                break;
+              }
+            }
+            if (needRefresh) {
+              if (entityCheckBoxVerticalScrollPanel != null) {
+                entityCheckBoxVerticalScrollPanel.removeAll();
+              }
+              display();
+            }
+          }
+          break;
+        case MODE_FEATURES:
+          if (featureValueCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox featureValueCheckBox : featureValueCheckBoxMap.values()) {
+              if (!featureValueCheckBox.isSelected()) {
+                // If any check boxes for feature values are unchecked, the UI needs to be refreshed.
+                needRefresh = true;
+                break;
+              }
+            }
+            if (needRefresh) {
+              if (featureValueCheckBoxVerticalScrollPanel != null) {
+                featureValueCheckBoxVerticalScrollPanel.removeAll();
+              }
+              display();
+              // Make the feature value tab as selected.
+              tabbedChoicePane.setSelectedIndex(2);
+            }
+          }
+          break;
+        default:
+          break;
+        }
       }
     });
   }
 
-  private void createDeselectAllButton() {
-    this.deselectAllButton = new JButton("Deselect All");
-    this.deselectAllButton.addActionListener(new ActionListener() {
+  private JButton createDeselectAllButton() {
+    JButton deselectAllButton = new JButton("Deselect All");
+    deselectAllButton.addActionListener(new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent e) {
-        Iterator<JCheckBox> cbIter = (viewMode == MODE_ANNOTATIONS) ? typeToCheckBoxMap.values().iterator()
-            : entityToCheckBoxMap.values().iterator();
-        while (cbIter.hasNext()) {
-          cbIter.next().setSelected(false);
+        switch (viewMode) {
+        case MODE_ANNOTATIONS:
+          if (typeToCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox typeCheckBox : typeToCheckBoxMap.values()) {
+              if (typeCheckBox.isSelected()) {
+                // If a check box for a type is checked, need to uncheck it,
+                // and the UI needs to be refreshed.
+                typeCheckBox.setSelected(false);
+                needRefresh = true;
+              }
+            }
+            if (needRefresh) {
+              display();
+            }
+          }
+          break;
+        case MODE_ENTITIES:
+          if (entityToCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox entityCheckBox : entityToCheckBoxMap.values()) {
+              if (entityCheckBox.isSelected()) {
+                // If a check box for an entity is checked, need to uncheck it,
+                // and the UI needs to be refreshed.
+                entityCheckBox.setSelected(false);
+                needRefresh = true;
+              }
+            }
+            if (needRefresh) {
+              display();
+            }
+          }
+          break;
+        case MODE_FEATURES:
+          if (featureValueCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox featureValueCheckBox : featureValueCheckBoxMap.values()) {
+              if (featureValueCheckBox.isSelected()) {
+                // If a check box for a feature value is checked, need to uncheck it,
+                // and the UI needs to be refreshed.
+                featureValueCheckBox.setSelected(false);
+                needRefresh = true;
+              }
+            }
+            if (needRefresh) {
+              display();
+              // Make the feature value tab as selected.
+              tabbedChoicePane.setSelectedIndex(2);
+            }
+          }
+          break;
+        default:
+          break;
         }
-        display();
       }
     });
+    return deselectAllButton;
   }
 
-  private void createSelectAllButton() {
-    this.selectAllButton = new JButton("Select All");
-    this.selectAllButton.addActionListener(new ActionListener() {
+  private JButton createSelectAllButton() {
+    JButton selectAllButton = new JButton("Select All");
+    selectAllButton.addActionListener(new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent e) {
-        Iterator<JCheckBox> cbIter = (viewMode == MODE_ANNOTATIONS) ? typeToCheckBoxMap.values().iterator()
-		              : entityToCheckBoxMap.values().iterator();
-        while (cbIter.hasNext()) {
-          cbIter.next().setSelected(true);
+        switch (viewMode) {
+        case MODE_ANNOTATIONS:
+          if (typeToCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox typeCheckBox : typeToCheckBoxMap.values()) {
+              if (!typeCheckBox.isSelected()) {
+                // If a check box for a type is unchecked, need to check it,
+                // and the UI needs to be refreshed.
+                typeCheckBox.setSelected(true);
+                needRefresh = true;
+              }
+            }
+            if (needRefresh) {
+              display();
+            }
+          }
+          break;
+        case MODE_ENTITIES:
+          if (entityToCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox entityCheckBox : entityToCheckBoxMap.values()) {
+              if (!entityCheckBox.isSelected()) {
+                // If a check box for an entity is unchecked, need to check it,
+                // and the UI needs to be refreshed.
+                entityCheckBox.setSelected(true);
+                needRefresh = true;
+              }
+            }
+            if (needRefresh) {
+              display();
+            }
+          }
+          break;
+        case MODE_FEATURES:
+          if (featureValueCheckBoxMap.size() > 0) {
+            boolean needRefresh = false;
+            for (JCheckBox featureValueCheckBox : featureValueCheckBoxMap.values()) {
+              if (!featureValueCheckBox.isSelected()) {
+                // If a check box for a feature value is unchecked, need to check it,
+                // and the UI needs to be refreshed.
+                featureValueCheckBox.setSelected(true);
+                needRefresh = true;
+              }
+            }
+            if (needRefresh) {
+              display();
+              // Make the feature value tab as selected.
+              tabbedChoicePane.setSelectedIndex(2);
+            }
+          }
+          break;
+        default:
+          break;
         }
-        display();
       }
     });
+    return selectAllButton;
+  }
+
+  /**
+   * Re-populate the tabbed pane's tabs depending on the current view mode.
+   */
+  private void resetTabbedPane() {
+    if (this.tabbedChoicePane == null) {
+      return;
+    }
+    this.tabbedChoicePane.removeAll();
+    
+    switch (this.viewMode) {
+    case MODE_ANNOTATIONS:
+      if (this.typeCheckBoxScrollPane != null) {
+        this.tabbedChoicePane.addTab("Annotation Types", this.typeCheckBoxScrollPane);
+      }
+      break;
+    case MODE_ENTITIES:
+      if (this.entityCheckBoxScrollPane != null) {
+        this.tabbedChoicePane.addTab("Entities", this.entityCheckBoxScrollPane);
+      }
+      break;
+    case MODE_FEATURES:
+      if (this.typeRadioButtonScrollPane != null) {
+        this.tabbedChoicePane.addTab("Annotation Types", this.typeRadioButtonScrollPane);
+        this.tabbedChoicePane.setMnemonicAt(0, KeyEvent.VK_1);
+      }
+      if (this.featureRadioButtonScrollPane != null) {
+        this.tabbedChoicePane.addTab("Features", this.featureRadioButtonScrollPane);
+        this.tabbedChoicePane.setMnemonicAt(1, KeyEvent.VK_2);
+      }
+      if (this.featureValueCheckBoxScrollPane != null) {
+        this.tabbedChoicePane.addTab("Feature Values", this.featureValueCheckBoxScrollPane);
+        this.tabbedChoicePane.setMnemonicAt(2, KeyEvent.VK_3);
+      }
+      break;
+    default:
+      break;
+    }
   }
 
   private void createHorizontalSplitPane() {
@@ -469,41 +701,95 @@ public class CasAnnotationViewer extends
     this.createTextScrollPane();
     this.verticalSplitPane.setTopComponent(this.textScrollPane);
     // bottom pane is the legend, with checkboxes
-    this.verticalSplitPane.setBottomComponent(this.createLegendPanel());
-  }
-
-  private JPanel createLegendPanel() {
-    JPanel legendPanel = new JPanel();
-    legendPanel.setPreferredSize(new Dimension(620, 200));
-    legendPanel.setLayout(new BorderLayout());
-    JLabel legendLabel = new JLabel("Legend");
-    legendPanel.add(legendLabel, BorderLayout.NORTH);
-
-    // checkboxes are contained in a scroll pane
-    this.createLegendScrollPane();
-    legendPanel.add(this.legendScrollPane, BorderLayout.CENTER);
-
-    return legendPanel;
-  }
-
-  private void createLegendScrollPane() {
-    this.legendScrollPane = new JScrollPane();
-    this.legendScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-    // there are two checkbox panels - one for annotations, one for entities
-    this.createAnnotationCheckBoxPanel();
-    this.createEntityCheckBoxPanel();
-    // add annotation panel first, since that is the default
-    this.legendScrollPane.setViewportView(this.annotationCheckboxPanel);
+    this.verticalSplitPane.setBottomComponent(this.createTabbedChoicePane());
   }
 
-  private void createEntityCheckBoxPanel() {
-    this.entityCheckboxPanel = new VerticallyScrollablePanel();
-    this.entityCheckboxPanel.setLayout(new GridLayout(0, 4));
-  }
+  private JTabbedPane createTabbedChoicePane() {
+    this.tabbedChoicePane = new JTabbedPane();
+    // Create the pane to hold check boxes for the annotation types in annotation view.
+    // It will be added to the first tab of the tabbed pane in annotation view.
+    this.createTypeCheckBoxPane();
+    // Create the pane to hold check boxes for the entities in entity view.
+    // It will be added to the first tab of the tabbed pane in entity view.
+    this.createEntityCheckBoxPane();
+    // Create the pane to hold radio buttons for the annotation types in features view.
+    // It will be added to the first tab of the tabbed pane in feature view.
+    this.createTypeRadioButtonPane();
+    // Create the pane to hold radio buttons for selected annotation type's features
+    // in feature view. It will be added as the second tab of the tabbed pane in
+    // feature view.
+    this.createFeatureRadioButtonPane();
+    // Create the pane to hold check boxes for feature values for selected feature of
+    // the selected annotation type in feature view. It will be added as the third tab
+    // of the tabbed pane in feature view.
+    this.createFeatureValueCheckBoxPane();
 
-  private void createAnnotationCheckBoxPanel() {
-    this.annotationCheckboxPanel = new VerticallyScrollablePanel();
-    this.annotationCheckboxPanel.setLayout(new GridLayout(0, 5));
+    switch (this.viewMode) {
+    case MODE_ANNOTATIONS:
+      this.tabbedChoicePane.addTab("Annotation Types", this.typeCheckBoxScrollPane);
+      this.tabbedChoicePane.setMnemonicAt(0, KeyEvent.VK_1);
+      break;
+    case MODE_ENTITIES:
+      this.tabbedChoicePane.addTab("Entities", this.entityCheckBoxScrollPane);
+      this.tabbedChoicePane.setMnemonicAt(0, KeyEvent.VK_1);
+      break;
+    case MODE_FEATURES:
+      this.tabbedChoicePane.addTab("Annotation Types", this.typeRadioButtonScrollPane);
+      this.tabbedChoicePane.setMnemonicAt(0, KeyEvent.VK_1);
+      this.tabbedChoicePane.addTab("Features", this.featureRadioButtonScrollPane);
+      this.tabbedChoicePane.setMnemonicAt(1, KeyEvent.VK_2);
+      this.tabbedChoicePane.addTab("Feature Values", this.featureValueCheckBoxScrollPane);
+      this.tabbedChoicePane.setMnemonicAt(2, KeyEvent.VK_3);
+      break;
+    default:
+      break;
+    }
+    return this.tabbedChoicePane;
+  }
+
+  private void createFeatureValueCheckBoxPane() {
+    this.featureValueCheckBoxVerticalScrollPanel = new VerticallyScrollablePanel();
+    this.featureValueCheckBoxVerticalScrollPanel.setLayout(new GridLayout(0, 5));
+
+    this.featureValueCheckBoxScrollPane = new JScrollPane();
+    this.featureValueCheckBoxScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+    this.featureValueCheckBoxScrollPane.setViewportView(this.featureValueCheckBoxVerticalScrollPanel);
+  }
+
+  private void createFeatureRadioButtonPane() {
+    this.featureRadioButtonVerticalScrollPanel = new VerticallyScrollablePanel();
+    this.featureRadioButtonVerticalScrollPanel.setLayout(new GridLayout(0, 5));
+
+    this.featureRadioButtonScrollPane = new JScrollPane();
+    this.featureRadioButtonScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+	this.featureRadioButtonScrollPane.setViewportView(this.featureRadioButtonVerticalScrollPanel);
+  }
+
+  private void createTypeRadioButtonPane() {
+    this.typeRadioButtonVerticalScrollPanel = new VerticallyScrollablePanel();
+    this.typeRadioButtonVerticalScrollPanel.setLayout(new GridLayout(0, 5));
+
+    this.typeRadioButtonScrollPane = new JScrollPane();
+    this.typeRadioButtonScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+    this.typeRadioButtonScrollPane.setViewportView(this.typeRadioButtonVerticalScrollPanel);
+  }
+
+  private void createEntityCheckBoxPane() {
+    this.entityCheckBoxVerticalScrollPanel = new VerticallyScrollablePanel();
+    this.entityCheckBoxVerticalScrollPanel.setLayout(new GridLayout(0, 5));
+
+    this.entityCheckBoxScrollPane = new JScrollPane();
+    this.entityCheckBoxScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+    this.entityCheckBoxScrollPane.setViewportView(this.entityCheckBoxVerticalScrollPanel);
+  }
+
+  private void createTypeCheckBoxPane() {
+    this.typeCheckBoxVerticalScrollPanel = new VerticallyScrollablePanel();
+    this.typeCheckBoxVerticalScrollPanel.setLayout(new GridLayout(0, 5));
+
+    this.typeCheckBoxScrollPane = new JScrollPane();
+    this.typeCheckBoxScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+    this.typeCheckBoxScrollPane.setViewportView(this.typeCheckBoxVerticalScrollPanel);
   }
 
   private void createTextScrollPane() {
@@ -535,6 +821,31 @@ public class CasAnnotationViewer extends
       public void mouseExited(MouseEvent e) {
       }
     });
+    // Added a key listener to support CTRL-C for copy selected text into clipboard.
+    this.textPane.addKeyListener(new KeyListener() {
+      @Override
+      public void keyTyped(KeyEvent e) {
+      }
+
+      @Override
+      public void keyPressed(KeyEvent ke) {
+        if (ke != null && ke.getKeyCode() == KeyEvent.VK_C &&
+            (ke.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
+          String selection = textPane.getSelectedText();
+          if (selection != null && selection.length() > 0) {
+            Clipboard clipboard = getToolkit().getSystemClipboard();
+            if (clipboard != null) {
+              StringSelection data = new StringSelection(selection);
+              clipboard.setContents(data, data);
+            }
+          }
+        }
+      }
+
+      @Override
+      public void keyReleased(KeyEvent e) {
+      }
+    });
     this.textScrollPane = new JScrollPane(this.textPane);
   }
 
@@ -571,16 +882,10 @@ public class CasAnnotationViewer extends
    */
   public void setHighFrequencyTypes(String[] aTypeNames) {
     // store these types for later
-    if (this.highFrequencyTypes == null) {
-      this.highFrequencyTypes = new ArrayList<String>();
-    }
     if (this.highFrequencyTypes.size() > 0) {
       this.highFrequencyTypes.clear();
     }
     this.highFrequencyTypes.addAll(Arrays.asList(aTypeNames));
-    if (this.typeColorMap == null) {
-      this.typeColorMap = new HashMap<String, Color>();
-    }
     if (this.typeColorMap.size() > 0) {
       this.typeColorMap.clear();
     }
@@ -618,9 +923,6 @@ public class CasAnnotationViewer extends
    *          names of types that are never to be highlighted.
    */
   public void setHiddenTypes(String[] aTypeNames) {
-    if (this.hiddenTypeNames == null) {
-      this.hiddenTypeNames = new HashSet<String>();
-    }
     if (this.hiddenTypeNames.size() > 0) {
       this.hiddenTypeNames.clear();
     }
@@ -635,17 +937,17 @@ public class CasAnnotationViewer extends
    *          array of fully-qualified names of types to be initially selected
    */
   public void setInitiallySelectedTypes(String[] aTypeNames) {
-    initiallySelectedTypeNames = new HashSet<String>();
+    this.initiallySelectedTypeNames = new HashSet<String>();
     for (int i = 0; i < aTypeNames.length; i++) {
-      initiallySelectedTypeNames.add(aTypeNames[i]);
+      this.initiallySelectedTypeNames.add(aTypeNames[i]);
     }
     // apply to existing checkboxes
-    Iterator<Map.Entry<Type, JCheckBox>> iterator = typeToCheckBoxMap.entrySet().iterator();
+    Iterator<Map.Entry<Type, JCheckBox>> iterator = this.typeToCheckBoxMap.entrySet().iterator();
     while (iterator.hasNext()) {
       Map.Entry<Type, JCheckBox> entry = iterator.next();
       String type = ((Type) entry.getKey()).getName();
       JCheckBox checkbox = (JCheckBox) entry.getValue();
-      checkbox.setSelected(typeNamesContains(initiallySelectedTypeNames, type));
+      checkbox.setSelected(typeNamesContains(this.initiallySelectedTypeNames, type));
     }
 
     // redisplay (if we have a CAS) - this allows this method to be called
@@ -662,9 +964,6 @@ public class CasAnnotationViewer extends
    *          array of (short) feature names to be hidden
    */
   public void setHiddenFeatures(String[] aFeatureNames) {
-    if (this.hiddenFeatureNames == null) {
-      this.hiddenFeatureNames = new HashSet<String>();
-    }
     if (this.hiddenFeatureNames.size() > 0) {
       this.hiddenFeatureNames.clear();
     }
@@ -675,21 +974,6 @@ public class CasAnnotationViewer extends
   }
 
   /**
-   * Configures whether the viewer will allow the user to switch to "Entity" view, which highlight
-   * entities rather than annotations.  Entity mode is typically only useful if the
-   * {@link #setEntityResolver(EntityResolver)} method has been called with a user-supplied
-   * class that can determine which annotations refer to the same entity.
-   * 
-   * @param aEnabled
-   *          true to enable entity viewing mode, false to allow annotation viewing only.
-   *          The default is false.
-   */
-  public void setEntityViewEnabled(boolean aEnabled) {
-    entityViewEnabled = aEnabled;
-    this.viewModePanel.setVisible(aEnabled);
-  }
-  
-  /**
    * Sets the {@link EntityResolver} to use when the viewer is in entity mode.
    * Entity mode must be turned on using the {@link #setEntityViewEnabled(boolean)} method.
    * @param aEntityResolver user-supplied class that can determine which annotations correspond
@@ -755,8 +1039,6 @@ public class CasAnnotationViewer extends
     // clear selected annotation details tree
     this.initializeSofaSelectionPanel();
 
-    // enable or disable entity view depending on user's choice 
-    this.viewModePanel.setVisible(entityViewEnabled);
     // Instead of relying on the SofaSelectionComboBox itemChanged event to trigger the initial display,
     // which does not necessarily need to happen unless we indeed switch over to different sofa,
     // explicitly invoke display() here.
@@ -807,8 +1089,18 @@ public class CasAnnotationViewer extends
   private void reset() {
     // clear type to color map if color consistency is off
     this.resetTypeColorMap();
+    // Clear the panel holding radio buttons for annotation types.
+    this.resetTypeRadioButtonPanel();
+    // Clear the panel holding radio buttons for features of selected annotation type.
+    this.resetFeaturePanel();
+    // Clear the panel holding check boxes for feature values of selected feature of
+    // selected annotation type.
+    this.resetFeatureValuePanel();
+    // Clear the panel holding check boxes for annotation types in annotation view.
     this.resetTypeCheckBoxPanel();
+    // Clear the panel holding check boxes for entities in entitiy view. 
     this.resetEntityCheckBoxPanel();
+    // Clear the tree view for selected annotations.
     this.resetSelectedAnnotationTree();
 
     // clear boldface
@@ -826,39 +1118,63 @@ public class CasAnnotationViewer extends
   }
 
   private void resetEntityCheckBoxPanel() {
-    if (this.entityToCheckBoxMap == null) {
-      this.entityToCheckBoxMap = new HashMap<Entity, JCheckBox>();
-    }
     if (this.entityToCheckBoxMap.size() > 0) {
       this.entityToCheckBoxMap.clear();
     }
-    if (this.entityCheckboxPanel != null) {
-      this.entityCheckboxPanel.removeAll();
+    if (this.entityCheckBoxVerticalScrollPanel != null) {
+      this.entityCheckBoxVerticalScrollPanel.removeAll();
     }
   }
 
   private void resetTypeCheckBoxPanel() {
     // clear checkbox panel so it will be repopulated
-    if (this.typeToCheckBoxMap == null) {
-      this.typeToCheckBoxMap = new HashMap<Type, JCheckBox>();
-    }
     if (this.typeToCheckBoxMap.size() > 0) {
       this.typeToCheckBoxMap.clear();
     }
-    if (this.annotationCheckboxPanel != null) {
-      this.annotationCheckboxPanel.removeAll();
+    if (this.typeCheckBoxVerticalScrollPanel != null) {
+      this.typeCheckBoxVerticalScrollPanel.removeAll();
     }
   }
 
-  private void resetTypeColorMap() {
-    if (this.typeColorMap == null) {
-      this.typeColorMap = new HashMap<String, Color>();
+  private void resetFeatureValuePanel() {
+    if (this.featureValueColorMap.size() > 0) {
+      this.featureValueColorMap.clear();
+    }
+    if (this.featureValueCheckBoxMap.size() > 0) {
+      this.featureValueCheckBoxMap.clear();
+    }
+    if (this.featureValueCheckBoxVerticalScrollPanel != null) {
+      this.featureValueCheckBoxVerticalScrollPanel.removeAll();
+    }
+  }
+
+  private void resetFeaturePanel() {
+    if (this.featureColorMap.size() > 0) {
+      this.featureColorMap.clear();
+    }
+    if (this.featureRadioButtonMap.size() > 0) {
+      this.featureRadioButtonMap.clear();
+    }
+    if (this.featureRadioButtonVerticalScrollPanel != null) {
+      this.featureRadioButtonVerticalScrollPanel.removeAll();
     }
+  }
+
+  private void resetTypeRadioButtonPanel() {
+    if (this.typeRadioButtonMap.size() > 0) {
+      this.typeRadioButtonMap.clear();
+    }
+    if (this.typeRadioButtonVerticalScrollPanel != null) {
+      this.typeRadioButtonVerticalScrollPanel.removeAll();
+    }
+  }
+
+  private void resetTypeColorMap() {
     if (!this.useConsistentColors) {
       if (this.typeColorMap.size() > 0) {
         this.typeColorMap.clear();
       }
-      if (this.highFrequencyTypes != null && this.highFrequencyTypes.size() > 0) {
+      if (this.highFrequencyTypes.size() > 0) {
         this.assignTypeColors(this.highFrequencyTypes);
       }
     }
@@ -966,12 +1282,7 @@ public class CasAnnotationViewer extends
     if (aNotChecked == null || aNotChecked.size() == 0) {
       return;
     }
-    if (this.typesNotChecked == null) {
-      this.typesNotChecked = new HashSet<String>();
-    }
-    for (String typeName : aNotChecked) {
-      this.typesNotChecked.add(typeName);
-    }
+    this.typesNotChecked.addAll(aNotChecked);
   }
 
   /**
@@ -987,15 +1298,9 @@ public class CasAnnotationViewer extends
     if (aColors == null || aColors.size() == 0 || aTypeNames == null || aTypeNames.size() == 0) {
       return;
     }
-    if (this.userTypes == null) {
-      this.userTypes = new ArrayList<String>();
-    }
     if (this.userTypes.size() > 0) {
       this.userTypes.clear();
     }
-    if (this.typeColorMap == null) {
-      this.typeColorMap = new HashMap<String, Color>();
-    }
     if (this.typeColorMap.size() > 0) {
       this.typeColorMap.clear();
     }
@@ -1008,8 +1313,20 @@ public class CasAnnotationViewer extends
     }
 
     // clear checkbox panel so it will be refreshed
-    this.annotationCheckboxPanel.removeAll();
+    if (this.typeRadioButtonVerticalScrollPanel != null) {
+      this.typeRadioButtonVerticalScrollPanel.removeAll();
+    }
+    if (this.typeCheckBoxVerticalScrollPanel != null) {
+      this.typeCheckBoxVerticalScrollPanel.removeAll();
+    }
+    if (this.featureRadioButtonVerticalScrollPanel != null) {
+      this.featureRadioButtonVerticalScrollPanel.removeAll();
+    }
+    if (this.featureValueCheckBoxVerticalScrollPanel != null) {
+      this.featureValueCheckBoxVerticalScrollPanel.removeAll();
+    }
     this.typeToCheckBoxMap.clear();
+    this.typeRadioButtonMap.clear();
   }
 
   /**
@@ -1024,9 +1341,6 @@ public class CasAnnotationViewer extends
       return;
     }
 
-    if (this.typeColorMap == null) {
-      this.typeColorMap = new HashMap<String, Color>();
-    }
     for (String typeName : aTypeNames) {
       if (!this.typeColorMap.containsKey(typeName)) {
         // assign background color
@@ -1035,10 +1349,14 @@ public class CasAnnotationViewer extends
     }
 
     // clear checkbox panel so it will be refreshed
-    this.annotationCheckboxPanel.removeAll();
-    if (this.typeToCheckBoxMap != null) {
-      this.typeToCheckBoxMap.clear();
+    if (this.typeRadioButtonVerticalScrollPanel != null) {
+      this.typeRadioButtonVerticalScrollPanel.removeAll();
+    }
+    if (this.typeCheckBoxVerticalScrollPanel != null) {
+      this.typeCheckBoxVerticalScrollPanel.removeAll();
     }
+    this.typeRadioButtonMap.clear();
+    this.typeToCheckBoxMap.clear();
   }
 
   /**
@@ -1061,6 +1379,11 @@ public class CasAnnotationViewer extends
       case MODE_ENTITIES:
         this.displayEntityView();
         break;
+      case MODE_FEATURES:
+        this.displayFeatureView();
+        break;
+      default:
+        break;
     }
 
     // apply boldface to keywords and spans as indicated by user
@@ -1083,6 +1406,334 @@ public class CasAnnotationViewer extends
   }
 
   /**
+   * Create/update the feature view.
+   */
+  private void displayFeatureView() {
+    StyledDocument doc = (StyledDocument) this.textPane.getDocument();
+    Document blank = new DefaultStyledDocument();
+    this.textPane.setDocument(blank);
+
+    // add text from CAS
+    try {
+      doc.remove(0, doc.getLength());
+      doc.insertString(0, this.cas.getDocumentText(), new SimpleAttributeSet());
+    } catch (BadLocationException e) {
+      throw new RuntimeException(e);
+    }
+
+    JCas jcas = null;
+    try {
+		jcas = this.cas.getJCas();
+	} catch (CASException e) {
+		e.printStackTrace();
+	}
+    AnnotationIndex<Annotation> annotationIndex = jcas.getAnnotationIndex();
+    if (annotationIndex == null) {
+      return;
+    }
+    FSIterator<Annotation> annotationIterator = annotationIndex.iterator();
+    if (annotationIterator == null || !annotationIterator.hasNext()) {
+      return;
+    }
+
+    // If we don't have any type radio button yet, then we will be showing the
+    // first type by default. Otherwise, we will show whichever type that is
+    // selected already.
+    boolean firstType = this.typeRadioButtonMap.size() == 0;
+    while (annotationIterator.hasNext()) {
+      if (this.processOneAnnotationInFeatureView(doc, annotationIterator.next(), firstType) && firstType) {
+        firstType = false;
+      }
+    }
+
+    this.addTypeRadioButtons();
+    this.addFeatureRadioButtons();
+    this.addFeatureValueCheckBoxes();
+    this.textPane.setDocument(doc);
+  }
+
+  private void addFeatureValueCheckBoxes() {
+    if (this.featureValueCheckBoxMap.size() == 0) {
+      return;
+    }
+
+    List<JCheckBox> checkBoxes = new ArrayList<JCheckBox>(this.featureValueCheckBoxMap.values());
+    Collections.sort(checkBoxes, new Comparator<JCheckBox>() {
+      @Override
+      public int compare(JCheckBox arg0, JCheckBox arg1) {
+        return arg0.getText().toLowerCase().compareTo(arg1.getText().toLowerCase());
+      }
+    });
+
+    for (JCheckBox checkBox : checkBoxes) {
+      if (checkBox.getParent() != this.featureValueCheckBoxVerticalScrollPanel &&
+          (checkBox.isSelected() || !this.hideUnselectedCheckBoxes)) {
+        this.featureValueCheckBoxVerticalScrollPanel.add(checkBox);
+      }
+    }
+  }
+
+  private void addFeatureRadioButtons() {
+    if (this.featureRadioButtonMap.size() == 0) {
+      return;
+    }
+
+    List<JRadioButton> radioButtons = new ArrayList<JRadioButton>(this.featureRadioButtonMap.values());
+    Collections.sort(radioButtons, new Comparator<JRadioButton>() {
+      @Override
+      public int compare(JRadioButton arg0, JRadioButton arg1) {
+        return arg0.getText().toLowerCase().compareTo(arg1.getText().toLowerCase());
+      }
+    });
+
+    ButtonGroup featureRadioButtonGroup = new ButtonGroup();
+    for (JRadioButton radioButton : radioButtons) {
+      if (radioButton.getParent() != this.featureRadioButtonVerticalScrollPanel) {
+        this.featureRadioButtonVerticalScrollPanel.add(radioButton);
+      }
+      featureRadioButtonGroup.add(radioButton);
+    }
+  }
+
+  private void addTypeRadioButtons() {
+    if (this.typeRadioButtonMap.size() == 0) {
+      return;
+    }
+
+    Map<String, JRadioButton> radioButtonMap = new HashMap<String, JRadioButton>();
+    Set<JRadioButton> radioButtonSet = new HashSet<JRadioButton>();
+    for (Type type : this.typeRadioButtonMap.keySet()) {
+      JRadioButton typeRadioButton = this.typeRadioButtonMap.get(type);
+      radioButtonMap.put(type.getName(), typeRadioButton);
+      radioButtonSet.add(typeRadioButton);
+    }
+
+    ButtonGroup typeRadioButtonGroup = new ButtonGroup();
+    if (this.userTypes.size() > 0) {
+      for (String typeName : this.userTypes) {
+        JRadioButton typeRadioButton = radioButtonMap.get(typeName);
+        if (typeRadioButton != null) {
+          this.typeRadioButtonVerticalScrollPanel.add(typeRadioButton);
+          typeRadioButtonGroup.add(typeRadioButton);
+          radioButtonSet.remove(typeRadioButton);
+        }
+      }
+    }
+
+    if (radioButtonSet != null && radioButtonSet.size() > 0) {
+      List<JRadioButton> remainingRadioButtons = new ArrayList<JRadioButton>(radioButtonSet);
+      Collections.sort(remainingRadioButtons, new Comparator<JRadioButton>() {
+        @Override
+        public int compare(JRadioButton arg0, JRadioButton arg1) {
+          return arg0.getText().toLowerCase().compareTo(arg1.getText().toLowerCase());
+        }
+      });
+      for (JRadioButton radioButton : remainingRadioButtons) {
+        if (radioButton.getParent() != this.typeRadioButtonVerticalScrollPanel) {
+          this.typeRadioButtonVerticalScrollPanel.add(radioButton);
+        }
+        typeRadioButtonGroup.add(radioButton);
+      }
+    }
+  }
+
+  /**
+   * Examine an annotation and add type/feature/value-related controls if
+   * necessary.
+   * 
+   * @param doc
+   * @param annotation
+   * @param firstType
+   *            If true, the annotation being examined has the first
+   *            type-related radio button that needs to be created and also
+   *            set as selected.
+   * @return If true, the annotation being examined has its type being
+   *         selected, its feature-related radio buttons being displayed and
+   *         one of its features being selected, and the feature's value is
+   *         selected. So that the next annotation of different type will not
+   *         have its features being displayed in the control panel.
+   */
+  private boolean processOneAnnotationInFeatureView(StyledDocument doc,
+      Annotation annotation, boolean firstType) {
+    Type type = annotation.getType();
+    String typeName = type.getName();
+    if ((this.displayedTypeNames != null && !typeNamesContains(this.displayedTypeNames, typeName))
+      || typeNamesContains(this.hiddenTypeNames, typeName)) {
+      return false;
+    }
+
+    JRadioButton typeRadioButton = this.typeRadioButtonMap.get(type);
+    if (typeRadioButton == null) {
+      Color typeColor = this.typeColorMap.get(typeName);
+      if (typeColor == null) {
+        typeColor = COLORS[this.typeColorMap.size() % COLORS.length];
+        this.typeColorMap.put(typeName, typeColor);
+      }
+      typeRadioButton = new JRadioButton(type.getShortName(), firstType);
+      typeRadioButton.setToolTipText(typeName);
+      typeRadioButton.setBackground(typeColor);
+      typeRadioButton.setOpaque(true);
+      typeRadioButton.addActionListener(new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent ae) {
+          JRadioButton typeRadioButton = (JRadioButton) ae.getSource();
+          if (typeRadioButton == null || !typeRadioButton.isSelected()) {
+            return;
+          }
+          // Need to reset the feature radio button panel and feature
+          // value check box panel.
+          resetFeaturePanel();
+          resetFeatureValuePanel();
+          resetSelectedAnnotationTree();
+          display();
+          // Make the feature tab as selected.
+          tabbedChoicePane.setSelectedIndex(1);
+        }
+      });
+      this.typeRadioButtonMap.put(type, typeRadioButton);
+    }
+
+    if (!typeRadioButton.isSelected()) {
+      return true;
+    }
+    List<Feature> features = type.getFeatures();
+    if (features == null || features.size() == 0) {
+      return false;
+    }
+
+    // If we don't have any feature radio button yet, then we will showing
+    // the first feature by default. Otherwise, we will show whichever
+    // feature that is selected already.
+    boolean firstFeature = this.featureRadioButtonMap.size() == 0;
+    for (Feature feature : features) {
+      if (this.processOneFeature(doc, annotation, feature, firstFeature) && firstFeature) {
+        firstFeature = false;
+      }
+    }
+    return this.featureValueCheckBoxMap.size() > 0;
+  }
+
+  /**
+   * Examine one feature of the given annotation and create the
+   * feature-related controls.
+   * 
+   * @param doc
+   * @param annotation
+   * @param feature
+   * @param firstFeature
+   * @return
+   */
+  private boolean processOneFeature(StyledDocument doc, Annotation annotation,
+      Feature feature, boolean firstFeature) {
+    String featureName = feature.getShortName();
+    if (this.hiddenFeatureNames.contains(featureName)) {
+      return false;
+    }
+
+    // Skip FSArray, array, and FS type features.
+    Type rangeType = feature.getRange();
+    if (this.typeSystem != null &&
+        (this.typeSystem.subsumes(this.fsArrayType, rangeType) || rangeType.isArray())) {
+      return false;
+    }
+    String featureValue = this.getFeatureValueInString(annotation, feature);
+    if (featureValue == null || featureValue.equalsIgnoreCase("*FS*")) {
+      return false;
+    }
+
+    JRadioButton featureRadioButton = this.featureRadioButtonMap.get(featureName);
+    if (featureRadioButton == null) {
+      Color featureColor = this.featureColorMap.get(featureName);
+      if (featureColor == null) {
+        featureColor = COLORS[this.featureColorMap.size() % COLORS.length];
+        this.featureColorMap.put(featureName, featureColor);
+      }
+      featureRadioButton = new JRadioButton(featureName, firstFeature);
+      featureRadioButton.setToolTipText(feature.getName());
+      featureRadioButton.setBackground(featureColor);
+      featureRadioButton.setOpaque(true);
+      featureRadioButton.addActionListener(new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent ae) {
+          JRadioButton featureRadioButton = (JRadioButton) ae.getSource();
+          if (featureRadioButton == null || !featureRadioButton.isSelected()) {
+            return;
+          }
+          // Need to reset the feature value check box panel.
+          resetFeatureValuePanel();
+          resetSelectedAnnotationTree();
+          display();
+          // Make the feature value tab as selected.
+          tabbedChoicePane.setSelectedIndex(2);
+        }
+      });
+      this.featureRadioButtonMap.put(featureName, featureRadioButton);
+    }
+
+    if (!featureRadioButton.isSelected()) {
+      return true;
+    }
+
+    return this.processOneFeatureValue(doc, annotation, feature);
+  }
+
+  /**
+   * Examine one feature of the given annotation and create the
+   * feature-value-related controls.
+   * 
+   * @param doc
+   * @param annotation
+   * @param feature
+   * @return
+   */
+  private boolean processOneFeatureValue(StyledDocument doc,
+      Annotation annotation, Feature feature) {
+    String featureValue = this.getFeatureValueInString(annotation, feature);
+    if (featureValue == null || featureValue.length() == 0) {
+      return false;
+    }
+
+    JCheckBox featureValueCheckBox = this.featureValueCheckBoxMap.get(featureValue);
+    if (featureValueCheckBox == null) {
+      Color featureValueColor = this.featureValueColorMap.get(featureValue);
+      if (featureValueColor == null) {
+        featureValueColor = COLORS[this.featureValueColorMap.size() % COLORS.length];
+        this.featureValueColorMap.put(featureValue, featureValueColor);
+      }
+      featureValueCheckBox = new JCheckBox(checkString(featureValue, "null", 16), true);
+      featureValueCheckBox.setToolTipText(featureValue);
+      featureValueCheckBox.setBackground(featureValueColor);
+      featureValueCheckBox.setOpaque(true);
+      featureValueCheckBox.addActionListener(new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          resetSelectedAnnotationTree();
+          display();
+          // Make the feature value tab as selected.
+          tabbedChoicePane.setSelectedIndex(2);
+        }
+      });
+      this.featureValueCheckBoxMap.put(featureValue, featureValueCheckBox);
+    }
+
+    if (featureValueCheckBox.isSelected()) {
+      int begin = annotation.getBegin();
+      int end = annotation.getEnd();
+
+      if (begin == 0 && end == this.cas.getDocumentText().length()) {
+        end--;
+      }
+
+      if (begin < end) {
+        MutableAttributeSet attrs = new SimpleAttributeSet();
+        StyleConstants.setBackground(attrs, featureValueCheckBox.getBackground());
+        doc.setCharacterAttributes(begin, end - begin, attrs, false);
+      }
+    }
+    return true;
+  }
+
+  /**
    * Creates the annotation display.
    */
   private void displayAnnotationView() {
@@ -1092,9 +1743,7 @@ public class CasAnnotationViewer extends
     this.textPane.setDocument(blank);
 
     // make sure annotationCheckboxPanel is showing
-    if (legendScrollPane.getViewport().getView() != annotationCheckboxPanel) {
-      legendScrollPane.setViewportView(annotationCheckboxPanel);
-    }
+    this.typeCheckBoxVerticalScrollPanel.removeAll();
 
     // add text from CAS
     try {
@@ -1123,7 +1772,7 @@ public class CasAnnotationViewer extends
   }
 
   private void addTypeCheckBoxes() {
-    if (this.typeToCheckBoxMap == null || this.typeToCheckBoxMap.size() == 0) {
+    if (this.typeToCheckBoxMap.size() == 0) {
       return;
     }
 
@@ -1131,28 +1780,21 @@ public class CasAnnotationViewer extends
     Set<JCheckBox> checkBoxSet = new HashSet<JCheckBox>();
     for (Type type : this.typeToCheckBoxMap.keySet()) {
       JCheckBox typeCheckBox = this.typeToCheckBoxMap.get(type);
-      if (typeCheckBox.getParent() == this.annotationCheckboxPanel) {
-        // The check box is already added to the check box panel.
-        if (!typeCheckBox.isSelected() && this.hideUnselectedCheckBoxes) {
-          // But it is unselected and we choose to hide the unselected.
-          // Then it needs to be removed from the panel.
-          this.annotationCheckboxPanel.remove(typeCheckBox);
-        }
-      } else {
-        // The check box is not yet added to the check box panel.
-        if (typeCheckBox.isSelected() || !this.hideUnselectedCheckBoxes) {
-          // The check box needs to be marked to be added.
-          checkBoxMap.put(type.getName(), typeCheckBox);
-          checkBoxSet.add(typeCheckBox);
-        }
+      // If the type check box is already added or not selected but needs
+      // to be hidden, skip it.
+      if (typeCheckBox.getParent() == this.typeCheckBoxVerticalScrollPanel ||
+          (!typeCheckBox.isSelected() && this.hideUnselectedCheckBoxes)) {
+        continue;
       }
+      checkBoxMap.put(type.getName(), typeCheckBox);
+      checkBoxSet.add(typeCheckBox);
     }
     // First add the type check boxes for the types specified by user.
-    if (this.userTypes != null && this.userTypes.size() > 0) {
+    if (this.userTypes.size() > 0) {
       for (String userType : this.userTypes) {
         JCheckBox typeCheckBox = checkBoxMap.get(userType);
         if (typeCheckBox != null) {
-          this.annotationCheckboxPanel.add(typeCheckBox);
+          this.typeCheckBoxVerticalScrollPanel.add(typeCheckBox);
           checkBoxSet.remove(typeCheckBox);
         }
       }
@@ -1168,7 +1810,7 @@ public class CasAnnotationViewer extends
         }
       });
       for (JCheckBox checkBox : remainingCheckBoxes) {
-        this.annotationCheckboxPanel.add(checkBox);
+        this.typeCheckBoxVerticalScrollPanel.add(checkBox);
       }
     }
   }
@@ -1178,20 +1820,14 @@ public class CasAnnotationViewer extends
     String typeName = type.getName();
     // check that type should be displayed
     if ((this.displayedTypeNames != null && !typeNamesContains(this.displayedTypeNames, typeName)) ||
-        (this.hiddenTypeNames != null && typeNamesContains(this.hiddenTypeNames, typeName))) {
+        typeNamesContains(this.hiddenTypeNames, typeName)) {
       return;
     }
-    if (this.typeToCheckBoxMap == null) {
-      this.typeToCheckBoxMap = new HashMap<Type, JCheckBox>();
-    }
     // have we seen this type before?
     JCheckBox typeCheckBox = this.typeToCheckBoxMap.get(type);
     if (typeCheckBox == null) {
       // There is no type checkbox representing this annotation yet.
       // if mTypeNameToColorMap exists, get color from there
-      if (this.typeColorMap == null) {
-        this.typeColorMap = new HashMap<String, Color>();
-      }
       Color typeColor = this.typeColorMap.get(typeName);
       if (typeColor == null) { // assign next available color
         typeColor = COLORS[this.typeColorMap.size() % COLORS.length];
@@ -1200,7 +1836,7 @@ public class CasAnnotationViewer extends
       // should type be initially selected?
       // document annotation is not initially selected in default case
       boolean selected = (this.initiallySelectedTypeNames == null &&
-          !CAS.TYPE_NAME_DOCUMENT_ANNOTATION.equals(typeName) && (this.typesNotChecked == null || !this.typesNotChecked.contains(typeName))) ||
+          !CAS.TYPE_NAME_DOCUMENT_ANNOTATION.equals(typeName) && !this.typesNotChecked.contains(typeName)) ||
           (this.initiallySelectedTypeNames != null && typeNamesContains(this.initiallySelectedTypeNames, typeName));
       // add checkbox
       typeCheckBox = new JCheckBox(type.getShortName(), selected);
@@ -1210,6 +1846,7 @@ public class CasAnnotationViewer extends
       typeCheckBox.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
+          resetSelectedAnnotationTree();
           display();
         }
       });
@@ -1246,11 +1883,6 @@ public class CasAnnotationViewer extends
     Document blank = new DefaultStyledDocument();
     this.textPane.setDocument(blank);
 
-    // make sure entityCheckboxPanel is showing
-    if (this.legendScrollPane.getViewport().getView() != this.entityCheckboxPanel) {
-      this.legendScrollPane.setViewportView(entityCheckboxPanel);
-    }
-
     // add text from CAS
     try {
       doc.remove(0, doc.getLength());
@@ -1284,9 +1916,9 @@ public class CasAnnotationViewer extends
     // add/remove checkboxes from display as determined by the
     // mHideUnselectedCheckboxes toggle
     for (JCheckBox entityCheckBox : this.entityToCheckBoxMap.values()) {
-      if (entityCheckBox.getParent() != this.entityCheckboxPanel &&
+      if (entityCheckBox.getParent() != this.entityCheckBoxVerticalScrollPanel &&
           (entityCheckBox.isSelected() || !this.hideUnselectedCheckBoxes)) {
-        this.entityCheckboxPanel.add(entityCheckBox);
+        this.entityCheckBoxVerticalScrollPanel.add(entityCheckBox);
       }
     }
 
@@ -1296,14 +1928,11 @@ public class CasAnnotationViewer extends
 
   private void processOneAnnotationInEntityView(StyledDocument doc, Annotation annotation) {
     // find out what entity this annotation represents
-    EntityResolver.Entity entity = this.entityResolver.getEntity(annotation);
+    Entity entity = this.entityResolver.getEntity(annotation);
     //if not an entity, skip it
     if (entity == null) {
       return;
     }
-    if (this.entityToCheckBoxMap == null) {
-      this.entityToCheckBoxMap = new HashMap<Entity, JCheckBox>();
-    }
     // have we seen this entity before?
     JCheckBox entityCheckBox = this.entityToCheckBoxMap.get(entity);
     if (entityCheckBox == null) {
@@ -1317,10 +1946,11 @@ public class CasAnnotationViewer extends
       entityCheckBox.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
+          resetSelectedAnnotationTree();
           display();
         }
       });
-      this.entityCheckboxPanel.add(entityCheckBox);
+      this.entityCheckBoxVerticalScrollPanel.add(entityCheckBox);
       // add to (Entity, Checkbox) map
       this.entityToCheckBoxMap.put(entity, entityCheckBox);
     }
@@ -1330,7 +1960,10 @@ public class CasAnnotationViewer extends
       int end = annotation.getEnd();
       // be careful of 0-length annotation. If we try to set background color when there
       // is no selection, it will set the input text style, which is not what we want.
-      if (begin != end) {
+      if (begin == 0 && end == this.cas.getDocumentText().length()) {
+        end--;
+      }
+      if (begin < end) {
         MutableAttributeSet attrs = new SimpleAttributeSet();
         StyleConstants.setBackground(attrs, entityCheckBox.getBackground());
         doc.setCharacterAttributes(begin, end - begin, attrs, false);
@@ -1401,13 +2034,13 @@ public class CasAnnotationViewer extends
     Type type = annotation.getType();
     switch (this.viewMode) {
       case MODE_ANNOTATIONS:
-        if (this.typeToCheckBoxMap == null || this.typeToCheckBoxMap.size() == 0) {
+        if (this.typeToCheckBoxMap.size() == 0) {
           return false;
         }
         JCheckBox typeCheckBox = this.typeToCheckBoxMap.get(type);
         return typeCheckBox != null && typeCheckBox.isSelected();
       case MODE_ENTITIES:
-        if (this.entityToCheckBoxMap == null || this.entityToCheckBoxMap.size() == 0) {
+        if (this.entityToCheckBoxMap.size() == 0) {
           return false;
         }
         Entity entity = this.entityResolver.getEntity(annotation);
@@ -1416,6 +2049,32 @@ public class CasAnnotationViewer extends
         }
         JCheckBox entityCheckBox = this.entityToCheckBoxMap.get(entity);
         return entityCheckBox != null && entityCheckBox.isSelected();
+      case MODE_FEATURES:
+        JRadioButton typeRadioButton = this.typeRadioButtonMap.get(type);
+        if (typeRadioButton == null || !typeRadioButton.isSelected()) {
+          return false;
+        }
+        List<Feature> features = type.getFeatures();
+        if (features == null || features.size() == 0) {
+          return false;
+        }
+        for (Feature feature : features) {
+          String featureName = feature.getShortName();
+          if (featureName == null || featureName.length() == 0) {
+            continue;
+          }
+          JRadioButton featureRadioButton = this.featureRadioButtonMap.get(featureName);
+          if (featureRadioButton == null || !featureRadioButton.isSelected()) {
+            continue;
+          }
+          String featureValue = this.getFeatureValueInString(annotation, feature);
+          if (featureValue == null || featureValue.length() == 0) {
+            continue;
+          }
+          JCheckBox featureValueCheckBox = this.featureValueCheckBoxMap.get(featureValue);
+          return featureValueCheckBox != null && featureValueCheckBox.isSelected();
+        }
+        break;
       default:
         break;
     }
@@ -1461,7 +2120,7 @@ public class CasAnnotationViewer extends
     for (Feature feature : features) {
       String featureName = feature.getShortName();
       // skip hidden features
-      if (this.hiddenFeatureNames != null && this.hiddenFeatureNames.contains(featureName)) {
+      if (this.hiddenFeatureNames.contains(featureName)) {
         continue;
       }
       // how we get feature value depends on feature's range type)
@@ -1876,7 +2535,7 @@ public class CasAnnotationViewer extends
      *      java.lang.Object, boolean, boolean, boolean, int, boolean)
      */
     @Override
-    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
+    public Component getTreeCellRendererComponent(JTree tree, final Object value, boolean sel,
             boolean expanded, boolean leaf, int row, boolean hasFocus) {
       // set background color if this is an Annotation or a Type
       Color background = null;
@@ -1891,15 +2550,36 @@ public class CasAnnotationViewer extends
         }
         if (type != null) {
           // look up checkbox to get color
-          JCheckBox checkbox = typeToCheckBoxMap.get(type);
-          if (checkbox != null) {
-            background = checkbox.getBackground();
-          }
+          background = typeColorMap.get(type.getName());
         }
       }
-      this.setBackgroundNonSelectionColor(background);
-      this.setBackgroundSelectionColor(background);
+      if (background != null) {
+        this.setBackgroundNonSelectionColor(background);
+        this.setBackgroundSelectionColor(background);
+      }
+      this.addKeyListener(new KeyListener() {
+        @Override
+        public void keyTyped(KeyEvent e) {
+        }
+
+        @Override
+        public void keyPressed(KeyEvent ke) {
+          if (ke == null || ke.getKeyCode() != KeyEvent.VK_C ||
+              (ke.getModifiers() & KeyEvent.CTRL_MASK) == 0) {
+            return;
+          }
+          String selection = ((DefaultMutableTreeNode) value).getUserObject().toString();
+          Clipboard clipboard = getToolkit().getSystemClipboard();
+          if (clipboard != null) {
+            StringSelection data = new StringSelection(selection);
+            clipboard.setContents(data, data);
+          }
+        }
 
+        @Override
+        public void keyReleased(KeyEvent e) {
+        }
+      });
       Component component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
               row, hasFocus);
       return component;