diff --git a/platform/platform-impl/src/com/intellij/ui/popup/NextStepHandler.java b/platform/platform-impl/src/com/intellij/ui/popup/NextStepHandler.java new file mode 100644 index 000000000000..ac8110cdbb0b --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/popup/NextStepHandler.java @@ -0,0 +1,8 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ui.popup; + +import com.intellij.openapi.ui.popup.PopupStep; + +public interface NextStepHandler { + void handleNextStep(final PopupStep nextStep, Object parentValue); +} diff --git a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java index b7f148d07fc0..3355b216075f 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java @@ -25,6 +25,8 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.ui.PopupBorder; import com.intellij.ui.ScreenUtil; import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.popup.async.AsyncPopupImpl; +import com.intellij.ui.popup.async.AsyncPopupStep; import com.intellij.ui.popup.list.ListPopupImpl; import com.intellij.ui.popup.tree.TreePopupImpl; import com.intellij.ui.popup.util.MnemonicsSearch; @@ -394,6 +396,9 @@ public abstract class WizardPopup extends AbstractPopup implements ActionListene } protected WizardPopup createPopup(WizardPopup parent, PopupStep step, Object parentValue) { + if (step instanceof AsyncPopupStep) { + return new AsyncPopupImpl(parent, (AsyncPopupStep)step, parentValue); + } if (step instanceof ListPopupStep) { return new ListPopupImpl(parent, (ListPopupStep)step, parentValue); } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/async/AsyncPopupImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/async/AsyncPopupImpl.java new file mode 100644 index 000000000000..e924a9368101 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/popup/async/AsyncPopupImpl.java @@ -0,0 +1,111 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ui.popup.async; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.util.Disposer; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.popup.NextStepHandler; +import com.intellij.ui.popup.WizardPopup; +import com.intellij.util.Alarm; +import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class AsyncPopupImpl extends WizardPopup implements Runnable { + + private final Future myFuture; + private final Object myParentValue; + private final NextStepHandler myCallBackParent; + private final Alarm myAlarm; + private JPanel myPanel; + + public AsyncPopupImpl(@Nullable WizardPopup parent, @NotNull AsyncPopupStep step, Object parentValue) { + super(parent, step); + + if (!(parent instanceof NextStepHandler)) throw new IllegalArgumentException("parent must be NextStepHandler"); + + myCallBackParent = (NextStepHandler)parent; + myParentValue = parentValue; + + myFuture = ApplicationManager.getApplication().executeOnPooledThread(step); + + myAlarm = new Alarm(this); + myAlarm.addRequest(this, 200); + Disposer.register(this, new Disposable() { + @Override + public void dispose() { + if (!myFuture.isCancelled() && !myFuture.isDone()) { + myFuture.cancel(false); + } + } + }); + } + + @Override + public void run() { + if (myFuture.isCancelled()) return; + + if (myFuture.isDone()) { + goBack(); + try { + myCallBackParent.handleNextStep(myFuture.get(), myParentValue); + } + catch (InterruptedException e) { + //ignore + } + catch (ExecutionException e) { + //ignore + } + return; + } + myAlarm.addRequest(this, 200); + } + + @Override + protected JComponent createContent() { + if (myPanel != null) return myPanel; + myPanel = new JPanel(new BorderLayout()); + myPanel.add(new AsyncProcessIcon("Async Popup Step"), BorderLayout.WEST); + myPanel.add(new JBLabel("Loading..."), BorderLayout.CENTER); + myPanel.setBorder(new EmptyBorder(UIUtil.getListCellPadding())); + myPanel.setBackground(UIUtil.getListSelectionBackground()); + myPanel.registerKeyboardAction(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + goBack(); + } + }, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), JComponent.WHEN_FOCUSED); + return myPanel; + } + + @Override + protected JComponent getPreferredFocusableComponent() { + return createContent(); + } + + @Override + protected InputMap getInputMap() { + return null; + } + + @Override + protected ActionMap getActionMap() { + return null; + } + + @Override + protected void onChildSelectedFor(Object value) { + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/popup/async/AsyncPopupStep.java b/platform/platform-impl/src/com/intellij/ui/popup/async/AsyncPopupStep.java new file mode 100644 index 000000000000..8e6dbbad9c03 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/popup/async/AsyncPopupStep.java @@ -0,0 +1,72 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ui.popup.async; + +import com.intellij.openapi.ui.popup.MnemonicNavigationFilter; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.SpeedSearchFilter; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.Callable; + +/** + * Use this "mock" popup step when you need long processing (I/O, network activity etc.) to build real one. + * The real example is a list of processes on remote host you can connect to with debugger. + * This step would be started as 'callable' on pooled thread as soon as AsyncPopupImpl instance with "Loading..." text is being created + * When real popup step is obtained from the background task, mock item would be automatically replaced with it. + */ +public abstract class AsyncPopupStep implements PopupStep, Callable { + @Nullable + @Override + public String getTitle() { + return null; + } + + @Nullable + @Override + public PopupStep onChosen(Object selectedValue, boolean finalChoice) { + return null; + } + + @Override + public boolean hasSubstep(Object selectedValue) { + return false; + } + + @Override + public void canceled() { + + } + + @Override + public boolean isMnemonicsNavigationEnabled() { + return false; + } + + @Nullable + @Override + public MnemonicNavigationFilter getMnemonicNavigationFilter() { + return null; + } + + @Override + public boolean isSpeedSearchEnabled() { + return false; + } + + @Nullable + @Override + public SpeedSearchFilter getSpeedSearchFilter() { + return null; + } + + @Override + public boolean isAutoSelectionEnabled() { + return false; + } + + @Nullable + @Override + public Runnable getFinalRunnable() { + return null; + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java index 28a6f4ca13f5..2b311cd75e8d 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java @@ -34,6 +34,7 @@ import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBList; import com.intellij.ui.popup.ClosableByLeftArrow; import com.intellij.ui.popup.HintUpdateSupply; +import com.intellij.ui.popup.NextStepHandler; import com.intellij.ui.popup.WizardPopup; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -47,7 +48,7 @@ import java.awt.event.*; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; -public class ListPopupImpl extends WizardPopup implements ListPopup { +public class ListPopupImpl extends WizardPopup implements ListPopup, NextStepHandler { private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.list.ListPopupImpl"); private MyList myList; @@ -421,7 +422,11 @@ public class ListPopupImpl extends WizardPopup implements ListPopup { } } - private boolean handleNextStep(final PopupStep nextStep, Object parentValue, InputEvent e) { + public void handleNextStep(final PopupStep nextStep, Object parentValue) { + handleNextStep(nextStep, parentValue, null); + } + + public boolean handleNextStep(final PopupStep nextStep, Object parentValue, InputEvent e) { if (nextStep != PopupStep.FINAL_CHOICE) { final Point point = myList.indexToLocation(myList.getSelectedIndex()); SwingUtilities.convertPointToScreen(point, myList); diff --git a/platform/platform-impl/src/com/intellij/ui/popup/tree/TreePopupImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/tree/TreePopupImpl.java index 038f1cdc7796..4d03a1c19302 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/tree/TreePopupImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/tree/TreePopupImpl.java @@ -27,6 +27,7 @@ import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.TreePopup; import com.intellij.openapi.ui.popup.TreePopupStep; import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.popup.NextStepHandler; import com.intellij.ui.popup.WizardPopup; import com.intellij.ui.treeStructure.SimpleTree; import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder; @@ -46,7 +47,7 @@ import java.util.Enumeration; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -public class TreePopupImpl extends WizardPopup implements TreePopup { +public class TreePopupImpl extends WizardPopup implements TreePopup, NextStepHandler { private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.tree.TreePopupImpl"); private MyTree myWizardTree; @@ -382,7 +383,7 @@ public class TreePopupImpl extends WizardPopup implements TreePopup { } } - private void handleNextStep(PopupStep nextStep, Object parentValue) { + public void handleNextStep(PopupStep nextStep, Object parentValue) { final Rectangle pathBounds = myWizardTree.getPathBounds(myWizardTree.getSelectionPath()); final Point point = new RelativePoint(myWizardTree, new Point(getContent().getWidth() + 2, (int) pathBounds.getY())).getScreenPoint(); myChild = createPopup(this, nextStep, parentValue);