blob: 1ec176990c871b57904c44ef61575a5fe57afbc3 [file] [log] [blame]
/*
* Copyright 2000-2010 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 org.jetbrains.idea.svn.dialogs;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.vcs.ObjectsConvertor;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.DottedBorder;
import com.intellij.ui.JBColor;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.components.labels.LinkListener;
import com.intellij.util.Consumer;
import com.intellij.util.containers.Convertor;
import com.intellij.util.io.EqualityPolicy;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.NestedCopyType;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.WorkingCopyFormat;
import org.jetbrains.idea.svn.actions.CleanupWorker;
import org.jetbrains.idea.svn.actions.SelectBranchPopup;
import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew;
import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider;
import org.jetbrains.idea.svn.integrate.QuickMergeInteractionImpl;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.wc.SVNRevision;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.*;
import java.util.List;
public class CopiesPanel {
private static final Logger LOG = Logger.getInstance(CopiesPanel.class);
private final Project myProject;
private MessageBusConnection myConnection;
private SvnVcs myVcs;
private JPanel myPanel;
private JComponent myHolder;
private LinkLabel myRefreshLabel;
// updated only on AWT
private List<OverrideEqualsWrapper<WCInfo>> myCurrentInfoList;
private int myTextHeight;
private final static String CHANGE_FORMAT = "CHANGE_FORMAT";
private final static String CLEANUP = "CLEANUP";
private final static String FIX_DEPTH = "FIX_DEPTH";
private final static String CONFIGURE_BRANCHES = "CONFIGURE_BRANCHES";
private final static String MERGE_FROM = "MERGE_FROM";
public CopiesPanel(final Project project) {
myProject = project;
myConnection = myProject.getMessageBus().connect(myProject);
myVcs = SvnVcs.getInstance(myProject);
myCurrentInfoList = null;
final Runnable focus = new Runnable() {
@Override
public void run() {
IdeFocusManager.getInstance(myProject).requestFocus(myRefreshLabel, true);
}
};
final Runnable refreshView = new Runnable() {
@Override
public void run() {
final List<WCInfo> infoList = myVcs.getAllWcInfos();
final List<WorkingCopyFormat> supportedFormats = getSupportedFormats();
Runnable runnable = new Runnable() {
@Override
public void run() {
if (myCurrentInfoList != null) {
final List<OverrideEqualsWrapper<WCInfo>> newList =
ObjectsConvertor.convert(infoList, new Convertor<WCInfo, OverrideEqualsWrapper<WCInfo>>() {
@Override
public OverrideEqualsWrapper<WCInfo> convert(WCInfo o) {
return new OverrideEqualsWrapper<WCInfo>(InfoEqualityPolicy.getInstance(), o);
}
}, ObjectsConvertor.NOT_NULL);
if (Comparing.haveEqualElements(newList, myCurrentInfoList)) {
myRefreshLabel.setEnabled(true);
return;
}
myCurrentInfoList = newList;
}
Collections.sort(infoList, WCComparator.getInstance());
updateList(infoList, supportedFormats);
myRefreshLabel.setEnabled(true);
SwingUtilities.invokeLater(focus);
}
};
ApplicationManager.getApplication().invokeLater(runnable, ModalityState.NON_MODAL);
}
};
final Consumer<Boolean> refreshOnPooled = new Consumer<Boolean>() {
@Override
public void consume(Boolean somethingNew) {
if (Boolean.TRUE.equals(somethingNew)) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
refreshView.run();
}
else {
ApplicationManager.getApplication().executeOnPooledThread(refreshView);
}
} else {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
myRefreshLabel.setEnabled(true);
}
}, ModalityState.NON_MODAL);
}
}
};
myConnection.subscribe(SvnVcs.ROOTS_RELOADED, refreshOnPooled);
final JPanel holderPanel = new JPanel(new BorderLayout());
FontMetrics fm = holderPanel.getFontMetrics(holderPanel.getFont());
myTextHeight = (int)(fm.getHeight() * 1.3);
myPanel = new JPanel(new GridBagLayout());
final JPanel panel = new JPanel(new BorderLayout());
panel.add(myPanel, BorderLayout.NORTH);
holderPanel.add(panel, BorderLayout.WEST);
myRefreshLabel = new MyLinkLabel(myTextHeight, "Refresh", new LinkListener() {
@Override
public void linkSelected(LinkLabel aSource, Object aLinkData) {
if (myRefreshLabel.isEnabled()) {
myVcs.invokeRefreshSvnRoots();
myRefreshLabel.setEnabled(false);
}
}
});
final JScrollPane pane = ScrollPaneFactory.createScrollPane(holderPanel);
myHolder = pane;
final JScrollBar vBar = pane.getVerticalScrollBar();
vBar.setBlockIncrement(vBar.getBlockIncrement() * 5);
vBar.setUnitIncrement(vBar.getUnitIncrement() * 5);
myHolder.setBorder(null);
setFocusableForLinks(myRefreshLabel);
refreshOnPooled.consume(true);
initView();
}
public JComponent getPreferredFocusedComponent() {
return myRefreshLabel;
}
private void updateList(@NotNull final List<WCInfo> infoList, @NotNull final List<WorkingCopyFormat> supportedFormats) {
myPanel.removeAll();
final Insets nullIndent = new Insets(1, 3, 1, 0);
final GridBagConstraints gb =
new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(2, 2, 0, 0), 0, 0);
gb.insets.left = 4;
myPanel.add(myRefreshLabel, gb);
gb.insets.left = 1;
final LocalFileSystem lfs = LocalFileSystem.getInstance();
final Insets topIndent = new Insets(10, 3, 0, 0);
for (final WCInfo wcInfo : infoList) {
final VirtualFile vf = lfs.refreshAndFindFileByIoFile(new File(wcInfo.getPath()));
final VirtualFile root = (vf == null) ? wcInfo.getVcsRoot() : vf;
final JEditorPane editorPane = new JEditorPane(UIUtil.HTML_MIME, "");
editorPane.setEditable(false);
editorPane.setFocusable(true);
editorPane.setBackground(UIUtil.getPanelBackground());
editorPane.setOpaque(false);
editorPane.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if (CONFIGURE_BRANCHES.equals(e.getDescription())) {
if (! checkRoot(root, wcInfo.getPath(), " invoke Configure Branches")) return;
BranchConfigurationDialog.configureBranches(myProject, root, true);
} else if (FIX_DEPTH.equals(e.getDescription())) {
final int result =
Messages.showOkCancelDialog(myVcs.getProject(), "You are going to checkout into '" + wcInfo.getPath() + "' with 'infinity' depth.\n" +
"This will update your working copy to HEAD revision as well.",
"Set Working Copy Infinity Depth",
Messages.getWarningIcon());
if (result == 0) {
// update of view will be triggered by roots changed event
SvnCheckoutProvider.checkout(myVcs.getProject(), new File(wcInfo.getPath()), wcInfo.getRootUrl(), SVNRevision.HEAD,
SVNDepth.INFINITY, false, null, wcInfo.getFormat());
}
} else if (CHANGE_FORMAT.equals(e.getDescription())) {
changeFormat(wcInfo, supportedFormats);
} else if (MERGE_FROM.equals(e.getDescription())) {
if (! checkRoot(root, wcInfo.getPath(), " invoke Merge From")) return;
mergeFrom(wcInfo, root, editorPane);
} else if (CLEANUP.equals(e.getDescription())) {
if (! checkRoot(root, wcInfo.getPath(), " invoke Cleanup")) return;
new CleanupWorker(new VirtualFile[] {root}, myVcs.getProject(), "action.Subversion.cleanup.progress.title").execute();
}
}
}
private boolean checkRoot(VirtualFile root, final String path, final String actionName) {
if (root == null) {
Messages.showWarningDialog(myProject, "Invalid working copy root: " + path, "Can not " + actionName);
return false;
}
return true;
}
});
editorPane.setBorder(null);
editorPane.setText(formatWc(wcInfo, supportedFormats));
final JPanel copyPanel = new JPanel(new GridBagLayout());
final GridBagConstraints gb1 =
new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, nullIndent, 0, 0);
gb1.insets.top = 1;
gb1.gridwidth = 3;
gb.insets = topIndent;
gb.fill = GridBagConstraints.HORIZONTAL;
++ gb.gridy;
final JPanel contForCopy = new JPanel(new BorderLayout());
contForCopy.add(copyPanel, BorderLayout.WEST);
myPanel.add(contForCopy, gb);
copyPanel.add(editorPane, gb1);
gb1.insets = nullIndent;
}
myPanel.revalidate();
myPanel.repaint();
}
@SuppressWarnings("MethodMayBeStatic")
private String formatWc(@NotNull WCInfo info, @NotNull List<WorkingCopyFormat> supportedFormats) {
final StringBuilder sb = new StringBuilder().append("<html><head>").append(UIUtil.getCssFontDeclaration(UIUtil.getLabelFont()))
.append("</head><body><table bgColor=\"").append(ColorUtil.toHex(UIUtil.getPanelBackground())).append("\">");
sb.append("<tr valign=\"top\"><td colspan=\"3\"><b>").append(info.getPath()).append("</b></td></tr>");
sb.append("<tr valign=\"top\"><td>URL:</td><td colspan=\"2\">").append(info.getRootUrl()).append("</td></tr>");
Collection<WorkingCopyFormat> upgradeFormats = getUpgradeFormats(info, supportedFormats);
if (upgradeFormats.size() > 1) {
sb.append("<tr valign=\"top\"><td>Format:</td><td>").append(info.getFormat().getName()).append("</td><td><a href=\"").
append(CHANGE_FORMAT).append("\">Change</a></td></tr>");
} else {
sb.append("<tr valign=\"top\"><td>Format:</td><td colspan=\"2\">").append(info.getFormat().getName()).append("</td></tr>");
}
if (! SVNDepth.INFINITY.equals(info.getStickyDepth())) {
// can fix
sb.append("<tr valign=\"top\"><td>Depth:</td><td>").append(info.getStickyDepth().getName()).append("</td><td><a href=\"").
append(FIX_DEPTH).append("\">Fix</a></td></tr>");
} else {
sb.append("<tr valign=\"top\"><td>Depth:</td><td colspan=\"2\">").append(info.getStickyDepth().getName()).append("</td></tr>");
}
final NestedCopyType type = info.getType();
if (NestedCopyType.external.equals(type) || NestedCopyType.switched.equals(type)) {
sb.append("<tr valign=\"top\"><td colspan=\"3\"><i>").append(type.getName()).append("</i></td></tr>");
}
if (info.isIsWcRoot()) {
sb.append("<tr valign=\"top\"><td colspan=\"3\"><i>").append("Working copy root</i></td></tr>");
}
if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(info.getFormat()) || WorkingCopyFormat.ONE_DOT_EIGHT.equals(info.getFormat())) {
sb.append("<tr valign=\"top\"><td colspan=\"3\"><a href=\"").append(CLEANUP).append("\">Cleanup</a></td></tr>");
}
sb.append("<tr valign=\"top\"><td colspan=\"3\"><a href=\"").append(CONFIGURE_BRANCHES).append("\">Configure Branches</a></td></tr>");
sb.append("<tr valign=\"top\"><td colspan=\"3\"><a href=\"").append(MERGE_FROM).append("\"><b>Merge From...</b></a></i></td></tr>");
sb.append("</table></body></html>");
return sb.toString();
}
@NotNull
private List<WorkingCopyFormat> getSupportedFormats() {
List<WorkingCopyFormat> result = Collections.emptyList();
try {
result = myVcs.getFactory().createUpgradeClient().getSupportedFormats();
}
catch (VcsException e) {
LOG.info(e);
}
return result;
}
public static Set<WorkingCopyFormat> getUpgradeFormats(@NotNull WCInfo info, @NotNull List<WorkingCopyFormat> supportedFormats) {
Set<WorkingCopyFormat> canUpgradeTo = EnumSet.noneOf(WorkingCopyFormat.class);
for (WorkingCopyFormat format : supportedFormats) {
if (format.isOrGreater(info.getFormat())) {
canUpgradeTo.add(format);
}
}
canUpgradeTo.add(info.getFormat());
return canUpgradeTo;
}
private void mergeFrom(@NotNull final WCInfo wcInfo, @NotNull final VirtualFile root, @Nullable final Component mergeLabel) {
SelectBranchPopup.showForBranchRoot(myProject, root, new SelectBranchPopup.BranchSelectedCallback() {
@Override
public void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision) {
new QuickMerge(project, url, wcInfo, SVNPathUtil.tail(url), root).execute(new QuickMergeInteractionImpl(myProject));
}
}, "Select branch", mergeLabel);
}
@SuppressWarnings("MethodMayBeStatic")
private void setFocusableForLinks(final LinkLabel label) {
final Border border = new DottedBorder(new Insets(1,2,1,1), JBColor.BLACK);
label.setFocusable(true);
label.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
super.focusGained(e);
label.setBorder(border);
}
@Override
public void focusLost(FocusEvent e) {
super.focusLost(e);
label.setBorder(null);
}
});
label.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (KeyEvent.VK_ENTER == e.getKeyCode()) {
label.doClick();
}
}
});
}
private void changeFormat(@NotNull final WCInfo wcInfo, @NotNull final List<WorkingCopyFormat> supportedFormats) {
ChangeFormatDialog dialog = new ChangeFormatDialog(myProject, new File(wcInfo.getPath()), false, ! wcInfo.isIsWcRoot());
dialog.setSupported(supportedFormats);
dialog.setData(wcInfo.getFormat());
dialog.show();
if (! dialog.isOK()) {
return;
}
final WorkingCopyFormat newFormat = dialog.getUpgradeMode();
if (!wcInfo.getFormat().equals(newFormat)) {
ApplicationManager.getApplication().saveAll();
final Task.Backgroundable task = new SvnFormatWorker(myProject, newFormat, wcInfo) {
@Override
public void onSuccess() {
super.onSuccess();
myRefreshLabel.doClick();
}
};
ProgressManager.getInstance().run(task);
}
}
private void initView() {
myRefreshLabel.doClick();
}
public JComponent getComponent() {
return myHolder;
}
public static class OverrideEqualsWrapper<T> {
private final EqualityPolicy<T> myPolicy;
private final T myT;
public OverrideEqualsWrapper(EqualityPolicy<T> policy, T t) {
myPolicy = policy;
myT = t;
}
public T getT() {
return myT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final OverrideEqualsWrapper<T> that = (OverrideEqualsWrapper<T>) o;
return myPolicy.isEqual(myT, that.getT());
}
@Override
public int hashCode() {
return myPolicy.getHashCode(myT);
}
}
private static class InfoEqualityPolicy implements EqualityPolicy<WCInfo> {
private final static InfoEqualityPolicy ourInstance = new InfoEqualityPolicy();
public static InfoEqualityPolicy getInstance() {
return ourInstance;
}
private static class HashCodeBuilder {
private int myCode;
private HashCodeBuilder() {
myCode = 0;
}
public void append(final Object o) {
myCode = 31 * myCode + (o != null ? o.hashCode() : 0);
}
public int getCode() {
return myCode;
}
}
@Override
public int getHashCode(WCInfo value) {
final HashCodeBuilder builder = new HashCodeBuilder();
builder.append(value.getPath());
builder.append(value.getUrl());
builder.append(value.getFormat());
builder.append(value.getType());
builder.append(value.getStickyDepth());
return builder.getCode();
}
@Override
public boolean isEqual(WCInfo val1, WCInfo val2) {
if (val1 == val2) return true;
if (val1 == null || val2 == null || val1.getClass() != val2.getClass()) return false;
if (! Comparing.equal(val1.getFormat(), val2.getFormat())) return false;
if (! Comparing.equal(val1.getPath(), val2.getPath())) return false;
if (! Comparing.equal(val1.getStickyDepth(), val2.getStickyDepth())) return false;
if (! Comparing.equal(val1.getType(), val2.getType())) return false;
if (! Comparing.equal(val1.getUrl(), val2.getUrl())) return false;
return true;
}
}
private static class WCComparator implements Comparator<WCInfo> {
private final static WCComparator ourComparator = new WCComparator();
public static WCComparator getInstance() {
return ourComparator;
}
@Override
public int compare(WCInfo o1, WCInfo o2) {
return o1.getPath().compareTo(o2.getPath());
}
}
private static class MyLinkLabel extends LinkLabel {
private final int myHeight;
public MyLinkLabel(final int height, final String text, final LinkListener linkListener) {
super(text, null, linkListener);
myHeight = height;
}
@Override
public Dimension getPreferredSize() {
final Dimension preferredSize = super.getPreferredSize();
return new Dimension(preferredSize.width, myHeight);
}
}
}