From 1437c1dcd3d6209b5d57a0490945d3cb34b70435 Mon Sep 17 00:00:00 2001 From: Viktor Shatrov Date: Tue, 3 Sep 2019 20:21:23 +0300 Subject: [PATCH] IDEA-167805 Keyboard navigation in debug tool window GitOrigin-RevId: 1b07992e854cfa45714154c4cf8cca109d7c36d7 --- .../debugger/engine/JavaDebugProcess.java | 6 +- .../debugger/ui/impl/ThreadsPanel.java | 5 ++ .../debugger/ui/overhead/OverheadView.java | 5 ++ .../ui/layout/impl/RunnerContentUi.java | 86 +++++++++++++++++++ .../openapi/ui/ThreeComponentsSplitter.java | 53 +++++++++--- .../ui/content/impl/ContentManagerImpl.java | 2 +- .../memory/ui/ClassesFilteredViewBase.java | 4 + 7 files changed, 144 insertions(+), 17 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java index 66723a96ed38..5aba0d920b5b 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java @@ -323,7 +323,7 @@ public class JavaDebugProcess extends XDebugProcess { final ThreadsPanel panel = new ThreadsPanel(myJavaSession.getProject(), getDebuggerStateManager()); final Content threadsContent = ui.createContent( DebuggerContentInfo.THREADS_CONTENT, panel, XDebuggerBundle.message("debugger.session.tab.threads.title"), - null, null); + null, panel.getDefaultFocusedComponent()); threadsContent.setCloseable(false); ui.addContent(threadsContent, 0, PlaceInGrid.left, true); ui.addListener(new ContentManagerAdapter() { @@ -355,7 +355,7 @@ public class JavaDebugProcess extends XDebugProcess { final Content memoryViewContent = ui.createContent(MemoryViewManager.MEMORY_VIEW_CONTENT, classesFilteredView, "Memory", - null, null); + null, classesFilteredView.getDefaultFocusedComponent()); memoryViewContent.setCloseable(false); memoryViewContent.setShouldDisposeContent(true); @@ -387,7 +387,7 @@ public class JavaDebugProcess extends XDebugProcess { DebugProcessImpl process = myJavaSession.getProcess(); OverheadView monitor = new OverheadView(process); - Content overheadContent = ui.createContent("OverheadMonitor", monitor, "Overhead", null, null); + Content overheadContent = ui.createContent("OverheadMonitor", monitor, "Overhead", null, monitor.getDefaultFocusedComponent()); monitor.setBouncer(() -> ui.setBouncing(overheadContent, true)); diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java index a371e1bb12a8..63884438e907 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java @@ -23,6 +23,7 @@ import com.intellij.util.Alarm; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import javax.swing.*; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; @@ -125,6 +126,10 @@ public class ThreadsPanel extends DebuggerTreePanel{ super.dispose(); } + public JComponent getDefaultFocusedComponent() { + return getThreadsTree(); + } + private static void updateNodeLabels(DebuggerTreeNodeImpl from) { Enumeration children = from.children(); try { diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/overhead/OverheadView.java b/java/debugger/impl/src/com/intellij/debugger/ui/overhead/OverheadView.java index 6ec65c2d7c64..ce88fb92c26e 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/overhead/OverheadView.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/overhead/OverheadView.java @@ -121,6 +121,11 @@ public class OverheadView extends BorderLayoutPanel implements Disposable, DataP .map(XBreakpoint::getNavigatable).nonNull(); } + + public JComponent getDefaultFocusedComponent() { + return myTable; + } + @Nullable @Override public Object getData(@NotNull String dataId) { diff --git a/platform/lang-impl/src/com/intellij/execution/ui/layout/impl/RunnerContentUi.java b/platform/lang-impl/src/com/intellij/execution/ui/layout/impl/RunnerContentUi.java index dabb3b12c6a3..65b8d3e58c03 100644 --- a/platform/lang-impl/src/com/intellij/execution/ui/layout/impl/RunnerContentUi.java +++ b/platform/lang-impl/src/com/intellij/execution/ui/layout/impl/RunnerContentUi.java @@ -2,6 +2,8 @@ package com.intellij.execution.ui.layout.impl; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.execution.ui.RunContentManager; import com.intellij.execution.ui.RunnerLayoutUi; import com.intellij.execution.ui.layout.*; import com.intellij.execution.ui.layout.actions.CloseViewAction; @@ -10,11 +12,17 @@ import com.intellij.execution.ui.layout.actions.RestoreViewAction; import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; import com.intellij.ide.actions.CloseAction; +import com.intellij.ide.actions.ShowContentAction; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.AbstractPainter; +import com.intellij.openapi.ui.ShadowAction; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListPopup; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.ActiveRunnable; import com.intellij.openapi.util.Disposer; @@ -25,6 +33,7 @@ import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.IdeGlassPaneUtil; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.impl.ToolWindowsPane; +import com.intellij.openapi.wm.impl.content.SelectContentStep; import com.intellij.ui.*; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.awt.RelativeRectangle; @@ -41,6 +50,7 @@ import com.intellij.ui.tabs.JBTabs; import com.intellij.ui.tabs.TabInfo; import com.intellij.ui.tabs.TabsListener; import com.intellij.ui.tabs.impl.JBTabsImpl; +import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; import com.intellij.util.NotNullFunction; import com.intellij.util.containers.ContainerUtil; @@ -67,6 +77,8 @@ import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; import java.util.stream.Collectors; +import static com.intellij.ui.tabs.JBTabsEx.NAVIGATION_ACTIONS_KEY; + public class RunnerContentUi implements ContentUI, Disposable, CellTransform.Facade, ViewContextEx, PropertyChangeListener, QuickActionProvider, DockContainer.Dialog { public static final DataKey KEY = DataKey.create("DebuggerContentUI"); @@ -77,6 +89,8 @@ public class RunnerContentUi implements ContentUI, Disposable, CellTransform.Fac @NonNls private static final String VIEW_POPUP = "Runner.View.Popup"; @NonNls static final String VIEW_TOOLBAR = "Runner.View.Toolbar"; + private ShowDebugContentAction myShowDebugContentAction = null; + private ContentManager myManager; private final RunnerLayout myLayoutSettings; @@ -320,6 +334,11 @@ public class RunnerContentUi implements ContentUI, Disposable, CellTransform.Fac DefaultActionGroup group = new DefaultActionGroup(VIEW_POPUP, original.isPopup()); + if (myShowDebugContentAction == null) { + myShowDebugContentAction = new ShowDebugContentAction(this, myTabs.getComponent(), this); + } + group.add(myShowDebugContentAction); + final AnActionEvent event = new AnActionEvent(null, DataManager.getInstance().getDataContext(), place, new Presentation(), ActionManager.getInstance(), 0); final AnAction[] originalActions = original.getChildren(event); @@ -585,6 +604,45 @@ public class RunnerContentUi implements ContentUI, Disposable, CellTransform.Fac return myCurrentOverImg; } + public void toggleContentPopup(JBTabs tabs) { + if (myOriginal != null) { + myOriginal.toggleContentPopup(tabs); + return; + } + + List contents = getPopupContents(); + final Content selectedContent = myManager.getSelectedContent(); + final SelectContentStep step = new SelectContentStep(contents); + if (selectedContent != null) { + step.setDefaultOptionIndex(myManager.getIndexOfContent(selectedContent)); + } + + final ListPopup popup = JBPopupFactory.getInstance().createListPopup(step); + popup.showUnderneathOf(tabs.getTabLabel(tabs.getSelectedInfo())); + + if (selectedContent instanceof TabbedContent) { + new Alarm(Alarm.ThreadToUse.SWING_THREAD, popup).addRequest(() -> popup.handleSelect(false), 50); + } + } + + public List getPopupContents() { + if (myOriginal != null) return myOriginal.getPopupContents(); + + List contents = new ArrayList<>(Arrays.asList(myManager.getContents())); + myChildren.stream() + .flatMap(child -> Arrays.stream(child.myManager.getContents())) + .forEachOrdered(contents::add); + + RunContentManager contentManager = RunContentManager.getInstance(myProject); + RunContentDescriptor selectedDescriptor = contentManager.getSelectedContent(); + + contentManager.getAllDescriptors().stream() + .filter(descriptor -> descriptor != selectedDescriptor) + .map(descriptor -> descriptor.getAttachedContent()) + .forEachOrdered(contents::add); + return contents; + } + @NotNull private static PlaceInGrid calcPlaceInGrid(Point point, Dimension size) { // 1/3 (left) | (center/bottom) | 1/3 (right) @@ -1889,6 +1947,34 @@ public class RunnerContentUi implements ContentUI, Disposable, CellTransform.Fac } } + public class ShowDebugContentAction extends AnAction implements DumbAware { + public static final String ACTION_ID = "ShowDebugContent"; + + private RunnerContentUi myContentUi; + + @SuppressWarnings({"UnusedDeclaration"}) + public ShowDebugContentAction() { + } + + public ShowDebugContentAction(RunnerContentUi runner, JComponent component, @NotNull Disposable parentDisposable) { + myContentUi = runner; + AnAction original = ActionManager.getInstance().getAction(ShowContentAction.ACTION_ID); + new ShadowAction(this, original, component, parentDisposable); + ActionUtil.copyFrom(this, ShowContentAction.ACTION_ID); + } + + @Override + public void update(@NotNull AnActionEvent e) { + e.getPresentation().setEnabledAndVisible(myContentUi != null && myContentUi.getPopupContents().size() > 1); + e.getPresentation().setText("Show List of Tabs"); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + myContentUi.toggleContentPopup(e.getData(NAVIGATION_ACTIONS_KEY)); + } + } + private void fireContentOpened(@NotNull Content content) { for (Listener each : myDockingListeners) { each.contentAdded(content); diff --git a/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java b/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java index b360b8c7d940..5a703ec77ed3 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java @@ -79,14 +79,17 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { @SuppressWarnings("Duplicates") public Component getComponentAfter(Container aContainer, Component aComponent) { Component comp; - if (aComponent == myFirstComponent) { - comp = findChildToFocus(myInnerComponent); + if (SwingUtilities.isDescendingFrom(aComponent, myFirstComponent)) { + Component next = nextVisible(myFirstComponent); + comp = (next != null) ? findChildToFocus(next) : aComponent; } - else if (aComponent == myInnerComponent) { - comp = findChildToFocus(myLastComponent); + else if (SwingUtilities.isDescendingFrom(aComponent, myInnerComponent)) { + Component next = nextVisible(myInnerComponent); + comp = (next != null) ? findChildToFocus(next) : aComponent; } else { - comp = findChildToFocus(myFirstComponent); + Component next = nextVisible(myLastComponent); + comp = (next != null) ? findChildToFocus(next) : aComponent; } if (comp == aComponent) { // if focus is stuck on the component let it go further @@ -99,14 +102,17 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { @SuppressWarnings("Duplicates") public Component getComponentBefore(Container aContainer, Component aComponent) { Component comp; - if (aComponent == myInnerComponent) { - comp = findChildToFocus(myFirstComponent); + if (SwingUtilities.isDescendingFrom(aComponent, myInnerComponent)) { + Component prev = prevVisible(myInnerComponent); + comp = (prev != null) ? findChildToFocus(prev) : aComponent; } - else if (aComponent == myLastComponent) { - comp = findChildToFocus(myInnerComponent); + else if (SwingUtilities.isDescendingFrom(aComponent, myLastComponent)) { + Component prev = prevVisible(myLastComponent); + comp = (prev != null) ? findChildToFocus(prev) : aComponent; } else { - comp = findChildToFocus(myFirstComponent); + Component prev = prevVisible(myFirstComponent); + comp = (prev != null) ? findChildToFocus(prev) : aComponent; } if (comp == aComponent) { // if focus is stuck on the component let it go further @@ -115,14 +121,33 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { return comp; } + private Component nextVisible(Component comp) { + if (comp == myFirstComponent) return innerVisible() ? myInnerComponent : lastVisible() ? myLastComponent : null; + if (comp == myInnerComponent) return lastVisible() ? myLastComponent : firstVisible() ? myFirstComponent : null; + if (comp == myLastComponent) return firstVisible() ? myFirstComponent : innerVisible() ? myInnerComponent : null; + return null; + } + + + private Component prevVisible(Component comp) { + if (comp == myFirstComponent) return lastVisible() ? myLastComponent : innerVisible() ? myInnerComponent : null; + if (comp == myInnerComponent) return firstVisible() ? myFirstComponent : lastVisible() ? myLastComponent : null; + if (comp == myLastComponent) return innerVisible() ? myInnerComponent : firstVisible() ? myFirstComponent : null; + return null; + } + @Override public Component getFirstComponent(Container aContainer) { - return findChildToFocus(myFirstComponent); + if (firstVisible()) return findChildToFocus(myFirstComponent); + Component next = nextVisible(myFirstComponent); + return next != null ? findChildToFocus(next) : null; } @Override public Component getLastComponent(Container aContainer) { - return findChildToFocus(myLastComponent); + if (lastVisible()) return findChildToFocus(myLastComponent); + Component prev = prevVisible(myLastComponent); + return prev != null ? findChildToFocus(prev) : null; } private boolean myReentrantLock = false; @@ -131,7 +156,9 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { if (myReentrantLock) return null; try { myReentrantLock = true; - return findChildToFocus(myInnerComponent); + if (innerVisible()) return findChildToFocus(myInnerComponent); + Component next = nextVisible(myLastComponent); + return next != null ? findChildToFocus(next) : null; } finally { myReentrantLock = false; } diff --git a/platform/platform-impl/src/com/intellij/ui/content/impl/ContentManagerImpl.java b/platform/platform-impl/src/com/intellij/ui/content/impl/ContentManagerImpl.java index d59ee8d7e94e..86d282ecc9e2 100644 --- a/platform/platform-impl/src/com/intellij/ui/content/impl/ContentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/content/impl/ContentManagerImpl.java @@ -433,7 +433,7 @@ public class ContentManagerImpl implements ContentManager, PropertyChangeListene mySelectionHistory.remove(content); mySelectionHistory.add(0, content); if (isSelected(content) && requestFocus) { - return requestFocus(content, forcedFocus); + return getFocusManager().requestFocus(getComponent(), true).doWhenProcessed(() -> requestFocus(content, forcedFocus)); } if (!checkSelectionChangeShouldBeProcessed(content, implicit)) { diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/memory/ui/ClassesFilteredViewBase.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/memory/ui/ClassesFilteredViewBase.java index 2274c7edcf49..54aaf9b1dbf4 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/memory/ui/ClassesFilteredViewBase.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/memory/ui/ClassesFilteredViewBase.java @@ -170,6 +170,10 @@ public abstract class ClassesFilteredViewBase extends BorderLayoutPanel implemen addToCenter(scroll); } + public JComponent getDefaultFocusedComponent() { + return myFilterTextField; + } + @NotNull protected ClassesTable createClassesTable(MemoryViewManagerState memoryViewManagerState) { return new ClassesTable(myProject,this, memoryViewManagerState.isShowWithDiffOnly,