1 /* 2 * Copyright (C) 2011 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.ide.eclipse.gltrace.editors; 18 19 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function; 20 import com.android.ide.eclipse.gltrace.SwtUtils; 21 import com.android.ide.eclipse.gltrace.TraceFileParserTask; 22 import com.android.ide.eclipse.gltrace.editors.DurationMinimap.ICallSelectionListener; 23 import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode; 24 import com.android.ide.eclipse.gltrace.model.GLCall; 25 import com.android.ide.eclipse.gltrace.model.GLFrame; 26 import com.android.ide.eclipse.gltrace.model.GLTrace; 27 import com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage; 28 import com.android.ide.eclipse.gltrace.views.detail.DetailsPage; 29 30 import org.eclipse.core.runtime.IProgressMonitor; 31 import org.eclipse.jface.dialogs.MessageDialog; 32 import org.eclipse.jface.dialogs.ProgressMonitorDialog; 33 import org.eclipse.jface.viewers.CellLabelProvider; 34 import org.eclipse.jface.viewers.ColumnLabelProvider; 35 import org.eclipse.jface.viewers.ISelection; 36 import org.eclipse.jface.viewers.ISelectionChangedListener; 37 import org.eclipse.jface.viewers.ISelectionProvider; 38 import org.eclipse.jface.viewers.ITreeContentProvider; 39 import org.eclipse.jface.viewers.TreeViewer; 40 import org.eclipse.jface.viewers.TreeViewerColumn; 41 import org.eclipse.jface.viewers.Viewer; 42 import org.eclipse.jface.viewers.ViewerCell; 43 import org.eclipse.jface.viewers.ViewerFilter; 44 import org.eclipse.swt.SWT; 45 import org.eclipse.swt.events.ControlAdapter; 46 import org.eclipse.swt.events.ControlEvent; 47 import org.eclipse.swt.events.ModifyEvent; 48 import org.eclipse.swt.events.ModifyListener; 49 import org.eclipse.swt.events.SelectionAdapter; 50 import org.eclipse.swt.events.SelectionEvent; 51 import org.eclipse.swt.graphics.Color; 52 import org.eclipse.swt.layout.GridData; 53 import org.eclipse.swt.layout.GridLayout; 54 import org.eclipse.swt.widgets.Combo; 55 import org.eclipse.swt.widgets.Composite; 56 import org.eclipse.swt.widgets.Display; 57 import org.eclipse.swt.widgets.Label; 58 import org.eclipse.swt.widgets.Scale; 59 import org.eclipse.swt.widgets.ScrollBar; 60 import org.eclipse.swt.widgets.Spinner; 61 import org.eclipse.swt.widgets.Text; 62 import org.eclipse.swt.widgets.Tree; 63 import org.eclipse.swt.widgets.TreeColumn; 64 import org.eclipse.swt.widgets.TreeItem; 65 import org.eclipse.ui.IEditorInput; 66 import org.eclipse.ui.IEditorSite; 67 import org.eclipse.ui.IURIEditorInput; 68 import org.eclipse.ui.PartInitException; 69 import org.eclipse.ui.part.EditorPart; 70 71 import java.io.File; 72 import java.lang.reflect.InvocationTargetException; 73 import java.util.ArrayList; 74 import java.util.List; 75 import java.util.regex.Matcher; 76 import java.util.regex.Pattern; 77 78 /** Display OpenGL function trace in a tabular view. */ 79 public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvider { 80 public static final String ID = "com.android.ide.eclipse.gltrace.GLFunctionTrace"; //$NON-NLS-1$ 81 82 private static final String DEFAULT_FILTER_MESSAGE = "Filter list of OpenGL calls. Accepts Java regexes."; 83 84 /** Width of thumbnail images of the framebuffer. */ 85 private static final int THUMBNAIL_WIDTH = 50; 86 87 /** Height of thumbnail images of the framebuffer. */ 88 private static final int THUMBNAIL_HEIGHT = 50; 89 90 private String mFilePath; 91 private Scale mFrameSelectionScale; 92 private Spinner mFrameSelectionSpinner; 93 94 private GLTrace mTrace; 95 96 private TreeViewer mFrameTreeViewer; 97 private List<GLCallNode> mTreeViewerNodes; 98 99 private Text mFilterText; 100 private GLCallFilter mGLCallFilter; 101 102 private Color mGldrawTextColor; 103 private Color mGlCallErrorColor; 104 105 // Currently displayed frame's start and end call indices. 106 private int mCallStartIndex; 107 private int mCallEndIndex; 108 109 private DurationMinimap mDurationMinimap; 110 private ScrollBar mVerticalScrollBar; 111 112 private Combo mContextSwitchCombo; 113 private boolean mShowContextSwitcher; 114 private int mCurrentlyDisplayedContext = -1; 115 116 private FrameSummaryViewPage mFrameSummaryViewPage; 117 GLFunctionTraceViewer()118 public GLFunctionTraceViewer() { 119 mGldrawTextColor = Display.getDefault().getSystemColor(SWT.COLOR_BLUE); 120 mGlCallErrorColor = Display.getDefault().getSystemColor(SWT.COLOR_RED); 121 } 122 123 @Override doSave(IProgressMonitor monitor)124 public void doSave(IProgressMonitor monitor) { 125 } 126 127 @Override doSaveAs()128 public void doSaveAs() { 129 } 130 131 @Override init(IEditorSite site, IEditorInput input)132 public void init(IEditorSite site, IEditorInput input) throws PartInitException { 133 // we use a IURIEditorInput to allow opening files not within the workspace 134 if (!(input instanceof IURIEditorInput)) { 135 throw new PartInitException("GL Function Trace View: unsupported input type."); 136 } 137 138 setSite(site); 139 setInput(input); 140 mFilePath = ((IURIEditorInput) input).getURI().getPath(); 141 142 // set the editor part name to be the name of the file. 143 File f = new File(mFilePath); 144 setPartName(f.getName()); 145 } 146 147 @Override isDirty()148 public boolean isDirty() { 149 return false; 150 } 151 152 @Override isSaveAsAllowed()153 public boolean isSaveAsAllowed() { 154 return false; 155 } 156 157 @Override createPartControl(Composite parent)158 public void createPartControl(Composite parent) { 159 Composite c = new Composite(parent, SWT.NONE); 160 c.setLayout(new GridLayout(1, false)); 161 GridData gd = new GridData(GridData.FILL_BOTH); 162 c.setLayoutData(gd); 163 164 ProgressMonitorDialog dlg = new ProgressMonitorDialog(parent.getShell()); 165 TraceFileParserTask parser = new TraceFileParserTask(mFilePath, parent.getDisplay(), 166 THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT); 167 try { 168 dlg.run(true, true, parser); 169 } catch (InvocationTargetException e) { 170 // exception while parsing, display error to user 171 MessageDialog.openError(parent.getShell(), 172 "Error parsing OpenGL Trace File", 173 e.getCause().getMessage()); 174 return; 175 } catch (InterruptedException e) { 176 // operation canceled by user, just return 177 return; 178 } 179 180 mTrace = parser.getTrace(); 181 if (mTrace == null) { 182 return; 183 } 184 185 mShowContextSwitcher = mTrace.getContexts().size() > 1; 186 187 createFrameSelectionControls(c); 188 createFilterBar(c); 189 createFrameTraceView(c); 190 191 getSite().setSelectionProvider(mFrameTreeViewer); 192 193 Display.getDefault().asyncExec(new Runnable() { 194 @Override 195 public void run() { 196 refreshUI(); 197 } 198 }); 199 } 200 refreshUI()201 private void refreshUI() { 202 int nFrames = 0; 203 204 nFrames = mTrace.getFrames().size(); 205 setFrameCount(nFrames); 206 selectFrame(1); 207 } 208 createFrameSelectionControls(Composite parent)209 private void createFrameSelectionControls(Composite parent) { 210 Composite c = new Composite(parent, SWT.NONE); 211 c.setLayout(new GridLayout(3, false)); 212 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 213 c.setLayoutData(gd); 214 215 Label l = new Label(c, SWT.NONE); 216 l.setText("Select Frame:"); 217 218 mFrameSelectionScale = new Scale(c, SWT.HORIZONTAL); 219 mFrameSelectionScale.setMinimum(1); 220 mFrameSelectionScale.setMaximum(1); 221 mFrameSelectionScale.setSelection(0); 222 gd = new GridData(GridData.FILL_HORIZONTAL); 223 mFrameSelectionScale.setLayoutData(gd); 224 225 mFrameSelectionScale.addSelectionListener(new SelectionAdapter() { 226 @Override 227 public void widgetSelected(SelectionEvent e) { 228 int selectedFrame = mFrameSelectionScale.getSelection(); 229 mFrameSelectionSpinner.setSelection(selectedFrame); 230 selectFrame(selectedFrame); 231 } 232 }); 233 234 mFrameSelectionSpinner = new Spinner(c, SWT.BORDER); 235 gd = new GridData(); 236 // width to hold atleast 6 digits 237 gd.widthHint = SwtUtils.getFontWidth(mFrameSelectionSpinner) * 6; 238 mFrameSelectionSpinner.setLayoutData(gd); 239 240 mFrameSelectionSpinner.setMinimum(1); 241 mFrameSelectionSpinner.setMaximum(1); 242 mFrameSelectionSpinner.setSelection(0); 243 mFrameSelectionSpinner.addSelectionListener(new SelectionAdapter() { 244 @Override 245 public void widgetSelected(SelectionEvent e) { 246 // Disable spinner until all necessary action is complete. 247 // This seems to be necessary (atleast on Linux) for the spinner to not get 248 // stuck in a pressed state if it is pressed for more than a few seconds 249 // continuously. 250 mFrameSelectionSpinner.setEnabled(false); 251 252 int selectedFrame = mFrameSelectionSpinner.getSelection(); 253 mFrameSelectionScale.setSelection(selectedFrame); 254 selectFrame(selectedFrame); 255 256 // re-enable spinner 257 mFrameSelectionSpinner.setEnabled(true); 258 } 259 }); 260 } 261 setFrameCount(int nFrames)262 private void setFrameCount(int nFrames) { 263 mFrameSelectionScale.setMaximum(nFrames); 264 mFrameSelectionSpinner.setMaximum(nFrames); 265 } 266 selectFrame(int selectedFrame)267 private void selectFrame(int selectedFrame) { 268 mFrameSelectionScale.setSelection(selectedFrame); 269 mFrameSelectionSpinner.setSelection(selectedFrame); 270 271 GLFrame f = mTrace.getFrame(selectedFrame - 1); 272 mCallStartIndex = f.getStartIndex(); 273 mCallEndIndex = f.getEndIndex(); 274 275 // update tree view in the editor 276 refreshTree(mCallStartIndex, mCallEndIndex, mCurrentlyDisplayedContext); 277 278 // update minimap view 279 mDurationMinimap.setCallRangeForCurrentFrame(mCallStartIndex, mCallEndIndex); 280 281 // update the frame summary view 282 if (mFrameSummaryViewPage != null) { 283 mFrameSummaryViewPage.setSelectedFrame(selectedFrame - 1); 284 } 285 } 286 287 /** 288 * Show only calls from the given context 289 * @param context context id whose calls should be displayed. Illegal values will result in 290 * calls from all contexts being displayed. 291 */ selectContext(int context)292 private void selectContext(int context) { 293 if (mCurrentlyDisplayedContext == context) { 294 return; 295 } 296 297 mCurrentlyDisplayedContext = context; 298 refreshTree(mCallStartIndex, mCallEndIndex, mCurrentlyDisplayedContext); 299 } 300 refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay)301 private void refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay) { 302 mTreeViewerNodes = GLCallGroups.constructCallHierarchy(mTrace, 303 startCallIndex, endCallIndex, 304 contextToDisplay); 305 mFrameTreeViewer.setInput(mTreeViewerNodes); 306 mFrameTreeViewer.refresh(); 307 mFrameTreeViewer.expandAll(); 308 } 309 createFilterBar(Composite parent)310 private void createFilterBar(Composite parent) { 311 int numColumns = mShowContextSwitcher ? 3 : 2; 312 313 Composite c = new Composite(parent, SWT.NONE); 314 c.setLayout(new GridLayout(numColumns, false)); 315 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 316 c.setLayoutData(gd); 317 318 Label l = new Label(c, SWT.NONE); 319 l.setText("Filter:"); 320 321 mFilterText = new Text(c, SWT.BORDER | SWT.ICON_SEARCH | SWT.SEARCH | SWT.ICON_CANCEL); 322 mFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 323 mFilterText.setMessage(DEFAULT_FILTER_MESSAGE); 324 mFilterText.addModifyListener(new ModifyListener() { 325 @Override 326 public void modifyText(ModifyEvent e) { 327 updateAppliedFilters(); 328 } 329 }); 330 331 if (mShowContextSwitcher) { 332 mContextSwitchCombo = new Combo(c, SWT.BORDER | SWT.READ_ONLY); 333 334 // Setup the combo such that "All Contexts" is the first item, 335 // and then we have an item for each context. 336 mContextSwitchCombo.add("All Contexts"); 337 mContextSwitchCombo.select(0); 338 mCurrentlyDisplayedContext = -1; // showing all contexts 339 for (int i = 0; i < mTrace.getContexts().size(); i++) { 340 mContextSwitchCombo.add("Context " + i); 341 } 342 343 mContextSwitchCombo.addSelectionListener(new SelectionAdapter() { 344 @Override 345 public void widgetSelected(SelectionEvent e) { 346 selectContext(mContextSwitchCombo.getSelectionIndex() - 1); 347 } 348 }); 349 } else { 350 mCurrentlyDisplayedContext = 0; 351 } 352 } 353 updateAppliedFilters()354 private void updateAppliedFilters() { 355 mGLCallFilter.setFilters(mFilterText.getText().trim()); 356 mFrameTreeViewer.refresh(); 357 } 358 createFrameTraceView(Composite parent)359 private void createFrameTraceView(Composite parent) { 360 Composite c = new Composite(parent, SWT.NONE); 361 c.setLayout(new GridLayout(2, false)); 362 GridData gd = new GridData(GridData.FILL_BOTH); 363 c.setLayoutData(gd); 364 365 final Tree tree = new Tree(c, SWT.BORDER | SWT.FULL_SELECTION); 366 gd = new GridData(GridData.FILL_BOTH); 367 tree.setLayoutData(gd); 368 tree.setLinesVisible(true); 369 tree.setHeaderVisible(true); 370 371 mFrameTreeViewer = new TreeViewer(tree); 372 CellLabelProvider labelProvider = new GLFrameLabelProvider(); 373 374 // column showing the GL context id 375 TreeViewerColumn tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE); 376 tvc.setLabelProvider(labelProvider); 377 TreeColumn column = tvc.getColumn(); 378 column.setText("Function"); 379 column.setWidth(500); 380 381 // column showing the GL function duration (wall clock time) 382 tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE); 383 tvc.setLabelProvider(labelProvider); 384 column = tvc.getColumn(); 385 column.setText("Wall Time (ns)"); 386 column.setWidth(150); 387 column.setAlignment(SWT.RIGHT); 388 389 // column showing the GL function duration (thread time) 390 tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE); 391 tvc.setLabelProvider(labelProvider); 392 column = tvc.getColumn(); 393 column.setText("Thread Time (ns)"); 394 column.setWidth(150); 395 column.setAlignment(SWT.RIGHT); 396 397 mFrameTreeViewer.setContentProvider(new GLFrameContentProvider()); 398 399 mGLCallFilter = new GLCallFilter(); 400 mFrameTreeViewer.addFilter(mGLCallFilter); 401 402 // when the control is resized, give all the additional space 403 // to the function name column. 404 tree.addControlListener(new ControlAdapter() { 405 @Override 406 public void controlResized(ControlEvent e) { 407 int w = mFrameTreeViewer.getTree().getClientArea().width; 408 if (w > 200) { 409 mFrameTreeViewer.getTree().getColumn(2).setWidth(100); 410 mFrameTreeViewer.getTree().getColumn(1).setWidth(100); 411 mFrameTreeViewer.getTree().getColumn(0).setWidth(w - 200); 412 } 413 } 414 }); 415 416 mDurationMinimap = new DurationMinimap(c, mTrace); 417 gd = new GridData(GridData.FILL_VERTICAL); 418 gd.widthHint = gd.minimumWidth = mDurationMinimap.getMinimumWidth(); 419 mDurationMinimap.setLayoutData(gd); 420 mDurationMinimap.addCallSelectionListener(new ICallSelectionListener() { 421 @Override 422 public void callSelected(int selectedCallIndex) { 423 if (selectedCallIndex > 0 && selectedCallIndex < mTreeViewerNodes.size()) { 424 TreeItem item = tree.getItem(selectedCallIndex); 425 tree.select(item); 426 tree.setTopItem(item); 427 } 428 } 429 }); 430 431 mVerticalScrollBar = tree.getVerticalBar(); 432 mVerticalScrollBar.addSelectionListener(new SelectionAdapter() { 433 @Override 434 public void widgetSelected(SelectionEvent e) { 435 updateVisibleRange(); 436 } 437 }); 438 } 439 updateVisibleRange()440 private void updateVisibleRange() { 441 int visibleCallTopIndex = mCallStartIndex; 442 int visibleCallBottomIndex = mCallEndIndex; 443 444 if (mVerticalScrollBar.isEnabled()) { 445 int selection = mVerticalScrollBar.getSelection(); 446 int thumb = mVerticalScrollBar.getThumb(); 447 int max = mVerticalScrollBar.getMaximum(); 448 449 // from the scrollbar values, compute the visible fraction 450 double top = (double) selection / max; 451 double bottom = (double) (selection + thumb) / max; 452 453 // map the fraction to the call indices 454 int range = mCallEndIndex - mCallStartIndex; 455 visibleCallTopIndex = mCallStartIndex + (int) Math.floor(range * top); 456 visibleCallBottomIndex = mCallStartIndex + (int) Math.ceil(range * bottom); 457 } 458 459 mDurationMinimap.setVisibleCallRange(visibleCallTopIndex, visibleCallBottomIndex); 460 } 461 462 @Override setFocus()463 public void setFocus() { 464 mFrameTreeViewer.getTree().setFocus(); 465 } 466 467 private static class GLFrameContentProvider implements ITreeContentProvider { 468 @Override dispose()469 public void dispose() { 470 } 471 472 @Override inputChanged(Viewer viewer, Object oldInput, Object newInput)473 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 474 } 475 476 @Override getElements(Object inputElement)477 public Object[] getElements(Object inputElement) { 478 return getChildren(inputElement); 479 } 480 481 @Override getChildren(Object parentElement)482 public Object[] getChildren(Object parentElement) { 483 if (parentElement instanceof List<?>) { 484 return ((List<?>) parentElement).toArray(); 485 } 486 487 if (!(parentElement instanceof GLCallNode)) { 488 return null; 489 } 490 491 GLCallNode parent = (GLCallNode) parentElement; 492 if (parent.hasChildren()) { 493 return parent.getChildren().toArray(); 494 } else { 495 return new Object[0]; 496 } 497 } 498 499 @Override getParent(Object element)500 public Object getParent(Object element) { 501 if (!(element instanceof GLCallNode)) { 502 return null; 503 } 504 505 return ((GLCallNode) element).getParent(); 506 } 507 508 @Override hasChildren(Object element)509 public boolean hasChildren(Object element) { 510 if (!(element instanceof GLCallNode)) { 511 return false; 512 } 513 514 return ((GLCallNode) element).hasChildren(); 515 } 516 } 517 518 private class GLFrameLabelProvider extends ColumnLabelProvider { 519 @Override update(ViewerCell cell)520 public void update(ViewerCell cell) { 521 Object element = cell.getElement(); 522 if (!(element instanceof GLCallNode)) { 523 return; 524 } 525 526 GLCall c = ((GLCallNode) element).getCall(); 527 528 if (c.getFunction() == Function.glDrawArrays 529 || c.getFunction() == Function.glDrawElements) { 530 cell.setForeground(mGldrawTextColor); 531 } 532 533 if (c.hasErrors()) { 534 cell.setForeground(mGlCallErrorColor); 535 } 536 537 cell.setText(getColumnText(c, cell.getColumnIndex())); 538 } 539 getColumnText(GLCall c, int columnIndex)540 private String getColumnText(GLCall c, int columnIndex) { 541 switch (columnIndex) { 542 case 0: 543 if (c.getFunction() == Function.glPushGroupMarkerEXT) { 544 Object marker = c.getProperty(GLCall.PROPERTY_MARKERNAME); 545 if (marker instanceof String) { 546 return ((String) marker); 547 } 548 } 549 try { 550 return c.toString(); 551 } catch (Exception e) { 552 // in case of any formatting errors, just return the function name. 553 return c.getFunction().toString(); 554 } 555 case 1: 556 return formatDuration(c.getWallDuration()); 557 case 2: 558 return formatDuration(c.getThreadDuration()); 559 default: 560 return Integer.toString(c.getContextId()); 561 } 562 } 563 formatDuration(int time)564 private String formatDuration(int time) { 565 // Max duration is in the 10s of milliseconds, so xx,xxx,xxx ns 566 // So we require a format specifier that is 10 characters wide 567 return String.format("%,10d", time); //$NON-NLS-1$ 568 } 569 } 570 571 private static class GLCallFilter extends ViewerFilter { 572 private final List<Pattern> mPatterns = new ArrayList<Pattern>(); 573 setFilters(String filter)574 public void setFilters(String filter) { 575 mPatterns.clear(); 576 577 // split the user input into multiple regexes 578 // we assume that the regexes are OR'ed together i.e., all text that matches 579 // any one of the regexes will be displayed 580 for (String regex : filter.split(" ")) { 581 mPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE)); 582 } 583 } 584 585 @Override select(Viewer viewer, Object parentElement, Object element)586 public boolean select(Viewer viewer, Object parentElement, Object element) { 587 if (!(element instanceof GLCallNode)) { 588 return true; 589 } 590 591 String text = getTextUnderNode((GLCallNode) element); 592 593 if (mPatterns.size() == 0) { 594 // match if there are no regex filters 595 return true; 596 } 597 598 for (Pattern p : mPatterns) { 599 Matcher matcher = p.matcher(text); 600 if (matcher.find()) { 601 // match if atleast one of the regexes matches this text 602 return true; 603 } 604 } 605 606 return false; 607 } 608 609 /** Obtain a string representation of all functions under a given tree node. */ getTextUnderNode(GLCallNode element)610 private String getTextUnderNode(GLCallNode element) { 611 String func = element.getCall().getFunction().toString(); 612 if (!element.hasChildren()) { 613 return func; 614 } 615 616 StringBuilder sb = new StringBuilder(100); 617 sb.append(func); 618 619 for (GLCallNode child : element.getChildren()) { 620 sb.append(getTextUnderNode(child)); 621 } 622 623 return sb.toString(); 624 } 625 } 626 627 @Override addSelectionChangedListener(ISelectionChangedListener listener)628 public void addSelectionChangedListener(ISelectionChangedListener listener) { 629 if (mFrameTreeViewer != null) { 630 mFrameTreeViewer.addSelectionChangedListener(listener); 631 } 632 } 633 634 @Override getSelection()635 public ISelection getSelection() { 636 if (mFrameTreeViewer != null) { 637 return mFrameTreeViewer.getSelection(); 638 } else { 639 return null; 640 } 641 } 642 643 @Override removeSelectionChangedListener(ISelectionChangedListener listener)644 public void removeSelectionChangedListener(ISelectionChangedListener listener) { 645 if (mFrameTreeViewer != null) { 646 mFrameTreeViewer.removeSelectionChangedListener(listener); 647 } 648 } 649 650 @Override setSelection(ISelection selection)651 public void setSelection(ISelection selection) { 652 if (mFrameTreeViewer != null) { 653 mFrameTreeViewer.setSelection(selection); 654 } 655 } 656 getTrace()657 public GLTrace getTrace() { 658 return mTrace; 659 } 660 getStateViewPage()661 public StateViewPage getStateViewPage() { 662 return new StateViewPage(mTrace); 663 } 664 getFrameSummaryViewPage()665 public FrameSummaryViewPage getFrameSummaryViewPage() { 666 if (mFrameSummaryViewPage == null) { 667 mFrameSummaryViewPage = new FrameSummaryViewPage(mTrace); 668 } 669 670 return mFrameSummaryViewPage; 671 } 672 getDetailsPage()673 public DetailsPage getDetailsPage() { 674 return new DetailsPage(mTrace); 675 } 676 } 677