New run/debug tool window UI (IJ-CR-10839):

* Added a new interface SingleContentSupplier.kt
* Added a new tool window header layout
* Clean up previous new debugger layout
* Implement new run layout

GitOrigin-RevId: 54e1277131e8463dcdf457131a674559eb22be1a
This commit is contained in:
maksim.pelevin
2021-06-30 11:42:46 +00:00
committed by intellij-monorepo-bot
parent 1f6cbe81b3
commit 1905e9712b
11 changed files with 878 additions and 184 deletions

View File

@@ -9,11 +9,15 @@ import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.ui.*;
import com.intellij.execution.ui.layout.PlaceInGrid;
import com.intellij.execution.ui.layout.impl.RunnerLayoutUiImpl;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.impl.ActionButton;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.impl.content.SingleContentSupplier;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.terminal.TerminalExecutionConsole;
import com.intellij.ui.content.Content;
@@ -38,6 +42,8 @@ public final class RunContentBuilder extends RunTab {
myUi.getOptions().setMoveToGridActionEnabled(false).setMinimizeActionEnabled(false);
}
private @Nullable SingleContentSupplier mySupplier;
@NotNull
public static ExecutionEnvironment fix(@NotNull ExecutionEnvironment environment, @Nullable ProgramRunner runner) {
if (runner == null || runner.equals(environment.getRunner())) {
@@ -82,7 +88,17 @@ public final class RunContentBuilder extends RunTab {
// clear console toolbar actions to remove the console toolbar
consoleContent.setActions(new DefaultActionGroup(), ActionPlaces.RUNNER_TOOLBAR, console.getComponent());
}
myUi.getOptions().setLeftToolbar(createActionToolbar(contentDescriptor, consoleActionsToMerge), ActionPlaces.RUNNER_TOOLBAR);
ActionGroup toolbar = createActionToolbar(contentDescriptor, consoleActionsToMerge);
if (Registry.is("debugger.new.tool.window.layout")) {
mySupplier = new RunTabSupplier(toolbar);
if (myUi instanceof RunnerLayoutUiImpl) {
((RunnerLayoutUiImpl)myUi).setLeftToolbarVisible(false);
}
myUi.getOptions().setTopLeftToolbar(toolbar, ActionPlaces.RUNNER_TOOLBAR);
} else {
myUi.getOptions().setLeftToolbar(toolbar, ActionPlaces.RUNNER_TOOLBAR);
}
if (profile instanceof RunConfigurationBase) {
if (console instanceof ObservableConsoleView && !ApplicationManager.getApplication().isUnitTestMode()) {
@@ -97,6 +113,11 @@ public final class RunContentBuilder extends RunTab {
return contentDescriptor;
}
@Override
protected @Nullable SingleContentSupplier getSupplier() {
return mySupplier;
}
@NotNull
private static String getRunnerType(@Nullable ExecutionConsole console) {
if (console instanceof ExecutionConsoleEx) {
@@ -134,12 +155,16 @@ public final class RunContentBuilder extends RunTab {
@NotNull
private ActionGroup createActionToolbar(@NotNull RunContentDescriptor contentDescriptor, AnAction @NotNull [] consoleActions) {
boolean isNewLayout = Registry.is("debugger.new.tool.window.layout");
final DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN));
final AnAction[] actions = contentDescriptor.getRestartActions();
actionGroup.addAll(actions);
actionGroup.add(new CreateAction(AllIcons.General.Settings));
actionGroup.addSeparator();
if (!isNewLayout) {
actionGroup.add(new CreateAction(AllIcons.General.Settings));
actionGroup.addSeparator();
}
actionGroup.add(ActionManager.getInstance().getAction(IdeActions.ACTION_STOP_PROGRAM));
actionGroup.addAll(myExecutionResult.getActions());
@@ -148,6 +173,10 @@ public final class RunContentBuilder extends RunTab {
actionGroup.addAll(consoleActions);
}
if (isNewLayout) {
actionGroup.addSeparator();
}
for (AnAction anAction : myRunnerActions) {
if (anAction != null) {
actionGroup.add(anAction);
@@ -157,10 +186,27 @@ public final class RunContentBuilder extends RunTab {
}
}
actionGroup.addSeparator();
actionGroup.add(myUi.getOptions().getLayoutActions());
actionGroup.addSeparator();
actionGroup.add(PinToolwindowTabAction.getPinAction());
if (!isNewLayout) {
actionGroup.addSeparator();
actionGroup.add(myUi.getOptions().getLayoutActions());
actionGroup.addSeparator();
actionGroup.add(PinToolwindowTabAction.getPinAction());
} else {
DefaultActionGroup more = new DefaultActionGroup() {
{
setPopup(true);
getTemplatePresentation().setIcon(AllIcons.Actions.More);
getTemplatePresentation().putClientProperty(ActionButton.HIDE_DROPDOWN_ICON, true);
}
@Override
public boolean isDumbAware() {
return true;
}
};
more.add(myUi.getOptions().getLayoutActions());
actionGroup.add(more);
}
return actionGroup;
}

View File

@@ -4,24 +4,34 @@ package com.intellij.execution.runners;
import com.intellij.diagnostic.logging.LogConsoleManagerBase;
import com.intellij.diagnostic.logging.LogFilesManager;
import com.intellij.diagnostic.logging.OutputFileUtil;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.execution.ui.RunnerLayoutUi;
import com.intellij.execution.ui.layout.impl.GridImpl;
import com.intellij.execution.ui.layout.impl.RunnerContentUi;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.impl.ActionButton;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.impl.content.SingleContentSupplier;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScopes;
import com.intellij.ui.content.Content;
import com.intellij.ui.tabs.JBTabs;
import com.intellij.ui.tabs.JBTabsEx;
import com.intellij.ui.tabs.TabInfo;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
import java.util.Objects;
public abstract class RunTab implements DataProvider, Disposable {
@NotNull
@@ -71,10 +81,17 @@ public abstract class RunTab implements DataProvider, Disposable {
}
else if (LangDataKeys.RUN_CONTENT_DESCRIPTOR.is(dataId)) {
return myRunContentDescriptor;
} else if (SingleContentSupplier.KEY.is(dataId)) {
return getSupplier();
}
return null;
}
@Nullable
protected SingleContentSupplier getSupplier() {
return null;
}
@NotNull
public LogConsoleManagerBase getLogConsoleManager() {
if (logConsoleManager == null) {
@@ -111,4 +128,80 @@ public abstract class RunTab implements DataProvider, Disposable {
}
}
}
/**
* Default implementation of {@link SingleContentSupplier}.
*
* Isn't used directly by {@link RunTab}, but can be used by inheritors.
*/
protected class RunTabSupplier implements SingleContentSupplier {
@Nullable
private final ActionGroup myActionGroup;
public RunTabSupplier(@Nullable ActionGroup group) {
myActionGroup = group;
}
@NotNull
@Override
public JBTabs getTabs() {
RunnerContentUi contentUi = RunnerContentUi.KEY.getData((DataProvider)myUi);
return Objects.requireNonNull(contentUi).getTabs();
}
@Nullable
@Override
public ActionGroup getToolbarActions() {
return myActionGroup;
}
@NotNull
@Override
public List<AnAction> getContentActions() {
var layout = new ActionGroup(ExecutionBundle.messagePointer("runner.content.tooltip.layout.settings"), () -> "", AllIcons.Debugger.RestoreLayout) {
@Override
public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) {
RunnerContentUi contentUi = RunnerContentUi.KEY.getData((DataProvider)myUi);
return Objects.requireNonNull(contentUi).getViewActions();
}
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabledAndVisible(getChildren(null).length > 0);
}
@Override
public boolean isDumbAware() {
return true;
}
};
layout.setPopup(true);
layout.getTemplatePresentation().putClientProperty(ActionButton.HIDE_DROPDOWN_ICON, Boolean.TRUE);
return List.of(layout);
}
@Override
public void init(@Nullable ActionToolbar toolbar) {
JBTabs tabs = getTabs();
if (tabs instanceof JBTabsEx) {
((JBTabsEx)tabs).setHideTopPanel(true);
}
}
@Override
public void reset() {
JBTabs tabs = getTabs();
if (tabs instanceof JBTabsEx) {
((JBTabsEx)tabs).setHideTopPanel(false);
}
}
@Override
public boolean isClosable(@NotNull TabInfo tab) {
List<Content> gridContents = ((GridImpl)tab.getComponent()).getContents();
return gridContents.size() > 0 && gridContents.get(0).isCloseable();
}
}
}

View File

@@ -47,13 +47,10 @@ import com.intellij.ui.docking.DockableContent;
import com.intellij.ui.docking.DragSession;
import com.intellij.ui.docking.impl.DockManagerImpl;
import com.intellij.ui.switcher.QuickActionProvider;
import com.intellij.ui.tabs.JBTabPainter;
import com.intellij.ui.tabs.JBTabs;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsListener;
import com.intellij.ui.tabs.impl.DefaultTabPainterAdapter;
import com.intellij.ui.tabs.impl.JBTabsImpl;
import com.intellij.ui.tabs.impl.TabPainterAdapter;
import com.intellij.util.Alarm;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
@@ -251,14 +248,7 @@ public final class RunnerContentUi implements ContentUI, Disposable, CellTransfo
private void initUi() {
if (myTabs != null) return;
myTabs = new JBRunnerTabs(myProject, this) {
@Override
protected TabPainterAdapter createTabPainterAdapter() {
return Registry.is("debugger.new.tool.window.layout")
? new DefaultTabPainterAdapter(JBTabPainter.getTOOL_WINDOW())
: super.createTabPainterAdapter();
}
};
myTabs = new JBRunnerTabs(myProject, this);
myTabs.getComponent().setOpaque(false);
myTabs.setDataProvider(dataId -> {
if (ViewContext.CONTENT_KEY.is(dataId)) {
@@ -480,6 +470,13 @@ public final class RunnerContentUi implements ContentUI, Disposable, CellTransfo
return myWindow;
}
@NotNull
public JBTabs getTabs() { return myTabs; }
public AnAction @NotNull[] getViewActions() {
return myViewActions.getChildren(null);
}
@Override
public void propertyChange(final @NotNull PropertyChangeEvent evt) {
Content content = (Content)evt.getSource();
@@ -948,7 +945,7 @@ public final class RunnerContentUi implements ContentUI, Disposable, CellTransfo
NonOpaquePanel sideComponent = new TwoSideComponent(leftWrapper, new TwoSideComponent(middleWrapper, new TwoSideComponent(right, rightWrapper)));
sideComponent.setVisible(!myTabs.isHideTopPanel());
tab.setSideComponent(sideComponent);
tab.setTabLabelActions((ActionGroup)myActionManager.getAction(VIEW_TOOLBAR), TAB_TOOLBAR_PLACE);

View File

@@ -41,4 +41,8 @@ public interface JBTabsEx extends JBTabs {
void updateTabsLayout(@NotNull TabsLayoutInfo newTabsLayoutInfo);
void setTitleProducer(@Nullable Producer<Pair<Icon, String>> titleProducer);
void setHideTopPanel(boolean isHideTopPanel);
boolean isHideTopPanel();
}

View File

@@ -137,6 +137,7 @@ public class JBTabsImpl extends JComponent
private boolean myPaintFocus;
private boolean myHideTabs;
private boolean myHideTopPanel;
@Nullable private Project myProject;
@NotNull private final Disposable myParentDisposable;
@@ -1185,7 +1186,7 @@ public class JBTabsImpl extends JComponent
return myPopupGroup != null ? myPopupGroup.get() : null;
}
String getPopupPlace() {
public String getPopupPlace() {
return myPopupPlace;
}
@@ -2516,7 +2517,7 @@ public class JBTabsImpl extends JComponent
@Override
public boolean isHideTabs() {
return myHideTabs;
return myHideTabs || myHideTopPanel;
}
@Override
@@ -2528,6 +2529,27 @@ public class JBTabsImpl extends JComponent
relayout(true, false);
}
/**
* @param hideTopPanel true if tabs and top toolbar should be hidden from a view
*/
@Override
public void setHideTopPanel(boolean hideTopPanel) {
if (isHideTopPanel() == hideTopPanel) return;
myHideTopPanel = hideTopPanel;
getTabs().stream()
.map(TabInfo::getSideComponent)
.forEach(component -> component.setVisible(!myHideTopPanel));
relayout(true, true);
}
@Override
public boolean isHideTopPanel() {
return myHideTopPanel;
}
@Override
public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) {
return this;
@@ -2934,6 +2956,10 @@ public class JBTabsImpl extends JComponent
return result;
}
public ActionGroup getNavigationActions() {
return myNavigationActions;
}
@Override
public DataProvider getDataProvider() {
return myDataProvider;

View File

@@ -137,6 +137,13 @@ class ContentTabLabel extends ContentLabel {
}
}
protected void closeContent() {
ContentManager contentManager = myUi.window.getContentManagerIfCreated();
if (contentManager != null) {
contentManager.removeContent(myContent, true);
}
}
public void update() {
setHorizontalAlignment(SwingConstants.LEFT);
if (myLayout.isToDrawTabs() == TabContentLayout.TabsDrawMode.HIDE) {
@@ -212,10 +219,7 @@ class ContentTabLabel extends ContentLabel {
content.setPinned(false);
return;
}
ContentManager contentManager = myUi.window.getContentManagerIfCreated();
if (contentManager != null) {
contentManager.removeContent(content, true);
}
closeContent();
}
@Override

View File

@@ -0,0 +1,596 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.wm.impl.content
import com.intellij.CommonBundle
import com.intellij.icons.AllIcons
import com.intellij.ide.impl.DataManagerImpl
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.JBPopupMenu
import com.intellij.openapi.util.BusyObject
import com.intellij.openapi.util.Computable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.ui.PopupHandler
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.components.panels.HorizontalLayout
import com.intellij.ui.components.panels.NonOpaquePanel
import com.intellij.ui.content.AlertIcon
import com.intellij.ui.content.Content
import com.intellij.ui.content.ContentManager
import com.intellij.ui.tabs.*
import com.intellij.ui.tabs.impl.JBTabsImpl
import com.intellij.ui.tabs.impl.TabLabel
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.ui.JBUI
import java.awt.*
import java.awt.event.ContainerEvent
import java.awt.event.ContainerListener
import java.beans.PropertyChangeEvent
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
import javax.swing.Icon
import javax.swing.JComponent
/**
* Tool window header that shows tabs and actions from its content.
*
* If toolwindow [Content] returns [SingleContentSupplier] as a data provider
* via [SingleContentSupplier.KEY] that in case of single content view
* all [JBTabs] and actions are moved into this header.
*
* When two or more contents exist then header looks like [TabContentLayout].
*/
internal class SingleContentLayout(
ui: ToolWindowContentUi
) : TabContentLayout(ui) {
private var isSingleContentView: Boolean = false
private var tabAdapter: TabAdapter? = null
private val toolbars = mutableMapOf<ActionToolbarPosition, ActionToolbar>()
private val wrapper = NonOpaquePanel(HorizontalLayout(0))
override fun update() {
super.update()
tryUpdateContentView()
}
override fun rebuild() {
super.rebuild()
tryUpdateContentView()
}
private fun Content.getSupplier(): SingleContentSupplier? {
return (component as? DataProvider)?.let(SingleContentSupplier.KEY::getData)
}
private fun getSingleContentOrNull(): Content? {
return if (myTabs.size == 1) myTabs[0].content else null
}
private fun tryUpdateContentView() {
val currentContent = getSingleContentOrNull()
val contentSupplier = currentContent?.getSupplier()
if (contentSupplier != null) {
if (isSingleContentView) {
updateSingleContentView(currentContent, contentSupplier)
}
else {
initSingleContentView(currentContent, contentSupplier)
}
}
else if (isSingleContentView) {
resetSingleContentView()
}
}
private fun initSingleContentView(content: Content, supplier: SingleContentSupplier) {
tabAdapter = TabAdapter(content, supplier.getTabs(), tabPainter, myUi).also {
Disposer.register(content, it)
myUi.tabComponent.add(it)
}
assert(toolbars.isEmpty())
supplier.getToolbarActions()?.let { mainActionGroup ->
toolbars[ActionToolbarPosition.LEFT] = createToolbar(
"MainSingleContentToolbar",
mainActionGroup,
content.component
)
}
let {
toolbars[ActionToolbarPosition.RIGHT] = createToolbar(
"CloseSingleContentGroup",
DefaultActionGroup(emptyList<AnAction>() + CloseCurrentContentAction() + supplier.getContentActions()),
content.component
).apply {
setReservePlaceAutoPopupIcon(false)
}
}
toolbars.forEach { (_, toolbar) -> myUi.tabComponent.add(toolbar.component) }
wrapper.removeAll()
ToolWindowContentUi.initMouseListeners(wrapper, myUi, true)
myUi.tabComponent.add(wrapper)
isSingleContentView = true
supplier.init(toolbars[ActionToolbarPosition.LEFT])
supplier.customize(wrapper)
}
private fun updateSingleContentView(content: Content, supplier: SingleContentSupplier) {
if (tabAdapter?.jbTabs != supplier.getTabs()) {
// in case of 'reusing content' just revoke old view and create new one
resetSingleContentView()
initSingleContentView(content, supplier)
}
else {
toolbars.forEach { (_, toolbar) ->
toolbar.updateActionsImmediately()
}
supplier.customize(wrapper)
myUi.tabComponent.repaint()
}
}
private fun resetSingleContentView() {
val adapter = tabAdapter ?: error("Adapter must not be null")
tabAdapter = null
myUi.tabComponent.remove(adapter)
Disposer.dispose(adapter)
toolbars.values.forEach {
myUi.tabComponent.remove(it.component)
}
toolbars.clear()
myUi.tabComponent.remove(wrapper)
wrapper.removeAll()
isSingleContentView = false
adapter.content.getSupplier()?.reset()
}
private fun createToolbar(place: String, group: ActionGroup, target: JComponent? = null): ActionToolbar {
val toolbar = ActionManager.getInstance().createActionToolbar(place, group, true)
toolbar.setTargetComponent(target)
toolbar.component.isOpaque = false
return toolbar
}
override fun isToDrawTabs(): TabsDrawMode {
return if (isSingleContentView) TabsDrawMode.HIDE else super.isToDrawTabs()
}
override fun layout() {
super.layout()
if (isSingleContentView) {
val component = myUi.tabComponent
component.bounds = component.bounds.apply { width = component.parent.width }
var toolbarWidth = myIdLabel.x + myIdLabel.width
tabAdapter?.apply {
bounds = Rectangle(toolbarWidth, 0, preferredSize.width, component.height)
toolbarWidth += bounds.width
}
val rightToolbarWidth = toolbars[ActionToolbarPosition.RIGHT]?.component?.run {
bounds = Rectangle(component.width - preferredSize.width, 0, preferredSize.width, component.height)
bounds.width
} ?: 0
toolbars[ActionToolbarPosition.LEFT]?.component?.apply {
bounds = Rectangle(
toolbarWidth,
0,
minOf(preferredSize.width, component.width - rightToolbarWidth - toolbarWidth),
component.height
)
toolbarWidth += bounds.width
}
wrapper.bounds = Rectangle(toolbarWidth, 0, maxOf(component.width - rightToolbarWidth - toolbarWidth, 0), component.height)
}
}
override fun updateIdLabel(label: BaseLabel) {
if (!isSingleContentView) {
label.icon = null
super.updateIdLabel(label)
}
else if (myTabs.size == 1) {
label.icon = myTabs[0].content.icon
label.text = myTabs[0].content.displayName
label.border = JBUI.Borders.empty(0, 2, 0, 7)
}
}
private inner class TabAdapter(
val content: Content,
val jbTabs: JBTabs,
val tabPainter: JBTabPainter,
val twcui: ToolWindowContentUi
) : NonOpaquePanel(HorizontalLayout(0)), TabsListener, PropertyChangeListener, Disposable {
val labels = mutableListOf<MyContentTabLabel>()
val popupHandler = object : PopupHandler() {
override fun invokePopup(comp: Component, x: Int, y: Int) = showPopup(comp, x, y)
}
val containerListener = object : ContainerListener {
override fun componentAdded(e: ContainerEvent) = update(e)
override fun componentRemoved(e: ContainerEvent) = update(e)
private fun update(e: ContainerEvent) {
if (e.child is TabLabel) {
checkAndUpdate()
}
}
}
init {
updateTabs()
jbTabs.addListener(object : TabsListener {
override fun selectionChanged(oldSelection: TabInfo?, newSelection: TabInfo?) = checkAndUpdate()
override fun tabRemoved(tabToRemove: TabInfo) = checkAndUpdate()
override fun tabsMoved() = checkAndUpdate()
}, this)
jbTabs.component.addContainerListener(containerListener)
}
private fun retrieveInfo(label: MyContentTabLabel): TabInfo {
return label.content.info
}
private fun checkAndUpdate() {
val currentContent = getSingleContentOrNull()
val currentSupplier = currentContent?.getSupplier()
val src = currentSupplier?.getTabs()?.tabs ?: return
val dst = labels.map(::retrieveInfo)
if (!ContainerUtil.equalsIdentity(src, dst)) {
updateTabs()
}
updateSingleContentView(currentContent, currentSupplier)
updateLabels(labels)
}
fun updateTabs() {
labels.removeAll {
retrieveInfo(it).changeSupport.removePropertyChangeListener(this)
remove(it)
true
}
val supplier = getSingleContentOrNull()?.getSupplier() ?: return
if (jbTabs.tabs.size > 1) {
labels.addAll(jbTabs.tabs.map { info ->
info.changeSupport.addPropertyChangeListener(this)
MyContentTabLabel(FakeContent(supplier, info), this@SingleContentLayout)
})
labels.forEach(::add)
}
updateLabels(labels)
}
fun updateLabels(labels: List<MyContentTabLabel>) {
labels.associateBy { it.content.info }.forEach(::copyValues)
}
override fun dispose() {
labels.forEach {
retrieveInfo(it).changeSupport.removePropertyChangeListener(this)
}
jbTabs.component.removeContainerListener(containerListener)
}
fun copyValues(from: TabInfo, to: ContentLabel) {
to.icon = from.icon
to.text = from.text
}
fun showPopup(component: Component, x: Int, y: Int) {
// ViewContext.CONTENT_KEY
val info = ((component as? ContentTabLabel)?.content as? FakeContent)?.info
if (info == null) {
logger<SingleContentLayout>().warn("Cannot resolve label popup component. Event will be ignored")
return
}
val supplier = getSingleContentOrNull()?.getSupplier()
val toShow = getPopupMenu(supplier?.getTabs()) ?: return
toShow.setTargetComponent(component)
JBPopupMenu.showAt(RelativePoint(component, Point(x, y)), toShow.component)
}
private fun getPopupMenu(tabs: JBTabs?): ActionPopupMenu? {
val jbTabsImpl = tabs as? JBTabsImpl ?: return null
val popup = jbTabsImpl.popupGroup ?: return null
val popupPlace = jbTabsImpl.popupPlace ?: ActionPlaces.UNKNOWN
val group = DefaultActionGroup()
group.addAll(popup)
group.addSeparator()
group.addAll(jbTabsImpl.navigationActions)
return ActionManager.getInstance().createActionPopupMenu(popupPlace, group)
}
override fun paintComponent(g: Graphics) {
if (g is Graphics2D) {
labels.forEach { comp ->
val labelBounds = comp.bounds
if (jbTabs.selectedInfo == retrieveInfo(comp)) {
tabPainter.paintSelectedTab(JBTabsPosition.top, g, labelBounds, 1, null, twcui.window.isActive, comp.isHovered)
}
else {
tabPainter.paintTab(JBTabsPosition.top, g, labelBounds, 1, null, twcui.window.isActive, comp.isHovered)
}
}
}
super.paintComponent(g)
}
override fun propertyChange(evt: PropertyChangeEvent) {
val source = evt.source as? TabInfo ?: error("Bad event source")
val label = labels.find { it.content.info == source } ?: error("Cannot find label for $source")
copyValues(source, label)
}
private inner class MyContentTabLabel(content: FakeContent, layout: TabContentLayout) : ContentTabLabel(content, layout), DataProvider {
init {
addMouseListener(popupHandler)
}
override fun selectContent() {
retrieveInfo(this).let { jbTabs.select(it, true) }
}
override fun closeContent() {
retrieveInfo(this).let(jbTabs::removeTab)
}
override fun getData(dataId: String): Any? {
if (JBTabsEx.NAVIGATION_ACTIONS_KEY.`is`(dataId)) {
return jbTabs
}
return DataManagerImpl.getDataProviderEx(retrieveInfo(this).component)?.getData(dataId)
}
override fun getContent(): FakeContent {
return super.getContent() as FakeContent
}
}
}
private inner class CloseCurrentContentAction : DumbAwareAction(CommonBundle.messagePointer("action.close"), AllIcons.Actions.Cancel) {
override fun actionPerformed(e: AnActionEvent) {
getSingleContentOrNull()?.let { it.manager?.removeContent(it, true) }
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = getSingleContentOrNull()?.isCloseable == true
}
}
/**
* The minimal [Content] implementation.
*
* Is used only to pass as an argument to support [SingleContentLayout.TabAdapter.MyContentTabLabel].
*
* All unused methods throw [IllegalStateException].
*/
private class FakeContent(val supplier: SingleContentSupplier, val info: TabInfo) : Content {
private val pcs = PropertyChangeSupport(this)
override fun addPropertyChangeListener(l: PropertyChangeListener?) {
pcs.addPropertyChangeListener(l)
}
override fun removePropertyChangeListener(l: PropertyChangeListener?) {
pcs.removePropertyChangeListener(l)
}
override fun isSelected(): Boolean {
return supplier.getTabs().selectedInfo == info
}
override fun isCloseable(): Boolean {
return supplier.isClosable(info)
}
override fun isPinned(): Boolean {
return false
}
override fun getManager(): ContentManager? {
error("An operation is not supported")
}
override fun <T : Any?> getUserData(key: Key<T>): T? {
error("An operation is not supported")
}
override fun <T : Any?> putUserData(key: Key<T>, value: T?) {
error("An operation is not supported")
}
override fun dispose() {
error("An operation is not supported")
}
override fun getComponent(): JComponent {
error("An operation is not supported")
}
override fun getPreferredFocusableComponent(): JComponent? {
error("An operation is not supported")
}
override fun setComponent(component: JComponent?) {
error("An operation is not supported")
}
override fun setPreferredFocusableComponent(component: JComponent?) {
error("An operation is not supported")
}
override fun setPreferredFocusedComponent(computable: Computable<out JComponent>?) {
error("An operation is not supported")
}
override fun setIcon(icon: Icon?) {
error("An operation is not supported")
}
override fun getIcon(): Icon? {
error("An operation is not supported")
}
override fun setDisplayName(displayName: String?) {
error("An operation is not supported")
}
override fun getDisplayName(): String? {
error("An operation is not supported")
}
override fun setTabName(tabName: String?) {
error("An operation is not supported")
}
override fun getTabName(): String {
error("An operation is not supported")
}
override fun getToolwindowTitle(): String {
error("An operation is not supported")
}
override fun setToolwindowTitle(toolwindowTitle: String?) {
error("An operation is not supported")
}
override fun getDisposer(): Disposable? {
error("An operation is not supported")
}
override fun setDisposer(disposer: Disposable) {
error("An operation is not supported")
}
override fun setShouldDisposeContent(value: Boolean) {
error("An operation is not supported")
}
override fun getDescription(): String? {
error("An operation is not supported")
}
override fun setDescription(description: String?) {
error("An operation is not supported")
}
override fun release() {
error("An operation is not supported")
}
override fun isValid(): Boolean {
error("An operation is not supported")
}
override fun setPinned(locked: Boolean) {
error("An operation is not supported")
}
override fun setPinnable(pinnable: Boolean) {
error("An operation is not supported")
}
override fun isPinnable(): Boolean {
error("An operation is not supported")
}
override fun setCloseable(closeable: Boolean) {
error("An operation is not supported")
}
override fun setActions(actions: ActionGroup?, place: String?, contextComponent: JComponent?) {
error("An operation is not supported")
}
override fun getActions(): ActionGroup? {
error("An operation is not supported")
}
override fun setSearchComponent(comp: JComponent?) {
error("An operation is not supported")
}
override fun getSearchComponent(): JComponent? {
error("An operation is not supported")
}
override fun getPlace(): String {
error("An operation is not supported")
}
override fun getActionsContextComponent(): JComponent? {
error("An operation is not supported")
}
override fun setAlertIcon(icon: AlertIcon?) {
error("An operation is not supported")
}
override fun getAlertIcon(): AlertIcon? {
error("An operation is not supported")
}
override fun fireAlert() {
error("An operation is not supported")
}
override fun getBusyObject(): BusyObject? {
error("An operation is not supported")
}
override fun setBusyObject(`object`: BusyObject?) {
error("An operation is not supported")
}
override fun getSeparator(): String {
error("An operation is not supported")
}
override fun setSeparator(separator: String?) {
error("An operation is not supported")
}
override fun setPopupIcon(icon: Icon?) {
error("An operation is not supported")
}
override fun getPopupIcon(): Icon? {
error("An operation is not supported")
}
override fun setExecutionId(executionId: Long) {
error("An operation is not supported")
}
override fun getExecutionId(): Long {
error("An operation is not supported")
}
}
}

View File

@@ -0,0 +1,77 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.wm.impl.content
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionToolbar
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.DataKey
import com.intellij.ui.tabs.JBTabs
import com.intellij.ui.tabs.TabInfo
import javax.swing.JComponent
/**
* Describes tabs and toolbar for the [SingleContentLayout].
*/
interface SingleContentSupplier {
/**
* Tabs will be copied into toolwindow header and managed by [SingleContentLayout].
*/
fun getTabs() : JBTabs
/**
* Toolbar follows after the tabs.
*
* By default, current toolwindow content is used for [ActionToolbar.setTargetComponent].
* Toolbars can be adjusted in [init].
*/
@JvmDefault
fun getToolbarActions() : ActionGroup? {
return null
}
/**
* Actions after close action.
*/
@JvmDefault
fun getContentActions() : List<AnAction> {
return emptyList()
}
/**
* Defines if a tab from [getTabs] can be closed.
*/
@JvmDefault
fun isClosable(tab: TabInfo) : Boolean {
return false
}
/**
* This method is called after a single view mode is activated.
*
* @param toolbar main toolbar that can be customized, e.g. [ActionToolbar.setTargetComponent]
*/
fun init(toolbar: ActionToolbar?) {
}
/**
* This method is called to customize wrappers after tabs are changed or new view is set.
*
* @param wrapper additional empty panel between toolbar and close action where something can be put
*/
@JvmDefault
fun customize(wrapper: JComponent) {
}
/**
* This method is called after a single view mode was revoked.
*/
@JvmDefault
fun reset() {
}
companion object {
@JvmField
val KEY = DataKey.create<SingleContentSupplier>("ToolbarContentSupplier")
}
}

View File

@@ -31,7 +31,7 @@ import java.awt.event.MouseEvent;
import java.util.List;
import java.util.*;
final class TabContentLayout extends ContentLayout implements MorePopupAware {
class TabContentLayout extends ContentLayout implements MorePopupAware {
static final int MORE_ICON_BORDER = 6;
public static final int TAB_LAYOUT_START = 4;
LayoutData myLastLayout;
@@ -288,7 +288,7 @@ final class TabContentLayout extends ContentLayout implements MorePopupAware {
}
}
private final JBTabPainter tabPainter = JBTabPainter.getTOOL_WINDOW();
protected final JBTabPainter tabPainter = JBTabPainter.getTOOL_WINDOW();
@Override
public void paintComponent(Graphics g) {

View File

@@ -86,7 +86,7 @@ public final class ToolWindowContentUi implements ContentUI, DataProvider {
@NotNull JPanel contentComponent) {
this.contentManager = contentManager;
type = window.getWindowInfo().getContentUiType();
tabsLayout = new TabContentLayout(this);
tabsLayout = new SingleContentLayout(this);
this.window = window;
this.contentComponent = contentComponent;

View File

@@ -3,33 +3,22 @@ package com.intellij.xdebugger.impl.ui
import com.intellij.debugger.ui.DebuggerContentInfo
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.runners.ExecutionUtil
import com.intellij.execution.ui.layout.LayoutAttractionPolicy
import com.intellij.execution.ui.layout.PlaceInGrid
import com.intellij.execution.ui.layout.impl.RunnerLayoutUiImpl
import com.intellij.icons.AllIcons
import com.intellij.ide.actions.TabListAction
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.ui.PersistentThreeComponentSplitter
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Pair.create
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.wm.ToolWindowId
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.ToolWindowType
import com.intellij.openapi.wm.ex.ToolWindowEx
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.ui.JBColor
import com.intellij.ui.content.ContentManagerEvent
import com.intellij.ui.content.ContentManagerListener
import com.intellij.util.Producer
import com.intellij.util.ui.JBUI
import com.intellij.openapi.wm.impl.content.SingleContentSupplier
import com.intellij.util.ui.UIUtil
import com.intellij.xdebugger.*
import com.intellij.xdebugger.XDebuggerBundle
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.actions.XDebuggerActions
import com.intellij.xdebugger.impl.frame.*
@@ -39,8 +28,6 @@ import java.awt.Component
import java.awt.Container
import javax.swing.Icon
import javax.swing.LayoutFocusTraversalPolicy
import javax.swing.event.AncestorEvent
import javax.swing.event.AncestorListener
//TODO: unify with XDebugSessionTab2
class XDebugSessionTab3(
@@ -70,86 +57,7 @@ class XDebugSessionTab3(
private val focusTraversalPolicy = MyFocusTraversalPolicy()
init {
// value from com.intellij.execution.ui.layout.impl.GridImpl
splitter.setMinSize(48)
splitter.isFocusCycleRoot = true
splitter.isFocusTraversalPolicyProvider = true
splitter.focusTraversalPolicy = focusTraversalPolicy
session.addSessionListener(object : XDebugSessionListener {
override fun sessionStopped() {
UIUtil.invokeLaterIfNeeded {
splitter.saveProportions()
Disposer.dispose(lifetime)
}
}
})
project.messageBus.connect(lifetime).subscribe(XDebuggerManager.TOPIC, object : XDebuggerManagerListener {
override fun processStarted(debugProcess: XDebugProcess) {
UIUtil.invokeLaterIfNeeded {
if (debugProcess.session != null && debugProcess.session != session) {
splitter.saveProportions()
}
}
}
override fun currentSessionChanged(previousSession: XDebugSession?, currentSession: XDebugSession?) {
UIUtil.invokeLaterIfNeeded {
if (previousSession == session) {
splitter.saveProportions()
//xThreadsFramesView.saveUiState()
}
else if (currentSession == session)
splitter.restoreProportions()
}
}
override fun processStopped(debugProcess: XDebugProcess) {
UIUtil.invokeLaterIfNeeded {
splitter.saveProportions()
//xThreadsFramesView.saveUiState()
if (debugProcess.session == session)
Disposer.dispose(lifetime)
}
}
})
val ancestorListener = object : AncestorListener {
override fun ancestorAdded(event: AncestorEvent?) {
if (XDebuggerManager.getInstance(project).currentSession == session) {
splitter.restoreProportions()
}
}
override fun ancestorRemoved(event: AncestorEvent?) {
if (XDebuggerManager.getInstance(project).currentSession == session) {
splitter.saveProportions()
//xThreadsFramesView.saveUiState()
}
}
override fun ancestorMoved(event: AncestorEvent?) {
}
}
toolWindow?.component?.addAncestorListener(ancestorListener)
Disposer.register(lifetime, Disposable {
toolWindow?.component?.removeAncestorListener(ancestorListener)
})
var oldToolWindowType: ToolWindowType? = null
project.messageBus.connect(lifetime).subscribe(ToolWindowManagerListener.TOPIC, object : ToolWindowManagerListener {
override fun stateChanged(toolWindowManager: ToolWindowManager) {
if (oldToolWindowType == toolWindow?.type) return
setHeaderState()
oldToolWindowType = toolWindow?.type
}
})
}
private var mySingleContentSupplier: SingleContentSupplier? = null
override fun getWatchesContentId() = debuggerContentId
override fun getFramesContentId() = debuggerContentId
@@ -207,25 +115,6 @@ class XDebugSessionTab3(
myUi.addContent(content, 0, PlaceInGrid.left, false)
ui.defaults.initContentAttraction(debuggerContentId, XDebuggerUIConstants.LAYOUT_VIEW_BREAKPOINT_CONDITION, LayoutAttractionPolicy.FocusOnce())
toolWindow?.let {
val contentManager = it.contentManager
val listener = object : ContentManagerListener {
override fun contentAdded(event: ContentManagerEvent) {
setHeaderState()
}
override fun contentRemoved(event: ContentManagerEvent) {
setHeaderState()
}
}
contentManager.addContentManagerListener(listener)
Disposer.register(lifetime, Disposable {
contentManager.removeContentManagerListener(listener)
})
}
setHeaderState()
}
private fun getComponents(): Iterator<Component> {
return iterator {
@@ -261,52 +150,14 @@ class XDebugSessionTab3(
myUi.options.setTopLeftToolbar(toolbar, ActionPlaces.DEBUGGER_TOOLBAR)
myUi.options.setTitleProducer(Producer {
val icon = if (session.isStopped) session.runProfile?.icon else ExecutionUtil.getLiveIndicator(session.runProfile?.icon)
return@Producer create(icon, StringUtil.shortenTextWithEllipsis(session.sessionName, 15, 0) + ":")
})
mySingleContentSupplier = RunTabSupplier(toolbar)
val settings = DefaultActionGroup(*myUi.options.settingsActionsList)
registerAdditionalActions(DefaultActionGroup(), DefaultActionGroup(), settings)
(toolWindow as ToolWindowEx).setAdditionalGearActions(settings)
}
private fun setHeaderState() {
toolWindow?.let { toolWindow ->
if (toolWindow !is ToolWindowEx) return@let
val singleContent = toolWindow.contentManager.contents.singleOrNull()
val headerVisible = toolWindow.isHeaderVisible
val topRightToolbar = DefaultActionGroup().apply {
if (headerVisible) return@apply
addAll(toolWindow.decorator.headerToolbar.actions.filter { it != null && it !is TabListAction })
}
myUi.options.setTopRightToolbar(topRightToolbar, ActionPlaces.DEBUGGER_TOOLBAR)
val topMiddleToolbar = DefaultActionGroup().apply {
if (singleContent == null || headerVisible) return@apply
add(object : AnAction(XDebuggerBundle.message("session.tab.close.debug.session"), null, AllIcons.Actions.Cancel) {
override fun actionPerformed(e: AnActionEvent) {
toolWindow.contentManager.removeContent(singleContent, true)
}
})
}
myUi.options.setTopMiddleToolbar(topMiddleToolbar, ActionPlaces.DEBUGGER_TOOLBAR)
toolWindow.decorator.isHeaderVisible = headerVisible
if (toolWindow.decorator.isHeaderVisible) {
toolWindow.component.border = null
toolWindow.component.invalidate()
toolWindow.component.repaint()
} else if (toolWindow.component.border == null) {
UIUtil.addBorder(toolWindow.component, JBUI.Borders.customLine(JBColor.border(), 1, 0, 0, 0))
}
}
}
private val ToolWindowEx.isHeaderVisible get() = (type != ToolWindowType.DOCKED) || contentManager.contents.singleOrNull() == null
override fun getSupplier(): SingleContentSupplier? = mySingleContentSupplier
override fun registerAdditionalActions(leftToolbar: DefaultActionGroup, topLeftToolbar: DefaultActionGroup, settings: DefaultActionGroup) {
leftToolbar.apply {