feat: semi-working state
This commit is contained in:
parent
9a813fbbaf
commit
ab513fb0e9
@ -1,38 +1,77 @@
|
||||
package io.gitlab.jfronny.globalmenu
|
||||
|
||||
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.ApplicationActivationListener
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.wm.IdeFrame
|
||||
import com.intellij.openapi.wm.impl.IdeFrameImpl
|
||||
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.SwingMenuHolder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.freedesktop.dbus.DBusPath
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder
|
||||
import org.freedesktop.dbus.types.UInt32
|
||||
import java.awt.Window
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.JMenuBar
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
class GlobalMenuService(private val app: Application) : ApplicationActivationListener {
|
||||
override fun applicationActivated(ideFrame: IdeFrame) {
|
||||
super.applicationActivated(ideFrame)
|
||||
if (!GlobalMenu.Native.isSupported) return
|
||||
ideFrame.project?.let { project ->
|
||||
println(ideFrame.javaClass)
|
||||
if (ideFrame is ProjectFrameHelper) {
|
||||
visualize(ideFrame.rootPane.jMenuBar, ideFrame.rootPane.peer)
|
||||
} else if (ideFrame is IdeFrameImpl) {
|
||||
visualize(ideFrame.jMenuBar, ideFrame.peer)
|
||||
}
|
||||
GlobalMenu.Log.warn("Activated: ${project.name}")
|
||||
if (ideFrame is ProjectFrameHelper) {
|
||||
visualize(ideFrame.rootPane.jMenuBar, ideFrame.rootPane.peer)
|
||||
} else if (ideFrame is IdeFrameImpl) {
|
||||
visualize(ideFrame.jMenuBar, ideFrame.peer)
|
||||
}
|
||||
}
|
||||
|
||||
fun visualize(menu: JMenuBar, peer: Peer) {
|
||||
val conn = DBusConnectionBuilder.forSessionBus().build()
|
||||
override fun applicationDeactivated(ideFrame: IdeFrame) {
|
||||
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 menu = DbusmenuImpl(windowPtr, SwingMenuHolder(menu, "DBusMenuRoot"))
|
||||
val menu = DbusmenuImpl(windowPtr, menuHolder)
|
||||
conn.unExportObject(menu.objectPath)
|
||||
conn.exportObject(menu)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package io.gitlab.jfronny.globalmenu.proxy
|
||||
|
||||
import com.canonical.*
|
||||
import io.gitlab.jfronny.globalmenu.DPair
|
||||
import io.gitlab.jfronny.globalmenu.GlobalMenu
|
||||
import org.freedesktop.dbus.types.UInt32
|
||||
import org.freedesktop.dbus.types.Variant
|
||||
|
||||
@ -65,9 +66,16 @@ class DbusmenuImpl(windowId: Long, private val menuHolder: MenuHolder) : Dbusmen
|
||||
return properties
|
||||
}
|
||||
|
||||
override fun Event(id: Int, eventId: String?, data: Variant<*>?, timestamp: UInt32?) {
|
||||
if ("clicked".endsWith(eventId!!)) { //TODO this seems off
|
||||
menuHolder.find(id)?.onEvent()
|
||||
override fun Event(id: Int, eventId: String, data: Variant<*>?, timestamp: UInt32?) {
|
||||
GlobalMenu.Log.warn("Event $eventId for menu $id (${menuHolder.find(id)})")
|
||||
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 children: List<Menu>?
|
||||
fun onEvent()
|
||||
fun update()
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
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 kotlinx.coroutines.*
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.InputEvent
|
||||
@ -13,6 +18,7 @@ import javax.swing.JCheckBoxMenuItem
|
||||
import javax.swing.JMenu
|
||||
import javax.swing.JMenuItem
|
||||
import javax.swing.JRadioButtonMenuItem
|
||||
import javax.swing.JSeparator
|
||||
|
||||
class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuHolder) : Menu {
|
||||
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) {
|
||||
is ActionMenuItem -> {
|
||||
//TODO handle action items
|
||||
if (menuItem.isToggleable) "checkmark"
|
||||
else null
|
||||
}
|
||||
is JRadioButtonMenuItem -> "radio"
|
||||
is JCheckBoxMenuItem -> "checkmark"
|
||||
else -> null
|
||||
}
|
||||
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) {
|
||||
(0 until menuItem.itemCount).map { SwingMenu(menuItem.getItem(it), holder) }
|
||||
} else null
|
||||
private var _children: List<Menu>? = null
|
||||
override val children: List<Menu>? get() = _children
|
||||
|
||||
override fun onEvent() {
|
||||
val event = ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, menuItem!!.actionCommand)
|
||||
for (it in menuItem.actionListeners) it.actionPerformed(event)
|
||||
if (menuItem is JCheckBoxMenuItem) menuItem.isSelected = !menuItem.isSelected
|
||||
if (menuItem is JRadioButtonMenuItem) menuItem.isSelected = true
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
for (it in menuItem.actionListeners) it.actionPerformed(event)
|
||||
}
|
||||
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 {
|
||||
|
@ -1,5 +1,7 @@
|
||||
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.JMenuItem
|
||||
|
||||
@ -12,15 +14,20 @@ class SwingMenuHolder(bar: JMenuBar, menuName: String): MenuHolder {
|
||||
}
|
||||
|
||||
private fun find(parent: Menu, menuId: Int): Menu? {
|
||||
parent.children?.forEach {
|
||||
if (it.id == menuId) return it
|
||||
val found = find(it, menuId)
|
||||
if (found != null) return found
|
||||
return parent.children?.let { children ->
|
||||
for (child in children) {
|
||||
if (child.id == menuId) return child
|
||||
val found = find(child, menuId)
|
||||
if (found != null) return found
|
||||
}
|
||||
null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getId(menuItem: JMenuItem?): Int {
|
||||
return System.identityHashCode(menuItem)
|
||||
fun getId(menuItem: JMenuItem?): Int = when (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
|
||||
|
||||
import com.intellij.openapi.actionSystem.impl.ActionMenu
|
||||
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 isSeparator: Boolean get() = menuItems == null
|
||||
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 toggleType: String? get() = null
|
||||
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 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