blob: 3f1bdf31935b682e6fcbbfc330f09265d47668ae [file] [log] [blame]
/*
* Copyright (C) 2013 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.tools.idea.editors.vmtrace;
import com.android.tools.idea.editors.vmtrace.treemodel.VmStatsTreeTableModel;
import com.android.tools.idea.editors.vmtrace.treemodel.VmStatsTreeUtils;
import com.android.tools.perflib.vmtrace.ClockType;
import com.android.tools.perflib.vmtrace.SearchResult;
import com.android.tools.perflib.vmtrace.ThreadInfo;
import com.android.tools.perflib.vmtrace.VmTraceData;
import com.android.tools.perflib.vmtrace.viz.TraceViewCanvas;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.intellij.find.editorHeaderActions.Utils;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.editor.impl.EditorHeaderComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.ColoredListCellRenderer;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.JBSplitter;
import com.intellij.ui.SearchTextField;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.treeStructure.treetable.TreeTable;
import icons.AndroidIcons;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.text.BadLocationException;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
public class TraceViewPanel {
@NonNls public static DataKey<TraceViewPanel> KEY = DataKey.create("android.traceview.panel");
// The names for the cards used in the card layout.
// Note that these are duplicated in the layout form.
@NonNls private static final String CARD_FIND = "FIND";
@NonNls private static final String CARD_DEFAULT = "DEFAULT";
/** Default name for main thread in Android apps. */
@NonNls private static final String MAIN_THREAD_NAME = "main";
private final Project myProject;
private JPanel myContainer;
private JPanel myHeaderPanel;
private TraceViewCanvas myTraceViewCanvas;
private TreeTable myTreeTable;
@SuppressWarnings("UnusedDeclaration") // custom creation only
private JPanel myDefaultHeaderPanel;
private JComboBox myThreadCombo;
private JComboBox myRenderClockSelectorCombo;
@SuppressWarnings("UnusedDeclaration") // custom creation only
private JPanel myFindPanel;
private JPanel myFindFieldWrapper;
private SearchTextField mySearchField;
private JLabel myCloseLabel;
private JBLabel myResultsLabel;
private JLabel mySearchLabel;
private JBSplitter mySplitter;
private JLabel myZoomFitLabel;
private JCheckBox myUseInclusiveTimeForColoring;
private static final String[] ourRenderClockOptions = new String[] {
"Wall Clock Time",
"Thread Time",
};
private static final ClockType[] ourRenderClockTypes = new ClockType[] {
ClockType.GLOBAL,
ClockType.THREAD,
};
private VmTraceData myTraceData;
private VmStatsTreeTableModel myVmStatsTreeTableModel;
public TraceViewPanel(Project project) {
myProject = project;
myRenderClockSelectorCombo.setModel(new DefaultComboBoxModel(ourRenderClockOptions));
myRenderClockSelectorCombo.setSelectedIndex(0);
ActionListener l = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == myThreadCombo) {
final ThreadInfo selectedThread = (ThreadInfo)myThreadCombo.getSelectedItem();
myTraceViewCanvas.displayThread(selectedThread);
myVmStatsTreeTableModel.setThread(selectedThread);
} else if (e.getSource() == myRenderClockSelectorCombo) {
myTraceViewCanvas.setRenderClock(getCurrentRenderClock());
myVmStatsTreeTableModel.setClockType(getCurrentRenderClock());
} else if (e.getSource() == myUseInclusiveTimeForColoring) {
myTraceViewCanvas.setUseInclusiveTimeForColorAssignment(myUseInclusiveTimeForColoring.isSelected());
}
}
};
myThreadCombo.addActionListener(l);
myRenderClockSelectorCombo.addActionListener(l);
myUseInclusiveTimeForColoring.addActionListener(l);
myUseInclusiveTimeForColoring.setOpaque(false);
}
private SearchTextField createSearchField() {
SearchTextField stf = new SearchTextField(true);
stf.setOpaque(false);
stf.setEnabled(true);
Utils.setSmallerFont(stf);
stf.addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
searchTextChanged(getText(e));
}
private String getText(DocumentEvent e) {
try {
return e.getDocument().getText(0, e.getDocument().getLength());
}
catch (BadLocationException e1) {
return "";
}
}
});
JTextField editorTextField = stf.getTextEditor();
editorTextField.setMinimumSize(new Dimension(200, -1));
editorTextField.registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
closeSearchComponent();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
return stf;
}
private void searchTextChanged(@Nullable String pattern) {
if (StringUtil.isEmpty(pattern)) {
myTraceViewCanvas.setHighlightMethods(null);
myResultsLabel.setText("");
return;
}
ThreadInfo thread = (ThreadInfo)myThreadCombo.getSelectedItem();
SearchResult results = myTraceData.searchFor(pattern, thread);
myTraceViewCanvas.setHighlightMethods(results.getMethods());
String result = String.format("%1$d %2$s, %3$d %4$s",
results.getMethods().size(),
StringUtil.pluralize("method", results.getMethods().size()),
results.getInstances().size(),
StringUtil.pluralize("instance", results.getInstances().size()));
myResultsLabel.setText(result);
}
public void setTrace(@NotNull VmTraceData trace) {
myTraceData = trace;
List<ThreadInfo> threads = trace.getThreads(true);
if (threads.isEmpty()) {
return;
}
ThreadInfo defaultThread = Iterables.find(threads, new Predicate<ThreadInfo>() {
@Override
public boolean apply(ThreadInfo input) {
return MAIN_THREAD_NAME.equals(input.getName());
}
}, threads.get(0));
myTraceViewCanvas.setTrace(trace, defaultThread, getCurrentRenderClock());
myThreadCombo.setModel(new DefaultComboBoxModel(threads.toArray()));
myThreadCombo.setSelectedItem(defaultThread);
myThreadCombo.setRenderer(new ColoredListCellRenderer() {
@Override
protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
String name = value instanceof ThreadInfo ? ((ThreadInfo)value).getName() : value.toString();
append(name);
}
});
myThreadCombo.setEnabled(true);
myRenderClockSelectorCombo.setEnabled(true);
myVmStatsTreeTableModel.setTraceData(trace, defaultThread);
myVmStatsTreeTableModel.setClockType(getCurrentRenderClock());
myTreeTable.setModel(myVmStatsTreeTableModel);
VmStatsTreeUtils.adjustTableColumnWidths(myTreeTable);
VmStatsTreeUtils.setCellRenderers(myTreeTable);
VmStatsTreeUtils.setSpeedSearch(myTreeTable);
VmStatsTreeUtils.enableSorting(myTreeTable, myVmStatsTreeTableModel);
}
private ClockType getCurrentRenderClock() {
return ourRenderClockTypes[myRenderClockSelectorCombo.getSelectedIndex()];
}
@NotNull
public JComponent getComponent() {
return myContainer;
}
private void createUIComponents() {
MouseAdapter l = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.getSource() == myCloseLabel) {
closeSearchComponent();
} else if (e.getSource() == mySearchLabel) {
showSearchComponent();
} else if (e.getSource() == myZoomFitLabel) {
myTraceViewCanvas.zoomFit();
}
}
};
myDefaultHeaderPanel = new EditorHeaderComponent();
mySearchLabel = new JLabel(AllIcons.Actions.Search);
mySearchLabel.addMouseListener(l);
mySearchLabel.setToolTipText("Find (Ctrl + F)");
myZoomFitLabel = new JLabel(AndroidIcons.ZoomFit);
myZoomFitLabel.setToolTipText("Zoom Fit");
myZoomFitLabel.addMouseListener(l);
myFindPanel = new EditorHeaderComponent();
myFindFieldWrapper = new NonOpaquePanel(new BorderLayout());
mySearchField = createSearchField();
myFindFieldWrapper.add(mySearchField);
myCloseLabel = new JLabel(AllIcons.Actions.Cross);
myCloseLabel.addMouseListener(l);
myVmStatsTreeTableModel = new VmStatsTreeTableModel();
myTreeTable = new TreeTable(myVmStatsTreeTableModel);
myTraceViewCanvas = new TraceViewCanvasWrapper();
JBScrollPane scrollPane = new JBScrollPane(myTreeTable);
mySplitter = new JBSplitter(true, 0.75f);
mySplitter.setShowDividerControls(true);
mySplitter.setShowDividerIcon(true);
mySplitter.setFirstComponent(myTraceViewCanvas);
mySplitter.setSecondComponent(scrollPane);
}
public void showSearchComponent() {
CardLayout layout = (CardLayout)myHeaderPanel.getLayout();
layout.show(myHeaderPanel, CARD_FIND);
IdeFocusManager.getInstance(myProject).requestFocus(mySearchField, true);
}
private void closeSearchComponent() {
CardLayout layout = (CardLayout)myHeaderPanel.getLayout();
layout.show(myHeaderPanel, CARD_DEFAULT);
IdeFocusManager.getInstance(myProject).requestFocus(myTraceViewCanvas, true);
}
/**
* {@link TraceViewCanvasWrapper} is a wrapper around {@link TraceViewCanvas} that also implements the {@link DataProvider} interface.
* This allows {@link VmTraceEditorSearchAction} to identify the editor from the current context of an event.
*/
private class TraceViewCanvasWrapper extends TraceViewCanvas implements DataProvider {
public TraceViewCanvasWrapper() {
addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
IdeFocusManager.getInstance(myProject).requestFocus(TraceViewCanvasWrapper.this, true);
}
});
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
return KEY.is(dataId) ? TraceViewPanel.this : null;
}
}
}