/*
* Copyright 2013 Simon Thiel
*
* This file is part of SitJar.
*
* SitJar is free software: you can redistribute it and/or modify
* it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SitJar 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SitJar. If not, see <http://www.gnu.org/licenses/lgpl.txt>.
*/
package sit.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
/**
*
* @author simon
* @param <T> Class of the objects to be added to the list
*/
public class ListPanel<T> implements ListDataListener {
private final ListPanel<T> listPanelRef = this;
private final ListPanelCellRenderer<T> cellRenderer;
private final JPanel panel = new JPanel();
private ListModel<T> listModel = null;
class ComponentProps{
final Component comp;
final Color oldColor;
final boolean wasOpaque;
final boolean isJComponent;
public ComponentProps(Component comp) {
this.comp = comp;
this.isJComponent = JComponent.class.isAssignableFrom(comp.getClass());
if (this.isJComponent){
this.wasOpaque = ((JComponent)comp).isOpaque();
}else{
this.wasOpaque=true;
}
this.oldColor = comp.getBackground();
}
private void restore() {
setOpaque(wasOpaque);
comp.setBackground(oldColor);
comp.revalidate();
comp.repaint();
}
private void setOpaque(boolean opaque){
if (isJComponent){
((JComponent)comp).setOpaque(opaque);
}
}
private void setSelected() {
setOpaque(true);
comp.setBackground(backGroundSelection);
}
}
private final Vector<ComponentProps> selection = new Vector();
private Color backGroundSelection = Color.GRAY;
private Color separationLineColor = Color.BLUE;
public ListPanel(ListPanelCellRenderer<T> cellRenderer) {
this.cellRenderer = cellRenderer;
this.panel.setLayout(new GridBagLayout());
}
public void setModel(ListModel<T> listModel) {
this.listModel = listModel;
//remove first to make sure only the model has only be added once
listModel.removeListDataListener(this);
listModel.addListDataListener(this);
refreshPanel();
}
public void intervalAdded(ListDataEvent e) {
refreshPanel();
}
public void intervalRemoved(ListDataEvent e) {
refreshPanel();
}
public void contentsChanged(ListDataEvent e) {
refreshPanel();
}
public JPanel getPanel() {
return panel;
}
private void refreshPanel() {
int maxCol = getMaxCol();
int row = 1; //in row 0 a line will be added
panel.removeAll();
for (int i = 0; i < listModel.getSize(); i++) {
T element = listModel.getElementAt(i);
Component[] comps = cellRenderer.getListCellRendererComponent(element, i);
for (int j = 0; j < comps.length; j++) {
Component comp = comps[j];
addMouseListener(comp, createMouseAdapter(element, comps));
GridBagConstraints gc = getGridBackConstraints(row, j);
if (j==0){
gc.insets= new Insets(0, 7, 0, 0);
}else if (j==comps.length-1){
gc.insets= new Insets(0, 0, 0, 7);
//the last column is oriented to the right
gc.anchor=GridBagConstraints.LAST_LINE_END;
}
panel.add(comp, gc);
}
row += 2;//the row counter is always increased by 2 to have space left for adding lines in later - as required
}
//add lines
addLines(maxCol);
//add spacer panel to fill the remaining Y-space
addSpacePanel(panel, row + 1, maxCol);
//repaint panel
panel.revalidate();
panel.repaint();
}
private GridBagConstraints getGridBackConstraints(int row, int column) {
return getGridBackConstraints(row, column, 0.5, 0.01);
}
private GridBagConstraints getGridBackConstraints(int row, int column, double weightx, double weighty) {
GridBagConstraints gridBagConstraints;
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = column;
gridBagConstraints.gridy = row;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = GridBagConstraints.LAST_LINE_START;
gridBagConstraints.weightx = weightx;
gridBagConstraints.weighty = weighty;
gridBagConstraints.ipadx = 12;
gridBagConstraints.ipady = 5;
return gridBagConstraints;
}
private void addMouseListener(Component comp, final MouseAdapter mouseListener) {
comp.addMouseListener(mouseListener);
//register also subcomponents if comp is a container
if (Container.class.isAssignableFrom(comp.getClass())) {
Container compC = (Container) comp;
for (Component compChild : compC.getComponents()) {
addMouseListener(compChild, mouseListener);
}
}
}
private MouseAdapter createMouseAdapter(final T element, final Component[] comps) {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
listPanelRef.showSelected(comps);
cellRenderer.mouseClicked(element, e);
}
@Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
}
};
}
private void addSpacePanel(final JPanel root, final int row, final int maxCol) {
JPanel result = new JPanel();
result.setOpaque(false);
root.add(result, getGridBackConstraints(row, 0, 1.0, 1.0));
}
private void addLine(final JPanel panel, final int row, final int maxCol) {
JLabel line = new JLabel();
line.setBorder(BorderFactory.createMatteBorder(1, -1, -1, -1, separationLineColor));
line.setPreferredSize(new Dimension(2, 1));
GridBagConstraints gbc = getGridBackConstraints(row, 0, 1.0, 0.0);
gbc.gridwidth = maxCol;
gbc.ipady = 0;
gbc.insets= new Insets(0, 7, 0, 7);
panel.add(line, gbc);
}
private void addLines(final int maxCol) {
//skip the first line
for (int i = 0; i < listModel.getSize() + 1; i++) {
addLine(panel, i * 2, maxCol);
}
}
private int getMaxCol() {
return cellRenderer.getColumnSize(); //currently this is fix set by the cell renderer
//for future releases it might be possible to have different cellRenderers
}
private void showSelected(Component[] newSelection) {
//restore old fields
for (ComponentProps comp : selection){
comp.restore();
}
//update selection with new selection
this.selection.clear();
for (Component comp : newSelection){
ComponentProps comProp = new ComponentProps(comp);
this.selection.add(comProp);
comProp.setSelected();
}
}
/**
* @return the backGroundSelection
*/
public Color getBackGroundSelection() {
return backGroundSelection;
}
/**
* @return the separationLineColor
*/
public Color getSeparationLineColor() {
return separationLineColor;
}
/**
* @param backGroundSelection the backGroundSelection to set
*/
public void setBackGroundSelection(Color backGroundSelection) {
this.backGroundSelection = backGroundSelection;
}
/**
* @param separationLineColor the separationLineColor to set
*/
public void setSeparationLineColor(Color separationLineColor) {
this.separationLineColor = separationLineColor;
}
}