/*
* 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 org.netbeans.lib.profiler.charts.ChartOverlay;
import org.netbeans.lib.profiler.charts.ChartSelectionListener;
import org.netbeans.lib.profiler.charts.ItemSelection;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.SwingUtilities;
import org.netbeans.lib.profiler.charts.ChartConfigurationListener;
import org.netbeans.lib.profiler.charts.ChartContext;
import org.netbeans.lib.profiler.charts.swing.Utils;
import org.netbeans.lib.profiler.charts.xy.XYItem;
import org.netbeans.lib.profiler.charts.xy.XYItemSelection;
import org.netbeans.lib.profiler.charts.xy.synchronous.SynchronousXYItemsModel;
/**
*
* @author Jiri Sedlacek
*/
final class TimelineSelectionOverlay extends ChartOverlay {
private TimelineChart chart;
private TimelineSupport support;
private int selectionExtent;
private ConfigurationListener configurationListener;
private SelectionListener selectionListener;
private TimeSelectionListener timeSelectionListener;
private final Set<Point> highlightedValues;
private final Set<Point> selectedValues;
private Paint sMarkPaint;
private Paint sOddPerfPaint;
private Paint sEvenPerfPaint;
private Paint hMarkPaint;
private Paint hOddPerfPaint;
private Paint hEvenPerfPaint;
private Stroke markStroke;
private Stroke oddPerfStroke;
private Stroke evenPerfStroke;
TimelineSelectionOverlay() {
configurationListener = new ConfigurationListener();
selectionListener = new SelectionListener();
timeSelectionListener = new TimeSelectionListener();
highlightedValues = new HashSet();
selectedValues = new HashSet();
initDefaultValues();
}
// --- Internal API --------------------------------------------------------
final void registerChart(TimelineSupport support) {
unregisterListener();
this.support = support;
this.chart = support.getChart();
registerListener();
}
final void unregisterChart(TimelineSupport support) {
unregisterListener();
this.support = null;
this.chart = null;
}
// --- Private implementation ----------------------------------------------
private void registerListener() {
if (support == null || chart == null) return;
chart.addConfigurationListener(configurationListener);
chart.addRowListener(configurationListener);
chart.getSelectionModel().addSelectionListener(selectionListener);
support.addSelectionListener(timeSelectionListener);
}
private void unregisterListener() {
if (support == null || chart == null) return;
chart.removeConfigurationListener(configurationListener);
chart.removeRowListener(configurationListener);
chart.getSelectionModel().removeSelectionListener(selectionListener);
support.removeSelectionListener(timeSelectionListener);
}
private void initDefaultValues() {
sMarkPaint = new Color(120, 120, 120);
sOddPerfPaint = new Color(120, 120, 120);
sEvenPerfPaint = Color.WHITE;
hMarkPaint = new Color(80, 80, 80);
hOddPerfPaint = Color.BLACK;
hEvenPerfPaint = Color.WHITE;
markStroke = new BasicStroke(2.8f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
oddPerfStroke = new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 0, new float[] { 1.0f, 3.0f }, 0);
evenPerfStroke = new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 0, new float[] { 1.0f, 3.0f }, 2);
selectionExtent = 3;
}
private final Set<Integer> paintedLines = new HashSet();
public void paint(Graphics g) {
if (highlightedValues.isEmpty() && selectedValues.isEmpty()) return;
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHints(chart.getRenderingHints());
Iterator<Point> it = selectedValues.iterator();
paintedLines.clear();
int height = getHeight();
int extentP = 1 - selectionExtent;
int extentD = selectionExtent * 2 - 1;
while (it.hasNext()) {
Point p = it.next();
int x = p.x;
int y = p.y;
if (!paintedLines.contains(x)) {
g2.setPaint(sEvenPerfPaint);
g2.setStroke(evenPerfStroke);
g2.drawLine(x, 0, x, height);
g2.setPaint(sOddPerfPaint);
g2.setStroke(oddPerfStroke);
g2.drawLine(x, 0, x, height);
g2.setPaint(sMarkPaint);
g2.setStroke(markStroke);
paintedLines.add(x);
}
if (y - selectionExtent >= 0 && y + selectionExtent <= height)
g2.fillOval(x + extentP, y + extentP, extentD, extentD);
}
it = highlightedValues.iterator();
paintedLines.clear();
while (it.hasNext()) {
Point p = it.next();
int x = p.x;
int y = p.y;
if (!paintedLines.contains(x)) {
g2.setPaint(hEvenPerfPaint);
g2.setStroke(evenPerfStroke);
g2.drawLine(x, 0, x, height);
g2.setPaint(hOddPerfPaint);
g2.setStroke(oddPerfStroke);
g2.drawLine(x, 0, x, height);
g2.setPaint(hMarkPaint);
g2.setStroke(markStroke);
paintedLines.add(x);
}
if (y - selectionExtent >= 0 && y + selectionExtent <= height)
g2.fillOval(x + extentP, y + extentP, extentD, extentD);
}
}
private void vLineBoundsChanged(Set<Point> oldSelection, Set<Point> newSelection) {
SortedSet<Integer> selectionBounds = new TreeSet();
for (Point p : oldSelection) selectionBounds.add(p.x);
int selections = selectionBounds.size();
if (selections == 1) {
repaint(selectionBounds.first() - selectionExtent,
0, selectionExtent * 2, getHeight());
selectionBounds.clear();
}
for (Point p : newSelection) selectionBounds.add(p.x);
selections = selectionBounds.size();
if (selections == 1) {
repaint(selectionBounds.first() - selectionExtent,
0, selectionExtent * 2, getHeight());
} else if (selections > 1) {
int firstX = selectionBounds.first() - selectionExtent;
int lastX = selectionBounds.last() + selectionExtent;
repaint(firstX, 0, lastX - firstX, getHeight());
}
}
private List<ItemSelection> getSelections() {
List<ItemSelection> items = new ArrayList();
Set<Integer> timestamps = support.getSelectedTimestamps();
SynchronousXYItemsModel model = (SynchronousXYItemsModel)chart.getItemsModel();
int itemsCount = model.getItemsCount();
for (int itemIndex = 0; itemIndex < itemsCount; itemIndex++)
for (int timestamp : timestamps)
items.add(new XYItemSelection.Default(model.getItem(itemIndex),
timestamp, XYItemSelection.DISTANCE_UNKNOWN));
return items;
}
private static void updateValues(Set<Point> values,
List<ItemSelection> selectedItems,
TimelineChart chart) {
values.clear();
for (ItemSelection sel : selectedItems) {
XYItemSelection xySel = (XYItemSelection)sel;
XYItem item = xySel.getItem();
TimelineXYPainter painter = (TimelineXYPainter)chart.getPaintersModel().getPainter(item);
ChartContext context = chart.getChartContext(item);
long xValue = item.getXValue(xySel.getValueIndex());
long yValue = item.getYValue(xySel.getValueIndex());
int xPos = Utils.checkedInt(Math.ceil(context.getViewX(xValue)));
int yPos = Utils.checkedInt(Math.ceil(painter.getItemView(yValue, item, context)));
if (xPos >= 0 && xPos <= chart.getWidth()) values.add(new Point(xPos, yPos));
}
}
private class ConfigurationListener extends ChartConfigurationListener.Adapter
implements TimelineChart.RowListener {
private final Runnable selectionUpdater = new Runnable() {
public void run() {
Set<Point> oldSelectedValues = new HashSet(selectedValues);
updateValues(selectedValues, getSelections(), chart);
vLineBoundsChanged(oldSelectedValues, selectedValues);
Set<Point> oldValues = new HashSet(highlightedValues);
updateValues(highlightedValues, chart.getSelectionModel().
getHighlightedItems(), chart);
vLineBoundsChanged(oldValues, highlightedValues);
}
};
public void contentsUpdated(long offsetX, long offsetY,
double scaleX, double scaleY,
long lastOffsetX, long lastOffsetY,
double lastScaleX, double lastScaleY,
int shiftX, int shiftY) {
if (highlightedValues.isEmpty() && !support.isTimestampSelection(true)) return;
if (lastOffsetX != offsetX || lastOffsetY != offsetY ||
scaleX != lastScaleX || scaleY != lastScaleY)
SwingUtilities.invokeLater(selectionUpdater);
}
public void rowsAdded(List<TimelineChart.Row> rows) { selectionUpdater.run(); };
public void rowsRemoved(List<TimelineChart.Row> rows) { selectionUpdater.run(); };
public void rowsResized(List<TimelineChart.Row> rows) { selectionUpdater.run(); };
}
private class SelectionListener implements ChartSelectionListener {
public void selectionModeChanged(int newMode, int oldMode) {}
public void selectionBoundsChanged(Rectangle newBounds, Rectangle oldBounds) {}
public void selectedItemsChanged(List<ItemSelection> currentItems,
List<ItemSelection> addedItems, List<ItemSelection> removedItems) {
}
public void highlightedItemsChanged(List<ItemSelection> currentItems,
List<ItemSelection> addedItems, List<ItemSelection> removedItems) {
Set<Point> oldHighlightedValues = new HashSet(highlightedValues);
updateValues(highlightedValues, currentItems, chart);
vLineBoundsChanged(oldHighlightedValues, highlightedValues);
}
}
private class TimeSelectionListener implements TimelineSupport.SelectionListener {
public void rowSelectionChanged(boolean rowsSelected) {}
public void timeSelectionChanged(boolean timestampsSelected, boolean justHovering) {
Set<Point> oldSelectedValues = new HashSet(selectedValues);
updateValues(selectedValues, getSelections(), chart);
vLineBoundsChanged(oldSelectedValues, selectedValues);
}
}
}