diff --git a/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/DynamicPluginListener.kt b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/DynamicPluginListener.kt new file mode 100644 index 0000000..48df655 --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/DynamicPluginListener.kt @@ -0,0 +1,21 @@ +package com.chylex.intellij.pinundockedtoolwindows + +import com.chylex.intellij.pinundockedtoolwindows.patch.ToolWindowPatcher +import com.intellij.ide.plugins.DynamicPluginListener +import com.intellij.ide.plugins.IdeaPluginDescriptor + +const val PLUGIN_ID = "com.chylex.intellij.pinundockedtoolwindows" + +class DynamicPluginListener : DynamicPluginListener { + override fun pluginLoaded(pluginDescriptor: IdeaPluginDescriptor) { + if (pluginDescriptor.pluginId.idString == PLUGIN_ID) { + ToolWindowPatcher.apply() + } + } + + override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) { + if (pluginDescriptor.pluginId.idString == PLUGIN_ID) { + ToolWindowPatcher.revert() + } + } +} diff --git a/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/ProjectActivityListener.kt b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/ProjectActivityListener.kt new file mode 100644 index 0000000..5d40fda --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/ProjectActivityListener.kt @@ -0,0 +1,12 @@ +package com.chylex.intellij.pinundockedtoolwindows + +import com.chylex.intellij.pinundockedtoolwindows.patch.ToolWindowPatcher +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity + +class ProjectActivityListener : ProjectActivity { + override suspend fun execute(project: Project) { + ApplicationManager.getApplication().invokeLater(ToolWindowPatcher::apply) + } +} diff --git a/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/patch/PatchedAwtFocusListener.kt b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/patch/PatchedAwtFocusListener.kt new file mode 100644 index 0000000..f95e7c2 --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/patch/PatchedAwtFocusListener.kt @@ -0,0 +1,47 @@ +package com.chylex.intellij.pinundockedtoolwindows.patch + +import com.intellij.openapi.wm.IdeFocusManager +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.openapi.wm.ToolWindowType +import com.intellij.openapi.wm.impl.ToolWindowManagerImpl +import com.intellij.toolWindow.InternalDecoratorImpl +import com.intellij.ui.ClientProperty +import java.awt.AWTEvent +import java.awt.Component +import java.awt.event.AWTEventListener +import java.awt.event.FocusEvent + +@Suppress("UnstableApiUsage") +class PatchedAwtFocusListener(internal val original: AWTEventListener) : AWTEventListener { + override fun eventDispatched(event: AWTEvent?) { + if (event is FocusEvent && event.id == FocusEvent.FOCUS_LOST && shouldIgnoreFocusLostEvent(event.component)) { + return + } + + original.eventDispatched(event) + } + + private fun shouldIgnoreFocusLostEvent(currentlyFocused: Component?): Boolean { + val project = IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project ?: return false + if (project.isDisposed || project.isDefault) { + return false + } + + val toolWindowManager = ToolWindowManager.getInstance(project) + val toolWindowId = getToolWindowIdForComponent(currentlyFocused) ?: return false + val toolWindow = toolWindowManager.getToolWindow(toolWindowId) ?: return false + + return toolWindow.type == ToolWindowType.SLIDING + } + + private fun getToolWindowIdForComponent(component: Component?): String? { + var c = component + while (c != null) { + if (c is InternalDecoratorImpl) { + return c.toolWindowId + } + c = ClientProperty.get(c, ToolWindowManagerImpl.PARENT_COMPONENT) ?: c.parent + } + return null + } +} diff --git a/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/patch/ToolWindowPatcher.kt b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/patch/ToolWindowPatcher.kt new file mode 100644 index 0000000..a279d4d --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/pinundockedtoolwindows/patch/ToolWindowPatcher.kt @@ -0,0 +1,43 @@ +package com.chylex.intellij.pinundockedtoolwindows.patch + +import java.awt.AWTEvent +import java.awt.Toolkit +import java.awt.event.AWTEventListenerProxy + +object ToolWindowPatcher { + private const val EVENT_MASK = AWTEvent.FOCUS_EVENT_MASK or AWTEvent.WINDOW_FOCUS_EVENT_MASK + + private var activePatch: ActivePatch? = null + + private data class ActivePatch(val listener: PatchedAwtFocusListener, val eventMask: Long) + + fun apply() { + if (activePatch != null) { + return + } + + val toolkit = Toolkit.getDefaultToolkit() + for (listenerProxy in toolkit.getAWTEventListeners(EVENT_MASK)) { + if (listenerProxy is AWTEventListenerProxy) { + val listener = listenerProxy.listener + if (listener.javaClass.name == "com.intellij.openapi.wm.impl.ToolWindowManagerImpl\$ToolWindowManagerAppLevelHelper\$MyListener") { + val patch = ActivePatch(PatchedAwtFocusListener(listener), listenerProxy.eventMask) + toolkit.removeAWTEventListener(listener) + toolkit.addAWTEventListener(patch.listener, patch.eventMask) + activePatch = patch + return + } + } + } + } + + fun revert() { + val patch = activePatch + if (patch != null) { + val toolkit = Toolkit.getDefaultToolkit() + toolkit.removeAWTEventListener(patch.listener) + toolkit.addAWTEventListener(patch.listener.original, patch.eventMask) + activePatch = null + } + } +}