package ru.batrdmi.svnplugin;
import com.intellij.ide.DataManager;
import com.intellij.ide.ui.LafManager;
import com.intellij.ide.ui.LafManagerListener;
import com.intellij.ide.ui.laf.darcula.DarculaLaf;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.progress.PerformInBackgroundOption;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.FrameWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.annotate.ShowAllAffectedGenericAction;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.history.CurrentRevision;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsHistoryUtil;
import com.intellij.openapi.vcs.vfs.ContentRevisionVirtualFile;
import com.intellij.openapi.vcs.vfs.VcsFileSystem;
import com.intellij.openapi.vcs.vfs.VcsVirtualFile;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.Gray;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBScrollPane;
import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.model.mxCell;
import com.mxgraph.shape.mxBasicShape;
import com.mxgraph.shape.mxConnectorShape;
import com.mxgraph.swing.handler.mxSelectionCellsHandler;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.*;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnRevisionNumber;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.history.SvnFileRevision;
import org.jetbrains.idea.svn.history.SvnRepositoryContentRevision;
import org.tmatesoft.svn.core.wc.SVNRevision;
import ru.batrdmi.svnplugin.actions.*;
import ru.batrdmi.svnplugin.gui.ScrollOverlayFactory;
import ru.batrdmi.svnplugin.logic.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.ScrollPaneUI;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class SVNRevisionGraph extends FrameWrapper implements mxEventSource.mxIEventListener {
private static final Logger log = Logger.getInstance("ru.batrdmi.svnplugin.SVNRevisionGraph");
public static final int CELL_SPACING = 16;
public static final String IN_BRANCH_LINK_STYLE = "inBranchLinkStyle";
public static final String COPY_OR_MERGE_LINK_STYLE = "copyOrMergeLinkStyle";
public static final String REVISION_STYLE = "revisionStyle";
public static final String REVISION_SHAPE = "revisionShape";
public static final String LINK_SHAPE = "linkShape";
private static final Color REVISION_COLOR = new JBColor(new Color(185, 230, 184), new Color(124, 184, 123));
private static final Color HIGHLIGHTED_REVISION_COLOR = new JBColor(new Color(255, 128, 128), new Color(128, 255, 255));
private static final Color PARTIALLY_HIGHLIGHTED_REVISION_COLOR = new JBColor(new Color(220, 179, 156), new Color(126, 220, 189));
private static final Color SELECTED_REVISION_COLOR = new JBColor(new Color(53, 128, 51), new Color(189, 237, 188));
private static final Color REVISION_BORDER_COLOR = new JBColor(Gray._0, new Color(53, 128, 51));
private static final Color DELETED_REVISION_BORDER_COLOR = new JBColor(Gray._0, new Color(124, 184, 123));
private static final Color LINK_COLOR = new JBColor(Gray._0, new Color(117, 183, 203));
private static final Color HIGHLIGHTED_LINK_COLOR = new JBColor(new Color(255, 0, 0), new Color(0, 255, 255));
private static final Color BRANCH_NAMES_COLOR = new JBColor(Gray._110, Gray._40);
private static final Color BRANCH_SEPARATING_LINE_COLOR = new JBColor(new Color(185, 185, 255), Gray._185);
private static final BasicStroke BRANCH_SEPARATING_LINE_STROKE = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1, new float[] {1, 2}, 0);
private static final Color BRANCH_LABEL_COLOR = new JBColor(new Color(255, 255, 255, 215), new Color(205, 205, 205, 215));
private static final Color BRANCH_LABEL_HIGHLIGHTED_COLOR = new JBColor(new Color(225, 225, 255, 215), new Color(250, 250, 250, 215));
private static final Color BRANCH_LABEL_BORDER_COLOR = new JBColor(new Color(225, 225, 255), Gray._210);
private static final Color BRANCH_LABEL_BORDER_HIGHLIGHTED_COLOR = new JBColor(new Color(185, 185, 255), Gray._255);
private static final int TEXTFIELD_WIDTH_IN_CHARS = 17;
private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static final DataKey<Revision> CURRENT_REVISION = DataKey.create("SVNRevisionGraph.currentRevision");
public static final DataKey<Revision> SELECTED_REVISION = DataKey.create("SVNRevisionGraph.selectedRevision");
public static final DataKey<List<Revision>> SELECTED_REVISIONS = DataKey.create("SVNRevisionGraph.selectedRevisions");
public static final DataKey<FilePath> SRC_FILE = DataKey.create("SVNRevisionGraph.srcFile");
public static final String REVISION_NUMBER_FIELD = "single";
public static final String REVISION_NUMBER_COMBOBOX = "multiple";
private final LafManagerListener myLookAndFeelListener = new LafManagerListener() {
@Override
public void lookAndFeelChanged(LafManager source) {
updateUI();
}
};
static {
mxGraphics2DCanvas.putShape(REVISION_SHAPE, new RevisionShape());
mxGraphics2DCanvas.putShape(LINK_SHAPE, new LinkShape());
}
public static void createAndShow(Project project, FilePath filePath) {
SVNRevisionGraphConfiguration configuration = SVNRevisionGraphConfiguration.getInstance(project);
SVNRevisionGraph graph = new SVNRevisionGraph(project, filePath);
createOrUpdateGraph(graph,
configuration.getScanMode(),
configuration.isCollapseRevisions(),
configuration.isDisplayingOnlyImpactingRevisions());
}
/**
* @param graph SVNRevisionGraph object
* @param scanMode if null, revision history, retrieved earlier will be used
* @param collapseRevisions whether graph should be displayed in 'collapsed revisions' mode
* @param displayOnlyImpactingRevisions whether only impacting revisions should be displayed in graph
*/
private static void createOrUpdateGraph(@NotNull final SVNRevisionGraph graph,
@Nullable final ScanMode scanMode,
final boolean collapseRevisions,
final boolean displayOnlyImpactingRevisions) {
graph.refreshInProgress = true;
final Project project = graph._project;
final FilePath filePath = graph._filePath;
new Task.Backgroundable(project, "Revision History", true, PerformInBackgroundOption.DEAF){
private FileRevisionHistory history;
private RevisionGraphModel graphModel;
private Exception myException;
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
SvnVcs svn = SvnVcs.getInstance(project);
if (scanMode != null) {
indicator.setText("Collecting revision history for file: " + filePath.getName());
history = new FileHistoryRetriever(svn, filePath.getVirtualFile())
.getFileHistory(scanMode, indicator);
} else {
history = graph.revisionHistory;
}
indicator.setText("Creating graph");
indicator.setText2(null);
graphModel = new RevisionGraphModel(history, collapseRevisions, displayOnlyImpactingRevisions);
} catch (Exception e) {
myException = e;
}
}
@Override
public void onSuccess() {
if (myException == null) {
graph.revisionHistory = history;
graph.collapseRevisions = collapseRevisions;
graph.displayOnlyImpactingRevisions = displayOnlyImpactingRevisions;
graph.setModel(graphModel);
if (graph.gc == null) {
LafManager.getInstance().addLafManagerListener(graph.myLookAndFeelListener);
graph.initComponents();
graph.show();
}
// We delay selecting current revision and scrolling to it,
// as validation of scroll pane caused by setting new model
// is also delayed. If we'll do scrolling before validation,
// we can get to wrong place
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
graph.selectAndShowRevision(graphModel.getCurrentRevision());
}
});
SVNRevisionGraphConfiguration configuration = SVNRevisionGraphConfiguration.getInstance(project);
configuration.setCollapseRevisions(collapseRevisions);
configuration.setDisplayOnlyImpactingRevisions(displayOnlyImpactingRevisions);
if (scanMode != null) {
graph.scanMode = scanMode;
configuration.setScanMode(scanMode);
if (history.getStatus().mergeInfoUnavailable
&& !configuration.isMergeInfoUnavailableWarningSuppressed()) {
Notifications.Bus.notify(new Notification("SvnRevisionGraph",
"Merge information is not available",
"Repository uses SVN version 1.4 or earlier. Displayed graph may contain less information.",
NotificationType.WARNING), project);
configuration.suppressMergeInfoUnavailableWarning();
}
if (history.getStatus().retrievedWithErrors) {
Notifications.Bus.notify(new Notification("SvnRevisionGraph",
"Error collecting revision information",
"Displayed graph may be incorrect or incomplete. More details are in IDEA's log.",
NotificationType.ERROR), project);
}
}
} else {
log.error("Error retrieving file history", myException);
Messages.showErrorDialog(project, "Error retrieving file history", "Revision Graph Error");
}
graph.refreshInProgress = false;
}
@Override
public void onCancel() {
graph.refreshInProgress = false;
}
}.queue();
}
private final KeyboardNavigator keyboardNavigator = new KeyboardNavigator(this);
private FilePath _filePath = null;
private Project _project = null;
private FileRevisionHistory revisionHistory;
private RevisionGraphModel model;
private GraphComponent gc;
private final BranchHighlighter branchHighlighter = new BranchHighlighter(this);
private JTextField _revisionTF = null;
private JComboBox revisionCB;
private JPanel revisionPanel;
private CardLayout revisionDisplay;
private JTextField _authorTF = null;
private JTextField _dateTF = null;
private JTextArea _messageTA = null;
private JViewport viewPort;
private ActionPopupMenu viewRevisionPopup;
private ScanMode scanMode;
private boolean collapseRevisions;
private boolean displayOnlyImpactingRevisions;
private boolean refreshInProgress;
public SVNRevisionGraph(Project project, FilePath filePath) {
super(project, "SvnRevisionGraphPlugin.dimensions");
_filePath = filePath;
_project = project;
setTitle("Revision Graph: " + filePath.getPath());
try {
setImage(ImageIO.read(getClass().getResource("/icons/svngraph.png")));
} catch (IOException e) {
log.error("Error loading icon", e);
}
setProject(project);
}
private void setModel(RevisionGraphModel graph) {
model = graph;
if (gc != null) {
gc.setGraph(model);
model.repaint();
}
model.getSelectionModel().addListener(mxEvent.CHANGE, this);
}
public RevisionGraphModel getModel() {
return model;
}
// should be called in EDT
public void initComponents() {
gc = new GraphComponent(model);
createComponents();
addMouseHandlers();
addKeyboardHandler();
createPopupActions();
}
private void createComponents() {
JPanel p = new JPanel(new BorderLayout());
p.add(createNorthPanel(), BorderLayout.NORTH);
p.add(createCenterPanel(), BorderLayout.CENTER);
p.add(createSouthPanel(), BorderLayout.SOUTH);
updateUI();
setComponent(p);
setPreferredFocusedComponent(gc.getGraphControl());
}
private JComponent createNorthPanel() {
DefaultActionGroup toolBarActions = new DefaultActionGroup(
new DisplayOnlyImpactingRevisionsAction(this),
new CollapseConsecutiveRevisionsAction(this),
new Separator(),
new CompareAction(this),
new MyGetVersionAction(),
new ShowPropertiesAction(),
new ShowPropertiesDiffAction(),
ShowAllAffectedGenericAction.getInstance(),
new Separator(),
new RefreshAction(this));
for (ScanMode scanMode : ScanMode.values()) {
toolBarActions.addAction(new ScanModeAction(this, scanMode)).setAsSecondary(true);
}
ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.CHANGES_VIEW_TOOLBAR,
toolBarActions, true);
// without this toolbar will be validated (and sized) only after 500 ms
// so focusing on current revision might not work properly
actionToolbar.updateActionsImmediately();
return actionToolbar.getComponent();
}
private void createPopupActions() {
ActionManager am = ActionManager.getInstance();
DefaultActionGroup popupActions = new DefaultActionGroup(
am.getAction(IdeActions.ACTION_EDIT_SOURCE),
new CompareAction(this),
new MyGetVersionAction(),
new ShowPropertiesAction(),
new ShowPropertiesDiffAction(),
ShowAllAffectedGenericAction.getInstance());
viewRevisionPopup = am.createActionPopupMenu(ActionPlaces.CHANGES_VIEW_POPUP, popupActions);
}
@Nullable
protected JComponent createCenterPanel() {
JBScrollPane scrollPane = new MyScrollPane(gc.getGraphControl());
viewPort = scrollPane.getViewport();
scrollPane.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, this);
return ScrollOverlayFactory.addOverlay(scrollPane, new BranchNamesOverlay(), false, true);
}
@Nullable
protected JComponent createSouthPanel() {
GridBagLayout gbl = new GridBagLayout();
JPanel retVal = new JPanel(gbl);
retVal.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.EAST,
GridBagConstraints.NONE, new Insets(5, 5, 0, 0), 0, 0);
retVal.add(new JLabel("Revision:"), gbc);
gbc.gridy = 1;
retVal.add(new JLabel("Author:"), gbc);
gbc.gridy = 2;
retVal.add(new JLabel("Date:"), gbc);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.CENTER;
_revisionTF = new MyTextField();
revisionCB = new MyComboBox();
revisionCB.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
RevisionInGroup item = (RevisionInGroup) e.getItem();
showRevisionProperties(item == null ? null : item.revision);
}
}
});
revisionDisplay = new CardLayout();
revisionPanel = new JPanel(revisionDisplay);
revisionPanel.add(_revisionTF, REVISION_NUMBER_FIELD);
revisionPanel.add(revisionCB, REVISION_NUMBER_COMBOBOX);
retVal.add(revisionPanel, gbc);
gbc.gridy = 1;
_authorTF = new MyTextField();
retVal.add(_authorTF, gbc);
gbc.gridy = 2;
_dateTF = new MyTextField();
retVal.add(_dateTF, gbc);
gbc.gridx = 2;
gbc.gridy = 0;
gbc.gridheight = 3;
gbc.anchor = GridBagConstraints.NORTHEAST;
retVal.add(new JLabel("Message:"), gbc);
gbc.gridx = 3;
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
_messageTA = new JTextArea();
_messageTA.setEditable(false);
JScrollPane sp = new MyScrollPane(_messageTA);
sp.setPreferredSize(new Dimension(300, 0));
retVal.add(sp, gbc);
return retVal;
}
private void updateUI() {
Color backgroundColor = UIManager.getColor("TextField.background");
Dimension preferredSize = _revisionTF.getPreferredSize();
_revisionTF.setMinimumSize(preferredSize);
_revisionTF.setBackground(backgroundColor);
_revisionTF.invalidate();
revisionCB.setPreferredSize(preferredSize);
revisionCB.setBackground(backgroundColor);
revisionCB.invalidate();
_authorTF.setMinimumSize(preferredSize);
_authorTF.setBackground(backgroundColor);
_authorTF.invalidate();
_dateTF.setMinimumSize(preferredSize);
_dateTF.setBackground(backgroundColor);
_dateTF.invalidate();
viewPort.setBackground(backgroundColor);
}
public void selectAndShowRevision(Revision rev) {
mxCell cell = model.getCellForRevision(rev);
model.setSelectionCell(cell);
gc.scrollCellToVisible(cell);
}
private void addMouseHandlers() {
mxGraphComponent.mxGraphControl graphControl = gc.getGraphControl();
graphControl.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
if (gc.getGraphControl().isEnabled() && !e.isConsumed() && e.getClickCount() == 2) {
mxCell cell = (mxCell) gc.getCellAt(e.getX(), e.getY(), false);
if (!revisionHistory.isDirectory() && cell != null && cell.isEdge()) {
mxCell cell1 = (mxCell) cell.getSource();
mxCell cell2 = (mxCell) cell.getTarget();
List<Revision> revs1 = ((RevisionGraphModel.Node) cell1.getValue()).revisions;
List<Revision> revs2 = ((RevisionGraphModel.Node) cell2.getValue()).revisions;
Revision r1 = revs1.get(revs1.size() - 1);
Revision r2 = revs2.get(revs2.size() - 1);
if (!r1.isDeleted() && !r2.isDeleted()) {
compareFileRevisions(r1, r2);
}
}
}
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
mxCell cell = (mxCell) gc.getCellAt(e.getX(), e.getY());
if (cell != null && cell.isVertex()) {
viewRevisionPopup.getComponent().show(e.getComponent(), e.getX(), e.getY());
}
}
}
});
RevisionHighlighter revisionHighlighter = new RevisionHighlighter(gc);
graphControl.addMouseListener(revisionHighlighter);
graphControl.addMouseMotionListener(revisionHighlighter);
graphControl.addMouseListener(branchHighlighter);
graphControl.addMouseMotionListener(branchHighlighter);
}
private void addKeyboardHandler() {
gc.getGraphControl().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (keyboardNavigator.processEvent(e)) {
e.consume();
}
}
});
}
@Override
public void dispose() {
model = null;
gc = null;
_project = null;
_filePath = null;
revisionHistory = null;
LafManager.getInstance().removeLafManagerListener(myLookAndFeelListener);
super.dispose();
}
public void compareFileRevisions(Revision rev1, Revision rev2) {
if (new Revision.RevisionNumberComparator().compare(rev1, rev2) > 0) {
Revision tempRev = rev2;
rev2 = rev1;
rev1 = tempRev;
}
try {
VcsFileRevision r1 = createVcsFileRevision(rev1, true);
VcsFileRevision r2 = createVcsFileRevision(rev2, true);
VcsHistoryUtil.showDiff(_project, _filePath, r1, r2,
rev1 + ((model.getCurrentRevision() == rev1) ? "(Local)" : ""),
rev2 + ((model.getCurrentRevision() == rev2) ? "(Local)" : ""));
} catch (Exception e) {
log.error("Error displaying diff", e);
Messages.showErrorDialog(_project, "Error showing diff", "Error");
}
}
private String getPathDisplayName(String path, String sourcePath) {
String[] pathSplit = FileNameUtil.splitPath(path);
String[] sourceSplit = FileNameUtil.splitPath(sourcePath);
if (pathSplit != null && sourceSplit != null
&& pathSplit[0].equals(sourceSplit[0])
&& pathSplit[2].equals(sourceSplit[2])) {
String innerPart = pathSplit[1];
if (innerPart.equals("/trunk")) {
return "trunk";
} else if (innerPart.startsWith("/branches/")) {
return innerPart.substring(10);
} else if (innerPart.startsWith("/tags/")) {
return "Tag: " + innerPart.substring(6);
}
}
return path;
}
@Override
public void invoke(Object sender, mxEventObject evt) {
selectionChanged();
}
public void selectionChanged() {
List<RevisionGraphModel.Node> selectedNodes = (model == null) ? null : model.getSelectedNodes();
if (selectedNodes != null && selectedNodes.size() == 1) {
List<Revision> selectedRevisions = selectedNodes.get(0).revisions;
if (selectedRevisions.size() == 1) {
Revision rev = selectedRevisions.get(0);
_revisionTF.setText("" + rev.getRevisionNumber());
revisionDisplay.show(revisionPanel, REVISION_NUMBER_FIELD);
showRevisionProperties(rev);
} else {
revisionCB.removeAllItems();
int index = 1;
for (Revision r : selectedRevisions) {
revisionCB.addItem(new RevisionInGroup(r, index++, selectedRevisions.size()));
}
revisionDisplay.show(revisionPanel, REVISION_NUMBER_COMBOBOX);
}
} else {
_revisionTF.setText(null);
showRevisionProperties(null);
revisionDisplay.show(revisionPanel, REVISION_NUMBER_FIELD);
}
}
public void moveUpRange() {
if (revisionCB.isVisible()) {
int i = revisionCB.getSelectedIndex();
if (i > 0) {
revisionCB.setSelectedIndex(i - 1);
}
}
}
public void moveDownRange() {
if (revisionCB.isVisible()) {
int i = revisionCB.getSelectedIndex();
if (i < revisionCB.getItemCount() - 1) {
revisionCB.setSelectedIndex(i + 1);
}
}
}
private void showRevisionProperties(@Nullable Revision rev) {
if (rev == null) {
_authorTF.setText(null);
_dateTF.setText(null);
_messageTA.setText(null);
} else {
_authorTF.setText(rev.getAuthor());
Date d = rev.getDate();
if (d != null) {
_dateTF.setText(DATE_TIME_FORMAT.format(d));
}
_messageTA.setText(rev.getMessage());
_messageTA.setCaretPosition(0); // to prevent scrolling to the end of message
}
}
public List<Revision> getSelectedRevisions() {
List<Revision> revs = new ArrayList<Revision>();
if (model != null) {
List<RevisionGraphModel.Node> selectedNodes = model.getSelectedNodes();
if (selectedNodes.size() == 1 && revisionCB.isVisible()) {
revs.add(((RevisionInGroup) revisionCB.getSelectedItem()).revision);
} else {
for (RevisionGraphModel.Node node : selectedNodes) {
revs.addAll(node.revisions);
}
}
}
return revs;
}
@Override
public Object getData(@NonNls String dataId) {
List<Revision> selectedRevisions = getSelectedRevisions();
Revision selectedRevision = (selectedRevisions.size() == 1) ? selectedRevisions.get(0) : null;
if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
if (selectedRevision != null && !selectedRevision.isDeleted()) {
VirtualFile vf = createVirtualFileForRevision(selectedRevision);
return new OpenFileDescriptor(_project, vf);
}
} else if (PlatformDataKeys.PROJECT.is(dataId)) {
return _project;
} else if (VcsDataKeys.VCS.is(dataId)) {
return SvnVcs.getInstance(_project).getKeyInstanceMethod();
} else if (VcsDataKeys.VCS_VIRTUAL_FILE.is(dataId)) {
if (selectedRevision != null) {
return new VcsVirtualFile(_filePath.getPath(),
createVcsFileRevision(selectedRevision, false),
VcsFileSystem.getInstance());
}
} else if (VcsDataKeys.VCS_FILE_REVISION.is(dataId)) {
if (selectedRevision != null) {
return createVcsFileRevision(selectedRevision, false);
}
} else if (CURRENT_REVISION.is(dataId)) {
return model.getCurrentRevision();
} else if (SELECTED_REVISION.is(dataId)) {
return selectedRevision;
} else if (SELECTED_REVISIONS.is(dataId)) {
return selectedRevisions;
} else if (SRC_FILE.is(dataId)) {
return _filePath;
}
return null;
}
private VirtualFile createVirtualFileForRevision(Revision r) {
ContentRevision cr = SvnRepositoryContentRevision.create(SvnVcs.getInstance(_project),
revisionHistory.getRepoRoot(), r.getRelPath(), null, r.getRevisionNumber());
return ContentRevisionVirtualFile.create(cr);
}
private VcsFileRevision createVcsFileRevision(Revision r, boolean checkCurrentRevision) {
SVNRevision svnRev = SVNRevision.create(r.getRevisionNumber());
if (checkCurrentRevision && r == model.getCurrentRevision()) {
return new CurrentRevision(_filePath.getVirtualFile(), new SvnRevisionNumber(svnRev));
} else {
SvnVcs svnVcs = SvnVcs.getInstance(_project);
return new SvnFileRevision(svnVcs, svnRev, svnRev,
revisionHistory.getRepoRoot() + r.getRelPath(), null, null, null, null);
}
}
public void refresh() {
createOrUpdateGraph(this, scanMode, collapseRevisions, displayOnlyImpactingRevisions);
}
public boolean isRefreshInProgress() {
return refreshInProgress;
}
public ScanMode getScanMode() {
return scanMode;
}
public void setScanMode(ScanMode scanMode) {
if (this.scanMode != scanMode) {
createOrUpdateGraph(this, scanMode, collapseRevisions, displayOnlyImpactingRevisions);
}
}
public boolean isCollapseRevisions() {
return collapseRevisions;
}
public void setCollapseRevisions(boolean collapseRevisions) {
if (collapseRevisions != this.collapseRevisions) {
createOrUpdateGraph(this, null, collapseRevisions, displayOnlyImpactingRevisions);
}
}
public boolean isDisplayingOnlyImpactingRevisions() {
return displayOnlyImpactingRevisions;
}
public void setDisplayOnlyImpactingRevisions(boolean displayOnlyImpactingRevisions) {
if (displayOnlyImpactingRevisions != this.displayOnlyImpactingRevisions) {
createOrUpdateGraph(this, null, collapseRevisions, displayOnlyImpactingRevisions);
}
}
public int getBranchForY(int y) {
if (model == null) {
return -1;
} else {
int stripeHeight = model.getNodeHeight() + SVNRevisionGraph.CELL_SPACING;
return y < 0 ? -1 : ((y - 7) / stripeHeight);
}
}
public void repaintBranch(int n) {
if (model != null) {
JComponent graphControl = gc.getGraphControl();
int step = model.getNodeHeight() + CELL_SPACING;
graphControl.repaint(0, CELL_SPACING - 14 + n * step, graphControl.getWidth(), 13);
}
}
private class BranchNamesOverlay extends JComponent {
@Override
public void paint(Graphics g) {
if (model != null) {
int step = model.getNodeHeight() + CELL_SPACING;
int currentY = CELL_SPACING - 4;
FontMetrics fm = g.getFontMetrics();
int i = 0;
int highlightedRow = branchHighlighter.getHighlightedRow();
for (String path : model.getAllPaths()) {
boolean highlighted = (i++ == highlightedRow);
String displayedPath = getPathDisplayName(path, revisionHistory.getRelPath());
int lineWidth = fm.stringWidth(" " + displayedPath + " ");
g.setColor(highlighted ? BRANCH_LABEL_HIGHLIGHTED_COLOR : BRANCH_LABEL_COLOR);
g.fillRoundRect(0, currentY - 10, lineWidth, 12, 7, 7);
g.setColor(highlighted ? BRANCH_LABEL_BORDER_HIGHLIGHTED_COLOR : BRANCH_LABEL_BORDER_COLOR);
g.drawRoundRect(0, currentY - 10, lineWidth, 12, 7, 7);
g.setColor(BRANCH_NAMES_COLOR);
g.drawString(displayedPath, 4, currentY);
currentY += step;
}
}
}
}
private static class RevisionShape extends mxBasicShape {
@Override
public Color getFillColor(mxGraphics2DCanvas canvas, mxCellState state) {
switch (RevisionHighlighter.getState((mxCell) state.getCell())) {
case OFF:
return state.getView().getGraph().isCellSelected(state.getCell()) ? SELECTED_REVISION_COLOR : REVISION_COLOR;
case PARTIAL:
return PARTIALLY_HIGHLIGHTED_REVISION_COLOR;
case ON:
return HIGHLIGHTED_REVISION_COLOR;
default:
return null;
}
}
@Override
public Color getStrokeColor(mxGraphics2DCanvas mxGraphics2DCanvas, mxCellState state) {
RevisionGraphModel.Node node = (RevisionGraphModel.Node) ((mxCell)state.getCell()).getValue();
return node.revisions.get(0).isDeleted() ? DELETED_REVISION_BORDER_COLOR : REVISION_BORDER_COLOR;
}
@Override
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state) {
Rectangle rect = state.getRectangle();
Rectangle innerRect = new Rectangle(rect.x + 1, rect.y + 1, rect.width - 1, rect.height - 1);
// Paints the background
if (configureGraphics(canvas, state, true))
{
canvas.fillShape(innerRect, hasShadow(canvas, state));
}
// Paints the foreground
if (configureGraphics(canvas, state, false))
{
canvas.getGraphics().drawRect(rect.x, rect.y, rect.width,
rect.height);
}
}
}
private static class LinkShape extends mxConnectorShape {
@Override
public void paintShape(mxGraphics2DCanvas canvas, mxCellState state) {
// mxConnectorShape doesn't properly use getStrokeColor, so we cannot just override it to change color
Object savedColor = state.getStyle().get(mxConstants.STYLE_STROKECOLOR);
try {
String color = mxUtils.getHexColorString(
RevisionHighlighter.getState((mxCell) state.getCell()) == RevisionHighlighter.State.OFF
? LINK_COLOR : HIGHLIGHTED_LINK_COLOR);
state.getStyle().put(mxConstants.STYLE_STROKECOLOR, color);
super.paintShape(canvas, state);
} finally {
state.getStyle().put(mxConstants.STYLE_STROKECOLOR, savedColor);
}
}
}
private static class RevisionInGroup {
private final Revision revision;
private final int index;
private final int groupSize;
private RevisionInGroup(Revision revision, int index, int groupSize) {
this.revision = revision;
this.index = index;
this.groupSize = groupSize;
}
@Override
public String toString() {
return Long.toString(revision.getRevisionNumber()) + " (" + index + " of " + groupSize + ")";
}
}
private class GraphComponent extends mxGraphComponent {
public GraphComponent(mxGraph graph) {
super(graph);
getConnectionHandler().setEnabled(false);
getGraphHandler().setMoveEnabled(false);
getGraphHandler().setMarkerEnabled(false);
getGraphHandler().setVisible(false);
getSelectionCellsHandler().setMaxHandlers(0);
setDragEnabled(false);
}
@Override
protected mxSelectionCellsHandler createSelectionCellsHandler() {
return new mxSelectionCellsHandler(this) {
@Override
public void paintHandles(Graphics g) {
}
};
}
@Override
// this is required as we use our own scroll pane, and not the one provided by JGraphX (mxGraphComponent)
protected void installFocusHandler() {
graphControl.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (!graphControl.hasFocus()) {
graphControl.requestFocus();
}
}
});
}
@Override
// this is required as we use our own scroll pane, and not the one provided by JGraphX (mxGraphComponent)
public JViewport getViewport() {
Container parent = (graphControl == null) ? null : graphControl.getParent();
return (parent instanceof JViewport) ? (JViewport) parent : super.getViewport();
}
@Override
// Original implementation doesn't recreate selection handler,
// it continues to refer to the previous mxGraph instance.
// I believe this is a bug.
public void setGraph(mxGraph value) {
super.setGraph(value);
selectionCellsHandler = createSelectionCellsHandler();
}
@Override
protected void paintBackground(Graphics g) {
if (model != null) {
int width = graphControl.getWidth();
int height = graphControl.getHeight();
Graphics2D g2 = (Graphics2D) g;
g2.setColor(BRANCH_SEPARATING_LINE_COLOR);
int stripeHeight = model.getNodeHeight() + CELL_SPACING;
int n = model.getAllPaths().size();
Rectangle clip = g2.getClipBounds();
g2.setStroke(new BasicStroke(
BRANCH_SEPARATING_LINE_STROKE.getLineWidth(),
BRANCH_SEPARATING_LINE_STROKE.getEndCap(),
BRANCH_SEPARATING_LINE_STROKE.getLineJoin(),
BRANCH_SEPARATING_LINE_STROKE.getMiterLimit(),
BRANCH_SEPARATING_LINE_STROKE.getDashArray(),
clip == null ? 0 : clip.x));
for (int y = 7, i = 0; y < height && i <= n; y += stripeHeight, i++) {
if (clip == null) {
g2.drawLine(0, y, width, y);
} else if (y >= clip.y && y <= (clip.y + clip.height)) {
g2.drawLine(clip.x, y, clip.x + clip.width, y);
}
}
}
}
}
private static class MyTextField extends JTextField {
private boolean inPaint;
private MyTextField() {
super(TEXTFIELD_WIDTH_IN_CHARS);
setEditable(false);
}
@Override
public void paint(Graphics g) {
inPaint = true;
try {
super.paint(g);
} finally {
inPaint = false;
}
}
@Override
public boolean isEditable() {
return inPaint && UIManager.getLookAndFeel() instanceof DarculaLaf || super.isEditable();
}
}
private static class MyComboBox extends JComboBox {
private boolean inPaint;
@Override
public void paint(Graphics g) {
inPaint = true;
try {
super.paint(g);
} finally {
inPaint = false;
}
}
@Override
public boolean isEditable() {
return inPaint && UIManager.getLookAndFeel() instanceof DarculaLaf || super.isEditable();
}
@Override
public void setUI(ComboBoxUI ui) {
setBorder(null);
super.setUI(ui);
}
}
private static class MyScrollPane extends JBScrollPane {
private MyScrollPane(Component view) {
super(view);
}
@Override
public void setUI(ScrollPaneUI ui) {
super.setUI(ui);
setupCorners();
}
}
}