Home | History | Annotate | Download | only in editors
      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 
    168     public GLFunctionTraceViewer() {
    169         mGldrawTextColor = Display.getDefault().getSystemColor(SWT.COLOR_BLUE);
    170         mGlCallErrorColor = Display.getDefault().getSystemColor(SWT.COLOR_RED);
    171     }
    172 
    173     @Override
    174     public void doSave(IProgressMonitor monitor) {
    175     }
    176 
    177     @Override
    178     public void doSaveAs() {
    179     }
    180 
    181     @Override
    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
    198     public boolean isDirty() {
    199         return false;
    200     }
    201 
    202     @Override
    203     public boolean isSaveAsAllowed() {
    204         return false;
    205     }
    206 
    207     @Override
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    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 
    518     private void updateAppliedFilters() {
    519         mGLCallFilter.setFilters(mFilterText.getText().trim());
    520         mFrameTreeViewer.refresh();
    521     }
    522 
    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 
    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
    627     public void setFocus() {
    628         mFrameTreeViewer.getTree().setFocus();
    629     }
    630 
    631     private static class GLFrameContentProvider implements ITreeContentProvider {
    632         @Override
    633         public void dispose() {
    634         }
    635 
    636         @Override
    637         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    638         }
    639 
    640         @Override
    641         public Object[] getElements(Object inputElement) {
    642             return getChildren(inputElement);
    643         }
    644 
    645         @Override
    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
    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
    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
    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 
    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 
    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 
    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
    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. */
    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
    787     public void addSelectionChangedListener(ISelectionChangedListener listener) {
    788         if (mFrameTreeViewer != null) {
    789             mFrameTreeViewer.addSelectionChangedListener(listener);
    790         }
    791     }
    792 
    793     @Override
    794     public ISelection getSelection() {
    795         if (mFrameTreeViewer != null) {
    796             return mFrameTreeViewer.getSelection();
    797         } else {
    798             return null;
    799         }
    800     }
    801 
    802     @Override
    803     public void removeSelectionChangedListener(ISelectionChangedListener listener) {
    804         if (mFrameTreeViewer != null) {
    805             mFrameTreeViewer.removeSelectionChangedListener(listener);
    806         }
    807     }
    808 
    809     @Override
    810     public void setSelection(ISelection selection) {
    811         if (mFrameTreeViewer != null) {
    812             mFrameTreeViewer.setSelection(selection);
    813         }
    814     }
    815 
    816     public GLTrace getTrace() {
    817         return mTrace;
    818     }
    819 
    820     public StateViewPage getStateViewPage() {
    821         if (mStateViewPage == null) {
    822             mStateViewPage = new StateViewPage(mTrace);
    823         }
    824 
    825         return mStateViewPage;
    826     }
    827 
    828     public FrameSummaryViewPage getFrameSummaryViewPage() {
    829         if (mFrameSummaryViewPage == null) {
    830             mFrameSummaryViewPage = new FrameSummaryViewPage(mTrace);
    831         }
    832 
    833         return mFrameSummaryViewPage;
    834     }
    835 
    836     public DetailsPage getDetailsPage() {
    837         if (mDetailsPage == null) {
    838             mDetailsPage = new DetailsPage(mTrace);
    839         }
    840 
    841         return mDetailsPage;
    842     }
    843 
    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 
    868     private void selectAll() {
    869         if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
    870             return;
    871         }
    872 
    873         mFrameTreeViewer.getTree().selectAll();
    874     }
    875 
    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 
    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 
    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 
    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
    938         public int getItemCount() {
    939             return mFrameTreeViewer.getTree().getItemCount();
    940         }
    941 
    942         @Override
    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
    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
    960         public int getStartingIndex() {
    961             return 0;
    962         }
    963     };
    964 
    965     private FindDialog mFindDialog;
    966     private TraceViewerFindTarget mFindTarget = new TraceViewerFindTarget();
    967 
    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 
    981     public String getInputPath() {
    982         return mFilePath;
    983     }
    984 }
    985