/*
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.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLayeredPane;
import javax.swing.SwingConstants;
import org.jdesktop.core.animation.timing.Animator;
import org.jdesktop.core.animation.timing.PropertySetter;
import org.jdesktop.core.animation.timing.TimingTarget;
import org.jdesktop.swingx.JXLabel;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.painter.MattePainter;
/**
* This class supports Labels in a front end UI on a TV.
*
* @author Doug Barnum
* @version 1.0
*/
public class LabelPanel 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.01;
private static final double HORIZONTAL_GAP = 0.01;
private int maxVisibleCount;
private int visibleCount;
private JXLabel leftTitleLabel;
private JXLabel rightTitleLabel;
private int orientation;
private double aspectRatio;
private JXLabel[] maxLabels;
private JXLabel[] labels;
private Animator[] animators;
private ArrayList<TextIcon> textIconList;
private TextIcon selectedTextIcon;
private int labelWidth;
private int labelHeight;
private MattePainter mattePainter;
private MattePainter highlightMattePainter;
private boolean selectedHighlighted;
/**
* Simple empty constructor.
*/
public LabelPanel() {
this(3, null);
}
/**
* Contructor where the visible labels can be controlled.
*
* @param count The label count.
*/
public LabelPanel(int count) {
this(count, null);
}
/**
* Contructor where the visible labels can be controlled.
*
* @param count The label count.
* @param title The title text.
*/
public LabelPanel(int count, String title) {
if ((count % 2) == 0) {
throw new RuntimeException("Have to be an odd number of labels.");
}
setSelectedHighlighted(true);
setMaxVisibleCount(count);
setVisibleCount(count);
setOrientation(SwingConstants.HORIZONTAL);
setAspectRatio(DEFAULT_ASPECT_RATIO);
setTextIconList(new ArrayList<TextIcon>());
MattePainter painter = new MattePainter(getPanelColor());
setMattePainter(painter);
Color hc = getHighlightColor();
Color highMatte = new Color(hc.getRed(), hc.getGreen(),
hc.getBlue(), (int) (getPanelAlpha() * 255));
MattePainter hpainter = new MattePainter(highMatte);
setHighlightMattePainter(hpainter);
JXLabel[] array = new JXLabel[count];
for (int i = 0; i < count; i++) {
array[i] = new JXLabel();
array[i].setFont(getSmallFont());
array[i].setVerticalTextPosition(SwingConstants.BOTTOM);
array[i].setHorizontalTextPosition(SwingConstants.CENTER);
array[i].setHorizontalAlignment(SwingConstants.CENTER);
array[i].setForeground(getSelectedColor());
}
setMaxLabels(array);
setLabels(array);
if (title != null) {
JXLabel ltitleLab = new JXLabel(title);
ltitleLab.setFont(getSmallFont());
ltitleLab.setTextRotation(JXLabel.VERTICAL_LEFT);
ltitleLab.setHorizontalTextPosition(SwingConstants.CENTER);
ltitleLab.setHorizontalAlignment(SwingConstants.CENTER);
ltitleLab.setForeground(getSelectedColor());
ltitleLab.setBackgroundPainter(getMattePainter());
setLeftTitleLabel(ltitleLab);
JXLabel rtitleLab = new JXLabel(title);
rtitleLab.setFont(getSmallFont());
rtitleLab.setTextRotation(JXLabel.VERTICAL_RIGHT);
rtitleLab.setHorizontalTextPosition(SwingConstants.CENTER);
rtitleLab.setHorizontalAlignment(SwingConstants.CENTER);
rtitleLab.setForeground(getSelectedColor());
rtitleLab.setBackgroundPainter(getMattePainter());
setRightTitleLabel(rtitleLab);
}
}
/**
* Sometimes one doesn't want the extra highlighting so this property
* allows it to be turned off. Default is True.
*
* @return True if highlighting is desired.
*/
public boolean isSelectedHighlighted() {
return (selectedHighlighted);
}
/**
* Sometimes one doesn't want the extra highlighting so this property
* allows it to be turned off. Default is True.
*
* @param b True if highlighting is desired.
*/
public void setSelectedHighlighted(boolean b) {
selectedHighlighted = b;
}
private int getMaxVisibleCount() {
return (maxVisibleCount);
}
private void setMaxVisibleCount(int i) {
maxVisibleCount = i;
}
private int getVisibleCount() {
return (visibleCount);
}
private void setVisibleCount(int i) {
visibleCount = i;
}
private JXLabel getLeftTitleLabel() {
return (leftTitleLabel);
}
private void setLeftTitleLabel(JXLabel l) {
leftTitleLabel = l;
}
private JXLabel getRightTitleLabel() {
return (rightTitleLabel);
}
private void setRightTitleLabel(JXLabel l) {
rightTitleLabel = l;
}
/**
* The layout orientation of this LabelPane. This must be set before
* adding to a container or it will be ignored.
*
* @return The orientation as an it (use SwingConstants.VERTICAL or
* SwingConstants.HORIZONTAL).
*/
public int getOrientation() {
return (orientation);
}
/**
* The layout orientation of this LabelPane. This must be set before
* adding to a container or it will be ignored.
*
* @param i The orientation as an it (use SwingConstants.VERTICAL or
* SwingConstants.HORIZONTAL).
*/
public void setOrientation(int i) {
orientation = i;
}
/**
* The layout aspect ratio of this LabelPane. This must be set before
* adding to a container or it will be ignored.
*
* @return The spect ratio, a value between 0-1.
*/
public double getAspectRatio() {
return (aspectRatio);
}
/**
* The layout aspect ratio of this LabelPane. This must be set before
* adding to a container or it will be ignored.
*
* @param d The spect ratio, a value between 0-1.
*/
public void setAspectRatio(double d) {
aspectRatio = d;
}
private JXLabel[] getMaxLabels() {
return (maxLabels);
}
private void setMaxLabels(JXLabel[] array) {
maxLabels = array;
}
private JXLabel[] getLabels() {
return (labels);
}
private void setLabels(JXLabel[] array) {
labels = array;
}
private Animator[] getAnimators() {
return (animators);
}
private void setAnimators(Animator[] array) {
animators = array;
}
/**
* The width in pixels of the Labels in our pane.
*
* @return The width as an int value.
*/
public int getLabelWidth() {
return (labelWidth);
}
private void setLabelWidth(int i) {
labelWidth = i;
}
/**
* The height in pixels of the Labels in our pane.
*
* @return The height as an int value.
*/
public int getLabelHeight() {
return (labelHeight);
}
private void setLabelHeight(int i) {
labelHeight = i;
}
private MattePainter getMattePainter() {
return (mattePainter);
}
private void setMattePainter(MattePainter p) {
mattePainter = p;
}
private MattePainter getHighlightMattePainter() {
return (highlightMattePainter);
}
private void setHighlightMattePainter(MattePainter p) {
highlightMattePainter = p;
}
/**
* The currently selected TextIcon.
*
* @return A TextIcon instance.
*/
public TextIcon getSelectedTextIcon() {
return (selectedTextIcon);
}
/**
* The currently selected TextIcon.
*
* @param ti A TextIcon instance.
*/
public void setSelectedTextIcon(TextIcon ti) {
TextIcon old = selectedTextIcon;
selectedTextIcon = ti;
firePropertyChange("SelectedTextIcon", old, ti);
}
/**
* {@inheritDoc}
*/
public void performControl() {
JXLabel ltlabel = getLeftTitleLabel();
JXLabel rtlabel = getRightTitleLabel();
if ((ltlabel != null) && (rtlabel != null)) {
if (isControl()) {
setBorder(BorderFactory.createLineBorder(getHighlightColor()));
ltlabel.setForeground(getHighlightColor());
rtlabel.setForeground(getHighlightColor());
} else {
setBorder(BorderFactory.createLineBorder(getUnselectedColor()));
ltlabel.setForeground(getUnselectedColor());
rtlabel.setForeground(getUnselectedColor());
}
}
}
/**
* {@inheritDoc}
*/
public void performLayout(Dimension d) {
JXLabel[] labs = getLabels();
JLayeredPane pane = getLayeredPane();
if ((d != null) && (labs != null) && (pane != null)) {
double width = d.getWidth();
double height = d.getHeight();
int count = getVisibleCount();
if (orientation == SwingConstants.HORIZONTAL) {
double[] xarray = new double[labs.length];
double center = width / 2.0;
double vgap = height * VERTICAL_GAP;
double labheight = height - (2 * vgap);
double labwidth = height * getAspectRatio();
double labspan = labwidth + (width * HORIZONTAL_GAP);
int midIndex = count / 2;
double ref = center - (labwidth / 2.0);
xarray[midIndex] = ref;
for (int i = midIndex - 1; i >= 0; i--) {
xarray[i] = ref - labspan;
ref -= labspan;
}
ref = xarray[midIndex];
for (int i = midIndex + 1; i < xarray.length; i++) {
xarray[i] = ref + labspan;
ref += labspan;
}
// Set the Image size, fudging a bit.
setLabelWidth((int) (labwidth - 20.0));
setLabelHeight((int) (labheight - 20.0));
Animator[] anis = new Animator[labs.length];
double y = (height - labheight) / 2.0;
for (int i = 0; i < labs.length; i++) {
JXPanel labelPanel = new JXPanel(new BorderLayout());
labelPanel.add(labs[i], BorderLayout.CENTER);
labelPanel.setOpaque(false);
labelPanel.setBounds((int) xarray[i], (int) y,
(int) labwidth, (int) labheight);
pane.add(labelPanel, Integer.valueOf(100));
TimingTarget tt = PropertySetter.getTarget(labelPanel,
"alpha", Float.valueOf(0.0f),
Float.valueOf((float) getPanelAlpha()));
anis[i] = new Animator.Builder().setDuration(750,
TimeUnit.MILLISECONDS).addTarget(tt).build();
}
setAnimators(anis);
JXLabel ltitleLab = getLeftTitleLabel();
if (ltitleLab != null) {
ltitleLab.setBounds(2, 2, 50, (int) labheight);
pane.add(ltitleLab, Integer.valueOf(100));
}
JXLabel rtitleLab = getRightTitleLabel();
if (rtitleLab != null) {
rtitleLab.setBounds((int) (width - 50 - 2), 2, 50,
(int) labheight);
pane.add(rtitleLab, Integer.valueOf(100));
}
} else {
double[] yarray = new double[labs.length];
double center = width / 2.0;
double vgap = height * VERTICAL_GAP;
double labheight = height - (2 * vgap);
double labwidth = height * getAspectRatio();
double labspan = labheight + (height * VERTICAL_GAP);
int midIndex = count / 2;
double ref = center - (labheight / 2.0);
yarray[midIndex] = ref;
for (int i = midIndex - 1; i >= 0; i--) {
yarray[i] = ref - labspan;
ref -= labspan;
}
ref = yarray[midIndex];
for (int i = midIndex + 1; i < yarray.length; i++) {
yarray[i] = ref + labspan;
ref += labspan;
}
// Set the Image size, fudging a bit.
setLabelWidth((int) (labwidth - 20.0));
setLabelHeight((int) (labheight - 20.0));
Animator[] anis = new Animator[labs.length];
double x = (width - labwidth) / 2.0;
for (int i = 0; i < labs.length; i++) {
JXPanel labelPanel = new JXPanel(new BorderLayout());
labelPanel.add(labs[i], BorderLayout.CENTER);
labelPanel.setOpaque(false);
labelPanel.setBounds((int) x, (int) yarray[i],
(int) labwidth, (int) labheight);
pane.add(labelPanel, Integer.valueOf(100));
TimingTarget tt = PropertySetter.getTarget(labelPanel,
"alpha", Float.valueOf(0.0f),
Float.valueOf((float) getPanelAlpha()));
anis[i] = new Animator.Builder().setDuration(750,
TimeUnit.MILLISECONDS).addTarget(tt).build();
}
setAnimators(anis);
JXLabel ltitleLab = getLeftTitleLabel();
if (ltitleLab != null) {
ltitleLab.setBounds(2, 2, 50, (int) labheight);
pane.add(ltitleLab, Integer.valueOf(100));
}
JXLabel rtitleLab = getRightTitleLabel();
if (rtitleLab != null) {
rtitleLab.setBounds((int) (width - 50 - 2), 2, 50,
(int) labheight);
pane.add(rtitleLab, Integer.valueOf(100));
}
}
}
}
private ArrayList<TextIcon> getTextIconList() {
return (textIconList);
}
private void setTextIconList(ArrayList<TextIcon> l) {
textIconList = l;
}
/**
* Acquire the defined TextIcon objects currently being displayed.
*
* @return An array of TextIcon instances.
*/
public TextIcon[] getTextIcons() {
TextIcon[] result = null;
ArrayList<TextIcon> l = getTextIconList();
if ((l != null) && (l.size() > 0)) {
result = l.toArray(new TextIcon[l.size()]);
}
return (result);
}
/**
* The TextIcon instances displayed are supplied here.
*
* @param array An array of TextIcon objects.
*/
public void setTextIcons(TextIcon[] array) {
if (array == null) {
throw new RuntimeException("Don't send null TextIcon array");
}
// First make sure we reset our count and array.
setVisibleCount(getMaxVisibleCount());
setLabels(getMaxLabels());
JXLabel[] maxlabs = getMaxLabels();
for (int i = 0; i < maxlabs.length; i++) {
maxlabs[i].setText("");
maxlabs[i].setIcon(null);
maxlabs[i].setBackgroundPainter(null);
}
repaint();
if (array.length < getMaxVisibleCount()) {
// Crap not enough icons to fill our defined max.
int count = getMaxVisibleCount();
boolean done = false;
while (!done) {
count--;
if (count < 1) {
throw new RuntimeException("Not enough TextIcon to show");
} else if (array.length >= count) {
setVisibleCount(count);
JXLabel[] oldlabs = getMaxLabels();
JXLabel[] newlabs = new JXLabel[count];
if (count > 1) {
int start = count / 2;
if ((start + count) >= oldlabs.length) {
start--;
}
for (int i = 0; i < newlabs.length; i++) {
newlabs[i] = oldlabs[start++];
}
} else {
newlabs[0] = oldlabs[getMaxVisibleCount() / 2];
}
setLabels(newlabs);
done = true;
}
}
}
ArrayList<TextIcon> l = getTextIconList();
if (l != null) {
l.clear();
for (int i = 0; i < array.length; i++) {
adjust(array[i]);
l.add(array[i]);
}
}
// We start in the middle so we have to move the first item to it.
int moves = centerIndex();
for (int i = 0; i < moves; i++) {
TextIcon last = l.remove(l.size() - 1);
l.add(0, last);
}
if ((moves >= 0) && (moves < l.size())) {
setSelectedTextIcon(l.get(moves));
}
update();
}
private void update() {
ArrayList<TextIcon> l = getTextIconList();
JXLabel[] labs = getLabels();
Animator[] anis = getAnimators();
if ((l != null) && (labs != null) && (anis != null)) {
for (int i = 0; i < anis.length; i++) {
if (anis[i].isRunning()) {
anis[i].stop();
}
}
int mid = centerIndex();
for (int i = 0; i < labs.length; i++) {
if (i == mid) {
Icon icon = l.get(i).getSelectedIcon();
if (icon != null) {
labs[i].setIcon(icon);
} else {
labs[i].setIcon(l.get(i).getIcon());
}
if (isSelectedHighlighted()) {
labs[i].setBackgroundPainter(
getHighlightMattePainter());
}
} else {
if (isSelectedHighlighted()) {
labs[i].setIcon(l.get(i).getIcon());
labs[i].setBackgroundPainter(getMattePainter());
}
}
if (labs[i].getIcon() == null) {
labs[i].setText(l.get(i).getText());
}
}
for (int i = 0; i < anis.length; i++) {
anis[i].start();
}
}
}
private int centerIndex() {
int result = getVisibleCount() / 2;
// If out visible count is an EVEN number then we have to subtract
// one. Well unless our count is 2.
if (((getVisibleCount() % 2) == 0) && (getVisibleCount() != 2)) {
result--;
}
return (result);
}
private void adjust(TextIcon ti) {
if (ti != null) {
ImageIcon ii = (ImageIcon) ti.getIcon();
if (ii != null) {
Image image = ii.getImage();
if (image.getWidth(this) > getLabelWidth()) {
ii.setImage(image.getScaledInstance(getLabelWidth(),
getLabelHeight(), Image.SCALE_DEFAULT));
}
}
}
}
/**
* Go to the previous TextIcon.
*/
public void prev() {
ArrayList<TextIcon> l = getTextIconList();
if (l != null) {
TextIcon last = l.remove(l.size() - 1);
l.add(0, last);
setSelectedTextIcon(l.get(centerIndex()));
update();
}
}
/**
* Go to the next TextIcon.
*/
public void next() {
ArrayList<TextIcon> l = getTextIconList();
if (l != null) {
TextIcon first = l.remove(0);
l.add(first);
setSelectedTextIcon(l.get(centerIndex()));
update();
}
}
}