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