feat: semi-working state

This commit is contained in:
Johannes Frohnmeyer 2024-07-20 22:54:25 +02:00
parent 9a813fbbaf
commit ab513fb0e9
Signed by: Johannes
GPG Key ID: E76429612C2929F4
6 changed files with 165 additions and 29 deletions

View File

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

View File

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

View File

@ -12,4 +12,5 @@ interface Menu {
val toggleState: Int
val children: List<Menu>?
fun onEvent()
fun update()
}

View File

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

View File

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

View File

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