/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.traceview; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ScrollBar; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Observable; import java.util.Observer; public class TimeLineView extends Composite implements Observer { private HashMap<String, RowData> mRowByName; private double mTotalElapsed; private RowData[] mRows; private Segment[] mSegments; private ArrayList<Segment> mSegmentList = new ArrayList<Segment>(); private HashMap<Integer, String> mThreadLabels; private Timescale mTimescale; private Surface mSurface; private RowLabels mLabels; private SashForm mSashForm; private int mScrollOffsetY; public static final int PixelsPerTick = 50; private TickScaler mScaleInfo = new TickScaler(0, 0, 0, PixelsPerTick); private static final int LeftMargin = 10; // blank space on left private static final int RightMargin = 60; // blank space on right private Color mColorBlack; private Color mColorGray; private Color mColorDarkGray; private Color mColorForeground; private Color mColorRowBack; private Color mColorZoomSelection; private FontRegistry mFontRegistry; /** vertical height of drawn blocks in each row */ private static final int rowHeight = 20; /** the blank space between rows */ private static final int rowYMargin = 12; private static final int rowYMarginHalf = rowYMargin / 2; /** total vertical space for row */ private static final int rowYSpace = rowHeight + rowYMargin; private static final int majorTickLength = 8; private static final int minorTickLength = 4; private static final int timeLineOffsetY = 38; private static final int tickToFontSpacing = 2; /** start of first row */ private static final int topMargin = 70; private int mMouseRow = -1; private int mNumRows; private int mStartRow; private int mEndRow; private TraceUnits mUnits; private int mSmallFontWidth; private int mSmallFontHeight; private int mMediumFontWidth; private SelectionController mSelectionController; private MethodData mHighlightMethodData; private Call mHighlightCall; private static final int MinInclusiveRange = 3; /** Setting the fonts looks good on Linux but bad on Macs */ private boolean mSetFonts = false; public static interface Block { public String getName(); public MethodData getMethodData(); public long getStartTime(); public long getEndTime(); public Color getColor(); public double addWeight(int x, int y, double weight); public void clearWeight(); } public static interface Row { public int getId(); public String getName(); } public static class Record { Row row; Block block; public Record(Row row, Block block) { this.row = row; this.block = block; } } public TimeLineView(Composite parent, TraceReader reader, SelectionController selectionController) { super(parent, SWT.NONE); mRowByName = new HashMap<String, RowData>(); this.mSelectionController = selectionController; selectionController.addObserver(this); mUnits = reader.getTraceUnits(); mThreadLabels = reader.getThreadLabels(); Display display = getDisplay(); mColorGray = display.getSystemColor(SWT.COLOR_GRAY); mColorDarkGray = display.getSystemColor(SWT.COLOR_DARK_GRAY); mColorBlack = display.getSystemColor(SWT.COLOR_BLACK); // mColorBackground = display.getSystemColor(SWT.COLOR_WHITE); mColorForeground = display.getSystemColor(SWT.COLOR_BLACK); mColorRowBack = new Color(display, 240, 240, 255); mColorZoomSelection = new Color(display, 230, 230, 230); mFontRegistry = new FontRegistry(display); mFontRegistry.put("small", // $NON-NLS-1$ new FontData[] { new FontData("Arial", 8, SWT.NORMAL) }); // $NON-NLS-1$ mFontRegistry.put("courier8", // $NON-NLS-1$ new FontData[] { new FontData("Courier New", 8, SWT.BOLD) }); // $NON-NLS-1$ mFontRegistry.put("medium", // $NON-NLS-1$ new FontData[] { new FontData("Courier New", 10, SWT.NORMAL) }); // $NON-NLS-1$ Image image = new Image(display, new Rectangle(100, 100, 100, 100)); GC gc = new GC(image); if (mSetFonts) { gc.setFont(mFontRegistry.get("small")); // $NON-NLS-1$ } mSmallFontWidth = gc.getFontMetrics().getAverageCharWidth(); mSmallFontHeight = gc.getFontMetrics().getHeight(); if (mSetFonts) { gc.setFont(mFontRegistry.get("medium")); // $NON-NLS-1$ } mMediumFontWidth = gc.getFontMetrics().getAverageCharWidth(); image.dispose(); gc.dispose(); setLayout(new FillLayout()); // Create a sash form for holding two canvas views, one for the // thread labels and one for the thread timeline. mSashForm = new SashForm(this, SWT.HORIZONTAL); mSashForm.setBackground(mColorGray); mSashForm.SASH_WIDTH = 3; // Create a composite for the left side of the sash Composite composite = new Composite(mSashForm, SWT.NONE); GridLayout layout = new GridLayout(1, true /* make columns equal width */); layout.marginHeight = 0; layout.marginWidth = 0; layout.verticalSpacing = 1; composite.setLayout(layout); // Create a blank corner space in the upper left corner BlankCorner corner = new BlankCorner(composite); GridData gridData = new GridData(GridData.FILL_HORIZONTAL); gridData.heightHint = topMargin; corner.setLayoutData(gridData); // Add the thread labels below the blank corner. mLabels = new RowLabels(composite); gridData = new GridData(GridData.FILL_BOTH); mLabels.setLayoutData(gridData); // Create another composite for the right side of the sash composite = new Composite(mSashForm, SWT.NONE); layout = new GridLayout(1, true /* make columns equal width */); layout.marginHeight = 0; layout.marginWidth = 0; layout.verticalSpacing = 1; composite.setLayout(layout); mTimescale = new Timescale(composite); gridData = new GridData(GridData.FILL_HORIZONTAL); gridData.heightHint = topMargin; mTimescale.setLayoutData(gridData); mSurface = new Surface(composite); gridData = new GridData(GridData.FILL_BOTH); mSurface.setLayoutData(gridData); mSashForm.setWeights(new int[] { 1, 5 }); final ScrollBar vBar = mSurface.getVerticalBar(); vBar.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { mScrollOffsetY = vBar.getSelection(); Point dim = mSurface.getSize(); int newScrollOffsetY = computeVisibleRows(dim.y); if (newScrollOffsetY != mScrollOffsetY) { mScrollOffsetY = newScrollOffsetY; vBar.setSelection(newScrollOffsetY); } mLabels.redraw(); mSurface.redraw(); } }); mSurface.addListener(SWT.Resize, new Listener() { public void handleEvent(Event e) { Point dim = mSurface.getSize(); // If we don't need the scroll bar then don't display it. if (dim.y >= mNumRows * rowYSpace) { vBar.setVisible(false); } else { vBar.setVisible(true); } int newScrollOffsetY = computeVisibleRows(dim.y); if (newScrollOffsetY != mScrollOffsetY) { mScrollOffsetY = newScrollOffsetY; vBar.setSelection(newScrollOffsetY); } int spaceNeeded = mNumRows * rowYSpace; vBar.setMaximum(spaceNeeded); vBar.setThumb(dim.y); mLabels.redraw(); mSurface.redraw(); } }); mSurface.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent me) { mSurface.mouseUp(me); } @Override public void mouseDown(MouseEvent me) { mSurface.mouseDown(me); } @Override public void mouseDoubleClick(MouseEvent me) { mSurface.mouseDoubleClick(me); } }); mSurface.addMouseMoveListener(new MouseMoveListener() { public void mouseMove(MouseEvent me) { mSurface.mouseMove(me); } }); mTimescale.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent me) { mTimescale.mouseUp(me); } @Override public void mouseDown(MouseEvent me) { mTimescale.mouseDown(me); } @Override public void mouseDoubleClick(MouseEvent me) { mTimescale.mouseDoubleClick(me); } }); mTimescale.addMouseMoveListener(new MouseMoveListener() { public void mouseMove(MouseEvent me) { mTimescale.mouseMove(me); } }); mLabels.addMouseMoveListener(new MouseMoveListener() { public void mouseMove(MouseEvent me) { mLabels.mouseMove(me); } }); setData(reader.getThreadTimeRecords()); } public void update(Observable objservable, Object arg) { // Ignore updates from myself if (arg == "TimeLineView") // $NON-NLS-1$ return; // System.out.printf("timeline update from %s\n", arg); boolean foundHighlight = false; ArrayList<Selection> selections; selections = mSelectionController.getSelections(); for (Selection selection : selections) { Selection.Action action = selection.getAction(); if (action != Selection.Action.Highlight) continue; String name = selection.getName(); // System.out.printf(" timeline highlight %s from %s\n", name, arg); if (name == "MethodData") { // $NON-NLS-1$ foundHighlight = true; mHighlightMethodData = (MethodData) selection.getValue(); // System.out.printf(" method %s\n", // highlightMethodData.getName()); mHighlightCall = null; startHighlighting(); } else if (name == "Call") { // $NON-NLS-1$ foundHighlight = true; mHighlightCall = (Call) selection.getValue(); // System.out.printf(" call %s\n", highlightCall.getName()); mHighlightMethodData = null; startHighlighting(); } } if (foundHighlight == false) mSurface.clearHighlights(); } public void setData(ArrayList<Record> records) { if (records == null) records = new ArrayList<Record>(); if (false) { System.out.println("TimelineView() list of records:"); // $NON-NLS-1$ for (Record r : records) { System.out.printf("row '%s' block '%s' [%d, %d]\n", r.row // $NON-NLS-1$ .getName(), r.block.getName(), r.block.getStartTime(), r.block.getEndTime()); if (r.block.getStartTime() > r.block.getEndTime()) { System.err.printf("Error: block startTime > endTime\n"); // $NON-NLS-1$ System.exit(1); } } } // Sort the records into increasing start time, and decreasing end time Collections.sort(records, new Comparator<Record>() { public int compare(Record r1, Record r2) { long start1 = r1.block.getStartTime(); long start2 = r2.block.getStartTime(); if (start1 > start2) return 1; if (start1 < start2) return -1; // The start times are the same, so compare the end times long end1 = r1.block.getEndTime(); long end2 = r2.block.getEndTime(); if (end1 > end2) return -1; if (end1 < end2) return 1; return 0; } }); // The records are sorted into increasing start time, // so the minimum start time is the start time of the first record. double minVal = 0; if (records.size() > 0) minVal = records.get(0).block.getStartTime(); // Sum the time spent in each row and block, and // keep track of the maximum end time. double maxVal = 0; for (Record rec : records) { Row row = rec.row; Block block = rec.block; String rowName = row.getName(); RowData rd = mRowByName.get(rowName); if (rd == null) { rd = new RowData(row); mRowByName.put(rowName, rd); } long blockStartTime = block.getStartTime(); long blockEndTime = block.getEndTime(); if (blockEndTime > rd.mEndTime) { long start = Math.max(blockStartTime, rd.mEndTime); rd.mElapsed += blockEndTime - start; mTotalElapsed += blockEndTime - start; rd.mEndTime = blockEndTime; } if (blockEndTime > maxVal) maxVal = blockEndTime; // Keep track of nested blocks by using a stack (for each row). // Create a Segment object for each visible part of a block. Block top = rd.top(); if (top == null) { rd.push(block); continue; } long topStartTime = top.getStartTime(); long topEndTime = top.getEndTime(); if (topEndTime >= blockStartTime) { // Add this segment if it has a non-zero elapsed time. if (topStartTime < blockStartTime) { Segment segment = new Segment(rd, top, topStartTime, blockStartTime); mSegmentList.add(segment); } // If this block starts where the previous (top) block ends, // then pop off the top block. if (topEndTime == blockStartTime) rd.pop(); rd.push(block); } else { // We may have to pop several frames here. popFrames(rd, top, blockStartTime); rd.push(block); } } // Clean up the stack of each row for (RowData rd : mRowByName.values()) { Block top = rd.top(); popFrames(rd, top, Integer.MAX_VALUE); } mSurface.setRange(minVal, maxVal); mSurface.setLimitRange(minVal, maxVal); // Sort the rows into decreasing elapsed time Collection<RowData> rv = mRowByName.values(); mRows = rv.toArray(new RowData[rv.size()]); Arrays.sort(mRows, new Comparator<RowData>() { public int compare(RowData rd1, RowData rd2) { return (int) (rd2.mElapsed - rd1.mElapsed); } }); // Assign ranks to the sorted rows for (int ii = 0; ii < mRows.length; ++ii) { mRows[ii].mRank = ii; } // Compute the number of rows with data mNumRows = 0; for (int ii = 0; ii < mRows.length; ++ii) { if (mRows[ii].mElapsed == 0) break; mNumRows += 1; } // Sort the blocks into increasing rows, and within rows into // increasing start values. mSegments = mSegmentList.toArray(new Segment[mSegmentList.size()]); Arrays.sort(mSegments, new Comparator<Segment>() { public int compare(Segment bd1, Segment bd2) { RowData rd1 = bd1.mRowData; RowData rd2 = bd2.mRowData; int diff = rd1.mRank - rd2.mRank; if (diff == 0) { long timeDiff = bd1.mStartTime - bd2.mStartTime; if (timeDiff == 0) timeDiff = bd1.mEndTime - bd2.mEndTime; return (int) timeDiff; } return diff; } }); if (false) { for (Segment segment : mSegments) { System.out.printf("seg '%s' [%6d, %6d] %s\n", segment.mRowData.mName, segment.mStartTime, segment.mEndTime, segment.mBlock.getName()); if (segment.mStartTime > segment.mEndTime) { System.err.printf("Error: segment startTime > endTime\n"); System.exit(1); } } } } private void popFrames(RowData rd, Block top, long startTime) { long topEndTime = top.getEndTime(); long lastEndTime = top.getStartTime(); while (topEndTime <= startTime) { if (topEndTime > lastEndTime) { Segment segment = new Segment(rd, top, lastEndTime, topEndTime); mSegmentList.add(segment); lastEndTime = topEndTime; } rd.pop(); top = rd.top(); if (top == null) return; topEndTime = top.getEndTime(); } // If we get here, then topEndTime > startTime if (lastEndTime < startTime) { Segment bd = new Segment(rd, top, lastEndTime, startTime); mSegmentList.add(bd); } } private class RowLabels extends Canvas { /** The space between the row label and the sash line */ private static final int labelMarginX = 2; public RowLabels(Composite parent) { super(parent, SWT.NO_BACKGROUND); addPaintListener(new PaintListener() { public void paintControl(PaintEvent pe) { draw(pe.display, pe.gc); } }); } private void mouseMove(MouseEvent me) { int rownum = (me.y + mScrollOffsetY) / rowYSpace; if (mMouseRow != rownum) { mMouseRow = rownum; redraw(); mSurface.redraw(); } } private void draw(Display display, GC gc) { if (mSegments.length == 0) { // gc.setBackground(colorBackground); // gc.fillRectangle(getBounds()); return; } Point dim = getSize(); // Create an image for double-buffering Image image = new Image(display, getBounds()); // Set up the off-screen gc GC gcImage = new GC(image); if (mSetFonts) gcImage.setFont(mFontRegistry.get("medium")); // $NON-NLS-1$ if (mNumRows > 2) { // Draw the row background stripes gcImage.setBackground(mColorRowBack); for (int ii = 1; ii < mNumRows; ii += 2) { RowData rd = mRows[ii]; int y1 = rd.mRank * rowYSpace - mScrollOffsetY; gcImage.fillRectangle(0, y1, dim.x, rowYSpace); } } // Draw the row labels int offsetY = rowYMarginHalf - mScrollOffsetY; for (int ii = mStartRow; ii <= mEndRow; ++ii) { RowData rd = mRows[ii]; int y1 = rd.mRank * rowYSpace + offsetY; Point extent = gcImage.stringExtent(rd.mName); int x1 = dim.x - extent.x - labelMarginX; gcImage.drawString(rd.mName, x1, y1, true); } // Draw a highlight box on the row where the mouse is. if (mMouseRow >= mStartRow && mMouseRow <= mEndRow) { gcImage.setForeground(mColorGray); int y1 = mMouseRow * rowYSpace - mScrollOffsetY; gcImage.drawRectangle(0, y1, dim.x, rowYSpace); } // Draw the off-screen buffer to the screen gc.drawImage(image, 0, 0); // Clean up image.dispose(); gcImage.dispose(); } } private class BlankCorner extends Canvas { public BlankCorner(Composite parent) { //super(parent, SWT.NO_BACKGROUND); super(parent, SWT.NONE); addPaintListener(new PaintListener() { public void paintControl(PaintEvent pe) { draw(pe.display, pe.gc); } }); } private void draw(Display display, GC gc) { // Create a blank image and draw it to the canvas Image image = new Image(display, getBounds()); gc.drawImage(image, 0, 0); // Clean up image.dispose(); } } private class Timescale extends Canvas { private Point mMouse = new Point(LeftMargin, 0); private Cursor mZoomCursor; private String mMethodName = null; private Color mMethodColor = null; private int mMethodStartY; private int mMarkStartX; private int mMarkEndX; /** The space between the colored block and the method name */ private static final int METHOD_BLOCK_MARGIN = 10; public Timescale(Composite parent) { //super(parent, SWT.NO_BACKGROUND); super(parent, SWT.NONE); Display display = getDisplay(); mZoomCursor = new Cursor(display, SWT.CURSOR_SIZEWE); setCursor(mZoomCursor); mMethodStartY = mSmallFontHeight + 1; addPaintListener(new PaintListener() { public void paintControl(PaintEvent pe) { draw(pe.display, pe.gc); } }); } public void setVbarPosition(int x) { mMouse.x = x; } public void setMarkStart(int x) { mMarkStartX = x; } public void setMarkEnd(int x) { mMarkEndX = x; } public void setMethodName(String name) { mMethodName = name; } public void setMethodColor(Color color) { mMethodColor = color; } private void mouseMove(MouseEvent me) { me.y = -1; mSurface.mouseMove(me); } private void mouseDown(MouseEvent me) { mSurface.startScaling(me.x); mSurface.redraw(); } private void mouseUp(MouseEvent me) { mSurface.stopScaling(me.x); } private void mouseDoubleClick(MouseEvent me) { mSurface.resetScale(); mSurface.redraw(); } private void draw(Display display, GC gc) { Point dim = getSize(); // Create an image for double-buffering Image image = new Image(display, getBounds()); // Set up the off-screen gc GC gcImage = new GC(image); if (mSetFonts) gcImage.setFont(mFontRegistry.get("medium")); // $NON-NLS-1$ if (mSurface.drawingSelection()) { drawSelection(display, gcImage); } drawTicks(display, gcImage); // Draw the vertical bar where the mouse is gcImage.setForeground(mColorDarkGray); gcImage.drawLine(mMouse.x, timeLineOffsetY, mMouse.x, dim.y); // Draw the current millseconds drawTickLegend(display, gcImage); // Draw the method name and color, if needed drawMethod(display, gcImage); // Draw the off-screen buffer to the screen gc.drawImage(image, 0, 0); // Clean up image.dispose(); gcImage.dispose(); } private void drawSelection(Display display, GC gc) { Point dim = getSize(); gc.setForeground(mColorGray); gc.drawLine(mMarkStartX, timeLineOffsetY, mMarkStartX, dim.y); gc.setBackground(mColorZoomSelection); int x, width; if (mMarkStartX < mMarkEndX) { x = mMarkStartX; width = mMarkEndX - mMarkStartX; } else { x = mMarkEndX; width = mMarkStartX - mMarkEndX; } if (width > 1) { gc.fillRectangle(x, timeLineOffsetY, width, dim.y); } } private void drawTickLegend(Display display, GC gc) { int mouseX = mMouse.x - LeftMargin; double mouseXval = mScaleInfo.pixelToValue(mouseX); String info = mUnits.labelledString(mouseXval); gc.setForeground(mColorForeground); gc.drawString(info, LeftMargin + 2, 1, true); // Display the maximum data value double maxVal = mScaleInfo.getMaxVal(); info = mUnits.labelledString(maxVal); info = String.format(" max %s ", info); // $NON-NLS-1$ Point extent = gc.stringExtent(info); Point dim = getSize(); int x1 = dim.x - RightMargin - extent.x; gc.drawString(info, x1, 1, true); } private void drawMethod(Display display, GC gc) { if (mMethodName == null) { return; } int x1 = LeftMargin; int y1 = mMethodStartY; gc.setBackground(mMethodColor); int width = 2 * mSmallFontWidth; gc.fillRectangle(x1, y1, width, mSmallFontHeight); x1 += width + METHOD_BLOCK_MARGIN; gc.drawString(mMethodName, x1, y1, true); } private void drawTicks(Display display, GC gc) { Point dim = getSize(); int y2 = majorTickLength + timeLineOffsetY; int y3 = minorTickLength + timeLineOffsetY; int y4 = y2 + tickToFontSpacing; gc.setForeground(mColorForeground); gc.drawLine(LeftMargin, timeLineOffsetY, dim.x - RightMargin, timeLineOffsetY); double minVal = mScaleInfo.getMinVal(); double maxVal = mScaleInfo.getMaxVal(); double minMajorTick = mScaleInfo.getMinMajorTick(); double tickIncrement = mScaleInfo.getTickIncrement(); double minorTickIncrement = tickIncrement / 5; double pixelsPerRange = mScaleInfo.getPixelsPerRange(); // Draw the initial minor ticks, if any if (minVal < minMajorTick) { gc.setForeground(mColorGray); double xMinor = minMajorTick; for (int ii = 1; ii <= 4; ++ii) { xMinor -= minorTickIncrement; if (xMinor < minVal) break; int x1 = LeftMargin + (int) (0.5 + (xMinor - minVal) * pixelsPerRange); gc.drawLine(x1, timeLineOffsetY, x1, y3); } } if (tickIncrement <= 10) { // TODO avoid rendering the loop when tickIncrement is invalid. It can be zero // or too small. // System.out.println(String.format("Timescale.drawTicks error: tickIncrement=%1f", tickIncrement)); return; } for (double x = minMajorTick; x <= maxVal; x += tickIncrement) { int x1 = LeftMargin + (int) (0.5 + (x - minVal) * pixelsPerRange); // Draw a major tick gc.setForeground(mColorForeground); gc.drawLine(x1, timeLineOffsetY, x1, y2); if (x > maxVal) break; // Draw the tick text String tickString = mUnits.valueOf(x); gc.drawString(tickString, x1, y4, true); // Draw 4 minor ticks between major ticks gc.setForeground(mColorGray); double xMinor = x; for (int ii = 1; ii <= 4; ii++) { xMinor += minorTickIncrement; if (xMinor > maxVal) break; x1 = LeftMargin + (int) (0.5 + (xMinor - minVal) * pixelsPerRange); gc.drawLine(x1, timeLineOffsetY, x1, y3); } } } } private static enum GraphicsState { Normal, Marking, Scaling, Animating }; private class Surface extends Canvas { public Surface(Composite parent) { super(parent, SWT.NO_BACKGROUND | SWT.V_SCROLL); Display display = getDisplay(); mNormalCursor = new Cursor(display, SWT.CURSOR_CROSS); mIncreasingCursor = new Cursor(display, SWT.CURSOR_SIZEE); mDecreasingCursor = new Cursor(display, SWT.CURSOR_SIZEW); initZoomFractionsWithExp(); addPaintListener(new PaintListener() { public void paintControl(PaintEvent pe) { draw(pe.display, pe.gc); } }); mZoomAnimator = new Runnable() { public void run() { animateZoom(); } }; mHighlightAnimator = new Runnable() { public void run() { animateHighlight(); } }; } private void initZoomFractionsWithExp() { mZoomFractions = new double[ZOOM_STEPS]; int next = 0; for (int ii = 0; ii < ZOOM_STEPS / 2; ++ii, ++next) { mZoomFractions[next] = (double) (1 << ii) / (double) (1 << (ZOOM_STEPS / 2)); // System.out.printf("%d %f\n", next, zoomFractions[next]); } for (int ii = 2; ii < 2 + ZOOM_STEPS / 2; ++ii, ++next) { mZoomFractions[next] = (double) ((1 << ii) - 1) / (double) (1 << ii); // System.out.printf("%d %f\n", next, zoomFractions[next]); } } @SuppressWarnings("unused") private void initZoomFractionsWithSinWave() { mZoomFractions = new double[ZOOM_STEPS]; for (int ii = 0; ii < ZOOM_STEPS; ++ii) { double offset = Math.PI * (double) ii / (double) ZOOM_STEPS; mZoomFractions[ii] = (Math.sin((1.5 * Math.PI + offset)) + 1.0) / 2.0; // System.out.printf("%d %f\n", ii, zoomFractions[ii]); } } public void setRange(double minVal, double maxVal) { mMinDataVal = minVal; mMaxDataVal = maxVal; mScaleInfo.setMinVal(minVal); mScaleInfo.setMaxVal(maxVal); } public void setLimitRange(double minVal, double maxVal) { mLimitMinVal = minVal; mLimitMaxVal = maxVal; } public void resetScale() { mScaleInfo.setMinVal(mLimitMinVal); mScaleInfo.setMaxVal(mLimitMaxVal); } private void draw(Display display, GC gc) { if (mSegments.length == 0) { // gc.setBackground(colorBackground); // gc.fillRectangle(getBounds()); return; } // Create an image for double-buffering Image image = new Image(display, getBounds()); // Set up the off-screen gc GC gcImage = new GC(image); if (mSetFonts) gcImage.setFont(mFontRegistry.get("small")); // $NON-NLS-1$ // Draw the background // gcImage.setBackground(colorBackground); // gcImage.fillRectangle(image.getBounds()); if (mGraphicsState == GraphicsState.Scaling) { double diff = mMouse.x - mMouseMarkStartX; if (diff > 0) { double newMinVal = mScaleMinVal - diff / mScalePixelsPerRange; if (newMinVal < mLimitMinVal) newMinVal = mLimitMinVal; mScaleInfo.setMinVal(newMinVal); // System.out.printf("diff %f scaleMin %f newMin %f\n", // diff, scaleMinVal, newMinVal); } else if (diff < 0) { double newMaxVal = mScaleMaxVal - diff / mScalePixelsPerRange; if (newMaxVal > mLimitMaxVal) newMaxVal = mLimitMaxVal; mScaleInfo.setMaxVal(newMaxVal); // System.out.printf("diff %f scaleMax %f newMax %f\n", // diff, scaleMaxVal, newMaxVal); } } // Recompute the ticks and strips only if the size has changed, // or we scrolled so that a new row is visible. Point dim = getSize(); if (mStartRow != mCachedStartRow || mEndRow != mCachedEndRow || mScaleInfo.getMinVal() != mCachedMinVal || mScaleInfo.getMaxVal() != mCachedMaxVal) { mCachedStartRow = mStartRow; mCachedEndRow = mEndRow; int xdim = dim.x - TotalXMargin; mScaleInfo.setNumPixels(xdim); boolean forceEndPoints = (mGraphicsState == GraphicsState.Scaling || mGraphicsState == GraphicsState.Animating); mScaleInfo.computeTicks(forceEndPoints); mCachedMinVal = mScaleInfo.getMinVal(); mCachedMaxVal = mScaleInfo.getMaxVal(); if (mLimitMinVal > mScaleInfo.getMinVal()) mLimitMinVal = mScaleInfo.getMinVal(); if (mLimitMaxVal < mScaleInfo.getMaxVal()) mLimitMaxVal = mScaleInfo.getMaxVal(); // Compute the strips computeStrips(); } if (mNumRows > 2) { // Draw the row background stripes gcImage.setBackground(mColorRowBack); for (int ii = 1; ii < mNumRows; ii += 2) { RowData rd = mRows[ii]; int y1 = rd.mRank * rowYSpace - mScrollOffsetY; gcImage.fillRectangle(0, y1, dim.x, rowYSpace); } } if (drawingSelection()) { drawSelection(display, gcImage); } String blockName = null; Color blockColor = null; if (mDebug) { double pixelsPerRange = mScaleInfo.getPixelsPerRange(); System.out .printf( "dim.x %d pixels %d minVal %f, maxVal %f ppr %f rpp %f\n", dim.x, dim.x - TotalXMargin, mScaleInfo .getMinVal(), mScaleInfo.getMaxVal(), pixelsPerRange, 1.0 / pixelsPerRange); } // Draw the strips Block selectBlock = null; for (Strip strip : mStripList) { if (strip.mColor == null) { // System.out.printf("strip.color is null\n"); continue; } gcImage.setBackground(strip.mColor); gcImage.fillRectangle(strip.mX, strip.mY - mScrollOffsetY, strip.mWidth, strip.mHeight); if (mMouseRow == strip.mRowData.mRank) { if (mMouse.x >= strip.mX && mMouse.x < strip.mX + strip.mWidth) { blockName = strip.mSegment.mBlock.getName(); blockColor = strip.mColor; } if (mMouseSelect.x >= strip.mX && mMouseSelect.x < strip.mX + strip.mWidth) { selectBlock = strip.mSegment.mBlock; } } } mMouseSelect.x = 0; mMouseSelect.y = 0; if (selectBlock != null) { ArrayList<Selection> selections = new ArrayList<Selection>(); // Get the row label RowData rd = mRows[mMouseRow]; selections.add(Selection.highlight("Thread", rd.mName)); // $NON-NLS-1$ selections.add(Selection.highlight("Call", selectBlock)); // $NON-NLS-1$ int mouseX = mMouse.x - LeftMargin; double mouseXval = mScaleInfo.pixelToValue(mouseX); selections.add(Selection.highlight("Time", mouseXval)); // $NON-NLS-1$ mSelectionController.change(selections, "TimeLineView"); // $NON-NLS-1$ mHighlightMethodData = null; mHighlightCall = (Call) selectBlock; startHighlighting(); } // Draw a highlight box on the row where the mouse is. // Except don't draw the box if we are animating the // highlighing of a call or method because the inclusive // highlight bar passes through the highlight box and // causes an annoying flashing artifact. if (mMouseRow >= 0 && mMouseRow < mNumRows && mHighlightStep == 0) { gcImage.setForeground(mColorGray); int y1 = mMouseRow * rowYSpace - mScrollOffsetY; gcImage.drawLine(0, y1, dim.x, y1); gcImage.drawLine(0, y1 + rowYSpace, dim.x, y1 + rowYSpace); } // Highlight a selected method, if any drawHighlights(gcImage, dim); // Draw a vertical line where the mouse is. gcImage.setForeground(mColorDarkGray); int lineEnd = Math.min(dim.y, mNumRows * rowYSpace); gcImage.drawLine(mMouse.x, 0, mMouse.x, lineEnd); if (blockName != null) { mTimescale.setMethodName(blockName); mTimescale.setMethodColor(blockColor); mShowHighlightName = false; } else if (mShowHighlightName) { // Draw the highlighted method name MethodData md = mHighlightMethodData; if (md == null && mHighlightCall != null) md = mHighlightCall.getMethodData(); if (md == null) System.out.printf("null highlight?\n"); // $NON-NLS-1$ if (md != null) { mTimescale.setMethodName(md.getProfileName()); mTimescale.setMethodColor(md.getColor()); } } else { mTimescale.setMethodName(null); mTimescale.setMethodColor(null); } mTimescale.redraw(); // Draw the off-screen buffer to the screen gc.drawImage(image, 0, 0); // Clean up image.dispose(); gcImage.dispose(); } private void drawHighlights(GC gc, Point dim) { int height = highlightHeight; if (height <= 0) return; for (Range range : mHighlightExclusive) { gc.setBackground(range.mColor); int xStart = range.mXdim.x; int width = range.mXdim.y; gc.fillRectangle(xStart, range.mY - height - mScrollOffsetY, width, height); } // Draw the inclusive lines a bit shorter height -= 1; if (height <= 0) height = 1; // Highlight the inclusive ranges gc.setForeground(mColorDarkGray); gc.setBackground(mColorDarkGray); for (Range range : mHighlightInclusive) { int x1 = range.mXdim.x; int x2 = range.mXdim.y; boolean drawLeftEnd = false; boolean drawRightEnd = false; if (x1 >= LeftMargin) drawLeftEnd = true; else x1 = LeftMargin; if (x2 >= LeftMargin) drawRightEnd = true; else x2 = dim.x - RightMargin; int y1 = range.mY + rowHeight + 2 - mScrollOffsetY; // If the range is very narrow, then just draw a small // rectangle. if (x2 - x1 < MinInclusiveRange) { int width = x2 - x1; if (width < 2) width = 2; gc.fillRectangle(x1, y1, width, height); continue; } if (drawLeftEnd) { if (drawRightEnd) { // Draw both ends int[] points = { x1, y1, x1, y1 + height, x2, y1 + height, x2, y1 }; gc.drawPolyline(points); } else { // Draw the left end int[] points = { x1, y1, x1, y1 + height, x2, y1 + height }; gc.drawPolyline(points); } } else { if (drawRightEnd) { // Draw the right end int[] points = { x1, y1 + height, x2, y1 + height, x2, y1 }; gc.drawPolyline(points); } else { // Draw neither end, just the line int[] points = { x1, y1 + height, x2, y1 + height }; gc.drawPolyline(points); } } // Draw the arrowheads, if necessary if (drawLeftEnd == false) { int[] points = { x1 + 7, y1 + height - 4, x1, y1 + height, x1 + 7, y1 + height + 4 }; gc.fillPolygon(points); } if (drawRightEnd == false) { int[] points = { x2 - 7, y1 + height - 4, x2, y1 + height, x2 - 7, y1 + height + 4 }; gc.fillPolygon(points); } } } private boolean drawingSelection() { return mGraphicsState == GraphicsState.Marking || mGraphicsState == GraphicsState.Animating; } private void drawSelection(Display display, GC gc) { Point dim = getSize(); gc.setForeground(mColorGray); gc.drawLine(mMouseMarkStartX, 0, mMouseMarkStartX, dim.y); gc.setBackground(mColorZoomSelection); int width; int mouseX = (mGraphicsState == GraphicsState.Animating) ? mMouseMarkEndX : mMouse.x; int x; if (mMouseMarkStartX < mouseX) { x = mMouseMarkStartX; width = mouseX - mMouseMarkStartX; } else { x = mouseX; width = mMouseMarkStartX - mouseX; } gc.fillRectangle(x, 0, width, dim.y); } private void computeStrips() { double minVal = mScaleInfo.getMinVal(); double maxVal = mScaleInfo.getMaxVal(); // Allocate space for the pixel data Pixel[] pixels = new Pixel[mNumRows]; for (int ii = 0; ii < mNumRows; ++ii) pixels[ii] = new Pixel(); // Clear the per-block pixel data for (int ii = 0; ii < mSegments.length; ++ii) { mSegments[ii].mBlock.clearWeight(); } mStripList.clear(); mHighlightExclusive.clear(); mHighlightInclusive.clear(); MethodData callMethod = null; long callStart = 0; long callEnd = -1; RowData callRowData = null; int prevMethodStart = -1; int prevCallStart = -1; if (mHighlightCall != null) { int callPixelStart = -1; int callPixelEnd = -1; callStart = mHighlightCall.mGlobalStartTime; callEnd = mHighlightCall.mGlobalEndTime; callMethod = mHighlightCall.mMethodData; if (callStart >= minVal) callPixelStart = mScaleInfo.valueToPixel(callStart); if (callEnd <= maxVal) callPixelEnd = mScaleInfo.valueToPixel(callEnd); // System.out.printf("callStart,End %d,%d minVal,maxVal %f,%f // callPixelStart,End %d,%d\n", // callStart, callEnd, minVal, maxVal, callPixelStart, // callPixelEnd); int threadId = mHighlightCall.getThreadId(); String threadName = mThreadLabels.get(threadId); callRowData = mRowByName.get(threadName); int y1 = callRowData.mRank * rowYSpace + rowYMarginHalf; Color color = callMethod.getColor(); mHighlightInclusive.add(new Range(callPixelStart + LeftMargin, callPixelEnd + LeftMargin, y1, color)); } for (Segment segment : mSegments) { if (segment.mEndTime <= minVal) continue; if (segment.mStartTime >= maxVal) continue; Block block = segment.mBlock; Color color = block.getColor(); if (color == null) continue; double recordStart = Math.max(segment.mStartTime, minVal); double recordEnd = Math.min(segment.mEndTime, maxVal); if (recordStart == recordEnd) continue; int pixelStart = mScaleInfo.valueToPixel(recordStart); int pixelEnd = mScaleInfo.valueToPixel(recordEnd); int width = pixelEnd - pixelStart; RowData rd = segment.mRowData; MethodData md = block.getMethodData(); // We will add the scroll offset later when we draw the strips int y1 = rd.mRank * rowYSpace + rowYMarginHalf; // If we can't display any more rows, then quit if (rd.mRank > mEndRow) break; // System.out.printf("segment %s val: [%.1f, %.1f] frac [%f, %f] // pixel: [%d, %d] pix.start %d weight %.2f %s\n", // block.getName(), recordStart, recordEnd, // scaleInfo.valueToPixelFraction(recordStart), // scaleInfo.valueToPixelFraction(recordEnd), // pixelStart, pixelEnd, pixels[rd.rank].start, // pixels[rd.rank].maxWeight, // pixels[rd.rank].segment != null // ? pixels[rd.rank].segment.block.getName() // : "null"); if (mHighlightMethodData != null) { if (mHighlightMethodData == md) { if (prevMethodStart != pixelStart) { prevMethodStart = pixelStart; int rangeWidth = width; if (rangeWidth == 0) rangeWidth = 1; mHighlightExclusive.add(new Range(pixelStart + LeftMargin, rangeWidth, y1, color)); Call call = (Call) block; callStart = call.mGlobalStartTime; int callPixelStart = -1; if (callStart >= minVal) callPixelStart = mScaleInfo.valueToPixel(callStart); if (prevCallStart != callPixelStart) { prevCallStart = callPixelStart; int callPixelEnd = -1; callEnd = call.mGlobalEndTime; if (callEnd <= maxVal) callPixelEnd = mScaleInfo.valueToPixel(callEnd); mHighlightInclusive.add(new Range( callPixelStart + LeftMargin, callPixelEnd + LeftMargin, y1, color)); } } } else if (mFadeColors) { color = md.getFadedColor(); } } else if (mHighlightCall != null) { if (segment.mStartTime >= callStart && segment.mEndTime <= callEnd && callMethod == md && callRowData == rd) { if (prevMethodStart != pixelStart) { prevMethodStart = pixelStart; int rangeWidth = width; if (rangeWidth == 0) rangeWidth = 1; mHighlightExclusive.add(new Range(pixelStart + LeftMargin, rangeWidth, y1, color)); } } else if (mFadeColors) { color = md.getFadedColor(); } } // Cases: // 1. This segment starts on a different pixel than the // previous segment started on. In this case, emit // the pixel strip, if any, and: // A. If the width is 0, then add this segment's // weight to the Pixel. // B. If the width > 0, then emit a strip for this // segment (no partial Pixel data). // // 2. Otherwise (the new segment starts on the same // pixel as the previous segment): add its "weight" // to the current pixel, and: // A. If the new segment has width 1, // then emit the pixel strip and then // add the segment's weight to the pixel. // B. If the new segment has width > 1, // then emit the pixel strip, and emit the rest // of the strip for this segment (no partial Pixel // data). Pixel pix = pixels[rd.mRank]; if (pix.mStart != pixelStart) { if (pix.mSegment != null) { // Emit the pixel strip. This also clears the pixel. emitPixelStrip(rd, y1, pix); } if (width == 0) { // Compute the "weight" of this segment for the first // pixel. For a pixel N, the "weight" of a segment is // how much of the region [N - 0.5, N + 0.5] is covered // by the segment. double weight = computeWeight(recordStart, recordEnd, pixelStart); weight = block.addWeight(pixelStart, rd.mRank, weight); if (weight > pix.mMaxWeight) { pix.setFields(pixelStart, weight, segment, color, rd); } } else { int x1 = pixelStart + LeftMargin; Strip strip = new Strip(x1, y1, width, rowHeight, rd, segment, color); mStripList.add(strip); } } else { double weight = computeWeight(recordStart, recordEnd, pixelStart); weight = block.addWeight(pixelStart, rd.mRank, weight); if (weight > pix.mMaxWeight) { pix.setFields(pixelStart, weight, segment, color, rd); } if (width == 1) { // Emit the pixel strip. This also clears the pixel. emitPixelStrip(rd, y1, pix); // Compute the weight for the next pixel pixelStart += 1; weight = computeWeight(recordStart, recordEnd, pixelStart); weight = block.addWeight(pixelStart, rd.mRank, weight); pix.setFields(pixelStart, weight, segment, color, rd); } else if (width > 1) { // Emit the pixel strip. This also clears the pixel. emitPixelStrip(rd, y1, pix); // Emit a strip for the rest of the segment. pixelStart += 1; width -= 1; int x1 = pixelStart + LeftMargin; Strip strip = new Strip(x1, y1, width, rowHeight, rd, segment, color); mStripList.add(strip); } } } // Emit the last pixels of each row, if any for (int ii = 0; ii < mNumRows; ++ii) { Pixel pix = pixels[ii]; if (pix.mSegment != null) { RowData rd = pix.mRowData; int y1 = rd.mRank * rowYSpace + rowYMarginHalf; // Emit the pixel strip. This also clears the pixel. emitPixelStrip(rd, y1, pix); } } if (false) { System.out.printf("computeStrips()\n"); for (Strip strip : mStripList) { System.out.printf("%3d, %3d width %3d height %d %s\n", strip.mX, strip.mY, strip.mWidth, strip.mHeight, strip.mSegment.mBlock.getName()); } } } private double computeWeight(double start, double end, int pixel) { double pixelStartFraction = mScaleInfo.valueToPixelFraction(start); double pixelEndFraction = mScaleInfo.valueToPixelFraction(end); double leftEndPoint = Math.max(pixelStartFraction, pixel - 0.5); double rightEndPoint = Math.min(pixelEndFraction, pixel + 0.5); double weight = rightEndPoint - leftEndPoint; return weight; } private void emitPixelStrip(RowData rd, int y, Pixel pixel) { Strip strip; if (pixel.mSegment == null) return; int x = pixel.mStart + LeftMargin; // Compute the percentage of the row height proportional to // the weight of this pixel. But don't let the proportion // exceed 3/4 of the row height so that we can easily see // if a given time range includes more than one method. int height = (int) (pixel.mMaxWeight * rowHeight * 0.75); if (height < mMinStripHeight) height = mMinStripHeight; int remainder = rowHeight - height; if (remainder > 0) { strip = new Strip(x, y, 1, remainder, rd, pixel.mSegment, mFadeColors ? mColorGray : mColorBlack); mStripList.add(strip); // System.out.printf("emitPixel (%d, %d) height %d black\n", // x, y, remainder); } strip = new Strip(x, y + remainder, 1, height, rd, pixel.mSegment, pixel.mColor); mStripList.add(strip); // System.out.printf("emitPixel (%d, %d) height %d %s\n", // x, y + remainder, height, pixel.segment.block.getName()); pixel.mSegment = null; pixel.mMaxWeight = 0.0; } private void mouseMove(MouseEvent me) { if (false) { if (mHighlightMethodData != null) { mHighlightMethodData = null; // Force a recomputation of the strip colors mCachedEndRow = -1; } } Point dim = mSurface.getSize(); int x = me.x; if (x < LeftMargin) x = LeftMargin; if (x > dim.x - RightMargin) x = dim.x - RightMargin; mMouse.x = x; mMouse.y = me.y; mTimescale.setVbarPosition(x); if (mGraphicsState == GraphicsState.Marking) { mTimescale.setMarkEnd(x); } if (mGraphicsState == GraphicsState.Normal) { // Set the cursor to the normal state. mSurface.setCursor(mNormalCursor); } else if (mGraphicsState == GraphicsState.Marking) { // Make the cursor point in the direction of the sweep if (mMouse.x >= mMouseMarkStartX) mSurface.setCursor(mIncreasingCursor); else mSurface.setCursor(mDecreasingCursor); } int rownum = (mMouse.y + mScrollOffsetY) / rowYSpace; if (me.y < 0 || me.y >= dim.y) { rownum = -1; } if (mMouseRow != rownum) { mMouseRow = rownum; mLabels.redraw(); } redraw(); } private void mouseDown(MouseEvent me) { Point dim = mSurface.getSize(); int x = me.x; if (x < LeftMargin) x = LeftMargin; if (x > dim.x - RightMargin) x = dim.x - RightMargin; mMouseMarkStartX = x; mGraphicsState = GraphicsState.Marking; mSurface.setCursor(mIncreasingCursor); mTimescale.setMarkStart(mMouseMarkStartX); mTimescale.setMarkEnd(mMouseMarkStartX); redraw(); } private void mouseUp(MouseEvent me) { mSurface.setCursor(mNormalCursor); if (mGraphicsState != GraphicsState.Marking) { mGraphicsState = GraphicsState.Normal; return; } mGraphicsState = GraphicsState.Animating; Point dim = mSurface.getSize(); // If the user released the mouse outside the drawing area then // cancel the zoom. if (me.y <= 0 || me.y >= dim.y) { mGraphicsState = GraphicsState.Normal; redraw(); return; } int x = me.x; if (x < LeftMargin) x = LeftMargin; if (x > dim.x - RightMargin) x = dim.x - RightMargin; mMouseMarkEndX = x; // If the user clicked and released the mouse at the same point // (+/- a pixel or two) then cancel the zoom (but select the // method). int dist = mMouseMarkEndX - mMouseMarkStartX; if (dist < 0) dist = -dist; if (dist <= 2) { mGraphicsState = GraphicsState.Normal; // Select the method underneath the mouse mMouseSelect.x = mMouseMarkStartX; mMouseSelect.y = me.y; redraw(); return; } // Make mouseEndX be the higher end point if (mMouseMarkEndX < mMouseMarkStartX) { int temp = mMouseMarkEndX; mMouseMarkEndX = mMouseMarkStartX; mMouseMarkStartX = temp; } // If the zoom area is the whole window (or nearly the whole // window) then cancel the zoom. if (mMouseMarkStartX <= LeftMargin + MinZoomPixelMargin && mMouseMarkEndX >= dim.x - RightMargin - MinZoomPixelMargin) { mGraphicsState = GraphicsState.Normal; redraw(); return; } // Compute some variables needed for zooming. // It's probably easiest to explain by an example. There // are two scales (or dimensions) involved: one for the pixels // and one for the values (microseconds). To keep the example // simple, suppose we have pixels in the range [0,16] and // values in the range [100, 260], and suppose the user // selects a zoom window from pixel 4 to pixel 8. // // usec: 100 140 180 260 // |-------|ZZZZZZZ|---------------| // pixel: 0 4 8 16 // // I've drawn the pixels starting at zero for simplicity, but // in fact the drawable area is offset from the left margin // by the value of "LeftMargin". // // The "pixels-per-range" (ppr) in this case is 0.1 (a tenth of // a pixel per usec). What we want is to redraw the screen in // several steps, each time increasing the zoom window until the // zoom window fills the screen. For simplicity, assume that // we want to zoom in four equal steps. Then the snapshots // of the screen at each step would look something like this: // // usec: 100 140 180 260 // |-------|ZZZZZZZ|---------------| // pixel: 0 4 8 16 // // usec: ? 140 180 ? // |-----|ZZZZZZZZZZZZZ|-----------| // pixel: 0 3 10 16 // // usec: ? 140 180 ? // |---|ZZZZZZZZZZZZZZZZZZZ|-------| // pixel: 0 2 12 16 // // usec: ?140 180 ? // |-|ZZZZZZZZZZZZZZZZZZZZZZZZZ|---| // pixel: 0 1 14 16 // // usec: 140 180 // |ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ| // pixel: 0 16 // // The problem is how to compute the endpoints (denoted by ?) // for each step. This is a little tricky. We first need to // compute the "fixed point": this is the point in the selection // that doesn't move left or right. Then we can recompute the // "ppr" (pixels per range) at each step and then find the // endpoints. The computation of the end points is done // in animateZoom(). This method computes the fixed point // and some other variables needed in animateZoom(). double minVal = mScaleInfo.getMinVal(); double maxVal = mScaleInfo.getMaxVal(); double ppr = mScaleInfo.getPixelsPerRange(); mZoomMin = minVal + ((mMouseMarkStartX - LeftMargin) / ppr); mZoomMax = minVal + ((mMouseMarkEndX - LeftMargin) / ppr); // Clamp the min and max values to the actual data min and max if (mZoomMin < mMinDataVal) mZoomMin = mMinDataVal; if (mZoomMax > mMaxDataVal) mZoomMax = mMaxDataVal; // Snap the min and max points to the grid determined by the // TickScaler // before we zoom. int xdim = dim.x - TotalXMargin; TickScaler scaler = new TickScaler(mZoomMin, mZoomMax, xdim, PixelsPerTick); scaler.computeTicks(false); mZoomMin = scaler.getMinVal(); mZoomMax = scaler.getMaxVal(); // Also snap the mouse points (in pixel space) to be consistent with // zoomMin and zoomMax (in value space). mMouseMarkStartX = (int) ((mZoomMin - minVal) * ppr + LeftMargin); mMouseMarkEndX = (int) ((mZoomMax - minVal) * ppr + LeftMargin); mTimescale.setMarkStart(mMouseMarkStartX); mTimescale.setMarkEnd(mMouseMarkEndX); // Compute the mouse selection end point distances mMouseEndDistance = dim.x - RightMargin - mMouseMarkEndX; mMouseStartDistance = mMouseMarkStartX - LeftMargin; mZoomMouseStart = mMouseMarkStartX; mZoomMouseEnd = mMouseMarkEndX; mZoomStep = 0; // Compute the fixed point in both value space and pixel space. mMin2ZoomMin = mZoomMin - minVal; mZoomMax2Max = maxVal - mZoomMax; mZoomFixed = mZoomMin + (mZoomMax - mZoomMin) * mMin2ZoomMin / (mMin2ZoomMin + mZoomMax2Max); mZoomFixedPixel = (mZoomFixed - minVal) * ppr + LeftMargin; mFixedPixelStartDistance = mZoomFixedPixel - LeftMargin; mFixedPixelEndDistance = dim.x - RightMargin - mZoomFixedPixel; mZoomMin2Fixed = mZoomFixed - mZoomMin; mFixed2ZoomMax = mZoomMax - mZoomFixed; getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator); redraw(); update(); } // No defined behavior yet for double-click. private void mouseDoubleClick(MouseEvent me) { } public void startScaling(int mouseX) { Point dim = mSurface.getSize(); int x = mouseX; if (x < LeftMargin) x = LeftMargin; if (x > dim.x - RightMargin) x = dim.x - RightMargin; mMouseMarkStartX = x; mGraphicsState = GraphicsState.Scaling; mScalePixelsPerRange = mScaleInfo.getPixelsPerRange(); mScaleMinVal = mScaleInfo.getMinVal(); mScaleMaxVal = mScaleInfo.getMaxVal(); } public void stopScaling(int mouseX) { mGraphicsState = GraphicsState.Normal; } private void animateHighlight() { mHighlightStep += 1; if (mHighlightStep >= HIGHLIGHT_STEPS) { mFadeColors = false; mHighlightStep = 0; // Force a recomputation of the strip colors mCachedEndRow = -1; } else { mFadeColors = true; mShowHighlightName = true; highlightHeight = highlightHeights[mHighlightStep]; getDisplay().timerExec(HIGHLIGHT_TIMER_INTERVAL, mHighlightAnimator); } redraw(); } private void clearHighlights() { // System.out.printf("clearHighlights()\n"); mShowHighlightName = false; highlightHeight = 0; mHighlightMethodData = null; mHighlightCall = null; mFadeColors = false; mHighlightStep = 0; // Force a recomputation of the strip colors mCachedEndRow = -1; redraw(); } private void animateZoom() { mZoomStep += 1; if (mZoomStep > ZOOM_STEPS) { mGraphicsState = GraphicsState.Normal; // Force a normal recomputation mCachedMinVal = mScaleInfo.getMinVal() + 1; } else if (mZoomStep == ZOOM_STEPS) { mScaleInfo.setMinVal(mZoomMin); mScaleInfo.setMaxVal(mZoomMax); mMouseMarkStartX = LeftMargin; Point dim = getSize(); mMouseMarkEndX = dim.x - RightMargin; mTimescale.setMarkStart(mMouseMarkStartX); mTimescale.setMarkEnd(mMouseMarkEndX); getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator); } else { // Zoom in slowly at first, then speed up, then slow down. // The zoom fractions are precomputed to save time. double fraction = mZoomFractions[mZoomStep]; mMouseMarkStartX = (int) (mZoomMouseStart - fraction * mMouseStartDistance); mMouseMarkEndX = (int) (mZoomMouseEnd + fraction * mMouseEndDistance); mTimescale.setMarkStart(mMouseMarkStartX); mTimescale.setMarkEnd(mMouseMarkEndX); // Compute the new pixels-per-range. Avoid division by zero. double ppr; if (mZoomMin2Fixed >= mFixed2ZoomMax) ppr = (mZoomFixedPixel - mMouseMarkStartX) / mZoomMin2Fixed; else ppr = (mMouseMarkEndX - mZoomFixedPixel) / mFixed2ZoomMax; double newMin = mZoomFixed - mFixedPixelStartDistance / ppr; double newMax = mZoomFixed + mFixedPixelEndDistance / ppr; mScaleInfo.setMinVal(newMin); mScaleInfo.setMaxVal(newMax); getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator); } redraw(); } private static final int TotalXMargin = LeftMargin + RightMargin; private static final int yMargin = 1; // blank space on top // The minimum margin on each side of the zoom window, in pixels. private static final int MinZoomPixelMargin = 10; private GraphicsState mGraphicsState = GraphicsState.Normal; private Point mMouse = new Point(LeftMargin, 0); private int mMouseMarkStartX; private int mMouseMarkEndX; private boolean mDebug = false; private ArrayList<Strip> mStripList = new ArrayList<Strip>(); private ArrayList<Range> mHighlightExclusive = new ArrayList<Range>(); private ArrayList<Range> mHighlightInclusive = new ArrayList<Range>(); private int mMinStripHeight = 2; private double mCachedMinVal; private double mCachedMaxVal; private int mCachedStartRow; private int mCachedEndRow; private double mScalePixelsPerRange; private double mScaleMinVal; private double mScaleMaxVal; private double mLimitMinVal; private double mLimitMaxVal; private double mMinDataVal; private double mMaxDataVal; private Cursor mNormalCursor; private Cursor mIncreasingCursor; private Cursor mDecreasingCursor; private static final int ZOOM_TIMER_INTERVAL = 10; private static final int HIGHLIGHT_TIMER_INTERVAL = 50; private static final int ZOOM_STEPS = 8; // must be even private int highlightHeight = 4; private final int[] highlightHeights = { 0, 2, 4, 5, 6, 5, 4, 2, 4, 5, 6 }; private final int HIGHLIGHT_STEPS = highlightHeights.length; private boolean mFadeColors; private boolean mShowHighlightName; private double[] mZoomFractions; private int mZoomStep; private int mZoomMouseStart; private int mZoomMouseEnd; private int mMouseStartDistance; private int mMouseEndDistance; private Point mMouseSelect = new Point(0, 0); private double mZoomFixed; private double mZoomFixedPixel; private double mFixedPixelStartDistance; private double mFixedPixelEndDistance; private double mZoomMin2Fixed; private double mMin2ZoomMin; private double mFixed2ZoomMax; private double mZoomMax2Max; private double mZoomMin; private double mZoomMax; private Runnable mZoomAnimator; private Runnable mHighlightAnimator; private int mHighlightStep; } private int computeVisibleRows(int ydim) { // If we resize, then move the bottom row down. Don't allow the scroll // to waste space at the bottom. int offsetY = mScrollOffsetY; int spaceNeeded = mNumRows * rowYSpace; if (offsetY + ydim > spaceNeeded) { offsetY = spaceNeeded - ydim; if (offsetY < 0) { offsetY = 0; } } mStartRow = offsetY / rowYSpace; mEndRow = (offsetY + ydim) / rowYSpace; if (mEndRow >= mNumRows) { mEndRow = mNumRows - 1; } return offsetY; } private void startHighlighting() { // System.out.printf("startHighlighting()\n"); mSurface.mHighlightStep = 0; mSurface.mFadeColors = true; // Force a recomputation of the color strips mSurface.mCachedEndRow = -1; getDisplay().timerExec(0, mSurface.mHighlightAnimator); } private static class RowData { RowData(Row row) { mName = row.getName(); mStack = new ArrayList<Block>(); } public void push(Block block) { mStack.add(block); } public Block top() { if (mStack.size() == 0) return null; return mStack.get(mStack.size() - 1); } public void pop() { if (mStack.size() == 0) return; mStack.remove(mStack.size() - 1); } private String mName; private int mRank; private long mElapsed; private long mEndTime; private ArrayList<Block> mStack; } private static class Segment { Segment(RowData rowData, Block block, long startTime, long endTime) { mRowData = rowData; mBlock = block; mStartTime = startTime; mEndTime = endTime; } private RowData mRowData; private Block mBlock; private long mStartTime; private long mEndTime; } private static class Strip { Strip(int x, int y, int width, int height, RowData rowData, Segment segment, Color color) { mX = x; mY = y; mWidth = width; mHeight = height; mRowData = rowData; mSegment = segment; mColor = color; } int mX; int mY; int mWidth; int mHeight; RowData mRowData; Segment mSegment; Color mColor; } private static class Pixel { public void setFields(int start, double weight, Segment segment, Color color, RowData rowData) { mStart = start; mMaxWeight = weight; mSegment = segment; mColor = color; mRowData = rowData; } int mStart = -2; // some value that won't match another pixel double mMaxWeight; Segment mSegment; Color mColor; // we need the color here because it may be faded RowData mRowData; } private static class Range { Range(int xStart, int width, int y, Color color) { mXdim.x = xStart; mXdim.y = width; mY = y; mColor = color; } Point mXdim = new Point(0, 0); int mY; Color mColor; } }