org.jetbrains.idea.svn.dialogs.RepositoryBrowserDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.jetbrains.idea.svn.dialogs.RepositoryBrowserDialog.java

Source

/*
 * Copyright 2000-2013 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 java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.datatransfer.StringSelection;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreeNode;

import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnApplicationSettings;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.SvnProgressCanceller;
import org.jetbrains.idea.svn.SvnRevisionNumber;
import org.jetbrains.idea.svn.SvnUtil;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.actions.BrowseRepositoryAction;
import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider;
import org.jetbrains.idea.svn.dialogs.browser.CheckoutOptionsDialog;
import org.jetbrains.idea.svn.dialogs.browser.CopyOptionsDialog;
import org.jetbrains.idea.svn.dialogs.browser.DeleteOptionsDialog;
import org.jetbrains.idea.svn.dialogs.browser.DiffOptionsDialog;
import org.jetbrains.idea.svn.dialogs.browser.ExportOptionsDialog;
import org.jetbrains.idea.svn.dialogs.browser.ImportOptionsDialog;
import org.jetbrains.idea.svn.dialogs.browser.MkdirOptionsDialog;
import org.jetbrains.idea.svn.dialogs.browser.OpeningExpander;
import org.jetbrains.idea.svn.dialogs.browserCache.Expander;
import org.jetbrains.idea.svn.dialogs.browserCache.KeepingExpandedExpander;
import org.jetbrains.idea.svn.dialogs.browserCache.SyntheticWorker;
import org.jetbrains.idea.svn.history.SvnRepositoryLocation;
import org.jetbrains.idea.svn.status.SvnDiffEditor;
import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.ISVNReporter;
import org.tmatesoft.svn.core.io.ISVNReporterBaton;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNCommitClient;
import org.tmatesoft.svn.core.wc.SVNCopyClient;
import org.tmatesoft.svn.core.wc.SVNCopySource;
import org.tmatesoft.svn.core.wc.SVNRevision;
import com.intellij.CommonBundle;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.TreeExpander;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vcs.AbstractVcsHelper;
import com.intellij.openapi.vcs.CheckoutProvider;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsConfiguration;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.changes.ui.ChangeListViewerDialog;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.PopupHandler;
import com.intellij.util.IconUtil;
import com.intellij.util.NotNullFunction;
import com.intellij.util.WaitForProgressToShow;
import com.intellij.vcsUtil.VcsUtil;

public class RepositoryBrowserDialog extends DialogWrapper {

    private final Project myProject;
    protected final SvnVcs myVCS;
    private RepositoryBrowserComponent myRepositoryBrowser;

    @NonNls
    public static final String COPY_OF_PREFIX = "CopyOf";
    @NonNls
    public static final String NEW_FOLDER_POSTFIX = "NewFolder";

    private final DeleteAction myDeleteAction;
    private AnAction copyUrlAction;
    private AnAction mkDirAction;

    private final boolean myShowFiles;

    @NonNls
    private static final String PLACE_TOOLBAR = "RepositoryBrowser.Toolbar";
    @NonNls
    private static final String PLACE_MENU = "RepositoryBrowser.Menu";
    private final String myRepositoriesLabelText;
    protected JLabel myRepositoriesLabel;

    public RepositoryBrowserDialog(Project project) {
        this(project, true, null);
    }

    public RepositoryBrowserDialog(Project project, final boolean showFiles,
            @Nullable final String repositoriesLabelText) {
        super(project, true);
        myRepositoriesLabelText = repositoriesLabelText == null ? "Repositories:" : repositoriesLabelText;
        myShowFiles = showFiles;
        myProject = project;
        myVCS = SvnVcs.getInstance(project);
        setTitle("SVN Repository Browser");
        setResizable(true);
        setOKButtonText(CommonBundle.getCloseButtonText());
        getHelpAction().setEnabled(true);
        Disposer.register(project, getDisposable());
        myDeleteAction = new DeleteAction(getRepositoryBrowser());
        init();
    }

    protected String getHelpId() {
        return "reference.svn.repository";
    }

    @NotNull
    protected Action[] createActions() {
        return new Action[] { getOKAction(), getHelpAction() };
    }

    protected String getDimensionServiceKey() {
        return "svn.repositoryBrowser";
    }

    protected boolean showImportAction() {
        return true;
    }

    public JComponent createToolbar(final boolean horizontal, final AnAction... additionalActions) {
        DefaultActionGroup group = new DefaultActionGroup();
        final RepositoryBrowserComponent browser = getRepositoryBrowser();
        group.add(new AddLocationAction(browser));
        group.add(new EditLocationAction());
        group.add(new DiscardLocationAction(browser));
        group.add(new DetailsAction());
        group.addSeparator();
        final RefreshAction refreshAction = new RefreshAction(browser);
        refreshAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), browser);
        group.add(refreshAction);

        copyUrlAction = new CopyUrlAction();
        copyUrlAction
                .registerCustomShortcutSet(
                        new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.CTRL_MASK
                                | InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_MASK | InputEvent.ALT_DOWN_MASK)),
                        browser);
        mkDirAction = new MkDirAction(browser);
        mkDirAction.registerCustomShortcutSet(
                new CustomShortcutSet(
                        KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.ALT_MASK | InputEvent.ALT_DOWN_MASK)),
                browser);

        AnAction action = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() {
            public void expandAll() {
            }

            public boolean canExpand() {
                return false;
            }

            public void collapseAll() {
                JTree tree = browser.getRepositoryTree();
                int row = tree.getRowCount() - 1;
                while (row >= 0) {
                    tree.collapseRow(row);
                    row--;
                }
            }

            public boolean canCollapse() {
                return true;
            }
        }, browser);
        group.add(action);

        if ((additionalActions != null) || (!horizontal)) {
            group.addSeparator();
        }
        if (additionalActions != null) {
            for (AnAction anAction : additionalActions) {
                group.add(anAction);
            }
        }
        if (!horizontal) {
            group.add(new CloseToolWindowAction());
        }
        return ActionManager.getInstance().createActionToolbar(PLACE_TOOLBAR, group, horizontal).getComponent();
    }

    protected JPopupMenu createPopup(boolean toolWindow) {
        DefaultActionGroup group = new DefaultActionGroup();
        DefaultActionGroup newGroup = new DefaultActionGroup("_New", true);
        final RepositoryBrowserComponent browser = getRepositoryBrowser();
        newGroup.add(new AddLocationAction(browser));
        newGroup.add(new MkDirAction(browser));
        group.add(newGroup);
        group.addSeparator();
        if (toolWindow) {
            group.add(new OpenAction());
            group.add(new HistoryAction());
        }
        group.add(new CheckoutAction());
        group.add(new DiffAction());
        group.add(new BrowseChangesAction());
        group.addSeparator();
        group.add(new ImportAction());
        group.add(new ExportAction());
        group.addSeparator();
        group.add(new CopyOrMoveAction("Branch or Tag...", "copy.dialog.title", false));
        group.add(new CopyOrMoveAction("_Move or Rename...", "move.dialog.title", true));
        group.add(myDeleteAction);
        group.add(copyUrlAction);
        group.addSeparator();
        group.add(new RefreshAction(browser));
        group.add(new EditLocationAction());
        group.add(new DiscardLocationAction(browser));
        ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(PLACE_MENU, group);
        return menu.getComponent();
    }

    public JComponent createCenterPanel() {
        JPanel parentPanel = new JPanel(new BorderLayout());
        JPanel top = new JPanel();
        final BoxLayout layout = new BoxLayout(top, BoxLayout.X_AXIS);
        top.setLayout(layout);

        myRepositoriesLabel = new JLabel(myRepositoriesLabelText);
        top.add(myRepositoriesLabel);
        top.add(createToolbar(true, null));
        parentPanel.add(top, BorderLayout.NORTH);

        JComponent panel = createBrowserComponent(false);
        parentPanel.add(panel, BorderLayout.CENTER);

        return parentPanel;
    }

    public JComponent createBrowserComponent(final boolean toolWindow) {
        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());

        GridBagConstraints gc = new GridBagConstraints();
        gc.gridx = 0;
        gc.gridwidth = 1;
        gc.gridy = 0;
        gc.gridheight = 1;

        gc.gridx = 0;
        gc.gridwidth = 2;
        gc.gridy += 1;
        gc.gridheight = 1;
        gc.weightx = 1;
        gc.weighty = 1;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.WEST;

        panel.add(getRepositoryBrowser(), gc);

        gc.gridy += 1;
        gc.weighty = 0;
        gc.fill = GridBagConstraints.HORIZONTAL;

        panel.add(new JLabel(), gc);

        Collection<String> urls = SvnApplicationSettings.getInstance().getCheckoutURLs();
        ArrayList<SVNURL> svnURLs = new ArrayList<SVNURL>();
        for (final String url : urls) {
            try {
                svnURLs.add(SVNURL.parseURIEncoded(url));
            } catch (SVNException e) {
                //
            }
        }
        getRepositoryBrowser().setRepositoryURLs(svnURLs.toArray(new SVNURL[svnURLs.size()]), myShowFiles);
        getRepositoryBrowser().getRepositoryTree().addMouseListener(new PopupHandler() {
            @Override
            public void invokePopup(Component comp, int x, int y) {
                JTree tree = getRepositoryBrowser().getRepositoryTree();
                int row = tree.getRowForLocation(x, y);
                if (row >= 0) {
                    tree.setSelectionRow(row);
                }
                JPopupMenu popupMenu = createPopup(toolWindow);
                if (popupMenu != null) {
                    popupMenu.show(comp, x, y);
                }
            }
        });
        return panel;
    }

    protected RepositoryBrowserComponent getRepositoryBrowser() {
        if (myRepositoryBrowser == null) {
            myRepositoryBrowser = new RepositoryBrowserComponent(SvnVcs.getInstance(myProject));
            myRepositoryBrowser.setPreferredSize(new Dimension(300, 300));
        }
        return myRepositoryBrowser;
    }

    public void disposeRepositoryBrowser() {
        if (myRepositoryBrowser != null) {
            Disposer.dispose(myRepositoryBrowser);
            myRepositoryBrowser = null;
        }
    }

    protected void dispose() {
        super.dispose();
        disposeRepositoryBrowser();
    }

    public JComponent getPreferredFocusedComponent() {
        return (JComponent) getRepositoryBrowser().getPreferredFocusedComponent();
    }

    public boolean shouldCloseOnCross() {
        return true;
    }

    public boolean isOKActionEnabled() {
        return true;
    }

    public String getSelectedURL() {
        return getRepositoryBrowser().getSelectedURL();
    }

    @Nullable
    protected RepositoryTreeNode getSelectedNode() {
        return getRepositoryBrowser().getSelectedNode();
    }

    protected class HistoryAction extends AnAction {
        public void update(AnActionEvent e) {
            e.getPresentation().setText(SvnBundle.message("repository.browser.history.action"));
            e.getPresentation().setDescription(SvnBundle.message("repository.browser.history.action"));
            final RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            e.getPresentation().setEnabled(node != null && node.getURL() != null && !myProject.isDefault());
        }

        public void actionPerformed(AnActionEvent e) {
            final RepositoryTreeNode node = getSelectedNode();
            if (node == null) {
                return;
            }
            boolean isDirectory = node.getUserObject() instanceof SVNURL
                    || (node.getSVNDirEntry() != null && node.getSVNDirEntry().getKind() == SVNNodeKind.DIR);
            String url = node.getURL().toDecodedString();

            AbstractVcsHelper.getInstance(myProject).showFileHistory(myVCS.getVcsHistoryProvider(),
                    VcsUtil.getFilePathOnNonLocal(url, isDirectory), myVCS, url);
            node.reload(false);
        }
    }

    public static class RefreshAction extends AnAction {
        private final RepositoryBrowserComponent myBrowserComponent;

        public RefreshAction(final RepositoryBrowserComponent browserComponent) {
            super(SvnBundle.message("action.name.refresh"), SvnBundle.message("repository.browser.refresh.action"),
                    AllIcons.Actions.Refresh);
            myBrowserComponent = browserComponent;
        }

        public void update(AnActionEvent e) {
            e.getPresentation().setText(SvnBundle.message("action.name.refresh"));
            e.getPresentation().setDescription(SvnBundle.message("repository.browser.refresh.action"));
            e.getPresentation().setIcon(AllIcons.Actions.Refresh);
            e.getPresentation().setEnabled(myBrowserComponent.getSelectedNode() != null);
        }

        public void actionPerformed(AnActionEvent e) {
            final RepositoryTreeNode selectedNode = myBrowserComponent.getSelectedNode();
            if (selectedNode != null) {
                selectedNode.reload(true);
            }
        }
    }

    protected static class AddLocationAction extends AnAction {

        private final RepositoryBrowserComponent myBrowserComponent;

        public AddLocationAction(final RepositoryBrowserComponent browserComponent) {
            super(SvnBundle.message("repository.browser.add.location.menu.item"));
            myBrowserComponent = browserComponent;
        }

        public void update(AnActionEvent e) {
            if (e.getPlace().equals(PLACE_TOOLBAR)) {
                e.getPresentation().setDescription(SvnBundle.message("repository.browser.add.location.action"));
                e.getPresentation().setText(SvnBundle.message("repository.browser.add.location.action"));
                e.getPresentation().setIcon(IconUtil.getAddIcon());
            }
        }

        public void actionPerformed(AnActionEvent e) {
            final SvnApplicationSettings settings = SvnApplicationSettings.getInstance();
            final AddRepositoryLocationDialog dialog = new AddRepositoryLocationDialog(
                    myBrowserComponent.getProject(), settings.getTypedUrlsListCopy());
            dialog.show();
            if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
                final String url = dialog.getSelected();
                if (url != null && url.length() > 0) {
                    settings.addTypedUrl(url);
                    settings.addCheckoutURL(url);
                    myBrowserComponent.addURL(url);
                }
            }
        }
    }

    protected class EditLocationAction extends AnAction {
        public EditLocationAction() {
            super(SvnBundle.message("repository.browser.edit.location.menu.item"));
        }

        public void update(AnActionEvent e) {
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            if (e.getPlace().equals(PLACE_TOOLBAR)) {
                e.getPresentation().setDescription(SvnBundle.message("repository.browser.edit.location.menu.item"));
                e.getPresentation().setText(SvnBundle.message("repository.browser.edit.location.menu.item"));
                e.getPresentation().setIcon(AllIcons.Actions.EditSource);
            }
            e.getPresentation().setEnabled(node != null && node.getParent() instanceof RepositoryTreeRootNode);
        }

        public void actionPerformed(AnActionEvent e) {
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            if (node == null || (!(node.getParent() instanceof RepositoryTreeRootNode))) {
                return;
            }
            final String oldUrl = node.getURL().toString();
            final SvnApplicationSettings settings = SvnApplicationSettings.getInstance();
            final AddRepositoryLocationDialog dialog = new AddRepositoryLocationDialog(myProject,
                    settings.getTypedUrlsListCopy()) {
                @Override
                protected String initText() {
                    return oldUrl;
                }

                @Override
                public String getTitle() {
                    return SvnBundle.message("repository.browser.edit.location.dialog.title");
                }
            };
            dialog.show();
            if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
                final String url = dialog.getSelected();
                if (url != null && url.length() > 0) {
                    settings.addTypedUrl(url);
                    settings.removeCheckoutURL(oldUrl);
                    settings.addCheckoutURL(url);
                    final RepositoryBrowserComponent browser = getRepositoryBrowser();
                    browser.removeURL(oldUrl);
                    browser.addURL(url);
                }
            }
        }
    }

    protected static class DiscardLocationAction extends AnAction {
        private final RepositoryBrowserComponent myBrowserComponent;

        public DiscardLocationAction(final RepositoryBrowserComponent browserComponent) {
            super(SvnBundle.message("repository.browser.discard.location.action"),
                    SvnBundle.message("repository.browser.discard.location.action"), AllIcons.General.Remove);
            myBrowserComponent = browserComponent;
        }

        public void update(AnActionEvent e) {
            RepositoryTreeNode node = myBrowserComponent.getSelectedNode();
            e.getPresentation().setText(SvnBundle.message("repository.browser.discard.location.action"), true);
            e.getPresentation().setIcon(AllIcons.General.Remove);
            e.getPresentation().setEnabled(node != null && node.getParent() instanceof RepositoryTreeRootNode);
        }

        public void actionPerformed(AnActionEvent e) {
            RepositoryTreeNode node = myBrowserComponent.getSelectedNode();
            if (node == null) {
                return;
            }
            SVNURL url = node.getURL();
            if (url != null) {
                int rc = Messages.showYesNoDialog(myBrowserComponent.getProject(),
                        SvnBundle.message("repository.browser.discard.location.prompt", url.toString()),
                        SvnBundle.message("repository.browser.discard.location.title"), Messages.getQuestionIcon());
                if (rc != Messages.YES) {
                    return;
                }
                SvnApplicationSettings.getInstance().removeCheckoutURL(url.toString());
                myBrowserComponent.removeURL(url.toString());
            }
        }
    }

    public static class MkDirAction extends AnAction {
        private final RepositoryBrowserComponent myBrowserComponent;

        public MkDirAction(final RepositoryBrowserComponent browserComponent) {
            super(SvnBundle.message("repository.browser.new.folder.action"));
            myBrowserComponent = browserComponent;
        }

        public void update(AnActionEvent e) {
            RepositoryTreeNode node = myBrowserComponent.getSelectedNode();
            //e.getPresentation().setText(SvnBundle.message("repository.browser.new.folder.action"), true);
            if (node != null) {
                SVNDirEntry entry = node.getSVNDirEntry();
                e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR);
            } else {
                e.getPresentation().setEnabled(false);
            }
        }

        public void actionPerformed(AnActionEvent e) {
            // show dialog for comment and folder name, then create folder
            // then refresh selected node.
            final RepositoryTreeNode node = myBrowserComponent.getSelectedNode();
            if (node == null) {
                return;
            }
            final Project project = myBrowserComponent.getProject();
            MkdirOptionsDialog dialog = new MkdirOptionsDialog(project, node.getURL());
            dialog.show();
            VcsConfiguration.getInstance(project).saveCommitMessage(dialog.getCommitMessage());
            if (dialog.isOK()) {
                SVNURL url = dialog.getURL();
                String message = dialog.getCommitMessage();
                doMkdir(url, message, project);

                final SVNURL repositoryUrl = (node.getSVNDirEntry() == null) ? node.getURL()
                        : node.getSVNDirEntry().getRepositoryRoot();
                final SyntheticWorker worker = new SyntheticWorker(node.getURL());
                worker.addSyntheticChildToSelf(url, repositoryUrl, dialog.getName(), true);

                node.reload(false);
            }
        }
    }

    protected class DiffAction extends AnAction {
        public void update(AnActionEvent e) {
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            e.getPresentation().setText("Compare With...", true);
            if (node != null) {
                SVNDirEntry entry = node.getSVNDirEntry();
                e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR);
            } else {
                e.getPresentation().setEnabled(false);
            }
        }

        public void actionPerformed(AnActionEvent e) {
            // show dialog for comment and folder name, then create folder
            // then refresh selected node.
            SVNURL root;
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            if (node == null) {
                return;
            }
            while (node.getSVNDirEntry() != null) {
                node = (RepositoryTreeNode) node.getParent();
            }
            root = node.getURL();
            final RepositoryTreeNode selectedNode = getSelectedNode();
            if (selectedNode == null) {
                return;
            }
            SVNURL sourceURL = selectedNode.getURL();
            DiffOptionsDialog dialog = new DiffOptionsDialog(myProject, root, sourceURL);
            dialog.show();
            if (dialog.isOK()) {
                SVNURL targetURL = dialog.getTargetURL();
                if (dialog.isReverseDiff()) {
                    targetURL = sourceURL;
                    sourceURL = dialog.getTargetURL();
                }

                final SVNURL sURL = sourceURL;
                final SVNURL tURL = targetURL;

                Runnable command;
                boolean cancelable;
                if (dialog.isUnifiedDiff()) {
                    final File targetFile = dialog.getTargetFile();
                    command = new Runnable() {
                        public void run() {
                            targetFile.getParentFile().mkdirs();
                            doUnifiedDiff(targetFile, sURL, tURL);
                        }
                    };
                    cancelable = false;
                } else {
                    command = new Runnable() {
                        public void run() {
                            try {
                                doGraphicalDiff(sURL, tURL);
                            } catch (SVNCancelException ex) {
                                // ignore
                            } catch (final SVNException e1) {
                                WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() {
                                    public void run() {
                                        Messages.showErrorDialog(myProject, e1.getErrorMessage().getFullMessage(),
                                                "Error");
                                    }
                                }, null, myProject);
                            }
                        }
                    };
                    cancelable = true;
                }
                ProgressManager.getInstance().runProcessWithProgressSynchronously(command,
                        SvnBundle.message("progress.computing.difference"), cancelable, myProject);
            }
        }
    }

    protected class CopyOrMoveAction extends AnAction {
        private final String myActionName;
        private final String myDialogTitleKey;
        private final boolean myMove;

        public CopyOrMoveAction(final String actionName, final String dialogTitleKey, final boolean move) {
            myActionName = actionName;
            myDialogTitleKey = dialogTitleKey;
            myMove = move;
        }

        public void update(AnActionEvent e) {
            e.getPresentation().setText(myActionName);
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null);
        }

        public void actionPerformed(final AnActionEvent e) {
            final RepositoryTreeNode node = getSelectedNode();
            if (node == null) {
                return;
            }
            RepositoryTreeNode rootNode = node;
            while (!rootNode.isRepositoryRoot()) {
                rootNode = (RepositoryTreeNode) rootNode.getParent();
            }

            CopyOptionsDialog dialog = new CopyOptionsDialog(SvnBundle.message(myDialogTitleKey), myProject,
                    rootNode, node, !myMove);
            dialog.show();
            VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage());
            if (dialog.isOK()) {
                SVNURL dst = dialog.getTargetURL();
                SVNURL src = dialog.getSourceURL();
                final String path = src.getPath();
                final int folder = path.replace('\\', '/').lastIndexOf('/');
                if (folder != -1) {
                    final String lastFolder = path.substring(folder + 1, path.length());
                    if (myMove && "trunk".equalsIgnoreCase(lastFolder)) {
                        final int result = Messages.showOkCancelDialog(myProject,
                                "You are about to move folder named '" + lastFolder + "'. Are you sure?",
                                SvnBundle.message(myDialogTitleKey), Messages.getWarningIcon());
                        if (Messages.OK == result)
                            return;
                    }
                }
                String message = dialog.getCommitMessage();
                doCopy(src, dst, myMove, message);

                final CopyMoveReloadHelper sourceReloader = myMove ? new MoveSourceReloader(node)
                        : CopyMoveReloadHelper.EMPTY;
                final TargetReloader destinationReloader = new TargetReloader(dialog, node, rootNode,
                        myRepositoryBrowser);

                sourceReloader.doSynthetic();
                destinationReloader.doSynthetic();
                if ((!myMove) || (!Comparing.equal(sourceReloader.parent(), destinationReloader.parent()))) {
                    destinationReloader.doRefresh();
                }
                sourceReloader.doRefresh();
            }
        }
    }

    private static class TargetReloader implements CopyMoveReloadHelper {
        private final RepositoryTreeNode myDialogParent;
        private final SVNURL myDst;
        private final RepositoryTreeNode mySourceNode;
        private final RepositoryTreeNode myRoot;
        private final RepositoryBrowserComponent myBrowserComponent;
        private final String myNewName;

        private TargetReloader(final CopyOptionsDialog dialog, final RepositoryTreeNode node,
                final RepositoryTreeNode root, final RepositoryBrowserComponent browserComponent) {
            myDialogParent = dialog.getTargetParentNode();
            myDst = dialog.getTargetURL();
            mySourceNode = node;
            myRoot = root;
            myBrowserComponent = browserComponent;
            myNewName = dialog.getName();
        }

        public void doRefresh() {
            final TreeNode[] oldPath = myDialogParent.getSelfPath();
            final TreeNode[] correctedPath = new TreeNode[oldPath.length + 1];
            System.arraycopy(oldPath, 0, correctedPath, 1, oldPath.length);

            myRoot.reload(new OpeningExpander(oldPath, myBrowserComponent, myDialogParent), false);
        }

        public void doSynthetic() {
            final SyntheticWorker parentWorker = new SyntheticWorker(myDialogParent.getURL());
            parentWorker.addSyntheticChildToSelf(myDst, myRoot.getURL(), myNewName, !mySourceNode.isLeaf());
            parentWorker.copyTreeToSelf(mySourceNode);
        }

        public SVNURL parent() {
            return myDialogParent.getURL();
        }
    }

    private static class MoveSourceReloader implements CopyMoveReloadHelper {
        private final RepositoryTreeNode mySource;
        private final RepositoryTreeNode myParent;

        private MoveSourceReloader(final RepositoryTreeNode source) {
            mySource = source;
            myParent = (RepositoryTreeNode) source.getParent();
        }

        public void doRefresh() {
            myParent.reload(false);
        }

        public void doSynthetic() {
            final SyntheticWorker worker = new SyntheticWorker(mySource.getURL());
            worker.removeSelf();
        }

        public SVNURL parent() {
            return myParent.getURL();
        }
    }

    private interface CopyMoveReloadHelper {
        void doRefresh();

        void doSynthetic();

        @Nullable
        SVNURL parent();

        CopyMoveReloadHelper EMPTY = new CopyMoveReloadHelper() {
            public void doRefresh() {
            }

            public void doSynthetic() {
            }

            @Nullable
            public SVNURL parent() {
                return null;
            }
        };
    }

    protected class CopyUrlAction extends AnAction {
        public void update(AnActionEvent e) {
            e.getPresentation().setText("Copy URL...");
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            e.getPresentation().setEnabled(node != null);
        }

        public void actionPerformed(final AnActionEvent e) {
            final RepositoryTreeNode treeNode = getRepositoryBrowser().getSelectedNode();
            if (treeNode != null) {
                final String url = treeNode.getURL().toString();
                CopyPasteManager.getInstance().setContents(new StringSelection(url));
            }
        }
    }

    public static class DeleteAction extends AnAction {
        private final RepositoryBrowserComponent myBrowserComponent;

        public DeleteAction(final RepositoryBrowserComponent browserComponent) {
            super("_Delete...");
            myBrowserComponent = browserComponent;
            registerCustomShortcutSet(CommonShortcuts.getDelete(), myBrowserComponent);
        }

        public void update(AnActionEvent e) {
            RepositoryTreeNode node = myBrowserComponent.getSelectedNode();
            e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null);
        }

        public void actionPerformed(AnActionEvent e) {
            DeleteOptionsDialog dialog = new DeleteOptionsDialog(myBrowserComponent.getProject());
            RepositoryTreeNode node = myBrowserComponent.getSelectedNode();
            dialog.show();
            VcsConfiguration.getInstance(myBrowserComponent.getProject())
                    .saveCommitMessage(dialog.getCommitMessage());
            if (dialog.isOK()) {
                SVNURL url = node.getURL();
                String message = dialog.getCommitMessage();
                final boolean successful = doDelete(url, message);

                if (successful) {
                    final SyntheticWorker worker = new SyntheticWorker(url);
                    worker.removeSelf();
                    final RepositoryTreeNode parentNode = (RepositoryTreeNode) node.getParent();
                    parentNode.reload(new KeepingExpandedExpander(myBrowserComponent,
                            new AfterDeletionSelectionInstaller(node, myBrowserComponent)), false);
                }
            }
        }

        private boolean doDelete(final SVNURL url, final String comment) {
            final SVNException[] exception = new SVNException[1];
            final Project project = myBrowserComponent.getProject();
            Runnable command = new Runnable() {
                public void run() {
                    ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
                    if (progress != null) {
                        progress.setText(SvnBundle.message("progres.text.deleting", url.toString()));
                    }
                    SvnVcs vcs = SvnVcs.getInstance(project);
                    try {
                        SVNCommitClient committer = vcs.createCommitClient();
                        committer.doDelete(new SVNURL[] { url }, comment);
                    } catch (SVNException e) {
                        exception[0] = e;
                    }
                }
            };
            ProgressManager.getInstance().runProcessWithProgressSynchronously(command,
                    SvnBundle.message("progress.title.browser.delete"), false, project);
            if (exception[0] != null) {
                Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error"));
            }
            return exception[0] == null;
        }
    }

    private static class AfterDeletionSelectionInstaller implements Expander {
        private final RepositoryTreeNode myParentNode;
        private final String myDeletedNodeName;
        private final boolean myIsFolder;
        private final RepositoryBrowserComponent myBrowserComponent;

        private AfterDeletionSelectionInstaller(final RepositoryTreeNode deletedNode,
                final RepositoryBrowserComponent browserComponent) {
            myBrowserComponent = browserComponent;
            myParentNode = (RepositoryTreeNode) deletedNode.getParent();
            myDeletedNodeName = deletedNode.toString();
            myIsFolder = !deletedNode.isLeaf();
        }

        public void onBeforeRefresh(final RepositoryTreeNode node) {
        }

        public void onAfterRefresh(final RepositoryTreeNode node) {
            TreeNode nodeToSelect = myParentNode.getNextChildByKey(myDeletedNodeName, myIsFolder);
            nodeToSelect = (nodeToSelect == null) ? myParentNode : nodeToSelect;
            myBrowserComponent.setSelectedNode(nodeToSelect);
        }
    }

    protected class ImportAction extends AnAction {
        public void update(AnActionEvent e) {
            e.getPresentation().setVisible(showImportAction());
            e.getPresentation().setText(SvnBundle.message("repository.browser.import.action"));
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            final boolean running = ProjectLevelVcsManager.getInstance(myProject).isBackgroundVcsOperationRunning();
            if (node != null) {
                SVNDirEntry entry = node.getSVNDirEntry();
                e.getPresentation().setEnabled((entry == null || entry.getKind() == SVNNodeKind.DIR) && (!running));
            } else {
                e.getPresentation().setEnabled(false);
            }
        }

        public void actionPerformed(AnActionEvent e) {
            // get directory, then import.
            doImport();
        }
    }

    protected class ExportAction extends AnAction {
        public void update(AnActionEvent e) {
            e.getPresentation().setText("_Export...");
            e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null);
        }

        public void actionPerformed(AnActionEvent e) {
            final RepositoryTreeNode selectedNode = getSelectedNode();
            if (selectedNode == null) {
                return;
            }
            SVNURL url = selectedNode.getURL();
            final File dir = selectFile("Destination directory", "Select export destination directory");
            if (dir == null) {
                return;
            }
            Project p = e.getData(CommonDataKeys.PROJECT);
            ExportOptionsDialog dialog = new ExportOptionsDialog(p, url, dir);
            dialog.show();
            if (dialog.isOK()) {
                SvnCheckoutProvider.doExport(myProject, dir, url, dialog.getDepth(), dialog.isIgnoreExternals(),
                        dialog.isForce(), dialog.getEOLStyle());
            }
        }
    }

    protected class CheckoutAction extends AnAction {
        public void update(AnActionEvent e) {
            e.getPresentation().setText("_Checkout...", true);
            RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode();
            if (node != null) {
                SVNDirEntry entry = node.getSVNDirEntry();
                e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR);
            } else {
                e.getPresentation().setEnabled(false);
            }
        }

        public void actionPerformed(AnActionEvent e) {
            final RepositoryTreeNode selectedNode = getSelectedNode();
            if (!ModalityState.NON_MODAL.equals(ModalityState.current())) {
                doCancelAction();
            }
            doCheckout(ProjectLevelVcsManager.getInstance(myProject).getCompositeCheckoutListener(), selectedNode);
        }
    }

    protected class BrowseChangesAction extends AnAction {
        public BrowseChangesAction() {
            super(SvnBundle.message("repository.browser.browse.changes.action"),
                    SvnBundle.message("repository.browser.browse.changes.description"), null);
        }

        public void actionPerformed(AnActionEvent e) {
            RepositoryTreeNode node = getSelectedNode();
            if (node == null) {
                return;
            }
            SVNURL url = node.getURL();
            AbstractVcsHelper.getInstance(myProject).showChangesBrowser(myVCS.getCommittedChangesProvider(),
                    new SvnRepositoryLocation(url.toString()), "Changes in " + url.toString(), null);
        }

        public void update(final AnActionEvent e) {
            e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null);
        }
    }

    protected class OpenAction extends AnAction {
        public void update(AnActionEvent e) {
            e.getPresentation().setEnabled(false);
            if (myVCS == null) {
                return;
            }
            e.getPresentation().setText("_Open", true);
            e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedVcsFile() != null);
        }

        public void actionPerformed(AnActionEvent e) {
            VirtualFile vcsVF = getRepositoryBrowser().getSelectedVcsFile();
            if (vcsVF != null) {
                FileEditorManager.getInstance(myVCS.getProject()).openFile(vcsVF, true);
            }
        }
    }

    protected class DetailsAction extends ToggleAction {

        private boolean myIsSelected;

        public void update(final AnActionEvent e) {
            e.getPresentation().setDescription(SvnBundle.message("repository.browser.details.action"));
            e.getPresentation().setText(SvnBundle.message("repository.browser.details.action"));
            e.getPresentation().setIcon(AllIcons.Actions.Annotate);
            super.update(e);
        }

        public boolean isSelected(AnActionEvent e) {
            return myIsSelected;
        }

        public void setSelected(AnActionEvent e, boolean state) {
            myIsSelected = state;
            SvnRepositoryTreeCellRenderer r = new SvnRepositoryTreeCellRenderer();
            r.setShowDetails(state);
            getRepositoryBrowser().getRepositoryTree().setCellRenderer(r);
        }
    }

    @Nullable
    private File selectFile(String title, String description) {
        FileChooserDescriptor fcd = FileChooserDescriptorFactory.createSingleFolderDescriptor();
        fcd.setShowFileSystemRoots(true);
        fcd.setTitle(title);
        fcd.setDescription(description);
        fcd.setHideIgnored(false);
        VirtualFile file = FileChooser.chooseFile(fcd, myProject, null);
        if (file == null) {
            return null;
        }
        final String path = file.getPath();
        if (path.endsWith(":")) { // workaround for VFS oddities with drive root (IDEADEV-20870)
            return new File(path + "/");
        }
        return new File(path);
    }

    protected static void doMkdir(final SVNURL url, final String comment, final Project project) {
        final SVNException[] exception = new SVNException[1];
        Runnable command = new Runnable() {
            public void run() {
                ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
                if (progress != null) {
                    progress.setText(SvnBundle.message("progress.text.browser.creating", url.toString()));
                }
                SvnVcs vcs = SvnVcs.getInstance(project);
                try {
                    SVNCommitClient committer = vcs.createCommitClient();
                    committer.doMkDir(new SVNURL[] { url }, comment);
                } catch (SVNException e) {
                    exception[0] = e;
                }
            }
        };
        ProgressManager.getInstance().runProcessWithProgressSynchronously(command,
                SvnBundle.message("progress.text.create.remote.folder"), false, project);
        if (exception[0] != null) {
            Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error"));
        }
    }

    private void doCopy(final SVNURL src, final SVNURL dst, final boolean move, final String comment) {
        final SVNException[] exception = new SVNException[1];
        Runnable command = new Runnable() {
            public void run() {
                ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
                if (progress != null) {
                    progress.setText((move ? SvnBundle.message("progress.text.browser.moving", src)
                            : SvnBundle.message("progress.text.browser.copying", src)));
                    progress.setText2(SvnBundle.message("progress.text.browser.remote.destination", dst));
                }
                SvnVcs vcs = SvnVcs.getInstance(myProject);
                try {
                    SVNCopyClient committer = vcs.createCopyClient();
                    final SVNCopySource[] copySource = new SVNCopySource[] {
                            new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, src) };
                    committer.doCopy(copySource, dst, move, true, true, comment, null);
                } catch (SVNException e) {
                    exception[0] = e;
                }
            }
        };
        String progressTitle = move ? SvnBundle.message("progress.title.browser.move")
                : SvnBundle.message("progress.title.browser.copy");
        ProgressManager.getInstance().runProcessWithProgressSynchronously(command, progressTitle, false, myProject);
        if (exception[0] != null) {
            Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error"));
        }
    }

    protected void doCheckout(@Nullable final CheckoutProvider.Listener listener,
            final RepositoryTreeNode selectedNode) {
        if (selectedNode == null) {
            return;
        }
        SVNURL url = selectedNode.getURL();

        String relativePath = "";
        final SVNDirEntry dirEntry = selectedNode.getSVNDirEntry();
        if (dirEntry != null) {
            if (dirEntry.getRepositoryRoot() != null) {
                if (!dirEntry.getRepositoryRoot().equals(url)) {
                    relativePath = SVNPathUtil.getRelativePath(dirEntry.getRepositoryRoot().toString(),
                            url.toDecodedString());
                }
            } else {
                relativePath = dirEntry.getRelativePath();
            }
        } else {
            relativePath = url.getPath();
        }

        File dir = selectFile(SvnBundle.message("svn.checkout.destination.directory.title"),
                SvnBundle.message("svn.checkout.destination.directory.description"));
        if (dir == null) {
            return;
        }

        Project p = myProject;
        CheckoutOptionsDialog dialog = new CheckoutOptionsDialog(p, url, dir,
                SvnUtil.getVirtualFile(dir.getAbsolutePath()), relativePath);
        dialog.show();
        dir = dialog.getTarget();
        if (dialog.isOK() && dir != null) {
            final SVNRevision revision;
            try {
                revision = dialog.getRevision();
            } catch (ConfigurationException e) {
                Messages.showErrorDialog(SvnBundle.message("message.text.cannot.checkout", e.getMessage()),
                        SvnBundle.message("message.title.check.out"));
                return;
            }

            SvnCheckoutProvider.doCheckout(myProject, dir, url.toString(), revision, dialog.getDepth(),
                    dialog.isIgnoreExternals(), listener);
        }
    }

    /**
     * @return true only if import was called
     */
    protected boolean doImport() {
        File dir = selectFile("Import Directory", "Select directory to import into repository");
        if (dir == null) {
            return false;
        }

        final RepositoryTreeNode selectedNode = getSelectedNode();
        if (selectedNode == null) {
            return false;
        }
        SVNURL url = selectedNode.getURL();
        ImportOptionsDialog dialog = new ImportOptionsDialog(myProject, url, dir);
        dialog.show();
        VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage());
        if (dialog.isOK()) {
            File src = dialog.getTarget();
            boolean ignored = dialog.isIncludeIgnored();
            String message = dialog.getCommitMessage();
            SvnCheckoutProvider.doImport(myProject, src, url, dialog.getDepth(), ignored, message);
            selectedNode.reload(false);
        }
        return dialog.isOK();
    }

    private void doUnifiedDiff(File targetFile, SVNURL sourceURL, SVNURL targetURL) {
        OutputStream os = null;
        try {
            os = new BufferedOutputStream(new FileOutputStream(targetFile));
            myVCS.createDiffClient().doDiff(sourceURL, SVNRevision.HEAD, targetURL, SVNRevision.HEAD, true, false,
                    os);
        } catch (IOException e1) {
            //
        } catch (SVNException e1) {
            //
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e1) {
                    //
                }
            }
        }
    }

    private void doGraphicalDiff(SVNURL sourceURL, SVNURL targetURL) throws SVNException {
        SVNRepository sourceRepository = myVCS.createRepository(sourceURL.toString());
        sourceRepository.setCanceller(new SvnProgressCanceller());
        SvnDiffEditor diffEditor;
        final long rev;
        SVNRepository targetRepository = null;
        try {
            rev = sourceRepository.getLatestRevision();
            // generate Map of path->Change
            targetRepository = myVCS.createRepository(targetURL.toString());
            diffEditor = new SvnDiffEditor(sourceRepository, targetRepository, -1, false);
            final ISVNEditor cancellableEditor = SVNCancellableEditor.newInstance(diffEditor,
                    new SvnProgressCanceller(), null);
            sourceRepository.diff(targetURL, rev, rev, null, true, true, false, new ISVNReporterBaton() {
                public void report(ISVNReporter reporter) throws SVNException {
                    reporter.setPath("", null, rev, false);
                    reporter.finishReport();
                }
            }, cancellableEditor);
        } finally {
            sourceRepository.closeSession();
            if (targetRepository != null) {
                targetRepository.closeSession();
            }
        }
        final String sourceTitle = SVNPathUtil.tail(sourceURL.toString());
        final String targetTitle = SVNPathUtil.tail(targetURL.toString());
        showDiffEditorResults(diffEditor.getChangesMap(), sourceTitle, targetTitle, sourceURL, targetURL, rev);
    }

    private void showDiffEditorResults(final Map<String, Change> changes, String sourceTitle, String targetTitle,
            final SVNURL sourceUrl, final SVNURL targetUrl, final long revision) {
        if (changes.isEmpty()) {
            // display no changes dialog.
            final String text = SvnBundle.message("repository.browser.compare.no.difference.message", sourceTitle,
                    targetTitle);
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    Messages.showInfoMessage(myProject, text,
                            SvnBundle.message("repository.browser.compare.no.difference.title"));
                }
            });
            return;
        }
        final Collection<Change> changesList = changes.values();
        /*final Collection<Change> changesListConverted = new ArrayList<Change>(changesList.size());
        for (Change change : changesList) {
          final FilePath path = ChangesUtil.getFilePath(change);
          final Change newChange = new Change(
              new UrlContentRevision(change.getBeforeRevision(),
                             FilePathImpl.createNonLocal(SVNPathUtil.append(sourceUrl.toString(), path.getName()), path.isDirectory()), revision),
              new UrlContentRevision(change.getAfterRevision(),
                             FilePathImpl.createNonLocal(SVNPathUtil.append(targetUrl.toString(), path.getName()), path.isDirectory()), revision));
          changesListConverted.add(newChange);
        }*/

        final String title = SvnBundle.message("repository.browser.compare.title", sourceTitle, targetTitle);
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                final ChangeListViewerDialog dlg = new ChangeListViewerDialog(myRepositoryBrowser, myProject,
                        changesList, true);
                dlg.setTitle(title);
                dlg.setConvertor(new NotNullFunction<Change, Change>() {
                    @NotNull
                    public Change fun(final Change change) {
                        final FilePath path = ChangesUtil.getFilePath(change);

                        return new Change(
                                new UrlContentRevision(change.getBeforeRevision(),
                                        FilePathImpl.createNonLocal(
                                                SVNPathUtil.append(sourceUrl.toString(), path.getPath()),
                                                path.isDirectory()),
                                        revision),
                                new UrlContentRevision(change.getAfterRevision(),
                                        FilePathImpl.createNonLocal(
                                                SVNPathUtil.append(targetUrl.toString(), path.getPath()),
                                                path.isDirectory()),
                                        revision));
                    }
                });
                dlg.show();
            }
        });
    }

    private class CloseToolWindowAction extends AnAction {
        public void actionPerformed(AnActionEvent e) {
            disposeRepositoryBrowser();
            Project p = e.getData(CommonDataKeys.PROJECT);
            ToolWindowManager.getInstance(p)
                    .unregisterToolWindow(BrowseRepositoryAction.REPOSITORY_BROWSER_TOOLWINDOW);

        }

        public void update(AnActionEvent e) {
            e.getPresentation().setText("Close");
            e.getPresentation().setDescription("Close this tool window");
            e.getPresentation().setIcon(AllIcons.Actions.Cancel);
        }
    }

    public void setDefaultExpander(final NotNullFunction<RepositoryBrowserComponent, Expander> expanderFactory) {
        myRepositoryBrowser.setLazyLoadingExpander(expanderFactory);
    }

    private static class UrlContentRevision implements ContentRevision {
        private final ContentRevision myContentRevision;
        private final FilePath myPath;
        private final SvnRevisionNumber myNumber;

        private UrlContentRevision(final ContentRevision contentRevision, final FilePath path,
                final long revision) {
            myContentRevision = contentRevision;
            myPath = path;
            myNumber = new SvnRevisionNumber(SVNRevision.create(revision));
        }

        public String getContent() throws VcsException {
            return (myContentRevision == null) ? "" : myContentRevision.getContent();
        }

        @NotNull
        public FilePath getFile() {
            return myPath;
        }

        @NotNull
        public VcsRevisionNumber getRevisionNumber() {
            return myNumber;
        }
    }
}