/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.swing.diff; import java.awt.Dimension; import java.awt.Font; import java.awt.Rectangle; import java.util.Hashtable; import java.util.Vector; import javax.swing.JScrollBar; import javax.swing.JTextArea; import org.openflexo.diff.ComputeDiff; import org.openflexo.diff.ComputeDiff.DiffChange; import org.openflexo.diff.ComputeDiff.DiffReport; import org.openflexo.jedit.JEditTextAreaWithHighlights; import org.openflexo.jedit.TokenMarker; import org.openflexo.toolbox.FontCst; import org.openflexo.toolbox.TokenMarkerStyle; public class DiffTextArea /* extends JTextArea */extends JEditTextAreaWithHighlights { // This flag is used to get either left perspective or right perspective private boolean isLeftOriented = true; private DiffChange selectedChange; private Hashtable<DiffChange, DiffHighlight> highlights; public enum Side { Right, Left }; private Side side; private DiffReport diffReport; // private DefaultHighlighter hl; // private DiffHighlightPainter selectedHLPainter; // private DiffHighlightPainter unselectedHLPainter; private Line[] text; private String globalText; private String linesTAText; private JTextArea linesTA; public DiffTextArea(String[] someText, DiffReport report, Side side, TokenMarkerStyle style, boolean isLeftOriented) { super(); this.isLeftOriented = isLeftOriented; disableDefaultMouseWheelListener(); setTokenMarker(TokenMarker.makeTokenMarker(style)); setPreferredSize(new Dimension(10, 10)); this.side = side; this.diffReport = report; setFont(FontCst.TEXT_FONT); globalText = buildText(someText); setText(globalText); setColumns(maxCols); setEditable(false); // System.out.println("DiffTextArea: columns="+maxCols+"\n"+globalText); _changes = new Hashtable<DiffChange, ChangeBounds>(); // if (diffReport.getChanges().size() > 0) { highlights = new Hashtable<DiffChange, DiffHighlight>(); for (DiffChange change : diffReport.getChanges()) { highlights.put(change, makeHighlightForChange(change)); } // } int cols; if (text.length < 10) { cols = 2; } else if (text.length < 100) { cols = 3; } else if (text.length < 1000) { cols = 4; } else { cols = 5; } linesTA = new JTextArea(text.length, cols); linesTA.setEditable(false); linesTA.setFont(FontCst.TEXT_FONT.getStyle() != Font.PLAIN ? FontCst.TEXT_FONT.deriveFont(Font.PLAIN) : FontCst.TEXT_FONT); linesTA.setBackground(null); linesTA.setText(linesTAText); /* addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { SwingUtilities.invokeLater(new Runnable() { public void run() { logger.info("Position " + getCaretPosition()); } }); } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } });*/ remove(vertical); remove(horizontal); validate(); // System.out.println("DiffTextArea: columns="+getSize()+" // "+getMinimumSize()+" "+getPreferredSize()+"\n"); } /** * Overrides setFont * * @see org.openflexo.jedit.JEditTextArea#setFont(java.awt.Font) */ @Override public void setFont(Font font) { super.setFont(font); if (linesTA != null && linesTA.getFont() != font) { linesTA.setFont(font.getStyle() == Font.PLAIN ? font : font.deriveFont(Font.PLAIN)); } } protected class Line { int lineNb; boolean isExtraLine; private String lineText; protected Line(String lineText, int lineNb) { isExtraLine = false; this.lineText = lineText; this.lineNb = lineNb; } protected Line(int lineNb) { isExtraLine = true; this.lineNb = lineNb; } protected String getStringValue() { if (isExtraLine) { return ""; } else { return lineText; } } protected boolean isExtraLine() { return isExtraLine; } } private int maxCols = 40; private String buildText(String[] someText) { Vector<Line> lines = new Vector<Line>(); for (int i = 0; i < someText.length; i++) { String line = someText[i]; lines.add(new Line(line, i)); if (line.length() > maxCols) { maxCols = line.length(); } } for (DiffChange change : diffReport.getChanges()) { int startIndex = isRight() ? change.getFirst1() : change.getFirst0(); int endIndex = isRight() ? change.getLast1() : change.getLast0(); int oppositeStartIndex = isLeft() ? change.getFirst1() : change.getFirst0(); int oppositeEndIndex = isLeft() ? change.getLast1() : change.getLast0(); int range = endIndex - startIndex; int oppositeRange = oppositeEndIndex - oppositeStartIndex; if (range != oppositeRange) { int indexOfLastLine = getIndexOfLineNb(lines, endIndex); for (int i = range; i < oppositeRange; i++) { lines.insertElementAt(new Line(endIndex), indexOfLastLine + 1); } } } text = lines.toArray(new Line[lines.size()]); StringBuffer returned = new StringBuffer(); StringBuffer linesTASB = new StringBuffer(); for (Line line : text) { if (!line.isExtraLine) { if (line.lineNb < 10) { linesTASB.append(" " + line.lineNb + " "); } else if (line.lineNb < 100) { linesTASB.append(" " + line.lineNb + " "); } else if (line.lineNb < 1000) { linesTASB.append(" " + line.lineNb + " "); } else { linesTASB.append("" + line.lineNb); } returned.append(line.getStringValue()); } returned.append("\n"); linesTASB.append("\n"); } linesTAText = linesTASB.toString(); return returned.toString(); } public JScrollBar getHorizontalScrollBar() { return horizontal; } public JScrollBar getVerticalScrollBar() { return vertical; } private static int getIndexOfLineNb(Vector<Line> lines, int lineNb) { for (int i = 0; i < lines.size(); i++) { if (lines.get(i).lineNb == lineNb) { return i; } } return -1; } int lineToChar(int lineNb, boolean isFirst) { int returned = 0; int i; for (i = 0; i < text.length && text[i].lineNb < lineNb; i++) { returned += text[i].isExtraLine() ? 1 : text[i].getStringValue().length() + 1; } if (isFirst) { int j = i - 1; while (j > 0 && text[j/*-1*/].isExtraLine() && text[j - 1].isExtraLine()) { returned -= 1; j = j - 1; } } return returned; } int lineToPhysLine(int lineNb, boolean isFirst) { if (isFirst) { int i = 0; while (i < text.length && text[i].lineNb < lineNb) { i++; } return i; } else { int i = 0; while (i < text.length && text[i].lineNb <= lineNb) { i++; } return i - 1; } } protected void setChange(ComputeDiff.DiffChange change, boolean shouldScroll, boolean forceSelect) { if (change != selectedChange || forceSelect) { if (selectedChange != null) { highlights.get(selectedChange).deselect(); } highlights.get(change).select(); selectedChange = change; if (shouldScroll) { ChangeBounds cb = boundsForChange(change); Rectangle bounds = new Rectangle(); bounds.x = 0; bounds.y = cb.beginPLine * getLineHeight(); bounds.height = (cb.endPLine - cb.beginPLine + 1) * getLineHeight(); bounds.width = getWidth(); scrollRectToVisible(bounds); } } } private DiffHighlight makeHighlightForChange(ComputeDiff.DiffChange change) { ChangeBounds cb = boundsForChange(change); DiffHighlight returned = new DiffHighlight(change, this); returned.setBeginLineNb(cb.beginPLine); returned.setEndLineNb(cb.endPLine); addCustomHighlight(returned); return returned; } private Hashtable<DiffChange, ChangeBounds> _changes; protected ChangeBounds boundsForChange(DiffChange change) { ChangeBounds returned = _changes.get(change); if (returned == null) { returned = new ChangeBounds(change); _changes.put(change, returned); } return returned; } private class ChangeBounds { int beginLine; int endLine; int beginIndex; int endIndex; int beginPLine; int endPLine; ChangeBounds(DiffChange change) { beginLine = isRight() ? change.getFirst1() : change.getFirst0(); endLine = isRight() ? change.getLast1() : change.getLast0(); beginIndex = lineToChar(beginLine, true); endIndex = lineToChar(endLine, false); if (isRight() && change.getFirst1() > change.getLast1() || isLeft() && change.getFirst0() > change.getLast0()) { beginIndex = beginIndex - 1; } if (beginLine <= endLine) { beginPLine = lineToPhysLine(beginLine, true); endPLine = lineToPhysLine(endLine, false); } else { int oppositeLength = (isRight() ? change.getLast0() - change.getFirst0() : change.getLast1() - change.getFirst1()) + 1; endPLine = lineToPhysLine(endLine, false); beginPLine = endPLine - oppositeLength + 1; } } } boolean isRight() { return side == Side.Right; } boolean isLeft() { return side == Side.Left; } public JTextArea getLinesTA() { return linesTA; } protected boolean readyToDisplay() { return getPainter().getFontMetrics() != null; } private int getLineHeight() { if (readyToDisplay()) { return getPainter().getFontMetrics().getHeight(); } return -1; } public int heightAboveChange(DiffChange change, int buttonHeight) { if (!readyToDisplay()) { return -1; } DiffChange previousChange = diffReport.changeBefore(change); if (previousChange == null) { return getFirstHeight(change) + (getHeightForChange(change) - buttonHeight) / 2; } else { return (getHeightForChange(previousChange) - buttonHeight) / 2 + getHeightBetweenChange(previousChange, change) + (getHeightForChange(change) - buttonHeight) / 2; } } private int getHeightForChange(DiffChange change) { ChangeBounds cb = boundsForChange(change); return (cb.endPLine - cb.beginPLine + 1) * getLineHeight(); } private int getHeightBetweenChange(DiffChange change1, DiffChange change2) { ChangeBounds cb1 = boundsForChange(change1); ChangeBounds cb2 = boundsForChange(change2); return (cb2.beginPLine - cb1.endPLine - 1) * getLineHeight(); } private int getFirstHeight(DiffChange firstChange) { ChangeBounds cb = boundsForChange(firstChange); return cb.beginPLine * getLineHeight(); } public boolean isLeftOriented() { return isLeftOriented; } }