1 /*
2  * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.apple.laf;
27 
28 import java.awt.*;
29 import java.awt.event.*;
30 import java.awt.geom.Rectangle2D;
31 import java.beans.*;
32 
33 import javax.swing.*;
34 import javax.swing.border.Border;
35 import javax.swing.plaf.UIResource;
36 import javax.swing.text.*;
37 
38 public class AquaCaret extends DefaultCaret implements UIResource, PropertyChangeListener {
39     final boolean isMultiLineEditor;
40     final JTextComponent c;
41 
42     boolean mFocused = false;
43 
AquaCaret(final Window inParentWindow, final JTextComponent inComponent)44     public AquaCaret(final Window inParentWindow, final JTextComponent inComponent) {
45         super();
46         c = inComponent;
47         isMultiLineEditor = (c instanceof JTextArea || c instanceof JEditorPane);
48         inComponent.addPropertyChangeListener(this);
49     }
50 
getSelectionPainter()51     protected Highlighter.HighlightPainter getSelectionPainter() {
52         return AquaHighlighter.getInstance();
53     }
54 
55     /**
56      * Only show the flashing caret if the selection range is zero
57      */
setVisible(boolean e)58     public void setVisible(boolean e) {
59         if (e) e = getDot() == getMark();
60         super.setVisible(e);
61     }
62 
fireStateChanged()63     protected void fireStateChanged() {
64         // If we have focus the caret should only flash if the range length is zero
65         if (mFocused) setVisible(getComponent().isEditable());
66 
67         super.fireStateChanged();
68     }
69 
propertyChange(final PropertyChangeEvent evt)70     public void propertyChange(final PropertyChangeEvent evt) {
71         final String propertyName = evt.getPropertyName();
72 
73         if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) {
74             final JTextComponent comp = ((JTextComponent)evt.getSource());
75 
76             if (evt.getNewValue() == Boolean.TRUE) {
77                 setVisible(comp.hasFocus());
78             } else {
79                 setVisible(false);
80             }
81 
82             if (getDot() != getMark()) comp.getUI().damageRange(comp, getDot(), getMark());
83         }
84     }
85 
86     // --- FocusListener methods --------------------------
87 
88     private boolean shouldSelectAllOnFocus = true;
focusGained(final FocusEvent e)89     public void focusGained(final FocusEvent e) {
90         final JTextComponent component = getComponent();
91         if (!component.isEnabled() || !component.isEditable()) {
92             super.focusGained(e);
93             return;
94         }
95 
96         mFocused = true;
97         if (!shouldSelectAllOnFocus) {
98             shouldSelectAllOnFocus = true;
99             super.focusGained(e);
100             return;
101         }
102 
103         if (isMultiLineEditor) {
104             super.focusGained(e);
105             return;
106         }
107 
108         final int end = component.getDocument().getLength();
109         final int dot = getDot();
110         final int mark = getMark();
111         if (dot == mark) {
112             if (dot == 0) {
113                 component.setCaretPosition(end);
114                 component.moveCaretPosition(0);
115             } else if (dot == end) {
116                 component.setCaretPosition(0);
117                 component.moveCaretPosition(end);
118             }
119         }
120 
121         super.focusGained(e);
122     }
123 
focusLost(final FocusEvent e)124     public void focusLost(final FocusEvent e) {
125         mFocused = false;
126         shouldSelectAllOnFocus = true;
127         if (isMultiLineEditor) {
128             setVisible(false);
129             c.repaint();
130         } else {
131             super.focusLost(e);
132         }
133     }
134 
135     // This fixes the problem where when on the mac you have to ctrl left click to
136     // get popup triggers the caret has code that only looks at button number.
137     // see radar # 3125390
mousePressed(final MouseEvent e)138     public void mousePressed(final MouseEvent e) {
139         if (!e.isPopupTrigger()) {
140             super.mousePressed(e);
141             shouldSelectAllOnFocus = false;
142         }
143     }
144 
145     /**
146      * Damages the area surrounding the caret to cause
147      * it to be repainted in a new location.  If paint()
148      * is reimplemented, this method should also be
149      * reimplemented.  This method should update the
150      * caret bounds (x, y, width, and height).
151      *
152      * @param r  the current location of the caret
153      * @see #paint
154      */
damage(final Rectangle r)155     protected synchronized void damage(final Rectangle r) {
156         if (r == null || fPainting) return;
157 
158         x = r.x - 4;
159         y = r.y;
160         width = 10;
161         height = r.height;
162 
163         // Don't damage the border area.  We can't paint a partial border, so get the
164         // intersection of the caret rectangle and the component less the border, if any.
165         final Rectangle caretRect = new Rectangle(x, y, width, height);
166         final Border border = getComponent().getBorder();
167         if (border != null) {
168             final Rectangle alloc = getComponent().getBounds();
169             alloc.x = alloc.y = 0;
170             final Insets borderInsets = border.getBorderInsets(getComponent());
171             alloc.x += borderInsets.left;
172             alloc.y += borderInsets.top;
173             alloc.width -= borderInsets.left + borderInsets.right;
174             alloc.height -= borderInsets.top + borderInsets.bottom;
175             Rectangle2D.intersect(caretRect, alloc, caretRect);
176         }
177         x = caretRect.x;
178         y = caretRect.y;
179         width = Math.max(caretRect.width, 1);
180         height = Math.max(caretRect.height, 1);
181         repaint();
182     }
183 
184     boolean fPainting = false;
185 
186     // See <rdar://problem/3833837> 1.4.2_05-141.3: JTextField performance with Aqua L&F
187     // We are getting into a circular condition with the BasicCaret paint code since it doesn't know about the fact that our
188     // damage routine above elminates the border. Sadly we can't easily change either one, so we will
189     // add a painting flag and not damage during a repaint.
paint(final Graphics g)190     public void paint(final Graphics g) {
191         if (isVisible()) {
192             fPainting = true;
193             super.paint(g);
194             fPainting = false;
195         }
196     }
197 }
198