/*
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 java.awt.Font;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.JViewport;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import org.jmeld.JMeldException;
import org.jmeld.diff.JMChunk;
import org.jmeld.diff.JMDelta;
import org.jmeld.diff.JMDiff;
import org.jmeld.diff.JMRevision;
import org.jmeld.ui.search.SearchCommand;
import org.jmeld.ui.search.SearchHit;
import org.jmeld.ui.search.SearchHits;
import org.jmeld.ui.text.AbstractBufferDocument;
import org.jmeld.ui.text.BufferDocumentIF;
import org.jmeld.ui.text.JMDocumentEvent;
import org.jmeld.util.StringUtil;
import org.jmeld.util.node.BufferNode;
import org.jmeld.util.node.JMDiffNode;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
public class BufferDiffPanel extends AbstractContentPanel {
public static final int LEFT = 0;
public static final int MIDDLE = 1;
public static final int RIGHT = 2;
private JMeldPanel mainPanel;
private FilePanel[] filePanels;
private JMDiffNode diffNode;
int filePanelSelectedIndex = -1;
private JMRevision currentRevision;
private JMDelta selectedDelta;
private int selectedLine;
private ScrollSynchronizer scrollSynchronizer;
private JMDiff diff;
public BufferDiffPanel(JMeldPanel mainPanel) {
this.mainPanel = mainPanel;
diff = new JMDiff();
filePanels = new FilePanel[3];
init();
setFocusable(true);
}
public void setDiffNode(JMDiffNode diffNode) {
BufferNode bnLeft;
BufferNode bnRight;
this.diffNode = diffNode;
bnLeft = diffNode.getBufferNodeLeft();
bnRight = diffNode.getBufferNodeRight();
setBufferDocuments(bnLeft == null ? null : bnLeft.getDocument(),
bnRight == null ? null : bnRight.getDocument(), diffNode.getDiff(), diffNode.getRevision());
}
private void setBufferDocuments(BufferDocumentIF bd1, BufferDocumentIF bd2, JMDiff diff, JMRevision revision) {
this.diff = diff;
currentRevision = revision;
if (bd1 != null) {
filePanels[LEFT].setBufferDocument(bd1);
}
if (bd2 != null) {
filePanels[RIGHT].setBufferDocument(bd2);
}
if (bd1 != null && bd2 != null) {
filePanels[LEFT].updateFileLabel(bd1.getName(), bd2.getName());
filePanels[RIGHT].updateFileLabel(bd2.getName(), bd1.getName());
}
if (bd1 != null && bd2 != null) {
reDisplay();
}
}
private void reDisplay() {
for (FilePanel fp : filePanels) {
if (fp != null) {
fp.reDisplay();
}
}
mainPanel.repaint();
}
public String getTitle() {
String title;
BufferDocumentIF bd;
List<String> titles;
title = "";
titles = new ArrayList<String>();
for (FilePanel filePanel : filePanels) {
if (filePanel == null) {
continue;
}
bd = filePanel.getBufferDocument();
if (bd == null) {
continue;
}
title = bd.getShortName();
if (StringUtil.isEmpty(title)) {
continue;
}
titles.add(title);
}
title = "";
if (titles.size() == 1) {
title = titles.get(0);
} else {
if (titles.get(0).equals(titles.get(1))) {
title = titles.get(0);
} else {
title = titles.get(0) + "-" + titles.get(1);
}
}
return title;
}
public boolean revisionChanged(JMDocumentEvent de) {
FilePanel fp;
BufferDocumentIF bd1;
BufferDocumentIF bd2;
if (currentRevision == null) {
diff();
} else {
fp = getFilePanel(de.getDocument());
if (fp == null) {
return false;
}
bd1 = filePanels[LEFT].getBufferDocument();
bd2 = filePanels[RIGHT].getBufferDocument();
if (!currentRevision.update(bd1 != null ? bd1.getLines() : null, bd2 != null ? bd2.getLines() : null,
fp == filePanels[LEFT], de.getStartLine(), de.getNumberOfLines())) {
return false;
}
reDisplay();
}
return true;
}
private FilePanel getFilePanel(AbstractBufferDocument document) {
for (FilePanel fp : filePanels) {
if (fp == null) {
continue;
}
if (fp.getBufferDocument() == document) {
return fp;
}
}
return null;
}
public void diff() {
BufferDocumentIF bd1;
BufferDocumentIF bd2;
bd1 = filePanels[LEFT].getBufferDocument();
bd2 = filePanels[RIGHT].getBufferDocument();
if (bd1 != null && bd2 != null) {
try {
currentRevision = diff.diff(bd1.getLines(), bd2.getLines(), diffNode.getIgnore());
reDisplay();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private void init() {
FormLayout layout;
String columns;
String rows;
CellConstraints cc;
columns = "3px, pref, 3px, 0:grow, 5px, min, 60px, 0:grow, 25px, min, 3px, pref, 3px";
rows = "6px, pref, 3px, fill:0:grow, pref";
layout = new FormLayout(columns, rows);
cc = new CellConstraints();
setLayout(layout);
filePanels[LEFT] = new FilePanel(this, BufferDocumentIF.ORIGINAL, LEFT);
filePanels[RIGHT] = new FilePanel(this, BufferDocumentIF.REVISED, RIGHT);
// panel for file1
add(new RevisionBar(this, filePanels[LEFT], true), cc.xy(2, 4));
add(filePanels[LEFT].getSaveButton(), cc.xy(2, 2));
add(filePanels[LEFT].getFileLabel(), cc.xyw(4, 2, 3));
add(filePanels[LEFT].getScrollPane(), cc.xyw(4, 4, 3));
add(filePanels[LEFT].getFilePanelBar(), cc.xyw(4, 5, 3));
add(new DiffScrollComponent(this, LEFT, RIGHT), cc.xy(7, 4));
// panel for file2
add(new RevisionBar(this, filePanels[RIGHT], false), cc.xy(12, 4));
add(filePanels[RIGHT].getFileLabel(), cc.xyw(8, 2, 3));
add(filePanels[RIGHT].getScrollPane(), cc.xyw(8, 4, 3));
add(filePanels[RIGHT].getSaveButton(), cc.xy(12, 2));
add(filePanels[RIGHT].getFilePanelBar(), cc.xyw(8, 5, 3));
scrollSynchronizer = new ScrollSynchronizer(this, filePanels[LEFT], filePanels[RIGHT]);
setSelectedPanel(filePanels[LEFT]);
}
void toNextDelta(boolean next) {
if (next) {
doDown();
} else {
doUp();
}
}
JMRevision getCurrentRevision() {
return currentRevision;
}
@Override
public boolean checkSave() {
SavePanelDialog dialog;
if (!isSaveEnabled()) {
return true;
}
dialog = new SavePanelDialog(mainPanel);
for (FilePanel filePanel : filePanels) {
if (filePanel != null) {
dialog.add(filePanel.getBufferDocument());
}
}
dialog.show();
if (dialog.isOK()) {
dialog.doSave();
return true;
}
return false;
}
@Override
public void doSave() {
BufferDocumentIF document;
for (FilePanel filePanel : filePanels) {
if (filePanel == null) {
continue;
}
if (!filePanel.isDocumentChanged()) {
continue;
}
document = filePanel.getBufferDocument();
try {
document.write();
} catch (JMeldException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(mainPanel, "Can't write file" + document.getName(),
"Problem writing file", JOptionPane.ERROR_MESSAGE);
}
}
}
@Override
public boolean isSaveEnabled() {
for (FilePanel filePanel : filePanels) {
if (filePanel != null && filePanel.isDocumentChanged()) {
return true;
}
}
return false;
}
@Override
public void doStopSearch() {
for (FilePanel filePanel : filePanels) {
if (filePanel != null) {
filePanel.doStopSearch();
}
}
}
public SearchCommand getSearchCommand() {
return mainPanel.getSearchCommand();
}
@Override
public SearchHits doSearch() {
FilePanel fp;
SearchHits searchHits;
fp = getSelectedPanel();
if (fp == null) {
return null;
}
searchHits = fp.doSearch();
scrollToSearch(fp, searchHits);
return searchHits;
}
@Override
public void doNextSearch() {
FilePanel fp;
SearchHits searchHits;
fp = getSelectedPanel();
if (fp == null) {
return;
}
searchHits = fp.getSearchHits();
searchHits.next();
fp.reDisplay();
scrollToSearch(fp, searchHits);
}
@Override
public void doPreviousSearch() {
FilePanel fp;
SearchHits searchHits;
fp = getSelectedPanel();
if (fp == null) {
return;
}
searchHits = fp.getSearchHits();
searchHits.previous();
fp.reDisplay();
scrollToSearch(fp, searchHits);
}
@Override
public void doRefresh() {
diff();
}
@Override
public void doMergeMode(boolean mergeMode) {
for (FilePanel fp : filePanels) {
if (fp != null) {
fp.getEditor().setFocusable(!mergeMode);
}
}
}
private void scrollToSearch(FilePanel fp, SearchHits searchHits) {
SearchHit currentHit;
int line;
if (searchHits == null) {
return;
}
currentHit = searchHits.getCurrent();
if (currentHit != null) {
line = currentHit.getLine();
scrollSynchronizer.scrollToLine(fp, line);
setSelectedLine(line);
}
}
private FilePanel getSelectedPanel() {
if (filePanelSelectedIndex >= 0 && filePanelSelectedIndex < filePanels.length) {
return filePanels[filePanelSelectedIndex];
}
return null;
}
void setSelectedPanel(FilePanel fp) {
int index;
index = -1;
for (int i = 0; i < filePanels.length; i++) {
if (filePanels[i] == fp) {
index = i;
}
}
if (index != filePanelSelectedIndex) {
if (filePanelSelectedIndex != -1) {
filePanels[filePanelSelectedIndex].setSelected(false);
}
filePanelSelectedIndex = index;
if (filePanelSelectedIndex != -1) {
filePanels[filePanelSelectedIndex].setSelected(true);
}
}
}
@Override
public void checkActions() {
mainPanel.checkActions();
}
@Override
public void doLeft() {
runChange(RIGHT, LEFT);
}
@Override
public void doRight() {
runChange(LEFT, RIGHT);
}
void runChange(int fromPanelIndex, int toPanelIndex) {
JMDelta delta;
BufferDocumentIF fromBufferDocument;
BufferDocumentIF toBufferDocument;
PlainDocument from;
String s;
int fromLine;
int fromOffset;
int toOffset;
int size;
JMChunk fromChunk;
JMChunk toChunk;
JTextComponent toEditor;
delta = getSelectedDelta();
if (delta == null) {
return;
}
// Some sanity checks.
if (fromPanelIndex < 0 || fromPanelIndex >= filePanels.length) {
return;
}
if (toPanelIndex < 0 || toPanelIndex >= filePanels.length) {
return;
}
try {
fromBufferDocument = filePanels[fromPanelIndex].getBufferDocument();
toBufferDocument = filePanels[toPanelIndex].getBufferDocument();
if (fromPanelIndex < toPanelIndex) {
fromChunk = delta.getOriginal();
toChunk = delta.getRevised();
} else {
fromChunk = delta.getRevised();
toChunk = delta.getOriginal();
}
toEditor = filePanels[toPanelIndex].getEditor();
if (fromBufferDocument == null || toBufferDocument == null) {
return;
}
fromLine = fromChunk.getAnchor();
size = fromChunk.getSize();
fromOffset = fromBufferDocument.getOffsetForLine(fromLine);
if (fromOffset < 0) {
return;
}
toOffset = fromBufferDocument.getOffsetForLine(fromLine + size);
if (toOffset < 0) {
return;
}
from = fromBufferDocument.getDocument();
s = from.getText(fromOffset, toOffset - fromOffset);
fromLine = toChunk.getAnchor();
size = toChunk.getSize();
fromOffset = toBufferDocument.getOffsetForLine(fromLine);
if (fromOffset < 0) {
return;
}
toOffset = toBufferDocument.getOffsetForLine(fromLine + size);
if (toOffset < 0) {
return;
}
getUndoHandler().start("replace");
toEditor.setSelectionStart(fromOffset);
toEditor.setSelectionEnd(toOffset);
toEditor.replaceSelection(s);
getUndoHandler().end("replace");
setSelectedDelta(null);
setSelectedLine(delta.getOriginal().getAnchor());
} catch (Exception ex) {
ex.printStackTrace();
}
}
void runDelete(int fromPanelIndex, int toPanelIndex) {
JMDelta delta;
BufferDocumentIF bufferDocument;
PlainDocument document;
String s;
int fromLine;
int fromOffset;
int toOffset;
int size;
JMChunk chunk;
JTextComponent toEditor;
try {
delta = getSelectedDelta();
if (delta == null) {
return;
}
// Some sanity checks.
if (fromPanelIndex < 0 || fromPanelIndex >= filePanels.length) {
return;
}
if (toPanelIndex < 0 || toPanelIndex >= filePanels.length) {
return;
}
bufferDocument = filePanels[fromPanelIndex].getBufferDocument();
if (fromPanelIndex < toPanelIndex) {
chunk = delta.getOriginal();
} else {
chunk = delta.getRevised();
}
toEditor = filePanels[fromPanelIndex].getEditor();
if (bufferDocument == null) {
return;
}
document = bufferDocument.getDocument();
fromLine = chunk.getAnchor();
size = chunk.getSize();
fromOffset = bufferDocument.getOffsetForLine(fromLine);
if (fromOffset < 0) {
return;
}
toOffset = bufferDocument.getOffsetForLine(fromLine + size);
if (toOffset < 0) {
return;
}
getUndoHandler().start("remove");
toEditor.setSelectionStart(fromOffset);
toEditor.setSelectionEnd(toOffset);
toEditor.replaceSelection("");
getUndoHandler().end("remove");
setSelectedDelta(null);
setSelectedLine(delta.getOriginal().getAnchor());
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void doDown() {
JMDelta d;
JMDelta sd;
List<JMDelta> deltas;
int index;
if (currentRevision == null) {
return;
}
deltas = currentRevision.getDeltas();
sd = getSelectedDelta();
index = deltas.indexOf(sd);
if (index == -1 || sd.getOriginal().getAnchor() != selectedLine) {
// Find the delta that would have been next to the
// disappeared delta:
d = null;
for (JMDelta delta : deltas) {
d = delta;
if (delta.getOriginal().getAnchor() > selectedLine) {
break;
}
}
setSelectedDelta(d);
} else {
// Select the next delta if there is any.
if (index + 1 < deltas.size()) {
setSelectedDelta(deltas.get(index + 1));
}
}
showSelectedDelta();
}
@Override
public void doUp() {
JMDelta d;
JMDelta sd;
JMDelta previousDelta;
List<JMDelta> deltas;
int index;
if (currentRevision == null) {
return;
}
deltas = currentRevision.getDeltas();
sd = getSelectedDelta();
index = deltas.indexOf(sd);
if (index == -1 || sd.getOriginal().getAnchor() != selectedLine) {
// Find the delta that would have been previous to the
// disappeared delta:
d = null;
previousDelta = null;
for (JMDelta delta : deltas) {
d = delta;
if (delta.getOriginal().getAnchor() > selectedLine) {
if (previousDelta != null) {
d = previousDelta;
}
break;
}
previousDelta = delta;
}
setSelectedDelta(d);
} else {
// Select the next delta if there is any.
if (index - 1 >= 0) {
setSelectedDelta(deltas.get(index - 1));
}
}
showSelectedDelta();
}
public void doGotoDelta(JMDelta delta) {
setSelectedDelta(delta);
showSelectedDelta();
}
public void doGotoLine(int line) {
BufferDocumentIF bd;
int offset;
int startOffset;
int endOffset;
JViewport viewport;
JTextComponent editor;
Point p;
FilePanel fp;
Rectangle rect;
setSelectedLine(line);
fp = getFilePanel(0);
bd = fp.getBufferDocument();
if (bd == null) {
return;
}
offset = bd.getOffsetForLine(line);
viewport = fp.getScrollPane().getViewport();
editor = fp.getEditor();
// Don't go anywhere if the line is already visible.
rect = viewport.getViewRect();
startOffset = editor.viewToModel(rect.getLocation());
endOffset = editor.viewToModel(new Point(rect.x, rect.y + rect.height));
if (offset >= startOffset && offset <= endOffset) {
return;
}
try {
p = editor.modelToView(offset).getLocation();
p.x = 0;
viewport.setViewPosition(p);
} catch (BadLocationException ex) {
}
}
@Override
public void doZoom(boolean direction) {
JTextComponent c;
Font font;
float size;
Zoom zoom;
for (FilePanel p : filePanels) {
if (p == null) {
continue;
}
c = p.getEditor();
zoom = (Zoom) c.getClientProperty("JMeld.zoom");
if (zoom == null) {
// Save the orginal font because that's the font which will
// give the derived font.
zoom = new Zoom();
zoom.font = c.getFont();
c.putClientProperty("JMeld.zoom", zoom);
}
size = c.getFont().getSize() + (direction ? 1.0f : -1.0f);
size = size > 100.0f ? 100.0f : size;
size = size < 5.0f ? 5.0f : size;
c.setFont(zoom.font.deriveFont(size));
}
}
@Override
public void doGoToSelected() {
showSelectedDelta();
}
@Override
public void doGoToFirst() {
JMDelta d;
List<JMDelta> deltas;
if (currentRevision == null) {
return;
}
deltas = currentRevision.getDeltas();
if (deltas.size() > 0) {
setSelectedDelta(deltas.get(0));
showSelectedDelta();
}
}
@Override
public void doGoToLast() {
JMDelta d;
List<JMDelta> deltas;
if (currentRevision == null) {
return;
}
deltas = currentRevision.getDeltas();
if (deltas.size() > 0) {
setSelectedDelta(deltas.get(deltas.size() - 1));
showSelectedDelta();
}
}
@Override
public void doGoToLine(int line) {
FilePanel fp;
fp = getSelectedPanel();
if (fp == null) {
return;
}
scrollSynchronizer.scrollToLine(fp, line);
setSelectedLine(line);
}
class Zoom {
Font font;
}
void setSelectedDelta(JMDelta delta) {
selectedDelta = delta;
setSelectedLine(delta == null ? 0 : delta.getOriginal().getAnchor());
}
private void setSelectedLine(int line) {
selectedLine = line;
}
private void showSelectedDelta() {
JMDelta delta;
delta = getSelectedDelta();
if (delta == null) {
return;
}
scrollSynchronizer.showDelta(delta);
}
public JMDelta getSelectedDelta() {
List<JMDelta> deltas;
if (currentRevision == null) {
return null;
}
deltas = currentRevision.getDeltas();
if (deltas.size() == 0) {
return null;
}
return selectedDelta;
}
FilePanel getFilePanel(int index) {
if (index < 0 || index > filePanels.length) {
return null;
}
return filePanels[index];
}
@Override
public String getSelectedText() {
FilePanel fp;
fp = getSelectedPanel();
if (fp == null) {
return null;
}
return fp.getSelectedText();
}
}