/*
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.visualvm.modules.tracer.impl.timeline;
import com.sun.tools.visualvm.modules.tracer.ItemValueFormatter;
import com.sun.tools.visualvm.modules.tracer.ProbeItemDescriptor;
import com.sun.tools.visualvm.modules.tracer.TracerProbe;
import com.sun.tools.visualvm.modules.tracer.TracerProbeDescriptor;
import com.sun.tools.visualvm.modules.tracer.impl.options.TracerOptions;
import com.sun.tools.visualvm.modules.tracer.impl.details.DetailsPanel;
import com.sun.tools.visualvm.modules.tracer.impl.details.DetailsTableModel;
import com.sun.tools.visualvm.modules.tracer.impl.export.DataExport;
import com.sun.tools.visualvm.modules.tracer.impl.timeline.TimelineChart.Row;
import com.sun.tools.visualvm.modules.tracer.impl.timeline.items.ValueItemDescriptor;
import java.awt.Color;
import java.text.Format;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import org.netbeans.lib.profiler.charts.ChartContext;
import org.netbeans.lib.profiler.charts.ChartSelectionModel;
import org.netbeans.lib.profiler.charts.ItemSelection;
import org.netbeans.lib.profiler.charts.PaintersModel;
import org.netbeans.lib.profiler.charts.Timeline;
import org.netbeans.lib.profiler.charts.axis.TimeAxisUtils;
import org.netbeans.lib.profiler.charts.xy.XYItemPainter;
import org.netbeans.lib.profiler.charts.xy.XYItemSelection;
import org.netbeans.lib.profiler.charts.xy.synchronous.SynchronousXYItem;
import org.netbeans.lib.profiler.charts.xy.synchronous.SynchronousXYItemsModel;
/**
* All methods must be invoked from the EDT.
*
* @author Jiri Sedlacek
*/
public final class TimelineSupport {
public static final int[] EMPTY_TIMESTAMPS = new int[0];
private final TimelineChart chart;
private final TimelineModel model;
private final SynchronousXYItemsModel itemsModel;
private final PointsComputer pointsComputer;
private final TimelineTooltipOverlay tooltips;
private final TimelineLegendOverlay legend;
private final TimelineUnitsOverlay units;
private final List<TracerProbe> probes = new ArrayList();
private final List<TimelineChart.Row> rows = new ArrayList();
private final DescriptorResolver descriptorResolver;
private final Set<ValuesListener> valuesListeners = new HashSet();
private final Set<Integer> selectedTimestamps = new HashSet();
private final Set<SelectionListener> selectionListeners = new HashSet();
// --- Constructor ---------------------------------------------------------
public TimelineSupport(DescriptorResolver descriptorResolver) {
this.descriptorResolver = descriptorResolver;
// TODO: must be called in EDT!
model = new TimelineModel();
itemsModel = new SynchronousXYItemsModel(model);
chart = new TimelineChart(itemsModel);
tooltips = new TimelineTooltipOverlay(this);
chart.addOverlayComponent(tooltips);
pointsComputer = new PointsComputer();
legend = new TimelineLegendOverlay(chart);
legend.setVisible(TracerOptions.getInstance().isShowLegendEnabled());
chart.addOverlayComponent(legend);
units = new TimelineUnitsOverlay(chart);
units.setVisible(TracerOptions.getInstance().isShowValuesEnabled());
chart.addOverlayComponent(units);
}
// --- Chart access --------------------------------------------------------
TimelineChart getChart() {
return chart;
}
public ChartSelectionModel getChartSelectionModel() {
return chart.getSelectionModel();
}
// --- Indexes computer access ---------------------------------------------
PointsComputer getPointsComputer() {
return pointsComputer;
}
// --- Overlays access -----------------------------------------------------
public void setShowValuesEnabled(boolean enabled) {
units.setVisible(enabled);
}
public boolean isShowValuesEnabled() {
return units.isVisible();
}
public void setShowLegendEnabled(boolean enabled) {
legend.setVisible(enabled);
}
public boolean isShowLegendEnabled() {
return legend.isVisible();
}
// --- Probes management ---------------------------------------------------
public void addProbe(final TracerProbe probe) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
resetValues();
TimelineChart.Row row = chart.addRow();
probes.add(probe);
rows.add(row);
ProbeItemDescriptor[] itemDescriptors = probe.getItemDescriptors();
TimelineXYItem[] items = model.createItems(itemDescriptors);
XYItemPainter[] painters = new XYItemPainter[items.length];
for (int i = 0; i < painters.length; i++)
painters[i] = TimelinePaintersFactory.createPainter(
itemDescriptors[i], i, pointsComputer);
row.addItems(items, painters);
setupOverlays();
}
});
}
public void removeProbe(final TracerProbe probe) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
resetValues();
TimelineChart.Row row = getRow(probe);
chart.removeRow(row);
model.removeItems(row.getItems());
rows.remove(row);
probes.remove(probe);
setupOverlays();
}
});
}
public List<TracerProbe> getProbes() {
return probes;
}
public int getItemsCount() {
return model.getItemsCount();
}
public boolean hasData() {
return model.getTimestampsCount() > 0;
}
// --- Tooltips support ----------------------------------------------------
private void setupOverlays() {
final int rowsCount = chart.getRowsCount();
TimelineTooltipPainter.Model[] rowModels = new TimelineTooltipPainter.Model[rowsCount];
for (int rowIndex = 0; rowIndex < rowModels.length; rowIndex++) {
final TimelineChart.Row row = chart.getRow(rowIndex);
final TracerProbe probe = getProbe(row);
final int itemsCount = row.getItemsCount();
final String[] rowNames = new String[itemsCount];
final ValueItemDescriptor[] viDescriptors = new ValueItemDescriptor[itemsCount];
final String[] unitsStrings = new String[itemsCount];
for (int itemIndex = 0; itemIndex < itemsCount; itemIndex++) {
rowNames[itemIndex] = ((TimelineXYItem)row.getItem(itemIndex)).getName();
viDescriptors[itemIndex] = (ValueItemDescriptor)probe.getItemDescriptors()[itemIndex];
unitsStrings[itemIndex] = viDescriptors[itemIndex].getUnitsString(ItemValueFormatter.FORMAT_TOOLTIP);
}
rowModels[rowIndex] = new TimelineTooltipPainter.Model() {
public int getRowsCount() {
return itemsCount;
}
public String getRowName(int index) {
return rowNames[index];
}
public String getRowValue(int index, long itemValue) {
return viDescriptors[index].getValueString(itemValue,
ItemValueFormatter.FORMAT_TOOLTIP);
}
public String getRowUnits(int index) {
return unitsStrings[index];
}
};
}
tooltips.setupModel(rowModels);
units.setupModel(new TimelineUnitsOverlay.Model() {
private final String LAST_UNITS_STRING = "lastUnitsString"; // NOI18N
private Color[][] rowColors = new Color[rowsCount][];
private String[][] rowMinValues = new String[rowsCount][];
private String[][] rowMaxValues = new String[rowsCount][];
private List<Color> visibleRowItemColors;
private List<String> visibleRowItemMinValues;
private List<String> visibleRowItemMaxValues;
public void prefetch() {
PaintersModel paintersModel = chart.getPaintersModel();
for (int rowIndex = 0; rowIndex < rowsCount; rowIndex++) {
Row row = chart.getRow(rowIndex);
TracerProbe probe = getProbe(row);
int rowItemsCount = row.getItemsCount();
ChartContext rowContext = row.getContext();
long commonMinY = rowContext.getDataOffsetY();
long commonMaxY = commonMinY + rowContext.getDataHeight();
if (visibleRowItemColors != null) {
visibleRowItemColors.clear();
visibleRowItemMinValues.clear();
visibleRowItemMaxValues.clear();
} else {
visibleRowItemColors = new ArrayList(rowItemsCount);
visibleRowItemMinValues = new ArrayList(rowItemsCount);
visibleRowItemMaxValues = new ArrayList(rowItemsCount);
}
boolean sameFactorUnits = true;
double lastDataFactor = -1;
String lastUnitsString = LAST_UNITS_STRING;
for (int itemIndex = 0; itemIndex < rowItemsCount; itemIndex++) {
TimelineXYItem item = (TimelineXYItem)row.getItem(itemIndex);
TimelineXYPainter painter =
(TimelineXYPainter)paintersModel.getPainter(item);
if (painter.isPainting()) {
visibleRowItemColors.add(painter.getDefiningColor());
ValueItemDescriptor descriptor = (ValueItemDescriptor)
probe.getItemDescriptors()[itemIndex];
double dataFactor = descriptor.getDataFactor();
String unitsString = descriptor.getUnitsString(
ItemValueFormatter.FORMAT_UNITS);
if (sameFactorUnits) {
if (lastDataFactor == -1)
lastDataFactor = dataFactor;
else if (lastDataFactor != dataFactor)
sameFactorUnits = false;
lastDataFactor = dataFactor;
if (lastUnitsString == LAST_UNITS_STRING)
lastUnitsString = unitsString;
else if (!equals(lastUnitsString, unitsString))
sameFactorUnits = false;
lastUnitsString = unitsString;
}
String minValueString = descriptor.getValueString(
(long)(commonMinY / painter.dataFactor),
ItemValueFormatter.FORMAT_UNITS);
visibleRowItemMinValues.add(unitsString == null ?
minValueString : minValueString + " " + unitsString);
String maxValueString = descriptor.getValueString(
(long)(commonMaxY / painter.dataFactor),
ItemValueFormatter.FORMAT_UNITS);
visibleRowItemMaxValues.add(unitsString == null ?
maxValueString : maxValueString + " " + unitsString);
}
}
if (sameFactorUnits) {
rowColors[rowIndex] = new Color[] { null };
rowMinValues[rowIndex] =
new String[] { visibleRowItemMinValues.get(0) };
rowMaxValues[rowIndex] =
new String[] { visibleRowItemMaxValues.get(0) };
} else {
rowColors[rowIndex] = visibleRowItemColors.toArray(
new Color[visibleRowItemColors.size()]);
rowMinValues[rowIndex] = visibleRowItemMinValues.toArray(
new String[visibleRowItemMinValues.size()]);
rowMaxValues[rowIndex] = visibleRowItemMaxValues.toArray(
new String[visibleRowItemMaxValues.size()]);
}
}
}
public Color[] getColors(Row row) {
return rowColors[row.getIndex()];
}
public String[] getMinUnits(TimelineChart.Row row) {
return rowMinValues[row.getIndex()];
}
public String[] getMaxUnits(TimelineChart.Row row) {
return rowMaxValues[row.getIndex()];
}
private boolean equals(String s1, String s2) {
if (s1 == null) {
if (s2 == null) return true;
else return false;
} else {
return s1.equals(s2);
}
}
});
}
// --- Rows <-> Probes mapping ---------------------------------------------
TimelineChart.Row getRow(TracerProbe probe) {
return rows.get(probes.indexOf(probe));
}
TracerProbe getProbe(TimelineChart.Row row) {
return probes.get(rows.indexOf(row));
}
// --- Probe -> Descriptor mapping -----------------------------------------
TracerProbeDescriptor getDescriptor(TracerProbe p) {
return descriptorResolver.getDescriptor(p);
}
// --- Values management ---------------------------------------------------
public void addValues(final long timestamp, final long[] newValues) {
int newRow = detailsModel == null ? -1 : detailsModel.getRowCount();
model.addValues(timestamp, newValues);
itemsModel.valuesAdded();
if (newRow != -1) detailsModel.fireTableRowsInserted(newRow, newRow);
fireValuesAdded();
}
public void resetValues() {
model.reset();
itemsModel.valuesReset();
resetSelectedTimestamps();
pointsComputer.reset();
if (detailsModel != null) detailsModel.fireTableStructureChanged();
fireValuesReset();
}
public void exportAllValues(String title) {
final int rowsCount = model.getTimestampsCount();
final int columnsCount = model.getItemsCount();
final Format timeFormatter = new SimpleDateFormat(MessageFormat.format(
TimeAxisUtils.TIME_DATE_FORMAT, new Object[] {
TimeAxisUtils.TIME_MSEC, TimeAxisUtils.DATE_YEAR}));
final List<ProbeItemDescriptor> probeDescriptors = new ArrayList(columnsCount);
for (TracerProbe probe : probes)
probeDescriptors.addAll(Arrays.asList(probe.getItemDescriptors()));
final ValueItemDescriptor[] descriptors = new ValueItemDescriptor[columnsCount];
for (int i = 0; i < columnsCount; i++)
descriptors[i] = (ValueItemDescriptor)probeDescriptors.get(i);
TableModel exportModel = new AbstractTableModel() {
public int getRowCount() {
return rowsCount;
}
public int getColumnCount() {
return columnsCount + 1;
}
public String getColumnName(int columnIndex) {
if (columnIndex == 0) return "Time [ms]";
String unitsString = descriptors[columnIndex - 1].getUnitsString(
ItemValueFormatter.FORMAT_EXPORT);
unitsString = unitsString == null ? "" : " [" + unitsString + "]";
return itemsModel.getItem(columnIndex - 1).getName() + unitsString;
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) return timeFormatter.format(model.
getTimestamp(rowIndex));
long value = itemsModel.getItem(columnIndex - 1).getYValue(rowIndex);
return descriptors[columnIndex - 1].getValueString(value,
ItemValueFormatter.FORMAT_EXPORT);
}
};
DataExport.exportData(exportModel, title);
}
public void exportDetailsValues(String title) {
if (detailsModel == null) return;
final int rowsCount = detailsModel.getRowCount();
final int columnsCount = detailsModel.getColumnCount();
final Format timeFormatter = new SimpleDateFormat(MessageFormat.format(
TimeAxisUtils.TIME_DATE_FORMAT, new Object[] {
TimeAxisUtils.TIME_MSEC, TimeAxisUtils.DATE_YEAR}));
TableModel exportModel = new AbstractTableModel() {
public int getRowCount() {
return rowsCount;
}
public int getColumnCount() {
return columnsCount - 1;
}
public String getColumnName(int columnIndex) {
return detailsModel.getColumnName(columnIndex + 1);
}
public Object getValueAt(int rowIndex, int columnIndex) {
Object value = detailsModel.getValueAt(rowIndex, columnIndex + 1);
if (columnIndex == 0) return timeFormatter.format(value);
return detailsModel.getDescriptor(columnIndex + 1).getValueString(
(Long)value, ItemValueFormatter.FORMAT_EXPORT);
}
};
DataExport.exportData(exportModel, title);
}
public void addValuesListener(ValuesListener listener) {
valuesListeners.add(listener);
}
public void removeValuesListener(ValuesListener listener) {
valuesListeners.remove(listener);
}
private void fireValuesAdded() {
for (ValuesListener listener : valuesListeners)
listener.valuesAdded();
}
private void fireValuesReset() {
for (ValuesListener listener : valuesListeners)
listener.valuesReset();
}
public static interface ValuesListener {
public void valuesAdded();
public void valuesReset();
}
// --- Row selection management --------------------------------------------
private DetailsTableModel detailsModel;
void rowSelectionChanged() {
updateSelectedItems();
notifyRowSelectionChanged();
}
public boolean isRowSelection() {
return chart.isRowSelection();
}
public TableModel getDetailsModel() {
if (!chart.isRowSelection()) detailsModel = null;
else detailsModel = createSelectionModel();
return detailsModel;
}
private DetailsTableModel createSelectionModel() {
final List<SynchronousXYItem> selectedItems = getSelectedItems();
final List<ValueItemDescriptor> selectedDescriptors = getSelectedDescriptors();
int selectedItemsCount = selectedItems.size();
final int columnCount = selectedItemsCount + 2;
final SynchronousXYItem[] selectedItemsArr =
selectedItems.toArray(new SynchronousXYItem[selectedItemsCount]);
final String[] columnNames = new String[columnCount];
columnNames[0] = "Mark";
columnNames[1] = "Time [ms]";
final String[] columnTooltips = new String[columnCount];
columnTooltips[0] = "Mark a timestamp in Timeline view";
columnTooltips[1] = "Timestamp of the data";
for (int i = 2; i < columnCount; i++) {
String itemName = selectedItemsArr[i - 2].getName();
String unitsString = selectedDescriptors.get(i - 2).
getUnitsString(ItemValueFormatter.FORMAT_DETAILS);
unitsString = unitsString == null ? "" : " [" + unitsString + "]";
columnNames[i] = itemName + unitsString;
columnTooltips[i] = selectedDescriptors.get(i - 2).getDescription();
}
return new DetailsTableModel() {
public int getRowCount() {
return model.getTimestampsCount();
}
public int getColumnCount() {
return columnCount;
}
public String getColumnName(int columnIndex) {
return columnNames[columnIndex];
}
public String getColumnTooltip(int columnIndex) {
return columnTooltips[columnIndex];
}
public Class getColumnClass(int columnIndex) {
if (columnIndex == 0) return Boolean.class;
if (columnIndex == 1) return DetailsPanel.class;
return Long.class;
}
public ValueItemDescriptor getDescriptor(int columnIndex) {
if (columnIndex == 0) return null;
if (columnIndex == 1) return null;
return selectedDescriptors.get(columnIndex - 2);
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) return selectedTimestamps.contains(rowIndex);
if (columnIndex == 1) return model.getTimestamp(rowIndex);
return selectedItemsArr[columnIndex - 2].getYValue(rowIndex);
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 0;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (Boolean.TRUE.equals(aValue)) selectTimestamp(rowIndex, true, false);
else unselectTimestamp(rowIndex, false);
}
};
}
// --- Time selection management -------------------------------------------
private static final int SCROLL_MARGIN_LEFT = 10;
private static final int SCROLL_MARGIN_RIGHT = 50;
private boolean hovering;
private boolean hoveredSelected;
void setTimestampHovering(boolean hovering, boolean hoveredSelected) {
this.hovering = hovering;
this.hoveredSelected = hoveredSelected;
notifyTimeSelectionChanged();
}
public void selectTimestamp(int index, boolean scrollToVisible) {
selectTimestamp(index, scrollToVisible, true);
}
private void selectTimestamp(int index, boolean scrollToVisible, boolean notifyTable) {
boolean change = selectedTimestamps.add(index);
if (notifyTable && detailsModel != null)
detailsModel.fireTableCellUpdated(index, 0);
if (change) {
updateSelectedItems();
notifyTimeSelectionChanged();
if (scrollToVisible) highlightTimestamp(index);
}
}
public void unselectTimestamp(int index) {
unselectTimestamp(index, true);
}
public void toggleTimestampSelection(int index) {
if (!selectedTimestamps.contains(index)) selectTimestamp(index, false);
else unselectTimestamp(index);
}
public boolean isTimestampSelected(int index) {
return selectedTimestamps.contains(index);
}
public boolean isTimestampSelection(boolean includeHover) {
int selectedTimestampsCount = selectedTimestamps.size();
if (selectedTimestampsCount == 0) return false;
if (selectedTimestampsCount > 1) return true;
return (includeHover || !hovering || hoveredSelected);
}
private void unselectTimestamp(int index, boolean notifyTable) {
boolean change = selectedTimestamps.remove(index);
if (notifyTable && detailsModel != null)
detailsModel.fireTableCellUpdated(index, 0);
if (change) {
updateSelectedItems();
notifyTimeSelectionChanged();
}
}
public void resetSelectedTimestamps() {
if (selectedTimestamps.isEmpty()) return;
selectedTimestamps.clear();
if (detailsModel != null) detailsModel.fireTableDataChanged();
updateSelectedItems();
notifyTimeSelectionChanged();
}
private void updateSelectedItems() {
List<SynchronousXYItem> selectedItems = getSelectedItems();
List<ItemSelection> selections =
new ArrayList(selectedItems.size() * selectedTimestamps.size());
for (int selectedIndex : selectedTimestamps)
for (SynchronousXYItem selectedItem : selectedItems)
selections.add(new XYItemSelection.Default(selectedItem,
selectedIndex, XYItemSelection.DISTANCE_UNKNOWN));
chart.getSelectionModel().setSelectedItems(selections);
}
public Set<Integer> getSelectedTimestamps() {
return selectedTimestamps;
}
private void highlightTimestamp(int selectedIndex) {
// List<SynchronousXYItem> selectedItems = new ArrayList();
// if (selectedIndex != -1) {
// int rowsCount = chart.getRowsCount();
// for (int i = 0; i < rowsCount; i++)
// selectedItems.addAll(Arrays.asList(chart.getRow(i).getItems()));
// }
// List<ItemSelection> selections = new ArrayList(selectedItems.size());
// if (selectedIndex != -1) {
// for (SynchronousXYItem selectedItem : selectedItems)
// selections.add(new XYItemSelection.Default(selectedItem,
// selectedIndex, XYItemSelection.DISTANCE_UNKNOWN));
// }
//
ChartSelectionModel selectionModel = chart.getSelectionModel();
List<ItemSelection> oldSelection = selectionModel.getHighlightedItems();
int oldSelectedIndex = -1;
if (!oldSelection.isEmpty()) {
XYItemSelection sel = (XYItemSelection)oldSelection.get(0);
oldSelectedIndex = sel.getValueIndex();
}
// selectionModel.setHighlightedItems(selections);
if (selectedIndex != -1)
scrollChartToSelection(oldSelectedIndex, selectedIndex);
}
public void scrollChartToIndex(int index) {
scrollChartToSelection(-1, index);
}
private void scrollChartToSelection(int oldIndex, int newIndex) {
Timeline timeline = itemsModel.getTimeline();
ChartContext context = chart.getChartContext();
long dataOffsetX = context.getDataOffsetX();
long newDataX = timeline.getTimestamp(newIndex);
long newOffsetX = (long)context.getViewWidth(newDataX - dataOffsetX);
long offsetX = chart.getOffsetX();
long viewWidth = context.getViewportWidth();
if (newOffsetX >= offsetX + SCROLL_MARGIN_LEFT &&
newOffsetX <= offsetX + viewWidth - SCROLL_MARGIN_RIGHT) return;
long oldDataX = oldIndex == -1 ? -1 : timeline.getTimestamp(oldIndex);
long oldOffsetX = oldIndex == -1 ? -1 : (long)context.getViewWidth(oldDataX - dataOffsetX);
if (oldIndex == -1) {
chart.setOffset(newOffsetX - context.getViewportWidth() / 2, chart.getOffsetY());
} else if (oldOffsetX > newOffsetX) {
chart.setOffset(newOffsetX - SCROLL_MARGIN_LEFT, chart.getOffsetY());
} else {
chart.setOffset(newOffsetX - context.getViewportWidth() + SCROLL_MARGIN_RIGHT, chart.getOffsetY());
}
chart.repaintDirty();
}
private List<SynchronousXYItem> getSelectedItems() {
List<TimelineChart.Row> selectedRows = chart.getSelectedRows();
List<SynchronousXYItem> selectedItems = new ArrayList();
for (TimelineChart.Row selectedRow : selectedRows)
selectedItems.addAll(Arrays.asList(selectedRow.getItems()));
return selectedItems;
}
private List<ValueItemDescriptor> getSelectedDescriptors() {
List<TimelineChart.Row> selectedRows = chart.getSelectedRows();
List selectedDescriptors = new ArrayList();
for (TimelineChart.Row selectedRow : selectedRows)
selectedDescriptors.addAll(Arrays.asList(getProbe(selectedRow).getItemDescriptors()));
return selectedDescriptors;
}
// --- General selection support -------------------------------------------
public void addSelectionListener(SelectionListener listener) {
selectionListeners.add(listener);
}
public void removeSelectionListener(SelectionListener listener) {
selectionListeners.remove(listener);
}
private void notifyRowSelectionChanged() {
boolean rowsSelected = chart.isRowSelection();
for (SelectionListener selectionListener: selectionListeners)
selectionListener.rowSelectionChanged(rowsSelected);
}
private void notifyTimeSelectionChanged() {
boolean sel = isTimestampSelection(true);
boolean hov = sel && !isTimestampSelection(false);
for (SelectionListener selectionListener: selectionListeners)
selectionListener.timeSelectionChanged(sel, hov);
}
public static interface SelectionListener {
public void rowSelectionChanged(boolean rowsSelected);
public void timeSelectionChanged(boolean timestampsSelected,
boolean justHovering);
}
public static interface DescriptorResolver {
public TracerProbeDescriptor getDescriptor(TracerProbe p);
}
}