1
0
mirror of https://github.com/chylex/IntelliJ-Pin-Undocked-Tool-Windows.git synced 2025-05-06 19:34:04 +02:00

Stop IntelliJ from hiding Undocked tool windows when they lose focus

This commit is contained in:
chylex 2023-05-18 08:08:51 +02:00
parent 96c63d6b06
commit 7ec831c0c3
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
4 changed files with 123 additions and 0 deletions

View File

@ -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()
}
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}