package ini.trakem2.display;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
public class RollingPanel extends JPanel implements ComponentListener, AdjustmentListener, MouseWheelListener
{
private static final long serialVersionUID = 1L;
private final Display display;
private final Class<?> clazz;
private final Map<Displayable,DisplayablePanel> current;
private final JPanel inner, filler;
private final JScrollBar scrollBar;
private final GridBagLayout gbInner;
private final GridBagConstraints cInner;
/**
* @param display
* @param clazz
*/
protected RollingPanel(final Display display, final Class<?> clazz) {
this.display = display;
this.clazz = clazz;
//
this.current = new HashMap<Displayable, DisplayablePanel>();
this.inner = new JPanel();
this.filler = new JPanel();
this.gbInner = new GridBagLayout();
this.cInner = new GridBagConstraints();
this.inner.setLayout(this.gbInner);
this.inner.setBackground(Color.white);
//
this.scrollBar = new JScrollBar();
this.scrollBar.setUnitIncrement(1);
this.scrollBar.setBlockIncrement(1);
this.scrollBar.setMaximum(getList().size());
this.setBackground(Color.white);
final GridBagLayout gb = new GridBagLayout();
final GridBagConstraints c = new GridBagConstraints();
this.setLayout(gb);
//
c.anchor = GridBagConstraints.NORTHWEST;
//
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
gb.setConstraints(inner, c);
this.add(inner);
//
c.gridx = 1;
c.weightx = 0;
c.fill = GridBagConstraints.VERTICAL;
gb.setConstraints(scrollBar, c);
this.add(scrollBar);
// Finally:
this.addComponentListener(this);
this.scrollBar.addAdjustmentListener(this);
this.addMouseWheelListener(this);
this.scrollBar.addMouseWheelListener(this);
}
public final void updateList() {
try {
updateList2();
} catch (Exception e) {
e.printStackTrace();
}
}
private final void updateList2() {
final List<? extends Displayable> list = getList();
final int count = getList().size();
if (list.isEmpty()) {
this.current.clear();
if (this.inner.getComponentCount() > 0) {
this.inner.removeAll();
this.scrollBar.setMaximum(0);
this.revalidate();
//if (clazz == Patch.class) Utils.log2("empty list, removeAll, revalidate");
}
return;
}
final int numCanFit = (int)Math.floor(inner.getBounds().height / (float)DisplayablePanel.HEIGHT);
if (!(this.scrollBar.getMaximum() == count && this.scrollBar.getVisibleAmount() == numCanFit)) {
this.scrollBar.getModel().setRangeProperties(
Math.max(0, this.scrollBar.getValue()),
numCanFit,
0, count, false);
}
this.current.clear();
// Scrollbar value:
int first = this.scrollBar.getValue();
if (-1 == first) {
this.scrollBar.setValue(0);
first = 0;
}
// Correct for inverse list:
first = list.size() - first -1;
int last = first - numCanFit +1;
if (last < 0) {
first = Math.min(numCanFit, count) -1;
last = 0;
}
// Reuse panels only if the exact same number is to be used
final DisplayablePanel[] toReuse;
final boolean notReusing;
if (first - last + 1 != inner.getComponentCount() -1) {
this.inner.removeAll();
notReusing = true;
toReuse = null;
} else {
notReusing = false;
toReuse = new DisplayablePanel[first - last + 1];
int i = 0;
for (final Component c : inner.getComponents()) {
if (DisplayablePanel.class == c.getClass()) {
toReuse[i++] = (DisplayablePanel)c;
}
}
}
this.cInner.anchor = GridBagConstraints.NORTHWEST;
this.cInner.fill = GridBagConstraints.HORIZONTAL;
this.cInner.weightx = 0;
this.cInner.weighty = 0;
this.cInner.gridy = 0;
for (int i=first, k=0; i >= last ; --i, ++k) {
final Displayable d = list.get(i);
final DisplayablePanel dp;
if (notReusing) {
dp = new DisplayablePanel(display, d);
this.gbInner.setConstraints(dp, this.cInner);
this.inner.add(dp);
this.cInner.gridy += 1;
} else {
dp = toReuse[k];
dp.set(d);
}
this.current.put(d, dp);
}
if (notReusing) {
this.cInner.fill = GridBagConstraints.BOTH;
this.cInner.weightx = 1;
this.cInner.weighty = 1;
this.gbInner.setConstraints(this.filler, this.cInner);
this.inner.add(this.filler);
this.inner.validate();
}
}
private final List<? extends Displayable> getList() {
if (ZDisplayable.class == clazz) {
return display.getLayerSet().getDisplayableList();
}
return display.getLayer().getDisplayables(clazz);
}
protected boolean isShowing(final Displayable d) {
return this.current.containsKey(d);
}
@Override
public void componentResized(ComponentEvent e) {
updateList();
}
@Override
public void componentMoved(ComponentEvent e) {}
@Override
public void componentShown(ComponentEvent e) {
if (0 == this.inner.getComponentCount()) {
updateList();
}
}
@Override
public void componentHidden(ComponentEvent e) {}
/** Adjust the sublist of displayed DisplayablePanel. */
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
updateList();
}
public void scrollToShow(Displayable d) {
final DisplayablePanel dp = current.get(d);
if (null == dp) {
// Linear look-up but it's acceptable
final List<? extends Displayable> list = getList();
this.scrollBar.setValue(list.size() - list.indexOf(d) -1);
} else {
dp.repaint(); // for select on click to repaint the background
}
}
@Override
public void update(Graphics g) {
if (0 == this.inner.getComponentCount()) {
updateList();
}
super.update(g);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
final int newVal = this.scrollBar.getValue() + (e.getWheelRotation() > 0 ? 1 : -1);
if (newVal >= 0 && newVal <= this.scrollBar.getMaximum()) {
this.scrollBar.setValue(newVal);
}
}
}