001 /*
002 * Copyright (c) 1995-2010, The University of Sheffield. See the file
003 * COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004 *
005 * This file is part of GATE (see http://gate.ac.uk/), and is free
006 * software, licenced under the GNU Library General Public License,
007 * Version 2, June 1991 (in the distribution as file licence.html,
008 * and also available at http://gate.ac.uk/gate/licence.html).
009 *
010 * AnnotationEditor.java
011 *
012 * Valentin Tablan, Apr 5, 2004
013 *
014 * $Id: AnnotationEditor.java 12399 2010-03-25 20:45:27Z thomas_heitz $
015 */
016
017 package gate.gui.docview;
018
019 import java.awt.*;
020 import java.awt.event.*;
021 import java.util.*;
022
023 import javax.swing.*;
024 import javax.swing.Timer;
025 import javax.swing.event.AncestorEvent;
026 import javax.swing.event.AncestorListener;
027 import javax.swing.table.TableCellRenderer;
028 import javax.swing.text.BadLocationException;
029
030 import gate.*;
031 import gate.creole.*;
032 import gate.event.CreoleEvent;
033 import gate.event.CreoleListener;
034 import gate.gui.FeaturesSchemaEditor;
035 import gate.gui.MainFrame;
036 import gate.gui.annedit.*;
037 import gate.util.*;
038
039
040 /**
041 * A generic annotation editor, which uses the known annotation schemas to help
042 * speed up the annotation process (e.g. by pre-populating sets of choices) but
043 * does not enforce the schemas, allowing the user full control.
044 */
045 public class AnnotationEditor extends AbstractVisualResource
046 implements OwnedAnnotationEditor{
047
048 private static final long serialVersionUID = 1L;
049
050 /* (non-Javadoc)
051 * @see gate.creole.AbstractVisualResource#init()
052 */
053 @Override
054 public Resource init() throws ResourceInstantiationException {
055
056 super.init();
057 initData();
058 initGUI();
059 initListeners();
060 annotationEditorInstance = this;
061 return this;
062 }
063
064 protected void initData(){
065 schemasByType = new HashMap<String, AnnotationSchema>();
066 java.util.List schemas = Gate.getCreoleRegister().
067 getLrInstances("gate.creole.AnnotationSchema");
068 for(Iterator schIter = schemas.iterator();
069 schIter.hasNext();){
070 AnnotationSchema aSchema = (AnnotationSchema)schIter.next();
071 schemasByType.put(aSchema.getAnnotationName(), aSchema);
072 }
073
074 CreoleListener creoleListener = new CreoleListener(){
075 public void resourceLoaded(CreoleEvent e){
076 Resource newResource = e.getResource();
077 if(newResource instanceof AnnotationSchema){
078 AnnotationSchema aSchema = (AnnotationSchema)newResource;
079 schemasByType.put(aSchema.getAnnotationName(), aSchema);
080 }
081 }
082
083 public void resourceUnloaded(CreoleEvent e){
084 Resource newResource = e.getResource();
085 if(newResource instanceof AnnotationSchema){
086 AnnotationSchema aSchema = (AnnotationSchema)newResource;
087 if(schemasByType.containsValue(aSchema)){
088 schemasByType.remove(aSchema.getAnnotationName());
089 }
090 }
091 }
092
093 public void datastoreOpened(CreoleEvent e){
094
095 }
096 public void datastoreCreated(CreoleEvent e){
097
098 }
099 public void datastoreClosed(CreoleEvent e){
100
101 }
102 public void resourceRenamed(Resource resource,
103 String oldName,
104 String newName){
105 }
106 };
107 Gate.getCreoleRegister().addCreoleListener(creoleListener);
108 }
109
110 protected void initGUI(){
111
112 popupWindow = new JWindow(SwingUtilities.getWindowAncestor(
113 owner.getTextComponent())) {
114 public void pack() {
115 // increase the feature table size only if not bigger
116 // than the main frame
117 if(isVisible()){
118 int maxHeight = MainFrame.getInstance().getHeight();
119 int otherHeight = getHeight() - featuresScroller.getHeight();
120 maxHeight -= otherHeight;
121 if(featuresScroller.getPreferredSize().height > maxHeight){
122 featuresScroller.setMaximumSize(new Dimension(
123 featuresScroller.getMaximumSize().width, maxHeight));
124 featuresScroller.setPreferredSize(new Dimension(
125 featuresScroller.getPreferredSize().width, maxHeight));
126 }
127
128 }
129 super.pack();
130 }
131 public void setVisible(boolean b) {
132 super.setVisible(b);
133 // when the editor is shown put the focus in the type combo box
134 if (b) { typeCombo.requestFocus(); }
135 }
136 };
137
138 JPanel pane = new JPanel();
139 pane.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
140 pane.setLayout(new GridBagLayout());
141 pane.setBackground(UIManager.getLookAndFeelDefaults().
142 getColor("ToolTip.background"));
143 popupWindow.setContentPane(pane);
144
145 Insets insets0 = new Insets(0, 0, 0, 0);
146 GridBagConstraints constraints = new GridBagConstraints();
147 constraints.fill = GridBagConstraints.NONE;
148 constraints.anchor = GridBagConstraints.CENTER;
149 constraints.gridwidth = 1;
150 constraints.gridy = 0;
151 constraints.gridx = GridBagConstraints.RELATIVE;
152 constraints.weightx = 0;
153 constraints.weighty= 0;
154 constraints.insets = insets0;
155
156 solButton = new JButton();
157 solButton.setContentAreaFilled(false);
158 solButton.setBorderPainted(false);
159 solButton.setMargin(insets0);
160 pane.add(solButton, constraints);
161
162 sorButton = new JButton();
163 sorButton.setContentAreaFilled(false);
164 sorButton.setBorderPainted(false);
165 sorButton.setMargin(insets0);
166 pane.add(sorButton, constraints);
167
168 delButton = new JButton();
169 delButton.setContentAreaFilled(false);
170 delButton.setBorderPainted(false);
171 delButton.setMargin(insets0);
172 constraints.insets = new Insets(0, 20, 0, 20);
173 pane.add(delButton, constraints);
174 constraints.insets = insets0;
175
176 eolButton = new JButton();
177 eolButton.setContentAreaFilled(false);
178 eolButton.setBorderPainted(false);
179 eolButton.setMargin(insets0);
180 pane.add(eolButton, constraints);
181
182 eorButton = new JButton();
183 eorButton.setContentAreaFilled(false);
184 eorButton.setBorderPainted(false);
185 eorButton.setMargin(insets0);
186 pane.add(eorButton, constraints);
187
188 pinnedButton = new JToggleButton(MainFrame.getIcon("pin"));
189 pinnedButton.setSelectedIcon(MainFrame.getIcon("pin-in"));
190 pinnedButton.setSelected(false);
191 pinnedButton.setBorderPainted(false);
192 pinnedButton.setContentAreaFilled(false);
193 constraints.weightx = 1;
194 constraints.insets = new Insets(0, 0, 0, 0);
195 constraints.anchor = GridBagConstraints.EAST;
196 pane.add(pinnedButton, constraints);
197
198 dismissButton = new JButton();
199 dismissButton.setBorder(null);
200 constraints.anchor = GridBagConstraints.NORTHEAST;
201 pane.add(dismissButton, constraints);
202 constraints.anchor = GridBagConstraints.CENTER;
203 constraints.insets = insets0;
204
205
206 typeCombo = new JComboBox();
207 typeCombo.setEditable(true);
208 typeCombo.setBackground(UIManager.getLookAndFeelDefaults().
209 getColor("ToolTip.background"));
210 constraints.fill = GridBagConstraints.HORIZONTAL;
211 constraints.gridy = 1;
212 constraints.gridwidth = 7;
213 constraints.weightx = 1;
214 constraints.insets = new Insets(3, 2, 2, 2);
215 pane.add(typeCombo, constraints);
216
217 featuresEditor = new FeaturesSchemaEditor();
218 featuresEditor.setBackground(UIManager.getLookAndFeelDefaults().
219 getColor("ToolTip.background"));
220 try{
221 featuresEditor.init();
222 }catch(ResourceInstantiationException rie){
223 throw new GateRuntimeException(rie);
224 }
225
226 constraints.gridy = 2;
227 constraints.weighty = 1;
228 constraints.fill = GridBagConstraints.BOTH;
229 featuresScroller = new JScrollPane(featuresEditor);
230 pane.add(featuresScroller, constraints);
231
232 // add the search and annotate GUI at the bottom of the annotator editor
233 SearchAndAnnotatePanel searchPanel =
234 new SearchAndAnnotatePanel(pane.getBackground(), this, popupWindow);
235 constraints.insets = new Insets(0, 0, 0, 0);
236 constraints.fill = GridBagConstraints.BOTH;
237 constraints.anchor = GridBagConstraints.WEST;
238 constraints.gridx = 0;
239 constraints.gridy = GridBagConstraints.RELATIVE;
240 constraints.gridwidth = GridBagConstraints.REMAINDER;
241 constraints.gridheight = GridBagConstraints.REMAINDER;
242 constraints.weightx = 0.0;
243 constraints.weighty = 0.0;
244 pane.add(searchPanel, constraints);
245
246 popupWindow.pack();
247 }
248
249 protected void initListeners(){
250 //resize the window when the table changes.
251 featuresEditor.addComponentListener(new ComponentAdapter() {
252 @Override
253 public void componentResized(ComponentEvent e) {
254 //the table has changed size -> resize the window too!
255 popupWindow.pack();
256 }
257 });
258
259 KeyAdapter keyAdapter = new KeyAdapter() {
260 public void keyPressed(KeyEvent e) {
261 hideTimer.stop();
262 }
263 };
264
265 typeCombo.getEditor().getEditorComponent().addKeyListener(keyAdapter);
266
267 MouseListener windowMouseListener = new MouseAdapter() {
268 public void mouseEntered(MouseEvent evt) {
269 hideTimer.stop();
270 }
271 // allow a JWindow to be dragged with a mouse
272 public void mousePressed(MouseEvent me) {
273 pressed = me;
274 }
275 };
276
277 MouseMotionListener windowMouseMotionListener = new MouseMotionAdapter() {
278 Point location;
279 // allow a JWindow to be dragged with a mouse
280 public void mouseDragged(MouseEvent me) {
281 location = popupWindow.getLocation(location);
282 int x = location.x - pressed.getX() + me.getX();
283 int y = location.y - pressed.getY() + me.getY();
284 popupWindow.setLocation(x, y);
285 pinnedButton.setSelected(true);
286 }
287 };
288
289 popupWindow.getRootPane().addMouseListener(windowMouseListener);
290 popupWindow.getRootPane().addMouseMotionListener(windowMouseMotionListener);
291
292 InputMap inputMap = ((JComponent) popupWindow.getContentPane()).
293 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
294 actionMap = ((JComponent)popupWindow.getContentPane()).getActionMap();
295
296 // add the key-action bindings of this Component to the parent window
297 solAction =
298 new StartOffsetLeftAction("", MainFrame.getIcon("extend-left"),
299 "<html><b>Extend start</b><small>" +
300 "<br>LEFT = 1 character" +
301 "<br> + SHIFT = 5 characters, "+
302 "<br> + CTRL + SHIFT = 10 characters</small></html>",
303 KeyEvent.VK_LEFT);
304 solButton.setAction(solAction);
305 inputMap.put(KeyStroke.getKeyStroke("LEFT"), "solAction");
306 inputMap.put(KeyStroke.getKeyStroke("shift LEFT"), "solAction");
307 inputMap.put(KeyStroke.getKeyStroke("control shift released LEFT"),
308 "solAction");
309 actionMap.put("solAction", solAction);
310
311 sorAction =
312 new StartOffsetRightAction("", MainFrame.getIcon("extend-right"),
313 "<html><b>Shrink start</b><small>" +
314 "<br>RIGHT = 1 character" +
315 "<br> + SHIFT = 5 characters, "+
316 "<br> + CTRL + SHIFT = 10 characters</small></html>",
317 KeyEvent.VK_RIGHT);
318 sorButton.setAction(sorAction);
319 inputMap.put(KeyStroke.getKeyStroke("RIGHT"), "sorAction");
320 inputMap.put(KeyStroke.getKeyStroke("shift RIGHT"), "sorAction");
321 inputMap.put(KeyStroke.getKeyStroke("control shift released RIGHT"),
322 "sorAction");
323 actionMap.put("sorAction", sorAction);
324
325 delAction =
326 new DeleteAnnotationAction("", MainFrame.getIcon("remove-annotation"),
327 "Delete the annotation", KeyEvent.VK_DELETE);
328 delButton.setAction(delAction);
329 inputMap.put(KeyStroke.getKeyStroke("alt DELETE"), "delAction");
330 actionMap.put("delAction", delAction);
331
332 eolAction =
333 new EndOffsetLeftAction("", MainFrame.getIcon("extend-left"),
334 "<html><b>Shrink end</b><small>" +
335 "<br>ALT + LEFT = 1 character" +
336 "<br> + SHIFT = 5 characters, "+
337 "<br> + CTRL + SHIFT = 10 characters</small></html>",
338 KeyEvent.VK_LEFT);
339 eolButton.setAction(eolAction);
340 inputMap.put(KeyStroke.getKeyStroke("alt LEFT"), "eolAction");
341 inputMap.put(KeyStroke.getKeyStroke("alt shift LEFT"), "eolAction");
342 inputMap.put(KeyStroke.getKeyStroke("control alt shift released LEFT"),
343 "eolAction");
344 actionMap.put("eolAction", eolAction);
345
346 eorAction =
347 new EndOffsetRightAction("", MainFrame.getIcon("extend-right"),
348 "<html><b>Extend end</b><small>" +
349 "<br>ALT + RIGHT = 1 character" +
350 "<br> + SHIFT = 5 characters, "+
351 "<br> + CTRL + SHIFT = 10 characters</small></html>",
352 KeyEvent.VK_RIGHT);
353 eorButton.setAction(eorAction);
354 inputMap.put(KeyStroke.getKeyStroke("alt RIGHT"), "eorAction");
355 inputMap.put(KeyStroke.getKeyStroke("alt shift RIGHT"), "eorAction");
356 inputMap.put(KeyStroke.getKeyStroke("control alt shift released RIGHT"),
357 "eorAction");
358 actionMap.put("eorAction", eorAction);
359
360 pinnedButton.setToolTipText("<html>Press to pin window in place"
361 + " <font color=#667799><small>Ctrl-P"
362 + " </small></font></html>");
363 inputMap.put(KeyStroke.getKeyStroke("control P"), "toggle pin");
364 actionMap.put("toggle pin", new AbstractAction() {
365 public void actionPerformed(ActionEvent e) {
366 pinnedButton.doClick();
367 }
368 });
369
370 DismissAction dismissAction = new DismissAction("", null,
371 "Close the window", KeyEvent.VK_ESCAPE);
372 dismissButton.setAction(dismissAction);
373 inputMap.put(KeyStroke.getKeyStroke("ESCAPE"), "dismissAction");
374 inputMap.put(KeyStroke.getKeyStroke("alt ESCAPE"), "dismissAction");
375 actionMap.put("dismissAction", dismissAction);
376
377 ApplyAction applyAction =
378 new ApplyAction("Apply", null, "", KeyEvent.VK_ENTER);
379 inputMap.put(KeyStroke.getKeyStroke("alt ENTER"), "applyAction");
380 actionMap.put("applyAction", applyAction);
381
382 typeCombo.addActionListener(new ActionListener(){
383 public void actionPerformed(ActionEvent evt){
384 String newType = typeCombo.getSelectedItem().toString();
385 if(ann == null || ann.getType().equals(newType)) return;
386 //annotation editing
387 Integer oldId = ann.getId();
388 Annotation oldAnn = ann;
389 set.remove(ann);
390 try{
391 set.add(oldId, oldAnn.getStartNode().getOffset(),
392 oldAnn.getEndNode().getOffset(),
393 newType, oldAnn.getFeatures());
394 Annotation newAnn = set.get(oldId);
395 //update the selection to the new annotation
396 getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
397 editAnnotation(newAnn, set);
398 owner.annotationChanged(newAnn, set, oldAnn.getType());
399 }catch(InvalidOffsetException ioe){
400 throw new GateRuntimeException(ioe);
401 }
402 }
403 });
404
405 hideTimer = new Timer(HIDE_DELAY, new ActionListener() {
406 public void actionPerformed(ActionEvent evt) {
407 annotationEditorInstance.setVisible(false);
408 }
409 });
410 hideTimer.setRepeats(false);
411
412 AncestorListener textAncestorListener = new AncestorListener() {
413 public void ancestorAdded(AncestorEvent event) {
414 if(wasShowing) {
415 annotationEditorInstance.setVisible(true);
416 }
417 wasShowing = false;
418 }
419
420 public void ancestorRemoved(AncestorEvent event) {
421 if(isShowing()) {
422 wasShowing = true;
423 popupWindow.dispose();
424 }
425 }
426
427 public void ancestorMoved(AncestorEvent event) {
428 }
429
430 private boolean wasShowing = false;
431 };
432 owner.getTextComponent().addAncestorListener(textAncestorListener);
433 }
434
435 /* (non-Javadoc)
436 * @see gate.gui.annedit.AnnotationEditor#isActive()
437 */
438 public boolean isActive() {
439 return popupWindow.isVisible();
440 }
441
442 public void editAnnotation(Annotation ann, AnnotationSet set){
443 this.ann = ann;
444 this.set = set;
445 if (ann == null) {
446 typeCombo.setModel(new DefaultComboBoxModel());
447 featuresEditor.setSchema(new AnnotationSchema());
448 // popupWindow.doLayout();
449 popupWindow.validate();
450 return;
451 }
452 //repopulate the types combo
453 String annType = ann.getType();
454 Set<String> types = new HashSet<String>(schemasByType.keySet());
455 types.add(annType);
456 types.addAll(set.getAllTypes());
457 java.util.List<String> typeList = new ArrayList<String>(types);
458 Collections.sort(typeList);
459 typeCombo.setModel(new DefaultComboBoxModel(typeList.toArray()));
460 typeCombo.setSelectedItem(annType);
461
462 featuresEditor.setSchema(schemasByType.get(annType));
463 featuresEditor.setTargetFeatures(ann.getFeatures());
464 // popupWindow.doLayout();
465 popupWindow.validate();
466 setEditingEnabled(true);
467 setVisible(true);
468 if (!pinnedButton.isSelected()) {
469 hideTimer.restart();
470 }
471 }
472
473 public Annotation getAnnotationCurrentlyEdited() {
474 return ann;
475 }
476
477 /* (non-Javadoc)
478 * @see gate.gui.annedit.AnnotationEditor#editingFinished()
479 */
480 public boolean editingFinished() {
481 //this editor implementation has no special requirements (such as schema
482 //compliance), so it always returns true.
483 return true;
484 }
485
486 public boolean isShowing(){
487 return popupWindow.isShowing();
488 }
489
490 /**
491 * Shows/Hides the UI(s) involved in annotation editing.
492 */
493 @Override
494 public void setVisible(boolean setVisible) {
495 super.setVisible(setVisible);
496 if (setVisible) {
497 placeDialog(ann.getStartNode().getOffset().intValue(),
498 ann.getEndNode().getOffset().intValue());
499
500 } else {
501 popupWindow.setVisible(false);
502 pinnedButton.setSelected(false);
503 SwingUtilities.invokeLater(new Runnable() { public void run() {
504 // when hiding the editor put back the focus in the document
505 owner.getTextComponent().requestFocus();
506 }});
507 }
508 }
509
510 /**
511 * Finds the best location for the editor dialog for a given span of text.
512 */
513 public void placeDialog(int start, int end){
514 if(popupWindow.isVisible() && pinnedButton.isSelected()){
515 //just resize
516 Point where = popupWindow.getLocation();
517 popupWindow.pack();
518 if(where != null){
519 popupWindow.setLocation(where);
520 }
521 }else{
522 //calculate position
523 try{
524 Rectangle startRect = owner.getTextComponent().modelToView(start);
525 Rectangle endRect = owner.getTextComponent().modelToView(end);
526 Point topLeft = owner.getTextComponent().getLocationOnScreen();
527 int x = topLeft.x + startRect.x;
528 int y = topLeft.y + endRect.y + endRect.height;
529
530 //make sure the window doesn't start lower
531 //than the end of the visible rectangle
532 Rectangle visRect = owner.getTextComponent().getVisibleRect();
533 int maxY = topLeft.y + visRect.y + visRect.height;
534
535 //make sure window doesn't get off-screen
536 popupWindow.pack();
537 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
538 boolean revalidate = false;
539 if(popupWindow.getSize().width > screenSize.width){
540 popupWindow.setSize(screenSize.width, popupWindow.getSize().height);
541 revalidate = true;
542 }
543 if(popupWindow.getSize().height > screenSize.height){
544 popupWindow.setSize(popupWindow.getSize().width, screenSize.height);
545 revalidate = true;
546 }
547
548 if(revalidate) popupWindow.validate();
549 //calculate max X
550 int maxX = screenSize.width - popupWindow.getSize().width;
551 //calculate max Y
552 if(maxY + popupWindow.getSize().height > screenSize.height){
553 maxY = screenSize.height - popupWindow.getSize().height;
554 }
555
556 //correct position
557 if(y > maxY) y = maxY;
558 if(x > maxX) x = maxX;
559 popupWindow.setLocation(x, y);
560 }catch(BadLocationException ble){
561 //this should never occur
562 throw new GateRuntimeException(ble);
563 }
564 }
565 if(!popupWindow.isVisible()) popupWindow.setVisible(true);
566 }
567
568 /**
569 * Changes the span of an existing annotation by creating a new annotation
570 * with the same ID, type and features but with the new start and end offsets.
571 * @param set the annotation set
572 * @param oldAnnotation the annotation to be moved
573 * @param newStartOffset the new start offset
574 * @param newEndOffset the new end offset
575 */
576 protected void moveAnnotation(AnnotationSet set, Annotation oldAnnotation,
577 Long newStartOffset, Long newEndOffset) throws InvalidOffsetException{
578 //Moving is done by deleting the old annotation and creating a new one.
579 //If this was the last one of one type it would mess up the gui which
580 //"forgets" about this type and then it recreates it (with a different
581 //colour and not visible.
582 //In order to avoid this problem, we'll create a new temporary annotation.
583 Annotation tempAnn = null;
584 if(set.get(oldAnnotation.getType()).size() == 1){
585 //create a clone of the annotation that will be deleted, to act as a
586 //placeholder
587 Integer tempAnnId = set.add(oldAnnotation.getStartNode(),
588 oldAnnotation.getStartNode(), oldAnnotation.getType(),
589 oldAnnotation.getFeatures());
590 tempAnn = set.get(tempAnnId);
591 }
592
593 Integer oldID = oldAnnotation.getId();
594 set.remove(oldAnnotation);
595 set.add(oldID, newStartOffset, newEndOffset,
596 oldAnnotation.getType(), oldAnnotation.getFeatures());
597 Annotation newAnn = set.get(oldID);
598 //update the selection to the new annotation
599 getOwner().selectAnnotation(new AnnotationDataImpl(set, newAnn));
600
601 editAnnotation(newAnn, set);
602 //remove the temporary annotation
603 if(tempAnn != null) set.remove(tempAnn);
604 owner.annotationChanged(newAnn, set, null);
605 }
606
607 /**
608 * Base class for actions on annotations.
609 */
610 protected abstract class AnnotationAction extends AbstractAction{
611 public AnnotationAction(String text, Icon icon,
612 String desc, int mnemonic){
613 super(text, icon);
614 putValue(SHORT_DESCRIPTION, desc);
615 putValue(MNEMONIC_KEY, mnemonic);
616 }
617 }
618
619 protected class StartOffsetLeftAction extends AnnotationAction{
620 private static final long serialVersionUID = 1L;
621 public StartOffsetLeftAction(String text, Icon icon,
622 String desc, int mnemonic) {
623 super(text, icon, desc, mnemonic);
624 }
625 public void actionPerformed(ActionEvent evt){
626 int increment = 1;
627 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
628 //CTRL pressed -> use tokens for advancing
629 increment = SHIFT_INCREMENT;
630 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
631 increment = CTRL_SHIFT_INCREMENT;
632 }
633 }
634 long newValue = ann.getStartNode().getOffset().longValue() - increment;
635 if(newValue < 0) newValue = 0;
636 try{
637 moveAnnotation(set, ann, new Long(newValue),
638 ann.getEndNode().getOffset());
639 }catch(InvalidOffsetException ioe){
640 throw new GateRuntimeException(ioe);
641 }
642 }
643 }
644
645 protected class StartOffsetRightAction extends AnnotationAction{
646 private static final long serialVersionUID = 1L;
647 public StartOffsetRightAction(String text, Icon icon,
648 String desc, int mnemonic) {
649 super(text, icon, desc, mnemonic);
650 }
651 public void actionPerformed(ActionEvent evt){
652 long endOffset = ann.getEndNode().getOffset().longValue();
653 int increment = 1;
654 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
655 //CTRL pressed -> use tokens for advancing
656 increment = SHIFT_INCREMENT;
657 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
658 increment = CTRL_SHIFT_INCREMENT;
659 }
660 }
661
662 long newValue = ann.getStartNode().getOffset().longValue() + increment;
663 if(newValue > endOffset) newValue = endOffset;
664 try{
665 moveAnnotation(set, ann, new Long(newValue),
666 ann.getEndNode().getOffset());
667 }catch(InvalidOffsetException ioe){
668 throw new GateRuntimeException(ioe);
669 }
670 }
671 }
672
673 protected class EndOffsetLeftAction extends AnnotationAction{
674 private static final long serialVersionUID = 1L;
675 public EndOffsetLeftAction(String text, Icon icon,
676 String desc, int mnemonic) {
677 super(text, icon, desc, mnemonic);
678 }
679 public void actionPerformed(ActionEvent evt){
680 long startOffset = ann.getStartNode().getOffset().longValue();
681 int increment = 1;
682 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
683 //CTRL pressed -> use tokens for advancing
684 increment = SHIFT_INCREMENT;
685 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
686 increment =CTRL_SHIFT_INCREMENT;
687 }
688 }
689
690 long newValue = ann.getEndNode().getOffset().longValue() - increment;
691 if(newValue < startOffset) newValue = startOffset;
692 try{
693 moveAnnotation(set, ann, ann.getStartNode().getOffset(),
694 new Long(newValue));
695 }catch(InvalidOffsetException ioe){
696 throw new GateRuntimeException(ioe);
697 }
698 }
699 }
700
701 protected class EndOffsetRightAction extends AnnotationAction{
702 private static final long serialVersionUID = 1L;
703 public EndOffsetRightAction(String text, Icon icon,
704 String desc, int mnemonic) {
705 super(text, icon, desc, mnemonic);
706 }
707 public void actionPerformed(ActionEvent evt){
708 long maxOffset = owner.getDocument().getContent().size().longValue();
709 int increment = 1;
710 if((evt.getModifiers() & ActionEvent.SHIFT_MASK) > 0){
711 //CTRL pressed -> use tokens for advancing
712 increment = SHIFT_INCREMENT;
713 if((evt.getModifiers() & ActionEvent.CTRL_MASK) > 0){
714 increment = CTRL_SHIFT_INCREMENT;
715 }
716 }
717 long newValue = ann.getEndNode().getOffset().longValue() + increment;
718 if(newValue > maxOffset) newValue = maxOffset;
719 try{
720 moveAnnotation(set, ann, ann.getStartNode().getOffset(),
721 new Long(newValue));
722 }catch(InvalidOffsetException ioe){
723 throw new GateRuntimeException(ioe);
724 }
725 }
726 }
727
728 protected class DeleteAnnotationAction extends AnnotationAction{
729 private static final long serialVersionUID = 1L;
730 public DeleteAnnotationAction(String text, Icon icon,
731 String desc, int mnemonic) {
732 super(text, icon, desc, mnemonic);
733 }
734 public void actionPerformed(ActionEvent evt){
735 set.remove(ann);
736
737 //clear the dialog
738 editAnnotation(null, set);
739
740 if(!pinnedButton.isSelected()){
741 //if not pinned, hide the dialog.
742 annotationEditorInstance.setVisible(false);
743 } else {
744 setEditingEnabled(false);
745 }
746 }
747 }
748
749 protected class DismissAction extends AnnotationAction{
750 private static final long serialVersionUID = 1L;
751 public DismissAction(String text, Icon icon,
752 String desc, int mnemonic) {
753 super(text, icon, desc, mnemonic);
754 Icon exitIcon = UIManager.getIcon("InternalFrame.closeIcon");
755 if(exitIcon == null) exitIcon = MainFrame.getIcon("exit");
756 putValue(SMALL_ICON, exitIcon);
757 }
758 public void actionPerformed(ActionEvent evt){
759 annotationEditorInstance.setVisible(false);
760 }
761 }
762
763 protected class ApplyAction extends AnnotationAction{
764 private static final long serialVersionUID = 1L;
765 public ApplyAction(String text, Icon icon,
766 String desc, int mnemonic) {
767 super(text, icon, desc, mnemonic);
768 }
769 public void actionPerformed(ActionEvent evt){
770 annotationEditorInstance.setVisible(false);
771 }
772 }
773
774 /**
775 * The popup window used by the editor.
776 */
777 protected JWindow popupWindow;
778
779 /**
780 * Toggle button used to pin down the dialog.
781 */
782 protected JToggleButton pinnedButton;
783
784 /**
785 * Combobox for annotation type.
786 */
787 protected JComboBox typeCombo;
788
789 /**
790 * Component for features editing.
791 */
792 protected FeaturesSchemaEditor featuresEditor;
793
794 protected JScrollPane featuresScroller;
795
796
797 protected JButton solButton;
798 protected JButton sorButton;
799 protected JButton delButton;
800 protected JButton eolButton;
801 protected JButton eorButton;
802 protected JButton dismissButton;
803
804 protected Timer hideTimer;
805 protected MouseEvent pressed;
806
807 /**
808 * Constant for delay before hiding the popup window (in milliseconds).
809 */
810 protected static final int HIDE_DELAY = 1500;
811
812 /**
813 * Constant for the number of characters when changing annotation boundary
814 * with Shift key pressed.
815 */
816 protected static final int SHIFT_INCREMENT = 5;
817
818 /**
819 * Constant for the number of characters when changing annotation boundary
820 * with Ctrl+Shift keys pressed.
821 */
822 protected static final int CTRL_SHIFT_INCREMENT = 10;
823
824 /**
825 * Stores the Annotation schema objects available in the system.
826 * The annotation types are used as keys for the map.
827 */
828 protected Map<String, AnnotationSchema> schemasByType;
829
830 /**
831 * The controlling object for this editor.
832 */
833 private AnnotationEditorOwner owner;
834
835 /**
836 * The annotation being edited.
837 */
838 protected Annotation ann;
839
840 /**
841 * The parent set of the current annotation.
842 */
843 protected AnnotationSet set;
844
845 /**
846 * Current instance of this class.
847 */
848 protected AnnotationEditor annotationEditorInstance;
849
850 /**
851 * Action bindings for the popup window.
852 */
853 private ActionMap actionMap;
854
855 private StartOffsetLeftAction solAction;
856 private StartOffsetRightAction sorAction;
857 private DeleteAnnotationAction delAction;
858 private EndOffsetLeftAction eolAction;
859 private EndOffsetRightAction eorAction;
860
861 /* (non-Javadoc)
862 * @see gate.gui.annedit.AnnotationEditor#getAnnotationSetCurrentlyEdited()
863 */
864 public AnnotationSet getAnnotationSetCurrentlyEdited() {
865 return set;
866 }
867
868 /**
869 * @return the owner
870 */
871 public AnnotationEditorOwner getOwner() {
872 return owner;
873 }
874
875 /**
876 * @param owner the owner to set
877 */
878 public void setOwner(AnnotationEditorOwner owner) {
879 this.owner = owner;
880 }
881
882 public void setPinnedMode(boolean pinned) {
883 pinnedButton.setSelected(pinned);
884 }
885
886 public void setEditingEnabled(boolean isEditingEnabled) {
887 solButton.setEnabled(isEditingEnabled);
888 sorButton.setEnabled(isEditingEnabled);
889 delButton.setEnabled(isEditingEnabled);
890 eolButton.setEnabled(isEditingEnabled);
891 eorButton.setEnabled(isEditingEnabled);
892 typeCombo.setEnabled(isEditingEnabled);
893 // cancel editing, if any
894 if (featuresEditor.isEditing()) {
895 featuresEditor.getColumnModel()
896 .getColumn(featuresEditor.getEditingColumn())
897 .getCellEditor().cancelCellEditing();
898 }
899 // en/disable the featuresEditor table, no easy way unfortunately : |
900 featuresEditor.setEnabled(isEditingEnabled);
901 if (isEditingEnabled) {
902 // avoid the background to be incorrectly reset to the default color
903 Color tableBG = featuresEditor.getBackground();
904 tableBG = new Color(tableBG.getRGB());
905 featuresEditor.setBackground(tableBG);
906 }
907 final boolean isEditingEnabledF = isEditingEnabled;
908 for (int col = 0;
909 col < featuresEditor.getColumnCount(); col++) {
910 final TableCellRenderer previousTcr = featuresEditor
911 .getColumnModel().getColumn(col).getCellRenderer();
912 TableCellRenderer tcr = new TableCellRenderer() {
913 public Component getTableCellRendererComponent(JTable table,
914 Object value, boolean isSelected, boolean hasFocus,
915 int row, int column) {
916 Component c = previousTcr.getTableCellRendererComponent(
917 table, value, isSelected, hasFocus, row, column);
918 c.setEnabled(isEditingEnabledF);
919 return c;
920 }
921 };
922 featuresEditor.getColumnModel().getColumn(col).setCellRenderer(tcr);
923 }
924 // enable/disable the key binding actions
925 if (isEditingEnabled) {
926 actionMap.put("solAction", solAction);
927 actionMap.put("sorAction", sorAction);
928 actionMap.put("delAction", delAction);
929 actionMap.put("eolAction", eolAction);
930 actionMap.put("eorAction", eorAction);
931 } else {
932 actionMap.put("solAction", null);
933 actionMap.put("sorAction", null);
934 actionMap.put("delAction", null);
935 actionMap.put("eolAction", null);
936 actionMap.put("eorAction", null);
937 }
938 }
939
940 /**
941 * Does nothing, as this editor does not support cancelling and rollbacks.
942 */
943 public void cancelAction() throws GateException {
944 }
945
946 /**
947 * Returns <tt>true</tt> always as this editor is generic and can edit any
948 * annotation type.
949 */
950 public boolean canDisplayAnnotationType(String annotationType) {
951 return true;
952 }
953
954 /**
955 * Does nothing as this editor works in auto-commit mode (changes are
956 * implemented immediately).
957 */
958 public void okAction() throws GateException {
959 }
960
961 /**
962 * Returns <tt>false</tt>, as this editor does not support cancel operations.
963 */
964 public boolean supportsCancel() {
965 return false;
966 }
967 }
|