001: /*
002: * Copyright (c) 2001-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.looks.windows;
032:
033: import java.awt.*;
034: import java.awt.event.FocusEvent;
035: import java.awt.event.MouseEvent;
036:
037: import javax.swing.BoundedRangeModel;
038: import javax.swing.JFormattedTextField;
039: import javax.swing.JTextField;
040: import javax.swing.SwingUtilities;
041: import javax.swing.plaf.TextUI;
042: import javax.swing.plaf.UIResource;
043: import javax.swing.text.*;
044:
045: /**
046: * WindowsFieldCaret has different scrolling behavior than the DefaultCaret.
047: * Also, this caret is visible in non-editable fields,
048: * and the text is selected after a keyboard focus gained event.
049: * For the latter see also issue #4337647 in Sun's bug database.
050: *
051: * @author Karsten Lentzsch
052: * @version $Revision: 1.5 $
053: *
054: */
055: final class WindowsFieldCaret extends DefaultCaret implements
056: UIResource {
057:
058: private static final LayeredHighlighter.LayerPainter WindowsPainter = new WindowsHighlightPainter(
059: null);
060:
061: WindowsFieldCaret() {
062: super ();
063: }
064:
065: // Begin of Added Code ----------------------------------------------
066:
067: private boolean isKeyboardFocusEvent = true;
068:
069: public void focusGained(FocusEvent e) {
070: if (getComponent().isEnabled()) {
071: setVisible(true);
072: setSelectionVisible(true);
073: }
074:
075: final JTextComponent c = getComponent();
076: if (c.isEnabled() && isKeyboardFocusEvent) {
077: if (c instanceof JFormattedTextField) {
078: EventQueue.invokeLater(new Runnable() {
079: public void run() {
080: WindowsFieldCaret.super .setDot(0);
081: WindowsFieldCaret.super .moveDot(c.getDocument()
082: .getLength());
083: }
084: });
085: } else {
086: super .setDot(0);
087: super .moveDot(c.getDocument().getLength());
088: }
089: }
090: }
091:
092: public void focusLost(FocusEvent e) {
093: super .focusLost(e);
094: if (!e.isTemporary()) {
095: isKeyboardFocusEvent = true;
096: }
097: }
098:
099: public void mousePressed(MouseEvent e) {
100: if (SwingUtilities.isLeftMouseButton(e) || e.isPopupTrigger()) {
101: isKeyboardFocusEvent = false;
102: }
103: super .mousePressed(e);
104:
105: }
106:
107: public void mouseReleased(MouseEvent e) {
108: super .mouseReleased(e);
109: // super.mousePressed() does not transfer focus on popup clicks.
110: // Windows does.
111: if (e.isPopupTrigger()) {
112: isKeyboardFocusEvent = false;
113: if ((getComponent() != null) && getComponent().isEnabled()
114: && getComponent().isRequestFocusEnabled()) {
115: getComponent().requestFocus();
116: }
117: }
118: }
119:
120: // End of Added Code ------------------------------------------------
121:
122: /**
123: * Adjusts the visibility of the caret according to
124: * the windows feel which seems to be to move the
125: * caret out into the field by about a quarter of
126: * a field length if not visible.
127: */
128: protected void adjustVisibility(Rectangle r) {
129: SwingUtilities.invokeLater(new SafeScroller(r));
130: }
131:
132: /**
133: * Gets the painter for the Highlighter.
134: *
135: * @return the painter
136: */
137: protected Highlighter.HighlightPainter getSelectionPainter() {
138: return WindowsPainter;
139: }
140:
141: private final class SafeScroller implements Runnable {
142:
143: SafeScroller(Rectangle r) {
144: this .r = r;
145: }
146:
147: public void run() {
148: JTextField field = (JTextField) getComponent();
149: if (field != null) {
150: TextUI ui = field.getUI();
151: int dot = getDot();
152: // PENDING: We need to expose the bias in DefaultCaret.
153: Position.Bias bias = Position.Bias.Forward;
154: Rectangle startRect = null;
155: try {
156: startRect = ui.modelToView(field, dot, bias);
157: } catch (BadLocationException ble) {
158: }
159:
160: Insets i = field.getInsets();
161: BoundedRangeModel vis = field.getHorizontalVisibility();
162: int x = r.x + vis.getValue() - i.left;
163: int quarterSpan = vis.getExtent() / 4;
164: if (r.x < i.left) {
165: vis.setValue(x - quarterSpan);
166: } else if (r.x + r.width > i.left + vis.getExtent()) {
167: vis.setValue(x - (3 * quarterSpan));
168: }
169: // If we scroll, our visual location will have changed,
170: // but we won't have updated our internal location as
171: // the model hasn't changed. This checks for the change,
172: // and if necessary, resets the internal location.
173: if (startRect != null) {
174: try {
175: Rectangle endRect;
176: endRect = ui.modelToView(field, dot, bias);
177: if (endRect != null
178: && !endRect.equals(startRect)) {
179: damage(endRect);
180: }
181: } catch (BadLocationException ble) {
182: }
183: }
184: }
185: }
186:
187: private Rectangle r;
188: }
189:
190: // Helper Classes *********************************************************
191:
192: private static final class WindowsHighlightPainter extends
193: DefaultHighlighter.DefaultHighlightPainter {
194: WindowsHighlightPainter(Color c) {
195: super (c);
196: }
197:
198: // --- HighlightPainter methods ---------------------------------------
199:
200: /**
201: * Paints a highlight.
202: *
203: * @param g the graphics context
204: * @param offs0 the starting model offset >= 0
205: * @param offs1 the ending model offset >= offs1
206: * @param bounds the bounding box for the highlight
207: * @param c the editor
208: */
209: public void paint(Graphics g, int offs0, int offs1,
210: Shape bounds, JTextComponent c) {
211: Rectangle alloc = bounds.getBounds();
212: try {
213: // --- determine locations ---
214: TextUI mapper = c.getUI();
215: Rectangle p0 = mapper.modelToView(c, offs0);
216: Rectangle p1 = mapper.modelToView(c, offs1);
217:
218: // --- render ---
219: Color color = getColor();
220:
221: if (color == null) {
222: g.setColor(c.getSelectionColor());
223: } else {
224: g.setColor(color);
225: }
226: boolean firstIsDot = false;
227: boolean secondIsDot = false;
228: if (c.isEditable()) {
229: int dot = c.getCaretPosition();
230: firstIsDot = (offs0 == dot);
231: secondIsDot = (offs1 == dot);
232: }
233: if (p0.y == p1.y) {
234: // same line, render a rectangle
235: Rectangle r = p0.union(p1);
236: if (r.width > 0) {
237: if (firstIsDot) {
238: r.x++;
239: r.width--;
240: } else if (secondIsDot) {
241: r.width--;
242: }
243: }
244: g.fillRect(r.x, r.y, r.width, r.height);
245: } else {
246: // different lines
247: int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
248: if (firstIsDot && p0ToMarginWidth > 0) {
249: p0.x++;
250: p0ToMarginWidth--;
251: }
252: g.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height);
253: if ((p0.y + p0.height) != p1.y) {
254: g.fillRect(alloc.x, p0.y + p0.height,
255: alloc.width, p1.y - (p0.y + p0.height));
256: }
257: if (secondIsDot && p1.x > alloc.x) {
258: p1.x--;
259: }
260: g.fillRect(alloc.x, p1.y, (p1.x - alloc.x),
261: p1.height);
262: }
263: } catch (BadLocationException e) {
264: // can't render
265: }
266: }
267:
268: // --- LayerPainter methods ----------------------------
269: /**
270: * Paints a portion of a highlight.
271: *
272: * @param g the graphics context
273: * @param offs0 the starting model offset >= 0
274: * @param offs1 the ending model offset >= offs1
275: * @param bounds the bounding box of the view, which is not
276: * necessarily the region to paint.
277: * @param c the editor
278: * @param view View painting for
279: * @return region drawing occured in
280: */
281: public Shape paintLayer(Graphics g, int offs0, int offs1,
282: Shape bounds, JTextComponent c, View view) {
283: Color color = getColor();
284:
285: if (color == null) {
286: g.setColor(c.getSelectionColor());
287: } else {
288: g.setColor(color);
289: }
290: boolean firstIsDot = false;
291: boolean secondIsDot = false;
292: if (c.isEditable()) {
293: int dot = c.getCaretPosition();
294: firstIsDot = (offs0 == dot);
295: secondIsDot = (offs1 == dot);
296: }
297: if (offs0 == view.getStartOffset()
298: && offs1 == view.getEndOffset()) {
299: // Contained in view, can just use bounds.
300: Rectangle alloc;
301: if (bounds instanceof Rectangle) {
302: alloc = (Rectangle) bounds;
303: } else {
304: alloc = bounds.getBounds();
305: }
306: if (firstIsDot && alloc.width > 0) {
307: g.fillRect(alloc.x + 1, alloc.y, alloc.width - 1,
308: alloc.height);
309: } else if (secondIsDot && alloc.width > 0) {
310: g.fillRect(alloc.x, alloc.y, alloc.width - 1,
311: alloc.height);
312: } else {
313: g.fillRect(alloc.x, alloc.y, alloc.width,
314: alloc.height);
315: }
316: return alloc;
317: } else {
318: // Should only render part of View.
319: try {
320: // --- determine locations ---
321: Shape shape = view.modelToView(offs0,
322: Position.Bias.Forward, offs1,
323: Position.Bias.Backward, bounds);
324: Rectangle r = (shape instanceof Rectangle) ? (Rectangle) shape
325: : shape.getBounds();
326: if (firstIsDot && r.width > 0) {
327: g.fillRect(r.x + 1, r.y, r.width - 1, r.height);
328: } else if (secondIsDot && r.width > 0) {
329: g.fillRect(r.x, r.y, r.width - 1, r.height);
330: } else {
331: g.fillRect(r.x, r.y, r.width, r.height);
332: }
333: return r;
334: } catch (BadLocationException e) {
335: // can't render
336: }
337: }
338: // Only if exception
339: return null;
340: }
341:
342: }
343:
344: }
|