unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#45502: [PATCH] Prettier key bindings in NS menu entries
@ 2020-12-28 14:23 Mattias Engdegård
  2020-12-28 18:36 ` Alan Third
  2020-12-28 22:46 ` Unknown
  0 siblings, 2 replies; 16+ messages in thread
From: Mattias Engdegård @ 2020-12-28 14:23 UTC (permalink / raw)
  To: 45502

[-- Attachment #1: Type: text/plain, Size: 587 bytes --]

The NS port shows key bindings in a rather cluttered way, with the key in brackets directly after the menu entry. The Mac port of Emacs is much neater with the bindings all aligned at a common tab position. We could do the same, but having done some experiments I actually prefer a right-alignment of the keys. Proof-of-concept patch attached.

The alignment is made by padding with spaces, and then with hair spaces for extra precision; the result is not perfect but probably better than what we have now. If I get some time, I might do an experiment with more precise formatting.


[-- Attachment #2: right-justify-keys.diff --]
[-- Type: application/octet-stream, Size: 6382 bytes --]

diff --git a/src/nsmenu.m b/src/nsmenu.m
index 3f0cd0c6ed..6c96f5f123 100644
--- a/src/nsmenu.m
+++ b/src/nsmenu.m
@@ -450,32 +450,14 @@ - (BOOL)performKeyEquivalent: (NSEvent *)theEvent
 }
 
 
-/* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>')
-   into an accelerator string.  We are only able to display a single character
-   for an accelerator, together with an optional modifier combination.  (Under
-   Carbon more control was possible, but in Cocoa multi-char strings passed to
-   NSMenuItem get ignored.  For now we try to display a super-single letter
-   combo, and return the others as strings to be appended to the item title.
-   (This is signaled by setting keyEquivModMask to 0 for now.) */
--(NSString *)parseKeyEquiv: (const char *)key
+static const char *
+skipspc (const char *s)
 {
-  const char *tpos = key;
-  keyEquivModMask = NSEventModifierFlagCommand;
-
-  if (!key || !*key)
-    return @"";
-
-  while (*tpos == ' ' || *tpos == '(')
-    tpos++;
-  if ((*tpos == 's') && (*(tpos+1) == '-'))
-    {
-      return [NSString stringWithFormat: @"%c", tpos[2]];
-    }
-  keyEquivModMask = 0; /* signal */
-  return [NSString stringWithUTF8String: tpos];
+  while (*s == ' ')
+    s++;
+  return s;
 }
 
-
 - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
 {
   NSMenuItem *item;
@@ -488,31 +470,20 @@ - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr
     }
   else
     {
-      NSString *title, *keyEq;
+      NSString *title;
       title = [NSString stringWithUTF8String: wv->name];
       if (title == nil)
         title = @"< ? >";  /* (get out in the open so we know about it) */
 
-      keyEq = [self parseKeyEquiv: wv->key];
-#ifdef NS_IMPL_COCOA
-      /* macOS mangles modifier strings longer than one character.  */
-      if (keyEquivModMask == 0)
-        {
-          title = [title stringByAppendingFormat: @" (%@)", keyEq];
-          item = [self addItemWithTitle: (NSString *)title
-                                 action: @selector (menuDown:)
-                          keyEquivalent: @""];
-        }
-      else
-        {
-#endif
-          item = [self addItemWithTitle: (NSString *)title
-                                 action: @selector (menuDown:)
-                          keyEquivalent: keyEq];
-#ifdef NS_IMPL_COCOA
-        }
-#endif
-      [item setKeyEquivalentModifierMask: keyEquivModMask];
+      /* Cocoa only permits a single key (with modifiers) as
+         keyEquivalent, so we stick with the standard Emacs notation
+         for all key bindings for the sake of uniformity. */
+      if (wv->key)
+        title = [title stringByAppendingString:
+                         [NSString stringWithUTF8String: skipspc (wv->key)]];
+      item = [self addItemWithTitle: (NSString *)title
+                             action: @selector (menuDown:)
+                      keyEquivalent: @""];
 
       [item setEnabled: wv->enabled];
 
@@ -557,14 +528,69 @@ -(void)removeAllItems
 
 - (void)fillWithWidgetValue: (void *)wvptr
 {
-  widget_value *wv = (widget_value *)wvptr;
+  widget_value *first_wv = (widget_value *)wvptr;
+  NSFont *menuFont = [NSFont menuFontOfSize:0];
+  NSDictionary <NSString *, id> *attributes =
+    [NSDictionary dictionaryWithObject:menuFont forKey:NSFontAttributeName];
+  NSSize spaceSize = [@" " sizeWithAttributes:attributes];
+  CGFloat spaceWidth = spaceSize.width;
+  const char hair_str[] = "\u200a";  // HAIR SPACE
+  int hair_bytes = sizeof hair_str - 1;
+  NSSize hairSize = [[NSString stringWithUTF8String: hair_str]
+                       sizeWithAttributes:attributes];
+  CGFloat hairWidth = spaceSize.width;
+  CGFloat maxNameWidth = 0;
+  CGFloat maxKeyWidth = 0;
+
+  /* Determine maximum width of menu items. */
+  for (widget_value *wv = first_wv; wv != NULL; wv = wv->next)
+    if (!menu_separator_name_p (wv->name))
+      {
+        NSString *name = [NSString stringWithUTF8String: wv->name];
+        NSSize nameSize = [name sizeWithAttributes: attributes];
+        CGFloat nameWidth = nameSize.width;
+        maxNameWidth = MAX(maxNameWidth, nameWidth);
+        if (wv->key)
+          {
+            NSString *key = [NSString stringWithUTF8String: skipspc (wv->key)];
+            NSSize keySize = [key sizeWithAttributes: attributes];
+            CGFloat keyWidth = keySize.width;
+            maxKeyWidth = MAX(maxKeyWidth, keyWidth);
+          }
+      }
+  CGFloat maxWidth = maxNameWidth + maxKeyWidth;
 
   /* clear existing contents */
   [self removeAllItems];
 
   /* add new contents */
-  for (; wv != NULL; wv = wv->next)
+  for (widget_value *wv = first_wv; wv != NULL; wv = wv->next)
     {
+      if (wv->key && !menu_separator_name_p (wv->name))
+        {
+          /* Modify name to include padding to right-justify key binding. */
+          NSString *name = [NSString stringWithUTF8String: wv->name];
+          NSSize nameSize = [name sizeWithAttributes: attributes];
+          CGFloat nameWidth = nameSize.width;
+          NSString *key = [NSString stringWithUTF8String: skipspc (wv->key)];
+          NSSize keySize = [key sizeWithAttributes: attributes];
+          CGFloat keyWidth = keySize.width;
+          CGFloat padWidth = maxWidth - (nameWidth + keyWidth);
+          /* Use plain spaces as far as possible, and hair spaces for
+             the rest. */
+          int extra_spaces = 4;
+          int nspaces = padWidth / spaceWidth + extra_spaces;
+          int nhairs = fmod(padWidth, spaceWidth) / hairWidth + 0.5;
+          int name_len = strlen (wv->name);
+          Lisp_Object s = make_uninit_string (name_len + nspaces
+                                              + nhairs * hair_bytes);
+          memcpy (SSDATA (s), wv->name, name_len);
+          memset (SDATA (s) + name_len, ' ', nspaces);
+          for (int i = 0; i < nhairs; i++)
+            memcpy (SDATA (s) + name_len + nspaces + i * hair_bytes, hair_str,
+                    hair_bytes);
+          wv->name = SSDATA (s);
+        }
       NSMenuItem *item = [self addItemWithWidgetValue: wv];
 
       if (wv->contents)
diff --git a/src/nsterm.h b/src/nsterm.h
index b7b4d3b047..c4873b6082 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -515,7 +515,6 @@ #define NS_DRAW_TO_BUFFER 1
 
 @interface EmacsMenu : NSMenu  <NSMenuDelegate>
 {
-  unsigned long keyEquivModMask;
   BOOL needsUpdate;
 }
 

^ permalink raw reply related	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2020-12-30 15:53 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2020-12-28 14:23 bug#45502: [PATCH] Prettier key bindings in NS menu entries Mattias Engdegård
2020-12-28 18:36 ` Alan Third
2020-12-29 12:02   ` Mattias Engdegård
2020-12-29 13:53     ` Alan Third
2020-12-29 14:41       ` Mattias Engdegård
2020-12-29 15:50         ` Alan Third
2020-12-29 17:34           ` Mattias Engdegård
2020-12-29 21:24             ` Alan Third
2020-12-29 22:53               ` Mattias Engdegård
2020-12-29 23:49                 ` Alan Third
2020-12-30 12:19                   ` Mattias Engdegård
2020-12-30 12:46                     ` Alan Third
2020-12-30 13:09                       ` Alan Third
2020-12-30 15:53                         ` Mattias Engdegård
2020-12-30 13:12                       ` Mattias Engdegård
2020-12-28 22:46 ` Unknown

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).