40 |
import java.awt.Graphics; |
import java.awt.Graphics; |
41 |
import java.awt.Point; |
import java.awt.Point; |
42 |
import java.awt.Rectangle; |
import java.awt.Rectangle; |
43 |
|
import java.awt.event.ActionEvent; |
44 |
|
import java.awt.event.ActionListener; |
45 |
import java.awt.event.FocusEvent; |
import java.awt.event.FocusEvent; |
46 |
import java.awt.event.FocusListener; |
import java.awt.event.FocusListener; |
47 |
import java.awt.event.MouseEvent; |
import java.awt.event.MouseEvent; |
51 |
import java.beans.PropertyChangeListener; |
import java.beans.PropertyChangeListener; |
52 |
import java.util.EventListener; |
import java.util.EventListener; |
53 |
|
|
54 |
|
import javax.swing.JComponent; |
55 |
import javax.swing.SwingUtilities; |
import javax.swing.SwingUtilities; |
56 |
|
import javax.swing.Timer; |
57 |
import javax.swing.event.ChangeEvent; |
import javax.swing.event.ChangeEvent; |
58 |
import javax.swing.event.ChangeListener; |
import javax.swing.event.ChangeListener; |
59 |
import javax.swing.event.DocumentEvent; |
import javax.swing.event.DocumentEvent; |
69 |
public class DefaultCaret extends Rectangle |
public class DefaultCaret extends Rectangle |
70 |
implements Caret, FocusListener, MouseListener, MouseMotionListener |
implements Caret, FocusListener, MouseListener, MouseMotionListener |
71 |
{ |
{ |
72 |
|
|
73 |
|
/** |
74 |
|
* Controls the blinking of the caret. |
75 |
|
* |
76 |
|
* @author Roman Kennke (kennke@aicas.com) |
77 |
|
* @author Audrius Meskauskas (AudriusA@Bioinformatics.org) |
78 |
|
*/ |
79 |
|
private class BlinkTimerListener implements ActionListener |
80 |
|
{ |
81 |
|
/** |
82 |
|
* Forces the next event to be ignored. The next event should be ignored |
83 |
|
* if we force the caret to appear. We do not know how long will it take |
84 |
|
* to fire the comming event; this may be near immediately. Better to leave |
85 |
|
* the caret visible one iteration longer. |
86 |
|
*/ |
87 |
|
boolean ignoreNextEvent; |
88 |
|
|
89 |
|
/** |
90 |
|
* Receives notification when the blink timer fires and updates the visible |
91 |
|
* state of the caret. |
92 |
|
* |
93 |
|
* @param event the action event |
94 |
|
*/ |
95 |
|
public void actionPerformed(ActionEvent event) |
96 |
|
{ |
97 |
|
if (ignoreNextEvent) |
98 |
|
ignoreNextEvent = false; |
99 |
|
else |
100 |
|
{ |
101 |
|
visible = !visible; |
102 |
|
repaint(); |
103 |
|
} |
104 |
|
} |
105 |
|
} |
106 |
|
|
107 |
/** |
/** |
108 |
* Listens for changes in the text component's document and updates the |
* Listens for changes in the text component's document and updates the |
109 |
* caret accordingly. |
* caret accordingly. |
277 |
private Point magicCaretPosition = null; |
private Point magicCaretPosition = null; |
278 |
|
|
279 |
/** |
/** |
280 |
* Indicates if this <code>Caret</code> is currently visible or not. |
* Indicates if this <code>Caret</code> is currently visible or not. This is |
281 |
|
* package private to avoid an accessor method. |
282 |
*/ |
*/ |
283 |
private boolean visible = true; |
boolean visible = false; |
284 |
|
|
285 |
/** |
/** |
286 |
* The current highlight entry. |
* The current highlight entry. |
287 |
*/ |
*/ |
288 |
private Object highlightEntry; |
private Object highlightEntry; |
289 |
|
|
290 |
|
private Timer blinkTimer; |
291 |
|
|
292 |
|
private BlinkTimerListener blinkListener; |
293 |
|
|
294 |
|
/** |
295 |
|
* Creates a new <code>DefaultCaret</code> instance. |
296 |
|
*/ |
297 |
|
public DefaultCaret() |
298 |
|
{ |
299 |
|
// Nothing to do here. |
300 |
|
} |
301 |
|
|
302 |
/** |
/** |
303 |
* Sets the Caret update policy. |
* Sets the Caret update policy. |
304 |
* |
* |
344 |
*/ |
*/ |
345 |
public void mouseDragged(MouseEvent event) |
public void mouseDragged(MouseEvent event) |
346 |
{ |
{ |
347 |
// FIXME: Implement this properly. |
moveCaret(event); |
348 |
} |
} |
349 |
|
|
350 |
/** |
/** |
374 |
*/ |
*/ |
375 |
public void mouseClicked(MouseEvent event) |
public void mouseClicked(MouseEvent event) |
376 |
{ |
{ |
377 |
// FIXME: Implement this properly. |
// TODO: Implement double- and triple-click behaviour here. |
378 |
} |
} |
379 |
|
|
380 |
/** |
/** |
396 |
*/ |
*/ |
397 |
public void mouseExited(MouseEvent event) |
public void mouseExited(MouseEvent event) |
398 |
{ |
{ |
399 |
// TODO: What to do here, if anything? |
// Nothing to do here. |
400 |
} |
} |
401 |
|
|
402 |
/** |
/** |
409 |
*/ |
*/ |
410 |
public void mousePressed(MouseEvent event) |
public void mousePressed(MouseEvent event) |
411 |
{ |
{ |
412 |
// FIXME: Implement this properly. |
positionCaret(event); |
|
if (!(event.getButton() == MouseEvent.BUTTON1)) |
|
|
return; |
|
|
setDot(textComponent.viewToModel(event.getPoint())); |
|
413 |
} |
} |
414 |
|
|
415 |
/** |
/** |
430 |
*/ |
*/ |
431 |
public void focusGained(FocusEvent event) |
public void focusGained(FocusEvent event) |
432 |
{ |
{ |
433 |
// TODO: Implement this properly. |
setVisible(true); |
434 |
|
updateTimerStatus(); |
435 |
} |
} |
436 |
|
|
437 |
/** |
/** |
438 |
* Sets the caret to <code>invisible</code>. |
* Sets the caret to <code>invisible</code>. |
439 |
* |
* |
440 |
* @param event the <code>FocusEvent</code> |
* @param event the <code>FocusEvent</code> |
441 |
*/ |
*/ |
442 |
public void focusLost(FocusEvent event) |
public void focusLost(FocusEvent event) |
443 |
{ |
{ |
444 |
// TODO: Implement this properly. |
if (event.isTemporary() == false) |
445 |
|
{ |
446 |
|
setVisible(false); |
447 |
|
// Stop the blinker, if running. |
448 |
|
if (blinkTimer != null && blinkTimer.isRunning()) |
449 |
|
blinkTimer.stop(); |
450 |
|
} |
451 |
|
} |
452 |
|
|
453 |
|
/** |
454 |
|
* Install (if not present) and start the timer, if the caret must blink. The |
455 |
|
* caret does not blink if it is invisible, or the component is disabled or |
456 |
|
* not editable. |
457 |
|
*/ |
458 |
|
private void updateTimerStatus() |
459 |
|
{ |
460 |
|
if (textComponent.isEnabled() && textComponent.isEditable()) |
461 |
|
{ |
462 |
|
if (blinkTimer == null) |
463 |
|
initBlinkTimer(); |
464 |
|
if (!blinkTimer.isRunning()) |
465 |
|
blinkTimer.start(); |
466 |
|
} |
467 |
|
else |
468 |
|
{ |
469 |
|
if (blinkTimer != null) |
470 |
|
blinkTimer.stop(); |
471 |
|
} |
472 |
} |
} |
473 |
|
|
474 |
/** |
/** |
479 |
*/ |
*/ |
480 |
protected void moveCaret(MouseEvent event) |
protected void moveCaret(MouseEvent event) |
481 |
{ |
{ |
482 |
// FIXME: Implement this properly. |
int newDot = getComponent().viewToModel(event.getPoint()); |
483 |
|
moveDot(newDot); |
484 |
} |
} |
485 |
|
|
486 |
/** |
/** |
491 |
*/ |
*/ |
492 |
protected void positionCaret(MouseEvent event) |
protected void positionCaret(MouseEvent event) |
493 |
{ |
{ |
494 |
// FIXME: Implement this properly. |
int newDot = getComponent().viewToModel(event.getPoint()); |
495 |
|
setDot(newDot); |
496 |
} |
} |
497 |
|
|
498 |
/** |
/** |
512 |
textComponent.removePropertyChangeListener(propertyChangeListener); |
textComponent.removePropertyChangeListener(propertyChangeListener); |
513 |
propertyChangeListener = null; |
propertyChangeListener = null; |
514 |
textComponent = null; |
textComponent = null; |
515 |
|
|
516 |
|
// Deinstall blink timer if present. |
517 |
|
if (blinkTimer != null) |
518 |
|
blinkTimer.stop(); |
519 |
|
blinkTimer = null; |
520 |
} |
} |
521 |
|
|
522 |
/** |
/** |
536 |
textComponent.addPropertyChangeListener(propertyChangeListener); |
textComponent.addPropertyChangeListener(propertyChangeListener); |
537 |
documentListener = new DocumentHandler(); |
documentListener = new DocumentHandler(); |
538 |
textComponent.getDocument().addDocumentListener(documentListener); |
textComponent.getDocument().addDocumentListener(documentListener); |
539 |
|
|
540 |
repaint(); |
repaint(); |
541 |
} |
} |
542 |
|
|
644 |
*/ |
*/ |
645 |
protected final void repaint() |
protected final void repaint() |
646 |
{ |
{ |
647 |
textComponent.repaint(this); |
getComponent().repaint(x, y, width, height); |
648 |
} |
} |
649 |
|
|
650 |
/** |
/** |
655 |
*/ |
*/ |
656 |
public void paint(Graphics g) |
public void paint(Graphics g) |
657 |
{ |
{ |
658 |
if (textComponent == null) |
JTextComponent comp = getComponent(); |
659 |
|
if (comp == null) |
660 |
return; |
return; |
661 |
|
|
662 |
int dot = getDot(); |
int dot = getDot(); |
664 |
|
|
665 |
try |
try |
666 |
{ |
{ |
667 |
rect = textComponent.modelToView(dot); |
rect = textComponent.modelToView(dot); |
668 |
} |
} |
669 |
catch (BadLocationException e) |
catch (BadLocationException e) |
670 |
{ |
{ |
671 |
// This should never happen as dot should be always valid. |
assert false : "Unexpected bad caret location: " + dot; |
672 |
return; |
return; |
673 |
} |
} |
674 |
|
|
675 |
if (rect == null) |
if (rect == null) |
676 |
return; |
return; |
677 |
|
|
678 |
// First we need to delete the old caret. |
// Check if paint has possibly been called directly, without a previous |
679 |
// FIXME: Implement deleting of old caret. |
// call to damage(). In this case we need to do some cleanup first. |
680 |
|
if ((x != rect.x) || (y != rect.y)) |
681 |
|
{ |
682 |
|
repaint(); // Erase previous location of caret. |
683 |
|
x = rect.x; |
684 |
|
y = rect.y; |
685 |
|
width = 1; |
686 |
|
height = rect.height; |
687 |
|
} |
688 |
|
|
689 |
// Now draw the caret on the new position if visible. |
// Now draw the caret on the new position if visible. |
690 |
if (visible) |
if (visible) |
691 |
{ |
{ |
692 |
g.setColor(textComponent.getCaretColor()); |
g.setColor(textComponent.getCaretColor()); |
693 |
g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height); |
g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height); |
694 |
} |
} |
695 |
} |
} |
696 |
|
|
781 |
*/ |
*/ |
782 |
public void setBlinkRate(int rate) |
public void setBlinkRate(int rate) |
783 |
{ |
{ |
784 |
|
if (blinkTimer != null) |
785 |
|
blinkTimer.setDelay(rate); |
786 |
blinkRate = rate; |
blinkRate = rate; |
787 |
} |
} |
788 |
|
|
808 |
*/ |
*/ |
809 |
public void moveDot(int dot) |
public void moveDot(int dot) |
810 |
{ |
{ |
811 |
this.dot = dot; |
if (dot >= 0) |
812 |
handleHighlight(); |
{ |
813 |
repaint(); |
this.dot = dot; |
814 |
|
handleHighlight(); |
815 |
|
adjustVisibility(this); |
816 |
|
appear(); |
817 |
|
} |
818 |
} |
} |
819 |
|
|
820 |
/** |
/** |
821 |
* Sets the current position of this <code>Caret</code> within the |
* Sets the current position of this <code>Caret</code> within the |
822 |
* <code>Document</code>. This also sets the <code>mark</code> to the |
* <code>Document</code>. This also sets the <code>mark</code> to the new |
823 |
* new location. |
* location. |
824 |
* |
* |
825 |
* @param dot the new position to be set |
* @param dot |
826 |
* |
* the new position to be set |
827 |
* @see #moveDot(int) |
* @see #moveDot(int) |
828 |
*/ |
*/ |
829 |
public void setDot(int dot) |
public void setDot(int dot) |
830 |
{ |
{ |
831 |
this.dot = dot; |
if (dot >= 0) |
832 |
this.mark = dot; |
{ |
833 |
handleHighlight(); |
this.mark = dot; |
834 |
repaint(); |
this.dot = dot; |
835 |
|
handleHighlight(); |
836 |
|
adjustVisibility(this); |
837 |
|
appear(); |
838 |
|
} |
839 |
} |
} |
840 |
|
|
841 |
|
/** |
842 |
|
* Show the caret (may be hidden due blinking) and adjust the timer not to |
843 |
|
* hide it (possibly immediately). |
844 |
|
* |
845 |
|
* @author Audrius Meskauskas (AudriusA@Bioinformatics.org) |
846 |
|
*/ |
847 |
|
void appear() |
848 |
|
{ |
849 |
|
// All machinery is only required if the carret is blinking. |
850 |
|
if (blinkListener != null) |
851 |
|
{ |
852 |
|
blinkListener.ignoreNextEvent = true; |
853 |
|
|
854 |
|
// If the caret is visible, erase the current position by repainting |
855 |
|
// over. |
856 |
|
if (visible) |
857 |
|
repaint(); |
858 |
|
|
859 |
|
// Draw the caret in the new position. |
860 |
|
visible = true; |
861 |
|
|
862 |
|
Rectangle area = null; |
863 |
|
try |
864 |
|
{ |
865 |
|
area = getComponent().modelToView(getDot()); |
866 |
|
} |
867 |
|
catch (BadLocationException ex) |
868 |
|
{ |
869 |
|
assert false : "Unexpected bad caret location: " + getDot(); |
870 |
|
} |
871 |
|
if (area != null) |
872 |
|
damage(area); |
873 |
|
} |
874 |
|
repaint(); |
875 |
|
} |
876 |
|
|
877 |
/** |
/** |
878 |
* Returns <code>true</code> if this <code>Caret</code> is currently visible, |
* Returns <code>true</code> if this <code>Caret</code> is currently visible, |
897 |
if (v != visible) |
if (v != visible) |
898 |
{ |
{ |
899 |
visible = v; |
visible = v; |
900 |
repaint(); |
updateTimerStatus(); |
901 |
|
Rectangle area = null; |
902 |
|
try |
903 |
|
{ |
904 |
|
area = getComponent().modelToView(getDot()); |
905 |
|
} |
906 |
|
catch (BadLocationException ex) |
907 |
|
{ |
908 |
|
assert false: "Unexpected bad caret location: " + getDot(); |
909 |
|
} |
910 |
|
if (area != null) |
911 |
|
damage(area); |
912 |
} |
} |
913 |
} |
} |
914 |
|
|
923 |
{ |
{ |
924 |
return DefaultHighlighter.DefaultPainter; |
return DefaultHighlighter.DefaultPainter; |
925 |
} |
} |
926 |
|
|
927 |
|
/** |
928 |
|
* Updates the carets rectangle properties to the specified rectangle and |
929 |
|
* repaints the caret. |
930 |
|
* |
931 |
|
* @param r the rectangle to set as the caret rectangle |
932 |
|
*/ |
933 |
|
protected void damage(Rectangle r) |
934 |
|
{ |
935 |
|
if (r == null) |
936 |
|
return; |
937 |
|
x = r.x; |
938 |
|
y = r.y; |
939 |
|
width = 1; |
940 |
|
// height is normally set in paint and we leave it untouched. However, we |
941 |
|
// must set a valid value here, since otherwise the painting mechanism |
942 |
|
// sets a zero clip and never calls paint. |
943 |
|
if (height <= 0) |
944 |
|
height = getComponent().getHeight(); |
945 |
|
repaint(); |
946 |
|
} |
947 |
|
|
948 |
|
/** |
949 |
|
* Adjusts the text component so that the caret is visible. This default |
950 |
|
* implementation simply calls |
951 |
|
* {@link JComponent#scrollRectToVisible(Rectangle)} on the text component. |
952 |
|
* Subclasses may wish to change this. |
953 |
|
*/ |
954 |
|
protected void adjustVisibility(Rectangle rect) |
955 |
|
{ |
956 |
|
getComponent().scrollRectToVisible(rect); |
957 |
|
} |
958 |
|
|
959 |
|
/** |
960 |
|
* Initializes the blink timer. |
961 |
|
*/ |
962 |
|
private void initBlinkTimer() |
963 |
|
{ |
964 |
|
// Setup the blink timer. |
965 |
|
blinkListener = new BlinkTimerListener(); |
966 |
|
blinkTimer = new Timer(getBlinkRate(), blinkListener); |
967 |
|
blinkTimer.setRepeats(true); |
968 |
|
} |
969 |
} |
} |