/*
This file is part of JFLICKS.
JFLICKS 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.
JFLICKS 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 JFLICKS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jflicks.ui.view.fe;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import javax.swing.JLayeredPane;
import org.jdesktop.core.animation.timing.Animator;
import org.jdesktop.core.animation.timing.TimingTarget;
import org.jdesktop.core.animation.timing.TimingTargetAdapter;
import org.jdesktop.core.animation.timing.PropertySetter;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.jdesktop.swingx.painter.MattePainter;
import org.jflicks.nms.Video;
import org.jflicks.ui.view.fe.BaseCustomizePanel;
import org.jflicks.util.Util;
/**
* This is a display of video posters in a panel.
*
* @author Doug Barnum
* @version 1.0
*/
public class PosterPanel extends BaseCustomizePanel {
/**
* The default aspect ratio is 0.68 which will size each to the shape
* of a movie poster in portrait mode.
*/
public static final double DEFAULT_ASPECT_RATIO = 0.68;
private static final double VERTICAL_GAP = 0.05;
private static final double HORIZONTAL_GAP = 0.05;
private static final int EXTRA_PIXEL = 10;
private int visibleCount;
private Point[] anchorPoints;
private Point[] positionPoints;
private Animator[] animators;
private ImageTimingTarget[] imageTimingTargets;
private ArrayList<Video> videoList;
private ArrayList<BufferedImage> bufferedImageList;
private BufferedImage[] drawImages;
private BufferedImage centerImage;
private double growCount;
private Point centerImagePoint;
private Point originalCenterImagePoint;
private Animator springAnimator;
private Video selectedVideo;
private int posterWidth;
private int posterHeight;
private double aspectRatio;
/**
* Simple empty constructor.
*/
public PosterPanel() {
this(3);
}
/**
* Constructor with the number of desired visible posters.
*
* @param count The number of visible posters displayed at once.
*/
public PosterPanel(int count) {
setDoubleBuffered(true);
setVisibleCount(count);
setAspectRatio(DEFAULT_ASPECT_RATIO);
setVideoList(new ArrayList<Video>());
setBufferedImageList(new ArrayList<BufferedImage>());
}
public PosterPanel(int count, double aspect) {
this(count);
setAspectRatio(aspect);
}
/**
* We act upon a set of Video instances to allow the user
* to select.
*
* @return An array of Video objects.
*/
public Video[] getVideos() {
Video[] result = null;
ArrayList<Video> l = getVideoList();
if ((l != null) && (l.size() > 0)) {
result = l.toArray(new Video[l.size()]);
}
return (result);
}
/**
* We act upon a set of Video instances to allow the user
* to select.
*
* @param array An array of Video objects.
*/
public void setVideos(Video[] array) {
ArrayList<Video> l = getVideoList();
if (l != null) {
l.clear();
if (array != null) {
for (int i = 0; i < array.length; i++) {
l.add(array[i]);
}
setSelectedVideo(l.get(0));
}
}
}
/**
* We have a open method so the glow animation can be started when
* we are to be displayed.
*/
public void open() {
}
/**
* We have a close method so the glow animation can be stopped.
*/
public void close() {
}
/**
* We act upon a set of BufferedImage instances to display
* posters.
*
* @return An array of BufferedImage objects.
*/
public BufferedImage[] getBufferedImages() {
BufferedImage[] result = null;
ArrayList<BufferedImage> l = getBufferedImageList();
if ((l != null) && (l.size() > 0)) {
result = l.toArray(new BufferedImage[l.size()]);
}
return (result);
}
/**
* We act upon a set of BufferedImage instances to display
* posters.
*
* @param array An array of BufferedImage objects.
*/
public void setBufferedImages(BufferedImage[] array) {
ArrayList<BufferedImage> l = getBufferedImageList();
if (l != null) {
l.clear();
if (array != null) {
for (int i = 0; i < array.length; i++) {
l.add(array[i]);
}
BufferedImage bi = GraphicsUtilities.toCompatibleImage(
array[0]);
setCenterImage(bi);
setCenterImagePoint(getOriginalCenterImagePoint());
SpringTimingTarget stt = new SpringTimingTarget(getGrowCount(),
bi.getWidth(), bi.getHeight());
Animator sani = new Animator.Builder().setDuration(250,
TimeUnit.MILLISECONDS).addTarget(stt).build();
setSpringAnimator(sani);
sani.start();
}
}
}
private BufferedImage getCenterImage() {
return (centerImage);
}
private void setCenterImage(BufferedImage bi) {
centerImage = bi;
}
private Point getOriginalCenterImagePoint() {
return (originalCenterImagePoint);
}
private void setOriginalCenterImagePoint(Point p) {
originalCenterImagePoint = p;
}
private double getGrowCount() {
return (growCount);
}
private void setGrowCount(double d) {
growCount = d;
}
private Point getCenterImagePoint() {
return (centerImagePoint);
}
private void setCenterImagePoint(Point p) {
centerImagePoint = p;
}
private Animator getSpringAnimator() {
return (springAnimator);
}
private void setSpringAnimator(Animator a) {
springAnimator = a;
}
/**
* The currently selected Video.
*
* @return A Video instance.
*/
public Video getSelectedVideo() {
return (selectedVideo);
}
/**
* The currently selected Video.
*
* @param v A Video instance.
*/
public void setSelectedVideo(Video v) {
Video old = selectedVideo;
selectedVideo = v;
firePropertyChange("SelectedVideo", old, v);
}
/**
* {@inheritDoc}
*/
public void performControl() {
}
/**
* {@inheritDoc}
*/
public void performLayout(Dimension d) {
JLayeredPane pane = getLayeredPane();
if ((d != null) && (pane != null)) {
double width = d.getWidth();
double height = d.getHeight();
int count = getVisibleCount();
double hgap = width * HORIZONTAL_GAP;
double vgap = height * VERTICAL_GAP;
double labwidth = (width - (((count + 1) * hgap))) / count;
double labheight = labwidth + (labwidth * (1.0 - getAspectRatio()));
double labspan = labwidth + hgap;
double labtop = (height - labheight) / 4;
setPosterWidth((int) labwidth);
setPosterHeight((int) labheight);
// Do the background.
Color color = getPanelColor();
color = new Color(color.getRed(), color.getGreen(),
color.getBlue(), (int) (getPanelAlpha() * 255));
MattePainter mpainter = new MattePainter(color);
setBackgroundPainter(mpainter);
setAlpha((float) getPanelAlpha());
// When NOT animating we need to have defined points
// that represent "anchor" points. These points are
// when the images are when not animating. The also
// should help us know when the animation is over
// because the destination has been reached. We keep
// two extra for the images off the edge on both sides.
Point[] array = new Point[count + 2];
for (int i = 0; i < array.length; i++) {
array[i] = new Point();
array[i].y = (int) labtop;
if (i == 0) {
// Our point is off on the left.
array[i].x = (int) (labspan * -1.0);
} else if ((i + 1) == array.length) {
// Our point is off on the right.
array[i].x = (int) (width + hgap);
} else {
// We can see our image.
array[i].x = (int) ((labspan * (i - 1)) + hgap);
}
}
setAnchorPoints(array);
// At any one point we should be (at most) animating count + 2
// posters. Worse case is that the fourth is clipped.
Point[] parray = new Point[array.length];
for (int i = 0; i < parray.length; i++) {
parray[i] = new Point(array[i]);
}
setPositionPoints(parray);
Animator[] anis = new Animator[array.length];
ImageTimingTarget[] itts = new ImageTimingTarget[array.length];
for (int i = 0; i < anis.length; i++) {
int leftIndex = i - 1;
int rightIndex = i + 1;
Point start = array[i];
Point left = null;
if (leftIndex >= 0) {
left = array[leftIndex];
}
Point right = null;
if (rightIndex < array.length) {
right = array[rightIndex];
}
itts[i] = new ImageTimingTarget(start, left, right, parray[i]);
anis[i] = new Animator.Builder().setDuration(250,
TimeUnit.MILLISECONDS).addTarget(itts[i]).build();
if (i == (array.length / 2)) {
setOriginalCenterImagePoint(new Point(start));
setCenterImagePoint(new Point(start));
setGrowCount(labtop - 2);
anis[i].addTarget(
new ShiftTimingTarget(getGrowCount(), itts[i]));
}
}
setImageTimingTargets(itts);
setAnimators(anis);
}
}
private Point[] getPositionPoints() {
return (positionPoints);
}
private void setPositionPoints(Point[] array) {
positionPoints = array;
}
private Point[] getAnchorPoints() {
return (anchorPoints);
}
private void setAnchorPoints(Point[] array) {
anchorPoints = array;
}
private ImageTimingTarget[] getImageTimingTargets() {
return (imageTimingTargets);
}
private void setImageTimingTargets(ImageTimingTarget[] array) {
imageTimingTargets = array;
}
private Animator[] getAnimators() {
return (animators);
}
private void setAnimators(Animator[] array) {
animators = array;
}
private int getVisibleCount() {
return (visibleCount);
}
private void setVisibleCount(int i) {
visibleCount = i;
}
private double getAspectRatio() {
return (aspectRatio);
}
private void setAspectRatio(double d) {
aspectRatio = d;
}
/**
* We set our poster to a particular width.
*
* @return The width in pixels as an int.
*/
public int getPosterWidth() {
return (posterWidth);
}
private void setPosterWidth(int i) {
posterWidth = i;
}
/**
* We set our poster to a particular height.
*
* @return The height in pixels as an int.
*/
public int getPosterHeight() {
return (posterHeight);
}
private void setPosterHeight(int i) {
posterHeight = i;
}
private ArrayList<Video> getVideoList() {
return (videoList);
}
private void setVideoList(ArrayList<Video> l) {
videoList = l;
}
private ArrayList<BufferedImage> getBufferedImageList() {
return (bufferedImageList);
}
private void setBufferedImageList(ArrayList<BufferedImage> l) {
bufferedImageList = l;
}
private BufferedImage[] getDrawImages() {
BufferedImage[] result = null;
if (drawImages != null) {
result = drawImages;
} else {
result = getCurrentBufferedImages();
}
return (result);
}
private void setDrawImages(BufferedImage[] array) {
drawImages = array;
}
private BufferedImage[] getCurrentBufferedImages() {
BufferedImage[] result = null;
ArrayList<BufferedImage> l = getBufferedImageList();
if (l != null) {
result = new BufferedImage[getVisibleCount() + 2];
if (getVisibleCount() > l.size()) {
int index = (result.length / 2);
for (int i = 0; i < l.size(); i++) {
result[index++] = l.get(i);
}
} else {
// We have at least one circle....
int index = l.size() - (result.length / 2);
for (int i = 0; i < result.length; i++) {
if ((index >= 0) && (index < l.size())) {
result[i] = l.get(index);
}
index++;
if (index == l.size()) {
index = 0;
}
}
}
}
return (result);
}
private void stopAll() {
Animator[] array = getAnimators();
if (array != null) {
for (int i = 0; i < array.length; i++) {
array[i].stop();
}
}
Animator sani = getSpringAnimator();
if (sani != null) {
sani.stop();
}
setCenterImage(null);
}
private void startAll() {
Animator[] array = getAnimators();
if (array != null) {
for (int i = 0; i < array.length; i++) {
array[i].start();
}
}
}
private void applyDirection(boolean left) {
ImageTimingTarget[] array = getImageTimingTargets();
if (array != null) {
for (int i = 0; i < array.length; i++) {
array[i].setGoLeft(left);
}
}
}
private boolean shouldAnimate() {
boolean result = false;
ArrayList<BufferedImage> l = getBufferedImageList();
if (l != null) {
result = l.size() >= getVisibleCount();
}
return (result);
}
/**
* Override so we can paint our images.
*
* @param g A given Graphics object.
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
// We have to paint our images...
Graphics2D g2d = (Graphics2D) g.create();
Point[] array = getPositionPoints();
BufferedImage[] images = getDrawImages();
if ((array != null) && (images != null)
&& (array.length == images.length)) {
int center = array.length / 2;
for (int i = 0; i < array.length; i++) {
if (images[i] != null) {
if (i == center) {
BufferedImage bi = getCenterImage();
Point p = getCenterImagePoint();
if ((bi != null) && (p != null)) {
g2d.drawImage(bi, p.x, p.y, null);
} else {
g2d.drawImage(images[i], array[i].x, array[i].y,
null);
}
} else {
// Just draw normally...
g2d.drawImage(images[i], array[i].x, array[i].y, null);
}
}
}
} else {
BufferedImage bi = getCenterImage();
Point p = getCenterImagePoint();
if ((bi != null) && (p != null)) {
g2d.drawImage(bi, p.x, p.y, null);
}
}
}
/**
* Go to the next video.
*/
public void next() {
stopAll();
if (shouldAnimate()) {
setDrawImages(getCurrentBufferedImages());
applyDirection(false);
startAll();
}
ArrayList<BufferedImage> l = getBufferedImageList();
ArrayList<Video> vl = getVideoList();
if ((l != null) && (vl != null)) {
BufferedImage last = l.remove(l.size() - 1);
l.add(0, last);
Video vlast = vl.remove(vl.size() - 1);
vl.add(0, vlast);
setSelectedVideo(vl.get(0));
if (!shouldAnimate()) {
BufferedImage bi = GraphicsUtilities.toCompatibleImage(
l.get(0));
setCenterImage(bi);
setCenterImagePoint(getOriginalCenterImagePoint());
SpringTimingTarget stt = new SpringTimingTarget(getGrowCount(),
bi.getWidth(), bi.getHeight());
Animator sani = new Animator.Builder().setDuration(250,
TimeUnit.MILLISECONDS).addTarget(stt).build();
setSpringAnimator(sani);
sani.start();
}
}
}
/**
* Go to the previous video.
*/
public void prev() {
stopAll();
if (shouldAnimate()) {
setDrawImages(getCurrentBufferedImages());
applyDirection(true);
startAll();
}
ArrayList<BufferedImage> l = getBufferedImageList();
ArrayList<Video> vl = getVideoList();
if ((l != null) && (vl != null)) {
BufferedImage first = l.remove(0);
l.add(first);
Video vfirst = vl.remove(0);
vl.add(vfirst);
setSelectedVideo(vl.get(0));
if (!shouldAnimate()) {
BufferedImage bi = GraphicsUtilities.toCompatibleImage(
l.get(0));
setCenterImage(bi);
setCenterImagePoint(getOriginalCenterImagePoint());
SpringTimingTarget stt = new SpringTimingTarget(getGrowCount(),
bi.getWidth(), bi.getHeight());
Animator sani = new Animator.Builder().setDuration(250,
TimeUnit.MILLISECONDS).addTarget(stt).build();
setSpringAnimator(sani);
sani.start();
}
}
}
private BufferedImage createCompatibleImage(BufferedImage bi) {
BufferedImage result = null;
if (bi != null) {
GraphicsConfiguration gc =
GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
if (gc != null) {
int w = bi.getWidth();
int h = bi.getHeight();
int t = bi.getTransparency();
result = gc.createCompatibleImage(w, h, t);
}
}
return (result);
}
class ShiftTimingTarget extends TimingTargetAdapter {
private ImageTimingTarget imageTimingTarget;
private double growCount;
public ShiftTimingTarget(double grow, ImageTimingTarget itt) {
imageTimingTarget = itt;
growCount = grow;
}
public void end(Animator source) {
// Now we want to Spring the centerImage.
BufferedImage[] array = getCurrentBufferedImages();
if (array != null) {
BufferedImage bi = GraphicsUtilities.toCompatibleImage(
array[array.length / 2]);
setCenterImage(bi);
setCenterImagePoint(getOriginalCenterImagePoint());
SpringTimingTarget stt = new SpringTimingTarget(growCount,
bi.getWidth(), bi.getHeight());
Animator sani = new Animator.Builder().setDuration(250,
TimeUnit.MILLISECONDS).addTarget(stt).build();
setSpringAnimator(sani);
sani.start();
}
}
}
class SpringTimingTarget extends TimingTargetAdapter {
private Point savePoint;
private Point workPoint;
private double growCount;
private int width;
private int height;
public SpringTimingTarget(double grow, int w, int h) {
savePoint = new Point(getOriginalCenterImagePoint());
growCount = grow;
width = w;
height = h;
}
public void timingEvent(Animator source, double fraction) {
int pixels = (int) (growCount * fraction);
workPoint = new Point(savePoint);
workPoint.x = workPoint.x - pixels;
workPoint.y = workPoint.y - pixels;
setCenterImagePoint(workPoint);
int newwidth = width + (2 * pixels);
int newheight = height + (2 * pixels);
BufferedImage cbi = getCenterImage();
if (cbi != null) {
cbi = Util.resize(cbi, newwidth, newheight);
setCenterImage(cbi);
}
repaint();
}
}
class ImageTimingTarget extends TimingTargetAdapter {
private Point start;
private Point left;
private Point right;
private Point current;
private boolean goLeft;
public ImageTimingTarget(Point start, Point left, Point right,
Point current) {
setStart(start);
setLeft(left);
setRight(right);
setCurrent(current);
}
public boolean isGoLeft() {
return (goLeft);
}
public void setGoLeft(boolean b) {
goLeft = b;
}
public Point getStart() {
return (start);
}
private void setStart(Point p) {
start = p;
}
private Point getLeft() {
return (left);
}
private void setLeft(Point p) {
left = p;
}
private Point getRight() {
return (right);
}
private void setRight(Point p) {
right = p;
}
public Point getCurrent() {
return (current);
}
private void setCurrent(Point p) {
current = p;
}
public void end(Animator source) {
current.x = start.x;
current.y = start.y;
setDrawImages(null);
}
public void timingEvent(Animator source, double fraction) {
if (isGoLeft()) {
if ((current != null) && (left != null) && (start != null)) {
current.x = (int) (start.x + (left.x - start.x) * fraction);
}
} else {
if ((current != null) && (right != null) && (start != null)) {
current.x =
(int) (start.x + (right.x - start.x) * fraction);
}
}
repaint();
}
}
}