All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.uima.tools.viewer.CasAnnotationViewer Maven / Gradle / Ivy

There is a newer version: 3.5.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.tools.viewer;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.Scrollable;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.apache.uima.cas.ArrayFS;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.CASException;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.SofaFS;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.impl.BooleanArrayFSImpl;
import org.apache.uima.cas.impl.ByteArrayFSImpl;
import org.apache.uima.cas.impl.DoubleArrayFSImpl;
import org.apache.uima.cas.impl.FloatArrayFSImpl;
import org.apache.uima.cas.impl.IntArrayFSImpl;
import org.apache.uima.cas.impl.LongArrayFSImpl;
import org.apache.uima.cas.impl.ShortArrayFSImpl;
import org.apache.uima.cas.impl.StringArrayFSImpl;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.tcas.Annotation;

/**
 * A Swing component that displays annotations in a text pane with highlighting. There is also a
 * tree view for display details of annotations on which the user clicks. This class extends
 * {@link JPanel} and so can be reused within any Swing application.
 * 

* To launch the viewer, call the {@link #setCAS(CAS)} method with the CAS to be viewed. *

* The viewer is configurable via the following methods: *

    *
  • {@link #setConsistentColors(boolean)} - if set to true (default), the color assigned to any * annotation type will be the same across all documents. If set to false, colors may vary across * documents.
  • *
  • {@link #setDisplayedTypes(String[])} - specifies a set of types that will be highlighted in * the viewer's text pane.
  • *
  • {@link #setHiddenTypes(String[])} - specifies a set of types that will NOT be highlighted in * the viewer's text pane.
  • *
  • {@link #setHiddenFeatures(String[])} - specifies a set of features that will never shown in * the viewer's annotation details tree.
  • *
  • {@link #setHighFrequencyTypes(String[])} - this can be used to specify a set of types that * occur frequently. These types will the be assigned the most distinguishable colors.
  • *
  • {@link #setInitiallySelectedTypes(String[])} - this can be used to specify a set of types * that will initially be selected (i.e. have their checkboxes checked) in the viewer. The default * is for all types to be initially selected.
  • *
  • {@link #setRightToLeftTextOrientation(boolean)} - switches the text pane from left-to-right * (default) to right-to-left mode. This is needed to support languages such as Arabic and Hebrew, * which are read from right to left.
  • *
*/ public class CasAnnotationViewer extends JPanel implements ActionListener, MouseListener, TreeWillExpandListener, TreeExpansionListener, ItemListener { private static final long serialVersionUID = 3559118488371946999L; // Mode constants private static final short MODE_ANNOTATIONS = 0; private static final short MODE_ENTITIES = 1; private ArrayList userTypes = null; /** * @return Returns the userTypes. */ public ArrayList getUserTypes() { return userTypes; } /** * @param userTypes * The userTypes to set. */ public void setUserTypes(ArrayList userTypes) { this.userTypes = userTypes; } // colors to use for highlighting annotations // (use high brightness for best contrast against black text) private static final float BRIGHT = 0.95f; private static final Color[] COLORS = new Color[] { // low saturation colors are best, so put them first Color.getHSBColor(55f / 360, 0.25f, BRIGHT), // butter yellow? Color.getHSBColor(0f / 360, 0.25f, BRIGHT), // pink? Color.getHSBColor(210f / 360, 0.25f, BRIGHT), // baby blue? Color.getHSBColor(120f / 360, 0.25f, BRIGHT), // mint green? Color.getHSBColor(290f / 360, 0.25f, BRIGHT), // lavender? Color.getHSBColor(30f / 360, 0.25f, BRIGHT), // tangerine? Color.getHSBColor(80f / 360, 0.25f, BRIGHT), // celery green? Color.getHSBColor(330f / 360, 0.25f, BRIGHT), // light coral? Color.getHSBColor(160f / 360, 0.25f, BRIGHT), // aqua? Color.getHSBColor(250f / 360, 0.25f, BRIGHT), // light violet? // higher saturation colors Color.getHSBColor(55f / 360, 0.5f, BRIGHT), Color.getHSBColor(0f / 360, 0.5f, BRIGHT), Color.getHSBColor(210f / 360, 0.5f, BRIGHT), Color.getHSBColor(120f / 360, 0.5f, BRIGHT), Color.getHSBColor(290f / 360, 0.5f, BRIGHT), Color.getHSBColor(30f / 360, 0.5f, BRIGHT), Color.getHSBColor(80f / 360, 0.5f, BRIGHT), Color.getHSBColor(330f / 360, 0.5f, BRIGHT), Color.getHSBColor(160f / 360, 0.5f, BRIGHT), Color.getHSBColor(250f / 360, 0.5f, BRIGHT), // even higher saturation colors Color.getHSBColor(55f / 360, 0.75f, BRIGHT), Color.getHSBColor(0f / 360, 0.75f, BRIGHT), Color.getHSBColor(210f / 360, 0.75f, BRIGHT), Color.getHSBColor(120f / 360, 0.75f, BRIGHT), Color.getHSBColor(290f / 360, 0.75f, BRIGHT), Color.getHSBColor(30f / 360, 0.75f, BRIGHT), Color.getHSBColor(80f / 360, 0.75f, BRIGHT), Color.getHSBColor(330f / 360, 0.75f, BRIGHT), Color.getHSBColor(160f / 360, 0.75f, BRIGHT), Color.getHSBColor(250f / 360, 0.75f, BRIGHT) }; private static String[] DEFAULT_HIDDEN_FEATURES = { "sofa" }; private Map mTypeNameToColorMap = new HashMap(); private HashSet noCheckSet = new HashSet(); private List mHighFrequencyTypes = new ArrayList(); private Set mDisplayedTypeNames = null; private Set mHiddenTypeNames = new HashSet(); private Set mInitiallySelectedTypeNames = null; private Map mTypeToCheckboxMap = new HashMap(); private Map mEntityToCheckboxMap = new HashMap(); private CAS mCAS; private Type mStringType; private Type mFsArrayType; private boolean mConsistentColors = true; private Set mHiddenFeatureNames = new HashSet(); private boolean mEntityViewEnabled = false; private short mViewMode = MODE_ANNOTATIONS; private boolean mHideUnselectedCheckboxes = false; // GUI components private JSplitPane horizSplitPane; private JSplitPane vertSplitPane; private JScrollPane textScrollPane; private JTextPane textPane; private JPanel legendPanel; private JLabel legendLabel; private JScrollPane legendScrollPane; private JPanel annotationCheckboxPanel; private JPanel entityCheckboxPanel; private JPanel buttonPanel; private JButton selectAllButton; private JButton deselectAllButton; private JButton showHideUnselectedButton; private JTree selectedAnnotationTree; private DefaultTreeModel selectedAnnotationTreeModel; private JPanel viewModePanel; private JRadioButton annotationModeButton; private JRadioButton entityModeButton; private String[] mBoldfaceKeywords = new String[0]; private int[] mBoldfaceSpans = new int[0]; private JPanel sofaSelectionPanel; private JComboBox sofaSelectionComboBox; private EntityResolver mEntityResolver = new DefaultEntityResolver(); /** * Creates a CAS Annotation Viewer. */ public CasAnnotationViewer() { // create a horizonal JSplitPane horizSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); horizSplitPane.setResizeWeight(0.6); this.setLayout(new BorderLayout()); this.add(horizSplitPane); // create a vertical JSplitPane and add to left of horizSplitPane vertSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); vertSplitPane.setResizeWeight(0.8); vertSplitPane.setPreferredSize(new Dimension(620, 600)); vertSplitPane.setMinimumSize(new Dimension(200, 200)); horizSplitPane.setLeftComponent(vertSplitPane); // add JTextPane to top of vertical split pane textPane = new JTextPane(); textPane.setEditable(false); textPane.setPreferredSize(new Dimension(620, 400)); textPane.setMinimumSize(new Dimension(200, 100)); textScrollPane = new JScrollPane(textPane); vertSplitPane.setTopComponent(textScrollPane); // bottom pane is the legend, with checkboxes legendPanel = new JPanel(); legendPanel.setPreferredSize(new Dimension(620, 200)); legendPanel.setLayout(new BorderLayout()); legendLabel = new JLabel("Legend"); legendPanel.add(legendLabel, BorderLayout.NORTH); // checkboxes are contained in a scroll pane legendScrollPane = new JScrollPane(); legendScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); legendPanel.add(legendScrollPane, BorderLayout.CENTER); // there are two checkbox panels - one for annotations, one for entities annotationCheckboxPanel = new VerticallyScrollablePanel(); annotationCheckboxPanel.setLayout(new GridLayout(0, 5)); entityCheckboxPanel = new VerticallyScrollablePanel(); entityCheckboxPanel.setLayout(new GridLayout(0, 4)); // add annotation panel first, since that is the default legendScrollPane.setViewportView(annotationCheckboxPanel); // at very bottom is the Button Panel, with Select All, Deselect All, // and Hide/Show Unselected Buttons. It also may show the sofa-selection // combo box and/or the Viewer Mode radio group; either are both may // be hidden. buttonPanel = new JPanel(); selectAllButton = new JButton("Select All"); selectAllButton.addActionListener(this); buttonPanel.add(selectAllButton); deselectAllButton = new JButton("Deselect All"); deselectAllButton.addActionListener(this); buttonPanel.add(deselectAllButton); showHideUnselectedButton = new JButton("Hide Unselected"); showHideUnselectedButton.addActionListener(this); buttonPanel.add(showHideUnselectedButton); sofaSelectionPanel = new JPanel(); JLabel sofaSelectionLabel = new JLabel("Sofa:"); sofaSelectionPanel.add(sofaSelectionLabel); sofaSelectionComboBox = new JComboBox(); sofaSelectionPanel.add(sofaSelectionComboBox); sofaSelectionComboBox.addItemListener(this); buttonPanel.add(sofaSelectionPanel); viewModePanel = new JPanel(); viewModePanel.add(new JLabel("Mode: ")); annotationModeButton = new JRadioButton("Annotations"); annotationModeButton.setSelected(true); annotationModeButton.addActionListener(this); viewModePanel.add(annotationModeButton); entityModeButton = new JRadioButton("Entities"); entityModeButton.addActionListener(this); viewModePanel.add(entityModeButton); ButtonGroup group = new ButtonGroup(); group.add(annotationModeButton); group.add(entityModeButton); buttonPanel.add(viewModePanel); viewModePanel.setVisible(false); this.add(buttonPanel, BorderLayout.SOUTH); textPane.setMinimumSize(new Dimension(200, 100)); vertSplitPane.setBottomComponent(legendPanel); // right pane has a JTree selectedAnnotationTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode("Annotations")); selectedAnnotationTree = new JTree(selectedAnnotationTreeModel) { private static final long serialVersionUID = -7882967150283952907L; public Dimension getPreferredScrollableViewportSize() { return new Dimension(230, 500); } }; selectedAnnotationTree.setMinimumSize(new Dimension(50, 100)); selectedAnnotationTree.setScrollsOnExpand(true); selectedAnnotationTree.setRootVisible(true); selectedAnnotationTree.setCellRenderer(new AnnotationTreeCellRenderer()); selectedAnnotationTree.addTreeWillExpandListener(this); selectedAnnotationTree.addTreeExpansionListener(this); JPanel treePanel = new JPanel(); treePanel.setLayout(new BorderLayout()); treePanel.add(new JLabel("Click In Text to See Annotation Detail"), BorderLayout.NORTH); treePanel.add(new JScrollPane(selectedAnnotationTree), BorderLayout.CENTER); horizSplitPane.setRightComponent(treePanel); // add mouse listener to update annotation tree textPane.addMouseListener(this); // initialize hidden feature names map mHiddenFeatureNames.addAll(Arrays.asList(DEFAULT_HIDDEN_FEATURES)); } /** * @deprecated use the zero-argument constructor and call {@link #setEntityViewEnabled(boolean)} */ @Deprecated public CasAnnotationViewer(boolean aEntityViewEnabled) { this(); } /** * Set the list of types that occur most frequently. This method assigns the most distinguishable * colors to these types. * * @param aTypeNames * names of types that are occur frequently. Ideally these should be ordered by * frequency, with the most frequent being first. */ public void setHighFrequencyTypes(String[] aTypeNames) { // store these types for later mHighFrequencyTypes.clear(); mHighFrequencyTypes.addAll(Arrays.asList(aTypeNames)); mTypeNameToColorMap.clear(); assignColors(mHighFrequencyTypes); } /** * Set the list of types that will be highlighted in the viewer. Types not in this list will not * appear in the legend and will never be highlighted. If this method is not called, the default * is to show all types in the CAS (except those specifically hidden by a call to * {@link #setHiddenTypes(String[])}. * * @param aDisplayedTypeNames * names of types that are to be highlighted. Null indicates that all types in the CAS * should be highlighted. */ public void setDisplayedTypes(String[] aDisplayedTypeNames) { if (aDisplayedTypeNames == null) { mDisplayedTypeNames = null; } else { mDisplayedTypeNames = new HashSet(); mDisplayedTypeNames.addAll(Arrays.asList(aDisplayedTypeNames)); } } /** * Set the list of types that will NOT be highlighted in the viewer. * * @param aTypeNames * names of types that are never to be highlighted. */ public void setHiddenTypes(String[] aTypeNames) { mHiddenTypeNames.clear(); mHiddenTypeNames.addAll(Arrays.asList(aTypeNames)); } /** * Configures the initially selected types in the viewer. If not called, all types will be * initially selected. * * @param aTypeNames * array of fully-qualified names of types to be initially selected */ public void setInitiallySelectedTypes(String[] aTypeNames) { mInitiallySelectedTypeNames = new HashSet(); for (int i = 0; i < aTypeNames.length; i++) { mInitiallySelectedTypeNames.add(aTypeNames[i]); } // apply to existing checkboxes Iterator iterator = mTypeToCheckboxMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); String type = ((Type) entry.getKey()).getName(); JCheckBox checkbox = (JCheckBox) entry.getValue(); checkbox.setSelected(typeNamesContains(mInitiallySelectedTypeNames, type)); } // redisplay (if we have a CAS) - this allows this method to be called // either before or after displaying the viewer if (mCAS != null) { display(); } } /** * Configures the viewer to hide certain features in the annotation deatail pane. * * @param aFeatureNames * array of (short) feature names to be hidden */ public void setHiddenFeatures(String[] aFeatureNames) { mHiddenFeatureNames.clear(); // add default hidden features mHiddenFeatureNames.addAll(Arrays.asList(DEFAULT_HIDDEN_FEATURES)); // add user-defined hidden features mHiddenFeatureNames.addAll(Arrays.asList(aFeatureNames)); } /** * 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) { mEntityViewEnabled = 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 * to the same entity. */ public void setEntityResolver(EntityResolver aEntityResolver) { mEntityResolver = aEntityResolver; } /** * Sets whether colors will be consistent in all documents viewed using this viewer. If set to * true, assignments of color to annotation type will persist across documents; if false, colors * will be reassigned in each new document. (Note that if high frequency types are set via * {@link #setHighFrequencyTypes(String[])}, the colors for those types will always be * consistent, regardless of the value passed to this method. * * @param aConsistent * true (the default) causes colors to be consistent across documents, false allows them * to vary */ public void setConsistentColors(boolean aConsistent) { mConsistentColors = aConsistent; } /** * Sets the text orientation. The default is left-to-right, but needs to be set to right-to-left * to properly display some languages, most notably Arabic and Hebrew. * * @param aRightToLeft * true to put the viewer in right-to-left mode, false for left-to-right (the default). */ public void setRightToLeftTextOrientation(boolean aRightToLeft) { textPane.applyComponentOrientation(aRightToLeft ? ComponentOrientation.RIGHT_TO_LEFT : ComponentOrientation.LEFT_TO_RIGHT); } /** * Sets whether unselected (unchecked) checkboxes will be hidden entirely from the legend. This * mode makes for a cleaner legend at the expense of making it more difficult to toggle which * types are selected. There's also a button in the GUI that lets the user change this setting. * * @param aHideUnselected */ public void setHideUnselectedCheckboxes(boolean aHideUnselected) { mHideUnselectedCheckboxes = aHideUnselected; display(); } /** * Sets the CAS to be viewed. This must be called before {@link #display()}. * * @param aCAS * the CSA to be viewed */ public void setCAS(CAS aCAS) { mCAS = aCAS; mStringType = mCAS.getTypeSystem().getType(CAS.TYPE_NAME_STRING); mFsArrayType = mCAS.getTypeSystem().getType(CAS.TYPE_NAME_FS_ARRAY); // clear checkbox panel so it will be repopulated annotationCheckboxPanel.removeAll(); entityCheckboxPanel.removeAll(); mTypeToCheckboxMap.clear(); mEntityToCheckboxMap.clear(); // clear selected annotation details tree this.updateSelectedAnnotationTree(-1); // clear type to color map if color consistency is off if (!mConsistentColors) { mTypeNameToColorMap.clear(); // but reassign colors to high frequency types assignColors(mHighFrequencyTypes); } // clear boldface mBoldfaceKeywords = new String[0]; mBoldfaceSpans = new int[0]; // enable or disable entity view depending on user's choice this.viewModePanel.setVisible(mEntityViewEnabled); // Populate sofa combo box with the names of all text Sofas in the CAS sofaSelectionComboBox.removeAllItems(); Iterator sofas = aCAS.getSofaIterator(); Feature sofaIdFeat = aCAS.getTypeSystem().getFeatureByFullName(CAS.FEATURE_FULL_NAME_SOFAID); boolean nonDefaultSofaFound = false; while (sofas.hasNext()) { SofaFS sofa = (SofaFS) sofas.next(); if (sofa.getLocalStringData() != null) { String sofaId = sofa.getStringValue(sofaIdFeat); if (CAS.NAME_DEFAULT_SOFA.equals(sofaId)) { sofaId = "DEFAULT"; // make nicer display } else { nonDefaultSofaFound = true; } sofaSelectionComboBox.addItem(sofaId); // if this sofa matches the view passed to this method, select it CAS viewOfSofa = aCAS.getView(sofa); if (viewOfSofa == aCAS) { sofaSelectionComboBox.setSelectedIndex(sofaSelectionComboBox.getItemCount() - 1); } } } if (sofaSelectionComboBox.getItemCount() == 0) { throw new RuntimeException("This CAS contains no document to view."); } // make sofa selector visible if any text sofa other than the default was found sofaSelectionPanel.setVisible(nonDefaultSofaFound); // Note that selection of the Sofa from the combo box happens during // population, and that triggers the call to display() to display // that document and its annotations/entities. // display(); } /** * Causes the specified words to appear in boldface wherever they occur in the document. This is * case-insensitive. Call this method after {@link #setCAS(CAS)}. It wil apply only to the current * document, and will be reset on the next call to {@link #setCAS(CAS)}. * * @param aWords * array of words to highlight in boldface. */ public void applyBoldfaceToKeywords(String[] aWords) { mBoldfaceKeywords = aWords; doBoldface(); } /** * Causes the specified spans to appear in boldface. This is case-insensitive. Call this method * after {@link #setCAS(CAS)}. It wil apply only to the current document, and will be reset on the * next call to {@link #setCAS(CAS)}. * * @param aSpans * spans to appear in boldface (begin1, end1, begin2, end2, ...) */ public void applyBoldfaceToSpans(int[] aSpans) { mBoldfaceSpans = aSpans; doBoldface(); } /** * Configures the viewer appropriately for displaying a hit against an XML fragments query. This * does not use a sophisticated algorithm for determining the location of the document that * matched the query. Currently all it does is call {@link #setInitiallySelectedTypes(String[])} * with the list of types mentioned in the query and {@link #applyBoldfaceToKeywords(String[])} on * any keywords mentioned in the query. * * @param aQuery * an XML fragments query * @param aTypeNamespace * namespace to prepend to the element names in the query in order to form * fully-qualified CAS type names. This is optional; if not specified, type namespaces * are ignored and any type whose local name matches the query will be selected. */ public void configureViewForXmlFragmentsQuery(String aQuery, String aTypeNamespace) { // need to parse query and produce type list and keyword list List typeList = new ArrayList(); List keywordList = new ArrayList(); String delims = "<>+-*\" \t\n"; StringTokenizer tokenizer = new StringTokenizer(aQuery, delims, true); boolean inTag = false; while (tokenizer.hasMoreTokens()) { String tok = tokenizer.nextToken(); if ("<".equals(tok)) { inTag = true; } else if (">".equals(tok) && inTag) { inTag = false; } else if (delims.indexOf(tok) == -1) // token is not a delimiter { if (inTag) { if (!tok.startsWith("/")) // ignore end tags { if (tok.endsWith("/")) { tok = tok.substring(0, tok.length() - 1); // strip trailing / from empty tags } typeList.add(aTypeNamespace + '.' + tok); } } else { keywordList.add(tok); } } } // System.out.println(typeList); // System.out.println(keywordList); setInitiallySelectedTypes((String[]) typeList.toArray(new String[0])); display(); applyBoldfaceToKeywords((String[]) keywordList.toArray(new String[0])); } /** * Configures the viewer appropriately for displaying a hit against an XML fragments query. This * does not use a sophisticated algorithm for determining the location of the document that * matched the query. Currently all it does is call {@link #setInitiallySelectedTypes(String[])} * with the list of types mentioned in the query and {@link #applyBoldfaceToKeywords(String[])} on * any keywords mentioned in the query. * * @param aQuery * an XML fragments query */ public void configureViewForXmlFragmentsQuery(String aQuery) { configureViewForXmlFragmentsQuery(aQuery, "*"); } /** * Assign initially checked to the specified types, pairing up down the lists * * @param aNotChecked * list of types not to be initially checked JMP */ public void assignCheckedFromList(ArrayList aNotChecked) { Iterator iterC = aNotChecked.iterator(); while (iterC.hasNext()) { String typeName = (String) iterC.next(); // assign to list of types not to be initially checked noCheckSet.add(typeName); } } /** * Assign colors to the specified types, pairing up down the lists * * @param aColors * list of colors * @param aTypeNames * list of type names JMP */ public void assignColorsFromList(List aColors, ArrayList aTypeNames) { // populate mTypeNameToColorMap Iterator iter = aTypeNames.iterator(); Iterator iterC = aColors.iterator(); while (iter.hasNext()) { if (!iterC.hasNext()) break; String typeName = (String) iter.next(); Color color = (Color) iterC.next(); // assign background color mTypeNameToColorMap.put(typeName, color); } setUserTypes(aTypeNames); // clear checkbox panel so it will be refreshed annotationCheckboxPanel.removeAll(); mTypeToCheckboxMap.clear(); } /** * Assign colors to the specified types * * @param aTypeNames * list of type names */ private void assignColors(List aTypeNames) { // populate mTypeNameToColorMap Iterator iter = aTypeNames.iterator(); while (iter.hasNext()) { String typeName = (String) iter.next(); // assign background color Color c = COLORS[mTypeNameToColorMap.size() % COLORS.length]; mTypeNameToColorMap.put(typeName, c); } // clear checkbox panel so it will be refreshed annotationCheckboxPanel.removeAll(); mTypeToCheckboxMap.clear(); } /** * Creates/updates the display. This is called when setCAS() is called and again each time to * user's mode or checkbox selections change. */ private void display() { // remember split pane divider location so we can restore it later int dividerLoc = vertSplitPane.getDividerLocation(); // remember caret pos and scroll position int caretPos = this.textPane.getCaretPosition(); int verticalScrollPos = this.textScrollPane.getVerticalScrollBar().getValue(); // type of display depends on whether we are in annotation or entity mode switch (mViewMode) { case MODE_ANNOTATIONS: displayAnnotations(); break; case MODE_ENTITIES: displayEntities(); break; } // apply boldface to keywords and spans as indicated by user doBoldface(); // update the label of the Show/Hide Unselected Button if (mHideUnselectedCheckboxes) { showHideUnselectedButton.setText("Show Unselected"); } else { showHideUnselectedButton.setText("Hide Unselected"); } // reset scroll position textPane.setCaretPosition(caretPos); textScrollPane.getVerticalScrollBar().setValue(verticalScrollPos); textScrollPane.revalidate(); // reset split pane divider vertSplitPane.setDividerLocation(dividerLoc); } /** * Creates the annotation display. */ private void displayAnnotations() { // for speed, detach document from text pane before updating StyledDocument doc = (StyledDocument) textPane.getDocument(); Document blank = new DefaultStyledDocument(); textPane.setDocument(blank); // make sure annotationCheckboxPanel is showing if (legendScrollPane.getViewport().getView() != annotationCheckboxPanel) { legendScrollPane.setViewportView(annotationCheckboxPanel); } // add text from CAS try { doc.remove(0, doc.getLength()); doc.insertString(0, mCAS.getDocumentText(), new SimpleAttributeSet()); } catch (BadLocationException e) { throw new RuntimeException(e); } // Iterate over annotations FSIterator iter = mCAS.getAnnotationIndex().iterator(); Hashtable checkBoxes = new Hashtable(); HashSet checkBoxesDone = new HashSet(); while (iter.isValid()) { AnnotationFS fs = (AnnotationFS) iter.get(); iter.moveToNext(); Type type = fs.getType(); // have we seen this type before? JCheckBox checkbox = (JCheckBox) mTypeToCheckboxMap.get(type); if (checkbox == null) { // check that type should be displayed if ((mDisplayedTypeNames == null || typeNamesContains(mDisplayedTypeNames, type.getName())) && !typeNamesContains(mHiddenTypeNames, type.getName())) { // if mTypeNameToColorMap exists, get color from there Color c = (Color) mTypeNameToColorMap.get(type.getName()); if (c == null) // assign next available color { c = COLORS[mTypeNameToColorMap.size() % COLORS.length]; mTypeNameToColorMap.put(type.getName(), c); } // This next section required until priorities work properly // HashSet noCheckSet = new HashSet(); String noCheckArray[] = { // "org.apache.jresporator.PROPER", // "DOCSTRUCT_ANNOT_TYPE", // "VOCAB_ANNOT_TYPE" }; for (int i = 0; i < noCheckArray.length; i++) { noCheckSet.add(noCheckArray[i]); } // end of section // should type be initially selected? boolean selected = ((mInitiallySelectedTypeNames == null && // document annotation is not initially selected in default case !CAS.TYPE_NAME_DOCUMENT_ANNOTATION.equals(type.getName()) && !noCheckSet .contains(type.getName()) // priorities JMP ) || (mInitiallySelectedTypeNames != null && typeNamesContains( mInitiallySelectedTypeNames, type.getName()))); // add checkbox checkbox = new JCheckBox(type.getShortName(), selected); checkbox.setToolTipText(type.getName()); checkbox.addActionListener(this); checkbox.setBackground(c); // annotationCheckboxPanel.add(checkbox); do it later JMP checkBoxes.put(type.getName(), checkbox); checkBoxesDone.add(checkbox); // add to (Type, Checkbox) map mTypeToCheckboxMap.put(type, checkbox); } else { // this type is not hidden, skip it continue; } } // if checkbox is checked, assign color to text if (checkbox.isSelected()) { int begin = fs.getBegin(); int end = fs.getEnd(); // Be careful of 0-length annotations and annotations that span the // entire document. In either of these cases, if we try to set // background color, it will set the input text style, which is not // what we want. if (begin == 0 && end == mCAS.getDocumentText().length()) { end--; } if (begin < end) { MutableAttributeSet attrs = new SimpleAttributeSet(); StyleConstants.setBackground(attrs, checkbox.getBackground()); doc.setCharacterAttributes(begin, end - begin, attrs, false); } } } // now populate panel with checkboxes in order specified in user file. JMP ArrayList aTypeNames = getUserTypes(); if (aTypeNames != null) { Iterator iterT = aTypeNames.iterator(); while (iterT.hasNext()) { String typeName = (String) iterT.next(); JCheckBox cb = (JCheckBox) checkBoxes.get(typeName); if (cb != null) { annotationCheckboxPanel.add(cb); checkBoxesDone.remove(cb); } } } // add additional checkboxes in alphabetical order LinkedList checkboxes = new LinkedList(checkBoxesDone); Collections.sort(checkboxes, new Comparator() { public int compare(Object o1, Object o2) { return ((JCheckBox) o1).getText().toLowerCase().compareTo( ((JCheckBox) o2).getText().toLowerCase()); } }); Iterator iterC = checkboxes.iterator(); while (iterC.hasNext()) { JCheckBox cb = (JCheckBox) iterC.next(); annotationCheckboxPanel.add(cb); } // add/remove checkboxes from display as determined by the // mHideUnselectedCheckboxes toggle Iterator cbIter = mTypeToCheckboxMap.values().iterator(); while (cbIter.hasNext()) { JCheckBox cb = (JCheckBox) cbIter.next(); if (mHideUnselectedCheckboxes && !cb.isSelected()) { if (cb.getParent() == annotationCheckboxPanel) { annotationCheckboxPanel.remove(cb); } } else if (cb.getParent() != annotationCheckboxPanel) { annotationCheckboxPanel.add(cb); } } // reattach document to text pane textPane.setDocument(doc); } /** * Creates the entity display. */ private void displayEntities() { // for speed, detach document from text pane before updating StyledDocument doc = (StyledDocument) textPane.getDocument(); Document blank = new DefaultStyledDocument(); textPane.setDocument(blank); // make sure entityCheckboxPanel is showing if (legendScrollPane.getViewport().getView() != entityCheckboxPanel) { legendScrollPane.setViewportView(entityCheckboxPanel); } // add text from CAS try { doc.remove(0, doc.getLength()); doc.insertString(0, mCAS.getDocumentText(), new SimpleAttributeSet()); } catch (BadLocationException e) { throw new RuntimeException(e); } // Iterate over EntityAnnotations using JCAS, because the EntityResolver interface // uses JCAS as a convenience to the user. JCas jcas; try { // NOTE: for a large type system, this can take a few seconds, which results in a // noticeable delay when the user first switches to Entity mode. jcas = mCAS.getJCas(); } catch (CASException e) { throw new RuntimeException(e); } FSIterator iter = jcas.getAnnotationIndex().iterator(); while (iter.isValid()) { Annotation annot = (Annotation) iter.get(); iter.moveToNext(); // find out what entity this annotation represents EntityResolver.Entity entity = mEntityResolver.getEntity(annot); //if not an entity, skip it if (entity == null) continue; // have we seen this entity before? JCheckBox checkbox = (JCheckBox) mEntityToCheckboxMap.get(entity); if (checkbox == null) { // assign next available color Color c = COLORS[mEntityToCheckboxMap.size() % COLORS.length]; // add checkbox checkbox = new JCheckBox(entity.getCanonicalForm(), true); checkbox.setToolTipText(entity.getCanonicalForm()); checkbox.addActionListener(this); checkbox.setBackground(c); entityCheckboxPanel.add(checkbox); // add to (Entity, Checkbox) map mEntityToCheckboxMap.put(entity, checkbox); } // if checkbox is checked, assign color to text if (checkbox.isSelected()) { int begin = annot.getBegin(); int end = annot.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) { MutableAttributeSet attrs = new SimpleAttributeSet(); StyleConstants.setBackground(attrs, checkbox.getBackground()); doc.setCharacterAttributes(begin, end - begin, attrs, false); } } } // add/remove checkboxes from display as determined by the // mHideUnselectedCheckboxes toggle Iterator cbIter = mEntityToCheckboxMap.values().iterator(); while (cbIter.hasNext()) { JCheckBox cb = (JCheckBox) cbIter.next(); if (mHideUnselectedCheckboxes && !cb.isSelected()) { if (cb.getParent() == entityCheckboxPanel) { entityCheckboxPanel.remove(cb); } } else if (cb.getParent() != entityCheckboxPanel) { entityCheckboxPanel.add(cb); } } // reattach document to text pane textPane.setDocument(doc); } /** * Refreshes the selected annotation tree. * * @param aPosition * the currently selected offset into the document. All annotations overlapping this * point will be rendered in the tree. */ private void updateSelectedAnnotationTree(int aPosition) { DefaultMutableTreeNode root = (DefaultMutableTreeNode) this.selectedAnnotationTreeModel .getRoot(); root.removeAllChildren(); FSIterator annotIter = this.mCAS.getAnnotationIndex().iterator(); while (annotIter.isValid()) { AnnotationFS annot = (AnnotationFS) annotIter.get(); // if (getPanePosition(annot.getBegin()) <= aPosition // && getPanePosition(annot.getEnd()) > aPosition) if (annot.getBegin() <= aPosition && annot.getEnd() > aPosition) { JCheckBox checkbox = (JCheckBox) mTypeToCheckboxMap.get(annot.getType()); if (checkbox != null && checkbox.isSelected()) { addAnnotationToTree(annot); } } // else if (getPanePosition(annot.getBegin()) > aPosition) else if (annot.getBegin() > aPosition) break; annotIter.moveToNext(); } this.selectedAnnotationTreeModel.nodeStructureChanged(root); // expand first level // int row = 0; // while (row < this.selectedAnnotationTree.getRowCount()) // { // if (this.selectedAnnotationTree.getPathForRow(row).getPathCount() <= 2) // { // this.selectedAnnotationTree.expandRow(row); // } // row++; // } // hmmm.. how to get scroll pane to resize properly?? this.selectedAnnotationTree.treeDidChange(); // this.selectedAnnotationTree.setPreferredSize(this.selectedAnnotationTree.getSize()); this.selectedAnnotationTree.revalidate(); this.horizSplitPane.revalidate(); } /** * Adds an annotation to the selected annotations tree. Annotations in the tree are grouped by * type. * * @param aAnnotation * the annotation to add */ protected void addAnnotationToTree(AnnotationFS aAnnotation) { DefaultMutableTreeNode root = (DefaultMutableTreeNode) this.selectedAnnotationTreeModel .getRoot(); // try to find a node for the type DefaultMutableTreeNode typeNode = null; Enumeration typeNodes = root.children(); while (typeNodes.hasMoreElements()) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) typeNodes.nextElement(); if (aAnnotation.getType().equals(((TypeTreeNodeObject) node.getUserObject()).getType())) { typeNode = node; break; } } if (typeNode == null) { typeNode = new DefaultMutableTreeNode(new TypeTreeNodeObject(aAnnotation.getType())); root.insert(typeNode, 0); } // add annotation node DefaultMutableTreeNode annotationNode = new DefaultMutableTreeNode(new FsTreeNodeObject( aAnnotation, null)); typeNode.insert(annotationNode, 0); // add child nodes for features addFeatureTreeNodes(annotationNode, aAnnotation); } private void addFeatureTreeNodes(DefaultMutableTreeNode aParentNode, FeatureStructure aFS) { List aFeatures = aFS.getType().getFeatures(); Iterator iter = aFeatures.iterator(); while (iter.hasNext()) { Feature feat = (Feature) iter.next(); String featName = feat.getShortName(); // skip hidden features if (mHiddenFeatureNames.contains(featName)) { continue; } // how we get feature value depends on feature's range type) String featVal = "null"; Type rangeType = feat.getRange(); String rangeTypeName = rangeType.getName(); if (mCAS.getTypeSystem().subsumes(mStringType, rangeType)) { featVal = aFS.getStringValue(feat); if (featVal == null) { featVal = "null"; } else if (featVal.length() > 64) { featVal = featVal.substring(0, 64) + "..."; } } else if (rangeType.isPrimitive()) { featVal = aFS.getFeatureValueAsString(feat); } else if (mCAS.getTypeSystem().subsumes(mFsArrayType, rangeType)) { ArrayFS arrayFS = (ArrayFS) aFS.getFeatureValue(feat); if (arrayFS != null) { // Add featName = FSArray node, then add each array element as a child DefaultMutableTreeNode arrayNode = new DefaultMutableTreeNode(featName + " = FSArray"); for (int i = 0; i < arrayFS.size(); i++) { FeatureStructure fsVal = arrayFS.get(i); if (fsVal != null) { // Add the FS node and a dummy child, so that user can expand it. // When user expands it, new nodes for feature values will be created. DefaultMutableTreeNode fsValNode = new DefaultMutableTreeNode(new FsTreeNodeObject( fsVal, featName)); if (!fsVal.getType().getFeatures().isEmpty()) { fsValNode.add(new DefaultMutableTreeNode(null)); } arrayNode.add(fsValNode); } else { arrayNode.add(new DefaultMutableTreeNode("null")); } } aParentNode.add(arrayNode); continue; } } else if (rangeType.isArray()) // primitive array { String[] vals = null; if (CAS.TYPE_NAME_STRING_ARRAY.equals(rangeTypeName)) { StringArrayFSImpl arrayFS = (StringArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toArray(); } else if (CAS.TYPE_NAME_INTEGER_ARRAY.equals(rangeTypeName)) { IntArrayFSImpl arrayFS = (IntArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toStringArray(); } else if (CAS.TYPE_NAME_FLOAT_ARRAY.equals(rangeTypeName)) { FloatArrayFSImpl arrayFS = (FloatArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toStringArray(); } else if (CAS.TYPE_NAME_BOOLEAN_ARRAY.equals(rangeTypeName)) { BooleanArrayFSImpl arrayFS = (BooleanArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toStringArray(); } else if (CAS.TYPE_NAME_BYTE_ARRAY.equals(rangeTypeName)) { ByteArrayFSImpl arrayFS = (ByteArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toStringArray(); } else if (CAS.TYPE_NAME_SHORT_ARRAY.equals(rangeTypeName)) { ShortArrayFSImpl arrayFS = (ShortArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toStringArray(); } else if (CAS.TYPE_NAME_LONG_ARRAY.equals(rangeTypeName)) { LongArrayFSImpl arrayFS = (LongArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toStringArray(); } if (CAS.TYPE_NAME_DOUBLE_ARRAY.equals(rangeTypeName)) { DoubleArrayFSImpl arrayFS = (DoubleArrayFSImpl) aFS.getFeatureValue(feat); if (arrayFS != null) vals = arrayFS.toStringArray(); } if (vals == null) { featVal = "null"; } else { StringBuffer displayVal = new StringBuffer(); displayVal.append('['); for (int i = 0; i < vals.length - 1; i++) { displayVal.append(vals[i]); displayVal.append(','); } if (vals.length > 0) { displayVal.append(vals[vals.length - 1]); } displayVal.append(']'); featVal = displayVal.toString(); } } else // single feature value { FeatureStructure fsVal = aFS.getFeatureValue(feat); if (fsVal != null) { // Add the FS node and a dummy child, so that user can expand it. // When user expands it, new nodes for feature values will be created. DefaultMutableTreeNode fsValNode = new DefaultMutableTreeNode(new FsTreeNodeObject(fsVal, featName)); if (!fsVal.getType().getFeatures().isEmpty()) { fsValNode.add(new DefaultMutableTreeNode(null)); } aParentNode.add(fsValNode); continue; } } aParentNode.add(new DefaultMutableTreeNode(featName + " = " + featVal)); } } /** * Does wildcard matching to determine if a give type name is "contained" in a Set of type names. * * @param names * Type names, which may include wildcards (e.g, uima.tt.*) * @param name * A type name * @return True iff name matches a name in type names */ private boolean typeNamesContains(Set names, String name) { if (names.contains(name)) return true; else { Iterator namesIterator = names.iterator(); while (namesIterator.hasNext()) { String otherName = (String) namesIterator.next(); if (otherName.indexOf('*') != -1) { if (wildCardMatch(name, otherName)) { return true; } } else { if (otherName.equalsIgnoreCase(name)) { return true; } } } } return false; } /** * Helper for {@link #typeNamesContains(HashSet, String)}. * * @param s * A litteral string * @param pattern * A string that includes one or more *'s as wildcards * @return True iff the string matches the pattern. */ private boolean wildCardMatch(String s, String pattern) { StringBuffer regexpPatternBuffer = new StringBuffer(); for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); if (c == '*') regexpPatternBuffer.append('.'); else if (c == '.') regexpPatternBuffer.append('\\'); if (Character.isLetter(c)) { regexpPatternBuffer.append('(').append(Character.toLowerCase(c)).append('|').append( Character.toUpperCase(c)).append(')'); } else { regexpPatternBuffer.append(c); } } return s.matches(new String(regexpPatternBuffer)); } /** * @see java.awt.Component#setSize(Dimension) */ public void setSize(Dimension d) { super.setSize(d); Insets insets = getInsets(); Dimension paneSize = new Dimension(d.width - insets.left - insets.right, d.height - insets.top - insets.bottom); horizSplitPane.setPreferredSize(paneSize); horizSplitPane.setSize(paneSize); } /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(ActionEvent e) { if (e.getSource() == selectAllButton) { Iterator cbIter = (mViewMode == MODE_ANNOTATIONS) ? mTypeToCheckboxMap.values().iterator() : mEntityToCheckboxMap.values().iterator(); while (cbIter.hasNext()) { ((JCheckBox) cbIter.next()).setSelected(true); } display(); } else if (e.getSource() == deselectAllButton) { Iterator cbIter = (mViewMode == MODE_ANNOTATIONS) ? mTypeToCheckboxMap.values().iterator() : mEntityToCheckboxMap.values().iterator(); while (cbIter.hasNext()) { ((JCheckBox) cbIter.next()).setSelected(false); } display(); } else if (e.getSource() == annotationModeButton) { mViewMode = MODE_ANNOTATIONS; display(); } else if (e.getSource() == entityModeButton) { mViewMode = MODE_ENTITIES; display(); // make sure we clear the annotation tree when we go into entity mode // this.updateSelectedAnnotationTree(0); } else if (e.getSource() == showHideUnselectedButton) { mHideUnselectedCheckboxes = !mHideUnselectedCheckboxes; display(); } else if (e.getSource() instanceof JCheckBox) { display(); } } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) */ public void mouseClicked(MouseEvent e) { if (mViewMode == MODE_ANNOTATIONS) { int pos = textPane.viewToModel(e.getPoint()); this.updateSelectedAnnotationTree(pos); } } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent) */ public void mouseEntered(MouseEvent e) { } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent) */ public void mouseExited(MouseEvent e) { } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ public void mousePressed(MouseEvent e) { } /* * (non-Javadoc) * * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ public void mouseReleased(MouseEvent e) { } /** * Inner class containing data for a tree node representing a FeatureStructure */ static class FsTreeNodeObject { public FsTreeNodeObject(FeatureStructure aFS, String aFeatureName) { mFS = aFS; mFeatureName = aFeatureName; mCaption = mFS.getType().getShortName(); if (mFS instanceof AnnotationFS) { String coveredText = ((AnnotationFS) mFS).getCoveredText(); if (coveredText.length() > 64) coveredText = coveredText.substring(0, 64) + "..."; mCaption += " (\"" + coveredText + "\")"; } if (mFeatureName != null) { mCaption = mFeatureName + " = " + mCaption; } } public FeatureStructure getFS() { return mFS; } public String toString() { return mCaption; } private FeatureStructure mFS; private String mFeatureName; private String mCaption; } /** * Inner class containing data for a tree node representing a Type */ static class TypeTreeNodeObject { public TypeTreeNodeObject(Type aType) { mType = aType; } public Type getType() { return mType; } public String toString() { return mType.getShortName(); } private Type mType; } class AnnotationTreeCellRenderer extends DefaultTreeCellRenderer { private static final long serialVersionUID = -8661556785397184756L; /* * (non-Javadoc) * * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree, * java.lang.Object, boolean, boolean, boolean, int, boolean) */ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean aHasFocus) { // set background color if this is an Annotation or a Type Color background = null; if (value instanceof DefaultMutableTreeNode) { Object userObj = ((DefaultMutableTreeNode) value).getUserObject(); Type type = null; if (userObj instanceof FsTreeNodeObject) { FeatureStructure fs = ((FsTreeNodeObject) userObj).getFS(); type = fs.getType(); } else if (userObj instanceof TypeTreeNodeObject) { type = ((TypeTreeNodeObject) userObj).getType(); } if (type != null) { // look up checkbox to get color JCheckBox checkbox = (JCheckBox) mTypeToCheckboxMap.get(type); if (checkbox != null) { background = checkbox.getBackground(); } } } this.setBackgroundNonSelectionColor(background); this.setBackgroundSelectionColor(background); Component component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, aHasFocus); return component; } } /* * (non-Javadoc) * * @see javax.swing.event.TreeWillExpandListener#treeWillCollapse(javax.swing.event.TreeExpansionEvent) */ public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { } /* * (non-Javadoc) * * @see javax.swing.event.TreeWillExpandListener#treeWillExpand(javax.swing.event.TreeExpansionEvent) */ public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { // if FS node is expanded and it has a dummy child, replace with // feature value nodes (this is what lets us do infinite tree) Object lastPathComponent = event.getPath().getLastPathComponent(); if (lastPathComponent instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode expandedNode = (DefaultMutableTreeNode) lastPathComponent; Object userObj = expandedNode.getUserObject(); if (userObj instanceof FsTreeNodeObject) { TreeNode firstChild = expandedNode.getFirstChild(); if (firstChild instanceof DefaultMutableTreeNode && ((DefaultMutableTreeNode) firstChild).getUserObject() == null) { expandedNode.removeAllChildren(); FeatureStructure fs = ((FsTreeNodeObject) userObj).getFS(); addFeatureTreeNodes(expandedNode, fs); ((JTree) event.getSource()).treeDidChange(); } } } } /* * (non-Javadoc) * * @see javax.swing.event.TreeExpansionListener#treeCollapsed(javax.swing.event.TreeExpansionEvent) */ public void treeCollapsed(TreeExpansionEvent event) { } /* * (non-Javadoc) * * @see javax.swing.event.TreeExpansionListener#treeExpanded(javax.swing.event.TreeExpansionEvent) */ public void treeExpanded(TreeExpansionEvent event) { // if a Type node is expanded and has only one child, // also expand this child (a usability improvement) Object lastPathComponent = event.getPath().getLastPathComponent(); if (lastPathComponent instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode expandedNode = (DefaultMutableTreeNode) lastPathComponent; Object userObj = expandedNode.getUserObject(); if (userObj instanceof TypeTreeNodeObject && expandedNode.getChildCount() == 1) { TreePath childPath = event.getPath().pathByAddingChild(expandedNode.getFirstChild()); ((JTree) event.getSource()).expandPath(childPath); ((JTree) event.getSource()).treeDidChange(); } } } /* * (non-Javadoc) * * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) */ public void itemStateChanged(ItemEvent e) { if (e.getSource() == sofaSelectionComboBox) { // a new sofa was selected. Switch to that view and update display String sofaId = (String) e.getItem(); if ("DEFAULT".equals(sofaId)) { mCAS = mCAS.getView(CAS.NAME_DEFAULT_SOFA); } else { mCAS = mCAS.getView(sofaId); } display(); } } /** * Applies boldface as per the mBoldfaceKeywords and mBoldfaceSpans fields. */ private void doBoldface() { // Keywords if (mBoldfaceKeywords.length > 0) { // build regular expression StringBuffer regEx = new StringBuffer(); for (int i = 0; i < mBoldfaceKeywords.length; i++) { if (i > 0) { regEx.append('|'); } regEx.append("\\b"); String word = mBoldfaceKeywords[i]; for (int j = 0; j < word.length(); j++) { char c = word.charAt(j); if (Character.isLetter(c)) { regEx.append('[').append(Character.toLowerCase(c)).append(Character.toUpperCase(c)) .append(']'); } else if (c == '.' || c == '^' || c == '&' || c == '\\' || c == '(' || c == ')') { regEx.append('\\').append(c); } else { regEx.append('c'); } } regEx.append("\\b"); } // System.out.println("RegEx: " + regEx); Pattern pattern = Pattern.compile(regEx.toString()); Matcher matcher = pattern.matcher(mCAS.getDocumentText()); // match int pos = 0; while (matcher.find(pos)) { MutableAttributeSet attrs = new SimpleAttributeSet(); StyleConstants.setBold(attrs, true); StyledDocument doc = (StyledDocument) textPane.getDocument(); doc.setCharacterAttributes(matcher.start(), matcher.end() - matcher.start(), attrs, false); if (pos == matcher.end()) // infinite loop check break; else pos = matcher.end(); } } // Spans int docLength = mCAS.getDocumentText().length(); int len = mBoldfaceSpans.length; len -= len % 2; // to avoid ArrayIndexOutOfBoundsException if some numbskull passes in an // odd-length array int i = 0; while (i < len) { int begin = mBoldfaceSpans[i]; int end = mBoldfaceSpans[i + 1]; if (begin >= 0 && begin <= docLength && end >= 0 && end <= docLength) { MutableAttributeSet attrs = new SimpleAttributeSet(); StyleConstants.setBold(attrs, true); StyledDocument doc = (StyledDocument) textPane.getDocument(); doc.setCharacterAttributes(begin, end - begin, attrs, false); } i += 2; } } /** * Gets the selected annotation tree component. * * @return the tree that displays annotation details about annotations overlapping the point where * the user last clicked in the text. */ protected JTree getSelectedAnnotationTree() { return this.selectedAnnotationTree; } /** * A panel that is to be placed in a JScrollPane that can only scroll vertically. This panel * should have its width track the viewport's width, and increase its height as necessary to * display all components. * * */ static class VerticallyScrollablePanel extends JPanel implements Scrollable { private static final long serialVersionUID = 1009744410018634511L; /* * (non-Javadoc) * * @see javax.swing.Scrollable#getPreferredScrollableViewportSize() */ public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } /* * (non-Javadoc) * * @see javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle, int, int) */ public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return 50; } /* * (non-Javadoc) * * @see javax.swing.Scrollable#getScrollableTracksViewportHeight() */ public boolean getScrollableTracksViewportHeight() { return false; } /* * (non-Javadoc) * * @see javax.swing.Scrollable#getScrollableTracksViewportWidth() */ public boolean getScrollableTracksViewportWidth() { return true; } /* * (non-Javadoc) * * @see javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle, int, int) */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 10; } } /** * Trivial entity resolver that's applied if the user turns on entity mode without * specifying their own entity resolver. Returns the covered text as the canonical form, * and treats annotations with equal covered text as belonging to the same entity. */ static class DefaultEntityResolver implements EntityResolver { /* (non-Javadoc) * @see org.apache.uima.tools.viewer.EntityResolver#getCanonicalForm(org.apache.uima.jcas.tcas.Annotation) */ public Entity getEntity(final Annotation aAnnotation) { return new Entity() { public String getCanonicalForm() { return aAnnotation.getCoveredText(); } public boolean equals(Object obj) { if (obj instanceof Entity) { String canon = ((Entity)obj).getCanonicalForm(); return getCanonicalForm().equals(canon); } return false; } public int hashCode() { return getCanonicalForm().hashCode(); } }; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy