Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
gate.gui.docview.AnnotationSetsView Maven / Gradle / Ivy
Go to download
GATE - general achitecture for text engineering - is open source
software capable of solving almost any text processing problem. This
artifact enables you to embed the core GATE Embedded with its essential
dependencies. You will able to use the GATE Embedded API and load and
store GATE XML documents. This artifact is the perfect dependency for
CREOLE plugins or for applications that need to customize the GATE
dependencies due to confict with their own dependencies or for lower
footprint.
/*
* Copyright (c) 1995-2012, The University of Sheffield. See the file
* COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
*
* This file is part of GATE (see http://gate.ac.uk/), and is free
* software, licenced under the GNU Library General Public License,
* Version 2, June 1991 (in the distribution as file licence.html,
* and also available at http://gate.ac.uk/gate/licence.html).
*
* Valentin Tablan, Mar 23, 2004
*
* $Id: AnnotationSetsView.java 20054 2017-02-02 06:44:12Z markagreenwood $
*/
package gate.gui.docview;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.Border;
import javax.swing.event.MouseInputListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.JTextComponent;
import gate.Annotation;
import gate.AnnotationSet;
import gate.Factory;
import gate.Gate;
import gate.Resource;
import gate.creole.ResourceData;
import gate.creole.ResourceInstantiationException;
import gate.event.AnnotationSetEvent;
import gate.event.AnnotationSetListener;
import gate.event.DocumentEvent;
import gate.event.DocumentListener;
import gate.event.GateEvent;
import gate.gui.MainFrame;
import gate.gui.annedit.AnnotationData;
import gate.gui.annedit.AnnotationDataImpl;
import gate.gui.annedit.AnnotationEditorOwner;
import gate.gui.annedit.OwnedAnnotationEditor;
import gate.resources.img.svg.ExpandedIcon;
import gate.resources.img.svg.ClosedIcon;
import gate.swing.ColorGenerator;
import gate.swing.XJTable;
import gate.util.Err;
import gate.util.GateRuntimeException;
import gate.util.InvalidOffsetException;
import gate.util.Strings;
/**
* Display document annotation sets and types in a tree view like with a table.
* Allow the selection of annotation type and modification of their color.
*/
@SuppressWarnings("serial")
public class AnnotationSetsView extends AbstractDocumentView
implements DocumentListener,
AnnotationSetListener,
AnnotationEditorOwner{
/* (non-Javadoc)
* @see gate.gui.annedit.AnnotationEditorOwner#annotationTypeChanged(gate.Annotation, java.lang.String, java.lang.String)
*/
@Override
public void annotationChanged(Annotation ann, AnnotationSet set,
String oldType) {
lastAnnotationType = ann.getType();
//show new annotation type
setTypeSelected(set.getName(), ann.getType(), true);
//select new annotation
// selectAnnotation(new AnnotationDataImpl(set, ann));
}
/**
* changes the orientation of the annotation editor component only
*
* @param orientation
*/
public void changeOrientation(ComponentOrientation orientation) {
currentOrientation = orientation;
if(annotationEditor != null) {
annotationEditor.changeOrientation(orientation);
}
}
/**
* Queues an an action for selecting the provided annotation
*/
@Override
public void selectAnnotation(final AnnotationData aData) {
Runnable action = new Runnable(){
@Override
public void run(){
List selAnns = Collections.singletonList(aData);
owner.setSelectedAnnotations(selAnns);
}
};
pendingEvents.offer(new PerformActionEvent(action));
eventMinder.restart();
}
/* (non-Javadoc)
* @see gate.gui.annedit.AnnotationEditorOwner#getNextAnnotation()
*/
@Override
public Annotation getNextAnnotation() {
return null;
}
/* (non-Javadoc)
* @see gate.gui.annedit.AnnotationEditorOwner#getPreviousAnnotation()
*/
@Override
public Annotation getPreviousAnnotation() {
return null;
}
/* (non-Javadoc)
* @see gate.gui.annedit.AnnotationEditorOwner#getTextComponent()
*/
@Override
public JTextComponent getTextComponent() {
return textPane;
}
/* (non-Javadoc)
* @see gate.gui.annedit.AnnotationEditorOwner#getListComponent()
* TODO: delete this obsolete method?
*/
public AnnotationList getListComponent() {
return listView;
}
public AnnotationSetsView(){
setHandlers = new ArrayList();
tableRows = new ArrayList();
visibleAnnotationTypes = new LinkedBlockingQueue();
pendingEvents = new LinkedBlockingQueue();
eventMinder = new Timer(EVENTS_HANDLE_DELAY,
new HandleDocumentEventsAction());
eventMinder.setRepeats(true);
eventMinder.setCoalesce(true);
}
/* (non-Javadoc)
* @see gate.gui.docview.DocumentView#getType()
*/
@Override
public int getType() {
return VERTICAL;
}
@Override
protected void initGUI(){
//get a pointer to the textual view used for highlights
Iterator centralViewsIter = owner.getCentralViews().iterator();
while(textView == null && centralViewsIter.hasNext()){
DocumentView aView = centralViewsIter.next();
if(aView instanceof TextualDocumentView)
textView = (TextualDocumentView)aView;
}
textPane = (JTextArea)((JScrollPane)textView.getGUI())
.getViewport().getView();
//get a pointer to the list view
Iterator horizontalViewsIter = owner.getHorizontalViews().iterator();
while(listView == null && horizontalViewsIter.hasNext()){
DocumentView aView = horizontalViewsIter.next();
if(aView instanceof AnnotationListView)
listView = (AnnotationListView)aView;
}
//get a pointer to the stack view
horizontalViewsIter = owner.getHorizontalViews().iterator();
while(stackView == null && horizontalViewsIter.hasNext()){
DocumentView aView = horizontalViewsIter.next();
if(aView instanceof AnnotationStackView)
stackView = (AnnotationStackView)aView;
}
mainTable = new XJTable();
tableModel = new SetsTableModel();
mainTable.setSortable(false);
mainTable.setModel(tableModel);
mainTable.setRowMargin(0);
mainTable.getColumnModel().setColumnMargin(0);
SetsTableCellRenderer cellRenderer = new SetsTableCellRenderer();
mainTable.getColumnModel().getColumn(NAME_COL).setCellRenderer(cellRenderer);
mainTable.getColumnModel().getColumn(SELECTED_COL).setCellRenderer(cellRenderer);
SetsTableCellEditor cellEditor = new SetsTableCellEditor();
mainTable.getColumnModel().getColumn(SELECTED_COL).setCellEditor(cellEditor);
mainTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mainTable.setColumnSelectionAllowed(false);
mainTable.setRowSelectionAllowed(true);
mainTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
//block autocreation of new columns from now on
mainTable.setAutoCreateColumnsFromModel(false);
mainTable.setTableHeader(null);
mainTable.setShowGrid(false);
mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
//the background colour seems to change somewhere when using the GTK+
//look and feel on Linux, so we copy the value now and set it
Color tableBG = mainTable.getBackground();
//make a copy of the value (as the reference gets changed somewhere)
tableBG = new Color(tableBG.getRGB());
mainTable.setBackground(tableBG);
scroller = new JScrollPane(mainTable);
scroller.getViewport().setOpaque(true);
scroller.getViewport().setBackground(tableBG);
try {
annotationEditor = createAnnotationEditor(textView, this);
}
catch(ResourceInstantiationException e) {
//this should not really happen
throw new GateRuntimeException(
"Could not initialise the annotation editor!", e);
}
mainPanel = new JPanel();
mainPanel.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridy = 0;
constraints.gridx = GridBagConstraints.RELATIVE;
constraints.gridwidth = 2;
constraints.weighty = 1;
constraints.weightx = 1;
constraints.fill = GridBagConstraints.BOTH;
mainPanel.add(scroller, constraints);
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.weighty = 0;
newSetNameTextField = new JTextField();
mainPanel.add(newSetNameTextField, constraints);
constraints.weightx = 0;
newSetAction = new NewAnnotationSetAction();
mainPanel.add(new JButton(newSetAction), constraints);
populateUI();
tableModel.fireTableDataChanged();
eventMinder.start();
initListeners();
}
/**
* Create the annotation editor (responsible for creating the window
* used to edit individual annotations).
* @throws ResourceInstantiationException if an error occurs
*/
protected gate.gui.annedit.OwnedAnnotationEditor createAnnotationEditor(
TextualDocumentView textView, AnnotationSetsView asView)
throws ResourceInstantiationException {
// find the last VR that implements the AnnotationEditor interface
List vrTypes = new ArrayList(Gate.getCreoleRegister()
.getPublicVrTypes());
Collections.reverse(vrTypes);
for(String aVrType : vrTypes) {
ResourceData rData = Gate.getCreoleRegister().get(aVrType);
try {
Class extends Resource> resClass = rData.getResourceClass();
if(OwnedAnnotationEditor.class.isAssignableFrom(resClass)) {
OwnedAnnotationEditor newEditor = (OwnedAnnotationEditor)resClass
.newInstance();
newEditor.setOwner(this);
newEditor.init();
if(currentOrientation != null) {
newEditor.changeOrientation(currentOrientation);
}
return newEditor;
}
}
catch(ClassNotFoundException cnfe) {
// ignore
Err.prln("Invalid CREOLE data:");
cnfe.printStackTrace(Err.getPrintWriter());
}
catch(InstantiationException e) {
e.printStackTrace();
}
catch(IllegalAccessException e) {
e.printStackTrace();
}
}
// if we got this far, we couldn't find an editor
Err.prln("Could not find any annotation editors. Editing annotations disabled.");
return null;
}
protected void populateUI(){
setHandlers.add(new SetHandler(document.getAnnotations()));
List setNames = document.getNamedAnnotationSets() == null ?
new ArrayList() :
new ArrayList(document.getNamedAnnotationSets().keySet());
Collections.sort(setNames);
Iterator setsIter = setNames.iterator();
while(setsIter.hasNext()){
setHandlers.add(new SetHandler(document.
getAnnotations(setsIter.next())));
}
tableRows.addAll(setHandlers);
}
@Override
public Component getGUI(){
return mainPanel;
}
/**
* Get the saved colour for this annotation type or create a new one
* and save it. The colours are saved in the user configuration file.
* @param annotationType type to get a colour for
* @return a colour
*/
public static Color getColor(String annotationSet, String annotationType) {
Map colourMap = Gate.getUserConfig()
.getMap(AnnotationSetsView.class.getName()+".colours");
String colourValue = colourMap.get(annotationSet+"."+annotationType);
if (colourValue == null) colourValue = colourMap.get(annotationType);
Color colour;
if (colourValue == null) {
float components[] = colourGenerator.getNextColor().getComponents(null);
colour = new Color(components[0], components[1], components[2], 0.5f);
int rgb = colour.getRGB();
int alpha = colour.getAlpha();
int rgba = rgb | (alpha << 24);
colourMap.put(annotationType, String.valueOf(rgba));
Gate.getUserConfig().put(
AnnotationSetsView.class.getName()+".colours", colourMap);
} else {
colour = new Color(Integer.parseInt(colourValue), true);
}
return colour;
}
protected void saveColor(String annotationSet, String annotationType, Color colour){
Map colourMap = Gate.getUserConfig()
.getMap(AnnotationSetsView.class.getName()+".colours");
int rgb = colour.getRGB();
int alpha = colour.getAlpha();
int rgba = rgb | (alpha << 24);
String defaultValue = colourMap.get(annotationType);
String newValue = String.valueOf(rgba);
if (newValue.equals(defaultValue)) {
colourMap.remove(annotationSet+"."+annotationType);
}
else {
colourMap.put(annotationSet+"."+annotationType, newValue);
}
Gate.getUserConfig().put(
AnnotationSetsView.class.getName()+".colours", colourMap);
}
/**
* Save type or remove unselected type in the preferences.
* @param setName set name to save/remove or null for the default set
* @param typeName type name to save/remove
* @param selected state of the selection
*/
public void saveType(String setName, String typeName, boolean selected) {
LinkedHashSet typeList = Gate.getUserConfig().getSet(
AnnotationSetsView.class.getName() + ".types");
String prefix = (setName == null) ? "." : setName + ".";
if (selected) {
typeList.add(prefix+typeName);
} else {
typeList.remove(prefix+typeName);
}
Gate.getUserConfig().put(
AnnotationSetsView.class.getName()+".types", typeList);
}
/**
* Restore previously selected types from the preferences.
*/
public void restoreSavedSelectedTypes() {
LinkedHashSet typeList = Gate.getUserConfig().getSet(
AnnotationSetsView.class.getName() + ".types");
for (SetHandler sHandler : setHandlers){
String prefix = (sHandler.set.getName() == null) ?
"." : sHandler.set.getName() + ".";
for (TypeHandler tHandler : sHandler.typeHandlers) {
if (typeList.contains(prefix + tHandler.name)) {
tHandler.setSelected(true);
}
}
}
}
/**
* Enables or disables creation of the new annotation set.
*/
public void setNewAnnSetCreationEnabled(boolean b) {
newSetAction.setEnabled(b);
newSetNameTextField.setEnabled(b);
}
/**
* This method will be called whenever the view becomes active. Implementers
* should use this to add hooks (such as mouse listeners) to the other views
* as required by their functionality.
*/
@Override
protected void registerHooks(){
textPane.addMouseListener(textMouseListener);
textPane.addMouseMotionListener(textMouseListener);
textPane.addPropertyChangeListener("highlighter", textChangeListener);
// textPane.addAncestorListener(textAncestorListener);
restoreSelectedTypes();
}
/**
* This method will be called whenever this view becomes inactive.
* Implementers should use it to unregister whatever hooks they registered
* in {@link #registerHooks()}.
*
*/
@Override
protected void unregisterHooks(){
textPane.removeMouseListener(textMouseListener);
textPane.removeMouseMotionListener(textMouseListener);
textPane.removePropertyChangeListener("highlighter", textChangeListener);
// textPane.removeAncestorListener(textAncestorListener);
storeSelectedTypes();
}
/**
* Populates the {@link #visibleAnnotationTypes} structure based on the
* current selection
*
*/
protected void storeSelectedTypes(){
visibleAnnotationTypes.clear(); // for security
for(SetHandler sHandler:setHandlers){
for(TypeHandler tHandler: sHandler.typeHandlers){
if(tHandler.isSelected()){
visibleAnnotationTypes.add(new TypeSpec(sHandler.set.getName(),
tHandler.name));
tHandler.setSelected(false);
}
}
}
}
/**
* Restores the selected types based on the state saved in the
* {@link #visibleAnnotationTypes} data structure.
*/
protected void restoreSelectedTypes(){
TypeSpec typeSpec;
while((typeSpec = visibleAnnotationTypes.poll()) != null){
TypeHandler typeHandler =
getTypeHandler(typeSpec.setName, typeSpec.type);
if (typeHandler != null) { // may have been deleted since
typeHandler.setSelected(true);
}
}
}
protected void initListeners(){
document.addDocumentListener(this);
// popup menu to change the color, select, unselect
// and delete annotation types
mainTable.addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent evt){
processMouseEvent(evt);
}
@Override
public void mousePressed(MouseEvent evt){
int row = mainTable.rowAtPoint(evt.getPoint());
if(evt.isPopupTrigger()
&& !mainTable.isRowSelected(row)) {
// if right click outside the selection then reset selection
mainTable.getSelectionModel().setSelectionInterval(row, row);
}
processMouseEvent(evt);
}
@Override
public void mouseReleased(MouseEvent evt){
processMouseEvent(evt);
}
protected void processMouseEvent(MouseEvent evt){
int row = mainTable.rowAtPoint(evt.getPoint());
int col = mainTable.columnAtPoint(evt.getPoint());
if(row >= 0 && col == NAME_COL){
Object handler = tableRows.get(row);
if(evt.isPopupTrigger()){
// popup menu
JPopupMenu popup = new JPopupMenu();
if(handler instanceof TypeHandler
&& mainTable.getSelectedRowCount() == 1){
TypeHandler tHandler = (TypeHandler)handler;
popup.add(tHandler.changeColourAction);
popup.add(new DeleteSelectedAnnotationsAction("Delete"));
} else if (mainTable.getSelectedRowCount() > 1
|| handler instanceof SetHandler) {
popup.add(new SetSelectedAnnotationsAction(true));
popup.add(new SetSelectedAnnotationsAction(false));
popup.add(new DeleteSelectedAnnotationsAction("Delete all"));
}
if (popup.getComponentCount() > 0) {
popup.show(mainTable, evt.getX(), evt.getY());
}
} else if(evt.getClickCount() >= 2
&& evt.getID() == MouseEvent.MOUSE_CLICKED
&& handler instanceof TypeHandler){
//double click
TypeHandler tHandler = (TypeHandler)handler;
tHandler.changeColourAction.actionPerformed(null);
}
}
}
});
// Enter key to change the color of annotation type
// Space key to select/unselect annotation type
// Left/Right keys to close/open an annotation set
mainTable.addKeyListener(new KeyAdapter(){
@Override
public void keyPressed(KeyEvent e) {
int row = mainTable.getSelectedRow();
int col = mainTable.getSelectedColumn();
if(row <= 0
|| mainTable.getSelectedRowCount() > 1) {
return;
}
Object handler = tableRows.get(row);
if (col == NAME_COL) {
if(e.getKeyCode() == KeyEvent.VK_ENTER
&& handler instanceof TypeHandler){
TypeHandler tHandler = (TypeHandler)handler;
tHandler.changeColourAction.actionPerformed(null);
e.consume();
} else if (e.getKeyCode() == KeyEvent.VK_SPACE) {
if (handler instanceof TypeHandler){
TypeHandler tHandler = (TypeHandler)handler;
(new SetSelectedAnnotationsAction(!tHandler.selected))
.actionPerformed(null);
} else if (handler instanceof SetHandler) {
SetHandler sHandler = (SetHandler)handler;
boolean allUnselected = true;
for (TypeHandler tHandler : sHandler.typeHandlers) {
if (tHandler.selected) {
allUnselected = false;
break;
}
}
(new SetSelectedAnnotationsAction(allUnselected))
.actionPerformed(null);
}
} else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
if (handler instanceof SetHandler) {
((SetHandler)handler).setExpanded(false);
mainTable.setColumnSelectionInterval(col, col);
mainTable.setRowSelectionInterval(row, row);
}
e.consume();
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
if (handler instanceof SetHandler) {
((SetHandler)handler).setExpanded(true);
mainTable.setColumnSelectionInterval(col, col);
mainTable.setRowSelectionInterval(row, row);
}
e.consume();
}
}
}
});
mouseStoppedMovingAction = new MouseStoppedMovingAction();
mouseMovementTimer = new javax.swing.Timer(MOUSE_MOVEMENT_TIMER_DELAY,
mouseStoppedMovingAction);
mouseMovementTimer.setRepeats(false);
textMouseListener = new TextMouseListener();
textChangeListener = new PropertyChangeListener(){
@Override
public void propertyChange(PropertyChangeEvent evt) {
if(evt.getNewValue() != null){
//we have a new highlighter
//we need to re-highlight all selected annotations
for(SetHandler sHandler : setHandlers){
for(TypeHandler tHandler : sHandler.typeHandlers){
if(tHandler.isSelected()){
setTypeSelected(sHandler.set.getName(), tHandler.name, false);
setTypeSelected(sHandler.set.getName(), tHandler.name, true);
}
}
}
}
}
};
mainTable.getInputMap().put(KeyStroke.getKeyStroke("DELETE"), "deleteAll");
mainTable.getInputMap()
.put(KeyStroke.getKeyStroke("shift DELETE"), "deleteAll");
mainTable.getActionMap().put("deleteAll",
new DeleteSelectedAnnotationsAction("Delete"));
newSetNameTextField.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "newSet");
newSetNameTextField.getActionMap().put("newSet", newSetAction);
textPane.getInputMap()
.put(KeyStroke.getKeyStroke("control E"), "edit annotation");
textPane.getActionMap().put("edit annotation", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// use the same action as when the mouse stop over a selection
// or annotation but this time for a keyboard shortcut
mouseStoppedMovingAction.setTextLocation(textPane.getCaretPosition());
mouseStoppedMovingAction.actionPerformed(null);
SwingUtilities.invokeLater(new Runnable() { @Override
public void run() {
annotationEditor.setPinnedMode(true);
}});
}});
// skip first column when tabbing
InputMap im =
mainTable.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke tab = KeyStroke.getKeyStroke("TAB");
final Action oldTabAction = mainTable.getActionMap().get(im.get(tab));
Action tabAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
oldTabAction.actionPerformed(e);
JTable table = (JTable) e.getSource();
if(table.getSelectedColumn() == SELECTED_COL) {
oldTabAction.actionPerformed(e);
}
}
};
mainTable.getActionMap().put(im.get(tab), tabAction);
KeyStroke shiftTab = KeyStroke.getKeyStroke("shift TAB");
final Action oldShiftTabAction =
mainTable.getActionMap().get(im.get(shiftTab));
Action shiftTabAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
oldShiftTabAction.actionPerformed(e);
JTable table = (JTable) e.getSource();
if(table.getSelectedColumn() == SELECTED_COL) {
oldShiftTabAction.actionPerformed(e);
}
}
};
mainTable.getActionMap().put(im.get(shiftTab), shiftTabAction);
}
/* (non-Javadoc)
* @see gate.Resource#cleanup()
*/
@Override
public void cleanup() {
document.removeDocumentListener(this);
for(SetHandler sHandler : setHandlers){
sHandler.set.removeAnnotationSetListener(AnnotationSetsView.this);
}
eventMinder.stop();
pendingEvents.clear();
super.cleanup();
document = null;
}
@Override
public void annotationSetAdded(final DocumentEvent e) {
pendingEvents.offer(e);
eventMinder.restart();
}//public void annotationSetAdded(DocumentEvent e)
@Override
public void annotationSetRemoved(final DocumentEvent e) {
pendingEvents.offer(e);
eventMinder.restart();
}//public void annotationSetRemoved(DocumentEvent e)
/**Called when the content of the document has changed through an edit
* operation.
*/
@Override
public void contentEdited(DocumentEvent e){
//go through all the type handlers and propagate the event
Iterator setIter = setHandlers.iterator();
while(setIter.hasNext()){
SetHandler sHandler = setIter.next();
Iterator typeIter = sHandler.typeHandlers.iterator();
while(typeIter.hasNext()){
TypeHandler tHandler = typeIter.next();
if(tHandler.isSelected())
tHandler.repairHighlights(e.getEditStart().intValue(),
e.getEditEnd().intValue());
}
}
}
@Override
public void annotationAdded(final AnnotationSetEvent e) {
pendingEvents.offer(e);
eventMinder.restart();
}
@Override
public void annotationRemoved(final AnnotationSetEvent e) {
pendingEvents.offer(e);
eventMinder.restart();
}
/**
* Get an annotation set handler in this annotation set view.
* @param name name of the annotation set or null for the default set
* @return the annotation set handler
*/
protected SetHandler getSetHandler(String name){
for (SetHandler setHandler : setHandlers) {
if (name == null) { // default annotation set
if (setHandler.set.getName().equals("")) return setHandler;
} else {
if (name.equals(setHandler.set.getName())) return setHandler;
}
}
// set handler not found
return null;
}
/**
* Get an annotation type handler in this annotation set view.
* @param set name of the annotation set or null for the default set
* @param type type of the annotation
* @return the type handler
*/
public TypeHandler getTypeHandler(String set, String type){
for(TypeHandler typeHandler : getSetHandler(set).typeHandlers){
if(typeHandler.name.equals(type)) {
return typeHandler;
}
}
// type handler not found
return null;
}
/**
* Un/select an annotation type in this annotation set view
* and indirectly highlight it in the document view.
* @param setName name of the annotation set or null for the default set
* @param typeName type of the annotation
* @param selected state of the selection
*/
public void setTypeSelected(final String setName,
final String typeName,
final boolean selected){
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run(){
TypeHandler tHandler = getTypeHandler(setName, typeName);
if(tHandler != null){
tHandler.setSelected(selected);
int row = tableRows.indexOf(tHandler);
tableModel.fireTableRowsUpdated(row, row);
mainTable.getSelectionModel().setSelectionInterval(row, row);
}else{
//type handler not created yet
visibleAnnotationTypes.add(new TypeSpec(setName, typeName));
}
}
});
}
/* (non-Javadoc)
* @see gate.gui.docview.AbstractDocumentView#setSelectedAnnotations(java.util.List)
*/
@Override
public void setSelectedAnnotations(List selectedAnnots) {
if(annotationEditor != null && annotationEditor.isActive()){
// editor active - let's update it.
// For annotation editing purposes, only a single selected annotation
// makes sense. Anything else is equivalent to no selection.
PerformActionEvent actionEvent = null;
if(selectedAnnots.size() == 1){
final AnnotationData aData = selectedAnnots.get(0);
//queue the select action to the events minder
actionEvent = new PerformActionEvent(new Runnable(){
@Override
public void run(){
//select the annotation for editing, if editing enabled
if(annotationEditor.getAnnotationSetCurrentlyEdited() !=
aData.getAnnotationSet() ||
annotationEditor.getAnnotationCurrentlyEdited() !=
aData.getAnnotation()){
annotationEditor.editAnnotation(aData.getAnnotation(),
aData.getAnnotationSet());
}
}
});
} else {
actionEvent = new PerformActionEvent(new Runnable(){
@Override
public void run(){
// un-select the edited annotation
annotationEditor.editAnnotation(null,
annotationEditor.getAnnotationSetCurrentlyEdited());
}
});
}
pendingEvents.offer(actionEvent);
eventMinder.restart();
}
}
/**
* Sets a particular annotation as selected. If the list view is visible
* and active, it makes sure that the same annotation is selected there.
* If the annotation editor exists and is active, it switches it to this
* current annotation.
* @param ann the annotation
* @param annSet the parent set
*/
public void selectAnnotation(final Annotation ann,
final AnnotationSet annSet){
selectAnnotation(new AnnotationDataImpl(annSet, ann));
}
protected class SetsTableModel extends AbstractTableModel{
@Override
public int getRowCount(){
return tableRows.size();
// //we have at least one row per set
// int rows = setHandlers.size();
// //expanded sets add rows
// for(int i =0; i < setHandlers.size(); i++){
// SetHandler sHandler = (SetHandler)setHandlers.get(i);
// rows += sHandler.expanded ? sHandler.set.getAllTypes().size() : 0;
// }
// return rows;
}
@Override
public int getColumnCount(){
return 2;
}
@Override
public Object getValueAt(int row, int column){
Object value = tableRows.get(row);
switch(column){
case NAME_COL:
return value;
case SELECTED_COL:
if(value instanceof SetHandler)
return ((SetHandler)value).isExpanded();
if(value instanceof TypeHandler)
return ((TypeHandler)value).isSelected();
return null;
default:
return null;
}
//
// int currentRow = 0;
// Iterator handlerIter = setHandlers.iterator();
// SetHandler sHandler = (SetHandler)handlerIter.next();
//
// while(currentRow < row){
// if(sHandler.expanded){
// if(sHandler.typeHandlers.size() + currentRow >= row){
// //we want a row in current set
// return sHandler.typeHandlers.get(row - currentRow);
// }else{
// currentRow += sHandler.typeHandlers.size();
// sHandler = (SetHandler)handlerIter.next();
// }
// }else{
// //just go to next handler
// currentRow++;
// sHandler = (SetHandler)handlerIter.next();
// }
// if(currentRow == row) return sHandler;
// }
// if(currentRow == row) return sHandler;
//System.out.println("BUG! row: " + row + " col: " + column);
// return null;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex){
Object value = tableRows.get(rowIndex);
switch(columnIndex){
case NAME_COL: return false;
case SELECTED_COL:
if(value instanceof SetHandler)
return ((SetHandler)value).typeHandlers.size() > 0;
if(value instanceof TypeHandler) return true;
}
return columnIndex == SELECTED_COL;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex){
Object receiver = tableRows.get(rowIndex);
switch(columnIndex){
case SELECTED_COL:
if(receiver instanceof SetHandler){
((SetHandler)receiver).setExpanded(((Boolean)aValue).booleanValue());
}else if(receiver instanceof TypeHandler){
((TypeHandler)receiver).setSelected(((Boolean)aValue).booleanValue());
}
break;
default:
break;
}
}
}//public Object getValueAt(int row, int column)
protected class SetsTableCellRenderer implements TableCellRenderer{
public SetsTableCellRenderer(){
typeLabel = new JLabel(){
@Override
public void repaint(long tm, int x, int y, int width, int height){}
@Override
public void repaint(Rectangle r){}
@Override
public void validate(){}
@Override
public void revalidate(){}
@Override
protected void firePropertyChange(String propertyName,
Object oldValue,
Object newValue){}
};
typeLabel.setOpaque(true);
typeLabel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0, 5, 0, 0,
mainTable.getBackground()),
BorderFactory.createEmptyBorder(0, 5, 0, 5)));
// typeLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
setLabel = new JLabel(){
@Override
public void repaint(long tm, int x, int y, int width, int height){}
@Override
public void repaint(Rectangle r){}
@Override
public void validate(){}
@Override
public void revalidate(){}
@Override
protected void firePropertyChange(String propertyName,
Object oldValue,
Object newValue){}
};
setLabel.setOpaque(true);
setLabel.setFont(setLabel.getFont().deriveFont(Font.BOLD));
setLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
typeChk = new JCheckBox(){
@Override
public void repaint(long tm, int x, int y, int width, int height){}
@Override
public void repaint(Rectangle r){}
@Override
public void validate(){}
@Override
public void revalidate(){}
@Override
protected void firePropertyChange(String propertyName,
Object oldValue,
Object newValue){}
};
typeChk.setOpaque(true);
// typeChk.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
setChk = new JCheckBox(){
@Override
public void repaint(long tm, int x, int y, int width, int height){}
@Override
public void repaint(Rectangle r){}
@Override
public void validate(){}
@Override
public void revalidate(){}
@Override
protected void firePropertyChange(String propertyName,
Object oldValue,
Object newValue){}
};
setChk.setSelectedIcon(new ExpandedIcon(12, 12));
setChk.setIcon(new ClosedIcon(12,12));
setChk.setMaximumSize(setChk.getMinimumSize());
setChk.setOpaque(true);
normalBorder = BorderFactory.createLineBorder(
mainTable.getBackground(), 2);
selectedBorder = BorderFactory.createLineBorder(
mainTable.getSelectionBackground(), 2);
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column){
value = tableRows.get(row);
if(value instanceof SetHandler){
SetHandler sHandler = (SetHandler)value;
switch(column){
case NAME_COL:
setLabel.setText(sHandler.set.getName());
setLabel.setBackground(isSelected ?
table.getSelectionBackground() :
table.getBackground());
return setLabel;
case SELECTED_COL:
setChk.setSelected(sHandler.isExpanded());
setChk.setBackground(isSelected ?
table.getSelectionBackground() :
table.getBackground());
setChk.setEnabled(sHandler.typeHandlers.size() > 0);
return setChk;
}
}else if(value instanceof TypeHandler){
TypeHandler tHandler = (TypeHandler)value;
switch(column){
case NAME_COL:
typeLabel.setBackground(tHandler.colour);
typeLabel.setText(tHandler.name);
typeLabel.setBorder(isSelected ? selectedBorder : normalBorder);
return typeLabel;
case SELECTED_COL:
typeChk.setBackground(isSelected ?
table.getSelectionBackground() :
table.getBackground());
typeChk.setSelected(tHandler.isSelected());
return typeChk;
}
}
typeLabel.setText("?");
return typeLabel;
//bugcheck!
}
protected JLabel typeLabel;
protected JLabel setLabel;
protected JCheckBox setChk;
protected JCheckBox typeChk;
protected Border selectedBorder;
protected Border normalBorder;
}
protected class SetsTableCellEditor extends AbstractCellEditor
implements TableCellEditor{
public SetsTableCellEditor(){
setChk = new JCheckBox();
setChk.setSelectedIcon(new ExpandedIcon(12,12));
setChk.setIcon(new ClosedIcon(12,12));
// setChk.setMaximumSize(setChk.getMinimumSize());
setChk.setOpaque(true);
setChk.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent evt){
fireEditingStopped();
}
});
typeChk = new JCheckBox();
typeChk.setOpaque(false);
// typeChk.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
typeChk.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent evt){
fireEditingStopped();
}
});
}
@Override
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row,
int column){
value = tableRows.get(row);
if(value instanceof SetHandler){
SetHandler sHandler = (SetHandler)value;
switch(column){
case NAME_COL: return null;
case SELECTED_COL:
setChk.setSelected(sHandler.isExpanded());
setChk.setEnabled(sHandler.typeHandlers.size() > 0);
setChk.setBackground(isSelected ?
table.getSelectionBackground() :
table.getBackground());
currentChk = setChk;
return setChk;
}
}else if(value instanceof TypeHandler){
TypeHandler tHandler = (TypeHandler)value;
switch(column){
case NAME_COL: return null;
case SELECTED_COL:
// typeChk.setBackground(tHandler.colour);
typeChk.setSelected(tHandler.isSelected());
currentChk = typeChk;
return typeChk;
}
}
return null;
}
@Override
public boolean stopCellEditing(){
return true;
}
@Override
public Object getCellEditorValue(){
return currentChk.isSelected();
}
@Override
public boolean shouldSelectCell(EventObject anEvent){
return false;
}
@Override
public boolean isCellEditable(EventObject anEvent){
return true;
}
JCheckBox currentChk;
JCheckBox setChk;
JCheckBox typeChk;
}
/**
* Stores the data related to an annotation set
*/
public class SetHandler{
SetHandler(AnnotationSet set){
this.set = set;
typeHandlers = new ArrayList();
typeHandlersByType = new HashMap();
List typeNames = new ArrayList(set.getAllTypes());
Collections.sort(typeNames);
Iterator typIter = typeNames.iterator();
while(typIter.hasNext()){
String name = typIter.next();
TypeHandler tHandler = new TypeHandler(this, name);
tHandler.annotationCount = set.get(name).size();
typeHandlers.add(tHandler);
typeHandlersByType.put(name, tHandler);
}
set.addAnnotationSetListener(AnnotationSetsView.this);
}
public void cleanup(){
set.removeAnnotationSetListener(AnnotationSetsView.this);
typeHandlers.clear();
}
/**
* Notifies this set handler that a new type of annotations has been created
* @param type the new type of annotations
* @return the new TypeHandler created as a result
*/
public TypeHandler newType(String type){
//create a new TypeHandler
TypeHandler tHandler = new TypeHandler(this, type);
//add it to the list at the right position
int pos = 0;
for(;
pos < typeHandlers.size() &&
typeHandlers.get(pos).name.compareTo(type) <= 0;
pos++);
typeHandlers.add(pos, tHandler);
typeHandlersByType.put(type, tHandler);
//preserve table selection
int row = mainTable.getSelectedRow();
int setRow = tableRows.indexOf(this);
if(typeHandlers.size() == 1)
tableModel.fireTableRowsUpdated(setRow, setRow);
if(expanded){
tableRows.add(setRow + pos + 1, tHandler);
tableModel.fireTableRowsInserted(setRow + pos + 1,
setRow + pos + 1);
}
//restore selection if any
if(row != -1) mainTable.getSelectionModel().setSelectionInterval(row, row);
//select the newly created type if previously requested
TypeSpec typeSpec = new TypeSpec(set.getName(), type);
if(visibleAnnotationTypes.remove(typeSpec)){
tHandler.setSelected(true);
}
return tHandler;
}
public void removeType(TypeHandler tHandler){
int setRow = tableRows.indexOf(this);
int pos = typeHandlers.indexOf(tHandler);
if(setRow != -1 && pos != -1){
typeHandlers.remove(pos);
typeHandlersByType.remove(tHandler.name);
//preserve table selection
int row = mainTable.getSelectedRow();
if(expanded){
tableRows.remove(setRow + pos + 1);
tableModel.fireTableRowsDeleted(setRow + pos + 1, setRow + pos + 1);
}
//restore selection if any
if(row != -1){
if(mainTable.getRowCount() <= row){
row = mainTable.getRowCount() -1;
}
mainTable.getSelectionModel().setSelectionInterval(row, row); }
}
}
public void removeType(String type){
removeType(typeHandlersByType.get(type));
}
public TypeHandler getTypeHandler(String type){
return typeHandlersByType.get(type);
}
public void setExpanded(boolean expanded){
if(this.expanded == expanded) return;
this.expanded = expanded;
int myPosition = tableRows.indexOf(this);
if(expanded){
//expand
tableRows.addAll(myPosition + 1, typeHandlers);
tableModel.fireTableRowsInserted(myPosition + 1,
myPosition + 1 + typeHandlers.size());
}else{
//collapse
for(int i = 0; i < typeHandlers.size(); i++){
tableRows.remove(myPosition + 1);
}
tableModel.fireTableRowsDeleted(myPosition + 1,
myPosition + 1 + typeHandlers.size());
}
tableModel.fireTableRowsUpdated(myPosition, myPosition);
}
public boolean isExpanded(){
return expanded;
}
AnnotationSet set;
List typeHandlers;
Map typeHandlersByType;
private boolean expanded = false;
}
public class TypeHandler{
TypeHandler (SetHandler setHandler, String name){
this.setHandler = setHandler;
this.name = name;
colour = getColor(setHandler.set.getName(),name);
hghltTagsForAnnId = new HashMap();
annListTagsForAnn = new HashMap();
changeColourAction = new ChangeColourAction();
annotationCount = 0;
}
/**
* @return the colour
*/
public Color getColour() {
return colour;
}
public void setColour(Color colour){
if(this.colour.equals(colour)) return;
this.colour = colour;
saveColor(setHandler.set.getName(),name, colour);
if(isSelected()){
//redraw the highlights
//hide highlights
textView.removeHighlights(hghltTagsForAnnId.values());
hghltTagsForAnnId.clear();
//show highlights
List annots = new ArrayList(
setHandler.set.get(name));
ListaDataList = new ArrayList();
for(Annotation ann : annots){
aDataList.add(new AnnotationDataImpl(setHandler.set, ann));
}
List tags = textView.addHighlights(aDataList, TypeHandler.this.colour);
for(int i = 0; i < annots.size(); i++){
hghltTagsForAnnId.put(annots.get(i).getId(), tags.get(i));
}
}
//update the table display
int row = tableRows.indexOf(this);
if(row >= 0) tableModel.fireTableRowsUpdated(row, row);
if (stackView.isActive()) stackView.updateStackView();
}
public void setSelected(boolean selected){
if(this.selected == selected) return;
this.selected = selected;
final List annots = new ArrayList(setHandler.set.get(name));
if(selected){
//make sure set is expanded
setHandler.setExpanded(true);
//add to the list view
annListTagsForAnn.clear();
List listTags =
listView.addAnnotations(annots, setHandler.set);
for(AnnotationData aData: listTags)
annListTagsForAnn.put(aData.getAnnotation().getId(), aData);
//show highlights
hghltTagsForAnnId.clear();
// List tags = textView.addHighlights(annots, setHandler.set, colour);
List tags = textView.addHighlights(listTags, colour);
for(int i = 0; i < annots.size(); i++){
hghltTagsForAnnId.put(annots.get(i).getId(), tags.get(i));
}
}else{
//hide highlights
try{
listView.removeAnnotations(annListTagsForAnn.values());
textView.removeHighlights(hghltTagsForAnnId.values());
}finally{
hghltTagsForAnnId.clear();
annListTagsForAnn.clear();
}
}
//update the table display
int row = tableRows.indexOf(this);
tableModel.fireTableRowsUpdated(row, row);
saveType(setHandler.set.getName(), name, selected);
//update the stack view
stackView.updateStackView();
}
public boolean isSelected(){
return selected;
}
/**
* Notifies this type handler that a new annotation was created of the
* right type
* @param ann
*/
public void annotationAdded(final Annotation ann){
annotationCount++;
if(selected){
//add new highlight
if(!hghltTagsForAnnId.containsKey(ann.getId()))
hghltTagsForAnnId.put(ann.getId(),
textView.addHighlight(
new AnnotationDataImpl(setHandler.set, ann),
colour));
if(!annListTagsForAnn.containsKey(ann.getId())){
annListTagsForAnn.put(ann.getId(),
listView.addAnnotation(ann, setHandler.set));
}
//update the stack view
stackView.updateStackView();
}
}
/**
* Notifies this type handler that an annotation has been removed
* @param ann the removed annotation
*/
public void annotationRemoved(Annotation ann){
annotationCount--;
if(selected){
//single annotation removal
TextualDocumentView.HighlightData tag = hghltTagsForAnnId.remove(ann.getId());
if(tag != null) textView.removeHighlight(tag);
AnnotationData listTag = annListTagsForAnn.remove(ann.getId());
if(tag != null) listView.removeAnnotation(listTag);
//update the stack view
stackView.updateStackView();
}
//if this was the last annotation of this type then the handler is no
//longer required
if(annotationCount == 0){
setHandler.removeType(TypeHandler.this);
}
}
protected void repairHighlights(int start, int end){
//map from tag to annotation
List tags = new ArrayList(hghltTagsForAnnId.size());
List annots = new ArrayList(hghltTagsForAnnId.size());
Iterator annIter = hghltTagsForAnnId.keySet().iterator();
while(annIter.hasNext()){
Annotation ann = setHandler.set.get(annIter.next());
// editing the text sometimes leads to annotations being deleted
if(ann == null) continue;
int annStart = ann.getStartNode().getOffset().intValue();
int annEnd = ann.getEndNode().getOffset().intValue();
if((annStart <= start && start <= annEnd) ||
(start <= annStart && annStart <= end)){
if(!hghltTagsForAnnId.containsKey(ann.getId())){
System.out.println("Error!!!");
}
tags.add(hghltTagsForAnnId.get(ann.getId()));
annots.add(ann);
}
}
for(int i = 0; i < tags.size(); i++){
Object tag = tags.get(i);
Annotation ann = annots.get(i);
try{
textView.moveHighlight(tag,
ann.getStartNode().getOffset().intValue(),
ann.getEndNode().getOffset().intValue());
}catch(BadLocationException ble){
//this should never happen as the offsets come from an annotation
}
}
}
protected class ChangeColourAction extends AbstractAction{
public ChangeColourAction(){
super("Change colour");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("ENTER"));
}
@Override
public void actionPerformed(ActionEvent evt){
Color col = JColorChooser.showDialog(mainTable,
"Select colour for \"" + name + "\"",
colour);
if(col != null){
Color colAlpha = new Color(col.getRed(), col.getGreen(),
col.getBlue(), 128);
setColour(colAlpha);
}
}
}
ChangeColourAction changeColourAction;
boolean selected;
/**
* Map from annotation ID (which is immutable) to highlight tag
*/
Map hghltTagsForAnnId;
/**
* Map from annotation ID (which is immutable) to AnnotationListView tag
*/
Map annListTagsForAnn;
String name;
SetHandler setHandler;
Color colour;
int annotationCount;
}
/**
* A class storing the identifying information for an annotation type (i.e.
* the set name and the type).
* @author Valentin Tablan (valyt)
*
*/
private static class TypeSpec{
private String setName;
private String type;
public TypeSpec(String setName, String type) {
super();
this.setName = setName;
this.type = type;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((setName == null) ? 0 : setName.hashCode());
result = PRIME * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(obj == null) return false;
if(getClass() != obj.getClass()) return false;
final TypeSpec other = (TypeSpec)obj;
if(setName == null) {
if(other.setName != null) return false;
}
else if(!setName.equals(other.setName)) return false;
if(type == null) {
if(other.type != null) return false;
}
else if(!type.equals(other.type)) return false;
return true;
}
}
/**
* A mouse listener used for events in the text view.
*/
protected class TextMouseListener implements MouseInputListener{
@Override
public void mouseDragged(MouseEvent e){
//do not create annotations while dragging
mouseMovementTimer.stop();
}
@Override
public void mouseMoved(MouseEvent e){
//this triggers select annotation leading to edit annotation or new
//annotation actions
//ignore movement if CTRL pressed or dragging
int modEx = e.getModifiersEx();
if((modEx & MouseEvent.CTRL_DOWN_MASK) != 0){
mouseMovementTimer.stop();
return;
}
if((modEx & MouseEvent.BUTTON1_DOWN_MASK) != 0){
mouseMovementTimer.stop();
return;
}
//check the text location is real
int textLocation = textPane.viewToModel(e.getPoint());
try {
Rectangle viewLocation = textPane.modelToView(textLocation);
//expand the rectangle a bit
int error = 10;
viewLocation = new Rectangle(viewLocation.x - error,
viewLocation.y - error,
viewLocation.width + 2*error,
viewLocation.height + 2*error);
if(viewLocation.contains(e.getPoint())){
mouseStoppedMovingAction.setTextLocation(textLocation);
}else{
mouseStoppedMovingAction.setTextLocation(-1);
}
}
catch(BadLocationException e1) {
//this should not happen, as the text location comes from the text view
//if it does. we'll just ignore it.
}finally{
mouseMovementTimer.restart();
}
}
@Override
public void mouseClicked(MouseEvent e){
}
@Override
public void mousePressed(MouseEvent e){
}
@Override
public void mouseReleased(MouseEvent e){
}
@Override
public void mouseEntered(MouseEvent e){
}
@Override
public void mouseExited(MouseEvent e){
mouseMovementTimer.stop();
}
}//protected class TextMouseListener implements MouseInputListener
protected class NewAnnotationSetAction extends AbstractAction{
public NewAnnotationSetAction(){
super("New");
putValue(SHORT_DESCRIPTION, "Creates a new annotation set");
}
@Override
public void actionPerformed(ActionEvent evt){
String name = newSetNameTextField.getText();
newSetNameTextField.setText("");
if(name != null && name.length() > 0){
AnnotationSet set = document.getAnnotations(name);
//select the newly added set
int row = -1;
for(int i = 0; i < tableRows.size() && row < 0; i++){
if(tableRows.get(i) instanceof SetHandler &&
((SetHandler)tableRows.get(i)).set == set) row = i;
}
if(row >= 0) mainTable.getSelectionModel().setSelectionInterval(row, row);
}
}
}
protected class NewAnnotationAction extends AbstractAction{
public NewAnnotationAction(String selection){
super("Create new annotation");
putValue(SHORT_DESCRIPTION, "Creates a new annotation from the" +
" selection: [" + Strings.crop(selection, 30) + "]");
}
@Override
public void actionPerformed(ActionEvent evt){
if(annotationEditor == null) return;
int start = textPane.getSelectionStart();
int end = textPane.getSelectionEnd();
if(start != end){
textPane.setSelectionStart(start);
textPane.setSelectionEnd(start);
//create a new annotation
//find the selected set
int row = mainTable.getSelectedRow();
//select the default annotation set if none selected
if(row < 0) row = 0;
//find the set handler
while(!(tableRows.get(row) instanceof SetHandler)) row --;
AnnotationSet set = ((SetHandler)tableRows.get(row)).set;
try{
Integer annId = set.add(new Long(start), new Long(end),
lastAnnotationType, Factory.newFeatureMap());
Annotation ann = set.get(annId);
//select the annotation set in the tree view and expand it
//to avoid the next annotation to be always in the default set
if (tableRows.get(row) instanceof SetHandler) {
((SetHandler)tableRows.get(row)).setExpanded(true);
mainTable.getSelectionModel().setSelectionInterval(row, row);
}
//make sure new annotation is visible
setTypeSelected(set.getName(), ann.getType(), true);
//edit the new annotation
pendingEvents.offer(new PerformActionEvent(
new EditAnnotationAction(new AnnotationDataImpl(set, ann))));
eventMinder.restart();
}catch(InvalidOffsetException ioe){
//this should never happen
throw new GateRuntimeException(ioe);
}
}
}
}
/**
* A fake GATE Event used to wrap a {@link Runnable} value. This is used for
* queueing actions to the document UI update timer.
*/
private class PerformActionEvent extends GateEvent{
public PerformActionEvent(Runnable runnable){
super(AnnotationSetsView.this, 0);
this.runnable = runnable;
this.action = null;
}
public PerformActionEvent(Action action){
super(AnnotationSetsView.this, 0);
this.runnable = null;
this.action = action;
}
/**
* Runs the action (or runnable) enclosed by this event.
*/
public void run(){
if(runnable != null){
runnable.run();
}else if(action != null){
action.actionPerformed(null);
}
}
private Action action;
private Runnable runnable;
}
protected class HandleDocumentEventsAction extends AbstractAction{
@Override
public void actionPerformed(ActionEvent ev) {
//see if we need to try again to rebuild from scratch
if(uiDirty){
//a previous call to rebuild has failed; try again
rebuildDisplay();
return;
}
//if too many individual events, then rebuild UI from scratch as it's
//faster.
if(pendingEvents.size() > MAX_EVENTS){
rebuildDisplay();
return;
}
//process the individual events
while(!pendingEvents.isEmpty()){
GateEvent event = pendingEvents.remove();
if(event instanceof DocumentEvent){
DocumentEvent e = (DocumentEvent)event;
if(event.getType() == DocumentEvent.ANNOTATION_SET_ADDED){
String newSetName = e.getAnnotationSetName();
SetHandler sHandler = new SetHandler(document.getAnnotations(newSetName));
//find the right location for the new set
//this is a named set and the first one is always the default one
int i = 0;
if(newSetName != null){
for(i = 1;
i < setHandlers.size() &&
setHandlers.get(i).set.
getName().compareTo(newSetName) <= 0;
i++);
}
setHandlers.add(i, sHandler);
//update the tableRows list
int j = 0;
if(i > 0){
SetHandler previousHandler = setHandlers.get(i -1);
//find the index for the previous handler - which is guaranteed to exist
for(; tableRows.get(j) != previousHandler; j++);
if(previousHandler.isExpanded()){
j += previousHandler.typeHandlers.size();
}
j++;
}
tableRows.add(j, sHandler);
//update the table view
tableModel.fireTableRowsInserted(j, j);
}else if(event.getType() == DocumentEvent.ANNOTATION_SET_REMOVED){
String setName = e.getAnnotationSetName();
//find the handler and remove it from the list of handlers
SetHandler sHandler = getSetHandler(setName);
if(sHandler != null){
sHandler.set.removeAnnotationSetListener(AnnotationSetsView.this);
//remove highlights if any
Iterator typeIter = sHandler.typeHandlers.iterator();
while(typeIter.hasNext()){
TypeHandler tHandler = typeIter.next();
tHandler.setSelected(false);
}
setHandlers.remove(sHandler);
//remove the set from the table
int row = tableRows.indexOf(sHandler);
tableRows.remove(row);
int removed = 1;
//remove the type rows as well
if(sHandler.isExpanded())
for(int i = 0; i < sHandler.typeHandlers.size(); i++){
tableRows.remove(row);
removed++;
}
tableModel.fireTableRowsDeleted(row, row + removed -1);
sHandler.cleanup();
}
}else{
//some other kind of event we don't care about
}
}else if(event instanceof AnnotationSetEvent){
AnnotationSetEvent e = (AnnotationSetEvent)event;
AnnotationSet set = (AnnotationSet)e.getSource();
Annotation ann = e.getAnnotation();
if(event.getType() == AnnotationSetEvent.ANNOTATION_ADDED){
TypeHandler tHandler = getTypeHandler(set.getName(), ann.getType());
if(tHandler == null){
//new type for this set
SetHandler sHandler = getSetHandler(set.getName());
tHandler = sHandler.newType(ann.getType());
}
tHandler.annotationAdded(ann);
}else if(event.getType() == AnnotationSetEvent.ANNOTATION_REMOVED){
TypeHandler tHandler = getTypeHandler(set.getName(), ann.getType());
if(tHandler != null) tHandler.annotationRemoved(ann);
}else{
//some other kind of event we don't care about
}
}else if(event instanceof PerformActionEvent){
((PerformActionEvent)event).run();
}else{
//unknown type of event -> ignore
}
}
}
/**
* This method is used to update the display by reading the associated
* document when it is considered that doing so would be cheaper than
* acting on the events queued
*/
protected void rebuildDisplay(){
//if there is a process still running, we may get concurrent modification
//exceptions, in which case we should give up and try again later.
//this method will always run from the UI thread, so no synchronisation
//is necessary
uiDirty = false;
try{
//Ignore all pending events, as we're rebuilding from scratch.
//Rotate once through the whole queue, filtering out events we want
//to ignore.
GateEvent event;
pendingEvents.offer(END_OF_LIST);
while((event = pendingEvents.poll()) != END_OF_LIST){
if(event instanceof DocumentEvent ||
event instanceof AnnotationSetEvent){
//ignore event
}else{
//event of unknown type -> we re-queue it!
pendingEvents.offer(event);
}
}
//store selection state and expanded sets
storeSelectedTypes();
Map expandedSets = new HashMap();
for(SetHandler sHandler : setHandlers){
// store expanded state
expandedSets.put(sHandler.set.getName(), sHandler.isExpanded());
// release all resources
sHandler.typeHandlers.clear();
sHandler.typeHandlersByType.clear();
sHandler.set.removeAnnotationSetListener(AnnotationSetsView.this);
}
setHandlers.clear();
tableRows.clear();
listView.removeAnnotations(listView.getAllAnnotations());
//update the stack view
stackView.updateStackView();
// textView.removeAllBlinkingHighlights();
//rebuild the UI
populateUI();
//restore the selection
restoreSelectedTypes();
tableModel.fireTableDataChanged();
// restore expansion state
for(SetHandler sHandler : setHandlers){
sHandler.setExpanded(expandedSets.get(sHandler.set.getName()));
}
}catch(Throwable t){
//something happened, we need to give up
uiDirty = true;
// t.printStackTrace();
}
}
boolean uiDirty = false;
/**
* Maximum number of events to treat individually. If we have more pending
* events than this value, the UI will be rebuilt from scratch
*/
private static final int MAX_EVENTS = 300;
}
/**
* Used to select an annotation for editing.
*
*/
protected class MouseStoppedMovingAction extends AbstractAction{
@Override
public void actionPerformed(ActionEvent evt){
if(annotationEditor == null) return;
//this action either creates a new annotation or starts editing an
//existing one. In either case we need first to make sure that the current
//annotation is finished editing.
if(!annotationEditor.editingFinished()) return;
if(textLocation == -1) return;
JPopupMenu popup = new JPopupMenu();
//check for selection hovering
if(textPane.getSelectedText() != null
&& textPane.getSelectionStart() <= textLocation
&& textPane.getSelectionEnd() >= textLocation){
//add 'New annotation' to the popup menu
popup.add(new NewAnnotationAction(textPane.getSelectedText()));
popup.addSeparator();
}
//check for annotations at location
for(SetHandler setHandler : setHandlers) {
for(Annotation ann : setHandler.set.get(
Math.max(0l, textLocation-1),
Math.min(document.getContent().size(), textLocation+1))) {
if(setHandler.getTypeHandler(ann.getType()).isSelected()) {
AnnotationDataImpl annotAtPoint =
new AnnotationDataImpl(setHandler.set, ann);
//add annotations to edit to the popup menu
popup.add(new HighlightMenuItem(
new EditAnnotationAction(annotAtPoint),
annotAtPoint.getAnnotation().getStartNode().getOffset().intValue(),
annotAtPoint.getAnnotation().getEndNode().getOffset().intValue(),
popup));
}
}
}
if (popup.getComponentCount() == 0) {
// nothing to do
} else if(popup.getComponentCount() == 1
|| (popup.getComponentCount() == 2
&& popup.getComponent(1) instanceof JSeparator)) {
//only one annotation, start the editing directly
//or only one selection, add new annotation
((JMenuItem)popup.getComponent(0)).getAction().actionPerformed(evt);
} else { //mouse hover a selection AND annotation(s)
try{
Rectangle rect = textPane.modelToView(textLocation);
//display the popup
popup.show(textPane, rect.x + 10, rect.y);
}catch(BadLocationException ble){
throw new GateRuntimeException(ble);
}
}
}
public void setTextLocation(int textLocation){
this.textLocation = textLocation;
}
int textLocation;
}//protected class SelectAnnotationAction extends AbstractAction{
/**
* The popup menu items used to select annotations
* Apart from the normal {@link javax.swing.JMenuItem} behaviour, this menu
* item also highlights the annotation which it would select if pressed.
*/
protected class HighlightMenuItem extends JMenuItem {
public HighlightMenuItem(Action action, int startOffset, int endOffset,
JPopupMenu popup) {
super(action);
this.start = startOffset;
this.end = endOffset;
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
showHighlight();
}
@Override
public void mouseExited(MouseEvent e) {
removeHighlight();
}
});
popup.addPopupMenuListener(new PopupMenuListener(){
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e){
}
@Override
public void popupMenuCanceled(PopupMenuEvent e){
removeHighlight();
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e){
removeHighlight();
}
});
}
protected void showHighlight(){
try {
highlight = textPane.getHighlighter().addHighlight(start, end,
DefaultHighlighter.DefaultPainter);
}catch(BadLocationException ble){
throw new GateRuntimeException(ble.toString());
}
}
protected void removeHighlight(){
if(highlight != null){
textPane.getHighlighter().removeHighlight(highlight);
highlight = null;
}
}
int start;
int end;
Action action;
Object highlight;
}
protected class EditAnnotationAction extends AbstractAction{
public EditAnnotationAction(AnnotationData aData){
super(aData.getAnnotation().getType() + " [" +
(aData.getAnnotationSet().getName() == null ? " " :
aData.getAnnotationSet().getName()) +
"]");
putValue(SHORT_DESCRIPTION, aData.getAnnotation().getFeatures().toString());
this.aData = aData;
}
@Override
public void actionPerformed(ActionEvent evt){
if(annotationEditor == null) return;
//if the editor is done with the current annotation, we can move to the
//next one
if(annotationEditor.editingFinished()){
//queue an event to set the annotation as selected
selectAnnotation(aData);
//queue an event to show the annotation editor
Runnable action = new Runnable() {
@Override
public void run() {
annotationEditor.editAnnotation(aData.getAnnotation(),
aData.getAnnotationSet());
}
};
pendingEvents.offer(new PerformActionEvent(action));
eventMinder.restart();
}
}
private AnnotationData aData;
}
protected class SetSelectedAnnotationsAction extends AbstractAction{
public SetSelectedAnnotationsAction(boolean selected){
String title = (selected) ? "Select all" : "Unselect all";
putValue(NAME, title);
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("SPACE"));
this.selected = selected;
}
@Override
public void actionPerformed(ActionEvent evt){
List handlersToSelect = new ArrayList();
int[] selectedRows = mainTable.getSelectedRows();
Arrays.sort(selectedRows);
for (int row : selectedRows) {
Object handler = tableRows.get(row);
if(handler instanceof SetHandler){
// store the set handler
handlersToSelect.add(0, handler);
} else if(handler instanceof TypeHandler
&& !handlersToSelect.contains(((TypeHandler)handler).setHandler)){
// store the type handler
// only if not included in a previous selected set handler
handlersToSelect.add(handlersToSelect.size(), handler);
}
}
for (Object handler : handlersToSelect) {
if(handler instanceof TypeHandler){
TypeHandler tHandler = (TypeHandler)handler;
tHandler.setSelected(selected);
}else if(handler instanceof SetHandler){
SetHandler sHandler = (SetHandler)handler;
for (String setName : sHandler.set.getAllTypes()) {
TypeHandler tHandler = sHandler.getTypeHandler(setName);
tHandler.setSelected(selected);
}
}
}
}
boolean selected;
}
protected class DeleteSelectedAnnotationsAction extends AbstractAction{
public DeleteSelectedAnnotationsAction(String name){
putValue(NAME, name);
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("DELETE"));
}
@Override
public void actionPerformed(ActionEvent event){
// builds the list of type and set handlers to delete
Vector resourcesToDelete = new Vector();
List handlersToDelete = new ArrayList();
int[] selectedRows = mainTable.getSelectedRows();
Arrays.sort(selectedRows);
for (int row : selectedRows) {
Object handler = tableRows.get(row);
if(handler instanceof SetHandler){
// store the set handler
handlersToDelete.add(0, handler);
String setName = ((SetHandler) handler).set.getName();
setName = (setName == null)? "Default set" : setName;
resourcesToDelete.add("set: " + setName);
} else if(handler instanceof TypeHandler
&& !handlersToDelete.contains(((TypeHandler)handler).setHandler)){
// store the type handler
// only if not included in a previous selected set handler
handlersToDelete.add(handlersToDelete.size(), handler);
String setName = ((TypeHandler) handler).setHandler.set.getName();
setName = (setName == null)? "Default set" : setName;
resourcesToDelete.add("type: " + ((TypeHandler) handler).name
+ " in set: " + setName);
}
}
if ((event.getModifiers() & ActionEvent.SHIFT_MASK)
!= ActionEvent.SHIFT_MASK
&& (event.getModifiers() & InputEvent.BUTTON1_MASK)
!= InputEvent.BUTTON1_MASK) {
// shows a confirm dialog to delete types and sets
JList list = new JList(resourcesToDelete);
list.setVisibleRowCount(Math.min(resourcesToDelete.size()+1, 10));
int choice = JOptionPane.showOptionDialog(MainFrame.getInstance(), new
Object[]{"Are you sure you want to delete the following annotations?",
'\n', new JScrollPane(list),
"You can use Shift+Delete to bypass this dialog. \n\n"},
"Delete annotations",
JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
new String[]{"Delete annotations", "Cancel"}, "Cancel");
if (choice == JOptionPane.CLOSED_OPTION || choice == 1) { return; }
}
// deletes types and sets
for (Object handler : handlersToDelete) {
if(handler instanceof SetHandler){
SetHandler sHandler = (SetHandler)handler;
if(sHandler.set == document.getAnnotations()){
//the default annotation set - clear
for (Annotation annotation: new HashSet(sHandler.set)) {
sHandler.set.remove(annotation);
}
}else{
document.removeAnnotationSet(sHandler.set.getName());
}
} else if(handler instanceof TypeHandler){
TypeHandler tHandler = (TypeHandler)handler;
AnnotationSet set = tHandler.setHandler.set;
AnnotationSet toDeleteAS = set.get(tHandler.name);
if(toDeleteAS != null && toDeleteAS.size() > 0){
List toDelete = new ArrayList(toDeleteAS);
set.removeAll(toDelete);
}
}
}
}
}
List setHandlers;
/** Contains the data of the main table. */
List tableRows;
XJTable mainTable;
SetsTableModel tableModel;
JScrollPane scroller;
JPanel mainPanel;
JTextField newSetNameTextField;
TextualDocumentView textView;
AnnotationListView listView;
AnnotationStackView stackView;
JTextArea textPane;
gate.gui.annedit.OwnedAnnotationEditor annotationEditor;
NewAnnotationSetAction newSetAction;
/**
* The listener for mouse and mouse motion events in the text view.
*/
protected TextMouseListener textMouseListener;
/**
* Listener for property changes on the text pane.
*/
protected PropertyChangeListener textChangeListener;
/**
* Stores the list of visible annotation types when the view is inactivated
* so that the selection can be restored when the view is made active again.
* The values are String[2] pairs of form <set name, type>.
*/
protected BlockingQueue visibleAnnotationTypes;
protected Timer mouseMovementTimer;
/**
* Timer used to handle events coming from the document
*/
protected Timer eventMinder;
protected BlockingQueue pendingEvents;
private static final int MOUSE_MOVEMENT_TIMER_DELAY = 500;
protected MouseStoppedMovingAction mouseStoppedMovingAction;
protected String lastAnnotationType = "_New_";
protected ComponentOrientation currentOrientation;
protected final static ColorGenerator colourGenerator = new ColorGenerator();
private static final int NAME_COL = 1;
private static final int SELECTED_COL = 0;
/**
* A special GateEvent used as a flag.
*/
private static final GateEvent END_OF_LIST = new GateEvent(
AnnotationSetsView.class,
Integer.MAX_VALUE);
private static final int EVENTS_HANDLE_DELAY = 300;
}