/*
* Copyright 2014 Luke Usherwood.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package net.bettyluke.util.swing.monitor;
import java.awt.Color;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.HierarchyEvent;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import net.bettyluke.util.swing.monitor.EdtMonitorModel.StatListener;
public class PerformancePlot extends JTable {
private static final Color BACKGROUND_COLOR = new Color(12, 12, 48);
public static class PlotModel extends AbstractTableModel {
EdtMonitorModel dataModel;
public PlotModel(EdtMonitorModel model) {
dataModel = model;
}
@Override
public int getRowCount() {
return 1;
}
@Override
public int getColumnCount() {
return dataModel.statBins.length;
}
@Override
public PeriodStatistics getValueAt(int rowIndex, int columnIndex) {
return dataModel.getBin(getColumnCount() - columnIndex - 1);
}
void fireChange() {
fireTableChanged(new TableModelEvent(
PlotModel.this, 0, 0, TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE));
}
}
public static class TableController implements StatListener {
private final PerformancePlot plot;
public TableController(PerformancePlot plot) {
this.plot = plot;
}
/**
* Auto-attach/detach the controller to the plot's data-model when the table is showing.
* As well as stopping doing work while it is hidden, this ensures the listener upon the
* {@link EdtMonitorModel} doesn't linger, holding everything in memory after the view has
* been closed/disposed.
*/
public void autoAttachListeners() {
plot.addHierarchyListener(e -> {
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
if (plot.isShowing()) {
attachListener();
} else {
detachListener();
}
}
});
}
public void attachListener() {
plot.getModel().dataModel.addStatListener(this);
plot.getModel().fireChange();
}
public void detachListener() {
plot.getModel().dataModel.removeStatListener(this);
}
@Override
public void statsUpdate(int updateCount) {
plot.setUpdateInProgress(true);
try {
ListSelectionModel selection = plot.getColumnModel().getSelectionModel();
int lead = Math.max(0, selection.getLeadSelectionIndex() - updateCount);
int anchor = Math.max(0, selection.getAnchorSelectionIndex() - updateCount);
plot.getModel().fireChange();
int min = selection.getMinSelectionIndex() - updateCount;
int max = selection.getMaxSelectionIndex() - updateCount;
if (max < 0) {
selection.clearSelection();
} else {
// Note there is a mystical ordering dependency here, solved by experimentation,
// to keep the selection updated accurately while dragging both left-to-right
// and right-to-left.
selection.setLeadSelectionIndex(lead);
selection.setSelectionInterval(Math.max(min, 0), max);
selection.setAnchorSelectionIndex(anchor);
}
} finally {
plot.setUpdateInProgress(false);
}
}
}
private boolean updateInProgress;
public PerformancePlot(EdtMonitorModel model) {
super(new PlotModel(model));
setDefaultRenderer(Object.class, new PercentageTimeTableRenderer());
setRowMargin(0);
TableColumnModel columns = getColumnModel();
columns.setColumnSelectionAllowed(true);
columns.setColumnMargin(1);
columns.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
setBackground(BACKGROUND_COLOR);
setGridColor(getBackground());
for (int i = 0; i < columns.getColumnCount(); i++) {
columns.getColumn(i).setMinWidth(1);
}
TableController controller = new TableController(this);
controller.autoAttachListeners();
addListenerToShowOrHideGrid();
}
private void addListenerToShowOrHideGrid() {
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
// TODO: Magic numbers
getColumnModel().setColumnMargin(getWidth() > 400 ? 1 : 0);
setRowHeight(getHeight());
}
});
}
public void setUpdateInProgress(boolean b) {
updateInProgress = b;
}
public boolean isUpdateInProgress() {
return updateInProgress;
}
@Override
public PlotModel getModel() {
return (PlotModel) super.getModel();
}
}