blob: 985423280f1665a5fb1d60fab0b6edbaf695f49a [file] [log] [blame]
package com.intellij.vcs.log.ui.frame;
import com.google.common.collect.Ordering;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ListUtil;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.vcs.log.RefGroup;
import com.intellij.vcs.log.VcsLogProvider;
import com.intellij.vcs.log.VcsLogRefManager;
import com.intellij.vcs.log.VcsRef;
import com.intellij.vcs.log.data.DataPack;
import com.intellij.vcs.log.data.RefsModel;
import com.intellij.vcs.log.data.VcsLogDataHolder;
import com.intellij.vcs.log.data.VcsLogRefreshListener;
import com.intellij.vcs.log.impl.SingletonRefGroup;
import com.intellij.vcs.log.impl.VcsLogUtil;
import com.intellij.vcs.log.ui.VcsLogUiImpl;
import com.intellij.vcs.log.ui.render.RefPainter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.intellij.vcs.log.graph.render.PrintParameters.HEIGHT_CELL;
/**
* Panel with branch labels, above the graph.
*/
public class BranchesPanel extends JPanel {
private final VcsLogDataHolder myDataHolder;
private final VcsLogUiImpl myUI;
private List<RefGroup> myRefGroups;
private final RefPainter myRefPainter;
private Map<Integer, RefGroup> myRefPositions = ContainerUtil.newHashMap();
public BranchesPanel(@NotNull VcsLogDataHolder dataHolder, @NotNull VcsLogUiImpl UI, @NotNull RefsModel initialRefsModel) {
myDataHolder = dataHolder;
myUI = UI;
myRefGroups = getRefsToDisplayOnPanel(initialRefsModel);
myRefPainter = new RefPainter(myUI.getColorManager(), true);
setPreferredSize(new Dimension(-1, HEIGHT_CELL + UIUtil.DEFAULT_VGAP));
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (!myUI.getMainFrame().areGraphActionsEnabled()) {
return;
}
final RefGroup group = findRef(e);
if (group == null) {
return;
}
if (group.getRefs().size() == 1) {
VcsRef ref = group.getRefs().iterator().next();
myUI.jumpToCommit(ref.getCommitHash());
}
else {
final RefPopupComponent view = new RefPopupComponent(group, myUI, myRefPainter);
JBPopup popup = view.getPopup();
popup.show(new RelativePoint(BranchesPanel.this, new Point(e.getX(), BranchesPanel.this.getHeight())));
}
}
});
Project project = dataHolder.getProject();
project.getMessageBus().connect(project).subscribe(VcsLogDataHolder.REFRESH_COMPLETED, new VcsLogRefreshListener() {
@Override
public void refresh(@NotNull DataPack dataPack) {
rebuild(dataPack.getRefsModel());
}
});
}
@Nullable
private RefGroup findRef(MouseEvent e) {
List<Integer> sortedPositions = Ordering.natural().sortedCopy(myRefPositions.keySet());
int index = Ordering.natural().binarySearch(sortedPositions, e.getX());
if (index < 0) {
index = -index - 2;
}
if (index < 0) {
return null;
}
return myRefPositions.get(sortedPositions.get(index));
}
@Override
protected void paintComponent(Graphics g) {
myRefPositions = ContainerUtil.newHashMap();
int paddingX = 0;
for (RefGroup group : myRefGroups) {
// TODO it is assumed here that all refs in a single group belong to a single root
Color rootIndicatorColor = myUI.getColorManager().getRootColor(group.getRefs().iterator().next().getRoot());
Rectangle rectangle = myRefPainter.drawLabel((Graphics2D)g, group.getName(), paddingX, group.getBgColor(), rootIndicatorColor);
paddingX += rectangle.width + UIUtil.DEFAULT_HGAP;
myRefPositions.put(rectangle.x, group);
}
}
public void rebuild(@NotNull RefsModel refsModel) {
myRefGroups = getRefsToDisplayOnPanel(refsModel);
getParent().repaint();
}
@NotNull
private List<RefGroup> getRefsToDisplayOnPanel(@NotNull RefsModel refsModel) {
Collection<VcsRef> allRefs = refsModel.getBranches();
List<RefGroup> groups = ContainerUtil.newArrayList();
for (Map.Entry<VirtualFile, Collection<VcsRef>> entry : VcsLogUtil.groupRefsByRoot(allRefs).entrySet()) {
VirtualFile root = entry.getKey();
Collection<VcsRef> refs = entry.getValue();
VcsLogProvider provider = myDataHolder.getLogProvider(root);
VcsLogRefManager refManager = provider.getReferenceManager();
groups.addAll(expandExpandableGroups(refManager.group(refs)));
}
return groups;
}
private static Collection<RefGroup> expandExpandableGroups(List<RefGroup> refGroups) {
Collection<RefGroup> groups = ContainerUtil.newArrayList();
for (RefGroup group : refGroups) {
if (group.isExpanded()) {
groups.addAll(ContainerUtil.map(group.getRefs(), new Function<VcsRef, RefGroup>() {
@Override
public RefGroup fun(VcsRef ref) {
return new SingletonRefGroup(ref);
}
}));
}
else {
groups.add(group);
}
}
return groups;
}
private static class RefPopupComponent extends JPanel {
private final JBPopup myPopup;
private final JBList myList;
private final VcsLogUiImpl myUi;
private final RefPainter myRefPainter;
private final SingleRefComponent myRendererComponent;
private final ListCellRenderer myCellRenderer;
RefPopupComponent(RefGroup group, VcsLogUiImpl ui, RefPainter refPainter) {
super(new BorderLayout());
myUi = ui;
myRefPainter = refPainter;
myRendererComponent = new SingleRefComponent(myRefPainter);
myCellRenderer = new ListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
myRendererComponent.setRef((VcsRef)value);
myRendererComponent.setSelected(isSelected);
return myRendererComponent;
}
};
myList = createList(group);
myPopup = createPopup();
add(new JBScrollPane(myList));
}
private JBList createList(RefGroup group) {
JBList list = new JBList(createListModel(group));
list.setCellRenderer(myCellRenderer);
ListUtil.installAutoSelectOnMouseMove(list);
list.setSelectedIndex(0);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jumpOnMouseClick(list);
jumpOnEnter(list);
return list;
}
private void jumpOnMouseClick(JBList list) {
list.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
jumpToSelectedRef();
}
});
}
private void jumpOnEnter(JBList list) {
list.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
jumpToSelectedRef();
}
}
});
}
private JBPopup createPopup() {
return JBPopupFactory.getInstance().
createComponentPopupBuilder(this, myList).
setCancelOnClickOutside(true).
setCancelOnWindowDeactivation(true).
setFocusable(true).
setRequestFocus(true).
setResizable(true).
setDimensionServiceKey(myUi.getProject(), "Vcs.Log.Branch.Panel.RefGroup.Popup", false).
createPopup();
}
private static DefaultListModel createListModel(RefGroup group) {
DefaultListModel model = new DefaultListModel();
for (final VcsRef vcsRef : group.getRefs()) {
model.addElement(vcsRef);
}
return model;
}
@NotNull
JBPopup getPopup() {
return myPopup;
}
private void jumpToSelectedRef() {
myPopup.cancel(); // close the popup immediately not to stay at the front if jumping to a commits takes long time.
VcsRef selectedRef = (VcsRef)myList.getSelectedValue();
if (selectedRef != null) {
myUi.jumpToCommit(selectedRef.getCommitHash());
}
}
}
private static class SingleRefComponent extends JPanel {
private final RefPainter myRefPainter;
private VcsRef myRef;
public boolean mySelected;
public SingleRefComponent(RefPainter refPainter) {
myRefPainter = refPainter;
}
@Override
protected void paintComponent(Graphics g) {
g.setColor(mySelected ? UIUtil.getListSelectionBackground() : UIUtil.getListBackground());
g.fillRect(0, 0, getWidth(), getHeight());
if (myRef != null) {
myRefPainter.draw((Graphics2D)g, Collections.singletonList(myRef), 0, calcWidth());
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(calcWidth(), HEIGHT_CELL);
}
private int calcWidth() {
FontMetrics metrics = getFontMetrics(getFont());
return metrics.stringWidth(myRef.getName());
}
public void setRef(@NotNull VcsRef ref) {
myRef = ref;
}
public void setSelected(boolean selected) {
mySelected = selected;
}
}
}