1 /*
2  * Copyright (c) 2011, 2015, 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.Graphics;
29 import java.awt.Insets;
30 import java.awt.Rectangle;
31 import java.awt.event.FocusEvent;
32 import java.awt.event.MouseEvent;
33 import java.awt.geom.Rectangle2D;
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyChangeListener;
36 
37 import javax.swing.JEditorPane;
38 import javax.swing.JTextArea;
39 import javax.swing.border.Border;
40 import javax.swing.plaf.UIResource;
41 import javax.swing.text.DefaultCaret;
42 import javax.swing.text.Highlighter;
43 import javax.swing.text.JTextComponent;
44 import javax.swing.SwingUtilities;
45 
46 @SuppressWarnings("serial") // Superclass is not serializable across versions
47 public class AquaCaret extends DefaultCaret
48         implements UIResource, PropertyChangeListener {
49 
50     private boolean isMultiLineEditor;
51     private boolean mFocused = false;
52     private boolean fPainting = false;
53 
54     @Override
install(final JTextComponent c)55     public void install(final JTextComponent c) {
56         super.install(c);
57         isMultiLineEditor = c instanceof JTextArea || c instanceof JEditorPane;
58         c.addPropertyChangeListener(this);
59     }
60 
61     @Override
deinstall(final JTextComponent c)62     public void deinstall(final JTextComponent c) {
63         c.removePropertyChangeListener(this);
64         super.deinstall(c);
65     }
66 
67     @Override
getSelectionPainter()68     protected Highlighter.HighlightPainter getSelectionPainter() {
69         return AquaHighlighter.getInstance();
70     }
71 
72     /**
73      * Only show the flashing caret if the selection range is zero
74      */
75     @Override
setVisible(boolean e)76     public void setVisible(boolean e) {
77         if (e) e = getDot() == getMark();
78         super.setVisible(e);
79     }
80 
81     @Override
fireStateChanged()82     protected void fireStateChanged() {
83         // If we have focus the caret should only flash if the range length is zero
84         if (mFocused) setVisible(getComponent().isEditable());
85 
86         super.fireStateChanged();
87     }
88 
89     @Override
propertyChange(final PropertyChangeEvent evt)90     public void propertyChange(final PropertyChangeEvent evt) {
91         final String propertyName = evt.getPropertyName();
92 
93         if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) {
94             final JTextComponent comp = ((JTextComponent)evt.getSource());
95 
96             if (evt.getNewValue() == Boolean.TRUE) {
97                 setVisible(comp.hasFocus());
98             } else {
99                 setVisible(false);
100             }
101 
102             if (getDot() != getMark()) comp.getUI().damageRange(comp, getDot(), getMark());
103         }
104     }
105 
106     // --- FocusListener methods --------------------------
107 
108     private boolean shouldSelectAllOnFocus = true;
109     @Override
focusGained(final FocusEvent e)110     public void focusGained(final FocusEvent e) {
111         final JTextComponent component = getComponent();
112         if (!component.isEnabled() || !component.isEditable()) {
113             super.focusGained(e);
114             return;
115         }
116 
117         mFocused = true;
118         if (!shouldSelectAllOnFocus) {
119             shouldSelectAllOnFocus = true;
120             super.focusGained(e);
121             return;
122         }
123 
124         if (isMultiLineEditor) {
125             super.focusGained(e);
126             return;
127         }
128 
129         final int end = component.getDocument().getLength();
130         final int dot = getDot();
131         final int mark = getMark();
132         if (dot == mark) {
133             if (dot == 0) {
134                 component.setCaretPosition(end);
135                 component.moveCaretPosition(0);
136             } else if (dot == end) {
137                 component.setCaretPosition(0);
138                 component.moveCaretPosition(end);
139             }
140         }
141 
142         super.focusGained(e);
143     }
144 
145     @Override
focusLost(final FocusEvent e)146     public void focusLost(final FocusEvent e) {
147         mFocused = false;
148         shouldSelectAllOnFocus = true;
149         if (isMultiLineEditor) {
150             setVisible(false);
151             getComponent().repaint();
152         } else {
153             super.focusLost(e);
154         }
155     }
156 
157     // This fixes the problem where when on the mac you have to ctrl left click to
158     // get popup triggers the caret has code that only looks at button number.
159     // see radar # 3125390
160     @Override
mousePressed(final MouseEvent e)161     public void mousePressed(final MouseEvent e) {
162         if (!e.isPopupTrigger() && !(SwingUtilities.isLeftMouseButton(e) &&
163                 e.getClickCount() == 3)) {
164             super.mousePressed(e);
165             shouldSelectAllOnFocus = false;
166         }
167     }
168 
169     /**
170      * Damages the area surrounding the caret to cause
171      * it to be repainted in a new location.  If paint()
172      * is reimplemented, this method should also be
173      * reimplemented.  This method should update the
174      * caret bounds (x, y, width, and height).
175      *
176      * @param r  the current location of the caret
177      * @see #paint
178      */
179     @Override
damage(final Rectangle r)180     protected synchronized void damage(final Rectangle r) {
181         if (r == null || fPainting) return;
182 
183         x = r.x - 4;
184         y = r.y;
185         width = 10;
186         height = r.height;
187 
188         // Don't damage the border area.  We can't paint a partial border, so get the
189         // intersection of the caret rectangle and the component less the border, if any.
190         final Rectangle caretRect = new Rectangle(x, y, width, height);
191         final Border border = getComponent().getBorder();
192         if (border != null) {
193             final Rectangle alloc = getComponent().getBounds();
194             alloc.x = alloc.y = 0;
195             final Insets borderInsets = border.getBorderInsets(getComponent());
196             alloc.x += borderInsets.left;
197             alloc.y += borderInsets.top;
198             alloc.width -= borderInsets.left + borderInsets.right;
199             alloc.height -= borderInsets.top + borderInsets.bottom;
200             Rectangle2D.intersect(caretRect, alloc, caretRect);
201         }
202         x = caretRect.x;
203         y = caretRect.y;
204         width = Math.max(caretRect.width, 1);
205         height = Math.max(caretRect.height, 1);
206         repaint();
207     }
208 
209     // See <rdar://problem/3833837> 1.4.2_05-141.3: JTextField performance with
210     // Aqua L&F. We are getting into a circular condition with the BasicCaret
211     // paint code since it doesn't know about the fact that our damage routine
212     // above elminates the border. Sadly we can't easily change either one, so
213     // we will add a painting flag and not damage during a repaint.
214     @Override
paint(final Graphics g)215     public void paint(final Graphics g) {
216         if (isVisible()) {
217             fPainting = true;
218             super.paint(g);
219             fPainting = false;
220         }
221     }
222 }
223