package org.yamcs.ui;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.*;
import java.util.List;
/**
* Simple component for displaying a visual representation of the index of a list of items with markers for specific
* indices in that list. Markers can be clicked on to fire a selection event to interested listeners.
*
* Author: Darren Scott, blog.darrenscott.com
*/
public class ListIndexBar extends JComponent {
private static final long serialVersionUID = 1L;
private int itemCount;
private double scaleFactor;
private int markerHeight;
// set of list indices associated with items to be marked
private Set<Integer> markerSet = new HashSet<Integer>();
// the index of the currently highlighted marker index
// gets set when the pointer hovers over a marker and cleared when the mouse is moved off a marker
// or the pointer leaves the component completely
private int highlightedIndex = -1;
// keep track of listeners interested in marker selection events
private List<ListSelectionListener> listeners = new ArrayList<ListSelectionListener>();
public ListIndexBar(int itemCount) {
this.itemCount = itemCount;
recalc();
// add a mouse motion listener to track the current highlighted marker
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
// calculate the list index which is under the mouse pointer
int pos = (int) (ListIndexBar.this.itemCount * (e.getPoint().getY() / getHeight()));
boolean found = false;
// Added by atu
// Have to allow for a modelled list having more entries than
// pixels to view.
if( scaleFactor < 1 ) {
for (int i = -markerHeight; i < markerHeight; i++) {
if( markerSet.contains( pos + i ) ) {
// we're over one of the markers so record the index and change the cursor
highlightedIndex = pos + i;
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
found = true;
break;
}
}
} else {
if( markerSet.contains( pos ) ) {
// we're over one of the markers so record the index and change the cursor
highlightedIndex = pos;
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
found = true;
}
}
if (!found) {
// we're not over any marker so clear the highlighted index and reset the cursor
highlightedIndex = -1;
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
});
// add a mouse listener to handle mouse clicks on markers
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (highlightedIndex != -1) {
ListSelectionEvent event = new ListSelectionEvent(ListIndexBar.this, highlightedIndex, highlightedIndex, false);
for (ListSelectionListener listener : listeners) {
listener.valueChanged(event);
}
}
}
@Override
public void mouseExited(MouseEvent e) {
// clear the highlighted index when we leave this component
highlightedIndex = -1;
}
});
// give the component a min and preferred size
setMinimumSize(new Dimension(16, 60));
setPreferredSize(new Dimension(16, 60));
}
public void addSelectionListener(ListSelectionListener listener) {
listeners.add(listener);
}
public void removeSelectionListener(ListSelectionListener listener) {
listeners.remove(listener);
}
public int getItemCount() {
return itemCount;
}
public void setItemCount(int itemCount) {
this.itemCount = itemCount;
recalc();
repaint();
}
private void recalc() {
scaleFactor = getHeight() / (double) itemCount;
markerHeight = Math.max(2, (int) scaleFactor);
}
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
recalc();
}
@Override
public void setBounds(Rectangle r) {
super.setBounds(r);
recalc();
}
public Set<Integer> getMarkerSet() {
return markerSet;
}
public void addMarkers(Collection<Integer> markers) {
markerSet.addAll(markers);
repaint();
}
public void addMarker(int index) {
// Added by atu
markerSet.add( new Integer(index) );
}
public void removeMarkers(Collection<Integer> markers) {
markerSet.removeAll(markers);
repaint();
}
public void clearMarkers() {
markerSet.clear();
repaint();
}
@Override
protected void paintComponent(Graphics g) {
// cast to a Graphics2D so we can do more with it
Graphics2D g2 = (Graphics2D) g;
// paint or clear the background depending on whether this component is opaque or not
Composite composite = g2.getComposite();
g2.setColor(getBackground());
if (!isOpaque()) {
// if not opaque, set the alpha composite to clear the background
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST));
}
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setComposite(composite);
// markers will be drawn with the foreground colour
g2.setColor(getForeground());
int pos;
for (Integer marker : markerSet) {
// for each marker, calculate the appropriate Y position and paint a marker of required size
pos = (int) (marker * scaleFactor);
g2.fillRect(0, pos, getWidth(), markerHeight);
}
}
}