/* * Copyright (c) 2002, 2015, 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.BreakIterator; 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; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.TextUIDrawing; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTextUIDrawing; /** * 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 {@code AppContext} key for our one {@code 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); /** * 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"); @SuppressWarnings("unchecked") public static void putAATextInfo(boolean lafCondition, Map map) { SunToolkit.setAAFontSettingsCondition(lafCondition); Toolkit tk = Toolkit.getDefaultToolkit(); Object desktopHints = tk.getDesktopProperty(SunToolkit.DESKTOPFONTHINTS); if (desktopHints instanceof Map) { Map hints = (Map) desktopHints; Object aaHint = hints.get(KEY_TEXT_ANTIALIASING); if (aaHint == null || aaHint == VALUE_TEXT_ANTIALIAS_OFF || aaHint == VALUE_TEXT_ANTIALIAS_DEFAULT) { return; } map.put(KEY_TEXT_ANTIALIASING, aaHint); map.put(KEY_TEXT_LCD_CONTRAST, hints.get(KEY_TEXT_LCD_CONTRAST)); } } /** 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 {@code true} if TextLayout is required * {@code 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 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. *

* 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 {@code 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) { AttributedString aString = new AttributedString(string); if (c != null) { aString.addAttribute(TextAttribute.NUMERIC_SHAPING, c.getClientProperty(TextAttribute.NUMERIC_SHAPING)); } LineBreakMeasurer measurer = new LineBreakMeasurer( aString.getIterator(), BreakIterator.getCharacterInstance(), getFontRenderContext(c, fm)); string = string.substring(0, measurer.nextOffset(availTextWidth)); } 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) { 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); } } Object aaHint = (c == null) ? null : c.getClientProperty(KEY_TEXT_ANTIALIASING); if (aaHint != null) { Object oldContrast = null; Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING); if (aaHint != oldAAValue) { g2.setRenderingHint(KEY_TEXT_ANTIALIASING, aaHint); } else { oldAAValue = null; } Object lcdContrastHint = c.getClientProperty( KEY_TEXT_LCD_CONTRAST); if (lcdContrastHint != null) { oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST); if (lcdContrastHint.equals(oldContrast)) { oldContrast = null; } else { g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, 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 Windows {@literal L&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 boolean pointIsInActualBounds(JList list, int index, Point point) { ListCellRenderer renderer = list.getCellRenderer(); T value = list.getModel().getElementAt(index); Component item = renderer.getListCellRendererComponent(list, value, index, false, false); Dimension itemSize = item.getPreferredSize(); Rectangle cellBounds = list.getCellBounds(index, index); if (!item.getComponentOrientation().isLeftToRight()) { cellBounds.x += (cellBounds.width - itemSize.width); } cellBounds.width = itemSize.width; return cellBounds.contains(point); } /** * Returns true if the given point is outside the preferredSize of the * item at the given row of the table. (Column must be 0). * Does not check the "Table.isFileList" property. That should be checked * before calling this method. * This is used to make Windows {@literal L&F} JFileChooser act * like native dialogs. */ public static boolean pointOutsidePrefSize(JTable table, int row, int column, Point p) { if (table.convertColumnIndexToModel(column) != 0 || row == -1) { return true; } TableCellRenderer tcr = table.getCellRenderer(row, column); Object value = table.getValueAt(row, column); Component cell = tcr.getTableCellRendererComponent(table, value, false, false, row, column); Dimension itemSize = cell.getPreferredSize(); Rectangle cellBounds = table.getCellRect(row, column, false); cellBounds.width = itemSize.width; cellBounds.height = itemSize.height; // See if coords are inside // ASSUME: mouse x,y will never be < cell's x,y assert (p.x >= cellBounds.x && p.y >= cellBounds.y); return p.x > cellBounds.x + cellBounds.width || p.y > cellBounds.y + cellBounds.height; } /** * Set the lead and anchor without affecting selection. */ public static void setLeadAnchorWithoutSelection(ListSelectionModel model, int lead, int anchor) { if (anchor == -1) { anchor = lead; } if (lead == -1) { model.setAnchorSelectionIndex(-1); model.setLeadSelectionIndex(-1); } else { if (model.isSelectedIndex(lead)) { model.addSelectionInterval(lead, lead); } else { model.removeSelectionInterval(lead, lead); } model.setAnchorSelectionIndex(anchor); } } /** * Ignore mouse events if the component is null, not enabled, the event * is not associated with the left mouse button, or the event has been * consumed. */ public static boolean shouldIgnore(MouseEvent me, JComponent c) { return c == null || !c.isEnabled() || !SwingUtilities.isLeftMouseButton(me) || me.isConsumed(); } /** * Request focus on the given component if it doesn't already have it * and {@code 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. Object aaHint = (c == null) ? null : c.getClientProperty(KEY_TEXT_ANTIALIASING); if (aaHint != null && (g instanceof Graphics2D)) { Graphics2D g2 = (Graphics2D)g; Object oldContrast = null; Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING); if (aaHint != null && aaHint != oldAAValue) { g2.setRenderingHint(KEY_TEXT_ANTIALIASING, aaHint); } else { oldAAValue = null; } Object lcdContrastHint = c.getClientProperty(KEY_TEXT_LCD_CONTRAST); if (lcdContrastHint != null) { oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST); if (lcdContrastHint.equals(oldContrast)) { oldContrast = null; } else { g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, 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 { Map a = new HashMap(); a.put(TextAttribute.FONT, f); a.put(TextAttribute.NUMERIC_SHAPING, shaper); return new TextLayout(s, a, frc); } } /* * Checks if two given FontRenderContexts are compatible for printing. * We can't just use equals as we want to exclude from the comparison : * + whether AA is set as irrelevant for printing and shouldn't affect * printed metrics anyway * + any translation component in the transform of either FRC, as it * does not affect metrics. * Compatible means no special handling needed for text painting */ private static boolean isFontRenderContextPrintCompatible(FontRenderContext frc1, FontRenderContext frc2) { if (frc1 == frc2) { return true; } if (frc1 == null || frc2 == null) { // not supposed to happen return false; } if (frc1.getFractionalMetricsHint() != frc2.getFractionalMetricsHint()) { return false; } /* If both are identity, return true */ if (!frc1.isTransformed() && !frc2.isTransformed()) { return true; } /* That's the end of the cheap tests, need to get and compare * the transform matrices. We don't care about the translation * components, so return true if they are otherwise identical. */ double[] mat1 = new double[4]; double[] mat2 = new double[4]; frc1.getTransform().getMatrix(mat1); frc2.getTransform().getMatrix(mat2); return mat1[0] == mat2[0] && mat1[1] == mat2[1] && mat1[2] == mat2[2] && mat1[3] == mat2[3]; } /* * Tries it best to get Graphics2D out of the given Graphics * returns null if can not derive it. */ public static Graphics2D getGraphics2D(Graphics g) { if (g instanceof Graphics2D) { return (Graphics2D) g; } else if (g instanceof ProxyPrintGraphics) { return (Graphics2D)(((ProxyPrintGraphics)g).getGraphics()); } else { return null; } } /* * Returns FontRenderContext associated with Component. * FontRenderContext from Component.getFontMetrics is associated * with the component. * * Uses Component.getFontMetrics to get the FontRenderContext from. * see JComponent.getFontMetrics and TextLayoutStrategy.java */ public static FontRenderContext getFontRenderContext(Component c) { assert c != null; if (c == null) { return DEFAULT_FRC; } else { return c.getFontMetrics(c.getFont()).getFontRenderContext(); } } /** * A convenience method to get FontRenderContext. * Returns the FontRenderContext for the passed in FontMetrics or * for the passed in Component if FontMetrics is null */ private static FontRenderContext getFontRenderContext(Component c, FontMetrics fm) { assert fm != null || c!= null; return (fm != null) ? fm.getFontRenderContext() : getFontRenderContext(c); } /* * This method is to be used only for JComponent.getFontMetrics. * In all other places to get FontMetrics we need to use * JComponent.getFontMetrics. * */ public static FontMetrics getFontMetrics(JComponent c, Font font) { FontRenderContext frc = getFRCProperty(c); if (frc == null) { frc = DEFAULT_FRC; } return FontDesignMetrics.getMetrics(font, frc); } /* Get any FontRenderContext associated with a JComponent * - may return null */ private static FontRenderContext getFRCProperty(JComponent c) { if (c != null) { Object aaHint = c.getClientProperty(KEY_TEXT_ANTIALIASING); if (aaHint != null) { return getFRCFromCache(aaHint); } } return null; } private static final Object APP_CONTEXT_FRC_CACHE_KEY = new Object(); private static FontRenderContext getFRCFromCache(Object aaHint) { @SuppressWarnings("unchecked") Map cache = (Map) AppContext.getAppContext().get(APP_CONTEXT_FRC_CACHE_KEY); if (cache == null) { cache = new HashMap<>(); AppContext.getAppContext().put(APP_CONTEXT_FRC_CACHE_KEY, cache); } FontRenderContext frc = cache.get(aaHint); if (frc == null) { frc = new FontRenderContext(null, aaHint, VALUE_FRACTIONALMETRICS_DEFAULT); cache.put(aaHint, frc); } return frc; } /* * returns true if the Graphics is print Graphics * false otherwise */ static boolean isPrinting(Graphics g) { return (g instanceof PrinterGraphics || g instanceof PrintGraphics); } private static String trimTrailingSpaces(String s) { int i = s.length() - 1; while(i >= 0 && Character.isWhitespace(s.charAt(i))) { i--; } return s.substring(0, i + 1); } private static AttributedCharacterIterator getTrimmedTrailingSpacesIterator (AttributedCharacterIterator iterator) { int curIdx = iterator.getIndex(); char c = iterator.last(); while(c != CharacterIterator.DONE && Character.isWhitespace(c)) { c = iterator.previous(); } if (c != CharacterIterator.DONE) { int endIdx = iterator.getIndex(); if (endIdx == iterator.getEndIndex() - 1) { iterator.setIndex(curIdx); return iterator; } else { AttributedString trimmedText = new AttributedString(iterator, iterator.getBeginIndex(), endIdx + 1); return trimmedText.getIterator(); } } else { return null; } } /** * Determines whether the SelectedTextColor should be used for painting text * foreground for the specified highlight. * * Returns true only if the highlight painter for the specified highlight * is the swing painter (whether inner class of javax.swing.text.DefaultHighlighter * or com.sun.java.swing.plaf.windows.WindowsTextUI) and its background color * is null or equals to the selection color of the text component. * * This is a hack for fixing both bugs 4761990 and 5003294 */ public static boolean useSelectedTextColor(Highlighter.Highlight h, JTextComponent c) { Highlighter.HighlightPainter painter = h.getPainter(); String painterClass = painter.getClass().getName(); if (painterClass.indexOf("javax.swing.text.DefaultHighlighter") != 0 && painterClass.indexOf("com.sun.java.swing.plaf.windows.WindowsTextUI") != 0) { return false; } try { DefaultHighlighter.DefaultHighlightPainter defPainter = (DefaultHighlighter.DefaultHighlightPainter) painter; if (defPainter.getColor() != null && !defPainter.getColor().equals(c.getSelectionColor())) { return false; } } catch (ClassCastException e) { return false; } return true; } /** * LSBCacheEntry is used to cache the left side bearing (lsb) for * a particular {@code Font} and {@code FontRenderContext}. * This only caches characters that fall in the range * {@code MIN_CHAR_INDEX} to {@code 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 {@code UIDefaults.LazyValue} that * creates an {@code ImageIcon} {@code UIResource} for the * specified image file name. The image is loaded using * {@code 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 {@code rootClass}. * * @param baseClass the first class to use in searching for the resource * @param rootClass an ancestor of {@code baseClass} to finish the * search at * @param imageFile the name of the file to be found * @return a lazy value that creates the {@code ImageIcon} * {@code 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 {@code UIDefaults.LazyValue} that * creates an {@code ImageIcon} {@code UIResource} for the * specified image file name. The image is loaded using * {@code 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 {@code 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 {@code baseClass} to finish the * search at * @param imageFile the name of the file to be found * @return a lazy value that creates the {@code ImageIcon} * {@code 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( (PrivilegedAction) () -> getIconBytes(baseClass, rootClass, imageFile)) : getIconBytes(baseClass, rootClass, imageFile); if (buffer == null) { return null; } if (buffer.length == 0) { System.err.println("warning: " + imageFile + " is zero-length"); return null; } return new ImageIconUIResource(buffer); }; } private static byte[] getIconBytes(final Class baseClass, final Class rootClass, final String imageFile) { /* Copy resource into a byte array. This is * necessary because several browsers consider * Class.getResource a security risk because it * can be used to load additional classes. * Class.getResourceAsStream just returns raw * bytes, which we can convert to an image. */ Class srchClass = baseClass; while (srchClass != null) { try (InputStream resource = srchClass.getResourceAsStream(imageFile)) { if (resource == null) { if (srchClass == rootClass) { break; } srchClass = srchClass.getSuperclass(); continue; } try (BufferedInputStream in = new BufferedInputStream(resource); ByteArrayOutputStream out = new ByteArrayOutputStream(1024)) { byte[] buffer = new byte[1024]; int n; while ((n = in.read(buffer)) > 0) { out.write(buffer, 0, n); } out.flush(); return out.toByteArray(); } } catch (IOException ioe) { System.err.println(ioe.toString()); } } return null; } /* Used to help decide if AA text rendering should be used, so * this local display test should be additionally qualified * against whether we have XRender support on both ends of the wire, * as with that support remote performance may be good enough to turn * on by default. An additional complication there is XRender does not * appear capable of performing gamma correction needed for LCD text. */ public static boolean isLocalDisplay() { boolean isLocal; GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); if (ge instanceof SunGraphicsEnvironment) { isLocal = ((SunGraphicsEnvironment) ge).isDisplayLocal(); } else { isLocal = true; } return isLocal; } /** * Returns an integer from the defaults table. If {@code key} does * not map to a valid {@code Integer}, or can not be convered from * a {@code String} to an integer, the value 0 is returned. * * @param key an {@code 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 {@code key} does not map to a valid * {@code Integer}, or can not be convered from a {@code String} * to an integer, the value 0 is returned. * * @param key an {@code Object} specifying the int. Returned value * is 0 if {@code key} is not available, * @param l the {@code 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 {@code key} does * not map to a valid {@code Integer}, or can not be convered from * a {@code String} to an integer, {@code default} is * returned. * * @param key an {@code Object} specifying the int. Returned value * is 0 if {@code key} is not available, * @param defaultValue Returned value if {@code 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 {@code key} does not map to a valid * {@code Integer}, or can not be convered from a {@code String} * to an integer, {@code default} is returned. * * @param key an {@code Object} specifying the int. Returned value * is 0 if {@code key} is not available, * @param l the {@code Locale} for which the int is desired * @param defaultValue Returned value if {@code 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 Future submit(Callable task) { if (task == null) { throw new NullPointerException(); } FutureTask future = new FutureTask(task); execute(future); return future; } /** * Submits a Runnable task for execution on the EDT and returns a * Future representing that task. * * @param task the task to submit * @param result the result to return upon successful completion * @return a Future representing pending completion of the task, * and whose {@code get()} method will return the given * result value upon completion * @throws NullPointerException if the task is null */ public static Future submit(Runnable task, V result) { if (task == null) { throw new NullPointerException(); } FutureTask future = new FutureTask(task, result); execute(future); return future; } /** * Sends a Runnable to the EDT for the execution. */ private static void execute(Runnable command) { SwingUtilities.invokeLater(command); } /** * Sets the {@code SKIP_CLICK_COUNT} client property on the component * if it is an instance of {@code JTextComponent} with a * {@code DefaultCaret}. This property, used for text components acting * as editors in a table or tree, tells {@code DefaultCaret} how many * clicks to skip before starting selection. */ public static void setSkipClickCount(Component comp, int count) { if (comp instanceof JTextComponent && ((JTextComponent) comp).getCaret() instanceof DefaultCaret) { ((JTextComponent) comp).putClientProperty(SKIP_CLICK_COUNT, count); } } /** * Return the MouseEvent's click count, possibly reduced by the value of * the component's {@code SKIP_CLICK_COUNT} client property. Clears * the {@code SKIP_CLICK_COUNT} property if the mouse event's click count * is 1. In order for clearing of the property to work correctly, there * must be a mousePressed implementation on the caller with this * call as the first line. */ public static int getAdjustedClickCount(JTextComponent comp, MouseEvent e) { int cc = e.getClickCount(); if (cc == 1) { comp.putClientProperty(SKIP_CLICK_COUNT, null); } else { Integer sub = (Integer) comp.getClientProperty(SKIP_CLICK_COUNT); if (sub != null) { return cc - sub; } } return cc; } /** * Used by the {@code liesIn} method to return which section * the point lies in. * * @see #liesIn */ public enum Section { /** The leading section */ LEADING, /** The middle section */ MIDDLE, /** The trailing section */ TRAILING } /** * This method divides a rectangle into two or three sections along * the specified axis and determines which section the given point * lies in on that axis; used by drag and drop when calculating drop * locations. *

* 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); } public static final TextUIDrawing DEFAULT_UI_TEXT_DRAWING = new BasicTextUIDrawing(); public static TextUIDrawing getTextUIDrawing(JComponent comp) { TextUIDrawing textUIDrawing = (TextUIDrawing) UIManager.get("uiDrawing.text"); return textUIDrawing != null ? textUIDrawing : DEFAULT_UI_TEXT_DRAWING; } public static TextUIDrawing getTextUIDrawing(TextUIDrawing textUIDrawing) { if (textUIDrawing == null || textUIDrawing instanceof UIResource) { textUIDrawing = (TextUIDrawing) UIManager.get("uiDrawing.text"); } if (textUIDrawing == null) { textUIDrawing = DEFAULT_UI_TEXT_DRAWING; } return textUIDrawing; } }