/*
* 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.simple;
import com.intellij.diff.DiffContext;
import com.intellij.diff.fragments.MergeLineFragment;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.tools.util.*;
import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffDividerDrawUtil.DividerPaintable;
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredDispatchThread;
import javax.swing.*;
import java.awt.*;
import java.util.Iterator;
import java.util.List;
public abstract class ThreesideTextDiffViewerEx extends ThreesideTextDiffViewer {
public static final Logger LOG = Logger.getInstance(ThreesideTextDiffViewerEx.class);
@NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable1;
@NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable2;
@NotNull protected final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
@NotNull protected final MyStatusPanel myStatusPanel;
@NotNull protected final MyFoldingModel myFoldingModel;
@NotNull protected final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
private int myChangesCount = -1;
private int myConflictsCount = -1;
public ThreesideTextDiffViewerEx(@NotNull DiffContext context, @NotNull ContentDiffRequest request) {
super(context, request);
mySyncScrollable1 = new MySyncScrollable(Side.LEFT);
mySyncScrollable2 = new MySyncScrollable(Side.RIGHT);
myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
myStatusPanel = new MyStatusPanel();
myFoldingModel = new MyFoldingModel(getEditors().toArray(new EditorEx[3]), this);
}
@Override
@RequiredDispatchThread
protected void onInit() {
super.onInit();
myContentPanel.setPainter(new MyDividerPainter(Side.LEFT), Side.LEFT);
myContentPanel.setPainter(new MyDividerPainter(Side.RIGHT), Side.RIGHT);
myContentPanel.setScrollbarPainter(new MyScrollbarPainter());
}
@Override
@RequiredDispatchThread
protected void onDispose() {
destroyChangedBlocks();
super.onDispose();
}
@Override
@RequiredDispatchThread
protected void processContextHints() {
super.processContextHints();
myInitialScrollHelper.processContext(myRequest);
}
@Override
@RequiredDispatchThread
protected void updateContextHints() {
super.updateContextHints();
myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
myInitialScrollHelper.updateContext(myRequest);
}
//
// Diff
//
@NotNull
public FoldingModelSupport.Settings getFoldingModelSettings() {
return TextDiffViewerUtil.getFoldingModelSettings(myContext);
}
@NotNull
protected Runnable applyNotification(@Nullable final JComponent notification) {
return new Runnable() {
@Override
public void run() {
clearDiffPresentation();
if (notification != null) myPanel.addNotification(notification);
}
};
}
protected void clearDiffPresentation() {
myStatusPanel.setBusy(false);
myPanel.resetNotifications();
destroyChangedBlocks();
myContentPanel.repaintDividers();
myStatusPanel.update();
}
protected void destroyChangedBlocks() {
myFoldingModel.destroy();
}
//
// Impl
//
@Override
protected void onDocumentChange(@NotNull DocumentEvent e) {
super.onDocumentChange(e);
myFoldingModel.onDocumentChanged(e);
}
@RequiredDispatchThread
protected boolean doScrollToChange(@NotNull ScrollToPolicy scrollToPolicy) {
ThreesideDiffChangeBase targetChange = scrollToPolicy.select(getChanges());
if (targetChange == null) return false;
doScrollToChange(targetChange, false);
return true;
}
protected void doScrollToChange(@NotNull ThreesideDiffChangeBase change, boolean animated) {
int[] startLines = new int[3];
int[] endLines = new int[3];
for (int i = 0; i < 3; i++) {
ThreeSide side = ThreeSide.fromIndex(i);
startLines[i] = change.getStartLine(side);
endLines[i] = change.getEndLine(side);
DiffUtil.moveCaret(getEditor(side), startLines[i]);
}
getSyncScrollSupport().makeVisible(getCurrentSide(), startLines, endLines, animated);
}
//
// Counters
//
public int getChangesCount() {
return myChangesCount;
}
public int getConflictsCount() {
return myConflictsCount;
}
protected void resetChangeCounters() {
myChangesCount = 0;
myConflictsCount = 0;
}
protected void onChangeAdded(@NotNull ThreesideDiffChangeBase change) {
if (change.isConflict()) {
myConflictsCount++;
}
else {
myChangesCount++;
}
myStatusPanel.update();
}
protected void onChangeRemoved(@NotNull ThreesideDiffChangeBase change) {
if (change.isConflict()) {
myConflictsCount--;
}
else {
myChangesCount--;
}
myStatusPanel.update();
}
//
// Getters
//
@NotNull
protected abstract DividerPaintable getDividerPaintable(@NotNull Side side);
/*
* Some changes (ex: applied ones) can be excluded from general processing, but should be painted/used for synchronized scrolling
*/
@NotNull
protected List<? extends ThreesideDiffChangeBase> getAllChanges() {
return getChanges();
}
@NotNull
protected abstract List<? extends ThreesideDiffChangeBase> getChanges();
@NotNull
@Override
protected SyncScrollSupport.SyncScrollable getSyncScrollable(@NotNull Side side) {
return side.select(mySyncScrollable1, mySyncScrollable2);
}
@NotNull
@Override
protected JComponent getStatusPanel() {
return myStatusPanel;
}
@NotNull
public SyncScrollSupport.ThreesideSyncScrollSupport getSyncScrollSupport() {
//noinspection ConstantConditions
return mySyncScrollSupport;
}
//
// Misc
//
@Nullable
@RequiredDispatchThread
protected ThreesideDiffChangeBase getSelectedChange(@NotNull ThreeSide side) {
int caretLine = getEditor(side).getCaretModel().getLogicalPosition().line;
for (ThreesideDiffChangeBase change : getChanges()) {
int line1 = change.getStartLine(side);
int line2 = change.getEndLine(side);
if (DiffUtil.isSelectedByLine(caretLine, line1, line2)) return change;
}
return null;
}
//
// Actions
//
protected class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<ThreesideDiffChangeBase> {
@NotNull
@Override
protected List<? extends ThreesideDiffChangeBase> getChanges() {
return ThreesideTextDiffViewerEx.this.getChanges();
}
@NotNull
@Override
protected EditorEx getEditor() {
return getCurrentEditor();
}
@Override
protected int getStartLine(@NotNull ThreesideDiffChangeBase change) {
return change.getStartLine(getCurrentSide());
}
@Override
protected int getEndLine(@NotNull ThreesideDiffChangeBase change) {
return change.getEndLine(getCurrentSide());
}
@Override
protected void scrollToChange(@NotNull ThreesideDiffChangeBase change) {
doScrollToChange(change, true);
}
}
protected class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
public MyToggleExpandByDefaultAction() {
super(getTextSettings());
}
@Override
protected void expandAll(boolean expand) {
myFoldingModel.expandAll(expand);
}
}
//
// Helpers
//
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
return myPrevNextDifferenceIterable;
}
else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
ThreesideDiffChangeBase change = getSelectedChange(getCurrentSide());
if (change != null) {
return new LineRange(change.getStartLine(getCurrentSide()), change.getEndLine(getCurrentSide()));
}
}
return super.getData(dataId);
}
protected class MySyncScrollable extends BaseSyncScrollable {
@NotNull private final Side mySide;
public MySyncScrollable(@NotNull Side side) {
mySide = side;
}
@RequiredDispatchThread
@Override
public boolean isSyncScrollEnabled() {
return getTextSettings().isEnableSyncScroll();
}
@Override
protected void processHelper(@NotNull ScrollHelper helper) {
ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
if (!helper.process(0, 0)) return;
for (ThreesideDiffChangeBase diffChange : getAllChanges()) {
if (!helper.process(diffChange.getStartLine(left), diffChange.getStartLine(right))) return;
if (!helper.process(diffChange.getEndLine(left), diffChange.getEndLine(right))) return;
}
helper.process(getEditor(left).getDocument().getLineCount(), getEditor(right).getDocument().getLineCount());
}
}
protected class MyDividerPainter implements DiffSplitter.Painter {
@NotNull private final Side mySide;
@NotNull private final DividerPaintable myPaintable;
public MyDividerPainter(@NotNull Side side) {
mySide = side;
myPaintable = getDividerPaintable(side);
}
@Override
public void paint(@NotNull Graphics g, @NotNull JComponent divider) {
Graphics2D gg = DiffDividerDrawUtil.getDividerGraphics(g, divider, getEditor(ThreeSide.BASE).getComponent());
gg.setColor(DiffDrawUtil.getDividerColor(getEditor(ThreeSide.BASE)));
gg.fill(gg.getClipBounds());
Editor editor1 = mySide.select(getEditor(ThreeSide.LEFT), getEditor(ThreeSide.BASE));
Editor editor2 = mySide.select(getEditor(ThreeSide.BASE), getEditor(ThreeSide.RIGHT));
//DividerPolygonUtil.paintSimplePolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
DiffDividerDrawUtil.paintPolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
myFoldingModel.paintOnDivider(gg, divider, mySide);
gg.dispose();
}
}
protected class MyScrollbarPainter implements Consumer<Graphics> {
@NotNull private final DividerPaintable myPaintable = getDividerPaintable(Side.RIGHT);
@Override
public void consume(Graphics g) {
EditorEx editor1 = getEditor(ThreeSide.BASE);
EditorEx editor2 = getEditor(ThreeSide.RIGHT);
int width = editor1.getScrollPane().getVerticalScrollBar().getWidth();
DiffDividerDrawUtil.paintPolygonsOnScrollbar((Graphics2D)g, width, editor1, editor2, myPaintable);
myFoldingModel.paintOnScrollbar((Graphics2D)g, width);
}
}
protected class MyStatusPanel extends StatusPanel {
@Nullable
@Override
protected String getMessage() {
if (myChangesCount < 0 || myConflictsCount < 0) return null;
if (myChangesCount == 0 && myConflictsCount == 0) {
return DiffBundle.message("merge.dialog.all.conflicts.resolved.message.text");
}
return makeCounterWord(myChangesCount, "change") + ". " + makeCounterWord(myConflictsCount, "conflict");
}
@NotNull
private String makeCounterWord(int number, @NotNull String word) {
if (number == 0) {
return "No " + StringUtil.pluralize(word);
}
return number + " " + StringUtil.pluralize(word, number);
}
}
protected static class MyFoldingModel extends FoldingModelSupport {
private final MyPaintable myPaintable1 = new MyPaintable(0, 1);
private final MyPaintable myPaintable2 = new MyPaintable(1, 2);
public MyFoldingModel(@NotNull EditorEx[] editors, @NotNull Disposable disposable) {
super(editors, disposable);
assert editors.length == 3;
}
public void install(@Nullable List<MergeLineFragment> fragments,
@NotNull UserDataHolder context,
@NotNull FoldingModelSupport.Settings settings) {
Iterator<int[]> it = map(fragments, new Function<MergeLineFragment, int[]>() {
@Override
public int[] fun(MergeLineFragment fragment) {
return new int[]{
fragment.getStartLine(ThreeSide.LEFT),
fragment.getEndLine(ThreeSide.LEFT),
fragment.getStartLine(ThreeSide.BASE),
fragment.getEndLine(ThreeSide.BASE),
fragment.getStartLine(ThreeSide.RIGHT),
fragment.getEndLine(ThreeSide.RIGHT)};
}
});
install(it, context, settings);
}
public void paintOnDivider(@NotNull Graphics2D gg, @NotNull Component divider, @NotNull Side side) {
MyPaintable paintable = side.select(myPaintable1, myPaintable2);
paintable.paintOnDivider(gg, divider);
}
public void paintOnScrollbar(@NotNull Graphics2D gg, int width) {
myPaintable2.paintOnScrollbar(gg, width);
}
}
protected class MyInitialScrollHelper extends MyInitialScrollPositionHelper {
@Override
protected boolean doScrollToChange() {
if (myScrollToChange == null) return false;
return ThreesideTextDiffViewerEx.this.doScrollToChange(myScrollToChange);
}
@Override
protected boolean doScrollToFirstChange() {
return ThreesideTextDiffViewerEx.this.doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
}
}
}