/* 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.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; import java.util.concurrent.TimeUnit; import javax.swing.BorderFactory; import javax.swing.JLayeredPane; import javax.swing.SwingConstants; import org.jdesktop.core.animation.timing.Animator; import org.jdesktop.core.animation.timing.KeyFrames; import org.jdesktop.core.animation.timing.PropertySetter; import org.jdesktop.core.animation.timing.TimingTarget; import org.jdesktop.swingx.JXLabel; import org.jdesktop.swingx.painter.MattePainter; /** * This is a base list panel class that handles all the drawing and * updating of the list. Extensions concentrate on laying themselves * out and handling their own objects. * * @author Doug Barnum * @version 1.0 */ public abstract class BaseListPanel extends BaseCustomizePanel { protected static final double HGAP = 0.02; protected static final double VGAP = 0.02; private JXLabel[] labels; private int visibleCount; private int selectedIndex; private int oldSelectedIndex; private int startIndex; private Animator[] animators; private Object selectedObject; private Object pageModeSelectedObject; private String propertyName; private boolean pageMode; /** * Extensions need to make their objects displayed in the list available * to this base class so it can draw and maintain the list properly. * * @return An array of Objects. */ public abstract Object[] getObjects(); /** * Simple empty constructor. */ public BaseListPanel() { setStartIndex(0); setOldSelectedIndex(-1); setSelectedIndex(0); } protected JXLabel[] getLabels() { return (labels); } protected void setLabels(JXLabel[] array) { labels = array; } protected Animator[] getAnimators() { return (animators); } protected void setAnimators(Animator[] array) { animators = array; } protected Object getSelectedObject() { return (selectedObject); } protected void setSelectedObject(Object o) { Object old = selectedObject; selectedObject = o; if (!isPageMode()) { firePropertyChange(getPropertyName(), old, selectedObject); } else { pageModeSelectedObject = old; } } protected String getPropertyName() { return (propertyName); } protected void setPropertyName(String s) { propertyName = s; } private boolean isPageMode() { return (pageMode); } private void setPageMode(boolean b) { pageMode = b; if (!pageMode) { firePropertyChange(getPropertyName(), pageModeSelectedObject, selectedObject); } } private int getCount() { int result = 0; Object[] array = getObjects(); if (array != null) { result = array.length; } return (result); } /** * Convenience method to find the general height any particular * JXLabel would need to be using our large Font property. * * @return A double value. */ public double getMaxHeight() { double result = 0.0; JXLabel tst = new JXLabel("TEST IT DUDE"); tst.setFont(getLargeFont()); tst.setHorizontalTextPosition(SwingConstants.CENTER); tst.setHorizontalAlignment(SwingConstants.LEFT); Dimension fd = tst.getPreferredSize(); if (fd != null) { result = fd.getHeight(); } return (result); } /** * Compute the max width that is needed so all the given String * instances will display correctly given our current large Font * property. * * @param array Need some String instances to check. * @return A double value. */ public double getMaxWidth(String[] array) { double result = 0.0; double fudge = 12; if ((array != null) && (array.length > 0)) { JXLabel tst = new JXLabel(); tst.setFont(getLargeFont()); tst.setHorizontalTextPosition(SwingConstants.CENTER); tst.setHorizontalAlignment(SwingConstants.LEFT); Insets in = tst.getInsets(); if (in != null) { fudge += (double) (in.left + in.right); } for (int i = 0; i < array.length; i++) { tst.setText(array[i]); Dimension fd = tst.getPreferredSize(); if (fd != null) { double tmp = fd.getWidth(); if (tmp > result) { result = tmp; } } } } return (result + fudge); } /** * {@inheritDoc} */ public void performControl() { applyColor(); } /** * {@inheritDoc} */ public void performLayout(Dimension d) { JLayeredPane pane = getLayeredPane(); if ((d != null) && (pane != null)) { // This most likely our screen size... double width = d.getWidth(); double height = d.getHeight(); // Compute our gaps... double hgap = width * HGAP; double vgap = height * VGAP; double realHeight = height - (vgap * 2.0); double labelMaxHeight = getMaxHeight(); int vcount = (int) (realHeight / labelMaxHeight); JXLabel[] array = new JXLabel[vcount]; for (int i = 0; i < vcount; i++) { array[i] = new JXLabel(); if (i == 0) { array[i].setFont(getLargeFont()); } else { array[i].setFont(getSmallFont()); } array[i].setHorizontalTextPosition(SwingConstants.CENTER); array[i].setHorizontalAlignment(SwingConstants.LEFT); } setVisibleCount(vcount); setLabels(array); // 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()); FontEvaluator feval = new FontEvaluator(getSmallFont(), getLargeFont(), getSmallFontSize(), getLargeFontSize()); KeyFrames.Builder<Font> kfb = new KeyFrames.Builder<Font>(); kfb.addFrames(feval.getFonts()); kfb.setEvaluator(feval); KeyFrames<Font> fontFrames = kfb.build(); Animator[] anis = new Animator[array.length]; double center = (realHeight - (vcount * labelMaxHeight)) / 2.0; double top = vgap + center; for (int i = 0; i < array.length; i++) { array[i].setBounds((int) hgap, (int) top, (int) width, (int) labelMaxHeight); pane.add(array[i], Integer.valueOf(110)); top += labelMaxHeight; TimingTarget tt = PropertySetter.getTarget(array[i], "font", fontFrames); anis[i] = new Animator.Builder().setDuration(250, TimeUnit.MILLISECONDS).addTarget(tt).build(); } setAnimators(anis); } } /** * Move the group of labels down a page. */ public void movePageUp() { int count = getVisibleCount() - 1; if (count > 0) { setPageMode(true); for (int i = 0; i < count; i++) { moveUp(); } setPageMode(false); update(); } } /** * Move the group of labels down a page. */ public void movePageDown() { int count = getVisibleCount() - 1; if (count > 0) { setPageMode(true); for (int i = 0; i < count; i++) { moveDown(); } setPageMode(false); update(); } } /** * Move the group of labels down one. */ public void moveDown() { Object[] array = getObjects(); if (array != null) { if (isWindowGreaterOrEqual()) { int selected = getSelectedIndex(); if ((selected + 1) < array.length) { setSelectedIndex(selected + 1); } else { setSelectedIndex(0); } } else { // We have more channels that can fit in the window. // First see if we can just move the selection. if (!isSelectedAtTheBottomWindow()) { // Then we can just update the index and be done. int selected = getSelectedIndex(); setSelectedIndex(selected + 1); } else { // Ok we have to increment our start and leave the // selected at the bottom as long as we haven't // reached the end of the list. if (!isSelectedAtTheBottomList()) { int start = getStartIndex(); setStartIndex(start + 1); } else { // No where to go down. We want to reset to the // top of the list. setStartIndex(0); setSelectedIndex(0); } } } } } /** * Move the group of labels up one. */ public void moveUp() { Object[] array = getObjects(); if (array != null) { if (isWindowGreaterOrEqual()) { int selected = getSelectedIndex(); if ((selected - 1) >= 0) { setSelectedIndex(selected - 1); } else { setSelectedIndex(array.length - 1); } } else { // We have more channels that can fit in the window. // First see if we can just move the selection. if (!isSelectedAtTheTopWindow()) { // Then we can just update the index and be done. int selected = getSelectedIndex(); setSelectedIndex(selected - 1); } else { // Ok we have to decrement our start and leave the // selected at the top as long as we haven't // reached the top of the list. if (!isSelectedAtTheTopList()) { int start = getStartIndex(); setStartIndex(start - 1); } else { // No where to go down. We want to reset to the // top of the list. setStartIndex(array.length - getVisibleCount()); setSelectedIndex(getVisibleCount() - 1); } } } } } protected void applyColor() { if (isControl()) { setBorder(BorderFactory.createLineBorder(getHighlightColor())); } else { setBorder(BorderFactory.createLineBorder(getUnselectedColor())); } JXLabel[] array = getLabels(); if (array != null) { for (int i = 0; i < array.length; i++) { if (isControl()) { if (i == getSelectedIndex()) { array[i].setForeground(getHighlightColor()); } else { array[i].setForeground(getUnselectedColor()); } } else { if (i == getSelectedIndex()) { array[i].setForeground(getSelectedColor()); } else { array[i].setForeground(getUnselectedColor()); } } } } } protected void animate() { JXLabel[] array = getLabels(); Animator[] anis = getAnimators(); if ((array != null) && (anis != null)) { int old = getOldSelectedIndex(); int current = getSelectedIndex(); // Stop all previous running animations... for (int i = 0; i < anis.length; i++) { if (anis[i].isRunning()) { anis[i].stop(); array[i].setFont(getSmallFont()); } } // Animate the old selected so it goes small... if ((old >= 0) && (old < array.length) && (old != current)) { anis[old].startReverse(); } // Animate the new selected so it goes large... if ((current >= 0) && (current < array.length)) { anis[current].start(); } } } /** * Extensions and users of lists need to know the visible count of * items. * * @return The visible count of items in our list. */ public int getVisibleCount() { return (visibleCount); } protected void setVisibleCount(int i) { visibleCount = i; } /** * Which item is selected by index. * * @return The selected index. */ public int getSelectedIndex() { return (selectedIndex); } /** * Which item is selected by index. * * @param i The selected index. */ public void setSelectedIndex(int i) { // In case we are being set to an index out of range, fix it to // the last item. int tmp = getCount(); if ((i + 1) > tmp) { if (tmp > 0) { i = tmp - 1; } else { i = 0; } } else if (i < 0) { i = 0; } int old = selectedIndex; setOldSelectedIndex(old); selectedIndex = i; update(); } protected int getOldSelectedIndex() { return (oldSelectedIndex); } protected void setOldSelectedIndex(int i) { oldSelectedIndex = i; } /** * We start at a particular index. * * @return The start index. */ public int getStartIndex() { return (startIndex); } /** * We start at a particular index. * * @param i The start index. */ public void setStartIndex(int i) { startIndex = i; update(); } private boolean isWindowGreaterOrEqual() { boolean result = false; Object[] array = getObjects(); if (array != null) { result = getVisibleCount() >= array.length; } return (result); } private boolean isSelectedAtTheBottomWindow() { return ((getVisibleCount() - 1) == getSelectedIndex()); } private boolean isSelectedAtTheBottomList() { boolean result = false; Object[] array = getObjects(); if (array != null) { Object selected = getSelectedObject(); Object last = array[array.length - 1]; if ((selected != null) && (last != null)) { result = selected.equals(last); } } return (result); } private boolean isSelectedAtTheTopWindow() { return (getSelectedIndex() == 0); } private boolean isSelectedAtTheTopList() { boolean result = false; Object[] array = getObjects(); if (array != null) { Object selected = getSelectedObject(); Object first = array[0]; if ((selected != null) && (first != null)) { result = selected.equals(first); } } return (result); } /** * Update the UI. */ protected void update() { Object[] array = getObjects(); JXLabel[] labs = getLabels(); if ((array != null) && (labs != null)) { int index = getStartIndex(); for (int i = 0; i < labs.length; i++) { if (index < array.length) { labs[i].setText(array[index].toString()); } else { labs[i].setText(""); } index++; } applyColor(); int sindex = getSelectedIndex() + getStartIndex(); if (array.length > sindex) { setSelectedObject(array[sindex]); } animate(); } } }