blob: 40675055d092d273a8b9b97574f0ad9de3afcfba [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2008-2010 The University of Manchester
*
* Modifications to the initial code base are copyright of their
* respective authors, or their employers as appropriate.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
******************************************************************************/
package net.sf.taverna.t2.workbench.report.view;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import net.sf.taverna.t2.lang.observer.Observable;
import net.sf.taverna.t2.lang.observer.Observer;
import net.sf.taverna.t2.lang.ui.DeselectingButton;
import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
import net.sf.taverna.t2.ui.menu.MenuManager;
import net.sf.taverna.t2.visit.VisitReport;
import net.sf.taverna.t2.visit.VisitReport.Status;
import net.sf.taverna.t2.workbench.edits.EditManager;
import net.sf.taverna.t2.workbench.file.FileManager;
import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
import net.sf.taverna.t2.workbench.report.DataflowReportEvent;
import net.sf.taverna.t2.workbench.report.ReportManager;
import net.sf.taverna.t2.workbench.report.ReportManagerEvent;
import net.sf.taverna.t2.workbench.report.explainer.VisitExplainer;
import net.sf.taverna.t2.workbench.selection.DataflowSelectionModel;
import net.sf.taverna.t2.workbench.selection.SelectionManager;
import net.sf.taverna.t2.workbench.selection.events.DataflowSelectionMessage;
import net.sf.taverna.t2.workbench.ui.Workbench;
import net.sf.taverna.t2.workbench.ui.zaria.UIComponentSPI;
import net.sf.taverna.t2.workflowmodel.Dataflow;
import net.sf.taverna.t2.workflowmodel.Processor;
import net.sf.taverna.t2.workflowmodel.health.RemoteHealthChecker;
import org.apache.log4j.Logger;
/**
* @author Alan R Williams
*
*/
@SuppressWarnings("serial")
public class ReportViewComponent extends JPanel implements UIComponentSPI {
private static Logger logger = Logger.getLogger(ReportViewComponent.class);
private static int TABLE_MARGIN = 5;
private ReportManager reportManager;
private List<VisitExplainer> visitExplainers;
private JLabel dataflowName;
// private static JTextArea solutionDescription;
// private static JTextArea issueDescription;
// private static JSplitPane subSplitPane = new JSplitPane();
private JTable table;
private static final JComponent defaultExplanation = new ReadOnlyTextArea(
"No additional explanation available");
private static final JComponent defaultSolution = new ReadOnlyTextArea("No suggested solutions");
private static final JComponent okExplanation = new ReadOnlyTextArea("No problem found");
private static final JComponent okSolution = new ReadOnlyTextArea("No change necessary");
private static final JComponent nothingToExplain = new ReadOnlyTextArea("No message selected");
private static final JComponent nothingToSolve = new ReadOnlyTextArea("No message selected");
private JComponent explanation = okExplanation;
private JComponent solution = okSolution;
private JTabbedPane messagePane;
private final JScrollPane explanationScrollPane = new JScrollPane();
private final JScrollPane solutionScrollPane = new JScrollPane();
private VisitReport lastSelectedReport = null;
private ReportViewTableModel reportViewTableModel;
private ReportViewConfigureAction reportViewConfigureAction = new ReportViewConfigureAction();
private JComboBox shownReports = null;
private TableListener tableListener = null;
private VisitReportProxySet ignoredReports = new VisitReportProxySet();
JButton ignoreReportButton;
public static String ALL_INCLUDING_IGNORED = "All";
public static String ALL_EXCEPT_IGNORED = "All except ignored";
protected FileManager fileManager;
protected FileManagerObserver fileManagerObserver = new FileManagerObserver();
private SelectionManager openedWorkflowsManager;
private Observer<DataflowSelectionMessage> workflowSelectionListener = new DataflowSelectionListener();
private final Workbench workbench;
private final EditManager editManager;
private final MenuManager menuManager;
public ReportViewComponent(EditManager editManager, FileManager fileManager, MenuManager menuManager,
ReportManager reportManager, Workbench workbench,
SelectionManager selectionManager, List<VisitExplainer> visitExplainers) {
super();
this.editManager = editManager;
this.fileManager = fileManager;
this.menuManager = menuManager;
this.reportManager = reportManager;
this.workbench = workbench;
openedWorkflowsManager = selectionManager;
this.visitExplainers = visitExplainers;
reportManager.addObserver(new ReportManagerObserver());
fileManager.addObserver(fileManagerObserver);
initialise();
}
private JScrollPane tableScrollPane;
private void initialise() {
shownReports = new JComboBox(new String[] { ALL_INCLUDING_IGNORED, ALL_EXCEPT_IGNORED,
ReportViewTableModel.WARNINGS_AND_ERRORS, ReportViewTableModel.JUST_ERRORS });
shownReports.setSelectedIndex(1);
shownReports.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ex) {
showReport(fileManager.getCurrentDataflow());
}
});
this.setLayout(new BorderLayout());
JPanel headerPanel = new JPanel();
headerPanel.setLayout(new BorderLayout());
dataflowName = new JLabel();
dataflowName.setText("No workflow");
headerPanel.add(dataflowName, BorderLayout.WEST);
JPanel shownReportsPanel = new JPanel();
shownReportsPanel.setLayout(new BorderLayout());
shownReportsPanel.add(new JLabel("Show messages:"), BorderLayout.WEST);
shownReportsPanel.add(shownReports, BorderLayout.EAST);
headerPanel.add(shownReportsPanel, BorderLayout.EAST);
this.add(headerPanel, BorderLayout.NORTH);
JPanel splitPanel = new JPanel();
splitPanel.setLayout(new GridLayout(2, 1));
tableScrollPane = new JScrollPane();
splitPanel.add(tableScrollPane);
messagePane = new JTabbedPane();
messagePane.addTab("Explanation", explanationScrollPane);
messagePane.addTab("Solution", solutionScrollPane);
splitPanel.add(messagePane);
this.add(splitPanel, BorderLayout.CENTER);
ignoreReportButton = new DeselectingButton("Hide message", new AbstractAction() {
public void actionPerformed(ActionEvent ex) {
if (lastSelectedReport != null) {
if (ignoredReports.contains(lastSelectedReport)) {
ignoredReports.remove(lastSelectedReport);
} else {
ignoredReports.add(lastSelectedReport);
if (shownReports.getSelectedItem().equals(ALL_INCLUDING_IGNORED)) {
shownReports.setSelectedItem(ALL_EXCEPT_IGNORED);
}
showReport();
}
}
}
});
// JButton quickCheckButton = new JButton(new
// ReportOnWorkflowAction("Quick check", false, true));
JButton fullCheckButton = new DeselectingButton("Validate workflow",
new ReportOnWorkflowAction("Validate workflow", true, false, editManager,
fileManager, reportManager, workbench) {
@Override
public void actionPerformed(ActionEvent e) {
// Full check always starts from scratch
RemoteHealthChecker.clearCachedEndpointStatus();
super.actionPerformed(e);
}
});
JPanel validateButtonPanel = new JPanel();
validateButtonPanel.add(ignoreReportButton);
// validateButtonPanel.add(quickCheckButton);
validateButtonPanel.add(fullCheckButton);
this.add(validateButtonPanel, BorderLayout.SOUTH);
showReport(fileManager.getCurrentDataflow());
}
public void onDisplay() {
}
public ImageIcon getIcon() {
return null;
}
public void onDispose() {
}
private void createTable(Dataflow dataflow, Map<Object, Set<VisitReport>> reportEntries) {
reportViewTableModel = new ReportViewTableModel(dataflow, reportEntries,
(String) shownReports.getSelectedItem(), ignoredReports, reportManager);
if (table == null) {
table = new JTable(reportViewTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setRowSelectionAllowed(true);
tableListener = new TableListener();
table.getSelectionModel().addListSelectionListener(tableListener);
table.setSurrendersFocusOnKeystroke(false);
table.getInputMap(JInternalFrame.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "configure");
table.getInputMap(JInternalFrame.WHEN_FOCUSED).put(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "configure");
table.getActionMap().put("configure", reportViewConfigureAction);
table.setDefaultRenderer(Status.class, new StatusRenderer());
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
} else {
table.setModel(reportViewTableModel);
}
packColumn(table, 0, TABLE_MARGIN, true);
packColumn(table, 1, TABLE_MARGIN, true);
packColumn(table, 2, TABLE_MARGIN, true);
packColumn(table, 3, TABLE_MARGIN, false);
packColumn(table, 4, TABLE_MARGIN, false);
}
private void showReport() {
showReport(fileManager.getCurrentDataflow());
}
private void showReport(final Dataflow dataflow) {
if (dataflow != null) {
String dfName = dataflow.getLocalName();
if (dfName.length() > 20) {
dfName = dfName.substring(0, 17) + "...";
}
dataflowName.setText(dfName);
} else {
dataflowName.setText("No workflow");
}
createTable(dataflow, reportManager.getReports(dataflow));
tableScrollPane.setViewportView(table);
boolean found = false;
DataflowSelectionModel selectionModel = openedWorkflowsManager
.getDataflowSelectionModel(fileManager.getCurrentDataflow());
Set<Object> selection = selectionModel.getSelection();
Object selectedObject = null;
if (selection.size() == 1) {
selectedObject = selection.iterator().next();
}
if ((lastSelectedReport != null)
&& (lastSelectedReport.getSubject().equals(selectedObject))) {
VisitReportProxy lastSelectedReportProxy = new VisitReportProxy(lastSelectedReport);
for (int i = 0; i < table.getRowCount(); i++) {
VisitReport vr = reportViewTableModel.getReport(i);
VisitReportProxy vrProxy = new VisitReportProxy(vr);
if (vrProxy.equals(lastSelectedReportProxy)) {
table.setRowSelectionInterval(i, i);
found = true;
scrollToVisible(i);
break;
}
}
/*
* if (!found) { found =
* selectSubject(lastSelectedReport.getSubject()); }
*/
}
if ((!found) && (selectedObject != null)) {
found = selectSubject(selectedObject);
}
if (!found) {
lastSelectedReport = null;
table.clearSelection();
}
updateExplanation(lastSelectedReport);
updateMessages();
messagePane.revalidate();
this.revalidate();
}
private boolean selectSubject(Object subject) {
int currentlySelected = table.getSelectedRow();
if (currentlySelected != -1) {
Object currentSubject = reportViewTableModel.getReport(currentlySelected).getSubject();
if (currentSubject == subject) {
return true;
}
}
for (int i = 0; i < table.getRowCount(); i++) {
VisitReport vr = reportViewTableModel.getReport(i);
if (vr.getSubject() == subject) {
table.setRowSelectionInterval(i, i);
scrollToVisible(i);
return true;
}
}
return false;
}
private void updateMessages() {
JPanel explainPanel = wrapComponent(explanation);
explanationScrollPane.setViewportView(explainPanel);
solutionScrollPane.setViewportView(wrapComponent(solution));
if (lastSelectedReport != null) {
ignoreReportButton.setEnabled(true);
if (ignoredReports.contains(lastSelectedReport)) {
ignoreReportButton.setText("Include message");
} else {
ignoreReportButton.setText("Ignore message");
}
} else {
ignoreReportButton.setEnabled(false);
ignoreReportButton.setText("Ignore message");
}
messagePane.revalidate();
}
private final class ReportManagerObserver implements Observer<ReportManagerEvent> {
public void notify(Observable<ReportManagerEvent> sender, ReportManagerEvent event)
throws Exception {
Dataflow currentDataflow = fileManager.getCurrentDataflow();
if (event instanceof DataflowReportEvent) {
DataflowReportEvent dre = (DataflowReportEvent) event;
final Dataflow dataflow = dre.getDataflow();
if (dataflow.equals(currentDataflow)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showReport(dataflow);
}
});
}
}
}
}
private void scrollToVisible(int rowIndex) {
Rectangle rect = table.getCellRect(rowIndex, 0, true);
table.scrollRectToVisible(rect);
}
final class TableListener implements ListSelectionListener {
public TableListener() {
}
public void valueChanged(ListSelectionEvent e) {
int row = table.getSelectedRow();
if (row >= 0) {
DataflowSelectionModel dsm = openedWorkflowsManager
.getDataflowSelectionModel(fileManager.getCurrentDataflow());
dsm.clearSelection();
VisitReport vr = reportViewTableModel.getReport(row);
final Object subject = reportViewTableModel.getSubject(row);
dsm.addSelection(subject);
updateExplanation(vr);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateMessages();
if (subject instanceof Processor) {
reportViewConfigureAction.setConfiguredProcessor((Processor) subject, menuManager);
}
}
});
}
}
}
private void updateExplanation(VisitReport vr) {
lastSelectedReport = vr;
if (vr == null) {
explanation = nothingToExplain;
solution = nothingToSolve;
return;
}
if (vr.getStatus().equals(Status.OK)) {
explanation = okExplanation;
solution = okSolution;
return;
}
for (VisitExplainer ve : visitExplainers) {
if (ve.canExplain(vr.getKind(), vr.getResultId())) {
try {
explanation = ve.getExplanation(vr);
} catch (Exception e) {
logger.error("Error creating explanation", e);
explanation = null;
}
if (explanation == null) {
explanation = defaultExplanation;
}
try {
solution = ve.getSolution(vr);
} catch (Exception e) {
logger.error("Error creating soluttion", e);
solution = null;
}
if (solution == null) {
solution = defaultSolution;
}
return;
}
}
explanation = defaultExplanation;
solution = defaultSolution;
}
// Sets the preferred width of the visible column specified by vColIndex.
// The column
// will be just wide enough to show the column head and the widest cell in
// the column.
// margin pixels are added to the left and right
// (resulting in an additional width of 2*margin pixels).
public void packColumn(JTable table, int vColIndex, int margin, boolean fixWidth) {
// TableModel model = table.getModel();
DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel();
TableColumn col = colModel.getColumn(vColIndex);
int width = 0;
// Get width of column header
TableCellRenderer renderer = col.getHeaderRenderer();
if (renderer == null) {
renderer = table.getTableHeader().getDefaultRenderer();
}
Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false,
false, 0, 0);
width = comp.getPreferredSize().width;
// Get maximum width of column data
for (int r = 0; r < table.getRowCount(); r++) {
renderer = table.getCellRenderer(r, vColIndex);
comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, vColIndex),
false, false, r, vColIndex);
width = Math.max(width, comp.getPreferredSize().width);
}
// Add margin
width += 2 * margin;
// Set the width
col.setPreferredWidth(width);
if (fixWidth) {
col.setMaxWidth(width);
col.setMinWidth(width);
}
}
private static Insets rightGap = new Insets(0, 0, 20, 0);
private JPanel wrapComponent(JComponent c) {
JPanel result = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = 2;
gbc.weightx = 0.9;
gbc.insets = rightGap;
result.add(c, gbc);
c.setBackground(SystemColor.text);
gbc.weightx = 0.9;
gbc.weighty = 0.9;
gbc.gridx = 0;
gbc.gridy++;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.BOTH;
JPanel filler = new JPanel();
filler.setBackground(SystemColor.text);
result.setBackground(SystemColor.text);
result.add(filler, gbc);
return result;
}
/**
* Update workflow explorer when current dataflow changes or closes.
*
*/
public class FileManagerObserver implements Observer<FileManagerEvent> {
public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
throws Exception {
if (message instanceof SetCurrentDataflowEvent) { // switched the
// current
// workflow
final Dataflow newWF = ((SetCurrentDataflowEvent) message).getDataflow(); // the
// newly
// switched
// to
// workflow
if (newWF != null) {
openedWorkflowsManager.getDataflowSelectionModel(newWF).addObserver(
workflowSelectionListener);
}
}
}
}
/**
* Observes events on workflow Selection Manager, i.e. when a workflow node
* is selected in the graph view.
*/
private final class DataflowSelectionListener implements Observer<DataflowSelectionMessage> {
public void notify(Observable<DataflowSelectionMessage> sender,
DataflowSelectionMessage message) throws Exception {
DataflowSelectionModel selectionModel = openedWorkflowsManager
.getDataflowSelectionModel(fileManager.getCurrentDataflow());
Set<Object> selection = selectionModel.getSelection();
if (selection.size() == 1) {
if (!selectSubject(selection.iterator().next())) {
lastSelectedReport = null;
table.clearSelection();
}
}
}
}
}