From 78d10bf9810b629cdb4d972838eba28b45a7bea4 Mon Sep 17 00:00:00 2001 From: Irina Chernushina Date: Mon, 24 Mar 2008 15:59:34 +0300 Subject: [PATCH] IDEADEV-14950 fixed (Subversion: BrowseSVNRepository: preserve the tree state (expanded/collapsed nodes) on rename/move operations) --- .../svn/dialogs/RepositoryBrowserComponent.java | 9 +++ .../idea/svn/dialogs/RepositoryBrowserDialog.java | 65 +++++++++------------- .../idea/svn/dialogs/RepositoryTreeModel.java | 8 +++ .../idea/svn/dialogs/RepositoryTreeNode.java | 64 ++++++++++++++++----- .../svn/dialogs/browser/CopyOptionsDialog.java | 24 +++++++- .../idea/svn/dialogs/browser/Expander.java | 53 ++++++++++++++++++ 6 files changed, 167 insertions(+), 56 deletions(-) create mode 100644 plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/Expander.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java index ff7b1394d6..c17cd1c928 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java @@ -81,6 +81,15 @@ public class RepositoryBrowserComponent extends JPanel implements Disposable, Da myRepositoryTree.setSelectionRow(0); } + public void expandNode(@NotNull final TreeNode treeNode) { + final TreeNode[] pathToNode = ((RepositoryTreeModel) myRepositoryTree.getModel()).getPathToRoot(treeNode); + + if ((pathToNode != null) && (pathToNode.length > 0)) { + final TreePath treePath = new TreePath(pathToNode); + myRepositoryTree.expandPath(treePath); + } + } + public void addURL(String url) { try { ((RepositoryTreeModel) myRepositoryTree.getModel()).addRoot(SVNURL.parseURIEncoded(url)); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java index 77efee1646..f28a2faf02 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java @@ -182,8 +182,8 @@ public class RepositoryBrowserDialog extends DialogWrapper { group.add(new ImportAction()); group.add(new ExportAction()); group.addSeparator(); - group.add(new CopyAction()); - group.add(new MoveAction()); + 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(); @@ -507,55 +507,42 @@ public class RepositoryBrowserDialog extends DialogWrapper { } } - protected class CopyAction extends AnAction { - public void update(AnActionEvent e) { - e.getPresentation().setText("Branch or Tag..."); - RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); - e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null); - } - public void actionPerformed(AnActionEvent e) { - SVNURL root; - RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); - while (node.getSVNDirEntry() != null) { - node = (RepositoryTreeNode) node.getParent(); - } - root = node.getURL(); - CopyOptionsDialog dialog = new CopyOptionsDialog(SvnBundle.message("copy.dialog.title"), - myProject, root, getNotNullSelectedNode().getURL()); - dialog.show(); - if (dialog.isOK()) { - SVNURL dst = dialog.getTargetURL(); - SVNURL src = dialog.getSourceURL(); - String message = dialog.getCommitMessage(); - doCopy(src, dst, false, message); - node.reload(); - } + 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; } - } - protected class MoveAction extends AnAction { public void update(AnActionEvent e) { - e.getPresentation().setText("_Move or Rename..."); + e.getPresentation().setText(myActionName); RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null); } - public void actionPerformed(AnActionEvent e) { - SVNURL root; - RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); - while (node.getSVNDirEntry() != null) { - node = (RepositoryTreeNode) node.getParent(); + public void actionPerformed(final AnActionEvent e) { + final RepositoryTreeNode node = getNotNullSelectedNode(); + RepositoryTreeNode rootNode = node; + while (! rootNode.isRepositoryRoot()) { + rootNode = (RepositoryTreeNode) rootNode.getParent(); } - root = node.getURL(); - CopyOptionsDialog dialog = new CopyOptionsDialog(SvnBundle.message("move.dialog.title"), - myProject, root, getRepositoryBrowser().getSelectedNode().getURL()); + + CopyOptionsDialog dialog = new CopyOptionsDialog(SvnBundle.message(myDialogTitleKey), myProject, rootNode, node); dialog.show(); if (dialog.isOK()) { SVNURL dst = dialog.getTargetURL(); SVNURL src = dialog.getSourceURL(); String message = dialog.getCommitMessage(); - doCopy(src, dst, true, message); + doCopy(src, dst, myMove, message); node.reload(); + final TreeNode[] path = dialog.getTargetPath(); + if (path != null) { + rootNode.reload(new Expander(path, myRepositoryBrowser)); + } } } } @@ -599,7 +586,7 @@ public class RepositoryBrowserDialog extends DialogWrapper { } } - private class AfterDeletionSelectionInstaller implements Runnable { + private class AfterDeletionSelectionInstaller implements ReloadListener { private final RepositoryTreeNode myParentNode; private final String myDeletedNodeName; private final boolean myIsFolder; @@ -610,7 +597,7 @@ public class RepositoryBrowserDialog extends DialogWrapper { myIsFolder = ! deletedNode.isLeaf(); } - public void run() { + public void onAfterReload(final RepositoryTreeNode node) { TreeNode nodeToSelect = myParentNode.getNextChildByKey(myDeletedNodeName, myIsFolder); nodeToSelect = (nodeToSelect == null) ? myParentNode : nodeToSelect; getRepositoryBrowser().setSelectedNode(nodeToSelect); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java index 94b3960834..dd6c7a104c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java @@ -9,6 +9,7 @@ import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.io.SVNRepository; import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; public class RepositoryTreeModel extends DefaultTreeModel implements Disposable { @@ -56,6 +57,13 @@ public class RepositoryTreeModel extends DefaultTreeModel implements Disposable return false; } + public TreeNode[] getPathToSubRoot(final TreeNode node) { + final TreeNode[] path = getPathToRoot(node); + final TreeNode[] result = new TreeNode[path.length - 1]; + System.arraycopy(path, 1, result, 0, path.length - 1); + return result; + } + public void addRoot(SVNURL url) { if (!hasRoot(url)) { ((RepositoryTreeRootNode) getRoot()).addRoot(url); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java index 51a3703e60..b5c080a32c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java @@ -23,7 +23,11 @@ public class RepositoryTreeNode implements TreeNode, Disposable { private final SVNURL myURL; private final Object myUserObject; - private Runnable myAfterChildrenLoad; + private ReloadListener myAfterChildrenLoad; + /** + * true is children was not loaded jet = are made null by reload OR contain only fake element + */ + private boolean myChildrenInvalidated; public RepositoryTreeNode(RepositoryTreeModel model, TreeNode parentNode, @NotNull SVNRepository repository, @NotNull SVNURL url, Object userObject) { @@ -38,6 +42,7 @@ public class RepositoryTreeNode implements TreeNode, Disposable { } myModel = model; myUserObject = userObject; + myChildrenInvalidated = true; } public Object getUserObject() { @@ -76,9 +81,12 @@ public class RepositoryTreeNode implements TreeNode, Disposable { reload(null); } - public void reload(@Nullable final Runnable doAfterChildrenLoad) { + public void reload(@Nullable final ReloadListener doAfterChildrenLoad) { + ApplicationManager.getApplication().assertIsDispatchThread(); + // couldn't do that when 'loading' is in progress. myChildren = null; + myChildrenInvalidated = true; if (doAfterChildrenLoad != null) { myAfterChildrenLoad = doAfterChildrenLoad; @@ -86,7 +94,22 @@ public class RepositoryTreeNode implements TreeNode, Disposable { myModel.reload(this); if (isLeaf()) { - ((RepositoryTreeNode) getParent()).reload(); + ((RepositoryTreeNode) getParent()).reload(doAfterChildrenLoad); + } + } + + public void registerReloadListener(@Nullable final ReloadListener doAfterChildrenLoad) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + if (doAfterChildrenLoad != null) { + if (myChildrenInvalidated) { + myAfterChildrenLoad = doAfterChildrenLoad; + + getChildren(); + } else { + // for next level children: call directly + doAfterChildrenLoad.onAfterReload(this); + } } } @@ -105,7 +128,9 @@ public class RepositoryTreeNode implements TreeNode, Disposable { } private List getChildren() { - if (myChildren == null) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + if ((myChildren == null) && myChildrenInvalidated) { myChildren = new ArrayList(); myChildren.add(new DefaultMutableTreeNode("Loading")); loadChildren(); @@ -143,16 +168,19 @@ public class RepositoryTreeNode implements TreeNode, Disposable { private void invokeLaterTreeFilling(@NotNull final List nodesToFillWith) { SwingUtilities.invokeLater(new Runnable() { public void run() { - // could be null if refresh was called during 'loading'. - if (myChildren != null) { - myChildren.clear(); - myChildren.addAll(nodesToFillWith); - } - myModel.reload(RepositoryTreeNode.this); - - if (myAfterChildrenLoad != null) { - myAfterChildrenLoad.run(); - myAfterChildrenLoad = null; + if (myChildrenInvalidated) { + // could be null if refresh was called during 'loading'. + if (myChildren != null) { + myChildren.clear(); + myChildren.addAll(nodesToFillWith); + } + myModel.reload(RepositoryTreeNode.this); + + if (myAfterChildrenLoad != null) { + myAfterChildrenLoad.onAfterReload(RepositoryTreeNode.this); + myAfterChildrenLoad = null; + } + myChildrenInvalidated = false; } } }); @@ -176,4 +204,12 @@ public class RepositoryTreeNode implements TreeNode, Disposable { public void dispose() { myRepository.closeSession(); } + + public TreeNode[] getSelfPath() { + return myModel.getPathToRoot(this); + } + + public boolean isRepositoryRoot() { + return ! (myUserObject instanceof SVNDirEntry); + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java index 7c22b9daff..9c9e55a196 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn.dialogs.RepositoryTreeModel; import org.jetbrains.idea.svn.dialogs.RepositoryTreeNode; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; @@ -18,6 +19,7 @@ import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreeNode; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; @@ -35,13 +37,13 @@ public class CopyOptionsDialog extends DialogWrapper { private JComboBox myMessagesBox; private JPanel myMainPanel; - public CopyOptionsDialog(String title, Project project, SVNURL rootURL, SVNURL url) { + public CopyOptionsDialog(String title, Project project, final RepositoryTreeNode root, final RepositoryTreeNode node) { super(project, true); - myURL = url; myProject = project; + myURL = node.getURL(); myURLLabel.setText(myURL.toString()); - myBrowser.setRepositoryURL(rootURL, false); + myBrowser.setRepositoryURL(root.getURL(), false); myBrowser.addChangeListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { update(); @@ -80,6 +82,16 @@ public class CopyOptionsDialog extends DialogWrapper { setTitle(title); init(); update(); + + expandNode(node); + } + + private void expandNode(final RepositoryTreeNode node) { + // we need only a path to root, i.e. without first element + final RepositoryTreeModel model = (RepositoryTreeModel) myBrowser.getRepositoryTree().getModel(); + final TreeNode[] subPath = model.getPathToSubRoot(node); + + ((RepositoryTreeNode) model.getRoot()).reload(new Expander(subPath, myBrowser)); } @Override @@ -114,6 +126,12 @@ public class CopyOptionsDialog extends DialogWrapper { } @Nullable + public TreeNode[] getTargetPath() { + final RepositoryTreeNode selectedNode = myBrowser.getSelectedNode(); + return (selectedNode == null) ? null : selectedNode.getSelfPath(); + } + + @Nullable protected JComponent createCenterPanel() { return myMainPanel; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/Expander.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/Expander.java new file mode 100644 index 0000000000..3c16cc57a7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/Expander.java @@ -0,0 +1,53 @@ +package org.jetbrains.idea.svn.dialogs.browser; + +import org.jetbrains.idea.svn.dialogs.ReloadListener; +import org.jetbrains.idea.svn.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn.dialogs.RepositoryTreeNode; + +import javax.swing.tree.TreeNode; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + +public class Expander implements ReloadListener { + private final List pathElements; + private final RepositoryBrowserComponent myBrowser; + + public Expander(final TreeNode[] path, final RepositoryBrowserComponent browser) { + myBrowser = browser; + pathElements = new LinkedList(); + // starting from 1st level child, not root (root is already defined/found) + for (int i = 1; i < path.length; i++) { + TreeNode node = path[i]; + pathElements.add(node.toString()); + } + } + + public void onAfterReload(final RepositoryTreeNode node) { + if (node.isLeaf()) { + return; + } + + myBrowser.expandNode(node); + + if (pathElements.isEmpty()) { + return; + } + + final String nextKey = pathElements.remove(0); + + final Enumeration children = node.children(); + while ((nextKey != null) && children.hasMoreElements()) { + final TreeNode treeNode = (TreeNode) children.nextElement(); + + if (treeNode instanceof RepositoryTreeNode) { + final RepositoryTreeNode repositoryTreeNode = (RepositoryTreeNode) treeNode; + // toString() is sufficient here -> user objects do not define equals() + if (nextKey.equals(treeNode.toString())) { + repositoryTreeNode.registerReloadListener(this); + break; + } + } + } + } +} -- 2.11.4.GIT