/** @author max */ public class InspectionResultsView extends JPanel implements Disposable, OccurenceNavigator, DataProvider { public static final DataKey<InspectionResultsView> DATA_KEY = DataKey.create("inspectionView"); private final Project myProject; private final InspectionTree myTree; private final Browser myBrowser; private final ConcurrentMap<HighlightDisplayLevel, ConcurrentMap<String, InspectionGroupNode>> myGroups = ContainerUtil.newConcurrentMap(); private final OccurenceNavigator myOccurenceNavigator; private volatile InspectionProfile myInspectionProfile; private final AnalysisScope myScope; @NonNls private static final String HELP_ID = "reference.toolWindows.inspections"; private final ConcurrentMap<HighlightDisplayLevel, InspectionSeverityGroupNode> mySeverityGroupNodes = ContainerUtil.newConcurrentMap(); private final Splitter mySplitter; @NotNull private final GlobalInspectionContextImpl myGlobalInspectionContext; private boolean myRerun; private volatile boolean myDisposed; @NotNull private final InspectionRVContentProvider myProvider; private AnAction myIncludeAction; private AnAction myExcludeAction; public InspectionResultsView( @NotNull final Project project, final InspectionProfile inspectionProfile, @NotNull AnalysisScope scope, @NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionRVContentProvider provider) { setLayout(new BorderLayout()); myProject = project; myInspectionProfile = inspectionProfile; myScope = scope; myGlobalInspectionContext = globalInspectionContext; myProvider = provider; myTree = new InspectionTree(project, globalInspectionContext); initTreeListeners(); myOccurenceNavigator = initOccurenceNavigator(); myBrowser = new Browser(this); mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION); mySplitter.setFirstComponent( ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT | SideBorder.RIGHT)); mySplitter.setSecondComponent(myBrowser); mySplitter.addPropertyChangeListener( new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) { myGlobalInspectionContext.setSplitterProportion( ((Float) evt.getNewValue()).floatValue()); } } }); add(mySplitter, BorderLayout.CENTER); myBrowser.addClickListener( new Browser.ClickListener() { @Override public void referenceClicked(final Browser.ClickEvent e) { if (e.getEventType() == Browser.ClickEvent.REF_ELEMENT) { final RefElement refElement = e.getClickedElement(); final OpenFileDescriptor descriptor = getOpenFileDescriptor(refElement); if (descriptor != null) { FileEditorManager.getInstance(project).openTextEditor(descriptor, false); } } else if (e.getEventType() == Browser.ClickEvent.FILE_OFFSET) { final VirtualFile file = e.getFile(); final OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file, e.getStartOffset()); final Editor editor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true); if (editor != null) { final TextAttributes selectionAttributes = EditorColorsManager.getInstance() .getGlobalScheme() .getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); HighlightManager.getInstance(project) .addRangeHighlight( editor, e.getStartOffset(), e.getEndOffset(), selectionAttributes, true, null); } } } }); createActionsToolbar(); TreeUtil.selectFirstNode(myTree); } private void initTreeListeners() { myTree .getSelectionModel() .addTreeSelectionListener( new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { syncBrowser(); if (isAutoScrollMode()) { OpenSourceUtil.openSourcesFrom( DataManager.getInstance().getDataContext(InspectionResultsView.this), false); } } }); EditSourceOnDoubleClickHandler.install(myTree); myTree.addKeyListener( new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { OpenSourceUtil.openSourcesFrom( DataManager.getInstance().getDataContext(InspectionResultsView.this), false); } } }); myTree.addMouseListener( new PopupHandler() { @Override public void invokePopup(Component comp, int x, int y) { popupInvoked(comp, x, y); } }); SmartExpander.installOn(myTree); } private OccurenceNavigatorSupport initOccurenceNavigator() { return new OccurenceNavigatorSupport(myTree) { @Override @Nullable protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) { if (node instanceof InspectionTreeNode && ((InspectionTreeNode) node).isResolved()) { return null; } if (node instanceof RefElementNode) { final RefElementNode refNode = (RefElementNode) node; if (refNode.hasDescriptorsUnder()) return null; final RefEntity element = refNode.getElement(); if (element == null || !element.isValid()) return null; final CommonProblemDescriptor problem = refNode.getProblem(); if (problem != null) { return navigate(problem); } if (element instanceof RefElement) { return getOpenFileDescriptor((RefElement) element); } } else if (node instanceof ProblemDescriptionNode) { if (!((ProblemDescriptionNode) node).isValid()) return null; return navigate(((ProblemDescriptionNode) node).getDescriptor()); } return null; } @Nullable private Navigatable navigate(final CommonProblemDescriptor descriptor) { return getSelectedNavigatable(descriptor); } @Override public String getNextOccurenceActionName() { return InspectionsBundle.message("inspection.action.go.next"); } @Override public String getPreviousOccurenceActionName() { return InspectionsBundle.message("inspection.actiongo.prev"); } }; } private void createActionsToolbar() { final JComponent leftActionsToolbar = createLeftActionsToolbar(); final JComponent rightActionsToolbar = createRightActionsToolbar(); JPanel westPanel = new JPanel(new BorderLayout()); westPanel.add(leftActionsToolbar, BorderLayout.WEST); westPanel.add(rightActionsToolbar, BorderLayout.EAST); add(westPanel, BorderLayout.WEST); } @SuppressWarnings({"NonStaticInitializer"}) private JComponent createRightActionsToolbar() { myIncludeAction = new AnAction(InspectionsBundle.message("inspections.result.view.include.action.text")) { { registerCustomShortcutSet(CommonShortcuts.INSERT, myTree); } @Override public void actionPerformed(AnActionEvent e) { final TreePath[] paths = myTree.getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { ((InspectionTreeNode) path.getLastPathComponent()).amnesty(); } } updateView(false); } @Override public void update(final AnActionEvent e) { final TreePath[] paths = myTree.getSelectionPaths(); e.getPresentation() .setEnabled( paths != null && paths.length > 0 && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS); } }; myExcludeAction = new AnAction(InspectionsBundle.message("inspections.result.view.exclude.action.text")) { { registerCustomShortcutSet(CommonShortcuts.getDelete(), myTree); } @Override public void actionPerformed(final AnActionEvent e) { final TreePath[] paths = myTree.getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { ((InspectionTreeNode) path.getLastPathComponent()).ignoreElement(); } } updateView(false); } @Override public void update(final AnActionEvent e) { final TreePath[] path = myTree.getSelectionPaths(); e.getPresentation().setEnabled(path != null && path.length > 0); } }; DefaultActionGroup specialGroup = new DefaultActionGroup(); specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupBySeverityAction(this)); specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupByDirectoryAction(this)); specialGroup.add( myGlobalInspectionContext.getUIOptions().createFilterResolvedItemsAction(this)); specialGroup.add( myGlobalInspectionContext.getUIOptions().createShowOutdatedProblemsAction(this)); specialGroup.add(myGlobalInspectionContext.getUIOptions().createShowDiffOnlyAction(this)); specialGroup.add(new EditSettingsAction()); specialGroup.add(new InvokeQuickFixAction(this)); specialGroup.add(new InspectionsOptionsToolbarAction(this)); return createToolbar(specialGroup); } private JComponent createLeftActionsToolbar() { final CommonActionsManager actionsManager = CommonActionsManager.getInstance(); DefaultActionGroup group = new DefaultActionGroup(); group.add(new RerunAction(this)); group.add(new CloseAction()); final TreeExpander treeExpander = new TreeExpander() { @Override public void expandAll() { TreeUtil.expandAll(myTree); } @Override public boolean canExpand() { return true; } @Override public void collapseAll() { TreeUtil.collapseAll(myTree, 0); } @Override public boolean canCollapse() { return true; } }; group.add(actionsManager.createExpandAllAction(treeExpander, myTree)); group.add(actionsManager.createCollapseAllAction(treeExpander, myTree)); group.add(actionsManager.createPrevOccurenceAction(getOccurenceNavigator())); group.add(actionsManager.createNextOccurenceAction(getOccurenceNavigator())); group.add(myGlobalInspectionContext.createToggleAutoscrollAction()); group.add(new ExportHTMLAction(this)); group.add(new ContextHelpAction(HELP_ID)); return createToolbar(group); } private static JComponent createToolbar(final DefaultActionGroup specialGroup) { return ActionManager.getInstance() .createActionToolbar(ActionPlaces.CODE_INSPECTION, specialGroup, false) .getComponent(); } @Override public void dispose() { mySplitter.dispose(); myBrowser.dispose(); myInspectionProfile = null; myDisposed = true; } private boolean isAutoScrollMode() { String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId(); return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE && (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION)); } @Nullable private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) { final VirtualFile[] file = new VirtualFile[1]; final int[] offset = new int[1]; ApplicationManager.getApplication() .runReadAction( new Runnable() { @Override public void run() { PsiElement psiElement = refElement.getElement(); if (psiElement != null) { final PsiFile containingFile = psiElement.getContainingFile(); if (containingFile != null) { file[0] = containingFile.getVirtualFile(); offset[0] = psiElement.getTextOffset(); } } else { file[0] = null; } } }); if (file[0] != null && file[0].isValid()) { return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]); } return null; } private void syncBrowser() { if (myTree.getSelectionModel().getSelectionCount() != 1) { myBrowser.showEmpty(); } else { TreePath pathSelected = myTree.getSelectionModel().getLeadSelectionPath(); if (pathSelected != null) { final InspectionTreeNode node = (InspectionTreeNode) pathSelected.getLastPathComponent(); if (node instanceof RefElementNode) { final RefElementNode refElementNode = (RefElementNode) node; final CommonProblemDescriptor problem = refElementNode.getProblem(); final RefEntity refSelected = refElementNode.getElement(); if (problem != null) { showInBrowser(refSelected, problem); } else { showInBrowser(refSelected); } } else if (node instanceof ProblemDescriptionNode) { final ProblemDescriptionNode problemNode = (ProblemDescriptionNode) node; showInBrowser(problemNode.getElement(), problemNode.getDescriptor()); } else if (node instanceof InspectionNode) { showInBrowser(((InspectionNode) node).getToolWrapper()); } else { myBrowser.showEmpty(); } } } } private void showInBrowser(final RefEntity refEntity) { Cursor currentCursor = getCursor(); setCursor(new Cursor(Cursor.WAIT_CURSOR)); myBrowser.showPageFor(refEntity); setCursor(currentCursor); } private void showInBrowser(@NotNull InspectionToolWrapper toolWrapper) { Cursor currentCursor = getCursor(); setCursor(new Cursor(Cursor.WAIT_CURSOR)); myBrowser.showDescription(toolWrapper); setCursor(currentCursor); } private void showInBrowser(final RefEntity refEntity, CommonProblemDescriptor descriptor) { Cursor currentCursor = getCursor(); setCursor(new Cursor(Cursor.WAIT_CURSOR)); myBrowser.showPageFor(refEntity, descriptor); setCursor(currentCursor); } @NotNull public InspectionNode addTool( @NotNull final InspectionToolWrapper toolWrapper, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) { String groupName = toolWrapper.getGroupDisplayName().isEmpty() ? InspectionProfileEntry.GENERAL_GROUP_NAME : toolWrapper.getGroupDisplayName(); InspectionTreeNode parentNode = getToolParentNode(groupName, errorLevel, groupedBySeverity); InspectionNode toolNode = new InspectionNode(toolWrapper); boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE; myProvider.appendToolNodeContent( myGlobalInspectionContext, toolNode, parentNode, showStructure); InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(toolWrapper); toolNode = presentation.createToolNode( myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure); ((DefaultInspectionToolPresentation) presentation).setToolNode(toolNode); registerActionShortcuts(presentation); return toolNode; } private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) { final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY); if (fixes != null) { for (QuickFixAction fix : fixes) { fix.registerCustomShortcutSet(fix.getShortcutSet(), this); } } } private void clearTree() { myTree.removeAllNodes(); mySeverityGroupNodes.clear(); } @Nullable public String getCurrentProfileName() { return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName(); } public InspectionProfile getCurrentProfile() { return myInspectionProfile; } public boolean update() { return updateView(true); } public boolean updateView(boolean strict) { if (!strict && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS) { myTree.repaint(); return false; } clearTree(); boolean resultsFound = buildTree(); myTree.restoreExpansionAndSelection(); return resultsFound; } private boolean buildTree() { InspectionProfile profile = myInspectionProfile; boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY; myGroups.clear(); final Map<String, Tools> tools = myGlobalInspectionContext.getTools(); boolean resultsFound = false; for (Tools currentTools : tools.values()) { InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool(); final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName()); for (ScopeToolState state : myProvider.getTools(currentTools)) { InspectionToolWrapper toolWrapper = state.getTool(); if (myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper)) { addTool( toolWrapper, ((InspectionProfileImpl) profile) .getErrorLevel(key, state.getScope(myProject), myProject), isGroupedBySeverity); resultsFound = true; } } } return resultsFound; } @NotNull private InspectionTreeNode getToolParentNode( @NotNull String groupName, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) { if (groupName.isEmpty()) { return getRelativeRootNode(groupedBySeverity, errorLevel); } ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel); if (map == null) { map = ConcurrencyUtil.cacheOrGet( myGroups, errorLevel, ContainerUtil.<String, InspectionGroupNode>newConcurrentMap()); } InspectionGroupNode group; if (groupedBySeverity) { group = map.get(groupName); } else { group = null; for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) { if ((group = groupMap.get(groupName)) != null) break; } } if (group == null) { group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName)); addChildNodeInEDT(getRelativeRootNode(groupedBySeverity, errorLevel), group); } return group; } @NotNull private InspectionTreeNode getRelativeRootNode( boolean isGroupedBySeverity, HighlightDisplayLevel level) { if (isGroupedBySeverity) { InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level); if (severityGroupNode == null) { InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level); severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode); if (severityGroupNode == newNode) { InspectionTreeNode root = myTree.getRoot(); addChildNodeInEDT(root, severityGroupNode); } } return severityGroupNode; } return myTree.getRoot(); } private void addChildNodeInEDT( @NotNull final DefaultMutableTreeNode root, @NotNull final MutableTreeNode severityGroupNode) { UIUtil.invokeLaterIfNeeded( new Runnable() { @Override public void run() { if (!myDisposed) { root.add(severityGroupNode); } } }); } private OccurenceNavigator getOccurenceNavigator() { return myOccurenceNavigator; } @Override public boolean hasNextOccurence() { return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence(); } @Override public boolean hasPreviousOccurence() { return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence(); } @Override public OccurenceInfo goNextOccurence() { return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null; } @Override public OccurenceInfo goPreviousOccurence() { return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null; } @Override public String getNextOccurenceActionName() { return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : ""; } @Override public String getPreviousOccurenceActionName() { return myOccurenceNavigator != null ? myOccurenceNavigator.getPreviousOccurenceActionName() : ""; } @NotNull public Project getProject() { return myProject; } @Override public Object getData(String dataId) { if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID; if (DATA_KEY.is(dataId)) return this; if (myTree == null) return null; TreePath[] paths = myTree.getSelectionPaths(); if (paths == null || paths.length == 0) return null; if (paths.length > 1) { if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { return collectPsiElements(); } return null; } TreePath path = paths[0]; InspectionTreeNode selectedNode = (InspectionTreeNode) path.getLastPathComponent(); if (selectedNode instanceof RefElementNode) { final RefElementNode refElementNode = (RefElementNode) selectedNode; RefEntity refElement = refElementNode.getElement(); if (refElement == null) return null; final RefEntity item = refElement.getRefManager().getRefinedElement(refElement); if (!item.isValid()) return null; PsiElement psiElement = item instanceof RefElement ? ((RefElement) item).getElement() : null; if (psiElement == null) return null; final CommonProblemDescriptor problem = refElementNode.getProblem(); if (problem != null) { if (problem instanceof ProblemDescriptor) { psiElement = ((ProblemDescriptor) problem).getPsiElement(); if (psiElement == null) return null; } else { return null; } } if (CommonDataKeys.NAVIGATABLE.is(dataId)) { return getSelectedNavigatable(problem, psiElement); } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { return psiElement.isValid() ? psiElement : null; } } else if (selectedNode instanceof ProblemDescriptionNode && CommonDataKeys.NAVIGATABLE.is(dataId)) { return getSelectedNavigatable(((ProblemDescriptionNode) selectedNode).getDescriptor()); } return null; } @Nullable private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) { return getSelectedNavigatable( descriptor, descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor) descriptor).getPsiElement() : null); } @Nullable private Navigatable getSelectedNavigatable( final CommonProblemDescriptor descriptor, final PsiElement psiElement) { if (descriptor instanceof ProblemDescriptorBase) { Navigatable navigatable = ((ProblemDescriptorBase) descriptor).getNavigatable(); if (navigatable != null) { return navigatable; } } if (psiElement == null || !psiElement.isValid()) return null; PsiFile containingFile = psiElement.getContainingFile(); VirtualFile virtualFile = containingFile == null ? null : containingFile.getVirtualFile(); if (virtualFile != null) { int startOffset = psiElement.getTextOffset(); if (descriptor instanceof ProblemDescriptorBase) { final TextRange textRange = ((ProblemDescriptorBase) descriptor).getTextRangeForNavigation(); if (textRange != null) { if (virtualFile instanceof VirtualFileWindow) { virtualFile = ((VirtualFileWindow) virtualFile).getDelegate(); } startOffset = textRange.getStartOffset(); } } return new OpenFileDescriptor(myProject, virtualFile, startOffset); } return null; } private PsiElement[] collectPsiElements() { RefEntity[] refElements = myTree.getSelectedElements(); List<PsiElement> psiElements = new ArrayList<PsiElement>(); for (RefEntity refElement : refElements) { PsiElement psiElement = refElement instanceof RefElement ? ((RefElement) refElement).getElement() : null; if (psiElement != null && psiElement.isValid()) { psiElements.add(psiElement); } } return PsiUtilCore.toPsiElementArray(psiElements); } private void popupInvoked(Component component, int x, int y) { final TreePath path = myTree.getLeadSelectionPath(); if (path == null) return; final DefaultActionGroup actions = new DefaultActionGroup(); final ActionManager actionManager = ActionManager.getInstance(); actions.add(actionManager.getAction(IdeActions.ACTION_EDIT_SOURCE)); actions.add(actionManager.getAction(IdeActions.ACTION_FIND_USAGES)); actions.add(myIncludeAction); actions.add(myExcludeAction); actions.addSeparator(); final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper(); if (toolWrapper != null) { final QuickFixAction[] quickFixes = myProvider.getQuickFixes(toolWrapper, myTree); if (quickFixes != null) { for (QuickFixAction quickFixe : quickFixes) { actions.add(quickFixe); } } final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName()); if (key == null) return; // e.g. DummyEntryPointsTool // options actions.addSeparator(); actions.add(new EditSettingsAction()); final List<AnAction> options = new InspectionsOptionsToolbarAction(this).createActions(); for (AnAction action : options) { actions.add(action); } } actions.addSeparator(); actions.add(actionManager.getAction(IdeActions.GROUP_VERSION_CONTROLS)); final ActionPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.CODE_INSPECTION, actions); menu.getComponent().show(component, x, y); } @NotNull public InspectionTree getTree() { return myTree; } @NotNull public GlobalInspectionContextImpl getGlobalInspectionContext() { return myGlobalInspectionContext; } @NotNull public InspectionRVContentProvider getProvider() { return myProvider; } public boolean isSingleToolInSelection() { return myTree != null && myTree.getSelectedToolWrapper() != null; } public boolean isRerun() { boolean rerun = myRerun; myRerun = false; return rerun; } private InspectionProfile guessProfileToSelect( final InspectionProjectProfileManager profileManager) { final Set<InspectionProfile> profiles = new HashSet<InspectionProfile>(); final RefEntity[] selectedElements = myTree.getSelectedElements(); for (RefEntity selectedElement : selectedElements) { if (selectedElement instanceof RefElement) { final RefElement refElement = (RefElement) selectedElement; final PsiElement element = refElement.getElement(); if (element != null) { profiles.add(profileManager.getInspectionProfile()); } } } if (profiles.isEmpty()) { return (InspectionProfile) profileManager.getProjectProfileImpl(); } return profiles.iterator().next(); } public boolean isProfileDefined() { return myInspectionProfile != null && myInspectionProfile.isEditable(); } public static void showPopup(AnActionEvent e, JBPopup popup) { final InputEvent event = e.getInputEvent(); if (event instanceof MouseEvent) { popup.showUnderneathOf(event.getComponent()); } else { popup.showInBestPositionFor(e.getDataContext()); } } public AnalysisScope getScope() { return myScope; } private class CloseAction extends AnAction implements DumbAware { private CloseAction() { super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel); } @Override public void actionPerformed(AnActionEvent e) { myGlobalInspectionContext.close(true); } } private class EditSettingsAction extends AnAction { private EditSettingsAction() { super( InspectionsBundle.message("inspection.action.edit.settings"), InspectionsBundle.message("inspection.action.edit.settings"), AllIcons.General.Settings); } @Override public void actionPerformed(AnActionEvent e) { final InspectionProjectProfileManager profileManager = InspectionProjectProfileManager.getInstance(myProject); final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper(); InspectionProfile inspectionProfile = myInspectionProfile; final boolean profileIsDefined = isProfileDefined(); if (!profileIsDefined) { inspectionProfile = guessProfileToSelect(profileManager); } if (toolWrapper != null) { final HighlightDisplayKey key = HighlightDisplayKey.find( toolWrapper.getShortName()); // do not search for dead code entry point tool if (key != null) { if (new EditInspectionToolsSettingsAction(key) .editToolSettings( myProject, (InspectionProfileImpl) inspectionProfile, profileIsDefined) && profileIsDefined) { updateCurrentProfile(); } return; } } if (EditInspectionToolsSettingsAction.editToolSettings( myProject, inspectionProfile, profileIsDefined, null) && profileIsDefined) { updateCurrentProfile(); } } } public void updateCurrentProfile() { final String name = myInspectionProfile.getName(); myInspectionProfile = (InspectionProfile) myInspectionProfile.getProfileManager().getProfile(name); } private class RerunAction extends AnAction { public RerunAction(JComponent comp) { super( InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"), AllIcons.Actions.Rerun); registerCustomShortcutSet(CommonShortcuts.getRerun(), comp); } @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(myScope.isValid()); } @Override public void actionPerformed(AnActionEvent e) { rerun(); } private void rerun() { myRerun = true; if (myScope.isValid()) { AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions()); myGlobalInspectionContext.doInspections(myScope); } } } }
/** @author max */ public class ChangesListView extends Tree implements TypeSafeDataProvider, AdvancedDnDSource { private ChangesListView.DropTarget myDropTarget; private DnDManager myDndManager; private ChangeListOwner myDragOwner; private final Project myProject; private TreeState myTreeState; private boolean myShowFlatten = false; private final CopyProvider myCopyProvider; @NonNls public static final String HELP_ID_KEY = "helpId"; @NonNls public static final String ourHelpId = "ideaInterface.changes"; @NonNls public static final DataKey<List<VirtualFile>> UNVERSIONED_FILES_DATA_KEY = DataKey.create("ChangeListView.UnversionedFiles"); @NonNls public static final DataKey<List<FilePath>> MISSING_FILES_DATA_KEY = DataKey.create("ChangeListView.MissingFiles"); @NonNls public static final DataKey<List<LocallyDeletedChange>> LOCALLY_DELETED_CHANGES = DataKey.create("ChangeListView.LocallyDeletedChanges"); @NonNls public static final DataKey<String> HELP_ID_DATA_KEY = DataKey.create(HELP_ID_KEY); private ActionGroup myMenuGroup; public ChangesListView(final Project project) { myProject = project; getModel().setRoot(ChangesBrowserNode.create(myProject, TreeModelBuilder.ROOT_NODE_VALUE)); setShowsRootHandles(true); setRootVisible(false); new TreeSpeedSearch(this, new NodeToTextConvertor()); SmartExpander.installOn(this); myCopyProvider = new TreeCopyProvider(this); new TreeLinkMouseListener(new ChangesBrowserNodeRenderer(myProject, false, false)) .installOn(this); } @Override public DefaultTreeModel getModel() { return (DefaultTreeModel) super.getModel(); } public void installDndSupport(ChangeListOwner owner) { myDragOwner = owner; myDropTarget = new DropTarget(); myDndManager = DnDManager.getInstance(); myDndManager.registerSource(this); myDndManager.registerTarget(myDropTarget, this); } @Override public void dispose() { if (myDropTarget != null) { myDndManager.unregisterSource(this); myDndManager.unregisterTarget(myDropTarget, this); myDropTarget = null; myDndManager = null; myDragOwner = null; } } private void storeState() { myTreeState = TreeState.createOn(this, (ChangesBrowserNode) getModel().getRoot()); } private void restoreState() { myTreeState.applyTo(this, (ChangesBrowserNode) getModel().getRoot()); } public boolean isShowFlatten() { return myShowFlatten; } public void setShowFlatten(final boolean showFlatten) { myShowFlatten = showFlatten; } public void updateModel( List<? extends ChangeList> changeLists, Trinity<List<VirtualFile>, Integer, Integer> unversionedFiles, final List<LocallyDeletedChange> locallyDeletedFiles, List<VirtualFile> modifiedWithoutEditing, MultiMap<String, VirtualFile> switchedFiles, @Nullable Map<VirtualFile, String> switchedRoots, @Nullable List<VirtualFile> ignoredFiles, final List<VirtualFile> lockedFolders, @Nullable final Map<VirtualFile, LogicalLock> logicallyLockedFiles) { TreeModelBuilder builder = new TreeModelBuilder(myProject, isShowFlatten()); final DefaultTreeModel model = builder.buildModel( changeLists, unversionedFiles, locallyDeletedFiles, modifiedWithoutEditing, switchedFiles, switchedRoots, ignoredFiles, lockedFolders, logicallyLockedFiles); storeState(); DefaultTreeModel oldModel = getModel(); setModel(model); setCellRenderer(new ChangesBrowserNodeRenderer(myProject, isShowFlatten(), true)); ChangesBrowserNode root = (ChangesBrowserNode) model.getRoot(); expandPath(new TreePath(root.getPath())); restoreState(); expandDefaultChangeList(oldModel, root); } private void expandDefaultChangeList(DefaultTreeModel oldModel, ChangesBrowserNode root) { if (((ChangesBrowserNode) oldModel.getRoot()).getCount() == 0 && TreeUtil.collectExpandedPaths(this).size() == 1) { TreeNode toExpand = null; for (int i = 0; i < root.getChildCount(); i++) { TreeNode node = root.getChildAt(i); if (node instanceof ChangesBrowserChangeListNode && node.getChildCount() > 0) { ChangeList object = ((ChangesBrowserChangeListNode) node).getUserObject(); if (object instanceof LocalChangeList) { if (((LocalChangeList) object).isDefault()) { toExpand = node; break; } } } } if (toExpand != null) { expandPath(new TreePath(new Object[] {root, toExpand})); } } } @Override public void calcData(DataKey key, DataSink sink) { if (key == VcsDataKeys.CHANGES) { sink.put(VcsDataKeys.CHANGES, getSelectedChanges()); } else if (key == VcsDataKeys.CHANGE_LEAD_SELECTION) { sink.put(VcsDataKeys.CHANGE_LEAD_SELECTION, getLeadSelection()); } else if (key == VcsDataKeys.CHANGE_LISTS) { sink.put(VcsDataKeys.CHANGE_LISTS, getSelectedChangeLists()); } else if (key == PlatformDataKeys.VIRTUAL_FILE_ARRAY) { sink.put(PlatformDataKeys.VIRTUAL_FILE_ARRAY, getSelectedFiles()); } else if (key == PlatformDataKeys.NAVIGATABLE) { final VirtualFile[] files = getSelectedFiles(); if (files.length == 1 && !files[0].isDirectory()) { sink.put(PlatformDataKeys.NAVIGATABLE, new OpenFileDescriptor(myProject, files[0], 0)); } } else if (key == PlatformDataKeys.NAVIGATABLE_ARRAY) { sink.put( PlatformDataKeys.NAVIGATABLE_ARRAY, ChangesUtil.getNavigatableArray(myProject, getSelectedFiles())); } else if (key == PlatformDataKeys.DELETE_ELEMENT_PROVIDER) { final TreePath[] paths = getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent(); if (!(node.getUserObject() instanceof ChangeList)) { sink.put(PlatformDataKeys.DELETE_ELEMENT_PROVIDER, new VirtualFileDeleteProvider()); break; } } } } else if (key == PlatformDataKeys.COPY_PROVIDER) { sink.put(PlatformDataKeys.COPY_PROVIDER, myCopyProvider); } else if (key == UNVERSIONED_FILES_DATA_KEY) { sink.put(UNVERSIONED_FILES_DATA_KEY, getSelectedUnversionedFiles()); } else if (key == VcsDataKeys.MODIFIED_WITHOUT_EDITING_DATA_KEY) { sink.put(VcsDataKeys.MODIFIED_WITHOUT_EDITING_DATA_KEY, getSelectedModifiedWithoutEditing()); } else if (key == LOCALLY_DELETED_CHANGES) { sink.put(LOCALLY_DELETED_CHANGES, getSelectedLocallyDeletedChanges()); } else if (key == MISSING_FILES_DATA_KEY) { sink.put(MISSING_FILES_DATA_KEY, getSelectedMissingFiles()); } else if (VcsDataKeys.HAVE_LOCALLY_DELETED == key) { sink.put(VcsDataKeys.HAVE_LOCALLY_DELETED, haveLocallyDeleted()); } else if (VcsDataKeys.HAVE_MODIFIED_WITHOUT_EDITING == key) { sink.put(VcsDataKeys.HAVE_MODIFIED_WITHOUT_EDITING, haveLocallyModified()); } else if (VcsDataKeys.HAVE_SELECTED_CHANGES == key) { sink.put(VcsDataKeys.HAVE_SELECTED_CHANGES, haveSelectedChanges()); } else if (key == HELP_ID_DATA_KEY) { sink.put(HELP_ID_DATA_KEY, ourHelpId); } else if (key == VcsDataKeys.CHANGES_IN_LIST_KEY) { final TreePath selectionPath = getSelectionPath(); if (selectionPath != null && selectionPath.getPathCount() > 1) { ChangesBrowserNode<?> firstNode = (ChangesBrowserNode) selectionPath.getPathComponent(1); if (firstNode instanceof ChangesBrowserChangeListNode) { final List<Change> list = firstNode.getAllChangesUnder(); sink.put(VcsDataKeys.CHANGES_IN_LIST_KEY, list); } } } } private List<VirtualFile> getSelectedUnversionedFiles() { return getSelectedVirtualFiles(ChangesBrowserNode.UNVERSIONED_FILES_TAG); } private List<VirtualFile> getSelectedModifiedWithoutEditing() { return getSelectedVirtualFiles(ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG); } private List<VirtualFile> getSelectedIgnoredFiles() { return getSelectedVirtualFiles(ChangesBrowserNode.IGNORED_FILES_TAG); } private List<VirtualFile> getSelectedVirtualFiles(final Object tag) { Set<VirtualFile> files = new HashSet<VirtualFile>(); final TreePath[] paths = getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { if (path.getPathCount() > 1) { ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1); if (tag == null || firstNode.getUserObject() == tag) { ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent(); files.addAll(node.getAllFilesUnder()); } } } } return new ArrayList<VirtualFile>(files); } private List<FilePath> getSelectedFilePaths(final Object tag) { Set<FilePath> files = new HashSet<FilePath>(); final TreePath[] paths = getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { if (path.getPathCount() > 1) { ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1); if (tag == null || firstNode.getUserObject() == tag) { ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent(); files.addAll(node.getAllFilePathsUnder()); } } } } return new ArrayList<FilePath>(files); } private List<LocallyDeletedChange> getSelectedLocallyDeletedChanges() { Set<LocallyDeletedChange> files = new HashSet<LocallyDeletedChange>(); final TreePath[] paths = getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { if (path.getPathCount() > 1) { ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1); if (firstNode.getUserObject() == TreeModelBuilder.LOCALLY_DELETED_NODE) { ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent(); final List<LocallyDeletedChange> objectsUnder = node.getAllObjectsUnder(LocallyDeletedChange.class); files.addAll(objectsUnder); } } } } return new ArrayList<LocallyDeletedChange>(files); } private List<FilePath> getSelectedMissingFiles() { return getSelectedFilePaths(TreeModelBuilder.LOCALLY_DELETED_NODE); } protected VirtualFile[] getSelectedFiles() { final Change[] changes = getSelectedChanges(); Collection<VirtualFile> files = new HashSet<VirtualFile>(); for (Change change : changes) { final ContentRevision afterRevision = change.getAfterRevision(); if (afterRevision != null) { final VirtualFile file = afterRevision.getFile().getVirtualFile(); if (file != null && file.isValid()) { files.add(file); } } } files.addAll(getSelectedVirtualFiles(null)); return VfsUtilCore.toVirtualFileArray(files); } protected boolean haveSelectedFileType(final Object tag) { final TreePath[] paths = getSelectionPaths(); if (paths != null) { for (TreePath path : paths) { if (path.getPathCount() > 1) { ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1); if ((tag == null || firstNode.getUserObject() == tag) && path.getPathCount() > 2) { return true; } } } } return false; } public boolean haveLocallyDeleted() { return haveSelectedFileType(TreeModelBuilder.LOCALLY_DELETED_NODE); } public boolean haveLocallyModified() { return haveSelectedFileType(ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG); } private Boolean haveSelectedChanges() { final TreePath[] paths = getSelectionPaths(); if (paths == null) return false; for (TreePath path : paths) { ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent(); if (node instanceof ChangesBrowserChangeNode) { return true; } else if (node instanceof ChangesBrowserChangeListNode) { final ChangesBrowserChangeListNode changeListNode = (ChangesBrowserChangeListNode) node; if (changeListNode.getChildCount() > 0) { return true; } } } return false; } private Change[] getLeadSelection() { final Set<Change> changes = new LinkedHashSet<Change>(); final TreePath[] paths = getSelectionPaths(); if (paths == null) return new Change[0]; for (TreePath path : paths) { ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent(); if (node instanceof ChangesBrowserChangeNode) { changes.add(((ChangesBrowserChangeNode) node).getUserObject()); } } return changes.toArray(new Change[changes.size()]); } @NotNull public Change[] getSelectedChanges() { Set<Change> changes = new LinkedHashSet<Change>(); final TreePath[] paths = getSelectionPaths(); if (paths == null) { return new Change[0]; } for (TreePath path : paths) { ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent(); changes.addAll(node.getAllChangesUnder()); } if (changes.isEmpty()) { final List<VirtualFile> selectedModifiedWithoutEditing = getSelectedModifiedWithoutEditing(); if (selectedModifiedWithoutEditing != null && !selectedModifiedWithoutEditing.isEmpty()) { for (VirtualFile file : selectedModifiedWithoutEditing) { AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(file); if (vcs == null) continue; final VcsCurrentRevisionProxy before = VcsCurrentRevisionProxy.create(file, myProject, vcs.getKeyInstanceMethod()); if (before != null) { ContentRevision afterRevision = new CurrentContentRevision(new FilePathImpl(file)); changes.add(new Change(before, afterRevision, FileStatus.HIJACKED)); } } } } return changes.toArray(new Change[changes.size()]); } @NotNull private ChangeList[] getSelectedChangeLists() { Set<ChangeList> lists = new HashSet<ChangeList>(); final TreePath[] paths = getSelectionPaths(); if (paths == null) return new ChangeList[0]; for (TreePath path : paths) { ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent(); final Object userObject = node.getUserObject(); if (userObject instanceof ChangeList) { lists.add((ChangeList) userObject); } } return lists.toArray(new ChangeList[lists.size()]); } public void setMenuActions(final ActionGroup menuGroup) { myMenuGroup = menuGroup; updateMenu(); editSourceRegistration(); } protected void editSourceRegistration() { EditSourceOnDoubleClickHandler.install(this); EditSourceOnEnterKeyHandler.install(this); } private void updateMenu() { PopupHandler.installPopupHandler( this, myMenuGroup, ActionPlaces.CHANGES_VIEW_POPUP, ActionManager.getInstance()); } @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"}) private static class DragImageFactory { private static void drawSelection(JTable table, int column, Graphics g, final int width) { int y = 0; final int[] rows = table.getSelectedRows(); final int height = table.getRowHeight(); for (int row : rows) { final TableCellRenderer renderer = table.getCellRenderer(row, column); final Component component = renderer.getTableCellRendererComponent( table, table.getValueAt(row, column), false, false, row, column); g.translate(0, y); component.setBounds(0, 0, width, height); boolean wasOpaque = false; if (component instanceof JComponent) { final JComponent j = (JComponent) component; if (j.isOpaque()) wasOpaque = true; j.setOpaque(false); } component.paint(g); if (wasOpaque) { ((JComponent) component).setOpaque(true); } y += height; g.translate(0, -y); } } private static void drawSelection(JTree tree, Graphics g, final int width) { int y = 0; final int[] rows = tree.getSelectionRows(); final int height = tree.getRowHeight(); for (int row : rows) { final TreeCellRenderer renderer = tree.getCellRenderer(); final Object value = tree.getPathForRow(row).getLastPathComponent(); if (value == null) continue; final Component component = renderer.getTreeCellRendererComponent(tree, value, false, false, false, row, false); if (component.getFont() == null) { component.setFont(tree.getFont()); } g.translate(0, y); component.setBounds(0, 0, width, height); boolean wasOpaque = false; if (component instanceof JComponent) { final JComponent j = (JComponent) component; if (j.isOpaque()) wasOpaque = true; j.setOpaque(false); } component.paint(g); if (wasOpaque) { ((JComponent) component).setOpaque(true); } y += height; g.translate(0, -y); } } public static Image createImage(final JTable table, int column) { final int height = Math.max(20, Math.min(100, table.getSelectedRowCount() * table.getRowHeight())); final int width = table.getColumnModel().getColumn(column).getWidth(); final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = (Graphics2D) image.getGraphics(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); drawSelection(table, column, g2, width); return image; } public static Image createImage(final JTree tree) { final TreeSelectionModel model = tree.getSelectionModel(); final TreePath[] paths = model.getSelectionPaths(); int count = 0; final List<ChangesBrowserNode> nodes = new ArrayList<ChangesBrowserNode>(); for (final TreePath path : paths) { final ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent(); if (!node.isLeaf()) { nodes.add(node); count += node.getCount(); } } for (TreePath path : paths) { final ChangesBrowserNode element = (ChangesBrowserNode) path.getLastPathComponent(); boolean child = false; for (final ChangesBrowserNode node : nodes) { if (node.isNodeChild(element)) { child = true; break; } } if (!child) { if (element.isLeaf()) count++; } else if (!element.isLeaf()) { count -= element.getCount(); } } final JLabel label = new JLabel(VcsBundle.message("changes.view.dnd.label", count)); label.setOpaque(true); label.setForeground(tree.getForeground()); label.setBackground(tree.getBackground()); label.setFont(tree.getFont()); label.setSize(label.getPreferredSize()); final BufferedImage image = new BufferedImage(label.getWidth(), label.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = (Graphics2D) image.getGraphics(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); label.paint(g2); g2.dispose(); return image; } } public class DropTarget implements DnDTarget { @Override public boolean update(DnDEvent aEvent) { aEvent.hideHighlighter(); aEvent.setDropPossible(false, ""); Object attached = aEvent.getAttachedObject(); if (!(attached instanceof ChangeListDragBean)) return false; final ChangeListDragBean dragBean = (ChangeListDragBean) attached; if (dragBean.getSourceComponent() != ChangesListView.this) return false; dragBean.setTargetNode(null); RelativePoint dropPoint = aEvent.getRelativePoint(); Point onTree = dropPoint.getPoint(ChangesListView.this); final TreePath dropPath = getPathForLocation(onTree.x, onTree.y); if (dropPath == null) return false; ChangesBrowserNode dropNode = (ChangesBrowserNode) dropPath.getLastPathComponent(); while (!((ChangesBrowserNode) dropNode.getParent()).isRoot()) { dropNode = (ChangesBrowserNode) dropNode.getParent(); } if (!dropNode.canAcceptDrop(dragBean)) { return false; } final Rectangle tableCellRect = getPathBounds(new TreePath(dropNode.getPath())); if (fitsInBounds(tableCellRect)) { aEvent.setHighlighting( new RelativeRectangle(ChangesListView.this, tableCellRect), DnDEvent.DropTargetHighlightingType.RECTANGLE); } aEvent.setDropPossible(true); dragBean.setTargetNode(dropNode); return false; } @Override public void drop(DnDEvent aEvent) { Object attached = aEvent.getAttachedObject(); if (!(attached instanceof ChangeListDragBean)) return; final ChangeListDragBean dragBean = (ChangeListDragBean) attached; final ChangesBrowserNode changesBrowserNode = dragBean.getTargetNode(); if (changesBrowserNode != null) { changesBrowserNode.acceptDrop(myDragOwner, dragBean); } } @Override public void cleanUpOnLeave() {} @Override public void updateDraggedImage(Image image, Point dropPoint, Point imageOffset) {} } private boolean fitsInBounds(final Rectangle rect) { final Container container = getParent(); if (container instanceof JViewport) { final Container scrollPane = container.getParent(); if (scrollPane instanceof JScrollPane) { final Rectangle rectangle = SwingUtilities.convertRectangle(this, rect, scrollPane.getParent()); return scrollPane.getBounds().contains(rectangle); } } return true; } private static class NodeToTextConvertor implements Convertor<TreePath, String> { @Override public String convert(final TreePath path) { ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent(); return node.getTextPresentation(); } } @Override public boolean canStartDragging(DnDAction action, Point dragOrigin) { return action == DnDAction.MOVE && (getSelectedChanges().length > 0 || !getSelectedUnversionedFiles().isEmpty() || !getSelectedIgnoredFiles().isEmpty()); } @Override public DnDDragStartBean startDragging(DnDAction action, Point dragOrigin) { return new DnDDragStartBean( new ChangeListDragBean( this, getSelectedChanges(), getSelectedUnversionedFiles(), getSelectedIgnoredFiles())); } @Override @Nullable public Pair<Image, Point> createDraggedImage(DnDAction action, Point dragOrigin) { final Image image = DragImageFactory.createImage(this); return new Pair<Image, Point>(image, new Point(-image.getWidth(null), -image.getHeight(null))); } @Override public void dragDropEnd() {} @Override public void dropActionChanged(final int gestureModifiers) {} @Override @NotNull public JComponent getComponent() { return this; } @Override public void processMouseEvent(final MouseEvent e) { if (MouseEvent.MOUSE_RELEASED == e.getID() && !isSelectionEmpty() && !e.isShiftDown() && !e.isControlDown() && !e.isMetaDown() && !e.isPopupTrigger()) { if (isOverSelection(e.getPoint())) { clearSelection(); final TreePath path = getPathForLocation(e.getPoint().x, e.getPoint().y); if (path != null) { setSelectionPath(path); } } } super.processMouseEvent(e); } @Override public boolean isOverSelection(final Point point) { return TreeUtil.isOverSelection(this, point); } @Override public void dropSelectionButUnderPoint(final Point point) { TreeUtil.dropSelectionButUnderPoint(this, point); } }
public abstract class ChangesBrowserBase<T> extends JPanel implements TypeSafeDataProvider, Disposable { private static final Logger LOG = Logger.getInstance(ChangesBrowserBase.class); // for backgroundable rollback to mark private boolean myDataIsDirty; protected final Class<T> myClass; protected final ChangesTreeList<T> myViewer; protected final JScrollPane myViewerScrollPane; protected ChangeList mySelectedChangeList; protected List<T> myChangesToDisplay; protected final Project myProject; private final boolean myCapableOfExcludingChanges; protected final JPanel myHeaderPanel; private JComponent myBottomPanel; private DefaultActionGroup myToolBarGroup; private String myToggleActionTitle = VcsBundle.message("commit.dialog.include.action.name"); private JComponent myDiffBottomComponent; public static DataKey<ChangesBrowserBase> DATA_KEY = DataKey.create("com.intellij.openapi.vcs.changes.ui.ChangesBrowser"); private AnAction myDiffAction; private final VirtualFile myToSelect; @NotNull private final DeleteProvider myDeleteProvider = new VirtualFileDeleteProvider(); public void setChangesToDisplay(final List<T> changes) { myChangesToDisplay = changes; myViewer.setChangesToDisplay(changes); } public void setDecorator(final ChangeNodeDecorator decorator) { myViewer.setChangeDecorator(decorator); } protected ChangesBrowserBase( final Project project, @NotNull List<T> changes, final boolean capableOfExcludingChanges, final boolean highlightProblems, @Nullable final Runnable inclusionListener, ChangesBrowser.MyUseCase useCase, @Nullable VirtualFile toSelect, Class<T> clazz) { super(new BorderLayout()); setFocusable(false); myClass = clazz; myDataIsDirty = false; myProject = project; myCapableOfExcludingChanges = capableOfExcludingChanges; myToSelect = toSelect; ChangeNodeDecorator decorator = ChangesBrowser.MyUseCase.LOCAL_CHANGES.equals(useCase) ? RemoteRevisionsCache.getInstance(myProject).getChangesNodeDecorator() : null; myViewer = new ChangesTreeList<T>( myProject, changes, capableOfExcludingChanges, highlightProblems, inclusionListener, decorator) { protected DefaultTreeModel buildTreeModel( final List<T> changes, ChangeNodeDecorator changeNodeDecorator) { return ChangesBrowserBase.this.buildTreeModel( changes, changeNodeDecorator, isShowFlatten()); } protected List<T> getSelectedObjects(final ChangesBrowserNode<T> node) { return ChangesBrowserBase.this.getSelectedObjects(node); } @Nullable protected T getLeadSelectedObject(final ChangesBrowserNode node) { return ChangesBrowserBase.this.getLeadSelectedObject(node); } @Override public void setScrollPaneBorder(Border border) { myViewerScrollPane.setBorder(border); } }; myViewerScrollPane = ScrollPaneFactory.createScrollPane(myViewer); myHeaderPanel = new JPanel(new BorderLayout()); } protected void init() { add(myViewerScrollPane, BorderLayout.CENTER); myHeaderPanel.add(createToolbar(), BorderLayout.CENTER); add(myHeaderPanel, BorderLayout.NORTH); myBottomPanel = new JPanel(new BorderLayout()); add(myBottomPanel, BorderLayout.SOUTH); myViewer.installPopupHandler(myToolBarGroup); myViewer.setDoubleClickHandler(getDoubleClickHandler()); } @NotNull protected abstract DefaultTreeModel buildTreeModel( final List<T> changes, ChangeNodeDecorator changeNodeDecorator, boolean showFlatten); @NotNull protected abstract List<T> getSelectedObjects(@NotNull ChangesBrowserNode<T> node); @Nullable protected abstract T getLeadSelectedObject(@NotNull ChangesBrowserNode node); @NotNull protected Runnable getDoubleClickHandler() { return new Runnable() { public void run() { showDiff(); } }; } protected void setInitialSelection( final List<? extends ChangeList> changeLists, @NotNull List<T> changes, final ChangeList initialListSelection) { mySelectedChangeList = initialListSelection; } public void dispose() {} public void addToolbarAction(AnAction action) { myToolBarGroup.add(action); } public void setDiffBottomComponent(JComponent diffBottomComponent) { myDiffBottomComponent = diffBottomComponent; } public void setToggleActionTitle(final String toggleActionTitle) { myToggleActionTitle = toggleActionTitle; } public JPanel getHeaderPanel() { return myHeaderPanel; } public ChangesTreeList<T> getViewer() { return myViewer; } @NotNull public JScrollPane getViewerScrollPane() { return myViewerScrollPane; } public void calcData(DataKey key, DataSink sink) { if (key == VcsDataKeys.CHANGES) { List<Change> list = getSelectedChanges(); if (list.isEmpty()) list = getAllChanges(); sink.put(VcsDataKeys.CHANGES, list.toArray(new Change[list.size()])); } else if (key == VcsDataKeys.CHANGES_SELECTION) { sink.put(VcsDataKeys.CHANGES_SELECTION, getChangesSelection()); } else if (key == VcsDataKeys.CHANGE_LISTS) { sink.put(VcsDataKeys.CHANGE_LISTS, getSelectedChangeLists()); } else if (key == VcsDataKeys.CHANGE_LEAD_SELECTION) { final Change highestSelection = ObjectUtils.tryCast(myViewer.getHighestLeadSelection(), Change.class); sink.put( VcsDataKeys.CHANGE_LEAD_SELECTION, (highestSelection == null) ? new Change[] {} : new Change[] {highestSelection}); } else if (key == CommonDataKeys.VIRTUAL_FILE_ARRAY) { sink.put(CommonDataKeys.VIRTUAL_FILE_ARRAY, getSelectedFiles().toArray(VirtualFile[]::new)); } else if (key == CommonDataKeys.NAVIGATABLE_ARRAY) { sink.put( CommonDataKeys.NAVIGATABLE_ARRAY, getNavigatableArray(myProject, getSelectedFiles())); } else if (VcsDataKeys.IO_FILE_ARRAY.equals(key)) { sink.put(VcsDataKeys.IO_FILE_ARRAY, getSelectedIoFiles()); } else if (key == DATA_KEY) { sink.put(DATA_KEY, this); } else if (VcsDataKeys.SELECTED_CHANGES_IN_DETAILS.equals(key)) { final List<Change> selectedChanges = getSelectedChanges(); sink.put( VcsDataKeys.SELECTED_CHANGES_IN_DETAILS, selectedChanges.toArray(new Change[selectedChanges.size()])); } else if (UNVERSIONED_FILES_DATA_KEY.equals(key)) { sink.put( UNVERSIONED_FILES_DATA_KEY, getVirtualFiles(myViewer.getSelectionPaths(), UNVERSIONED_FILES_TAG)); } else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.equals(key)) { sink.put(PlatformDataKeys.DELETE_ELEMENT_PROVIDER, myDeleteProvider); } } public void select(List<T> changes) { myViewer.select(changes); } public JComponent getBottomPanel() { return myBottomPanel; } private class ToggleChangeAction extends CheckboxAction { public ToggleChangeAction() { super(myToggleActionTitle); } public boolean isSelected(AnActionEvent e) { T change = ObjectUtils.tryCast(e.getData(VcsDataKeys.CURRENT_CHANGE), myClass); if (change == null) return false; return myViewer.isIncluded(change); } public void setSelected(AnActionEvent e, boolean state) { T change = ObjectUtils.tryCast(e.getData(VcsDataKeys.CURRENT_CHANGE), myClass); if (change == null) return; if (state) { myViewer.includeChange(change); } else { myViewer.excludeChange(change); } } } protected void showDiffForChanges(Change[] changesArray, final int indexInSelection) { final ShowDiffContext context = new ShowDiffContext(isInFrame() ? DiffDialogHints.FRAME : DiffDialogHints.MODAL); context.addActions(createDiffActions()); if (myDiffBottomComponent != null) { context.putChainContext(DiffUserDataKeysEx.BOTTOM_PANEL, myDiffBottomComponent); } updateDiffContext(context); ShowDiffAction.showDiffForChange( myProject, Arrays.asList(changesArray), indexInSelection, context); } protected void updateDiffContext(@NotNull ShowDiffContext context) {} private boolean canShowDiff() { return ShowDiffAction.canShowDiff(myProject, getChangesSelection().getChanges()); } private void showDiff() { ChangesSelection selection = getChangesSelection(); List<Change> changes = selection.getChanges(); Change[] changesArray = changes.toArray(new Change[changes.size()]); showDiffForChanges(changesArray, selection.getIndex()); afterDiffRefresh(); } @NotNull protected ChangesSelection getChangesSelection() { final Change leadSelection = ObjectUtils.tryCast(myViewer.getLeadSelection(), Change.class); List<Change> changes = getSelectedChanges(); if (changes.size() < 2) { List<Change> allChanges = getAllChanges(); if (allChanges.size() > 1 || changes.isEmpty()) { changes = allChanges; } } if (leadSelection != null) { int indexInSelection = changes.indexOf(leadSelection); if (indexInSelection == -1) { return new ChangesSelection(Collections.singletonList(leadSelection), 0); } else { return new ChangesSelection(changes, indexInSelection); } } else { return new ChangesSelection(changes, 0); } } protected void afterDiffRefresh() {} private static boolean isInFrame() { return ModalityState.current().equals(ModalityState.NON_MODAL); } protected List<AnAction> createDiffActions() { List<AnAction> actions = new ArrayList<>(); if (myCapableOfExcludingChanges) { actions.add(new ToggleChangeAction()); } return actions; } public void rebuildList() { myViewer.setChangesToDisplay(getCurrentDisplayedObjects(), myToSelect); } public void setAlwayExpandList(final boolean value) { myViewer.setAlwaysExpandList(value); } @NotNull protected JComponent createToolbar() { DefaultActionGroup toolbarGroups = new DefaultActionGroup(); myToolBarGroup = new DefaultActionGroup(); toolbarGroups.add(myToolBarGroup); buildToolBar(myToolBarGroup); toolbarGroups.addSeparator(); DefaultActionGroup treeActionsGroup = new DefaultActionGroup(); toolbarGroups.add(treeActionsGroup); for (AnAction action : myViewer.getTreeActions()) { treeActionsGroup.add(action); } ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLBAR, toolbarGroups, true); toolbar.setTargetComponent(this); return toolbar.getComponent(); } protected void buildToolBar(final DefaultActionGroup toolBarGroup) { myDiffAction = new DumbAwareAction() { public void update(AnActionEvent e) { e.getPresentation().setEnabled(canShowDiff()); } public void actionPerformed(AnActionEvent e) { showDiff(); } }; ActionUtil.copyFrom(myDiffAction, "ChangesView.Diff"); myDiffAction.registerCustomShortcutSet(myViewer, null); toolBarGroup.add(myDiffAction); } @NotNull public Set<AbstractVcs> getAffectedVcses() { return ChangesUtil.getAffectedVcses(getCurrentDisplayedChanges(), myProject); } @NotNull public abstract List<Change> getCurrentIncludedChanges(); @NotNull public List<Change> getCurrentDisplayedChanges() { return mySelectedChangeList != null ? ContainerUtil.newArrayList(mySelectedChangeList.getChanges()) : Collections.emptyList(); } @NotNull public abstract List<T> getCurrentDisplayedObjects(); @NotNull public List<VirtualFile> getIncludedUnversionedFiles() { return Collections.emptyList(); } public int getUnversionedFilesCount() { return 0; } public ChangeList getSelectedChangeList() { return mySelectedChangeList; } public JComponent getPreferredFocusedComponent() { return myViewer.getPreferredFocusedComponent(); } private ChangeList[] getSelectedChangeLists() { if (mySelectedChangeList != null) { return new ChangeList[] {mySelectedChangeList}; } return null; } private File[] getSelectedIoFiles() { final List<Change> changes = getSelectedChanges(); final List<File> files = new ArrayList<>(); for (Change change : changes) { final ContentRevision afterRevision = change.getAfterRevision(); if (afterRevision != null) { final FilePath file = afterRevision.getFile(); final File ioFile = file.getIOFile(); files.add(ioFile); } } return files.toArray(new File[files.size()]); } @NotNull public abstract List<Change> getSelectedChanges(); @NotNull public abstract List<Change> getAllChanges(); @NotNull protected Stream<VirtualFile> getSelectedFiles() { return Stream.concat( getAfterRevisionsFiles(getSelectedChanges().stream()), getVirtualFiles(myViewer.getSelectionPaths(), null)) .distinct(); } public AnAction getDiffAction() { return myDiffAction; } public boolean isDataIsDirty() { return myDataIsDirty; } public void setDataIsDirty(boolean dataIsDirty) { myDataIsDirty = dataIsDirty; } public void setSelectionMode(@JdkConstants.TreeSelectionMode int mode) { myViewer.setSelectionMode(mode); } @Contract(pure = true) @NotNull protected static <T> List<Change> findChanges(@NotNull Collection<T> items) { return ContainerUtil.findAll(items, Change.class); } static boolean isUnderUnversioned(@NotNull ChangesBrowserNode node) { return isUnderTag(new TreePath(node.getPath()), UNVERSIONED_FILES_TAG); } }