diff options
| author | Mattias EngdegÄrd | 2020-12-28 15:24:08 +0100 |
|---|---|---|
| committer | Mattias EngdegÄrd | 2020-12-29 17:34:53 +0100 |
| commit | b5ada7f9afc157cce2d58ad157841b65b2450fb9 (patch) | |
| tree | 1c433453238d0dd862122439fa7baa839a363e74 /src | |
| parent | 7fbcca29b883e68b7a92d4bc706aa0a0bd19b5a4 (diff) | |
| download | emacs-b5ada7f9afc157cce2d58ad157841b65b2450fb9.tar.gz emacs-b5ada7f9afc157cce2d58ad157841b65b2450fb9.zip | |
More readable keys in NS menu entries (bug#45502)
Each menu entry now has the key binding in a right-aligned column, as
an attempt to improve readability. Previously the keys were given in
brackets immediately following the menu string.
* src/nsmenu.m ([EmacsMenu parseKeyEquiv:]): Remove.
(skipspc): New helper function.
([EmacsMenu addItemWithWidgetValue:]): Add attributes argument.
Use attributed title string. Don't special-case Super bindings.
([EmacsMenu fillWithWidgetValue:]): Compute maximum width. Prepare
attributes for title.
Diffstat (limited to 'src')
| -rw-r--r-- | src/nsmenu.m | 105 | ||||
| -rw-r--r-- | src/nsterm.h | 5 |
2 files changed, 61 insertions, 49 deletions
diff --git a/src/nsmenu.m b/src/nsmenu.m index 201c02bb35d..d5321dcdc6d 100644 --- a/src/nsmenu.m +++ b/src/nsmenu.m | |||
| @@ -457,33 +457,16 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p) | |||
| 457 | } | 457 | } |
| 458 | 458 | ||
| 459 | 459 | ||
| 460 | /* Parse a widget_value's key rep (examples: 's-p', 's-S', '(C-x C-s)', '<f13>') | 460 | static const char * |
| 461 | into an accelerator string. We are only able to display a single character | 461 | skipspc (const char *s) |
| 462 | for an accelerator, together with an optional modifier combination. (Under | ||
| 463 | Carbon more control was possible, but in Cocoa multi-char strings passed to | ||
| 464 | NSMenuItem get ignored. For now we try to display a super-single letter | ||
| 465 | combo, and return the others as strings to be appended to the item title. | ||
| 466 | (This is signaled by setting keyEquivModMask to 0 for now.) */ | ||
| 467 | -(NSString *)parseKeyEquiv: (const char *)key | ||
| 468 | { | 462 | { |
| 469 | const char *tpos = key; | 463 | while (*s == ' ') |
| 470 | keyEquivModMask = NSEventModifierFlagCommand; | 464 | s++; |
| 471 | 465 | return s; | |
| 472 | if (!key || !*key) | ||
| 473 | return @""; | ||
| 474 | |||
| 475 | while (*tpos == ' ' || *tpos == '(') | ||
| 476 | tpos++; | ||
| 477 | if ((*tpos == 's') && (*(tpos+1) == '-')) | ||
| 478 | { | ||
| 479 | return [NSString stringWithFormat: @"%c", tpos[2]]; | ||
| 480 | } | ||
| 481 | keyEquivModMask = 0; /* signal */ | ||
| 482 | return [NSString stringWithUTF8String: tpos]; | ||
| 483 | } | 466 | } |
| 484 | 467 | ||
| 485 | |||
| 486 | - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr | 468 | - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr |
| 469 | attributes: (NSDictionary *)attributes | ||
| 487 | { | 470 | { |
| 488 | NSMenuItem *item; | 471 | NSMenuItem *item; |
| 489 | widget_value *wv = (widget_value *)wvptr; | 472 | widget_value *wv = (widget_value *)wvptr; |
| @@ -491,36 +474,32 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p) | |||
| 491 | if (menu_separator_name_p (wv->name)) | 474 | if (menu_separator_name_p (wv->name)) |
| 492 | { | 475 | { |
| 493 | item = [NSMenuItem separatorItem]; | 476 | item = [NSMenuItem separatorItem]; |
| 494 | [self addItem: item]; | ||
| 495 | } | 477 | } |
| 496 | else | 478 | else |
| 497 | { | 479 | { |
| 498 | NSString *title, *keyEq; | 480 | NSString *title = [NSString stringWithUTF8String: wv->name]; |
| 499 | title = [NSString stringWithUTF8String: wv->name]; | ||
| 500 | if (title == nil) | 481 | if (title == nil) |
| 501 | title = @"< ? >"; /* (get out in the open so we know about it) */ | 482 | title = @"< ? >"; /* (get out in the open so we know about it) */ |
| 502 | 483 | ||
| 503 | keyEq = [self parseKeyEquiv: wv->key]; | 484 | item = [[NSMenuItem alloc] init]; |
| 504 | #ifdef NS_IMPL_COCOA | 485 | if (wv->key) |
| 505 | /* macOS mangles modifier strings longer than one character. */ | ||
| 506 | if (keyEquivModMask == 0) | ||
| 507 | { | 486 | { |
| 508 | title = [title stringByAppendingFormat: @" (%@)", keyEq]; | 487 | NSString *key = [NSString stringWithUTF8String: skipspc (wv->key)]; |
| 509 | item = [self addItemWithTitle: (NSString *)title | ||
| 510 | action: @selector (menuDown:) | ||
| 511 | keyEquivalent: @""]; | ||
| 512 | } | ||
| 513 | else | ||
| 514 | { | ||
| 515 | #endif | ||
| 516 | item = [self addItemWithTitle: (NSString *)title | ||
| 517 | action: @selector (menuDown:) | ||
| 518 | keyEquivalent: keyEq]; | ||
| 519 | #ifdef NS_IMPL_COCOA | 488 | #ifdef NS_IMPL_COCOA |
| 520 | } | 489 | /* Cocoa only permits a single key (with modifiers) as |
| 490 | keyEquivalent, so we put them in the title string | ||
| 491 | in a tab-separated column. */ | ||
| 492 | title = [title stringByAppendingFormat: @"\t%@", key]; | ||
| 493 | #else | ||
| 494 | [item setKeyEquivalent: key]; | ||
| 521 | #endif | 495 | #endif |
| 522 | [item setKeyEquivalentModifierMask: keyEquivModMask]; | 496 | } |
| 523 | 497 | ||
| 498 | NSAttributedString *atitle = [[NSAttributedString alloc] | ||
| 499 | initWithString: title | ||
| 500 | attributes: attributes]; | ||
| 501 | [item setAction: @selector (menuDown:)]; | ||
| 502 | [item setAttributedTitle: atitle]; | ||
| 524 | [item setEnabled: wv->enabled]; | 503 | [item setEnabled: wv->enabled]; |
| 525 | 504 | ||
| 526 | /* Draw radio buttons and tickboxes. */ | 505 | /* Draw radio buttons and tickboxes. */ |
| @@ -533,6 +512,7 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p) | |||
| 533 | [item setTag: (NSInteger)wv->call_data]; | 512 | [item setTag: (NSInteger)wv->call_data]; |
| 534 | } | 513 | } |
| 535 | 514 | ||
| 515 | [self addItem: item]; | ||
| 536 | return item; | 516 | return item; |
| 537 | } | 517 | } |
| 538 | 518 | ||
| @@ -557,15 +537,48 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p) | |||
| 557 | 537 | ||
| 558 | - (void)fillWithWidgetValue: (void *)wvptr | 538 | - (void)fillWithWidgetValue: (void *)wvptr |
| 559 | { | 539 | { |
| 560 | widget_value *wv = (widget_value *)wvptr; | 540 | widget_value *first_wv = (widget_value *)wvptr; |
| 541 | NSFont *menuFont = [NSFont menuFontOfSize:0]; | ||
| 542 | NSDictionary *font_attribs = @{NSFontAttributeName: menuFont}; | ||
| 543 | CGFloat maxNameWidth = 0; | ||
| 544 | CGFloat maxKeyWidth = 0; | ||
| 545 | |||
| 546 | /* Determine the maximum width of all menu items. */ | ||
| 547 | for (widget_value *wv = first_wv; wv != NULL; wv = wv->next) | ||
| 548 | if (!menu_separator_name_p (wv->name)) | ||
| 549 | { | ||
| 550 | NSString *name = [NSString stringWithUTF8String: wv->name]; | ||
| 551 | NSSize nameSize = [name sizeWithAttributes: font_attribs]; | ||
| 552 | maxNameWidth = MAX(maxNameWidth, nameSize.width); | ||
| 553 | if (wv->key) | ||
| 554 | { | ||
| 555 | NSString *key = [NSString stringWithUTF8String: skipspc (wv->key)]; | ||
| 556 | NSSize keySize = [key sizeWithAttributes: font_attribs]; | ||
| 557 | maxKeyWidth = MAX(maxKeyWidth, keySize.width); | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | /* Put some space between the names and keys. */ | ||
| 562 | CGFloat maxWidth = maxNameWidth + maxKeyWidth + 40; | ||
| 563 | |||
| 564 | /* Set a right-aligned tab stop at the maximum width, so that the | ||
| 565 | key will appear immediately to the left of it. */ | ||
| 566 | NSTextTab *tab = | ||
| 567 | [[NSTextTab alloc] initWithTextAlignment: NSTextAlignmentRight | ||
| 568 | location: maxWidth | ||
| 569 | options: @{}]; | ||
| 570 | NSMutableParagraphStyle *pstyle = [[NSMutableParagraphStyle alloc] init]; | ||
| 571 | [pstyle setTabStops: @[tab]]; | ||
| 572 | NSDictionary *attributes = @{NSParagraphStyleAttributeName: pstyle}; | ||
| 561 | 573 | ||
| 562 | /* clear existing contents */ | 574 | /* clear existing contents */ |
| 563 | [self removeAllItems]; | 575 | [self removeAllItems]; |
| 564 | 576 | ||
| 565 | /* add new contents */ | 577 | /* add new contents */ |
| 566 | for (; wv != NULL; wv = wv->next) | 578 | for (widget_value *wv = first_wv; wv != NULL; wv = wv->next) |
| 567 | { | 579 | { |
| 568 | NSMenuItem *item = [self addItemWithWidgetValue: wv]; | 580 | NSMenuItem *item = [self addItemWithWidgetValue: wv |
| 581 | attributes: attributes]; | ||
| 569 | 582 | ||
| 570 | if (wv->contents) | 583 | if (wv->contents) |
| 571 | { | 584 | { |
diff --git a/src/nsterm.h b/src/nsterm.h index b7b4d3b047c..f1d5acde2e6 100644 --- a/src/nsterm.h +++ b/src/nsterm.h | |||
| @@ -515,13 +515,12 @@ typedef id instancetype; | |||
| 515 | 515 | ||
| 516 | @interface EmacsMenu : NSMenu <NSMenuDelegate> | 516 | @interface EmacsMenu : NSMenu <NSMenuDelegate> |
| 517 | { | 517 | { |
| 518 | unsigned long keyEquivModMask; | ||
| 519 | BOOL needsUpdate; | 518 | BOOL needsUpdate; |
| 520 | } | 519 | } |
| 521 | 520 | ||
| 522 | - (void)menuNeedsUpdate: (NSMenu *)menu; /* (delegate method) */ | 521 | - (void)menuNeedsUpdate: (NSMenu *)menu; /* (delegate method) */ |
| 523 | - (NSString *)parseKeyEquiv: (const char *)key; | 522 | - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr |
| 524 | - (NSMenuItem *)addItemWithWidgetValue: (void *)wvptr; | 523 | attributes: (NSDictionary *)attributes; |
| 525 | - (void)fillWithWidgetValue: (void *)wvptr; | 524 | - (void)fillWithWidgetValue: (void *)wvptr; |
| 526 | - (EmacsMenu *)addSubmenuWithTitle: (const char *)title; | 525 | - (EmacsMenu *)addSubmenuWithTitle: (const char *)title; |
| 527 | - (void) removeAllItems; | 526 | - (void) removeAllItems; |