From d4456d65ef315179b3cacd8cdc359af5a2cd2145 Mon Sep 17 00:00:00 2001 From: Artem Bochkarev Date: Thu, 4 Oct 2018 23:34:49 +0700 Subject: [PATCH] linux-menubar: remake interaction model, add events filtering some of desktop env (KDE for example) doesn't like hierarchy changes when menu is shown, so use 'fixed' internal hierarchy KDE regulary calls 'about-to-show' for all top-level menus, avoid handling of them with use of event-filter --- bin/linux/libdbm64.so | Bin 32272 -> 38096 bytes native/LinuxGlobalMenu/DbusMenuWrapper.c | 28 +- native/LinuxGlobalMenu/DbusMenuWrapper.h | 3 + .../openapi/actionSystem/impl/ActionMenu.java | 2 + .../openapi/wm/impl/GlobalMenuLinux.java | 486 ++++++++++++++---- 5 files changed, 430 insertions(+), 89 deletions(-) diff --git a/bin/linux/libdbm64.so b/bin/linux/libdbm64.so index 45b7b6b39d14bccc230c9cb2b4a2e0174137d884..f46f96e1496dd6acd120a1964df3d563474125c9 100755 GIT binary patch literal 38096 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 literal 32272 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 Re|85L2$g&|a0PGv{}1=*MSB1M diff --git a/native/LinuxGlobalMenu/DbusMenuWrapper.c b/native/LinuxGlobalMenu/DbusMenuWrapper.c index 91660af03899..9fb340423803 100644 --- a/native/LinuxGlobalMenu/DbusMenuWrapper.c +++ b/native/LinuxGlobalMenu/DbusMenuWrapper.c @@ -169,6 +169,27 @@ static void _releaseWindow(WndInfo *wi) { free(wi); } +void createMenuRootForWnd(WndInfo *wi) { + if (wi->menuroot != NULL) { + _releaseMenuItem(wi->menuroot); + wi->menuroot = NULL; + } + + wi->menuroot = dbusmenu_menuitem_new(); + if (wi->menuroot == NULL) { + _error("can't create menuitem for new root"); + return; + } + + g_object_set_data(G_OBJECT(wi->menuroot), MENUITEM_JHANDLER_PROPERTY, wi->jhandler); + dbusmenu_menuitem_property_set(wi->menuroot, DBUSMENU_MENUITEM_PROP_LABEL, "DBusMenuRoot"); + + if (wi->server == NULL) + _error("can't set new root because wi->server is null"); + else + dbusmenu_server_set_root(wi->server, wi->menuroot); +} + WndInfo *registerWindow(long windowXid, jeventcallback handler) { // _info("register new window"); @@ -301,7 +322,7 @@ static void _handleItemSignal(DbusmenuMenuitem *item, int type) { } -static void _onItemEvent(DbusmenuMenuitem *item, const char *event) { +static void _onItemEvent(DbusmenuMenuitem *item, const char *event, GVariant * info, guint32 time) { // _logmsg(LOG_LEVEL_INFO, "_onItemEvent %s", event); int eventType = -1; @@ -314,6 +335,7 @@ static void _onItemEvent(DbusmenuMenuitem *item, const char *event) { else _error("unknown event type"); + //_logmsg(LOG_LEVEL_INFO, "time %d", time); _handleItemSignal(item, eventType); } @@ -325,7 +347,7 @@ static void _onItemAboutToShow(DbusmenuMenuitem *item) { _handleItemSignal(item, SIGNAL_ABOUT_TO_SHOW); } -static void _onItemShowToUser(DbusmenuMenuitem *item) { +static void _onItemShowToUser(DbusmenuMenuitem *item, guint32 time) { _handleItemSignal(item, SIGNAL_SHOWN); } @@ -384,6 +406,8 @@ DbusmenuMenuitem* addSeparator(DbusmenuMenuitem * parent, int uid) { return item; } +void removeMenuItem(DbusmenuMenuitem * parent, DbusmenuMenuitem* item) { dbusmenu_menuitem_child_delete(parent, item); } + void setItemLabel(DbusmenuMenuitem *item, const char *label) { dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_LABEL, label); } diff --git a/native/LinuxGlobalMenu/DbusMenuWrapper.h b/native/LinuxGlobalMenu/DbusMenuWrapper.h index fdfa48a6b8fd..3989eaa93780 100644 --- a/native/LinuxGlobalMenu/DbusMenuWrapper.h +++ b/native/LinuxGlobalMenu/DbusMenuWrapper.h @@ -44,6 +44,7 @@ void execOnMainLoop(jrunnable run); WndInfo* registerWindow(long windowXid, jeventcallback handler); // creates menu-server and binds to xid void releaseWindowOnMainLoop(WndInfo* wi); +void createMenuRootForWnd(WndInfo *wi); void clearRootMenu(WndInfo* wi); void clearMenu(DbusmenuMenuitem* menu); @@ -51,6 +52,8 @@ DbusmenuMenuitem* addRootMenu(WndInfo* wi, int uid, const char * label); DbusmenuMenuitem* addMenuItem(DbusmenuMenuitem * parent, int uid, const char * label, int type); DbusmenuMenuitem* addSeparator(DbusmenuMenuitem * parent, int uid); +void removeMenuItem(DbusmenuMenuitem * parent, DbusmenuMenuitem* item); + void setItemLabel(DbusmenuMenuitem* item, const char * label); void setItemEnabled(DbusmenuMenuitem* item, bool isEnabled); void setItemIcon(DbusmenuMenuitem* item, const char * iconBytesPng, int iconBytesCount); diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java index 13b79b28205d..d707c4afb552 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java @@ -82,6 +82,8 @@ public final class ActionMenu extends JBMenu { myContext = context; } + public AnAction getAnAction() { return myGroup.getAction(); } + @Override public void addNotify() { super.addNotify(); diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java index f9519bb26317..cdd2ad8d5fc2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java @@ -2,6 +2,7 @@ package com.intellij.openapi.wm.impl; import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.impl.ActionMenu; import com.intellij.openapi.actionSystem.impl.ActionMenuItem; import com.intellij.openapi.actionSystem.impl.StubItem; @@ -18,6 +19,7 @@ import com.sun.jna.Pointer; import org.jetbrains.annotations.NotNull; import javax.imageio.ImageIO; +import javax.swing.Timer; import javax.swing.*; import java.awt.*; import java.awt.event.InputEvent; @@ -27,10 +29,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; +import java.text.SimpleDateFormat; import java.util.List; -import java.util.Map; +import java.util.*; interface GlobalMenuLib extends Library { void runDbusServer(JLogger jlogger); @@ -47,6 +48,8 @@ interface GlobalMenuLib extends Library { Pointer addMenuItem(Pointer parent, int uid, String label, int type); Pointer addSeparator(Pointer wi, int uid); + void removeMenuItem(Pointer parent, Pointer item); + void setItemLabel(Pointer item, String label); void setItemEnabled(Pointer item, boolean isEnabled); void setItemIcon(Pointer item, byte[] iconBytesPng, int iconBytesCount); @@ -86,6 +89,13 @@ interface GlobalMenuLib extends Library { } public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { + private static final SimpleDateFormat ourDtf = new SimpleDateFormat("hhmmss.SSS"); // for debug only + private static final boolean TRACE_SYSOUT = Registry.is("linux.native.menu.debug.trace.sysout", false); + private static final boolean TRACE_DISABLED = Registry.is("linux.native.menu.debug.trace.disabled", true); + private static final boolean TRACE_SYNC_STATS = Registry.is("linux.native.menu.debug.trace.sync-stats", false); + private static final boolean TRACE_EVENTS = Registry.is("linux.native.menu.debug.trace.events", false); + private static final boolean TRACE_EVENT_FILTER = Registry.is("linux.native.menu.debug.trace.event-filter", false); + private static final Logger LOG = Logger.getInstance(GlobalMenuLinux.class); private static final GlobalMenuLib ourLib; private static final GlobalMenuLib.JLogger ourGLogger; @@ -96,6 +106,8 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { private Pointer myWindowHandle; private GlobalMenuLib.JRunnable myGlibLoopRunnable; // only to hold runnable object until it executed + private final EventFilter myEventFilter = new EventFilter(); + static { ourLib = _loadLibrary(); if (ourLib != null) { @@ -131,8 +143,10 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { if (ourLib == null) return; - if (myWindowHandle != null) + if (myWindowHandle != null) { + LOG.info("scheduled destroying of GlobalMenuLinux for xid=0x" + Long.toHexString(myXid)); ourLib.releaseWindowOnMainLoop(myWindowHandle); + } } public void setRoots(List roots) { @@ -141,16 +155,19 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { ApplicationManager.getApplication().assertIsDispatchThread(); - final List newRoots = roots != null && !roots.isEmpty() ? new ArrayList<>(roots.size()) : null; - if (newRoots != null) { + final int size = roots == null ? 0 : roots.size(); + final List newRoots = new ArrayList<>(size); + if (roots != null) { for (ActionMenu am: roots) { final int uid = System.identityHashCode(am); - final MenuItemInternal mi = new MenuItemInternal(uid, GlobalMenuLib.ITEM_SUBMENU, _buildMnemonicLabel(am), null, true, am); + final MenuItemInternal mi = new MenuItemInternal(newRoots.size(), uid, GlobalMenuLib.ITEM_SUBMENU, _buildMnemonicLabel(am), am.getText(), null, true, am.getAnAction()); + mi.jitem = am; newRoots.add(mi); } } myRoots = newRoots; + _trace("set new menu roots, count=%d", size); myGlibLoopRunnable = () -> { // Glib-loop @@ -168,8 +185,9 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { if (croots == null || croots.isEmpty()) return; - for (MenuItemInternal mi: croots) + for (MenuItemInternal mi: croots) { mi.nativePeer = ourLib.addRootMenu(myWindowHandle, mi.uid, mi.txt); + } }; ourLib.execOnMainLoop(myGlibLoopRunnable); // TODO: clean ref myGlibLoopRunnable @@ -179,7 +197,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { return _findMenuItem(myRoots, uid); } - private MenuItemInternal _findMenuItem(List kids, int uid) { + private static MenuItemInternal _findMenuItem(List kids, int uid) { if (kids == null || kids.isEmpty()) return null; @@ -233,27 +251,102 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { } } - private static MenuItemInternal _component2mi(Component each) { + private static MenuItemInternal _createInternalFromSwing(Component each) { if (each == null) return null; - + MenuItemInternal result = null; if (each instanceof ActionMenuItem) { final ActionMenuItem ami = (ActionMenuItem)each; - return new MenuItemInternal(System.identityHashCode(ami), ami.isToggleable() ? GlobalMenuLib.ITEM_CHECK : GlobalMenuLib.ITEM_SIMPLE, ami.getText(), _icon2png(ami.getIcon()), ami.isEnabled(), ami); - } - if (each instanceof ActionMenu) { + result = new MenuItemInternal(-1, System.identityHashCode(ami), ami.isToggleable() ? GlobalMenuLib.ITEM_CHECK : GlobalMenuLib.ITEM_SIMPLE, ami.getText(), ami.getText(), _icon2png(ami.getIcon()), ami.isEnabled(), ami.getAnAction()); + result.jitem = ami; + } else if (each instanceof ActionMenu) { final ActionMenu am2 = (ActionMenu)each; - return new MenuItemInternal(System.identityHashCode(am2), GlobalMenuLib.ITEM_SUBMENU, am2.getText(), null, am2.isEnabled(), am2); - } - if (each instanceof JSeparator) { - return new MenuItemInternal(System.identityHashCode(each), GlobalMenuLib.ITEM_SIMPLE, null, null, true, null); - } - if (each instanceof StubItem) { - // System.out.println("skip separator"); + result = new MenuItemInternal(-1, System.identityHashCode(am2), GlobalMenuLib.ITEM_SUBMENU, am2.getText(), am2.getText(), null, am2.isEnabled(), am2.getAnAction()); + result.jitem = am2; + } else if (each instanceof JSeparator) { + result = new MenuItemInternal(-1, System.identityHashCode(each), GlobalMenuLib.ITEM_SIMPLE, null, null, null, true, null); + } else if (each instanceof StubItem) { + // System.out.println("skip StubItem"); } else { LOG.error("unknown type of menu-item, class: " + each.getClass()); } - return null; + return result; + } + + private static final int STAT_CREATED = 0; + private static final int STAT_DELETED = 1; + private static final int STAT_UPDATED = 2; + + private static String _stats2str(int[] stats) { + if (stats == null) + return "empty"; + return String.format("created=%d, deleted=%d, updated=%d", stats[STAT_CREATED], stats[STAT_DELETED], stats[STAT_UPDATED]); + } + + private static void _syncChildren(@NotNull MenuItemInternal mi, @NotNull ActionMenu am, int deepness, int[] stats) { + // exec at EDT + + // 1. mark all kids to delete + mi.clearChildrenSwingRefs(); + for (MenuItemInternal cmi: mi.children) + cmi.toDelete = true; + if (stats != null) stats[STAT_DELETED] += mi.children.size(); + + // 2. check all children from ActionMenu + for (Component each : am.getPopupMenu().getComponents()) { + MenuItemInternal cmi = mi.findCorrespondingChild(each); + if (cmi == null) { + cmi = _createInternalFromSwing(each); + if (cmi != null) { + mi.children.add(cmi); + if (stats != null) ++stats[STAT_CREATED]; + } + } else { + cmi.toDelete = false; + if (stats != null) --stats[STAT_DELETED]; + if (each instanceof JMenuItem) { + final boolean changed = cmi.updateBySwingPeer((JMenuItem)each); + if (stats != null && changed) ++stats[STAT_UPDATED]; + } + } + if (cmi != null) { + if (deepness > 1 && (each instanceof ActionMenu)) + _syncChildren(cmi, (ActionMenu)each, deepness - 1, stats); + } + } + } + + private static void _processChildren(@NotNull MenuItemInternal mi) { + // exec at glib main-loop thread + if (mi.nativePeer == null) + return; + + for (MenuItemInternal child: mi.children) { + if (child.nativePeer != null) { + if (child.toDelete) { + ourLib.removeMenuItem(mi.nativePeer, child.nativePeer); + } else { + // update states and icons + // NOTE: probably it's better to use sync flags, to avoid frequent calls, to avoid applet destabilization) + ourLib.setItemEnabled(child.nativePeer, child.isEnabled); + ourLib.setItemIcon(child.nativePeer, child.iconPngBytes, child.iconPngBytes != null ? child.iconPngBytes.length : 0); + } + } else { + if (child.action == null) { + child.nativePeer = ourLib.addSeparator(mi.nativePeer, child.uid); + continue; + } + + child.nativePeer = ourLib.addMenuItem(mi.nativePeer, child.uid, child.txt, child.type); + + if (!child.isEnabled) + ourLib.setItemEnabled(child.nativePeer, false); + if (child.iconPngBytes != null && child.iconPngBytes.length > 0) + ourLib.setItemIcon(child.nativePeer, child.iconPngBytes, child.iconPngBytes.length); + } + + _processChildren(child); + } } @Override @@ -268,83 +361,87 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { LOG.error("menu-item hasn't native peer, uid = " + uid + ", eventType=" + eventType); return; } - if (mi.jmenuitem == null) { - LOG.error("menu-item hasn't associated swing peer, uid = " + uid + ", eventType=" + eventType); + if (mi.action == null) { + LOG.error("menu-item hasn't associated AnAction, uid = " + uid + ", eventType=" + eventType); return; } - if (eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW || eventType == GlobalMenuLib.EVENT_CLOSED) { - if (!(mi.jmenuitem instanceof ActionMenu)) { - LOG.error("about-to-show is emitted for non-ActionMenu item: " + mi.jmenuitem.getClass().getName()); - return; - } + if (TRACE_EVENTS) _trace("received event '%s' from item %s", _evtype2str(eventType), mi); - final ActionMenu am = (ActionMenu)mi.jmenuitem; - // System.out.printf("handle event %s from %s\n", _evtype2str(eventType), mi.toString()); + if (eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW || eventType == GlobalMenuLib.EVENT_CLOSED) { + final boolean check = myEventFilter.check(uid, eventType, mi); + if (!check) + return; if (eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW) { // glib main-loop thread - final List children = new ArrayList<>(); - final long startMs = System.currentTimeMillis(); + int[] stats = new int[]{0, 0, 0}; + ApplicationManager.getApplication().invokeAndWait(()-> { // ETD-start + final JMenuItem jmi = mi.jitem; + if (jmi == null) { + LOG.error("can't find corresponding (opening) ActionMenu, event source: " + mi + ", swing menu hierarchy:\n" + _dumpSwingHierarchy()); + return; + } + if (!(jmi instanceof ActionMenu)) { + LOG.error("corresponding (opening) swing item isn't instance of ActionMenu, class=" + jmi.getClass().getName() + ", event source: " + mi); + return; + } + + final ActionMenu am = (ActionMenu)jmi; am.removeAll(); am.fillMenu(); - // System.out.println("\t size of components : " + am.getPopupMenu().getComponents().length); - - // collect children - for (Component each : ((ActionMenu)mi.jmenuitem).getPopupMenu().getComponents()) { - final MenuItemInternal cmi = _component2mi(each); - if (cmi != null) - children.add(cmi); - } + _syncChildren(mi, am, 1, stats); }); + + // glib main-loop thread final long elapsedMs = System.currentTimeMillis() - startMs; - if (elapsedMs > 1000) - LOG.info("global menu filled with " + children.size() + " components, spent " + elapsedMs + " ms"); + if (TRACE_SYNC_STATS) _trace("opened %s '%s', spent (in EDT) %d ms, stats: %s", (mi.isRoot() ? "root menu" : "submenu"), String.valueOf(mi.txt), elapsedMs, _stats2str(stats)); - // return to glib main-loop thread - ourLib.clearMenu(mi.nativePeer); // just for extra insurance - for (MenuItemInternal child: children) { - if (child.jmenuitem == null) { - child.nativePeer = ourLib.addSeparator(mi.nativePeer, child.uid); - continue; - } - child.nativePeer = ourLib.addMenuItem(mi.nativePeer, child.uid, child.txt, child.type); - if (!child.isEnabled) - ourLib.setItemEnabled(child.nativePeer, false); - if (child.iconPngBytes != null && child.iconPngBytes.length > 0) - ourLib.setItemIcon(child.nativePeer, child.iconPngBytes, child.iconPngBytes.length); - } - mi.children = children; + _processChildren(mi); } else if (eventType == GlobalMenuLib.EVENT_CLOSED) { - // final long startMs = System.currentTimeMillis(); - ApplicationManager.getApplication().invokeLater(()-> { + // glib main-loop thread + final Timer timer = new Timer(100, (e) -> { // ETD-start - am.clearItems(); - }); - // final long elapsedMs = System.currentTimeMillis() - startMs; - // System.out.printf("\t cleared menu '%s', spent %d ms\n", mi.txt, elapsedMs); + final JMenuItem jmi = mi.jitem; + if (jmi == null) { + LOG.error("can't find corresponding (closing) ActionMenu, event source: " + mi + ", swing menu hierarchy:\n" + _dumpSwingHierarchy()); + return; + } + if (!(jmi instanceof ActionMenu)) { + LOG.error("corresponding (closing) swing item isn't instance of ActionMenu, class=" + jmi.getClass().getName() + ", event source: " + mi); + return; + } - // return to glib main-loop thread - ourLib.clearMenu(mi.nativePeer); - ourLib.addSeparator(mi.nativePeer, Integer.MAX_VALUE); // to prevent glib-warnings (about empty submenus) - mi.children = null; + final ActionMenu am = (ActionMenu)jmi; + am.clearItems(); + mi.clearChildrenSwingRefs(); + }); + timer.setRepeats(false); + timer.start(); + + _trace("\t scheduled (100 ms later) to clear menu '%s'", mi.txt); } return; } if (eventType == GlobalMenuLib.EVENT_CLICKED) { - if (!(mi.jmenuitem instanceof ActionMenuItem)) { - LOG.error("clicked event for non-ActionMenuItem item: " + mi.jmenuitem.getClass().getName()); + _trace("process click event (%s), event source: %s", _evtype2str(eventType), mi); + + final JMenuItem jmi = mi.jitem; + if (jmi == null) { + LOG.error("can't find corresponding (clicked) ActionMenuItem, event source: " + mi + ", swing menu hierarchy:\n" + _dumpSwingHierarchy()); + return; + } + if (!(jmi instanceof ActionMenuItem)) { + LOG.error("corresponding (clicked) swing item isn't instance of ActionMenuItem, class=" + jmi.getClass().getName() + ", event source: " + mi); return; } - final ActionMenuItem ami = (ActionMenuItem)mi.jmenuitem; - // System.out.printf("handle click event %s from %s\n", _evtype2str(eventType), mi.toString()); - + final ActionMenuItem ami = (ActionMenuItem)jmi; ApplicationManager.getApplication().invokeLater(()-> ami.doClick()); } } @@ -358,8 +455,12 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { if (!Registry.is("linux.native.menu.debug.check.desktop")) return true; - final String desktop = System.getenv("XDG_CURRENT_DESKTOP"); - return desktop != null && (desktop.startsWith("Unity") || desktop.startsWith("ubuntu")); + String desktop = System.getenv("XDG_CURRENT_DESKTOP"); + if (desktop == null) + return false; + + desktop = desktop.toLowerCase(); + return desktop.startsWith("unity") || desktop.startsWith("ubuntu") || desktop.equals("kde"); } private static GlobalMenuLib _loadLibrary() { @@ -390,30 +491,189 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { } private static class MenuItemInternal { + final int rootPos; final int uid; final int type; - final String txt; - final byte[] iconPngBytes; - final int jmodifiers; - final int jkeycode; - final JMenuItem jmenuitem; - final boolean isEnabled; - Pointer nativePeer; - List children; + final AnAction action; - MenuItemInternal(int uid, int type, String txt, byte[] iconPngBytes, boolean isEnabled, JMenuItem jmenuitem) { + final List children = new ArrayList<>(); + + String txt; + String originTxt; + boolean isEnabled; + byte[] iconPngBytes; + + JMenuItem jitem; + Pointer nativePeer; + boolean toDelete = false; + + long lastOpenedMs = 0; + long lastClosedMs = 0; + + MenuItemInternal(int rootPos, int uid, int type, String txt, String originTxt, byte[] iconPngBytes, boolean isEnabled, AnAction action) { + this.rootPos = rootPos; this.uid = uid; this.type = type; this.txt = txt; + this.originTxt = originTxt; this.iconPngBytes = iconPngBytes; this.isEnabled = isEnabled; - this.jmenuitem = jmenuitem; - this.jmodifiers = _calcModifiers(jmenuitem); - this.jkeycode = _calcKeyCode(jmenuitem); + this.action = action; + } + + boolean isRoot() { return rootPos >= 0; } + + void clearChildrenSwingRefs() { + for (MenuItemInternal cmi: children) { + cmi.jitem = null; + cmi.clearChildrenSwingRefs(); + } + } + + // returns true when changed + boolean updateBySwingPeer(JMenuItem peer) { + // exec at EDT + jitem = peer; + // probably need to calc sync flags like: if (cmi.isEnabled != each.isEnabled()) cmi.needUpdate = true; + boolean res = isEnabled != peer.isEnabled(); + isEnabled = peer.isEnabled(); + // TODO: update icons (text must be the same) + return res; + } + + MenuItemInternal findCorrespondingChild(@NotNull Component target) { + if (target == null) + return null; + if (children.isEmpty()) + return null; + + if (target instanceof JSeparator) { + for (MenuItemInternal child : children) + if (child.toDelete && child.action == null) + return child; + return null; + } + + if (!(target instanceof JMenuItem)) + return null; + + final JMenuItem jmi = (JMenuItem)target; + + // find by text + final String label = jmi.getText(); + if (label != null && !label.isEmpty()) { + for (MenuItemInternal child : children) + if (label.equals(child.originTxt)) + return child; + } + + // find by Action + AnAction act = null; + if (target instanceof ActionMenuItem) + act = ((ActionMenuItem)target).getAnAction(); + if (target instanceof ActionMenu) + act = ((ActionMenu)target).getAnAction(); + + if (act == null) + return null; + + for (MenuItemInternal child : children) + if (act.equals(child.action)) { + // System.out.println("WARN: can't find child of " + toString() + " corresponding by label '" + String.valueOf(label) + "' (will search by action), all children:\n" + printKids()); + return child; + } + + return null; + } + + String printKids() { + final StringBuilder res = new StringBuilder(); + printKids(res, 0); + return res.toString(); + } + + void printKids(StringBuilder out, int indent) { + for (MenuItemInternal kid: children) { + if (out.length() > 0) + out.append('\n'); + for (int c = 0; c < indent; ++c) out.append('\t'); + out.append(kid.toString()); + } + } + + String printHierarchy() { + final StringBuilder res = new StringBuilder(); + printHierarchy(res, 0); + return res.toString(); + } + + void printHierarchy(StringBuilder out, int indent) { + for (MenuItemInternal kid: children) { + if (out.length() > 0) + out.append('\n'); + for (int c = 0; c < indent; ++c) out.append('\t'); + out.append(kid.toString()); + kid.printHierarchy(out, indent + 1); + } } @Override - public String toString() { return String.format("'%s' (%d)",txt, uid); } + public String toString() { + String res = String.format("'%s' (uid=%d, act=%s)", txt, uid, String.valueOf(action)); + if (toDelete) + res = res + " [toDelele]"; + return res; + } + } + + private class EventFilter { + private Timer myTimer; + private long myLastFirstRootEventMs = 0; + + boolean check(int uid, int eventType, @NotNull MenuItemInternal mi) { + final long timeMs = System.currentTimeMillis(); + if (eventType == GlobalMenuLib.EVENT_CLOSED) { + mi.lastClosedMs = timeMs; + } else { + if (mi.rootPos == 0) { + if (myTimer == null) { + myLastFirstRootEventMs = timeMs; + // start timer to call handleEvent(uid, eventType) after several ms + myTimer = new Timer(50, (e) -> { + if (myTimer == null) { + if (TRACE_EVENT_FILTER) _trace("EventFilter: skip delayed 'about-to-show' processing of first-root because timer was reset (i.e. myTimer == null)"); + return; + } + ourLib.execOnMainLoop(myGlibLoopRunnable = () -> handleEvent(uid, eventType)); + }); + myTimer.setRepeats(false); + myTimer.start(); + if (TRACE_EVENT_FILTER) _trace("EventFilter: start timer to process 'about-to-show' of first-root later"); + return false; + } + } else if (mi.rootPos > 0) { + if ((timeMs - myLastFirstRootEventMs) < 50) { + if (TRACE_EVENT_FILTER) _trace("EventFilter: skip fake 'about-to-show' of root[%d]%s", mi.rootPos, myTimer != null ? " (reset timer)" : ""); + if (myTimer != null) { + myTimer.stop(); + myTimer = null; + } + return false; + } + if (TRACE_EVENT_FILTER) _trace("EventFilter: process real 'about-to-show' on root[%d]", mi.rootPos); + } else { + if (TRACE_EVENT_FILTER) _trace("EventFilter: process real 'about-to-show' on non-root item '%s'%s", mi.txt, myTimer != null ? " (reset timer)" : ""); + if (myTimer != null) { + myTimer.stop(); + myTimer = null; + } + } + } + + if (mi.rootPos == 0) + myTimer = null; + return true; + } } private static int _calcModifiers(JMenuItem jmenuitem) { @@ -456,6 +716,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { return res.toString(); } + @SuppressWarnings("deprecation") private static long _getX11WindowXid(@NotNull JFrame frame) { final ComponentPeer wndPeer = frame.getPeer(); if (wndPeer == null) { @@ -494,4 +755,55 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable { } return 0; } + + private String _dumpSwingHierarchy() { + StringBuilder out = new StringBuilder(); + _dumpSwingHierarchy(out); + return out.toString(); + } + + private void _dumpSwingHierarchy(StringBuilder out) { + for (MenuItemInternal root: myRoots) { + final ActionMenu am = (ActionMenu)root.jitem; + out.append(am.getText()); + out.append('\n'); + _dumpActionMenuKids(am, out, 1); + } + } + + private static void _dumpActionMenuKids(@NotNull ActionMenu am, StringBuilder out, int indent) { + // exec at EDT + for (Component each : am.getPopupMenu().getComponents()) { + if (each == null) + continue; + if (!(each instanceof JMenuItem)) + continue; + + for (int c = 0; c < indent; ++c) out.append('\t'); + String txt = each instanceof JSeparator ? "--separ--" : ((JMenuItem)each).getText(); + if (txt == null || txt.isEmpty()) + txt = "null"; + out.append(txt); + out.append('\n'); + + if (each instanceof ActionMenu) { + _dumpActionMenuKids((ActionMenu)each, out, indent + 1); + } + } + } + + private static void _trace(String fmt, Object... args) { + if (TRACE_DISABLED) + return; + final String msg = String.format(fmt, args); + _trace(msg); + } + private static void _trace(String msg) { + if (TRACE_DISABLED) + return; + if (TRACE_SYSOUT) + System.out.println(ourDtf.format(new Date()) + ": " + msg); + else + LOG.info(msg); + } } \ No newline at end of file