java-org.hwo.ui/src/org/hwo/ui/treetable/TreeTable.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;
}
}