/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* 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.jetbrains.lang.dart.ide.errorTreeView;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CopyProvider;
import com.intellij.ide.actions.ContextHelpAction;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.ui.*;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.table.TableView;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.ui.JBUI;
import com.jetbrains.lang.dart.DartBundle;
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
import icons.DartIcons;
import org.dartlang.analysis.server.protocol.AnalysisError;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.table.JTableHeader;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class DartProblemsViewPanel extends SimpleToolWindowPanel implements DataProvider, CopyProvider {
private static final long DEFAULT_SERVER_WAIT_MILLIS = 5000L; // Switch to UNKNOWN after 5s with no response.
private static final LayeredIcon DART_ERRORS_ICON;
private static final LayeredIcon DART_WARNINGS_ICON;
static {
DART_ERRORS_ICON = new LayeredIcon(2);
DART_ERRORS_ICON.setIcon(DartIcons.Dart_13, 0);
DART_ERRORS_ICON.setIcon(AllIcons.Ide.ErrorPoint, 1, SwingConstants.SOUTH_WEST);
DART_WARNINGS_ICON = new LayeredIcon(2);
DART_WARNINGS_ICON.setIcon(DartIcons.Dart_13, 0);
DART_WARNINGS_ICON.setIcon(DartIcons.Warning_point, 1, SwingConstants.SOUTH_WEST);
}
@NotNull private final Project myProject;
@NotNull private final TableView<DartProblem> myTable;
@NotNull private final DartProblemsPresentationHelper myPresentationHelper;
private DartProblemsView.ToolWindowUpdater myToolWindowUpdater;
public DartProblemsViewPanel(@NotNull final Project project,
@NotNull final DartProblemsPresentationHelper presentationHelper) {
super(false, true);
myProject = project;
myPresentationHelper = presentationHelper;
myTable = createTable();
setToolbar(createToolbar());
setContent(createCenterPanel());
DartAnalysisServerService.getInstance(project).maxMillisToWaitForServerResponse = DEFAULT_SERVER_WAIT_MILLIS;
}
@NotNull
private TableView<DartProblem> createTable() {
final TableView<DartProblem> table = new TableView<>(new DartProblemsTableModel(myProject, myPresentationHelper));
table.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
navigate(false); // as in NewErrorTreeViewPanel
}
}
});
EditSourceOnDoubleClickHandler.install(table);
table.addMouseListener(new PopupHandler() {
@Override
public void invokePopup(Component comp, int x, int y) {
popupInvoked(comp, x, y);
}
});
//noinspection unchecked
((DefaultRowSorter)table.getRowSorter()).setRowFilter(myPresentationHelper.getRowFilter());
table.getRowSorter().addRowSorterListener(e -> {
final List<? extends RowSorter.SortKey> sortKeys = myTable.getRowSorter().getSortKeys();
assert sortKeys.size() == 1 : sortKeys;
((DartProblemsTableModel)myTable.getModel()).setSortKey(sortKeys.get(0));
});
new TableSpeedSearch(table, object -> object instanceof DartProblem
? ((DartProblem)object).getErrorMessage() + " " + ((DartProblem)object).getPresentableLocation()
: "");
table.setShowVerticalLines(false);
table.setShowHorizontalLines(false);
table.setStriped(true);
table.setRowHeight(table.getRowHeight() + JBUI.scale(4));
JTableHeader tableHeader = table.getTableHeader();
tableHeader.setPreferredSize(new Dimension(0, table.getRowHeight()));
return table;
}
private void popupInvoked(final Component component, final int x, final int y) {
final DefaultActionGroup group = new DefaultActionGroup();
if (getData(CommonDataKeys.NAVIGATABLE.getName()) != null) {
group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE));
}
group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_COPY));
final ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.TOOLBAR, group);
menu.getComponent().show(component, x, y);
}
@NotNull
private JComponent createToolbar() {
final DefaultActionGroup group = new DefaultActionGroup();
addReanalyzeActions(group);
group.addSeparator();
addAutoScrollToSourceAction(group);
addGroupBySeverityAction(group);
group.addAction(new FilterProblemsAction());
group.addSeparator();
group.addAction(new ContextHelpAction("reference.toolWindow.DartAnalysis"));
return ActionManager.getInstance().createActionToolbar("DartProblemsView", group, false).getComponent();
}
@NotNull
private JPanel createCenterPanel() {
final JPanel panel = new JPanel(new BorderLayout());
panel.add(ScrollPaneFactory.createScrollPane(myTable), BorderLayout.CENTER);
return panel;
}
private void updateStatusDescription() {
if (myToolWindowUpdater != null) {
final DartProblemsTableModel model = (DartProblemsTableModel)myTable.getModel();
myToolWindowUpdater.setHeaderText(model.getStatusText());
myToolWindowUpdater.setIcon(model.hasErrors() ? DART_ERRORS_ICON : model.hasWarnings() ? DART_WARNINGS_ICON : DartIcons.Dart_13);
}
}
private static void addReanalyzeActions(@NotNull final DefaultActionGroup group) {
final AnAction restartAction = ActionManager.getInstance().getAction("Dart.Restart.Analysis.Server");
if (restartAction != null) {
group.add(restartAction);
}
}
private void addAutoScrollToSourceAction(@NotNull final DefaultActionGroup group) {
final AutoScrollToSourceHandler autoScrollToSourceHandler = new AutoScrollToSourceHandler() {
@Override
protected boolean isAutoScrollMode() {
return myPresentationHelper.isAutoScrollToSource();
}
@Override
protected void setAutoScrollMode(boolean autoScrollToSource) {
myPresentationHelper.setAutoScrollToSource(autoScrollToSource);
}
};
autoScrollToSourceHandler.install(myTable);
group.addAction(autoScrollToSourceHandler.createToggleAction());
}
private void addGroupBySeverityAction(@NotNull final DefaultActionGroup group) {
final AnAction action = new ToggleAction(DartBundle.message("group.by.severity"),
DartBundle.message("group.by.severity.description"),
AllIcons.Nodes.SortBySeverity) {
@Override
public boolean isSelected(AnActionEvent e) {
return myPresentationHelper.isGroupBySeverity();
}
@Override
public void setSelected(AnActionEvent e, boolean groupBySeverity) {
myPresentationHelper.setGroupBySeverity(groupBySeverity);
fireGroupingOrFilterChanged();
}
};
group.addAction(action);
}
void fireGroupingOrFilterChanged() {
myTable.getRowSorter().allRowsChanged();
((DartProblemsTableModel)myTable.getModel()).onFilterChanged();
updateStatusDescription();
}
private void showFiltersPopup() {
final DartProblemsFilterForm form = new DartProblemsFilterForm();
form.reset(myPresentationHelper);
form.addListener(new DartProblemsFilterForm.FilterListener() {
@Override
public void filtersChanged() {
myPresentationHelper.updateFromUI(form);
fireGroupingOrFilterChanged();
}
@Override
public void filtersResetRequested() {
myPresentationHelper.resetAllFilters();
form.reset(myPresentationHelper);
fireGroupingOrFilterChanged();
}
});
final Rectangle visibleRect = myTable.getVisibleRect();
final Point tableTopLeft = new Point(myTable.getLocationOnScreen().x + visibleRect.x, myTable.getLocationOnScreen().y + visibleRect.y);
JBPopupFactory.getInstance()
.createComponentPopupBuilder(form.getMainPanel(), form.getMainPanel())
.setProject(myProject)
.setTitle("Dart Problems Filter")
.setMovable(true)
.setRequestFocus(true)
.createPopup().show(RelativePoint.fromScreen(tableTopLeft));
}
@Override
public boolean isCopyVisible(@NotNull DataContext dataContext) {
return true;
}
@Override
public boolean isCopyEnabled(@NotNull DataContext dataContext) {
return myTable.getSelectedObject() != null;
}
@Override
public void performCopy(@NotNull DataContext dataContext) {
final List<DartProblem> selectedObjects = myTable.getSelectedObjects();
final String s = StringUtil.join(selectedObjects, problem -> problem.getSeverity().toLowerCase(Locale.US) +
": " +
problem.getErrorMessage() +
" (" +
problem.getCode() + " at " + problem.getPresentableLocation() +
")", "\n");
if (!s.isEmpty()) {
CopyPasteManager.getInstance().setContents(new StringSelection(s));
}
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
return this;
}
if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
return createNavigatable();
}
return null;
}
@Nullable
private Navigatable createNavigatable() {
final DartProblem problem = myTable.getSelectedObject();
if (problem != null) {
final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(problem.getSystemIndependentPath());
if (file != null) {
final OpenFileDescriptor navigatable = new OpenFileDescriptor(myProject, file, problem.getOffset());
navigatable.setScrollType(ScrollType.MAKE_VISIBLE);
return navigatable;
}
}
return null;
}
private void navigate(@SuppressWarnings("SameParameterValue") final boolean requestFocus) {
final Navigatable navigatable = createNavigatable();
if (navigatable != null && navigatable.canNavigateToSource()) {
navigatable.navigate(requestFocus);
}
}
public void setErrors(@NotNull final Map<String, List<AnalysisError>> filePathToErrors) {
final DartProblemsTableModel model = (DartProblemsTableModel)myTable.getModel();
final DartProblem oldSelectedProblem = myTable.getSelectedObject();
final DartProblem updatedSelectedProblem = model.setErrorsAndReturnReplacementForSelection(filePathToErrors, oldSelectedProblem);
if (updatedSelectedProblem != null) {
myTable.setSelection(Collections.singletonList(updatedSelectedProblem));
}
updateStatusDescription();
}
public void clearAll() {
((DartProblemsTableModel)myTable.getModel()).removeAll();
updateStatusDescription();
}
void setToolWindowUpdater(@NotNull final DartProblemsView.ToolWindowUpdater toolWindowUpdater) {
myToolWindowUpdater = toolWindowUpdater;
}
private class FilterProblemsAction extends DumbAwareAction implements Toggleable {
public FilterProblemsAction() {
super(DartBundle.message("filter.problems"), DartBundle.message("filter.problems.description"), AllIcons.General.Filter);
}
@Override
public void update(final AnActionEvent e) {
// show icon as toggled on if any filter is active
e.getPresentation().putClientProperty(Toggleable.SELECTED_PROPERTY, myPresentationHelper.areFiltersApplied());
}
@Override
public void actionPerformed(final AnActionEvent e) {
showFiltersPopup();
}
}
}