package nodebox.ui; import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; /** * A custom button that uses an icon and shadowed text. */ public class NButton extends JComponent implements MouseListener { public static final int BUTTON_HEIGHT = 21; public static final int IMAGE_TEXT_MARGIN = 3; public static final int TEXT_BASELINE = 14; public static final Image checkOn, checkOff, checkDisabledOn, checkDisabledOff; private String text; private Image normalImage, checkedImage; private Object actionObject; private Method actionMethod; private Mode mode; private boolean armed = false; private boolean pressed = false; private boolean checked = false; private boolean warn = false; static { try { checkOn = ImageIO.read(NButton.class.getResourceAsStream("/check-on.png")); checkOff = ImageIO.read(NButton.class.getResourceAsStream("/check-off.png")); checkDisabledOn = ImageIO.read(NButton.class.getResourceAsStream("/check-disabled-on.png")); checkDisabledOff = ImageIO.read(NButton.class.getResourceAsStream("/check-disabled-off.png")); } catch (IOException e) { throw new RuntimeException(e); } } /** * Create a push button. * * @param text the button label * @param imageFile the button image */ public NButton(String text, String imageFile) { this(Mode.PUSH, text, imageFile, null); } public NButton(String text, InputStream imageStream) { this(Mode.PUSH, text, imageStream, null); } public NButton(String text, Image image) { init(Mode.PUSH, text, image, null); } /** * Create a check button. * * @param text the button label * @param normalImage the image to show when unchecked * @param checkedImage the image to show when checked */ public NButton(String text, String normalImage, String checkedImage) { this(Mode.CHECK, text, normalImage, checkedImage); } public NButton(String text, InputStream normalImage, InputStream checkedImage) { this(Mode.CHECK, text, normalImage, checkedImage); } public NButton(Mode mode, String text) { if (mode != Mode.CHECK) throw new AssertionError("Only use Mode.CHECK."); init(mode, text, checkOff, checkOn); } private NButton(Mode mode, String text, InputStream normalImage, InputStream checkedImage) { Image normal, checked = null; try { normal = ImageIO.read(normalImage); if (checkedImage != null) checked = ImageIO.read(checkedImage); } catch (IOException e) { throw new IllegalArgumentException("Cannot load image.", e); } init(mode, text, normal, checked); } private NButton(Mode mode, String text, String normalImage, String checkedImage) { Image normal, checked = null; try { normal = ImageIO.read(new File(normalImage)); if (checkedImage != null) checked = ImageIO.read(new File(checkedImage)); } catch (IOException e) { throw new IllegalArgumentException("Cannot load image.", e); } init(mode, text, normal, checked); } private void init(Mode mode, String text, Image normalImage, Image checkedImage) { this.mode = mode; this.text = text; this.normalImage = normalImage; this.checkedImage = checkedImage; int width = measureWidth(); Dimension d = new Dimension(width, BUTTON_HEIGHT); setSize(d); setPreferredSize(d); setMinimumSize(d); setMaximumSize(d); addMouseListener(this); } private int measureWidth() { // To measure text we need a graphics context. Create an image and use its' graphics context. BufferedImage tmp = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = tmp.createGraphics(); g2.setFont(Theme.SMALL_BOLD_FONT); int width = normalImage.getWidth(null); width += IMAGE_TEXT_MARGIN; width += (int) g2.getFontMetrics().stringWidth(text); width += 1; // Anti-aliasing can take up an extra pixel. return width; } /** * Set the method that will be called when the button is pressed. * * @param obj the instance * @param methodName the name of the method on the instance. The method should not take any arguments. */ public void setActionMethod(Object obj, String methodName) { actionObject = obj; try { actionMethod = obj.getClass().getMethod(methodName); } catch (NullPointerException e) { throw new IllegalArgumentException("Ojbect or method name cannot be null."); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Cannot find method " + methodName + " on object " + actionObject); } } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); repaint(); } public Mode getMode() { return mode; } public boolean isChecked() { return checked; } public void setChecked(boolean v) { if (v == checked) return; checked = v; repaint(); } public String getText() { return text; } public void setText(String text) { this.text = text; int width = measureWidth(); Dimension d = new Dimension(width, BUTTON_HEIGHT); setSize(d); setPreferredSize(d); setMinimumSize(d); setMaximumSize(d); } public boolean isWarning() { return warn; } public void setWarning(boolean warn) { if (warn == this.warn) return; this.warn = warn; repaint(); } @Override protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; if (isEnabled()) { if (checked) { g2.drawImage(checkedImage, 0, 0, null); } else { g2.drawImage(normalImage, 0, 0, null); } } else { if (mode == Mode.CHECK && checked) { g2.drawImage(checkDisabledOn, 0, 0, null); } else if (mode == Mode.CHECK && !checked) { g2.drawImage(checkDisabledOff, 0, 0, null); } else { Composite oldComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); g2.drawImage(normalImage, 0, 0, null); g2.setComposite(oldComposite); } } int w = normalImage.getWidth(null); g2.setFont(Theme.SMALL_BOLD_FONT); if (warn) { g2.setColor(Theme.TEXT_WARNING_COLOR); } else if (armed) { g2.setColor(Theme.TEXT_ARMED_COLOR); } else if (!isEnabled()) { g2.setColor(Theme.TEXT_DISABLED_COLOR); } else { g2.setColor(Theme.TEXT_NORMAL_COLOR); } SwingUtils.drawShadowText(g2, text, w + IMAGE_TEXT_MARGIN, TEXT_BASELINE); } public void mouseClicked(MouseEvent e) { } public void mousePressed(MouseEvent e) { if (!isEnabled()) return; pressed = true; armed = true; repaint(); } public void mouseReleased(MouseEvent e) { if (!isEnabled()) return; pressed = false; if (armed) { armed = false; if (mode == Mode.CHECK) checked = !checked; try { actionMethod.invoke(actionObject); } catch (Exception e1) { throw new RuntimeException("Could not invoke method " + actionMethod + " on object " + actionObject, e1); } repaint(); } } public void mouseEntered(MouseEvent e) { if (!isEnabled()) return; if (pressed) { armed = true; repaint(); } } public void mouseExited(MouseEvent e) { if (!isEnabled()) return; armed = false; repaint(); } public static enum Mode { PUSH, CHECK } }