Home | History | Annotate | Download | only in traceview
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.traceview;
     18 
     19 import org.eclipse.jface.resource.FontRegistry;
     20 import org.eclipse.swt.SWT;
     21 import org.eclipse.swt.custom.SashForm;
     22 import org.eclipse.swt.events.MouseAdapter;
     23 import org.eclipse.swt.events.MouseEvent;
     24 import org.eclipse.swt.events.MouseMoveListener;
     25 import org.eclipse.swt.events.MouseWheelListener;
     26 import org.eclipse.swt.events.PaintEvent;
     27 import org.eclipse.swt.events.PaintListener;
     28 import org.eclipse.swt.graphics.Color;
     29 import org.eclipse.swt.graphics.Cursor;
     30 import org.eclipse.swt.graphics.FontData;
     31 import org.eclipse.swt.graphics.GC;
     32 import org.eclipse.swt.graphics.Image;
     33 import org.eclipse.swt.graphics.Point;
     34 import org.eclipse.swt.graphics.Rectangle;
     35 import org.eclipse.swt.layout.FillLayout;
     36 import org.eclipse.swt.layout.GridData;
     37 import org.eclipse.swt.layout.GridLayout;
     38 import org.eclipse.swt.widgets.Canvas;
     39 import org.eclipse.swt.widgets.Composite;
     40 import org.eclipse.swt.widgets.Display;
     41 import org.eclipse.swt.widgets.Event;
     42 import org.eclipse.swt.widgets.Listener;
     43 import org.eclipse.swt.widgets.ScrollBar;
     44 
     45 import java.util.ArrayList;
     46 import java.util.Arrays;
     47 import java.util.Collection;
     48 import java.util.Collections;
     49 import java.util.Comparator;
     50 import java.util.HashMap;
     51 import java.util.Observable;
     52 import java.util.Observer;
     53 
     54 public class TimeLineView extends Composite implements Observer {
     55 
     56     private HashMap<String, RowData> mRowByName;
     57     private RowData[] mRows;
     58     private Segment[] mSegments;
     59     private HashMap<Integer, String> mThreadLabels;
     60     private Timescale mTimescale;
     61     private Surface mSurface;
     62     private RowLabels mLabels;
     63     private SashForm mSashForm;
     64     private int mScrollOffsetY;
     65 
     66     public static final int PixelsPerTick = 50;
     67     private TickScaler mScaleInfo = new TickScaler(0, 0, 0, PixelsPerTick);
     68     private static final int LeftMargin = 10; // blank space on left
     69     private static final int RightMargin = 60; // blank space on right
     70 
     71     private Color mColorBlack;
     72     private Color mColorGray;
     73     private Color mColorDarkGray;
     74     private Color mColorForeground;
     75     private Color mColorRowBack;
     76     private Color mColorZoomSelection;
     77     private FontRegistry mFontRegistry;
     78 
     79     /** vertical height of drawn blocks in each row */
     80     private static final int rowHeight = 20;
     81 
     82     /** the blank space between rows */
     83     private static final int rowYMargin = 12;
     84     private static final int rowYMarginHalf = rowYMargin / 2;
     85 
     86     /** total vertical space for row */
     87     private static final int rowYSpace = rowHeight + rowYMargin;
     88     private static final int majorTickLength = 8;
     89     private static final int minorTickLength = 4;
     90     private static final int timeLineOffsetY = 58;
     91     private static final int tickToFontSpacing = 2;
     92 
     93     /** start of first row */
     94     private static final int topMargin = 90;
     95     private int mMouseRow = -1;
     96     private int mNumRows;
     97     private int mStartRow;
     98     private int mEndRow;
     99     private TraceUnits mUnits;
    100     private String mClockSource;
    101     private boolean mHaveCpuTime;
    102     private boolean mHaveRealTime;
    103     private int mSmallFontWidth;
    104     private int mSmallFontHeight;
    105     private SelectionController mSelectionController;
    106     private MethodData mHighlightMethodData;
    107     private Call mHighlightCall;
    108     private static final int MinInclusiveRange = 3;
    109 
    110     /** Setting the fonts looks good on Linux but bad on Macs */
    111     private boolean mSetFonts = false;
    112 
    113     public static interface Block {
    114         public String getName();
    115         public MethodData getMethodData();
    116         public long getStartTime();
    117         public long getEndTime();
    118         public Color getColor();
    119         public double addWeight(int x, int y, double weight);
    120         public void clearWeight();
    121         public long getExclusiveCpuTime();
    122         public long getInclusiveCpuTime();
    123         public long getExclusiveRealTime();
    124         public long getInclusiveRealTime();
    125         public boolean isContextSwitch();
    126         public boolean isIgnoredBlock();
    127         public Block getParentBlock();
    128     }
    129 
    130     public static interface Row {
    131         public int getId();
    132         public String getName();
    133     }
    134 
    135     public static class Record {
    136         Row row;
    137         Block block;
    138 
    139         public Record(Row row, Block block) {
    140             this.row = row;
    141             this.block = block;
    142         }
    143     }
    144 
    145     public TimeLineView(Composite parent, TraceReader reader,
    146             SelectionController selectionController) {
    147         super(parent, SWT.NONE);
    148         mRowByName = new HashMap<String, RowData>();
    149         this.mSelectionController = selectionController;
    150         selectionController.addObserver(this);
    151         mUnits = reader.getTraceUnits();
    152         mClockSource = reader.getClockSource();
    153         mHaveCpuTime = reader.haveCpuTime();
    154         mHaveRealTime = reader.haveRealTime();
    155         mThreadLabels = reader.getThreadLabels();
    156 
    157         Display display = getDisplay();
    158         mColorGray = display.getSystemColor(SWT.COLOR_GRAY);
    159         mColorDarkGray = display.getSystemColor(SWT.COLOR_DARK_GRAY);
    160         mColorBlack = display.getSystemColor(SWT.COLOR_BLACK);
    161         // mColorBackground = display.getSystemColor(SWT.COLOR_WHITE);
    162         mColorForeground = display.getSystemColor(SWT.COLOR_BLACK);
    163         mColorRowBack = new Color(display, 240, 240, 255);
    164         mColorZoomSelection = new Color(display, 230, 230, 230);
    165 
    166         mFontRegistry = new FontRegistry(display);
    167         mFontRegistry.put("small",  //$NON-NLS-1$
    168                 new FontData[] { new FontData("Arial", 8, SWT.NORMAL) });  //$NON-NLS-1$
    169         mFontRegistry.put("courier8",  //$NON-NLS-1$
    170                 new FontData[] { new FontData("Courier New", 8, SWT.BOLD) });  //$NON-NLS-1$
    171         mFontRegistry.put("medium",  //$NON-NLS-1$
    172                 new FontData[] { new FontData("Courier New", 10, SWT.NORMAL) });  //$NON-NLS-1$
    173 
    174         Image image = new Image(display, new Rectangle(100, 100, 100, 100));
    175         GC gc = new GC(image);
    176         if (mSetFonts) {
    177             gc.setFont(mFontRegistry.get("small"));  //$NON-NLS-1$
    178         }
    179         mSmallFontWidth = gc.getFontMetrics().getAverageCharWidth();
    180         mSmallFontHeight = gc.getFontMetrics().getHeight();
    181 
    182         image.dispose();
    183         gc.dispose();
    184 
    185         setLayout(new FillLayout());
    186 
    187         // Create a sash form for holding two canvas views, one for the
    188         // thread labels and one for the thread timeline.
    189         mSashForm = new SashForm(this, SWT.HORIZONTAL);
    190         mSashForm.setBackground(mColorGray);
    191         mSashForm.SASH_WIDTH = 3;
    192 
    193         // Create a composite for the left side of the sash
    194         Composite composite = new Composite(mSashForm, SWT.NONE);
    195         GridLayout layout = new GridLayout(1, true /* make columns equal width */);
    196         layout.marginHeight = 0;
    197         layout.marginWidth = 0;
    198         layout.verticalSpacing = 1;
    199         composite.setLayout(layout);
    200 
    201         // Create a blank corner space in the upper left corner
    202         BlankCorner corner = new BlankCorner(composite);
    203         GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
    204         gridData.heightHint = topMargin;
    205         corner.setLayoutData(gridData);
    206 
    207         // Add the thread labels below the blank corner.
    208         mLabels = new RowLabels(composite);
    209         gridData = new GridData(GridData.FILL_BOTH);
    210         mLabels.setLayoutData(gridData);
    211 
    212         // Create another composite for the right side of the sash
    213         composite = new Composite(mSashForm, SWT.NONE);
    214         layout = new GridLayout(1, true /* make columns equal width */);
    215         layout.marginHeight = 0;
    216         layout.marginWidth = 0;
    217         layout.verticalSpacing = 1;
    218         composite.setLayout(layout);
    219 
    220         mTimescale = new Timescale(composite);
    221         gridData = new GridData(GridData.FILL_HORIZONTAL);
    222         gridData.heightHint = topMargin;
    223         mTimescale.setLayoutData(gridData);
    224 
    225         mSurface = new Surface(composite);
    226         gridData = new GridData(GridData.FILL_BOTH);
    227         mSurface.setLayoutData(gridData);
    228         mSashForm.setWeights(new int[] { 1, 5 });
    229 
    230         final ScrollBar vBar = mSurface.getVerticalBar();
    231         vBar.addListener(SWT.Selection, new Listener() {
    232            public void handleEvent(Event e) {
    233                mScrollOffsetY = vBar.getSelection();
    234                Point dim = mSurface.getSize();
    235                int newScrollOffsetY = computeVisibleRows(dim.y);
    236                if (newScrollOffsetY != mScrollOffsetY) {
    237                    mScrollOffsetY = newScrollOffsetY;
    238                    vBar.setSelection(newScrollOffsetY);
    239                }
    240                mLabels.redraw();
    241                mSurface.redraw();
    242            }
    243         });
    244 
    245         final ScrollBar hBar = mSurface.getHorizontalBar();
    246         hBar.addListener(SWT.Selection, new Listener() {
    247             public void handleEvent(Event e) {
    248                 mSurface.setScaleFromHorizontalScrollBar(hBar.getSelection());
    249                 mSurface.redraw();
    250             }
    251         });
    252 
    253         mSurface.addListener(SWT.Resize, new Listener() {
    254             public void handleEvent(Event e) {
    255                 Point dim = mSurface.getSize();
    256 
    257                 // If we don't need the scroll bar then don't display it.
    258                 if (dim.y >= mNumRows * rowYSpace) {
    259                     vBar.setVisible(false);
    260                 } else {
    261                     vBar.setVisible(true);
    262                 }
    263                 int newScrollOffsetY = computeVisibleRows(dim.y);
    264                 if (newScrollOffsetY != mScrollOffsetY) {
    265                     mScrollOffsetY = newScrollOffsetY;
    266                     vBar.setSelection(newScrollOffsetY);
    267                 }
    268 
    269                 int spaceNeeded = mNumRows * rowYSpace;
    270                 vBar.setMaximum(spaceNeeded);
    271                 vBar.setThumb(dim.y);
    272 
    273                 mLabels.redraw();
    274                 mSurface.redraw();
    275             }
    276         });
    277 
    278         mSurface.addMouseListener(new MouseAdapter() {
    279             @Override
    280             public void mouseUp(MouseEvent me) {
    281                 mSurface.mouseUp(me);
    282             }
    283 
    284             @Override
    285             public void mouseDown(MouseEvent me) {
    286                 mSurface.mouseDown(me);
    287             }
    288 
    289             @Override
    290             public void mouseDoubleClick(MouseEvent me) {
    291                 mSurface.mouseDoubleClick(me);
    292             }
    293         });
    294 
    295         mSurface.addMouseMoveListener(new MouseMoveListener() {
    296             public void mouseMove(MouseEvent me) {
    297                 mSurface.mouseMove(me);
    298             }
    299         });
    300 
    301         mSurface.addMouseWheelListener(new MouseWheelListener() {
    302             public void mouseScrolled(MouseEvent me) {
    303                 mSurface.mouseScrolled(me);
    304             }
    305         });
    306 
    307         mTimescale.addMouseListener(new MouseAdapter() {
    308             @Override
    309             public void mouseUp(MouseEvent me) {
    310                 mTimescale.mouseUp(me);
    311             }
    312 
    313             @Override
    314             public void mouseDown(MouseEvent me) {
    315                 mTimescale.mouseDown(me);
    316             }
    317 
    318             @Override
    319             public void mouseDoubleClick(MouseEvent me) {
    320                 mTimescale.mouseDoubleClick(me);
    321             }
    322         });
    323 
    324         mTimescale.addMouseMoveListener(new MouseMoveListener() {
    325             public void mouseMove(MouseEvent me) {
    326                 mTimescale.mouseMove(me);
    327             }
    328         });
    329 
    330         mLabels.addMouseMoveListener(new MouseMoveListener() {
    331             public void mouseMove(MouseEvent me) {
    332                 mLabels.mouseMove(me);
    333             }
    334         });
    335 
    336         setData(reader.getThreadTimeRecords());
    337     }
    338 
    339     public void update(Observable objservable, Object arg) {
    340         // Ignore updates from myself
    341         if (arg == "TimeLineView")  //$NON-NLS-1$
    342             return;
    343         // System.out.printf("timeline update from %s\n", arg);
    344         boolean foundHighlight = false;
    345         ArrayList<Selection> selections;
    346         selections = mSelectionController.getSelections();
    347         for (Selection selection : selections) {
    348             Selection.Action action = selection.getAction();
    349             if (action != Selection.Action.Highlight)
    350                 continue;
    351             String name = selection.getName();
    352             // System.out.printf(" timeline highlight %s from %s\n", name, arg);
    353             if (name == "MethodData") {  //$NON-NLS-1$
    354                 foundHighlight = true;
    355                 mHighlightMethodData = (MethodData) selection.getValue();
    356                 // System.out.printf(" method %s\n",
    357                 // highlightMethodData.getName());
    358                 mHighlightCall = null;
    359                 startHighlighting();
    360             } else if (name == "Call") {  //$NON-NLS-1$
    361                 foundHighlight = true;
    362                 mHighlightCall = (Call) selection.getValue();
    363                 // System.out.printf(" call %s\n", highlightCall.getName());
    364                 mHighlightMethodData = null;
    365                 startHighlighting();
    366             }
    367         }
    368         if (foundHighlight == false)
    369             mSurface.clearHighlights();
    370     }
    371 
    372     public void setData(ArrayList<Record> records) {
    373         if (records == null)
    374             records = new ArrayList<Record>();
    375 
    376         if (false) {
    377             System.out.println("TimelineView() list of records:");  //$NON-NLS-1$
    378             for (Record r : records) {
    379                 System.out.printf("row '%s' block '%s' [%d, %d]\n", r.row  //$NON-NLS-1$
    380                         .getName(), r.block.getName(), r.block.getStartTime(),
    381                         r.block.getEndTime());
    382                 if (r.block.getStartTime() > r.block.getEndTime()) {
    383                     System.err.printf("Error: block startTime > endTime\n");  //$NON-NLS-1$
    384                     System.exit(1);
    385                 }
    386             }
    387         }
    388 
    389         // Sort the records into increasing start time, and decreasing end time
    390         Collections.sort(records, new Comparator<Record>() {
    391             public int compare(Record r1, Record r2) {
    392                 long start1 = r1.block.getStartTime();
    393                 long start2 = r2.block.getStartTime();
    394                 if (start1 > start2)
    395                     return 1;
    396                 if (start1 < start2)
    397                     return -1;
    398 
    399                 // The start times are the same, so compare the end times
    400                 long end1 = r1.block.getEndTime();
    401                 long end2 = r2.block.getEndTime();
    402                 if (end1 > end2)
    403                     return -1;
    404                 if (end1 < end2)
    405                     return 1;
    406 
    407                 return 0;
    408             }
    409         });
    410 
    411         ArrayList<Segment> segmentList = new ArrayList<Segment>();
    412 
    413         // The records are sorted into increasing start time,
    414         // so the minimum start time is the start time of the first record.
    415         double minVal = 0;
    416         if (records.size() > 0)
    417             minVal = records.get(0).block.getStartTime();
    418 
    419         // Sum the time spent in each row and block, and
    420         // keep track of the maximum end time.
    421         double maxVal = 0;
    422         for (Record rec : records) {
    423             Row row = rec.row;
    424             Block block = rec.block;
    425             if (block.isIgnoredBlock()) {
    426                 continue;
    427             }
    428 
    429             String rowName = row.getName();
    430             RowData rd = mRowByName.get(rowName);
    431             if (rd == null) {
    432                 rd = new RowData(row);
    433                 mRowByName.put(rowName, rd);
    434             }
    435             long blockStartTime = block.getStartTime();
    436             long blockEndTime = block.getEndTime();
    437             if (blockEndTime > rd.mEndTime) {
    438                 long start = Math.max(blockStartTime, rd.mEndTime);
    439                 rd.mElapsed += blockEndTime - start;
    440                 rd.mEndTime = blockEndTime;
    441             }
    442             if (blockEndTime > maxVal)
    443                 maxVal = blockEndTime;
    444 
    445             // Keep track of nested blocks by using a stack (for each row).
    446             // Create a Segment object for each visible part of a block.
    447             Block top = rd.top();
    448             if (top == null) {
    449                 rd.push(block);
    450                 continue;
    451             }
    452 
    453             long topStartTime = top.getStartTime();
    454             long topEndTime = top.getEndTime();
    455             if (topEndTime >= blockStartTime) {
    456                 // Add this segment if it has a non-zero elapsed time.
    457                 if (topStartTime < blockStartTime) {
    458                     Segment segment = new Segment(rd, top, topStartTime,
    459                             blockStartTime);
    460                     segmentList.add(segment);
    461                 }
    462 
    463                 // If this block starts where the previous (top) block ends,
    464                 // then pop off the top block.
    465                 if (topEndTime == blockStartTime)
    466                     rd.pop();
    467                 rd.push(block);
    468             } else {
    469                 // We may have to pop several frames here.
    470                 popFrames(rd, top, blockStartTime, segmentList);
    471                 rd.push(block);
    472             }
    473         }
    474 
    475         // Clean up the stack of each row
    476         for (RowData rd : mRowByName.values()) {
    477             Block top = rd.top();
    478             popFrames(rd, top, Integer.MAX_VALUE, segmentList);
    479         }
    480 
    481         mSurface.setRange(minVal, maxVal);
    482         mSurface.setLimitRange(minVal, maxVal);
    483 
    484         // Sort the rows into decreasing elapsed time
    485         Collection<RowData> rv = mRowByName.values();
    486         mRows = rv.toArray(new RowData[rv.size()]);
    487         Arrays.sort(mRows, new Comparator<RowData>() {
    488             public int compare(RowData rd1, RowData rd2) {
    489                 return (int) (rd2.mElapsed - rd1.mElapsed);
    490             }
    491         });
    492 
    493         // Assign ranks to the sorted rows
    494         for (int ii = 0; ii < mRows.length; ++ii) {
    495             mRows[ii].mRank = ii;
    496         }
    497 
    498         // Compute the number of rows with data
    499         mNumRows = 0;
    500         for (int ii = 0; ii < mRows.length; ++ii) {
    501             if (mRows[ii].mElapsed == 0)
    502                 break;
    503             mNumRows += 1;
    504         }
    505 
    506         // Sort the blocks into increasing rows, and within rows into
    507         // increasing start values.
    508         mSegments = segmentList.toArray(new Segment[segmentList.size()]);
    509         Arrays.sort(mSegments, new Comparator<Segment>() {
    510             public int compare(Segment bd1, Segment bd2) {
    511                 RowData rd1 = bd1.mRowData;
    512                 RowData rd2 = bd2.mRowData;
    513                 int diff = rd1.mRank - rd2.mRank;
    514                 if (diff == 0) {
    515                     long timeDiff = bd1.mStartTime - bd2.mStartTime;
    516                     if (timeDiff == 0)
    517                         timeDiff = bd1.mEndTime - bd2.mEndTime;
    518                     return (int) timeDiff;
    519                 }
    520                 return diff;
    521             }
    522         });
    523 
    524         if (false) {
    525             for (Segment segment : mSegments) {
    526                 System.out.printf("seg '%s' [%6d, %6d] %s\n",
    527                         segment.mRowData.mName, segment.mStartTime,
    528                         segment.mEndTime, segment.mBlock.getName());
    529                 if (segment.mStartTime > segment.mEndTime) {
    530                     System.err.printf("Error: segment startTime > endTime\n");
    531                     System.exit(1);
    532                 }
    533             }
    534         }
    535     }
    536 
    537     private static void popFrames(RowData rd, Block top, long startTime,
    538             ArrayList<Segment> segmentList) {
    539         long topEndTime = top.getEndTime();
    540         long lastEndTime = top.getStartTime();
    541         while (topEndTime <= startTime) {
    542             if (topEndTime > lastEndTime) {
    543                 Segment segment = new Segment(rd, top, lastEndTime, topEndTime);
    544                 segmentList.add(segment);
    545                 lastEndTime = topEndTime;
    546             }
    547             rd.pop();
    548             top = rd.top();
    549             if (top == null)
    550                 return;
    551             topEndTime = top.getEndTime();
    552         }
    553 
    554         // If we get here, then topEndTime > startTime
    555         if (lastEndTime < startTime) {
    556             Segment bd = new Segment(rd, top, lastEndTime, startTime);
    557             segmentList.add(bd);
    558         }
    559     }
    560 
    561     private class RowLabels extends Canvas {
    562 
    563         /** The space between the row label and the sash line */
    564         private static final int labelMarginX = 2;
    565 
    566         public RowLabels(Composite parent) {
    567             super(parent, SWT.NO_BACKGROUND);
    568             addPaintListener(new PaintListener() {
    569                 public void paintControl(PaintEvent pe) {
    570                     draw(pe.display, pe.gc);
    571                 }
    572             });
    573         }
    574 
    575         private void mouseMove(MouseEvent me) {
    576             int rownum = (me.y + mScrollOffsetY) / rowYSpace;
    577             if (mMouseRow != rownum) {
    578                 mMouseRow = rownum;
    579                 redraw();
    580                 mSurface.redraw();
    581             }
    582         }
    583 
    584         private void draw(Display display, GC gc) {
    585             if (mSegments.length == 0) {
    586                 // gc.setBackground(colorBackground);
    587                 // gc.fillRectangle(getBounds());
    588                 return;
    589             }
    590             Point dim = getSize();
    591 
    592             // Create an image for double-buffering
    593             Image image = new Image(display, getBounds());
    594 
    595             // Set up the off-screen gc
    596             GC gcImage = new GC(image);
    597             if (mSetFonts)
    598                 gcImage.setFont(mFontRegistry.get("medium"));  //$NON-NLS-1$
    599 
    600             if (mNumRows > 2) {
    601                 // Draw the row background stripes
    602                 gcImage.setBackground(mColorRowBack);
    603                 for (int ii = 1; ii < mNumRows; ii += 2) {
    604                     RowData rd = mRows[ii];
    605                     int y1 = rd.mRank * rowYSpace - mScrollOffsetY;
    606                     gcImage.fillRectangle(0, y1, dim.x, rowYSpace);
    607                 }
    608             }
    609 
    610             // Draw the row labels
    611             int offsetY = rowYMarginHalf - mScrollOffsetY;
    612             for (int ii = mStartRow; ii <= mEndRow; ++ii) {
    613                 RowData rd = mRows[ii];
    614                 int y1 = rd.mRank * rowYSpace + offsetY;
    615                 Point extent = gcImage.stringExtent(rd.mName);
    616                 int x1 = dim.x - extent.x - labelMarginX;
    617                 gcImage.drawString(rd.mName, x1, y1, true);
    618             }
    619 
    620             // Draw a highlight box on the row where the mouse is.
    621             if (mMouseRow >= mStartRow && mMouseRow <= mEndRow) {
    622                 gcImage.setForeground(mColorGray);
    623                 int y1 = mMouseRow * rowYSpace - mScrollOffsetY;
    624                 gcImage.drawRectangle(0, y1, dim.x, rowYSpace);
    625             }
    626 
    627             // Draw the off-screen buffer to the screen
    628             gc.drawImage(image, 0, 0);
    629 
    630             // Clean up
    631             image.dispose();
    632             gcImage.dispose();
    633         }
    634     }
    635 
    636     private class BlankCorner extends Canvas {
    637         public BlankCorner(Composite parent) {
    638             //super(parent, SWT.NO_BACKGROUND);
    639             super(parent, SWT.NONE);
    640             addPaintListener(new PaintListener() {
    641                 public void paintControl(PaintEvent pe) {
    642                     draw(pe.display, pe.gc);
    643                 }
    644             });
    645         }
    646 
    647         private void draw(Display display, GC gc) {
    648             // Create a blank image and draw it to the canvas
    649             Image image = new Image(display, getBounds());
    650             gc.drawImage(image, 0, 0);
    651 
    652             // Clean up
    653             image.dispose();
    654         }
    655     }
    656 
    657     private class Timescale extends Canvas {
    658         private Point mMouse = new Point(LeftMargin, 0);
    659         private Cursor mZoomCursor;
    660         private String mMethodName = null;
    661         private Color mMethodColor = null;
    662         private String mDetails;
    663         private int mMethodStartY;
    664         private int mDetailsStartY;
    665         private int mMarkStartX;
    666         private int mMarkEndX;
    667 
    668         /** The space between the colored block and the method name */
    669         private static final int METHOD_BLOCK_MARGIN = 10;
    670 
    671         public Timescale(Composite parent) {
    672             //super(parent, SWT.NO_BACKGROUND);
    673             super(parent, SWT.NONE);
    674             Display display = getDisplay();
    675             mZoomCursor = new Cursor(display, SWT.CURSOR_SIZEWE);
    676             setCursor(mZoomCursor);
    677             mMethodStartY = mSmallFontHeight + 1;
    678             mDetailsStartY = mMethodStartY + mSmallFontHeight + 1;
    679             addPaintListener(new PaintListener() {
    680                 public void paintControl(PaintEvent pe) {
    681                     draw(pe.display, pe.gc);
    682                 }
    683             });
    684         }
    685 
    686         public void setVbarPosition(int x) {
    687             mMouse.x = x;
    688         }
    689 
    690         public void setMarkStart(int x) {
    691             mMarkStartX = x;
    692         }
    693 
    694         public void setMarkEnd(int x) {
    695             mMarkEndX = x;
    696         }
    697 
    698         public void setMethodName(String name) {
    699             mMethodName = name;
    700         }
    701 
    702         public void setMethodColor(Color color) {
    703             mMethodColor = color;
    704         }
    705 
    706         public void setDetails(String details) {
    707             mDetails = details;
    708         }
    709 
    710         private void mouseMove(MouseEvent me) {
    711             me.y = -1;
    712             mSurface.mouseMove(me);
    713         }
    714 
    715         private void mouseDown(MouseEvent me) {
    716             mSurface.startScaling(me.x);
    717             mSurface.redraw();
    718         }
    719 
    720         private void mouseUp(MouseEvent me) {
    721             mSurface.stopScaling(me.x);
    722         }
    723 
    724         private void mouseDoubleClick(MouseEvent me) {
    725             mSurface.resetScale();
    726             mSurface.redraw();
    727         }
    728 
    729         private void draw(Display display, GC gc) {
    730             Point dim = getSize();
    731 
    732             // Create an image for double-buffering
    733             Image image = new Image(display, getBounds());
    734 
    735             // Set up the off-screen gc
    736             GC gcImage = new GC(image);
    737             if (mSetFonts)
    738                 gcImage.setFont(mFontRegistry.get("medium"));  //$NON-NLS-1$
    739 
    740             if (mSurface.drawingSelection()) {
    741                 drawSelection(display, gcImage);
    742             }
    743 
    744             drawTicks(display, gcImage);
    745 
    746             // Draw the vertical bar where the mouse is
    747             gcImage.setForeground(mColorDarkGray);
    748             gcImage.drawLine(mMouse.x, timeLineOffsetY, mMouse.x, dim.y);
    749 
    750             // Draw the current millseconds
    751             drawTickLegend(display, gcImage);
    752 
    753             // Draw the method name and color, if needed
    754             drawMethod(display, gcImage);
    755 
    756             // Draw the details, if needed
    757             drawDetails(display, gcImage);
    758 
    759             // Draw the off-screen buffer to the screen
    760             gc.drawImage(image, 0, 0);
    761 
    762             // Clean up
    763             image.dispose();
    764             gcImage.dispose();
    765         }
    766 
    767         private void drawSelection(Display display, GC gc) {
    768             Point dim = getSize();
    769             gc.setForeground(mColorGray);
    770             gc.drawLine(mMarkStartX, timeLineOffsetY, mMarkStartX, dim.y);
    771             gc.setBackground(mColorZoomSelection);
    772             int x, width;
    773             if (mMarkStartX < mMarkEndX) {
    774                 x = mMarkStartX;
    775                 width = mMarkEndX - mMarkStartX;
    776             } else {
    777                 x = mMarkEndX;
    778                 width = mMarkStartX - mMarkEndX;
    779             }
    780             if (width > 1) {
    781                 gc.fillRectangle(x, timeLineOffsetY, width, dim.y);
    782             }
    783         }
    784 
    785         private void drawTickLegend(Display display, GC gc) {
    786             int mouseX = mMouse.x - LeftMargin;
    787             double mouseXval = mScaleInfo.pixelToValue(mouseX);
    788             String info = mUnits.labelledString(mouseXval);
    789             gc.setForeground(mColorForeground);
    790             gc.drawString(info, LeftMargin + 2, 1, true);
    791 
    792             // Display the maximum data value
    793             double maxVal = mScaleInfo.getMaxVal();
    794             info = mUnits.labelledString(maxVal);
    795             if (mClockSource != null) {
    796                 info = String.format(" max %s (%s)", info, mClockSource);  //$NON-NLS-1$
    797             } else {
    798                 info = String.format(" max %s ", info);  //$NON-NLS-1$
    799             }
    800             Point extent = gc.stringExtent(info);
    801             Point dim = getSize();
    802             int x1 = dim.x - RightMargin - extent.x;
    803             gc.drawString(info, x1, 1, true);
    804         }
    805 
    806         private void drawMethod(Display display, GC gc) {
    807             if (mMethodName == null) {
    808                 return;
    809             }
    810 
    811             int x1 = LeftMargin;
    812             int y1 = mMethodStartY;
    813             gc.setBackground(mMethodColor);
    814             int width = 2 * mSmallFontWidth;
    815             gc.fillRectangle(x1, y1, width, mSmallFontHeight);
    816             x1 += width + METHOD_BLOCK_MARGIN;
    817             gc.drawString(mMethodName, x1, y1, true);
    818         }
    819 
    820         private void drawDetails(Display display, GC gc) {
    821             if (mDetails == null) {
    822                 return;
    823             }
    824 
    825             int x1 = LeftMargin + 2 * mSmallFontWidth + METHOD_BLOCK_MARGIN;
    826             int y1 = mDetailsStartY;
    827             gc.drawString(mDetails, x1, y1, true);
    828         }
    829 
    830         private void drawTicks(Display display, GC gc) {
    831             Point dim = getSize();
    832             int y2 = majorTickLength + timeLineOffsetY;
    833             int y3 = minorTickLength + timeLineOffsetY;
    834             int y4 = y2 + tickToFontSpacing;
    835             gc.setForeground(mColorForeground);
    836             gc.drawLine(LeftMargin, timeLineOffsetY, dim.x - RightMargin,
    837                     timeLineOffsetY);
    838             double minVal = mScaleInfo.getMinVal();
    839             double maxVal = mScaleInfo.getMaxVal();
    840             double minMajorTick = mScaleInfo.getMinMajorTick();
    841             double tickIncrement = mScaleInfo.getTickIncrement();
    842             double minorTickIncrement = tickIncrement / 5;
    843             double pixelsPerRange = mScaleInfo.getPixelsPerRange();
    844 
    845             // Draw the initial minor ticks, if any
    846             if (minVal < minMajorTick) {
    847                 gc.setForeground(mColorGray);
    848                 double xMinor = minMajorTick;
    849                 for (int ii = 1; ii <= 4; ++ii) {
    850                     xMinor -= minorTickIncrement;
    851                     if (xMinor < minVal)
    852                         break;
    853                     int x1 = LeftMargin
    854                             + (int) (0.5 + (xMinor - minVal) * pixelsPerRange);
    855                     gc.drawLine(x1, timeLineOffsetY, x1, y3);
    856                 }
    857             }
    858 
    859             if (tickIncrement <= 10) {
    860                 // TODO avoid rendering the loop when tickIncrement is invalid. It can be zero
    861                 // or too small.
    862                 // System.out.println(String.format("Timescale.drawTicks error: tickIncrement=%1f", tickIncrement));
    863                 return;
    864             }
    865             for (double x = minMajorTick; x <= maxVal; x += tickIncrement) {
    866                 int x1 = LeftMargin
    867                         + (int) (0.5 + (x - minVal) * pixelsPerRange);
    868 
    869                 // Draw a major tick
    870                 gc.setForeground(mColorForeground);
    871                 gc.drawLine(x1, timeLineOffsetY, x1, y2);
    872                 if (x > maxVal)
    873                     break;
    874 
    875                 // Draw the tick text
    876                 String tickString = mUnits.valueOf(x);
    877                 gc.drawString(tickString, x1, y4, true);
    878 
    879                 // Draw 4 minor ticks between major ticks
    880                 gc.setForeground(mColorGray);
    881                 double xMinor = x;
    882                 for (int ii = 1; ii <= 4; ii++) {
    883                     xMinor += minorTickIncrement;
    884                     if (xMinor > maxVal)
    885                         break;
    886                     x1 = LeftMargin
    887                             + (int) (0.5 + (xMinor - minVal) * pixelsPerRange);
    888                     gc.drawLine(x1, timeLineOffsetY, x1, y3);
    889                 }
    890             }
    891         }
    892     }
    893 
    894     private static enum GraphicsState {
    895         Normal, Marking, Scaling, Animating, Scrolling
    896     };
    897 
    898     private class Surface extends Canvas {
    899 
    900         public Surface(Composite parent) {
    901             super(parent, SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL);
    902             Display display = getDisplay();
    903             mNormalCursor = new Cursor(display, SWT.CURSOR_CROSS);
    904             mIncreasingCursor = new Cursor(display, SWT.CURSOR_SIZEE);
    905             mDecreasingCursor = new Cursor(display, SWT.CURSOR_SIZEW);
    906 
    907             initZoomFractionsWithExp();
    908 
    909             addPaintListener(new PaintListener() {
    910                 public void paintControl(PaintEvent pe) {
    911                     draw(pe.display, pe.gc);
    912                 }
    913             });
    914 
    915             mZoomAnimator = new Runnable() {
    916                 public void run() {
    917                     animateZoom();
    918                 }
    919             };
    920 
    921             mHighlightAnimator = new Runnable() {
    922                 public void run() {
    923                     animateHighlight();
    924                 }
    925             };
    926         }
    927 
    928         private void initZoomFractionsWithExp() {
    929             mZoomFractions = new double[ZOOM_STEPS];
    930             int next = 0;
    931             for (int ii = 0; ii < ZOOM_STEPS / 2; ++ii, ++next) {
    932                 mZoomFractions[next] = (double) (1 << ii)
    933                         / (double) (1 << (ZOOM_STEPS / 2));
    934                 // System.out.printf("%d %f\n", next, zoomFractions[next]);
    935             }
    936             for (int ii = 2; ii < 2 + ZOOM_STEPS / 2; ++ii, ++next) {
    937                 mZoomFractions[next] = (double) ((1 << ii) - 1)
    938                         / (double) (1 << ii);
    939                 // System.out.printf("%d %f\n", next, zoomFractions[next]);
    940             }
    941         }
    942 
    943         @SuppressWarnings("unused")
    944         private void initZoomFractionsWithSinWave() {
    945             mZoomFractions = new double[ZOOM_STEPS];
    946             for (int ii = 0; ii < ZOOM_STEPS; ++ii) {
    947                 double offset = Math.PI * (double) ii / (double) ZOOM_STEPS;
    948                 mZoomFractions[ii] = (Math.sin((1.5 * Math.PI + offset)) + 1.0) / 2.0;
    949                 // System.out.printf("%d %f\n", ii, zoomFractions[ii]);
    950             }
    951         }
    952 
    953         public void setRange(double minVal, double maxVal) {
    954             mMinDataVal = minVal;
    955             mMaxDataVal = maxVal;
    956             mScaleInfo.setMinVal(minVal);
    957             mScaleInfo.setMaxVal(maxVal);
    958         }
    959 
    960         public void setLimitRange(double minVal, double maxVal) {
    961             mLimitMinVal = minVal;
    962             mLimitMaxVal = maxVal;
    963         }
    964 
    965         public void resetScale() {
    966             mScaleInfo.setMinVal(mLimitMinVal);
    967             mScaleInfo.setMaxVal(mLimitMaxVal);
    968         }
    969 
    970         public void setScaleFromHorizontalScrollBar(int selection) {
    971             double minVal = mScaleInfo.getMinVal();
    972             double maxVal = mScaleInfo.getMaxVal();
    973             double visibleRange = maxVal - minVal;
    974 
    975             minVal = mLimitMinVal + selection;
    976             maxVal = minVal + visibleRange;
    977             if (maxVal > mLimitMaxVal) {
    978                 maxVal = mLimitMaxVal;
    979                 minVal = maxVal - visibleRange;
    980             }
    981             mScaleInfo.setMinVal(minVal);
    982             mScaleInfo.setMaxVal(maxVal);
    983 
    984             mGraphicsState = GraphicsState.Scrolling;
    985         }
    986 
    987         private void updateHorizontalScrollBar() {
    988             double minVal = mScaleInfo.getMinVal();
    989             double maxVal = mScaleInfo.getMaxVal();
    990             double visibleRange = maxVal - minVal;
    991             double fullRange = mLimitMaxVal - mLimitMinVal;
    992 
    993             ScrollBar hBar = getHorizontalBar();
    994             if (fullRange > visibleRange) {
    995                 hBar.setVisible(true);
    996                 hBar.setMinimum(0);
    997                 hBar.setMaximum((int)Math.ceil(fullRange));
    998                 hBar.setThumb((int)Math.ceil(visibleRange));
    999                 hBar.setSelection((int)Math.floor(minVal - mLimitMinVal));
   1000             } else {
   1001                 hBar.setVisible(false);
   1002             }
   1003         }
   1004 
   1005         private void draw(Display display, GC gc) {
   1006             if (mSegments.length == 0) {
   1007                 // gc.setBackground(colorBackground);
   1008                 // gc.fillRectangle(getBounds());
   1009                 return;
   1010             }
   1011 
   1012             // Create an image for double-buffering
   1013             Image image = new Image(display, getBounds());
   1014 
   1015             // Set up the off-screen gc
   1016             GC gcImage = new GC(image);
   1017             if (mSetFonts)
   1018                 gcImage.setFont(mFontRegistry.get("small"));  //$NON-NLS-1$
   1019 
   1020             // Draw the background
   1021             // gcImage.setBackground(colorBackground);
   1022             // gcImage.fillRectangle(image.getBounds());
   1023 
   1024             if (mGraphicsState == GraphicsState.Scaling) {
   1025                 double diff = mMouse.x - mMouseMarkStartX;
   1026                 if (diff > 0) {
   1027                     double newMinVal = mScaleMinVal - diff / mScalePixelsPerRange;
   1028                     if (newMinVal < mLimitMinVal)
   1029                         newMinVal = mLimitMinVal;
   1030                     mScaleInfo.setMinVal(newMinVal);
   1031                     // System.out.printf("diff %f scaleMin %f newMin %f\n",
   1032                     // diff, scaleMinVal, newMinVal);
   1033                 } else if (diff < 0) {
   1034                     double newMaxVal = mScaleMaxVal - diff / mScalePixelsPerRange;
   1035                     if (newMaxVal > mLimitMaxVal)
   1036                         newMaxVal = mLimitMaxVal;
   1037                     mScaleInfo.setMaxVal(newMaxVal);
   1038                     // System.out.printf("diff %f scaleMax %f newMax %f\n",
   1039                     // diff, scaleMaxVal, newMaxVal);
   1040                 }
   1041             }
   1042 
   1043             // Recompute the ticks and strips only if the size has changed,
   1044             // or we scrolled so that a new row is visible.
   1045             Point dim = getSize();
   1046             if (mStartRow != mCachedStartRow || mEndRow != mCachedEndRow
   1047                     || mScaleInfo.getMinVal() != mCachedMinVal
   1048                     || mScaleInfo.getMaxVal() != mCachedMaxVal) {
   1049                 mCachedStartRow = mStartRow;
   1050                 mCachedEndRow = mEndRow;
   1051                 int xdim = dim.x - TotalXMargin;
   1052                 mScaleInfo.setNumPixels(xdim);
   1053                 boolean forceEndPoints = (mGraphicsState == GraphicsState.Scaling
   1054                         || mGraphicsState == GraphicsState.Animating
   1055                         || mGraphicsState == GraphicsState.Scrolling);
   1056                 mScaleInfo.computeTicks(forceEndPoints);
   1057                 mCachedMinVal = mScaleInfo.getMinVal();
   1058                 mCachedMaxVal = mScaleInfo.getMaxVal();
   1059                 if (mLimitMinVal > mScaleInfo.getMinVal())
   1060                     mLimitMinVal = mScaleInfo.getMinVal();
   1061                 if (mLimitMaxVal < mScaleInfo.getMaxVal())
   1062                     mLimitMaxVal = mScaleInfo.getMaxVal();
   1063 
   1064                 // Compute the strips
   1065                 computeStrips();
   1066 
   1067                 // Update the horizontal scrollbar.
   1068                 updateHorizontalScrollBar();
   1069             }
   1070 
   1071             if (mNumRows > 2) {
   1072                 // Draw the row background stripes
   1073                 gcImage.setBackground(mColorRowBack);
   1074                 for (int ii = 1; ii < mNumRows; ii += 2) {
   1075                     RowData rd = mRows[ii];
   1076                     int y1 = rd.mRank * rowYSpace - mScrollOffsetY;
   1077                     gcImage.fillRectangle(0, y1, dim.x, rowYSpace);
   1078                 }
   1079             }
   1080 
   1081             if (drawingSelection()) {
   1082                 drawSelection(display, gcImage);
   1083             }
   1084 
   1085             String blockName = null;
   1086             Color blockColor = null;
   1087             String blockDetails = null;
   1088 
   1089             if (mDebug) {
   1090                 double pixelsPerRange = mScaleInfo.getPixelsPerRange();
   1091                 System.out
   1092                         .printf(
   1093                                 "dim.x %d pixels %d minVal %f, maxVal %f ppr %f rpp %f\n",
   1094                                 dim.x, dim.x - TotalXMargin, mScaleInfo
   1095                                         .getMinVal(), mScaleInfo.getMaxVal(),
   1096                                 pixelsPerRange, 1.0 / pixelsPerRange);
   1097             }
   1098 
   1099             // Draw the strips
   1100             Block selectBlock = null;
   1101             for (Strip strip : mStripList) {
   1102                 if (strip.mColor == null) {
   1103                     // System.out.printf("strip.color is null\n");
   1104                     continue;
   1105                 }
   1106                 gcImage.setBackground(strip.mColor);
   1107                 gcImage.fillRectangle(strip.mX, strip.mY - mScrollOffsetY, strip.mWidth,
   1108                         strip.mHeight);
   1109                 if (mMouseRow == strip.mRowData.mRank) {
   1110                     if (mMouse.x >= strip.mX
   1111                             && mMouse.x < strip.mX + strip.mWidth) {
   1112                         Block block = strip.mSegment.mBlock;
   1113                         blockName = block.getName();
   1114                         blockColor = strip.mColor;
   1115                         if (mHaveCpuTime) {
   1116                             if (mHaveRealTime) {
   1117                                 blockDetails = String.format(
   1118                                         "excl cpu %s, incl cpu %s, "
   1119                                         + "excl real %s, incl real %s",
   1120                                         mUnits.labelledString(block.getExclusiveCpuTime()),
   1121                                         mUnits.labelledString(block.getInclusiveCpuTime()),
   1122                                         mUnits.labelledString(block.getExclusiveRealTime()),
   1123                                         mUnits.labelledString(block.getInclusiveRealTime()));
   1124                             } else {
   1125                                 blockDetails = String.format(
   1126                                         "excl cpu %s, incl cpu %s",
   1127                                         mUnits.labelledString(block.getExclusiveCpuTime()),
   1128                                         mUnits.labelledString(block.getInclusiveCpuTime()));
   1129                             }
   1130                         } else {
   1131                             blockDetails = String.format(
   1132                                     "excl real %s, incl real %s",
   1133                                     mUnits.labelledString(block.getExclusiveRealTime()),
   1134                                     mUnits.labelledString(block.getInclusiveRealTime()));
   1135                         }
   1136                     }
   1137                     if (mMouseSelect.x >= strip.mX
   1138                             && mMouseSelect.x < strip.mX + strip.mWidth) {
   1139                         selectBlock = strip.mSegment.mBlock;
   1140                     }
   1141                 }
   1142             }
   1143             mMouseSelect.x = 0;
   1144             mMouseSelect.y = 0;
   1145 
   1146             if (selectBlock != null) {
   1147                 ArrayList<Selection> selections = new ArrayList<Selection>();
   1148                 // Get the row label
   1149                 RowData rd = mRows[mMouseRow];
   1150                 selections.add(Selection.highlight("Thread", rd.mName));  //$NON-NLS-1$
   1151                 selections.add(Selection.highlight("Call", selectBlock));  //$NON-NLS-1$
   1152 
   1153                 int mouseX = mMouse.x - LeftMargin;
   1154                 double mouseXval = mScaleInfo.pixelToValue(mouseX);
   1155                 selections.add(Selection.highlight("Time", mouseXval));  //$NON-NLS-1$
   1156 
   1157                 mSelectionController.change(selections, "TimeLineView");  //$NON-NLS-1$
   1158                 mHighlightMethodData = null;
   1159                 mHighlightCall = (Call) selectBlock;
   1160                 startHighlighting();
   1161             }
   1162 
   1163             // Draw a highlight box on the row where the mouse is.
   1164             // Except don't draw the box if we are animating the
   1165             // highlighing of a call or method because the inclusive
   1166             // highlight bar passes through the highlight box and
   1167             // causes an annoying flashing artifact.
   1168             if (mMouseRow >= 0 && mMouseRow < mNumRows && mHighlightStep == 0) {
   1169                 gcImage.setForeground(mColorGray);
   1170                 int y1 = mMouseRow * rowYSpace - mScrollOffsetY;
   1171                 gcImage.drawLine(0, y1, dim.x, y1);
   1172                 gcImage.drawLine(0, y1 + rowYSpace, dim.x, y1 + rowYSpace);
   1173             }
   1174 
   1175             // Highlight a selected method, if any
   1176             drawHighlights(gcImage, dim);
   1177 
   1178             // Draw a vertical line where the mouse is.
   1179             gcImage.setForeground(mColorDarkGray);
   1180             int lineEnd = Math.min(dim.y, mNumRows * rowYSpace);
   1181             gcImage.drawLine(mMouse.x, 0, mMouse.x, lineEnd);
   1182 
   1183             if (blockName != null) {
   1184                 mTimescale.setMethodName(blockName);
   1185                 mTimescale.setMethodColor(blockColor);
   1186                 mTimescale.setDetails(blockDetails);
   1187                 mShowHighlightName = false;
   1188             } else if (mShowHighlightName) {
   1189                 // Draw the highlighted method name
   1190                 MethodData md = mHighlightMethodData;
   1191                 if (md == null && mHighlightCall != null)
   1192                     md = mHighlightCall.getMethodData();
   1193                 if (md == null)
   1194                     System.out.printf("null highlight?\n");  //$NON-NLS-1$
   1195                 if (md != null) {
   1196                     mTimescale.setMethodName(md.getProfileName());
   1197                     mTimescale.setMethodColor(md.getColor());
   1198                     mTimescale.setDetails(null);
   1199                 }
   1200             } else {
   1201                 mTimescale.setMethodName(null);
   1202                 mTimescale.setMethodColor(null);
   1203                 mTimescale.setDetails(null);
   1204             }
   1205             mTimescale.redraw();
   1206 
   1207             // Draw the off-screen buffer to the screen
   1208             gc.drawImage(image, 0, 0);
   1209 
   1210             // Clean up
   1211             image.dispose();
   1212             gcImage.dispose();
   1213         }
   1214 
   1215         private void drawHighlights(GC gc, Point dim) {
   1216             int height = mHighlightHeight;
   1217             if (height <= 0)
   1218                 return;
   1219             for (Range range : mHighlightExclusive) {
   1220                 gc.setBackground(range.mColor);
   1221                 int xStart = range.mXdim.x;
   1222                 int width = range.mXdim.y;
   1223                 gc.fillRectangle(xStart, range.mY - height - mScrollOffsetY, width, height);
   1224             }
   1225 
   1226             // Draw the inclusive lines a bit shorter
   1227             height -= 1;
   1228             if (height <= 0)
   1229                 height = 1;
   1230 
   1231             // Highlight the inclusive ranges
   1232             gc.setForeground(mColorDarkGray);
   1233             gc.setBackground(mColorDarkGray);
   1234             for (Range range : mHighlightInclusive) {
   1235                 int x1 = range.mXdim.x;
   1236                 int x2 = range.mXdim.y;
   1237                 boolean drawLeftEnd = false;
   1238                 boolean drawRightEnd = false;
   1239                 if (x1 >= LeftMargin)
   1240                     drawLeftEnd = true;
   1241                 else
   1242                     x1 = LeftMargin;
   1243                 if (x2 >= LeftMargin)
   1244                     drawRightEnd = true;
   1245                 else
   1246                     x2 = dim.x - RightMargin;
   1247                 int y1 = range.mY + rowHeight + 2 - mScrollOffsetY;
   1248 
   1249                 // If the range is very narrow, then just draw a small
   1250                 // rectangle.
   1251                 if (x2 - x1 < MinInclusiveRange) {
   1252                     int width = x2 - x1;
   1253                     if (width < 2)
   1254                         width = 2;
   1255                     gc.fillRectangle(x1, y1, width, height);
   1256                     continue;
   1257                 }
   1258                 if (drawLeftEnd) {
   1259                     if (drawRightEnd) {
   1260                         // Draw both ends
   1261                         int[] points = { x1, y1, x1, y1 + height, x2,
   1262                                 y1 + height, x2, y1 };
   1263                         gc.drawPolyline(points);
   1264                     } else {
   1265                         // Draw the left end
   1266                         int[] points = { x1, y1, x1, y1 + height, x2,
   1267                                 y1 + height };
   1268                         gc.drawPolyline(points);
   1269                     }
   1270                 } else {
   1271                     if (drawRightEnd) {
   1272                         // Draw the right end
   1273                         int[] points = { x1, y1 + height, x2, y1 + height, x2,
   1274                                 y1 };
   1275                         gc.drawPolyline(points);
   1276                     } else {
   1277                         // Draw neither end, just the line
   1278                         int[] points = { x1, y1 + height, x2, y1 + height };
   1279                         gc.drawPolyline(points);
   1280                     }
   1281                 }
   1282 
   1283                 // Draw the arrowheads, if necessary
   1284                 if (drawLeftEnd == false) {
   1285                     int[] points = { x1 + 7, y1 + height - 4, x1, y1 + height,
   1286                             x1 + 7, y1 + height + 4 };
   1287                     gc.fillPolygon(points);
   1288                 }
   1289                 if (drawRightEnd == false) {
   1290                     int[] points = { x2 - 7, y1 + height - 4, x2, y1 + height,
   1291                             x2 - 7, y1 + height + 4 };
   1292                     gc.fillPolygon(points);
   1293                 }
   1294             }
   1295         }
   1296 
   1297         private boolean drawingSelection() {
   1298             return mGraphicsState == GraphicsState.Marking
   1299                     || mGraphicsState == GraphicsState.Animating;
   1300         }
   1301 
   1302         private void drawSelection(Display display, GC gc) {
   1303             Point dim = getSize();
   1304             gc.setForeground(mColorGray);
   1305             gc.drawLine(mMouseMarkStartX, 0, mMouseMarkStartX, dim.y);
   1306             gc.setBackground(mColorZoomSelection);
   1307             int width;
   1308             int mouseX = (mGraphicsState == GraphicsState.Animating) ? mMouseMarkEndX : mMouse.x;
   1309             int x;
   1310             if (mMouseMarkStartX < mouseX) {
   1311                 x = mMouseMarkStartX;
   1312                 width = mouseX - mMouseMarkStartX;
   1313             } else {
   1314                 x = mouseX;
   1315                 width = mMouseMarkStartX - mouseX;
   1316             }
   1317             gc.fillRectangle(x, 0, width, dim.y);
   1318         }
   1319 
   1320         private void computeStrips() {
   1321             double minVal = mScaleInfo.getMinVal();
   1322             double maxVal = mScaleInfo.getMaxVal();
   1323 
   1324             // Allocate space for the pixel data
   1325             Pixel[] pixels = new Pixel[mNumRows];
   1326             for (int ii = 0; ii < mNumRows; ++ii)
   1327                 pixels[ii] = new Pixel();
   1328 
   1329             // Clear the per-block pixel data
   1330             for (int ii = 0; ii < mSegments.length; ++ii) {
   1331                 mSegments[ii].mBlock.clearWeight();
   1332             }
   1333 
   1334             mStripList.clear();
   1335             mHighlightExclusive.clear();
   1336             mHighlightInclusive.clear();
   1337             MethodData callMethod = null;
   1338             long callStart = 0;
   1339             long callEnd = -1;
   1340             RowData callRowData = null;
   1341             int prevMethodStart = -1;
   1342             int prevMethodEnd = -1;
   1343             int prevCallStart = -1;
   1344             int prevCallEnd = -1;
   1345             if (mHighlightCall != null) {
   1346                 int callPixelStart = -1;
   1347                 int callPixelEnd = -1;
   1348                 callStart = mHighlightCall.getStartTime();
   1349                 callEnd = mHighlightCall.getEndTime();
   1350                 callMethod = mHighlightCall.getMethodData();
   1351                 if (callStart >= minVal)
   1352                     callPixelStart = mScaleInfo.valueToPixel(callStart);
   1353                 if (callEnd <= maxVal)
   1354                     callPixelEnd = mScaleInfo.valueToPixel(callEnd);
   1355                 // System.out.printf("callStart,End %d,%d minVal,maxVal %f,%f
   1356                 // callPixelStart,End %d,%d\n",
   1357                 // callStart, callEnd, minVal, maxVal, callPixelStart,
   1358                 // callPixelEnd);
   1359                 int threadId = mHighlightCall.getThreadId();
   1360                 String threadName = mThreadLabels.get(threadId);
   1361                 callRowData = mRowByName.get(threadName);
   1362                 int y1 = callRowData.mRank * rowYSpace + rowYMarginHalf;
   1363                 Color color = callMethod.getColor();
   1364                 mHighlightInclusive.add(new Range(callPixelStart + LeftMargin,
   1365                         callPixelEnd + LeftMargin, y1, color));
   1366             }
   1367             for (Segment segment : mSegments) {
   1368                 if (segment.mEndTime <= minVal)
   1369                     continue;
   1370                 if (segment.mStartTime >= maxVal)
   1371                     continue;
   1372 
   1373                 Block block = segment.mBlock;
   1374 
   1375                 // Skip over blocks that were not assigned a color, including the
   1376                 // top level block and others that have zero inclusive time.
   1377                 Color color = block.getColor();
   1378                 if (color == null)
   1379                     continue;
   1380 
   1381                 double recordStart = Math.max(segment.mStartTime, minVal);
   1382                 double recordEnd = Math.min(segment.mEndTime, maxVal);
   1383                 if (recordStart == recordEnd)
   1384                     continue;
   1385                 int pixelStart = mScaleInfo.valueToPixel(recordStart);
   1386                 int pixelEnd = mScaleInfo.valueToPixel(recordEnd);
   1387                 int width = pixelEnd - pixelStart;
   1388                 boolean isContextSwitch = segment.mIsContextSwitch;
   1389 
   1390                 RowData rd = segment.mRowData;
   1391                 MethodData md = block.getMethodData();
   1392 
   1393                 // We will add the scroll offset later when we draw the strips
   1394                 int y1 = rd.mRank * rowYSpace + rowYMarginHalf;
   1395 
   1396                 // If we can't display any more rows, then quit
   1397                 if (rd.mRank > mEndRow)
   1398                     break;
   1399 
   1400                 // System.out.printf("segment %s val: [%.1f, %.1f] frac [%f, %f]
   1401                 // pixel: [%d, %d] pix.start %d weight %.2f %s\n",
   1402                 // block.getName(), recordStart, recordEnd,
   1403                 // scaleInfo.valueToPixelFraction(recordStart),
   1404                 // scaleInfo.valueToPixelFraction(recordEnd),
   1405                 // pixelStart, pixelEnd, pixels[rd.rank].start,
   1406                 // pixels[rd.rank].maxWeight,
   1407                 // pixels[rd.rank].segment != null
   1408                 // ? pixels[rd.rank].segment.block.getName()
   1409                 // : "null");
   1410 
   1411                 if (mHighlightMethodData != null) {
   1412                     if (mHighlightMethodData == md) {
   1413                         if (prevMethodStart != pixelStart || prevMethodEnd != pixelEnd) {
   1414                             prevMethodStart = pixelStart;
   1415                             prevMethodEnd = pixelEnd;
   1416                             int rangeWidth = width;
   1417                             if (rangeWidth == 0)
   1418                                 rangeWidth = 1;
   1419                             mHighlightExclusive.add(new Range(pixelStart
   1420                                     + LeftMargin, rangeWidth, y1, color));
   1421                             callStart = block.getStartTime();
   1422                             int callPixelStart = -1;
   1423                             if (callStart >= minVal)
   1424                                 callPixelStart = mScaleInfo.valueToPixel(callStart);
   1425                             int callPixelEnd = -1;
   1426                             callEnd = block.getEndTime();
   1427                             if (callEnd <= maxVal)
   1428                                 callPixelEnd = mScaleInfo.valueToPixel(callEnd);
   1429                             if (prevCallStart != callPixelStart || prevCallEnd != callPixelEnd) {
   1430                                 prevCallStart = callPixelStart;
   1431                                 prevCallEnd = callPixelEnd;
   1432                                 mHighlightInclusive.add(new Range(
   1433                                         callPixelStart + LeftMargin,
   1434                                         callPixelEnd + LeftMargin, y1, color));
   1435                             }
   1436                         }
   1437                     } else if (mFadeColors) {
   1438                         color = md.getFadedColor();
   1439                     }
   1440                 } else if (mHighlightCall != null) {
   1441                     if (segment.mStartTime >= callStart
   1442                             && segment.mEndTime <= callEnd && callMethod == md
   1443                             && callRowData == rd) {
   1444                         if (prevMethodStart != pixelStart || prevMethodEnd != pixelEnd) {
   1445                             prevMethodStart = pixelStart;
   1446                             prevMethodEnd = pixelEnd;
   1447                             int rangeWidth = width;
   1448                             if (rangeWidth == 0)
   1449                                 rangeWidth = 1;
   1450                             mHighlightExclusive.add(new Range(pixelStart
   1451                                     + LeftMargin, rangeWidth, y1, color));
   1452                         }
   1453                     } else if (mFadeColors) {
   1454                         color = md.getFadedColor();
   1455                     }
   1456                 }
   1457 
   1458                 // Cases:
   1459                 // 1. This segment starts on a different pixel than the
   1460                 // previous segment started on. In this case, emit
   1461                 // the pixel strip, if any, and:
   1462                 // A. If the width is 0, then add this segment's
   1463                 // weight to the Pixel.
   1464                 // B. If the width > 0, then emit a strip for this
   1465                 // segment (no partial Pixel data).
   1466                 //
   1467                 // 2. Otherwise (the new segment starts on the same
   1468                 // pixel as the previous segment): add its "weight"
   1469                 // to the current pixel, and:
   1470                 // A. If the new segment has width 1,
   1471                 // then emit the pixel strip and then
   1472                 // add the segment's weight to the pixel.
   1473                 // B. If the new segment has width > 1,
   1474                 // then emit the pixel strip, and emit the rest
   1475                 // of the strip for this segment (no partial Pixel
   1476                 // data).
   1477 
   1478                 Pixel pix = pixels[rd.mRank];
   1479                 if (pix.mStart != pixelStart) {
   1480                     if (pix.mSegment != null) {
   1481                         // Emit the pixel strip. This also clears the pixel.
   1482                         emitPixelStrip(rd, y1, pix);
   1483                     }
   1484 
   1485                     if (width == 0) {
   1486                         // Compute the "weight" of this segment for the first
   1487                         // pixel. For a pixel N, the "weight" of a segment is
   1488                         // how much of the region [N - 0.5, N + 0.5] is covered
   1489                         // by the segment.
   1490                         double weight = computeWeight(recordStart, recordEnd,
   1491                                 isContextSwitch, pixelStart);
   1492                         weight = block.addWeight(pixelStart, rd.mRank, weight);
   1493                         if (weight > pix.mMaxWeight) {
   1494                             pix.setFields(pixelStart, weight, segment, color,
   1495                                     rd);
   1496                         }
   1497                     } else {
   1498                         int x1 = pixelStart + LeftMargin;
   1499                         Strip strip = new Strip(
   1500                                 x1, isContextSwitch ? y1 + rowHeight - 1 : y1,
   1501                                 width, isContextSwitch ? 1 : rowHeight,
   1502                                 rd, segment, color);
   1503                         mStripList.add(strip);
   1504                     }
   1505                 } else {
   1506                     double weight = computeWeight(recordStart, recordEnd,
   1507                             isContextSwitch, pixelStart);
   1508                     weight = block.addWeight(pixelStart, rd.mRank, weight);
   1509                     if (weight > pix.mMaxWeight) {
   1510                         pix.setFields(pixelStart, weight, segment, color, rd);
   1511                     }
   1512                     if (width == 1) {
   1513                         // Emit the pixel strip. This also clears the pixel.
   1514                         emitPixelStrip(rd, y1, pix);
   1515 
   1516                         // Compute the weight for the next pixel
   1517                         pixelStart += 1;
   1518                         weight = computeWeight(recordStart, recordEnd,
   1519                                 isContextSwitch, pixelStart);
   1520                         weight = block.addWeight(pixelStart, rd.mRank, weight);
   1521                         pix.setFields(pixelStart, weight, segment, color, rd);
   1522                     } else if (width > 1) {
   1523                         // Emit the pixel strip. This also clears the pixel.
   1524                         emitPixelStrip(rd, y1, pix);
   1525 
   1526                         // Emit a strip for the rest of the segment.
   1527                         pixelStart += 1;
   1528                         width -= 1;
   1529                         int x1 = pixelStart + LeftMargin;
   1530                         Strip strip = new Strip(
   1531                                 x1, isContextSwitch ? y1 + rowHeight - 1 : y1,
   1532                                 width, isContextSwitch ? 1 : rowHeight,
   1533                                 rd,segment, color);
   1534                         mStripList.add(strip);
   1535                     }
   1536                 }
   1537             }
   1538 
   1539             // Emit the last pixels of each row, if any
   1540             for (int ii = 0; ii < mNumRows; ++ii) {
   1541                 Pixel pix = pixels[ii];
   1542                 if (pix.mSegment != null) {
   1543                     RowData rd = pix.mRowData;
   1544                     int y1 = rd.mRank * rowYSpace + rowYMarginHalf;
   1545                     // Emit the pixel strip. This also clears the pixel.
   1546                     emitPixelStrip(rd, y1, pix);
   1547                 }
   1548             }
   1549 
   1550             if (false) {
   1551                 System.out.printf("computeStrips()\n");
   1552                 for (Strip strip : mStripList) {
   1553                     System.out.printf("%3d, %3d width %3d height %d %s\n",
   1554                             strip.mX, strip.mY, strip.mWidth, strip.mHeight,
   1555                             strip.mSegment.mBlock.getName());
   1556                 }
   1557             }
   1558         }
   1559 
   1560         private double computeWeight(double start, double end,
   1561                 boolean isContextSwitch, int pixel) {
   1562             if (isContextSwitch) {
   1563                 return 0;
   1564             }
   1565             double pixelStartFraction = mScaleInfo.valueToPixelFraction(start);
   1566             double pixelEndFraction = mScaleInfo.valueToPixelFraction(end);
   1567             double leftEndPoint = Math.max(pixelStartFraction, pixel - 0.5);
   1568             double rightEndPoint = Math.min(pixelEndFraction, pixel + 0.5);
   1569             double weight = rightEndPoint - leftEndPoint;
   1570             return weight;
   1571         }
   1572 
   1573         private void emitPixelStrip(RowData rd, int y, Pixel pixel) {
   1574             Strip strip;
   1575 
   1576             if (pixel.mSegment == null)
   1577                 return;
   1578 
   1579             int x = pixel.mStart + LeftMargin;
   1580             // Compute the percentage of the row height proportional to
   1581             // the weight of this pixel. But don't let the proportion
   1582             // exceed 3/4 of the row height so that we can easily see
   1583             // if a given time range includes more than one method.
   1584             int height = (int) (pixel.mMaxWeight * rowHeight * 0.75);
   1585             if (height < mMinStripHeight)
   1586                 height = mMinStripHeight;
   1587             int remainder = rowHeight - height;
   1588             if (remainder > 0) {
   1589                 strip = new Strip(x, y, 1, remainder, rd, pixel.mSegment,
   1590                         mFadeColors ? mColorGray : mColorBlack);
   1591                 mStripList.add(strip);
   1592                 // System.out.printf("emitPixel (%d, %d) height %d black\n",
   1593                 // x, y, remainder);
   1594             }
   1595             strip = new Strip(x, y + remainder, 1, height, rd, pixel.mSegment,
   1596                     pixel.mColor);
   1597             mStripList.add(strip);
   1598             // System.out.printf("emitPixel (%d, %d) height %d %s\n",
   1599             // x, y + remainder, height, pixel.segment.block.getName());
   1600             pixel.mSegment = null;
   1601             pixel.mMaxWeight = 0.0;
   1602         }
   1603 
   1604         private void mouseMove(MouseEvent me) {
   1605             if (false) {
   1606                 if (mHighlightMethodData != null) {
   1607                     mHighlightMethodData = null;
   1608                     // Force a recomputation of the strip colors
   1609                     mCachedEndRow = -1;
   1610                 }
   1611             }
   1612             Point dim = mSurface.getSize();
   1613             int x = me.x;
   1614             if (x < LeftMargin)
   1615                 x = LeftMargin;
   1616             if (x > dim.x - RightMargin)
   1617                 x = dim.x - RightMargin;
   1618             mMouse.x = x;
   1619             mMouse.y = me.y;
   1620             mTimescale.setVbarPosition(x);
   1621             if (mGraphicsState == GraphicsState.Marking) {
   1622                 mTimescale.setMarkEnd(x);
   1623             }
   1624 
   1625             if (mGraphicsState == GraphicsState.Normal) {
   1626                 // Set the cursor to the normal state.
   1627                 mSurface.setCursor(mNormalCursor);
   1628             } else if (mGraphicsState == GraphicsState.Marking) {
   1629                 // Make the cursor point in the direction of the sweep
   1630                 if (mMouse.x >= mMouseMarkStartX)
   1631                     mSurface.setCursor(mIncreasingCursor);
   1632                 else
   1633                     mSurface.setCursor(mDecreasingCursor);
   1634             }
   1635             int rownum = (mMouse.y + mScrollOffsetY) / rowYSpace;
   1636             if (me.y < 0 || me.y >= dim.y) {
   1637                 rownum = -1;
   1638             }
   1639             if (mMouseRow != rownum) {
   1640                 mMouseRow = rownum;
   1641                 mLabels.redraw();
   1642             }
   1643             redraw();
   1644         }
   1645 
   1646         private void mouseDown(MouseEvent me) {
   1647             Point dim = mSurface.getSize();
   1648             int x = me.x;
   1649             if (x < LeftMargin)
   1650                 x = LeftMargin;
   1651             if (x > dim.x - RightMargin)
   1652                 x = dim.x - RightMargin;
   1653             mMouseMarkStartX = x;
   1654             mGraphicsState = GraphicsState.Marking;
   1655             mSurface.setCursor(mIncreasingCursor);
   1656             mTimescale.setMarkStart(mMouseMarkStartX);
   1657             mTimescale.setMarkEnd(mMouseMarkStartX);
   1658             redraw();
   1659         }
   1660 
   1661         private void mouseUp(MouseEvent me) {
   1662             mSurface.setCursor(mNormalCursor);
   1663             if (mGraphicsState != GraphicsState.Marking) {
   1664                 mGraphicsState = GraphicsState.Normal;
   1665                 return;
   1666             }
   1667             mGraphicsState = GraphicsState.Animating;
   1668             Point dim = mSurface.getSize();
   1669 
   1670             // If the user released the mouse outside the drawing area then
   1671             // cancel the zoom.
   1672             if (me.y <= 0 || me.y >= dim.y) {
   1673                 mGraphicsState = GraphicsState.Normal;
   1674                 redraw();
   1675                 return;
   1676             }
   1677 
   1678             int x = me.x;
   1679             if (x < LeftMargin)
   1680                 x = LeftMargin;
   1681             if (x > dim.x - RightMargin)
   1682                 x = dim.x - RightMargin;
   1683             mMouseMarkEndX = x;
   1684 
   1685             // If the user clicked and released the mouse at the same point
   1686             // (+/- a pixel or two) then cancel the zoom (but select the
   1687             // method).
   1688             int dist = mMouseMarkEndX - mMouseMarkStartX;
   1689             if (dist < 0)
   1690                 dist = -dist;
   1691             if (dist <= 2) {
   1692                 mGraphicsState = GraphicsState.Normal;
   1693 
   1694                 // Select the method underneath the mouse
   1695                 mMouseSelect.x = mMouseMarkStartX;
   1696                 mMouseSelect.y = me.y;
   1697                 redraw();
   1698                 return;
   1699             }
   1700 
   1701             // Make mouseEndX be the higher end point
   1702             if (mMouseMarkEndX < mMouseMarkStartX) {
   1703                 int temp = mMouseMarkEndX;
   1704                 mMouseMarkEndX = mMouseMarkStartX;
   1705                 mMouseMarkStartX = temp;
   1706             }
   1707 
   1708             // If the zoom area is the whole window (or nearly the whole
   1709             // window) then cancel the zoom.
   1710             if (mMouseMarkStartX <= LeftMargin + MinZoomPixelMargin
   1711                     && mMouseMarkEndX >= dim.x - RightMargin - MinZoomPixelMargin) {
   1712                 mGraphicsState = GraphicsState.Normal;
   1713                 redraw();
   1714                 return;
   1715             }
   1716 
   1717             // Compute some variables needed for zooming.
   1718             // It's probably easiest to explain by an example. There
   1719             // are two scales (or dimensions) involved: one for the pixels
   1720             // and one for the values (microseconds). To keep the example
   1721             // simple, suppose we have pixels in the range [0,16] and
   1722             // values in the range [100, 260], and suppose the user
   1723             // selects a zoom window from pixel 4 to pixel 8.
   1724             //
   1725             // usec: 100 140 180 260
   1726             // |-------|ZZZZZZZ|---------------|
   1727             // pixel: 0 4 8 16
   1728             //
   1729             // I've drawn the pixels starting at zero for simplicity, but
   1730             // in fact the drawable area is offset from the left margin
   1731             // by the value of "LeftMargin".
   1732             //
   1733             // The "pixels-per-range" (ppr) in this case is 0.1 (a tenth of
   1734             // a pixel per usec). What we want is to redraw the screen in
   1735             // several steps, each time increasing the zoom window until the
   1736             // zoom window fills the screen. For simplicity, assume that
   1737             // we want to zoom in four equal steps. Then the snapshots
   1738             // of the screen at each step would look something like this:
   1739             //
   1740             // usec: 100 140 180 260
   1741             // |-------|ZZZZZZZ|---------------|
   1742             // pixel: 0 4 8 16
   1743             //
   1744             // usec: ? 140 180 ?
   1745             // |-----|ZZZZZZZZZZZZZ|-----------|
   1746             // pixel: 0 3 10 16
   1747             //
   1748             // usec: ? 140 180 ?
   1749             // |---|ZZZZZZZZZZZZZZZZZZZ|-------|
   1750             // pixel: 0 2 12 16
   1751             //
   1752             // usec: ?140 180 ?
   1753             // |-|ZZZZZZZZZZZZZZZZZZZZZZZZZ|---|
   1754             // pixel: 0 1 14 16
   1755             //
   1756             // usec: 140 180
   1757             // |ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ|
   1758             // pixel: 0 16
   1759             //
   1760             // The problem is how to compute the endpoints (denoted by ?)
   1761             // for each step. This is a little tricky. We first need to
   1762             // compute the "fixed point": this is the point in the selection
   1763             // that doesn't move left or right. Then we can recompute the
   1764             // "ppr" (pixels per range) at each step and then find the
   1765             // endpoints. The computation of the end points is done
   1766             // in animateZoom(). This method computes the fixed point
   1767             // and some other variables needed in animateZoom().
   1768 
   1769             double minVal = mScaleInfo.getMinVal();
   1770             double maxVal = mScaleInfo.getMaxVal();
   1771             double ppr = mScaleInfo.getPixelsPerRange();
   1772             mZoomMin = minVal + ((mMouseMarkStartX - LeftMargin) / ppr);
   1773             mZoomMax = minVal + ((mMouseMarkEndX - LeftMargin) / ppr);
   1774 
   1775             // Clamp the min and max values to the actual data min and max
   1776             if (mZoomMin < mMinDataVal)
   1777                 mZoomMin = mMinDataVal;
   1778             if (mZoomMax > mMaxDataVal)
   1779                 mZoomMax = mMaxDataVal;
   1780 
   1781             // Snap the min and max points to the grid determined by the
   1782             // TickScaler
   1783             // before we zoom.
   1784             int xdim = dim.x - TotalXMargin;
   1785             TickScaler scaler = new TickScaler(mZoomMin, mZoomMax, xdim,
   1786                     PixelsPerTick);
   1787             scaler.computeTicks(false);
   1788             mZoomMin = scaler.getMinVal();
   1789             mZoomMax = scaler.getMaxVal();
   1790 
   1791             // Also snap the mouse points (in pixel space) to be consistent with
   1792             // zoomMin and zoomMax (in value space).
   1793             mMouseMarkStartX = (int) ((mZoomMin - minVal) * ppr + LeftMargin);
   1794             mMouseMarkEndX = (int) ((mZoomMax - minVal) * ppr + LeftMargin);
   1795             mTimescale.setMarkStart(mMouseMarkStartX);
   1796             mTimescale.setMarkEnd(mMouseMarkEndX);
   1797 
   1798             // Compute the mouse selection end point distances
   1799             mMouseEndDistance = dim.x - RightMargin - mMouseMarkEndX;
   1800             mMouseStartDistance = mMouseMarkStartX - LeftMargin;
   1801             mZoomMouseStart = mMouseMarkStartX;
   1802             mZoomMouseEnd = mMouseMarkEndX;
   1803             mZoomStep = 0;
   1804 
   1805             // Compute the fixed point in both value space and pixel space.
   1806             mMin2ZoomMin = mZoomMin - minVal;
   1807             mZoomMax2Max = maxVal - mZoomMax;
   1808             mZoomFixed = mZoomMin + (mZoomMax - mZoomMin) * mMin2ZoomMin
   1809                     / (mMin2ZoomMin + mZoomMax2Max);
   1810             mZoomFixedPixel = (mZoomFixed - minVal) * ppr + LeftMargin;
   1811             mFixedPixelStartDistance = mZoomFixedPixel - LeftMargin;
   1812             mFixedPixelEndDistance = dim.x - RightMargin - mZoomFixedPixel;
   1813 
   1814             mZoomMin2Fixed = mZoomFixed - mZoomMin;
   1815             mFixed2ZoomMax = mZoomMax - mZoomFixed;
   1816 
   1817             getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator);
   1818             redraw();
   1819             update();
   1820         }
   1821 
   1822         private void mouseScrolled(MouseEvent me) {
   1823             mGraphicsState = GraphicsState.Scrolling;
   1824             double tMin = mScaleInfo.getMinVal();
   1825             double tMax = mScaleInfo.getMaxVal();
   1826             double zoomFactor = 2;
   1827             double tMinRef = mLimitMinVal;
   1828             double tMaxRef = mLimitMaxVal;
   1829             double t; // the fixed point
   1830             double tMinNew;
   1831             double tMaxNew;
   1832             if (me.count > 0) {
   1833                 // we zoom in
   1834                 Point dim = mSurface.getSize();
   1835                 int x = me.x;
   1836                 if (x < LeftMargin)
   1837                     x = LeftMargin;
   1838                 if (x > dim.x - RightMargin)
   1839                     x = dim.x - RightMargin;
   1840                 double ppr = mScaleInfo.getPixelsPerRange();
   1841                 t = tMin + ((x - LeftMargin) / ppr);
   1842                 tMinNew = Math.max(tMinRef, t - (t - tMin) / zoomFactor);
   1843                 tMaxNew = Math.min(tMaxRef, t + (tMax - t) / zoomFactor);
   1844             } else {
   1845                 // we zoom out
   1846                 double factor = (tMax - tMin) / (tMaxRef - tMinRef);
   1847                 if (factor < 1) {
   1848                     t = (factor * tMinRef - tMin) / (factor - 1);
   1849                     tMinNew = Math.max(tMinRef, t - zoomFactor * (t - tMin));
   1850                     tMaxNew = Math.min(tMaxRef, t + zoomFactor * (tMax - t));
   1851                 } else {
   1852                     return;
   1853                 }
   1854             }
   1855             mScaleInfo.setMinVal(tMinNew);
   1856             mScaleInfo.setMaxVal(tMaxNew);
   1857             mSurface.redraw();
   1858         }
   1859 
   1860         // No defined behavior yet for double-click.
   1861         private void mouseDoubleClick(MouseEvent me) {
   1862         }
   1863 
   1864         public void startScaling(int mouseX) {
   1865             Point dim = mSurface.getSize();
   1866             int x = mouseX;
   1867             if (x < LeftMargin)
   1868                 x = LeftMargin;
   1869             if (x > dim.x - RightMargin)
   1870                 x = dim.x - RightMargin;
   1871             mMouseMarkStartX = x;
   1872             mGraphicsState = GraphicsState.Scaling;
   1873             mScalePixelsPerRange = mScaleInfo.getPixelsPerRange();
   1874             mScaleMinVal = mScaleInfo.getMinVal();
   1875             mScaleMaxVal = mScaleInfo.getMaxVal();
   1876         }
   1877 
   1878         public void stopScaling(int mouseX) {
   1879             mGraphicsState = GraphicsState.Normal;
   1880         }
   1881 
   1882         private void animateHighlight() {
   1883             mHighlightStep += 1;
   1884             if (mHighlightStep >= HIGHLIGHT_STEPS) {
   1885                 mFadeColors = false;
   1886                 mHighlightStep = 0;
   1887                 // Force a recomputation of the strip colors
   1888                 mCachedEndRow = -1;
   1889             } else {
   1890                 mFadeColors = true;
   1891                 mShowHighlightName = true;
   1892                 mHighlightHeight = highlightHeights[mHighlightStep];
   1893                 getDisplay().timerExec(HIGHLIGHT_TIMER_INTERVAL, mHighlightAnimator);
   1894             }
   1895             redraw();
   1896         }
   1897 
   1898         private void clearHighlights() {
   1899             // System.out.printf("clearHighlights()\n");
   1900             mShowHighlightName = false;
   1901             mHighlightHeight = 0;
   1902             mHighlightMethodData = null;
   1903             mHighlightCall = null;
   1904             mFadeColors = false;
   1905             mHighlightStep = 0;
   1906             // Force a recomputation of the strip colors
   1907             mCachedEndRow = -1;
   1908             redraw();
   1909         }
   1910 
   1911         private void animateZoom() {
   1912             mZoomStep += 1;
   1913             if (mZoomStep > ZOOM_STEPS) {
   1914                 mGraphicsState = GraphicsState.Normal;
   1915                 // Force a normal recomputation
   1916                 mCachedMinVal = mScaleInfo.getMinVal() + 1;
   1917             } else if (mZoomStep == ZOOM_STEPS) {
   1918                 mScaleInfo.setMinVal(mZoomMin);
   1919                 mScaleInfo.setMaxVal(mZoomMax);
   1920                 mMouseMarkStartX = LeftMargin;
   1921                 Point dim = getSize();
   1922                 mMouseMarkEndX = dim.x - RightMargin;
   1923                 mTimescale.setMarkStart(mMouseMarkStartX);
   1924                 mTimescale.setMarkEnd(mMouseMarkEndX);
   1925                 getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator);
   1926             } else {
   1927                 // Zoom in slowly at first, then speed up, then slow down.
   1928                 // The zoom fractions are precomputed to save time.
   1929                 double fraction = mZoomFractions[mZoomStep];
   1930                 mMouseMarkStartX = (int) (mZoomMouseStart - fraction * mMouseStartDistance);
   1931                 mMouseMarkEndX = (int) (mZoomMouseEnd + fraction * mMouseEndDistance);
   1932                 mTimescale.setMarkStart(mMouseMarkStartX);
   1933                 mTimescale.setMarkEnd(mMouseMarkEndX);
   1934 
   1935                 // Compute the new pixels-per-range. Avoid division by zero.
   1936                 double ppr;
   1937                 if (mZoomMin2Fixed >= mFixed2ZoomMax)
   1938                     ppr = (mZoomFixedPixel - mMouseMarkStartX) / mZoomMin2Fixed;
   1939                 else
   1940                     ppr = (mMouseMarkEndX - mZoomFixedPixel) / mFixed2ZoomMax;
   1941                 double newMin = mZoomFixed - mFixedPixelStartDistance / ppr;
   1942                 double newMax = mZoomFixed + mFixedPixelEndDistance / ppr;
   1943                 mScaleInfo.setMinVal(newMin);
   1944                 mScaleInfo.setMaxVal(newMax);
   1945 
   1946                 getDisplay().timerExec(ZOOM_TIMER_INTERVAL, mZoomAnimator);
   1947             }
   1948             redraw();
   1949         }
   1950 
   1951         private static final int TotalXMargin = LeftMargin + RightMargin;
   1952         private static final int yMargin = 1; // blank space on top
   1953         // The minimum margin on each side of the zoom window, in pixels.
   1954         private static final int MinZoomPixelMargin = 10;
   1955         private GraphicsState mGraphicsState = GraphicsState.Normal;
   1956         private Point mMouse = new Point(LeftMargin, 0);
   1957         private int mMouseMarkStartX;
   1958         private int mMouseMarkEndX;
   1959         private boolean mDebug = false;
   1960         private ArrayList<Strip> mStripList = new ArrayList<Strip>();
   1961         private ArrayList<Range> mHighlightExclusive = new ArrayList<Range>();
   1962         private ArrayList<Range> mHighlightInclusive = new ArrayList<Range>();
   1963         private int mMinStripHeight = 2;
   1964         private double mCachedMinVal;
   1965         private double mCachedMaxVal;
   1966         private int mCachedStartRow;
   1967         private int mCachedEndRow;
   1968         private double mScalePixelsPerRange;
   1969         private double mScaleMinVal;
   1970         private double mScaleMaxVal;
   1971         private double mLimitMinVal;
   1972         private double mLimitMaxVal;
   1973         private double mMinDataVal;
   1974         private double mMaxDataVal;
   1975         private Cursor mNormalCursor;
   1976         private Cursor mIncreasingCursor;
   1977         private Cursor mDecreasingCursor;
   1978         private static final int ZOOM_TIMER_INTERVAL = 10;
   1979         private static final int HIGHLIGHT_TIMER_INTERVAL = 50;
   1980         private static final int ZOOM_STEPS = 8; // must be even
   1981         private int mHighlightHeight = 4;
   1982         private final int[] highlightHeights = { 0, 2, 4, 5, 6, 5, 4, 2, 4, 5,
   1983                 6 };
   1984         private final int HIGHLIGHT_STEPS = highlightHeights.length;
   1985         private boolean mFadeColors;
   1986         private boolean mShowHighlightName;
   1987         private double[] mZoomFractions;
   1988         private int mZoomStep;
   1989         private int mZoomMouseStart;
   1990         private int mZoomMouseEnd;
   1991         private int mMouseStartDistance;
   1992         private int mMouseEndDistance;
   1993         private Point mMouseSelect = new Point(0, 0);
   1994         private double mZoomFixed;
   1995         private double mZoomFixedPixel;
   1996         private double mFixedPixelStartDistance;
   1997         private double mFixedPixelEndDistance;
   1998         private double mZoomMin2Fixed;
   1999         private double mMin2ZoomMin;
   2000         private double mFixed2ZoomMax;
   2001         private double mZoomMax2Max;
   2002         private double mZoomMin;
   2003         private double mZoomMax;
   2004         private Runnable mZoomAnimator;
   2005         private Runnable mHighlightAnimator;
   2006         private int mHighlightStep;
   2007     }
   2008 
   2009     private int computeVisibleRows(int ydim) {
   2010         // If we resize, then move the bottom row down.  Don't allow the scroll
   2011         // to waste space at the bottom.
   2012         int offsetY = mScrollOffsetY;
   2013         int spaceNeeded = mNumRows * rowYSpace;
   2014         if (offsetY + ydim > spaceNeeded) {
   2015             offsetY = spaceNeeded - ydim;
   2016             if (offsetY < 0) {
   2017                 offsetY = 0;
   2018             }
   2019         }
   2020         mStartRow = offsetY / rowYSpace;
   2021         mEndRow = (offsetY + ydim) / rowYSpace;
   2022         if (mEndRow >= mNumRows) {
   2023             mEndRow = mNumRows - 1;
   2024         }
   2025 
   2026         return offsetY;
   2027     }
   2028 
   2029     private void startHighlighting() {
   2030         // System.out.printf("startHighlighting()\n");
   2031         mSurface.mHighlightStep = 0;
   2032         mSurface.mFadeColors = true;
   2033         // Force a recomputation of the color strips
   2034         mSurface.mCachedEndRow = -1;
   2035         getDisplay().timerExec(0, mSurface.mHighlightAnimator);
   2036     }
   2037 
   2038     private static class RowData {
   2039         RowData(Row row) {
   2040             mName = row.getName();
   2041             mStack = new ArrayList<Block>();
   2042         }
   2043 
   2044         public void push(Block block) {
   2045             mStack.add(block);
   2046         }
   2047 
   2048         public Block top() {
   2049             if (mStack.size() == 0)
   2050                 return null;
   2051             return mStack.get(mStack.size() - 1);
   2052         }
   2053 
   2054         public void pop() {
   2055             if (mStack.size() == 0)
   2056                 return;
   2057             mStack.remove(mStack.size() - 1);
   2058         }
   2059 
   2060         private String mName;
   2061         private int mRank;
   2062         private long mElapsed;
   2063         private long mEndTime;
   2064         private ArrayList<Block> mStack;
   2065     }
   2066 
   2067     private static class Segment {
   2068         Segment(RowData rowData, Block block, long startTime, long endTime) {
   2069             mRowData = rowData;
   2070             if (block.isContextSwitch()) {
   2071                 mBlock = block.getParentBlock();
   2072                 mIsContextSwitch = true;
   2073             } else {
   2074                 mBlock = block;
   2075             }
   2076             mStartTime = startTime;
   2077             mEndTime = endTime;
   2078         }
   2079 
   2080         private RowData mRowData;
   2081         private Block mBlock;
   2082         private long mStartTime;
   2083         private long mEndTime;
   2084         private boolean mIsContextSwitch;
   2085     }
   2086 
   2087     private static class Strip {
   2088         Strip(int x, int y, int width, int height, RowData rowData,
   2089                 Segment segment, Color color) {
   2090             mX = x;
   2091             mY = y;
   2092             mWidth = width;
   2093             mHeight = height;
   2094             mRowData = rowData;
   2095             mSegment = segment;
   2096             mColor = color;
   2097         }
   2098 
   2099         int mX;
   2100         int mY;
   2101         int mWidth;
   2102         int mHeight;
   2103         RowData mRowData;
   2104         Segment mSegment;
   2105         Color mColor;
   2106     }
   2107 
   2108     private static class Pixel {
   2109         public void setFields(int start, double weight, Segment segment,
   2110                 Color color, RowData rowData) {
   2111             mStart = start;
   2112             mMaxWeight = weight;
   2113             mSegment = segment;
   2114             mColor = color;
   2115             mRowData = rowData;
   2116         }
   2117 
   2118         int mStart = -2; // some value that won't match another pixel
   2119         double mMaxWeight;
   2120         Segment mSegment;
   2121         Color mColor; // we need the color here because it may be faded
   2122         RowData mRowData;
   2123     }
   2124 
   2125     private static class Range {
   2126         Range(int xStart, int width, int y, Color color) {
   2127             mXdim.x = xStart;
   2128             mXdim.y = width;
   2129             mY = y;
   2130             mColor = color;
   2131         }
   2132 
   2133         Point mXdim = new Point(0, 0);
   2134         int mY;
   2135         Color mColor;
   2136     }
   2137 }
   2138