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