/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diff.impl;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.diff.*;
import com.intellij.diff.FrameDiffTool.DiffViewer;
import com.intellij.diff.actions.impl.*;
import com.intellij.diff.impl.DiffSettingsHolder.DiffSettings;
import com.intellij.diff.requests.*;
import com.intellij.diff.tools.ErrorDiffTool;
import com.intellij.diff.tools.external.ExternalDiffTool;
import com.intellij.diff.tools.util.DiffDataKeys;
import com.intellij.diff.tools.util.PrevNextDifferenceIterable;
import com.intellij.diff.util.DiffUserDataKeys;
import com.intellij.diff.util.DiffUserDataKeysEx;
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.LineRange;
import com.intellij.ide.DataManager;
import com.intellij.ide.impl.DataManagerImpl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.*;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import com.intellij.ui.HintHint;
import com.intellij.ui.JBProgressBar;
import com.intellij.ui.LightweightHint;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import consulo.annotations.RequiredDispatchThread;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@SuppressWarnings("InnerClassMayBeStatic")
public abstract class DiffRequestProcessor implements Disposable {
private static final Logger LOG = Logger.getInstance(DiffRequestProcessor.class);
private boolean myDisposed;
@Nullable private final Project myProject;
@NotNull private final DiffContext myContext;
@NotNull private final DiffSettings mySettings;
@NotNull private final List<DiffTool> myAvailableTools;
@NotNull private final LinkedList<DiffTool> myToolOrder;
@NotNull private final OpenInEditorAction myOpenInEditorAction;
@Nullable private DefaultActionGroup myPopupActionGroup;
@NotNull private final JPanel myPanel;
@NotNull private final MyPanel myMainPanel;
@NotNull private final Wrapper myContentPanel;
@NotNull private final Wrapper myToolbarPanel; // TODO: allow to call 'updateToolbar' from Viewer ?
@NotNull private final Wrapper myToolbarStatusPanel;
@NotNull private final MyProgressBar myProgressBar;
@NotNull private DiffRequest myActiveRequest;
@NotNull private ViewerState myState;
public DiffRequestProcessor(@Nullable Project project) {
this(project, new UserDataHolderBase());
}
public DiffRequestProcessor(@Nullable Project project, @NotNull String place) {
this(project, DiffUtil.createUserDataHolder(DiffUserDataKeys.PLACE, place));
}
public DiffRequestProcessor(@Nullable Project project, @NotNull UserDataHolder context) {
myProject = project;
myContext = new MyDiffContext(context);
myActiveRequest = new LoadingDiffRequest();
mySettings = DiffSettingsHolder.getInstance().getSettings(myContext.getUserData(DiffUserDataKeys.PLACE));
myAvailableTools = DiffManagerEx.getInstance().getDiffTools();
myToolOrder = new LinkedList<DiffTool>(getToolOrderFromSettings(myAvailableTools));
// UI
myMainPanel = new MyPanel();
myContentPanel = new Wrapper();
myToolbarPanel = new Wrapper();
myToolbarPanel.setFocusable(true);
myToolbarStatusPanel = new Wrapper();
myProgressBar = new MyProgressBar();
myPanel = JBUI.Panels.simplePanel(myMainPanel);
JPanel statusPanel = JBUI.Panels.simplePanel(myToolbarStatusPanel).addToLeft(myProgressBar);
JPanel topPanel = JBUI.Panels.simplePanel(myToolbarPanel).addToRight(statusPanel);
myMainPanel.add(topPanel, BorderLayout.NORTH);
myMainPanel.add(myContentPanel, BorderLayout.CENTER);
myMainPanel.setFocusTraversalPolicyProvider(true);
myMainPanel.setFocusTraversalPolicy(new MyFocusTraversalPolicy());
JComponent bottomPanel = myContext.getUserData(DiffUserDataKeysEx.BOTTOM_PANEL);
if (bottomPanel != null) myMainPanel.add(bottomPanel, BorderLayout.SOUTH);
if (bottomPanel instanceof Disposable) Disposer.register(this, (Disposable)bottomPanel);
myState = EmptyState.INSTANCE;
myContentPanel.setContent(DiffUtil.createMessagePanel(((LoadingDiffRequest)myActiveRequest).getMessage()));
myOpenInEditorAction = new OpenInEditorAction(new Runnable() {
@Override
public void run() {
onAfterNavigate();
}
});
}
//
// Update
//
@RequiredDispatchThread
protected void reloadRequest() {
updateRequest(true);
}
@RequiredDispatchThread
public void updateRequest() {
updateRequest(false);
}
@RequiredDispatchThread
public void updateRequest(boolean force) {
updateRequest(force, null);
}
@RequiredDispatchThread
public abstract void updateRequest(boolean force, @Nullable ScrollToPolicy scrollToChangePolicy);
@NotNull
private FrameDiffTool getFittedTool() {
List<FrameDiffTool> tools = new ArrayList<FrameDiffTool>();
for (DiffTool tool : myToolOrder) {
try {
if (tool instanceof FrameDiffTool && tool.canShow(myContext, myActiveRequest)) {
tools.add((FrameDiffTool)tool);
}
}
catch (Throwable e) {
LOG.error(e);
}
}
tools = DiffUtil.filterSuppressedTools(tools);
return tools.isEmpty() ? ErrorDiffTool.INSTANCE : tools.get(0);
}
@NotNull
private List<FrameDiffTool> getAvailableFittedTools() {
List<FrameDiffTool> tools = new ArrayList<FrameDiffTool>();
for (DiffTool tool : myAvailableTools) {
try {
if (tool instanceof FrameDiffTool && tool.canShow(myContext, myActiveRequest)) {
tools.add((FrameDiffTool)tool);
}
}
catch (Throwable e) {
LOG.error(e);
}
}
return DiffUtil.filterSuppressedTools(tools);
}
private void moveToolOnTop(@NotNull DiffTool tool) {
myToolOrder.remove(tool);
FrameDiffTool toolToReplace = getFittedTool();
int index;
for (index = 0; index < myToolOrder.size(); index++) {
if (myToolOrder.get(index) == toolToReplace) break;
}
myToolOrder.add(index, tool);
updateToolOrderSettings(myToolOrder);
}
@NotNull
private ViewerState createState() {
FrameDiffTool frameTool = getFittedTool();
DiffViewer viewer = frameTool.createComponent(myContext, myActiveRequest);
for (DiffExtension extension : DiffExtension.EP_NAME.getExtensions()) {
extension.onViewerCreated(viewer, myContext, myActiveRequest);
}
DiffViewerWrapper wrapper = myActiveRequest.getUserData(DiffViewerWrapper.KEY);
if (wrapper == null) {
return new DefaultState(viewer, frameTool);
}
else {
return new WrapperState(viewer, frameTool, wrapper);
}
}
//
// Abstract
//
@Nullable private ApplyData myQueuedApplyRequest;
@RequiredDispatchThread
protected void applyRequest(@NotNull DiffRequest request, boolean force, @Nullable ScrollToPolicy scrollToChangePolicy) {
myIterationState = IterationState.NONE;
force = force || (myQueuedApplyRequest != null && myQueuedApplyRequest.force);
myQueuedApplyRequest = new ApplyData(request, force, scrollToChangePolicy);
Runnable task = new Runnable() {
@Override
public void run() {
if (myQueuedApplyRequest == null || myDisposed) return;
doApplyRequest(myQueuedApplyRequest.request, myQueuedApplyRequest.force, myQueuedApplyRequest.scrollToChangePolicy);
myQueuedApplyRequest = null;
}
};
IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(task);
}
@RequiredDispatchThread
private void doApplyRequest(@NotNull DiffRequest request, boolean force, @Nullable ScrollToPolicy scrollToChangePolicy) {
if (!force && request == myActiveRequest) return;
request.putUserData(DiffUserDataKeysEx.SCROLL_TO_CHANGE, scrollToChangePolicy);
boolean hadFocus = isFocused();
myState.destroy();
myToolbarStatusPanel.setContent(null);
myToolbarPanel.setContent(null);
myContentPanel.setContent(null);
ActionUtil.clearActions(myMainPanel);
myActiveRequest.onAssigned(false);
myActiveRequest = request;
myActiveRequest.onAssigned(true);
try {
myState = createState();
myState.init();
}
catch (Throwable e) {
LOG.error(e);
myState = new ErrorState(new ErrorDiffRequest("Error: can't show diff"), getFittedTool());
myState.init();
}
if (hadFocus) requestFocusInternal();
}
protected void setWindowTitle(@NotNull String title) {
}
protected void onAfterNavigate() {
}
@RequiredDispatchThread
protected void onDispose() {
}
@Nullable
public <T> T getContextUserData(@NotNull Key<T> key) {
return myContext.getUserData(key);
}
public <T> void putContextUserData(@NotNull Key<T> key, @Nullable T value) {
myContext.putUserData(key, value);
}
@NotNull
protected List<AnAction> getNavigationActions() {
return ContainerUtil.<AnAction>list(
new MyPrevDifferenceAction(),
new MyNextDifferenceAction(),
new MyPrevChangeAction(),
new MyNextChangeAction()
);
}
//
// Misc
//
public boolean isWindowFocused() {
Window window = SwingUtilities.getWindowAncestor(myPanel);
return window != null && window.isFocused();
}
public boolean isFocused() {
return DiffUtil.isFocusedComponent(myProject, myPanel);
}
public void requestFocus() {
DiffUtil.requestFocus(myProject, getPreferredFocusedComponent());
}
protected void requestFocusInternal() {
JComponent component = getPreferredFocusedComponent();
if (component != null) component.requestFocusInWindow();
}
@NotNull
protected List<DiffTool> getToolOrderFromSettings(@NotNull List<DiffTool> availableTools) {
List<DiffTool> result = new ArrayList<DiffTool>();
List<String> savedOrder = getSettings().getDiffToolsOrder();
for (final String clazz : savedOrder) {
DiffTool tool = ContainerUtil.find(availableTools, new Condition<DiffTool>() {
@Override
public boolean value(DiffTool tool) {
return tool.getClass().getCanonicalName().equals(clazz);
}
});
if (tool != null) result.add(tool);
}
for (DiffTool tool : availableTools) {
if (!result.contains(tool)) result.add(tool);
}
return result;
}
protected void updateToolOrderSettings(@NotNull List<DiffTool> toolOrder) {
List<String> savedOrder = new ArrayList<String>();
for (DiffTool tool : toolOrder) {
savedOrder.add(tool.getClass().getCanonicalName());
}
getSettings().setDiffToolsOrder(savedOrder);
}
@Override
public void dispose() {
if (myDisposed) return;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (myDisposed) return;
myDisposed = true;
onDispose();
myState.destroy();
myToolbarStatusPanel.setContent(null);
myToolbarPanel.setContent(null);
myContentPanel.setContent(null);
myActiveRequest.onAssigned(false);
myState = EmptyState.INSTANCE;
myActiveRequest = NoDiffRequest.INSTANCE;
}
});
}
@NotNull
protected DefaultActionGroup collectToolbarActions(@Nullable List<AnAction> viewerActions) {
DefaultActionGroup group = new DefaultActionGroup();
List<AnAction> navigationActions = new ArrayList<AnAction>();
navigationActions.addAll(getNavigationActions());
navigationActions.add(myOpenInEditorAction);
navigationActions.add(new MyChangeDiffToolAction());
DiffUtil.addActionBlock(group,
navigationActions);
DiffUtil.addActionBlock(group, viewerActions);
List<AnAction> requestContextActions = myActiveRequest.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
DiffUtil.addActionBlock(group, requestContextActions);
List<AnAction> contextActions = myContext.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
DiffUtil.addActionBlock(group, contextActions);
DiffUtil.addActionBlock(group,
new ShowInExternalToolAction(),
ActionManager.getInstance().getAction(IdeActions.ACTION_CONTEXT_HELP));
return group;
}
@NotNull
protected DefaultActionGroup collectPopupActions(@Nullable List<AnAction> viewerActions) {
DefaultActionGroup group = new DefaultActionGroup();
List<AnAction> selectToolActions = new ArrayList<AnAction>();
for (DiffTool tool : getAvailableFittedTools()) {
if (tool == myState.getActiveTool()) continue;
selectToolActions.add(new DiffToolToggleAction(tool));
}
DiffUtil.addActionBlock(group, selectToolActions);
DiffUtil.addActionBlock(group, viewerActions);
return group;
}
protected void buildToolbar(@Nullable List<AnAction> viewerActions) {
ActionGroup group = collectToolbarActions(viewerActions);
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.DIFF_TOOLBAR, group, true);
DataManager.registerDataProvider(toolbar.getComponent(), myMainPanel);
toolbar.setTargetComponent(toolbar.getComponent());
myToolbarPanel.setContent(toolbar.getComponent());
for (AnAction action : group.getChildren(null)) {
DiffUtil.registerAction(action, myMainPanel);
}
}
protected void buildActionPopup(@Nullable List<AnAction> viewerActions) {
ShowActionGroupPopupAction action = new ShowActionGroupPopupAction();
DiffUtil.registerAction(action, myMainPanel);
myPopupActionGroup = collectPopupActions(viewerActions);
}
private void setTitle(@Nullable String title) {
if (getContextUserData(DiffUserDataKeys.DO_NOT_CHANGE_WINDOW_TITLE) == Boolean.TRUE) return;
if (title == null) title = "Diff";
setWindowTitle(title);
}
//
// Getters
//
@NotNull
public JComponent getComponent() {
return myPanel;
}
@Nullable
public JComponent getPreferredFocusedComponent() {
JComponent component = myState.getPreferredFocusedComponent();
return component != null ? component : myToolbarPanel.getTargetComponent();
}
@Nullable
public Project getProject() {
return myProject;
}
@NotNull
public DiffContext getContext() {
return myContext;
}
@NotNull
protected DiffSettings getSettings() {
return mySettings;
}
public boolean isDisposed() {
return myDisposed;
}
//
// Actions
//
private class ShowInExternalToolAction extends DumbAwareAction {
public ShowInExternalToolAction() {
EmptyAction.setupAction(this, "Diff.ShowInExternalTool", null);
}
@Override
public void update(AnActionEvent e) {
if (!ExternalDiffTool.isEnabled()) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
e.getPresentation().setEnabled(ExternalDiffTool.canShow(myActiveRequest));
e.getPresentation().setVisible(true);
}
@Override
public void actionPerformed(AnActionEvent e) {
try {
ExternalDiffTool.showRequest(e.getProject(), myActiveRequest);
}
catch (Throwable ex) {
Messages.showErrorDialog(e.getProject(), ex.getMessage(), "Can't Show Diff In External Tool");
}
}
}
private class MyChangeDiffToolAction extends ComboBoxAction implements DumbAware {
public MyChangeDiffToolAction() {
// TODO: add icons for diff tools, show only icon in toolbar - to reduce jumping on change ?
setEnabledInModalContext(true);
}
@RequiredDispatchThread
@Override
public void update(AnActionEvent e) {
Presentation presentation = e.getPresentation();
DiffTool activeTool = myState.getActiveTool();
presentation.setText(activeTool.getName());
if (activeTool == ErrorDiffTool.INSTANCE) {
presentation.setEnabledAndVisible(false);
}
for (DiffTool tool : getAvailableFittedTools()) {
if (tool != activeTool) {
presentation.setEnabledAndVisible(true);
return;
}
}
presentation.setEnabledAndVisible(false);
}
@NotNull
@Override
protected DefaultActionGroup createPopupActionGroup(JComponent button) {
DefaultActionGroup group = new DefaultActionGroup();
for (DiffTool tool : getAvailableFittedTools()) {
group.add(new DiffToolToggleAction(tool));
}
return group;
}
}
private class DiffToolToggleAction extends AnAction implements DumbAware {
@NotNull private final DiffTool myDiffTool;
private DiffToolToggleAction(@NotNull DiffTool tool) {
super(tool.getName());
setEnabledInModalContext(true);
myDiffTool = tool;
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
if (myState.getActiveTool() == myDiffTool) return;
moveToolOnTop(myDiffTool);
updateRequest(true);
}
}
private class ShowActionGroupPopupAction extends DumbAwareAction {
public ShowActionGroupPopupAction() {
EmptyAction.setupAction(this, "Diff.ShowSettingsPopup", null);
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(myPopupActionGroup != null && myPopupActionGroup.getChildrenCount() > 0);
}
@Override
public void actionPerformed(AnActionEvent e) {
assert myPopupActionGroup != null;
ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup("Diff Actions", myPopupActionGroup, e.getDataContext(),
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);
popup.showInCenterOf(myPanel);
}
}
//
// Navigation
//
private enum IterationState {NEXT, PREV, NONE}
@NotNull private IterationState myIterationState = IterationState.NONE;
@RequiredDispatchThread
protected boolean hasNextChange() {
return false;
}
@RequiredDispatchThread
protected boolean hasPrevChange() {
return false;
}
@RequiredDispatchThread
protected void goToNextChange(boolean fromDifferences) {
}
@RequiredDispatchThread
protected void goToPrevChange(boolean fromDifferences) {
}
@RequiredDispatchThread
protected boolean isNavigationEnabled() {
return false;
}
protected class MyNextDifferenceAction extends NextDifferenceAction {
@Override
public void update(@NotNull AnActionEvent e) {
if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
e.getPresentation().setEnabledAndVisible(true);
return;
}
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoNext()) {
e.getPresentation().setEnabled(true);
return;
}
if (getSettings().isGoToNextFileOnNextDifference() && isNavigationEnabled() && hasNextChange()) {
e.getPresentation().setEnabled(true);
return;
}
e.getPresentation().setEnabled(false);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoNext()) {
iterable.goNext();
myIterationState = IterationState.NONE;
return;
}
if (!isNavigationEnabled() || !hasNextChange() || !getSettings().isGoToNextFileOnNextDifference()) return;
if (myIterationState != IterationState.NEXT) {
notifyMessage(e, true);
myIterationState = IterationState.NEXT;
return;
}
goToNextChange(true);
}
}
protected class MyPrevDifferenceAction extends PrevDifferenceAction {
@Override
public void update(@NotNull AnActionEvent e) {
if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
e.getPresentation().setEnabledAndVisible(true);
return;
}
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoPrev()) {
e.getPresentation().setEnabled(true);
return;
}
if (getSettings().isGoToNextFileOnNextDifference() && isNavigationEnabled() && hasPrevChange()) {
e.getPresentation().setEnabled(true);
return;
}
e.getPresentation().setEnabled(false);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoPrev()) {
iterable.goPrev();
myIterationState = IterationState.NONE;
return;
}
if (!isNavigationEnabled() || !hasPrevChange() || !getSettings().isGoToNextFileOnNextDifference()) return;
if (myIterationState != IterationState.PREV) {
notifyMessage(e, false);
myIterationState = IterationState.PREV;
return;
}
goToPrevChange(true);
}
}
private void notifyMessage(@NotNull AnActionEvent e, boolean next) {
Editor editor = e.getData(DiffDataKeys.CURRENT_EDITOR);
// TODO: provide "change" word in chain UserData - for tests/etc
String message = DiffUtil.createNotificationText(next ? "Press again to go to the next file" : "Press again to go to the previous file",
"You can disable this feature in " + DiffUtil.getSettingsConfigurablePath());
final LightweightHint hint = new LightweightHint(HintUtil.createInformationLabel(message));
Point point = new Point(myContentPanel.getWidth() / 2, next ? myContentPanel.getHeight() - JBUI.scale(40) : JBUI.scale(40));
if (editor == null) {
final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
final HintHint hintHint = createNotifyHint(myContentPanel, point, next);
hint.show(myContentPanel, point.x, point.y, owner instanceof JComponent ? (JComponent)owner : null, hintHint);
}
else {
int x = SwingUtilities.convertPoint(myContentPanel, point, editor.getComponent()).x;
JComponent header = editor.getHeaderComponent();
int shift = editor.getScrollingModel().getVerticalScrollOffset() - (header != null ? header.getHeight() : 0);
LogicalPosition position;
LineRange changeRange = e.getData(DiffDataKeys.CURRENT_CHANGE_RANGE);
if (changeRange == null) {
position = new LogicalPosition(editor.getCaretModel().getLogicalPosition().line + (next ? 1 : 0), 0);
}
else {
position = new LogicalPosition(next ? changeRange.end : changeRange.start, 0);
}
int y = editor.logicalPositionToXY(position).y - shift;
Point editorPoint = new Point(x, y);
final HintHint hintHint = createNotifyHint(editor.getComponent(), editorPoint, !next);
HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, editorPoint, HintManager.HIDE_BY_ANY_KEY |
HintManager.HIDE_BY_TEXT_CHANGE |
HintManager.HIDE_BY_SCROLLING, 0, false, hintHint);
}
}
@NotNull
private static HintHint createNotifyHint(@NotNull JComponent component, @NotNull Point point, boolean above) {
return new HintHint(component, point)
.setPreferredPosition(above ? Balloon.Position.above : Balloon.Position.below)
.setAwtTooltip(true)
.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD))
.setTextBg(HintUtil.INFORMATION_COLOR)
.setShowImmediately(true);
}
// Iterate requests
protected class MyNextChangeAction extends NextChangeAction {
@Override
public void update(@NotNull AnActionEvent e) {
if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
e.getPresentation().setEnabledAndVisible(true);
return;
}
if (!isNavigationEnabled()) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
e.getPresentation().setVisible(true);
e.getPresentation().setEnabled(hasNextChange());
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
if (!isNavigationEnabled() || !hasNextChange()) return;
goToNextChange(false);
}
}
protected class MyPrevChangeAction extends PrevChangeAction {
@Override
public void update(@NotNull AnActionEvent e) {
if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
e.getPresentation().setEnabledAndVisible(true);
return;
}
if (!isNavigationEnabled()) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
e.getPresentation().setVisible(true);
e.getPresentation().setEnabled(hasPrevChange());
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
if (!isNavigationEnabled() || !hasPrevChange()) return;
goToPrevChange(false);
}
}
//
// Helpers
//
private class MyPanel extends JPanel implements DataProvider {
public MyPanel() {
super(new BorderLayout());
}
@Override
public Dimension getPreferredSize() {
Dimension windowSize = DiffUtil.getDefaultDiffPanelSize();
Dimension size = super.getPreferredSize();
return new Dimension(Math.max(windowSize.width, size.width), Math.max(windowSize.height, size.height));
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
Object data;
DataProvider contentProvider = DataManagerImpl.getDataProviderEx(myContentPanel.getTargetComponent());
if (contentProvider != null) {
data = contentProvider.getData(dataId);
if (data != null) return data;
}
if (OpenInEditorAction.KEY.is(dataId)) {
return myOpenInEditorAction;
}
else if (DiffDataKeys.DIFF_REQUEST.is(dataId)) {
return myActiveRequest;
}
else if (CommonDataKeys.PROJECT.is(dataId)) {
return myProject;
}
else if (PlatformDataKeys.HELP_ID.is(dataId)) {
if (myActiveRequest.getUserData(DiffUserDataKeys.HELP_ID) != null) {
return myActiveRequest.getUserData(DiffUserDataKeys.HELP_ID);
}
else {
return "reference.dialogs.diff.file";
}
}
else if (DiffDataKeys.DIFF_CONTEXT.is(dataId)) {
return myContext;
}
data = myState.getData(dataId);
if (data != null) return data;
DataProvider requestProvider = myActiveRequest.getUserData(DiffUserDataKeys.DATA_PROVIDER);
if (requestProvider != null) {
data = requestProvider.getData(dataId);
if (data != null) return data;
}
DataProvider contextProvider = myContext.getUserData(DiffUserDataKeys.DATA_PROVIDER);
if (contextProvider != null) {
data = contextProvider.getData(dataId);
if (data != null) return data;
}
return null;
}
}
private static class MyProgressBar extends JBProgressBar {
private int myProgressCount = 0;
public MyProgressBar() {
setIndeterminate(true);
setVisible(false);
}
public void startProgress() {
myProgressCount++;
setVisible(true);
}
public void stopProgress() {
myProgressCount--;
LOG.assertTrue(myProgressCount >= 0);
if (myProgressCount == 0) setVisible(false);
}
}
private class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy {
@Override
public final Component getDefaultComponentImpl(final Container focusCycleRoot) {
JComponent component = DiffRequestProcessor.this.getPreferredFocusedComponent();
if (component == null) return null;
return IdeFocusTraversalPolicy.getPreferredFocusedComponent(component, this);
}
}
private class MyDiffContext extends DiffContextEx {
@NotNull private final UserDataHolder myContext;
public MyDiffContext(@NotNull UserDataHolder context) {
myContext = context;
}
@Override
public void reopenDiffRequest() {
updateRequest(true);
}
@Override
public void reloadDiffRequest() {
reloadRequest();
}
@Override
public void showProgressBar(boolean enabled) {
if (enabled) {
myProgressBar.startProgress();
}
else {
myProgressBar.stopProgress();
}
}
@Nullable
@Override
public Project getProject() {
return DiffRequestProcessor.this.getProject();
}
@Override
public boolean isFocused() {
return DiffRequestProcessor.this.isFocused();
}
@Override
public boolean isWindowFocused() {
return DiffRequestProcessor.this.isWindowFocused();
}
@Override
public void requestFocus() {
DiffRequestProcessor.this.requestFocusInternal();
}
@Nullable
@Override
public <T> T getUserData(@NotNull Key<T> key) {
return myContext.getUserData(key);
}
@Override
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
myContext.putUserData(key, value);
}
}
private static class ApplyData {
@NotNull private final DiffRequest request;
private final boolean force;
@Nullable private final ScrollToPolicy scrollToChangePolicy;
public ApplyData(@NotNull DiffRequest request, boolean force, @Nullable ScrollToPolicy scrollToChangePolicy) {
this.request = request;
this.force = force;
this.scrollToChangePolicy = scrollToChangePolicy;
}
}
//
// States
//
private interface ViewerState {
@RequiredDispatchThread
void init();
@RequiredDispatchThread
void destroy();
@Nullable
JComponent getPreferredFocusedComponent();
@Nullable
Object getData(@NonNls String dataId);
@NotNull
DiffTool getActiveTool();
}
private static class EmptyState implements ViewerState {
private static final EmptyState INSTANCE = new EmptyState();
@Override
public void init() {
}
@Override
public void destroy() {
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return null;
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
return null;
}
@NotNull
@Override
public DiffTool getActiveTool() {
return ErrorDiffTool.INSTANCE;
}
}
private class ErrorState implements ViewerState {
@Nullable private final DiffTool myDiffTool;
@NotNull private final MessageDiffRequest myRequest;
@NotNull private final DiffViewer myViewer;
public ErrorState(@NotNull MessageDiffRequest request) {
this(request, null);
}
public ErrorState(@NotNull MessageDiffRequest request, @Nullable DiffTool diffTool) {
myDiffTool = diffTool;
myRequest = request;
myViewer = ErrorDiffTool.INSTANCE.createComponent(myContext, myRequest);
}
@Override
@RequiredDispatchThread
public void init() {
myContentPanel.setContent(myViewer.getComponent());
FrameDiffTool.ToolbarComponents init = myViewer.init();
buildToolbar(init.toolbarActions);
}
@Override
@RequiredDispatchThread
public void destroy() {
Disposer.dispose(myViewer);
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return null;
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
return null;
}
@NotNull
@Override
public DiffTool getActiveTool() {
return myDiffTool != null ? myDiffTool : ErrorDiffTool.INSTANCE;
}
}
private class DefaultState implements ViewerState {
@NotNull private final DiffViewer myViewer;
@NotNull private final FrameDiffTool myTool;
public DefaultState(@NotNull DiffViewer viewer, @NotNull FrameDiffTool tool) {
myViewer = viewer;
myTool = tool;
}
@Override
@RequiredDispatchThread
public void init() {
myContentPanel.setContent(myViewer.getComponent());
setTitle(myActiveRequest.getTitle());
FrameDiffTool.ToolbarComponents toolbarComponents = myViewer.init();
buildToolbar(toolbarComponents.toolbarActions);
buildActionPopup(toolbarComponents.popupActions);
myToolbarStatusPanel.setContent(toolbarComponents.statusPanel);
}
@Override
@RequiredDispatchThread
public void destroy() {
Disposer.dispose(myViewer);
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myViewer.getPreferredFocusedComponent();
}
@NotNull
@Override
public DiffTool getActiveTool() {
return myTool;
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (DiffDataKeys.DIFF_VIEWER.is(dataId)) {
return myViewer;
}
return null;
}
}
private class WrapperState implements ViewerState {
@NotNull private final DiffViewer myViewer;
@NotNull private final FrameDiffTool myTool;
@NotNull private DiffViewer myWrapperViewer;
public WrapperState(@NotNull DiffViewer viewer, @NotNull FrameDiffTool tool, @NotNull DiffViewerWrapper wrapper) {
myViewer = viewer;
myTool = tool;
myWrapperViewer = wrapper.createComponent(myContext, myActiveRequest, myViewer);
}
@Override
@RequiredDispatchThread
public void init() {
myContentPanel.setContent(myWrapperViewer.getComponent());
setTitle(myActiveRequest.getTitle());
FrameDiffTool.ToolbarComponents toolbarComponents1 = myViewer.init();
FrameDiffTool.ToolbarComponents toolbarComponents2 = myWrapperViewer.init();
List<AnAction> toolbarActions = new ArrayList<AnAction>();
if (toolbarComponents1.toolbarActions != null) toolbarActions.addAll(toolbarComponents1.toolbarActions);
if (toolbarComponents2.toolbarActions != null) {
if (!toolbarActions.isEmpty() && !toolbarComponents2.toolbarActions.isEmpty()) toolbarActions.add(AnSeparator.getInstance());
toolbarActions.addAll(toolbarComponents2.toolbarActions);
}
buildToolbar(toolbarActions);
List<AnAction> popupActions = new ArrayList<AnAction>();
if (toolbarComponents1.popupActions != null) popupActions.addAll(toolbarComponents1.popupActions);
if (toolbarComponents2.popupActions != null) {
if (!popupActions.isEmpty() && !toolbarComponents2.popupActions.isEmpty()) popupActions.add(AnSeparator.getInstance());
popupActions.addAll(toolbarComponents2.popupActions);
}
buildActionPopup(popupActions);
myToolbarStatusPanel.setContent(toolbarComponents1.statusPanel); // TODO: combine both panels ?
}
@Override
@RequiredDispatchThread
public void destroy() {
Disposer.dispose(myViewer);
Disposer.dispose(myWrapperViewer);
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myWrapperViewer.getPreferredFocusedComponent();
}
@NotNull
@Override
public DiffTool getActiveTool() {
return myTool;
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (DiffDataKeys.WRAPPING_DIFF_VIEWER.is(dataId)) {
return myWrapperViewer;
}
if (DiffDataKeys.DIFF_VIEWER.is(dataId)) {
return myViewer;
}
return null;
}
}
}