feat: semi-working state
This commit is contained in:
parent
9a813fbbaf
commit
ab513fb0e9
@ -1,38 +1,77 @@
|
|||||||
package io.gitlab.jfronny.globalmenu
|
package io.gitlab.jfronny.globalmenu
|
||||||
|
|
||||||
import com.canonical.appmenu.Registrar
|
import com.canonical.appmenu.Registrar
|
||||||
|
import com.intellij.ide.IdeEventQueue
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
|
import com.intellij.openapi.actionSystem.impl.ActionMenu
|
||||||
import com.intellij.openapi.application.Application
|
import com.intellij.openapi.application.Application
|
||||||
import com.intellij.openapi.application.ApplicationActivationListener
|
import com.intellij.openapi.application.ApplicationActivationListener
|
||||||
|
import com.intellij.openapi.progress.runBlockingCancellable
|
||||||
|
import com.intellij.openapi.util.Disposer
|
||||||
import com.intellij.openapi.wm.IdeFrame
|
import com.intellij.openapi.wm.IdeFrame
|
||||||
import com.intellij.openapi.wm.impl.IdeFrameImpl
|
import com.intellij.openapi.wm.impl.IdeFrameImpl
|
||||||
import com.intellij.openapi.wm.impl.ProjectFrameHelper
|
import com.intellij.openapi.wm.impl.ProjectFrameHelper
|
||||||
|
import com.intellij.platform.ide.menu.IdeJMenuBar
|
||||||
import io.gitlab.jfronny.globalmenu.proxy.DbusmenuImpl
|
import io.gitlab.jfronny.globalmenu.proxy.DbusmenuImpl
|
||||||
import io.gitlab.jfronny.globalmenu.proxy.SwingMenuHolder
|
import io.gitlab.jfronny.globalmenu.proxy.SwingMenuHolder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.freedesktop.dbus.DBusPath
|
import org.freedesktop.dbus.DBusPath
|
||||||
|
import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||||
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder
|
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder
|
||||||
import org.freedesktop.dbus.types.UInt32
|
import org.freedesktop.dbus.types.UInt32
|
||||||
|
import java.awt.Window
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.JMenuBar
|
import javax.swing.JMenuBar
|
||||||
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
class GlobalMenuService(private val app: Application) : ApplicationActivationListener {
|
class GlobalMenuService(private val app: Application) : ApplicationActivationListener {
|
||||||
override fun applicationActivated(ideFrame: IdeFrame) {
|
override fun applicationActivated(ideFrame: IdeFrame) {
|
||||||
super.applicationActivated(ideFrame)
|
super.applicationActivated(ideFrame)
|
||||||
if (!GlobalMenu.Native.isSupported) return
|
if (!GlobalMenu.Native.isSupported) return
|
||||||
ideFrame.project?.let { project ->
|
if (ideFrame is ProjectFrameHelper) {
|
||||||
println(ideFrame.javaClass)
|
visualize(ideFrame.rootPane.jMenuBar, ideFrame.rootPane.peer)
|
||||||
if (ideFrame is ProjectFrameHelper) {
|
} else if (ideFrame is IdeFrameImpl) {
|
||||||
visualize(ideFrame.rootPane.jMenuBar, ideFrame.rootPane.peer)
|
visualize(ideFrame.jMenuBar, ideFrame.peer)
|
||||||
} else if (ideFrame is IdeFrameImpl) {
|
|
||||||
visualize(ideFrame.jMenuBar, ideFrame.peer)
|
|
||||||
}
|
|
||||||
GlobalMenu.Log.warn("Activated: ${project.name}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visualize(menu: JMenuBar, peer: Peer) {
|
override fun applicationDeactivated(ideFrame: IdeFrame) {
|
||||||
val conn = DBusConnectionBuilder.forSessionBus().build()
|
lastMenu?.let { Disposer.dispose(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lastMenu: Disposable? = null
|
||||||
|
private var connection: DBusConnection? = null
|
||||||
|
|
||||||
|
fun visualize(menu: JMenuBar, peer: Peer) = app.invokeLater {
|
||||||
|
lastMenu?.let { Disposer.dispose(it) }
|
||||||
|
lastMenu = Disposer.newDisposable()
|
||||||
|
|
||||||
|
val conn = connection ?: DBusConnectionBuilder.forSessionBus().build()
|
||||||
|
connection = conn
|
||||||
|
|
||||||
|
val menuHolder = SwingMenuHolder(menu, "DBusMenuRoot")
|
||||||
|
if (menu is IdeJMenuBar) {
|
||||||
|
menu.addUpdateGlobalMenuRootsListener {
|
||||||
|
menuHolder.update(menu.rootMenuItems)
|
||||||
|
}
|
||||||
|
menu.updateMenuActions(true)
|
||||||
|
}
|
||||||
|
// IdeEventQueue.getInstance().addDispatcher({ e ->
|
||||||
|
// if (e !is KeyEvent) false
|
||||||
|
// else if (!e.isAltDown) false
|
||||||
|
// else {
|
||||||
|
// val src = e.component
|
||||||
|
// val wndParent = if (src is Window) src else SwingUtilities.windowForComponent(src)
|
||||||
|
// val eventChar = e.keyChar.uppercaseChar()
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// true
|
||||||
|
// }
|
||||||
|
// }, lastMenu!!)
|
||||||
|
|
||||||
val windowPtr = peer.nativePtr
|
val windowPtr = peer.nativePtr
|
||||||
val menu = DbusmenuImpl(windowPtr, SwingMenuHolder(menu, "DBusMenuRoot"))
|
val menu = DbusmenuImpl(windowPtr, menuHolder)
|
||||||
conn.unExportObject(menu.objectPath)
|
conn.unExportObject(menu.objectPath)
|
||||||
conn.exportObject(menu)
|
conn.exportObject(menu)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package io.gitlab.jfronny.globalmenu.proxy
|
|||||||
|
|
||||||
import com.canonical.*
|
import com.canonical.*
|
||||||
import io.gitlab.jfronny.globalmenu.DPair
|
import io.gitlab.jfronny.globalmenu.DPair
|
||||||
|
import io.gitlab.jfronny.globalmenu.GlobalMenu
|
||||||
import org.freedesktop.dbus.types.UInt32
|
import org.freedesktop.dbus.types.UInt32
|
||||||
import org.freedesktop.dbus.types.Variant
|
import org.freedesktop.dbus.types.Variant
|
||||||
|
|
||||||
@ -65,9 +66,16 @@ class DbusmenuImpl(windowId: Long, private val menuHolder: MenuHolder) : Dbusmen
|
|||||||
return properties
|
return properties
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Event(id: Int, eventId: String?, data: Variant<*>?, timestamp: UInt32?) {
|
override fun Event(id: Int, eventId: String, data: Variant<*>?, timestamp: UInt32?) {
|
||||||
if ("clicked".endsWith(eventId!!)) { //TODO this seems off
|
GlobalMenu.Log.warn("Event $eventId for menu $id (${menuHolder.find(id)})")
|
||||||
menuHolder.find(id)?.onEvent()
|
try {
|
||||||
|
when (eventId) {
|
||||||
|
"clicked" -> menuHolder.find(id)?.onEvent()
|
||||||
|
"opened" -> menuHolder.find(id)?.update()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GlobalMenu.Log.error("Failed to handle event $eventId for menu $id", e)
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,4 +12,5 @@ interface Menu {
|
|||||||
val toggleState: Int
|
val toggleState: Int
|
||||||
val children: List<Menu>?
|
val children: List<Menu>?
|
||||||
fun onEvent()
|
fun onEvent()
|
||||||
|
fun update()
|
||||||
}
|
}
|
@ -1,6 +1,11 @@
|
|||||||
package io.gitlab.jfronny.globalmenu.proxy
|
package io.gitlab.jfronny.globalmenu.proxy
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.impl.ActionMenu
|
||||||
|
import com.intellij.openapi.actionSystem.impl.ActionMenuItem
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.EDT
|
||||||
import io.gitlab.jfronny.globalmenu.GlobalMenu
|
import io.gitlab.jfronny.globalmenu.GlobalMenu
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import org.apache.commons.io.output.ByteArrayOutputStream
|
import org.apache.commons.io.output.ByteArrayOutputStream
|
||||||
import java.awt.event.ActionEvent
|
import java.awt.event.ActionEvent
|
||||||
import java.awt.event.InputEvent
|
import java.awt.event.InputEvent
|
||||||
@ -13,6 +18,7 @@ import javax.swing.JCheckBoxMenuItem
|
|||||||
import javax.swing.JMenu
|
import javax.swing.JMenu
|
||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
import javax.swing.JRadioButtonMenuItem
|
import javax.swing.JRadioButtonMenuItem
|
||||||
|
import javax.swing.JSeparator
|
||||||
|
|
||||||
class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuHolder) : Menu {
|
class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuHolder) : Menu {
|
||||||
override val id = holder.getId(menuItem)
|
override val id = holder.getId(menuItem)
|
||||||
@ -64,20 +70,76 @@ class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuH
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val toggleType: String? get() = when (menuItem) {
|
override val toggleType: String? get() = when (menuItem) {
|
||||||
|
is ActionMenuItem -> {
|
||||||
|
//TODO handle action items
|
||||||
|
if (menuItem.isToggleable) "checkmark"
|
||||||
|
else null
|
||||||
|
}
|
||||||
is JRadioButtonMenuItem -> "radio"
|
is JRadioButtonMenuItem -> "radio"
|
||||||
is JCheckBoxMenuItem -> "checkmark"
|
is JCheckBoxMenuItem -> "checkmark"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
override val toggleState: Int get() = if (toggleType?.isNotEmpty() == true) if (menuItem!!.isSelected) 1 else 0 else -1
|
override val toggleState: Int get() = if (toggleType?.isNotEmpty() == true) if (menuItem!!.isSelected) 1 else 0 else -1
|
||||||
override val children: List<Menu>? get() = if (menuItem is JMenu) {
|
private var _children: List<Menu>? = null
|
||||||
(0 until menuItem.itemCount).map { SwingMenu(menuItem.getItem(it), holder) }
|
override val children: List<Menu>? get() = _children
|
||||||
} else null
|
|
||||||
|
|
||||||
override fun onEvent() {
|
override fun onEvent() {
|
||||||
val event = ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, menuItem!!.actionCommand)
|
val event = ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, menuItem!!.actionCommand)
|
||||||
for (it in menuItem.actionListeners) it.actionPerformed(event)
|
ApplicationManager.getApplication().invokeLater {
|
||||||
if (menuItem is JCheckBoxMenuItem) menuItem.isSelected = !menuItem.isSelected
|
for (it in menuItem.actionListeners) it.actionPerformed(event)
|
||||||
if (menuItem is JRadioButtonMenuItem) menuItem.isSelected = true
|
}
|
||||||
|
menuItem.doClick()
|
||||||
|
GlobalMenu.Log.warn("Event $event for menu $id (${menuItem.javaClass})")
|
||||||
|
when (menuItem) {
|
||||||
|
is JCheckBoxMenuItem -> menuItem.isSelected = !menuItem.isSelected
|
||||||
|
is JRadioButtonMenuItem -> menuItem.isSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update() {
|
||||||
|
try {
|
||||||
|
if (menuItem is ActionMenu) {
|
||||||
|
runBlocking {
|
||||||
|
launch(Dispatchers.EDT) {
|
||||||
|
menuItem.removeAll()
|
||||||
|
menuItem.isSelected = true
|
||||||
|
menuItem.fillMenu()
|
||||||
|
syncChildren(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GlobalMenu.Log.error("Failed to update menu", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun syncChildren(deepness: Int) {
|
||||||
|
GlobalMenu.Log.warn("Syncing children for $label")
|
||||||
|
_children = when (menuItem) {
|
||||||
|
is ActionMenu -> {
|
||||||
|
val ch = mutableListOf<Menu>()
|
||||||
|
for (each in menuItem.popupMenu.components) {
|
||||||
|
if (each == null) continue
|
||||||
|
if (each is JSeparator) {
|
||||||
|
ch.add(SwingMenu(null, holder))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (each !is JMenuItem) continue
|
||||||
|
val cmi = SwingMenu(each, holder)
|
||||||
|
if (deepness > 1 && each is ActionMenu) {
|
||||||
|
each.removeAll()
|
||||||
|
each.isSelected = true
|
||||||
|
each.fillMenu()
|
||||||
|
cmi.syncChildren(deepness - 1)
|
||||||
|
}
|
||||||
|
ch.add(cmi)
|
||||||
|
}
|
||||||
|
ch
|
||||||
|
}
|
||||||
|
is JMenu -> (0 until menuItem.itemCount).map { SwingMenu(menuItem.getItem(it), holder) }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package io.gitlab.jfronny.globalmenu.proxy
|
package io.gitlab.jfronny.globalmenu.proxy
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.impl.ActionMenu
|
||||||
|
import com.intellij.openapi.actionSystem.impl.ActionMenuItem
|
||||||
import javax.swing.JMenuBar
|
import javax.swing.JMenuBar
|
||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
|
|
||||||
@ -12,15 +14,20 @@ class SwingMenuHolder(bar: JMenuBar, menuName: String): MenuHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun find(parent: Menu, menuId: Int): Menu? {
|
private fun find(parent: Menu, menuId: Int): Menu? {
|
||||||
parent.children?.forEach {
|
return parent.children?.let { children ->
|
||||||
if (it.id == menuId) return it
|
for (child in children) {
|
||||||
val found = find(it, menuId)
|
if (child.id == menuId) return child
|
||||||
if (found != null) return found
|
val found = find(child, menuId)
|
||||||
|
if (found != null) return found
|
||||||
|
}
|
||||||
|
null
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getId(menuItem: JMenuItem?): Int {
|
fun getId(menuItem: JMenuItem?): Int = when (menuItem) {
|
||||||
return System.identityHashCode(menuItem)
|
is ActionMenu -> menuItem.anAction.toString().hashCode()
|
||||||
|
is ActionMenuItem -> menuItem.anAction.toString().hashCode()
|
||||||
|
else -> System.identityHashCode(menuItem)
|
||||||
}
|
}
|
||||||
|
fun update(items: List<ActionMenu>) = root.update(items)
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
package io.gitlab.jfronny.globalmenu.proxy
|
package io.gitlab.jfronny.globalmenu.proxy
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.impl.ActionMenu
|
||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
|
|
||||||
class SwingRootMenu(private val menuItems: List<JMenuItem>?, private val name: String, private val holder: SwingMenuHolder): Menu {
|
class SwingRootMenu(private var menuItems: List<JMenuItem>?, private val name: String, private val holder: SwingMenuHolder): Menu {
|
||||||
override val id: Int get() = 0
|
override val id: Int get() = 0
|
||||||
override val isSeparator: Boolean get() = menuItems == null
|
override val isSeparator: Boolean get() = menuItems == null
|
||||||
override val label: String get() = name
|
override val label: String get() = name
|
||||||
@ -12,8 +13,26 @@ class SwingRootMenu(private val menuItems: List<JMenuItem>?, private val name: S
|
|||||||
override val shortcut: Array<String>? get() = null
|
override val shortcut: Array<String>? get() = null
|
||||||
override val toggleType: String? get() = null
|
override val toggleType: String? get() = null
|
||||||
override val toggleState: Int get() = 0
|
override val toggleState: Int get() = 0
|
||||||
override val children: List<Menu>? get() = menuItems?.map { SwingMenu(it, holder) }
|
private var _children: List<Menu>? = null
|
||||||
|
override val children: List<Menu>? get() = _children
|
||||||
|
|
||||||
override fun onEvent() {
|
override fun onEvent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun update() {
|
||||||
|
syncChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(items: List<ActionMenu>) {
|
||||||
|
menuItems = items
|
||||||
|
syncChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
syncChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun syncChildren() {
|
||||||
|
_children = menuItems?.map { SwingMenu(it, holder).apply { syncChildren(1) } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user