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