Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ profile
DerivedData
.idea/
*.hmap

Demo.xcodeproj/project.xcworkspace/xcshareddata/Demo.xccheckout
32 changes: 28 additions & 4 deletions AXStatusItemPopup/AXStatusItemPopup.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,37 @@

#import <Cocoa/Cocoa.h>

@interface AXStatusItemPopup : NSView
@interface NSWindow (canBecomeKeyWindow)

@end

@protocol AXStatusItemPopupDelegate <NSObject>

@optional

- (BOOL) shouldPopupOpen;
- (void) popupWillOpen;
- (void) popupDidOpen;

- (BOOL) shouldPopupClose;
- (void) popupWillClose;
- (void) popupDidClose;

@end

@interface AXStatusItemPopup : NSView <NSPopoverDelegate>

// properties
@property(assign, nonatomic, getter=isActive) BOOL active;
@property(assign, nonatomic) BOOL animated;
@property(assign, nonatomic, getter=isAnimated) BOOL animated;
@property(strong, nonatomic) NSImage *image;
@property(strong, nonatomic) NSImage *alternateImage;
@property(strong, nonatomic) NSStatusItem *statusItem;
@property(weak) id<AXStatusItemPopupDelegate> delegate;


// alloc
+ (id)statusItemPopupWithViewController:(NSViewController *)controller;
+ (id)statusItemPopupWithViewController:(NSViewController *)controller image:(NSImage *)image;
+ (id)statusItemPopupWithViewController:(NSViewController *)controller image:(NSImage *)image alternateImage:(NSImage *)alternateImage;

// init
- (id)initWithViewController:(NSViewController *)controller;
Expand All @@ -25,6 +47,8 @@


// show / hide popover
- (void)togglePopover;
- (void)togglePopoverAnimated: (BOOL)animated;
- (void)showPopover;
- (void)showPopoverAnimated:(BOOL)animated;
- (void)hidePopover;
Expand Down
238 changes: 177 additions & 61 deletions AXStatusItemPopup/AXStatusItemPopup.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,50 @@

#define kMinViewWidth 22

BOOL shouldBecomeKeyWindow;
NSWindow* windowToOverride;

//
// Private variables
// Private properties
//
@interface AXStatusItemPopup () {
NSViewController *_viewController;
BOOL _active;
NSImageView *_imageView;
NSStatusItem *_statusItem;
NSPopover *_popover;
id _popoverTransiencyMonitor;
}
@interface AXStatusItemPopup ()
@property NSViewController *viewController;
@property NSImageView *imageView;
@property NSStatusItem *statusItem;
@property NSPopover *popover;
@property(assign, nonatomic, getter=isActive) BOOL active;

@end

///////////////////////////////////
//#####################################################################################
#pragma mark - Implementation AXStatusItemPopup
//#####################################################################################

//
// Implementation
//
@implementation AXStatusItemPopup

//*******************************************************************************
#pragma mark - Allocators
//*******************************************************************************

+ (id) statusItemPopupWithViewController:(NSViewController *)controller
{
return [[self alloc] initWithViewController:controller];
}

+ (id) statusItemPopupWithViewController:(NSViewController *)controller image:(NSImage *)image
{
return [[self alloc] initWithViewController:controller image:image];
}

+ (id) statusItemPopupWithViewController:(NSViewController *)controller image:(NSImage *)image alternateImage:(NSImage *)alternateImage
{
return [[self alloc] initWithViewController:controller image:image alternateImage:alternateImage];
}

//*******************************************************************************
#pragma mark - Initiators
//*******************************************************************************

- (id)initWithViewController:(NSViewController *)controller
{
return [self initWithViewController:controller image:nil];
Expand All @@ -45,7 +69,10 @@ - (id)initWithViewController:(NSViewController *)controller image:(NSImage *)ima
CGFloat height = [NSStatusBar systemStatusBar].thickness;

self = [super initWithFrame:NSMakeRect(0, 0, kMinViewWidth, height)];
if (self) {
if (self)
{
_active = NO;
_animated = YES;
_viewController = controller;

self.image = image;
Expand All @@ -56,54 +83,61 @@ - (id)initWithViewController:(NSViewController *)controller image:(NSImage *)ima

self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
self.statusItem.view = self;
self.statusItem.target = self;
self.statusItem.action = @selector(togglePopover:);

_active = NO;
_animated = YES;
self.popover = [[NSPopover alloc] init];
self.popover.contentViewController = self.viewController;
self.popover.animates = self.animated;
self.popover.delegate = self;

windowToOverride = self.window;

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:nil];
}
return self;
}


////////////////////////////////////
//*******************************************************************************
#pragma mark - Drawing
////////////////////////////////////
//*******************************************************************************

