/*
* 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.tools.util.base;
import com.intellij.diff.DiffContext;
import com.intellij.diff.FrameDiffTool;
import com.intellij.diff.FrameDiffTool.DiffViewer;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.util.DiffTaskQueue;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.pom.Navigatable;
import com.intellij.util.Alarm;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.*;
import consulo.annotations.RequiredDispatchThread;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public abstract class DiffViewerBase implements DiffViewer, DataProvider {
protected static final Logger LOG = Logger.getInstance(DiffViewerBase.class);
@NotNull private final List<DiffViewerListener> myListeners = new SmartList<>();
@Nullable protected final Project myProject;
@NotNull protected final DiffContext myContext;
@NotNull protected final ContentDiffRequest myRequest;
@NotNull private final DiffTaskQueue myTaskExecutor = new DiffTaskQueue();
@NotNull private final Alarm myTaskAlarm = new Alarm();
private volatile boolean myDisposed;
public DiffViewerBase(@NotNull DiffContext context, @NotNull ContentDiffRequest request) {
myProject = context.getProject();
myContext = context;
myRequest = request;
}
@NotNull
@Override
public final FrameDiffTool.ToolbarComponents init() {
processContextHints();
onInit();
FrameDiffTool.ToolbarComponents components = new FrameDiffTool.ToolbarComponents();
components.toolbarActions = createToolbarActions();
components.popupActions = createPopupActions();
components.statusPanel = getStatusPanel();
fireEvent(EventType.INIT);
rediff(true);
return components;
}
@Override
@RequiredDispatchThread
public final void dispose() {
if (myDisposed) return;
if (!ApplicationManager.getApplication().isDispatchThread()) LOG.warn(new Throwable("dispose() not from EDT"));
UIUtil.invokeLaterIfNeeded(() -> {
if (myDisposed) return;
myDisposed = true;
abortRediff();
updateContextHints();
fireEvent(EventType.DISPOSE);
onDispose();
});
}
@RequiredDispatchThread
protected void processContextHints() {
}
@RequiredDispatchThread
protected void updateContextHints() {
}
@RequiredDispatchThread
public final void scheduleRediff() {
if (isDisposed()) return;
abortRediff();
myTaskAlarm.addRequest(this::rediff, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS);
}
@RequiredDispatchThread
public final void abortRediff() {
myTaskExecutor.abort();
myTaskAlarm.cancelAllRequests();
fireEvent(EventType.REDIFF_ABORTED);
}
@RequiredDispatchThread
public final void rediff() {
rediff(false);
}
@RequiredDispatchThread
public void rediff(boolean trySync) {
if (isDisposed()) return;
abortRediff();
fireEvent(EventType.BEFORE_REDIFF);
onBeforeRediff();
boolean forceEDT = forceRediffSynchronously();
int waitMillis = trySync || tryRediffSynchronously() ? ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS : 0;
myTaskExecutor.executeAndTryWait(
indicator -> {
final Runnable callback = performRediff(indicator);
return () -> {
callback.run();
onAfterRediff();
fireEvent(EventType.AFTER_REDIFF);
};
},
this::onSlowRediff, waitMillis, forceEDT
);
}
//
// Getters
//
@Nullable
public Project getProject() {
return myProject;
}
@NotNull
public ContentDiffRequest getRequest() {
return myRequest;
}
@NotNull
public DiffContext getContext() {
return myContext;
}
public boolean isDisposed() {
return myDisposed;
}
//
// Abstract
//
@RequiredDispatchThread
protected boolean tryRediffSynchronously() {
return myContext.isWindowFocused();
}
@RequiredDispatchThread
protected boolean forceRediffSynchronously() {
// most of performRediff implementations take ReadLock inside. If EDT is holding write lock - this will never happen,
// and diff will not be calculated. This could happen for diff from FileDocumentManager.
return ApplicationManager.getApplication().isWriteAccessAllowed();
}
protected List<AnAction> createToolbarActions() {
List<AnAction> group = new ArrayList<>();
ContainerUtil.addAll(group, ((ActionGroup)ActionManager.getInstance().getAction(IdeActions.DIFF_VIEWER_TOOLBAR)).getChildren(null));
return group;
}
protected List<AnAction> createPopupActions() {
List<AnAction> group = new ArrayList<>();
ContainerUtil.addAll(group, ((ActionGroup)ActionManager.getInstance().getAction(IdeActions.DIFF_VIEWER_POPUP)).getChildren(null));
return group;
}
@Nullable
protected JComponent getStatusPanel() {
return null;
}
@RequiredDispatchThread
protected void onInit() {
}
@RequiredDispatchThread
protected void onSlowRediff() {
}
@RequiredDispatchThread
protected void onBeforeRediff() {
}
@RequiredDispatchThread
protected void onAfterRediff() {
}
@NotNull
protected abstract Runnable performRediff(@NotNull ProgressIndicator indicator);
@RequiredDispatchThread
protected void onDispose() {
Disposer.dispose(myTaskAlarm);
}
@Nullable
protected Navigatable getNavigatable() {
return null;
}
//
// Listeners
//
@RequiredDispatchThread
public void addListener(@NotNull DiffViewerListener listener) {
myListeners.add(listener);
}
@RequiredDispatchThread
public void removeListener(@NotNull DiffViewerListener listener) {
myListeners.remove(listener);
}
@NotNull
@RequiredDispatchThread
protected List<DiffViewerListener> getListeners() {
return myListeners;
}
@RequiredDispatchThread
private void fireEvent(@NotNull EventType type) {
for (DiffViewerListener listener : myListeners) {
switch (type) {
case INIT:
listener.onInit();
break;
case DISPOSE:
listener.onDispose();
break;
case BEFORE_REDIFF:
listener.onBeforeRediff();
break;
case AFTER_REDIFF:
listener.onAfterRediff();
break;
case REDIFF_ABORTED:
listener.onRediffAborted();
break;
}
}
}
//
// Helpers
//
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
return getNavigatable();
}
else if (CommonDataKeys.PROJECT.is(dataId)) {
return myProject;
}
else {
return null;
}
}
private enum EventType {
INIT, DISPOSE, BEFORE_REDIFF, AFTER_REDIFF, REDIFF_ABORTED,
}
}