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