Skip to content

Commit

Permalink
Draw tab triggers and key equivalents in menu
Browse files Browse the repository at this point in the history
The old techniques used to set key equivalents no longer work. Unfortunately, this technique is only a workaround and is not pixel-for-pixel identical due to the way NSMenuItem lays itself out when rendering a key equivalent; but I think it's the best that can be done for now.
  • Loading branch information
jtbandes authored and sorbits committed Aug 13, 2012
1 parent df84fa9 commit 25071ce
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 3 deletions.
16 changes: 16 additions & 0 deletions Frameworks/BundleMenu/src/BundleItemMenuItem.h
@@ -0,0 +1,16 @@
#import <bundles/bundles.h>

struct BundleItemMenuItemAlignment
{
BundleItemMenuItemAlignment () : maxAlignmentWidth(0), maxRightWidth(0) {}
CGFloat maxAlignmentWidth;
CGFloat maxRightWidth;
};

@interface BundleItemMenuItem : NSMenuItem
{
BOOL hasRightPart;
}
+ (BundleItemMenuItem*)menuItemWithBundleItem:(bundles::item_ptr const&)bundleItem alignmentData:(BundleItemMenuItemAlignment&)alignment;
- (void)updateAlignment:(BundleItemMenuItemAlignment&)alignment;
@end
104 changes: 104 additions & 0 deletions Frameworks/BundleMenu/src/BundleItemMenuItem.mm
@@ -0,0 +1,104 @@
#import "BundleItemMenuItem.h"
#import <OakFoundation/NSString Additions.h>
#import <ns/ns.h>

@interface BundleItemMenuItem ()
- (BundleItemMenuItem*)initWithBundleItem:(bundles::item_ptr const&)bundleItem alignmentData:(BundleItemMenuItemAlignment&)alignment;
- (void)setAttributedTitleWithTitle:(NSAttributedString*)itemTitle equivLeft:(NSAttributedString*)equivLeft equivRight:(NSAttributedString*)equivRight alignmentData:(BundleItemMenuItemAlignment&)alignment;
@end

@implementation BundleItemMenuItem
+ (BundleItemMenuItem*)menuItemWithBundleItem:(bundles::item_ptr const&)bundleItem alignmentData:(BundleItemMenuItemAlignment&)alignment
{
return [[[self alloc] initWithBundleItem:bundleItem alignmentData:alignment] autorelease];
}

- (BundleItemMenuItem*)initWithBundleItem:(bundles::item_ptr const&)bundleItem alignmentData:(BundleItemMenuItemAlignment&)alignment
{
if ((self = [super init]))
{
NSDictionary* fontAttrs = @{ NSFontAttributeName : [NSFont menuFontOfSize:14] /* passing 0 should return the default size, but it doesn’t */ };
NSDictionary* smallFontAttrs = @{ NSFontAttributeName : [NSFont menuFontOfSize:11] };
NSAttributedString* title = [[NSAttributedString alloc] initWithString:[NSString stringWithCxxString:bundleItem->name()] attributes:fontAttrs];
NSAttributedString* equivLeft = nil;
NSAttributedString* equivRight = nil;

std::string const tabTrigger(bundleItem->value_for_field(bundles::kFieldTabTrigger));
std::string const keyEquiv(bundleItem->value_for_field(bundles::kFieldKeyEquivalent));

if(tabTrigger != NULL_STR)
{
equivLeft = [[[NSAttributedString alloc] initWithString:[NSString stringWithCxxString:(" "+tabTrigger+"\u21E5 ")] attributes:smallFontAttrs] autorelease];
}
else if(keyEquiv != NULL_STR)
{
size_t keyStart = 0;
std::string const glyphStr(ns::glyphs_for_event_string(keyEquiv, &keyStart));

equivLeft = [[[NSAttributedString alloc] initWithString:[NSString stringWithCxxString:glyphStr.substr(0, keyStart)] attributes:fontAttrs] autorelease];
equivRight = [[[NSAttributedString alloc] initWithString:[NSString stringWithCxxString:glyphStr.substr(keyStart)] attributes:fontAttrs] autorelease];
}

[self setAttributedTitleWithTitle:title equivLeft:equivLeft equivRight:equivRight alignmentData:alignment];
}

return self;
}

