| /* |
| * 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.MouseWheelListener; |
| 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 RowData[] mRows; |
| private Segment[] mSegments; |
| 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 = 58; |
| private static final int tickToFontSpacing = 2; |
| |
| /** start of first row */ |
| private static final int topMargin = 90; |
| private int mMouseRow = -1; |
| private int mNumRows; |
| private int mStartRow; |
| private int mEndRow; |
| private TraceUnits mUnits; |
| private String mClockSource; |
| private boolean mHaveCpuTime; |
| private boolean mHaveRealTime; |
| private int mSmallFontWidth; |
| private int mSmallFontHeight; |
| 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 long getExclusiveCpuTime(); |
| public long getInclusiveCpuTime(); |
| public long getExclusiveRealTime(); |
| public long getInclusiveRealTime(); |
| public boolean isContextSwitch(); |
| public boolean isIgnoredBlock(); |
| public Block getParentBlock(); |
| } |
| |
| 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(); |
| mClockSource = reader.getClockSource(); |
| mHaveCpuTime = reader.haveCpuTime(); |
| mHaveRealTime = reader.haveRealTime(); |
| 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(); |
| |
| 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() { |
| @Override |
| 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(); |
| } |
| }); |
| |
| final ScrollBar hBar = mSurface.getHorizontalBar(); |
| hBar.addListener(SWT.Selection, new Listener() { |
| @Override |
| public void handleEvent(Event e) { |
| mSurface.setScaleFromHorizontalScrollBar(hBar.getSelection()); |
| mSurface.redraw(); |
| } |
| }); |
| |
| mSurface.addListener(SWT.Resize, new Listener() { |
| @Override |
| 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() { |
| @Override |
| public void mouseMove(MouseEvent me) { |
| mSurface.mouseMove(me); |
| } |
| }); |
| |
| mSurface.addMouseWheelListener(new MouseWheelListener() { |
| @Override |
| public void mouseScrolled(MouseEvent me) { |
| mSurface.mouseScrolled(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() { |
| @Override |
| public void mouseMove(MouseEvent me) { |
| mTimescale.mouseMove(me); |
| } |
| }); |
| |
| mLabels.addMouseMoveListener(new MouseMoveListener() { |
| @Override |
| public void mouseMove(MouseEvent me) { |
| mLabels.mouseMove(me); |
| } |
| }); |
| |
| setData(reader.getThreadTimeRecords()); |
| } |
| |
| @Override |
| 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>() { |
| @Override |
| 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; |
| } |
| }); |
| |
| ArrayList<Segment> segmentList = new ArrayList<Segment>(); |
| |
| // 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; |
| if (block.isIgnoredBlock()) { |
| continue; |
| } |
| |
| 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; |
| 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); |
| segmentList.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, segmentList); |
| 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, segmentList); |
| } |
| |
| 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>() { |
| @Override |
| 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 = segmentList.toArray(new Segment[segmentList.size()]); |
| Arrays.sort(mSegments, new Comparator<Segment>() { |
| @Override |
| 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 static void popFrames(RowData rd, Block top, long startTime, |
| ArrayList<Segment> segmentList) { |
| long topEndTime = top.getEndTime(); |
| long lastEndTime = top.getStartTime(); |
| while (topEndTime <= startTime) { |
| if (topEndTime > lastEndTime) { |
| Segment segment = new Segment(rd, top, lastEndTime, topEndTime); |
| segmentList.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); |
| segmentList.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() { |
| @Override |
| 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() { |
| @Override |
| 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 String mDetails; |
| private int mMethodStartY; |
| private int mDetailsStartY; |
| 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; |
| mDetailsStartY = mMethodStartY + mSmallFontHeight + 1; |
| addPaintListener(new PaintListener() { |
| @Override |
| 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; |
| } |
| |
| public void setDetails(String details) { |
| mDetails = details; |
| } |
| |
| 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 details, if needed |
| drawDetails(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); |
| if (mClockSource != null) { |
| info = String.format(" max %s (%s)", info, mClockSource); //$NON-NLS-1$ |
| } else { |
| 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 drawDetails(Display display, GC gc) { |
| if (mDetails == null) { |
| return; |
| } |
| |
| int x1 = LeftMargin + 2 * mSmallFontWidth + METHOD_BLOCK_MARGIN; |
| int y1 = mDetailsStartY; |
| gc.drawString(mDetails, 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, Scrolling |
| }; |
| |
| private class Surface extends Canvas { |
| |
| public Surface(Composite parent) { |
| super(parent, SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_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() { |
| @Override |
| public void paintControl(PaintEvent pe) { |
| draw(pe.display, pe.gc); |
| } |
| }); |
| |
| mZoomAnimator = new Runnable() { |
| @Override |
| public void run() { |
| animateZoom(); |
| } |
| }; |
| |
| mHighlightAnimator = new Runnable() { |
| @Override |
| 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 * ii / 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); |
| } |
| |
| public void setScaleFromHorizontalScrollBar(int selection) { |
| double minVal = mScaleInfo.getMinVal(); |
| double maxVal = mScaleInfo.getMaxVal(); |
| double visibleRange = maxVal - minVal; |
| |
| minVal = mLimitMinVal + selection; |
| maxVal = minVal + visibleRange; |
| if (maxVal > mLimitMaxVal) { |
| maxVal = mLimitMaxVal; |
| minVal = maxVal - visibleRange; |
| } |
| mScaleInfo.setMinVal(minVal); |
| mScaleInfo.setMaxVal(maxVal); |
| |
| mGraphicsState = GraphicsState.Scrolling; |
| } |
| |
| private void updateHorizontalScrollBar() { |
| double minVal = mScaleInfo.getMinVal(); |
| double maxVal = mScaleInfo.getMaxVal(); |
| double visibleRange = maxVal - minVal; |
| double fullRange = mLimitMaxVal - mLimitMinVal; |
| |
| ScrollBar hBar = getHorizontalBar(); |
| if (fullRange > visibleRange) { |
| hBar.setVisible(true); |
| hBar.setMinimum(0); |
| hBar.setMaximum((int)Math.ceil(fullRange)); |
| hBar.setThumb((int)Math.ceil(visibleRange)); |
| hBar.setSelection((int)Math.floor(minVal - mLimitMinVal)); |
| } else { |
| hBar.setVisible(false); |
| } |
| } |
| |
| 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 |
| || mGraphicsState == GraphicsState.Scrolling); |
| 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(); |
| |
| // Update the horizontal scrollbar. |
| updateHorizontalScrollBar(); |
| } |
| |
| 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; |
| String blockDetails = 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) { |
| Block block = strip.mSegment.mBlock; |
| blockName = block.getName(); |
| blockColor = strip.mColor; |
| if (mHaveCpuTime) { |
| if (mHaveRealTime) { |
| blockDetails = String.format( |
| "excl cpu %s, incl cpu %s, " |
| + "excl real %s, incl real %s", |
| mUnits.labelledString(block.getExclusiveCpuTime()), |
| mUnits.labelledString(block.getInclusiveCpuTime()), |
| mUnits.labelledString(block.getExclusiveRealTime()), |
| mUnits.labelledString(block.getInclusiveRealTime())); |
| } else { |
| blockDetails = String.format( |
| "excl cpu %s, incl cpu %s", |
| mUnits.labelledString(block.getExclusiveCpuTime()), |
| mUnits.labelledString(block.getInclusiveCpuTime())); |
| } |
| } else { |
| blockDetails = String.format( |
| "excl real %s, incl real %s", |
| mUnits.labelledString(block.getExclusiveRealTime()), |
| mUnits.labelledString(block.getInclusiveRealTime())); |
| } |
| } |
| 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); |
| mTimescale.setDetails(blockDetails); |
| 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()); |
| mTimescale.setDetails(null); |
| } |
| } else { |
| mTimescale.setMethodName(null); |
| mTimescale.setMethodColor(null); |
| mTimescale.setDetails(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 = mHighlightHeight; |
| 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 prevMethodEnd = -1; |
| int prevCallStart = -1; |
| int prevCallEnd = -1; |
| if (mHighlightCall != null) { |
| int callPixelStart = -1; |
| int callPixelEnd = -1; |
| callStart = mHighlightCall.getStartTime(); |
| callEnd = mHighlightCall.getEndTime(); |
| callMethod = mHighlightCall.getMethodData(); |
| 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; |
| |
| // Skip over blocks that were not assigned a color, including the |
| // top level block and others that have zero inclusive time. |
| 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; |
| boolean isContextSwitch = segment.mIsContextSwitch; |
| |
| 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 || prevMethodEnd != pixelEnd) { |
| prevMethodStart = pixelStart; |
| prevMethodEnd = pixelEnd; |
| int rangeWidth = width; |
| if (rangeWidth == 0) |
| rangeWidth = 1; |
| mHighlightExclusive.add(new Range(pixelStart |
| + LeftMargin, rangeWidth, y1, color)); |
| callStart = block.getStartTime(); |
| int callPixelStart = -1; |
| if (callStart >= minVal) |
| callPixelStart = mScaleInfo.valueToPixel(callStart); |
| int callPixelEnd = -1; |
| callEnd = block.getEndTime(); |
| if (callEnd <= maxVal) |
| callPixelEnd = mScaleInfo.valueToPixel(callEnd); |
| if (prevCallStart != callPixelStart || prevCallEnd != callPixelEnd) { |
| prevCallStart = callPixelStart; |
| prevCallEnd = callPixelEnd; |
| 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 || prevMethodEnd != pixelEnd) { |
| prevMethodStart = pixelStart; |
| prevMethodEnd = pixelEnd; |
| 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, |
| isContextSwitch, 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, isContextSwitch ? y1 + rowHeight - 1 : y1, |
| width, isContextSwitch ? 1 : rowHeight, |
| rd, segment, color); |
| mStripList.add(strip); |
| } |
| } else { |
| double weight = computeWeight(recordStart, recordEnd, |
| isContextSwitch, 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, |
| isContextSwitch, 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, isContextSwitch ? y1 + rowHeight - 1 : y1, |
| width, isContextSwitch ? 1 : 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, |
| boolean isContextSwitch, int pixel) { |
| if (isContextSwitch) { |
| return 0; |
| } |
| 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(); |
| } |
| |
| private void mouseScrolled(MouseEvent me) { |
| mGraphicsState = GraphicsState.Scrolling; |
| double tMin = mScaleInfo.getMinVal(); |
| double tMax = mScaleInfo.getMaxVal(); |
| double zoomFactor = 2; |
| double tMinRef = mLimitMinVal; |
| double tMaxRef = mLimitMaxVal; |
| double t; // the fixed point |
| double tMinNew; |
| double tMaxNew; |
| if (me.count > 0) { |
| // we zoom in |
| Point dim = mSurface.getSize(); |
| int x = me.x; |
| if (x < LeftMargin) |
| x = LeftMargin; |
| if (x > dim.x - RightMargin) |
| x = dim.x - RightMargin; |
| double ppr = mScaleInfo.getPixelsPerRange(); |
| t = tMin + ((x - LeftMargin) / ppr); |
| tMinNew = Math.max(tMinRef, t - (t - tMin) / zoomFactor); |
| tMaxNew = Math.min(tMaxRef, t + (tMax - t) / zoomFactor); |
| } else { |
| // we zoom out |
| double factor = (tMax - tMin) / (tMaxRef - tMinRef); |
| if (factor < 1) { |
| t = (factor * tMinRef - tMin) / (factor - 1); |
| tMinNew = Math.max(tMinRef, t - zoomFactor * (t - tMin)); |
| tMaxNew = Math.min(tMaxRef, t + zoomFactor * (tMax - t)); |
| } else { |
| return; |
| } |
| } |
| mScaleInfo.setMinVal(tMinNew); |
| mScaleInfo.setMaxVal(tMaxNew); |
| mSurface.redraw(); |
| } |
| |
| // 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; |
| mHighlightHeight = highlightHeights[mHighlightStep]; |
| getDisplay().timerExec(HIGHLIGHT_TIMER_INTERVAL, mHighlightAnimator); |
| } |
| redraw(); |
| } |
| |
| private void clearHighlights() { |
| // System.out.printf("clearHighlights()\n"); |
| mShowHighlightName = false; |
| mHighlightHeight = 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 mHighlightHeight = 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; |
| if (block.isContextSwitch()) { |
| mBlock = block.getParentBlock(); |
| mIsContextSwitch = true; |
| } else { |
| mBlock = block; |
| } |
| mStartTime = startTime; |
| mEndTime = endTime; |
| } |
| |
| private RowData mRowData; |
| private Block mBlock; |
| private long mStartTime; |
| private long mEndTime; |
| private boolean mIsContextSwitch; |
| } |
| |
| 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; |
| } |
| } |