blob: b809ddddf7d0ec83d161eda22d3cd055913c4d31 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.gltrace.editors;
import com.android.ddmuilib.AbstractBufferFindTarget;
import com.android.ddmuilib.FindDialog;
import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function;
import com.android.ide.eclipse.gltrace.GlTracePlugin;
import com.android.ide.eclipse.gltrace.SwtUtils;
import com.android.ide.eclipse.gltrace.TraceFileParserTask;
import com.android.ide.eclipse.gltrace.editors.DurationMinimap.ICallSelectionListener;
import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode;
import com.android.ide.eclipse.gltrace.model.GLCall;
import com.android.ide.eclipse.gltrace.model.GLFrame;
import com.android.ide.eclipse.gltrace.model.GLTrace;
import com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage;
import com.android.ide.eclipse.gltrace.views.detail.DetailsPage;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.EditorPart;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Display OpenGL function trace in a tabular view. */
public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvider {
public static final String ID = "com.android.ide.eclipse.gltrace.GLFunctionTrace"; //$NON-NLS-1$
private static final String DEFAULT_FILTER_MESSAGE = "Filter list of OpenGL calls. Accepts Java regexes.";
private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
private static Image sExpandAllIcon;
private static String sLastExportedToFolder;
private String mFilePath;
private Scale mFrameSelectionScale;
private Spinner mFrameSelectionSpinner;
private GLTrace mTrace;
private TreeViewer mFrameTreeViewer;
private List<GLCallNode> mTreeViewerNodes;
private Text mFilterText;
private GLCallFilter mGLCallFilter;
private Color mGldrawTextColor;
private Color mGlCallErrorColor;
/**
* Job to refresh the tree view & frame summary view.
*
* When the currently displayed frame is changed, either via the {@link #mFrameSelectionScale}
* or via {@link #mFrameSelectionSpinner}, we need to update the displayed tree of calls for
* that frame, and the frame summary view. Both these operations need to happen on the UI
* thread, but are time consuming. This works out ok if the frame selection is not changing
* rapidly (i.e., when the spinner or scale is moved to the target frame in a single action).
* However, if the spinner is constantly pressed, then the user is scrolling through a sequence
* of frames, and rather than refreshing the details for each of the intermediate frames,
* we create a job to refresh the details and schedule the job after a short interval
* {@link #TREE_REFRESH_INTERVAL}. This allows us to stay responsive to the spinner/scale,
* and not do the costly refresh for each of the intermediate frames.
*/
private Job mTreeRefresherJob;
private final Object mTreeRefresherLock = new Object();
private static final int TREE_REFRESH_INTERVAL_MS = 250;
private int mCurrentFrame;
// Currently displayed frame's start and end call indices.
private int mCallStartIndex;
private int mCallEndIndex;
private DurationMinimap mDurationMinimap;
private ScrollBar mVerticalScrollBar;
private Combo mContextSwitchCombo;
private boolean mShowContextSwitcher;
private int mCurrentlyDisplayedContext = -1;
private StateViewPage mStateViewPage;
private FrameSummaryViewPage mFrameSummaryViewPage;
private DetailsPage mDetailsPage;
private ToolItem mExpandAllToolItem;
private ToolItem mCollapseAllToolItem;
private ToolItem mSaveAsToolItem;
public GLFunctionTraceViewer() {
mGldrawTextColor = Display.getDefault().getSystemColor(SWT.COLOR_BLUE);
mGlCallErrorColor = Display.getDefault().getSystemColor(SWT.COLOR_RED);
}
@Override
public void doSave(IProgressMonitor monitor) {
}
@Override
public void doSaveAs() {
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
// we use a IURIEditorInput to allow opening files not within the workspace
if (!(input instanceof IURIEditorInput)) {
throw new PartInitException("GL Function Trace View: unsupported input type.");
}
setSite(site);
setInput(input);
mFilePath = ((IURIEditorInput) input).getURI().getPath();
// set the editor part name to be the name of the file.
File f = new File(mFilePath);
setPartName(f.getName());
}
@Override
public boolean isDirty() {
return false;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public void createPartControl(Composite parent) {
Composite c = new Composite(parent, SWT.NONE);
c.setLayout(new GridLayout(1, false));
GridData gd = new GridData(GridData.FILL_BOTH);
c.setLayoutData(gd);
setInput(parent.getShell(), mFilePath);
createFrameSelectionControls(c);
createOptionsBar(c);
createFrameTraceView(c);
getSite().setSelectionProvider(mFrameTreeViewer);
IActionBars actionBars = getEditorSite().getActionBars();
actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(),
new Action("Copy") {
@Override
public void run() {
copySelectionToClipboard();
}
});
actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
new Action("Select All") {
@Override
public void run() {
selectAll();
}
});
actionBars.setGlobalActionHandler(ActionFactory.FIND.getId(),
new Action("Find") {
@Override
public void run() {
showFindDialog();
}
});
}
public void setInput(Shell shell, String tracePath) {
ProgressMonitorDialog dlg = new ProgressMonitorDialog(shell);
TraceFileParserTask parser = new TraceFileParserTask(mFilePath);
try {
dlg.run(true, true, parser);
} catch (InvocationTargetException e) {
// exception while parsing, display error to user
MessageDialog.openError(shell,
"Error parsing OpenGL Trace File",
e.getCause().getMessage());
return;
} catch (InterruptedException e) {
// operation canceled by user, just return
return;
}
mTrace = parser.getTrace();
mShowContextSwitcher = (mTrace == null) ? false : mTrace.getContexts().size() > 1;
if (mStateViewPage != null) {
mStateViewPage.setInput(mTrace);
}
if (mFrameSummaryViewPage != null) {
mFrameSummaryViewPage.setInput(mTrace);
}
if (mDetailsPage != null) {
mDetailsPage.setInput(mTrace);
}
if (mDurationMinimap != null) {
mDurationMinimap.setInput(mTrace);
}
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
refreshUI();
}
});
}
private void refreshUI() {
if (mTrace == null || mTrace.getGLCalls().size() == 0) {
setFrameCount(0);
return;
}
setFrameCount(mTrace.getFrames().size());
selectFrame(1);
}
private void createFrameSelectionControls(Composite parent) {
Composite c = new Composite(parent, SWT.NONE);
c.setLayout(new GridLayout(3, false));
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
c.setLayoutData(gd);
Label l = new Label(c, SWT.NONE);
l.setText("Select Frame:");
mFrameSelectionScale = new Scale(c, SWT.HORIZONTAL);
mFrameSelectionScale.setMinimum(1);
mFrameSelectionScale.setMaximum(1);
mFrameSelectionScale.setSelection(0);
gd = new GridData(GridData.FILL_HORIZONTAL);
mFrameSelectionScale.setLayoutData(gd);
mFrameSelectionScale.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int selectedFrame = mFrameSelectionScale.getSelection();
mFrameSelectionSpinner.setSelection(selectedFrame);
selectFrame(selectedFrame);
}
});
mFrameSelectionSpinner = new Spinner(c, SWT.BORDER);
gd = new GridData();
// width to hold atleast 6 digits
gd.widthHint = SwtUtils.getApproximateFontWidth(mFrameSelectionSpinner) * 6;
mFrameSelectionSpinner.setLayoutData(gd);
mFrameSelectionSpinner.setMinimum(1);
mFrameSelectionSpinner.setMaximum(1);
mFrameSelectionSpinner.setSelection(0);
mFrameSelectionSpinner.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int selectedFrame = mFrameSelectionSpinner.getSelection();
mFrameSelectionScale.setSelection(selectedFrame);
selectFrame(selectedFrame);
}
});
}
private void setFrameCount(int nFrames) {
boolean en = nFrames > 0;
mFrameSelectionScale.setEnabled(en);
mFrameSelectionSpinner.setEnabled(en);
mFrameSelectionScale.setMaximum(nFrames);
mFrameSelectionSpinner.setMaximum(nFrames);
}
private void selectFrame(int selectedFrame) {
mFrameSelectionScale.setSelection(selectedFrame);
mFrameSelectionSpinner.setSelection(selectedFrame);
synchronized (mTreeRefresherLock) {
if (mTrace != null) {
GLFrame f = mTrace.getFrame(selectedFrame - 1);
mCallStartIndex = f.getStartIndex();
mCallEndIndex = f.getEndIndex();
} else {
mCallStartIndex = mCallEndIndex = 0;
}
mCurrentFrame = selectedFrame - 1;
scheduleNewRefreshJob();
}
// update minimap view
mDurationMinimap.setCallRangeForCurrentFrame(mCallStartIndex, mCallEndIndex);
}
/**
* Show only calls from the given context
* @param context context id whose calls should be displayed. Illegal values will result in
* calls from all contexts being displayed.
*/
private void selectContext(int context) {
if (mCurrentlyDisplayedContext == context) {
return;
}
synchronized (mTreeRefresherLock) {
mCurrentlyDisplayedContext = context;
scheduleNewRefreshJob();
}
}
private void scheduleNewRefreshJob() {
if (mTreeRefresherJob != null) {
return;
}
mTreeRefresherJob = new Job("Refresh GL Trace View Tree") {
@Override
protected IStatus run(IProgressMonitor monitor) {
final int start, end, context;
synchronized (mTreeRefresherLock) {
start = mCallStartIndex;
end = mCallEndIndex;
context = mCurrentlyDisplayedContext;
mTreeRefresherJob = null;
}
// update tree view in the editor
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
refreshTree(start, end, context);
// update the frame summary view
if (mFrameSummaryViewPage != null) {
mFrameSummaryViewPage.setSelectedFrame(mCurrentFrame);
}
}
});
return Status.OK_STATUS;
}
};
mTreeRefresherJob.setPriority(Job.SHORT);
mTreeRefresherJob.schedule(TREE_REFRESH_INTERVAL_MS);
}
private void refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay) {
mTreeViewerNodes = GLCallGroups.constructCallHierarchy(mTrace,
startCallIndex, endCallIndex,
contextToDisplay);
mFrameTreeViewer.setInput(mTreeViewerNodes);
mFrameTreeViewer.refresh();
mFrameTreeViewer.expandAll();
}
private void createOptionsBar(Composite parent) {
int numColumns = mShowContextSwitcher ? 4 : 3;
Composite c = new Composite(parent, SWT.NONE);
c.setLayout(new GridLayout(numColumns, false));
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
c.setLayoutData(gd);
Label l = new Label(c, SWT.NONE);
l.setText("Filter:");
mFilterText = new Text(c, SWT.BORDER | SWT.ICON_SEARCH | SWT.SEARCH | SWT.ICON_CANCEL);
mFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mFilterText.setMessage(DEFAULT_FILTER_MESSAGE);
mFilterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
updateAppliedFilters();
}
});
if (mShowContextSwitcher) {
mContextSwitchCombo = new Combo(c, SWT.BORDER | SWT.READ_ONLY);
// Setup the combo such that "All Contexts" is the first item,
// and then we have an item for each context.
mContextSwitchCombo.add("All Contexts");
mContextSwitchCombo.select(0);
mCurrentlyDisplayedContext = -1; // showing all contexts
for (int i = 0; i < mTrace.getContexts().size(); i++) {
mContextSwitchCombo.add("Context " + i);
}
mContextSwitchCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
selectContext(mContextSwitchCombo.getSelectionIndex() - 1);
}
});
} else {
mCurrentlyDisplayedContext = 0;
}
ToolBar toolBar = new ToolBar(c, SWT.FLAT | SWT.BORDER);
mExpandAllToolItem = new ToolItem(toolBar, SWT.PUSH);
mExpandAllToolItem.setToolTipText("Expand All");
if (sExpandAllIcon == null) {
ImageDescriptor id = GlTracePlugin.getImageDescriptor("/icons/expandall.png");
sExpandAllIcon = id.createImage();
}
if (sExpandAllIcon != null) {
mExpandAllToolItem.setImage(sExpandAllIcon);
}
mCollapseAllToolItem = new ToolItem(toolBar, SWT.PUSH);
mCollapseAllToolItem.setToolTipText("Collapse All");
mCollapseAllToolItem.setImage(
PlatformUI.getWorkbench().getSharedImages().getImage(
ISharedImages.IMG_ELCL_COLLAPSEALL));
mSaveAsToolItem = new ToolItem(toolBar, SWT.PUSH);
mSaveAsToolItem.setToolTipText("Export Trace");
mSaveAsToolItem.setImage(
PlatformUI.getWorkbench().getSharedImages().getImage(
ISharedImages.IMG_ETOOL_SAVEAS_EDIT));
SelectionListener toolbarSelectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (e.getSource() == mCollapseAllToolItem) {
setTreeItemsExpanded(false);
} else if (e.getSource() == mExpandAllToolItem) {
setTreeItemsExpanded(true);
} else if (e.getSource() == mSaveAsToolItem) {
exportTrace();
}
}
};
mExpandAllToolItem.addSelectionListener(toolbarSelectionListener);
mCollapseAllToolItem.addSelectionListener(toolbarSelectionListener);
mSaveAsToolItem.addSelectionListener(toolbarSelectionListener);
}
private void updateAppliedFilters() {
mGLCallFilter.setFilters(mFilterText.getText().trim());
mFrameTreeViewer.refresh();
}
private void createFrameTraceView(Composite parent) {
Composite c = new Composite(parent, SWT.NONE);
c.setLayout(new GridLayout(2, false));
GridData gd = new GridData(GridData.FILL_BOTH);
c.setLayoutData(gd);
final Tree tree = new Tree(c, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
gd = new GridData(GridData.FILL_BOTH);
tree.setLayoutData(gd);
tree.setLinesVisible(true);
tree.setHeaderVisible(true);
mFrameTreeViewer = new TreeViewer(tree);
CellLabelProvider labelProvider = new GLFrameLabelProvider();
// column showing the GL context id
TreeViewerColumn tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
tvc.setLabelProvider(labelProvider);
TreeColumn column = tvc.getColumn();
column.setText("Function");
column.setWidth(500);
// column showing the GL function duration (wall clock time)
tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
tvc.setLabelProvider(labelProvider);
column = tvc.getColumn();
column.setText("Wall Time (ns)");
column.setWidth(150);
column.setAlignment(SWT.RIGHT);
// column showing the GL function duration (thread time)
tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
tvc.setLabelProvider(labelProvider);
column = tvc.getColumn();
column.setText("Thread Time (ns)");
column.setWidth(150);
column.setAlignment(SWT.RIGHT);
mFrameTreeViewer.setContentProvider(new GLFrameContentProvider());
mGLCallFilter = new GLCallFilter();
mFrameTreeViewer.addFilter(mGLCallFilter);
// when the control is resized, give all the additional space
// to the function name column.
tree.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
int w = mFrameTreeViewer.getTree().getClientArea().width;
if (w > 200) {
mFrameTreeViewer.getTree().getColumn(2).setWidth(100);
mFrameTreeViewer.getTree().getColumn(1).setWidth(100);
mFrameTreeViewer.getTree().getColumn(0).setWidth(w - 200);
}
}
});
mDurationMinimap = new DurationMinimap(c, mTrace);
gd = new GridData(GridData.FILL_VERTICAL);
gd.widthHint = gd.minimumWidth = mDurationMinimap.getMinimumWidth();
mDurationMinimap.setLayoutData(gd);
mDurationMinimap.addCallSelectionListener(new ICallSelectionListener() {
@Override
public void callSelected(int selectedCallIndex) {
if (selectedCallIndex > 0 && selectedCallIndex < mTreeViewerNodes.size()) {
TreeItem item = tree.getItem(selectedCallIndex);
tree.select(item);
tree.setTopItem(item);
}
}
});
mVerticalScrollBar = tree.getVerticalBar();
mVerticalScrollBar.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateVisibleRange();
}
});
}
private void updateVisibleRange() {
int visibleCallTopIndex = mCallStartIndex;
int visibleCallBottomIndex = mCallEndIndex;
if (mVerticalScrollBar.isEnabled()) {
int selection = mVerticalScrollBar.getSelection();
int thumb = mVerticalScrollBar.getThumb();
int max = mVerticalScrollBar.getMaximum();
// from the scrollbar values, compute the visible fraction
double top = (double) selection / max;
double bottom = (double) (selection + thumb) / max;
// map the fraction to the call indices
int range = mCallEndIndex - mCallStartIndex;
visibleCallTopIndex = mCallStartIndex + (int) Math.floor(range * top);
visibleCallBottomIndex = mCallStartIndex + (int) Math.ceil(range * bottom);
}
mDurationMinimap.setVisibleCallRange(visibleCallTopIndex, visibleCallBottomIndex);
}
@Override
public void setFocus() {
mFrameTreeViewer.getTree().setFocus();
}
private static class GLFrameContentProvider implements ITreeContentProvider {
@Override
public void dispose() {
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
@Override
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof List<?>) {
return ((List<?>) parentElement).toArray();
}
if (!(parentElement instanceof GLCallNode)) {
return null;
}
GLCallNode parent = (GLCallNode) parentElement;
if (parent.hasChildren()) {
return parent.getChildren().toArray();
} else {
return new Object[0];
}
}
@Override
public Object getParent(Object element) {
if (!(element instanceof GLCallNode)) {
return null;
}
return ((GLCallNode) element).getParent();
}
@Override
public boolean hasChildren(Object element) {
if (!(element instanceof GLCallNode)) {
return false;
}
return ((GLCallNode) element).hasChildren();
}
}
private class GLFrameLabelProvider extends ColumnLabelProvider {
@Override
public void update(ViewerCell cell) {
Object element = cell.getElement();
if (!(element instanceof GLCallNode)) {
return;
}
GLCall c = ((GLCallNode) element).getCall();
if (c.getFunction() == Function.glDrawArrays
|| c.getFunction() == Function.glDrawElements) {
cell.setForeground(mGldrawTextColor);
}
if (c.hasErrors()) {
cell.setForeground(mGlCallErrorColor);
}
cell.setText(getColumnText(c, cell.getColumnIndex()));
}
private String getColumnText(GLCall c, int columnIndex) {
switch (columnIndex) {
case 0:
if (c.getFunction() == Function.glPushGroupMarkerEXT) {
Object marker = c.getProperty(GLCall.PROPERTY_MARKERNAME);
if (marker instanceof String) {
return ((String) marker);
}
}
return c.toString();
case 1:
return formatDuration(c.getWallDuration());
case 2:
return formatDuration(c.getThreadDuration());
default:
return Integer.toString(c.getContextId());
}
}
private String formatDuration(int time) {
// Max duration is in the 10s of milliseconds, so xx,xxx,xxx ns
// So we require a format specifier that is 10 characters wide
return String.format("%,10d", time); //$NON-NLS-1$
}
}
private static class GLCallFilter extends ViewerFilter {
private final List<Pattern> mPatterns = new ArrayList<Pattern>();
public void setFilters(String filter) {
mPatterns.clear();
// split the user input into multiple regexes
// we assume that the regexes are OR'ed together i.e., all text that matches
// any one of the regexes will be displayed
for (String regex : filter.split(" ")) {
mPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE));
}
}
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (!(element instanceof GLCallNode)) {
return true;
}
String text = getTextUnderNode((GLCallNode) element);
if (mPatterns.size() == 0) {
// match if there are no regex filters
return true;
}
for (Pattern p : mPatterns) {
Matcher matcher = p.matcher(text);
if (matcher.find()) {
// match if atleast one of the regexes matches this text
return true;
}
}
return false;
}
/** Obtain a string representation of all functions under a given tree node. */
private String getTextUnderNode(GLCallNode element) {
String func = element.getCall().getFunction().toString();
if (!element.hasChildren()) {
return func;
}
StringBuilder sb = new StringBuilder(100);
sb.append(func);
for (GLCallNode child : element.getChildren()) {
sb.append(getTextUnderNode(child));
}
return sb.toString();
}
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
if (mFrameTreeViewer != null) {
mFrameTreeViewer.addSelectionChangedListener(listener);
}
}
@Override
public ISelection getSelection() {
if (mFrameTreeViewer != null) {
return mFrameTreeViewer.getSelection();
} else {
return null;
}
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
if (mFrameTreeViewer != null) {
mFrameTreeViewer.removeSelectionChangedListener(listener);
}
}
@Override
public void setSelection(ISelection selection) {
if (mFrameTreeViewer != null) {
mFrameTreeViewer.setSelection(selection);
}
}
public GLTrace getTrace() {
return mTrace;
}
public StateViewPage getStateViewPage() {
if (mStateViewPage == null) {
mStateViewPage = new StateViewPage(mTrace);
}
return mStateViewPage;
}
public FrameSummaryViewPage getFrameSummaryViewPage() {
if (mFrameSummaryViewPage == null) {
mFrameSummaryViewPage = new FrameSummaryViewPage(mTrace);
}
return mFrameSummaryViewPage;
}
public DetailsPage getDetailsPage() {
if (mDetailsPage == null) {
mDetailsPage = new DetailsPage(mTrace);
}
return mDetailsPage;
}
private void copySelectionToClipboard() {
if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
return;
}
StringBuilder sb = new StringBuilder();
for (TreeItem it: mFrameTreeViewer.getTree().getSelection()) {
Object data = it.getData();
if (data instanceof GLCallNode) {
sb.append(((GLCallNode) data).getCall());
sb.append(NEWLINE);
}
}
if (sb.length() > 0) {
Clipboard cb = new Clipboard(Display.getDefault());
cb.setContents(
new Object[] { sb.toString() },
new Transfer[] { TextTransfer.getInstance() });
cb.dispose();
}
}
private void selectAll() {
if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
return;
}
mFrameTreeViewer.getTree().selectAll();
}
private void exportTrace() {
if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
return;
}
if (mCallEndIndex == 0) {
return;
}
FileDialog fd = new FileDialog(mFrameTreeViewer.getTree().getShell(), SWT.SAVE);
fd.setFilterExtensions(new String[] { "*.txt" });
if (sLastExportedToFolder != null) {
fd.setFilterPath(sLastExportedToFolder);
}
String path = fd.open();
if (path == null) {
return;
}
File f = new File(path);
sLastExportedToFolder = f.getParent();
try {
exportFrameTo(f);
} catch (IOException e) {
ErrorDialog.openError(mFrameTreeViewer.getTree().getShell(),
"Export trace file.",
"Unexpected error exporting trace file.",
new Status(Status.ERROR, GlTracePlugin.PLUGIN_ID, e.toString()));
}
}
private void exportFrameTo(File f) throws IOException {
String glCalls = serializeGlCalls(mTrace.getGLCalls(), mCallStartIndex, mCallEndIndex);
Files.write(glCalls, f, Charsets.UTF_8);
}
private String serializeGlCalls(List<GLCall> glCalls, int start, int end) {
StringBuilder sb = new StringBuilder();
while (start < end) {
sb.append(glCalls.get(start).toString());
sb.append("\n"); //$NON-NLS-1$
start++;
}
return sb.toString();
}
private void setTreeItemsExpanded(boolean expand) {
if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
return;
}
if (expand) {
mFrameTreeViewer.expandAll();
} else {
mFrameTreeViewer.collapseAll();
}
}
private class TraceViewerFindTarget extends AbstractBufferFindTarget {
@Override
public int getItemCount() {
return mFrameTreeViewer.getTree().getItemCount();
}
@Override
public String getItem(int index) {
Object data = mFrameTreeViewer.getTree().getItem(index).getData();
if (data instanceof GLCallNode) {
return ((GLCallNode) data).getCall().toString();
}
return null;
}
@Override
public void selectAndReveal(int index) {
Tree t = mFrameTreeViewer.getTree();
t.deselectAll();
t.select(t.getItem(index));
t.showSelection();
}
@Override
public int getStartingIndex() {
return 0;
}
};
private FindDialog mFindDialog;
private TraceViewerFindTarget mFindTarget = new TraceViewerFindTarget();
private void showFindDialog() {
if (mFindDialog != null) {
// the dialog is already displayed
return;
}
mFindDialog = new FindDialog(Display.getDefault().getActiveShell(),
mFindTarget,
FindDialog.FIND_NEXT_ID);
mFindDialog.open(); // blocks until find dialog is closed
mFindDialog = null;
}
public String getInputPath() {
return mFilePath;
}
}