/*
* Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
*
* This file is part of the SeaGlass Pluggable Look and Feel.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id$
*/
package com.seaglasslookandfeel.util;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.synth.ColorType;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthGraphicsUtils;
import javax.swing.plaf.synth.SynthStyle;
import javax.swing.text.View;
import sun.swing.MenuItemLayoutHelper;
import sun.swing.SwingUtilities2;
import sun.swing.plaf.synth.SynthIcon;
import com.seaglasslookandfeel.SeaGlassContext;
import com.seaglasslookandfeel.SeaGlassLookAndFeel;
import com.seaglasslookandfeel.component.SeaGlassIcon;
/**
* Customize the graphics utilities used by Synth.
*
* @author Kathryn Huxtable
*/
public class SeaGlassGraphicsUtils extends 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);
public 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);
}
}
public 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 = SeaGlassLookAndFeel.isLeftToRight(mi);
applyInsets(viewRect, mi.getInsets(), leftToRight);
SeaGlassMenuItemLayoutHelper lh = new SeaGlassMenuItemLayoutHelper(context, accContext, mi, checkIcon, arrowIcon, viewRect,
defaultTextIconGap, acceleratorDelimiter, leftToRight, MenuItemLayoutHelper.useCheckAndArrow(mi), propertyPrefix);
MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem();
paintMenuItem(g, lh, lr);
}
public static void paintMenuItem(Graphics g, SeaGlassMenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) {
// Save original graphics font and color
Font holdf = g.getFont();
Color holdc = g.getColor();
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, SeaGlassMenuItemLayoutHelper lh) {
paintBackground(lh.getContext(), g, lh.getMenuItem());
}
static void paintBackground(SynthContext context, Graphics g, JComponent c) {
((SeaGlassContext)context).getPainter().paintMenuItemBackground(context, g, 0, 0,
c.getWidth(), c.getHeight());
}
static void paintIcon(Graphics g, SeaGlassMenuItemLayoutHelper 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, SeaGlassMenuItemLayoutHelper 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, SeaGlassMenuItemLayoutHelper 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, SeaGlassMenuItemLayoutHelper 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, SeaGlassMenuItemLayoutHelper 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);
}
}
/**
* 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) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
JComponent c = ss.getComponent();
// SynthStyle style = ss.getStyle();
FontMetrics fm = SwingUtilities2.getFontMetrics(c, g2d);
y += fm.getAscent();
SwingUtilities2.drawString(c, g2d, text, x, y);
if (mnemonicIndex >= 0 && mnemonicIndex < text.length()) {
int underlineX = x + SwingUtilities2.stringWidth(c, fm, text.substring(0, mnemonicIndex));
int underlineY = y;
int underlineWidth = fm.charWidth(text.charAt(mnemonicIndex));
int underlineHeight = 1;
g2d.fillRect(underlineX, underlineY + fm.getDescent() - 1, underlineWidth, underlineHeight);
}
}
}
/**
* Returns a new color with the alpha of the old color cut in half.
*
* @param color
* the original color.
*
* @return the new color.
*/
// Rossi: used in slider ui "paint ticks".
public static Color disable(Color color) {
int alpha = color.getAlpha();
alpha /= 2;
return new Color((color.getRGB() & 0xFFFFFF) | (alpha << 24), true);
}
/**
* 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;
}
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
JComponent c = ss.getComponent();
FontMetrics fm = SwingUtilities2.getFontMetrics(c, g2d);
Insets insets = SeaGlassLookAndFeel.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 = g2d.getColor();
paintIconR.x += textOffset;
paintIconR.y += textOffset;
SeaGlassIcon.paintIcon(icon, ss, g2d, paintIconR.x, paintIconR.y, paintIconR.width, paintIconR.height);
g2d.setColor(color);
}
if (text != null) {
View v = (View) c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
v.paint(g2d, paintTextR);
} else {
paintTextR.x += textOffset;
paintTextR.y += textOffset;
paintText(ss, g2d, clippedText, paintTextR, mnemonicIndex);
}
}
}
/**
* Draw text with an emphasized background.
*
* @param g
* the Graphics context to draw with.
* @param foreground
* the foreground color.
* @param emphasis
* the emphasis color.
* @param s
* the text to draw.
* @param x
* the x coordinate to draw at.
* @param y
* the y coordinate to draw at.
*/
public void drawEmphasizedText(Graphics g, Color foreground, Color emphasis, String s, int x, int y) {
drawEmphasizedText(g, foreground, emphasis, s, -1, x, y);
}
/**
* Draw text with an emphasized background.
*
* @param g
* the Graphics context to draw with.
* @param foreground
* the foreground color.
* @param emphasis
* the emphasis color.
* @param s
* the text to draw.
* @param underlinedIndex
* the index to underline.
* @param x
* the x coordinate to draw at.
* @param y
* the y coordinate to draw at.
*/
public void drawEmphasizedText(Graphics g, Color foreground, Color emphasis, String s, int underlinedIndex, int x, int y) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setColor(emphasis);
BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, s, underlinedIndex, x, y + 1);
g2d.setColor(foreground);
BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, s, underlinedIndex, x, y);
}
public 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;
SeaGlassMenuItemLayoutHelper lh = new SeaGlassMenuItemLayoutHelper(context, accContext, mi, checkIcon, arrowIcon,
MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap, acceleratorDelimiter, SeaGlassLookAndFeel.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;
}
}