662 lines
14 KiB
Java
662 lines
14 KiB
Java
package org.hwo.ui.treetable;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Dimension;
|
|
import java.awt.FlowLayout;
|
|
import java.awt.Graphics;
|
|
import java.awt.GridLayout;
|
|
import java.awt.Point;
|
|
import java.awt.Polygon;
|
|
import java.awt.Rectangle;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.util.ArrayList;
|
|
import java.util.Hashtable;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JViewport;
|
|
import javax.swing.Scrollable;
|
|
import javax.swing.SwingConstants;
|
|
|
|
import org.hwo.ui.CellEditor;
|
|
import org.hwo.ui.CellEditorArgs;
|
|
import org.hwo.ui.CellEditorListener;
|
|
import org.hwo.ui.CellRenderer;
|
|
import org.hwo.ui.DefaultCellEditor;
|
|
import org.hwo.ui.DefaultCellRenderer;
|
|
import org.hwo.ui.SelectionMode;
|
|
import org.hwo.ui.shapes.Triangle;
|
|
import org.hwo.ui.treetable.DefaultTreeTableColumnModel.Column;
|
|
|
|
public class TreeTable extends JComponent implements Scrollable, TreeTableModelListener, CellEditorListener {
|
|
|
|
class NodeState
|
|
{
|
|
private boolean opened;
|
|
|
|
public int indentation;
|
|
|
|
public boolean isOpened() {
|
|
return opened;
|
|
}
|
|
|
|
public void setOpened(boolean opened) {
|
|
this.opened = opened;
|
|
}
|
|
}
|
|
public class HeaderView extends JComponent
|
|
{
|
|
|
|
@Override
|
|
public void paint(Graphics g) {
|
|
|
|
g.drawLine(0, getHeight()-1, TreeTable.this.preferredSize.width - 1, getHeight()-1);
|
|
|
|
for (int i=0;i<columnModel.getColumnCount();i++)
|
|
{
|
|
int left = columnModel.getColumnPos(i);
|
|
int width = columnModel.getColumnWidth(i);
|
|
int right = left + width - 1;
|
|
|
|
g.setColor(getForeground());
|
|
g.drawString(columnModel.getColumnLabel(i), left + 2, rowHeight - 3);
|
|
g.drawLine( right, 0, right, getHeight());
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private TreeTableModel model;
|
|
private TreeTableColumnModel
|
|
columnModel;
|
|
|
|
private int rowHeight;
|
|
|
|
private List<Object> rowObjects;
|
|
private Hashtable<Object, NodeState>
|
|
nodeStates;
|
|
|
|
private Hashtable<Class<?>, CellRenderer> cellRendererList;
|
|
private Hashtable<Class<?>, CellEditor> cellEditorList;
|
|
|
|
private JViewport headerViewport;
|
|
private Dimension preferredSize;
|
|
|
|
private List<TreeTablePopupListener> popupListener;
|
|
private List<TreeTableMouseListener> mouseListener;
|
|
|
|
private CellEditor activeEditor;
|
|
|
|
private SelectionMode selectionMode;
|
|
private int selectedRow;
|
|
|
|
|
|
public TreeTable()
|
|
{
|
|
popupListener = new LinkedList<TreeTablePopupListener>();
|
|
mouseListener = new LinkedList<TreeTableMouseListener>();
|
|
|
|
preferredSize = new Dimension(1,1);
|
|
|
|
rowHeight = 20;
|
|
setBackground(Color.WHITE);
|
|
|
|
cellRendererList = new Hashtable<Class<?>, CellRenderer>();
|
|
cellRendererList.put(Object.class, new DefaultCellRenderer());
|
|
|
|
cellEditorList = new Hashtable<Class<?>, CellEditor>();
|
|
addCellEditor(Object.class, new DefaultCellEditor());
|
|
|
|
rowObjects = new ArrayList<Object>();
|
|
nodeStates = new Hashtable<Object, TreeTable.NodeState>();
|
|
setModel(new DefaultTreeTableModel());
|
|
|
|
addMouseListener(new MouseListener() {
|
|
|
|
@Override
|
|
public void mouseReleased(MouseEvent e) {
|
|
if (e.isPopupTrigger())
|
|
firePopupRequestedFromMouseEvent(e);
|
|
}
|
|
|
|
@Override
|
|
public void mousePressed(MouseEvent e) {
|
|
if (e.isPopupTrigger())
|
|
firePopupRequestedFromMouseEvent(e);
|
|
|
|
}
|
|
|
|
@Override
|
|
public void mouseExited(MouseEvent e) {
|
|
// TODO Auto-generated method stub
|
|
|
|
}
|
|
|
|
@Override
|
|
public void mouseEntered(MouseEvent e) {
|
|
// TODO Auto-generated method stub
|
|
|
|
}
|
|
|
|
@Override
|
|
public void mouseClicked(MouseEvent e) {
|
|
int row,column;
|
|
|
|
row = e.getY() / rowHeight;
|
|
column = columnModel.getColumnAtX(e.getX());
|
|
|
|
TreeTable.this.mouseClicked(e.getX(), e.getY(), e.getClickCount(), row, column);
|
|
|
|
if (e.isPopupTrigger())
|
|
firePopupRequestedFromMouseEvent(e);
|
|
else
|
|
fireMouseClicked(e);
|
|
|
|
}
|
|
});
|
|
|
|
prepareWidth();
|
|
prepareHeight();
|
|
|
|
selectedRow = -1;
|
|
}
|
|
|
|
public Point mouseToCell(int x,int y)
|
|
{
|
|
Point p = new Point();
|
|
p.x = columnModel.getColumnAtX(x);
|
|
p.y = y / rowHeight;
|
|
|
|
if ((p.x >= columnModel.getColumnCount()) || (p.y >= rowObjects.size()))
|
|
return null;
|
|
|
|
return p;
|
|
}
|
|
|
|
public Object getRowObject(int row)
|
|
{
|
|
return this.rowObjects.get(row);
|
|
}
|
|
|
|
private CellRenderer findCellRenderer(Class<?> clazz)
|
|
{
|
|
if (clazz == null)
|
|
return new DefaultCellRenderer();
|
|
if (cellRendererList.containsKey(clazz))
|
|
return cellRendererList.get(clazz);
|
|
if (clazz.getSuperclass()!=null)
|
|
return findCellRenderer(clazz.getSuperclass());
|
|
return new DefaultCellRenderer();
|
|
}
|
|
|
|
public void addCellEditor(Class<?> clazz,CellEditor editor)
|
|
{
|
|
editor.addCellEditorListener(this);
|
|
cellEditorList.put(clazz,editor);
|
|
}
|
|
|
|
private CellEditor findCellEditor(Class<?> clazz)
|
|
{
|
|
if (clazz == null)
|
|
return null;
|
|
if (cellEditorList.containsKey(clazz))
|
|
return cellEditorList.get(clazz);
|
|
|
|
return cellEditorList.get(Object.class);
|
|
|
|
}
|
|
|
|
private void mouseClicked(int x,int y,int clickCount,int row,int column)
|
|
{
|
|
System.err.println("Click: " + x + "/" + y + " ( " + row + " : " + column + " )");
|
|
if (clickCount == 1)
|
|
{
|
|
if (column == 0)
|
|
{
|
|
if (x < rowHeight)
|
|
{
|
|
NodeState state = getState(rowObjects.get(row));
|
|
setOpened(rowObjects.get(row), !state.opened);
|
|
} else if (selectionMode == SelectionMode.ROW)
|
|
{
|
|
selectedRow = row;
|
|
repaint();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void prepareWidth()
|
|
{
|
|
preferredSize.width = columnModel.getColumnPos(columnModel.getColumnCount());
|
|
}
|
|
|
|
private void prepareHeight()
|
|
{
|
|
prepareRows();
|
|
preferredSize.height = rowHeight * rowObjects.size();
|
|
}
|
|
|
|
private void prepareRow(Object row)
|
|
{
|
|
if (row!=null)
|
|
{
|
|
int ind = getState(row).indentation;
|
|
|
|
rowObjects.add(row);
|
|
if (model.getChildCount(row)>0)
|
|
{
|
|
if (getState(row).isOpened())
|
|
{
|
|
for (int i=0;i<model.getChildCount(row);i++)
|
|
{
|
|
Object child = model.getChild(row, i);
|
|
getState(child).indentation = ind + 1 ;
|
|
prepareRow( child );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private void prepareRows()
|
|
{
|
|
if (rowObjects == null)
|
|
return;
|
|
|
|
rowObjects.clear();
|
|
|
|
if (model.getRoot()!=null)
|
|
{
|
|
getState(model.getRoot()).indentation = 0;
|
|
prepareRow(model.getRoot());
|
|
};
|
|
}
|
|
|
|
private NodeState getState(Object node)
|
|
{
|
|
if (!nodeStates.containsKey(node))
|
|
nodeStates.put(node, new NodeState());
|
|
return nodeStates.get(node);
|
|
}
|
|
|
|
private Rectangle getCellRect(int row,int column)
|
|
{
|
|
int left = columnModel.getColumnPos(column);
|
|
int width = columnModel.getColumnWidth(column);
|
|
int top = rowHeight * row;
|
|
|
|
return new Rectangle(left, top, width, rowHeight);
|
|
}
|
|
|
|
public void paintRow(Graphics g,int row)
|
|
{
|
|
Object ro = rowObjects.get(row);
|
|
NodeState state = getState(ro);
|
|
int top = rowHeight * row;
|
|
int bottom = top + rowHeight - 1;
|
|
|
|
if (selectedRow == row)
|
|
{
|
|
g.setColor(Color.BLUE);
|
|
g.fillRect(0, top, getColumnModel().getColumnPos(getColumnModel().getColumnCount()) - 1, rowHeight - 1);
|
|
}
|
|
|
|
g.setColor(Color.black);
|
|
g.drawLine(0, bottom, getWidth(), bottom);
|
|
|
|
for (int column = 0; column < columnModel.getColumnCount(); column++)
|
|
{
|
|
int left = columnModel.getColumnPos(column);
|
|
int width = columnModel.getColumnWidth(column);
|
|
int right = left + width - 1;
|
|
|
|
g.drawLine(right , top, right, bottom);
|
|
|
|
|
|
if (column==0)
|
|
{
|
|
int a = rowHeight / 4;
|
|
int b = rowHeight / 2;
|
|
|
|
left += state.indentation * 5;
|
|
|
|
g.setColor(Color.BLACK);
|
|
|
|
if (model.getChildCount(ro)>0)
|
|
{
|
|
if (!state.isOpened())
|
|
{
|
|
g.drawPolygon(new Triangle(left + a, top + a, left + b, top + b, left + a, bottom - a));
|
|
} else
|
|
{
|
|
g.drawPolygon(new Triangle(left + a, top + b, left + a + b, top + b, left + b, bottom - a ));
|
|
}
|
|
}
|
|
|
|
left += rowHeight;
|
|
}
|
|
|
|
g.setColor(getForeground());
|
|
|
|
Object value = model.getValue(ro, columnModel.getColumnModelColumn(column));
|
|
CellRenderer renderer = columnModel.getColumnCellRenderer(column);
|
|
|
|
if (renderer == null)
|
|
if (value != null)
|
|
renderer = findCellRenderer(value.getClass());
|
|
else
|
|
renderer = findCellRenderer(null);
|
|
|
|
Rectangle rect = new Rectangle(left, top, width, rowHeight);
|
|
|
|
Rectangle clipRestore = g.getClipBounds();
|
|
|
|
if (clipRestore.intersects(rect))
|
|
{
|
|
Rectangle rectClip = clipRestore.intersection(rect);
|
|
|
|
g.setClip(rectClip);
|
|
renderer.renderCell(g, row, column, rect, value, ro);
|
|
g.setClip(clipRestore);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
public void paint(Graphics g) {
|
|
prepareRows();
|
|
|
|
g.setColor(getBackground());
|
|
g.fillRect(0, 0, getWidth(), getHeight());
|
|
|
|
for (int i=0;i<rowObjects.size();i++)
|
|
{
|
|
paintRow(g, i);
|
|
}
|
|
|
|
}
|
|
|
|
private void firePopupRequestedFromMouseEvent(MouseEvent event)
|
|
{
|
|
Point p = mouseToCell(event.getX(), event.getY());
|
|
TreeTablePopupArgs args = new TreeTablePopupArgs(this, p.y,p.x, event.getX(), event.getY());
|
|
firePopupRequested(args);
|
|
}
|
|
private void firePopupRequested(TreeTablePopupArgs args)
|
|
{
|
|
TreeTablePopupListener listener = columnModel.getPopup(args.getRow(), args.getColumn());
|
|
if (listener != null)
|
|
listener.popupRequest(args);
|
|
else
|
|
for (TreeTablePopupListener l:popupListener)
|
|
l.popupRequest(args);
|
|
}
|
|
|
|
public void addPopupListener(TreeTablePopupListener listener)
|
|
{
|
|
popupListener.add(listener);
|
|
}
|
|
public void removePopupListener(TreeTablePopupListener listener)
|
|
{
|
|
popupListener.remove(listener);
|
|
}
|
|
|
|
private void fireMouseClicked(MouseEvent event)
|
|
{
|
|
TreeTableMouseArgs args = new TreeTableMouseArgs(this,event);
|
|
|
|
if (args.getMouseEvent().getClickCount()==2)
|
|
{
|
|
if (args.getRowObject() != null)
|
|
{
|
|
startEditor(args.getRow(),args.getColumn());
|
|
}
|
|
}
|
|
|
|
for (TreeTableMouseListener listener:mouseListener)
|
|
listener.mouseClicked(args);
|
|
}
|
|
|
|
public void addMouseListener(TreeTableMouseListener listener)
|
|
{
|
|
mouseListener.add(listener);
|
|
}
|
|
public void removeMouseListener(TreeTableMouseListener listener)
|
|
{
|
|
mouseListener.remove(listener);
|
|
}
|
|
|
|
|
|
@Override
|
|
public Dimension getPreferredScrollableViewportSize() {
|
|
return getPreferredSize();
|
|
}
|
|
|
|
@Override
|
|
public int getScrollableBlockIncrement(Rectangle visibleRect,
|
|
int orientation, int direction) {
|
|
if (orientation == SwingConstants.HORIZONTAL)
|
|
return visibleRect.width / 2;
|
|
else
|
|
return visibleRect.height / 2;
|
|
}
|
|
|
|
@Override
|
|
public boolean getScrollableTracksViewportHeight() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean getScrollableTracksViewportWidth() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int getScrollableUnitIncrement(Rectangle visibleRect,
|
|
int orientation, int direction) {
|
|
if (orientation == SwingConstants.HORIZONTAL)
|
|
return 10;
|
|
else
|
|
return rowHeight;
|
|
}
|
|
|
|
public TreeTableModel getModel() {
|
|
return model;
|
|
}
|
|
|
|
public DefaultTreeTableModel getDefaultModel()
|
|
{
|
|
return (DefaultTreeTableModel)model;
|
|
}
|
|
|
|
public void setModel(TreeTableModel model) {
|
|
|
|
if (this.model != null)
|
|
this.model.removeTreeTableModelListener(this);
|
|
|
|
rowObjects.clear();
|
|
nodeStates.clear();
|
|
|
|
this.model = model;
|
|
this.model.addTreeTableModelListener(this);
|
|
|
|
TreeTableColumnModel cm = model.getDefaultColumnModel();
|
|
if (cm != null)
|
|
setColumnModel(cm);
|
|
|
|
prepareRows();
|
|
}
|
|
|
|
public int getRowHeight() {
|
|
return rowHeight;
|
|
}
|
|
|
|
public void setRowHeight(int rowHeight) {
|
|
this.rowHeight = rowHeight;
|
|
}
|
|
|
|
|
|
public void setOpened(Object node,boolean opened)
|
|
{
|
|
getState(node).opened = opened;
|
|
prepareHeight();
|
|
repaint();
|
|
}
|
|
|
|
public TreeTableColumnModel getColumnModel() {
|
|
return columnModel;
|
|
}
|
|
|
|
public void setColumnModel(TreeTableColumnModel columnModel) {
|
|
if (this.columnModel != null)
|
|
this.columnModel.removeTreeTableModelListener(this);
|
|
this.columnModel = columnModel;
|
|
|
|
this.columnModel.addTreeTableModelListener(this);
|
|
}
|
|
|
|
@Override
|
|
public void columnsChanged() {
|
|
buildHeader();
|
|
prepareWidth();
|
|
repaint();
|
|
}
|
|
|
|
@Override
|
|
public void rowsChanged() {
|
|
prepareHeight();
|
|
repaint();
|
|
}
|
|
|
|
@Override
|
|
public void repaint() {
|
|
invalidate();
|
|
|
|
if (getParent()!=null)
|
|
getParent().validate();
|
|
|
|
super.repaint();
|
|
}
|
|
|
|
private void buildHeader()
|
|
{
|
|
if (headerViewport == null)
|
|
headerViewport = new JViewport();
|
|
|
|
headerViewport.removeAll();
|
|
|
|
HeaderView hv = new HeaderView();
|
|
hv.setSize(columnModel.getColumnPos(columnModel.getColumnCount()),24);
|
|
hv.setPreferredSize(new Dimension(columnModel.getColumnPos(columnModel.getColumnCount()),24));
|
|
headerViewport.setView(hv);
|
|
}
|
|
|
|
private JViewport getHeaderViewport()
|
|
{
|
|
if (headerViewport == null)
|
|
buildHeader();
|
|
return headerViewport;
|
|
}
|
|
|
|
private void configureEnclosingScrollPane()
|
|
{
|
|
prepareWidth();
|
|
prepareHeight();
|
|
|
|
if (JViewport.class.isInstance(getParent()) && JScrollPane.class.isInstance(getParent().getParent()))
|
|
{
|
|
JScrollPane sp = (JScrollPane)getParent().getParent();
|
|
sp.setColumnHeader(getHeaderViewport());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addNotify() {
|
|
configureEnclosingScrollPane();
|
|
super.addNotify();
|
|
}
|
|
|
|
@Override
|
|
public void removeNotify() {
|
|
super.removeNotify();
|
|
}
|
|
|
|
@Override
|
|
public Dimension getPreferredSize() {
|
|
return preferredSize;
|
|
}
|
|
|
|
|
|
public void cancelEditor()
|
|
{
|
|
if (activeEditor != null)
|
|
{
|
|
activeEditor.editCancel();
|
|
activeEditor = null;
|
|
}
|
|
}
|
|
|
|
public void startEditor(int row,int column)
|
|
{
|
|
if (activeEditor != null)
|
|
cancelEditor();
|
|
|
|
Object value = getModel().getValue(rowObjects.get(row), column);
|
|
|
|
activeEditor = findCellEditor(value.getClass());
|
|
|
|
if (activeEditor != null)
|
|
{
|
|
activeEditor.editBegin(this, getCellRect(row, column), value);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
@Override
|
|
public void editBegin(CellEditorArgs args) {
|
|
// TODO Auto-generated method stub
|
|
|
|
}
|
|
|
|
@Override
|
|
public void editCanceled(CellEditorArgs args) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void editFinished(CellEditorArgs args) {
|
|
|
|
|
|
}
|
|
|
|
public int getSelectedRow() {
|
|
return selectedRow;
|
|
}
|
|
|
|
public void setSelectedRow(int selectedRow) {
|
|
this.selectedRow = selectedRow;
|
|
}
|
|
|
|
public SelectionMode getSelectionMode() {
|
|
return selectionMode;
|
|
}
|
|
|
|
public void setSelectionMode(SelectionMode selectionMode) {
|
|
this.selectionMode = selectionMode;
|
|
}
|
|
|
|
|
|
|
|
}
|