package rocks.inspectit.ui.rcp.details;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.SectionPart;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
/**
* Our own HTML-like table to use for displaying one part of the details for an element.
*
* @see #DetailsTable(Composite, FormToolkit, String, int)
* @see #addContentRow(String, Image, DetailsCellContent[])
*
* @author Ivan Senic
*
*/
public class DetailsTable extends SectionPart {
/**
* Width of the row title.
*/
private static final int ROW_TITLE_WIDTH_HINT = 150;
/**
* Maximum suggested width for the text box displaying large text.
*/
private static final int CONTENT_CONTROL_MAX_WIDTH = 600;
/**
* Maximum suggested height for the text box displaying large text.
*/
private static final int CONTENT_CONTROL_MAX_HEIGHT = 150;
/**
* Maximum suggested height for the tables.
*/
private static final int CONTENT_TABLE_MAX_HEIGHT = 200;
/**
* {@link FormText} to create elements.
*/
private FormToolkit toolkit;
/**
* Number of columns in the information area. Note that this number should not include the
* column used for row title.
*/
private int columns;
/**
* Content composite.
*/
private Composite contentComposite;
/**
* Copy string builder.
*/
private StringBuilder copyStringBuilder = new StringBuilder(); // NOPMD
/**
* {@link GC} used to calculate the text size in pixels.
*
* @see GC#textExtent(String)
*/
private GC gc;
/**
* List of controls that should be resized according to the table main composite resizing.
*/
List<Text> textControls = new ArrayList<>();
/**
* Default constructor.
*
* @param parent
* Composite to create on.
* @param toolkit
* {@link FormToolkit}
* @param heading
* Name of the table that will be displayed as header.
* @param columns
* Number of columns in the information area. Note that this number should not
* include the column used for row title.
*/
public DetailsTable(Composite parent, FormToolkit toolkit, String heading, int columns) {
super(parent, toolkit, ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED);
this.columns = columns;
this.toolkit = toolkit;
getSection().setLayout(new TableWrapLayout());
createHeading(heading);
copyStringBuilder.append(heading);
copyStringBuilder.append('\n');
this.gc = new GC(parent);
initTable();
}
/**
* Adds one row to the table..
*
* @param title
* Row title.
* @param image
* Row image displayed next to the row title. Can be <code>null</code> for no image.
* @param cellContents
* Array of {@link DetailsCellContent} objects representing the data in the row. Note
* that the provided array should have same length as the {@link #columns} value
* provided in the constructor.
*/
public void addContentRow(String title, Image image, DetailsCellContent... cellContents) {
createRowHeading(title, image);
if (null != title) {
copyStringBuilder.append(title);
}
copyStringBuilder.append('\t');
// add each column
for (int i = 0; i < columns; i++) {
if (i < cellContents.length) {
DetailsCellContent cellContent = cellContents[i];
String content = cellContent.getText();
// calculate the properties based on the text
int heightHint = 0;
boolean canFitWidth = true;
if (null != content) {
// first calculate the properties based on the text
canFitWidth = canFit(content, CONTENT_CONTROL_MAX_WIDTH);
if (!canFitWidth) {
heightHint = heightHint(content, CONTENT_CONTROL_MAX_WIDTH, CONTENT_CONTROL_MAX_HEIGHT);
}
}
TableWrapData tableWrapData = getLayoutData(cellContent);
if (!canFitWidth) {
// define styles based on the scroll bars
int style = SWT.WRAP | SWT.READ_ONLY | SWT.MULTI | SWT.V_SCROLL;
// save border, create with none, and then reset
int borderStyle = toolkit.getBorderStyle();
toolkit.setBorderStyle(SWT.NULL);
Text text = toolkit.createText(contentComposite, cellContent.getText(), style);
toolkit.setBorderStyle(borderStyle);
tableWrapData.grabHorizontal = true;
tableWrapData.maxWidth = CONTENT_CONTROL_MAX_WIDTH;
tableWrapData.maxHeight = CONTENT_CONTROL_MAX_HEIGHT;
tableWrapData.heightHint = heightHint;
text.setLayoutData(tableWrapData);
textControls.add(text);
} else {
FormText formText = toolkit.createFormText(contentComposite, false);
fillFormText(formText, cellContent);
formText.setLayoutData(tableWrapData);
}
// copy append if there is text, if not then image tool-tip
if (null != cellContent.getText()) {
copyStringBuilder.append(cellContent.getText());
} else if (null != cellContent.getImageToolTip()) {
copyStringBuilder.append(cellContent.getImageToolTip());
}
} else {
toolkit.createLabel(contentComposite, "");
}
if (i < (columns - 1)) {
copyStringBuilder.append('\t');
}
}
copyStringBuilder.append('\n');
}
/**
* Adds one row to the table by inserting SWT table as content.
*
* @param title
* Row title.
* @param image
* Row image displayed next to the row title. Can be <code>null</code> for no image.
* @param cols
* Number of columns in the SWT table
* @param headers
* Titles for the columns.
* @param rows
* Rows data. Each string array represents one row in the table.
*/
public void addContentTable(String title, Image image, int cols, String[] headers, List<String[]> rows) {
createRowHeading(title, image);
if (null != title) {
copyStringBuilder.append(title);
}
Table table = toolkit.createTable(contentComposite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.VIRTUAL);
table.setHeaderVisible(true);
TableWrapData tableWrapData = new TableWrapData(TableWrapData.FILL);
tableWrapData.colspan = this.columns;
tableWrapData.maxWidth = CONTENT_CONTROL_MAX_WIDTH;
tableWrapData.maxHeight = CONTENT_TABLE_MAX_HEIGHT;
table.setLayoutData(tableWrapData);
for (int i = 0; i < cols; i++) {
TableColumn tableColumn = new TableColumn(table, SWT.LEFT);
tableColumn.setResizable(true);
if (i < headers.length) {
tableColumn.setText(headers[i]);
}
}
for (String[] row : rows) {
new TableItem(table, SWT.NONE).setText(row);
copyStringBuilder.append('\t');
for (int i = 0; i < row.length; i++) {
copyStringBuilder.append(row[i]);
if (i < (row.length - 1)) {
copyStringBuilder.append('\t');
}
}
copyStringBuilder.append('\n');
}
for (TableColumn column : table.getColumns()) {
column.pack();
}
}
/**
* @param cellContent
* {@link DetailsCellContent}
* @return Returns the {@link TableWrapData} based in the information in the
* {@link DetailsCellContent}.
*/
private TableWrapData getLayoutData(DetailsCellContent cellContent) {
TableWrapData tableWrapData = new TableWrapData(TableWrapData.FILL);
tableWrapData.colspan = cellContent.getColspan();
tableWrapData.grabHorizontal = cellContent.isGrab();
return tableWrapData;
}
/**
* Fills {@link FormText} with content based on the {@link DetailsCellContent}.
*
* @param formText
* {@link FormText} to fill.
* @param cellContent
* {@link DetailsCellContent} describing the content.
*/
private void fillFormText(FormText formText, DetailsCellContent cellContent) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<form><p>");
if (null != cellContent.getText()) {
String text = StringUtils.replaceEach(cellContent.getText(), new String[] { "<", ">", "&" }, new String[] { "<", ">", "&" });
stringBuilder.append(text);
}
if (null != cellContent.getImage()) {
Label label = new Label(formText, SWT.NONE);
label.setImage(cellContent.getImage());
if (null != cellContent.getImageToolTip()) {
label.setToolTipText(cellContent.getImageToolTip());
}
stringBuilder.append("<control href=\"ctrl\"/>");
formText.setControl("ctrl", label);
}
stringBuilder.append("</p></form>");
formText.setText(stringBuilder.toString(), true, false);
}
/**
* Calculates if the given text can fit into specified max width. Uses {@link #gc} to make these
* calculations.
*
* @param text
* Text to check.
* @param maxWidth
* Width to check against.
* @return <code>true</code> if given text can fit in the specified width, <code>false</code>
* otherwise.
*/
private boolean canFit(String text, int maxWidth) {
Point fontPoint = gc.textExtent(text);
return fontPoint.x < maxWidth;
}
/**
* Returns the height hint for the text that should fit into specified maximum width. If the
* calculated height hint is higher than the given max height, then max height is returned.
*
* @param text
* Text to check
* @param maxWidth
* Width to fit into
* @param maxHeight
* Maximum hint to consider
* @return Calculated hint
*/
private int heightHint(String text, int maxWidth, int maxHeight) {
Point fontPoint = gc.textExtent(text);
// -50 to ensure word splitting to another row does not kill us
int rows = (fontPoint.x / (maxWidth - 50)) + 1;
return Math.min(rows * fontPoint.y, maxHeight);
}
/**
* Initializes the {@link #contentComposite}.
*/
private void initTable() {
contentComposite = toolkit.createComposite(getSection());
contentComposite.setLayoutData(new TableWrapData(TableWrapData.FILL));
TableWrapLayout layout = new TableWrapLayout();
layout.numColumns = columns + 1;
layout.horizontalSpacing = 2;
layout.verticalSpacing = 2;
contentComposite.setLayout(layout);
getSection().setClient(contentComposite);
contentComposite.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Point size = contentComposite.getSize();
// decreased for 20 because of margins/paddings
int contentCurrentMaxWidth = size.x - ROW_TITLE_WIDTH_HINT - 20;
// need to re-calculate the text controls
for (Text t : textControls) {
Object layoutData = t.getLayoutData();
if (layoutData instanceof TableWrapData) {
int heightHint = heightHint(t.getText(), contentCurrentMaxWidth, CONTENT_CONTROL_MAX_HEIGHT);
((TableWrapData) layoutData).maxWidth = contentCurrentMaxWidth;
((TableWrapData) layoutData).heightHint = heightHint;
}
}
}
});
}
/**
* Creates header.
*
* @param heading
* Heading to use.
*/
private void createHeading(String heading) {
getSection().setText(heading);
}
/**
* Creates row heading.
*
* @param title
* Title to use.
* @param image
* Image to use. Can be <code>null</code>.
*/
private void createRowHeading(String title, Image image) {
// first create help composite
Composite composite = toolkit.createComposite(contentComposite);
composite.setLayoutData(new TableWrapData(TableWrapData.FILL));
GridLayout compositeLayout = new GridLayout(1, true);
compositeLayout.marginHeight = 0;
compositeLayout.marginBottom = 0;
compositeLayout.horizontalSpacing = 0;
compositeLayout.verticalSpacing = 0;
composite.setLayout(compositeLayout);
// create row text in bold
FormText rowText = toolkit.createFormText(composite, false);
if (null != image) {
rowText.setText("<form><p><img href=\"img\"/> <span color=\"headingColor\">" + title + "</span></p></form>", true, false);
rowText.setImage("img", image);
} else {
rowText.setText("<form><p><span color=\"headingColor\">" + title + "</span></p></form>", true, false);
}
rowText.setColor("headingColor", toolkit.getColors().getColor(IFormColors.TITLE));
GridData rowTextGridData = getFixedWidthGridData(ROW_TITLE_WIDTH_HINT);
rowTextGridData.verticalAlignment = SWT.TOP;
rowText.setLayoutData(rowTextGridData);
}
/**
* Creates {@link GridData} with fixed width.
*
* @param width
* wanted width.
* @return {@link GridData}
*/
private GridData getFixedWidthGridData(int width) {
GridData gridData = new GridData();
gridData.minimumWidth = width;
gridData.widthHint = width;
return gridData;
}
/**
* Returns String of table content for copying purposes.
*
* @return Returns String of table content for copying purposes.
*/
public String getCopyString() {
return copyStringBuilder.toString();
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
gc.dispose();
super.dispose();
}
}