blob: 1539b94216c03283435471f38a5c67688bde92b1 [file] [log] [blame]
/*
* Copyright 2000-2014 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 com.intellij.dvcs.push.ui;
import com.intellij.dvcs.push.PushTargetPanel;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.DataSink;
import com.intellij.openapi.actionSystem.TypeSafeDataProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowser;
import com.intellij.ui.CheckboxTree;
import com.intellij.ui.CheckedTreeNode;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.tree.TreeUtil;
import com.intellij.vcs.log.VcsFullCommitDetails;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
public class PushLog extends JPanel implements TypeSafeDataProvider {
private static final String START_EDITING = "startEditing";
private final ChangesBrowser myChangesBrowser;
private final CheckboxTree myTree;
private final MyTreeCellRenderer myTreeCellRenderer;
private boolean myEditingSucceeded;
public PushLog(Project project, CheckedTreeNode root) {
DefaultTreeModel treeModel = new DefaultTreeModel(root);
treeModel.nodeStructureChanged(root);
myTreeCellRenderer = new MyTreeCellRenderer();
myTree = new CheckboxTree(myTreeCellRenderer, root) {
public boolean isPathEditable(TreePath path) {
return isEditable() && path.getLastPathComponent() instanceof DefaultMutableTreeNode;
}
@Override
protected void onNodeStateChanged(CheckedTreeNode node) {
if (node instanceof EditableTreeNode) {
((EditableTreeNode)node).fireOnSelectionChange(node.isChecked());
}
}
@Override
public String getToolTipText(MouseEvent event) {
final TreePath path = myTree.getPathForLocation(event.getX(), event.getY());
if (path == null) {
return "";
}
Object node = path.getLastPathComponent();
if (node == null || (!(node instanceof DefaultMutableTreeNode))) {
return "";
}
if (node instanceof TooltipNode) {
return ((TooltipNode)node).getTooltip();
}
return "";
}
@Override
public boolean stopEditing() {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)myTree.getLastSelectedPathComponent();
if (node instanceof EditableTreeNode) {
JComponent editedComponent = (JComponent)node.getUserObject();
InputVerifier verifier = editedComponent.getInputVerifier();
if (verifier != null && !verifier.verify(editedComponent)) return false;
}
myEditingSucceeded = true;
try {
return super.stopEditing();
}
finally {
myEditingSucceeded = false;
}
}
};
myTree.setEditable(true);
MyTreeCellEditor treeCellEditor = new MyTreeCellEditor(new JBTextField());
myTree.setCellEditor(treeCellEditor);
treeCellEditor.addCellEditorListener(new CellEditorListener() {
@Override
public void editingStopped(ChangeEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)myTree.getLastSelectedPathComponent();
if (node != null && node instanceof EditableTreeNode) {
((EditableTreeNode)node).fireOnChange();
}
}
@Override
public void editingCanceled(ChangeEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)myTree.getLastSelectedPathComponent();
if (node != null && node instanceof EditableTreeNode) {
((EditableTreeNode)node).fireOnCancel();
}
}
});
myTree.setRootVisible(false);
TreeUtil.expandAll(myTree);
final VcsBranchEditorListener linkMouseListener = new VcsBranchEditorListener(myTreeCellRenderer);
linkMouseListener.installOn(myTree);
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
myTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
TreePath[] nodes = myTree.getSelectionPaths();
if (nodes != null) {
ArrayList<Change> changes = new ArrayList<Change>();
for (TreePath path : nodes) {
if (path.getLastPathComponent() instanceof VcsFullCommitDetailsNode) {
VcsFullCommitDetailsNode commitDetailsNode = (VcsFullCommitDetailsNode)path.getLastPathComponent();
changes.addAll(commitDetailsNode.getUserObject().getChanges());
}
else if (path.getLastPathComponent() instanceof RepositoryNode) {
changes.addAll(collectAllChanges((RepositoryNode)path.getLastPathComponent()));
}
}
myChangesBrowser.getViewer().setEmptyText("No differences");
myChangesBrowser.setChangesToDisplay(CommittedChangesTreeBrowser.zipChanges(changes));
return;
}
setDefaultEmptyText();
myChangesBrowser.setChangesToDisplay(Collections.<Change>emptyList());
}
});
myTree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), START_EDITING);
//override default tree behaviour.
myTree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "");
myTree.setRowHeight(0);
ToolTipManager.sharedInstance().registerComponent(myTree);
myChangesBrowser =
new ChangesBrowser(project, null, Collections.<Change>emptyList(), null, false, true, null, ChangesBrowser.MyUseCase.LOCAL_CHANGES,
null);
myChangesBrowser.getDiffAction().registerCustomShortcutSet(CommonShortcuts.getDiff(), myTree);
setDefaultEmptyText();
Splitter splitter = new Splitter(false, 0.7f);
splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree));
splitter.setSecondComponent(myChangesBrowser);
setLayout(new BorderLayout());
add(splitter);
}
@NotNull
private static Collection<? extends Change> collectAllChanges(@NotNull RepositoryNode rootNode) {
ArrayList<Change> changes = new ArrayList<Change>();
if (rootNode.getChildCount() <= 0) return changes;
for (DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)rootNode.getFirstChild();
childNode != null;
childNode = (DefaultMutableTreeNode)rootNode.getChildAfter(childNode)) {
if (childNode instanceof VcsFullCommitDetailsNode) {
changes.addAll(((VcsFullCommitDetailsNode)childNode).getUserObject().getChanges());
}
}
return changes;
}
private void setDefaultEmptyText() {
myChangesBrowser.getViewer().setEmptyText("No commits selected");
}
public void selectNode(@NotNull DefaultMutableTreeNode node) {
TreePath selectionPath = new TreePath(node.getPath());
myTree.addSelectionPath(selectionPath);
}
// Make changes available for diff action
@Override
public void calcData(DataKey key, DataSink sink) {
if (VcsDataKeys.CHANGES.equals(key)) {
DefaultMutableTreeNode[] selectedNodes = myTree.getSelectedNodes(DefaultMutableTreeNode.class, null);
if (selectedNodes.length == 0) {
return;
}
Object object = selectedNodes[0].getUserObject();
if (object instanceof VcsFullCommitDetails) {
sink.put(key, ArrayUtil.toObjectArray(((VcsFullCommitDetails)object).getChanges(), Change.class));
}
}
}
@Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
if (e.getKeyCode() == KeyEvent.VK_ENTER && pressed) {
if (myTree.isEditing()) {
myTree.stopEditing();
}
else {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)myTree.getLastSelectedPathComponent();
myTree.startEditingAtPath(TreeUtil.getPathFromRoot(node));
}
return true;
}
return super.processKeyBinding(ks, e, condition, pressed);
}
public void startLoading(DefaultMutableTreeNode parentNode) {
LoadingTreeNode loading = new LoadingTreeNode();
loading.getIcon().setImageObserver(new NodeImageObserver(myTree, loading));
setChildren(parentNode, Collections.singleton(loading));
}
public JComponent getPreferredFocusedComponent() {
return myTree;
}
private class MyTreeCellEditor extends DefaultCellEditor {
public MyTreeCellEditor(JTextField field) {
super(field);
setClickCountToStart(1);
}
@Override
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
final Object node = ((DefaultMutableTreeNode)value).getUserObject();
editorComponent =
(JComponent)((RepositoryWithBranchPanel)node).getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, true);
return editorComponent;
}
@Override
public boolean isCellEditable(EventObject anEvent) {
if (anEvent instanceof MouseEvent) {
MouseEvent me = ((MouseEvent)anEvent);
final TreePath path = myTree.getClosestPathForLocation(me.getX(), me.getY());
final int row = myTree.getRowForLocation(me.getX(), me.getY());
myTree.getCellRenderer().getTreeCellRendererComponent(myTree, path.getLastPathComponent(), false, false, true, row, true);
Object tag = me.getClickCount() >= clickCountToStart
? PushLogTreeUtil.getTagAtForRenderer(myTreeCellRenderer, me)
: null;
return tag instanceof PushTargetPanel;
}
//if keyboard event - then anEvent will be null =( See BasicTreeUi
TreePath treePath = myTree.getAnchorSelectionPath();
//there is no selection path if we start editing during initial validation//
if (treePath == null) return true;
Object treeNode = treePath.getLastPathComponent();
return treeNode instanceof EditableTreeNode;
}
//Implement the one CellEditor method that AbstractCellEditor doesn't.
public Object getCellEditorValue() {
return myEditingSucceeded ? ((RepositoryWithBranchPanel)editorComponent).getEditableValue() : null;
}
}
private static class MyTreeCellRenderer extends CheckboxTree.CheckboxTreeCellRenderer {
@Override
public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if (!(value instanceof DefaultMutableTreeNode)) {
return;
}
if (value instanceof RepositoryNode) {
//todo simplify, remove instance of
myCheckbox.setVisible(((RepositoryNode)value).isCheckboxVisible());
}
Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
ColoredTreeCellRenderer renderer = getTextRenderer();
renderer.setBorder(null);
if (value instanceof CustomRenderedTreeNode) {
((CustomRenderedTreeNode)value).render(renderer);
}
else {
renderer.append(userObject == null ? "" : userObject.toString());
}
}
}
public void setChildren(DefaultMutableTreeNode parentNode, @NotNull Collection<? extends DefaultMutableTreeNode> childrenNodes) {
setChildren(parentNode, childrenNodes, true);
}
public void setChildren(DefaultMutableTreeNode parentNode,
@NotNull Collection<? extends DefaultMutableTreeNode> childrenNodes,
boolean shouldExpand) {
parentNode.removeAllChildren();
for (DefaultMutableTreeNode child : childrenNodes) {
parentNode.add(child);
}
final DefaultTreeModel model = ((DefaultTreeModel)myTree.getModel());
model.nodeStructureChanged(parentNode);
TreePath path = TreeUtil.getPathFromRoot(parentNode);
if (shouldExpand) {
myTree.expandPath(path);
}
else {
myTree.collapsePath(path);
}
}
}