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