diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SpecificFilesViewDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SpecificFilesViewDialog.java index 8af7cafd49c6..55d3e211685b 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SpecificFilesViewDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SpecificFilesViewDialog.java @@ -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 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); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/IgnoreUnversionedAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/IgnoreUnversionedAction.java index 5eab0ea9edf5..ed91b0806575 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/IgnoreUnversionedAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/IgnoreUnversionedAction.java @@ -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 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 diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RollbackDialogAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RollbackDialogAction.java index 671682a8d8e3..8a952cfc8094 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RollbackDialogAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/RollbackDialogAction.java @@ -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); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/ScheduleForAdditionAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/ScheduleForAdditionAction.java index 7f5d29350903..1ccf521f69a9 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/ScheduleForAdditionAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/ScheduleForAdditionAction.java @@ -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 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 files, @NotNull Condition unversionedFileCondition, - @Nullable ChangesBrowserBase browser) { + @Nullable OldChangesBrowserBase browser) { boolean result = true; if (!files.isEmpty()) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowser.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowser.java index 6872c1524637..cfde0a60b1e8 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowser.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowser.java @@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.tree.DefaultTreeModel; import java.util.List; -public class ChangesBrowser extends ChangesBrowserBase { +public class ChangesBrowser extends OldChangesBrowserBase { public ChangesBrowser(@NotNull Project project, @Nullable List changeLists, diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserBase.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserBase.java index d4d9ceca168d..5313c674b8db 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserBase.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserBase.java @@ -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 DATA_KEY = + DataKey.create("com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase"); -public abstract class ChangesBrowserBase 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 myClass; - protected final ChangesTreeList myViewer; - protected final JScrollPane myViewerScrollPane; - protected ChangeList mySelectedChangeList; - protected List 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 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 changes) { - myChangesToDisplay = changes; - myViewer.setChangesToDisplay(changes); - } - public void setDecorator(final ChangeNodeDecorator decorator) { - myViewer.setChangeDecorator(decorator); - } - - protected ChangesBrowserBase(@NotNull final Project project, - @NotNull List changes, - final boolean capableOfExcludingChanges, - final boolean highlightProblems, - @Nullable final Runnable inclusionListener, - @NotNull ChangesBrowser.MyUseCase useCase, - @Nullable VirtualFile toSelect, - @NotNull Class 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(myProject, changes, capableOfExcludingChanges, highlightProblems, inclusionListener, decorator) { - protected DefaultTreeModel buildTreeModel(final List changes, ChangeNodeDecorator changeNodeDecorator) { - return ChangesBrowserBase.this.buildTreeModel(changes, changeNodeDecorator, isShowFlatten()); - } - - protected List 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 changes, ChangeNodeDecorator changeNodeDecorator, boolean showFlatten); @NotNull - protected abstract List 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 createToolbarActions() { + return ContainerUtil.list( + myShowDiffAction + ); } - protected void setInitialSelection(final List changeLists, - @NotNull List changes, - final ChangeList initialListSelection) { - mySelectedChangeList = initialListSelection; + @NotNull + protected List 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 getViewer() { - return myViewer; + @NotNull + public ActionToolbar getToolbar() { + return myToolbar; } @NotNull @@ -199,271 +161,78 @@ public abstract class ChangesBrowserBase extends JPanel implements TypeSafeDa return myViewerScrollPane; } - public void calcData(DataKey key, DataSink sink) { - if (key == VcsDataKeys.CHANGES) { - List 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 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 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 selection = getChangesSelection(); - List changes = selection.getList(); - - Change[] changesArray = changes.toArray(new Change[changes.size()]); - showDiffForChanges(changesArray, selection.getSelectedIndex()); - - afterDiffRefresh(); - } @NotNull - protected ListSelection getChangesSelection() { - final Change leadSelection = ObjectUtils.tryCast(myViewer.getLeadSelection(), Change.class); - List changes = getSelectedChanges(); - - if (changes.size() < 2) { - List 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 createDiffActions() { - List 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 getCurrentIncludedChanges(); - - @NotNull - public List getCurrentDisplayedChanges() { - return mySelectedChangeList != null ? ContainerUtil.newArrayList(mySelectedChangeList.getChanges()) : Collections.emptyList(); - } - - @NotNull - public abstract List getCurrentDisplayedObjects(); - - @NotNull - public List 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 changes = getSelectedChanges(); - final List 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 getSelectedChanges(); - - @NotNull - public abstract List getAllChanges(); - - @NotNull - protected Stream 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 selection = VcsTreeModelData.getListSelection(myViewer); + return ContainerUtil.exists(selection.getList(), entry -> getDiffRequestProducer(entry) != null); } - public void setDataIsDirty(boolean dataIsDirty) { - myDataIsDirty = dataIsDirty; + public void showDiff() { + ListSelection selection = VcsTreeModelData.getListSelection(myViewer); + ListSelection 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 List findChanges(@NotNull Collection 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); + } } } + diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java index d30107deeb02..cd17073b4972 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java @@ -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; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleChangeListBrowser.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleChangeListBrowser.java index 338b283b9cfd..1b2e7eeec734 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleChangeListBrowser.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleChangeListBrowser.java @@ -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 { +public class MultipleChangeListBrowser extends OldChangesBrowserBase { @NotNull private final ChangeListChooser myChangeListChooser; @NotNull final ChangeListListener myChangeListListener = new MyChangeListListener(); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleLocalChangeListsBrowser.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleLocalChangeListsBrowser.java new file mode 100644 index 000000000000..f186cd0fa1ef --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleLocalChangeListsBrowser.java @@ -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 myChanges = new ArrayList<>(); + private final List 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 createToolbarActions() { + List 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 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 changeLists = ChangeListManager.getInstance(myProject).getChangeLists(); + myChangeListChooser.setAvailableLists(changeLists); + } + + public void updateDisplayedChanges() { + myChanges.clear(); + myUnversioned.clear(); + myHasHiddenUnversioned = false; + + myChanges.addAll(myChangeList.getChanges()); + + if (myEnableUnversioned) { + List 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 myChooser = new ComboBox<>(); + + public ChangeListChooser() { + myChooser.setEditable(false); + myChooser.setRenderer(new ColoredListCellRenderer() { + @Override + protected void customizeCellRenderer(@NotNull JList 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 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 changes, ChangeList fromList) { + doUpdate(); + } + + @Override + public void changesAdded(Collection changes, ChangeList toList) { + doUpdate(); + } + + @Override + public void changesMoved(Collection changes, ChangeList fromList, ChangeList toList) { + doUpdate(); + } + + @Override + public void changeListUpdateDone() { + doUpdate(); + } + } +} \ No newline at end of file diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/OldChangesBrowserBase.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/OldChangesBrowserBase.java new file mode 100644 index 000000000000..fd1818497382 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/OldChangesBrowserBase.java @@ -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 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 myClass; + protected final ChangesTreeList myViewer; + protected final JScrollPane myViewerScrollPane; + protected ChangeList mySelectedChangeList; + protected List 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 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 changes) { + myChangesToDisplay = changes; + myViewer.setChangesToDisplay(changes); + } + + public void setDecorator(final ChangeNodeDecorator decorator) { + myViewer.setChangeDecorator(decorator); + } + + protected OldChangesBrowserBase(@NotNull final Project project, + @NotNull List changes, + final boolean capableOfExcludingChanges, + final boolean highlightProblems, + @Nullable final Runnable inclusionListener, + @NotNull ChangesBrowser.MyUseCase useCase, + @Nullable VirtualFile toSelect, + @NotNull Class 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(myProject, changes, capableOfExcludingChanges, highlightProblems, inclusionListener, decorator) { + protected DefaultTreeModel buildTreeModel(final List changes, ChangeNodeDecorator changeNodeDecorator) { + return OldChangesBrowserBase.this.buildTreeModel(changes, changeNodeDecorator, isShowFlatten()); + } + + protected List 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 changes, ChangeNodeDecorator changeNodeDecorator, boolean showFlatten); + + @NotNull + protected abstract List getSelectedObjects(@NotNull ChangesBrowserNode node); + + @Nullable + protected abstract T getLeadSelectedObject(@NotNull ChangesBrowserNode node); + + @NotNull + protected Runnable getDoubleClickHandler() { + return () -> showDiff(); + } + + protected void setInitialSelection(final List changeLists, + @NotNull List 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 getViewer() { + return myViewer; + } + + @NotNull + public JScrollPane getViewerScrollPane() { + return myViewerScrollPane; + } + + public void calcData(DataKey key, DataSink sink) { + if (key == VcsDataKeys.CHANGES) { + List 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 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 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 selection = getChangesSelection(); + List changes = selection.getList(); + + Change[] changesArray = changes.toArray(new Change[changes.size()]); + showDiffForChanges(changesArray, selection.getSelectedIndex()); + + afterDiffRefresh(); + } + + @NotNull + protected ListSelection getChangesSelection() { + final Change leadSelection = ObjectUtils.tryCast(myViewer.getLeadSelection(), Change.class); + List changes = getSelectedChanges(); + + if (changes.size() < 2) { + List 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 createDiffActions() { + List 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 getCurrentIncludedChanges(); + + @NotNull + public List getCurrentDisplayedChanges() { + return mySelectedChangeList != null ? ContainerUtil.newArrayList(mySelectedChangeList.getChanges()) : Collections.emptyList(); + } + + @NotNull + public abstract List getCurrentDisplayedObjects(); + + @NotNull + public List 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 changes = getSelectedChanges(); + final List 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 getSelectedChanges(); + + @NotNull + public abstract List getAllChanges(); + + @NotNull + protected Stream 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 List findChanges(@NotNull Collection items) { + return ContainerUtil.findAll(items, Change.class); + } + + static boolean isUnderUnversioned(@NotNull ChangesBrowserNode node) { + return isUnderTag(new TreePath(node.getPath()), UNVERSIONED_FILES_TAG); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/SimpleChangesBrowser.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/SimpleChangesBrowser.java new file mode 100644 index 000000000000..7ba0d52fbb33 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/SimpleChangesBrowser.java @@ -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 myChanges = new ArrayList<>(); + @Nullable private ChangeNodeDecorator myChangeNodeDecorator; + + public SimpleChangesBrowser(@NotNull Project project, + @NotNull List 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 changes) { + myChanges.clear(); + myChanges.addAll(changes); + myViewer.rebuildTree(); + } + + public void setChangeNodeDecorator(@Nullable ChangeNodeDecorator value) { + myChangeNodeDecorator = value; + myViewer.rebuildTree(); + } + + + @NotNull + public List getAllChanges() { + return VcsTreeModelData.all(myViewer).userObjects(Change.class); + } + + @NotNull + public List getSelectedChanges() { + return VcsTreeModelData.selected(myViewer).userObjects(Change.class); + } + + @NotNull + public List getIncludedChanges() { + return VcsTreeModelData.included(myViewer).userObjects(Change.class); + } +} \ No newline at end of file diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/VcsTreeModelData.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/VcsTreeModelData.java index 3c8257890278..552e512cc7db 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/VcsTreeModelData.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/VcsTreeModelData.java @@ -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 getListSelection(@NotNull JTree tree) { + List entries = selected(tree).userObjects(); + Object selection = ContainerUtil.getFirstItem(entries); + + if (entries.size() < 2) { + List 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 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 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 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); + } }