/* * @(#)OSXButtonStateBorder.java * * Copyright (c) 2011 Werner Randelshofer, Immensee, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package ch.randelshofer.quaqua; import java.awt.FlowLayout; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.ImageIcon; import javax.swing.JFrame; import ch.randelshofer.quaqua.border.AbstractFocusedPainter; import ch.randelshofer.quaqua.border.VisualMarginBorder; import ch.randelshofer.quaqua.border.ImageBevelBorder; import javax.swing.JComponent; import ch.randelshofer.quaqua.border.BackgroundBorder; import ch.randelshofer.quaqua.border.FocusedBorder; import ch.randelshofer.quaqua.osx.OSXAquaPainter; import ch.randelshofer.quaqua.util.CachedPainter; import ch.randelshofer.quaqua.util.InsetsUtil; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Insets; import java.awt.image.BufferedImage; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.text.JTextComponent; import static ch.randelshofer.quaqua.osx.OSXAquaPainter.*; /** * Native Aqua border for text components. * * @author Werner Randelshofer * @version $Id$ */ public class QuaquaNativeTextFieldBorder extends VisualMarginBorder implements Border, BackgroundBorder { private Insets imageInsets; // private Insets borderInsets; private Border bgBorder; private final static int ARG_TEXT_FIELD = 2; private final static int ARG_SMALL_SIZE = 32; private class BGBorder implements Border { private Border searchFieldBorder; private Border textFieldBorder; private Border getActualBorder(Component c) { if ((c instanceof JComponent) && isSearchField((JComponent) c)) { if (searchFieldBorder == null) { searchFieldBorder = new FocusedBorder(new BGSearchFieldBorder()); } return searchFieldBorder; } else { if (textFieldBorder == null) { textFieldBorder = new BGTextFieldBorder(); } return textFieldBorder; } } public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { getActualBorder(c).paintBorder(c, g, x, y, width, height); } public Insets getBorderInsets(Component c) { return getActualBorder(c).getBorderInsets(c); } public boolean isBorderOpaque() { return false; } } private class BGTextFieldBorder implements Border { private BufferedImage regularPainterImage; private BufferedImage smallPainterImage; private BufferedImage regularFocusImage; private BufferedImage smallFocusImage; private ImageBevelBorder regularIbb; private ImageBevelBorder smallIbb; private OSXAquaPainter painter; public BGTextFieldBorder() { painter = new OSXAquaPainter(); } @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { int args = 0; JTextComponent b = (JTextComponent) c; boolean isEditable = b.isEditable(); // PREPARE THE PAINTER // ------------------- { State state; if (QuaquaUtilities.isOnActiveWindow(c)) { state = State.active; args |= 1; } else { state = State.inactive; } Widget widget; if (isSearchField(b)) { widget = Widget.frameTextFieldRound; } else { args |= ARG_TEXT_FIELD; widget = Widget.frameTextField; } painter.setWidget(widget); if (!b.isEnabled() || !isEditable) { state = State.disabled; args |= 4; } painter.setState(state); boolean isFocusedAndEditable = QuaquaUtilities.isFocused(c) && isEditable; args |= (isFocusedAndEditable) ? 16 : 0; painter.setValueByKey(OSXAquaPainter.Key.focused, isFocusedAndEditable ? 1 : 0); Size size; switch (QuaquaUtilities.getSizeVariant(c)) { case REGULAR: default: size = Size.regular; break; case SMALL: size = Size.small; args |= ARG_SMALL_SIZE; break; case MINI: size = Size.small; // paint mini with small artwork args |= ARG_SMALL_SIZE; break; } painter.setSize(size); } // Create an ImageBevelBorder // FIXME - We have a caching opportunity here! // ------------------------------------------- { Insets vm = getVisualMargin(c); // The painter can not render text fields in arbitrary sizes. // We render it first into an ImageBevelBorder, and then onto the // image. BufferedImage painterImg; BufferedImage ibbImg; BufferedImage focusImg; ImageBevelBorder ibb; int fixedWidth, fixedHeight, fixedYOffset; int slack = 6; if ((args & ARG_SMALL_SIZE) == ARG_SMALL_SIZE) { fixedWidth = 40 + slack * 2; fixedHeight = 19 + slack * 2; fixedYOffset = 3; if (smallPainterImage == null) { smallPainterImage = new BufferedImage(fixedWidth, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); } painterImg = smallPainterImage; if (smallFocusImage == null) { smallFocusImage = new BufferedImage(fixedWidth, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); } focusImg = smallFocusImage; if (smallIbb == null) { ibbImg = new BufferedImage(fixedWidth, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); ibb = smallIbb = new ImageBevelBorder(ibbImg, new Insets(4 + slack, 4 + slack, 4 + slack, 4 + slack), new Insets(4 + slack, 4 + slack, 4 + slack, 4 + slack)); } else { ibb = smallIbb; } } else { fixedWidth = 40 + slack * 2; fixedHeight = 22 + slack * 2; fixedYOffset = 3; if (regularPainterImage == null) { regularPainterImage = new BufferedImage(fixedWidth, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); } painterImg = regularPainterImage; if (regularFocusImage == null) { regularFocusImage = new BufferedImage(fixedWidth, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); } focusImg = regularFocusImage; if (regularIbb == null) { ibbImg = new BufferedImage(fixedWidth, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); ibb = regularIbb = new ImageBevelBorder(ibbImg, new Insets(8 + slack, 8 + slack, 8 + slack, 8 + slack), new Insets(8 + slack, 8 + slack, 8 + slack, 8 + slack)); } else { ibb = regularIbb; } } ibbImg = (BufferedImage) ibb.getImage(); Graphics2D pg = painterImg.createGraphics(); pg.setColor(new Color(0x0, true)); pg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); pg.fillRect(0, 0, painterImg.getWidth(), painterImg.getHeight()); pg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); pg.dispose(); painter.paint(painterImg,// slack, fixedYOffset + slack,// painterImg.getWidth() - 2 * slack, painterImg.getHeight() - 2 * slack); Graphics2D ibbg = ibbImg.createGraphics(); ibbg.setColor(new Color(0x0, true)); ibbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); ibbg.fillRect(0, 0, painterImg.getWidth(), painterImg.getHeight()); ibbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); ibbg.drawImage(painterImg, 0, 0, null); if (QuaquaUtilities.isFocused(c)&&isEditable) AbstractFocusedPainter.paintFocusRing(painterImg, focusImg, ibbg, 0, 0); ibbg.dispose(); ibb.paintBorder(c, g,// imageInsets.left - slack + vm.left, // imageInsets.top - slack + vm.top,// width - imageInsets.left - imageInsets.right + 2 * slack - vm.left - vm.right, // height - imageInsets.top - imageInsets.bottom + 2 * slack - vm.top - vm.bottom); ibbg.dispose(); } } public Insets getBorderInsets(Component c) { return new Insets(0, 0, 0, 0); } public boolean isBorderOpaque() { return false; } } private class BGSearchFieldBorder extends CachedPainter implements Border { private OSXAquaPainter painter; private ImageBevelBorder imageBevelBorder; public BGSearchFieldBorder() { super(12); painter = new OSXAquaPainter(); } @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { JTextComponent b = (JTextComponent) c; //ButtonModel bm = b.getModel(); boolean isEditable = b.isEditable(); int args = 0; State state; if (QuaquaUtilities.isOnActiveWindow(c)) { state = State.active; args |= 1; } else { state = State.inactive; } Widget widget; if (isSearchField(b)) { widget = Widget.frameTextFieldRound; } else { args |= ARG_TEXT_FIELD; widget = Widget.frameTextField; } painter.setWidget(widget); if (!b.isEnabled() || !isEditable) { state = State.disabled; args |= 4; } painter.setState(state); boolean isFocusedAndEditable = QuaquaUtilities.isFocused(c) && isEditable; args |= (isFocusedAndEditable) ? 16 : 0; painter.setValueByKey(OSXAquaPainter.Key.focused, isFocusedAndEditable ? 1 : 0); Size size; switch (QuaquaUtilities.getSizeVariant(c)) { case REGULAR: default: size = Size.regular; break; case SMALL: size = Size.small; args |= ARG_SMALL_SIZE; break; case MINI: size = Size.small; // paint mini with small artwork args |= ARG_SMALL_SIZE; break; } painter.setSize(size); paint(c, g, x, y, width, height, args); } @Override protected Image createImage(Component c, int w, int h, GraphicsConfiguration config) { return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); } @Override protected void paintToImage(Component c, Image img, int w, int h, Object argsObj) { int args = (Integer) argsObj; Insets vm = getVisualMargin(c); if ((args & ARG_TEXT_FIELD) == ARG_TEXT_FIELD) { // => Okay: this is a hard nut to crack. // The painter can not render text fields in arbitrary sizes. // We render it first into an ImageBevelBorder, and then onto the // image. BufferedImage ibbImg; boolean isShow = false; int fixedHeight, fixedYOffset; if ((args & ARG_SMALL_SIZE) == ARG_SMALL_SIZE) { fixedHeight = 19; fixedYOffset = 3; } else { fixedHeight = 22; fixedYOffset = 3; } if (imageBevelBorder == null) { ibbImg = new BufferedImage(40, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); imageBevelBorder = new ImageBevelBorder(ibbImg, new Insets(4, 4, 4, 4), new Insets(0, 0, 0, 0)); } else { ibbImg = (BufferedImage) imageBevelBorder.getImage(); if (ibbImg.getHeight() != fixedHeight) { ibbImg = new BufferedImage(40, fixedHeight, BufferedImage.TYPE_INT_ARGB_PRE); imageBevelBorder.setImage(ibbImg); } } Graphics2D ibbg = (Graphics2D) ibbImg.getGraphics(); ibbg.setColor(new Color(0x0, true)); ibbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); ibbg.fillRect(0, 0, ibbImg.getWidth(), ibbImg.getHeight()); ibbg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); // FIXME - Find a way to fill the field with its background color //ibbg.setColor(c.getBackground()); //ibbg.fillRect(0, 0, ibbImg.getWidth(), ibbImg.getHeight()); ibbg.dispose(); painter.paint(ibbImg,// 0, fixedYOffset,// ibbImg.getWidth(), ibbImg.getHeight()); // Now render the imageBevelBorder Graphics2D ig = (Graphics2D) img.getGraphics(); ig.setColor(new Color(0x0, true)); ig.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); ig.fillRect(0, 0, img.getWidth(null), img.getHeight(null)); ig.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); imageBevelBorder.paintBorder(c, ig,// imageInsets.left - 3 + vm.left, // imageInsets.top - 3 + vm.top,// w - imageInsets.left - imageInsets.right + 6 - vm.left - vm.right, // h - imageInsets.top - imageInsets.bottom + 6 - vm.top - vm.bottom); ig.dispose(); } else { Graphics2D ig = (Graphics2D) img.getGraphics(); ig.setColor(new Color(0x0, true)); ig.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); ig.fillRect(0, 0, img.getWidth(null), img.getHeight(null)); ig.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); ig.dispose(); painter.paint((BufferedImage) img,// imageInsets.left + vm.left, // imageInsets.top + vm.top,// w - imageInsets.left - imageInsets.right - vm.left - vm.right, // h - imageInsets.top - imageInsets.bottom - vm.top - vm.bottom); } } @Override protected void paintToImage(Component c, Graphics g, int w, int h, Object args) { // round up image size to reduce memory thrashing BufferedImage img = (BufferedImage) createImage(c, (w / 32 + 1) * 32, (h / 32 + 1) * 32, null); paintToImage(c, img, w, h, args); g.drawImage(img, 0, 0, null); img.flush(); } public Insets getBorderInsets(Component c) { return new Insets(0, 0, 0, 0); } public boolean isBorderOpaque() { return false; } } public QuaquaNativeTextFieldBorder() { this(new Insets(0, 0, 0, 0), new Insets(0, 0, 0, 0), true); } public QuaquaNativeTextFieldBorder(Insets imageInsets, Insets borderInsets, boolean fill) { super(new Insets(0, 0, 0, 0)); this.imageInsets = imageInsets; //this.borderInsets = borderInsets; } private boolean isSearchField(JComponent b) { Object variant = b.getClientProperty("Quaqua.TextField.style"); if (variant == null) { variant = b.getClientProperty("JTextField.variant"); } return variant != null && variant.equals("search"); } @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { // empty } public Border getBackgroundBorder() { if (bgBorder == null) { this.bgBorder = new BGBorder(); } return bgBorder; } @Override public Insets getBorderInsets(Component c, Insets insets) { Insets vm = getVisualMargin(c); Insets bm; if (isSearchField((JComponent) c)) { bm = UIManager.getInsets("TextField.searchBorderInsets"); } else { switch (QuaquaUtilities.getSizeVariant(c)) { default: bm = UIManager.getInsets("TextField.borderInsets"); break; case SMALL: bm = UIManager.getInsets("TextField.smallBorderInsets"); break; case MINI: bm = UIManager.getInsets("TextField.miniBorderInsets"); break; } } if (bm != null) { InsetsUtil.setTo(bm, insets); } else { InsetsUtil.clear(insets); } if (vm != null) { InsetsUtil.addTo(vm, insets); } if (c instanceof JTextComponent) { Insets margin = ((JTextComponent) c).getMargin(); if (margin != null) { InsetsUtil.addTo(margin, insets); } } return insets; } @Override public boolean isBorderOpaque() { return false; } public static class UIResource extends QuaquaNativeTextFieldBorder implements javax.swing.plaf.UIResource { public UIResource() { super(); } /** * Creates a new instance. * All borders must have the same dimensions. */ public UIResource(Insets imageInsets, Insets borderInsets, boolean fill) { super(imageInsets, borderInsets, fill); } } }