- (void)drawRect:(NSRect)dirtyRect
{
// set view background color
if (_active) {
if (self.isActive)
{
[[NSColor selectedMenuItemColor] setFill];
} else {
[[NSColor clearColor] setFill];
}
NSRectFill(dirtyRect);

// set image
NSImage *image = (_active ? _alternateImage : _image);
NSImage *image = (self.isActive ? self.alternateImage : self.image);
_imageView.image = image;
}

////////////////////////////////////
#pragma mark - Mouse Actions
////////////////////////////////////
//*******************************************************************************
#pragma mark - Mouse Events
//*******************************************************************************

- (void)mouseDown:(NSEvent *)theEvent
{
if (_popover.isShown) {
[self hidePopover];
} else {
[self showPopover];
}
[self togglePopover];
}

////////////////////////////////////
//*******************************************************************************
#pragma mark - Setter
////////////////////////////////////
//*******************************************************************************

- (void)setActive:(BOOL)active
{
_active = active;
shouldBecomeKeyWindow = active;
[self setNeedsDisplay:YES];
[NSApp activateIgnoringOtherApps:active];
}

- (void)setImage:(NSImage *)image
Expand All @@ -121,9 +155,100 @@ - (void)setAlternateImage:(NSImage *)image
[self updateViewFrame];
}

////////////////////////////////////
//*******************************************************************************
#pragma mark - Notification Handler
//*******************************************************************************

- (void)applicationDidResignActive:(NSNotification*)note
{
[self hidePopover];
}

//*******************************************************************************
#pragma mark - Popover Delegate
//*******************************************************************************

//This is safer then caring for the sended events. Sometimes to popup doesn't close, in these
//cases popover and status item became out of sync
- (void) popoverWillShow: (NSNotification*) note
{
self.active = YES;
}

- (void) popoverWillClose: (NSNotification*) note
{
self.active = NO;
}

//*******************************************************************************
#pragma mark - Show / Hide Popover
//*******************************************************************************

- (void) togglePopover
{
[self togglePopoverAnimated:self.isAnimated];
}

- (void) togglePopoverAnimated:(BOOL)animated
{
if (self.isActive)
{
[self hidePopover];
} else {
[self showPopoverAnimated:animated];
}
}

- (void)showPopover
{
[self showPopoverAnimated:self.isAnimated];
}

- (void)showPopoverAnimated:(BOOL)animated
{
BOOL willAnswer = [self.delegate respondsToSelector:@selector(shouldPopupOpen)];
if (!willAnswer || (willAnswer && [self.delegate shouldPopupOpen]))
{
if (!self.popover.isShown)
{
_popover.animates = animated;
if ([self.delegate respondsToSelector:@selector(popupWillOpen)])
{
[self.delegate popupWillOpen];
}
[_popover showRelativeToRect:self.frame ofView:self preferredEdge:NSMinYEdge];
}
[self.window makeKeyWindow];
if ([self.delegate respondsToSelector:@selector(popupDidOpen)])
{
[self.delegate popupDidOpen];
}
}
}

- (void)hidePopover
{
BOOL willAnswer = [self.delegate respondsToSelector:@selector(shouldPopupClose)];
if (!willAnswer || (willAnswer && [self.delegate shouldPopupClose]))
{
if (_popover && _popover.isShown)
{
if ([self.delegate respondsToSelector:@selector(popupWillClose)])
{
[self.delegate popupWillClose];
}
[_popover close];
}
if ([self.delegate respondsToSelector:@selector(popupDidClose)])
{
[self.delegate popupDidClose];
}
}
}

//*******************************************************************************
#pragma mark - Helper
////////////////////////////////////
//*******************************************************************************

- (void)updateViewFrame
{
Expand All @@ -132,47 +257,38 @@ - (void)updateViewFrame

NSRect frame = NSMakeRect(0, 0, width, height);
self.frame = frame;
_imageView.frame = frame;
self.imageView.frame = frame;

[self setNeedsDisplay:YES];
}

@end

////////////////////////////////////
#pragma mark - Show / Hide Popover
////////////////////////////////////
//#####################################################################################
#pragma mark - Implementation NSWindow+canBecomeKeyWindow
//#####################################################################################

- (void)showPopover
{
[self showPopoverAnimated:_animated];
}
#import <objc/objc-class.h>

- (void)showPopoverAnimated:(BOOL)animated
@implementation NSWindow (canBecomeKeyWindow)

//This is to fix a bug with 10.7 where an NSPopover with a text field
//cannot be edited if its parent window won't become key
//This technique is called method swizzling.
- (BOOL)swizzledPopoverCanBecomeKeyWindow
{
self.active = YES;

if (!_popover) {
_popover = [[NSPopover alloc] init];
_popover.contentViewController = _viewController;
}

if (!_popover.isShown) {
_popover.animates = animated;
[_popover showRelativeToRect:self.frame ofView:self preferredEdge:NSMinYEdge];
_popoverTransiencyMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask handler:^(NSEvent* event) {
[self hidePopover];
}];
if (self == windowToOverride) {
return shouldBecomeKeyWindow;
} else {
return [self swizzledPopoverCanBecomeKeyWindow];
}
}

- (void)hidePopover
+ (void)load
{
self.active = NO;

if (_popover && _popover.isShown) {
[_popover close];
[NSEvent removeMonitor:_popoverTransiencyMonitor];
}
method_exchangeImplementations(
class_getInstanceMethod(self, @selector(canBecomeKeyWindow)),
class_getInstanceMethod(self, @selector(swizzledPopoverCanBecomeKeyWindow)));
}

@end
Expand Down
Loading