package org.nightlabs.jfire.issuetracking.ui.issue;
import java.awt.Dimension;
import java.io.ByteArrayInputStream;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jdo.JDOHelper;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.nightlabs.base.ui.composite.XComposite;
import org.nightlabs.base.ui.table.IColumnComparatorProvider;
import org.nightlabs.eclipse.compatibility.CompatibleGC;
import org.nightlabs.jfire.issue.Issue;
import org.nightlabs.jfire.issue.IssuePriority;
import org.nightlabs.jfire.issue.Issue.FieldName;
import org.nightlabs.jfire.issue.config.IssueTableConfigModule;
import org.nightlabs.jfire.issue.id.IssueID;
import org.nightlabs.jfire.issue.issuemarker.IssueMarker;
import org.nightlabs.jfire.issuetracking.ui.resource.Messages;
import org.nightlabs.jfire.jbpm.graph.def.Statable;
import org.nightlabs.jfire.jbpm.graph.def.StatableLocal;
import org.nightlabs.jfire.jbpm.graph.def.State;
import org.nightlabs.jfire.table.config.ColumnContentProperty;
import org.nightlabs.jfire.table.config.IColumnContentDescriptor;
import org.nightlabs.tableprovider.ui.TableLabelProvider;
import org.nightlabs.util.BaseComparator;
/**
* This is essentially a label-provider, one that is configured on-the-fly for use in the context
* of the {@link IssueTable}, based on instructions received from the {@link IssueTableConfigModule}.
* This now supports both Text and Images to be correctly loaded from the server and displayed onto the
* this label-provider.
*
* TESTING still in progress. This class might change... ideas mainly from Daniel's TableProviderBuilder.
*
* @author khaireel at nightlabs dot de
*/
public class ConfigurableIssueTableLabelProvider
//extends ColumnSpanLabelProvider
extends CellLabelProvider
implements TableLabelProvider<IssueID, Issue>, IColumnComparatorProvider, ITableLabelProvider
{
// The scope of the fields; in retrospect, this should be the same for ALL JDOObjects represented and displayed in the table columns (right?).
private String scope; // = TradePlugin.ZONE_SALE;
// Keeps tab of the fieldNames from the given ColumnDescriptors.
private Set<String> collatedFieldNames = null;
private List<? extends IColumnContentDescriptor> columnContentDescriptors;
// Helpers.
private static DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
private static DateFormat deadlineDateTimeFormat = DateFormat.getDateInstance(DateFormat.SHORT);
private static Dimension ISSUE_MARKER_IMAGE_DIMENSION = new Dimension(16, 16);
// Special variables for handling Images loaded from the server.
private int maxIssueMarkerCountPerIssue = -1;
private Map<String, Image> imageKey2Image = new HashMap<String, Image>();
private XComposite parent = null;
/**
* Creates a new instance of a ConfigurableIssueTableLabelProvider.
* Note: If this LabelProvider is setup to also dispense Images, then it's internal tracking mechanism (involving disposals, etc.)
* will require a reference to the XComposite.
*/
public ConfigurableIssueTableLabelProvider
(ColumnViewer columnViewer, XComposite parent, List<? extends IColumnContentDescriptor> columnContentDescriptors, String scope) {
// super(columnViewer);
super();
this.parent = parent;
this.columnContentDescriptors = columnContentDescriptors;
this.scope = scope;
// Sets up the Image tracking mechanism.
if (parent != null && !parent.isDisposed()) {
// Attach the images to the parent's dispose listener.
parent.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent event) { disposeAllImages(); }
});
}
}
/**
* Creates a new instance of a ConfigurableTableLabelProvider, solely for use to handle Texts.
*/
public ConfigurableIssueTableLabelProvider(ColumnViewer columnViewer, List<? extends IColumnContentDescriptor> columnContentDescriptors, String scope) {
this(columnViewer, null, columnContentDescriptors, scope);
}
/**
* @return true if the given fieldName is within the specifications of the columnDescriptors.
*/
public boolean isFieldNameInConfiguration(String fieldName) {
if (collatedFieldNames == null) {
collatedFieldNames = new HashSet<String>();
for (IColumnContentDescriptor columnContentDescriptor : columnContentDescriptors)
for (String fName : columnContentDescriptor.getFieldNames())
collatedFieldNames.add(fName);
}
return collatedFieldNames.contains(fieldName);
}
/**
* Call this method when setting the input to the table.
*/
public void setMaxIssueMarkerCountPerIssue(int maxIssueMarkerCountPerIssue) {
disposeAllImages();
this.maxIssueMarkerCountPerIssue = maxIssueMarkerCountPerIssue;
}
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
// [Section] The general known handlers of a LabelProvider.
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
// @Override
protected int[][] getColumnSpan(Object element) { return null; }
@Override
public String getColumnText(Object element, int spanColIndex) {
if (!(element instanceof Issue) ||spanColIndex > columnContentDescriptors.size()-1)
return null;
// Retrieve the necessary instruction from the corresponding ColumnDescriptor.
IColumnContentDescriptor columnContentDescriptor = columnContentDescriptors.get(spanColIndex);
return !columnContentDescriptor.getContentProperty().equals(ColumnContentProperty.IMAGE_ONLY) ? getText(columnContentDescriptor.getFieldNames(), (Issue) element, scope) : null;
}
@Override
public Image getColumnImage(Object element, int spanColIndex) {
if (parent == null || !(element instanceof Issue) || spanColIndex > columnContentDescriptors.size()-1)
return null;
// Note: In the current codes, there is an internal management of Images, which are tracked, and properly disposed. See IssueTable's imageKey2Image.
// Also, in the current settings in the IssueTable, it handles multiple images, by building a new combined image, based on the combined keys.
//
// Retrieve the necessary instruction from the corresponding ColumnDescriptor.
IColumnContentDescriptor columnContentDescriptor = columnContentDescriptors.get(spanColIndex);
return !columnContentDescriptor.getContentProperty().equals(ColumnContentProperty.TEXT_ONLY) ? getColumnImage(columnContentDescriptor.getFieldNames(), (Issue) element, scope) : null;
}
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
// [Section] The configurable handlers.
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
@Override
public String getText(Set<String> fieldNames, Issue element, String scope) {
String lblText = "";
for (String fieldName : fieldNames)
lblText = String.format("%s %s", lblText, getTextForField(fieldName, element, scope));
return lblText;
}
@Override
public Image getColumnImage(Set<String> fieldNames, Issue element, String scope) {
for (String fieldName : fieldNames)
if (fieldName.equals(Issue.FieldName.issueMarkers))
return getCombiIssueMarkerImage(element);
return null;
}
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
// [Section] Central archive: The place to define how textual-information based on a given fieldName of a JDOObject is to be returned.
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
// Tests only. Not sure how to arrive at Daniel's final plan with properties and extension points... yet.
protected String getTextForField(String fieldName, Issue issue, String scope) {
// So far, we shall assume that the JDOObject has been successfully retrieved...
if (fieldName.equals(Issue.FieldName.issueID))
return issue.getIssueIDAsString();
if (fieldName.equals(Issue.FieldName.createTimestamp))
return dateTimeFormat.format(issue.getCreateTimestamp());
if (fieldName.equals(Issue.FieldName.issueType))
return issue.getIssueType().getName().getText();
if (fieldName.equals(Issue.FieldName.subject))
return issue.getSubject().getText();
if (fieldName.equals(Issue.FieldName.issuePriority))
return issue.getIssuePriority() == null
? Messages.getString("org.nightlabs.jfire.issuetracking.ui.issue.IssueTable.tableColumnText.priority.noData") // <-- FIXME
: issue.getIssuePriority().getIssuePriorityText().getText();
if (fieldName.equals(Issue.FieldName.issueSeverityType))
return issue.getIssueSeverityType() == null
? Messages.getString("org.nightlabs.jfire.issuetracking.ui.issue.IssueTable.tableColumnText.severity.noData") // <-- FIXME
: issue.getIssueSeverityType().getIssueSeverityTypeText().getText();
if (fieldName.equals(Issue.FieldName.description))
if (issue.getDescription() != null) {
String descriptionText = issue.getDescription().getText();
if (descriptionText.indexOf('\n') != -1)
return descriptionText.substring(0, descriptionText.indexOf('\n')).concat("(...)"); // From original codes: Which handles multi-lines.
else
return descriptionText;
}
if (fieldName.equals(Issue.FieldName.state)) {
// From the original codes.
Statable statable = issue;
StatableLocal statableLocal = statable.getStatableLocal();
State state = statable.getState();
State state2 = statableLocal.getState();
if (state2 != null)
if (state == null)
state = state2;
else if (state.getCreateDT().compareTo(state2.getCreateDT()) < 0)
state = state2;
if (state != null)
return state.getStateDefinition().getName().getText();
return "";
}
if (fieldName.equals(Issue.FieldName.deadlineTimestamp)) {
if (issue.getDeadlineTimestamp() != null)
return deadlineDateTimeFormat.format(issue.getDeadlineTimestamp());
}
if (fieldName.equals(Issue.FieldName.issueResolution)) {
if (issue.getIssueResolution() != null)
return issue.getIssueResolution().getName().getText();
}
return "";
}
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
// [Section] Image management routine.
// ---------------------------------------------------------------------------------------- || -------------------------------------------->>
private void disposeAllImages() {
for (Image image : imageKey2Image.values())
image.dispose();
imageKey2Image.clear();
}
protected Image getCombiIssueMarkerImage(Issue issue) {
if (maxIssueMarkerCountPerIssue < 0)
throw new IllegalStateException("maxIssueMarkerCountPerIssue < 0");
String imageKey = generateCombiIssueMarkerImageKey(issue);
Image combiImage = imageKey2Image.get(imageKey);
if (combiImage == null && maxIssueMarkerCountPerIssue > 0) { // It is possible that none of the Issues has a single IssueMarker; in which case, we dont need to display any icons.
combiImage = new Image(
parent.getDisplay(),
ISSUE_MARKER_IMAGE_DIMENSION.width * maxIssueMarkerCountPerIssue + maxIssueMarkerCountPerIssue - 1,
ISSUE_MARKER_IMAGE_DIMENSION.height
);
// RAP Implementation is not yet working
GC gc = CompatibleGC.newGC(combiImage);
try {
Iterator<IssueMarker> itIssueMarkers = issue.getIssueMarkers().iterator();
for(int i=0; i<maxIssueMarkerCountPerIssue; i++) {
if (!itIssueMarkers.hasNext())
break;
IssueMarker issueMarker = itIssueMarkers.next();
String issueMarkerIDString = JDOHelper.getObjectId(issueMarker).toString();
Image icon = imageKey2Image.get(issueMarkerIDString);
if (icon == null)
if (issueMarker.getIcon16x16Data() != null) {
ByteArrayInputStream in = new ByteArrayInputStream(issueMarker.getIcon16x16Data());
icon = new Image(parent.getDisplay(), in);
imageKey2Image.put(issueMarkerIDString, icon);
}
if (icon != null)
gc.drawImage(icon, ISSUE_MARKER_IMAGE_DIMENSION.width * i + i, 0);
}
} finally {
gc.dispose();
}
imageKey2Image.put(imageKey, combiImage);
}
return combiImage;
}
private String generateCombiIssueMarkerImageKey(Issue issue) {
List<IssueMarker> issueMarkers = new ArrayList<IssueMarker>(issue.getIssueMarkers());
Collections.sort(issueMarkers, new Comparator<IssueMarker>() {
@Override
public int compare(IssueMarker o1, IssueMarker o2) {
int c = o1.getOrganisationID().compareTo(o2.getOrganisationID());
if (c != 0)
return c;
return o1.getIssueMarkerID() < o2.getIssueMarkerID() ? -1 : 1;
}
});
StringBuilder sb = new StringBuilder();
for (IssueMarker issueMarker : issueMarkers) {
if (sb.length() > 0)
sb.append("::");
sb.append(issueMarker.getOrganisationID());
sb.append('/');
sb.append(issueMarker.getIssueMarkerID());
}
return sb.toString();
}
private List<String> specialComparableFieldNames;
protected List<String> getSpecialComparableFieldNames() {
if (specialComparableFieldNames == null) {
specialComparableFieldNames = new ArrayList<String>();
specialComparableFieldNames.add(FieldName.deadlineTimestamp);
specialComparableFieldNames.add(FieldName.createTimestamp);
specialComparableFieldNames.add(FieldName.issueMarkers);
specialComparableFieldNames.add(FieldName.issuePriority);
specialComparableFieldNames.add(FieldName.issueID);
}
return specialComparableFieldNames;
}
@Override
public Comparator<?> getColumnComparator(Object element, int columnIndex)
{
if (element instanceof Issue)
{
// Issue issue = (Issue) element;
IColumnContentDescriptor cd = getColumnDescriptor(columnIndex);
for (String fieldName : cd.getFieldNames()) {
if (getSpecialComparableFieldNames().contains(fieldName)) {
return new IssueColumnComparator(cd.getFieldNames());
}
}
}
return null;
}
protected IColumnContentDescriptor getColumnDescriptor(int columnIndex)
{
return columnContentDescriptors.get(columnIndex);
}
class IssueColumnComparator implements Comparator<Issue>
{
private Set<String> fieldNames;
public IssueColumnComparator(Set<String> fieldNames) {
this.fieldNames = fieldNames;
}
@Override
public int compare(Issue i1, Issue i2)
{
if (fieldNames.contains(FieldName.deadlineTimestamp)) {
Date d1 = i1.getDeadlineTimestamp();
Date d2 = i2.getDeadlineTimestamp();
int result = BaseComparator.comparatorNullCheck(d2, d1);
if (result == BaseComparator.COMPARE_RESULT_NOT_NULL) {
return d1.compareTo(d2);
}
return result;
}
else if (fieldNames.contains(FieldName.createTimestamp)) {
Date d1 = i1.getCreateTimestamp();
Date d2 = i2.getCreateTimestamp();
int result = BaseComparator.comparatorNullCheck(d2, d1);
if (result == BaseComparator.COMPARE_RESULT_NOT_NULL) {
return d1.compareTo(d2);
}
return result;
}
else if (fieldNames.contains(FieldName.issueMarkers)) {
Set<IssueMarker> markers1 = i1.getIssueMarkers();
Set<IssueMarker> markers2 = i2.getIssueMarkers();
if (markers1.isEmpty() && markers2.isEmpty())
return 0;
else if (markers1.isEmpty() && !markers2.isEmpty())
return -1;
else if (!markers1.isEmpty() && markers2.isEmpty())
return 1;
else {
// TODO: define priority order for issueMarkers
return markers1.size() - markers2.size();
}
}
else if (fieldNames.contains(FieldName.issuePriority)) {
IssuePriority p1 = i1.getIssuePriority();
IssuePriority p2 = i2.getIssuePriority();
int result = BaseComparator.comparatorNullCheck(p2, p1);
if (result == BaseComparator.COMPARE_RESULT_NOT_NULL)
{
List<IssuePriority> ips1 = i1.getIssueType().getIssuePriorities();
List<IssuePriority> ips2 = i2.getIssueType().getIssuePriorities();
int index1 = ips1.indexOf(p1);
int index2 = ips2.indexOf(p2);
return index1 - index2;
}
}
else if (fieldNames.contains(FieldName.issueID)) {
return ((int) i1.getIssueID() - (int)i2.getIssueID());
}
return 0;
}
}
@Override
public void update(ViewerCell cell) {
// TODO Auto-generated method stub
}
}