0001 /*
0002 * Copyright (c) 1995-2010, The University of Sheffield. See the file
0003 * COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0004 *
0005 * This file is part of GATE (see http://gate.ac.uk/), and is free
0006 * software, licenced under the GNU Library General Public License,
0007 * Version 2, June 1991 (in the distribution as file licence.html,
0008 * and also available at http://gate.ac.uk/gate/licence.html).
0009 *
0010 * AnnotationEditor.java
0011 *
0012 * Valentin Tablan, Sep 10, 2007
0013 *
0014 * $Id: SchemaAnnotationEditor.java 13631 2011-04-06 11:41:56Z ian_roberts $
0015 */
0016
0017
0018 package gate.gui.annedit;
0019
0020 import java.awt.*;
0021 import java.awt.event.*;
0022 import java.util.*;
0023 import java.util.List;
0024
0025 import javax.swing.*;
0026 import javax.swing.border.Border;
0027 import javax.swing.event.*;
0028 import javax.swing.text.BadLocationException;
0029 import javax.swing.text.JTextComponent;
0030
0031 import gate.*;
0032 import gate.creole.*;
0033 import gate.event.*;
0034 import gate.gui.MainFrame;
0035 import gate.swing.JChoice;
0036 import gate.util.*;
0037
0038 public class SchemaAnnotationEditor extends AbstractVisualResource
0039 implements OwnedAnnotationEditor{
0040
0041 private static final long serialVersionUID = 1L;
0042
0043 public void editAnnotation(Annotation ann, AnnotationSet set) {
0044 //the external components we listen to (the text and the list view) can
0045 //change outside of our control, so we need to update the values frequently
0046 updateListeners();
0047
0048 this.annotation = ann;
0049 this.annSet = set;
0050 //update the selection in the list view
0051 //this is necessary because sometimes the call to eidtAnnotaiton is
0052 //internally received from the search and annotate function.
0053 //update the editor display
0054 String annType = annotation == null ? null : annotation.getType();
0055 //update the border for the types choice
0056 if(annType == null){
0057 //no annotation -> ok
0058 if(typesChoice.getBorder() != typeDefaultBorder)
0059 typesChoice.setBorder(typeDefaultBorder);
0060 }else{
0061 if(schemasByType.containsKey(annType)){
0062 //accepted type
0063 if(typesChoice.getBorder() != typeDefaultBorder)
0064 typesChoice.setBorder(typeDefaultBorder);
0065 }else{
0066 //wrong type
0067 if(typesChoice.getBorder() != typeHighlightBorder)
0068 typesChoice.setBorder(typeHighlightBorder);
0069
0070 }
0071 }
0072 //update the features editor
0073 SchemaFeaturesEditor newFeaturesEditor = featureEditorsByType.get(annType);
0074 //if new type, we need to change the features editor and selected type
0075 //button
0076 if(newFeaturesEditor != featuresEditor){
0077 typesChoice.setSelectedItem(annType);
0078 if(featuresEditor != null){
0079 featuresBox.remove(featuresEditor);
0080 featuresEditor.editFeatureMap(null);
0081 }
0082 featuresEditor = newFeaturesEditor;
0083 if(featuresEditor != null){
0084 featuresBox.add(featuresEditor);
0085 }
0086 }
0087 if(featuresEditor != null){
0088 FeatureMap features = ann.getFeatures();
0089 if(features == null){
0090 features = Factory.newFeatureMap();
0091 ann.setFeatures(features);
0092 }
0093 featuresEditor.editFeatureMap(features);
0094 }
0095 // enable editing if there is an annotation, disable if not
0096 setEditingEnabled(annType != null);
0097 if(dialog != null){
0098 if(annotation != null){
0099 placeDialog(annotation.getStartNode().getOffset().intValue(),
0100 annotation.getEndNode().getOffset().intValue());
0101 }else{
0102 //this should only occur when the dialog is pinned, so offsets are
0103 //irrelevant
0104 placeDialog(0,0);
0105 }
0106 }
0107 }
0108
0109 /**
0110 * This editor implementation is designed to enforce schema compliance. This
0111 * method will return <tt>false</tt> if the current annotation type does not
0112 * have a schema or if the features of the current annotation do not comply
0113 * with the schema.
0114 * @see gate.gui.annedit.OwnedAnnotationEditor#editingFinished()
0115 */
0116 public boolean editingFinished() {
0117 if(annotation == null) return true;
0118 //if the dialog is hidden, we've missed the train and we can't force
0119 //compliance for the old annotation any more. Just give up and
0120 //allow further editing
0121 if(!dialog.isVisible()) return true;
0122
0123 if(!schemasByType.containsKey(annotation.getType())) return false;
0124
0125 //we need to check that:
0126 //1) all required features have values
0127 //2) all features known by schema that have values, comply with the schema
0128 if(annotation == null) return true;
0129 AnnotationSchema aSchema = schemasByType.get(annotation.getType());
0130 if(aSchema.getFeatureSchemaSet() == null ||
0131 aSchema.getFeatureSchemaSet().isEmpty()){
0132 //known type but no schema restrictions -> OK
0133 return true;
0134 }
0135 FeatureMap annotationFeatures = annotation.getFeatures();
0136 Map<String, FeatureSchema> featureSchemaByName =
0137 new HashMap<String, FeatureSchema>();
0138 //store all the feature schemas, and check the required ones
0139 for(FeatureSchema aFeatureSchema : aSchema.getFeatureSchemaSet()){
0140 featureSchemaByName.put(aFeatureSchema.getFeatureName(), aFeatureSchema);
0141 Object featureValue = annotationFeatures == null ? null :
0142 annotationFeatures.get(aFeatureSchema.getFeatureName());
0143 if(aFeatureSchema.isRequired() && featureValue == null) return false;
0144 }
0145 //check all the actual values for compliance
0146 for(Object featureName : annotationFeatures.keySet()){
0147 Object featureValue = annotationFeatures.get(featureName);
0148 FeatureSchema fSchema = featureSchemaByName.get(featureName);
0149 if(fSchema != null){
0150 //this is a schema feature
0151 if(fSchema.getFeatureValueClass().equals(Boolean.class) ||
0152 fSchema.getFeatureValueClass().equals(Integer.class) ||
0153 fSchema.getFeatureValueClass().equals(Short.class) ||
0154 fSchema.getFeatureValueClass().equals(Byte.class) ||
0155 fSchema.getFeatureValueClass().equals(Float.class) ||
0156 fSchema.getFeatureValueClass().equals(Double.class)){
0157 //just check the right type
0158 if(!fSchema.getFeatureValueClass().isAssignableFrom(
0159 featureValue.getClass())){
0160 //invalid value type
0161 return false;
0162 }
0163 }else if(fSchema.getFeatureValueClass().equals(String.class)){
0164 if(fSchema.getPermittedValues() != null &&
0165 !fSchema.getPermittedValues().contains(featureValue)){
0166 //invalid value
0167 return false;
0168 }
0169 }
0170 }
0171 }
0172 return true;
0173 }
0174
0175 /**
0176 * Does nothing, as this editor does not support cancelling and rollbacks.
0177 */
0178 public void cancelAction() throws GateException {
0179 }
0180
0181 /**
0182 * Returns <tt>true</tt> always as this editor is generic and can edit any
0183 * annotation type.
0184 */
0185 public boolean canDisplayAnnotationType(String annotationType) {
0186 return true;
0187 }
0188
0189 /**
0190 * Does nothing as this editor works in auto-commit mode (changes are
0191 * implemented immediately).
0192 */
0193 public void okAction() throws GateException {
0194 }
0195
0196 /**
0197 * Returns <tt>false</tt>, as this editor does not support cancel operations.
0198 */
0199 public boolean supportsCancel() {
0200 return false;
0201 }
0202
0203 /* (non-Javadoc)
0204 * @see gate.gui.annedit.AnnotationEditor#isActive()
0205 */
0206 public boolean isActive() {
0207 return dialog.isVisible();
0208 }
0209
0210 /**
0211 * Finds the best location for the editor dialog for a given span of text
0212 */
0213 public void placeDialog(int start, int end){
0214 if(pinnedButton.isSelected()){
0215 //just resize
0216 Point where = null;
0217 if(dialog.isVisible()){
0218 // where = dialog.getLocationOnScreen();
0219 where = dialog.getLocation();
0220 }
0221
0222 dialog.pack();
0223 if(where != null){
0224 dialogLocation.move(where.x, where.y);
0225 dialog.setLocation(dialogLocation);
0226 }
0227 }else{
0228 //calculate position
0229 try{
0230 Rectangle startRect = owner.getTextComponent().modelToView(start);
0231 Rectangle endRect = owner.getTextComponent().modelToView(end);
0232 Point topLeft = owner.getTextComponent().getLocationOnScreen();
0233 int x = topLeft.x + startRect.x;
0234 int y = topLeft.y + endRect.y + endRect.height;
0235
0236 //make sure the window doesn't start lower
0237 //than the end of the visible rectangle
0238 Rectangle visRect = owner.getTextComponent().getVisibleRect();
0239 int maxY = topLeft.y + visRect.y + visRect.height;
0240
0241 //make sure window doesn't get off-screen
0242 dialog.pack();
0243 // dialog.validate();
0244 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
0245 boolean revalidate = false;
0246 if(dialog.getSize().width > screenSize.width){
0247 dialog.setSize(screenSize.width, dialog.getSize().height);
0248 revalidate = true;
0249 }
0250 if(dialog.getSize().height > screenSize.height){
0251 dialog.setSize(dialog.getSize().width, screenSize.height);
0252 revalidate = true;
0253 }
0254
0255 if(revalidate) dialog.validate();
0256 //calculate max X
0257 int maxX = screenSize.width - dialog.getSize().width;
0258 //calculate max Y
0259 if(maxY + dialog.getSize().height > screenSize.height){
0260 maxY = screenSize.height - dialog.getSize().height;
0261 }
0262
0263 //correct position
0264 if(y > maxY) y = maxY;
0265 if(x > maxX) x = maxX;
0266 dialogLocation.move(x, y);
0267 dialog.setLocation(dialogLocation);
0268 }catch(BadLocationException ble){
0269 //this should never occur
0270 throw new GateRuntimeException(ble);
0271 }
0272 }
0273 if(!dialog.isVisible()) dialog.setVisible(true);
0274 }
0275
0276 protected static final int HIDE_DELAY = 1500;
0277 protected static final int SHIFT_INCREMENT = 5;
0278 protected static final int CTRL_SHIFT_INCREMENT = 10;
0279
0280
0281 /**
0282 * The annotation currently being edited.
0283 */
0284 protected Annotation annotation;
0285
0286 /**
0287 * The annotation set containing the currently edited annotation.
0288 */
0289 protected AnnotationSet annSet;
0290
0291 /**
0292 * The controlling object for this editor.
0293 */
0294 private AnnotationEditorOwner owner;
0295
0296 /**
0297 * The text component (obtained from the owner) that this editor listens to.
0298 */
0299 private JTextComponent textComponent;
0300
0301 /**
0302 * JChoice used for selecting the annotation type.
0303 */
0304 protected JChoice typesChoice;
0305
0306 /**
0307 * The default border for the types choice
0308 */
0309 protected Border typeDefaultBorder;
0310
0311 /**
0312 * The highlight border for the types choice
0313 */
0314 protected Border typeHighlightBorder;
0315
0316 /**
0317 * The dialog used to show this annotation editor.
0318 */
0319 protected JDialog dialog;
0320
0321 protected CreoleListener creoleListener;
0322
0323 /**
0324 * Listener used to hide the editing window when the text is hidden.
0325 */
0326 protected AncestorListener textAncestorListener;
0327
0328
0329 /**
0330 * Stores the Annotation schema objects available in the system.
0331 * The annotation types are used as keys for the map.
0332 */
0333 protected Map<String, AnnotationSchema> schemasByType;
0334
0335 /**
0336 * Caches the features editor for each annotation type.
0337 */
0338 protected Map<String, SchemaFeaturesEditor> featureEditorsByType;
0339
0340 /**
0341 * The box used to host the features editor pane.
0342 */
0343 protected Box featuresBox;
0344
0345 /**
0346 * Toggle button used to pin down the dialog.
0347 */
0348 protected JToggleButton pinnedButton;
0349
0350 /**
0351 * The current features editor, one of the ones stored in
0352 * {@link #featureEditorsByType}.
0353 */
0354 protected SchemaFeaturesEditor featuresEditor = null;
0355
0356 protected MouseEvent pressed;
0357
0358
0359 public SchemaAnnotationEditor(){
0360 initData();
0361 }
0362
0363
0364 /* (non-Javadoc)
0365 * @see gate.creole.AbstractVisualResource#init()
0366 */
0367 @Override
0368 public Resource init() throws ResourceInstantiationException {
0369 super.init();
0370 initGui();
0371 initListeners();
0372 return this;
0373 }
0374
0375 protected void updateListeners(){
0376 if(owner != null){
0377 //we have a new owner
0378 //if the components that we listen to have changed, we need to update the
0379 //listeners
0380 if(textComponent != getOwner().getTextComponent()){
0381 //remove old listener
0382 if(textComponent != null){
0383 textComponent.removeAncestorListener(textAncestorListener);
0384 }
0385 this.textComponent = owner.getTextComponent();
0386 //register new listener
0387 if(textComponent != null){
0388 textComponent.addAncestorListener(textAncestorListener);
0389 }
0390 }
0391 }else{
0392 //no new owner -> just remove old listeners
0393 if(textComponent != null){
0394 textComponent.removeAncestorListener(textAncestorListener);
0395 }
0396 }
0397 }
0398
0399
0400 protected void initData(){
0401 schemasByType = new TreeMap<String, AnnotationSchema>();
0402 for(LanguageResource aSchema : Gate.getCreoleRegister().
0403 getLrInstances("gate.creole.AnnotationSchema")){
0404 schemasByType.put(((AnnotationSchema)aSchema).getAnnotationName(),
0405 (AnnotationSchema)aSchema);
0406 }
0407 creoleListener = new CreoleListener(){
0408 public void resourceLoaded(CreoleEvent e){
0409 Resource newResource = e.getResource();
0410 if(newResource instanceof AnnotationSchema){
0411 AnnotationSchema aSchema = (AnnotationSchema)newResource;
0412 schemasByType.put(aSchema.getAnnotationName(), aSchema);
0413 }
0414 }
0415
0416 public void resourceUnloaded(CreoleEvent e){
0417 Resource newResource = e.getResource();
0418 if(newResource instanceof AnnotationSchema){
0419 AnnotationSchema aSchema = (AnnotationSchema)newResource;
0420 if(schemasByType.containsValue(aSchema)){
0421 schemasByType.remove(aSchema.getAnnotationName());
0422 }
0423 }
0424 }
0425
0426 public void datastoreOpened(CreoleEvent e){
0427
0428 }
0429 public void datastoreCreated(CreoleEvent e){
0430
0431 }
0432 public void datastoreClosed(CreoleEvent e){
0433
0434 }
0435 public void resourceRenamed(Resource resource,
0436 String oldName,
0437 String newName){
0438 }
0439 };
0440 Gate.getCreoleRegister().addCreoleListener(creoleListener);
0441
0442 textAncestorListener = new AncestorListener(){
0443 /**
0444 * A flag used to mark the fact that the dialog is active and was hidden
0445 * by this listener.
0446 */
0447 private boolean dialogActive = false;
0448
0449 public void ancestorAdded(AncestorEvent event) {
0450 if(dialogActive){
0451 if(annotation != null){
0452 placeDialog(annotation.getStartNode().getOffset().intValue(),
0453 annotation.getEndNode().getOffset().intValue());
0454 }
0455 dialogActive = false;
0456 }
0457 }
0458 public void ancestorMoved(AncestorEvent event) {
0459 if(dialog.isVisible() && annotation != null){
0460 placeDialog(annotation.getStartNode().getOffset().intValue(),
0461 annotation.getEndNode().getOffset().intValue());
0462 }
0463 }
0464
0465 public void ancestorRemoved(AncestorEvent event) {
0466 if(dialog.isVisible()){
0467 dialogActive = true;
0468 dialog.setVisible(false);
0469 }
0470 }
0471 };
0472
0473 }
0474
0475 public void cleanup(){
0476 Gate.getCreoleRegister().removeCreoleListener(creoleListener);
0477 }
0478
0479 protected void initGui(){
0480
0481 //make the dialog
0482 Window parentWindow =
0483 SwingUtilities.windowForComponent(owner.getTextComponent());
0484 if(parentWindow != null){
0485 dialog = parentWindow instanceof Frame ?
0486 new JDialog((Frame)parentWindow,
0487 "Annotation Editor Dialog", false) :
0488 new JDialog((Dialog)parentWindow,
0489 "Annotation Editor Dialog", false);
0490 dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
0491 MainFrame.getGuiRoots().add(dialog);
0492
0493 }
0494
0495 setLayout(new BorderLayout());
0496
0497 //build the toolbar
0498 JPanel tBar = new JPanel();
0499 tBar.setLayout(new GridBagLayout());
0500 GridBagConstraints constraints = new GridBagConstraints();
0501 constraints.gridx = GridBagConstraints.RELATIVE;
0502 constraints.gridy = 0;
0503 constraints.weightx = 0;
0504
0505 solButton = new IconOnlyButton(null);
0506 solButton.setIcon(MainFrame.getIcon("bounds-sol"));
0507 solButton.setPressedIcon(MainFrame.getIcon("bounds-sol-pressed"));
0508 tBar.add(solButton, constraints);
0509 JLabel aLabel = new JLabel(MainFrame.getIcon("bounds-left"));
0510 aLabel.setBorder(null);
0511 tBar.add(aLabel, constraints);
0512 sorButton = new IconOnlyButton(null);
0513 sorButton.setIcon(MainFrame.getIcon("bounds-sor"));
0514 sorButton.setPressedIcon(MainFrame.getIcon("bounds-sor-pressed"));
0515 tBar.add(sorButton, constraints);
0516 aLabel = new JLabel(MainFrame.getIcon("bounds-span"));
0517 aLabel.setBorder(null);
0518 tBar.add(aLabel, constraints);
0519 eolButton = new IconOnlyButton(null);
0520 eolButton.setIcon(MainFrame.getIcon("bounds-eol"));
0521 eolButton.setPressedIcon(MainFrame.getIcon("bounds-eol-pressed"));
0522 tBar.add(eolButton, constraints);
0523 aLabel = new JLabel(MainFrame.getIcon("bounds-right"));
0524 aLabel.setBorder(null);
0525 tBar.add(aLabel, constraints);
0526 eorButton = new IconOnlyButton(null);
0527 eorButton.setIcon(MainFrame.getIcon("bounds-eor"));
0528 eorButton.setPressedIcon(MainFrame.getIcon("bounds-eor-pressed"));
0529 tBar.add(eorButton, constraints);
0530
0531 tBar.add(Box.createHorizontalStrut(15), constraints);
0532 tBar.add(delButton = new SmallButton(null), constraints);
0533 constraints.weightx = 1;
0534 tBar.add(Box.createHorizontalGlue(), constraints);
0535
0536 constraints.weightx = 0;
0537 pinnedButton = new JToggleButton(MainFrame.getIcon("pin"));
0538 pinnedButton.setSelectedIcon(MainFrame.getIcon("pin-in"));
0539 pinnedButton.setSelected(false);
0540 pinnedButton.setToolTipText("Press to pin window in place.");
0541 pinnedButton.setMargin(new Insets(0, 2, 0, 2));
0542 pinnedButton.setBorderPainted(false);
0543 pinnedButton.setContentAreaFilled(false);
0544 tBar.add(pinnedButton);
0545
0546 add(tBar, BorderLayout.NORTH);
0547
0548 //build the main pane
0549 mainPane = new JPanel();
0550 mainPane.setLayout(new BorderLayout());
0551
0552 featureEditorsByType = new HashMap<String, SchemaFeaturesEditor>();
0553 //for each schema we need to create a type button and a features editor
0554 for(String annType : schemasByType.keySet()){
0555 AnnotationSchema annSchema = schemasByType.get(annType);
0556 SchemaFeaturesEditor aFeaturesEditor = new SchemaFeaturesEditor(annSchema);
0557 featureEditorsByType.put(annType, aFeaturesEditor);
0558 }
0559 List<String> typeList = new ArrayList<String>(schemasByType.keySet());
0560 Collections.sort(typeList);
0561 String[] typesArray = new String[typeList.size()];
0562 typeList.toArray(typesArray);
0563 typesChoice = new JChoice(typesArray);
0564 typesChoice.setDefaultButtonMargin(new Insets(0, 2, 0, 2));
0565 typesChoice.setMaximumFastChoices(20);
0566 typesChoice.setMaximumWidth(300);
0567 String aTitle = "Type ";
0568
0569 Border titleBorder = BorderFactory.createTitledBorder(aTitle);
0570 typeDefaultBorder = BorderFactory.createCompoundBorder(titleBorder,
0571 BorderFactory.createEmptyBorder(2, 2, 2, 2));
0572 typeHighlightBorder = BorderFactory.createCompoundBorder(titleBorder,
0573 BorderFactory.createLineBorder(Color.RED, 2));
0574
0575 typesChoice.setBorder(typeDefaultBorder);
0576 aLabel = new JLabel(aTitle);
0577 typesChoice.setMinimumSize(
0578 new Dimension(aLabel.getPreferredSize().width, 0));
0579 mainPane.add(typesChoice, BorderLayout.NORTH);
0580
0581 //add the features box
0582 featuresBox = Box.createVerticalBox();
0583 aTitle = "Features ";
0584 featuresBox.setBorder(BorderFactory.createTitledBorder(aTitle));
0585 aLabel = new JLabel(aTitle);
0586 mainPane.add(featuresBox, BorderLayout.SOUTH);
0587
0588 add(mainPane, BorderLayout.CENTER);
0589
0590 // add the search and annotate GUI at the bottom of the annotator editor
0591 SearchAndAnnotatePanel searchPanel =
0592 new SearchAndAnnotatePanel(mainPane.getBackground(), this, dialog);
0593
0594 add(searchPanel, BorderLayout.SOUTH);
0595
0596 dialog.add(this);
0597 dialog.pack();
0598 }
0599
0600 protected void initListeners(){
0601 typesChoice.addActionListener(new ActionListener(){
0602 public void actionPerformed(ActionEvent e) {
0603 String newType;
0604 if(typesChoice.getSelectedItem() == null){
0605 newType = "";
0606 }else{
0607 newType = typesChoice.getSelectedItem().toString();
0608 }
0609 if(annotation != null && annSet != null &&
0610 !annotation.getType().equals(newType)){
0611 //annotation type change
0612 Integer oldId = annotation.getId();
0613 Annotation oldAnn = annotation;
0614 annSet.remove(oldAnn);
0615 try{
0616 annSet.add(oldId, oldAnn.getStartNode().getOffset(),
0617 oldAnn.getEndNode().getOffset(),
0618 newType, oldAnn.getFeatures());
0619 Annotation newAnn = annSet.get(oldId);
0620 //update the selection to the new annotation
0621 getOwner().selectAnnotation(new AnnotationDataImpl(annSet, newAnn));
0622 editAnnotation(newAnn, annSet);
0623 owner.annotationChanged(newAnn, annSet, oldAnn.getType());
0624 }catch(InvalidOffsetException ioe){
0625 //this should never happen
0626 throw new LuckyException(ioe);
0627 }
0628 }
0629 }
0630 });
0631
0632 dialog.addWindowListener(new WindowAdapter(){
0633 public void windowClosing(WindowEvent e) {
0634 if(editingFinished()){
0635 //we can close
0636 dialog.setVisible(false);
0637 if(pinnedButton.isSelected())pinnedButton.setSelected(false);
0638 }else{
0639 //let's be really snotty
0640 getToolkit().beep();
0641 }
0642 }
0643 });
0644
0645 dialog.getRootPane().addMouseListener(new MouseAdapter() {
0646 // allow dialog to be dragged with a mouse
0647 public void mousePressed(MouseEvent me) {
0648 pressed = me;
0649 }
0650 });
0651
0652 dialog.getRootPane().addMouseMotionListener(new MouseMotionAdapter() {
0653 Point location;
0654 // allow a dialog to be dragged with a mouse
0655 public void mouseDragged(MouseEvent me) {
0656 location = dialog.getLocation(location);
0657 int x = location.x - pressed.getX() + me.getX();
0658 int y = location.y - pressed.getY() + me.getY();
0659 dialog.setLocation(x, y);
0660 pinnedButton.setSelected(true);
0661 }
0662 });
0663
0664 dialog.addComponentListener(new ComponentAdapter(){
0665 /* (non-Javadoc)
0666 * @see java.awt.event.ComponentAdapter#componentMoved(java.awt.event.ComponentEvent)
0667 */
0668 @Override
0669 public void componentMoved(ComponentEvent e) {
0670 Point newLocation = dialog.getLocation();
0671 if(!newLocation.equals(dialogLocation)){
0672 pinnedButton.setSelected(true);
0673 }
0674 }
0675 });
0676
0677 InputMap inputMap = ((JComponent)dialog.getContentPane()).
0678 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0679 actionMap = ((JComponent)dialog.getContentPane()).getActionMap();
0680 // add the key-action bindings of this Component to the parent window
0681
0682 solAction =
0683 new StartOffsetLeftAction("", MainFrame.getIcon("extend-left"),
0684 "<html><b>Extend start</b><small>" +
0685 "<br>LEFT = 1 character" +
0686 "<br> + SHIFT = 5 characters, "+
0687 "<br> + CTRL + SHIFT = 10 characters</small></html>",
0688 KeyEvent.VK_LEFT);
0689 solButton.setAction(solAction);
0690 inputMap.put(KeyStroke.getKeyStroke("LEFT"), "solAction");
0691 inputMap.put(KeyStroke.getKeyStroke("shift LEFT"), "solAction");
0692 inputMap.put(KeyStroke.getKeyStroke("control shift released LEFT"), "solAction");
0693 actionMap.put("solAction", solAction);
0694
0695 sorAction =
0696 new StartOffsetRightAction("", MainFrame.getIcon("extend-right"),
0697 "<html><b>Shrink start</b><small>" +
0698 "<br>RIGHT = 1 character" +
0699 "<br> + SHIFT = 5 characters, "+
0700 "<br> + CTRL + SHIFT = 10 characters</small></html>",
0701 KeyEvent.VK_RIGHT);
0702 sorButton.setAction(sorAction);
0703 inputMap.put(KeyStroke.getKeyStroke("RIGHT"), "sorAction");
0704 inputMap.put(KeyStroke.getKeyStroke("shift RIGHT"), "sorAction");
0705 inputMap.put(KeyStroke.getKeyStroke("control shift released RIGHT"), "sorAction");
0706 actionMap.put("sorAction", sorAction);
0707
0708 delAction =
0709 new DeleteAnnotationAction("", MainFrame.getIcon("remove-annotation"),
0710 "Delete the annotation", KeyEvent.VK_DELETE);
0711 delButton.setAction(delAction);
0712 inputMap.put(KeyStroke.getKeyStroke("alt DELETE"), "delAction");
0713 actionMap.put("delAction", delAction);
0714
0715 eolAction =
0716 new EndOffsetLeftAction("", MainFrame.getIcon("extend-left"),
0717 "<html><b>Shrink end</b><small>" +
0718 "<br>ALT + LEFT = 1 character" +
0719 "<br> + SHIFT = 5 characters, "+
0720 "<br> + CTRL + SHIFT = 10 characters</small></html>",
0721 KeyEvent.VK_LEFT);
0722 eolButton.setAction(eolAction);
0723 inputMap.put(KeyStroke.getKeyStroke("alt LEFT"), "eolAction");
0724 inputMap.put(KeyStroke.getKeyStroke("alt shift LEFT"), "eolAction");
0725 inputMap.put(KeyStroke.getKeyStroke("control alt shift released LEFT"), "eolAction");
0726 actionMap.put("eolAction", eolAction);
0727
0728 eorAction =
0729 new EndOffsetRightAction("", MainFrame.getIcon("extend-right"),
0730 "<html><b>Extend end</b><small>" +
0731 "<br>ALT + RIGHT = 1 character" +
0732 "<br> + SHIFT = 5 characters, "+
0733 "<br> + CTRL + SHIFT = 10 characters</small></html>",
0734 KeyEvent.VK_RIGHT);
0735 eorButton.setAction(eorAction);
0736 inputMap.put(KeyStroke.getKeyStroke("alt RIGHT"), "eorAction");
0737 inputMap.put(KeyStroke.getKeyStroke("alt shift RIGHT"), "eorAction");
0738 inputMap.put(KeyStroke.getKeyStroke("control alt shift released RIGHT"), "eorAction");
0739 actionMap.put("eorAction", eorAction);
0740
0741 Action dismissAction = new AbstractAction() {
0742 private static final long serialVersionUID = 1L;
0743 public void actionPerformed(ActionEvent evt){
0744 dialog.setVisible(false);
0745 }
0746 };
0747 inputMap.put(KeyStroke.getKeyStroke("ESCAPE"), "dismissAction");
0748 actionMap.put("dismissAction", dismissAction);
0749
0750 }
0751
0752 /**
0753 * Stores the currently set dialog location (which is used to identify cases
0754 * when the dialog was moved by hand, which causes the dialog to be pinned).
0755 */
0756 private Point dialogLocation = new Point(0, 0);
0757
0758
0759 /**
0760 * @param args
0761 */
0762 public static void main(String[] args) {
0763 try {
0764 Gate.init();
0765
0766
0767 JFrame aFrame = new JFrame("New Annotation Editor");
0768 aFrame.setSize( 800, 600);
0769 aFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
0770
0771 JDialog annDialog = new JDialog(aFrame, "Annotation Editor Dialog", false);
0772 annDialog.setFocusableWindowState(false);
0773 // annDialog.setResizable(false);
0774 // annDialog.setUndecorated(true);
0775
0776 SchemaAnnotationEditor pane = new SchemaAnnotationEditor();
0777 annDialog.add(pane);
0778 annDialog.pack();
0779
0780 // JToolBar tBar = new JToolBar("Annotation Editor", JToolBar.HORIZONTAL);
0781 // tBar.setLayout(new BorderLayout());
0782 // tBar.setMinimumSize(tBar.getPreferredSize());
0783 // tBar.add(pane);
0784 // aFrame.getContentPane().add(tBar, BorderLayout.NORTH);
0785
0786 StringBuffer strBuf = new StringBuffer();
0787 for(int i = 0; i < 100; i++){
0788 strBuf.append("The quick brown fox jumped over the lazy dog.\n");
0789 }
0790 JTextArea aTextPane = new JTextArea(strBuf.toString());
0791 JScrollPane scroller = new JScrollPane(aTextPane);
0792 aFrame.getContentPane().add(scroller, BorderLayout.CENTER);
0793
0794 // Box aBox = Box.createVerticalBox();
0795 // aFrame.getContentPane().add(aBox);
0796 //
0797 // FeatureEditor aFeatEditor = new FeatureEditor("F-nominal-small",
0798 // FeatureType.nominal, "val1");
0799 // aFeatEditor.setValues(new String[]{"val1", "val2", "val3"});
0800 // aBox.add(aFeatEditor.getGui());
0801 //
0802 // aFeatEditor = new FeatureEditor("F-nominal-large",
0803 // FeatureType.nominal, "val1");
0804 // aFeatEditor.setValues(new String[]{"val1", "val2", "val3", "val4", "val5",
0805 // "val6", "val7", "val8", "val9"});
0806 // aBox.add(aFeatEditor.getGui());
0807 //
0808 // aFeatEditor = new FeatureEditor("F-boolean-true",
0809 // FeatureType.bool, "true");
0810 // aBox.add(aFeatEditor.getGui());
0811 //
0812 // aFeatEditor = new FeatureEditor("F-boolean-false",
0813 // FeatureType.bool, "false");
0814 // aBox.add(aFeatEditor.getGui());
0815
0816 aFrame.setVisible(true);
0817 System.out.println("Window up");
0818 annDialog.setVisible(true);
0819 System.out.println("Dialog up");
0820
0821 }catch(HeadlessException e) {
0822 e.printStackTrace();
0823 }
0824 catch(GateException e) {
0825 e.printStackTrace();
0826 }
0827 }
0828
0829 /**
0830 * Base class for actions on annotations.
0831 */
0832 protected abstract class AnnotationAction extends AbstractAction{
0833 public AnnotationAction(String text, Icon icon,
0834 String desc, int mnemonic){
0835 super(text, icon);
0836 putValue(SHORT_DESCRIPTION, desc);
0837 putValue(MNEMONIC_KEY, mnemonic);
0838 }
0839 }
0840
0841 protected class StartOffsetLeftAction extends AnnotationAction{
0842 private static final long serialVersionUID = 1L;
0843 public StartOffsetLeftAction(String text, Icon icon,
0844 String desc, int mnemonic) {
0845 super(text, icon, desc, mnemonic);
0846 }
0847 public void actionPerformed(ActionEvent evt){
0848 int increment = 1;
0849 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
0850 //CTRL pressed -> use tokens for advancing
0851 increment = SHIFT_INCREMENT;
0852 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
0853 increment = CTRL_SHIFT_INCREMENT;
0854 }
0855 }
0856 long newValue = annotation.getStartNode().getOffset().longValue() - increment;
0857 if(newValue < 0) newValue = 0;
0858 try{
0859 moveAnnotation(annSet, annotation, new Long(newValue),
0860 annotation.getEndNode().getOffset());
0861 }catch(InvalidOffsetException ioe){
0862 throw new GateRuntimeException(ioe);
0863 }
0864 }
0865 }
0866
0867 protected class StartOffsetRightAction extends AnnotationAction{
0868 private static final long serialVersionUID = 1L;
0869 public StartOffsetRightAction(String text, Icon icon,
0870 String desc, int mnemonic) {
0871 super(text, icon, desc, mnemonic);
0872 }
0873 public void actionPerformed(ActionEvent evt){
0874 long endOffset = annotation.getEndNode().getOffset().longValue();
0875 int increment = 1;
0876 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
0877 //CTRL pressed -> use tokens for advancing
0878 increment = SHIFT_INCREMENT;
0879 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
0880 increment = CTRL_SHIFT_INCREMENT;
0881 }
0882 }
0883
0884 long newValue = annotation.getStartNode().getOffset().longValue() + increment;
0885 if(newValue > endOffset) newValue = endOffset;
0886 try{
0887 moveAnnotation(annSet, annotation, new Long(newValue),
0888 annotation.getEndNode().getOffset());
0889 }catch(InvalidOffsetException ioe){
0890 throw new GateRuntimeException(ioe);
0891 }
0892 }
0893 }
0894
0895 protected class EndOffsetLeftAction extends AnnotationAction{
0896 private static final long serialVersionUID = 1L;
0897 public EndOffsetLeftAction(String text, Icon icon,
0898 String desc, int mnemonic) {
0899 super(text, icon, desc, mnemonic);
0900 }
0901 public void actionPerformed(ActionEvent evt){
0902 long startOffset = annotation.getStartNode().getOffset().longValue();
0903 int increment = 1;
0904 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
0905 //CTRL pressed -> use tokens for advancing
0906 increment = SHIFT_INCREMENT;
0907 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
0908 increment =CTRL_SHIFT_INCREMENT;
0909 }
0910 }
0911
0912 long newValue = annotation.getEndNode().getOffset().longValue() - increment;
0913 if(newValue < startOffset) newValue = startOffset;
0914 try{
0915 moveAnnotation(annSet, annotation, annotation.getStartNode().getOffset(),
0916 new Long(newValue));
0917 }catch(InvalidOffsetException ioe){
0918 throw new GateRuntimeException(ioe);
0919 }
0920 }
0921 }
0922
0923 protected class EndOffsetRightAction extends AnnotationAction{
0924 private static final long serialVersionUID = 1L;
0925 public EndOffsetRightAction(String text, Icon icon,
0926 String desc, int mnemonic) {
0927 super(text, icon, desc, mnemonic);
0928 }
0929 public void actionPerformed(ActionEvent evt){
0930 long maxOffset = owner.getDocument().
0931 getContent().size().longValue() -1;
0932 int increment = 1;
0933 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
0934 //CTRL pressed -> use tokens for advancing
0935 increment = SHIFT_INCREMENT;
0936 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
0937 increment = CTRL_SHIFT_INCREMENT;
0938 }
0939 }
0940 long newValue = annotation.getEndNode().getOffset().longValue() + increment;
0941 if(newValue > maxOffset) newValue = maxOffset;
0942 try{
0943 moveAnnotation(annSet, annotation, annotation.getStartNode().getOffset(),
0944 new Long(newValue));
0945 }catch(InvalidOffsetException ioe){
0946 throw new GateRuntimeException(ioe);
0947 }
0948 }
0949 }
0950
0951 protected class DeleteAnnotationAction extends AnnotationAction{
0952 private static final long serialVersionUID = 1L;
0953 public DeleteAnnotationAction(String text, Icon icon,
0954 String desc, int mnemonic) {
0955 super(text, icon, desc, mnemonic);
0956 }
0957 public void actionPerformed(ActionEvent evt){
0958 annSet.remove(annotation);
0959
0960 //clear the dialog
0961 editAnnotation(null, annSet);
0962
0963 if(!pinnedButton.isSelected()){
0964 //if not pinned, hide the dialog.
0965 dialog.setVisible(false);
0966 } else {
0967 setEditingEnabled(false);
0968 }
0969 }
0970 }
0971
0972 /**
0973 * Changes the span of an existing annotation by creating a new annotation
0974 * with the same ID, type and features but with the new start and end offsets.
0975 * @param set the annotation set
0976 * @param oldAnnotation the annotation to be moved
0977 * @param newStartOffset the new start offset
0978 * @param newEndOffset the new end offset
0979 */
0980 protected void moveAnnotation(AnnotationSet set, Annotation oldAnnotation,
0981 Long newStartOffset, Long newEndOffset) throws InvalidOffsetException{
0982 //Moving is done by deleting the old annotation and creating a new one.
0983 //If this was the last one of one type it would mess up the gui which
0984 //"forgets" about this type and then it recreates it (with a different
0985 //colour and not visible.
0986 //In order to avoid this problem, we'll create a new temporary annotation.
0987 Annotation tempAnn = null;
0988 if(set.get(oldAnnotation.getType()).size() == 1){
0989 //create a clone of the annotation that will be deleted, to act as a
0990 //placeholder
0991 Integer tempAnnId = set.add(oldAnnotation.getStartNode(),
0992 oldAnnotation.getStartNode(), oldAnnotation.getType(),
0993 oldAnnotation.getFeatures());
0994 tempAnn = set.get(tempAnnId);
0995 }
0996
0997 Integer oldID = oldAnnotation.getId();
0998 set.remove(oldAnnotation);
0999 set.add(oldID, newStartOffset, newEndOffset,
1000 oldAnnotation.getType(), oldAnnotation.getFeatures());
1001 Annotation newAnn = set.get(oldID);
1002 //update the selection to the new annotation
1003 getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
1004 editAnnotation(newAnn, set);
1005 //remove the temporary annotation
1006 if(tempAnn != null) set.remove(tempAnn);
1007 owner.annotationChanged(newAnn, set, null);
1008 }
1009
1010 /**
1011 * A JButton with content are not filled and border not painted (in order to
1012 * save screen real estate)
1013 */
1014 protected class SmallButton extends JButton{
1015
1016 private static final long serialVersionUID = 1L;
1017
1018 public SmallButton(Action a) {
1019 super(a);
1020 // setBorder(null);
1021 setMargin(new Insets(0, 2, 0, 2));
1022 // setBorderPainted(false);
1023 // setContentAreaFilled(false);
1024 }
1025 }
1026
1027 protected class IconOnlyButton extends JButton{
1028
1029 private static final long serialVersionUID = 1L;
1030
1031 public IconOnlyButton(Action a) {
1032 super(a);
1033 setMargin(new Insets(0, 0, 0, 0));
1034 setBorder(null);
1035 setBorderPainted(false);
1036 setContentAreaFilled(false);
1037 }
1038 }
1039
1040 protected IconOnlyButton solButton;
1041 protected IconOnlyButton sorButton;
1042 protected SmallButton delButton;
1043 protected IconOnlyButton eolButton;
1044 protected IconOnlyButton eorButton;
1045 protected JPanel mainPane;
1046
1047 /**
1048 * Action bindings for the popup window.
1049 */
1050 ActionMap actionMap;
1051
1052 private StartOffsetLeftAction solAction;
1053 private StartOffsetRightAction sorAction;
1054 private DeleteAnnotationAction delAction;
1055 private EndOffsetLeftAction eolAction;
1056 private EndOffsetRightAction eorAction;
1057
1058 /**
1059 * @return the owner
1060 */
1061 public AnnotationEditorOwner getOwner() {
1062 return owner;
1063 }
1064
1065 /**
1066 * @param owner the owner to set
1067 */
1068 public void setOwner(AnnotationEditorOwner owner) {
1069 //if the owner is new, register existing listeners to new owner elements
1070 if(this.owner != owner){
1071 this.owner = owner;
1072 updateListeners();
1073 }
1074 }
1075
1076 public AnnotationSet getAnnotationSetCurrentlyEdited() {
1077 return annSet;
1078 }
1079
1080 public Annotation getAnnotationCurrentlyEdited() {
1081 return annotation;
1082 }
1083
1084 public void setPinnedMode(boolean pinned) {
1085 pinnedButton.setSelected(pinned);
1086 }
1087
1088 public void setEditingEnabled(boolean isEditingEnabled) {
1089 solButton.setEnabled(isEditingEnabled);
1090 sorButton.setEnabled(isEditingEnabled);
1091 delButton.setEnabled(isEditingEnabled);
1092 eolButton.setEnabled(isEditingEnabled);
1093 eorButton.setEnabled(isEditingEnabled);
1094 for (Component c : typesChoice.getComponents()) {
1095 c.setEnabled(isEditingEnabled);
1096 }
1097 // en/disable the components in the featuresBox
1098 Vector<Component> components = new Vector<Component>();
1099 Collections.addAll(components, featuresBox.getComponents());
1100 while (!components.isEmpty()) {
1101 Component component = components.remove(0);
1102 if (component instanceof JToggleButton
1103 || component instanceof JTextField) {
1104 component.setEnabled(isEditingEnabled);
1105 } else if (component instanceof Container) {
1106 Collections.addAll(components,
1107 ((Container)component).getComponents());
1108 }
1109 }
1110 // enable/disable the key binding actions
1111 if (isEditingEnabled) {
1112 actionMap.put("solAction", solAction);
1113 actionMap.put("sorAction", sorAction);
1114 actionMap.put("delAction", delAction);
1115 actionMap.put("eolAction", eolAction);
1116 actionMap.put("eorAction", eorAction);
1117 } else {
1118 actionMap.put("solAction", null);
1119 actionMap.put("sorAction", null);
1120 actionMap.put("delAction", null);
1121 actionMap.put("eolAction", null);
1122 actionMap.put("eorAction", null);
1123 }
1124 }
1125
1126 }
|