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