0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: /**
0018: * @author Alexander T. Simbirtsev, Anton Avtamonov
0019: * @version $Revision$
0020: */package org.apache.harmony.x.swing;
0021:
0022: import java.awt.Color;
0023: import java.awt.Component;
0024: import java.awt.Container;
0025: import java.awt.Dialog;
0026: import java.awt.Dimension;
0027: import java.awt.Font;
0028: import java.awt.FontMetrics;
0029: import java.awt.Frame;
0030: import java.awt.Graphics;
0031: import java.awt.GraphicsConfiguration;
0032: import java.awt.GraphicsEnvironment;
0033: import java.awt.HeadlessException;
0034: import java.awt.Insets;
0035: import java.awt.MouseInfo;
0036: import java.awt.Point;
0037: import java.awt.Rectangle;
0038: import java.awt.Toolkit;
0039: import java.awt.Window;
0040: import java.awt.event.InputEvent;
0041: import java.awt.event.KeyEvent;
0042: import java.security.AccessController;
0043: import java.security.PrivilegedAction;
0044: import java.util.ArrayList;
0045:
0046: import javax.swing.Icon;
0047: import javax.swing.InputMap;
0048: import javax.swing.JComponent;
0049: import javax.swing.JMenuBar;
0050: import javax.swing.JPopupMenu;
0051: import javax.swing.KeyStroke;
0052: import javax.swing.LookAndFeel;
0053: import javax.swing.MenuElement;
0054: import javax.swing.SwingConstants;
0055: import javax.swing.SwingUtilities;
0056: import javax.swing.UIManager;
0057: import javax.swing.plaf.UIResource;
0058: import javax.swing.plaf.basic.BasicComboPopup;
0059: import javax.swing.plaf.basic.BasicGraphicsUtils;
0060: import javax.swing.text.Position;
0061:
0062: import org.apache.harmony.x.swing.internal.nls.Messages;
0063:
0064: /**
0065: * SwingUtilities extension. This class provides utility
0066: * methods which are widely used in Swing classes.
0067: *
0068: */
0069: public class Utilities implements SwingConstants {
0070: /**
0071: * This interface allows to access list data
0072: */
0073: public interface ListModelAccessor {
0074: /**
0075: * Returns the list data element according to specified index
0076: *
0077: * @param index of the element
0078: * @return element, specified by index
0079: */
0080: public Object getElementAt(final int index);
0081:
0082: /**
0083: * Returns the size of the list
0084: * @return size of the list
0085: */
0086: public int getSize();
0087: }
0088:
0089: /**
0090: * Returns the index of the next element of the list according to specified
0091: * prefix, start index and bias
0092: *
0093: * @param model of the list
0094: * @param prefix of the list element
0095: * @param startIndex index to start search from
0096: * @param bias
0097: * @return index of the next element
0098: */
0099: public static int getNextMatch(final ListModelAccessor model,
0100: final String prefix, final int startIndex,
0101: final Position.Bias bias) {
0102: if (prefix == null) {
0103: throw new IllegalArgumentException(Messages
0104: .getString("swing.6F")); //$NON-NLS-1$
0105: }
0106:
0107: if (startIndex < 0 || startIndex >= model.getSize()) {
0108: throw new IllegalArgumentException(Messages
0109: .getString("swing.6D")); //$NON-NLS-1$
0110: }
0111:
0112: String ucPrefix = prefix.toUpperCase();
0113: if (Position.Bias.Forward == bias) {
0114: for (int i = startIndex; i < model.getSize(); i++) {
0115: String elementAsString = model.getElementAt(i)
0116: .toString().toUpperCase();
0117: if (elementAsString.startsWith(ucPrefix)) {
0118: return i;
0119: }
0120: }
0121: for (int i = 0; i < startIndex; i++) {
0122: String elementAsString = model.getElementAt(i)
0123: .toString().toUpperCase();
0124: if (elementAsString.startsWith(ucPrefix)) {
0125: return i;
0126: }
0127: }
0128: } else if (Position.Bias.Backward == bias) {
0129: for (int i = startIndex; i >= 0; i--) {
0130: String elementAsString = model.getElementAt(i)
0131: .toString().toUpperCase();
0132: if (elementAsString.startsWith(ucPrefix)) {
0133: return i;
0134: }
0135: }
0136: for (int i = model.getSize() - 1; i > startIndex; i--) {
0137: String elementAsString = model.getElementAt(i)
0138: .toString().toUpperCase();
0139: if (elementAsString.startsWith(ucPrefix)) {
0140: return i;
0141: }
0142: }
0143: }
0144:
0145: return -1;
0146: }
0147:
0148: /**
0149: * Clips string due to the width of an area available for painting the text.
0150: *
0151: * @param fm FontMetrics for the text font
0152: * @param text original (not clipped) text
0153: * @param width width of the area available for painting the text
0154: * @return clipped string (ending with "...") or the original string if it fits the available width
0155: */
0156: public static String clipString(final FontMetrics fm,
0157: final String text, final int width) {
0158: final String dots = "...";
0159: final int stringWidth = fm.stringWidth(text);
0160: if (width >= stringWidth) {
0161: return text;
0162: }
0163:
0164: final int dotsWidth = fm.stringWidth(dots);
0165: if (width <= dotsWidth) {
0166: return dots;
0167: }
0168:
0169: return text.substring(0, getSuitableSubstringLength(fm, text,
0170: width - dotsWidth))
0171: + dots;
0172: }
0173:
0174: /**
0175: * Returns given index if underscored part of the string isn't clipped,
0176: * <code>-1</code> otherwise.
0177: *
0178: * @param original original text
0179: * @param clipped clipped text
0180: * @param underscoreIndex index of the character in <code>text</code> to be
0181: * underscored
0182: * @return underscoreIndex or <code>-1</code> if the position is clipped
0183: */
0184: public static int getClippedUnderscoreIndex(final String original,
0185: final String clipped, final int underscoreIndex) {
0186: return insideString(clipped, underscoreIndex)
0187: && original.charAt(underscoreIndex) == clipped
0188: .charAt(underscoreIndex) ? underscoreIndex : -1;
0189: }
0190:
0191: /**
0192: * Draws part of 3D-like rectangle with given parameters.
0193: *
0194: * @see java.awt.Graphics2D#draw3DRect(int, int, int, int, boolean)
0195: * @param shadow color for dark parts of the rectangle
0196: * @param highlight color for light parts of the rectangle
0197: * @param raised if <code>true</code> left and top sides of rectangle will be drawn light
0198: */
0199: public static void draw3DRect(final Graphics g, final int x,
0200: final int y, final int w, final int h, final Color shadow,
0201: final Color highlight, final boolean raised) {
0202: final Color oldColor = g.getColor();
0203: final int bottom = y + h - 1;
0204: final int right = x + w - 1;
0205: final Color topLeft = raised ? highlight : shadow;
0206: final Color bottomRight = raised ? shadow : highlight;
0207: g.setColor(topLeft);
0208: g.drawLine(x, y, x, bottom);
0209: g.drawLine(x, y, right, y);
0210: g.setColor(bottomRight);
0211: g.drawLine(right, y, right, bottom);
0212: g.drawLine(x, bottom, right, bottom);
0213: g.setColor(oldColor);
0214: }
0215:
0216: /**
0217: * Converts keyCode to mnemonic keyChar if possible. Note there is no 1 to 1 conversion.
0218: * @param keyCode KeyCode to be converted to a char
0219: * @return char or {@link java.awt.event.KeyEvent#VK_UNDEFINED} if conversion could not be done.
0220: */
0221: public static char keyCodeToKeyChar(final int keyCode) {
0222: if (keyCode < 0 || keyCode > 0xFFFF) {
0223: return KeyEvent.VK_UNDEFINED;
0224: }
0225:
0226: return Character.toUpperCase((char) keyCode);
0227: }
0228:
0229: /**
0230: * Converts mnemonic keyChar to keyCode.
0231: *
0232: * @param keyChar char to be converted to keyCode
0233: * @return converted keyCode or the <code>int</code> value of <code>keyChar</code>.
0234: */
0235: public static int keyCharToKeyCode(final char keyChar) {
0236: if (keyChar > 127) {
0237: return keyChar;
0238: }
0239:
0240: return Character.toUpperCase(keyChar);
0241: }
0242:
0243: /**
0244: * Returns optimal location to display popup based on the anchor bounds
0245: * requested, popup size and location of popup related to anchor. The optimal
0246: * location fulfills the condition popup fits on screen, not overlaps with
0247: * anchor and is verticaly or horizontaly positioned relative to anchor. If
0248: * position is vertical the popup left or right bound is aligned with
0249: * anchor bound depending on <code>leftToRight</code>.
0250: *
0251: * @param anchor anchor bounds relative to
0252: * @param size requested popup size
0253: * @param leftToRight horizontal alignment
0254: * @param horizontal placement relative to anchor
0255: * @return optimal popup location
0256: */
0257: public static Point getPopupLocation(final Rectangle anchor,
0258: final Dimension size, final boolean leftToRight,
0259: final boolean horizontal, final GraphicsConfiguration gc) {
0260: Point result = getPopupLocation(anchor, size, leftToRight,
0261: horizontal);
0262: return adjustPopupLocation(result, size, anchor, horizontal, gc);
0263: }
0264:
0265: /**
0266: * Gets popup location basing on the invoker position.
0267: *
0268: * @param anchor Rectangle which specified the bounds of the popup invoker
0269: * @param size Dimension of the popup size
0270: * @param leftToRight boolean value representing ComponentOrientation.isLeftToRight() state
0271: * @param horizontal boolean value representing ComponentOrientation.isHorizontal() state
0272: * @return Popup location
0273: */
0274: public static Point getPopupLocation(final Rectangle anchor,
0275: final Dimension size, final boolean leftToRight,
0276: final boolean horizontal) {
0277: Point result = anchor.getLocation();
0278: if (horizontal) {
0279: if (leftToRight) {
0280: result.x += anchor.width;
0281: } else {
0282: result.x -= size.width;
0283: }
0284: } else {
0285: result.y += anchor.height;
0286: if (!leftToRight) {
0287: result.x += anchor.width - size.width;
0288: }
0289: }
0290:
0291: return result;
0292: }
0293:
0294: /**
0295: * Determizes the 'optimal' popup location to provide the entire popup is shown.
0296: *
0297: * @param location Point of the proposed location
0298: * @param size Dimension of the popup size
0299: * @param anchor Rectangle which specified the bounds of the popup invoker
0300: * @param horizontal boolean value representing ComponentOrientation.isHorizontal() state
0301: * @param gc GraphicsConfiguration of the parent Window
0302: *
0303: * @return Optimal popup location
0304: */
0305: public static Point adjustPopupLocation(final Point location,
0306: final Dimension size, final Rectangle anchor,
0307: final boolean horizontal, final GraphicsConfiguration gc) {
0308: final Point result = location;
0309: final Rectangle screenBounds = getScreenClientBounds(gc);
0310: if (screenBounds.contains(new Rectangle(result, size))) {
0311: return result;
0312: }
0313: if (horizontal) {
0314: if (screenBounds.width < result.x + size.width) {
0315: result.x = anchor.x - size.width;
0316: }
0317: if (screenBounds.x > result.x) {
0318: result.x = anchor.x + anchor.width;
0319: }
0320: if (screenBounds.height < result.y + size.height) {
0321: result.y = anchor.y + anchor.height - size.height;
0322: }
0323: if (screenBounds.y > result.y + size.height) {
0324: result.y = screenBounds.y + 20;
0325: }
0326: } else {
0327: if (screenBounds.height < result.y + size.height) {
0328: result.y = anchor.y - size.height;
0329: }
0330: if (screenBounds.width < result.x + size.width) {
0331: result.x = screenBounds.width - size.width - 20;
0332: }
0333: if (screenBounds.x > result.x) {
0334: result.x = screenBounds.x + 20;
0335: }
0336: if (screenBounds.y > result.y + size.height) {
0337: result.y = screenBounds.y + 20;
0338: }
0339: }
0340:
0341: return result;
0342: }
0343:
0344: /**
0345: * If the window is <code>Frame</code> or <code>Dialog</code>, the function
0346: * returns <code>true</code> the window is resizable. Otherwise
0347: * <code>false</code> is returned. This function can be used
0348: * when implementing L&F for <code>JRootPane</code>.
0349: *
0350: * @param window the window to determine if it is resizable
0351: *
0352: * @return <code>true</code> if the window is <code>Frame</code> or
0353: * <code>Dialog</code> and is resizable, otherwise
0354: * <code>false</code> is returned
0355: */
0356: public static final boolean isResizableWindow(final Window window) {
0357: if (window instanceof Frame) {
0358: return ((Frame) window).isResizable();
0359: } else if (window instanceof Dialog) {
0360: return ((Dialog) window).isResizable();
0361: }
0362: return false;
0363: }
0364:
0365: /**
0366: * Returns <code>true</code> if the parameter is a maximized frame.
0367: * This function can be used when implementing L&F for
0368: * <code>JRootPane</code>.
0369: *
0370: * @param window the window to determine if it is maximized
0371: *
0372: * @return <code>true</code> if the window is <code>Frame</code>
0373: * and it is maximized
0374: */
0375: public static final boolean isMaximumFrame(final Window window) {
0376: return window instanceof Frame
0377: && (((Frame) window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0;
0378: }
0379:
0380: /**
0381: * This function implements evaluation of maximum/minimum/preferred sizes
0382: * of <code>JRootPane</code>. Depending on the size to be evaluated,
0383: * the parameters are maximum/minimum/preferred sizes of subcomponents.
0384: *
0385: * @param contentPaneSize the maximum/minimum/preferred size of
0386: * <code>contentPane</code>
0387: * @param menuBarSize the maximum/minimum/preferred size of
0388: * <code>menuBar</code>; may be <code>null</code>
0389: * @param titlePaneSize the maximum/minimum/preferred size of
0390: * <code>titlePane</code>; may be <code>null</code>
0391: * @param insets the insets of <code>JRootPane</code>
0392: *
0393: * @return the maximum/minimum/preferred size of <code>JRootPane</code>
0394: */
0395: public static final Dimension getRootPaneLayoutSize(
0396: final Dimension contentPaneSize,
0397: final Dimension menuBarSize, final Dimension titlePaneSize,
0398: final Insets insets) {
0399:
0400: Dimension result = new Dimension(contentPaneSize);
0401:
0402: if (menuBarSize != null) {
0403: result.height += menuBarSize.height;
0404: result.width = Math.max(result.width, menuBarSize.width);
0405: }
0406:
0407: if (titlePaneSize != null) {
0408: result.height += titlePaneSize.height;
0409: result.width = Math.max(result.width, titlePaneSize.width);
0410: }
0411:
0412: return addInsets(result, insets);
0413: }
0414:
0415: /**
0416: * Removes colors and font installed by <code>LookAndFeel</code>.
0417: *
0418: * @see javax.swing.LookAndFeel#installColorsAndFont(JComponent, String, String, String)
0419: * @param comp Component for which UI colors and font should be uninstalled
0420: */
0421: public static final void uninstallColorsAndFont(
0422: final JComponent comp) {
0423: if (comp.getBackground() instanceof UIResource) {
0424: comp.setBackground(null);
0425: }
0426: if (comp.getForeground() instanceof UIResource) {
0427: comp.setForeground(null);
0428: }
0429: if (comp.getFont() instanceof UIResource) {
0430: comp.setFont(null);
0431: }
0432: }
0433:
0434: /**
0435: * Enlarges <code>size</code> by insets size. <code>size</code> fields
0436: * are changed.
0437: *
0438: * @param size initial dimension
0439: * @param insets insets to add
0440: * @return Enlarged dimension
0441: */
0442: public static final Dimension addInsets(final Dimension size,
0443: final Insets insets) {
0444: size.setSize(size.width + insets.left + insets.right,
0445: size.height + insets.top + insets.bottom);
0446: return size;
0447: }
0448:
0449: /**
0450: * Reduces width and height of <code>rect</code> by insets
0451: * and moves its origin. The fields of <code>insets</code>
0452: * are not changed.
0453: *
0454: * @param rect initial rectangle, its fields will be modified
0455: * @param insets insets to subract
0456: * @return Area inside the insets
0457: */
0458: public static final Rectangle subtractInsets(final Rectangle rect,
0459: final Insets insets) {
0460: if (insets == null) {
0461: return rect;
0462: }
0463: rect.setBounds(rect.x + insets.left, rect.y + insets.top,
0464: rect.width - insets.left - insets.right, rect.height
0465: - insets.top - insets.bottom);
0466: return rect;
0467: }
0468:
0469: /**
0470: * Adds values from second argument to the first one and returns first argument.
0471: *
0472: * @param recipient Insets to be enlarged
0473: * @param addition Instes to be added to the recipient
0474: *
0475: * @return Recipient
0476: */
0477: public static final Insets addInsets(final Insets recipient,
0478: final Insets addition) {
0479: if (addition == null) {
0480: return recipient;
0481: }
0482: recipient.set(recipient.top + addition.top, recipient.left
0483: + addition.left, recipient.bottom + addition.bottom,
0484: recipient.right + addition.right);
0485:
0486: return recipient;
0487: }
0488:
0489: /**
0490: * Returns string size due to the given font metrics.
0491: *
0492: * @param str String which size should be calculated
0493: * @param fm FontMetrics of the measuring String
0494: * @return String size
0495: */
0496: public static Dimension getStringSize(final String str,
0497: final FontMetrics fm) {
0498: return !isEmptyString(str) ? new Dimension(fm.stringWidth(str),
0499: fm.getHeight()) : new Dimension();
0500: }
0501:
0502: /**
0503: * Draws string with given font and color.
0504: *
0505: * @param g Graphics to draw on
0506: * @param str String to draw
0507: * @param x int representing text x-coordinate
0508: * @param y int representing text y-coordinate
0509: * @param fm FontMetrics of the text to draw
0510: * @param c Color to draw with
0511: * @param underscoreIndex int value representing underscore index to be underlined or -1 if
0512: * no underlining is required
0513: */
0514: public static void drawString(final Graphics g, final String str,
0515: final int x, final int y, final FontMetrics fm,
0516: final Color c, final int underscoreIndex) {
0517: if (isEmptyString(str)) {
0518: return;
0519: }
0520: final Color oldColor = g.getColor();
0521: final Font oldFont = g.getFont();
0522: g.setColor(c);
0523: g.setFont(fm.getFont());
0524: BasicGraphicsUtils.drawStringUnderlineCharAt(g, str,
0525: underscoreIndex, x, y);
0526: g.setColor(oldColor);
0527: g.setFont(oldFont);
0528: }
0529:
0530: /**
0531: * Calculates the baseline for text being rendered on compound label
0532: * based on the FontMetrics and bounding rectangle <code>y</code> coordinate.
0533: *
0534: * @param fm FontMetrics of the text
0535: * @param textR Rectangle representing text bounds
0536: *
0537: * @return Y-coordinate to draw text from
0538: */
0539: public static int getTextY(final FontMetrics fm,
0540: final Rectangle textR) {
0541: return textR.y + fm.getAscent();
0542: }
0543:
0544: /**
0545: * Calculates size for the label that consists of a text message and an icon.
0546: *
0547: * @see javax.swing.SwingUtilities#layoutCompoundLabel(FontMetrics, String, Icon, int, int, int, int, Rectangle, Rectangle, Rectangle, int)
0548: * @return Preferred size
0549: */
0550: public static Dimension getCompoundLabelSize(final JComponent c,
0551: final String text, final Icon icon,
0552: final int verticalTextPosition,
0553: final int horizontalTextPosition, final int iconTextGap) {
0554:
0555: final Dimension result = new Dimension();
0556: final FontMetrics fm = getFontMetrics(c);
0557: if (fm == null) {
0558: return result;
0559: }
0560: Rectangle viewR = new Rectangle(Short.MAX_VALUE,
0561: Short.MAX_VALUE);
0562: Rectangle iconR = new Rectangle();
0563: Rectangle textR = new Rectangle();
0564:
0565: SwingUtilities.layoutCompoundLabel(fm, text, icon, TOP, LEFT,
0566: verticalTextPosition, horizontalTextPosition, viewR,
0567: iconR, textR, iconTextGap);
0568: result.width = Math.max(iconR.x + iconR.width, textR.x
0569: + textR.width);
0570: result.height = Math.max(iconR.y + iconR.height, textR.y
0571: + textR.height);
0572:
0573: return result;
0574: }
0575:
0576: /**
0577: * Checks whether a string is empty, null is considered to be empty string.
0578: *
0579: * @param str string to check
0580: * @return <code>true</code>, if <code>str</code> is <code>null</code>
0581: * or is empty.
0582: */
0583: public static boolean isEmptyString(final String str) {
0584: return str == null || str.length() == 0;
0585: }
0586:
0587: /**
0588: * Gets component FontMetrics.
0589: *
0590: * @param c Component which FontMetrics requested
0591: * @return Component FontMetrics correspondent to current components font
0592: */
0593: public static FontMetrics getFontMetrics(final JComponent c) {
0594: final Font font = c.getFont();
0595: return font != null ? c.getFontMetrics(font) : null;
0596: }
0597:
0598: /**
0599: * Checks whether an array is empty, null is considered to be empty array.
0600: *
0601: * @param arr array to check
0602: * @return <code>true</code>, if <code>array</code> is <code>null</code>
0603: * or contains 0 elements.
0604: */
0605: public static boolean isEmptyArray(final Object[] arr) {
0606: return arr == null || arr.length == 0;
0607: }
0608:
0609: /**
0610: * Checks whether an array is empty, null is considered to be empty array.
0611: *
0612: * @param arr array to check
0613: * @return <code>true</code>, if <code>array</code> is <code>null</code>
0614: * or contains 0 elements.
0615: */
0616: public static boolean isEmptyArray(final int[] arr) {
0617: return arr == null || arr.length == 0;
0618: }
0619:
0620: /**
0621: * Aligns rectangle with the inner bounds of the box taking into account
0622: * alignments given.
0623: *
0624: * @param rect rectangle to align
0625: * @param box box to align with
0626: * @param horizontalAlign one of LEFT/RIGHT/CENTER
0627: * @param verticalAlign one of TOP/BOTTOM/CENTER
0628: */
0629: public static void alignRect(final Rectangle rect,
0630: final Rectangle box, final int horizontalAlign,
0631: final int verticalAlign) {
0632: rect.y = verticallyAlignRect(rect.height, box, verticalAlign);
0633: rect.x = horizontallyAlignRect(rect.width, box, horizontalAlign);
0634: }
0635:
0636: /**
0637: * Installs UI input map to the given component
0638: * also installs RTL input map if available and needed.
0639: *
0640: * @param c component input map should be installed to
0641: * @param condition condition for the input map
0642: * @param inputMapKey key of the input map record in the defaults table.
0643: * @param rtlInputMapKey key of the right-to-left input map record in the defaults table.
0644: */
0645: public static void installKeyboardActions(final JComponent c,
0646: final int condition, final String inputMapKey,
0647: final String rtlInputMapKey) {
0648: InputMap uiInputMap = (InputMap) UIManager.get(inputMapKey);
0649: if (rtlInputMapKey != null
0650: && !c.getComponentOrientation().isLeftToRight()) {
0651: final InputMap rtlInputMap = (InputMap) UIManager
0652: .get(rtlInputMapKey);
0653: if (rtlInputMap != null) {
0654: rtlInputMap.setParent(uiInputMap);
0655: uiInputMap = rtlInputMap;
0656: }
0657: }
0658: SwingUtilities.replaceUIInputMap(c, condition, uiInputMap);
0659: }
0660:
0661: /**
0662: * Uninstalls UI InputMap from the component.
0663: *
0664: * @param c component from which UI input map to be removed
0665: * @param condition condition for which input map should be removed.
0666: */
0667: public static void uninstallKeyboardActions(final JComponent c,
0668: final int condition) {
0669: SwingUtilities.replaceUIInputMap(c, condition, null);
0670: }
0671:
0672: /**
0673: * Checks if object is installed by UI or <code>null</code>.
0674: * @param obj Object to be checked if it is an instance of UIResource or not.
0675: *
0676: * @return true if the obj instance of UIResource or null, false otherwise
0677: */
0678: public static boolean isUIResource(final Object obj) {
0679: return (obj == null) || (obj instanceof UIResource);
0680: }
0681:
0682: /**
0683: * Draws triangle-like arrow (for scrollbars).
0684: *
0685: * @param g Grpahics to draw on
0686: * @param x x-coordinate of the bounding rectangle
0687: * @param y y-coordinate of the bounding rectangle
0688: * @param direction of the NORTH, SOUTH, WEST, EAST
0689: * @param width int representing size of the bounding rectangle
0690: * @param wide boolean determining if the arrow basis should be wide
0691: * @param color Color to draw the arrow with
0692: */
0693: public static void paintArrow(final Graphics g, final int x,
0694: final int y, final int direction, final int width,
0695: final boolean wide, final Color color) {
0696: paintArrow(g, x, y, direction, width, wide, color, false);
0697: }
0698:
0699: /**
0700: * Draws filled triangle-like arrow (for scrollbars).
0701: *
0702: * @param g Grpahics to draw on
0703: * @param x x-coordinate of the bounding rectangle
0704: * @param y y-coordinate of the bounding rectangle
0705: * @param direction of the NORTH, SOUTH, WEST, EAST
0706: * @param width int representing size of the bounding rectangle
0707: * @param wide boolean determining if the arrow basis should be wide
0708: * @param color Color to fill the arrow with
0709: */
0710: public static void fillArrow(final Graphics g, final int x,
0711: final int y, final int direction, final int width,
0712: final boolean wide, final Color color) {
0713: paintArrow(g, x, y, direction, width, wide, color, true);
0714: }
0715:
0716: /**
0717: * Returns mouse pointer location in the screen coordinates.
0718: * If <code>GraphicsEnvironment.isHeadless()</code> returns
0719: * <code>true</code> or there is no mouse, <code>null</code> is returned.
0720: * This function should be used to determine mouse displacement
0721: * when <code>JFrame</code> is dragged or resized with mouse.
0722: *
0723: * @return Point of the current mouse pointer location in the screen coordinates
0724: * @see java.awt.MouseInfo#getPointerInfo()
0725: */
0726: public static Point getMousePointerScreenLocation() {
0727: Point p = null;
0728: try {
0729: p = AccessController
0730: .doPrivileged(new PrivilegedAction<Point>() {
0731: public Point run() {
0732: return MouseInfo.getPointerInfo()
0733: .getLocation();
0734: }
0735: });
0736: } catch (final HeadlessException ex) {
0737: }
0738:
0739: return p;
0740: }
0741:
0742: /**
0743: * Returns parent of <code>c</code> if <code>c</code> is not <code>Window</code> decsendant,
0744: * <code>null</code> otherwise.
0745: *
0746: * @param c Component which parent is requested
0747: * @return Container where c is located or null if specified component is Window
0748: */
0749: public static Container getNotWindowParent(final Component c) {
0750: return !(c instanceof Window) ? c.getParent() : null;
0751: }
0752:
0753: /**
0754: * Converts LEADING/TRAILING to LEFT/RIGHT regarding to component orientation.
0755: * Passes by any other values.
0756: *
0757: * @param value to convert
0758: * @param c component where value is to be applied
0759: * @return converted value
0760: */
0761: public static int convertLeadTrail(final int value,
0762: final Component c) {
0763: final boolean isLTR = (c == null)
0764: || c.getComponentOrientation().isLeftToRight();
0765: if (value == LEADING) {
0766: return isLTR ? LEFT : RIGHT;
0767: }
0768: if (value == TRAILING) {
0769: return isLTR ? RIGHT : LEFT;
0770: }
0771: return value;
0772: }
0773:
0774: /**
0775: * Generates accelarator displayed text.
0776: *
0777: * @param acc KeyStroke of the accelerator
0778: * @param delimiter String representation the delimiter of the key in a key sequence
0779: * @return Accelerator displayed text
0780: */
0781: public static String getAcceleratorText(final KeyStroke acc,
0782: final String delimiter) {
0783: if (acc == null) {
0784: return null;
0785: }
0786:
0787: String text = InputEvent.getModifiersExText(acc.getModifiers());
0788: if (!delimiter.equals("+")) {
0789: text = text.replaceAll("[+]", delimiter);
0790: }
0791: if (text.length() > 0) {
0792: text += delimiter;
0793: }
0794: text += acc.getKeyCode() != KeyEvent.VK_UNDEFINED ? KeyEvent
0795: .getKeyText(acc.getKeyCode()) : "" + acc.getKeyChar();
0796: return text;
0797: }
0798:
0799: /**
0800: * Checks if given key is appropriate constant for vertical alignment, throws IAE otherwise.
0801: *
0802: * @param key value to check
0803: * @param exceptionText text of exception message
0804: * @return given key
0805: * @throws IllegalArgumentException with the specified exceptionText if the key is not valid
0806: */
0807: public static int checkVerticalKey(final int key,
0808: final String exceptionText) {
0809: switch (key) {
0810: case TOP:
0811: case CENTER:
0812: case BOTTOM:
0813: return key;
0814: default:
0815: throw new IllegalArgumentException(exceptionText);
0816: }
0817: }
0818:
0819: /**
0820: * Checks if given key is appropriate constant for horizontal alignment, throws IAE otherwise.
0821: *
0822: * @param key value to check
0823: * @param exceptionText text of exception message
0824: * @return given key
0825: * @throws IllegalArgumentException with the specified exceptionText if the key is not valid
0826: */
0827: public static int checkHorizontalKey(final int key,
0828: final String exceptionText) {
0829: switch (key) {
0830: case RIGHT:
0831: case LEFT:
0832: case CENTER:
0833: case LEADING:
0834: case TRAILING:
0835: return key;
0836: default:
0837: throw new IllegalArgumentException(exceptionText);
0838: }
0839: }
0840:
0841: /**
0842: * Calculates mnemonic index in the text for the given keyChar.
0843: *
0844: * @param text text in which mnemonic index should be found
0845: * @param mnemonicChar mnemonic char
0846: * @return index in the text or -1 if there is no index for the specified mnemonic
0847: */
0848: public static int getDisplayedMnemonicIndex(final String text,
0849: final char mnemonicChar) {
0850: return text != null ? text.toUpperCase().indexOf(
0851: Character.toUpperCase(mnemonicChar)) : -1;
0852: }
0853:
0854: /**
0855: * Returns components that implements MenuElement interface.
0856: *
0857: * @param menu container among whose components MenuElements will be looked for
0858: * @return MenuElements array
0859: */
0860: public static MenuElement[] getSubElements(final Container menu) {
0861: final Component[] components = menu.getComponents();
0862: final ArrayList<MenuElement> result = new ArrayList<MenuElement>(
0863: components.length);
0864: for (int i = 0; i < components.length; i++) {
0865: if (components[i] instanceof MenuElement) {
0866: result.add((MenuElement) components[i]);
0867: }
0868: }
0869:
0870: return result.toArray(new MenuElement[result.size()]);
0871: }
0872:
0873: /**
0874: * Returns if <code>element</code> is the subElement of <code>container</code>.
0875: *
0876: * @param container menu to look for sub element at
0877: * @param element element to check
0878: * @return <code>true</code> if <code>element</code> is the subElement of <code>container</code>,
0879: * <code>false</code> otherwise
0880: */
0881: public static boolean isMenuSubElement(final MenuElement container,
0882: final MenuElement element) {
0883: final MenuElement[] subElements = container.getSubElements();
0884: for (int i = 0; i < subElements.length; i++) {
0885: if (subElements[i] == element) {
0886: return true;
0887: }
0888: }
0889: return false;
0890: }
0891:
0892: /**
0893: * Returns menu path of given menuElement up to the <code>JMenuBar</code>.
0894: *
0895: * @param element MenuElement which path is requested
0896: * @return MenuElement[] representing menu path for the specified element
0897: */
0898: public static MenuElement[] getMenuElementPath(
0899: final MenuElement element) {
0900: if (!(element instanceof Container)) {
0901: return new MenuElement[0];
0902: }
0903: final ArrayList<MenuElement> hierarchy = new ArrayList<MenuElement>();
0904: Container c = (Container) element;
0905: do {
0906: hierarchy.add(0, (MenuElement) c);
0907: if (c instanceof JMenuBar) {
0908: break;
0909: }
0910: if (c instanceof JPopupMenu) {
0911: c = (Container) ((JPopupMenu) c).getInvoker();
0912: } else {
0913: c = c.getParent();
0914: }
0915: } while (c != null && c instanceof MenuElement);
0916:
0917: return hierarchy.toArray(new MenuElement[hierarchy.size()]);
0918: }
0919:
0920: /**
0921: * Adds item to the corresponding place in the menu selection path.
0922: * Trims path if necessary.
0923: *
0924: * @param path old path
0925: * @param item element being added
0926: * @return new path including new element
0927: */
0928: public static MenuElement[] addToPath(final MenuElement[] path,
0929: final MenuElement item) {
0930: int commonPathLength;
0931: for (commonPathLength = 0; commonPathLength < path.length; commonPathLength++) {
0932: if (Utilities
0933: .isMenuSubElement(path[commonPathLength], item)) {
0934: commonPathLength++;
0935: break;
0936: }
0937: }
0938: final MenuElement[] result = new MenuElement[commonPathLength + 1];
0939: System.arraycopy(path, 0, result, 0, commonPathLength);
0940: result[result.length - 1] = item;
0941: return result;
0942: }
0943:
0944: /**
0945: * Gets first visible and enabled item from the list of MenuElements.
0946: *
0947: * @param children the list of elements to select from
0948: * @return visible and enabled element if any, null otherwise
0949: */
0950: public static MenuElement getFirstSelectableItem(
0951: final MenuElement[] children) {
0952: if (isEmptyArray(children)) {
0953: return null;
0954: }
0955:
0956: for (int i = 0; i < children.length; i++) {
0957: Component component = children[i].getComponent();
0958: if (component == null && children[i] instanceof Component) {
0959: component = (Component) children[i];
0960: }
0961: if (component != null && component.isVisible()
0962: && component.isEnabled()) {
0963: return children[i];
0964: }
0965: }
0966:
0967: return null;
0968: }
0969:
0970: /**
0971: * Checks if the given item is the valid menu path root.
0972: *
0973: * @param item MenuItem to be checked
0974: * @return true if the specified item is valid menu path root, false otherwise
0975: */
0976: public static boolean isValidFirstPathElement(final MenuElement item) {
0977: return (item instanceof JMenuBar)
0978: || ((item instanceof JPopupMenu) && !(item instanceof BasicComboPopup));
0979: }
0980:
0981: /**
0982: * Removes element from menu selection path.
0983: * Trims path if necessary.
0984: *
0985: * @param path old path
0986: * @param item element being removed
0987: * @return updated path
0988: */
0989: public static MenuElement[] removeFromPath(
0990: final MenuElement[] path, final MenuElement item) {
0991: if (isEmptyArray(path)) {
0992: return new MenuElement[0];
0993: }
0994: int lastSurvivor = path.length - 1;
0995: for (int i = path.length - 1; i >= 0; i--) {
0996: if (path[i] == item) {
0997: lastSurvivor = i - 1;
0998: break;
0999: }
1000: }
1001: final MenuElement[] result = new MenuElement[lastSurvivor + 1];
1002: System.arraycopy(path, 0, result, 0, result.length);
1003: return result;
1004: }
1005:
1006: /**
1007: * Returns value that lies between given bounds.
1008: *
1009: * @param x given value
1010: * @param min bottom bound for x
1011: * @param max top bound for x
1012: * @return min if x less than min, max if x larger than max, x otherwise
1013: */
1014: public static int range(final int x, final int min, final int max) {
1015: return Math.max(min, Math.min(x, max));
1016: }
1017:
1018: /**
1019: * Returns sum of two integers. This function prevents overflow, and
1020: * if the result were greater than Integer.MAX_VALUE, the maximum
1021: * integer would be returned.
1022: * <p><strong>Note:</strong> this does not prevent underflow.
1023: *
1024: * @param item1 the first number
1025: * @param item2 the second number
1026: * @return the sum
1027: */
1028: public static int safeIntSum(final int item1, final int item2) {
1029: if (item2 > 0) {
1030: return (item1 > Integer.MAX_VALUE - item2) ? Integer.MAX_VALUE
1031: : item1 + item2;
1032: }
1033: // TODO Handle negative values correctly: MIN_VALUE - 1 == MIN_VALUE
1034: return item1 + item2;
1035: }
1036:
1037: /**
1038: * Checks if <code>underscoreIndex</code> is inside <code>clipped</code>.
1039: * @param clipped String which may be clipped (partially replaced with "...")
1040: * @param underscoreIndex the index to check
1041: * @return <code>true</code> if <code>underscoreIndex</code> is inside
1042: * <code>clipped</code>.
1043: *
1044: * @see Utilities#clipString(FontMetrics, String, int)
1045: */
1046: public static boolean insideString(final String clipped,
1047: final int underscoreIndex) {
1048: return (-1 < underscoreIndex && underscoreIndex < clipped
1049: .length());
1050: }
1051:
1052: /**
1053: * Checks if the currently installed Look and feel supports window
1054: * decorations.
1055: *
1056: * @return <code>true</code> if the currently installed Look and feel
1057: * is not <code>null</code> and supports window decorations.
1058: */
1059: public static boolean lookAndFeelSupportsWindowDecorations() {
1060: LookAndFeel lnf = UIManager.getLookAndFeel();
1061: return lnf != null && lnf.getSupportsWindowDecorations();
1062: }
1063:
1064: /**
1065: * Calculates the Container from which paint process should be started. The result
1066: * can differ from the specified component in case component hierarchy contains
1067: * JComponents with non-optimized drawing (#see javax.swing.JComponent.isOptimizedDrawingEnabled())
1068: * and specified component is not on the top child hierarchy. Durign the calculation
1069: * paintRect should be considered to decide if its overlapped with the potentially
1070: * overlapping hierarchy or not. Is not curretnly used due to performance reasons.
1071: *
1072: * @param c JComponent to be painted
1073: * @param paintRect region of JComponent to painted. Currently is not used due to
1074: * the performance reasons
1075: *
1076: * @return Container from which actual painting to be started
1077: */
1078: public static Container getDrawingRoot(final JComponent c,
1079: final Rectangle paintRect) {
1080: Container parent = c.getParent();
1081: Component child = c;
1082: Container result = c;
1083: while (parent instanceof JComponent) {
1084: if (!((JComponent) parent).isOptimizedDrawingEnabled()
1085: && ((JComponent) parent).getComponentZOrder(child) != 0) {
1086:
1087: result = parent;
1088: }
1089:
1090: child = parent;
1091: parent = parent.getParent();
1092: }
1093:
1094: return result;
1095: }
1096:
1097: private static int getSuitableSubstringLength(final FontMetrics fm,
1098: final String text, final int width) {
1099: final int textLength = text.length();
1100: for (int i = 1; i < textLength; i++) {
1101: final int substrWidth = fm
1102: .stringWidth(text.substring(0, i));
1103: if (substrWidth > width) {
1104: return i - 1;
1105: }
1106: }
1107:
1108: return textLength;
1109: }
1110:
1111: private static Rectangle getScreenClientBounds(
1112: final GraphicsConfiguration graphConfig) {
1113: GraphicsConfiguration gc = graphConfig;
1114: if (gc == null) {
1115: gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
1116: .getDefaultScreenDevice().getDefaultConfiguration();
1117: }
1118: final Rectangle screenRect = gc.getBounds();
1119: final Insets screenInsets = Toolkit.getDefaultToolkit()
1120: .getScreenInsets(gc);
1121: return subtractInsets(screenRect, screenInsets);
1122: }
1123:
1124: private static void paintArrow(final Graphics g, final int x,
1125: final int y, final int direction, final int size,
1126: final boolean wide, final Color color, final boolean fill) {
1127:
1128: final int halfHeight = (size + 1) / 2;
1129: final int height = halfHeight * 2;
1130: final int width = wide ? halfHeight : height;
1131:
1132: final int[] heights = new int[] { 0, halfHeight - 1, height - 2 };
1133: final int[] lWidths = new int[] { width - 1, 0, width - 1 };
1134: final int[] rWidths = new int[] { 0, width - 1, 0 };
1135: int[] px = null;
1136: int[] py = null;
1137: switch (direction) {
1138: case NORTH:
1139: px = heights;
1140: py = lWidths;
1141: break;
1142: case SOUTH:
1143: px = heights;
1144: py = rWidths;
1145: break;
1146: case WEST:
1147: case LEFT:
1148: px = lWidths;
1149: py = heights;
1150: break;
1151: case EAST:
1152: case RIGHT:
1153: px = rWidths;
1154: py = heights;
1155: break;
1156: default:
1157: assert false : "incorrect direction";
1158: return;
1159: }
1160:
1161: final Color oldColor = g.getColor();
1162: g.setColor(color);
1163: g.translate(x, y);
1164: g.drawPolygon(px, py, 3);
1165: if (fill) {
1166: g.fillPolygon(px, py, 3);
1167: }
1168: g.translate(-x, -y);
1169: g.setColor(oldColor);
1170: }
1171:
1172: private static int horizontallyAlignRect(final int rectWidth,
1173: final Rectangle box, final int horizontalAlign) {
1174: int result = 0;
1175: if (horizontalAlign != LEFT) {
1176: result = box.width - rectWidth;
1177: if (horizontalAlign == CENTER) {
1178: result /= 2;
1179: }
1180: }
1181: result += box.x;
1182: return result;
1183: }
1184:
1185: private static int verticallyAlignRect(final int rectHeight,
1186: final Rectangle box, final int verticalAlign) {
1187: int result = 0;
1188: if (verticalAlign != TOP) {
1189: result = box.height - rectHeight;
1190: if (verticalAlign == CENTER) {
1191: result /= 2;
1192: }
1193: }
1194: result += box.y;
1195: return result;
1196: }
1197: }
|