linux-menubar: support mnemonics
authorArtem Bochkarev <artem.bochkarev@jetbrains.com>
Mon, 24 Sep 2018 05:59:46 +0000 (12:59 +0700)
committerArtem Bochkarev <artem.bochkarev@jetbrains.com>
Mon, 24 Sep 2018 09:35:53 +0000 (16:35 +0700)
also add setItemShortcut

bin/linux/libdbm64.so
native/LinuxGlobalMenu/DbusMenuWrapper.c
native/LinuxGlobalMenu/DbusMenuWrapper.h
platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java

index bd86a3953fe9b14adb3985263b9ad6e02dc25d8c..45b7b6b39d14bccc230c9cb2b4a2e0174137d884 100755 (executable)
Binary files a/bin/linux/libdbm64.so and b/bin/linux/libdbm64.so differ
index c2b0bfbe1a4d08040eede53da8c44ff1c3070380..91660af038999044f2b1b3cde1ef2a2cc7f4024a 100644 (file)
@@ -3,6 +3,9 @@
 #include <stdarg.h>
 #include <stdio.h>
 
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+
 #include <gio/gio.h>
 #include <libdbusmenu-glib/server.h>
 
@@ -400,6 +403,36 @@ void setItemIcon(DbusmenuMenuitem *item, const char *iconBytesPng, int iconBytes
 //    _logmsg(LOG_LEVEL_ERROR, "\tcan't set %d icon bytes for item %s", iconBytesCount, _getItemLabel(item));
 }
 
+void setItemShortcut(DbusmenuMenuitem *item, int jmodifiers, int jkeycode) {
+  GVariantBuilder builder;
+  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+  if ((jmodifiers & JMOD_SHIFT) != 0)
+    g_variant_builder_add(&builder, "s",  DBUSMENU_MENUITEM_SHORTCUT_SHIFT);
+  if ((jmodifiers & JMOD_CTRL) != 0)
+    g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_CONTROL);
+  if ((jmodifiers & JMOD_ALT) != 0)
+    g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_ALT);
+  if ((jmodifiers & JMOD_META) != 0)
+    g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_SUPER);
+
+  char* xname = XKeysymToString(jkeycode);
+  if (xname == NULL) {
+    _logmsg(LOG_LEVEL_ERROR, "XKeysymToString returns null for jkeycode=%d", jkeycode);
+    return;
+  }
+
+  // _logmsg(LOG_LEVEL_INFO, "XKeysymToString returns %s for jkeycode=%d", xname, jkeycode);
+
+  g_variant_builder_add(&builder, "s", xname);
+
+  GVariant *insideArr = g_variant_builder_end(&builder);
+  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+  g_variant_builder_add_value(&builder, insideArr);
+
+  GVariant *outsideArr = g_variant_builder_end(&builder);
+  dbusmenu_menuitem_property_set_variant(item, DBUSMENU_MENUITEM_PROP_SHORTCUT, outsideArr);
+}
+
 static gboolean _execJRunnable(gpointer user_data) {
   (*((jrunnable) user_data))();
   return FALSE;
index ef0c8ea43809b8a97b8748838547a930b57005f7..fdfa48a6b8fd26d1b2019db0f6b0a3f9eb2b401d 100644 (file)
 #define ITEM_CHECK 2
 #define ITEM_RADIO 3
 
+#define JMOD_SHIFT  1
+#define JMOD_CTRL   (1 << 1)
+#define JMOD_ALT    (1 << 2)
+#define JMOD_META   (1 << 3)
+
 typedef void (*jeventcallback)(int/*uid*/, int/*ev-type*/);
 typedef void (*jlogger)(int/*level*/, const char*);
 typedef void (*jrunnable)(void);
@@ -49,6 +54,7 @@ DbusmenuMenuitem* addSeparator(DbusmenuMenuitem * parent, int uid);
 void setItemLabel(DbusmenuMenuitem* item, const char * label);
 void setItemEnabled(DbusmenuMenuitem* item, bool isEnabled);
 void setItemIcon(DbusmenuMenuitem* item, const char * iconBytesPng, int iconBytesCount);
+void setItemShortcut(DbusmenuMenuitem *item, int jmodifiers, int jkeycode);
 
 #ifdef __cplusplus
 }
index 5be48bd32fc8f2d7ca52bcb63c22c37001f6dd52..f524a249d5725cd716b641aaaf8f2372cdde2f57 100644 (file)
@@ -19,6 +19,7 @@ import org.jetbrains.annotations.NotNull;
 import javax.imageio.ImageIO;
 import javax.swing.*;
 import java.awt.*;
