2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.diff.impl;
18 import com.intellij.codeInsight.hint.HintManager;
19 import com.intellij.codeInsight.hint.HintManagerImpl;
20 import com.intellij.codeInsight.hint.HintUtil;
21 import com.intellij.diff.*;
22 import com.intellij.diff.FrameDiffTool.DiffViewer;
23 import com.intellij.diff.actions.impl.*;
24 import com.intellij.diff.impl.DiffSettingsHolder.DiffSettings;
25 import com.intellij.diff.requests.*;
26 import com.intellij.diff.tools.ErrorDiffTool;
27 import com.intellij.diff.tools.external.ExternalDiffTool;
28 import com.intellij.diff.tools.util.DiffDataKeys;
29 import com.intellij.diff.tools.util.PrevNextDifferenceIterable;
30 import com.intellij.diff.util.DiffUserDataKeys;
31 import com.intellij.diff.util.DiffUserDataKeysEx;
32 import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
33 import com.intellij.diff.util.DiffUtil;
34 import com.intellij.diff.util.LineRange;
35 import com.intellij.ide.DataManager;
36 import com.intellij.ide.impl.DataManagerImpl;
37 import com.intellij.internal.statistic.UsageTrigger;
38 import com.intellij.internal.statistic.beans.ConvertUsagesUtil;
39 import com.intellij.openapi.Disposable;
40 import com.intellij.openapi.actionSystem.*;
41 import com.intellij.openapi.actionSystem.ex.ActionUtil;
42 import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
43 import com.intellij.openapi.application.ApplicationManager;
44 import com.intellij.openapi.diagnostic.Logger;
45 import com.intellij.openapi.editor.Editor;
46 import com.intellij.openapi.editor.LogicalPosition;
47 import com.intellij.openapi.project.DumbAware;
48 import com.intellij.openapi.project.DumbAwareAction;
49 import com.intellij.openapi.project.Project;
50 import com.intellij.openapi.ui.Messages;
51 import com.intellij.openapi.ui.popup.Balloon;
52 import com.intellij.openapi.ui.popup.JBPopupFactory;
53 import com.intellij.openapi.ui.popup.ListPopup;
54 import com.intellij.openapi.util.Disposer;
55 import com.intellij.openapi.util.Key;
56 import com.intellij.openapi.util.UserDataHolder;
57 import com.intellij.openapi.util.UserDataHolderBase;
58 import com.intellij.openapi.wm.IdeFocusManager;
59 import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
60 import com.intellij.ui.HintHint;
61 import com.intellij.ui.JBProgressBar;
62 import com.intellij.ui.LightweightHint;
63 import com.intellij.ui.components.panels.Wrapper;
64 import com.intellij.util.containers.ContainerUtil;
65 import com.intellij.util.ui.JBUI;
66 import com.intellij.util.ui.UIUtil;
67 import org.jetbrains.annotations.CalledInAwt;
68 import org.jetbrains.annotations.NonNls;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
74 import java.util.ArrayList;
75 import java.util.List;
77 @SuppressWarnings("InnerClassMayBeStatic")
78 public abstract class DiffRequestProcessor implements Disposable {
79 private static final Logger LOG = Logger.getInstance(DiffRequestProcessor.class);
81 private boolean myDisposed;
83 @Nullable private final Project myProject;
84 @NotNull private final DiffContext myContext;
86 @NotNull private final DiffSettings mySettings;
87 @NotNull private final List<DiffTool> myAvailableTools;
88 @NotNull private final List<DiffTool> myToolOrder;
90 @NotNull private final OpenInEditorAction myOpenInEditorAction;
91 @Nullable private DefaultActionGroup myPopupActionGroup;
93 @NotNull private final JPanel myPanel;
94 @NotNull private final MyPanel myMainPanel;
95 @NotNull private final Wrapper myContentPanel;
96 @NotNull private final Wrapper myToolbarPanel; // TODO: allow to call 'updateToolbar' from Viewer ?
97 @NotNull private final Wrapper myToolbarStatusPanel;
98 @NotNull private final MyProgressBar myProgressBar;
100 @NotNull private DiffRequest myActiveRequest;
102 @NotNull private ViewerState myState;
104 public DiffRequestProcessor(@Nullable Project project) {
105 this(project, new UserDataHolderBase());
108 public DiffRequestProcessor(@Nullable Project project, @NotNull String place) {
109 this(project, DiffUtil.createUserDataHolder(DiffUserDataKeys.PLACE, place));
112 public DiffRequestProcessor(@Nullable Project project, @NotNull UserDataHolder context) {
115 myContext = new MyDiffContext(context);
116 myActiveRequest = new LoadingDiffRequest();
118 mySettings = DiffSettingsHolder.getInstance().getSettings(myContext.getUserData(DiffUserDataKeys.PLACE));
120 myAvailableTools = DiffManagerEx.getInstance().getDiffTools();
121 myToolOrder = new ArrayList<>(getToolOrderFromSettings(myAvailableTools));
125 myMainPanel = new MyPanel();
126 myContentPanel = new Wrapper();
127 myToolbarPanel = new Wrapper();
128 myToolbarPanel.setFocusable(true);
129 myToolbarStatusPanel = new Wrapper();
130 myProgressBar = new MyProgressBar();
132 myPanel = JBUI.Panels.simplePanel(myMainPanel);
134 JPanel statusPanel = JBUI.Panels.simplePanel(myToolbarStatusPanel).addToLeft(myProgressBar);
135 JPanel topPanel = JBUI.Panels.simplePanel(myToolbarPanel).addToRight(statusPanel);
137 myMainPanel.add(topPanel, BorderLayout.NORTH);
138 myMainPanel.add(myContentPanel, BorderLayout.CENTER);
140 myMainPanel.setFocusTraversalPolicyProvider(true);
141 myMainPanel.setFocusTraversalPolicy(new MyFocusTraversalPolicy());
143 JComponent bottomPanel = myContext.getUserData(DiffUserDataKeysEx.BOTTOM_PANEL);
144 if (bottomPanel != null) myMainPanel.add(bottomPanel, BorderLayout.SOUTH);
145 if (bottomPanel instanceof Disposable) Disposer.register(this, (Disposable)bottomPanel);
147 myState = EmptyState.INSTANCE;
148 myContentPanel.setContent(DiffUtil.createMessagePanel(((LoadingDiffRequest)myActiveRequest).getMessage()));
150 myOpenInEditorAction = new OpenInEditorAction(() -> onAfterNavigate());
158 protected void reloadRequest() {
163 public void updateRequest() {
164 updateRequest(false);
168 public void updateRequest(boolean force) {
169 updateRequest(force, null);
173 public abstract void updateRequest(boolean force, @Nullable ScrollToPolicy scrollToChangePolicy);
176 private FrameDiffTool getFittedTool() {
177 List<FrameDiffTool> tools = filterFittedTools(myToolOrder);
178 return tools.isEmpty() ? ErrorDiffTool.INSTANCE : tools.get(0);
182 private List<FrameDiffTool> getAvailableFittedTools() {
183 return filterFittedTools(myAvailableTools);
187 private List<FrameDiffTool> filterFittedTools(@NotNull List<DiffTool> tools) {
188 List<FrameDiffTool> result = new ArrayList<>();
189 for (DiffTool tool : tools) {
191 if (tool instanceof FrameDiffTool && tool.canShow(myContext, myActiveRequest)) {
192 result.add((FrameDiffTool)tool);
195 catch (Throwable e) {
200 return DiffUtil.filterSuppressedTools(result);
203 private void moveToolOnTop(@NotNull DiffTool tool) {
204 myToolOrder.remove(tool);
206 FrameDiffTool toolToReplace = getFittedTool();
209 for (index = 0; index < myToolOrder.size(); index++) {
210 if (myToolOrder.get(index) == toolToReplace) break;
212 myToolOrder.add(index, tool);
214 updateToolOrderSettings(myToolOrder);
218 private ViewerState createState() {
219 FrameDiffTool frameTool = getFittedTool();
221 DiffViewer viewer = frameTool.createComponent(myContext, myActiveRequest);
223 for (DiffExtension extension : DiffExtension.EP_NAME.getExtensions()) {
225 extension.onViewerCreated(viewer, myContext, myActiveRequest);
227 catch (Throwable e) {
232 DiffViewerWrapper wrapper = myActiveRequest.getUserData(DiffViewerWrapper.KEY);
233 if (wrapper == null) {
234 return new DefaultState(viewer, frameTool);
237 return new WrapperState(viewer, frameTool, wrapper);
245 @Nullable private ApplyData myQueuedApplyRequest;
248 protected void applyRequest(@NotNull DiffRequest request, boolean force, @Nullable ScrollToPolicy scrollToChangePolicy) {
249 ApplicationManager.getApplication().assertIsDispatchThread();
250 myIterationState = IterationState.NONE;
252 force = force || (myQueuedApplyRequest != null && myQueuedApplyRequest.force);
253 myQueuedApplyRequest = new ApplyData(request, force, scrollToChangePolicy);
255 IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(() -> {
256 if (myQueuedApplyRequest == null || myDisposed) return;
257 doApplyRequest(myQueuedApplyRequest.request, myQueuedApplyRequest.force, myQueuedApplyRequest.scrollToChangePolicy);
258 myQueuedApplyRequest = null;
263 private void doApplyRequest(@NotNull DiffRequest request, boolean force, @Nullable ScrollToPolicy scrollToChangePolicy) {
264 if (!force && request == myActiveRequest) return;
266 request.putUserData(DiffUserDataKeysEx.SCROLL_TO_CHANGE, scrollToChangePolicy);
268 boolean hadFocus = isFocused();
271 myToolbarStatusPanel.setContent(null);
272 myToolbarPanel.setContent(null);
273 myContentPanel.setContent(null);
274 ActionUtil.clearActions(myMainPanel);
276 myActiveRequest.onAssigned(false);
277 myActiveRequest = request;
278 myActiveRequest.onAssigned(true);
281 myState = createState();
284 catch (Throwable e) {
286 myState = new ErrorState(new ErrorDiffRequest("Error: can't show diff"), getFittedTool());
290 if (hadFocus) requestFocusInternal();
293 protected void setWindowTitle(@NotNull String title) {
296 protected void onAfterNavigate() {
300 protected void onDispose() {
304 public <T> T getContextUserData(@NotNull Key<T> key) {
305 return myContext.getUserData(key);
308 public <T> void putContextUserData(@NotNull Key<T> key, @Nullable T value) {
309 myContext.putUserData(key, value);
313 protected List<AnAction> getNavigationActions() {
314 return ContainerUtil.<AnAction>list(
315 new MyPrevDifferenceAction(),
316 new MyNextDifferenceAction(),
317 new MyPrevChangeAction(),
318 new MyNextChangeAction()
326 public boolean isWindowFocused() {
327 Window window = SwingUtilities.getWindowAncestor(myPanel);
328 return window != null && window.isFocused();
331 public boolean isFocused() {
332 return DiffUtil.isFocusedComponent(myProject, myPanel);
335 public void requestFocus() {
336 DiffUtil.requestFocus(myProject, getPreferredFocusedComponent());
339 protected void requestFocusInternal() {
340 JComponent component = getPreferredFocusedComponent();
341 if (component != null) component.requestFocusInWindow();
345 protected List<DiffTool> getToolOrderFromSettings(@NotNull List<DiffTool> availableTools) {
346 List<DiffTool> result = new ArrayList<>();
347 List<String> savedOrder = getSettings().getDiffToolsOrder();
349 for (final String clazz : savedOrder) {
350 DiffTool tool = ContainerUtil.find(availableTools, t -> t.getClass().getCanonicalName().equals(clazz));
351 if (tool != null) result.add(tool);
354 for (DiffTool tool : availableTools) {
355 if (!result.contains(tool)) result.add(tool);
361 protected void updateToolOrderSettings(@NotNull List<DiffTool> toolOrder) {
362 List<String> savedOrder = new ArrayList<>();
363 for (DiffTool tool : toolOrder) {
364 savedOrder.add(tool.getClass().getCanonicalName());
366 getSettings().setDiffToolsOrder(savedOrder);
370 public void dispose() {
371 if (myDisposed) return;
372 UIUtil.invokeLaterIfNeeded(() -> {
373 if (myDisposed) return;
379 myToolbarStatusPanel.setContent(null);
380 myToolbarPanel.setContent(null);
381 myContentPanel.setContent(null);
383 myActiveRequest.onAssigned(false);
385 myState = EmptyState.INSTANCE;
386 myActiveRequest = NoDiffRequest.INSTANCE;
391 protected DefaultActionGroup collectToolbarActions(@Nullable List<AnAction> viewerActions) {
392 DefaultActionGroup group = new DefaultActionGroup();
394 List<AnAction> navigationActions = new ArrayList<>();
395 navigationActions.addAll(getNavigationActions());
396 navigationActions.add(myOpenInEditorAction);
397 navigationActions.add(new MyChangeDiffToolAction());
398 DiffUtil.addActionBlock(group,
401 DiffUtil.addActionBlock(group, viewerActions);
403 List<AnAction> requestContextActions = myActiveRequest.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
404 DiffUtil.addActionBlock(group, requestContextActions);
406 List<AnAction> contextActions = myContext.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
407 DiffUtil.addActionBlock(group, contextActions);
409 DiffUtil.addActionBlock(group,
410 new ShowInExternalToolAction(),
411 new ShowOldDiffAction(),
412 ActionManager.getInstance().getAction(IdeActions.ACTION_CONTEXT_HELP));
418 protected DefaultActionGroup collectPopupActions(@Nullable List<AnAction> viewerActions) {
419 DefaultActionGroup group = new DefaultActionGroup();
421 List<AnAction> selectToolActions = new ArrayList<>();
422 for (DiffTool tool : getAvailableFittedTools()) {
423 if (tool == myState.getActiveTool()) continue;
424 selectToolActions.add(new DiffToolToggleAction(tool));
426 DiffUtil.addActionBlock(group, selectToolActions);
428 DiffUtil.addActionBlock(group, viewerActions);
433 protected void buildToolbar(@Nullable List<AnAction> viewerActions) {
434 ActionGroup group = collectToolbarActions(viewerActions);
435 ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.DIFF_TOOLBAR, group, true);
437 DataManager.registerDataProvider(toolbar.getComponent(), myMainPanel);
438 toolbar.setTargetComponent(toolbar.getComponent());
440 myToolbarPanel.setContent(toolbar.getComponent());
441 for (AnAction action : group.getChildren(null)) {
442 DiffUtil.registerAction(action, myMainPanel);
446 protected void buildActionPopup(@Nullable List<AnAction> viewerActions) {
447 ShowActionGroupPopupAction action = new ShowActionGroupPopupAction();
448 DiffUtil.registerAction(action, myMainPanel);
450 myPopupActionGroup = collectPopupActions(viewerActions);
453 private void setTitle(@Nullable String title) {
454 if (getContextUserData(DiffUserDataKeys.DO_NOT_CHANGE_WINDOW_TITLE) == Boolean.TRUE) return;
455 if (title == null) title = "Diff";
456 setWindowTitle(title);
464 public JComponent getComponent() {
469 public JComponent getPreferredFocusedComponent() {
470 JComponent component = myState.getPreferredFocusedComponent();
471 return component != null ? component : myToolbarPanel.getTargetComponent();
475 public Project getProject() {
480 public DiffContext getContext() {
485 protected DiffSettings getSettings() {
489 public boolean isDisposed() {
497 private class ShowInExternalToolAction extends DumbAwareAction {
498 public ShowInExternalToolAction() {
499 ActionUtil.copyFrom(this, "Diff.ShowInExternalTool");
503 public void update(AnActionEvent e) {
504 if (!ExternalDiffTool.isEnabled()) {
505 e.getPresentation().setEnabledAndVisible(false);
508 e.getPresentation().setEnabled(ExternalDiffTool.canShow(myActiveRequest));
509 e.getPresentation().setVisible(true);
513 public void actionPerformed(AnActionEvent e) {
515 ExternalDiffTool.showRequest(e.getProject(), myActiveRequest);
517 catch (Throwable ex) {
518 Messages.showErrorDialog(e.getProject(), ex.getMessage(), "Can't Show Diff In External Tool");
523 private class MyChangeDiffToolAction extends ComboBoxAction implements DumbAware {
524 public MyChangeDiffToolAction() {
525 // TODO: add icons for diff tools, show only icon in toolbar - to reduce jumping on change ?
526 setEnabledInModalContext(true);
530 public void update(AnActionEvent e) {
531 Presentation presentation = e.getPresentation();
533 DiffTool activeTool = myState.getActiveTool();
534 presentation.setText(activeTool.getName());
536 if (activeTool == ErrorDiffTool.INSTANCE) {
537 presentation.setEnabledAndVisible(false);
540 for (DiffTool tool : getAvailableFittedTools()) {
541 if (tool != activeTool) {
542 presentation.setEnabledAndVisible(true);
547 presentation.setEnabledAndVisible(false);
552 protected DefaultActionGroup createPopupActionGroup(JComponent button) {
553 DefaultActionGroup group = new DefaultActionGroup();
554 for (DiffTool tool : getAvailableFittedTools()) {
555 group.add(new DiffToolToggleAction(tool));
562 private class DiffToolToggleAction extends AnAction implements DumbAware {
563 @NotNull private final DiffTool myDiffTool;
565 private DiffToolToggleAction(@NotNull DiffTool tool) {
566 super(tool.getName());
567 setEnabledInModalContext(true);
572 public void actionPerformed(@NotNull AnActionEvent e) {
573 if (myState.getActiveTool() == myDiffTool) return;
575 UsageTrigger.trigger("diff.DiffSettings.Tool." + ConvertUsagesUtil.ensureProperKey(myDiffTool.getName()));
576 moveToolOnTop(myDiffTool);
582 private class ShowActionGroupPopupAction extends DumbAwareAction {
583 public ShowActionGroupPopupAction() {
584 ActionUtil.copyFrom(this, "Diff.ShowSettingsPopup");
588 public void update(AnActionEvent e) {
589 e.getPresentation().setEnabled(myPopupActionGroup != null && myPopupActionGroup.getChildrenCount() > 0);
593 public void actionPerformed(AnActionEvent e) {
594 assert myPopupActionGroup != null;
595 ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup("Diff Actions", myPopupActionGroup, e.getDataContext(),
596 JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);
597 popup.showInCenterOf(myPanel);
605 private enum IterationState {NEXT, PREV, NONE}
607 @NotNull private IterationState myIterationState = IterationState.NONE;
610 protected boolean hasNextChange() {
615 protected boolean hasPrevChange() {
620 protected void goToNextChange(boolean fromDifferences) {
624 protected void goToPrevChange(boolean fromDifferences) {
628 protected boolean isNavigationEnabled() {
632 protected class MyNextDifferenceAction extends NextDifferenceAction {
634 public void update(@NotNull AnActionEvent e) {
635 if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
636 e.getPresentation().setEnabledAndVisible(true);
640 PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
641 if (iterable != null && iterable.canGoNext()) {
642 e.getPresentation().setEnabled(true);
646 if (getSettings().isGoToNextFileOnNextDifference() && isNavigationEnabled() && hasNextChange()) {
647 e.getPresentation().setEnabled(true);
651 e.getPresentation().setEnabled(false);
655 public void actionPerformed(@NotNull AnActionEvent e) {
656 PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
657 if (iterable != null && iterable.canGoNext()) {
659 myIterationState = IterationState.NONE;
663 if (!isNavigationEnabled() || !hasNextChange() || !getSettings().isGoToNextFileOnNextDifference()) return;
665 if (myIterationState != IterationState.NEXT) {
666 notifyMessage(e, true);
667 myIterationState = IterationState.NEXT;
671 goToNextChange(true);
675 protected class MyPrevDifferenceAction extends PrevDifferenceAction {
677 public void update(@NotNull AnActionEvent e) {
678 if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
679 e.getPresentation().setEnabledAndVisible(true);
683 PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
684 if (iterable != null && iterable.canGoPrev()) {
685 e.getPresentation().setEnabled(true);
689 if (getSettings().isGoToNextFileOnNextDifference() && isNavigationEnabled() && hasPrevChange()) {
690 e.getPresentation().setEnabled(true);
694 e.getPresentation().setEnabled(false);
698 public void actionPerformed(@NotNull AnActionEvent e) {
699 PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
700 if (iterable != null && iterable.canGoPrev()) {
702 myIterationState = IterationState.NONE;
706 if (!isNavigationEnabled() || !hasPrevChange() || !getSettings().isGoToNextFileOnNextDifference()) return;
708 if (myIterationState != IterationState.PREV) {
709 notifyMessage(e, false);
710 myIterationState = IterationState.PREV;
714 goToPrevChange(true);
718 private void notifyMessage(@NotNull AnActionEvent e, boolean next) {
719 Editor editor = e.getData(DiffDataKeys.CURRENT_EDITOR);
721 // TODO: provide "change" word in chain UserData - for tests/etc
722 String message = DiffUtil.createNotificationText(next ? "Press again to go to the next file" : "Press again to go to the previous file",
723 "You can disable this feature in " + DiffUtil.getSettingsConfigurablePath());
725 final LightweightHint hint = new LightweightHint(HintUtil.createInformationLabel(message));
726 Point point = new Point(myContentPanel.getWidth() / 2, next ? myContentPanel.getHeight() - JBUI.scale(40) : JBUI.scale(40));
728 if (editor == null) {
729 final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
730 final HintHint hintHint = createNotifyHint(myContentPanel, point, next);
731 hint.show(myContentPanel, point.x, point.y, owner instanceof JComponent ? (JComponent)owner : null, hintHint);
734 int x = SwingUtilities.convertPoint(myContentPanel, point, editor.getComponent()).x;
736 JComponent header = editor.getHeaderComponent();
737 int shift = editor.getScrollingModel().getVerticalScrollOffset() - (header != null ? header.getHeight() : 0);
739 LogicalPosition position;
740 LineRange changeRange = e.getData(DiffDataKeys.CURRENT_CHANGE_RANGE);
741 if (changeRange == null) {
742 position = new LogicalPosition(editor.getCaretModel().getLogicalPosition().line + (next ? 1 : 0), 0);
745 position = new LogicalPosition(next ? changeRange.end : changeRange.start, 0);
747 int y = editor.logicalPositionToXY(position).y - shift;
749 Point editorPoint = new Point(x, y);
750 final HintHint hintHint = createNotifyHint(editor.getComponent(), editorPoint, !next);
751 HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, editorPoint, HintManager.HIDE_BY_ANY_KEY |
752 HintManager.HIDE_BY_TEXT_CHANGE |
753 HintManager.HIDE_BY_SCROLLING, 0, false, hintHint);
758 private static HintHint createNotifyHint(@NotNull JComponent component, @NotNull Point point, boolean above) {
759 return new HintHint(component, point)
760 .setPreferredPosition(above ? Balloon.Position.above : Balloon.Position.below)
762 .setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD))
763 .setTextBg(HintUtil.INFORMATION_COLOR)
764 .setShowImmediately(true);
769 protected class MyNextChangeAction extends NextChangeAction {
771 public void update(@NotNull AnActionEvent e) {
772 if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
773 e.getPresentation().setEnabledAndVisible(true);
777 if (!isNavigationEnabled()) {
778 e.getPresentation().setEnabledAndVisible(false);
782 e.getPresentation().setVisible(true);
783 e.getPresentation().setEnabled(hasNextChange());
787 public void actionPerformed(@NotNull AnActionEvent e) {
788 if (!isNavigationEnabled() || !hasNextChange()) return;
790 goToNextChange(false);
794 protected class MyPrevChangeAction extends PrevChangeAction {
796 public void update(@NotNull AnActionEvent e) {
797 if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
798 e.getPresentation().setEnabledAndVisible(true);
802 if (!isNavigationEnabled()) {
803 e.getPresentation().setEnabledAndVisible(false);
807 e.getPresentation().setVisible(true);
808 e.getPresentation().setEnabled(hasPrevChange());
812 public void actionPerformed(@NotNull AnActionEvent e) {
813 if (!isNavigationEnabled() || !hasPrevChange()) return;
815 goToPrevChange(false);
823 private class MyPanel extends JPanel implements DataProvider {
825 super(new BorderLayout());
829 public Dimension getPreferredSize() {
830 Dimension windowSize = DiffUtil.getDefaultDiffPanelSize();
831 Dimension size = super.getPreferredSize();
832 return new Dimension(Math.max(windowSize.width, size.width), Math.max(windowSize.height, size.height));
837 public Object getData(@NonNls String dataId) {
840 DataProvider contentProvider = DataManagerImpl.getDataProviderEx(myContentPanel.getTargetComponent());
841 if (contentProvider != null) {
842 data = contentProvider.getData(dataId);
843 if (data != null) return data;
846 if (OpenInEditorAction.KEY.is(dataId)) {
847 return myOpenInEditorAction;
849 else if (DiffDataKeys.DIFF_REQUEST.is(dataId)) {
850 return myActiveRequest;
852 else if (CommonDataKeys.PROJECT.is(dataId)) {
855 else if (PlatformDataKeys.HELP_ID.is(dataId)) {
856 if (myActiveRequest.getUserData(DiffUserDataKeys.HELP_ID) != null) {
857 return myActiveRequest.getUserData(DiffUserDataKeys.HELP_ID);
860 return "reference.dialogs.diff.file";
863 else if (DiffDataKeys.DIFF_CONTEXT.is(dataId)) {
867 data = myState.getData(dataId);
868 if (data != null) return data;
870 DataProvider requestProvider = myActiveRequest.getUserData(DiffUserDataKeys.DATA_PROVIDER);
871 if (requestProvider != null) {
872 data = requestProvider.getData(dataId);
873 if (data != null) return data;
876 DataProvider contextProvider = myContext.getUserData(DiffUserDataKeys.DATA_PROVIDER);
877 if (contextProvider != null) {
878 data = contextProvider.getData(dataId);
879 if (data != null) return data;
885 private static class MyProgressBar extends JBProgressBar {
886 private int myProgressCount = 0;
888 public MyProgressBar() {
889 setIndeterminate(true);
893 public void startProgress() {
898 public void stopProgress() {
900 LOG.assertTrue(myProgressCount >= 0);
901 if (myProgressCount == 0) setVisible(false);
905 private class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy {
907 public final Component getDefaultComponentImpl(final Container focusCycleRoot) {
908 JComponent component = DiffRequestProcessor.this.getPreferredFocusedComponent();
909 if (component == null) return null;
910 return IdeFocusTraversalPolicy.getPreferredFocusedComponent(component, this);
914 private class MyDiffContext extends DiffContextEx {
915 @NotNull private final UserDataHolder myContext;
917 public MyDiffContext(@NotNull UserDataHolder context) {
922 public void reopenDiffRequest() {
927 public void reloadDiffRequest() {
932 public void showProgressBar(boolean enabled) {
934 myProgressBar.startProgress();
937 myProgressBar.stopProgress();
943 public Project getProject() {
944 return DiffRequestProcessor.this.getProject();
948 public boolean isFocused() {
949 return DiffRequestProcessor.this.isFocused();
953 public boolean isWindowFocused() {
954 return DiffRequestProcessor.this.isWindowFocused();
958 public void requestFocus() {
959 DiffRequestProcessor.this.requestFocusInternal();
964 public <T> T getUserData(@NotNull Key<T> key) {
965 return myContext.getUserData(key);
969 public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
970 myContext.putUserData(key, value);
974 private static class ApplyData {
975 @NotNull private final DiffRequest request;
976 private final boolean force;
977 @Nullable private final ScrollToPolicy scrollToChangePolicy;
979 public ApplyData(@NotNull DiffRequest request, boolean force, @Nullable ScrollToPolicy scrollToChangePolicy) {
980 this.request = request;
982 this.scrollToChangePolicy = scrollToChangePolicy;
990 private interface ViewerState {
998 JComponent getPreferredFocusedComponent();
1001 Object getData(@NonNls String dataId);
1004 DiffTool getActiveTool();
1007 private static class EmptyState implements ViewerState {
1008 private static final EmptyState INSTANCE = new EmptyState();
1011 public void init() {
1015 public void destroy() {
1020 public JComponent getPreferredFocusedComponent() {
1026 public Object getData(@NonNls String dataId) {
1032 public DiffTool getActiveTool() {
1033 return ErrorDiffTool.INSTANCE;
1037 private class ErrorState implements ViewerState {
1038 @Nullable private final DiffTool myDiffTool;
1039 @NotNull private final MessageDiffRequest myRequest;
1041 @NotNull private final DiffViewer myViewer;
1043 public ErrorState(@NotNull MessageDiffRequest request) {
1044 this(request, null);
1047 public ErrorState(@NotNull MessageDiffRequest request, @Nullable DiffTool diffTool) {
1048 myDiffTool = diffTool;
1049 myRequest = request;
1051 myViewer = ErrorDiffTool.INSTANCE.createComponent(myContext, myRequest);
1056 public void init() {
1057 myContentPanel.setContent(myViewer.getComponent());
1059 FrameDiffTool.ToolbarComponents init = myViewer.init();
1060 buildToolbar(init.toolbarActions);
1065 public void destroy() {
1066 Disposer.dispose(myViewer);
1071 public JComponent getPreferredFocusedComponent() {
1077 public Object getData(@NonNls String dataId) {
1083 public DiffTool getActiveTool() {
1084 return myDiffTool != null ? myDiffTool : ErrorDiffTool.INSTANCE;
1088 private class DefaultState implements ViewerState {
1089 @NotNull private final DiffViewer myViewer;
1090 @NotNull private final FrameDiffTool myTool;
1092 public DefaultState(@NotNull DiffViewer viewer, @NotNull FrameDiffTool tool) {
1099 public void init() {
1100 myContentPanel.setContent(myViewer.getComponent());
1101 setTitle(myActiveRequest.getTitle());
1103 FrameDiffTool.ToolbarComponents toolbarComponents = myViewer.init();
1105 buildToolbar(toolbarComponents.toolbarActions);
1106 buildActionPopup(toolbarComponents.popupActions);
1108 myToolbarStatusPanel.setContent(toolbarComponents.statusPanel);
1113 public void destroy() {
1114 Disposer.dispose(myViewer);
1119 public JComponent getPreferredFocusedComponent() {
1120 return myViewer.getPreferredFocusedComponent();
1125 public DiffTool getActiveTool() {
1131 public Object getData(@NonNls String dataId) {
1132 if (DiffDataKeys.DIFF_VIEWER.is(dataId)) {
1139 private class WrapperState implements ViewerState {
1140 @NotNull private final DiffViewer myViewer;
1141 @NotNull private final FrameDiffTool myTool;
1143 @NotNull private DiffViewer myWrapperViewer;
1145 public WrapperState(@NotNull DiffViewer viewer, @NotNull FrameDiffTool tool, @NotNull DiffViewerWrapper wrapper) {
1148 myWrapperViewer = wrapper.createComponent(myContext, myActiveRequest, myViewer);
1153 public void init() {
1154 myContentPanel.setContent(myWrapperViewer.getComponent());
1155 setTitle(myActiveRequest.getTitle());
1158 FrameDiffTool.ToolbarComponents toolbarComponents1 = myViewer.init();
1159 FrameDiffTool.ToolbarComponents toolbarComponents2 = myWrapperViewer.init();
1161 List<AnAction> toolbarActions = new ArrayList<>();
1162 if (toolbarComponents1.toolbarActions != null) toolbarActions.addAll(toolbarComponents1.toolbarActions);
1163 if (toolbarComponents2.toolbarActions != null) {
1164 if (!toolbarActions.isEmpty() && !toolbarComponents2.toolbarActions.isEmpty()) toolbarActions.add(Separator.getInstance());
1165 toolbarActions.addAll(toolbarComponents2.toolbarActions);
1167 buildToolbar(toolbarActions);
1169 List<AnAction> popupActions = new ArrayList<>();
1170 if (toolbarComponents1.popupActions != null) popupActions.addAll(toolbarComponents1.popupActions);
1171 if (toolbarComponents2.popupActions != null) {
1172 if (!popupActions.isEmpty() && !toolbarComponents2.popupActions.isEmpty()) popupActions.add(Separator.getInstance());
1173 popupActions.addAll(toolbarComponents2.popupActions);
1175 buildActionPopup(popupActions);
1178 myToolbarStatusPanel.setContent(toolbarComponents1.statusPanel); // TODO: combine both panels ?
1183 public void destroy() {
1184 Disposer.dispose(myViewer);
1185 Disposer.dispose(myWrapperViewer);
1190 public JComponent getPreferredFocusedComponent() {
1191 return myWrapperViewer.getPreferredFocusedComponent();
1196 public DiffTool getActiveTool() {
1202 public Object getData(@NonNls String dataId) {
1203 if (DiffDataKeys.WRAPPING_DIFF_VIEWER.is(dataId)) {
1204 return myWrapperViewer;
1206 if (DiffDataKeys.DIFF_VIEWER.is(dataId)) {