/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.ui.components.gradientslider; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.TexturePaint; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import javax.swing.JComponent; /** The UI for the GradientSlider class. * * There are 3 properties you can use to customize the UI * of a GradientSlider. You can set these for each slider * by calling: * <BR><code>slider.putClientProperty(key,value);</code> * <P>Or you can set these globally by calling: * <BR><code>UIManager.put(key,value);</code> * <P>The three properties are: * <P><TABLE BORDER="1" CELLPADDING=5> * <TR> * <TD>Property Name</TD><TD>Default Value</TD><TD>Description</DT> * </TR> * <TR> * <TD>GradientSlider.useBevel</TD><TD>"false"</TD><TD>If this is <code>true</code>, then this slider will be painted in a rectangle with a bevel effect around the borders. If this is <code>false</code>, then this slider will be painted in a rounded rectangle.</DT> * </TR> * <TR> * <TD>GradientSlider.showTranslucency</TD><TD>"true"</TD><TD>If this is <code>true</code>, then the slider will reflect the opacity of the colors in the gradient, and paint a checkered background underneath the colors to indicate opacity. If this is <code>false</code>, then this slider will always paint with completely opaque colors, although the actual colors may be translucent.</DT> * </TR> * <TR> * <TD>GradientSlider.includeOpacity</TD><TD>"true"</TD><TD>This is used when the user double-clicks a color and a ColorPicker dialog is invoked. (So this value may not have any meaning if you override <code>GradientSlider.doDoubleClick()</code>.) This controls whether the opacity/alpha controls are available in that dialog. This does <i>not</i> control whether translucent colors can be used in this slider: translucent colors are always allowed, if the user can enter them.</TD> * </TR> * <TR> * <TD>MultiThumbSlider.indicateComponent</TD><TD>"true"</TD><TD>If this is <code>true</code>, then the thumbs will only paint on this component when the mouse is inside this slider <i>or</i> when this slider as the keyboard focus.</DT> * </TR> * <TR> * <TD>MultiThumbSlider.indicateThumb</TD><TD>"true"</TD><TD>If this is <code>true</code>, then the thumb the mouse is over will gently fade into a slightly different color.</DT> * </TR> * </TABLE> * */ public class GradientSliderUI extends MultiThumbSliderUI { int TRIANGLE_SIZE = 8; /** The width of this image is the absolute widest the track will * ever become. */ BufferedImage img = new BufferedImage(1000, 1, BufferedImage.TYPE_INT_ARGB); /** A temporary array used for the buffered image */ int[] array = new int[img.getWidth()]; public GradientSliderUI(GradientSlider slider) { super(slider); } public int getClickLocationTolerance() { return TRIANGLE_SIZE; } protected void calculateImage() { float[] f = slider.getThumbPositions(); Color[] c = ((GradientSlider) slider).getColors(); /** make sure we DO have a value at 0 and 1: */ if (f[0] != 0) { float[] f2 = new float[f.length + 1]; System.arraycopy(f, 0, f2, 1, f.length); Color[] c2 = new Color[c.length + 1]; System.arraycopy(c, 0, c2, 1, f.length); f = f2; c = c2; f[0] = 0; c[0] = c[1]; } if (f[f.length - 1] != 1) { float[] f2 = new float[f.length + 1]; System.arraycopy(f, 0, f2, 0, f.length); Color[] c2 = new Color[c.length + 1]; System.arraycopy(c, 0, c2, 0, f.length); f = f2; c = c2; f[f.length - 1] = 1; c[c.length - 1] = c[c.length - 2]; } /** Now, finally paint */ int[] argb = new int[c.length]; for (int a = 0; a < argb.length; a++) { argb[a] = ((c[a].getAlpha() & 0xff) << 24) + ((c[a].getRed() & 0xff) << 16) + ((c[a].getGreen() & 0xff) << 8) + ((c[a].getBlue() & 0xff) << 0); } int max; if (slider.getOrientation() == GradientSlider.HORIZONTAL) { max = trackRect.width; } else { max = trackRect.height; } if (max <= 0) { return; } boolean alwaysOpaque = getProperty(slider, "GradientSlider.showTranslucency", "true").equals("false"); float fraction; int i1 = 0; int i2 = 1; int a1 = (argb[0] >> 24) & 0xff; int r1 = (argb[0] & 0x00ff0000) >> 16; int g1 = (argb[0] & 0x0000ff00) >> 8; int b1 = (argb[0] & 0x000000ff) >> 0; int a2 = (argb[1] >> 24) & 0xff; int r2 = (argb[1] & 0x00ff0000) >> 16; int g2 = (argb[1] & 0x0000ff00) >> 8; int b2 = (argb[1] & 0x000000ff) >> 0; for (int z = 0; z < max; z++) { fraction = ((float) z) / ((float) (max - 1)); if (fraction < 1 && fraction >= f[i2]) { while (fraction < 1 && fraction >= f[i2]) { i1++; i2++; } a1 = (argb[i1] >> 24) & 0xff; r1 = (argb[i1] & 0x00ff0000) >> 16; g1 = (argb[i1] & 0x0000ff00) >> 8; b1 = (argb[i1] & 0x000000ff) >> 0; a2 = (argb[i2] >> 24) & 0xff; r2 = (argb[i2] & 0x00ff0000) >> 16; g2 = (argb[i2] & 0x0000ff00) >> 8; b2 = (argb[i2] & 0x000000ff) >> 0; } float colorFraction = (fraction - f[i1]) / (f[i2] - f[i1]); if (colorFraction > 1) { colorFraction = 1; } if (colorFraction < 0) { colorFraction = 0; } if (alwaysOpaque) { a1 = 255; a2 = 255; } array[z] = (((int) (a1 * (1 - colorFraction) + a2 * colorFraction)) << 24) + (((int) (r1 * (1 - colorFraction) + r2 * colorFraction)) << 16) + (((int) (g1 * (1 - colorFraction) + g2 * colorFraction)) << 8) + (((int) (b1 * (1 - colorFraction) + b2 * colorFraction))); } img.getRaster().setDataElements(0, 0, max, 1, array); } public Dimension getMinimumSize(JComponent s) { Dimension d = super.getMinimumSize(s); if (slider.getOrientation() == GradientSlider.HORIZONTAL) { d.height += 2; } else { d.width += 2; } return d; } public Dimension getPreferredSize(JComponent s) { Dimension d = super.getPreferredSize(s); if (slider.getOrientation() == GradientSlider.HORIZONTAL) { d.height += 2; } else { d.width += 2; } return d; } protected Rectangle calculateTrackRect() { int w = slider.getWidth(); int h = slider.getHeight(); Rectangle r = new Rectangle(); if (slider.getOrientation() == GradientSlider.HORIZONTAL) { r.x = TRIANGLE_SIZE; r.y = 3; r.height = h - TRIANGLE_SIZE - r.y; r.width = w - 2 * TRIANGLE_SIZE; if (r.width > img.getWidth()) { r.width = img.getWidth(); r.x = (w - 2 * TRIANGLE_SIZE) / 2 - r.width / 2; } if (r.height > 2 * DEPTH) { r.height = 2 * DEPTH; r.y = (h - TRIANGLE_SIZE) / 2 - r.height / 2; } } else { r.x = 3; r.y = TRIANGLE_SIZE; r.width = w - TRIANGLE_SIZE - r.x; r.height = h - 2 * TRIANGLE_SIZE; if (r.height > img.getWidth()) { r.height = img.getWidth(); r.y = (h - 2 * TRIANGLE_SIZE) / 2 - r.height / 2; } if (r.width > 2 * DEPTH) { r.width = 2 * DEPTH; r.x = (w - TRIANGLE_SIZE) / 2 - r.width / 2; } } return r; } protected void calculateGeometry() { super.calculateGeometry(); calculateImage(); } static TexturePaint checkerPaint; private static void createCheckerPaint() { int k = 4; BufferedImage bi = new BufferedImage(2 * k, 2 * k, BufferedImage.TYPE_INT_RGB); Graphics2D g = bi.createGraphics(); g.setColor(Color.white); g.fillRect(0, 0, 2 * k, 2 * k); g.setColor(Color.lightGray); g.fillRect(0, 0, k, k); g.fillRect(k, k, k, k); checkerPaint = new TexturePaint(bi, new Rectangle(0, 0, bi.getWidth(), bi.getHeight())); } /** The "frame" includes the trackRect and possible some extra padding. * For example, the frame might be the rounded rectangle enclosing the * track (if rounded rectangles are turned on) * @return */ private Shape getFrame() { if (getProperty(slider, "GradientSlider.useBevel", "false").equals("true")) { return trackRect; } if (slider.getOrientation() == GradientSlider.HORIZONTAL) { int curve = Math.min(TRIANGLE_SIZE - 2, trackRect.height / 2); return new RoundRectangle2D.Float( trackRect.x - curve, trackRect.y, trackRect.width + 2 * curve, trackRect.height, curve * 2, curve * 2); } else { int curve = Math.min(TRIANGLE_SIZE - 2, trackRect.width / 2); return new RoundRectangle2D.Float( trackRect.x, trackRect.y - curve, trackRect.width, trackRect.height + 2 * curve, curve * 2, curve * 2); } } protected void paintTrack(Graphics2D g) { Composite oldComposite = g.getComposite(); float alpha = slider.isEnabled() ? 1 : .5f; g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); Shape frame = getFrame(); boolean alwaysOpaque = getProperty(slider, "GradientSlider.showTranslucency", "true").equals("false"); if (alwaysOpaque == false) { if (checkerPaint == null) { createCheckerPaint(); } g.setPaint(checkerPaint); g.fill(frame); } TexturePaint tp = new TexturePaint(img, new Rectangle(trackRect.x, 0, img.getWidth(), 1)); g.setPaint(tp); AffineTransform oldTransform = null; oldTransform = g.getTransform(); AffineTransform transform = new AffineTransform(); if (slider.getOrientation() == GradientSlider.VERTICAL) { if (slider.isInverted()) { transform.rotate(Math.PI / 2, trackRect.x, trackRect.y); } else { transform.rotate(-Math.PI / 2, trackRect.x, trackRect.y + trackRect.height); } } else { if (slider.isInverted()) { //flip horizontal: double x1 = trackRect.x; double x2 = trackRect.x + trackRect.width; //m00*x1+m02 = x2 //m00*x2+m02 = x1 double m00 = (x2 - x1) / (x1 - x2); double m02 = x1 - m00 * x2; transform.setTransform(m00, 0, 0, 1, m02, 0); } else { //no transform necessary } } g.transform(transform); try { g.fill(transform.createInverse().createTransformedShape(trackRect)); } catch (NoninvertibleTransformException e) { //this won't happen; unless a width/height //is zero somewhere, in which case we have nothing to paint anyway. } if (oldTransform != null) { g.setTransform(oldTransform); } if (getProperty(slider, "GradientSlider.useBevel", "false").equals("true")) { PaintUtils.drawBevel(g, trackRect); } else { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Shape oldClip = g.getClip(); int first, last; Color[] colors = ((GradientSlider) slider).getColors(); float[] f = slider.getThumbPositions(); if ((slider.isInverted() == false && slider.getOrientation() == GradientSlider.HORIZONTAL) || (slider.isInverted() == true && slider.getOrientation() == GradientSlider.VERTICAL)) { first = 0; last = colors.length - 1; while (f[first] < 0) { first++; } while (f[last] > 1) { last--; } } else { last = 0; first = colors.length - 1; while (f[last] < 0) { last++; } while (f[first] > 1) { first--; } } if (slider.getOrientation() == GradientSlider.HORIZONTAL) { g.clip(frame); g.setColor(colors[first]); g.fillRect(0, 0, trackRect.x, slider.getHeight()); g.setColor(colors[last]); g.fillRect(trackRect.x + trackRect.width, 0, slider.getWidth() - (trackRect.x + trackRect.width), slider.getHeight()); } else { g.clip(frame); g.setColor(colors[first]); g.fillRect(0, 0, slider.getWidth(), trackRect.y); g.setColor(colors[last]); g.fillRect(0, trackRect.y + trackRect.height, slider.getWidth(), slider.getHeight() - (trackRect.y + trackRect.height)); } g.setStroke(new BasicStroke(1)); g.setClip(oldClip); g.setColor(new Color(0, 0, 0, 130)); g.draw(frame); g.setColor(new Color(0, 0, 0, 130)); } if (slider.isPaintTicks()) { paintTick(g, .25f, 2); paintTick(g, .5f, 2); paintTick(g, .75f, 2); paintTick(g, 0f, 2); paintTick(g, 1f, 2); } g.setComposite(oldComposite); } protected void paintTick(Graphics2D g, float f, int d) { if (slider.getOrientation() == GradientSlider.HORIZONTAL) { int x = (int) (trackRect.x + trackRect.width * f + .5f); int y = trackRect.y + trackRect.height; g.drawLine(x, y, x, y + d); y = trackRect.y; g.drawLine(x, y, x, y - d); } else { int y = (int) (trackRect.y + trackRect.height * f + .5f); int x = trackRect.x + trackRect.width; g.drawLine(x, y, x + d, y); x = trackRect.x; g.drawLine(x, y, x - d, y); } } protected void paintFocus(Graphics2D g) { } static GeneralPath hTriangle = null; static GeneralPath vTriangle = null; protected void paintThumbs(Graphics2D g) { if (slider.isEnabled() == false) { return; } if (hTriangle == null) { hTriangle = new GeneralPath(); hTriangle.moveTo(0, 0); hTriangle.lineTo(TRIANGLE_SIZE, TRIANGLE_SIZE); hTriangle.lineTo(-TRIANGLE_SIZE, TRIANGLE_SIZE); hTriangle.lineTo(0, 0); hTriangle.closePath(); vTriangle = new GeneralPath(); vTriangle.moveTo(0, 0); vTriangle.lineTo(TRIANGLE_SIZE, TRIANGLE_SIZE); vTriangle.lineTo(TRIANGLE_SIZE, -TRIANGLE_SIZE); vTriangle.lineTo(0, 0); vTriangle.closePath(); } AffineTransform t = new AffineTransform(); int dx = trackRect.x + trackRect.width; int dy = trackRect.y + trackRect.height; dy -= trackRect.height / 6; dx -= trackRect.width / 6; int selected = slider.getSelectedThumb(false); float[] f = slider.getThumbPositions(); int orientation = slider.getOrientation(); Shape shape; Composite oldComposite = g.getComposite(); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, indication)); for (int a = 0; a < thumbPositions.length; a++) { if (f[a] >= 0 && f[a] <= 1 && a != selected) { if (orientation == GradientSlider.HORIZONTAL) { dx = thumbPositions[a]; shape = hTriangle; } else { dy = thumbPositions[a]; shape = vTriangle; } t.setToTranslation(dx, dy); g.transform(t); float brightness = Math.max(0, thumbIndications[a] * .6f); g.setColor(new Color((int) (255 * brightness), (int) (255 * brightness), (int) (255 * brightness))); g.fill(shape); g.translate(-.5f, -.5f); g.setColor(new Color(255, 255, 255)); g.draw(shape); g.translate(.5f, .5f); t.setToTranslation(-dx, -dy); g.transform(t); } } g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, indication)); if (selected != -1 && f[selected] >= 0 && f[selected] <= 1) { if (orientation == GradientSlider.HORIZONTAL) { dx = thumbPositions[selected]; shape = hTriangle; } else { dy = thumbPositions[selected]; shape = vTriangle; } t.setToTranslation(dx, dy); g.transform(t); g.setColor(new Color(255, 255, 255)); g.fill(shape); g.translate(-.5f, -.5f); g.setColor(new Color(0, 0, 0)); g.draw(shape); g.translate(.5f, .5f); t.setToTranslation(-dx, -dy); g.transform(t); } g.setComposite(oldComposite); } }