blob: 3bc3e90b81b375cd767cfa267d23a15ca39d62b2 [file] [log] [blame]
/*
* Copyright 2000-2014 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.diff.impl.util;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.EditingSides;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.ScrollingModel;
import com.intellij.openapi.editor.event.VisibleAreaEvent;
import com.intellij.openapi.editor.event.VisibleAreaListener;
import com.intellij.openapi.util.Disposer;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class SyncScrollSupport implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.util.SyncScrollSupport");
private boolean myDuringVerticalScroll = false;
@NotNull private final ArrayList<ScrollListener> myScrollers = new ArrayList<ScrollListener>();
private boolean myEnabled = true;
public void install(EditingSides[] sideContainers) {
Disposer.dispose(this);
Editor[] editors = new Editor[sideContainers.length + 1];
editors[0] = sideContainers[0].getEditor(FragmentSide.SIDE1);
for (int i = 0; i < sideContainers.length; i++) {
EditingSides sideContainer = sideContainers[i];
LOG.assertTrue(sideContainer.getEditor(FragmentSide.SIDE1) == editors[i]);
editors[i + 1] = sideContainer.getEditor(FragmentSide.SIDE2);
}
if (editors.length == 3) install3(editors, sideContainers);
else if (editors.length == 2) install2(editors, sideContainers);
else LOG.error(String.valueOf(editors.length));
}
public void dispose() {
for (ScrollListener scrollListener : myScrollers) {
Disposer.dispose(scrollListener);
}
myScrollers.clear();
}
public void setEnabled(boolean enabled) {
myEnabled = enabled;
}
public boolean isEnabled() {
return myEnabled;
}
private void install2(@NotNull Editor[] editors, @NotNull EditingSides[] sideContainers) {
addSlavesScroller(editors[0], new ScrollingContext(FragmentSide.SIDE1, sideContainers[0], FragmentSide.SIDE1));
addSlavesScroller(editors[1], new ScrollingContext(FragmentSide.SIDE2, sideContainers[0], FragmentSide.SIDE2));
}
private void install3(@NotNull Editor[] editors, @NotNull EditingSides[] sideContainers) {
addSlavesScroller(editors[0],
new ScrollingContext(FragmentSide.SIDE1, sideContainers[0], FragmentSide.SIDE2),
new ScrollingContext(FragmentSide.SIDE1, sideContainers[1], FragmentSide.SIDE1));
addSlavesScroller(editors[1],
new ScrollingContext(FragmentSide.SIDE2, sideContainers[0], FragmentSide.SIDE1),
new ScrollingContext(FragmentSide.SIDE1, sideContainers[1], FragmentSide.SIDE1));
addSlavesScroller(editors[2],
new ScrollingContext(FragmentSide.SIDE2, sideContainers[1], FragmentSide.SIDE2),
new ScrollingContext(FragmentSide.SIDE2, sideContainers[0], FragmentSide.SIDE1));
}
private void addSlavesScroller(@NotNull Editor editor, @NotNull ScrollingContext... contexts) {
ScrollListener scroller = new ScrollListener(contexts, editor);
scroller.install();
myScrollers.add(scroller);
}
private class ScrollListener implements VisibleAreaListener, Disposable {
private ScrollingContext[] myScrollContexts;
@NotNull private final Editor myEditor;
public ScrollListener(@NotNull ScrollingContext[] scrollContexts, @NotNull Editor editor) {
myScrollContexts = scrollContexts;
myEditor = editor;
}
public void install() {
myEditor.getScrollingModel().addVisibleAreaListener(this);
}
@Override
public void dispose() {
myEditor.getScrollingModel().removeVisibleAreaListener(this);
myScrollContexts = null;
}
@Override
public void visibleAreaChanged(VisibleAreaEvent e) {
if (!myEnabled || myDuringVerticalScroll) return;
Rectangle newRectangle = e.getNewRectangle();
Rectangle oldRectangle = e.getOldRectangle();
if (newRectangle == null || oldRectangle == null) return;
myDuringVerticalScroll = true;
try {
for (ScrollingContext context : myScrollContexts) {
syncVerticalScroll(context, newRectangle, oldRectangle);
syncHorizontalScroll(context, newRectangle, oldRectangle);
}
}
finally { myDuringVerticalScroll = false; }
}
}
private static void syncHorizontalScroll(@NotNull ScrollingContext context,
@NotNull Rectangle newRectangle,
@NotNull Rectangle oldRectangle) {
int newScrollOffset = newRectangle.x;
if (newScrollOffset == oldRectangle.x) return;
EditingSides sidesContainer = context.getSidesContainer();
FragmentSide masterSide = context.getMasterSide();
Editor slaveEditor = sidesContainer.getEditor(masterSide.otherSide());
if (slaveEditor == null) return;
doScrollHorizontally(slaveEditor.getScrollingModel(), newScrollOffset);
}
private static void syncVerticalScroll(@NotNull ScrollingContext context,
@NotNull Rectangle newRectangle,
@NotNull Rectangle oldRectangle) {
if (newRectangle.y == oldRectangle.y) return;
EditingSides sidesContainer = context.getSidesContainer();
FragmentSide masterSide = context.getMasterSide();
FragmentSide masterDiffSide = context.getMasterDiffSide();
Editor master = sidesContainer.getEditor(masterSide);
Editor slave = sidesContainer.getEditor(masterSide.otherSide());
if (master == null || slave == null) return;
int masterVerticalScrollOffset = master.getScrollingModel().getVerticalScrollOffset();
int slaveVerticalScrollOffset = slave.getScrollingModel().getVerticalScrollOffset();
Rectangle viewRect = master.getScrollingModel().getVisibleArea();
int middleY = viewRect.height / 3;
if (master.getDocument().getTextLength() == 0) return;
LogicalPosition masterPos = master.xyToLogicalPosition(new Point(viewRect.x, masterVerticalScrollOffset + middleY));
int masterCenterLine = masterPos.line;
int scrollToLine = sidesContainer.getLineBlocks().transform(masterDiffSide, masterCenterLine);
int offset;
if (scrollToLine < 0) {
offset = slaveVerticalScrollOffset + newRectangle.y - oldRectangle.y;
}
else {
int correction = (masterVerticalScrollOffset + middleY) % master.getLineHeight();
Point point = slave.logicalPositionToXY(new LogicalPosition(scrollToLine, masterPos.column));
offset = point.y - middleY + correction;
}
int deltaHeaderOffset = getHeaderOffset(slave) - getHeaderOffset(master);
doScrollVertically(slave.getScrollingModel(), offset + deltaHeaderOffset);
}
private static int getHeaderOffset(@NotNull final Editor editor) {
final JComponent header = editor.getHeaderComponent();
return header == null ? 0 : header.getHeight();
}
private static void doScrollVertically(@NotNull ScrollingModel model, int offset) {
model.disableAnimation();
try {
model.scrollVertically(offset);
}
finally {
model.enableAnimation();
}
}
private static void doScrollHorizontally(@NotNull ScrollingModel model, int offset) {
model.disableAnimation();
try {
model.scrollHorizontally(offset);
}
finally {
model.enableAnimation();
}
}
public static void scrollEditor(@NotNull Editor editor, int logicalLine) {
editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(logicalLine, 0));
ScrollingModel scrollingModel = editor.getScrollingModel();
scrollingModel.disableAnimation();
scrollingModel.scrollToCaret(ScrollType.CENTER);
scrollingModel.enableAnimation();
}
private static class ScrollingContext {
@NotNull private final EditingSides mySidesContainer;
@NotNull private final FragmentSide myMasterSide;
@NotNull private final FragmentSide myMasterDiffSide;
public ScrollingContext(@NotNull FragmentSide masterSide, @NotNull EditingSides sidesContainer, @NotNull FragmentSide masterDiffSide) {
mySidesContainer = sidesContainer;
myMasterSide = masterSide;
myMasterDiffSide = masterDiffSide;
}
@NotNull
public EditingSides getSidesContainer() {
return mySidesContainer;
}
@NotNull
public FragmentSide getMasterSide() {
return myMasterSide;
}
@NotNull
public FragmentSide getMasterDiffSide() {
return myMasterDiffSide;
}
}
}