FileDocCategorySizeDatePackage
TimeLineView.javaAPI DocAndroid 1.5 API77533Wed May 06 22:41:10 BST 2009com.android.traceview

TimeLineView

public class TimeLineView extends org.eclipse.swt.widgets.Composite implements Observer

Fields Summary
private HashMap
mRowByName
private double
mTotalElapsed
private RowData[]
mRows
private Segment[]
mSegments
private ArrayList
mSegmentList
private HashMap
mThreadLabels
private Timescale
mTimescale
private Surface
mSurface
private RowLabels
mLabels
private org.eclipse.swt.custom.SashForm
mSashForm
private int
mScrollOffsetY
public static final int
PixelsPerTick
private TickScaler
mScaleInfo
private static final int
LeftMargin
private static final int
RightMargin
private org.eclipse.swt.graphics.Color
mColorBlack
private org.eclipse.swt.graphics.Color
mColorGray
private org.eclipse.swt.graphics.Color
mColorDarkGray
private org.eclipse.swt.graphics.Color
mColorForeground
private org.eclipse.swt.graphics.Color
mColorRowBack
private org.eclipse.swt.graphics.Color
mColorZoomSelection
private org.eclipse.jface.resource.FontRegistry
mFontRegistry
private static final int
rowHeight
vertical height of drawn blocks in each row
private static final int
rowYMargin
the blank space between rows
private static final int
rowYMarginHalf
private static final int
rowYSpace
total vertical space for row
private static final int
majorTickLength
private static final int
minorTickLength
private static final int
timeLineOffsetY
private static final int
tickToFontSpacing
private static final int
topMargin
start of first row
private int
mMouseRow
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
private boolean
mSetFonts
Setting the fonts looks good on Linux but bad on Macs
Constructors Summary
public TimeLineView(org.eclipse.swt.widgets.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());
    
Methods Summary
private intcomputeVisibleRows(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 voidpopFrames(com.android.traceview.TimeLineView$RowData rd, com.android.traceview.TimeLineView$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);
        }
    
public voidsetData(java.util.ArrayList 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 voidstartHighlighting()

        // 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);
    
public voidupdate(java.util.Observable objservable, java.lang.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();