/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program 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 for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.ui; import javax.swing.Action; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.event.MouseInputAdapter; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.Insets; import java.awt.LinearGradientPaint; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; /** * A navigation control which appears as a screen overlay. * * @author Norman Fomferra * @version $Revision$ $Date$ */ public class ButtonOverlayControl extends JComponent { private static final int BUTTON_SPACING = 3; private static final int BUTTON_AREA_INSET = 5; private static final int ARC_SIZE = BUTTON_AREA_INSET * 2; private final Dimension buttonDimension; private Rectangle2D.Double buttonArea; private List<ButtonDef> buttonDefList; private int numCols; public ButtonOverlayControl(Action... actions) { this(2, actions); } public ButtonOverlayControl(int numCols, Action... actions) { this.numCols = numCols; buttonDimension = new Dimension(24, 24); buttonDefList = new ArrayList<ButtonDef>(); for (Action action : actions) { buttonDefList.add(new ButtonDef(action, buttonDimension, numCols)); } Dimension preferredSize = computePreferredSize(); setPreferredSize(preferredSize); setBounds(0, 0, preferredSize.width, preferredSize.height); updateGeom(getPaintArea()); final MouseHandler mouseHandler = new MouseHandler(); addMouseListener(mouseHandler); addMouseMotionListener(mouseHandler); } @Override public Dimension getPreferredSize() { if (isPreferredSizeSet()) { return super.getPreferredSize(); } return computePreferredSize(); } @Override public final void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); updateGeom(getPaintArea()); } @Override protected void paintComponent(Graphics g) { final Rectangle bounds = getPaintArea(); if (bounds.isEmpty()) { return; } final Graphics2D graphics2D = (Graphics2D) g; graphics2D.setStroke(new BasicStroke(0.6f)); graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); drawBackground(bounds, graphics2D); for (int i = 0; i < buttonDefList.size(); i++) { drawButton(graphics2D, i); } } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); for (ButtonDef buttonDef : buttonDefList) { buttonDef.getAction().setEnabled(enabled); } } private void drawBackground(Rectangle bounds, Graphics2D graphics2D) { final Shape backgroundShape = new RoundRectangle2D.Double(bounds.x, bounds.y, bounds.width, bounds.height, ARC_SIZE, ARC_SIZE); graphics2D.setColor(Color.BLACK); graphics2D.draw(backgroundShape); graphics2D.setColor(new Color(255, 255, 255, 64)); graphics2D.fill(backgroundShape); } private void drawButton(Graphics2D graphics2D, int buttonIndex) { ButtonDef buttonDef = buttonDefList.get(buttonIndex); Shape shape = buttonDef.getShape(); drawGradientShape(graphics2D, shape, buttonDef.isHighlighted()); Image image = buttonDef.getImage(); Point imageOffset = buttonDef.getImageOffset(); graphics2D.drawImage(image, imageOffset.x, imageOffset.y, null); } private void drawGradientShape(Graphics2D graphics2D, Shape shape, boolean highlighted) { graphics2D.setColor(Color.BLACK); graphics2D.draw(shape); final Point startPos = shape.getBounds().getLocation(); final Point endPos = (Point) startPos.clone(); endPos.y = startPos.y + shape.getBounds().height; final LinearGradientPaint paint; if (highlighted) { paint = new LinearGradientPaint(startPos, endPos, new float[]{0.0f, 0.5f, 0.6f, 0.8f, 1.0f}, new Color[]{ new Color(255, 255, 255, 64), new Color(255, 255, 255, 255), new Color(255, 255, 255, 255), new Color(255, 255, 255, 160), new Color(0, 0, 0, 160) }); } else { paint = new LinearGradientPaint(startPos, endPos, new float[]{0.0f, 0.5f, 0.6f, 0.8f, 1.0f}, new Color[]{ new Color(255, 255, 255, 0), new Color(255, 255, 255, 64), new Color(255, 255, 255, 64), new Color(255, 255, 255, 30), new Color(0, 0, 0, 40) }); } graphics2D.setPaint(paint); graphics2D.fill(shape); } private Point computeButtonLocation(int buttonIndex) { final int xIndex = buttonIndex % numCols; final int yIndex = buttonIndex / numCols; int xLocation = (int) buttonArea.x + xIndex * (buttonDimension.width + BUTTON_SPACING); int yLocation = (int) buttonArea.y + yIndex * (buttonDimension.height + BUTTON_SPACING); return new Point(xLocation, yLocation); } private Dimension computePreferredSize() { int numButtons = buttonDefList.size(); int buttonCols = numButtons <= 1 ? 1 : numCols; int buttonRows = numButtons <= 1 ? 1 : (numButtons + 1) / buttonCols; int width = BUTTON_AREA_INSET; int height = BUTTON_AREA_INSET; width += buttonCols * (buttonDimension.width); height += buttonRows * (buttonDimension.height); width += (buttonCols - 1) * (BUTTON_SPACING); height += (buttonRows - 1) * (BUTTON_SPACING); width += BUTTON_AREA_INSET; height += BUTTON_AREA_INSET; return new Dimension(width, height); } private void updateGeom(Rectangle bounds) { buttonArea = new Rectangle2D.Double(bounds.x + BUTTON_AREA_INSET - 1, bounds.y + BUTTON_AREA_INSET - 1, bounds.width - BUTTON_AREA_INSET * 2 + 2, bounds.height - BUTTON_AREA_INSET * 2 + 2); for (int i = 0; i < buttonDefList.size(); i++) { ButtonDef buttonDef = buttonDefList.get(i); buttonDef.setShapeLocation(computeButtonLocation(i)); } } private Rectangle getPaintArea() { final Insets insets = getInsets(); int x = insets.left + 1; int y = insets.top + 1; int w = getWidth() - (insets.left + insets.right) - 2; int h = getHeight() - (insets.top + insets.bottom) - 2; return new Rectangle(x, y, w, h); } private ButtonDef getButtonDef(int x, int y) { for (ButtonDef buttonDef : buttonDefList) { if (buttonDef.getShape().contains(x, y)) { return buttonDef; } } return null; } private class MouseHandler extends MouseInputAdapter { @Override public void mouseClicked(MouseEvent e) { ButtonDef buttonDef = getButtonDef(e.getX(), e.getY()); if (buttonDef != null) { final Action action = buttonDef.getAction(); action.actionPerformed(new ActionEvent(e.getSource(), e.getID(), e.paramString())); e.consume(); } } @Override public void mouseMoved(MouseEvent e) { for (ButtonDef buttonDef : buttonDefList) { if (buttonDef.getShape().contains(e.getX(), e.getY())) { buttonDef.setHighlighted(true); } else { buttonDef.setHighlighted(false); } } repaint(); } } private static class ButtonDef { private final Action action; private final int numCols; private final Image image; private RoundRectangle2D.Double shape; private boolean highlighted; private ButtonDef(Action action, Dimension buttonDimension, int numCols) { this.action = action; this.numCols = numCols; Image rawImage = iconToImage((Icon) this.action.getValue(Action.LARGE_ICON_KEY)); image = rawImage.getScaledInstance(buttonDimension.width, buttonDimension.height, Image.SCALE_SMOOTH); shape = new RoundRectangle2D.Double(); shape.arcwidth = 4; shape.archeight = 4; shape.setFrame(new Point(), buttonDimension); } public Action getAction() { return action; } public Image getImage() { return image; } public Point getImageOffset() { final Rectangle bounds = shape.getBounds(); final int xOffset = bounds.x + (bounds.width - image.getWidth(null)) / numCols; final int yOffset = bounds.y + (bounds.height - image.getHeight(null)) / numCols; return new Point(xOffset, yOffset); } public Shape getShape() { return (Shape) shape.clone(); } public void setShapeLocation(Point point) { shape.setFrame(point, shape.getBounds().getSize()); } public void setShapeDimension(Dimension dimension) { shape.setFrame(shape.getBounds().getLocation(), dimension); } private static Image iconToImage(Icon icon) { if (icon instanceof ImageIcon) { return ((ImageIcon) icon).getImage(); } else { int w = icon.getIconWidth(); int h = icon.getIconHeight(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gd.getDefaultConfiguration(); BufferedImage image = gc.createCompatibleImage(w, h); Graphics2D g = image.createGraphics(); icon.paintIcon(null, g, 0, 0); g.dispose(); return image; } } public void setHighlighted(boolean highlighted) { this.highlighted = highlighted; } public boolean isHighlighted() { return highlighted; } } }