+import java.awt.event.InputEvent;
 import java.awt.image.BufferedImage;
 import java.awt.peer.ComponentPeer;
 import java.io.ByteArrayOutputStream;
@@ -48,6 +49,7 @@ interface GlobalMenuLib extends Library {
   void setItemLabel(Pointer item, String label);
   void setItemEnabled(Pointer item, boolean isEnabled);
   void setItemIcon(Pointer item, byte[] iconBytesPng, int iconBytesCount);
+  void setItemShortcut(Pointer item, int jmodifiers, int jkeycode);
 
   interface EventHandler extends Callback {
     void handleEvent(int uid, int eventType);
@@ -75,6 +77,11 @@ interface GlobalMenuLib extends Library {
   int ITEM_SUBMENU = 1;
   int ITEM_CHECK = 2;
   int ITEM_RADIO = 3;
+
+  int JMOD_SHIFT = 1;
+  int JMOD_CTRL  = 1 << 1;
+  int JMOD_ALT   = 1 << 2;
+  int JMOD_META  = 1 << 3;
 }
 
 public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
@@ -136,10 +143,8 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
     final List<MenuItemInternal> newRoots = roots != null && !roots.isEmpty() ? new ArrayList<>(roots.size()) : null;
     if (newRoots != null) {
       for (ActionMenu am: roots) {
-        // final int uid = myUid2MI.size();
         final int uid = System.identityHashCode(am);
-        final MenuItemInternal mi = new MenuItemInternal(uid, GlobalMenuLib.ITEM_SUBMENU, am.getText(), null, true, am);
-        //myUid2MI.put(uid, mi);
+        final MenuItemInternal mi = new MenuItemInternal(uid, GlobalMenuLib.ITEM_SUBMENU, _buildMnemonicLabel(am), null, true, am);
         newRoots.add(mi);
       }
     }
@@ -377,6 +382,8 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
     final int type;
     final String txt;
     final byte[] iconPngBytes;
+    final int jmodifiers;
+    final int jkeycode;
     final JMenuItem jmenuitem;
     final boolean isEnabled;
     Pointer nativePeer;
@@ -389,12 +396,54 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
       this.iconPngBytes = iconPngBytes;
       this.isEnabled = isEnabled;
       this.jmenuitem = jmenuitem;
+      this.jmodifiers = _calcModifiers(jmenuitem);
+      this.jkeycode = _calcKeyCode(jmenuitem);
     }
 
     @Override
     public String toString() { return String.format("'%s' (%d)",txt, uid); }
   }
 
+  private static int _calcModifiers(JMenuItem jmenuitem) {
+    if (jmenuitem == null || jmenuitem.getAccelerator() == null)
+      return 0;
+
+    final int modifiers = jmenuitem.getAccelerator().getModifiers();
+    int result = 0;
+    if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0 ) result |= GlobalMenuLib.JMOD_SHIFT;
+    if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0 ) result |= GlobalMenuLib.JMOD_CTRL;
+    if ((modifiers & InputEvent.META_DOWN_MASK) != 0 ) result |= GlobalMenuLib.JMOD_META;
+    if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0 ) result |= GlobalMenuLib.JMOD_ALT;
+    return result;
+  }
+
+  private static int _calcKeyCode(JMenuItem jmenuitem) {
+    if (jmenuitem == null || jmenuitem.getAccelerator() == null)
+      return 0;
+    return jmenuitem.getAccelerator().getKeyCode();
+  }
+
+  private static String _buildMnemonicLabel(JMenuItem jmenuitem) {
+    String text = jmenuitem.getText();
+    final int mnemonicCode = jmenuitem.getMnemonic();
+    final int mnemonicIndex = jmenuitem.getDisplayedMnemonicIndex();
+    if (text == null)
+      text = "";
+    final int index;
+    if (mnemonicIndex >= 0 && mnemonicIndex < text.length() && Character.toUpperCase(text.charAt(mnemonicIndex)) == mnemonicCode) {
+      index = mnemonicIndex;
+    } else {
+      // Mnemonic mismatch index
+      index = -1;
+      // LOG.error("Mnemonic code " + mnemonicCode + " mismatch index " + mnemonicIndex + " with txt: " + text);
+    }
+
+    final StringBuilder res = new StringBuilder(text);
+    if(index != -1)
+      res.insert(index, '_');
+    return res.toString();
+  }
+
   private static long _getX11WindowXid(@NotNull JFrame frame) {
     final ComponentPeer wndPeer = frame.getPeer();
     if (wndPeer == null) {