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