/* * Copyright (c) 2002, 2008, 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 javax.swing.plaf.synth; import sun.swing.SwingUtilities2; import sun.swing.MenuItemLayoutHelper; import java.awt.*; import javax.swing.*; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.*; import sun.swing.plaf.synth.*; /** * Wrapper for primitive graphics calls. * * @since 1.5 * @author Scott Violet */ public class SynthGraphicsUtils { // These are used in the text painting code to avoid allocating a bunch of // garbage. private Rectangle paintIconR = new Rectangle(); private Rectangle paintTextR = new Rectangle(); private Rectangle paintViewR = new Rectangle(); private Insets paintInsets = new Insets(0, 0, 0, 0); // These Rectangles/Insets are used in the text size calculation to avoid a // a bunch of garbage. private Rectangle iconR = new Rectangle(); private Rectangle textR = new Rectangle(); private Rectangle viewR = new Rectangle(); private Insets viewSizingInsets = new Insets(0, 0, 0, 0); /** * Creates a <code>SynthGraphicsUtils</code>. */ public SynthGraphicsUtils() { } /** * Draws a line between the two end points. * * @param context Identifies hosting region. * @param paintKey Identifies the portion of the component being asked * to paint, may be null. * @param g Graphics object to paint to * @param x1 x origin * @param y1 y origin * @param x2 x destination * @param y2 y destination */ public void drawLine(SynthContext context, Object paintKey, Graphics g, int x1, int y1, int x2, int y2) { g.drawLine(x1, y1, x2, y2); } /** * Draws a line between the two end points. * <p>This implementation supports only one line style key, * <code>"dashed"</code>. The <code>"dashed"</code> line style is applied * only to vertical and horizontal lines. * <p>Specifying <code>null</code> or any key different from * <code>"dashed"</code> will draw solid lines. * * @param context identifies hosting region * @param paintKey identifies the portion of the component being asked * to paint, may be null * @param g Graphics object to paint to * @param x1 x origin * @param y1 y origin * @param x2 x destination * @param y2 y destination * @param styleKey identifies the requested style of the line (e.g. "dashed") * @since 1.6 */ public void drawLine(SynthContext context, Object paintKey, Graphics g, int x1, int y1, int x2, int y2, Object styleKey) { if ("dashed".equals(styleKey)) { // draw vertical line if (x1 == x2) { y1 += (y1 % 2); for (int y = y1; y <= y2; y+=2) { g.drawLine(x1, y, x2, y); } // draw horizontal line } else if (y1 == y2) { x1 += (x1 % 2); for (int x = x1; x <= x2; x+=2) { g.drawLine(x, y1, x, y2); } // oblique lines are not supported } } else { drawLine(context, paintKey, g, x1, y1, x2, y2); } } /** * Lays out text and an icon returning, by reference, the location to * place the icon and text. * * @param ss SynthContext * @param fm FontMetrics for the Font to use, this may be ignored * @param text Text to layout * @param icon Icon to layout * @param hAlign horizontal alignment * @param vAlign vertical alignment * @param hTextPosition horizontal text position * @param vTextPosition vertical text position * @param viewR Rectangle to layout text and icon in. * @param iconR Rectangle to place icon bounds in * @param textR Rectangle to place text in * @param iconTextGap gap between icon and text */ public String layoutText(SynthContext ss, FontMetrics fm, String text, Icon icon, int hAlign, int vAlign, int hTextPosition, int vTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int iconTextGap) { if (icon instanceof SynthIcon) { SynthIconWrapper wrapper = SynthIconWrapper.get((SynthIcon)icon, ss); String formattedText = SwingUtilities.layoutCompoundLabel( ss.getComponent(), fm, text, wrapper, vAlign, hAlign, vTextPosition, hTextPosition, viewR, iconR, textR, iconTextGap); SynthIconWrapper.release(wrapper); return formattedText; } return SwingUtilities.layoutCompoundLabel( ss.getComponent(), fm, text, icon, vAlign, hAlign, vTextPosition, hTextPosition, viewR, iconR, textR, iconTextGap); } /** * Returns the size of the passed in string. * * @param ss SynthContext * @param font Font to use * @param metrics FontMetrics, may be ignored * @param text Text to get size of. */ public int computeStringWidth(SynthContext ss, Font font, FontMetrics metrics, String text) { return SwingUtilities2.stringWidth(ss.getComponent(), metrics, text); } /** * Returns the minimum size needed to properly render an icon and text. * * @param ss SynthContext * @param font Font to use * @param text Text to layout * @param icon Icon to layout * @param hAlign horizontal alignment * @param vAlign vertical alignment * @param hTextPosition horizontal text position * @param vTextPosition vertical text position * @param iconTextGap gap between icon and text * @param mnemonicIndex Index into text to render the mnemonic at, -1 * indicates no mnemonic. */ public Dimension getMinimumSize(SynthContext ss, Font font, String text, Icon icon, int hAlign, int vAlign, int hTextPosition, int vTextPosition, int iconTextGap, int mnemonicIndex) { JComponent c = ss.getComponent(); Dimension size = getPreferredSize(ss, font, text, icon, hAlign, vAlign, hTextPosition, vTextPosition, iconTextGap, mnemonicIndex); View v = (View) c.getClientProperty(BasicHTML.propertyKey); if (v != null) { size.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS); } return size; } /** * Returns the maximum size needed to properly render an icon and text. * * @param ss SynthContext * @param font Font to use * @param text Text to layout * @param icon Icon to layout * @param hAlign horizontal alignment * @param vAlign vertical alignment * @param hTextPosition horizontal text position * @param vTextPosition vertical text position * @param iconTextGap gap between icon and text * @param mnemonicIndex Index into text to render the mnemonic at, -1 * indicates no mnemonic. */ public Dimension getMaximumSize(SynthContext ss, Font font, String text, Icon icon, int hAlign, int vAlign, int hTextPosition, int vTextPosition, int iconTextGap, int mnemonicIndex) { JComponent c = ss.getComponent(); Dimension size = getPreferredSize(ss, font, text, icon, hAlign, vAlign, hTextPosition, vTextPosition, iconTextGap, mnemonicIndex); View v = (View) c.getClientProperty(BasicHTML.propertyKey); if (v != null) { size.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS); } return size; } /** * Returns the maximum height of the the Font from the passed in * SynthContext. * * @param context SynthContext used to determine font. * @return maximum height of the characters for the font from the passed * in context. */ public int getMaximumCharHeight(SynthContext context) { FontMetrics fm = context.getComponent().getFontMetrics( context.getStyle().getFont(context)); return (fm.getAscent() + fm.getDescent()); } /** * Returns the preferred size needed to properly render an icon and text. * * @param ss SynthContext * @param font Font to use * @param text Text to layout * @param icon Icon to layout * @param hAlign horizontal alignment * @param vAlign vertical alignment * @param hTextPosition horizontal text position * @param vTextPosition vertical text position * @param iconTextGap gap between icon and text * @param mnemonicIndex Index into text to render the mnemonic at, -1 * indicates no mnemonic. */ public Dimension getPreferredSize(SynthContext ss, Font font, String text, Icon icon, int hAlign, int vAlign, int hTextPosition, int vTextPosition, int iconTextGap, int mnemonicIndex) { JComponent c = ss.getComponent(); Insets insets = c.getInsets(viewSizingInsets); int dx = insets.left + insets.right; int dy = insets.top + insets.bottom; if (icon == null && (text == null || font == null)) { return new Dimension(dx, dy); } else if ((text == null) || ((icon != null) && (font == null))) { return new Dimension(SynthIcon.getIconWidth(icon, ss) + dx, SynthIcon.getIconHeight(icon, ss) + dy); } else { FontMetrics fm = c.getFontMetrics(font); iconR.x = iconR.y = iconR.width = iconR.height = 0; textR.x = textR.y = textR.width = textR.height = 0; viewR.x = dx; viewR.y = dy; viewR.width = viewR.height = Short.MAX_VALUE; layoutText(ss, fm, text, icon, hAlign, vAlign, hTextPosition, vTextPosition, viewR, iconR, textR, iconTextGap); int x1 = Math.min(iconR.x, textR.x); int x2 = Math.max(iconR.x + iconR.width, textR.x + textR.width); int y1 = Math.min(iconR.y, textR.y); int y2 = Math.max(iconR.y + iconR.height, textR.y + textR.height); Dimension rv = new Dimension(x2 - x1, y2 - y1); rv.width += dx; rv.height += dy; return rv; } } /** * Paints text at the specified location. This will not attempt to * render the text as html nor will it offset by the insets of the * component. * * @param ss SynthContext * @param g Graphics used to render string in. * @param text Text to render * @param bounds Bounds of the text to be drawn. * @param mnemonicIndex Index to draw string at. */ public void paintText(SynthContext ss, Graphics g, String text, Rectangle bounds, int mnemonicIndex) { paintText(ss, g, text, bounds.x, bounds.y, mnemonicIndex); } /** * Paints text at the specified location. This will not attempt to * render the text as html nor will it offset by the insets of the * component. * * @param ss SynthContext * @param g Graphics used to render string in. * @param text Text to render * @param x X location to draw text at. * @param y Upper left corner to draw text at. * @param mnemonicIndex Index to draw string at. */ public void paintText(SynthContext ss, Graphics g, String text, int x, int y, int mnemonicIndex) { if (text != null) { JComponent c = ss.getComponent(); FontMetrics fm = SwingUtilities2.getFontMetrics(c, g); y += fm.getAscent(); SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemonicIndex, x, y); } } /** * Paints an icon and text. This will render the text as html, if * necessary, and offset the location by the insets of the component. * * @param ss SynthContext * @param g Graphics to render string and icon into * @param text Text to layout * @param icon Icon to layout * @param hAlign horizontal alignment * @param vAlign vertical alignment * @param hTextPosition horizontal text position * @param vTextPosition vertical text position * @param iconTextGap gap between icon and text * @param mnemonicIndex Index into text to render the mnemonic at, -1 * indicates no mnemonic. * @param textOffset Amount to offset the text when painting */ public void paintText(SynthContext ss, Graphics g, String text, Icon icon, int hAlign, int vAlign, int hTextPosition, int vTextPosition, int iconTextGap, int mnemonicIndex, int textOffset) { if ((icon == null) && (text == null)) { return; } JComponent c = ss.getComponent(); FontMetrics fm = SwingUtilities2.getFontMetrics(c, g); Insets insets = SynthLookAndFeel.getPaintingInsets(ss, paintInsets); paintViewR.x = insets.left; paintViewR.y = insets.top; paintViewR.width = c.getWidth() - (insets.left + insets.right); paintViewR.height = c.getHeight() - (insets.top + insets.bottom); paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; String clippedText = layoutText(ss, fm, text, icon, hAlign, vAlign, hTextPosition, vTextPosition, paintViewR, paintIconR, paintTextR, iconTextGap); if (icon != null) { Color color = g.getColor(); if (ss.getStyle().getBoolean(ss, "TableHeader.alignSorterArrow", false) && "TableHeader.renderer".equals(c.getName())) { paintIconR.x = paintViewR.width - paintIconR.width; } else { paintIconR.x += textOffset; } paintIconR.y += textOffset; SynthIcon.paintIcon(icon, ss, g, paintIconR.x, paintIconR.y, paintIconR.width, paintIconR.height); g.setColor(color); } if (text != null) { View v = (View) c.getClientProperty(BasicHTML.propertyKey); if (v != null) { v.paint(g, paintTextR); } else { paintTextR.x += textOffset; paintTextR.y += textOffset; paintText(ss, g, clippedText, paintTextR, mnemonicIndex); } } } /** * A quick note about how preferred sizes are calculated... Generally * speaking, SynthPopupMenuUI will run through the list of its children * (from top to bottom) and ask each for its preferred size. Each menu * item will add up the max width of each element (icons, text, * accelerator spacing, accelerator text or arrow icon) encountered thus * far, so by the time all menu items have been calculated, we will * know the maximum (preferred) menu item size for that popup menu. * Later when it comes time to paint each menu item, we can use those * same accumulated max element sizes in order to layout the item. */ static Dimension getPreferredMenuItemSize(SynthContext context, SynthContext accContext, JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap, String acceleratorDelimiter, boolean useCheckAndArrow, String propertyPrefix) { JMenuItem mi = (JMenuItem) c; SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper( context, accContext, mi, checkIcon, arrowIcon, MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap, acceleratorDelimiter, SynthLookAndFeel.isLeftToRight(mi), useCheckAndArrow, propertyPrefix); Dimension result = new Dimension(); // Calculate the result width int gap = lh.getGap(); result.width = 0; MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(), gap, result); MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), gap, result); MenuItemLayoutHelper.addWidth(lh.getMaxAccOrArrowWidth(), 5 * gap, result); // The last gap is unnecessary result.width -= gap; // Calculate the result height result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(), lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(), lh.getArrowSize().getHeight()); // Take into account menu item insets Insets insets = lh.getMenuItem().getInsets(); if (insets != null) { result.width += insets.left + insets.right; result.height += insets.top + insets.bottom; } // if the width is even, bump it up one. This is critical // for the focus dash lhne to draw properly if (result.width % 2 == 0) { result.width++; } // if the height is even, bump it up one. This is critical // for the text to center properly if (result.height % 2 == 0) { result.height++; } return result; } static void applyInsets(Rectangle rect, Insets insets, boolean leftToRight) { if (insets != null) { rect.x += (leftToRight ? insets.left : insets.right); rect.y += insets.top; rect.width -= (leftToRight ? insets.right : insets.left) + rect.x; rect.height -= (insets.bottom + rect.y); } } static void paint(SynthContext context, SynthContext accContext, Graphics g, Icon checkIcon, Icon arrowIcon, String acceleratorDelimiter, int defaultTextIconGap, String propertyPrefix) { JMenuItem mi = (JMenuItem) context.getComponent(); SynthStyle style = context.getStyle(); g.setFont(style.getFont(context)); Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight()); boolean leftToRight = SynthLookAndFeel.isLeftToRight(mi); applyInsets(viewRect, mi.getInsets(), leftToRight); SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper( context, accContext, mi, checkIcon, arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter, leftToRight, MenuItemLayoutHelper.useCheckAndArrow(mi), propertyPrefix); MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem(); paintMenuItem(g, lh, lr); } static void paintMenuItem(Graphics g, SynthMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) { // Save original graphics font and color Font holdf = g.getFont(); Color holdc = g.getColor(); paintBackground(g, lh); paintCheckIcon(g, lh, lr); paintIcon(g, lh, lr); paintText(g, lh, lr); paintAccText(g, lh, lr); paintArrowIcon(g, lh, lr); // Restore original graphics font and color g.setColor(holdc); g.setFont(holdf); } static void paintBackground(Graphics g, SynthMenuItemLayoutHelper lh) { paintBackground(lh.getContext(), g, lh.getMenuItem()); } static void paintBackground(SynthContext context, Graphics g, JComponent c) { context.getPainter().paintMenuItemBackground(context, g, 0, 0, c.getWidth(), c.getHeight()); } static void paintIcon(Graphics g, SynthMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) { if (lh.getIcon() != null) { Icon icon; JMenuItem mi = lh.getMenuItem(); ButtonModel model = mi.getModel(); if (!model.isEnabled()) { icon = mi.getDisabledIcon(); } else if (model.isPressed() && model.isArmed()) { icon = mi.getPressedIcon(); if (icon == null) { // Use default icon icon = mi.getIcon(); } } else { icon = mi.getIcon(); } if (icon != null) { Rectangle iconRect = lr.getIconRect(); SynthIcon.paintIcon(icon, lh.getContext(), g, iconRect.x, iconRect.y, iconRect.width, iconRect.height); } } } static void paintCheckIcon(Graphics g, SynthMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) { if (lh.getCheckIcon() != null) { Rectangle checkRect = lr.getCheckRect(); SynthIcon.paintIcon(lh.getCheckIcon(), lh.getContext(), g, checkRect.x, checkRect.y, checkRect.width, checkRect.height); } } static void paintAccText(Graphics g, SynthMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) { String accText = lh.getAccText(); if (accText != null && !accText.equals("")) { g.setColor(lh.getAccStyle().getColor(lh.getAccContext(), ColorType.TEXT_FOREGROUND)); g.setFont(lh.getAccStyle().getFont(lh.getAccContext())); lh.getAccGraphicsUtils().paintText(lh.getAccContext(), g, accText, lr.getAccRect().x, lr.getAccRect().y, -1); } } static void paintText(Graphics g, SynthMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) { if (!lh.getText().equals("")) { if (lh.getHtmlView() != null) { // Text is HTML lh.getHtmlView().paint(g, lr.getTextRect()); } else { // Text isn't HTML g.setColor(lh.getStyle().getColor( lh.getContext(), ColorType.TEXT_FOREGROUND)); g.setFont(lh.getStyle().getFont(lh.getContext())); lh.getGraphicsUtils().paintText(lh.getContext(), g, lh.getText(), lr.getTextRect().x, lr.getTextRect().y, lh.getMenuItem().getDisplayedMnemonicIndex()); } } } static void paintArrowIcon(Graphics g, SynthMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) { if (lh.getArrowIcon() != null) { Rectangle arrowRect = lr.getArrowRect(); SynthIcon.paintIcon(lh.getArrowIcon(), lh.getContext(), g, arrowRect.x, arrowRect.y, arrowRect.width, arrowRect.height); } } /** * Wraps a SynthIcon around the Icon interface, forwarding calls to * the SynthIcon with a given SynthContext. */ private static class SynthIconWrapper implements Icon { private static final java.util.List CACHE = new java.util.ArrayList(1); private SynthIcon synthIcon; private SynthContext context; static SynthIconWrapper get(SynthIcon icon, SynthContext context) { synchronized(CACHE) { int size = CACHE.size(); if (size > 0) { SynthIconWrapper wrapper = (SynthIconWrapper)CACHE.remove( size - 1); wrapper.reset(icon, context); return wrapper; } } return new SynthIconWrapper(icon, context); } static void release(SynthIconWrapper wrapper) { wrapper.reset(null, null); synchronized(CACHE) { CACHE.add(wrapper); } } SynthIconWrapper(SynthIcon icon, SynthContext context) { reset(icon, context); } void reset(SynthIcon icon, SynthContext context) { synthIcon = icon; this.context = context; } public void paintIcon(Component c, Graphics g, int x, int y) { // This is a noop as this should only be for sizing calls. } public int getIconWidth() { return synthIcon.getIconWidth(context); } public int getIconHeight() { return synthIcon.getIconHeight(context); } } }