/* JMeld is a visual diff and merge tool. Copyright (C) 2007 Kees Kuip This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jmeld.ui; import org.jmeld.diff.JMChunk; import org.jmeld.diff.JMDelta; import org.jmeld.diff.JMRevision; import org.jmeld.ui.text.BufferDocumentIF; import org.jmeld.util.DiffUtil; import javax.swing.*; import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.util.List; public class ScrollSynchronizer { private BufferDiffPanel diffPanel; private FilePanel filePanelLeft; private FilePanel filePanelRight; private AdjustmentListener horizontalAdjustmentListener; private AdjustmentListener verticalAdjustmentListener; public ScrollSynchronizer(BufferDiffPanel diffPanel, FilePanel filePanelLeft, FilePanel filePanelRight) { this.diffPanel = diffPanel; this.filePanelLeft = filePanelLeft; this.filePanelRight = filePanelRight; init(); } private void init() { JScrollBar o; JScrollBar r; // Synchronize the horizontal scrollbars: o = filePanelLeft.getScrollPane().getHorizontalScrollBar(); r = filePanelRight.getScrollPane().getHorizontalScrollBar(); r.addAdjustmentListener(getHorizontalAdjustmentListener()); o.addAdjustmentListener(getHorizontalAdjustmentListener()); // Synchronize the vertical scrollbars: o = filePanelLeft.getScrollPane().getVerticalScrollBar(); r = filePanelRight.getScrollPane().getVerticalScrollBar(); r.addAdjustmentListener(getVerticalAdjustmentListener()); o.addAdjustmentListener(getVerticalAdjustmentListener()); } private void scroll(boolean leftScrolled) { JMRevision revision; FilePanel fp1; FilePanel fp2; int line; revision = diffPanel.getCurrentRevision(); if (revision == null) { return; } if (leftScrolled) { fp1 = filePanelLeft; fp2 = filePanelRight; } else { fp1 = filePanelRight; fp2 = filePanelLeft; } line = getCurrentLineCenter(fp1); if (leftScrolled) { line = DiffUtil.getRevisedLine(revision, line); } else { line = DiffUtil.getOriginalLine(revision, line); } scrollToLine(fp2, line); } void toNextDelta(boolean next) { int line; JMRevision revision; JMDelta previousDelta; JMDelta currentDelta; JMDelta nextDelta; JMDelta toDelta; JMChunk original; int currentIndex; int nextIndex; List<JMDelta> deltas; int i; revision = diffPanel.getCurrentRevision(); if (revision == null) { return; } deltas = revision.getDeltas(); line = getCurrentLineCenter(filePanelLeft); currentDelta = null; currentIndex = -1; i = 0; for (JMDelta delta : deltas) { original = delta.getOriginal(); currentIndex = i; i++; if (line >= original.getAnchor()) { if (line <= original.getAnchor() + original.getSize()) { currentDelta = delta; break; } } else { break; } } previousDelta = null; nextDelta = null; if (currentIndex != -1) { if (currentIndex > 0) { previousDelta = deltas.get(currentIndex - 1); } nextIndex = currentIndex; if (currentDelta != null) { nextIndex++; } if (nextIndex < deltas.size()) { nextDelta = deltas.get(nextIndex); } } if (next) { toDelta = nextDelta; } else { toDelta = previousDelta; } if (toDelta != null) { scrollToLine(filePanelLeft, toDelta.getOriginal().getAnchor()); scroll(true); } } void showDelta(JMDelta delta) { scrollToLine(filePanelLeft, delta.getOriginal().getAnchor()); scroll(true); } private int getCurrentLineCenter(FilePanel fp) { JScrollPane scrollPane; BufferDocumentIF bd; JTextComponent editor; JViewport viewport; int line; Rectangle rect; int offset; Point p; editor = fp.getEditor(); scrollPane = fp.getScrollPane(); viewport = scrollPane.getViewport(); p = viewport.getViewPosition(); offset = editor.viewToModel(p); // Scroll around the center of the editpane p.y += getHeightOffset(fp); offset = editor.viewToModel(p); bd = fp.getBufferDocument(); if (bd == null) { return -1; } line = bd.getLineForOffset(offset); return line; } public void scrollToLine(FilePanel fp, int line) { JScrollPane scrollPane; FilePanel fp2; BufferDocumentIF bd; JTextComponent editor; JViewport viewport; Rectangle rect; int offset; Point p; Rectangle viewRect; Dimension viewSize; Dimension extentSize; int x; fp2 = fp == filePanelLeft ? filePanelRight : filePanelLeft; bd = fp.getBufferDocument(); if (bd == null) { return; } offset = bd.getOffsetForLine(line); if (offset < 0) { return; } viewport = fp.getScrollPane().getViewport(); editor = fp.getEditor(); try { rect = editor.modelToView(offset); if (rect == null) { return; } p = rect.getLocation(); p.y -= getHeightOffset(fp); p.y += getCorrectionOffset(fp2); // Do not allow scrolling before the begin. if (p.y < 0) { p.y = 0; } // Do not allow scrolling after the end. viewSize = viewport.getViewSize(); viewRect = viewport.getViewRect(); extentSize = viewport.getExtentSize(); if (p.y > viewSize.height - extentSize.height) { p.y = viewSize.height - extentSize.height; } p.x = viewRect.x; viewport.setViewPosition(p); } catch (Exception ex) { ex.printStackTrace(); } } private int getHeightOffset(FilePanel fp) { JScrollPane scrollPane; JViewport viewport; int offset; int unitIncrement; scrollPane = fp.getScrollPane(); viewport = scrollPane.getViewport(); offset = viewport.getSize().height / 2; unitIncrement = scrollPane.getHorizontalScrollBar().getUnitIncrement(); offset = offset - (offset % unitIncrement); return offset; } private int getCorrectionOffset(FilePanel fp) { JTextComponent editor; int offset; Rectangle rect; Point p; JViewport viewport; editor = fp.getEditor(); viewport = fp.getScrollPane().getViewport(); p = viewport.getViewPosition(); offset = editor.viewToModel(p); try { // This happens when you scroll to the bottom. The upper line won't // start at the right position (You can see half of the line) // Correct this offset with the pane next to it to keep in sync. rect = editor.modelToView(offset); if (rect != null) { return p.y - rect.getLocation().y; } } catch (Exception ex) { ex.printStackTrace(); } return 0; } private AdjustmentListener getHorizontalAdjustmentListener() { if (horizontalAdjustmentListener == null) { horizontalAdjustmentListener = new AdjustmentListener() { private boolean insideScroll; public void adjustmentValueChanged(AdjustmentEvent e) { JScrollBar scFrom; JScrollBar scTo; if (insideScroll) { return; } if (filePanelLeft.getScrollPane().getHorizontalScrollBar() == e .getSource()) { scFrom = filePanelLeft.getScrollPane().getHorizontalScrollBar(); scTo = filePanelRight.getScrollPane().getHorizontalScrollBar(); } else { scFrom = filePanelRight.getScrollPane().getHorizontalScrollBar(); scTo = filePanelLeft.getScrollPane().getHorizontalScrollBar(); } // Stop possible recursion! // An left scroll will have a right scroll as // a result. That revised scroll could have a orginal // scroll as result. etc... insideScroll = true; insideScroll = true; scTo.setValue(scFrom.getValue()); insideScroll = false; } }; } return horizontalAdjustmentListener; } private AdjustmentListener getVerticalAdjustmentListener() { if (verticalAdjustmentListener == null) { verticalAdjustmentListener = new AdjustmentListener() { private boolean insideScroll; private int counter; public void adjustmentValueChanged(AdjustmentEvent e) { boolean leftScrolled; if (insideScroll) { return; } if (filePanelLeft.getScrollPane().getVerticalScrollBar() == e .getSource()) { leftScrolled = true; } else { leftScrolled = false; } // Stop possible recursion! // An left scroll will have a right scroll as // a result. That revised scroll could have a orginal // scroll as result. etc... insideScroll = true; scroll(leftScrolled); insideScroll = false; } }; } return verticalAdjustmentListener; } }