package net.sourceforge.pmd.eclipse.ui.views.dataflow;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ScrollBar;
/**
* A SWT-Composite for showing a DataflowGraph
* as well as other information in form of a table
*
* @author SebastianRaffel ( 31.05.2005 )
*/
public class DataflowGraphTable extends Composite implements PaintListener {
private int numRows;
private int numCols;
private Integer[] colWidths;
private Integer rowHeight;
private Color bgColor;
private Color fgColor;
private Color lineColor;
protected Composite header;
protected Composite bodyFrame;
protected Composite bodyArea;
protected Composite graphArea;
protected Point tablePosition;
protected Point tableSize;
protected int graphColumn;
protected static final Color DEFAULT_BG_COLOR = new Color(null,255,255,255);
protected static final Color DEFAULT_FG_COLOR = new Color(null,0,0,0);
protected static final Color DEFAULT_LINE_COLOR = new Color(null,192,192,192);
protected static final int DEFAULT_ROW_HEIGHT = 20;
protected static final int DEFAULT_COL_WIDTH = 100;
/**
* Constructor
*
* @param parent, the parent Composite
* @param style, the SWT-Style
*/
public DataflowGraphTable(Composite parent, int style) {
super(parent, style);
// first set default Values for avoiding Errors
// when building the Table Elements
numCols = 0;
numRows = 0;
graphColumn = 1;
setLayoutData(new GridData(GridData.FILL_BOTH));
// build the Header
header = buildTableHeader(this);
// ... and the Body
Composite[] tableBody = buildTableBody(this);
bodyFrame = tableBody[0];
bodyArea = tableBody[1];
// add Listeners
bodyFrame.addPaintListener(this);
bodyArea.addPaintListener(this);
bodyFrame.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent event) {
// redraw the whole thing when resized
redraw();
}
});
// ... and init the ScrollBars
initScrollBars(bodyFrame);
GridLayout mainLayout = new GridLayout(1, false);
mainLayout.horizontalSpacing = mainLayout.verticalSpacing = mainLayout.marginHeight = mainLayout.marginWidth = 0;
setLayout(mainLayout);
}
/**
* Builds the TableHeader (made of Buttons)
*
* @param parent
* @return the Composite representing the Table's Header
*/
private Composite buildTableHeader(Composite parent) {
Composite headerCanvas = new Composite(parent, SWT.NONE);
headerCanvas.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
GridLayout layout = new GridLayout(1, false);
layout.horizontalSpacing = layout.verticalSpacing = layout.marginHeight = layout.marginWidth = 0;
headerCanvas.setLayout(layout);
return headerCanvas;
}
/**
* Builds the Table Body,
* creates two areas, one that carries the real Table,
* another one that is the table "Background" or "Frame"
* and fills the empty space when the viewing area is
* larger than the table
*
* @param parent
* @return a Composite-Array with the
* Table Frame [0] and the Table's Body [1]
*/
private Composite[] buildTableBody(Composite parent) {
Composite frameCanvas = new Composite(parent, SWT.V_SCROLL | SWT.H_SCROLL);
frameCanvas.setLayoutData(new GridData(GridData.FILL_BOTH));
// create the body
Composite bodyCanvas = new Composite(frameCanvas, SWT.NONE);
GridData data = new GridData(GridData.FILL_BOTH);
data.grabExcessHorizontalSpace = true;
data.grabExcessVerticalSpace = true;
bodyCanvas.setLayoutData(data);
bodyCanvas.setSize(numCols*DEFAULT_COL_WIDTH, numRows*DEFAULT_ROW_HEIGHT);
int spacing = 10;
GridLayout bodyLayout = new GridLayout(numCols, false);
bodyLayout.marginHeight = bodyLayout.marginWidth = spacing/2;
bodyLayout.horizontalSpacing = bodyLayout.verticalSpacing = spacing;
bodyCanvas.setLayout(bodyLayout);
// create the frame
GridLayout frameLayout = new GridLayout(1, false);
frameLayout.horizontalSpacing = frameLayout.verticalSpacing = frameLayout.marginWidth = frameLayout.marginHeight = 0;
frameCanvas.setLayout(frameLayout);
// get the Position and size of the Table and store them
// this is needed later when resizing the Table
tablePosition = new Point(bodyCanvas.getLocation().x, bodyCanvas.getLocation().y);
tableSize = new Point(bodyCanvas.getSize().x, bodyCanvas.getSize().y);
return new Composite[] {frameCanvas, bodyCanvas};
}
/**
* Inits the Columns, thereby gives the Header something to show
* (the Widths- and Titles-Field should be the same size, so that
* every Column has a real Width and Title)
*
* @param widths, int-Field with the Widths of Columns
* @param titles, String-Field with the Titles
* @param graphPos, Number of Column, where to show the Graph
*/
public void setColumns(int[] widths, String[] titles, int graphPos) {
// set the Number of Columns
numCols = widths.length;
// create an Integer-Arr out of the int-Array
colWidths = new Integer[numCols];
for (int i=0; i<widths.length; i++) {
colWidths[i] = Integer.valueOf(widths[i]);
}
// check and (if correct) set the Graph's Column
if (graphPos >= 0 && graphPos <= numCols)
graphColumn = graphPos;
// check the Titles
String[] headerTitles = getHeaderTitles(titles);
GridLayout headerLayout = (GridLayout) header.getLayout();
headerLayout.numColumns = numCols+1;
GridData data;
for (int i=0; i<=numCols; i++) {
// ... and create a Button with the appropriate Title
Button button = new Button(header, SWT.NONE);
data = new GridData();
if (i < numCols) {
button.setText(headerTitles[i]);
data.widthHint = widths[i];
} else if (i == numCols) {
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
}
button.setLayoutData(data);
}
int newWidth = 0;
for (int k=0; k<widths.length; k++) {
newWidth += colWidths[k].intValue();
}
((GridLayout) bodyArea.getLayout()).numColumns = numCols;
tableSize.x = newWidth;
redraw();
}
/**
* Checks, if the header titles are correct,
* if there are fewer titles than columns, it adds empty Strings
*
* @param givenTitles
* @return the corrected String-Array of Titles
*/
private String[] getHeaderTitles(String[] givenTitles) {
String[] headerTitles = new String[numCols];
// creates the Titles when nothing is given
if (givenTitles == null) {
for (int i=0; i<numCols; i++) {
headerTitles[i] = "";
}
} else if (givenTitles.length < numCols) {
// fills the remaining Field with ""-Strings,
// so that every Header gets at least a non-null-Title
int remain = numCols-givenTitles.length;
for (int j=0; j<givenTitles.length; j++) {
headerTitles[j] = givenTitles[j];
}
for (int k=0; k<remain; k++) {
headerTitles[givenTitles.length+k] = "";
}
} else {
headerTitles = givenTitles;
}
return headerTitles;
}
/**
* Sets the Number of Rows and the Height of each Row of this Table
*
* @param count
* @param height
*/
public void setRows(int count, int height) {
numRows = count;
rowHeight = Integer.valueOf(height);
tableSize.y = numRows*height;
redraw();
}
/**
* Set the Table's Foreground-, Background- and Line-Color
*
* @param foreGround
* @param backGround
* @param line
*/
public void setColors(Color foreGround, Color backGround, Color line) {
this.fgColor = foreGround;
this.bgColor = backGround;
this.lineColor = line;
bodyArea.setBackground(bgColor);
bodyArea.setForeground(fgColor);
redraw();
}
/**
* Gives the Table real Data to show
*
* @param data
*/
public void setTableData(List<List<DataflowGraphTableData>> data) {
buildTableData(bodyArea, data);
redraw();
}
/**
* Returns the Composite-Area, the Graph should be built in
*
* @return the DataflowGraph-Composite
*/
public Composite getGraphArea() {
return graphArea;
}
/**
* Redraws this Table
*/
@Override
public void redraw() {
Point bodySize = bodyFrame.getSize();
Point parentSize = getParent().getSize();
Point empty = new Point(0,0);
if (bodySize.equals(empty) && !parentSize.equals(empty)) {
bodyFrame.setSize(parentSize);
}
syncScrollBars(bodyFrame);
syncViewPosition(bodyFrame);
syncHeader();
super.redraw();
}
/**
* Initializes the ScrollBars for the Table
*
* @param parent
*/
private void initScrollBars(Composite parent) {
ScrollBar horizontal = parent.getHorizontalBar();
horizontal.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
scrollHorizontally((ScrollBar) event.widget);
}
});
ScrollBar vertical = parent.getVerticalBar();
vertical.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
scrollVertically((ScrollBar) event.widget);
}
});
}
/**
* Synchronizes the ScrollBars when the Source-View is resized
*
* @param source
*/
private void syncScrollBars(Composite source) {
// get the Width and Height
int sourceWidth = source.getSize().x
- source.getVerticalBar().getSize().x;
int sourceHeight = source.getSize().y
- source.getHorizontalBar().getSize().y;
// get the Bars
ScrollBar horizontalBar = bodyFrame.getHorizontalBar();
ScrollBar verticalBar = bodyFrame.getVerticalBar();
// first set all Bars enabled
horizontalBar.setEnabled(true);
verticalBar.setEnabled(true);
if (sourceWidth >= tableSize.x) {
// if the viewed Area is larger than the Table
// we don't need to scroll
horizontalBar.setEnabled(false);
} else {
// ... else we adjust the Bar
horizontalBar.setMaximum(tableSize.x);
horizontalBar.setIncrement(tableSize.x/100);
horizontalBar.setPageIncrement(tableSize.x);
horizontalBar.setThumb(sourceWidth);
}
if (sourceHeight >= tableSize.y) {
// if the viewed Area is larger than the Table
// we don't need to scroll
verticalBar.setEnabled(false);
} else {
// ... else we adjust the Bar
verticalBar.setMaximum(tableSize.y);
verticalBar.setIncrement(tableSize.y/100);
verticalBar.setPageIncrement(tableSize.y);
verticalBar.setThumb(sourceHeight);
}
}
/**
* Synchronizes the Frame's and Source's (the TableBody's)
* Position when scrolled
*
* @param source
*/
private void syncViewPosition(Composite source) {
tablePosition.x = -bodyFrame.getHorizontalBar().getSelection();
tablePosition.y = -bodyFrame.getVerticalBar().getSelection();
int viewWidth = source.getSize().x - source.getVerticalBar().getSize().x;
int viewHeight = source.getSize().y - source.getHorizontalBar().getSize().y;
if (viewWidth > tableSize.x) tablePosition.x = 0;
if (viewHeight > tableSize.y) tablePosition.y = 0;
}
/**
* Synchronizes the Header with the TableBody when scrolled
* so the Header won't stay on its Position while the Body
* is scrolled
*/
protected void syncHeader() {
Control[] buttons = header.getChildren();
int width = 0;
// Adjust each Header-Buttons Location
// to the Table's Location
for (int k=0; k<buttons.length; k++) {
Button button = (Button) buttons[k];
button.setLocation(tablePosition.x+width, button.getLocation().y);
width += button.getSize().x;
if (k == buttons.length-1) {
button.setSize(button.getSize().x-tablePosition.x,button.getSize().y);
}
}
}
/**
* Scrolls the horizontal Bar horizontally
*
* @param bar
*/
protected void scrollHorizontally(ScrollBar bar) {
int x = bar.getSelection();
int y = bodyArea.getLocation().y;
bodyArea.setLocation(-x,y);
tablePosition.x = -x;
syncHeader();
}
/**
* Scrolls the vertical Bar vertically
*
* @param bar
*/
protected void scrollVertically(ScrollBar bar) {
int x = bodyArea.getLocation().x;
int y = bar.getSelection();
bodyArea.setLocation(x,-y);
tablePosition.y = -y;
}
/* @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent) */
public void paintControl(PaintEvent e) {
Composite source = (Composite) e.getSource();
// re-new the Table's Size and Position
bodyArea.getClientArea().width = tableSize.x;
bodyArea.getClientArea().height = tableSize.y;
bodyArea.setSize(tableSize);
bodyArea.setLocation(tablePosition);
// update the Lines and the Graph
if (source.equals(bodyFrame)) {
buildFrameLines(e.gc);
} else if (source.equals(bodyArea)) {
buildBodyLines(e.gc);
clearGraphArea(e.gc);
bodyFrame.redraw();
}
}
/**
* Clears the Area of the DataflowGraph
*
* @param g
*/
protected void clearGraphArea(GC g) {
if (colWidths == null || rowHeight == null)
return;
int xPos = 0;
if (graphColumn > 0) {
for (int i=0; i<graphColumn; i++) {
xPos += colWidths[i].intValue();
}
}
int width = colWidths[graphColumn].intValue();
int height = numRows*rowHeight.intValue();
Color formerColor = g.getBackground();
if (bgColor == null) bgColor = DEFAULT_BG_COLOR;
g.setBackground(bgColor);
g.fillRectangle(xPos, 1, width-1, height);
g.setBackground(bgColor);
}
/**
* Builds Lines for the TableBody
*
* @param g
*/
protected void buildBodyLines(GC g) {
if (lineColor == null) lineColor = DEFAULT_LINE_COLOR;
g.setForeground(lineColor);
// create the Lines for each Row
// from 0 to the Table's Width
if (rowHeight == null)
rowHeight = Integer.valueOf(DEFAULT_ROW_HEIGHT);
int rowWidth = tableSize.x;
for (int i=0; i<=numRows; i++) {
g.drawLine(0, i*rowHeight.intValue(),
rowWidth, i*rowHeight.intValue());
}
// create Lines for each Column
// from 0 to the Table's Height
int colHeight = tableSize.y;
int width = 0;
for (int j=0; j<numCols; j++) {
if (colWidths == null)
width += DEFAULT_COL_WIDTH;
else
width += colWidths[j].intValue();
g.drawLine(width-1, 0, width-1, colHeight);
}
}
/**
* Build Lines, that are visible, when the Table's Size is
* smaller than the Viewers Size; then, - like other Eclipse Views -
* the Rest is filled with Lines till the End of the View
*
* @param g
*/
protected void buildFrameLines(GC g) {
if (bgColor == null) bgColor = DEFAULT_BG_COLOR;
bodyFrame.setBackground(bgColor);
if (lineColor == null) lineColor = DEFAULT_LINE_COLOR;
g.setForeground(lineColor);
int tableX = tablePosition.x;
int tableY = tablePosition.y;
if (rowHeight == null)
rowHeight = Integer.valueOf(DEFAULT_ROW_HEIGHT);
// create the filling Lines
// from the Table's Width to the Viewer's Width
int viewWidth = bodyFrame.getSize().x;
if (viewWidth > tableSize.x) {
for (int i=0; i<numRows; i++) {
g.drawLine(tableSize.x,
tableY+i*rowHeight.intValue(),
viewWidth,
tableY+i*rowHeight.intValue());
}
}
// Create Lines for the Columns
// from Table's Height to the Viewer's Height
int viewHeight = bodyFrame.getSize().y;
if (viewHeight > tableSize.y) {
int yPos = tableY+tableSize.y;
while(yPos < viewHeight) {
g.drawLine(0, yPos, viewWidth, yPos);
yPos += rowHeight.intValue();
}
yPos = tableY+tableSize.y;
int width = 0;
for (int j=0; j<numCols; j++) {
if (colWidths == null)
width += DEFAULT_COL_WIDTH;
else
width += colWidths[j].intValue();
g.drawLine(tableX+width-1, yPos,
tableX+width-1, viewHeight);
}
}
}
/**
* Build labels showing the TableData
*
* @param table
* @param tableData
*/
private void buildTableData(Composite table, List<List<DataflowGraphTableData>> tableData) {
if (bgColor == null) bgColor = DEFAULT_BG_COLOR;
if (fgColor == null) fgColor = DEFAULT_FG_COLOR;
int hSpace = ((GridLayout) table.getLayout()).horizontalSpacing;
int vSpace = ((GridLayout) table.getLayout()).verticalSpacing;
for (int i=0; i<tableData.size(); i++) {
List<DataflowGraphTableData> rowData = tableData.get(i);
int xPos = 0;
int width;
int height;
for (int j=0; j<numCols; j++) {
DataflowGraphTableData data = rowData.get(j);
// check, if Style and Data are correct
String text = "";
int style = SWT.NONE;
if (data != null) {
text = data.getData().toString();
style = data.getStyle();
}
width = colWidths[j].intValue()-hSpace;
height = rowHeight.intValue()-vSpace;
// create the GraphColumn on the given Column
if (j == graphColumn) {
if (i == 0) {
graphArea = createGraphArea(table, bgColor, fgColor,
new Point(xPos, i*rowHeight.intValue()),
new Point(width,numRows*height));
} else {
xPos += colWidths[j].intValue();
}
// it spans over all Rows, so we can continue
// when the Area is set once
continue;
}
// create the Area for showing the Data
createLabel(table, style, text,
bgColor, fgColor,
new Point(xPos,i*rowHeight.intValue()),
new Point(width,height));
xPos += colWidths[j].intValue();
}
}
}
/**
* Creates a Label that shows the given Text
*
* @param parent
* @param style
* @param text, the Text to show
* @param bgColor
* @param fgColor
* @param coord, where to put the Label
* @param size, how big the Label should be
*/
protected void createLabel(Composite parent, int style, String text, Color bgColor, Color fgColor, Point coord, Point size) {
Label label = new Label(parent, style);
GridData data = new GridData();
data.widthHint = size.x;
data.heightHint = size.y;
label.setLayoutData(data);
label.setLocation(coord.x, coord.y);
label.setBackground(bgColor);
label.setText(text==null?"":text);
}
/**
* Creates a Composite, where the graph should be shown
* it spans over all Rows of the Table
*
* @param parent
* @param bgColor
* @param fgColor
* @param coord
* @param size
* @return the Graph's Area
*/
private Composite createGraphArea(Composite parent, Color bgColor, Color fgColor, Point coord, Point size) {
Composite graphCanvas = new Composite(parent, SWT.NONE);
GridData data = new GridData(GridData.FILL_VERTICAL);
data.widthHint = size.x;
data.heightHint = size.y;
data.verticalSpan = numRows;
graphCanvas.setLayoutData(data);
graphCanvas.setSize(size);
graphCanvas.setLayout(new FillLayout());
return graphCanvas;
}
}