vcs: new ChangesBrowser implementation

* simplify available API
* completely move data manipulation to the implementations
* prefer listeners to the explicit "refresh changes" callbacks
This commit is contained in:
Aleksey Pivovarov
2017-10-03 18:13:41 +03:00
committed by Aleksey Pivovarov
parent 89e6dda095
commit d489bfccc6
12 changed files with 1203 additions and 385 deletions
@@ -27,9 +27,9 @@ import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode;
import com.intellij.openapi.vcs.changes.ui.ChangesListView;
import com.intellij.openapi.vcs.changes.ui.OldChangesBrowserBase;
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ScrollPaneFactory;
@@ -179,11 +179,11 @@ abstract class SpecificFilesViewDialog extends DialogWrapper {
@NotNull
protected abstract List<VirtualFile> getFiles();
protected static ChangesBrowserBase getBrowserBase(@NotNull ChangesListView view) {
return ChangesBrowserBase.DATA_KEY.getData(DataManager.getInstance().getDataContext(view));
protected static OldChangesBrowserBase getBrowserBase(@NotNull ChangesListView view) {
return OldChangesBrowserBase.DATA_KEY.getData(DataManager.getInstance().getDataContext(view));
}
public static void refreshChanges(@NotNull Project project, @Nullable ChangesBrowserBase browser) {
public static void refreshChanges(@NotNull Project project, @Nullable OldChangesBrowserBase browser) {
if (browser != null) {
ChangeListManager.getInstance(project)
.invokeAfterUpdate(browser::rebuildList, InvokeAfterUpdateMode.SYNCHRONOUS_CANCELLABLE, "Delete files", null);
@@ -21,8 +21,8 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase;
import com.intellij.openapi.vcs.changes.ui.IgnoreUnversionedDialog;
import com.intellij.openapi.vcs.changes.ui.OldChangesBrowserBase;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
@@ -39,7 +39,7 @@ public class IgnoreUnversionedAction extends AnAction {
if (!ChangeListManager.getInstance(project).isFreezedWithNotification(null)) {
List<VirtualFile> files = e.getRequiredData(UNVERSIONED_FILES_DATA_KEY).collect(Collectors.toList());
ChangesBrowserBase<?> browser = e.getData(ChangesBrowserBase.DATA_KEY);
OldChangesBrowserBase<?> browser = e.getData(OldChangesBrowserBase.DATA_KEY);
Runnable callback = browser == null ? null : () -> {
browser.rebuildList();
//noinspection unchecked
@@ -25,7 +25,7 @@ import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase;
import com.intellij.openapi.vcs.changes.ui.OldChangesBrowserBase;
import com.intellij.openapi.vcs.changes.ui.RollbackChangesDialog;
import com.intellij.vcsUtil.RollbackUtil;
@@ -43,7 +43,7 @@ public class RollbackDialogAction extends AnAction implements DumbAware {
FileDocumentManager.getInstance().saveAllDocuments();
Change[] changes = e.getData(VcsDataKeys.CHANGES);
Project project = e.getData(CommonDataKeys.PROJECT);
final ChangesBrowserBase browser = e.getData(ChangesBrowserBase.DATA_KEY);
final OldChangesBrowserBase browser = e.getData(OldChangesBrowserBase.DATA_KEY);
if (browser != null) {
browser.setDataIsDirty(true);
}
@@ -28,8 +28,8 @@ import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManagerImpl;
import com.intellij.openapi.vcs.changes.LocalChangeList;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase;
import com.intellij.openapi.vcs.changes.ui.ChangesListView;
import com.intellij.openapi.vcs.changes.ui.OldChangesBrowserBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
@@ -59,13 +59,13 @@ public class ScheduleForAdditionAction extends AnAction implements DumbAware {
Project project = e.getRequiredData(CommonDataKeys.PROJECT);
List<VirtualFile> unversionedFiles = getUnversionedFiles(e, project).collect(Collectors.toList());
addUnversioned(project, unversionedFiles, this::isStatusForAddition, e.getData(ChangesBrowserBase.DATA_KEY));
addUnversioned(project, unversionedFiles, this::isStatusForAddition, e.getData(OldChangesBrowserBase.DATA_KEY));
}
public static boolean addUnversioned(@NotNull Project project,
@NotNull List<VirtualFile> files,
@NotNull Condition<FileStatus> unversionedFileCondition,
@Nullable ChangesBrowserBase browser) {
@Nullable OldChangesBrowserBase browser) {
boolean result = true;
if (!files.isEmpty()) {
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.tree.DefaultTreeModel;
import java.util.List;
public class ChangesBrowser extends ChangesBrowserBase<Change> {
public class ChangesBrowser extends OldChangesBrowserBase<Change> {
public ChangesBrowser(@NotNull Project project,
@Nullable List<? extends ChangeList> changeLists,
@@ -16,182 +16,144 @@
package com.intellij.openapi.vcs.changes.ui;
import com.intellij.diff.DiffDialogHints;
import com.intellij.diff.util.DiffUserDataKeysEx;
import com.intellij.ide.DeleteProvider;
import com.intellij.openapi.Disposable;
import com.intellij.diff.DiffManager;
import com.intellij.diff.chains.DiffRequestChain;
import com.intellij.diff.util.DiffUserDataKeys;
import com.intellij.openapi.ListSelection;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.ex.CheckboxAction;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.actions.VirtualFileDeleteProvider;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeList;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.changes.RemoteRevisionsCache;
import com.intellij.openapi.vcs.changes.actions.diff.ShowDiffAction;
import com.intellij.openapi.vcs.changes.actions.diff.ShowDiffContext;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import static com.intellij.openapi.vcs.changes.ChangesUtil.getFiles;
import static com.intellij.openapi.vcs.changes.ChangesUtil.getNavigatableArray;
import static com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode.UNVERSIONED_FILES_TAG;
import static com.intellij.openapi.vcs.changes.ui.ChangesListView.*;
public abstract class ChangesBrowserBase extends JPanel implements DataProvider {
public static final DataKey<ChangesBrowserBase> DATA_KEY =
DataKey.create("com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase");
public abstract class ChangesBrowserBase<T> extends JPanel implements TypeSafeDataProvider, Disposable {
private static final Logger LOG = Logger.getInstance(ChangesBrowserBase.class);
@NotNull protected final Project myProject;
// 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");
protected final ChangesTree myViewer;
private JComponent myDiffBottomComponent;
private final DefaultActionGroup myToolBarGroup = new DefaultActionGroup();
private final ActionToolbar myToolbar;
private final JScrollPane myViewerScrollPane;
private final AnAction myShowDiffAction;
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();
@Nullable private Runnable myInclusionChangedListener;
public void setChangesToDisplay(final List<T> changes) {
myChangesToDisplay = changes;
myViewer.setChangesToDisplay(changes);
}
public void setDecorator(final ChangeNodeDecorator decorator) {
myViewer.setChangeDecorator(decorator);
}
protected ChangesBrowserBase(@NotNull final Project project,
@NotNull List<T> changes,
final boolean capableOfExcludingChanges,
final boolean highlightProblems,
@Nullable final Runnable inclusionListener,
@NotNull ChangesBrowser.MyUseCase useCase,
@Nullable VirtualFile toSelect,
@NotNull Class<T> clazz) {
super(new BorderLayout());
setFocusable(false);
myClass = clazz;
myDataIsDirty = false;
protected ChangesBrowserBase(@NotNull Project project,
boolean showCheckboxes,
boolean highlightProblems) {
myProject = project;
myCapableOfExcludingChanges = capableOfExcludingChanges;
myToSelect = toSelect;
myViewer = new MyChangesTreeList(this, project, showCheckboxes, highlightProblems);
ChangeNodeDecorator decorator =
ChangesBrowser.MyUseCase.LOCAL_CHANGES.equals(useCase) ? RemoteRevisionsCache.getInstance(myProject).getChangesNodeDecorator() : null;
DefaultActionGroup toolbarGroups = new DefaultActionGroup();
toolbarGroups.add(myToolBarGroup);
toolbarGroups.addSeparator();
toolbarGroups.addAll(myViewer.getTreeActions());
myToolbar = ActionManager.getInstance().createActionToolbar("ChangesBrowser", toolbarGroups, true);
myToolbar.setTargetComponent(this);
myViewer.installPopupHandler(myToolBarGroup);
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<?> 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());
myShowDiffAction = new MyShowDiffAction();
}
protected void init() {
setLayout(new BorderLayout());
setFocusable(false);
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.add(myToolbar.getComponent(), BorderLayout.CENTER);
JComponent headerPanel = createHeaderPanel();
if (headerPanel != null) topPanel.add(headerPanel, BorderLayout.EAST);
add(topPanel, BorderLayout.NORTH);
add(myViewerScrollPane, BorderLayout.CENTER);
myHeaderPanel.add(createToolbar(), BorderLayout.CENTER);
add(myHeaderPanel, BorderLayout.NORTH);
myBottomPanel = new JPanel(new BorderLayout());
add(myBottomPanel, BorderLayout.SOUTH);
myToolBarGroup.addAll(createToolbarActions());
myViewer.installPopupHandler(myToolBarGroup);
myViewer.setDoubleClickHandler(getDoubleClickHandler());
myShowDiffAction.registerCustomShortcutSet(this, null);
}
@NotNull
protected abstract DefaultTreeModel buildTreeModel(final List<T> changes, ChangeNodeDecorator changeNodeDecorator, boolean showFlatten);
@NotNull
protected abstract List<T> getSelectedObjects(@NotNull ChangesBrowserNode<?> node);
protected abstract DefaultTreeModel buildTreeModel(boolean showFlatten);
@Nullable
protected abstract T getLeadSelectedObject(@NotNull ChangesBrowserNode<?> node);
protected ChangeDiffRequestChain.Producer getDiffRequestProducer(@NotNull Object userObject) {
if (userObject instanceof Change) {
return ChangeDiffRequestProducer.create(myProject, (Change)userObject);
}
return null;
}
@Nullable
protected JComponent createHeaderPanel() {
return null;
}
@NotNull
protected Runnable getDoubleClickHandler() {
return () -> showDiff();
protected List<AnAction> createToolbarActions() {
return ContainerUtil.list(
myShowDiffAction
);
}
protected void setInitialSelection(final List<? extends ChangeList> changeLists,
@NotNull List<T> changes,
final ChangeList initialListSelection) {
mySelectedChangeList = initialListSelection;
@NotNull
protected List<AnAction> createDiffActions() {
return ContainerUtil.list(
);
}
public void dispose() {
protected void onDoubleClick() {
showDiff();
}
public void addToolbarAction(AnAction action) {
protected void onIncludedChanged() {
if (myInclusionChangedListener != null) myInclusionChangedListener.run();
}
public void selectEntries(@NotNull Collection<?> changes) {
myViewer.setSelectedChanges(changes);
}
public void setInclusionChangedListener(@Nullable Runnable value) {
myInclusionChangedListener = value;
}
public void addToolbarAction(@NotNull AnAction action) {
myToolBarGroup.add(action);
}
public void setDiffBottomComponent(JComponent diffBottomComponent) {
myDiffBottomComponent = diffBottomComponent;
@NotNull
public JComponent getPreferredFocusedComponent() {
return myViewer.getPreferredFocusedComponent();
}
public void setToggleActionTitle(final String toggleActionTitle) {
myToggleActionTitle = toggleActionTitle;
}
public JPanel getHeaderPanel() {
return myHeaderPanel;
}
public ChangesTreeList<T> getViewer() {
return myViewer;
@NotNull
public ActionToolbar getToolbar() {
return myToolbar;
}
@NotNull
@@ -199,271 +161,78 @@ public abstract class ChangesBrowserBase<T> extends JPanel implements TypeSafeDa
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);
}
@NotNull
public ChangesTree getViewer() {
return myViewer;
}
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);
}
@Nullable
@Override
public Object getData(String dataId) {
if (DATA_KEY.is(dataId)) {
return this;
}
return VcsTreeModelData.getData(myProject, myViewer, dataId);
}
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().getList());
}
private void showDiff() {
ListSelection<Change> selection = getChangesSelection();
List<Change> changes = selection.getList();
Change[] changesArray = changes.toArray(new Change[changes.size()]);
showDiffForChanges(changesArray, selection.getSelectedIndex());
afterDiffRefresh();
}
@NotNull
protected ListSelection<Change> 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 && !changes.contains(leadSelection)) {
return ListSelection.createSingleton(leadSelection);
}
return ListSelection.create(changes, leadSelection);
}
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("ChangesBrowser", 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() || e.getInputEvent() instanceof KeyEvent);
}
public void actionPerformed(AnActionEvent e) {
showDiff();
}
};
ActionUtil.copyFrom(myDiffAction, IdeActions.ACTION_SHOW_DIFF_COMMON);
myDiffAction.registerCustomShortcutSet(myViewer, null);
toolBarGroup.add(myDiffAction);
}
@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(
getFiles(getSelectedChanges().stream()),
getVirtualFiles(myViewer.getSelectionPaths(), null)
).distinct();
}
public AnAction getDiffAction() {
return myDiffAction;
return myShowDiffAction;
}
public boolean isDataIsDirty() {
return myDataIsDirty;
public boolean canShowDiff() {
ListSelection<Object> selection = VcsTreeModelData.getListSelection(myViewer);
return ContainerUtil.exists(selection.getList(), entry -> getDiffRequestProducer(entry) != null);
}
public void setDataIsDirty(boolean dataIsDirty) {
myDataIsDirty = dataIsDirty;
public void showDiff() {
ListSelection<Object> selection = VcsTreeModelData.getListSelection(myViewer);
ListSelection<ChangeDiffRequestChain.Producer> producers = selection.map(this::getDiffRequestProducer);
DiffRequestChain chain = new ChangeDiffRequestChain(producers.getList(), producers.getSelectedIndex());
updateDiffContext(chain);
DiffManager.getInstance().showDiff(myProject, chain, new DiffDialogHints(null, this));
}
public void setSelectionMode(@JdkConstants.TreeSelectionMode int mode) {
myViewer.setSelectionMode(mode);
protected void updateDiffContext(@NotNull DiffRequestChain chain) {
chain.putUserData(DiffUserDataKeys.CONTEXT_ACTIONS, createDiffActions());
}
@Contract(pure = true)
@NotNull
protected static <T> List<Change> findChanges(@NotNull Collection<T> items) {
return ContainerUtil.findAll(items, Change.class);
private class MyShowDiffAction extends DumbAwareAction {
public MyShowDiffAction() {
ActionUtil.copyFrom(this, IdeActions.ACTION_SHOW_DIFF_COMMON);
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(canShowDiff() || e.getInputEvent() instanceof KeyEvent);
}
@Override
public void actionPerformed(AnActionEvent e) {
showDiff();
}
}
static boolean isUnderUnversioned(@NotNull ChangesBrowserNode node) {
return isUnderTag(new TreePath(node.getPath()), UNVERSIONED_FILES_TAG);
private static class MyChangesTreeList extends ChangesTree {
@NotNull private final ChangesBrowserBase myViewer;
public MyChangesTreeList(@NotNull ChangesBrowserBase viewer,
@NotNull Project project,
boolean showCheckboxes,
boolean highlightProblems) {
super(project, showCheckboxes, highlightProblems);
myViewer = viewer;
setDoubleClickHandler(myViewer::onDoubleClick);
setInclusionListener(myViewer::onIncludedChanged);
}
@Override
public void rebuildTree() {
DefaultTreeModel newModel = myViewer.buildTreeModel(isShowFlatten());
updateTreeModel(newModel);
}
}
}
@@ -110,7 +110,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj
@NotNull private final CommitOptionsPanel myCommitOptions;
@NotNull private final CommitContext myCommitContext;
@NotNull private final ChangeInfoCalculator myChangesInfoCalculator;
@NotNull private final ChangesBrowserBase<?> myBrowser;
@NotNull private final OldChangesBrowserBase<?> myBrowser;
@NotNull private final MyChangeProcessor myDiffDetails;
@NotNull private final CommitMessage myCommitMessageArea;
@NotNull private final CommitLegendPanel myLegend;
@@ -54,7 +54,7 @@ import static com.intellij.openapi.util.text.StringUtil.capitalize;
import static com.intellij.openapi.util.text.StringUtil.shortenTextWithEllipsis;
import static java.util.stream.Collectors.toList;
public class MultipleChangeListBrowser extends ChangesBrowserBase<Object> {
public class MultipleChangeListBrowser extends OldChangesBrowserBase<Object> {
@NotNull private final ChangeListChooser myChangeListChooser;
@NotNull final ChangeListListener myChangeListListener = new MyChangeListListener();
@@ -0,0 +1,395 @@
/*
* Copyright 2000-2017 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.intellij.openapi.vcs.changes.ui;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.DeleteProvider;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.CheckboxAction;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.fileChooser.actions.VirtualFileDeleteProvider;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsConfiguration;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.actions.MoveChangesToAnotherListAction;
import com.intellij.openapi.vcs.changes.actions.RollbackDialogAction;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.CollectionComboBoxModel;
import com.intellij.ui.ColoredListCellRenderer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultTreeModel;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.intellij.openapi.util.text.StringUtil.shortenTextWithEllipsis;
import static com.intellij.openapi.vcs.changes.ui.ChangesListView.UNVERSIONED_FILES_DATA_KEY;
public class MultipleLocalChangeListsBrowser extends ChangesBrowserBase implements Disposable {
private final boolean myEnableUnversioned;
@NotNull private final ChangeListChooser myChangeListChooser;
@NotNull private final DeleteProvider myDeleteProvider = new VirtualFileDeleteProvider();
private final List<Change> myChanges = new ArrayList<>();
private final List<VirtualFile> myUnversioned = new ArrayList<>();
private boolean myHasHiddenUnversioned;
@NotNull private LocalChangeList myChangeList;
public MultipleLocalChangeListsBrowser(@NotNull Project project,
boolean showCheckboxes,
boolean highlightProblems,
boolean enableUnversioned) {
super(project, showCheckboxes, highlightProblems);
myEnableUnversioned = enableUnversioned;
myChangeList = ChangeListManager.getInstance(project).getDefaultChangeList();
myChangeListChooser = new ChangeListChooser();
installChangeListListener();
init();
updateDisplayedChangeLists();
setSelectedChangeList(myChangeList);
}
@Override
public void dispose() {
}
private void installChangeListListener() {
ChangeListAdapter changeListListener = new MyChangeListListener();
ChangeListManager.getInstance(myProject).addChangeListListener(changeListListener);
Disposer.register(this, () -> ChangeListManager.getInstance(myProject).removeChangeListListener(changeListListener));
}
@Nullable
@Override
protected JComponent createHeaderPanel() {
return myChangeListChooser;
}
@NotNull
@Override
protected List<AnAction> createToolbarActions() {
List<AnAction> result = new ArrayList<>();
result.addAll(super.createToolbarActions());
result.add(new MyRefreshAction());
if (myEnableUnversioned) {
result.add(new ShowHideUnversionedFilesAction());
// TODO: do not access DataContext
// We do not add "Delete" key shortcut for deleting unversioned files as this shortcut is already used to uncheck checkboxes in the tree.
result.add(UnversionedViewDialog.getUnversionedActionGroup());
UnversionedViewDialog.registerUnversionedActionsShortcuts(DataManager.getInstance().getDataContext(this), myViewer);
}
else {
// avoid duplicated actions on toolbar
result.add(ActionManager.getInstance().getAction(IdeActions.MOVE_TO_ANOTHER_CHANGE_LIST));
EmptyAction.registerWithShortcutSet(IdeActions.MOVE_TO_ANOTHER_CHANGE_LIST, CommonShortcuts.getMove(), myViewer);
}
RollbackDialogAction rollbackAction = new RollbackDialogAction();
rollbackAction.registerCustomShortcutSet(this, null);
result.add(rollbackAction);
EditSourceForDialogAction editSourceAction = new EditSourceForDialogAction(this);
editSourceAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), this);
result.add(editSourceAction);
result.add(ActionManager.getInstance().getAction("Vcs.CheckinProjectToolbar"));
return result;
}
@NotNull
@Override
protected List<AnAction> createDiffActions() {
return ContainerUtil.append(
super.createDiffActions(),
new ToggleChangeDiffAction(),
new MoveChangeDiffAction()
);
}
private boolean isShowUnversioned() {
return myEnableUnversioned && VcsConfiguration.getInstance(myProject).SHOW_UNVERSIONED_FILES_WHILE_COMMIT;
}
private void setShowUnversioned(boolean value) {
VcsConfiguration.getInstance(myProject).SHOW_UNVERSIONED_FILES_WHILE_COMMIT = value;
updateDisplayedChanges();
}
@NotNull
public LocalChangeList getSelectedChangeList() {
return myChangeList;
}
public void setSelectedChangeList(@NotNull LocalChangeList list) {
myChangeList = list;
myChangeListChooser.setToolTipText(list.getName());
updateDisplayedChanges();
}
public void updateDisplayedChangeLists() {
List<LocalChangeList> changeLists = ChangeListManager.getInstance(myProject).getChangeLists();
myChangeListChooser.setAvailableLists(changeLists);
}
public void updateDisplayedChanges() {
myChanges.clear();
myUnversioned.clear();
myHasHiddenUnversioned = false;
myChanges.addAll(myChangeList.getChanges());
if (myEnableUnversioned) {
List<VirtualFile> unversioned = ChangeListManagerImpl.getInstanceImpl(myProject).getUnversionedFiles();
if (isShowUnversioned()) {
myUnversioned.addAll(unversioned);
}
if (!isShowUnversioned() && !unversioned.isEmpty()) {
myHasHiddenUnversioned = true;
}
}
myViewer.rebuildTree();
}
@NotNull
@Override
protected DefaultTreeModel buildTreeModel(boolean showFlatten) {
RemoteStatusChangeNodeDecorator decorator = RemoteRevisionsCache.getInstance(myProject).getChangesNodeDecorator();
TreeModelBuilder builder = new TreeModelBuilder(myProject, showFlatten);
builder.setChanges(myChanges, decorator);
builder.setUnversioned(myUnversioned);
if (myHasHiddenUnversioned) {
myViewer.getEmptyText()
.setText("Unversioned files available. ")
.appendText("Show", SimpleTextAttributes.LINK_ATTRIBUTES, e -> setShowUnversioned(true));
}
else {
myViewer.getEmptyText()
.setText(DiffBundle.message("diff.count.differences.status.text", 0));
}
return builder.build();
}
@Nullable
@Override
public Object getData(String dataId) {
if (UNVERSIONED_FILES_DATA_KEY.is(dataId)) {
return VcsTreeModelData.selected(myViewer).userObjectsStream(VirtualFile.class);
}
else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
return myDeleteProvider;
}
else if (VcsDataKeys.CHANGE_LISTS.is(dataId)) {
return new ChangeList[]{myChangeList};
}
return super.getData(dataId);
}
private class ChangeListChooser extends JPanel {
private final static int MAX_NAME_LEN = 35;
@NotNull private final ComboBox<LocalChangeList> myChooser = new ComboBox<>();
public ChangeListChooser() {
myChooser.setEditable(false);
myChooser.setRenderer(new ColoredListCellRenderer<LocalChangeList>() {
@Override
protected void customizeCellRenderer(@NotNull JList<? extends LocalChangeList> list, LocalChangeList value,
int index, boolean selected, boolean hasFocus) {
String name = shortenTextWithEllipsis(value.getName().trim(), MAX_NAME_LEN, 0);
append(name, value.isDefault() ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
});
myChooser.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
LocalChangeList changeList = (LocalChangeList)myChooser.getSelectedItem();
if (changeList != null) {
setSelectedChangeList(changeList);
}
}
}
});
setLayout(new BorderLayout(4, 2));
JLabel label = new JLabel(VcsBundle.message("commit.dialog.changelist.label"));
label.setLabelFor(myChooser);
add(label, BorderLayout.WEST);
add(myChooser, BorderLayout.CENTER);
}
public void setAvailableLists(@NotNull List<LocalChangeList> lists) {
LocalChangeList currentList = ContainerUtil.find(lists, getSelectedChangeList());
if (currentList == null) currentList = lists.get(0);
myChooser.setModel(new CollectionComboBoxModel<>(lists, currentList));
myChooser.setEnabled(lists.size() > 1);
setSelectedChangeList(currentList);
}
}
private class ShowHideUnversionedFilesAction extends ToggleAction implements DumbAware {
private ShowHideUnversionedFilesAction() {
super("Show Unversioned Files", null, AllIcons.Vcs.ShowUnversionedFiles);
}
@Override
public boolean isSelected(@NotNull AnActionEvent e) {
return isShowUnversioned();
}
@Override
public void setSelected(@NotNull AnActionEvent e, boolean state) {
setShowUnversioned(state);
}
}
private class MoveChangeDiffAction extends MoveChangesToAnotherListAction {
@Override
protected boolean isEnabled(@NotNull AnActionEvent e) {
return e.getData(VcsDataKeys.CURRENT_CHANGE) != null;
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Change change = e.getRequiredData(VcsDataKeys.CURRENT_CHANGE);
askAndMove(myProject, Collections.singletonList(change), Collections.emptyList());
}
}
private class ToggleChangeDiffAction extends CheckboxAction implements DumbAware {
public ToggleChangeDiffAction() {
super(VcsBundle.message("commit.dialog.include.action.name"));
}
public boolean isSelected(AnActionEvent e) {
Change change = e.getData(VcsDataKeys.CURRENT_CHANGE);
if (change == null) return false;
return myViewer.isIncluded(change);
}
public void setSelected(AnActionEvent e, boolean state) {
Change change = e.getData(VcsDataKeys.CURRENT_CHANGE);
if (change == null) return;
if (state) {
myViewer.includeChange(change);
}
else {
myViewer.excludeChange(change);
}
}
}
private class MyRefreshAction extends DumbAwareAction {
public MyRefreshAction() {
super("Refresh Changes", null, AllIcons.Actions.Refresh);
}
@Override
public void actionPerformed(AnActionEvent e) {
updateDisplayedChangeLists();
}
}
private class MyChangeListListener extends ChangeListAdapter {
@NotNull private final MergingUpdateQueue myUpdateQueue =
new MergingUpdateQueue("MultipleLocalChangeListsBrowser", 300, true,
MultipleLocalChangeListsBrowser.this, MultipleLocalChangeListsBrowser.this);
private void doUpdate() {
myUpdateQueue.queue(new Update("update") {
@Override
public void run() {
updateDisplayedChangeLists();
}
});
}
public void changeListAdded(ChangeList list) {
doUpdate();
}
@Override
public void changeListRemoved(ChangeList list) {
doUpdate();
}
@Override
public void changeListRenamed(ChangeList list, String oldName) {
doUpdate();
}
@Override
public void changesRemoved(Collection<Change> changes, ChangeList fromList) {
doUpdate();
}
@Override
public void changesAdded(Collection<Change> changes, ChangeList toList) {
doUpdate();
}
@Override
public void changesMoved(Collection<Change> changes, ChangeList fromList, ChangeList toList) {
doUpdate();
}
@Override
public void changeListUpdateDone() {
doUpdate();
}
}
}
@@ -0,0 +1,472 @@
/*
* Copyright 2000-2017 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.intellij.openapi.vcs.changes.ui;
import com.intellij.diff.DiffDialogHints;
import com.intellij.diff.util.DiffUserDataKeysEx;
import com.intellij.ide.DeleteProvider;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.ListSelection;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.ex.CheckboxAction;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.actions.VirtualFileDeleteProvider;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeList;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.changes.RemoteRevisionsCache;
import com.intellij.openapi.vcs.changes.actions.diff.ShowDiffAction;
import com.intellij.openapi.vcs.changes.actions.diff.ShowDiffContext;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.*;
import java.util.List;
import java.util.stream.Stream;
import static com.intellij.openapi.vcs.changes.ChangesUtil.getFiles;
import static com.intellij.openapi.vcs.changes.ChangesUtil.getNavigatableArray;
import static com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode.UNVERSIONED_FILES_TAG;
import static com.intellij.openapi.vcs.changes.ui.ChangesListView.*;
/**
* @deprecated Use {@link ChangesBrowserBase}
*/
public abstract class OldChangesBrowserBase<T> extends JPanel implements TypeSafeDataProvider, Disposable {
private static final Logger LOG = Logger.getInstance(OldChangesBrowserBase.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<OldChangesBrowserBase> 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 OldChangesBrowserBase(@NotNull final Project project,
@NotNull List<T> changes,
final boolean capableOfExcludingChanges,
final boolean highlightProblems,
@Nullable final Runnable inclusionListener,
@NotNull ChangesBrowser.MyUseCase useCase,
@Nullable VirtualFile toSelect,
@NotNull 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 OldChangesBrowserBase.this.buildTreeModel(changes, changeNodeDecorator, isShowFlatten());
}
protected List<T> getSelectedObjects(final ChangesBrowserNode<?> node) {
return OldChangesBrowserBase.this.getSelectedObjects(node);
}
@Nullable
protected T getLeadSelectedObject(final ChangesBrowserNode<?> node) {
return OldChangesBrowserBase.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<?> node);
@Nullable
protected abstract T getLeadSelectedObject(@NotNull ChangesBrowserNode<?> node);
@NotNull
protected Runnable getDoubleClickHandler() {
return () -> 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().getList());
}
private void showDiff() {
ListSelection<Change> selection = getChangesSelection();
List<Change> changes = selection.getList();
Change[] changesArray = changes.toArray(new Change[changes.size()]);
showDiffForChanges(changesArray, selection.getSelectedIndex());
afterDiffRefresh();
}
@NotNull
protected ListSelection<Change> 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 && !changes.contains(leadSelection)) {
return ListSelection.createSingleton(leadSelection);
}
return ListSelection.create(changes, leadSelection);
}
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("ChangesBrowser", 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() || e.getInputEvent() instanceof KeyEvent);
}
public void actionPerformed(AnActionEvent e) {
showDiff();
}
};
ActionUtil.copyFrom(myDiffAction, IdeActions.ACTION_SHOW_DIFF_COMMON);
myDiffAction.registerCustomShortcutSet(myViewer, null);
toolBarGroup.add(myDiffAction);
}
@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(
getFiles(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);
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2000-2017 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.intellij.openapi.vcs.changes.ui;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.changes.Change;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.tree.DefaultTreeModel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class SimpleChangesBrowser extends ChangesBrowserBase {
private final List<Change> myChanges = new ArrayList<>();
@Nullable private ChangeNodeDecorator myChangeNodeDecorator;
public SimpleChangesBrowser(@NotNull Project project,
@NotNull List<Change> changes) {
this(project, false, false);
setChangesToDisplay(changes);
}
public SimpleChangesBrowser(@NotNull Project project,
boolean showCheckboxes,
boolean highlightProblems) {
super(project, showCheckboxes, highlightProblems);
init();
}
@NotNull
@Override
protected DefaultTreeModel buildTreeModel(boolean showFlatten) {
return TreeModelBuilder.buildFromChanges(myProject, showFlatten, myChanges, myChangeNodeDecorator);
}
public void setChangesToDisplay(@NotNull Collection<? extends Change> changes) {
myChanges.clear();
myChanges.addAll(changes);
myViewer.rebuildTree();
}
public void setChangeNodeDecorator(@Nullable ChangeNodeDecorator value) {
myChangeNodeDecorator = value;
myViewer.rebuildTree();
}
@NotNull
public List<Change> getAllChanges() {
return VcsTreeModelData.all(myViewer).userObjects(Change.class);
}
@NotNull
public List<Change> getSelectedChanges() {
return VcsTreeModelData.selected(myViewer).userObjects(Change.class);
}
@NotNull
public List<Change> getIncludedChanges() {
return VcsTreeModelData.included(myViewer).userObjects(Change.class);
}
}
@@ -3,10 +3,23 @@
// found in the LICENSE file.
package com.intellij.openapi.vcs.changes.ui;
import com.intellij.openapi.ListSelection;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.TreePath;
import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -143,5 +156,95 @@ public abstract class VcsTreeModelData {
return root.getNodesUnderStream().filter(node -> included.contains(node.getUserObject()));
}
}
@NotNull
public static ListSelection<Object> getListSelection(@NotNull JTree tree) {
List<Object> entries = selected(tree).userObjects();
Object selection = ContainerUtil.getFirstItem(entries);
if (entries.size() < 2) {
List<Object> allEntries = all(tree).userObjects();
if (allEntries.size() > 1 || entries.isEmpty()) {
entries = allEntries;
}
}
return ListSelection.create(entries, selection);
}
@Nullable
public static Object getData(@Nullable Project project, @NotNull JTree tree, String dataId) {
if (VcsDataKeys.CHANGES.is(dataId)) {
Change[] changes = mapToChange(selected(tree)).toArray(Change[]::new);
if (changes.length != 0) return changes;
return mapToChange(all(tree)).toArray(Change[]::new);
}
else if (VcsDataKeys.SELECTED_CHANGES.is(dataId) ||
VcsDataKeys.SELECTED_CHANGES_IN_DETAILS.is(dataId)) {
return mapToChange(selected(tree)).toArray(Change[]::new);
}
else if (VcsDataKeys.CHANGES_SELECTION.is(dataId)) {
return getListSelection(tree).map(entry -> ObjectUtils.tryCast(entry, Change.class));
}
else if (VcsDataKeys.CHANGE_LEAD_SELECTION.is(dataId)) {
return mapToChange(exactlySelected(tree)).limit(1).toArray(Change[]::new);
}
else if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
return mapToVirtualFile(selected(tree)).toArray(VirtualFile[]::new);
}
else if (CommonDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
if (project == null) return null;
return ChangesUtil.getNavigatableArray(project, mapToVirtualFile(selected(tree)));
}
else if (VcsDataKeys.IO_FILE_ARRAY.is(dataId)) {
return mapToIoFile(selected(tree)).toArray(File[]::new);
}
return null;
}
@NotNull
private static Stream<Change> mapToChange(@NotNull VcsTreeModelData data) {
return data.userObjectsStream().filter(it -> it instanceof Change)
.map(entry -> {
if (entry instanceof Change) {
return (Change)entry;
}
return null;
})
.filter(Objects::nonNull);
}
@NotNull
private static Stream<VirtualFile> mapToVirtualFile(@NotNull VcsTreeModelData data) {
return data.userObjectsStream()
.flatMap(entry -> {
if (entry instanceof Change) {
return ChangesUtil.getPathsCaseSensitive((Change)entry)
.map(FilePath::getVirtualFile);
}
else if (entry instanceof VirtualFile) {
return Stream.of((VirtualFile)entry);
}
return Stream.empty();
})
.filter(Objects::nonNull);
}
@NotNull
private static Stream<File> mapToIoFile(@NotNull VcsTreeModelData data) {
return data.userObjectsStream()
.map(entry -> {
if (entry instanceof Change) {
ContentRevision afterRevision = ((Change)entry).getAfterRevision();
if (afterRevision == null) return null;
return afterRevision.getFile().getIOFile();
}
return null;
})
.filter(Objects::nonNull);
}
}