/* * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.swing; import java.lang.reflect.*; import java.awt.*; import static java.awt.RenderingHints.*; import java.awt.event.*; import java.awt.font.*; import java.awt.print.PrinterGraphics; import java.text.CharacterIterator; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import javax.swing.*; import javax.swing.event.TreeModelEvent; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; import javax.swing.text.DefaultHighlighter; import javax.swing.text.DefaultCaret; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import sun.print.ProxyPrintGraphics; import sun.awt.*; import java.io.*; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.*; import sun.font.FontDesignMetrics; import sun.font.FontUtilities; import sun.java2d.SunGraphicsEnvironment; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; /** * A collection of utility methods for Swing. *
* WARNING: While this class is public, it should not be treated as
* public API and its API may change in incompatable ways between dot dot
* releases and even patch releases. You should not rely on this class even
* existing.
*
*/
public class SwingUtilities2 {
/**
* The
* Callers should pass in a non-null JComponent, the exception
* to this is if a JComponent is not readily available at the time of
* painting.
*
* This does not necessarily return the FontMetrics from the
* Graphics.
*
* @param c JComponent requesting FontMetrics, may be null
* @param g Graphics Graphics
*/
public static FontMetrics getFontMetrics(JComponent c, Graphics g) {
return getFontMetrics(c, g, g.getFont());
}
/**
* Returns the FontMetrics for the specified Font.
* This method is used when a Graphics is available, typically when
* painting. If a Graphics is not available the JComponent method of
* the same name should be used.
*
* Callers should pass in a non-null JComonent, the exception
* to this is if a JComponent is not readily available at the time of
* painting.
*
* This does not necessarily return the FontMetrics from the
* Graphics.
*
* @param c JComponent requesting FontMetrics, may be null
* @param c Graphics Graphics
* @param font Font to get FontMetrics for
*/
@SuppressWarnings("deprecation")
public static FontMetrics getFontMetrics(JComponent c, Graphics g,
Font font) {
if (c != null) {
// Note: We assume that we're using the FontMetrics
// from the widget to layout out text, otherwise we can get
// mismatches when printing.
return c.getFontMetrics(font);
}
return Toolkit.getDefaultToolkit().getFontMetrics(font);
}
/**
* Returns the width of the passed in String.
* If the passed String is
* For two sections, the rectangle is divided equally and the method
* returns whether the point lies in {@code Section.LEADING} or
* {@code Section.TRAILING}. For horizontal divisions, the calculation
* respects component orientation.
*
* For three sections, if the rectangle is greater than or equal to
* 30 pixels in length along the axis, the calculation gives 10 pixels
* to each of the leading and trailing sections and the remainder to the
* middle. For smaller sizes, the rectangle is divided equally into three
* sections.
*
* Note: This method assumes that the point is within the bounds of
* the given rectangle on the specified axis. However, in cases where
* it isn't, the results still have meaning: {@code Section.MIDDLE}
* remains the same, {@code Section.LEADING} indicates that the point
* is in or somewhere before the leading section, and
* {@code Section.TRAILING} indicates that the point is in or somewhere
* after the trailing section.
*
* @param rect the rectangle
* @param p the point the check
* @param horizontal {@code true} to use the horizontal axis,
* or {@code false} for the vertical axis
* @param ltr {@code true} for left to right orientation,
* or {@code false} for right to left orientation;
* only used for horizontal calculations
* @param three {@code true} for three sections,
* or {@code false} for two
*
* @return the {@code Section} where the point lies
*
* @throws NullPointerException if {@code rect} or {@code p} are
* {@code null}
*/
private static Section liesIn(Rectangle rect, Point p, boolean horizontal,
boolean ltr, boolean three) {
/* beginning of the rectangle on the axis */
int p0;
/* point on the axis we're interested in */
int pComp;
/* length of the rectangle on the axis */
int length;
/* value of ltr if horizontal, else true */
boolean forward;
if (horizontal) {
p0 = rect.x;
pComp = p.x;
length = rect.width;
forward = ltr;
} else {
p0 = rect.y;
pComp = p.y;
length = rect.height;
forward = true;
}
if (three) {
int boundary = (length >= 30) ? 10 : length / 3;
if (pComp < p0 + boundary) {
return forward ? Section.LEADING : Section.TRAILING;
} else if (pComp >= p0 + length - boundary) {
return forward ? Section.TRAILING : Section.LEADING;
}
return Section.MIDDLE;
} else {
int middle = p0 + length / 2;
if (forward) {
return pComp >= middle ? Section.TRAILING : Section.LEADING;
} else {
return pComp < middle ? Section.TRAILING : Section.LEADING;
}
}
}
/**
* This method divides a rectangle into two or three sections along
* the horizontal axis and determines which section the given point
* lies in; used by drag and drop when calculating drop locations.
*
* See the documentation for {@link #liesIn} for more information
* on how the section is calculated.
*
* @param rect the rectangle
* @param p the point the check
* @param ltr {@code true} for left to right orientation,
* or {@code false} for right to left orientation
* @param three {@code true} for three sections,
* or {@code false} for two
*
* @return the {@code Section} where the point lies
*
* @throws NullPointerException if {@code rect} or {@code p} are
* {@code null}
*/
public static Section liesInHorizontal(Rectangle rect, Point p,
boolean ltr, boolean three) {
return liesIn(rect, p, true, ltr, three);
}
/**
* This method divides a rectangle into two or three sections along
* the vertical axis and determines which section the given point
* lies in; used by drag and drop when calculating drop locations.
*
* See the documentation for {@link #liesIn} for more information
* on how the section is calculated.
*
* @param rect the rectangle
* @param p the point the check
* @param three {@code true} for three sections,
* or {@code false} for two
*
* @return the {@code Section} where the point lies
*
* @throws NullPointerException if {@code rect} or {@code p} are
* {@code null}
*/
public static Section liesInVertical(Rectangle rect, Point p,
boolean three) {
return liesIn(rect, p, false, false, three);
}
/**
* Maps the index of the column in the view at
* {@code viewColumnIndex} to the index of the column
* in the table model. Returns the index of the corresponding
* column in the model. If {@code viewColumnIndex}
* is less than zero, returns {@code viewColumnIndex}.
*
* @param cm the table model
* @param viewColumnIndex the index of the column in the view
* @return the index of the corresponding column in the model
*
* @see JTable#convertColumnIndexToModel(int)
* @see javax.swing.plaf.basic.BasicTableHeaderUI
*/
public static int convertColumnIndexToModel(TableColumnModel cm,
int viewColumnIndex) {
if (viewColumnIndex < 0) {
return viewColumnIndex;
}
return cm.getColumn(viewColumnIndex).getModelIndex();
}
/**
* Maps the index of the column in the {@code cm} at
* {@code modelColumnIndex} to the index of the column
* in the view. Returns the index of the
* corresponding column in the view; returns {@code -1} if this column
* is not being displayed. If {@code modelColumnIndex} is less than zero,
* returns {@code modelColumnIndex}.
*
* @param cm the table model
* @param modelColumnIndex the index of the column in the model
* @return the index of the corresponding column in the view
*
* @see JTable#convertColumnIndexToView(int)
* @see javax.swing.plaf.basic.BasicTableHeaderUI
*/
public static int convertColumnIndexToView(TableColumnModel cm,
int modelColumnIndex) {
if (modelColumnIndex < 0) {
return modelColumnIndex;
}
for (int column = 0; column < cm.getColumnCount(); column++) {
if (cm.getColumn(column).getModelIndex() == modelColumnIndex) {
return column;
}
}
return -1;
}
public static int getSystemMnemonicKeyMask() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
if (toolkit instanceof SunToolkit) {
return ((SunToolkit) toolkit).getFocusAcceleratorKeyMask();
}
return InputEvent.ALT_MASK;
}
/**
* Returns the {@link TreePath} that identifies the changed nodes.
*
* @param event changes in a tree model
* @param model corresponing tree model
* @return the path to the changed nodes
*/
public static TreePath getTreePath(TreeModelEvent event, TreeModel model) {
TreePath path = event.getTreePath();
if ((path == null) && (model != null)) {
Object root = model.getRoot();
if (root != null) {
path = new TreePath(root);
}
}
return path;
}
/**
* Used to listen to "blit" repaints in RepaintManager.
*/
public interface RepaintListener {
void repaintPerformed(JComponent c, int x, int y, int w, int h);
}
}
AppContext
key for our one LAFState
* instance.
*/
public static final Object LAF_STATE_KEY =
new StringBuffer("LookAndFeel State");
public static final Object MENU_SELECTION_MANAGER_LISTENER_KEY =
new StringBuffer("MenuSelectionManager listener key");
// Maintain a cache of CACHE_SIZE fonts and the left side bearing
// of the characters falling into the range MIN_CHAR_INDEX to
// MAX_CHAR_INDEX. The values in fontCache are created as needed.
private static LSBCacheEntry[] fontCache;
// Windows defines 6 font desktop properties, we will therefore only
// cache the metrics for 6 fonts.
private static final int CACHE_SIZE = 6;
// nextIndex in fontCache to insert a font into.
private static int nextIndex;
// LSBCacheEntry used to search in fontCache to see if we already
// have an entry for a particular font
private static LSBCacheEntry searchKey;
// getLeftSideBearing will consult all characters that fall in the
// range MIN_CHAR_INDEX to MAX_CHAR_INDEX.
private static final int MIN_CHAR_INDEX = (int)'W';
private static final int MAX_CHAR_INDEX = (int)'W' + 1;
public static final FontRenderContext DEFAULT_FRC =
new FontRenderContext(null, false, false);
/**
* A JComponent client property is used to determine text aa settings.
* To avoid having this property persist between look and feels changes
* the value of the property is set to null in JComponent.setUI
*/
public static final Object AA_TEXT_PROPERTY_KEY =
new StringBuffer("AATextInfoPropertyKey");
/**
* Attribute key for the content elements. If it is set on an element, the
* element is considered to be a line break.
*/
public static final String IMPLIED_CR = "CR";
/**
* Used to tell a text component, being used as an editor for table
* or tree, how many clicks it took to start editing.
*/
private static final StringBuilder SKIP_CLICK_COUNT =
new StringBuilder("skipClickCount");
/* Presently this class assumes default fractional metrics.
* This may need to change to emulate future platform L&Fs.
*/
public static class AATextInfo {
private static AATextInfo getAATextInfoFromMap(MapComponentUI
of the JComponent instance should be returned.
*/
public static final Object COMPONENT_UI_PROPERTY_KEY =
new StringBuffer("ComponentUIPropertyKey");
/** Client Property key for the text maximal offsets for BasicMenuItemUI */
public static final StringUIClientPropertyKey BASICMENUITEMUI_MAX_TEXT_OFFSET =
new StringUIClientPropertyKey ("maxTextOffset");
// security stuff
private static final String UntrustedClipboardAccess =
"UNTRUSTED_CLIPBOARD_ACCESS_KEY";
//all access to charsBuffer is to be synchronized on charsBufferLock
private static final int CHAR_BUFFER_SIZE = 100;
private static final Object charsBufferLock = new Object();
private static char[] charsBuffer = new char[CHAR_BUFFER_SIZE];
static {
fontCache = new LSBCacheEntry[CACHE_SIZE];
}
/**
* Fill the character buffer cache. Return the buffer length.
*/
private static int syncCharsBuffer(String s) {
int length = s.length();
if ((charsBuffer == null) || (charsBuffer.length < length)) {
charsBuffer = s.toCharArray();
} else {
s.getChars(0, length, charsBuffer, 0);
}
return length;
}
/**
* checks whether TextLayout is required to handle characters.
*
* @param text characters to be tested
* @param start start
* @param limit limit
* @return true if TextLayout is required
* false if TextLayout is not required
*/
public static final boolean isComplexLayout(char[] text, int start, int limit) {
return FontUtilities.isComplexText(text, start, limit);
}
//
// WARNING WARNING WARNING WARNING WARNING WARNING
// Many of the following methods are invoked from older API.
// As this older API was not passed a Component, a null Component may
// now be passsed in. For example, SwingUtilities.computeStringWidth
// is implemented to call SwingUtilities2.stringWidth, the
// SwingUtilities variant does not take a JComponent, as such
// SwingUtilities2.stringWidth can be passed a null Component.
// In other words, if you add new functionality to these methods you
// need to gracefully handle null.
//
/**
* Returns whether or not text should be drawn antialiased.
*
* @param c JComponent to test.
* @return Whether or not text should be drawn antialiased for the
* specified component.
*/
public static AATextInfo drawTextAntialiased(JComponent c) {
if (c != null) {
/* a non-null property implies some form of AA requested */
return (AATextInfo)c.getClientProperty(AA_TEXT_PROPERTY_KEY);
}
// No component, assume aa is off
return null;
}
/**
* Returns the left side bearing of the first character of string. The
* left side bearing is calculated from the passed in
* FontMetrics. If the passed in String is less than one
* character {@code 0} is returned.
*
* @param c JComponent that will display the string
* @param fm FontMetrics used to measure the String width
* @param string String to get the left side bearing for.
* @throws NullPointerException if {@code string} is {@code null}
*
* @return the left side bearing of the first character of string
* or {@code 0} if the string is empty
*/
public static int getLeftSideBearing(JComponent c, FontMetrics fm,
String string) {
if ((string == null) || (string.length() == 0)) {
return 0;
}
return getLeftSideBearing(c, fm, string.charAt(0));
}
/**
* Returns the left side bearing of the first character of string. The
* left side bearing is calculated from the passed in FontMetrics.
*
* @param c JComponent that will display the string
* @param fm FontMetrics used to measure the String width
* @param firstChar Character to get the left side bearing for.
*/
public static int getLeftSideBearing(JComponent c, FontMetrics fm,
char firstChar) {
int charIndex = (int) firstChar;
if (charIndex < MAX_CHAR_INDEX && charIndex >= MIN_CHAR_INDEX) {
byte[] lsbs = null;
FontRenderContext frc = getFontRenderContext(c, fm);
Font font = fm.getFont();
synchronized (SwingUtilities2.class) {
LSBCacheEntry entry = null;
if (searchKey == null) {
searchKey = new LSBCacheEntry(frc, font);
} else {
searchKey.reset(frc, font);
}
// See if we already have an entry for this pair
for (LSBCacheEntry cacheEntry : fontCache) {
if (searchKey.equals(cacheEntry)) {
entry = cacheEntry;
break;
}
}
if (entry == null) {
// No entry for this pair, add it.
entry = searchKey;
fontCache[nextIndex] = searchKey;
searchKey = null;
nextIndex = (nextIndex + 1) % CACHE_SIZE;
}
return entry.getLeftSideBearing(firstChar);
}
}
return 0;
}
/**
* Returns the FontMetrics for the current Font of the passed
* in Graphics. This method is used when a Graphics
* is available, typically when painting. If a Graphics is not
* available the JComponent method of the same name should be used.
* null
, returns zero.
*
* @param c JComponent that will display the string, may be null
* @param fm FontMetrics used to measure the String width
* @param string String to get the width of
*/
public static int stringWidth(JComponent c, FontMetrics fm, String string){
if (string == null || string.equals("")) {
return 0;
}
boolean needsTextLayout = ((c != null) &&
(c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null));
if (needsTextLayout) {
synchronized(charsBufferLock) {
int length = syncCharsBuffer(string);
needsTextLayout = isComplexLayout(charsBuffer, 0, length);
}
}
if (needsTextLayout) {
TextLayout layout = createTextLayout(c, string,
fm.getFont(), fm.getFontRenderContext());
return (int) layout.getAdvance();
} else {
return fm.stringWidth(string);
}
}
/**
* Clips the passed in String to the space provided.
*
* @param c JComponent that will display the string, may be null
* @param fm FontMetrics used to measure the String width
* @param string String to display
* @param availTextWidth Amount of space that the string can be drawn in
* @return Clipped string that can fit in the provided space.
*/
public static String clipStringIfNecessary(JComponent c, FontMetrics fm,
String string,
int availTextWidth) {
if ((string == null) || (string.equals(""))) {
return "";
}
int textWidth = SwingUtilities2.stringWidth(c, fm, string);
if (textWidth > availTextWidth) {
return SwingUtilities2.clipString(c, fm, string, availTextWidth);
}
return string;
}
/**
* Clips the passed in String to the space provided. NOTE: this assumes
* the string does not fit in the available space.
*
* @param c JComponent that will display the string, may be null
* @param fm FontMetrics used to measure the String width
* @param string String to display
* @param availTextWidth Amount of space that the string can be drawn in
* @return Clipped string that can fit in the provided space.
*/
public static String clipString(JComponent c, FontMetrics fm,
String string, int availTextWidth) {
// c may be null here.
String clipString = "...";
availTextWidth -= SwingUtilities2.stringWidth(c, fm, clipString);
if (availTextWidth <= 0) {
//can not fit any characters
return clipString;
}
boolean needsTextLayout;
synchronized (charsBufferLock) {
int stringLength = syncCharsBuffer(string);
needsTextLayout =
isComplexLayout(charsBuffer, 0, stringLength);
if (!needsTextLayout) {
int width = 0;
for (int nChars = 0; nChars < stringLength; nChars++) {
width += fm.charWidth(charsBuffer[nChars]);
if (width > availTextWidth) {
string = string.substring(0, nChars);
break;
}
}
}
}
if (needsTextLayout) {
FontRenderContext frc = getFontRenderContext(c, fm);
AttributedString aString = new AttributedString(string);
if (c != null) {
aString.addAttribute(TextAttribute.NUMERIC_SHAPING,
c.getClientProperty(TextAttribute.NUMERIC_SHAPING));
}
LineBreakMeasurer measurer =
new LineBreakMeasurer(aString.getIterator(), frc);
int nChars = measurer.nextOffset(availTextWidth);
string = string.substring(0, nChars);
}
return string + clipString;
}
/**
* Draws the string at the specified location.
*
* @param c JComponent that will display the string, may be null
* @param g Graphics to draw the text to
* @param text String to display
* @param x X coordinate to draw the text at
* @param y Y coordinate to draw the text at
*/
public static void drawString(JComponent c, Graphics g, String text,
int x, int y) {
// c may be null
// All non-editable widgets that draw strings call into this
// methods. By non-editable that means widgets like JLabel, JButton
// but NOT JTextComponents.
if ( text == null || text.length() <= 0 ) { //no need to paint empty strings
return;
}
if (isPrinting(g)) {
Graphics2D g2d = getGraphics2D(g);
if (g2d != null) {
/* The printed text must scale linearly with the UI.
* Calculate the width on screen, obtain a TextLayout with
* advances for the printer graphics FRC, and then justify
* it to fit in the screen width. This distributes the spacing
* more evenly than directly laying out to the screen advances.
*/
String trimmedText = trimTrailingSpaces(text);
if (!trimmedText.isEmpty()) {
float screenWidth = (float) g2d.getFont().getStringBounds
(trimmedText, DEFAULT_FRC).getWidth();
TextLayout layout = createTextLayout(c, text, g2d.getFont(),
g2d.getFontRenderContext());
layout = layout.getJustifiedLayout(screenWidth);
/* Use alternate print color if specified */
Color col = g2d.getColor();
if (col instanceof PrintColorUIResource) {
g2d.setColor(((PrintColorUIResource)col).getPrintColor());
}
layout.draw(g2d, x, y);
g2d.setColor(col);
}
return;
}
}
// If we get here we're not printing
if (g instanceof Graphics2D) {
AATextInfo info = drawTextAntialiased(c);
Graphics2D g2 = (Graphics2D)g;
boolean needsTextLayout = ((c != null) &&
(c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null));
if (needsTextLayout) {
synchronized(charsBufferLock) {
int length = syncCharsBuffer(text);
needsTextLayout = isComplexLayout(charsBuffer, 0, length);
}
}
if (info != null) {
Object oldContrast = null;
Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING);
if (info.aaHint != oldAAValue) {
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, info.aaHint);
} else {
oldAAValue = null;
}
if (info.lcdContrastHint != null) {
oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST);
if (info.lcdContrastHint.equals(oldContrast)) {
oldContrast = null;
} else {
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST,
info.lcdContrastHint);
}
}
if (needsTextLayout) {
TextLayout layout = createTextLayout(c, text, g2.getFont(),
g2.getFontRenderContext());
layout.draw(g2, x, y);
} else {
g.drawString(text, x, y);
}
if (oldAAValue != null) {
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue);
}
if (oldContrast != null) {
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast);
}
return;
}
if (needsTextLayout){
TextLayout layout = createTextLayout(c, text, g2.getFont(),
g2.getFontRenderContext());
layout.draw(g2, x, y);
return;
}
}
g.drawString(text, x, y);
}
/**
* Draws the string at the specified location underlining the specified
* character.
*
* @param c JComponent that will display the string, may be null
* @param g Graphics to draw the text to
* @param text String to display
* @param underlinedIndex Index of a character in the string to underline
* @param x X coordinate to draw the text at
* @param y Y coordinate to draw the text at
*/
public static void drawStringUnderlineCharAt(JComponent c,Graphics g,
String text, int underlinedIndex, int x,int y) {
if (text == null || text.length() <= 0) {
return;
}
SwingUtilities2.drawString(c, g, text, x, y);
int textLength = text.length();
if (underlinedIndex >= 0 && underlinedIndex < textLength ) {
int underlineRectY = y;
int underlineRectHeight = 1;
int underlineRectX = 0;
int underlineRectWidth = 0;
boolean isPrinting = isPrinting(g);
boolean needsTextLayout = isPrinting;
if (!needsTextLayout) {
synchronized (charsBufferLock) {
syncCharsBuffer(text);
needsTextLayout =
isComplexLayout(charsBuffer, 0, textLength);
}
}
if (!needsTextLayout) {
FontMetrics fm = g.getFontMetrics();
underlineRectX = x +
SwingUtilities2.stringWidth(c,fm,
text.substring(0,underlinedIndex));
underlineRectWidth = fm.charWidth(text.
charAt(underlinedIndex));
} else {
Graphics2D g2d = getGraphics2D(g);
if (g2d != null) {
TextLayout layout =
createTextLayout(c, text, g2d.getFont(),
g2d.getFontRenderContext());
if (isPrinting) {
float screenWidth = (float)g2d.getFont().
getStringBounds(text, DEFAULT_FRC).getWidth();
layout = layout.getJustifiedLayout(screenWidth);
}
TextHitInfo leading =
TextHitInfo.leading(underlinedIndex);
TextHitInfo trailing =
TextHitInfo.trailing(underlinedIndex);
Shape shape =
layout.getVisualHighlightShape(leading, trailing);
Rectangle rect = shape.getBounds();
underlineRectX = x + rect.x;
underlineRectWidth = rect.width;
}
}
g.fillRect(underlineRectX, underlineRectY + 1,
underlineRectWidth, underlineRectHeight);
}
}
/**
* A variation of locationToIndex() which only returns an index if the
* Point is within the actual bounds of a list item (not just in the cell)
* and if the JList has the "List.isFileList" client property set.
* Otherwise, this method returns -1.
* This is used to make WindowsL&F JFileChooser act like native dialogs.
*/
public static int loc2IndexFileList(JList> list, Point point) {
int index = list.locationToIndex(point);
if (index != -1) {
Object bySize = list.getClientProperty("List.isFileList");
if (bySize instanceof Boolean && ((Boolean)bySize).booleanValue() &&
!pointIsInActualBounds(list, index, point)) {
index = -1;
}
}
return index;
}
/**
* Returns true if the given point is within the actual bounds of the
* JList item at index (not just inside the cell).
*/
private static isRequestFocusEnabled()
returns true.
*/
public static void adjustFocus(JComponent c) {
if (!c.hasFocus() && c.isRequestFocusEnabled()) {
c.requestFocus();
}
}
/**
* The following draw functions have the same semantic as the
* Graphics methods with the same names.
*
* this is used for printing
*/
public static int drawChars(JComponent c, Graphics g,
char[] data,
int offset,
int length,
int x,
int y) {
if ( length <= 0 ) { //no need to paint empty strings
return x;
}
int nextX = x + getFontMetrics(c, g).charsWidth(data, offset, length);
if (isPrinting(g)) {
Graphics2D g2d = getGraphics2D(g);
if (g2d != null) {
FontRenderContext deviceFontRenderContext = g2d.
getFontRenderContext();
FontRenderContext frc = getFontRenderContext(c);
if (frc != null &&
!isFontRenderContextPrintCompatible
(deviceFontRenderContext, frc)) {
String text = new String(data, offset, length);
TextLayout layout = new TextLayout(text, g2d.getFont(),
deviceFontRenderContext);
String trimmedText = trimTrailingSpaces(text);
if (!trimmedText.isEmpty()) {
float screenWidth = (float)g2d.getFont().
getStringBounds(trimmedText, frc).getWidth();
layout = layout.getJustifiedLayout(screenWidth);
/* Use alternate print color if specified */
Color col = g2d.getColor();
if (col instanceof PrintColorUIResource) {
g2d.setColor(((PrintColorUIResource)col).getPrintColor());
}
layout.draw(g2d,x,y);
g2d.setColor(col);
}
return nextX;
}
}
}
// Assume we're not printing if we get here, or that we are invoked
// via Swing text printing which is laid out for the printer.
AATextInfo info = drawTextAntialiased(c);
if (info != null && (g instanceof Graphics2D)) {
Graphics2D g2 = (Graphics2D)g;
Object oldContrast = null;
Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING);
if (info.aaHint != null && info.aaHint != oldAAValue) {
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, info.aaHint);
} else {
oldAAValue = null;
}
if (info.lcdContrastHint != null) {
oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST);
if (info.lcdContrastHint.equals(oldContrast)) {
oldContrast = null;
} else {
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST,
info.lcdContrastHint);
}
}
g.drawChars(data, offset, length, x, y);
if (oldAAValue != null) {
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue);
}
if (oldContrast != null) {
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast);
}
}
else {
g.drawChars(data, offset, length, x, y);
}
return nextX;
}
/*
* see documentation for drawChars
* returns the advance
*/
public static float drawString(JComponent c, Graphics g,
AttributedCharacterIterator iterator,
int x,
int y) {
float retVal;
boolean isPrinting = isPrinting(g);
Color col = g.getColor();
if (isPrinting) {
/* Use alternate print color if specified */
if (col instanceof PrintColorUIResource) {
g.setColor(((PrintColorUIResource)col).getPrintColor());
}
}
Graphics2D g2d = getGraphics2D(g);
if (g2d == null) {
g.drawString(iterator,x,y); //for the cases where advance
//matters it should not happen
retVal = x;
} else {
FontRenderContext frc;
if (isPrinting) {
frc = getFontRenderContext(c);
if (frc.isAntiAliased() || frc.usesFractionalMetrics()) {
frc = new FontRenderContext(frc.getTransform(), false, false);
}
} else if ((frc = getFRCProperty(c)) != null) {
/* frc = frc; ! */
} else {
frc = g2d.getFontRenderContext();
}
TextLayout layout;
if (isPrinting) {
FontRenderContext deviceFRC = g2d.getFontRenderContext();
if (!isFontRenderContextPrintCompatible(frc, deviceFRC)) {
layout = new TextLayout(iterator, deviceFRC);
AttributedCharacterIterator trimmedIt =
getTrimmedTrailingSpacesIterator(iterator);
if (trimmedIt != null) {
float screenWidth = new TextLayout(trimmedIt, frc).
getAdvance();
layout = layout.getJustifiedLayout(screenWidth);
}
} else {
layout = new TextLayout(iterator, frc);
}
} else {
layout = new TextLayout(iterator, frc);
}
layout.draw(g2d, x, y);
retVal = layout.getAdvance();
}
if (isPrinting) {
g.setColor(col);
}
return retVal;
}
/**
* This method should be used for drawing a borders over a filled rectangle.
* Draws vertical line, using the current color, between the points {@code
* (x, y1)} and {@code (x, y2)} in graphics context's coordinate system.
* Note: it use {@code Graphics.fillRect()} internally.
*
* @param g Graphics to draw the line to.
* @param x the x coordinate.
* @param y1 the first point's y coordinate.
* @param y2 the second point's y coordinate.
*/
public static void drawVLine(Graphics g, int x, int y1, int y2) {
if (y2 < y1) {
final int temp = y2;
y2 = y1;
y1 = temp;
}
g.fillRect(x, y1, 1, y2 - y1 + 1);
}
/**
* This method should be used for drawing a borders over a filled rectangle.
* Draws horizontal line, using the current color, between the points {@code
* (x1, y)} and {@code (x2, y)} in graphics context's coordinate system.
* Note: it use {@code Graphics.fillRect()} internally.
*
* @param g Graphics to draw the line to.
* @param x1 the first point's x coordinate.
* @param x2 the second point's x coordinate.
* @param y the y coordinate.
*/
public static void drawHLine(Graphics g, int x1, int x2, int y) {
if (x2 < x1) {
final int temp = x2;
x2 = x1;
x1 = temp;
}
g.fillRect(x1, y, x2 - x1 + 1, 1);
}
/**
* This method should be used for drawing a borders over a filled rectangle.
* Draws the outline of the specified rectangle. The left and right edges of
* the rectangle are at {@code x} and {@code x + w}. The top and bottom
* edges are at {@code y} and {@code y + h}. The rectangle is drawn using
* the graphics context's current color. Note: it use {@code
* Graphics.fillRect()} internally.
*
* @param g Graphics to draw the rectangle to.
* @param x the x coordinate of the rectangle to be drawn.
* @param y the y coordinate of the rectangle to be drawn.
* @param w the w of the rectangle to be drawn.
* @param h the h of the rectangle to be drawn.
* @see SwingUtilities2#drawVLine(java.awt.Graphics, int, int, int)
* @see SwingUtilities2#drawHLine(java.awt.Graphics, int, int, int)
*/
public static void drawRect(Graphics g, int x, int y, int w, int h) {
if (w < 0 || h < 0) {
return;
}
if (h == 0 || w == 0) {
g.fillRect(x, y, w + 1, h + 1);
} else {
g.fillRect(x, y, w, 1);
g.fillRect(x + w, y, 1, h);
g.fillRect(x + 1, y + h, w, 1);
g.fillRect(x, y + 1, 1, h);
}
}
private static TextLayout createTextLayout(JComponent c, String s,
Font f, FontRenderContext frc) {
Object shaper = (c == null ?
null : c.getClientProperty(TextAttribute.NUMERIC_SHAPING));
if (shaper == null) {
return new TextLayout(s, f, frc);
} else {
MapFont
and FontRenderContext
.
* This only caches characters that fall in the range
* MIN_CHAR_INDEX
to MAX_CHAR_INDEX
.
*/
private static class LSBCacheEntry {
// Used to indicate a particular entry in lsb has not been set.
private static final byte UNSET = Byte.MAX_VALUE;
// Used in creating a GlyphVector to get the lsb
private static final char[] oneChar = new char[1];
private byte[] lsbCache;
private Font font;
private FontRenderContext frc;
public LSBCacheEntry(FontRenderContext frc, Font font) {
lsbCache = new byte[MAX_CHAR_INDEX - MIN_CHAR_INDEX];
reset(frc, font);
}
public void reset(FontRenderContext frc, Font font) {
this.font = font;
this.frc = frc;
for (int counter = lsbCache.length - 1; counter >= 0; counter--) {
lsbCache[counter] = UNSET;
}
}
public int getLeftSideBearing(char aChar) {
int index = aChar - MIN_CHAR_INDEX;
assert (index >= 0 && index < (MAX_CHAR_INDEX - MIN_CHAR_INDEX));
byte lsb = lsbCache[index];
if (lsb == UNSET) {
oneChar[0] = aChar;
GlyphVector gv = font.createGlyphVector(frc, oneChar);
lsb = (byte) gv.getGlyphPixelBounds(0, frc, 0f, 0f).x;
if (lsb < 0) {
/* HRGB/HBGR LCD glyph images will always have a pixel
* on the left used in colour fringe reduction.
* Text rendering positions this correctly but here
* we are using the glyph image to adjust that position
* so must account for it.
*/
Object aaHint = frc.getAntiAliasingHint();
if (aaHint == VALUE_TEXT_ANTIALIAS_LCD_HRGB ||
aaHint == VALUE_TEXT_ANTIALIAS_LCD_HBGR) {
lsb++;
}
}
lsbCache[index] = lsb;
}
return lsb;
}
public boolean equals(Object entry) {
if (entry == this) {
return true;
}
if (!(entry instanceof LSBCacheEntry)) {
return false;
}
LSBCacheEntry oEntry = (LSBCacheEntry) entry;
return (font.equals(oEntry.font) &&
frc.equals(oEntry.frc));
}
public int hashCode() {
int result = 17;
if (font != null) {
result = 37 * result + font.hashCode();
}
if (frc != null) {
result = 37 * result + frc.hashCode();
}
return result;
}
}
/*
* here goes the fix for 4856343 [Problem with applet interaction
* with system selection clipboard]
*
* NOTE. In case isTrustedContext() no checking
* are to be performed
*/
/**
* checks the security permissions for accessing system clipboard
*
* for untrusted context (see isTrustedContext) checks the
* permissions for the current event being handled
*
*/
public static boolean canAccessSystemClipboard() {
boolean canAccess = false;
if (!GraphicsEnvironment.isHeadless()) {
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
canAccess = true;
} else {
try {
sm.checkPermission(AWTPermissions.ACCESS_CLIPBOARD_PERMISSION);
canAccess = true;
} catch (SecurityException e) {
}
if (canAccess && ! isTrustedContext()) {
canAccess = canCurrentEventAccessSystemClipboard(true);
}
}
}
return canAccess;
}
/**
* Returns true if EventQueue.getCurrentEvent() has the permissions to
* access the system clipboard
*/
public static boolean canCurrentEventAccessSystemClipboard() {
return isTrustedContext()
|| canCurrentEventAccessSystemClipboard(false);
}
/**
* Returns true if the given event has permissions to access the
* system clipboard
*
* @param e AWTEvent to check
*/
public static boolean canEventAccessSystemClipboard(AWTEvent e) {
return isTrustedContext()
|| canEventAccessSystemClipboard(e, false);
}
/**
* Returns true if the given event is corrent gesture for
* accessing clipboard
*
* @param ie InputEvent to check
*/
private static boolean isAccessClipboardGesture(InputEvent ie) {
boolean allowedGesture = false;
if (ie instanceof KeyEvent) { //we can validate only keyboard gestures
KeyEvent ke = (KeyEvent)ie;
int keyCode = ke.getKeyCode();
int keyModifiers = ke.getModifiers();
switch(keyCode) {
case KeyEvent.VK_C:
case KeyEvent.VK_V:
case KeyEvent.VK_X:
allowedGesture = (keyModifiers == InputEvent.CTRL_MASK);
break;
case KeyEvent.VK_INSERT:
allowedGesture = (keyModifiers == InputEvent.CTRL_MASK ||
keyModifiers == InputEvent.SHIFT_MASK);
break;
case KeyEvent.VK_COPY:
case KeyEvent.VK_PASTE:
case KeyEvent.VK_CUT:
allowedGesture = true;
break;
case KeyEvent.VK_DELETE:
allowedGesture = ( keyModifiers == InputEvent.SHIFT_MASK);
break;
}
}
return allowedGesture;
}
/**
* Returns true if e has the permissions to
* access the system clipboard and if it is allowed gesture (if
* checkGesture is true)
*
* @param e AWTEvent to check
* @param checkGesture boolean
*/
private static boolean canEventAccessSystemClipboard(AWTEvent e,
boolean checkGesture) {
if (EventQueue.isDispatchThread()) {
/*
* Checking event permissions makes sense only for event
* dispathing thread
*/
if (e instanceof InputEvent
&& (! checkGesture || isAccessClipboardGesture((InputEvent)e))) {
return AWTAccessor.getInputEventAccessor().
canAccessSystemClipboard((InputEvent) e);
} else {
return false;
}
} else {
return true;
}
}
/**
* Utility method that throws SecurityException if SecurityManager is set
* and modifiers are not public
*
* @param modifiers a set of modifiers
*/
public static void checkAccess(int modifiers) {
if (System.getSecurityManager() != null
&& !Modifier.isPublic(modifiers)) {
throw new SecurityException("Resource is not accessible");
}
}
/**
* Returns true if EventQueue.getCurrentEvent() has the permissions to
* access the system clipboard and if it is allowed gesture (if
* checkGesture true)
*
* @param checkGesture boolean
*/
private static boolean canCurrentEventAccessSystemClipboard(boolean
checkGesture) {
AWTEvent event = EventQueue.getCurrentEvent();
return canEventAccessSystemClipboard(event, checkGesture);
}
/**
* see RFE 5012841 [Per AppContect security permissions] for the
* details
*
*/
private static boolean isTrustedContext() {
return (System.getSecurityManager() == null)
|| (AppContext.getAppContext().
get(UntrustedClipboardAccess) == null);
}
public static String displayPropertiesToCSS(Font font, Color fg) {
StringBuilder rule = new StringBuilder("body {");
if (font != null) {
rule.append(" font-family: ");
rule.append(font.getFamily());
rule.append(" ; ");
rule.append(" font-size: ");
rule.append(font.getSize());
rule.append("pt ;");
if (font.isBold()) {
rule.append(" font-weight: 700 ; ");
}
if (font.isItalic()) {
rule.append(" font-style: italic ; ");
}
}
if (fg != null) {
rule.append(" color: #");
if (fg.getRed() < 16) {
rule.append('0');
}
rule.append(Integer.toHexString(fg.getRed()));
if (fg.getGreen() < 16) {
rule.append('0');
}
rule.append(Integer.toHexString(fg.getGreen()));
if (fg.getBlue() < 16) {
rule.append('0');
}
rule.append(Integer.toHexString(fg.getBlue()));
rule.append(" ; ");
}
rule.append(" }");
return rule.toString();
}
/**
* Utility method that creates a UIDefaults.LazyValue
that
* creates an ImageIcon
UIResource
for the
* specified image file name. The image is loaded using
* getResourceAsStream
, starting with a call to that method
* on the base class parameter. If it cannot be found, searching will
* continue through the base class' inheritance hierarchy, up to and
* including rootClass
.
*
* @param baseClass the first class to use in searching for the resource
* @param rootClass an ancestor of baseClass
to finish the
* search at
* @param imageFile the name of the file to be found
* @return a lazy value that creates the ImageIcon
* UIResource
for the image,
* or null if it cannot be found
*/
public static Object makeIcon(final Class> baseClass,
final Class> rootClass,
final String imageFile) {
return makeIcon(baseClass, rootClass, imageFile, true);
}
/**
* Utility method that creates a UIDefaults.LazyValue
that
* creates an ImageIcon
UIResource
for the
* specified image file name. The image is loaded using
* getResourceAsStream
, starting with a call to that method
* on the base class parameter. If it cannot be found, searching will
* continue through the base class' inheritance hierarchy, up to and
* including rootClass
.
*
* Finds an image with a given name without privileges enabled.
*
* @param baseClass the first class to use in searching for the resource
* @param rootClass an ancestor of baseClass
to finish the
* search at
* @param imageFile the name of the file to be found
* @return a lazy value that creates the ImageIcon
* UIResource
for the image,
* or null if it cannot be found
*/
public static Object makeIcon_Unprivileged(final Class> baseClass,
final Class> rootClass,
final String imageFile) {
return makeIcon(baseClass, rootClass, imageFile, false);
}
private static Object makeIcon(final Class> baseClass,
final Class> rootClass,
final String imageFile,
final boolean enablePrivileges) {
return (UIDefaults.LazyValue) (table) -> {
byte[] buffer = enablePrivileges ? AccessController.doPrivileged(
(PrivilegedActionkey
does
* not map to a valid Integer
, or can not be convered from
* a String
to an integer, the value 0 is returned.
*
* @param key an Object
specifying the int.
* @return the int
*/
public static int getUIDefaultsInt(Object key) {
return getUIDefaultsInt(key, 0);
}
/**
* Returns an integer from the defaults table that is appropriate
* for the given locale. If key
does not map to a valid
* Integer
, or can not be convered from a String
* to an integer, the value 0 is returned.
*
* @param key an Object
specifying the int. Returned value
* is 0 if key
is not available,
* @param l the Locale
for which the int is desired
* @return the int
*/
public static int getUIDefaultsInt(Object key, Locale l) {
return getUIDefaultsInt(key, l, 0);
}
/**
* Returns an integer from the defaults table. If key
does
* not map to a valid Integer
, or can not be convered from
* a String
to an integer, default
is
* returned.
*
* @param key an Object
specifying the int. Returned value
* is 0 if key
is not available,
* @param defaultValue Returned value if key
is not available,
* or is not an Integer
* @return the int
*/
public static int getUIDefaultsInt(Object key, int defaultValue) {
return getUIDefaultsInt(key, null, defaultValue);
}
/**
* Returns an integer from the defaults table that is appropriate
* for the given locale. If key
does not map to a valid
* Integer
, or can not be convered from a String
* to an integer, default
is returned.
*
* @param key an Object
specifying the int. Returned value
* is 0 if key
is not available,
* @param l the Locale
for which the int is desired
* @param defaultValue Returned value if key
is not available,
* or is not an Integer
* @return the int
*/
public static int getUIDefaultsInt(Object key, Locale l, int defaultValue) {
Object value = UIManager.get(key, l);
if (value instanceof Integer) {
return ((Integer)value).intValue();
}
if (value instanceof String) {
try {
return Integer.parseInt((String)value);
} catch (NumberFormatException nfe) {}
}
return defaultValue;
}
// At this point we need this method here. But we assume that there
// will be a common method for this purpose in the future releases.
public static Component compositeRequestFocus(Component component) {
if (component instanceof Container) {
Container container = (Container)component;
if (container.isFocusCycleRoot()) {
FocusTraversalPolicy policy = container.getFocusTraversalPolicy();
Component comp = policy.getDefaultComponent(container);
if (comp!=null) {
comp.requestFocus();
return comp;
}
}
Container rootAncestor = container.getFocusCycleRootAncestor();
if (rootAncestor!=null) {
FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy();
Component comp = policy.getComponentAfter(rootAncestor, container);
if (comp!=null && SwingUtilities.isDescendingFrom(comp, container)) {
comp.requestFocus();
return comp;
}
}
}
if (component.isFocusable()) {
component.requestFocus();
return component;
}
return null;
}
/**
* Change focus to the visible component in {@code JTabbedPane}.
* This is not a general-purpose method and is here only to permit
* sharing code.
*/
@SuppressWarnings("deprecation")
public static boolean tabbedPaneChangeFocusTo(Component comp) {
if (comp != null) {
if (comp.isFocusTraversable()) {
SwingUtilities2.compositeRequestFocus(comp);
return true;
} else if (comp instanceof JComponent
&& ((JComponent)comp).requestDefaultFocus()) {
return true;
}
}
return false;
}
/**
* Submits a value-returning task for execution on the EDT and
* returns a Future representing the pending results of the task.
*
* @param task the task to submit
* @return a Future representing pending completion of the task
* @throws NullPointerException if the task is null
*/
public static