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