package com.ibm.nmon.gui.analysis;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultComboBoxModel;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.RowFilter;
import javax.swing.TransferHandler;
import javax.swing.SwingConstants;
import javax.swing.table.TableRowSorter;
import com.ibm.nmon.analysis.AnalysisSet;
import com.ibm.nmon.analysis.AnalysisSetListener;
import com.ibm.nmon.analysis.Statistic;
import com.ibm.nmon.data.DataType;
import com.ibm.nmon.gui.GUITable;
import com.ibm.nmon.gui.Styles;
import com.ibm.nmon.gui.dnd.TableTransferHandler;
import com.ibm.nmon.gui.file.AnalysisSetFileChooser;
import com.ibm.nmon.gui.main.NMONVisualizerGui;
import com.ibm.nmon.gui.table.ChoosableColumnTableModel;
import com.ibm.nmon.gui.table.DoubleCellRenderer;
import com.ibm.nmon.gui.table.IntegerCellRenderer;
import com.ibm.nmon.gui.table.StringCellRenderer;
import com.ibm.nmon.gui.table.TableColumnChooser;
import com.ibm.nmon.gui.util.ScrollingTableFix;
import com.ibm.nmon.interval.Interval;
import com.ibm.nmon.interval.IntervalListener;
/**
* Holder panel for summary data. Holds a scrolling JTable which supports drag and drop from the
* tree of parsed files. The table is actually 2 tables, one that shows summary data (min, max,
* average, std dev) for selected measurements and another that shows only a single statistic. The
* latter table is meant for easy entry into a spreadsheet for test result tracking but both tables
* support copying CSV formatted text to the clipboard.
*/
public final class SummaryTablePanel extends JPanel implements IntervalListener, AnalysisSetListener,
PropertyChangeListener {
private static final long serialVersionUID = 5581753452007550696L;
private static final Icon TRANSPOSE_ICON = Styles.buildIcon("arrow_rotate_clockwise.png");
private final NMONVisualizerGui gui;
private final JFrame parent;
private final GUITable dataSetTable;
private final GUITable statisticsTable;
private final JPanel statsPanel;
private final JScrollPane scrollPane;
private final JMenu menu;
private final AnalysisSet analysisSet = new AnalysisSet();
private final AnalysisSetFileChooser fileChooser;
@SuppressWarnings("unchecked")
public SummaryTablePanel(NMONVisualizerGui gui, JFrame parent) {
super();
this.gui = gui;
this.parent = parent;
fileChooser = new AnalysisSetFileChooser(gui, analysisSet);
menu = new JMenu("Table");
setupMenu(parent);
setLayout(new BorderLayout());
// combo box with various data statistics for use with the results table
statsPanel = new JPanel();
setupStatsPanel();
JPanel top = new JPanel(new BorderLayout());
setupTopPanel(top);
add(top, BorderLayout.PAGE_START);
// each table has its own data model
dataSetTable = new ByDataSetTable(gui);
statisticsTable = new GUITable(gui);
ByDataSetTableModel dataSetTableModel = new ByDataSetTableModel(gui, analysisSet);
ByStatisticTableModel statTableModel = new ByStatisticTableModel(gui, analysisSet);
dataSetTable.setModel(dataSetTableModel);
statisticsTable.setModel(statTableModel);
setupTable(dataSetTable, parent);
setupTable(statisticsTable, parent);
scrollPane = new JScrollPane(statisticsTable);
scrollPane.getViewport().setBackground(java.awt.Color.WHITE);
// could be inefficient to have both tables updating when only 1 is visible...
scrollPane.addComponentListener(new ScrollingTableFix(statisticsTable, scrollPane));
scrollPane.addComponentListener(new ScrollingTableFix(dataSetTable, scrollPane));
scrollPane.setBorder(Styles.createBottomLineBorder(this));
add(scrollPane, BorderLayout.CENTER);
// alert users they can drag onto the tables
JLabel label = new JLabel("Click and drag measurements from the tree onto this table");
label.setFont(Styles.BOLD);
label.setHorizontalAlignment(SwingConstants.CENTER);
add(label, BorderLayout.PAGE_END);
analysisSet.addListener(this);
// check the count column, if it is 0, do not display
((TableRowSorter<ByStatisticTableModel>) statisticsTable.getRowSorter())
.setRowFilter(new RowFilter<ByStatisticTableModel, Integer>() {
@Override
public boolean include(RowFilter.Entry<? extends ByStatisticTableModel, ? extends Integer> entry) {
ByStatisticTableModel model = ((ByStatisticTableModel) entry.getModel());
int idx = model.getColumnIndex(Statistic.COUNT.toString());
if (idx == -1) {
throw new IllegalStateException(model + "has no column named " + Statistic.COUNT.toString());
}
else {
Object value = model.getEnabledValueAt(entry.getIdentifier(), idx);
int i = (Integer) value;
return i != 0;
}
}
});
}
private void updateTable() {
if (statsPanel.isVisible()) {
((AnalysisSetTableModel) dataSetTable.getModel()).fireTableDataChanged();
}
else {
ByStatisticTableModel model = (ByStatisticTableModel) statisticsTable.getModel();
model.updateGranularityMax();
model.fireTableDataChanged();
}
}
private void updateStatisticsComboBox() {
String newName = Statistic.GRANULARITY_MAXIMUM.getName(gui.getGranularity());
@SuppressWarnings("unchecked")
DefaultComboBoxModel<String> model = (DefaultComboBoxModel<String>) ((JComboBox<String>) statsPanel
.getComponent(1)).getModel();
boolean reselect = false;
if (model.getSelectedItem() == model.getElementAt(3)) {
reselect = true;
}
model.removeElementAt(3);
model.insertElementAt(newName, 3);
if (reselect) {
model.setSelectedItem(newName);
}
}
// when the analysis is not enabled, do not update the table
@Override
public void setEnabled(boolean enabled) {
if (enabled) {
gui.getIntervalManager().addListener(this);
gui.addPropertyChangeListener("granularity", this);
gui.addPropertyChangeListener("granularity", (ByStatisticTableModel) statisticsTable.getModel());
// note update setSaveEnabled() if menu position changes
gui.getMainFrame().getJMenuBar().add(menu, 3);
gui.getMainFrame().getJMenuBar().revalidate();
updateStatisticsComboBox();
updateTable();
}
else {
gui.getIntervalManager().removeListener(this);
gui.removePropertyChangeListener("granularity", this);
gui.removePropertyChangeListener("granularity", (ByStatisticTableModel) statisticsTable.getModel());
gui.getMainFrame().getJMenuBar().remove(menu);
gui.getMainFrame().getJMenuBar().revalidate();
}
// note there is no need to remove the 2 table models as DataSetListeners and
// AnalysisSetListeners
// the only thing that adds data to the analysis set is drag and drop, which is
// impossible if this panel is not displayed
// adding or removing a DataSet fires table changes, but the table is not refreshed
// until it is displayed
super.setEnabled(enabled);
}
// this class is an IntervalListener rather than each of the table models so that only 1 table
// model can be updated on interval events
@Override
public void intervalAdded(Interval interval) {}
@Override
public void intervalRemoved(Interval interval) {}
@Override
public void intervalsCleared() {}
@Override
public void currentIntervalChanged(Interval interval) {
updateTable();
}
@Override
public void intervalRenamed(Interval interval) {}
@Override
public void analysisAdded(DataType type) {
setSaveEnabled();
}
@Override
public void analysisAdded(DataType type, String field) {
setSaveEnabled();
}
@Override
public void analysisRemoved(DataType type) {
setSaveEnabled();
}
@Override
public void analysisRemoved(DataType type, String field) {
setSaveEnabled();
}
@Override
public void analysisCleared() {
setSaveEnabled();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("granularity".equals(evt.getPropertyName())) {
updateTable();
updateStatisticsComboBox();
}
}
private void setSaveEnabled() {
boolean enabled = analysisSet.size() > 0;
gui.getMainFrame().getJMenuBar().getMenu(3).getItem(1).setEnabled(enabled);
}
private void setupTable(final JTable table, JFrame parent) {
table.setDragEnabled(true);
table.setTransferHandler(new TableTransferHandler(table, analysisSet));
table.setDefaultRenderer(Double.class, new DoubleCellRenderer());
table.setDefaultRenderer(Integer.class, new IntegerCellRenderer());
table.setDefaultRenderer(String.class, new StringCellRenderer());
table.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
mouseReleased(e);
}
@Override
public void mouseReleased(MouseEvent e) {
// right click only if any rows are selected
if (e.isPopupTrigger()) {
JPopupMenu menu = new JPopupMenu();
if ((table.rowAtPoint(e.getPoint()) != -1) && table.getSelectedRowCount() > 0) {
JMenuItem item = new JMenuItem("Copy");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;
table.getTransferHandler().exportToClipboard(table,
SummaryTablePanel.this.gui.getMainFrame().getToolkit().getSystemClipboard(),
TransferHandler.COPY);
}
});
menu.add(item);
item = new JMenuItem("Copy All");
item.addActionListener(copyTable);
menu.add(item);
menu.addSeparator();
item = new JMenuItem("Remove");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Set<String> keys = new java.util.HashSet<String>();
for (int row : table.getSelectedRows()) {
keys.add(((AnalysisSetTableModel) table.getModel()).getKey(table
.convertRowIndexToModel(row)));
}
for (String key : keys) {
analysisSet.removeData(key);
}
}
});
menu.add(item);
menu.addSeparator();
}
JMenuItem item = new JMenuItem("Select Columns...");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;
ChoosableColumnTableModel model = (ChoosableColumnTableModel) table.getModel();
new TableColumnChooser(gui, SummaryTablePanel.this.parent, model);
}
});
menu.add(item);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
});
}
private void setupStatsPanel() {
statsPanel.setVisible(false);
JLabel label = new JLabel("Statistic:");
label.setHorizontalAlignment(SwingConstants.TRAILING);
label.setFont(Styles.LABEL);
statsPanel.add(label);
JComboBox<String> statistic = new JComboBox<String>();
DefaultComboBoxModel<String> model = (DefaultComboBoxModel<String>) statistic.getModel();
model.addElement(Statistic.AVERAGE.toString());
model.addElement(Statistic.MINIMUM.toString());
model.addElement(Statistic.MAXIMUM.toString());
model.addElement(Statistic.GRANULARITY_MAXIMUM.getName(gui.getGranularity()));
model.addElement(Statistic.STD_DEV.toString());
model.addElement(Statistic.MEDIAN.toString());
model.addElement(Statistic.SUM.toString());
model.addElement(Statistic.COUNT.toString());
statistic.setSelectedItem(Statistic.AVERAGE);
statistic.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@SuppressWarnings("unchecked")
Object o = ((JComboBox<String>) e.getSource()).getModel().getSelectedItem();
if (o.getClass().equals(String.class)) {
((ByDataSetTableModel) dataSetTable.getModel()).setStatistic(Statistic.GRANULARITY_MAXIMUM);
}
else {
((ByDataSetTableModel) dataSetTable.getModel()).setStatistic((Statistic) o);
}
}
});
statsPanel.add(statistic);
}
private void setupTopPanel(JPanel top) {
JButton copy = new JButton("Copy");
copy.setIcon(Styles.COPY_ICON);
copy.addActionListener(copyTable);
JButton clear = new JButton("Clear");
clear.setIcon(Styles.CLEAR_ICON);
clear.addActionListener(clearTable);
// holder so both buttons are grouped on the edge of the panel
JPanel buttonPanel = new JPanel();
buttonPanel.add(copy);
buttonPanel.add(clear);
// switches between the two tables
JButton transpose = new JButton("Transpose");
transpose.setIcon(TRANSPOSE_ICON);
transpose.addActionListener(transposeTable);
// use temp panel to keep button from filling the space
JPanel temp = new JPanel();
temp.add(transpose);
// check box, stats combo box and buttons all at the top
top.add(temp, BorderLayout.LINE_START);
top.add(statsPanel, BorderLayout.CENTER);
top.add(buttonPanel, BorderLayout.LINE_END);
}
private void setupMenu(JFrame parent) {
menu.setMnemonic('t');
JMenuItem item = new JMenuItem("Load Definition...");
item.setMnemonic('n');
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
item.addActionListener(loadAnalysis);
menu.add(item);
item = new JMenuItem("Save Definition...");
item.setMnemonic('s');
item.setIcon(Styles.SAVE_ICON);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
item.addActionListener(saveAnalysis);
item.setEnabled(analysisSet.size() > 0);
menu.add(item);
menu.addSeparator();
item = new JMenuItem("Copy All");
item.setMnemonic('a');
item.setIcon(Styles.COPY_ICON);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK
| InputEvent.SHIFT_DOWN_MASK));
item.addActionListener(copyTable);
menu.add(item);
item = new JMenuItem("Clear");
item.setMnemonic('c');
item.setIcon(Styles.CLEAR_ICON);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_DOWN_MASK
| InputEvent.SHIFT_DOWN_MASK));
item.addActionListener(clearTable);
menu.add(item);
menu.addSeparator();
item = new JMenuItem("Select Columns...");
item.setMnemonic('m');
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK
| InputEvent.SHIFT_DOWN_MASK));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;
new TableColumnChooser(gui, SummaryTablePanel.this.parent, ((AnalysisSetTableModel) table.getModel()));
}
});
menu.add(item);
item = new JMenuItem("Transpose");
item.setMnemonic('t');
item.setIcon(TRANSPOSE_ICON);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.CTRL_DOWN_MASK
| InputEvent.SHIFT_DOWN_MASK));
item.addActionListener(transposeTable);
menu.add(item);
}
private final ActionListener copyTable = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;
table.selectAll();
SummaryTablePanel.this.gui.getMainFrame().getToolkit().getSystemClipboard()
.setContents(((TableTransferHandler) table.getTransferHandler()).copyAll(), null);
}
};
private final ActionListener clearTable = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
analysisSet.clearData();
}
};
private final ActionListener saveAnalysis = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (analysisSet.size() > 0) {
fileChooser.save();
}
}
};
private final ActionListener loadAnalysis = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fileChooser.load();
}
};
private final Action transposeTable = new AbstractAction() {
private static final long serialVersionUID = 8993427044737174594L;
@Override
public void actionPerformed(ActionEvent e) {
if (statsPanel.isVisible()) {
statsPanel.setVisible(false);
scrollPane.setViewportView(statisticsTable);
((AnalysisSetTableModel) statisticsTable.getModel()).fireTableDataChanged();
}
else {
statsPanel.setVisible(true);
scrollPane.setViewportView(dataSetTable);
((AnalysisSetTableModel) dataSetTable.getModel()).fireTableDataChanged();
}
SummaryTablePanel.this.requestFocus();
}
};
}