/*
* 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.impl.options.TracerOptions;
import com.sun.tools.visualvm.modules.tracer.impl.swing.ScrollBar;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import org.netbeans.lib.profiler.charts.ChartConfigurationListener;
import org.netbeans.lib.profiler.charts.ChartSelectionModel;
import org.netbeans.lib.profiler.charts.Timeline;
import org.netbeans.lib.profiler.charts.swing.Utils;
import org.netbeans.lib.profiler.charts.xy.synchronous.SynchronousXYItemsModel;
import org.openide.util.ImageUtilities;
/**
*
* @author Jiri Sedlacek
*/
final class ChartPanel extends JPanel {
private static final String ZOOM_IN_STRING = "Zoom in"; //"Zoom In (Mouse Wheel)";
private static final String ZOOM_OUT_STRING = "Zoom out"; //"Zoom Out (Mouse Wheel)";
private static final String FIXED_SCALE_STRING = "Fixed scale"; //"Fixed Scale (Mouse Wheel Click)";
private static final String SCALE_TO_FIT_STRING = "Scale to fit"; //"Scale To Fit (Mouse Wheel Click)";
private static final Icon ZOOM_IN_ICON = new ImageIcon(ImageUtilities.loadImage(
"com/sun/tools/visualvm/modules/tracer/impl/resources/zoomIn.png")); // NOI18N
private static final Icon ZOOM_OUT_ICON = new ImageIcon(ImageUtilities.loadImage(
"com/sun/tools/visualvm/modules/tracer/impl/resources/zoomOut.png")); // NOI18N
private static final Icon FIXED_SCALE_ICON = new ImageIcon(ImageUtilities.loadImage(
"com/sun/tools/visualvm/modules/tracer/impl/resources/zoom.png")); // NOI18N
private static final Icon SCALE_TO_FIT_ICON = new ImageIcon(ImageUtilities.loadImage(
"com/sun/tools/visualvm/modules/tracer/impl/resources/scaleToFit.png")); // NOI18N
private static final Icon ZMWHEEL_ICON = new ImageIcon(ImageUtilities.loadImage(
"com/sun/tools/visualvm/modules/tracer/impl/resources/zmwheel.png")); // NOI18N
private static final Icon HMWHEEL_ICON = new ImageIcon(ImageUtilities.loadImage(
"com/sun/tools/visualvm/modules/tracer/impl/resources/hmwheel.png")); // NOI18N
private static final Icon VMWHEEL_ICON = new ImageIcon(ImageUtilities.loadImage(
"com/sun/tools/visualvm/modules/tracer/impl/resources/vmwheel.png")); // NOI18N
private final TimelineChart chart;
private ZoomInAction zoomInAction;
private ZoomOutAction zoomOutAction;
private ToggleViewAction toggleViewAction;
private final JScrollBar hScrollBar;
private final JScrollBar vScrollBar;
private final MouseWheelListener defaultWheelHandler;
private AbstractButton mouseZoom;
private AbstractButton mouseHScroll;
private AbstractButton mouseVScroll;
ChartPanel(TimelineChart chart, TimelineSupport support) {
this.chart = chart;
boolean speed = Utils.forceSpeed();
chart.setBackground(Color.WHITE);
if (speed && TracerOptions.getInstance().isRowsSelectionEnabled())
chart.addPreDecorator(new RowBackgroundDecorator(chart));
chart.addPreDecorator(new RowBoundsDecorator(chart));
chart.addPostDecorator(new RowForegroundDecorator(chart,
TracerOptions.getInstance().isRowsDecorationEnabled(),
speed ? false : TracerOptions.getInstance().isRowsSelectionEnabled()));
TimelineSelectionOverlay selectionOverlay = new TimelineSelectionOverlay();
chart.addOverlayComponent(selectionOverlay);
selectionOverlay.registerChart(support);
ChartSelectionModel selectionModel = chart.getSelectionModel();
if (selectionModel != null) {
selectionModel.setHoverMode(ChartSelectionModel.HOVER_EACH_NEAREST);
selectionModel.setMoveMode(ChartSelectionModel.SELECTION_LINE_V);
}
TimelineAxis axis = new TimelineAxis(chart, support);
hScrollBar = new ScrollBar(JScrollBar.HORIZONTAL);
hScrollBar.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
if (hScrollBar.getValueIsAdjusting())
ChartPanel.this.chart.updateSelection(false, hScrollBar);
else
ChartPanel.this.chart.updateSelection(true, hScrollBar);
}
});
chart.attachHorizontalScrollBar(hScrollBar);
vScrollBar = new ScrollBar(JScrollBar.VERTICAL);
vScrollBar.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
if (vScrollBar.getValueIsAdjusting())
ChartPanel.this.chart.updateSelection(false, vScrollBar);
else
ChartPanel.this.chart.updateSelection(true, vScrollBar);
}
});
chart.attachVerticalScrollBar(vScrollBar);
defaultWheelHandler = chart.getMouseWheelListeners()[0];
chart.addConfigurationListener(new VisibleBoundsListener());
setOpaque(false);
setLayout(new BorderLayout());
add(axis, BorderLayout.NORTH);
add(chart, BorderLayout.CENTER);
add(vScrollBar, BorderLayout.EAST);
add(hScrollBar, BorderLayout.SOUTH);
resetChart();
}
// --- Internal interface --------------------------------------------------
void resetChart() {
chart.setScale(0.02, 1);
chart.setOffset(0, 0);
}
boolean resetSelection() {
if (chart.isRowSelection()) {
chart.clearRowsSelection();
return true;
} else {
return false;
}
}
Action zoomInAction() {
if (zoomInAction == null) zoomInAction = new ZoomInAction();
return zoomInAction;
}
Action zoomOutAction() {
if (zoomOutAction == null) zoomOutAction = new ZoomOutAction();
return zoomOutAction;
}
Action toggleViewAction() {
if (toggleViewAction == null) {
toggleViewAction = new ToggleViewAction();
if (TracerOptions.getInstance().getZoomMode().equals(TracerOptions.
SCALE_TO_FIT)) toggleViewAction.actionPerformed(null);
}
return toggleViewAction;
}
AbstractButton mouseZoom() {
if (mouseZoom == null) {
mouseZoom = new OneWayToggleButton(ZMWHEEL_ICON, "Mouse wheel zooms") {
protected void performAction() { mouseZoomImpl(); }
};
if (TracerOptions.getInstance().getMouseWheelAction().equals(
TracerOptions.MOUSE_WHEEL_ZOOMS)) {
mouseZoom.setSelected(true);
mouseZoomImpl();
}
}
return mouseZoom;
}
AbstractButton mouseHScroll() {
if (mouseHScroll == null) {
mouseHScroll = new OneWayToggleButton(HMWHEEL_ICON, "Mouse wheel scrolls horizontally") {
protected void performAction() { mouseHScrollImpl(); }
};
if (TracerOptions.getInstance().getMouseWheelAction().equals(
TracerOptions.MOUSE_WHEEL_HSCROLLS)) {
mouseHScroll.setSelected(true);
mouseHScrollImpl();
}
}
return mouseHScroll;
}
AbstractButton mouseVScroll() {
if (mouseVScroll == null) {
mouseVScroll = new OneWayToggleButton(VMWHEEL_ICON, "Mouse wheel scrolls vertically") {
protected void performAction() { mouseVScrollImpl(); }
};
if (TracerOptions.getInstance().getMouseWheelAction().equals(
TracerOptions.MOUSE_WHEEL_VSCROLLS)) {
mouseVScroll.setSelected(true);
mouseVScrollImpl();
}
}
return mouseVScroll;
}
// --- Mouse wheel handling ------------------------------------------------
void vScroll(MouseWheelEvent e) {
scroll(vScrollBar, e);
}
private void mouseZoomImpl() {
clearWheelHandlers();
chart.setMouseZoomingEnabled(true);
chart.addMouseWheelListener(defaultWheelHandler);
}
private void mouseHScrollImpl() {
chart.setMouseZoomingEnabled(false);
clearWheelHandlers();
setWheelScrollHandler(hScrollBar);
}
private void mouseVScrollImpl() {
chart.setMouseZoomingEnabled(false);
clearWheelHandlers();
setWheelScrollHandler(vScrollBar);
}
private void clearWheelHandlers() {
MouseWheelListener[] handlers = chart.getMouseWheelListeners();
for (MouseWheelListener handler : handlers)
chart.removeMouseWheelListener(handler);
}
private void setWheelScrollHandler(final JScrollBar scrollBar) {
chart.addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
scroll(scrollBar, e);
}
});
}
private static void scroll(JScrollBar scrollBar, MouseWheelEvent e) {
if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
int unitsToScroll = e.getUnitsToScroll();
int direction = unitsToScroll < 0 ? -1 : 1;
if (unitsToScroll != 0) {
int increment = scrollBar.getUnitIncrement(direction);
int oldValue = scrollBar.getValue();
int newValue = oldValue + increment * unitsToScroll;
newValue = Math.max(Math.min(newValue, scrollBar.getMaximum() -
scrollBar.getVisibleAmount()), scrollBar.getMinimum());
if (oldValue != newValue) scrollBar.setValue(newValue);
}
}
}
// --- Actions support -----------------------------------------------------
private class ZoomInAction extends AbstractAction {
private static final int ONE_SECOND_WIDTH_THRESHOLD = 200;
public ZoomInAction() {
super();
putValue(SHORT_DESCRIPTION, ZOOM_IN_STRING);
putValue(SMALL_ICON, ZOOM_IN_ICON);
updateAction();
}
public void actionPerformed(ActionEvent e) {
boolean followsWidth = chart.currentlyFollowingDataWidth();
chart.zoom(getWidth() / 2, getHeight() / 2, 2d);
if (followsWidth) chart.setOffset(chart.maxOffsetX(), chart.getOffsetY());
chart.repaintDirty();
}
private void updateAction() {
Timeline timeline = ((SynchronousXYItemsModel)chart.getItemsModel()).getTimeline();
setEnabled(timeline.getTimestampsCount() > 1 && !chart.fitsWidth() &&
chart.viewWidth(1000) < ONE_SECOND_WIDTH_THRESHOLD);
}
}
private class ZoomOutAction extends AbstractAction {
private static final float USED_CHART_WIDTH_THRESHOLD = 0.33f;
public ZoomOutAction() {
super();
putValue(SHORT_DESCRIPTION, ZOOM_OUT_STRING);
putValue(SMALL_ICON, ZOOM_OUT_ICON);
updateAction();
}
public void actionPerformed(ActionEvent e) {
boolean followsWidth = chart.currentlyFollowingDataWidth();
chart.zoom(getWidth() / 2, getHeight() / 2, 0.5d);
if (followsWidth) chart.setOffset(chart.maxOffsetX(), chart.getOffsetY());
chart.repaintDirty();
}
private void updateAction() {
Timeline timeline = ((SynchronousXYItemsModel)chart.getItemsModel()).getTimeline();
setEnabled(timeline.getTimestampsCount() > 0 && !chart.fitsWidth() &&
chart.getContentsWidth() > getWidth() * USED_CHART_WIDTH_THRESHOLD);
}
}
private class ToggleViewAction extends AbstractAction {
private long origOffsetX = -1;
private double origScaleX = -1;
public ToggleViewAction() {
super();
updateAction();
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isMiddleMouseButton(e))
actionPerformed(null);
}
});
}
public void actionPerformed(ActionEvent e) {
boolean fitsWidth = chart.fitsWidth();
if (!fitsWidth) {
origOffsetX = chart.getOffsetX();
if (chart.tracksDataWidth() && origOffsetX == chart.maxOffsetX())
origOffsetX = Long.MAX_VALUE;
origScaleX = chart.getScaleX();
}
chart.setFitsWidth(!fitsWidth);
if (fitsWidth && origOffsetX != -1 && origScaleX != -1) {
chart.setScale(origScaleX, chart.getScaleY());
chart.setOffset(origOffsetX, chart.getOffsetY());
}
updateAction();
if (zoomInAction != null) zoomInAction.updateAction();
if (zoomOutAction != null) zoomOutAction.updateAction();
chart.repaintDirty();
}
private void updateAction() {
boolean fitsWidth = chart.fitsWidth();
Icon icon = fitsWidth ? FIXED_SCALE_ICON : SCALE_TO_FIT_ICON;
String name = fitsWidth ? FIXED_SCALE_STRING : SCALE_TO_FIT_STRING;
putValue(SHORT_DESCRIPTION, name);
putValue(SMALL_ICON, icon);
}
}
private static abstract class OneWayToggleButton extends JToggleButton {
private boolean action;
public OneWayToggleButton(Icon icon, String toolTip) {
super(icon);
setToolTipText(toolTip);
}
protected void processMouseEvent(MouseEvent e) {
if (isSelected()) {
e.consume();
action = false;
} else {
action = true;
}
super.processMouseEvent(e);
}
protected void processKeyEvent(KeyEvent e) {
if (isSelected()) {
e.consume();
action = false;
} else {
action = true;
}
super.processKeyEvent(e);
}
protected final void fireActionPerformed(ActionEvent e) {
if (action) performAction();
}
protected abstract void performAction();
}
// --- ChartConfigurationListener implementation ---------------------------
private class VisibleBoundsListener extends ChartConfigurationListener.Adapter {
public void dataBoundsChanged(long dataOffsetX, long dataOffsetY,
long dataWidth, long dataHeight,
long oldDataOffsetX, long oldDataOffsetY,
long oldDataWidth, long oldDataHeight) {
if (zoomInAction != null) zoomInAction.updateAction();
if (zoomOutAction != null) zoomOutAction.updateAction();
}
public void scaleChanged(double oldScaleX, double oldScaleY,
double newScaleX, double newScaleY) {
if (zoomInAction != null) zoomInAction.updateAction();
if (zoomOutAction != null) zoomOutAction.updateAction();
}
}
}