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