blob: 52f0a9bdb2cb11d476a5333de0a47955af9b29cc [file] [log] [blame]
/*
* Copyright 2000-2011 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 git4idea.history.wholeTree;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.Gray;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.UIUtil;
import icons.Git4ideaIcons;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.*;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 8/26/11
* Time: 4:11 PM
*/
public class GraphGutter {
private static final int ourLineWidth = 8;
private static final int ourInterLineWidth = 2; // todo!
private static final int ourInterRepoLineWidth = 5;
public static final int ourIndent = 2;
private boolean myCurrentBranchColored;
private int myHeaderHeight;
private int myRowHeight;
private final BigTableTableModel myModel;
private MyComponent myComponent;
private JViewport myTableViewPort;
private JBTable myJBTable;
private boolean myStarted;
private static final Logger LOG = Logger.getInstance("#git4idea.history.wholeTree.GraphGutter");
private PresentationStyle myStyle;
public GraphGutter(BigTableTableModel model) {
myModel = model;
myComponent = new MyComponent();
myStyle = PresentationStyle.multicolour;
myCurrentBranchColored = true;
}
public void setHeaderHeight(int headerHeight) {
//myHeaderHeight = headerHeight;
myHeaderHeight = 0;
}
public void setRowHeight(int rowHeight) {
myRowHeight = rowHeight;
}
public MyComponent getComponent() {
return myComponent;
}
public void setTableViewPort(JViewport tableViewPort) {
myTableViewPort = tableViewPort;
}
public void setJBTable(JBTable jbTable) {
myJBTable = jbTable;
}
public void start() {
myStarted = true;
}
public PresentationStyle getStyle() {
return myStyle;
}
public void setStyle(PresentationStyle style) {
myStyle = style;
myComponent.repaint();
}
public static final Color OUTLINE_NOT_INCLUDED = Gray._180;
// will lay near but not inside scroll pane
class MyComponent extends JPanel {
public static final int diameter = 7;
MyComponent() {
setDoubleBuffered(true);
setBackground(UIUtil.getTableBackground());
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
final MouseEvent mouseEvent = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(), e.getModifiers(), 0,
(int)myTableViewPort.getViewPosition().getY() + e.getY(), e.getClickCount(),
e.isPopupTrigger(), e.getButton());
myJBTable.dispatchEvent(mouseEvent);
}
});
addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
myTableViewPort.dispatchEvent(e);
}
});
setBorder(BorderFactory.createMatteBorder(0,0,1,0, UIUtil.getBorderColor()));
}
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
if (! myStarted) return preferredSize;
int totalWires = myModel.getTotalWires();
List<Integer> wiresGroups = getWiresGroups();
return new Dimension(endPoint(totalWires, wiresGroups), preferredSize.height);
}
private int endPoint(final int wireNumber, List<Integer> wiresGroups) {
int add = 0;
for (Integer wiresGroup : wiresGroups) {
if (wiresGroup - 1 <= wireNumber) {
add += ourInterRepoLineWidth - ourInterLineWidth;
}
}
return (wireNumber + 1) * ourLineWidth + wireNumber * ourInterLineWidth + add + ourIndent;
}
private void drawConnectorsFragment(final Graphics g,
final int idxFrom,
final int yOffset,
final Set<Integer> wires,
@Nullable final WireEventI event,
final HashSet<Integer> selected,
final List<Integer> wiresGroups,
final Map<Integer, Set<Integer>> grey,
final HashSet<Integer> wireModificationSet) {
int rowYOffset = yOffset;
for (int i = idxFrom; i < event.getCommitIdx(); i++) {
final CommitI commitAt = myModel.getCommitAt(i);
if (commitAt == null) continue;
final int commitWire = myModel.getCorrectedWire(commitAt);
for (Integer wire : wires) {
verticalLine(g, selected, wiresGroups, grey, rowYOffset, i, wire, wireModificationSet, wire == commitWire);
}
rowYOffset += myRowHeight;
}
final CommitI commitAt1 = myModel.getCommitAt(event.getCommitIdx());
// fictive wire umber. for the end of the graph
final int eventWire = commitAt1 == null ? -1 : myModel.getCorrectedWire(commitAt1);
final Set<Integer> skip = new HashSet<Integer>();
final int finalRowYOffset = rowYOffset;
Runnable drawEventRowLines = new Runnable() {
public void run() {
for (Integer wire : wires) {
if (skip.contains(wire)) continue;
verticalLine(g, selected, wiresGroups, grey, finalRowYOffset, event.getCommitIdx(), wire, wireModificationSet, wire == eventWire);
}
}
};
if (commitAt1 == null) {
// fictive wire
drawEventRowLines.run();
return;
}
g.setColor(getConnectorColor(g, selected, event.getCommitIdx(), eventWire, grey));
int eventStartXPoint = startPoint(eventWire, wiresGroups) + ourLineWidth/2;
if (event.isStart() && event.isEnd()) {
return;
}
if (event.isStart()) {
skip.add(eventWire);
doubleLine(g, eventStartXPoint, rowYOffset + myRowHeight / 2, eventStartXPoint, rowYOffset + myRowHeight);
}
if (event.isEnd()) {
skip.add(eventWire);
doubleLine(g, eventStartXPoint, rowYOffset, eventStartXPoint, rowYOffset + myRowHeight / 2);
}
final int[] commitsStarts = event.getCommitsStarts();
final int[] wireEnds = event.getWireEnds();
if (wireEnds != null) {
for (int end : wireEnds) {
if (end == -1) continue;
final int wire = myModel.getCorrectedWire(myModel.getCommitAt(end));
skip.add(wire);
}
}
drawEventRowLines.run();
if (commitsStarts != null) {
for (int commitsStart : commitsStarts) {
if (commitsStart == -1) continue;
final CommitI commitAt = myModel.getCommitAt(commitsStart);
final int wire = myModel.getCorrectedWire(commitAt);
int startXPoint = startPoint(wire, wiresGroups) + ourLineWidth/2;
g.setColor(getConnectorColor(g, selected, event.getCommitIdx(), eventWire, grey));
doubleLine(g, startXPoint, rowYOffset + myRowHeight, eventStartXPoint, rowYOffset + myRowHeight / 2);
}
}
if (wireEnds != null) {
for (int end : wireEnds) {
if (end == -1) continue;
final int wire = myModel.getCorrectedWire(myModel.getCommitAt(end));
int startXPoint = startPoint(wire, wiresGroups) + ourLineWidth/2;
g.setColor(getConnectorColor(g, selected, event.getCommitIdx(), wire, grey));
doubleLine(g, startXPoint, rowYOffset, eventStartXPoint, rowYOffset + myRowHeight / 2);
}
}
}
private void verticalLine(Graphics g,
HashSet<Integer> selected,
List<Integer> wiresGroups,
Map<Integer, Set<Integer>> grey,
int rowYOffset, int i, Integer wire, HashSet<Integer> wireModificationSet, boolean isCommitWire) {
Color connectorColor = getConnectorColor(g, selected, i, wire, grey);
Color connectorStartColor = connectorColor;
if (isCommitWire && wireModificationSet.contains(i)) {
connectorStartColor = OUTLINE_NOT_INCLUDED;
}
int startXPoint = startPoint(wire, wiresGroups) + ourLineWidth/2;
if (connectorColor.equals(connectorStartColor)) {
g.setColor(connectorColor);
doubleLine(g, startXPoint, rowYOffset, startXPoint, rowYOffset + myRowHeight);
} else {
g.setColor(connectorStartColor);
doubleLine(g, startXPoint, rowYOffset, startXPoint, rowYOffset + myRowHeight/2);
g.setColor(connectorColor);
doubleLine(g, startXPoint, rowYOffset + myRowHeight/2, startXPoint, rowYOffset + myRowHeight);
}
}
//getPreviousAbsoluteIdx
private Color getConnectorStartColor(Graphics g, HashSet<Integer> selected, int i, Integer wire, Map<Integer, Set<Integer>> grey) {
final int prevIdx = myModel.getPreviousAbsoluteIdx(i);
if (prevIdx == -1) return getConnectorColor(g,selected, i,wire, grey);
final Set<Integer> integers = grey.get(prevIdx);
if (integers == null || ! integers.contains(wire)) {
return OUTLINE_NOT_INCLUDED;
} else {
return selected.contains(i) ? Color.white : myStyle.getColorForWire(wire);
}
}
private Color getConnectorColor(Graphics g, Set<Integer> selected, int i, Integer wire, Map<Integer, Set<Integer>> grey) {
if (grey == null) {
return selected.contains(i) ? Color.white : myStyle.getColorForWire(wire);
}
final Set<Integer> integers = grey.get(i);
if (myCurrentBranchColored && (integers == null || ! integers.contains(wire))) {
return OUTLINE_NOT_INCLUDED;
} else {
return selected.contains(i) ? Color.white : myStyle.getColorForWire(wire);
}
}
private void doubleLine(final Graphics g, final int x1, final int y1, final int x2, final int y2) {
final Color color = g.getColor();
//g.drawLine(x1 - 1,y1,x2 - 1,y2);
//g.setColor(Color.gray.brighter());
g.drawLine(x1,y1,x2,y2);
g.drawLine(x1 + 1, y1, x2 + 1, y2);
//g.setColor(Gray._200);
//g.drawLine(x1 + 2, y1, x2 + 2, y2);
g.setColor(color);
}
@Override
protected void paintComponent(Graphics g) {
try {
super.paintComponent(g);
if (! myStarted) return;
Graphics graphics = g.create();
try {
int height = getHeight();
// cells
int yOffset = (int) myTableViewPort.getViewPosition().getY();
int integerPart = yOffset / myRowHeight;
int firstCellPart = yOffset - integerPart * myRowHeight;
int width = getWidth();
int upBound = firstCellPart == 0 ? 0 : - firstCellPart;
upBound += myHeaderHeight;
//int idx = integerPart + (firstCellPart > 0 ? 1 : 0);
int idx = integerPart;
int lastIdx = idx + (getHeight()/myRowHeight + 1);
final int[] selectedRows = myJBTable.getSelectedRows();
drawSelection(graphics, width, upBound, idx, lastIdx, selectedRows);
final HashSet<Integer> selected = new HashSet<Integer>();
for (int selectedRow : selectedRows) {
selected.add(selectedRow);
}
// separators
List<Integer> wiresGroups = getWiresGroups();
drawRepoBounds(graphics, height, wiresGroups);
drawConnectors(graphics, lastIdx, upBound, idx, selected, wiresGroups);
drawPoints(graphics, lastIdx, upBound, idx, selected, wiresGroups);
} finally {
graphics.dispose();
}
} catch (Exception e) {
LOG.info(e);
}
}
private List<Integer> getWiresGroups() {
List<Integer> wiresGroups = myModel.getWiresGroups();
if (wiresGroups == null) return Collections.emptyList();
int running = 0;
for (int i = 0; i < wiresGroups.size(); i++) {
Integer integer = wiresGroups.get(i);
wiresGroups.set(i, running + integer);
running += integer;
}
return wiresGroups;
}
private void drawSelection(Graphics graphics, int width, int upBound, int idx, int lastIdx, int[] selectedRows) {
for (int selectedRow : selectedRows) {
if (selectedRow >= idx && selectedRow <= lastIdx) {
graphics.setColor(UIUtil.getTableSelectionBackground());
graphics.fillRect(0, upBound + (selectedRow - idx) * myRowHeight + 1,width,myRowHeight - 1);
}
}
}
private void drawPoints(Graphics graphics, int lastIdx, int upBound, int idx, HashSet<Integer> selected, List<Integer> wiresGroups) {
final Color darker = new Color(35,107,178);
//final Color fill = new Color(176,230,255);
final Color fill = new Color(166,220,255);
final Color fillNotIncluded = Gray._200;
while (idx <= lastIdx && idx < myModel.getRowCount()) {
CommitI commitAt = myModel.getCommitAt(idx);
final boolean contains = selected.contains(idx);
final boolean isInActiveRoots = myModel.getActiveRoots().contains(commitAt.selectRepository(myModel.getRootsHolder().getRoots()));
if (! commitAt.holdsDecoration() && isInActiveRoots) {
int correctedWire = myModel.getCorrectedWire(commitAt);
int startXPoint = startPoint(correctedWire, wiresGroups);
final boolean inCurrentBranch = myCurrentBranchColored ? myModel.isInCurrentBranch(idx) : true;
final Icon icon;
if (inCurrentBranch) {
icon = myStyle.getNodeIcon(correctedWire);
} else {
icon = Git4ideaIcons.Greyball;
}
icon.paintIcon(this, graphics, startXPoint + 1, upBound + myRowHeight/2 - 3);
/* graphics.fillArc(startXPoint + ourLineWidth / 2 - 4, upBound + myRowHeight / 2 - 4, diameter, diameter, 0, 360);
if (inCurrentBranch) {
if (PresentationStyle.calm.equals(myStyle)) {
graphics.setColor(darker);
} else {
graphics.setColor(myStyle.getColorForWire(correctedWire));
}
}
else {
graphics.setColor(OUTLINE_NOT_INCLUDED);
}
graphics.drawArc(startXPoint + ourLineWidth / 2 - 4 + 1, upBound + myRowHeight / 2 - 4, diameter, diameter, 0, 360);
graphics.drawArc(startXPoint + ourLineWidth / 2 - 4, upBound + myRowHeight / 2 - 4, diameter, diameter, 0, 360);
if (inCurrentBranch) {
graphics.setColor(Color.white);
graphics.drawArc(startXPoint + ourLineWidth / 2 - 4 + 1, upBound + myRowHeight / 2 - 4 + 1, diameter - 1, diameter - 1, 100, 90);
}*/
}
++ idx;
upBound += myRowHeight;
}
}
private void drawConnectors(Graphics graphics, int lastIdx, int upBound, int idx, HashSet<Integer> selected, List<Integer> wiresGroups) {
((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
final Map<VirtualFile,WireEventsIterator> groupIterators = myModel.getGroupIterators(idx);
for (Map.Entry<VirtualFile, WireEventsIterator> entry : groupIterators.entrySet()) {
final WireEventsIterator eventsIterator = entry.getValue();
int idxFrom = idx;
int yOff = upBound;
Set<Integer> used = new HashSet<Integer>(eventsIterator.getFirstUsed());
final Iterator<WireEventI> iterator = eventsIterator.getWireEventsIterator();
final int repoCorrection = myModel.getRepoCorrection(entry.getKey());
final HashSet<Integer> wireModificationSet = new HashSet<Integer>();
final Map<Integer, Set<Integer>> grey = myModel.getGrey(entry.getKey(), idxFrom, lastIdx, repoCorrection, wireModificationSet);
while (iterator.hasNext()) {
final WireEventI wireEvent = iterator.next();
if (wireEvent.getCommitIdx() >= idx) {
drawConnectorsFragment(graphics, idxFrom, yOff, used, wireEvent, selected, wiresGroups, grey, wireModificationSet);
int delta = wireEvent.getCommitIdx() + 1 - idxFrom;
delta = delta < 0 ? 0 : delta;
yOff += delta * myRowHeight;
idxFrom = wireEvent.getCommitIdx() + 1;
}
// add starts, minus ended
final int[] wireEnds = wireEvent.getWireEnds();
if (wireEnds != null) {
for (int wireEnd : wireEnds) {
used.remove(Integer.valueOf(myModel.getCorrectedWire(myModel.getCommitAt(wireEnd))));
}
}
if (wireEvent.isEnd()) {
used.remove(Integer.valueOf(myModel.getCorrectedWire(myModel.getCommitAt(wireEvent.getCommitIdx()))));
}
final int[] commitsStarts = wireEvent.getCommitsStarts();
if (commitsStarts != null) {
for (int commitsStart : commitsStarts) {
if (commitsStart == -1) continue;
used.add(myModel.getCorrectedWire(myModel.getCommitAt(commitsStart)));
}
}
if (wireEvent.isStart() && ! wireEvent.isEnd()) {
used.add(myModel.getCorrectedWire(myModel.getCommitAt(wireEvent.getCommitIdx())));
}
if (wireEvent.getCommitIdx() > lastIdx) break;
}
drawConnectorsFragment(graphics, idxFrom, yOff, used, new WireEvent(lastIdx, ArrayUtil.EMPTY_INT_ARRAY), selected, wiresGroups,
grey, wireModificationSet);
}
((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
}
private void drawRepoBounds(Graphics graphics, int height, List<Integer> wiresGroups) {
graphics.setColor(UIUtil.getBorderColor());
if (wiresGroups.size() > 1) {
for (int i = 0; i < wiresGroups.size() - 1; i++) {
Integer integer = wiresGroups.get(i);
int x1 = integer == 0 ? 0 : endPoint(integer - 1, wiresGroups);
if (x1 > 0) {
x1 -= ourInterRepoLineWidth/2 + 1;
}
graphics.drawLine(x1, 0, x1, height);
}
}
}
private int startPoint(int correctedWire, List<Integer> wiresGroups) {
return correctedWire == 0 ? ourIndent : endPoint(correctedWire - 1, wiresGroups);
}
}
private final static Icon[] ourIcons = new Icon[] {Git4ideaIcons.Ball0, Git4ideaIcons.Ball1,
Git4ideaIcons.Ball2, Git4ideaIcons.Ball3, Git4ideaIcons.Ball4,
Git4ideaIcons.Ball5, Git4ideaIcons.Ball6, Git4ideaIcons.Ball7};
public static enum PresentationStyle {
multicolour() {
final Color[] ourColors = {new Color(255,128,0), new Color(0,102,204), new Color(0, 130,130), new Color(255,45,45), new Color(64,64,255),
new Color(128,64,0), new Color(255,0,255), new Color(128,0,255)};
@Override
public Color getColorForWire(int wire) {
return ourColors[wire % ourColors.length];
}
@Override
public Icon getNodeIcon(int wire) {
return ourIcons[wire%ourIcons.length];
}
},
calm() {
final Color darker2 = new Color(35,107,178);
final Color darker = new Color(255,128,0);
@Override
public Color getColorForWire(int wire) {
return (wire/2 % 2 == 1 ? darker : darker2);
}
@Override
public Icon getNodeIcon(int wire) {
return (wire/2 % 2 == 1 ? ourIcons[0] : ourIcons[1]);
}
};
public abstract Color getColorForWire(int wire);
public abstract Icon getNodeIcon(int wire);
}
public boolean isCurrentBranchColored() {
return myCurrentBranchColored;
}
public void setCurrentBranchColored(boolean currentBranchColored) {
myCurrentBranchColored = currentBranchColored;
}
}