/* * (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.merge; import java.awt.Component; 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 javax.swing.JViewport; import javax.swing.SwingUtilities; import org.openflexo.diff.DiffSource; import org.openflexo.diff.DiffSource.MergeToken; import org.openflexo.diff.merge.IMerge; import org.openflexo.diff.merge.MergeChange; import org.openflexo.jedit.JEditTextAreaWithHighlights; import org.openflexo.jedit.TokenMarker; import org.openflexo.toolbox.FontCst; import org.openflexo.toolbox.TokenMarkerStyle; public class MergeTextArea extends JEditTextAreaWithHighlights { private MergeChange selectedChange; private Hashtable<MergeChange, MergeHighlight> highlights; public enum Side { Right, Left, Merge }; private Side side; private IMerge _merge; private Line[] text; private String globalText; private String linesTAText; private JTextArea linesTA; protected class Line { int lineNb; boolean isExtraLine; 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(MergeToken[] someText) { Vector<Line> lines = new Vector<Line>(); for (int i = 0; i < someText.length; i++) { String line = someText[i].getToken(); lines.add(new Line(line, i)); if (line.length() > maxCols) { maxCols = line.length(); } } for (MergeChange change : _merge.getChanges()) { int startIndex = isRight() ? change.getFirst2() : change.getFirst0(); int endIndex = isRight() ? change.getLast2() : change.getLast0(); int oppositeStartIndex = isLeft() ? change.getFirst2() : change.getFirst0(); int oppositeEndIndex = isLeft() ? change.getLast2() : 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(); } private DiffSource _source; public MergeTextArea(DiffSource source, IMerge merge, Side side, TokenMarkerStyle style) { super(); disableDefaultMouseWheelListener(); setTokenMarker(TokenMarker.makeTokenMarker(style)); setPreferredSize(new Dimension(10, 10)); this.side = side; this._merge = merge; this._source = source; setFont(FontCst.TEXT_FONT); globalText = buildText(source.getTextTokens()); setText(globalText); setColumns(maxCols); setEditable(false); _changes = new Hashtable<MergeChange, ChangeBounds>(); // if (MergeReport.getChanges().size() > 0) { highlights = new Hashtable<MergeChange, MergeHighlight>(); for (MergeChange change : _merge.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(new Font("Verdana", Font.PLAIN, 11)); linesTA.setBackground(null); linesTA.setText(linesTAText); linesTA.setFont(FontCst.TEXT_FONT.getStyle() != Font.PLAIN ? FontCst.TEXT_FONT.deriveFont(Font.PLAIN) : FontCst.TEXT_FONT); if (side != Side.Merge) { remove(vertical); remove(horizontal); } } /** * 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 void update() { removeAllCustomHighlight(); globalText = buildText(_source.getTextTokens()); setText(globalText); _changes.clear(); highlights = new Hashtable<MergeChange, MergeHighlight>(); for (MergeChange change : _merge.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.setRows(text.length); linesTA.setColumns(cols); linesTA.setText(linesTAText); selectedChange = null; } 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(final MergeChange change, boolean shouldScroll) { if (change == null) { return; } // TODO: (SGU) quelqu'un (moi ?) a supprime le test if (change != selectedChange) ... // Je me rappelle que ca greve pas mal les performances, il faudrait regarder ca un jour. // if ((change != selectedChange) && (getPainter().isValid())) { if (selectedChange != null && highlights.get(selectedChange) != null) { highlights.get(selectedChange).deselect(); } if (highlights.get(change) != null) { highlights.get(change).select(); } selectedChange = change; if (shouldScroll) { JViewport viewPort = getViewPort(); ChangeBounds cb = boundsForChange(change); final Rectangle bounds = new Rectangle(); bounds.x = 0; bounds.y = cb.beginPLine * getLineHeight(); if (bounds.y < 50) { bounds.y = 0; } else { bounds.y -= 50; } // int requiredHeight = (cb.endPLine-cb.beginPLine+1)*getLineHeight()+50; bounds.height = viewPort.getHeight(); bounds.width = viewPort.getWidth(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { scrollRectToVisible(bounds); } }); } // } } private JViewport getViewPort() { Component current = getPainter(); while (current != null && !(current instanceof JViewport)) { current = current.getParent(); } if (current instanceof JViewport) { return (JViewport) current; } return null; } /* public void scrollRectToVisible(Rectangle aRect) { System.out.println("scrollRectToVisible() with "+aRect); super.scrollRectToVisible(aRect); }*/ private MergeHighlight makeHighlightForChange(MergeChange change) { ChangeBounds cb = boundsForChange(change); MergeHighlight returned = new MergeHighlight(change, this); returned.setBeginLineNb(cb.beginPLine); returned.setEndLineNb(cb.endPLine); addCustomHighlight(returned); return returned; } private Hashtable<MergeChange, ChangeBounds> _changes; protected ChangeBounds boundsForChange(MergeChange 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(MergeChange change) { if (!isMerge()) { beginLine = isRight() ? change.getFirst2() : change.getFirst0(); endLine = isRight() ? change.getLast2() : change.getLast0(); beginIndex = lineToChar(beginLine, true); endIndex = lineToChar(endLine, false); if (isRight() && change.getFirst2() > change.getLast2() || 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.getLast2() - change.getFirst2()) + 1; endPLine = lineToPhysLine(endLine, false); beginPLine = endPLine - oppositeLength + 1; } } else { beginLine = change.getFirstMergeIndex(); endLine = change.getLastMergeIndex(); beginIndex = lineToChar(beginLine, true); endIndex = lineToChar(endLine, false); beginPLine = beginLine; endPLine = endLine; } } } boolean isMerge() { return side == Side.Merge; } 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 float heightAboveChange(MergeChange change, int buttonHeight) { if (!readyToDisplay()) { return -1; } MergeChange previousChange = _merge.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 float getHeightForChange(MergeChange change) { ChangeBounds cb = boundsForChange(change); return (cb.endPLine - cb.beginPLine + 1) * getLineHeight(); } private float getHeightBetweenChange(MergeChange change1, MergeChange change2) { ChangeBounds cb1 = boundsForChange(change1); ChangeBounds cb2 = boundsForChange(change2); return (cb2.beginPLine - cb1.endPLine - 1) * getLineHeight(); } private float getFirstHeight(MergeChange firstChange) { ChangeBounds cb = boundsForChange(firstChange); return cb.beginPLine * getLineHeight(); } /*public void setText(String text) { (new Exception("setText()")).printStackTrace(); super.setText(text); }*/ }