package com.towel.swing.table.headerpopup; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.List; import javax.swing.AbstractListModel; import javax.swing.BoxLayout; import javax.swing.DefaultListCellRenderer; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.UIManager; import javax.swing.border.LineBorder; import javax.swing.table.JTableHeader; /** * @author Vinicius Godoy */ public class HeaderPopup extends JPopupMenu { private JScrollPane scrPane = null; private JList list = null; private JTableHeader header = null; private int modelndex; private PopupListModel listModel; private static class PopupListModel extends AbstractListModel { private List<Object> elements = new ArrayList<Object>(); public Object getElementAt(int index) { return elements.get(index); } public int getSize() { return elements.size(); } public void add(Object o) { elements.add(o); fireIntervalAdded(this, elements.indexOf(o), elements.indexOf(o)); } public void remove(Object o) { int index = elements.indexOf(o); elements.remove(o); fireIntervalRemoved(this, index, index); } public void removeAllElements() { int size = elements.size(); elements.clear(); if (size > 0) fireIntervalRemoved(this, 0, size-1); } public boolean isEmpty() { return elements.isEmpty(); } public Object get(int index) { return elements.get(index); } } public HeaderPopup(JTableHeader header, int modelIndex) { this.header = header; this.modelndex = modelIndex; this.listModel = new PopupListModel(); initialize(); } private void initialize() { this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); this.setBorderPainted(true); this.setBorder(new LineBorder(Color.BLACK, 1)); this.setOpaque(false); this.setDoubleBuffered(true); this.setFocusable(false); this.add(getScrPane()); } private JScrollPane getScrPane() { if (scrPane == null) { scrPane = new JScrollPane(getList(), ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrPane.getVerticalScrollBar().setFocusable(false); scrPane.setFocusable(false); scrPane.setBorder(null); } return scrPane; } private JList getList() { if (list == null) { list = new JList(); list.setModel(listModel); list.setFont(header.getFont()); list.setForeground(header.getForeground()); list.setBackground(header.getBackground()); list.setSelectionForeground(UIManager.getColor("ComboBox.selectionForeground")); list.setSelectionBackground(UIManager.getColor("ComboBox.selectionBackground")); list.setBorder(null); list.setFocusable(false); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.addMouseMotionListener(new MouseMotionListener() { public void mouseDragged(MouseEvent e) { } public void mouseMoved(MouseEvent e) { if (e.getSource() == list) { updateListBoxSelectionForEvent(e); } } }); list.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { Element element = (Element) list.getSelectedValue(); if (element == null) return; element.listener.elementSelected(new HeaderPopupEvent( element.getObject(), modelndex)); setVisible(false); } }); list.setCellRenderer(new DefaultListCellRenderer() { private final Component SEPARATOR = new SeparatorComponent(); @Override public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if ( value == null ) { return SEPARATOR; } return super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus ); } }); } return list; } private static class SeparatorComponent extends JComponent { private static final Dimension PREFERRED_SIZE = new Dimension( 5, 9 ); private static final int LINE_POS = 4; private SeparatorComponent() { setOpaque( false ); } @Override public Dimension getPreferredSize() { return PREFERRED_SIZE; } @Override public void paintComponent( Graphics g ) { g.setColor( Color.BLACK ); // @TODO: That's evil. g.drawLine( 0, LINE_POS, getWidth(), LINE_POS ); } } private void updateListBoxSelectionForEvent(MouseEvent e) { Point location = e.getPoint(); Rectangle r = new Rectangle(); list.computeVisibleRect(r); if (r.contains(location)) { if (list == null) return; int index = list.locationToIndex(location); if (index == -1) return; if (listModel.get(index) == null) index++; if (list.getSelectedIndex() != index) list.setSelectedIndex(index); } } @Override public boolean isFocusTraversable() { return false; } public void show(int columnIndex) { list.clearSelection(); Rectangle rect = header.getHeaderRect(columnIndex); Dimension d = null; if (rect.getWidth() < 180) { d = new Dimension(180, (int) list.getPreferredSize().getHeight()); if ((int) (rect.getX() - 1) - (180 - rect.getWidth()) > 0) rect.setBounds( (int) ((rect.getX() - 1) - (180 - rect.getWidth())), (int) rect.getY(), (int) rect.getWidth(), (int) rect.getHeight()); else rect.setBounds(0, (int) rect.getY(), (int) rect.getWidth(), (int) rect.getHeight()); } else d = new Dimension((int) rect.getWidth() - 1, (int) list.getPreferredSize().getHeight()); if (d.height > 300) d.height = 300; scrPane.setPreferredSize(d); scrPane.setMinimumSize(d); scrPane.setMaximumSize(d); super.show(header, (int) rect.getX() - 1, (int) (rect.getY() + rect.getHeight()) - 1); } public int getModelIndex() { return modelndex; } private class Element { private Object obj; private HeaderPopupListener listener; public Element(Object obj, HeaderPopupListener listener) { this.obj = obj; this.listener = listener; } public HeaderPopupListener getListener() { return listener; } public Object getObject() { return obj; } @Override public String toString() { if (obj == null) return ""; return obj.toString(); } @Override public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (!(obj instanceof Element)) return false; Element other = (Element)obj; if (this.obj == other.obj) return true; if (this.obj == null || other.obj == null) return false; return this.obj.equals(((Element) obj).getObject()); } @Override public int hashCode() { return obj.hashCode(); } } public void addElement(Object element, HeaderPopupListener listener) { listModel.add(new Element(element, listener)); } public void removeElement(Object element) { listModel.remove(new Element(element, null)); } public void removeAllElements() { listModel.removeAllElements(); } public boolean isEmpty() { return listModel.isEmpty(); } public void addListSeparator() { listModel.add(null); } }