diff options
Diffstat (limited to 'platform/dvcs-impl')
14 files changed, 467 insertions, 229 deletions
diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java index 26c87c7de627..76d58125b719 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsCommitAdditionalComponent.java @@ -43,9 +43,6 @@ import java.io.File; import java.util.*; import java.util.List; -/** - * @author Nadya Zabrodina - */ public abstract class DvcsCommitAdditionalComponent implements RefreshableOnComponent { private static final Logger log = Logger.getInstance(DvcsCommitAdditionalComponent.class); @@ -179,4 +176,8 @@ public abstract class DvcsCommitAdditionalComponent implements RefreshableOnComp @Nullable protected abstract String getLastCommitMessage(@NotNull VirtualFile repo) throws VcsException; + + public boolean isAmend() { + return myAmend.isSelected(); + } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java new file mode 100644 index 000000000000..a98cf27ab5d6 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java @@ -0,0 +1,174 @@ +/* + * 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.branch; + +import com.intellij.dvcs.DvcsUtil; +import com.intellij.dvcs.repo.AbstractRepositoryManager; +import com.intellij.dvcs.repo.Repository; +import com.intellij.dvcs.ui.BranchActionGroupPopup; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationListener; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.options.ShowSettingsUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsNotifier; +import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import java.util.List; + +public abstract class DvcsBranchPopup<Repo extends Repository> { + @NotNull protected final Project myProject; + @NotNull protected final AbstractRepositoryManager<Repo> myRepositoryManager; + @NotNull protected final DvcsSyncBranchSettings myVcsSettings; + @NotNull protected final AbstractVcs myVcs; + @NotNull protected final DvcsMultiRootBranchConfig<Repo> myMultiRootBranchConfig; + + @NotNull protected final Repo myCurrentRepository; + @NotNull protected final ListPopupImpl myPopup; + + protected DvcsBranchPopup(@NotNull Repo currentRepository, + @NotNull AbstractRepositoryManager<Repo> repositoryManager, + @NotNull DvcsMultiRootBranchConfig<Repo> multiRootBranchConfig, + @NotNull DvcsSyncBranchSettings vcsSettings, + @NotNull Condition<AnAction> preselectActionCondition) { + myProject = currentRepository.getProject(); + myCurrentRepository = currentRepository; + myRepositoryManager = repositoryManager; + myVcs = currentRepository.getVcs(); + myVcsSettings = vcsSettings; + myMultiRootBranchConfig = multiRootBranchConfig; + String title = createPopupTitle(currentRepository); + myPopup = new BranchActionGroupPopup(title, myProject, preselectActionCondition, createActions()); + + initBranchSyncPolicyIfNotInitialized(); + setCurrentBranchInfo(); + warnThatBranchesDivergedIfNeeded(); + } + + public ListPopup asListPopup() { + return myPopup; + } + + private void initBranchSyncPolicyIfNotInitialized() { + if (myRepositoryManager.moreThanOneRoot() && myVcsSettings.getSyncSetting() == DvcsBranchSync.NOT_DECIDED) { + if (!myMultiRootBranchConfig.diverged()) { + notifyAboutSyncedBranches(); + myVcsSettings.setSyncSetting(DvcsBranchSync.SYNC); + } + else { + myVcsSettings.setSyncSetting(DvcsBranchSync.DONT); + } + } + } + + @NotNull + private String createPopupTitle(@NotNull Repo currentRepository) { + String title = myVcs.getDisplayName() + " Branches"; + if (myRepositoryManager.moreThanOneRoot() && + (myMultiRootBranchConfig.diverged() || myVcsSettings.getSyncSetting() == DvcsBranchSync.DONT)) { + title += " in " + DvcsUtil.getShortRepositoryName(currentRepository); + } + return title; + } + + protected void setCurrentBranchInfo() { + String branchText = "Current branch : "; + myPopup.setAdText(branchText + myCurrentRepository.getCurrentBranchName(), SwingConstants.CENTER); + } + + private void notifyAboutSyncedBranches() { + String description = + "You have several " + myVcs.getDisplayName() + "roots in the project and they all are checked out at the same branch. " + + "We've enabled synchronous branch control for the project. <br/>" + + "If you wish to control branches in different roots separately, " + + "you may <a href='settings'>disable</a> the setting."; + NotificationListener listener = new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + ShowSettingsUtil.getInstance().showSettingsDialog(myProject, myVcs.getConfigurable().getDisplayName()); + if (myVcsSettings.getSyncSetting() == DvcsBranchSync.DONT) { + notification.expire(); + } + } + } + }; + VcsNotifier.getInstance(myProject).notifyImportantInfo("Synchronous branch control enabled", description, listener); + } + + @NotNull + private ActionGroup createActions() { + DefaultActionGroup popupGroup = new DefaultActionGroup(null, false); + AbstractRepositoryManager<Repo> repositoryManager = myRepositoryManager; + if (repositoryManager.moreThanOneRoot()) { + if (userWantsSyncControl()) { + fillWithCommonRepositoryActions(popupGroup, repositoryManager); + } + else { + fillPopupWithCurrentRepositoryActions(popupGroup, createRepositoriesActions()); + } + } + else { + fillPopupWithCurrentRepositoryActions(popupGroup, null); + } + popupGroup.addSeparator(); + return popupGroup; + } + + private boolean userWantsSyncControl() { + return (myVcsSettings.getSyncSetting() != DvcsBranchSync.DONT); + } + + protected abstract void fillWithCommonRepositoryActions(@NotNull DefaultActionGroup popupGroup, + @NotNull AbstractRepositoryManager<Repo> repositoryManager); + + @NotNull + protected List<Repo> filterRepositoriesNotOnThisBranch(@NotNull final String branch, + @NotNull List<Repo> allRepositories) { + return ContainerUtil.filter(allRepositories, new Condition<Repo>() { + @Override + public boolean value(Repo repository) { + return !branch.equals(repository.getCurrentBranchName()); + } + }); + } + + private void warnThatBranchesDivergedIfNeeded() { + if (myRepositoryManager.moreThanOneRoot() && myMultiRootBranchConfig.diverged() && userWantsSyncControl()) { + myPopup.setWarning("Branches have diverged"); + } + } + + @NotNull + protected abstract DefaultActionGroup createRepositoriesActions(); + + protected boolean highlightCurrentRepo() { + return !userWantsSyncControl() || myMultiRootBranchConfig.diverged(); + } + + protected abstract void fillPopupWithCurrentRepositoryActions(@NotNull DefaultActionGroup popupGroup, + @Nullable DefaultActionGroup actions); +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchSync.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchSync.java new file mode 100644 index 000000000000..9fab273be2e1 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchSync.java @@ -0,0 +1,22 @@ +/* + * 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.branch; + +public enum DvcsBranchSync { + SYNC, + DONT, + NOT_DECIDED +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java new file mode 100644 index 000000000000..ee24209167fa --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java @@ -0,0 +1,71 @@ +/* + * 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.branch; + +import com.intellij.dvcs.repo.Repository; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public abstract class DvcsMultiRootBranchConfig<Repo extends Repository> { + @NotNull protected final Collection<Repo> myRepositories; + + public DvcsMultiRootBranchConfig(@NotNull Collection<Repo> repositories) { + myRepositories = repositories; + } + + public boolean diverged() { + return getCurrentBranch() == null; + } + + @Nullable + public String getCurrentBranch() { + String commonBranch = null; + for (Repo repository : myRepositories) { + String branchName = repository.getCurrentBranchName(); + if (branchName == null) { + return null; + } + // NB: if all repositories are in the rebasing state on the same branches, this branch is returned + if (commonBranch == null) { + commonBranch = branchName; + } + else if (!commonBranch.equals(branchName)) { + return null; + } + } + return commonBranch; + } + + @Nullable + public Repository.State getState() { + Repository.State commonState = null; + for (Repo repository : myRepositories) { + Repository.State state = repository.getState(); + if (commonState == null) { + commonState = state; + } + else if (!commonState.equals(state)) { + return null; + } + } + return commonState; + } + + @NotNull + public abstract Collection<String> getLocalBranchNames(); +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsSyncBranchSettings.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsSyncBranchSettings.java new file mode 100644 index 000000000000..115872a66552 --- /dev/null +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsSyncBranchSettings.java @@ -0,0 +1,25 @@ +/* + * 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.branch; + +import org.jetbrains.annotations.NotNull; + +public interface DvcsSyncBranchSettings { + @NotNull + DvcsBranchSync getSyncSetting(); + + void setSyncSetting(@NotNull DvcsBranchSync syncSetting); +} diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java index 45f5c68e1d0a..cd0cc7a1ef7a 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java @@ -20,31 +20,32 @@ import com.intellij.dvcs.push.ui.*; import com.intellij.dvcs.repo.Repository; import com.intellij.dvcs.repo.RepositoryManager; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; -import com.intellij.openapi.progress.impl.ProgressManagerImpl; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.openapi.ui.popup.util.PopupUtil; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.ui.CheckedTreeNode; -import com.intellij.ui.SimpleColoredText; -import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.hash.HashMap; +import com.intellij.util.ui.UIUtil; import com.intellij.vcs.log.VcsFullCommitDetails; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; public class PushController implements Disposable { @@ -56,6 +57,7 @@ public class PushController implements Disposable { private boolean mySingleRepoProject; private static final int DEFAULT_CHILDREN_PRESENTATION_NUMBER = 20; private final Map<PushSupport, MyPushOptionValueModel> myAdditionalValuesMap; + private final ExecutorService myExecutorService = Executors.newSingleThreadExecutor(); private final Map<RepositoryNode, MyRepoModel> myView2Model = new TreeMap<RepositoryNode, MyRepoModel>(); //todo need to sort repositories in ui tree using natural order @@ -162,33 +164,31 @@ public class PushController implements Disposable { if (target == null) { model.setError(VcsError.createEmptyTargetError(repoName)); } - RepositoryWithBranchPanel repoPanel = new RepositoryWithBranchPanel(myProject, repoName, - support.getSource(repository).getPresentation(), - target == null ? "" : target.getPresentation(), - support.getTargetNames(repository)); + final PushTargetPanel<T> pushTargetPanel = support.createTargetPanel(repository, target); + RepositoryWithBranchPanel<T> repoPanel = + new RepositoryWithBranchPanel<T>(repoName, support.getSource(repository).getPresentation(), pushTargetPanel); + repoPanel.setInputVerifier(new InputVerifier() { + @Override + public boolean verify(JComponent input) { + ValidationInfo error = pushTargetPanel.verify(); + if (error != null) { + //noinspection ConstantConditions + PopupUtil.showBalloonForComponent(error.component, error.message, MessageType.WARNING, false, myProject); + } + return error == null; + } + }); final RepositoryNode repoNode = isSingleRepositoryProject - ? new SingleRepositoryNode(repoPanel, support.renderTarget(target)) - : new RepositoryNode(repoPanel, support.renderTarget(target)); + ? new SingleRepositoryNode(repoPanel) + : new RepositoryNode(repoPanel); myView2Model.put(repoNode, model); repoNode.setChecked(model.isSelected()); - repoPanel.addRepoNodeListener(new RepositoryNodeListener() { + repoPanel.addRepoNodeListener(new RepositoryNodeListener<T>() { @Override - public void onTargetChanged(String newValue) { - VcsError validationError = support.validate(model.getRepository(), newValue); - if (validationError == null) { - T newTarget = support.createTarget(repository, newValue); - repoNode.setTargetPresentation(support.renderTarget(newTarget)); - model.setTarget(newTarget); - model.clearErrors(); - loadCommits(model, repoNode, false); - } - else { - repoNode.setTargetPresentation(StringUtil.isEmptyOrSpaces(newValue) - ? support.renderTarget(null) - : new SimpleColoredText(newValue, SimpleTextAttributes.ERROR_ATTRIBUTES)); - model.setError(validationError); // todo may be should accept and store errors collection, now store one major target error - model.setTarget(null); - } + public void onTargetChanged(T newTarget) { + model.setTarget(newTarget); + model.clearErrors(); + loadCommits(model, repoNode, false); myDialog.updateButtons(); } @@ -209,56 +209,49 @@ public class PushController implements Disposable { final T target = model.getTarget(); if (target == null) return; //todo should be removed when commit loader executor will be modified myPushLog.startLoading(node); - final ProgressIndicator indicator = node.startLoading(); final PushSupport<R, S, T> support = model.getSupport(); final AtomicReference<OutgoingResult> result = new AtomicReference<OutgoingResult>(); - Task.Backgroundable task = new Task.Backgroundable(myProject, "Loading Commits", true) { - + Runnable task = new Runnable() { @Override - public void onCancel() { - node.stopLoading(); - } - - @Override - public void onSuccess() { - OutgoingResult outgoing = result.get(); - List<VcsError> errors = outgoing.getErrors(); - if (!errors.isEmpty()) { - myPushLog.setChildren(node, ContainerUtil.map(errors, new Function<VcsError, DefaultMutableTreeNode>() { - @Override - public DefaultMutableTreeNode fun(final VcsError error) { - VcsLinkedText errorLinkText = new VcsLinkedText(error.getText(), new VcsLinkListener() { + public void run() { + OutgoingResult outgoing = support.getOutgoingCommitsProvider() + .getOutgoingCommits(model.getRepository(), new PushSpec<S, T>(model.getSource(), model.getTarget()), initial); + result.compareAndSet(null, outgoing); + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + OutgoingResult outgoing = result.get(); + List<VcsError> errors = outgoing.getErrors(); + if (!errors.isEmpty()) { + myPushLog.setChildren(node, ContainerUtil.map(errors, new Function<VcsError, DefaultMutableTreeNode>() { @Override - public void hyperlinkActivated(@NotNull DefaultMutableTreeNode sourceNode) { - error.handleError(new CommitLoader() { + public DefaultMutableTreeNode fun(final VcsError error) { + VcsLinkedText errorLinkText = new VcsLinkedText(error.getText(), new VcsLinkListener() { @Override - public void reloadCommits() { - loadCommits(model, node, false); + public void hyperlinkActivated(@NotNull DefaultMutableTreeNode sourceNode) { + error.handleError(new CommitLoader() { + @Override + public void reloadCommits() { + loadCommits(model, node, false); + } + }); } }); + return new TextWithLinkNode(errorLinkText); } - }); - return new TextWithLinkNode(errorLinkText); + }), model.isSelected()); } - }), model.isSelected()); - } - else { - model.setLoadedCommits(outgoing.getCommits()); - myPushLog.setChildren(node, - getPresentationForCommits(PushController.this.myProject, model.getLoadedCommits(), - model.getNumberOfShownCommits()), model.isSelected()); - } - } - - @Override - public void run(@NotNull ProgressIndicator indicator) { - OutgoingResult outgoing = support.getOutgoingCommitsProvider() - .getOutgoingCommits(model.getRepository(), new PushSpec<S, T>(model.getSource(), model.getTarget()), initial); - result.compareAndSet(null, outgoing); + else { + model.setLoadedCommits(outgoing.getCommits()); + myPushLog.setChildren(node, + getPresentationForCommits(PushController.this.myProject, model.getLoadedCommits(), + model.getNumberOfShownCommits()), model.isSelected()); + } + } + }); } }; - - ProgressManagerImpl.runProcessWithProgressAsynchronously(task, indicator, null, ModalityState.any()); + node.startLoading(myExecutorService.submit(task, result)); } public PushLog getPushPanelLog() { @@ -315,9 +308,7 @@ public class PushController implements Disposable { @Override public void dispose() { - for (RepositoryNode node : myView2Model.keySet()) { - node.stopLoading(); - } + myExecutorService.shutdownNow(); } private void addMoreCommits(RepositoryNode repositoryNode) { diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/RepositoryNodeListener.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/RepositoryNodeListener.java index 7ca285fc95aa..91bb711cc384 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/RepositoryNodeListener.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/RepositoryNodeListener.java @@ -15,9 +15,9 @@ */ package com.intellij.dvcs.push; -public interface RepositoryNodeListener { +public interface RepositoryNodeListener<T extends PushTarget> { - void onTargetChanged(String newValue); + void onTargetChanged(T newTarget); void onSelectionChanged(boolean isSelected); } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java index b5747de91bf7..aab70818082d 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java @@ -15,9 +15,12 @@ */ package com.intellij.dvcs.push.ui; -import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.dvcs.push.OutgoingResult; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + public interface EditableTreeNode extends CustomRenderedTreeNode { void fireOnChange(); @@ -28,8 +31,5 @@ public interface EditableTreeNode extends CustomRenderedTreeNode { void stopLoading(); - @NotNull - ProgressIndicator startLoading(); - - String getValue(); + void startLoading(@NotNull Future<AtomicReference<OutgoingResult>> future); } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java index 0b4bcb0fbef0..1539b94216c0 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java @@ -15,6 +15,7 @@ */ 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; @@ -25,7 +26,10 @@ 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.*; +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; @@ -37,7 +41,10 @@ import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.*; +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; @@ -45,17 +52,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EventObject; -import java.util.concurrent.locks.ReentrantReadWriteLock; public class PushLog extends JPanel implements TypeSafeDataProvider { - private final ReentrantReadWriteLock TREE_CONSTRUCTION_LOCK = new ReentrantReadWriteLock(); - private static final String START_EDITING = "startEditing"; private final ChangesBrowser myChangesBrowser; private final CheckboxTree myTree; private final MyTreeCellRenderer myTreeCellRenderer; - //private final AtomicBoolean myIgnoreStopEditing = new AtomicBoolean(false); + private boolean myEditingSucceeded; public PushLog(Project project, CheckedTreeNode root) { DefaultTreeModel treeModel = new DefaultTreeModel(root); @@ -89,6 +93,23 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { } 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()); @@ -140,6 +161,9 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { } }); 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); @@ -197,8 +221,14 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { - if (e.getKeyCode() == KeyEvent.VK_ENTER && myTree.isEditing()) { - myTree.stopEditing(); + 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); @@ -239,7 +269,7 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { Object tag = me.getClickCount() >= clickCountToStart ? PushLogTreeUtil.getTagAtForRenderer(myTreeCellRenderer, me) : null; - return tag instanceof EditorTextField; + return tag instanceof PushTargetPanel; } //if keyboard event - then anEvent will be null =( See BasicTreeUi TreePath treePath = myTree.getAnchorSelectionPath(); @@ -251,7 +281,7 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { //Implement the one CellEditor method that AbstractCellEditor doesn't. public Object getCellEditorValue() { - return ((RepositoryWithBranchPanel)editorComponent).getRemoteTargetName(); + return myEditingSucceeded ? ((RepositoryWithBranchPanel)editorComponent).getEditableValue() : null; } } @@ -285,34 +315,19 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { public void setChildren(DefaultMutableTreeNode parentNode, @NotNull Collection<? extends DefaultMutableTreeNode> childrenNodes, boolean shouldExpand) { - try { - TREE_CONSTRUCTION_LOCK.writeLock().lock(); - parentNode.removeAllChildren(); - for (DefaultMutableTreeNode child : childrenNodes) { - parentNode.add(child); - } - final DefaultTreeModel model = ((DefaultTreeModel)myTree.getModel()); - model.nodeStructureChanged(parentNode); - TreePath path = TreeUtil.getPathFromRoot(parentNode); - //myIgnoreStopEditing.set(true); - if (shouldExpand) { - myTree.expandPath(path); - } - else { - myTree.collapsePath(path); - } + parentNode.removeAllChildren(); + for (DefaultMutableTreeNode child : childrenNodes) { + parentNode.add(child); } - finally { - TREE_CONSTRUCTION_LOCK.writeLock().unlock(); - //myIgnoreStopEditing.set(false); + final DefaultTreeModel model = ((DefaultTreeModel)myTree.getModel()); + model.nodeStructureChanged(parentNode); + TreePath path = TreeUtil.getPathFromRoot(parentNode); + if (shouldExpand) { + myTree.expandPath(path); } - } - - public void startEditNode(@NotNull TreeNode node) { - TreePath path = TreeUtil.getPathFromRoot(node); - if (!myTree.isEditing()) { - myTree.setSelectionPath(path); - myTree.startEditingAtPath(path); + else { + myTree.collapsePath(path); } } + } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java index 05e752c4be85..4808064f5c8d 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java @@ -15,29 +15,26 @@ */ package com.intellij.dvcs.push.ui; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.ui.*; +import com.intellij.dvcs.push.OutgoingResult; +import com.intellij.dvcs.push.PushTargetPanel; +import com.intellij.ui.CheckedTreeNode; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; -import java.util.ArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; public class RepositoryNode extends CheckedTreeNode implements EditableTreeNode, Comparable<RepositoryNode> { @NotNull private final RepositoryWithBranchPanel myRepositoryPanel; - @NotNull protected SimpleColoredText myTargetPresentation; - private ProgressIndicator myCurrentIndicator; + private Future<AtomicReference<OutgoingResult>> myFuture; - public RepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel, @NotNull SimpleColoredText targetPresentation) { + public RepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel) { super(repositoryPanel); myRepositoryPanel = repositoryPanel; - myTargetPresentation = targetPresentation; - } - - public void setTargetPresentation(@NotNull SimpleColoredText targetPresentation) { - myTargetPresentation = targetPresentation; } public boolean isCheckboxVisible() { @@ -51,32 +48,18 @@ public class RepositoryNode extends CheckedTreeNode implements EditableTreeNode, renderer.appendFixedTextFragmentWidth(120); renderer.append(myRepositoryPanel.getSourceName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); renderer.append(myRepositoryPanel.getArrow(), SimpleTextAttributes.REGULAR_ATTRIBUTES); - EditorTextField textField = myRepositoryPanel.getRemoteTextFiled(); - renderTargetName(renderer, textField); - Insets insets = BorderFactory.createEmptyBorder().getBorderInsets(textField); + PushTargetPanel pushTargetPanel = myRepositoryPanel.getTargetPanel(); + pushTargetPanel.render(renderer); + Insets insets = BorderFactory.createEmptyBorder().getBorderInsets(pushTargetPanel); renderer.setBorder(new EmptyBorder(insets)); } - protected void renderTargetName(@NotNull ColoredTreeCellRenderer renderer, @NotNull EditorTextField textField) { - ArrayList<String> strings = myTargetPresentation.getTexts(); - ArrayList<SimpleTextAttributes> attributes = myTargetPresentation.getAttributes(); - for (int i = 0; i < strings.size(); i++) { - renderer.append(strings.get(i), attributes.get(i), textField); - } - } - @Override public Object getUserObject() { return myRepositoryPanel; } @Override - @NotNull - public String getValue() { - return myRepositoryPanel.getRemoteTargetName(); - } - - @Override public void fireOnChange() { myRepositoryPanel.fireOnChange(); } @@ -93,15 +76,14 @@ public class RepositoryNode extends CheckedTreeNode implements EditableTreeNode, @Override public void stopLoading() { - if (myCurrentIndicator != null && myCurrentIndicator.isRunning()) { - myCurrentIndicator.cancel(); + if (myFuture != null && !myFuture.isDone()) { + myFuture.cancel(true); } } @Override - @NotNull - public ProgressIndicator startLoading() { - return myCurrentIndicator = new EmptyProgressIndicator(); + public void startLoading(@NotNull Future<AtomicReference<OutgoingResult>> future) { + myFuture = future; } public int compareTo(@NotNull RepositoryNode repositoryNode) { diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryWithBranchPanel.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryWithBranchPanel.java index cd93d0653c36..29a32237ca3a 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryWithBranchPanel.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryWithBranchPanel.java @@ -15,18 +15,15 @@ */ package com.intellij.dvcs.push.ui; +import com.intellij.dvcs.push.PushTarget; +import com.intellij.dvcs.push.PushTargetPanel; import com.intellij.dvcs.push.RepositoryNodeListener; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.project.Project; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.SimpleTextAttributes; -import com.intellij.ui.TextFieldWithAutoCompletion; -import com.intellij.ui.TextFieldWithAutoCompletionListProvider; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -34,23 +31,20 @@ import javax.swing.tree.TreeCellRenderer; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; import java.util.List; -public class RepositoryWithBranchPanel extends NonOpaquePanel implements TreeCellRenderer { +public class RepositoryWithBranchPanel<T extends PushTarget> extends NonOpaquePanel implements TreeCellRenderer { private final JBCheckBox myRepositoryCheckbox; - private final TextFieldWithAutoCompletion myDestBranchTextField; + private final PushTargetPanel<T> myDestPushTargetPanelComponent; private final JBLabel myLocalBranch; private final JLabel myArrowLabel; private final JLabel myRepositoryLabel; private final ColoredTreeCellRenderer myTextRenderer; - @NotNull private final List<RepositoryNodeListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); - private String myOldDestination; + @NotNull private final List<RepositoryNodeListener<T>> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); - public RepositoryWithBranchPanel(Project project, @NotNull String repoName, - @NotNull String sourceName, String targetName, @NotNull final List<String> targetVariants) { + public RepositoryWithBranchPanel(@NotNull String repoName, + @NotNull String sourceName, @NotNull PushTargetPanel<T> destPushTargetPanelComponent) { super(); setLayout(new BorderLayout()); myRepositoryCheckbox = new JBCheckBox(); @@ -65,37 +59,7 @@ public class RepositoryWithBranchPanel extends NonOpaquePanel implements TreeCel myRepositoryLabel = new JLabel(repoName); myLocalBranch = new JBLabel(sourceName); myArrowLabel = new JLabel(" -> "); - myOldDestination = targetName; - TextFieldWithAutoCompletionListProvider<String> provider = - new TextFieldWithAutoCompletion.StringsCompletionProvider(targetVariants, null) { - @Override - public int compare(String item1, String item2) { - return Integer.valueOf(ContainerUtil.indexOf(targetVariants, item1)).compareTo(ContainerUtil.indexOf(targetVariants, item2)); - } - }; - myDestBranchTextField = new TextFieldWithAutoCompletion<String>(project, provider, true, targetName) { - - @Override - public boolean shouldHaveBorder() { - return false; - } - - @Override - protected void updateBorder(@NotNull final EditorEx editor) { - } - }; - myDestBranchTextField.setBorder(UIUtil.getTableFocusCellHighlightBorder()); - myDestBranchTextField.setOneLineMode(true); - myDestBranchTextField.setOpaque(true); - FocusAdapter focusListener = new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - myDestBranchTextField.selectAll(); - } - }; - myDestBranchTextField.addFocusListener(focusListener); - addFocusListener(focusListener); - + myDestPushTargetPanelComponent = destPushTargetPanelComponent; myTextRenderer = new ColoredTreeCellRenderer() { public void customizeCellRenderer(@NotNull JTree tree, Object value, @@ -115,7 +79,7 @@ public class RepositoryWithBranchPanel extends NonOpaquePanel implements TreeCel add(myRepositoryCheckbox, BorderLayout.WEST); JPanel panel = new NonOpaquePanel(new BorderLayout()); panel.add(myTextRenderer, BorderLayout.WEST); - panel.add(myDestBranchTextField, BorderLayout.CENTER); + panel.add(myDestPushTargetPanelComponent, BorderLayout.CENTER); add(panel, BorderLayout.CENTER); } @@ -132,15 +96,6 @@ public class RepositoryWithBranchPanel extends NonOpaquePanel implements TreeCel return myArrowLabel.getText(); } - public TextFieldWithAutoCompletion getRemoteTextFiled() { - return myDestBranchTextField; - } - - @NotNull - public String getRemoteTargetName() { - return myDestBranchTextField.getText(); - } - @Override public Component getTreeCellRendererComponent(JTree tree, Object value, @@ -168,20 +123,20 @@ public class RepositoryWithBranchPanel extends NonOpaquePanel implements TreeCel if (bounds != null) { setPreferredSize(new Dimension(tree.getWidth() - bounds.x, bounds.height)); } - myDestBranchTextField.grabFocus(); - myDestBranchTextField.requestFocus(); + myDestPushTargetPanelComponent.grabFocus(); + myDestPushTargetPanelComponent.requestFocus(); revalidate(); return this; } - public void addRepoNodeListener(@NotNull RepositoryNodeListener listener) { + public void addRepoNodeListener(@NotNull RepositoryNodeListener<T> listener) { myListeners.add(listener); } public void fireOnChange() { - myOldDestination = myDestBranchTextField.getText(); - for (RepositoryNodeListener listener : myListeners) { - listener.onTargetChanged(myOldDestination); + myDestPushTargetPanelComponent.fireOnChange(); + for (RepositoryNodeListener<T> listener : myListeners) { + listener.onTargetChanged(myDestPushTargetPanelComponent.getValue()); } } @@ -192,7 +147,15 @@ public class RepositoryWithBranchPanel extends NonOpaquePanel implements TreeCel } public void fireOnCancel() { - myDestBranchTextField.setText(myOldDestination); + myDestPushTargetPanelComponent.fireOnCancel(); + } + + public PushTargetPanel getTargetPanel() { + return myDestPushTargetPanelComponent; + } + + public T getEditableValue() { + return myDestPushTargetPanelComponent.getValue(); } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java index 81fb2bd1a9d0..8506e1d50fea 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java @@ -15,9 +15,8 @@ */ package com.intellij.dvcs.push.ui; +import com.intellij.dvcs.push.PushTargetPanel; import com.intellij.ui.ColoredTreeCellRenderer; -import com.intellij.ui.EditorTextField; -import com.intellij.ui.SimpleColoredText; import com.intellij.ui.SimpleTextAttributes; import org.jetbrains.annotations.NotNull; @@ -29,8 +28,8 @@ public class SingleRepositoryNode extends RepositoryNode { @NotNull private final RepositoryWithBranchPanel myRepositoryPanel; - public SingleRepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel, @NotNull SimpleColoredText customTargetPresentation) { - super(repositoryPanel, customTargetPresentation); + public SingleRepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel) { + super(repositoryPanel); myRepositoryPanel = repositoryPanel; } @@ -43,9 +42,9 @@ public class SingleRepositoryNode extends RepositoryNode { public void render(@NotNull ColoredTreeCellRenderer renderer) { renderer.append(myRepositoryPanel.getSourceName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); renderer.append(myRepositoryPanel.getArrow(), SimpleTextAttributes.REGULAR_ATTRIBUTES); - EditorTextField textField = myRepositoryPanel.getRemoteTextFiled(); - renderTargetName(renderer, textField); - Insets insets = BorderFactory.createEmptyBorder().getBorderInsets(textField); + PushTargetPanel pushTargetPanel = myRepositoryPanel.getTargetPanel(); + pushTargetPanel.render(renderer); + Insets insets = BorderFactory.createEmptyBorder().getBorderInsets(pushTargetPanel); renderer.setBorder(new EmptyBorder(insets)); } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsBranchEditorListener.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsBranchEditorListener.java index d8e35fd25d53..0b3df11db78a 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsBranchEditorListener.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsBranchEditorListener.java @@ -15,9 +15,9 @@ */ package com.intellij.dvcs.push.ui; +import com.intellij.dvcs.push.PushTargetPanel; import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase; import com.intellij.ui.CheckboxTree; -import com.intellij.ui.EditorTextField; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -35,7 +35,7 @@ public class VcsBranchEditorListener extends LinkMouseListenerBase { public void mouseMoved(MouseEvent e) { Component component = (Component)e.getSource(); Object tag = getTagAt(e); - if (tag != null && tag instanceof EditorTextField) { + if (tag != null && tag instanceof PushTargetPanel) { component.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); } else if (tag != null && tag instanceof TextWithLinkNode) { diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryUtil.java b/platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryUtil.java index 5a5a555768ba..35b5bceebff4 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryUtil.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/repo/RepositoryUtil.java @@ -20,7 +20,7 @@ import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.Processor; @@ -101,15 +101,10 @@ public class RepositoryUtil { } public static void visitAllChildrenRecursively(@Nullable VirtualFile dir) { - if (dir == null) { - return; + if (dir != null) { + //noinspection unchecked + VfsUtilCore.processFilesRecursively(dir, Processor.TRUE); } - VfsUtil.processFilesRecursively(dir, new Processor<VirtualFile>() { - @Override - public boolean process(VirtualFile virtualFile) { - return true; - } - }); } public static class Updater implements Consumer<Object> { |