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.ddmuilib.AbstractBufferFindTarget;
20 import com.android.ddmuilib.FindDialog;
21 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function;
22 import com.android.ide.eclipse.gltrace.GlTracePlugin;
23 import com.android.ide.eclipse.gltrace.SwtUtils;
24 import com.android.ide.eclipse.gltrace.TraceFileParserTask;
25 import com.android.ide.eclipse.gltrace.editors.DurationMinimap.ICallSelectionListener;
26 import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode;
27 import com.android.ide.eclipse.gltrace.model.GLCall;
28 import com.android.ide.eclipse.gltrace.model.GLFrame;
29 import com.android.ide.eclipse.gltrace.model.GLTrace;
30 import com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage;
31 import com.android.ide.eclipse.gltrace.views.detail.DetailsPage;
32 import com.google.common.base.Charsets;
33 import com.google.common.io.Files;
34 
35 import org.eclipse.core.runtime.IProgressMonitor;
36 import org.eclipse.core.runtime.IStatus;
37 import org.eclipse.core.runtime.Status;
38 import org.eclipse.core.runtime.jobs.Job;
39 import org.eclipse.jface.action.Action;
40 import org.eclipse.jface.dialogs.ErrorDialog;
41 import org.eclipse.jface.dialogs.MessageDialog;
42 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
43 import org.eclipse.jface.resource.ImageDescriptor;
44 import org.eclipse.jface.viewers.CellLabelProvider;
45 import org.eclipse.jface.viewers.ColumnLabelProvider;
46 import org.eclipse.jface.viewers.ISelection;
47 import org.eclipse.jface.viewers.ISelectionChangedListener;
48 import org.eclipse.jface.viewers.ISelectionProvider;
49 import org.eclipse.jface.viewers.ITreeContentProvider;
50 import org.eclipse.jface.viewers.TreeViewer;
51 import org.eclipse.jface.viewers.TreeViewerColumn;
52 import org.eclipse.jface.viewers.Viewer;
53 import org.eclipse.jface.viewers.ViewerCell;
54 import org.eclipse.jface.viewers.ViewerFilter;
55 import org.eclipse.swt.SWT;
56 import org.eclipse.swt.dnd.Clipboard;
57 import org.eclipse.swt.dnd.TextTransfer;
58 import org.eclipse.swt.dnd.Transfer;
59 import org.eclipse.swt.events.ControlAdapter;
60 import org.eclipse.swt.events.ControlEvent;
61 import org.eclipse.swt.events.ModifyEvent;
62 import org.eclipse.swt.events.ModifyListener;
63 import org.eclipse.swt.events.SelectionAdapter;
64 import org.eclipse.swt.events.SelectionEvent;
65 import org.eclipse.swt.events.SelectionListener;
66 import org.eclipse.swt.graphics.Color;
67 import org.eclipse.swt.graphics.Image;
68 import org.eclipse.swt.layout.GridData;
69 import org.eclipse.swt.layout.GridLayout;
70 import org.eclipse.swt.widgets.Combo;
71 import org.eclipse.swt.widgets.Composite;
72 import org.eclipse.swt.widgets.Display;
73 import org.eclipse.swt.widgets.FileDialog;
74 import org.eclipse.swt.widgets.Label;
75 import org.eclipse.swt.widgets.Scale;
76 import org.eclipse.swt.widgets.ScrollBar;
77 import org.eclipse.swt.widgets.Shell;
78 import org.eclipse.swt.widgets.Spinner;
79 import org.eclipse.swt.widgets.Text;
80 import org.eclipse.swt.widgets.ToolBar;
81 import org.eclipse.swt.widgets.ToolItem;
82 import org.eclipse.swt.widgets.Tree;
83 import org.eclipse.swt.widgets.TreeColumn;
84 import org.eclipse.swt.widgets.TreeItem;
85 import org.eclipse.ui.IActionBars;
86 import org.eclipse.ui.IEditorInput;
87 import org.eclipse.ui.IEditorSite;
88 import org.eclipse.ui.ISharedImages;
89 import org.eclipse.ui.IURIEditorInput;
90 import org.eclipse.ui.PartInitException;
91 import org.eclipse.ui.PlatformUI;
92 import org.eclipse.ui.actions.ActionFactory;
93 import org.eclipse.ui.part.EditorPart;
94 
95 import java.io.File;
96 import java.io.IOException;
97 import java.lang.reflect.InvocationTargetException;
98 import java.util.ArrayList;
99 import java.util.List;
100 import java.util.regex.Matcher;
101 import java.util.regex.Pattern;
102 
103 /** Display OpenGL function trace in a tabular view. */
104 public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvider {
105     public static final String ID = "com.android.ide.eclipse.gltrace.GLFunctionTrace"; //$NON-NLS-1$
106 
107     private static final String DEFAULT_FILTER_MESSAGE = "Filter list of OpenGL calls. Accepts Java regexes.";
108     private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
109 
110     private static Image sExpandAllIcon;
111 
112     private static String sLastExportedToFolder;
113 
114     private String mFilePath;
115     private Scale mFrameSelectionScale;
116     private Spinner mFrameSelectionSpinner;
117 
118     private GLTrace mTrace;
119 
120     private TreeViewer mFrameTreeViewer;
121     private List<GLCallNode> mTreeViewerNodes;
122 
123     private Text mFilterText;
124     private GLCallFilter mGLCallFilter;
125 
126     private Color mGldrawTextColor;
127     private Color mGlCallErrorColor;
128 
129     /**
130      * Job to refresh the tree view & frame summary view.
131      *
132      * When the currently displayed frame is changed, either via the {@link #mFrameSelectionScale}
133      * or via {@link #mFrameSelectionSpinner}, we need to update the displayed tree of calls for
134      * that frame, and the frame summary view. Both these operations need to happen on the UI
135      * thread, but are time consuming. This works out ok if the frame selection is not changing
136      * rapidly (i.e., when the spinner or scale is moved to the target frame in a single action).
137      * However, if the spinner is constantly pressed, then the user is scrolling through a sequence
138      * of frames, and rather than refreshing the details for each of the intermediate frames,
139      * we create a job to refresh the details and schedule the job after a short interval
140      * {@link #TREE_REFRESH_INTERVAL}. This allows us to stay responsive to the spinner/scale,
141      * and not do the costly refresh for each of the intermediate frames.
142      */
143     private Job mTreeRefresherJob;
144     private final Object mTreeRefresherLock = new Object();
145     private static final int TREE_REFRESH_INTERVAL_MS = 250;
146 
147     private int mCurrentFrame;
148 
149     // Currently displayed frame's start and end call indices.
150     private int mCallStartIndex;
151     private int mCallEndIndex;
152 
153     private DurationMinimap mDurationMinimap;
154     private ScrollBar mVerticalScrollBar;
155 
156     private Combo mContextSwitchCombo;
157     private boolean mShowContextSwitcher;
158     private int mCurrentlyDisplayedContext = -1;
159 
160     private StateViewPage mStateViewPage;
161     private FrameSummaryViewPage mFrameSummaryViewPage;
162     private DetailsPage mDetailsPage;
163 
164     private ToolItem mExpandAllToolItem;
165     private ToolItem mCollapseAllToolItem;
166     private ToolItem mSaveAsToolItem;
167 
GLFunctionTraceViewer()168     public GLFunctionTraceViewer() {
169         mGldrawTextColor = Display.getDefault().getSystemColor(SWT.COLOR_BLUE);
170         mGlCallErrorColor = Display.getDefault().getSystemColor(SWT.COLOR_RED);
171     }
172 
173     @Override
doSave(IProgressMonitor monitor)174     public void doSave(IProgressMonitor monitor) {
175     }
176 
177     @Override
doSaveAs()178     public void doSaveAs() {
179     }
180 
181     @Override
init(IEditorSite site, IEditorInput input)182     public void init(IEditorSite site, IEditorInput input) throws PartInitException {
183         // we use a IURIEditorInput to allow opening files not within the workspace
184         if (!(input instanceof IURIEditorInput)) {
185             throw new PartInitException("GL Function Trace View: unsupported input type.");
186         }
187 
188         setSite(site);
189         setInput(input);
190         mFilePath = ((IURIEditorInput) input).getURI().getPath();
191 
192         // set the editor part name to be the name of the file.
193         File f = new File(mFilePath);
194         setPartName(f.getName());
195     }
196 
197     @Override
isDirty()198     public boolean isDirty() {
199         return false;
200     }
201 
202     @Override
isSaveAsAllowed()203     public boolean isSaveAsAllowed() {
204         return false;
205     }
206 
207     @Override
createPartControl(Composite parent)208     public void createPartControl(Composite parent) {
209         Composite c = new Composite(parent, SWT.NONE);
210         c.setLayout(new GridLayout(1, false));
211         GridData gd = new GridData(GridData.FILL_BOTH);
212         c.setLayoutData(gd);
213 
214         setInput(parent.getShell(), mFilePath);
215 
216         createFrameSelectionControls(c);
217         createOptionsBar(c);
218         createFrameTraceView(c);
219 
220         getSite().setSelectionProvider(mFrameTreeViewer);
221 
222         IActionBars actionBars = getEditorSite().getActionBars();
223         actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(),
224                 new Action("Copy") {
225             @Override
226             public void run() {
227                 copySelectionToClipboard();
228             }
229         });
230 
231         actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
232                 new Action("Select All") {
233             @Override
234             public void run() {
235                 selectAll();
236             }
237         });
238 
239         actionBars.setGlobalActionHandler(ActionFactory.FIND.getId(),
240                 new Action("Find") {
241             @Override
242             public void run() {
243                 showFindDialog();
244             }
245         });
246     }
247 
setInput(Shell shell, String tracePath)248     public void setInput(Shell shell, String tracePath) {
249         ProgressMonitorDialog dlg = new ProgressMonitorDialog(shell);
250         TraceFileParserTask parser = new TraceFileParserTask(mFilePath);
251         try {
252             dlg.run(true, true, parser);
253         } catch (InvocationTargetException e) {
254             // exception while parsing, display error to user
255             MessageDialog.openError(shell,
256                     "Error parsing OpenGL Trace File",
257                     e.getCause().getMessage());
258             return;
259         } catch (InterruptedException e) {
260             // operation canceled by user, just return
261             return;
262         }
263 
264         mTrace = parser.getTrace();
265         mShowContextSwitcher = (mTrace == null) ? false : mTrace.getContexts().size() > 1;
266         if (mStateViewPage != null) {
267             mStateViewPage.setInput(mTrace);
268         }
269         if (mFrameSummaryViewPage != null) {
270             mFrameSummaryViewPage.setInput(mTrace);
271         }
272         if (mDetailsPage != null) {
273             mDetailsPage.setInput(mTrace);
274         }
275         if (mDurationMinimap != null) {
276             mDurationMinimap.setInput(mTrace);
277         }
278 
279         Display.getDefault().asyncExec(new Runnable() {
280             @Override
281             public void run() {
282                 refreshUI();
283             }
284         });
285     }
286 
refreshUI()287     private void refreshUI() {
288         if (mTrace == null || mTrace.getGLCalls().size() == 0) {
289             setFrameCount(0);
290             return;
291         }
292 
293         setFrameCount(mTrace.getFrames().size());
294         selectFrame(1);
295     }
296 
createFrameSelectionControls(Composite parent)297     private void createFrameSelectionControls(Composite parent) {
298         Composite c = new Composite(parent, SWT.NONE);
299         c.setLayout(new GridLayout(3, false));
300         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
301         c.setLayoutData(gd);
302 
303         Label l = new Label(c, SWT.NONE);
304         l.setText("Select Frame:");
305 
306         mFrameSelectionScale = new Scale(c, SWT.HORIZONTAL);
307         mFrameSelectionScale.setMinimum(1);
308         mFrameSelectionScale.setMaximum(1);
309         mFrameSelectionScale.setSelection(0);
310         gd = new GridData(GridData.FILL_HORIZONTAL);
311         mFrameSelectionScale.setLayoutData(gd);
312 
313         mFrameSelectionScale.addSelectionListener(new SelectionAdapter() {
314             @Override
315             public void widgetSelected(SelectionEvent e) {
316                 int selectedFrame = mFrameSelectionScale.getSelection();
317                 mFrameSelectionSpinner.setSelection(selectedFrame);
318                 selectFrame(selectedFrame);
319             }
320         });
321 
322         mFrameSelectionSpinner = new Spinner(c, SWT.BORDER);
323         gd = new GridData();
324         // width to hold atleast 6 digits
325         gd.widthHint = SwtUtils.getApproximateFontWidth(mFrameSelectionSpinner) * 6;
326         mFrameSelectionSpinner.setLayoutData(gd);
327 
328         mFrameSelectionSpinner.setMinimum(1);
329         mFrameSelectionSpinner.setMaximum(1);
330         mFrameSelectionSpinner.setSelection(0);
331         mFrameSelectionSpinner.addSelectionListener(new SelectionAdapter() {
332             @Override
333             public void widgetSelected(SelectionEvent e) {
334                 int selectedFrame = mFrameSelectionSpinner.getSelection();
335                 mFrameSelectionScale.setSelection(selectedFrame);
336                 selectFrame(selectedFrame);
337             }
338         });
339     }
340 
setFrameCount(int nFrames)341     private void setFrameCount(int nFrames) {
342         boolean en = nFrames > 0;
343         mFrameSelectionScale.setEnabled(en);
344         mFrameSelectionSpinner.setEnabled(en);
345 
346         mFrameSelectionScale.setMaximum(nFrames);
347         mFrameSelectionSpinner.setMaximum(nFrames);
348     }
349 
selectFrame(int selectedFrame)350     private void selectFrame(int selectedFrame) {
351         mFrameSelectionScale.setSelection(selectedFrame);
352         mFrameSelectionSpinner.setSelection(selectedFrame);
353 
354         synchronized (mTreeRefresherLock) {
355             if (mTrace != null) {
356                 GLFrame f = mTrace.getFrame(selectedFrame - 1);
357                 mCallStartIndex = f.getStartIndex();
358                 mCallEndIndex = f.getEndIndex();
359             } else {
360                 mCallStartIndex = mCallEndIndex = 0;
361             }
362 
363             mCurrentFrame = selectedFrame - 1;
364 
365             scheduleNewRefreshJob();
366         }
367 
368         // update minimap view
369         mDurationMinimap.setCallRangeForCurrentFrame(mCallStartIndex, mCallEndIndex);
370     }
371 
372     /**
373      * Show only calls from the given context
374      * @param context context id whose calls should be displayed. Illegal values will result in
375      *                calls from all contexts being displayed.
376      */
selectContext(int context)377     private void selectContext(int context) {
378         if (mCurrentlyDisplayedContext == context) {
379             return;
380         }
381 
382         synchronized (mTreeRefresherLock) {
383             mCurrentlyDisplayedContext = context;
384             scheduleNewRefreshJob();
385         }
386     }
387 
scheduleNewRefreshJob()388     private void scheduleNewRefreshJob() {
389         if (mTreeRefresherJob != null) {
390             return;
391         }
392 
393         mTreeRefresherJob = new Job("Refresh GL Trace View Tree") {
394             @Override
395             protected IStatus run(IProgressMonitor monitor) {
396                 final int start, end, context;
397 
398                 synchronized (mTreeRefresherLock) {
399                     start = mCallStartIndex;
400                     end = mCallEndIndex;
401                     context = mCurrentlyDisplayedContext;
402 
403                     mTreeRefresherJob = null;
404                 }
405 
406                 // update tree view in the editor
407                 Display.getDefault().syncExec(new Runnable() {
408                     @Override
409                     public void run() {
410                         refreshTree(start, end, context);
411 
412                         // update the frame summary view
413                         if (mFrameSummaryViewPage != null) {
414                             mFrameSummaryViewPage.setSelectedFrame(mCurrentFrame);
415                         }
416                     }
417                 });
418                 return Status.OK_STATUS;
419             }
420         };
421         mTreeRefresherJob.setPriority(Job.SHORT);
422         mTreeRefresherJob.schedule(TREE_REFRESH_INTERVAL_MS);
423     }
424 
refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay)425     private void refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay) {
426         mTreeViewerNodes = GLCallGroups.constructCallHierarchy(mTrace,
427                 startCallIndex, endCallIndex,
428                 contextToDisplay);
429         mFrameTreeViewer.setInput(mTreeViewerNodes);
430         mFrameTreeViewer.refresh();
431         mFrameTreeViewer.expandAll();
432     }
433 
createOptionsBar(Composite parent)434     private void createOptionsBar(Composite parent) {
435         int numColumns = mShowContextSwitcher ? 4 : 3;
436 
437         Composite c = new Composite(parent, SWT.NONE);
438         c.setLayout(new GridLayout(numColumns, false));
439         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
440         c.setLayoutData(gd);
441 
442         Label l = new Label(c, SWT.NONE);
443         l.setText("Filter:");
444 
445         mFilterText = new Text(c, SWT.BORDER | SWT.ICON_SEARCH | SWT.SEARCH | SWT.ICON_CANCEL);
446         mFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
447         mFilterText.setMessage(DEFAULT_FILTER_MESSAGE);
448         mFilterText.addModifyListener(new ModifyListener() {
449             @Override
450             public void modifyText(ModifyEvent e) {
451                 updateAppliedFilters();
452             }
453         });
454 
455         if (mShowContextSwitcher) {
456             mContextSwitchCombo = new Combo(c, SWT.BORDER | SWT.READ_ONLY);
457 
458             // Setup the combo such that "All Contexts" is the first item,
459             // and then we have an item for each context.
460             mContextSwitchCombo.add("All Contexts");
461             mContextSwitchCombo.select(0);
462             mCurrentlyDisplayedContext = -1; // showing all contexts
463             for (int i = 0; i < mTrace.getContexts().size(); i++) {
464                 mContextSwitchCombo.add("Context " + i);
465             }
466 
467             mContextSwitchCombo.addSelectionListener(new SelectionAdapter() {
468                 @Override
469                 public void widgetSelected(SelectionEvent e) {
470                     selectContext(mContextSwitchCombo.getSelectionIndex() - 1);
471                 }
472             });
473         } else {
474             mCurrentlyDisplayedContext = 0;
475         }
476 
477         ToolBar toolBar = new ToolBar(c, SWT.FLAT | SWT.BORDER);
478 
479         mExpandAllToolItem = new ToolItem(toolBar, SWT.PUSH);
480         mExpandAllToolItem.setToolTipText("Expand All");
481         if (sExpandAllIcon == null) {
482             ImageDescriptor id = GlTracePlugin.getImageDescriptor("/icons/expandall.png");
483             sExpandAllIcon = id.createImage();
484         }
485         if (sExpandAllIcon != null) {
486             mExpandAllToolItem.setImage(sExpandAllIcon);
487         }
488 
489         mCollapseAllToolItem = new ToolItem(toolBar, SWT.PUSH);
490         mCollapseAllToolItem.setToolTipText("Collapse All");
491         mCollapseAllToolItem.setImage(
492                 PlatformUI.getWorkbench().getSharedImages().getImage(
493                         ISharedImages.IMG_ELCL_COLLAPSEALL));
494 
495         mSaveAsToolItem = new ToolItem(toolBar, SWT.PUSH);
496         mSaveAsToolItem.setToolTipText("Export Trace");
497         mSaveAsToolItem.setImage(
498                 PlatformUI.getWorkbench().getSharedImages().getImage(
499                         ISharedImages.IMG_ETOOL_SAVEAS_EDIT));
500 
501         SelectionListener toolbarSelectionListener = new SelectionAdapter() {
502             @Override
503             public void widgetSelected(SelectionEvent e) {
504                 if (e.getSource() == mCollapseAllToolItem) {
505                     setTreeItemsExpanded(false);
506                 } else if (e.getSource() == mExpandAllToolItem) {
507                     setTreeItemsExpanded(true);
508                 } else if (e.getSource() == mSaveAsToolItem) {
509                     exportTrace();
510                 }
511             }
512         };
513         mExpandAllToolItem.addSelectionListener(toolbarSelectionListener);
514         mCollapseAllToolItem.addSelectionListener(toolbarSelectionListener);
515         mSaveAsToolItem.addSelectionListener(toolbarSelectionListener);
516     }
517 
updateAppliedFilters()518     private void updateAppliedFilters() {
519         mGLCallFilter.setFilters(mFilterText.getText().trim());
520         mFrameTreeViewer.refresh();
521     }
522 
createFrameTraceView(Composite parent)523     private void createFrameTraceView(Composite parent) {
524         Composite c = new Composite(parent, SWT.NONE);
525         c.setLayout(new GridLayout(2, false));
526         GridData gd = new GridData(GridData.FILL_BOTH);
527         c.setLayoutData(gd);
528 
529         final Tree tree = new Tree(c, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
530         gd = new GridData(GridData.FILL_BOTH);
531         tree.setLayoutData(gd);
532         tree.setLinesVisible(true);
533         tree.setHeaderVisible(true);
534 
535         mFrameTreeViewer = new TreeViewer(tree);
536         CellLabelProvider labelProvider = new GLFrameLabelProvider();
537 
538         // column showing the GL context id
539         TreeViewerColumn tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
540         tvc.setLabelProvider(labelProvider);
541         TreeColumn column = tvc.getColumn();
542         column.setText("Function");
543         column.setWidth(500);
544 
545         // column showing the GL function duration (wall clock time)
546         tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
547         tvc.setLabelProvider(labelProvider);
548         column = tvc.getColumn();
549         column.setText("Wall Time (ns)");
550         column.setWidth(150);
551         column.setAlignment(SWT.RIGHT);
552 
553         // column showing the GL function duration (thread time)
554         tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
555         tvc.setLabelProvider(labelProvider);
556         column = tvc.getColumn();
557         column.setText("Thread Time (ns)");
558         column.setWidth(150);
559         column.setAlignment(SWT.RIGHT);
560 
561         mFrameTreeViewer.setContentProvider(new GLFrameContentProvider());
562 
563         mGLCallFilter = new GLCallFilter();
564         mFrameTreeViewer.addFilter(mGLCallFilter);
565 
566         // when the control is resized, give all the additional space
567         // to the function name column.
568         tree.addControlListener(new ControlAdapter() {
569             @Override
570             public void controlResized(ControlEvent e) {
571                 int w = mFrameTreeViewer.getTree().getClientArea().width;
572                 if (w > 200) {
573                     mFrameTreeViewer.getTree().getColumn(2).setWidth(100);
574                     mFrameTreeViewer.getTree().getColumn(1).setWidth(100);
575                     mFrameTreeViewer.getTree().getColumn(0).setWidth(w - 200);
576                 }
577             }
578         });
579 
580         mDurationMinimap = new DurationMinimap(c, mTrace);
581         gd = new GridData(GridData.FILL_VERTICAL);
582         gd.widthHint = gd.minimumWidth = mDurationMinimap.getMinimumWidth();
583         mDurationMinimap.setLayoutData(gd);
584         mDurationMinimap.addCallSelectionListener(new ICallSelectionListener() {
585             @Override
586             public void callSelected(int selectedCallIndex) {
587                 if (selectedCallIndex > 0 && selectedCallIndex < mTreeViewerNodes.size()) {
588                     TreeItem item = tree.getItem(selectedCallIndex);
589                     tree.select(item);
590                     tree.setTopItem(item);
591                 }
592             }
593         });
594 
595         mVerticalScrollBar = tree.getVerticalBar();
596         mVerticalScrollBar.addSelectionListener(new SelectionAdapter() {
597             @Override
598             public void widgetSelected(SelectionEvent e) {
599                 updateVisibleRange();
600             }
601         });
602     }
603 
updateVisibleRange()604     private void updateVisibleRange() {
605         int visibleCallTopIndex = mCallStartIndex;
606         int visibleCallBottomIndex = mCallEndIndex;
607 
608         if (mVerticalScrollBar.isEnabled()) {
609             int selection = mVerticalScrollBar.getSelection();
610             int thumb = mVerticalScrollBar.getThumb();
611             int max = mVerticalScrollBar.getMaximum();
612 
613             // from the scrollbar values, compute the visible fraction
614             double top = (double) selection / max;
615             double bottom = (double) (selection + thumb) / max;
616 
617             // map the fraction to the call indices
618             int range = mCallEndIndex - mCallStartIndex;
619             visibleCallTopIndex = mCallStartIndex + (int) Math.floor(range * top);
620             visibleCallBottomIndex = mCallStartIndex + (int) Math.ceil(range * bottom);
621         }
622 
623         mDurationMinimap.setVisibleCallRange(visibleCallTopIndex, visibleCallBottomIndex);
624     }
625 
626     @Override
setFocus()627     public void setFocus() {
628         mFrameTreeViewer.getTree().setFocus();
629     }
630 
631     private static class GLFrameContentProvider implements ITreeContentProvider {
632         @Override
dispose()633         public void dispose() {
634         }
635 
636         @Override
inputChanged(Viewer viewer, Object oldInput, Object newInput)637         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
638         }
639 
640         @Override
getElements(Object inputElement)641         public Object[] getElements(Object inputElement) {
642             return getChildren(inputElement);
643         }
644 
645         @Override
getChildren(Object parentElement)646         public Object[] getChildren(Object parentElement) {
647             if (parentElement instanceof List<?>) {
648                 return ((List<?>) parentElement).toArray();
649             }
650 
651             if (!(parentElement instanceof GLCallNode)) {
652                 return null;
653             }
654 
655             GLCallNode parent = (GLCallNode) parentElement;
656             if (parent.hasChildren()) {
657                 return parent.getChildren().toArray();
658             } else {
659                 return new Object[0];
660             }
661         }
662 
663         @Override
getParent(Object element)664         public Object getParent(Object element) {
665             if (!(element instanceof GLCallNode)) {
666                 return null;
667             }
668 
669             return ((GLCallNode) element).getParent();
670         }
671 
672         @Override
hasChildren(Object element)673         public boolean hasChildren(Object element) {
674             if (!(element instanceof GLCallNode)) {
675                 return false;
676             }
677 
678             return ((GLCallNode) element).hasChildren();
679         }
680     }
681 
682     private class GLFrameLabelProvider extends ColumnLabelProvider {
683         @Override
update(ViewerCell cell)684         public void update(ViewerCell cell) {
685             Object element = cell.getElement();
686             if (!(element instanceof GLCallNode)) {
687                 return;
688             }
689 
690             GLCall c = ((GLCallNode) element).getCall();
691 
692             if (c.getFunction() == Function.glDrawArrays
693                     || c.getFunction() == Function.glDrawElements) {
694                 cell.setForeground(mGldrawTextColor);
695             }
696 
697             if (c.hasErrors()) {
698                 cell.setForeground(mGlCallErrorColor);
699             }
700 
701             cell.setText(getColumnText(c, cell.getColumnIndex()));
702         }
703 
getColumnText(GLCall c, int columnIndex)704         private String getColumnText(GLCall c, int columnIndex) {
705             switch (columnIndex) {
706             case 0:
707                 if (c.getFunction() == Function.glPushGroupMarkerEXT) {
708                     Object marker = c.getProperty(GLCall.PROPERTY_MARKERNAME);
709                     if (marker instanceof String) {
710                         return ((String) marker);
711                     }
712                 }
713                 return c.toString();
714             case 1:
715                 return formatDuration(c.getWallDuration());
716             case 2:
717                 return formatDuration(c.getThreadDuration());
718             default:
719                 return Integer.toString(c.getContextId());
720             }
721         }
722 
formatDuration(int time)723         private String formatDuration(int time) {
724             // Max duration is in the 10s of milliseconds, so xx,xxx,xxx ns
725             // So we require a format specifier that is 10 characters wide
726             return String.format("%,10d", time);            //$NON-NLS-1$
727         }
728     }
729 
730     private static class GLCallFilter extends ViewerFilter {
731         private final List<Pattern> mPatterns = new ArrayList<Pattern>();
732 
setFilters(String filter)733         public void setFilters(String filter) {
734             mPatterns.clear();
735 
736             // split the user input into multiple regexes
737             // we assume that the regexes are OR'ed together i.e., all text that matches
738             // any one of the regexes will be displayed
739             for (String regex : filter.split(" ")) {
740                 mPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE));
741             }
742         }
743 
744         @Override
select(Viewer viewer, Object parentElement, Object element)745         public boolean select(Viewer viewer, Object parentElement, Object element) {
746             if (!(element instanceof GLCallNode)) {
747                 return true;
748             }
749 
750             String text = getTextUnderNode((GLCallNode) element);
751 
752             if (mPatterns.size() == 0) {
753                 // match if there are no regex filters
754                 return true;
755             }
756 
757             for (Pattern p : mPatterns) {
758                 Matcher matcher = p.matcher(text);
759                 if (matcher.find()) {
760                     // match if atleast one of the regexes matches this text
761                     return true;
762                 }
763             }
764 
765             return false;
766         }
767 
768         /** Obtain a string representation of all functions under a given tree node. */
getTextUnderNode(GLCallNode element)769         private String getTextUnderNode(GLCallNode element) {
770             String func = element.getCall().getFunction().toString();
771             if (!element.hasChildren()) {
772                 return func;
773             }
774 
775             StringBuilder sb = new StringBuilder(100);
776             sb.append(func);
777 
778             for (GLCallNode child : element.getChildren()) {
779                 sb.append(getTextUnderNode(child));
780             }
781 
782             return sb.toString();
783         }
784     }
785 
786     @Override
addSelectionChangedListener(ISelectionChangedListener listener)787     public void addSelectionChangedListener(ISelectionChangedListener listener) {
788         if (mFrameTreeViewer != null) {
789             mFrameTreeViewer.addSelectionChangedListener(listener);
790         }
791     }
792 
793     @Override
getSelection()794     public ISelection getSelection() {
795         if (mFrameTreeViewer != null) {
796             return mFrameTreeViewer.getSelection();
797         } else {
798             return null;
799         }
800     }
801 
802     @Override
removeSelectionChangedListener(ISelectionChangedListener listener)803     public void removeSelectionChangedListener(ISelectionChangedListener listener) {
804         if (mFrameTreeViewer != null) {
805             mFrameTreeViewer.removeSelectionChangedListener(listener);
806         }
807     }
808 
809     @Override
setSelection(ISelection selection)810     public void setSelection(ISelection selection) {
811         if (mFrameTreeViewer != null) {
812             mFrameTreeViewer.setSelection(selection);
813         }
814     }
815 
getTrace()816     public GLTrace getTrace() {
817         return mTrace;
818     }
819 
getStateViewPage()820     public StateViewPage getStateViewPage() {
821         if (mStateViewPage == null) {
822             mStateViewPage = new StateViewPage(mTrace);
823         }
824 
825         return mStateViewPage;
826     }
827 
getFrameSummaryViewPage()828     public FrameSummaryViewPage getFrameSummaryViewPage() {
829         if (mFrameSummaryViewPage == null) {
830             mFrameSummaryViewPage = new FrameSummaryViewPage(mTrace);
831         }
832 
833         return mFrameSummaryViewPage;
834     }
835 
getDetailsPage()836     public DetailsPage getDetailsPage() {
837         if (mDetailsPage == null) {
838             mDetailsPage = new DetailsPage(mTrace);
839         }
840 
841         return mDetailsPage;
842     }
843 
copySelectionToClipboard()844     private void copySelectionToClipboard() {
845         if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
846             return;
847         }
848 
849         StringBuilder sb = new StringBuilder();
850 
851         for (TreeItem it: mFrameTreeViewer.getTree().getSelection()) {
852             Object data = it.getData();
853             if (data instanceof GLCallNode) {
854                 sb.append(((GLCallNode) data).getCall());
855                 sb.append(NEWLINE);
856             }
857         }
858 
859         if (sb.length() > 0) {
860             Clipboard cb = new Clipboard(Display.getDefault());
861             cb.setContents(
862                     new Object[] { sb.toString() },
863                     new Transfer[] { TextTransfer.getInstance() });
864             cb.dispose();
865         }
866     }
867 
selectAll()868     private void selectAll() {
869         if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
870             return;
871         }
872 
873         mFrameTreeViewer.getTree().selectAll();
874     }
875 
exportTrace()876     private void exportTrace() {
877         if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
878             return;
879         }
880 
881         if (mCallEndIndex == 0) {
882             return;
883         }
884 
885         FileDialog fd = new FileDialog(mFrameTreeViewer.getTree().getShell(), SWT.SAVE);
886         fd.setFilterExtensions(new String[] { "*.txt" });
887         if (sLastExportedToFolder != null) {
888             fd.setFilterPath(sLastExportedToFolder);
889         }
890 
891         String path = fd.open();
892         if (path == null) {
893             return;
894         }
895 
896         File f = new File(path);
897         sLastExportedToFolder = f.getParent();
898         try {
899             exportFrameTo(f);
900         } catch (IOException e) {
901             ErrorDialog.openError(mFrameTreeViewer.getTree().getShell(),
902                     "Export trace file.",
903                     "Unexpected error exporting trace file.",
904                     new Status(Status.ERROR, GlTracePlugin.PLUGIN_ID, e.toString()));
905         }
906     }
907 
exportFrameTo(File f)908     private void exportFrameTo(File f) throws IOException {
909         String glCalls = serializeGlCalls(mTrace.getGLCalls(), mCallStartIndex, mCallEndIndex);
910         Files.write(glCalls, f, Charsets.UTF_8);
911     }
912 
serializeGlCalls(List<GLCall> glCalls, int start, int end)913     private String serializeGlCalls(List<GLCall> glCalls, int start, int end) {
914         StringBuilder sb = new StringBuilder();
915         while (start < end) {
916             sb.append(glCalls.get(start).toString());
917             sb.append("\n"); //$NON-NLS-1$
918             start++;
919         }
920 
921         return sb.toString();
922     }
923 
setTreeItemsExpanded(boolean expand)924     private void setTreeItemsExpanded(boolean expand) {
925         if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
926             return;
927         }
928 
929         if (expand) {
930             mFrameTreeViewer.expandAll();
931         } else {
932             mFrameTreeViewer.collapseAll();
933         }
934     }
935 
936     private class TraceViewerFindTarget extends AbstractBufferFindTarget {
937         @Override
getItemCount()938         public int getItemCount() {
939             return mFrameTreeViewer.getTree().getItemCount();
940         }
941 
942         @Override
getItem(int index)943         public String getItem(int index) {
944             Object data = mFrameTreeViewer.getTree().getItem(index).getData();
945             if (data instanceof GLCallNode) {
946                 return ((GLCallNode) data).getCall().toString();
947             }
948             return null;
949         }
950 
951         @Override
selectAndReveal(int index)952         public void selectAndReveal(int index) {
953             Tree t = mFrameTreeViewer.getTree();
954             t.deselectAll();
955             t.select(t.getItem(index));
956             t.showSelection();
957         }
958 
959         @Override
getStartingIndex()960         public int getStartingIndex() {
961             return 0;
962         }
963     };
964 
965     private FindDialog mFindDialog;
966     private TraceViewerFindTarget mFindTarget = new TraceViewerFindTarget();
967 
showFindDialog()968     private void showFindDialog() {
969         if (mFindDialog != null) {
970             // the dialog is already displayed
971             return;
972         }
973 
974         mFindDialog = new FindDialog(Display.getDefault().getActiveShell(),
975                 mFindTarget,
976                 FindDialog.FIND_NEXT_ID);
977         mFindDialog.open(); // blocks until find dialog is closed
978         mFindDialog = null;
979     }
980 
getInputPath()981     public String getInputPath() {
982         return mFilePath;
983     }
984 }
985