- (void)setAttributedTitleWithTitle:(NSAttributedString*)itemTitle equivLeft:(NSAttributedString*)equivLeft equivRight:(NSAttributedString*)equivRight alignmentData:(BundleItemMenuItemAlignment&)alignment
{
NSMutableAttributedString* title = [[[NSMutableAttributedString alloc] initWithAttributedString:itemTitle] autorelease];
[title beginEditing];

CGFloat alignmentWidth = [itemTitle size].width;
CGFloat rightWidth = [equivRight size].width;
CGFloat leftWidth = [equivLeft size].width;

if(equivLeft)
{
[title appendAttributedString:[[[NSAttributedString alloc] initWithString:@"\t"] autorelease]];
[title appendAttributedString:equivLeft];

alignmentWidth += leftWidth + 20;

if(equivRight)
{
[title appendAttributedString:[[[NSAttributedString alloc] initWithString:@"\t"] autorelease]];
[title appendAttributedString:equivRight];
hasRightPart = YES;
}
}

if(alignmentWidth > alignment.maxAlignmentWidth)
alignment.maxAlignmentWidth = alignmentWidth;

if(rightWidth > alignment.maxRightWidth)
alignment.maxRightWidth = rightWidth;

[title endEditing];
[self setAttributedTitle:title];
}

- (void)updateAlignment:(BundleItemMenuItemAlignment&)alignment
{
NSMutableParagraphStyle* pStyle = [[NSMutableParagraphStyle new] autorelease];
if(hasRightPart)
{
[pStyle setTabStops:@[
[[[NSTextTab alloc] initWithType:NSRightTabStopType location:alignment.maxAlignmentWidth] autorelease],
[[[NSTextTab alloc] initWithType:NSLeftTabStopType location:alignment.maxAlignmentWidth + 0.01] autorelease]
]];
}
else
{
[pStyle setTabStops:@[
[[[NSTextTab alloc] initWithType:NSRightTabStopType location:alignment.maxAlignmentWidth + alignment.maxRightWidth] autorelease]
]];
}

NSMutableAttributedString* title = [[self attributedTitle] mutableCopy];

This comment has been minimized.

Copy link
@jtbandes

jtbandes Aug 13, 2012

Author Contributor

Whoops, leaked this. Will fix in next commit.

[title addAttribute:NSParagraphStyleAttributeName value:pStyle range:NSMakeRange(0, [title length])];

[self setAttributedTitle:title];
}
@end
14 changes: 11 additions & 3 deletions Frameworks/BundleMenu/src/BundleMenuDelegate.mm
@@ -1,4 +1,5 @@
#import "BundleMenuDelegate.h"
#import "BundleItemMenuItem.h"
#import <OakAppKit/NSMenu Additions.h>
#import <OakAppKit/NSMenuItem Additions.h>
#import <OakFoundation/NSString Additions.h>
Expand Down Expand Up @@ -33,6 +34,9 @@ - (void)menuNeedsUpdate:(NSMenu*)aMenu
D(DBF_BundleMenu, bug("\n"););
[aMenu removeAllItems];
[subdelegates removeAllObjects];

BundleItemMenuItemAlignment alignmentData;
NSMutableArray* menuItems = [NSMutableArray array];

citerate(item, umbrellaItem->menu())
{
Expand All @@ -56,14 +60,18 @@ - (void)menuNeedsUpdate:(NSMenu*)aMenu

default:
{
NSMenuItem* menuItem = [aMenu addItemWithTitle:[NSString stringWithCxxString:(*item)->name()] action:@selector(doBundleItem:) keyEquivalent:@""];
[menuItem setKeyEquivalentCxxString:(*item)->value_for_field(bundles::kFieldKeyEquivalent)];
[menuItem setTabTriggerCxxString:(*item)->value_for_field(bundles::kFieldTabTrigger)];
BundleItemMenuItem* menuItem = [BundleItemMenuItem menuItemWithBundleItem:*item alignmentData:alignmentData];
[menuItem setRepresentedObject:[NSString stringWithCxxString:(*item)->uuid()]];
[aMenu addItem:menuItem];

This comment has been minimized.

Copy link
@sorbits

sorbits Aug 14, 2012

Member

The new code lacks an action argument in the API so the items are all no-ops if used with mouse.

This comment has been minimized.

Copy link
@jtbandes

jtbandes Aug 14, 2012

Author Contributor

Fixed in jtbandes@5aaebb9


[menuItems addObject:menuItem];
}
break;
}
}

for(BundleItemMenuItem* menuItem in menuItems)
[menuItem updateAlignment:alignmentData];
}

- (void)menuWillOpen:(NSMenu*)aMenu
Expand Down

0 comments on commit 25071ce

Please sign in to comment.